diff --git a/.dockerignore b/.dockerignore index f5faf1f0e3da..46c83b0467bd 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1 @@ -.git awx/ui/node_modules diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index caceb2de456e..2a7fb229d879 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -30,8 +30,9 @@ https://www.ansible.com/security ##### STEPS TO REPRODUCE - + ##### EXPECTED RESULTS diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 7a7c6954c843..384b1dc78b84 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -3,6 +3,12 @@ name: "\U0001F41B Bug report" about: Create a report to help us improve --- + ##### ISSUE TYPE - Bug Report diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 097706e6d8ce..98fe2f5869da 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -3,6 +3,12 @@ name: "✨ Feature request" about: Suggest an idea for this project --- + ##### ISSUE TYPE - Feature Idea diff --git a/.gitignore b/.gitignore index d8f97b52bd14..71d2015cf257 100644 --- a/.gitignore +++ b/.gitignore @@ -31,11 +31,14 @@ awx/ui/client/languages awx/ui/templates/ui/index.html awx/ui/templates/ui/installing.html awx/ui_next/node_modules/ +awx/ui_next/src/locales/ awx/ui_next/coverage/ -awx/ui_next/build/locales/_build -/tower-license -/tower-license/** +awx/ui_next/build +awx/ui_next/.env.local +awx/ui_next/instrumented +rsyslog.pid tools/prometheus/data +tools/docker-compose/Dockerfile # Tower setup playbook testing setup/test/roles/postgresql @@ -139,9 +142,12 @@ use_dev_supervisor.txt # Ansible module tests /awx_collection_test_venv/ /awx_collection/*.tar.gz -/awx_collection/galaxy.yml /sanity/ +/awx_collection_build/ .idea/* *.unison.tmp *.# +/tools/docker-compose/overrides/ +/awx/ui_next/.ui-built +/Dockerfile diff --git a/CHANGELOG.md b/CHANGELOG.md index 9dfe6a912a60..6237a9f54aca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,259 @@ This is a list of high-level changes for each release of AWX. A full list of commits can be found at `https://github.com/ansible/awx/releases/tag/`. +# 17.0.1 (January 26, 2021) +- Fixed pgdocker directory permissions issue with Local Docker installer: https://github.com/ansible/awx/pull/9152 +- Fixed a bug in the UI which caused toggle settings to not be changed when clicked: https://github.com/ansible/awx/pull/9093 + +# 17.0.0 (January 22, 2021) +- AWX now requires PostgreSQL 12 by default: https://github.com/ansible/awx/pull/8943 + **Note:** users who encounter permissions errors at upgrade time should `chown -R ~/.awx/pgdocker` to ensure it's owned by the user running the install playbook +- Added support for region name for OpenStack inventory: https://github.com/ansible/awx/issues/5080 +- Added the ability to chain undefined attributes in custom notification templates: https://github.com/ansible/awx/issues/8677 +- Dramatically simplified the `image_build` role: https://github.com/ansible/awx/pull/8980 +- Fixed a bug which can cause schema migrations to fail at install time: https://github.com/ansible/awx/issues/9077 +- Fixed a bug which caused the `is_superuser` user property to be out of date in certain circumstances: https://github.com/ansible/awx/pull/8833 +- Fixed a bug which sometimes results in race conditions on setting access: https://github.com/ansible/awx/pull/8580 +- Fixed a bug which sometimes causes an unexpected delay in stdout for some playbooks: https://github.com/ansible/awx/issues/9085 +- (UI) Added support for credential password prompting on job launch: https://github.com/ansible/awx/pull/9028 +- (UI) Added the ability to configure LDAP settings in the UI: https://github.com/ansible/awx/issues/8291 +- (UI) Added a sync button to the Project detail view: https://github.com/ansible/awx/issues/8847 +- (UI) Added a form for configuring Google Outh 2.0 settings: https://github.com/ansible/awx/pull/8762 +- (UI) Added searchable keys and related keys to the Credentials list: https://github.com/ansible/awx/issues/8603 +- (UI) Added support for advanced search and copying to Notification Templates: https://github.com/ansible/awx/issues/7879 +- (UI) Added support for prompting on workflow nodes: https://github.com/ansible/awx/issues/5913 +- (UI) Added support for session timeouts: https://github.com/ansible/awx/pull/8250 +- (UI) Fixed a bug that broke websocket streaming for the insecure ws:// protocol: https://github.com/ansible/awx/pull/8877 +- (UI) Fixed a bug in the user interface when a translation for the browser's preferred locale isn't available: https://github.com/ansible/awx/issues/8884 +- (UI) Fixed bug where navigating from one survey question form directly to another wasn't reloading the form: https://github.com/ansible/awx/issues/7522 +- (UI) Fixed a bug which can cause an uncaught error while launching a Job Template: https://github.com/ansible/awx/issues/8936 +- Updated autobahn to address CVE-2020-35678 + +## 16.0.0 (December 10, 2020) +- AWX now ships with a reimagined user interface. **Please read this before upgrading:** https://groups.google.com/g/awx-project/c/KuT5Ao92HWo +- Removed support for syncing inventory from Red Hat CloudForms - https://github.com/ansible/awx/commit/0b701b3b2 +- Removed support for Mercurial-based project updates - https://github.com/ansible/awx/issues/7932 +- Upgraded NodeJS to actively maintained LTS 14.15.1 - https://github.com/ansible/awx/pull/8766 +- Added Git-LFS to the default image build - https://github.com/ansible/awx/pull/8700 +- Added the ability to specify `metadata.labels` in the podspec for container groups - https://github.com/ansible/awx/issues/8486 +- Added support for Kubernetes pod annotations - https://github.com/ansible/awx/pull/8434 +- Added the ability to label the web container in local Docker installs - https://github.com/ansible/awx/pull/8449 +- Added additional metadata (as an extra var) to playbook runs to report the SCM branch name - https://github.com/ansible/awx/pull/8433 +- Fixed a bug that caused k8s installations to fail due to an incorrect Helm repo - https://github.com/ansible/awx/issues/8715 +- Fixed a bug that prevented certain Workflow Approval resources from being deleted - https://github.com/ansible/awx/pull/8612 +- Fixed a bug that prevented the deletion of inventories stuck in "pending deletion" state - https://github.com/ansible/awx/issues/8525 +- Fixed a display bug in webhook notifications with certain unicode characters - https://github.com/ansible/awx/issues/7400 +- Improved support for exporting dependent objects (Inventory Hosts and Groups) in the `awx export` CLI tool - https://github.com/ansible/awx/commit/607bc0788 + +## 15.0.1 (October 20, 2020) +- Added several optimizations to improve performance for a variety of high-load simultaneous job launch use cases https://github.com/ansible/awx/pull/8403 +- Added the ability to source roles and collections from requirements.yaml files (not just requirements.yml) - https://github.com/ansible/awx/issues/4540 +- awx.awx collection modules now provide a clearer error message for incompatible versions of awxkit - https://github.com/ansible/awx/issues/8127 +- Fixed a bug in notification messages that contain certain unicode characters - https://github.com/ansible/awx/issues/7400 +- Fixed a bug that prevents the deletion of Workflow Approval records - https://github.com/ansible/awx/issues/8305 +- Fixed a bug that broke the selection of webhook credentials - https://github.com/ansible/awx/issues/7892 +- Fixed a bug which can cause confusing behavior for social auth logins across distinct browser tabs - https://github.com/ansible/awx/issues/8154 +- Fixed several bugs in the output of Workflow Job Templates using the `awx export` tool - https://github.com/ansible/awx/issues/7798 https://github.com/ansible/awx/pull/7847 +- Fixed a race condition that can lead to missing hosts when running parallel inventory syncs - https://github.com/ansible/awx/issues/5571 +- Fixed an HTTP 500 error when certain LDAP group parameters aren't properly set - https://github.com/ansible/awx/issues/7622 +- Updated a few dependencies in response to several CVEs: + * CVE-2020-7720 + * CVE-2020-7743 + * CVE-2020-7676 + +## 15.0.0 (September 30, 2020) +- Added improved support for fetching Ansible collections from private Galaxy content sources (such as https://github.com/ansible/galaxy_ng) - https://github.com/ansible/awx/issues/7813 + **Note:** as part of this change, new Organizations created in the AWX API will _no longer_ automatically synchronize roles and collections from galaxy.ansible.com by default. More details on this change can be found at: https://github.com/ansible/awx/issues/8341#issuecomment-707310633 +- AWX now utilizes a version of certifi that auto-discovers certificates in the system certificate store - https://github.com/ansible/awx/pull/8242 +- Added support for arbitrary custom inventory plugin configuration: https://github.com/ansible/awx/issues/5150 +- Added an optional setting to disable the auto-creation of organizations and teams on successful SAML login. - https://github.com/ansible/awx/pull/8069 +- Added a number of optimizations to AWX's callback receiver to improve the speed of stdout processing for simultaneous playbooks runs - https://github.com/ansible/awx/pull/8193 https://github.com/ansible/awx/pull/8191 +- Added the ability to use `!include` and `!import` constructors when constructing YAML for use with the AWX CLI - https://github.com/ansible/awx/issues/8135 +- Fixed a bug that prevented certain users from being able to edit approval nodes in Workflows - https://github.com/ansible/awx/pull/8253 +- Fixed a bug that broke password prompting for credentials in certain cases - https://github.com/ansible/awx/issues/8202 +- Fixed a bug which can cause PostgreSQL deadlocks when running many parallel playbooks against large shared inventories - https://github.com/ansible/awx/issues/8145 +- Fixed a bug which can cause delays in AWX's task manager when large numbers of simultaneous jobs are scheduled - https://github.com/ansible/awx/issues/7655 +- Fixed a bug which can cause certain scheduled jobs - those that run every X minute(s) or hour(s) - to fail to run at the proper time - https://github.com/ansible/awx/issues/8071 +- Fixed a performance issue for playbooks that store large amounts of data using the `set_stats` module - https://github.com/ansible/awx/issues/8006 +- Fixed a bug related to AWX's handling of the auth_path argument for the HashiVault KeyValue credential plugin - https://github.com/ansible/awx/pull/7991 +- Fixed a bug that broke support for Remote Archive SCM Type project syncs on platforms that utilize Python2 - https://github.com/ansible/awx/pull/8057 +- Updated to the latest version of Django Rest Framework to address CVE-2020-25626 +- Updated to the latest version of Django to address CVE-2020-24583 and CVE-2020-24584 +- Updated to the latest verson of channels_redis to address a bug that slowly causes Daphne processes to leak memory over time - https://github.com/django/channels_redis/issues/212 + +## 14.1.0 (Aug 25, 2020) +- AWX images can now be built on ARM64 - https://github.com/ansible/awx/pull/7607 +- Added the Remote Archive SCM Type to support using immutable artifacts and releases (such as tarballs and zip files) as projects - https://github.com/ansible/awx/issues/7954 +- Deprecated official support for Mercurial-based project updates - https://github.com/ansible/awx/issues/7932 +- Added resource import/export support to the official AWX collection - https://github.com/ansible/awx/issues/7329 +- Added the ability to import YAML-based resources (instead of just JSON) when using the AWX CLI - https://github.com/ansible/awx/pull/7808 +- Users upgrading from older versions of AWX may encounter an issue that causes their postgres container to restart in a loop (https://github.com/ansible/awx/issues/7854) - if you encounter this, bring your containers down and then back up (e.g., `docker-compose down && docker-compose up -d`) after upgrading to 14.1.0. +- Updated the AWX CLI to export labels associated with Workflow Job Templates - https://github.com/ansible/awx/pull/7847 +- Updated to the latest python-ldap to address a bug - https://github.com/ansible/awx/issues/7868 +- Upgraded git-python to fix a bug that caused workflows to sometimes fail - https://github.com/ansible/awx/issues/6119 +- Worked around a bug in the channels_redis library that slowly causes Daphne processes to leak memory over time - https://github.com/django/channels_redis/issues/212 +- Fixed a bug in the AWX CLI that prevented Workflow nodes from importing properly - https://github.com/ansible/awx/issues/7793 +- Fixed a bug in the awx.awx collection release process that templated the wrong version - https://github.com/ansible/awx/issues/7870 +- Fixed a bug that caused errors rendering stdout that contained UTF-16 surrogate pairs - https://github.com/ansible/awx/pull/7918 + +## 14.0.0 (Aug 6, 2020) +- As part of our commitment to inclusivity in open source, we recently took some time to audit AWX's source code and user interface and replace certain terminology with more inclusive language. Strictly speaking, this isn't a bug or a feature, but we think it's important and worth calling attention to: + * https://github.com/ansible/awx/commit/78229f58715fbfbf88177e54031f532543b57acc + * https://www.redhat.com/en/blog/making-open-source-more-inclusive-eradicating-problematic-language +- Installing roles and collections via requirements.yml as part of Project Updates now requires at least Ansible 2.9 - https://github.com/ansible/awx/issues/7769 +- Deprecated the use of the `PRIMARY_GALAXY_USERNAME` and `PRIMARY_GALAXY_PASSWORD` settings. We recommend using tokens to access Galaxy or Automation Hub. +- Added local caching for downloaded roles and collections so they are not re-downloaded on nodes where they are up to date with the project - https://github.com/ansible/awx/issues/5518 +- Added the ability to associate K8S/OpenShift credentials to Job Template for playbook interaction with the `community.kubernetes` collection - https://github.com/ansible/awx/issues/5735 +- Added the ability to include HTML in the Custom Login Info presented on the login page - https://github.com/ansible/awx/issues/7600 +- Fixed https://access.redhat.com/security/cve/cve-2020-14327 - Server-side request forgery on credentials +- Fixed https://access.redhat.com/security/cve/cve-2020-14328 - Server-side request forgery on webhooks +- Fixed https://access.redhat.com/security/cve/cve-2020-14329 - Sensitive data exposure on labels +- Fixed https://access.redhat.com/security/cve/cve-2020-14337 - Named URLs allow for testing the presence or absence of objects +- Fixed a number of bugs in the user interface related to an upgrade of jQuery: + * https://github.com/ansible/awx/issues/7530 + * https://github.com/ansible/awx/issues/7546 + * https://github.com/ansible/awx/issues/7534 + * https://github.com/ansible/awx/issues/7606 +- Fixed a bug that caused the `-f yaml` flag of the AWX CLI to not print properly formatted YAML - https://github.com/ansible/awx/issues/7795 +- Fixed a bug in the installer that caused errors when `docker_registry_password` was set - https://github.com/ansible/awx/issues/7695 +- Fixed a permissions error that prevented certain users from starting AWX services - https://github.com/ansible/awx/issues/7545 +- Fixed a bug that allows superusers to run unsafe Jinja code when defining custom Credential Types - https://github.com/ansible/awx/pull/7584/ +- Fixed a bug that prevented users from creating (or editing) custom Credential Types containing boolean fields - https://github.com/ansible/awx/issues/7483 +- Fixed a bug that prevented users with postgres usernames containing uppercase letters from restoring backups succesfully - https://github.com/ansible/awx/pull/7519 +- Fixed a bug which allowed the creation (in the Tower API) of Groups and Hosts with the same name - https://github.com/ansible/awx/issues/4680 + +## 13.0.0 (Jun 23, 2020) +- Added import and export commands to the official AWX CLI, replacing send and receive from the old tower-cli (https://github.com/ansible/awx/pull/6125). +- Removed scripts as a means of running inventory updates of built-in types (https://github.com/ansible/awx/pull/6911) +- Ansible 2.8 is now partially unsupported; some inventory source types are known to no longer work. +- Fixed an issue where the vmware inventory source ssl_verify source variable was not recognized (https://github.com/ansible/awx/pull/7360) +- Fixed a bug that caused redis' listen socket to have too-permissive file permissions (https://github.com/ansible/awx/pull/7317) +- Fixed a bug that caused rsyslogd's configuration file to have world-readable file permissions, potentially leaking secrets (CVE-2020-10782) + +## 12.0.0 (Jun 9, 2020) +- Removed memcached as a dependency of AWX (https://github.com/ansible/awx/pull/7240) +- Moved to a single container image build instead of separate awx_web and awx_task images. The container image is just `awx` (https://github.com/ansible/awx/pull/7228) +- Official AWX container image builds now use a two-stage container build process that notably reduces the size of our published images (https://github.com/ansible/awx/pull/7017) +- Removed support for HipChat notifications ([EoL announcement](https://www.atlassian.com/partnerships/slack/faq#faq-98b17ca3-247f-423b-9a78-70a91681eff0)); all previously-created HipChat notification templates will be deleted due to this removal. +- Fixed a bug which broke AWX installations with oc version 4.3 (https://github.com/ansible/awx/pull/6948/) +- Fixed a performance issue that caused notable delay of stdout processing for playbooks run against large numbers of hosts (https://github.com/ansible/awx/issues/6991) +- Fixed a bug that caused CyberArk AIM credential plugin looks to hang forever in some environments (https://github.com/ansible/awx/issues/6986) +- Fixed a bug that caused ANY/ALL converage settings not to properly save when editing approval nodes in the UI (https://github.com/ansible/awx/issues/6998) +- Fixed a bug that broke support for the satellite6_group_prefix source variable (https://github.com/ansible/awx/issues/7031) +- Fixed a bug that prevented changes to workflow node convergence settings when approval nodes were in use (https://github.com/ansible/awx/issues/7063) +- Fixed a bug that caused notifications to fail on newer version of Mattermost (https://github.com/ansible/awx/issues/7264) +- Fixed a bug (by upgrading to 0.8.1 of the foreman collection) that prevented host_filters from working properly with Foreman-based inventory (https://github.com/ansible/awx/issues/7225) +- Fixed a bug that prevented the usage of the Conjur credential plugin with secrets that contain spaces (https://github.com/ansible/awx/issues/7191) +- Fixed a bug in awx-manage run_wsbroadcast --status in kubernetes (https://github.com/ansible/awx/pull/7009) +- Fixed a bug that broke notification toggles for system jobs in the UI (https://github.com/ansible/awx/pull/7042) +- Fixed a bug that broke local pip installs of awxkit (https://github.com/ansible/awx/issues/7107) +- Fixed a bug that prevented PagerDuty notifications from sending for workflow job template approvals (https://github.com/ansible/awx/issues/7094) +- Fixed a bug that broke external log aggregation support for URL paths that include the = character (such as the tokens for SumoLogic) (https://github.com/ansible/awx/issues/7139) +- Fixed a bug that prevented organization admins from removing labels from workflow job templates (https://github.com/ansible/awx/pull/7143) + +## 11.2.0 (Apr 29, 2020) + +- Inventory updates now use collection-based plugins by default (in Ansible 2.9+): + - amazon.aws.aws_ec2 + - community.vmware.vmware_vm_inventory + - azure.azcollection.azure_rm + - google.cloud.gcp_compute + - theforeman.foreman.foreman + - openstack.cloud.openstack + - ovirt.ovirt_collection.ovirt + - awx.awx.tower +- Added support for Approle and LDAP/AD mechanisms to the Hashicorp Vault credential plugin (https://github.com/ansible/awx/issues/5076) +- Added Project (Domain Name) support for the OpenStack Keystone v3 API (https://github.com/ansible/awx/issues/6831) +- Added a new setting for raising log verbosity for rsyslogd (https://github.com/ansible/awx/pull/6818) +- Added the ability to monitor stdout in the CLI for running jobs and workflow jobs (https://github.com/ansible/awx/issues/6165) +- Fixed a bug which prevented the AWX CLI from properly installing with newer versions of pip (https://github.com/ansible/awx/issues/6870) +- Fixed a bug which broke AWX's external logging support when configured with HTTPS endpoints that utilize self-signed certificates (https://github.com/ansible/awx/issues/6851) +- Fixed a local docker installer bug that mistakenly attempted to upgrade PostgreSQL when an external pg_hostname is specified (https://github.com/ansible/awx/pull/5398) +- Fixed a race condition that caused task container crashes when pods are quickly brought down and back up (https://github.com/ansible/awx/issues/6750) +- Fixed a bug that caused 404 errors when attempting to view the second page of the workflow approvals view (https://github.com/ansible/awx/issues/6803) +- Fixed a bug that prevented the use of ANSIBLE_SSH_ARGS for ad-hoc-commands (https://github.com/ansible/awx/pull/6811) +- Fixed a bug that broke AWX installs/upgrades on Red Hat OpenShift (https://github.com/ansible/awx/issues/6791) + + +## 11.1.0 (Apr 22, 2020) +- Changed rsyslogd to persist queued events to disk (to prevent a risk of out-of-memory errors) (https://github.com/ansible/awx/issues/6746) +- Added the ability to configure the destination and maximum disk size of rsyslogd spool (in the event of a log aggregator outage) (https://github.com/ansible/awx/pull/6763) +- Added the ability to discover playbooks in project clones from symlinked directories (https://github.com/ansible/awx/pull/6773) +- Fixed a bug that caused certain log aggregator settings to break logging integration (https://github.com/ansible/awx/issues/6760) +- Fixed a bug that caused playbook execution in container groups to sometimes unexpectedly deadlock (https://github.com/ansible/awx/issues/6692) +- Improved stability of the new redis clustering implementation (https://github.com/ansible/awx/pull/6739 https://github.com/ansible/awx/pull/6720) +- Improved stability of the new rsyslogd-based logging implementation (https://github.com/ansible/awx/pull/6796) + +## 11.0.0 (Apr 16, 2020) +- As of AWX 11.0.0, Kubernetes-based deployments use a Deployment rather than a StatefulSet. +- Reimplemented external logging support using rsyslogd to improve reliability and address a number of issues (https://github.com/ansible/awx/issues/5155) +- Changed activity stream logs to include summary fields for related objects (https://github.com/ansible/awx/issues/1761) +- Added code to more gracefully attempt to reconnect to redis if it restarts/becomes unavailable (https://github.com/ansible/awx/pull/6670) +- Fixed a bug that caused REFRESH_TOKEN_EXPIRE_SECONDS to not properly be respected for OAuth2.0 refresh tokens generated by AWX (https://github.com/ansible/awx/issues/6630) +- Fixed a bug that broke schedules containing RRULES with very old DTSTART dates (https://github.com/ansible/awx/pull/6550) +- Fixed a bug that broke installs on older versions of Ansible packaged with certain Linux distributions (https://github.com/ansible/awx/issues/5501) +- Fixed a bug that caused the activity stream to sometimes report the incorrect actor when associating user membership on SAML login (https://github.com/ansible/awx/pull/6525) +- Fixed a bug in AWX's Grafana notification support when annotation tags are omitted (https://github.com/ansible/awx/issues/6580) +- Fixed a bug that prevented some users from searching for Source Control credentials in the AWX user interface (https://github.com/ansible/awx/issues/6600) +- Fixed a bug that prevented disassociating orphaned users from credentials (https://github.com/ansible/awx/pull/6554) +- Updated Twisted to address CVE-2020-10108 and CVE-2020-10109. + +## 10.0.0 (Mar 30, 2020) +- As of AWX 10.0.0, the official AWX CLI no longer supports Python 2 (it requires at least Python 3.6) (https://github.com/ansible/awx/pull/6327) +- AWX no longer relies on RabbitMQ; Redis is added as a new dependency (https://github.com/ansible/awx/issues/5443) +- Altered AWX's event tables to allow more than ~2 billion total events (https://github.com/ansible/awx/issues/6010) +- Improved the performance (time to execute, and memory consumption) of the periodic job cleanup system job (https://github.com/ansible/awx/pull/6166) +- Updated Job Templates so they now have an explicit Organization field (it is no longer inferred from the associated Project) (https://github.com/ansible/awx/issues/3903) +- Updated social-auth-core to address an upcoming GitHub API deprecation (https://github.com/ansible/awx/issues/5970) +- Updated to ansible-runner 1.4.6 to address various bugs. +- Updated Django to address CVE-2020-9402 +- Updated pyyaml version to address CVE-2017-18342 +- Fixed a bug which prevented the new `scm_branch` field from being used in custom notification templates (https://github.com/ansible/awx/issues/6258) +- Fixed a race condition that sometimes causes success/failure notifications to include an incomplete list of hosts (https://github.com/ansible/awx/pull/6290) +- Fixed a bug that can cause certain setting pages to lose unsaved form edits when a playbook is launched (https://github.com/ansible/awx/issues/5265) +- Fixed a bug that can prevent the "Use TLS/SSL" field from properly saving when editing email notification templates (https://github.com/ansible/awx/issues/6383) +- Fixed a race condition that sometimes broke event/stdout processing for jobs launched in container groups (https://github.com/ansible/awx/issues/6280) + +## 9.3.0 (Mar 12, 2020) +- Added the ability to specify an OAuth2 token description in the AWX CLI (https://github.com/ansible/awx/issues/6122) +- Added support for K8S service account annotations to the installer (https://github.com/ansible/awx/pull/6007) +- Added support for K8S imagePullSecrets to the installer (https://github.com/ansible/awx/pull/5989) +- Launching jobs (and workflows) using the --monitor flag in the AWX CLI now returns a non-zero exit code on job failure (https://github.com/ansible/awx/issues/5920) +- Improved UI performance for various job views when many simultaneous users are logged into AWX (https://github.com/ansible/awx/issues/5883) +- Updated to the latest version of Django to address a few open CVEs (https://github.com/ansible/awx/pull/6080) +- Fixed a critical bug which can cause AWX to hang and stop launching playbooks after a periodic of time (https://github.com/ansible/awx/issues/5617) +- Fixed a bug which caused delays in project update stdout for certain large SCM clones (as of Ansible 2.9+) (https://github.com/ansible/awx/pull/6254) +- Fixed a bug which caused certain smart inventory filters to mistakenly return duplicate hosts (https://github.com/ansible/awx/pull/5972) +- Fixed an unclear server error when creating smart inventories with the AWX collection (https://github.com/ansible/awx/issues/6250) +- Fixed a bug that broke Grafana notification support (https://github.com/ansible/awx/issues/6137) +- Fixed a UI bug which prevent users with read access to an organization from editing credentials for that organization (https://github.com/ansible/awx/pull/6241) +- Fixed a bug which prevent workflow approval records from recording a `started` and `elapsed` date (https://github.com/ansible/awx/issues/6202) +- Fixed a bug which caused workflow nodes to have a confusing option for `verbosity` (https://github.com/ansible/awx/issues/6196) +- Fixed an RBAC bug which prevented projects and inventory schedules from being created by certain users in certain contexts (https://github.com/ansible/awx/issues/5717) +- Fixed a bug that caused `role_path` in a project's config to not be respected due to an error processing `/etc/ansible/ansible.cfg` (https://github.com/ansible/awx/pull/6038) +- Fixed a bug that broke inventory updates for installs with custom home directories for the awx user (https://github.com/ansible/awx/pull/6152) +- Fixed a bug that broke fact data collection when AWX encounters invalid/unexpected fact data (https://github.com/ansible/awx/issues/5935) + + +## 9.2.0 (Feb 12, 2020) +- Added the ability to configure the convergence behavior of workflow nodes https://github.com/ansible/awx/issues/3054 +- AWX now allows for a configurable global limit for fork count (per-job run). The default maximum is 200. https://github.com/ansible/awx/pull/5604 +- Added the ability to specify AZURE_PUBLIC_CLOUD (for e.g., Azure Government KeyVault support) for the Azure credential plugin https://github.com/ansible/awx/issues/5138 +- Added support for several additional parameters for Satellite dynamic inventory https://github.com/ansible/awx/pull/5598 +- Added a new field to jobs for tracking the date/time a job is cancelled https://github.com/ansible/awx/pull/5610 +- Made a series of additional optimizations to the callback receiver to further improve stdout write speed for running playbooks https://github.com/ansible/awx/pull/5677 https://github.com/ansible/awx/pull/5739 +- Updated AWX to be compatible with Helm 3.x (https://github.com/ansible/awx/pull/5776) +- Optimized AWX's job dependency/scheduling code to drastically improve processing time in scenarios where there are many pending jobs scheduled simultaneously https://github.com/ansible/awx/issues/5154 +- Fixed a bug which could cause SCM authentication details (basic auth passwords) to be reported to external loggers in certain failure scenarios (e.g., when a git clone fails and ansible itself prints an error message to stdout) https://github.com/ansible/awx/pull/5812 +- Fixed a k8s installer bug that caused installs to fail in certain situations https://github.com/ansible/awx/issues/5574 +- Fixed a number of issues that caused analytics gathering and reporting to run more often than necessary https://github.com/ansible/awx/pull/5721 +- Fixed a bug in the AWX CLI that prevented JSON-type settings from saving properly https://github.com/ansible/awx/issues/5528 +- Improved support for fetching custom virtualenv dependencies when AWX is installed behind a proxy https://github.com/ansible/awx/pull/5805 +- Updated the bundled version of openstacksdk to address a known issue https://github.com/ansible/awx/issues/5821 +- Updated the bundled vmware_inventory plugin to the latest version to address a bug https://github.com/ansible/awx/pull/5668 +- Fixed a bug that can cause inventory updates to fail to properly save their output when run within a workflow https://github.com/ansible/awx/pull/5666 +- Removed a number of pre-computed fields from the Host and Group models to improve AWX performance. As part of this change, inventory group UIs throughout the interface no longer display status icons https://github.com/ansible/awx/pull/5448 + ## 9.1.1 (Jan 14, 2020) - Fixed a bug that caused database migrations on Kubernetes installs to hang https://github.com/ansible/awx/pull/5579 @@ -39,7 +292,7 @@ This is a list of high-level changes for each release of AWX. A full list of com - Fixed a bug in the CLI which incorrectly parsed launch time arguments for `awx job_templates launch` and `awx workflow_job_templates launch` (https://github.com/ansible/awx/issues/5093). - Fixed a bug that caused inventory updates using "sourced from a project" to stop working (https://github.com/ansible/awx/issues/4750). - Fixed a bug that caused Slack notifications to sometimes show the wrong bot avatar (https://github.com/ansible/awx/pull/5125). -- Fixed a bug that prevented the use of digits in Tower's URL settings (https://github.com/ansible/awx/issues/5081). +- Fixed a bug that prevented the use of digits in AWX's URL settings (https://github.com/ansible/awx/issues/5081). ## 8.0.0 (Oct 21, 2019) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c18bafa152aa..e311ecfa1c37 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -80,12 +80,12 @@ For Linux platforms, refer to the following from Docker: If you're not using Docker for Mac, or Docker for Windows, you may need, or choose to, install the Docker compose Python module separately, in which case you'll need to run the following: ```bash -(host)$ pip install docker-compose +(host)$ pip3 install docker-compose ``` #### Frontend Development -See [the ui development documentation](awx/ui/README.md). +See [the ui development documentation](awx/ui_next/CONTRIBUTING.md). ### Build the environment @@ -155,12 +155,10 @@ If you start a second terminal session, you can take a look at the running conta (host)$ docker ps $ docker ps -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -aa4a75d6d77b gcr.io/ansible-tower-engineering/awx_devel:devel "/tini -- /bin/sh ..." 23 seconds ago Up 15 seconds 0.0.0.0:5555->5555/tcp, 0.0.0.0:7899-7999->7899-7999/tcp, 0.0.0.0:8013->8013/tcp, 0.0.0.0:8043->8043/tcp, 22/tcp, 0.0.0.0:8080->8080/tcp tools_awx_1 -e4c0afeb548c postgres:10 "docker-entrypoint..." 26 seconds ago Up 23 seconds 5432/tcp tools_postgres_1 -0089699d5afd tools_logstash "/docker-entrypoin..." 26 seconds ago Up 25 seconds tools_logstash_1 -4d4ff0ced266 memcached:alpine "docker-entrypoint..." 26 seconds ago Up 25 seconds 0.0.0.0:11211->11211/tcp tools_memcached_1 -92842acd64cd rabbitmq:3-management "docker-entrypoint..." 26 seconds ago Up 24 seconds 4369/tcp, 5671-5672/tcp, 15671/tcp, 25672/tcp, 0.0.0.0:15672->15672/tcp tools_rabbitmq_1 +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +44251b476f98 gcr.io/ansible-tower-engineering/awx_devel:devel "/entrypoint.sh /bin…" 27 seconds ago Up 23 seconds 0.0.0.0:6899->6899/tcp, 0.0.0.0:7899-7999->7899-7999/tcp, 0.0.0.0:8013->8013/tcp, 0.0.0.0:8043->8043/tcp, 0.0.0.0:8080->8080/tcp, 22/tcp, 0.0.0.0:8888->8888/tcp tools_awx_run_9e820694d57e +40de380e3c2e redis:latest "docker-entrypoint.s…" 28 seconds ago Up 26 seconds +b66a506d3007 postgres:12 "docker-entrypoint.s…" 28 seconds ago Up 26 seconds 0.0.0.0:5432->5432/tcp tools_postgres_1 ``` **NOTE** @@ -216,18 +214,23 @@ Using `docker exec`, this will create a session in the running *awx* container, If you want to start and use the development environment, you'll first need to bootstrap it by running the following command: ```bash -(container)# /bootstrap_development.sh +(container)# /usr/bin/bootstrap_development.sh ``` -The above will do all the setup tasks, including running database migrations, so it may take a couple minutes. +The above will do all the setup tasks, including running database migrations, so it may take a couple minutes. Once it's done it +will drop you back to the shell. -Now you can start each service individually, or start all services in a pre-configured tmux session like so: +In order to launch all developer services: ```bash -(container)# cd /awx_devel -(container)# make server +(container)# /usr/bin/launch_awx.sh ``` +`launch_awx.sh` also calls `bootstrap_development.sh` so if all you are doing is launching the supervisor to start all services, you don't +need to call `bootstrap_development.sh` first. + + + ### Post Build Steps Before you can log in and use the system, you will need to create an admin user. Optionally, you may also want to load some demo data. diff --git a/DATA_MIGRATION.md b/DATA_MIGRATION.md index 8e46cf69064e..e7ea3b391d57 100644 --- a/DATA_MIGRATION.md +++ b/DATA_MIGRATION.md @@ -2,96 +2,8 @@ ## Introduction -Upgrades using Django migrations are not expected to work in AWX. As a result, to upgrade to a new version, it is necessary to export resources from the old AWX node and import them into a freshly-installed node with the new version. The recommended way to do this is to use the tower-cli send/receive feature. +Early versions of AWX did not support seamless upgrades between major versions and required the use of a backup and restore tool to perform upgrades. -This tool does __not__ support export/import of the following: -* Logs/history -* Credential passwords -* LDAP/AWX config +Users who wish to upgrade modern AWX installations should follow the instructions at: -### Install & Configure Tower-CLI - -In terminal, pip install tower-cli (if you do not have pip already, install [here](https://pip.pypa.io/en/stable/installing/)): -``` -$ pip install --upgrade ansible-tower-cli -``` - -The AWX host URL, user, and password must be set for the AWX instance to be exported: -``` -$ tower-cli config host http:// -$ tower-cli config username -$ tower-cli config password -``` - -For more information on installing tower-cli look [here](http://tower-cli.readthedocs.io/en/latest/quickstart.html). - - -### Export Resources - -Export all objects - -```$ tower-cli receive --all > assets.json``` - - - -### Teardown Old AWX - -Clean up remnants of the old AWX install: - -```docker rm -f $(docker ps -aq)``` # remove all old awx containers - -```make clean-ui``` # clean up ui artifacts - - -### Install New AWX version - -If you are installing AWX as a dev container, pull down the latest code or version you want from GitHub, build -the image locally, then start the container - -``` -git pull # retrieve latest AWX changes from repository -make docker-compose-build # build AWX image -make docker-compose # run container -``` -For other install methods, refer to the [Install.md](https://github.com/ansible/awx/blob/devel/INSTALL.md). - - -### Import Resources - - -Configure tower-cli for your new AWX host as shown earlier. Import from a JSON file named assets.json - -``` -$ tower-cli config host http:// -$ tower-cli config username -$ tower-cli config password -$ tower-cli send assets.json -``` - --------------------------------------------------------------------------------- - -## Additional Info - -If you have two running AWX hosts, it is possible to copy all assets from one instance to another - -```$ tower-cli receive --tower-host old-awx-host.example.com --all | tower-cli send --tower-host new-awx-host.example.com``` - - - -#### More Granular Exports: - -Export all credentials - -```$ tower-cli receive --credential all > credentials.json``` -> Note: This exports the credentials with blank strings for passwords and secrets - -Export a credential named "My Credential" - -```$ tower-cli receive --credential "My Credential"``` - -#### More Granular Imports: - - -You could import anything except an organization defined in a JSON file named assets.json - -```$ tower-cli send --prevent organization assets.json``` +https://github.com/ansible/awx/blob/devel/INSTALL.md#upgrading-from-previous-versions diff --git a/INSTALL.md b/INSTALL.md index 604ba91281a2..f7ab93a6e354 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -10,7 +10,6 @@ This document provides a guide for installing AWX. + [AWX branding](#awx-branding) + [Prerequisites](#prerequisites) + [System Requirements](#system-requirements) - + [AWX Tunables](#awx-tunables) + [Choose a deployment platform](#choose-a-deployment-platform) + [Official vs Building Images](#official-vs-building-images) * [Upgrading from previous versions](#upgrading-from-previous-versions) @@ -41,15 +40,27 @@ This document provides a guide for installing AWX. + [Run the installer](#run-the-installer-2) + [Post-install](#post-install-2) + [Accessing AWX](#accessing-awx-2) +- [Installing the AWX CLI](#installing-the-awx-cli) + * [Building the CLI Documentation](#building-the-cli-documentation) + - ## Getting started ### Clone the repo -If you have not already done so, you will need to clone, or create a local copy, of the [AWX repo](https://github.com/ansible/awx). For more on how to clone the repo, view [git clone help](https://git-scm.com/docs/git-clone). +If you have not already done so, you will need to clone, or create a local copy, of the [AWX repo](https://github.com/ansible/awx). We generally recommend that you view the releases page: + +https://github.com/ansible/awx/releases + +...and clone the latest stable release, e.g., + +`git clone -b x.y.z https://github.com/ansible/awx.git` -Once you have a local copy, run commands within the root of the project tree. +Please note that deploying from `HEAD` (or the latest commit) is **not** stable, and that if you want to do this, you should proceed at your own risk (also, see the section #official-vs-building-images for building your own image). + +For more on how to clone the repo, view [git clone help](https://git-scm.com/docs/git-clone). + +Once you have a local copy, run the commands in the following sections from the root of the project tree. ### AWX branding @@ -67,10 +78,15 @@ Before you can run a deployment, you'll need the following installed in your loc - [docker](https://pypi.org/project/docker/) Python module + This is incompatible with `docker-py`. If you have previously installed `docker-py`, please uninstall it. + We use this module instead of `docker-py` because it is what the `docker-compose` Python module requires. +- [community.general.docker_image collection](https://docs.ansible.com/ansible/latest/collections/community/general/docker_image_module.html) + + This is only required if you are using Ansible >= 2.10 - [GNU Make](https://www.gnu.org/software/make/) - [Git](https://git-scm.com/) Requires Version 1.8.4+ -- [Node 10.x LTS version](https://nodejs.org/en/download/) +- Python 3.6+ +- [Node 14.x LTS version](https://nodejs.org/en/download/) + + This is only required if you're [building your own container images](#official-vs-building-images) with `use_container_for_build=false` - [NPM 6.x LTS](https://docs.npmjs.com/) + + This is only required if you're [building your own container images](#official-vs-building-images) with `use_container_for_build=false` ### System Requirements @@ -80,11 +96,7 @@ The system that runs the AWX service will need to satisfy the following requirem - At least 2 cpu cores - At least 20GB of space - Running Docker, Openshift, or Kubernetes -- If you choose to use an external PostgreSQL database, please note that the minimum version is 9.6+. - -### AWX Tunables - -**TODO** add tunable bits +- If you choose to use an external PostgreSQL database, please note that the minimum version is 10+. ### Choose a deployment platform @@ -99,7 +111,7 @@ In the sections below, you'll find deployment details and instructions for each ### Official vs Building Images -When installing AWX you have the option of building your own images or using the images provided on DockerHub (see [awx_web](https://hub.docker.com/r/ansible/awx_web/) and [awx_task](https://hub.docker.com/r/ansible/awx_task/)) +When installing AWX you have the option of building your own image or using the image provided on DockerHub (see [awx](https://hub.docker.com/r/ansible/awx/)) This is controlled by the following variables in the `inventory` file @@ -112,12 +124,16 @@ If these variables are present then all deployments will use these hosted images *dockerhub_base* -> The base location on DockerHub where the images are hosted (by default this pulls container images named `ansible/awx_web:tag` and `ansible/awx_task:tag`) +> The base location on DockerHub where the images are hosted (by default this pulls a container image named `ansible/awx:tag`) *dockerhub_version* > Multiple versions are provided. `latest` always pulls the most recent. You may also select version numbers at different granularities: 1, 1.0, 1.0.1, 1.0.0.123 +*use_container_for_build* + +> Use a local distribution build container image for building the AWX package. This is helpful if you don't want to bother installing the build-time dependencies as it is taken care of already. + ## Upgrading from previous versions @@ -128,7 +144,6 @@ For convenience, you can create a file called `vars.yml`: ``` admin_password: 'adminpass' pg_password: 'pgpass' -rabbitmq_password: 'rabbitpass' secret_key: 'mysupersecret' ``` @@ -142,7 +157,7 @@ $ ansible-playbook -i inventory install.yml -e @vars.yml ### Prerequisites -To complete a deployment to OpenShift, you will obviously need access to an OpenShift cluster. For demo and testing purposes, you can use [Minishift](https://github.com/minishift/minishift) to create a single node cluster running inside a virtual machine. +To complete a deployment to OpenShift, you will need access to an OpenShift cluster. For demo and testing purposes, you can use [Minishift](https://github.com/minishift/minishift) to create a single node cluster running inside a virtual machine. When using OpenShift for deploying AWX make sure you have correct privileges to add the security context 'privileged', otherwise the installation will fail. The privileged context is needed because of the use of [the bubblewrap tool](https://github.com/containers/bubblewrap) to add an additional layer of security when using containers. @@ -338,7 +353,7 @@ Once you access the AWX server, you will be prompted with a login dialog. The de A Kubernetes deployment will require you to have access to a Kubernetes cluster as well as the following tools: - [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) -- [helm](https://docs.helm.sh/using_helm/#quickstart-guide) +- [helm](https://helm.sh/docs/intro/quickstart/) The installation program will reference `kubectl` directly. `helm` is only necessary if you are letting the installer configure PostgreSQL for you. @@ -369,9 +384,11 @@ Before starting the install process, review the [inventory](./installer/inventor ### Configuring Helm -If you want the AWX installer to manage creating the database pod (rather than installing and configuring postgres on your own). Then you will need to have a working `helm` installation, you can find details here: [https://docs.helm.sh/using_helm/#quickstart-guide](https://docs.helm.sh/using_helm/#quickstart-guide). +If you want the AWX installer to manage creating the database pod (rather than installing and configuring postgres on your own). Then you will need to have a working `helm` installation, you can find details here: [https://helm.sh/docs/intro/quickstart/](https://helm.sh/docs/intro/quickstart/). + +You do not need to create a [Persistent Volume Claim](https://docs.openshift.org/latest/dev_guide/persistent_volumes.html) as Helm does it for you. However, an existing one may be used by setting the `pg_persistence_existingclaim` variable. -Newer Kubernetes clusters with RBAC enabled will need to make sure a service account is created, make sure to follow the instructions here [https://docs.helm.sh/using_helm/#role-based-access-control](https://docs.helm.sh/using_helm/#role-based-access-control) +Newer Kubernetes clusters with RBAC enabled will need to make sure a service account is created, make sure to follow the instructions here [https://helm.sh/docs/topics/rbac/](https://helm.sh/docs/topics/rbac/) ### Run the installer @@ -468,19 +485,19 @@ Before starting the install process, review the [inventory](./installer/inventor *host_port* -> Provide a port number that can be mapped from the Docker daemon host to the web server running inside the AWX container. Defaults to *80*. +> Provide a port number that can be mapped from the Docker daemon host to the web server running inside the AWX container. If undefined no port will be exposed. Defaults to *80*. *host_port_ssl* -> Provide a port number that can be mapped from the Docker daemon host to the web server running inside the AWX container for SSL support. Defaults to *443*, only works if you also set `ssl_certificate` (see below). +> Provide a port number that can be mapped from the Docker daemon host to the web server running inside the AWX container for SSL support. If undefined no port will be exposed. Defaults to *443*, only works if you also set `ssl_certificate` (see below). *ssl_certificate* -> Optionally, provide the path to a file that contains a certificate and its private key. +> Optionally, provide the path to a file that contains a certificate and its private key. This needs to be a .pem-file *docker_compose_dir* -> When using docker-compose, the `docker-compose.yml` file will be created there (default `/tmp/awxcompose`). +> When using docker-compose, the `docker-compose.yml` file will be created there (default `~/.awx/awxcompose`). *custom_venv_dir* @@ -506,10 +523,6 @@ If you wish to tag and push built images to a Docker registry, set the following > Username of the user that will push images to the registry. Defaults to *developer*. -*docker_remove_local_images* - -> Due to the way that the docker_image module behaves, images will not be pushed to a remote repository if they are present locally. Set this to delete local versions of the images that will be pushed to the remote. This will fail if containers are currently running from those images. - **Note** > These settings are ignored if using official images @@ -559,23 +572,14 @@ $ ansible-playbook -i inventory -e docker_registry_password=password install.yml ### Post-install -After the playbook run completes, Docker will report up to 5 running containers. If you chose to use an existing PostgresSQL database, then it will report 4. You can view the running containers using the `docker ps` command, as follows: - -```bash -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -e240ed8209cd awx_task:1.0.0.8 "/tini -- /bin/sh ..." 2 minutes ago Up About a minute 8052/tcp awx_task -1cfd02601690 awx_web:1.0.0.8 "/tini -- /bin/sh ..." 2 minutes ago Up About a minute 0.0.0.0:443->8052/tcp awx_web -55a552142bcd memcached:alpine "docker-entrypoint..." 2 minutes ago Up 2 minutes 11211/tcp memcached -84011c072aad rabbitmq:3 "docker-entrypoint..." 2 minutes ago Up 2 minutes 4369/tcp, 5671-5672/tcp, 25672/tcp rabbitmq -97e196120ab3 postgres:9.6 "docker-entrypoint..." 2 minutes ago Up 2 minutes 5432/tcp postgres -``` +After the playbook run completes, Docker starts a series of containers that provide the services that make up AWX. You can view the running containers using the `docker ps` command. If you're deploying using Docker Compose, container names will be prefixed by the name of the folder where the docker-compose.yml file is created (by default, `awx`). Immediately after the containers start, the *awx_task* container will perform required setup tasks, including database migrations. These tasks need to complete before the web interface can be accessed. To monitor the progress, you can follow the container's STDOUT by running the following: ```bash -# Tail the the awx_task log +# Tail the awx_task log $ docker logs -f awx_task ``` @@ -634,3 +638,33 @@ Added instance awx to tower The AWX web server is accessible on the deployment host, using the *host_port* value set in the *inventory* file. The default URL is [http://localhost](http://localhost). You will prompted with a login dialog. The default administrator username is `admin`, and the password is `password`. + + +# Installing the AWX CLI + +`awx` is the official command-line client for AWX. It: + +* Uses naming and structure consistent with the AWX HTTP API +* Provides consistent output formats with optional machine-parsable formats +* To the extent possible, auto-detects API versions, available endpoints, and + feature support across multiple versions of AWX. + +Potential uses include: + +* Configuring and launching jobs/playbooks +* Checking on the status and output of job runs +* Managing objects like organizations, users, teams, etc... + +The preferred way to install the AWX CLI is through pip directly from PyPI: + + pip3 install awxkit + awx --help + +## Building the CLI Documentation + +To build the docs, spin up a real AWX server, `pip3 install sphinx sphinxcontrib-autoprogram`, and run: + + ~ cd awxkit/awxkit/cli/docs + ~ TOWER_HOST=https://awx.example.org TOWER_USERNAME=example TOWER_PASSWORD=secret make clean html + ~ cd build/html/ && python -m http.server + Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) .. diff --git a/ISSUES.md b/ISSUES.md index 317209ff7403..aa1889a99c9b 100644 --- a/ISSUES.md +++ b/ISSUES.md @@ -31,7 +31,7 @@ If your issue isn't considered high priority, then please be patient as it may t `state:needs_info` The issue needs more information. This could be more debug output, more specifics out the system such as version information. Any detail that is currently preventing this issue from moving forward. This should be considered a blocked state. -`state:needs_review` The the issue/pull request needs to be reviewed by other maintainers and contributors. This is usually used when there is a question out to another maintainer or when a person is less familar with an area of the code base the issue is for. +`state:needs_review` The issue/pull request needs to be reviewed by other maintainers and contributors. This is usually used when there is a question out to another maintainer or when a person is less familar with an area of the code base the issue is for. `state:needs_revision` More commonly used on pull requests, this state represents that there are changes that are being waited on. diff --git a/MANIFEST.in b/MANIFEST.in index 3c687ce2dabe..146636160286 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,12 +4,13 @@ recursive-include awx *.mo recursive-include awx/static * recursive-include awx/templates *.html recursive-include awx/api/templates *.md *.html -recursive-include awx/ui/templates *.html -recursive-include awx/ui/static * +recursive-include awx/ui_next/build *.html +recursive-include awx/ui_next/build * recursive-include awx/playbooks *.yml recursive-include awx/lib/site-packages * recursive-include awx/plugins *.ps1 recursive-include requirements *.txt +recursive-include requirements *.yml recursive-include config * recursive-include docs/licenses * recursive-exclude awx devonly.py* diff --git a/Makefile b/Makefile index 5f54354bb421..9c7ccd028b87 100644 --- a/Makefile +++ b/Makefile @@ -6,26 +6,28 @@ PACKER ?= packer PACKER_BUILD_OPTS ?= -var 'official=$(OFFICIAL)' -var 'aw_repo_url=$(AW_REPO_URL)' NODE ?= node NPM_BIN ?= npm +CHROMIUM_BIN=/tmp/chrome-linux/chrome DEPS_SCRIPT ?= packaging/bundle/deps.py GIT_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD) MANAGEMENT_COMMAND ?= awx-manage IMAGE_REPOSITORY_AUTH ?= IMAGE_REPOSITORY_BASE ?= https://gcr.io VERSION := $(shell cat VERSION) +PYCURL_SSL_LIBRARY ?= openssl # NOTE: This defaults the container image version to the branch that's active COMPOSE_TAG ?= $(GIT_BRANCH) COMPOSE_HOST ?= $(shell hostname) -VENV_BASE ?= /venv -COLLECTION_VENV ?= /awx_devel/awx_collection_test_venv +VENV_BASE ?= /var/lib/awx/venv/ +COLLECTION_BASE ?= /var/lib/awx/vendor/awx_ansible_collections SCL_PREFIX ?= CELERY_SCHEDULE_FILE ?= /var/lib/awx/beat.db DEV_DOCKER_TAG_BASE ?= gcr.io/ansible-tower-engineering # Python packages to install only from source (not from binary wheels) # Comma separated list -SRC_ONLY_PKGS ?= cffi,pycparser,psycopg2,twilio +SRC_ONLY_PKGS ?= cffi,pycparser,psycopg2,twilio,pycurl # These should be upgraded in the AWX and Ansible venv before attempting # to install the actual requirements VENV_BOOTSTRAP ?= pip==19.3.1 setuptools==41.6.0 @@ -55,11 +57,6 @@ WHEEL_COMMAND ?= bdist_wheel SDIST_TAR_FILE ?= $(SDIST_TAR_NAME).tar.gz WHEEL_FILE ?= $(WHEEL_NAME)-py2-none-any.whl -# UI flag files -UI_DEPS_FLAG_FILE = awx/ui/.deps_built -UI_RELEASE_DEPS_FLAG_FILE = awx/ui/.release_deps_built -UI_RELEASE_FLAG_FILE = awx/ui/.release_built - I18N_FLAG_FILE = .i18n_built .PHONY: awx-link clean clean-tmp clean-venv requirements requirements_dev \ @@ -69,21 +66,6 @@ I18N_FLAG_FILE = .i18n_built ui-docker-machine ui-docker ui-release ui-devel \ ui-test ui-deps ui-test-ci VERSION -# remove ui build artifacts -clean-ui: clean-languages - rm -rf awx/ui/static/ - rm -rf awx/ui/node_modules/ - rm -rf awx/ui/test/unit/reports/ - rm -rf awx/ui/test/spec/reports/ - rm -rf awx/ui/test/e2e/reports/ - rm -rf awx/ui/client/languages/ - rm -rf awx/ui_next/node_modules/ - rm -rf awx/ui_next/coverage/ - rm -rf awx/ui_next/build/locales/_build/ - rm -f $(UI_DEPS_FLAG_FILE) - rm -f $(UI_RELEASE_DEPS_FLAG_FILE) - rm -f $(UI_RELEASE_FLAG_FILE) - clean-tmp: rm -rf tmp/ @@ -122,7 +104,7 @@ clean-api: rm -rf awx/projects clean-awxkit: - rm -rf awxkit/*.egg-info awxkit/.tox + rm -rf awxkit/*.egg-info awxkit/.tox awxkit/build/* # convenience target to assert environment variables are defined guard-%: @@ -167,17 +149,16 @@ virtualenv_awx: fi; \ if [ ! -d "$(VENV_BASE)/awx" ]; then \ virtualenv -p $(PYTHON) $(VENV_BASE)/awx; \ - $(VENV_BASE)/awx/bin/pip install $(PIP_OPTIONS) $(VENV_BOOTSTRAP) && \ - $(VENV_BASE)/awx/bin/pip install $(PIP_OPTIONS) flit; \ + $(VENV_BASE)/awx/bin/pip install $(PIP_OPTIONS) $(VENV_BOOTSTRAP); \ fi; \ fi # --ignore-install flag is not used because *.txt files should specify exact versions requirements_ansible: virtualenv_ansible if [[ "$(PIP_OPTIONS)" == *"--no-index"* ]]; then \ - cat requirements/requirements_ansible.txt requirements/requirements_ansible_local.txt | $(VENV_BASE)/ansible/bin/pip install $(PIP_OPTIONS) -r /dev/stdin ; \ + cat requirements/requirements_ansible.txt requirements/requirements_ansible_local.txt | PYCURL_SSL_LIBRARY=$(PYCURL_SSL_LIBRARY) $(VENV_BASE)/ansible/bin/pip install $(PIP_OPTIONS) -r /dev/stdin ; \ else \ - cat requirements/requirements_ansible.txt requirements/requirements_ansible_git.txt | $(VENV_BASE)/ansible/bin/pip install $(PIP_OPTIONS) --no-binary $(SRC_ONLY_PKGS) -r /dev/stdin ; \ + cat requirements/requirements_ansible.txt requirements/requirements_ansible_git.txt | PYCURL_SSL_LIBRARY=$(PYCURL_SSL_LIBRARY) $(VENV_BASE)/ansible/bin/pip install $(PIP_OPTIONS) --no-binary $(SRC_ONLY_PKGS) -r /dev/stdin ; \ fi $(VENV_BASE)/ansible/bin/pip uninstall --yes -r requirements/requirements_ansible_uninstall.txt # Same effect as using --system-site-packages flag on venv creation @@ -185,9 +166,9 @@ requirements_ansible: virtualenv_ansible requirements_ansible_py3: virtualenv_ansible_py3 if [[ "$(PIP_OPTIONS)" == *"--no-index"* ]]; then \ - cat requirements/requirements_ansible.txt requirements/requirements_ansible_local.txt | $(VENV_BASE)/ansible/bin/pip3 install $(PIP_OPTIONS) -r /dev/stdin ; \ + cat requirements/requirements_ansible.txt requirements/requirements_ansible_local.txt | PYCURL_SSL_LIBRARY=$(PYCURL_SSL_LIBRARY) $(VENV_BASE)/ansible/bin/pip3 install $(PIP_OPTIONS) -r /dev/stdin ; \ else \ - cat requirements/requirements_ansible.txt requirements/requirements_ansible_git.txt | $(VENV_BASE)/ansible/bin/pip3 install $(PIP_OPTIONS) --no-binary $(SRC_ONLY_PKGS) -r /dev/stdin ; \ + cat requirements/requirements_ansible.txt requirements/requirements_ansible_git.txt | PYCURL_SSL_LIBRARY=$(PYCURL_SSL_LIBRARY) $(VENV_BASE)/ansible/bin/pip3 install $(PIP_OPTIONS) --no-binary $(SRC_ONLY_PKGS) -r /dev/stdin ; \ fi $(VENV_BASE)/ansible/bin/pip3 uninstall --yes -r requirements/requirements_ansible_uninstall.txt # Same effect as using --system-site-packages flag on venv creation @@ -211,7 +192,15 @@ requirements_awx: virtualenv_awx requirements_awx_dev: $(VENV_BASE)/awx/bin/pip install -r requirements/requirements_dev.txt -requirements: requirements_ansible requirements_awx +requirements_collections: + mkdir -p $(COLLECTION_BASE) + n=0; \ + until [ "$$n" -ge 5 ]; do \ + ansible-galaxy collection install -r requirements/collections_requirements.yml -p $(COLLECTION_BASE) && break; \ + n=$$((n+1)); \ + done + +requirements: requirements_ansible requirements_awx requirements_collections requirements_dev: requirements_awx requirements_ansible_py3 requirements_awx_dev requirements_ansible_dev @@ -266,28 +255,6 @@ migrate: dbchange: $(MANAGEMENT_COMMAND) makemigrations -server_noattach: - tmux new-session -d -s awx 'exec make uwsgi' - tmux rename-window 'AWX' - tmux select-window -t awx:0 - tmux split-window -v 'exec make dispatcher' - tmux new-window 'exec make daphne' - tmux select-window -t awx:1 - tmux rename-window 'WebSockets' - tmux split-window -h 'exec make runworker' - tmux split-window -v 'exec make nginx' - tmux new-window 'exec make receiver' - tmux select-window -t awx:2 - tmux rename-window 'Extra Services' - tmux select-window -t awx:0 - -server: server_noattach - tmux -2 attach-session -t awx - -# Use with iterm2's native tmux protocol support -servercc: server_noattach - tmux -2 -CC attach-session -t awx - supervisor: @if [ "$(VENV_BASE)" ]; then \ . $(VENV_BASE)/awx/bin/activate; \ @@ -304,7 +271,7 @@ uwsgi: collectstatic @if [ "$(VENV_BASE)" ]; then \ . $(VENV_BASE)/awx/bin/activate; \ fi; \ - uwsgi -b 32768 --socket 127.0.0.1:8050 --module=awx.wsgi:application --home=/venv/awx --chdir=/awx_devel/ --vacuum --processes=5 --harakiri=120 --master --no-orphans --py-autoreload 1 --max-requests=1000 --stats /tmp/stats.socket --lazy-apps --logformat "%(addr) %(method) %(uri) - %(proto) %(status)" --hook-accepting1="exec:supervisorctl restart tower-processes:awx-dispatcher tower-processes:awx-receiver" + uwsgi -b 32768 --socket 127.0.0.1:8050 --module=awx.wsgi:application --home=/var/lib/awx/venv/awx --chdir=/awx_devel/ --vacuum --processes=5 --harakiri=120 --master --no-orphans --py-autoreload 1 --max-requests=1000 --stats /tmp/stats.socket --lazy-apps --logformat "%(addr) %(method) %(uri) - %(proto) %(status)" --hook-accepting1="exec:supervisorctl restart tower-processes:awx-dispatcher tower-processes:awx-receiver" daphne: @if [ "$(VENV_BASE)" ]; then \ @@ -312,18 +279,11 @@ daphne: fi; \ daphne -b 127.0.0.1 -p 8051 awx.asgi:channel_layer -runworker: - @if [ "$(VENV_BASE)" ]; then \ - . $(VENV_BASE)/awx/bin/activate; \ - fi; \ - $(PYTHON) manage.py runworker --only-channels websocket.* - -# Run the built-in development webserver (by default on http://localhost:8013). -runserver: +wsbroadcast: @if [ "$(VENV_BASE)" ]; then \ . $(VENV_BASE)/awx/bin/activate; \ fi; \ - $(PYTHON) manage.py runserver + $(PYTHON) manage.py run_wsbroadcast # Run to start the background task dispatcher for development. dispatcher: @@ -380,55 +340,63 @@ swagger: reports check: flake8 pep8 # pyflakes pylint awx-link: - cp -R /tmp/awx.egg-info /awx_devel/ || true - sed -i "s/placeholder/$(shell cat VERSION)/" /awx_devel/awx.egg-info/PKG-INFO - cp -f /tmp/awx.egg-link /venv/awx/lib/python$(PYTHON_VERSION)/site-packages/awx.egg-link + [ -d "/awx_devel/awx.egg-info" ] || python3 /awx_devel/setup.py egg_info_dev + cp -f /tmp/awx.egg-link /var/lib/awx/venv/awx/lib/python$(PYTHON_VERSION)/site-packages/awx.egg-link TEST_DIRS ?= awx/main/tests/unit awx/main/tests/functional awx/conf/tests awx/sso/tests # Run all API unit tests. test: - @if [ "$(VENV_BASE)" ]; then \ + if [ "$(VENV_BASE)" ]; then \ . $(VENV_BASE)/awx/bin/activate; \ fi; \ PYTHONDONTWRITEBYTECODE=1 py.test -p no:cacheprovider -n auto $(TEST_DIRS) - cd awxkit && $(VENV_BASE)/awx/bin/tox -re py2,py3 - awx-manage check_migrations --dry-run --check -n 'vNNN_missing_migration_file' - -prepare_collection_venv: - rm -rf $(COLLECTION_VENV) - mkdir $(COLLECTION_VENV) - $(VENV_BASE)/awx/bin/pip install --target=$(COLLECTION_VENV) git+https://github.com/ansible/tower-cli.git + cmp VERSION awxkit/VERSION || "VERSION and awxkit/VERSION *must* match" + cd awxkit && $(VENV_BASE)/awx/bin/tox -re py3 + awx-manage check_migrations --dry-run --check -n 'missing_migration_file' COLLECTION_TEST_DIRS ?= awx_collection/test/awx +COLLECTION_TEST_TARGET ?= COLLECTION_PACKAGE ?= awx COLLECTION_NAMESPACE ?= awx +COLLECTION_INSTALL = ~/.ansible/collections/ansible_collections/$(COLLECTION_NAMESPACE)/$(COLLECTION_PACKAGE) test_collection: - @if [ "$(VENV_BASE)" ]; then \ + rm -f $(shell ls -d $(VENV_BASE)/awx/lib/python* | head -n 1)/no-global-site-packages.txt + if [ "$(VENV_BASE)" ]; then \ . $(VENV_BASE)/awx/bin/activate; \ fi; \ - PYTHONPATH=$(COLLECTION_VENV):/awx_devel/awx_collection:$PYTHONPATH:/usr/lib/python3.6/site-packages py.test $(COLLECTION_TEST_DIRS) + py.test $(COLLECTION_TEST_DIRS) -v + # The python path needs to be modified so that the tests can find Ansible within the container + # First we will use anything expility set as PYTHONPATH + # Second we will load any libraries out of the virtualenv (if it's unspecified that should be ok because python should not load out of an empty directory) + # Finally we will add the system path so that the tests can find the ansible libraries flake8_collection: flake8 awx_collection/ # Different settings, in main exclude list -test_collection_all: prepare_collection_venv test_collection flake8_collection +test_collection_all: test_collection flake8_collection -test_collection_sanity: - rm -rf sanity - mkdir -p sanity/ansible_collections/awx - cp -Ra awx_collection sanity/ansible_collections/awx/awx # symlinks do not work - cd sanity/ansible_collections/awx/awx && git init && git add . # requires both this file structure and a git repo, so there you go - cd sanity/ansible_collections/awx/awx && ansible-test sanity +# WARNING: symlinking a collection is fundamentally unstable +# this is for rapid development iteration with playbooks, do not use with other test targets +symlink_collection: + rm -rf $(COLLECTION_INSTALL) + mkdir -p ~/.ansible/collections/ansible_collections/$(COLLECTION_NAMESPACE) # in case it does not exist + ln -s $(shell pwd)/awx_collection $(COLLECTION_INSTALL) build_collection: - ansible-playbook -i localhost, awx_collection/template_galaxy.yml -e collection_package=$(COLLECTION_PACKAGE) -e collection_namespace=$(COLLECTION_NAMESPACE) -e collection_version=$(VERSION) - ansible-galaxy collection build awx_collection --force --output-path=awx_collection + ansible-playbook -i localhost, awx_collection/tools/template_galaxy.yml -e collection_package=$(COLLECTION_PACKAGE) -e collection_namespace=$(COLLECTION_NAMESPACE) -e collection_version=$(VERSION) -e '{"awx_template_version":false}' + ansible-galaxy collection build awx_collection_build --force --output-path=awx_collection_build install_collection: build_collection - rm -rf ~/.ansible/collections/ansible_collections/awx/awx - ansible-galaxy collection install awx_collection/awx-awx-$(VERSION).tar.gz + rm -rf $(COLLECTION_INSTALL) + ansible-galaxy collection install awx_collection_build/$(COLLECTION_NAMESPACE)-$(COLLECTION_PACKAGE)-$(VERSION).tar.gz + +test_collection_sanity: install_collection + cd $(COLLECTION_INSTALL) && ansible-test sanity + +test_collection_integration: install_collection + cd $(COLLECTION_INSTALL) && ansible-test integration $(COLLECTION_TEST_TARGET) test_unit: @if [ "$(VENV_BASE)" ]; then \ @@ -492,114 +460,47 @@ else @echo No PO files endif -# generate UI .pot -pot: $(UI_DEPS_FLAG_FILE) - $(NPM_BIN) --prefix awx/ui run pot - -# generate django .pot .po -LANG = "en-us" -messages: - @if [ "$(VENV_BASE)" ]; then \ - . $(VENV_BASE)/awx/bin/activate; \ - fi; \ - $(PYTHON) manage.py makemessages -l $(LANG) --keep-pot - -# generate l10n .json .mo -languages: $(I18N_FLAG_FILE) - -$(I18N_FLAG_FILE): $(UI_RELEASE_DEPS_FLAG_FILE) - $(NPM_BIN) --prefix awx/ui run languages - $(PYTHON) tools/scripts/compilemessages.py - touch $(I18N_FLAG_FILE) - -# End l10n TASKS -# -------------------------------------- - -# UI RELEASE TASKS -# -------------------------------------- -ui-release: $(UI_RELEASE_FLAG_FILE) - -$(UI_RELEASE_FLAG_FILE): $(I18N_FLAG_FILE) $(UI_RELEASE_DEPS_FLAG_FILE) - $(NPM_BIN) --prefix awx/ui run build-release - touch $(UI_RELEASE_FLAG_FILE) - -$(UI_RELEASE_DEPS_FLAG_FILE): - PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=1 $(NPM_BIN) --unsafe-perm --prefix awx/ui ci --no-save awx/ui - touch $(UI_RELEASE_DEPS_FLAG_FILE) - -# END UI RELEASE TASKS -# -------------------------------------- # UI TASKS # -------------------------------------- -ui-deps: $(UI_DEPS_FLAG_FILE) -$(UI_DEPS_FLAG_FILE): - @if [ -f ${UI_RELEASE_DEPS_FLAG_FILE} ]; then \ - rm -rf awx/ui/node_modules; \ - rm -f ${UI_RELEASE_DEPS_FLAG_FILE}; \ - fi; \ - $(NPM_BIN) --unsafe-perm --prefix awx/ui ci --no-save awx/ui - touch $(UI_DEPS_FLAG_FILE) - -ui-docker-machine: $(UI_DEPS_FLAG_FILE) - $(NPM_BIN) --prefix awx/ui run ui-docker-machine -- $(MAKEFLAGS) - -# Native docker. Builds UI and raises BrowserSync & filesystem polling. -ui-docker: $(UI_DEPS_FLAG_FILE) - $(NPM_BIN) --prefix awx/ui run ui-docker -- $(MAKEFLAGS) - -# Builds UI with development UI without raising browser-sync or filesystem polling. -ui-devel: $(UI_DEPS_FLAG_FILE) - $(NPM_BIN) --prefix awx/ui run build-devel -- $(MAKEFLAGS) - -ui-test: $(UI_DEPS_FLAG_FILE) - $(NPM_BIN) --prefix awx/ui run test - -ui-lint: $(UI_DEPS_FLAG_FILE) - $(NPM_BIN) run --prefix awx/ui jshint - $(NPM_BIN) run --prefix awx/ui lint - -# A standard go-to target for API developers to use building the frontend -ui: clean-ui ui-devel - -ui-test-ci: $(UI_DEPS_FLAG_FILE) - $(NPM_BIN) --prefix awx/ui run test:ci - $(NPM_BIN) --prefix awx/ui run unit - -jshint: $(UI_DEPS_FLAG_FILE) - $(NPM_BIN) run --prefix awx/ui jshint - $(NPM_BIN) run --prefix awx/ui lint - -ui-zuul-lint-and-test: $(UI_DEPS_FLAG_FILE) - $(NPM_BIN) run --prefix awx/ui jshint - $(NPM_BIN) run --prefix awx/ui lint - $(NPM_BIN) --prefix awx/ui run test:ci - $(NPM_BIN) --prefix awx/ui run unit - -# END UI TASKS -# -------------------------------------- - -# UI NEXT TASKS -# -------------------------------------- - -ui-next-lint: - $(NPM_BIN) --prefix awx/ui_next install - $(NPM_BIN) run --prefix awx/ui_next lint - $(NPM_BIN) run --prefix awx/ui_next prettier-check - -ui-next-test: - $(NPM_BIN) --prefix awx/ui_next install - $(NPM_BIN) run --prefix awx/ui_next test - -ui-next-zuul-lint-and-test: +UI_BUILD_FLAG_FILE = awx/ui_next/.ui-built + +clean-ui: + rm -rf node_modules + rm -rf awx/ui_next/node_modules + rm -rf awx/ui_next/build + rm -rf awx/ui_next/src/locales/_build + rm -rf $(UI_BUILD_FLAG_FILE) + git checkout awx/ui_next/src/locales + +awx/ui_next/node_modules: + $(NPM_BIN) --prefix awx/ui_next --loglevel warn --ignore-scripts install + +$(UI_BUILD_FLAG_FILE): + $(NPM_BIN) --prefix awx/ui_next --loglevel warn run extract-strings + $(NPM_BIN) --prefix awx/ui_next --loglevel warn run compile-strings + $(NPM_BIN) --prefix awx/ui_next --loglevel warn run build + git checkout awx/ui_next/src/locales + mkdir -p awx/public/static/css + mkdir -p awx/public/static/js + mkdir -p awx/public/static/media + cp -r awx/ui_next/build/static/css/* awx/public/static/css + cp -r awx/ui_next/build/static/js/* awx/public/static/js + cp -r awx/ui_next/build/static/media/* awx/public/static/media + touch $@ + +ui-release: awx/ui_next/node_modules $(UI_BUILD_FLAG_FILE) + +ui-devel: awx/ui_next/node_modules + @$(MAKE) -B $(UI_BUILD_FLAG_FILE) + +ui-zuul-lint-and-test: $(NPM_BIN) --prefix awx/ui_next install $(NPM_BIN) run --prefix awx/ui_next lint $(NPM_BIN) run --prefix awx/ui_next prettier-check $(NPM_BIN) run --prefix awx/ui_next test -# END UI NEXT TASKS -# -------------------------------------- # Build a pip-installable package into dist/ with a timestamped version number. dev_build: @@ -646,9 +547,11 @@ awx/projects: docker-compose-isolated: awx/projects CURRENT_UID=$(shell id -u) TAG=$(COMPOSE_TAG) DEV_DOCKER_TAG_BASE=$(DEV_DOCKER_TAG_BASE) docker-compose -f tools/docker-compose.yml -f tools/docker-isolated-override.yml up +COMPOSE_UP_OPTS ?= + # Docker Compose Development environment docker-compose: docker-auth awx/projects - CURRENT_UID=$(shell id -u) OS="$(shell docker info | grep 'Operating System')" TAG=$(COMPOSE_TAG) DEV_DOCKER_TAG_BASE=$(DEV_DOCKER_TAG_BASE) docker-compose -f tools/docker-compose.yml up --no-recreate awx + CURRENT_UID=$(shell id -u) OS="$(shell docker info | grep 'Operating System')" TAG=$(COMPOSE_TAG) DEV_DOCKER_TAG_BASE=$(DEV_DOCKER_TAG_BASE) docker-compose -f tools/docker-compose.yml $(COMPOSE_UP_OPTS) up --no-recreate awx docker-compose-cluster: docker-auth awx/projects CURRENT_UID=$(shell id -u) TAG=$(COMPOSE_TAG) DEV_DOCKER_TAG_BASE=$(DEV_DOCKER_TAG_BASE) docker-compose -f tools/docker-compose-cluster.yml up @@ -664,7 +567,7 @@ docker-compose-runtest: awx/projects cd tools && CURRENT_UID=$(shell id -u) TAG=$(COMPOSE_TAG) DEV_DOCKER_TAG_BASE=$(DEV_DOCKER_TAG_BASE) docker-compose run --rm --service-ports awx /start_tests.sh docker-compose-build-swagger: awx/projects - cd tools && CURRENT_UID=$(shell id -u) TAG=$(COMPOSE_TAG) DEV_DOCKER_TAG_BASE=$(DEV_DOCKER_TAG_BASE) docker-compose run --rm --service-ports awx /start_tests.sh swagger + cd tools && CURRENT_UID=$(shell id -u) TAG=$(COMPOSE_TAG) DEV_DOCKER_TAG_BASE=$(DEV_DOCKER_TAG_BASE) docker-compose run --rm --service-ports --no-deps awx /start_tests.sh swagger detect-schema-change: genschema curl https://s3.amazonaws.com/awx-public-ci-files/schema.json -o reference-schema.json @@ -672,29 +575,28 @@ detect-schema-change: genschema diff -u -b reference-schema.json schema.json docker-compose-clean: awx/projects - cd tools && CURRENT_UID=$(shell id -u) TAG=$(COMPOSE_TAG) DEV_DOCKER_TAG_BASE=$(DEV_DOCKER_TAG_BASE) docker-compose run --rm -w /awx_devel --service-ports awx make clean cd tools && TAG=$(COMPOSE_TAG) DEV_DOCKER_TAG_BASE=$(DEV_DOCKER_TAG_BASE) docker-compose rm -sf -docker-compose-build: awx-devel-build - # Base development image build -awx-devel-build: +docker-compose-build: + ansible localhost -m template -a "src=installer/roles/image_build/templates/Dockerfile.j2 dest=tools/docker-compose/Dockerfile" -e build_dev=True docker build -t ansible/awx_devel -f tools/docker-compose/Dockerfile \ --cache-from=$(DEV_DOCKER_TAG_BASE)/awx_devel:$(COMPOSE_TAG) . docker tag ansible/awx_devel $(DEV_DOCKER_TAG_BASE)/awx_devel:$(COMPOSE_TAG) #docker push $(DEV_DOCKER_TAG_BASE)/awx_devel:$(COMPOSE_TAG) # For use when developing on "isolated" AWX deployments -docker-compose-isolated-build: awx-devel-build +docker-compose-isolated-build: docker-compose-build docker build -t ansible/awx_isolated -f tools/docker-isolated/Dockerfile . docker tag ansible/awx_isolated $(DEV_DOCKER_TAG_BASE)/awx_isolated:$(COMPOSE_TAG) #docker push $(DEV_DOCKER_TAG_BASE)/awx_isolated:$(COMPOSE_TAG) -MACHINE?=default docker-clean: - eval $$(docker-machine env $(MACHINE)) $(foreach container_id,$(shell docker ps -f name=tools_awx -aq),docker stop $(container_id); docker rm -f $(container_id);) - -docker images | grep "awx_devel" | awk '{print $$1 ":" $$2}' | xargs docker rmi + docker images | grep "awx_devel" | awk '{print $$1 ":" $$2}' | xargs docker rmi + +docker-clean-volumes: docker-compose-clean + docker volume rm tools_awx_db docker-refresh: docker-clean docker-compose @@ -708,9 +610,6 @@ docker-compose-cluster-elk: docker-auth awx/projects prometheus: docker run -u0 --net=tools_default --link=`docker ps | egrep -o "tools_awx(_run)?_([^ ]+)?"`:awxweb --volume `pwd`/tools/prometheus:/prometheus --name prometheus -d -p 0.0.0.0:9090:9090 prom/prometheus --web.enable-lifecycle --config.file=/prometheus/prometheus.yml -minishift-dev: - ansible-playbook -i localhost, -e devtree_directory=$(CURDIR) tools/clusterdevel/start_minishift_dev.yml - clean-elk: docker stop tools_kibana_1 docker stop tools_logstash_1 @@ -720,7 +619,10 @@ clean-elk: docker rm tools_kibana_1 psql-container: - docker run -it --net tools_default --rm postgres:10 sh -c 'exec psql -h "postgres" -p "5432" -U postgres' + docker run -it --net tools_default --rm postgres:12 sh -c 'exec psql -h "postgres" -p "5432" -U postgres' VERSION: @echo "awx: $(VERSION)" + +Dockerfile: installer/roles/image_build/templates/Dockerfile.j2 + ansible localhost -m template -a "src=installer/roles/image_build/templates/Dockerfile.j2 dest=Dockerfile" diff --git a/README.md b/README.md index 43a94ace6189..359d3c19c76d 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,10 @@ AWX -This a internal fork of the official AWX Project on GitHub. This AWX has MLU specific design changes and has a more user readable email notification. -The Web UI is changed too - for better integration in the daily admin workflow. +This a internal fork of the official AWX Project on GitHub. This AWX has a more user readable email notification. -The official fork can be found on https://github.com/MBcom/awx . -some impressions --------- -![screenshot1](https://user-images.githubusercontent.com/27956078/41818184-a59e49d0-77a9-11e8-94e8-ca0bb9a9d3af.png) -- landing page -![screenshot3](https://user-images.githubusercontent.com/27956078/41818197-f1d9d26a-77a9-11e8-9662-d773b674a057.png) -- opened menu bar -![screenshot2](https://user-images.githubusercontent.com/27956078/41818202-084b53a2-77aa-11e8-9d46-956951f0c9f7.png) -- login screen +The official fork can be found on https://github.com/MBcom/awx . The following information are related to the official AWX project: ------- @@ -32,20 +23,20 @@ Contributing ------------ - Refer to the [Contributing guide](./CONTRIBUTING.md) to get started developing, testing, and building AWX. -- All code submissions are done through pull requests against the `devel` branch. -- All contributors must use git commit --signoff for any commit to be merged, and agree that usage of --signoff constitutes agreement with the terms of [DCO 1.1](./DCO_1_1.md) -- Take care to make sure no merge commits are in the submission, and use `git rebase` vs `git merge` for this reason. -- If submitting a large code change, it's a good idea to join the `#ansible-awx` channel on irc.freenode.net, and talk about what you would like to do or add first. This not only helps everyone know what's going on, it also helps save time and effort, if the community decides some changes are needed. +- All code submissions are made through pull requests against the `devel` branch. +- All contributors must use git commit --signoff for any commit to be merged and agree that usage of --signoff constitutes agreement with the terms of [DCO 1.1](./DCO_1_1.md) +- Take care to make sure no merge commits are in the submission, and use `git rebase` vs. `git merge` for this reason. +- If submitting a large code change, it's a good idea to join the `#ansible-awx` channel on irc.freenode.net and talk about what you would like to do or add first. This not only helps everyone know what's going on, but it also helps save time and effort if the community decides some changes are needed. Reporting Issues ---------------- -If you're experiencing a problem that you feel is a bug in AWX, or have ideas for how to improve AWX, we encourage you to open an issue, and share your feedback. But before opening a new issue, we ask that you please take a look at our [Issues guide](./ISSUES.md). +If you're experiencing a problem that you feel is a bug in AWX or have ideas for improving AWX, we encourage you to open an issue and share your feedback. But before opening a new issue, we ask that you please take a look at our [Issues guide](./ISSUES.md). Code of Conduct --------------- -We ask all of our community members and contributors to adhere to the [Ansible code of conduct](http://docs.ansible.com/ansible/latest/community/code_of_conduct.html). If you have questions, or need assistance, please reach out to our community team at [codeofconduct@ansible.com](mailto:codeofconduct@ansible.com) +We ask all of our community members and contributors to adhere to the [Ansible code of conduct](http://docs.ansible.com/ansible/latest/community/code_of_conduct.html). If you have questions or need assistance, please reach out to our community team at [codeofconduct@ansible.com](mailto:codeofconduct@ansible.com) Get Involved ------------ @@ -59,4 +50,3 @@ License ------- [Apache v2](./LICENSE.md) - diff --git a/VERSION b/VERSION index 44931da2660c..3e17df0287b8 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -9.1.1 +17.0.1 diff --git a/awx/__init__.py b/awx/__init__.py index 0e2547752d60..5afb83d11513 100644 --- a/awx/__init__.py +++ b/awx/__init__.py @@ -30,6 +30,7 @@ HAS_DJANGO = False else: from django.db.backends.base import schema + from django.db.models import indexes from django.db.backends.utils import names_digest @@ -50,6 +51,7 @@ def names_digest(*args, length): return h.hexdigest()[:length] schema.names_digest = names_digest + indexes.names_digest = names_digest def find_commands(management_dir): diff --git a/awx/api/conf.py b/awx/api/conf.py index 493eed6981b0..f7da95200475 100644 --- a/awx/api/conf.py +++ b/awx/api/conf.py @@ -16,6 +16,7 @@ help_text=_('Number of seconds that a user is inactive before they will need to login again.'), category=_('Authentication'), category_slug='authentication', + unit=_('seconds'), ) register( 'SESSIONS_PER_USER', @@ -49,6 +50,7 @@ 'in the number of seconds.'), category=_('Authentication'), category_slug='authentication', + unit=_('seconds'), ) register( 'ALLOW_OAUTH2_FOR_EXTERNAL_USERS', diff --git a/awx/api/filters.py b/awx/api/filters.py index ea9d0115629e..6d51441c2845 100644 --- a/awx/api/filters.py +++ b/awx/api/filters.py @@ -9,7 +9,7 @@ # Django from django.core.exceptions import FieldError, ValidationError from django.db import models -from django.db.models import Q +from django.db.models import Q, CharField, IntegerField, BooleanField from django.db.models.fields import FieldDoesNotExist from django.db.models.fields.related import ForeignObjectRel, ManyToManyField, ForeignKey from django.contrib.contenttypes.models import ContentType @@ -63,19 +63,19 @@ def filter_queryset(self, request, queryset, view): raise ParseError(*e.args) -def get_field_from_path(model, path): +def get_fields_from_path(model, path): ''' Given a Django ORM lookup path (possibly over multiple models) - Returns the last field in the line, and also the revised lookup path + Returns the fields in the line, and also the revised lookup path ex., given model=Organization path='project__timeout' - returns tuple of field at the end of the line as well as a corrected - path, for special cases we do substitutions - (, 'project__timeout') + returns tuple of fields traversed as well and a corrected path, + for special cases we do substitutions + ([], 'project__timeout') ''' # Store of all the fields used to detect repeats - field_set = set([]) + field_list = [] new_parts = [] for name in path.split('__'): if model is None: @@ -111,13 +111,24 @@ def get_field_from_path(model, path): raise PermissionDenied(_('Filtering on %s is not allowed.' % name)) elif getattr(field, '__prevent_search__', False): raise PermissionDenied(_('Filtering on %s is not allowed.' % name)) - if field in field_set: + if field in field_list: # Field traversed twice, could create infinite JOINs, DoSing Tower raise ParseError(_('Loops not allowed in filters, detected on field {}.').format(field.name)) - field_set.add(field) + field_list.append(field) model = getattr(field, 'related_model', None) - return field, '__'.join(new_parts) + return field_list, '__'.join(new_parts) + + +def get_field_from_path(model, path): + ''' + Given a Django ORM lookup path (possibly over multiple models) + Returns the last field in the line, and the revised lookup path + ex. + (, 'project__timeout') + ''' + field_list, new_path = get_fields_from_path(model, path) + return (field_list[-1], new_path) class FieldLookupBackend(BaseFilterBackend): @@ -133,7 +144,11 @@ class FieldLookupBackend(BaseFilterBackend): 'regex', 'iregex', 'gt', 'gte', 'lt', 'lte', 'in', 'isnull', 'search') - def get_field_from_lookup(self, model, lookup): + # A list of fields that we know can be filtered on without the possiblity + # of introducing duplicates + NO_DUPLICATES_ALLOW_LIST = (CharField, IntegerField, BooleanField) + + def get_fields_from_lookup(self, model, lookup): if '__' in lookup and lookup.rsplit('__', 1)[-1] in self.SUPPORTED_LOOKUPS: path, suffix = lookup.rsplit('__', 1) @@ -147,11 +162,16 @@ def get_field_from_lookup(self, model, lookup): # FIXME: Could build up a list of models used across relationships, use # those lookups combined with request.user.get_queryset(Model) to make # sure user cannot query using objects he could not view. - field, new_path = get_field_from_path(model, path) + field_list, new_path = get_fields_from_path(model, path) new_lookup = new_path new_lookup = '__'.join([new_path, suffix]) - return field, new_lookup + return field_list, new_lookup + + def get_field_from_lookup(self, model, lookup): + '''Method to match return type of single field, if needed.''' + field_list, new_lookup = self.get_fields_from_lookup(model, lookup) + return (field_list[-1], new_lookup) def to_python_related(self, value): value = force_text(value) @@ -182,7 +202,10 @@ def value_to_python(self, model, lookup, value): except UnicodeEncodeError: raise ValueError("%r is not an allowed field name. Must be ascii encodable." % lookup) - field, new_lookup = self.get_field_from_lookup(model, lookup) + field_list, new_lookup = self.get_fields_from_lookup(model, lookup) + field = field_list[-1] + + needs_distinct = (not all(isinstance(f, self.NO_DUPLICATES_ALLOW_LIST) for f in field_list)) # Type names are stored without underscores internally, but are presented and # and serialized over the API containing underscores so we remove `_` @@ -211,10 +234,10 @@ def value_to_python(self, model, lookup, value): for rm_field in related_model._meta.fields: if rm_field.name in ('username', 'first_name', 'last_name', 'email', 'name', 'description', 'playbook'): new_lookups.append('{}__{}__icontains'.format(new_lookup[:-8], rm_field.name)) - return value, new_lookups + return value, new_lookups, needs_distinct else: value = self.value_to_python_for_field(field, value) - return value, new_lookup + return value, new_lookup, needs_distinct def filter_queryset(self, request, queryset, view): try: @@ -225,6 +248,7 @@ def filter_queryset(self, request, queryset, view): chain_filters = [] role_filters = [] search_filters = {} + needs_distinct = False # Can only have two values: 'AND', 'OR' # If 'AND' is used, an iterm must satisfy all condition to show up in the results. # If 'OR' is used, an item just need to satisfy one condition to appear in results. @@ -233,6 +257,11 @@ def filter_queryset(self, request, queryset, view): if key in self.RESERVED_NAMES: continue + # HACK: make `created` available via API for the Django User ORM model + # so it keep compatiblity with other objects which exposes the `created` attr. + if queryset.model._meta.object_name == 'User' and key.startswith('created'): + key = key.replace('created', 'date_joined') + # HACK: Make job event filtering by host name mostly work even # when not capturing job event hosts M2M. if queryset.model._meta.object_name == 'JobEvent' and key.startswith('hosts__name'): @@ -256,9 +285,12 @@ def filter_queryset(self, request, queryset, view): search_filter_relation = 'AND' values = reduce(lambda list1, list2: list1 + list2, [i.split(',') for i in values]) for value in values: - search_value, new_keys = self.value_to_python(queryset.model, key, force_text(value)) + search_value, new_keys, _ = self.value_to_python(queryset.model, key, force_text(value)) assert isinstance(new_keys, list) search_filters[search_value] = new_keys + # by definition, search *only* joins across relations, + # so it _always_ needs a .distinct() + needs_distinct = True continue # Custom chain__ and or__ filters, mutually exclusive (both can @@ -282,7 +314,9 @@ def filter_queryset(self, request, queryset, view): for value in values: if q_int: value = int(value) - value, new_key = self.value_to_python(queryset.model, key, value) + value, new_key, distinct = self.value_to_python(queryset.model, key, value) + if distinct: + needs_distinct = True if q_chain: chain_filters.append((q_not, new_key, value)) elif q_or: @@ -332,7 +366,9 @@ def filter_queryset(self, request, queryset, view): else: q = Q(**{k:v}) queryset = queryset.filter(q) - queryset = queryset.filter(*args).distinct() + queryset = queryset.filter(*args) + if needs_distinct: + queryset = queryset.distinct() return queryset except (FieldError, FieldDoesNotExist, ValueError, TypeError) as e: raise ParseError(e.args[0]) diff --git a/awx/api/generics.py b/awx/api/generics.py index 37ca8bef42ef..ac9ab0390776 100644 --- a/awx/api/generics.py +++ b/awx/api/generics.py @@ -5,10 +5,12 @@ import inspect import logging import time +import uuid import urllib.parse # Django from django.conf import settings +from django.core.cache import cache from django.db import connection from django.db.models.fields import FieldDoesNotExist from django.db.models.fields.related import OneToOneRel @@ -43,9 +45,11 @@ get_search_fields, getattrd, get_object_or_400, - decrypt_field + decrypt_field, + get_awx_version, ) from awx.main.utils.db import get_all_field_names +from awx.main.views import ApiErrorView from awx.api.serializers import ResourceAccessListElementSerializer, CopySerializer, UserSerializer from awx.api.versioning import URLPathVersioning from awx.api.metadata import SublistAttachDetatchMetadata, Metadata @@ -154,11 +158,11 @@ def initialize_request(self, request, *args, **kwargs): self.queries_before = len(connection.queries) # If there are any custom headers in REMOTE_HOST_HEADERS, make sure - # they respect the proxy whitelist + # they respect the allowed proxy list if all([ - settings.PROXY_IP_WHITELIST, - request.environ.get('REMOTE_ADDR') not in settings.PROXY_IP_WHITELIST, - request.environ.get('REMOTE_HOST') not in settings.PROXY_IP_WHITELIST + settings.PROXY_IP_ALLOWED_LIST, + request.environ.get('REMOTE_ADDR') not in settings.PROXY_IP_ALLOWED_LIST, + request.environ.get('REMOTE_HOST') not in settings.PROXY_IP_ALLOWED_LIST ]): for custom_header in settings.REMOTE_HOST_HEADERS: if custom_header.startswith('HTTP_'): @@ -183,6 +187,30 @@ def finalize_response(self, request, response, *args, **kwargs): ''' Log warning for 400 requests. Add header with elapsed time. ''' + from awx.main.utils import get_licenser + from awx.main.utils.licensing import OpenLicense + # + # If the URL was rewritten, and we get a 404, we should entirely + # replace the view in the request context with an ApiErrorView() + # Without this change, there will be subtle differences in the BrowseableAPIRenderer + # + # These differences could provide contextual clues which would allow + # anonymous users to determine if usernames were valid or not + # (e.g., if an anonymous user visited `/api/v2/users/valid/`, and got a 404, + # but also saw that the page heading said "User Detail", they might notice + # that's a difference in behavior from a request to `/api/v2/users/not-valid/`, which + # would show a page header of "Not Found"). Changing the view here + # guarantees that the rendered response will look exactly like the response + # when you visit a URL that has no matching URL paths in `awx.api.urls`. + # + if response.status_code == 404 and 'awx.named_url_rewritten' in request.environ: + self.headers.pop('Allow', None) + response = super(APIView, self).finalize_response(request, response, *args, **kwargs) + view = ApiErrorView() + setattr(view, 'request', request) + response.renderer_context['view'] = view + return response + if response.status_code >= 400: status_msg = "status %s received by user %s attempting to access %s from %s" % \ (response.status_code, request.user, request.path, request.META.get('REMOTE_ADDR', None)) @@ -192,9 +220,12 @@ def finalize_response(self, request, response, *args, **kwargs): response.data['detail'] += ' To establish a login session, visit /api/login/.' logger.info(status_msg) else: - logger.warn(status_msg) + logger.warning(status_msg) response = super(APIView, self).finalize_response(request, response, *args, **kwargs) time_started = getattr(self, 'time_started', None) + response['X-API-Product-Version'] = get_awx_version() + response['X-API-Product-Name'] = 'AWX' if isinstance(get_licenser(), OpenLicense) else 'Red Hat Ansible Tower' + response['X-API-Node'] = settings.CLUSTER_HOST_ID if time_started: time_elapsed = time.time() - self.time_started @@ -548,6 +579,15 @@ def get_description_context(self): }) return d + def get_queryset(self): + if hasattr(self, 'parent_key'): + # Prefer this filtering because ForeignKey allows us more assumptions + parent = self.get_parent_object() + self.check_parent_access(parent) + qs = self.request.user.get_queryset(self.model) + return qs.filter(**{self.parent_key: parent}) + return super(SubListCreateAPIView, self).get_queryset() + def create(self, request, *args, **kwargs): # If the object ID was not specified, it probably doesn't exist in the # DB yet. We want to see if we can create it. The URL may choose to @@ -821,7 +861,7 @@ def _get_copy_return_serializer(self, *args, **kwargs): @staticmethod def _decrypt_model_field_if_needed(obj, field_name, field_val): - if field_name in getattr(type(obj), 'REENCRYPTION_BLACKLIST_AT_COPY', []): + if field_name in getattr(type(obj), 'REENCRYPTION_BLOCKLIST_AT_COPY', []): return field_val if isinstance(obj, Credential) and field_name == 'inputs': for secret in obj.credential_type.secret_fields: @@ -867,7 +907,7 @@ def copy_model_obj(old_parent, new_parent, model, obj, creater, copy_name='', cr field_val = getattr(obj, field.name) except AttributeError: continue - # Adjust copy blacklist fields here. + # Adjust copy blocked fields here. if field.name in fields_to_discard or field.name in [ 'id', 'pk', 'polymorphic_ctype', 'unifiedjobtemplate_ptr', 'created_by', 'modified_by' ] or field.name.endswith('_role'): @@ -964,6 +1004,11 @@ def post(self, request, *args, **kwargs): if hasattr(new_obj, 'admin_role') and request.user not in new_obj.admin_role.members.all(): new_obj.admin_role.members.add(request.user) if sub_objs: + # store the copied object dict into cache, because it's + # often too large for postgres' notification bus + # (which has a default maximum message size of 8k) + key = 'deep-copy-{}'.format(str(uuid.uuid4())) + cache.set(key, sub_objs, timeout=3600) permission_check_func = None if hasattr(type(self), 'deep_copy_permission_check_func'): permission_check_func = ( @@ -971,7 +1016,7 @@ def post(self, request, *args, **kwargs): ) trigger_delayed_deep_copy( self.model.__module__, self.model.__name__, - obj.pk, new_obj.pk, request.user.pk, sub_objs, + obj.pk, new_obj.pk, request.user.pk, key, permission_check_func=permission_check_func ) serializer = self._get_copy_return_serializer(new_obj) diff --git a/awx/api/metadata.py b/awx/api/metadata.py index b9d71e12c5fd..0b60f9a1ef06 100644 --- a/awx/api/metadata.py +++ b/awx/api/metadata.py @@ -2,6 +2,7 @@ # All Rights Reserved. from collections import OrderedDict +from uuid import UUID # Django from django.core.exceptions import PermissionDenied @@ -20,8 +21,9 @@ from rest_framework.request import clone_request # AWX +from awx.api.fields import ChoiceNullField from awx.main.fields import JSONField, ImplicitRoleField -from awx.main.models import InventorySource, NotificationTemplate +from awx.main.models import NotificationTemplate from awx.main.scheduler.kubernetes import PodManager @@ -37,7 +39,7 @@ def get_field_info(self, field): 'min_length', 'max_length', 'min_value', 'max_value', 'category', 'category_slug', - 'defined_in_file' + 'defined_in_file', 'unit', ] for attr in text_attrs: @@ -59,7 +61,8 @@ def get_field_info(self, field): 'type': _('Data type for this {}.'), 'url': _('URL for this {}.'), 'related': _('Data structure with URLs of related resources.'), - 'summary_fields': _('Data structure with name/description for related resources.'), + 'summary_fields': _('Data structure with name/description for related resources. ' + 'The output for some objects may be limited for performance reasons.'), 'created': _('Timestamp when this {} was created.'), 'modified': _('Timestamp when this {} was last modified.'), } @@ -84,6 +87,8 @@ def get_field_info(self, field): # FIXME: Still isn't showing all default values? try: default = field.get_default() + if type(default) is UUID: + default = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' if field.field_name == 'TOWER_URL_BASE' and default == 'https://towerhost': default = '{}://{}'.format(self.request.scheme, self.request.get_host()) field_info['default'] = default @@ -96,25 +101,20 @@ def get_field_info(self, field): field_info['children'] = self.get_serializer_info(field) if not isinstance(field, (RelatedField, ManyRelatedField)) and hasattr(field, 'choices'): - field_info['choices'] = [(choice_value, choice_name) for choice_value, choice_name in field.choices.items()] + choices = [ + (choice_value, choice_name) for choice_value, choice_name in field.choices.items() + ] + if not any(choice in ('', None) for choice, _ in choices): + if field.allow_blank: + choices = [("", "---------")] + choices + if field.allow_null and not isinstance(field, ChoiceNullField): + choices = [(None, "---------")] + choices + field_info['choices'] = choices # Indicate if a field is write-only. if getattr(field, 'write_only', False): field_info['write_only'] = True - # Special handling of inventory source_region choices that vary based on - # selected inventory source. - if field.field_name == 'source_regions': - for cp in ('azure_rm', 'ec2', 'gce'): - get_regions = getattr(InventorySource, 'get_%s_region_choices' % cp) - field_info['%s_region_choices' % cp] = get_regions() - - # Special handling of group_by choices for EC2. - if field.field_name == 'group_by': - for cp in ('ec2',): - get_group_by_choices = getattr(InventorySource, 'get_%s_group_by_choices' % cp) - field_info['%s_group_by_choices' % cp] = get_group_by_choices() - # Special handling of notification configuration where the required properties # are conditional on the type selected. if field.field_name == 'notification_configuration': diff --git a/awx/api/renderers.py b/awx/api/renderers.py index bd4136a76a60..92d59e2c7b7e 100644 --- a/awx/api/renderers.py +++ b/awx/api/renderers.py @@ -7,6 +7,24 @@ # Django REST Framework from rest_framework import renderers from rest_framework.request import override_method +from rest_framework.utils import encoders + + +class SurrogateEncoder(encoders.JSONEncoder): + + def encode(self, obj): + ret = super(SurrogateEncoder, self).encode(obj) + try: + ret.encode() + except UnicodeEncodeError as e: + if 'surrogates not allowed' in e.reason: + ret = ret.encode('utf-8', 'replace').decode() + return ret + + +class DefaultJSONRenderer(renderers.JSONRenderer): + + encoder_class = SurrogateEncoder class BrowsableAPIRenderer(renderers.BrowsableAPIRenderer): diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 2598fd0e1e28..ecce831a1988 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -72,6 +72,7 @@ prefetch_page_capabilities, get_external_account, truncate_stdout, ) from awx.main.utils.filters import SmartFilter +from awx.main.utils.named_url_graph import reset_counters from awx.main.redact import UriCleaner, REPLACE_STR from awx.main.validators import vars_validate_or_raise @@ -98,26 +99,19 @@ 'total_hosts', 'hosts_with_active_failures', 'total_groups', - 'groups_with_active_failures', 'has_inventory_sources', 'total_inventory_sources', 'inventory_sources_with_failures', 'organization_id', 'kind', 'insights_credential_id',), - 'host': DEFAULT_SUMMARY_FIELDS + ('has_active_failures', - 'has_inventory_sources'), - 'group': DEFAULT_SUMMARY_FIELDS + ('has_active_failures', - 'total_hosts', - 'hosts_with_active_failures', - 'total_groups', - 'groups_with_active_failures', - 'has_inventory_sources'), + 'host': DEFAULT_SUMMARY_FIELDS, + 'group': DEFAULT_SUMMARY_FIELDS, 'project': DEFAULT_SUMMARY_FIELDS + ('status', 'scm_type'), 'source_project': DEFAULT_SUMMARY_FIELDS + ('status', 'scm_type'), 'project_update': DEFAULT_SUMMARY_FIELDS + ('status', 'failed',), 'credential': DEFAULT_SUMMARY_FIELDS + ('kind', 'cloud', 'kubernetes', 'credential_type_id'), - 'job': DEFAULT_SUMMARY_FIELDS + ('status', 'failed', 'elapsed', 'type'), + 'job': DEFAULT_SUMMARY_FIELDS + ('status', 'failed', 'elapsed', 'type', 'canceled_on'), 'job_template': DEFAULT_SUMMARY_FIELDS, 'workflow_job_template': DEFAULT_SUMMARY_FIELDS, 'workflow_job': DEFAULT_SUMMARY_FIELDS, @@ -125,21 +119,21 @@ 'workflow_approval': DEFAULT_SUMMARY_FIELDS + ('timeout',), 'schedule': DEFAULT_SUMMARY_FIELDS + ('next_run',), 'unified_job_template': DEFAULT_SUMMARY_FIELDS + ('unified_job_type',), - 'last_job': DEFAULT_SUMMARY_FIELDS + ('finished', 'status', 'failed', 'license_error'), + 'last_job': DEFAULT_SUMMARY_FIELDS + ('finished', 'status', 'failed', 'license_error', 'canceled_on'), 'last_job_host_summary': DEFAULT_SUMMARY_FIELDS + ('failed',), 'last_update': DEFAULT_SUMMARY_FIELDS + ('status', 'failed', 'license_error'), 'current_update': DEFAULT_SUMMARY_FIELDS + ('status', 'failed', 'license_error'), 'current_job': DEFAULT_SUMMARY_FIELDS + ('status', 'failed', 'license_error'), 'inventory_source': ('source', 'last_updated', 'status'), 'custom_inventory_script': DEFAULT_SUMMARY_FIELDS, - 'source_script': ('name', 'description'), + 'source_script': DEFAULT_SUMMARY_FIELDS, 'role': ('id', 'role_field'), 'notification_template': DEFAULT_SUMMARY_FIELDS, 'instance_group': ('id', 'name', 'controller_id', 'is_containerized'), 'insights_credential': DEFAULT_SUMMARY_FIELDS, 'source_credential': DEFAULT_SUMMARY_FIELDS + ('kind', 'cloud', 'credential_type_id'), 'target_credential': DEFAULT_SUMMARY_FIELDS + ('kind', 'cloud', 'credential_type_id'), - 'webhook_credential': DEFAULT_SUMMARY_FIELDS, + 'webhook_credential': DEFAULT_SUMMARY_FIELDS + ('kind', 'cloud', 'credential_type_id'), 'approved_or_denied_by': ('id', 'username', 'first_name', 'last_name'), 'credential_type': DEFAULT_SUMMARY_FIELDS, } @@ -354,6 +348,7 @@ def _get_related(self, obj): def _generate_named_url(self, url_path, obj, node): url_units = url_path.split('/') + reset_counters() named_url = node.generate_named_url(obj) url_units[4] = named_url return '/'.join(url_units) @@ -458,7 +453,7 @@ def _obj_capability_dict(self, obj): if 'capability_map' not in self.context: if hasattr(self, 'polymorphic_base'): model = self.polymorphic_base.Meta.model - prefetch_list = self.polymorphic_base._capabilities_prefetch + prefetch_list = self.polymorphic_base.capabilities_prefetch else: model = self.Meta.model prefetch_list = self.capabilities_prefetch @@ -645,12 +640,9 @@ class EmptySerializer(serializers.Serializer): class UnifiedJobTemplateSerializer(BaseSerializer): - # As a base serializer, the capabilities prefetch is not used directly - _capabilities_prefetch = [ - 'admin', 'execute', - {'copy': ['jobtemplate.project.use', 'jobtemplate.inventory.use', - 'workflowjobtemplate.organization.workflow_admin']} - ] + # As a base serializer, the capabilities prefetch is not used directly, + # instead they are derived from the Workflow Job Template Serializer and the Job Template Serializer, respectively. + capabilities_prefetch = [] class Meta: model = UnifiedJobTemplate @@ -700,7 +692,7 @@ def to_representation(self, obj): serializer.polymorphic_base = self # capabilities prefetch is only valid for these models if isinstance(obj, (JobTemplate, WorkflowJobTemplate)): - serializer.capabilities_prefetch = self._capabilities_prefetch + serializer.capabilities_prefetch = serializer_class.capabilities_prefetch else: serializer.capabilities_prefetch = None return serializer.to_representation(obj) @@ -719,7 +711,7 @@ class UnifiedJobSerializer(BaseSerializer): class Meta: model = UnifiedJob fields = ('*', 'unified_job_template', 'launch_type', 'status', - 'failed', 'started', 'finished', 'elapsed', 'job_args', + 'failed', 'started', 'finished', 'canceled_on', 'elapsed', 'job_args', 'job_cwd', 'job_env', 'job_explanation', 'execution_node', 'controller_node', 'result_traceback', 'event_processing_finished') @@ -811,7 +803,9 @@ def to_representation(self, obj): td = now() - obj.started ret['elapsed'] = (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10 ** 6) / (10 ** 6 * 1.0) ret['elapsed'] = float(ret['elapsed']) - + # Because this string is saved in the db in the source language, + # it must be marked for translation after it is pulled from the db, not when set + ret['job_explanation'] = _(obj.job_explanation) return ret @@ -891,6 +885,9 @@ class Meta: fields = ('*', '-name', '-description', '-modified', 'username', 'first_name', 'last_name', 'email', 'is_superuser', 'is_system_auditor', 'password', 'ldap_dn', 'last_login', 'external_account') + extra_kwargs = { + 'last_login': {'read_only': True} + } def to_representation(self, obj): ret = super(UserSerializer, self).to_representation(obj) @@ -1253,6 +1250,7 @@ def get_related(self, obj): res.update(dict( projects = self.reverse('api:organization_projects_list', kwargs={'pk': obj.pk}), inventories = self.reverse('api:organization_inventories_list', kwargs={'pk': obj.pk}), + job_templates = self.reverse('api:organization_job_templates_list', kwargs={'pk': obj.pk}), workflow_job_templates = self.reverse('api:organization_workflow_job_templates_list', kwargs={'pk': obj.pk}), users = self.reverse('api:organization_users_list', kwargs={'pk': obj.pk}), admins = self.reverse('api:organization_admins_list', kwargs={'pk': obj.pk}), @@ -1268,6 +1266,7 @@ def get_related(self, obj): object_roles = self.reverse('api:organization_object_roles_list', kwargs={'pk': obj.pk}), access_list = self.reverse('api:organization_access_list', kwargs={'pk': obj.pk}), instance_groups = self.reverse('api:organization_instance_groups_list', kwargs={'pk': obj.pk}), + galaxy_credentials = self.reverse('api:organization_galaxy_credentials_list', kwargs={'pk': obj.pk}), )) return res @@ -1281,6 +1280,14 @@ def get_summary_fields(self, obj): 'job_templates': 0, 'admins': 0, 'projects': 0} else: summary_dict['related_field_counts'] = counts_dict[obj.id] + + # Organization participation roles (admin, member) can't be assigned + # to a team. This provides a hint to the ui so it can know to not + # display these roles for team role selection. + for key in ('admin_role', 'member_role',): + if key in summary_dict.get('object_roles', {}): + summary_dict['object_roles'][key]['user_only'] = True + return summary_dict def validate(self, attrs): @@ -1323,10 +1330,14 @@ def validate(self, attrs): scm_type = attrs.get('scm_type', u'') or u'' if self.instance and not scm_type: valid_local_paths.append(self.instance.local_path) + if self.instance and scm_type and "local_path" in attrs and self.instance.local_path != attrs['local_path']: + errors['local_path'] = _(f'Cannot change local_path for {scm_type}-based projects') if scm_type: attrs.pop('local_path', None) if 'local_path' in attrs and attrs['local_path'] not in valid_local_paths: errors['local_path'] = _('This path is already being used by another manual project.') + if attrs.get('scm_branch') and scm_type == 'archive': + errors['scm_branch'] = _('SCM branch cannot be used with archive projects.') if attrs.get('scm_refspec') and scm_type != 'git': errors['scm_refspec'] = _('SCM refspec can only be used with git projects.') @@ -1394,12 +1405,6 @@ def validate(self, attrs): def get_field_from_model_or_attrs(fd): return attrs.get(fd, self.instance and getattr(self.instance, fd) or None) - organization = None - if 'organization' in attrs: - organization = attrs['organization'] - elif self.instance: - organization = self.instance.organization - if 'allow_override' in attrs and self.instance: # case where user is turning off this project setting if self.instance.allow_override and not attrs['allow_override']: @@ -1415,11 +1420,7 @@ def get_field_from_model_or_attrs(fd): ' '.join([str(pk) for pk in used_by]) )}) - view = self.context.get('view', None) - if not organization and not view.request.user.is_superuser: - # Only allow super users to create orgless projects - raise serializers.ValidationError(_('Organization is missing')) - elif get_field_from_model_or_attrs('scm_type') == '': + if get_field_from_model_or_attrs('scm_type') == '': for fd in ('scm_update_on_launch', 'scm_delete_on_update', 'scm_clean'): if get_field_from_model_or_attrs(fd): raise serializers.ValidationError({fd: _('Update options must be set to false for manual projects.')}) @@ -1549,20 +1550,15 @@ class InventorySerializer(BaseSerializerWithVariables): 'admin', 'adhoc', {'copy': 'organization.inventory_admin'} ] - groups_with_active_failures = serializers.IntegerField( - read_only=True, - min_value=0, - help_text=_('This field has been deprecated and will be removed in a future release') - ) class Meta: model = Inventory fields = ('*', 'organization', 'kind', 'host_filter', 'variables', 'has_active_failures', 'total_hosts', 'hosts_with_active_failures', 'total_groups', - 'groups_with_active_failures', 'has_inventory_sources', - 'total_inventory_sources', 'inventory_sources_with_failures', - 'insights_credential', 'pending_deletion',) + 'has_inventory_sources', 'total_inventory_sources', + 'inventory_sources_with_failures', 'insights_credential', + 'pending_deletion',) def get_related(self, obj): res = super(InventorySerializer, self).get_related(obj) @@ -1612,7 +1608,7 @@ def validate_host_filter(self, host_filter): }) SmartFilter().query_from_string(host_filter) except RuntimeError as e: - raise models.base.ValidationError(e) + raise models.base.ValidationError(str(e)) return host_filter def validate(self, attrs): @@ -1644,6 +1640,9 @@ class HostSerializer(BaseSerializerWithVariables): show_capabilities = ['edit', 'delete'] capabilities_prefetch = ['inventory.admin'] + has_active_failures = serializers.SerializerMethodField() + has_inventory_sources = serializers.SerializerMethodField() + class Meta: model = Host fields = ('*', 'inventory', 'enabled', 'instance_id', 'variables', @@ -1700,9 +1699,13 @@ def get_summary_fields(self, obj): d.setdefault('recent_jobs', [{ 'id': j.job.id, 'name': j.job.job_template.name if j.job.job_template is not None else "", + 'type': j.job.job_type_name, 'status': j.job.status, 'finished': j.job.finished, - } for j in obj.job_host_summaries.select_related('job__job_template').order_by('-created')[:5]]) + } for j in obj.job_host_summaries.select_related('job__job_template').order_by('-created').defer( + 'job__extra_vars', + 'job__artifacts', + )[:5]]) return d def _get_host_port_from_name(self, name): @@ -1734,6 +1737,7 @@ def validate_variables(self, value): def validate(self, attrs): name = force_text(attrs.get('name', self.instance and self.instance.name or '')) + inventory = attrs.get('inventory', self.instance and self.instance.inventory or '') host, port = self._get_host_port_from_name(name) if port: @@ -1742,6 +1746,8 @@ def validate(self, attrs): vars_dict = parse_yaml_or_json(variables) vars_dict['ansible_ssh_port'] = port attrs['variables'] = json.dumps(vars_dict) + if Group.objects.filter(name=name, inventory=inventory).exists(): + raise serializers.ValidationError(_('A Group with that name already exists.')) return super(HostSerializer, self).validate(attrs) @@ -1757,6 +1763,14 @@ def to_representation(self, obj): ret['last_job_host_summary'] = None return ret + def get_has_active_failures(self, obj): + return bool( + obj.last_job_host_summary and obj.last_job_host_summary.failed + ) + + def get_has_inventory_sources(self, obj): + return obj.inventory_sources.exists() + class AnsibleFactsSerializer(BaseSerializer): class Meta: @@ -1769,17 +1783,10 @@ def to_representation(self, obj): class GroupSerializer(BaseSerializerWithVariables): show_capabilities = ['copy', 'edit', 'delete'] capabilities_prefetch = ['inventory.admin', 'inventory.adhoc'] - groups_with_active_failures = serializers.IntegerField( - read_only=True, - min_value=0, - help_text=_('This field has been deprecated and will be removed in a future release') - ) class Meta: model = Group - fields = ('*', 'inventory', 'variables', 'has_active_failures', - 'total_hosts', 'hosts_with_active_failures', 'total_groups', - 'groups_with_active_failures', 'has_inventory_sources') + fields = ('*', 'inventory', 'variables') def build_relational_field(self, field_name, relation_info): field_class, field_kwargs = super(GroupSerializer, self).build_relational_field(field_name, relation_info) @@ -1807,6 +1814,13 @@ def get_related(self, obj): res['inventory'] = self.reverse('api:inventory_detail', kwargs={'pk': obj.inventory.pk}) return res + def validate(self, attrs): + name = force_text(attrs.get('name', self.instance and self.instance.name or '')) + inventory = attrs.get('inventory', self.instance and self.instance.inventory or '') + if Host.objects.filter(name=name, inventory=inventory).exists(): + raise serializers.ValidationError(_('A Host with that name already exists.')) + return super(GroupSerializer, self).validate(attrs) + def validate_name(self, value): if value in ('all', '_meta'): raise serializers.ValidationError(_('Invalid group name.')) @@ -1923,7 +1937,7 @@ class InventorySourceOptionsSerializer(BaseSerializer): class Meta: fields = ('*', 'source', 'source_path', 'source_script', 'source_vars', 'credential', - 'source_regions', 'instance_filters', 'group_by', 'overwrite', 'overwrite_vars', + 'enabled_var', 'enabled_value', 'host_filter', 'overwrite', 'overwrite_vars', 'custom_virtualenv', 'timeout', 'verbosity') def get_related(self, obj): @@ -1938,12 +1952,12 @@ def get_related(self, obj): def validate_source_vars(self, value): ret = vars_validate_or_raise(value) for env_k in parse_yaml_or_json(value): - if env_k in settings.INV_ENV_VARIABLE_BLACKLIST: + if env_k in settings.INV_ENV_VARIABLE_BLOCKED: raise serializers.ValidationError(_("`{}` is a prohibited environment variable".format(env_k))) return ret def validate(self, attrs): - # TODO: Validate source, validate source_regions + # TODO: Validate source errors = {} source = attrs.get('source', self.instance and self.instance.source or '') @@ -2038,11 +2052,6 @@ def get_related(self, obj): res['credentials'] = self.reverse('api:inventory_source_credentials_list', kwargs={'pk': obj.pk}) return res - def get_group(self, obj): # TODO: remove in 3.3 - if obj.deprecated_group: - return obj.deprecated_group.id - return None - def build_relational_field(self, field_name, relation_info): field_class, field_kwargs = super(InventorySourceSerializer, self).build_relational_field(field_name, relation_info) # SCM Project and inventory are read-only unless creating a new inventory. @@ -2123,7 +2132,13 @@ def validate(self, attrs): def get_field_from_model_or_attrs(fd): return attrs.get(fd, self.instance and getattr(self.instance, fd) or None) - if get_field_from_model_or_attrs('source') != 'scm': + if get_field_from_model_or_attrs('source') == 'scm': + if (('source' in attrs or 'source_project' in attrs) and + get_field_from_model_or_attrs('source_project') is None): + raise serializers.ValidationError( + {"source_project": _("Project required for scm type sources.")} + ) + else: redundant_scm_fields = list(filter( lambda x: attrs.get(x, None), ['source_project', 'source_path', 'update_on_project_update'] @@ -2309,6 +2324,7 @@ def to_representation(self, obj): content_model = obj.content_type.model_class() ret['summary_fields']['resource_type'] = get_type_for_model(content_model) ret['summary_fields']['resource_type_display_name'] = content_model._meta.verbose_name.title() + ret['summary_fields']['resource_id'] = obj.object_id return ret @@ -2520,10 +2536,11 @@ def filter_field_metadata(self, fields, method): class CredentialSerializer(BaseSerializer): show_capabilities = ['edit', 'delete', 'copy', 'use'] capabilities_prefetch = ['admin', 'use'] + managed_by_tower = serializers.ReadOnlyField() class Meta: model = Credential - fields = ('*', 'organization', 'credential_type', 'inputs', 'kind', 'cloud', 'kubernetes') + fields = ('*', 'organization', 'credential_type', 'managed_by_tower', 'inputs', 'kind', 'cloud', 'kubernetes') extra_kwargs = { 'credential_type': { 'label': _('Credential Type'), @@ -2587,6 +2604,13 @@ def get_summary_fields(self, obj): return summary_dict + def validate(self, attrs): + if self.instance and self.instance.managed_by_tower: + raise PermissionDenied( + detail=_("Modifications not allowed for managed credentials") + ) + return super(CredentialSerializer, self).validate(attrs) + def get_validation_exclusions(self, obj=None): ret = super(CredentialSerializer, self).get_validation_exclusions(obj) for field in ('credential_type', 'inputs'): @@ -2594,6 +2618,17 @@ def get_validation_exclusions(self, obj=None): ret.remove(field) return ret + def validate_organization(self, org): + if ( + self.instance and + self.instance.credential_type.kind == 'galaxy' and + org is None + ): + raise serializers.ValidationError(_( + "Galaxy credentials must be owned by an Organization." + )) + return org + def validate_credential_type(self, credential_type): if self.instance and credential_type.pk != self.instance.credential_type.pk: for related_objects in ( @@ -2644,12 +2679,29 @@ def validate(self, attrs): owner_fields.add(field) else: attrs.pop(field) + if not owner_fields: raise serializers.ValidationError({"detail": _("Missing 'user', 'team', or 'organization'.")}) + if len(owner_fields) > 1: + received = ", ".join(sorted(owner_fields)) + raise serializers.ValidationError({"detail": _( + "Only one of 'user', 'team', or 'organization' should be provided, " + "received {} fields.".format(received) + )}) + if attrs.get('team'): attrs['organization'] = attrs['team'].organization + if ( + 'credential_type' in attrs and + attrs['credential_type'].kind == 'galaxy' and + list(owner_fields) != ['organization'] + ): + raise serializers.ValidationError({"organization": _( + "Galaxy credentials must be owned by an Organization." + )}) + return super(CredentialSerializerCreate, self).validate(attrs) def create(self, validated_data): @@ -2740,7 +2792,8 @@ class Meta: fields = ('*', 'job_type', 'inventory', 'project', 'playbook', 'scm_branch', 'forks', 'limit', 'verbosity', 'extra_vars', 'job_tags', 'force_handlers', 'skip_tags', 'start_at_task', 'timeout', - 'use_fact_cache',) + 'use_fact_cache', 'organization',) + read_only_fields = ('organization',) def get_related(self, obj): res = super(JobOptionsSerializer, self).get_related(obj) @@ -2755,17 +2808,14 @@ def get_related(self, obj): res['project'] = self.reverse('api:project_detail', kwargs={'pk': obj.project.pk}) except ObjectDoesNotExist: setattr(obj, 'project', None) + if obj.organization_id: + res['organization'] = self.reverse('api:organization_detail', kwargs={'pk': obj.organization_id}) if isinstance(obj, UnifiedJobTemplate): - res['extra_credentials'] = self.reverse( - 'api:job_template_extra_credentials_list', - kwargs={'pk': obj.pk} - ) res['credentials'] = self.reverse( 'api:job_template_credentials_list', kwargs={'pk': obj.pk} ) elif isinstance(obj, UnifiedJob): - res['extra_credentials'] = self.reverse('api:job_extra_credentials_list', kwargs={'pk': obj.pk}) res['credentials'] = self.reverse('api:job_credentials_list', kwargs={'pk': obj.pk}) return res @@ -2823,9 +2873,9 @@ def _recent_jobs(self, obj): # .only('id', 'status', 'finished', 'polymorphic_ctype_id') optimized_qs = uj_qs.non_polymorphic() return [{ - 'id': x.id, 'status': x.status, 'finished': x.finished, + 'id': x.id, 'status': x.status, 'finished': x.finished, 'canceled_on': x.canceled_on, # Make type consistent with API top-level key, for instance workflow_job - 'type': x.get_real_instance_class()._meta.verbose_name.replace(' ', '_') + 'type': x.job_type_name } for x in optimized_qs[:10]] def get_summary_fields(self, obj): @@ -2901,6 +2951,10 @@ def get_related(self, obj): ) if obj.host_config_key: res['callback'] = self.reverse('api:job_template_callback', kwargs={'pk': obj.pk}) + if obj.organization_id: + res['organization'] = self.reverse('api:organization_detail', kwargs={'pk': obj.organization_id}) + if obj.webhook_credential_id: + res['webhook_credential'] = self.reverse('api:credential_detail', kwargs={'pk': obj.webhook_credential_id}) return res def validate(self, attrs): @@ -2930,7 +2984,6 @@ def get_summary_fields(self, obj): summary_fields = super(JobTemplateSerializer, self).get_summary_fields(obj) all_creds = [] # Organize credential data into multitude of deprecated fields - extra_creds = [] if obj.pk: for cred in obj.credentials.all(): summarized_cred = { @@ -2941,10 +2994,6 @@ def get_summary_fields(self, obj): 'cloud': cred.credential_type.kind == 'cloud' } all_creds.append(summarized_cred) - if cred.credential_type.kind in ('cloud', 'net'): - extra_creds.append(summarized_cred) - if self.is_detail_view: - summary_fields['extra_credentials'] = extra_creds summary_fields['credentials'] = all_creds return summary_fields @@ -3019,7 +3068,6 @@ def get_summary_fields(self, obj): summary_fields = super(JobSerializer, self).get_summary_fields(obj) all_creds = [] # Organize credential data into multitude of deprecated fields - extra_creds = [] if obj.pk: for cred in obj.credentials.all(): summarized_cred = { @@ -3030,10 +3078,6 @@ def get_summary_fields(self, obj): 'cloud': cred.credential_type.kind == 'cloud' } all_creds.append(summarized_cred) - if cred.credential_type.kind in ('cloud', 'net'): - extra_creds.append(summarized_cred) - if self.is_detail_view: - summary_fields['extra_credentials'] = extra_creds summary_fields['credentials'] = all_creds return summary_fields @@ -3206,7 +3250,7 @@ def build_standard_field(self, field_name, model_field): field_kwargs['choices'] = module_name_choices field_kwargs['required'] = bool(not module_name_default) field_kwargs['default'] = module_name_default or serializers.empty - field_kwargs['allow_blank'] = bool(module_name_default) + field_kwargs['allow_blank'] = False field_kwargs.pop('max_length', None) return field_class, field_kwargs @@ -3391,6 +3435,14 @@ def get_related(self, obj): ) if obj.organization: res['organization'] = self.reverse('api:organization_detail', kwargs={'pk': obj.organization.pk}) + if obj.webhook_credential_id: + res['webhook_credential'] = self.reverse('api:credential_detail', kwargs={'pk': obj.webhook_credential_id}) + if obj.inventory_id: + res['inventory'] = self.reverse( + 'api:inventory_detail', kwargs={ + 'pk': obj.inventory_id + } + ) return res def validate_extra_vars(self, value): @@ -3605,9 +3657,11 @@ def validate(self, attrs): elif self.instance: ujt = self.instance.unified_job_template if ujt is None: - if 'workflow_job_template' in attrs: - return {'workflow_job_template': attrs['workflow_job_template']} - return {} + ret = {} + for fd in ('workflow_job_template', 'identifier', 'all_parents_must_converge'): + if fd in attrs: + ret[fd] = attrs[fd] + return ret # build additional field survey_passwords to track redacted variables password_dict = {} @@ -3660,7 +3714,7 @@ def validate(self, attrs): attrs.get('survey_passwords', {}).pop(key, None) else: errors.setdefault('extra_vars', []).append( - _('"$encrypted$ is a reserved keyword, may not be used for {var_name}."'.format(key)) + _('"$encrypted$ is a reserved keyword, may not be used for {}."'.format(key)) ) # Launch configs call extra_vars extra_data for historical reasons @@ -3685,7 +3739,8 @@ class WorkflowJobTemplateNodeSerializer(LaunchConfigurationBaseSerializer): class Meta: model = WorkflowJobTemplateNode fields = ('*', 'workflow_job_template', '-name', '-description', 'id', 'url', 'related', - 'unified_job_template', 'success_nodes', 'failure_nodes', 'always_nodes',) + 'unified_job_template', 'success_nodes', 'failure_nodes', 'always_nodes', 'all_parents_must_converge', + 'identifier',) def get_related(self, obj): res = super(WorkflowJobTemplateNodeSerializer, self).get_related(obj) @@ -3725,7 +3780,7 @@ class Meta: model = WorkflowJobNode fields = ('*', 'job', 'workflow_job', '-name', '-description', 'id', 'url', 'related', 'unified_job_template', 'success_nodes', 'failure_nodes', 'always_nodes', - 'do_not_run',) + 'all_parents_must_converge', 'do_not_run', 'identifier') def get_related(self, obj): res = super(WorkflowJobNodeSerializer, self).get_related(obj) @@ -3833,7 +3888,7 @@ class Meta: model = JobEvent fields = ('*', '-name', '-description', 'job', 'event', 'counter', 'event_display', 'event_data', 'event_level', 'failed', - 'changed', 'uuid', 'parent_uuid', 'host', 'host_name', 'parent', + 'changed', 'uuid', 'parent_uuid', 'host', 'host_name', 'playbook', 'play', 'task', 'role', 'stdout', 'start_line', 'end_line', 'verbosity') @@ -3842,13 +3897,9 @@ def get_related(self, obj): res.update(dict( job = self.reverse('api:job_detail', kwargs={'pk': obj.job_id}), )) - if obj.parent_id: - res['parent'] = self.reverse('api:job_event_detail', kwargs={'pk': obj.parent_id}) res['children'] = self.reverse('api:job_event_children_list', kwargs={'pk': obj.pk}) if obj.host_id: res['host'] = self.reverse('api:host_detail', kwargs={'pk': obj.host_id}) - if obj.hosts.exists(): - res['hosts'] = self.reverse('api:job_event_hosts_list', kwargs={'pk': obj.pk}) return res def get_summary_fields(self, obj): @@ -3894,15 +3945,23 @@ def get_stdout(self, obj): return UriCleaner.remove_sensitive(obj.stdout) def get_event_data(self, obj): - try: - return json.loads( - UriCleaner.remove_sensitive( - json.dumps(obj.event_data) + # the project update playbook uses the git or svn modules + # to clone repositories, and those modules are prone to printing + # raw SCM URLs in their stdout (which *could* contain passwords) + # attempt to detect and filter HTTP basic auth passwords in the stdout + # of these types of events + if obj.event_data.get('task_action') in ('git', 'svn'): + try: + return json.loads( + UriCleaner.remove_sensitive( + json.dumps(obj.event_data) + ) ) - ) - except Exception: - logger.exception("Failed to sanitize event_data") - return {} + except Exception: + logger.exception("Failed to sanitize event_data") + return {} + else: + return obj.event_data class AdHocCommandEventSerializer(BaseSerializer): @@ -4060,6 +4119,13 @@ def validate(self, attrs): **attrs) self._ignored_fields = rejected + # Basic validation - cannot run a playbook without a playbook + if not template.project: + errors['project'] = _("A project is required to run a job.") + elif template.project.status in ('error', 'failed'): + errors['playbook'] = _("Missing a revision to run due to failed project update.") + + # cannot run a playbook without an inventory if template.inventory and template.inventory.pending_deletion is True: errors['inventory'] = _("The inventory associated with this Job Template is being deleted.") elif 'inventory' in accepted and accepted['inventory'].pending_deletion: @@ -4073,7 +4139,8 @@ def validate(self, attrs): errors.setdefault('credentials', []).append(_( 'Cannot assign multiple {} credentials.' ).format(cred.unique_hash(display=True))) - if cred.credential_type.kind not in ('ssh', 'vault', 'cloud', 'net'): + if cred.credential_type.kind not in ('ssh', 'vault', 'cloud', + 'net', 'kubernetes'): errors.setdefault('credentials', []).append(_( 'Cannot assign a Credential of kind `{}`' ).format(cred.credential_type.kind)) @@ -4095,7 +4162,10 @@ def validate(self, attrs): # verify that credentials (either provided or existing) don't # require launch-time passwords that have not been provided if 'credentials' in accepted: - launch_credentials = accepted['credentials'] + launch_credentials = Credential.unique_dict( + list(template_credentials.all()) + + list(accepted['credentials']) + ).values() else: launch_credentials = template_credentials passwords = attrs.get('credential_passwords', {}) # get from original attrs @@ -4524,6 +4594,8 @@ def validate_rrule(self, value): try: Schedule.rrulestr(rrule_value) except Exception as e: + import traceback + logger.error(traceback.format_exc()) raise serializers.ValidationError(_("rrule parsing failed validation: {}").format(e)) return value @@ -4635,6 +4707,8 @@ def get_percent_capacity_remaining(self, obj): class InstanceGroupSerializer(BaseSerializer): + show_capabilities = ['edit', 'delete'] + committed_capacity = serializers.SerializerMethodField() consumed_capacity = serializers.SerializerMethodField() percent_capacity_remaining = serializers.SerializerMethodField() diff --git a/awx/api/templates/api/_schedule_detail.md b/awx/api/templates/api/_schedule_detail.md index 04eecb4f234c..d42b2ac3526b 100644 --- a/awx/api/templates/api/_schedule_detail.md +++ b/awx/api/templates/api/_schedule_detail.md @@ -4,7 +4,6 @@ The following lists the expected format and details of our rrules: * DTSTART is expected to be in UTC * INTERVAL is required * SECONDLY is not supported -* TZID is not supported * RRULE must precede the rule statements * BYDAY is supported but not BYDAY with a numerical prefix * BYYEARDAY and BYWEEKNO are not supported diff --git a/awx/api/templates/api/dashboard_jobs_graph_view.md b/awx/api/templates/api/dashboard_jobs_graph_view.md index 2e510b2a56cc..baadd4d561b7 100644 --- a/awx/api/templates/api/dashboard_jobs_graph_view.md +++ b/awx/api/templates/api/dashboard_jobs_graph_view.md @@ -8,7 +8,7 @@ The `period` of the data can be adjusted with: ?period=month -Where `month` can be replaced with `week`, or `day`. `month` is the default. +Where `month` can be replaced with `week`, `two_weeks`, or `day`. `month` is the default. The type of job can be filtered with: diff --git a/awx/api/templates/api/setting_logging_test.md b/awx/api/templates/api/setting_logging_test.md index 149fac28ae1e..5b6c49dc578c 100644 --- a/awx/api/templates/api/setting_logging_test.md +++ b/awx/api/templates/api/setting_logging_test.md @@ -1 +1,2 @@ # Test Logging Configuration + diff --git a/awx/api/urls/oauth2_root.py b/awx/api/urls/oauth2_root.py index 4b5b8d619adc..1ddfb5320b23 100644 --- a/awx/api/urls/oauth2_root.py +++ b/awx/api/urls/oauth2_root.py @@ -1,11 +1,15 @@ # Copyright (c) 2017 Ansible, Inc. # All Rights Reserved. +from datetime import timedelta +from django.utils.timezone import now +from django.conf import settings from django.conf.urls import url from oauthlib import oauth2 from oauth2_provider import views +from awx.main.models import RefreshToken from awx.api.views import ( ApiOAuthAuthorizationRootView, ) @@ -14,6 +18,21 @@ class TokenView(views.TokenView): def create_token_response(self, request): + # Django OAuth2 Toolkit has a bug whereby refresh tokens are *never* + # properly expired (ugh): + # + # https://github.com/jazzband/django-oauth-toolkit/issues/746 + # + # This code detects and auto-expires them on refresh grant + # requests. + if request.POST.get('grant_type') == 'refresh_token' and 'refresh_token' in request.POST: + refresh_token = RefreshToken.objects.filter( + token=request.POST['refresh_token'] + ).first() + if refresh_token: + expire_seconds = settings.OAUTH2_PROVIDER.get('REFRESH_TOKEN_EXPIRE_SECONDS', 0) + if refresh_token.created + timedelta(seconds=expire_seconds) < now(): + return request.build_absolute_uri(), {}, 'The refresh token has expired.', '403' try: return super(TokenView, self).create_token_response(request) except oauth2.AccessDeniedError as e: diff --git a/awx/api/urls/organization.py b/awx/api/urls/organization.py index 1b0997b05c8e..12b2807905bc 100644 --- a/awx/api/urls/organization.py +++ b/awx/api/urls/organization.py @@ -10,6 +10,7 @@ OrganizationAdminsList, OrganizationInventoriesList, OrganizationProjectsList, + OrganizationJobTemplatesList, OrganizationWorkflowJobTemplatesList, OrganizationTeamsList, OrganizationCredentialList, @@ -20,6 +21,7 @@ OrganizationNotificationTemplatesSuccessList, OrganizationNotificationTemplatesApprovalList, OrganizationInstanceGroupsList, + OrganizationGalaxyCredentialsList, OrganizationObjectRolesList, OrganizationAccessList, OrganizationApplicationList, @@ -33,6 +35,7 @@ url(r'^(?P[0-9]+)/admins/$', OrganizationAdminsList.as_view(), name='organization_admins_list'), url(r'^(?P[0-9]+)/inventories/$', OrganizationInventoriesList.as_view(), name='organization_inventories_list'), url(r'^(?P[0-9]+)/projects/$', OrganizationProjectsList.as_view(), name='organization_projects_list'), + url(r'^(?P[0-9]+)/job_templates/$', OrganizationJobTemplatesList.as_view(), name='organization_job_templates_list'), url(r'^(?P[0-9]+)/workflow_job_templates/$', OrganizationWorkflowJobTemplatesList.as_view(), name='organization_workflow_job_templates_list'), url(r'^(?P[0-9]+)/teams/$', OrganizationTeamsList.as_view(), name='organization_teams_list'), url(r'^(?P[0-9]+)/credentials/$', OrganizationCredentialList.as_view(), name='organization_credential_list'), @@ -47,6 +50,7 @@ url(r'^(?P[0-9]+)/notification_templates_approvals/$', OrganizationNotificationTemplatesApprovalList.as_view(), name='organization_notification_templates_approvals_list'), url(r'^(?P[0-9]+)/instance_groups/$', OrganizationInstanceGroupsList.as_view(), name='organization_instance_groups_list'), + url(r'^(?P[0-9]+)/galaxy_credentials/$', OrganizationGalaxyCredentialsList.as_view(), name='organization_galaxy_credentials_list'), url(r'^(?P[0-9]+)/object_roles/$', OrganizationObjectRolesList.as_view(), name='organization_object_roles_list'), url(r'^(?P[0-9]+)/access_list/$', OrganizationAccessList.as_view(), name='organization_access_list'), url(r'^(?P[0-9]+)/applications/$', OrganizationApplicationList.as_view(), name='organization_applications_list'), diff --git a/awx/api/urls/urls.py b/awx/api/urls/urls.py index ab7d61fd233c..636e68e4bdd4 100644 --- a/awx/api/urls/urls.py +++ b/awx/api/urls/urls.py @@ -15,6 +15,7 @@ ApiV2PingView, ApiV2ConfigView, ApiV2SubscriptionView, + ApiV2AttachView, AuthView, UserMeList, DashboardView, @@ -23,9 +24,7 @@ UnifiedJobList, HostAnsibleFactsDetail, JobCredentialsList, - JobExtraCredentialsList, JobTemplateCredentialsList, - JobTemplateExtraCredentialsList, SchedulePreview, ScheduleZoneInfo, OAuth2ApplicationList, @@ -34,7 +33,9 @@ OAuth2ApplicationDetail, ) -from awx.api.views.metrics import MetricsView +from awx.api.views.metrics import ( + MetricsView, +) from .organization import urls as organization_urls from .user import urls as user_urls @@ -81,9 +82,7 @@ url(r'^credential_types/', include(credential_type_urls)), url(r'^credential_input_sources/', include(credential_input_source_urls)), url(r'^hosts/(?P[0-9]+)/ansible_facts/$', HostAnsibleFactsDetail.as_view(), name='host_ansible_facts_detail'), - url(r'^jobs/(?P[0-9]+)/extra_credentials/$', JobExtraCredentialsList.as_view(), name='job_extra_credentials_list'), url(r'^jobs/(?P[0-9]+)/credentials/$', JobCredentialsList.as_view(), name='job_credentials_list'), - url(r'^job_templates/(?P[0-9]+)/extra_credentials/$', JobTemplateExtraCredentialsList.as_view(), name='job_template_extra_credentials_list'), url(r'^job_templates/(?P[0-9]+)/credentials/$', JobTemplateCredentialsList.as_view(), name='job_template_credentials_list'), url(r'^schedules/preview/$', SchedulePreview.as_view(), name='schedule_rrule'), url(r'^schedules/zoneinfo/$', ScheduleZoneInfo.as_view(), name='schedule_zoneinfo'), @@ -96,6 +95,7 @@ url(r'^ping/$', ApiV2PingView.as_view(), name='api_v2_ping_view'), url(r'^config/$', ApiV2ConfigView.as_view(), name='api_v2_config_view'), url(r'^config/subscriptions/$', ApiV2SubscriptionView.as_view(), name='api_v2_subscription_view'), + url(r'^config/attach/$', ApiV2AttachView.as_view(), name='api_v2_attach_view'), url(r'^auth/$', AuthView.as_view()), url(r'^me/$', UserMeList.as_view(), name='user_me_list'), url(r'^dashboard/$', DashboardView.as_view(), name='dashboard_view'), diff --git a/awx/api/views/__init__.py b/awx/api/views/__init__.py index e791173ceb3f..43e845af0c3a 100644 --- a/awx/api/views/__init__.py +++ b/awx/api/views/__init__.py @@ -12,7 +12,9 @@ import sys import time from base64 import b64encode -from collections import OrderedDict, Iterable +from collections import OrderedDict + +from urllib3.exceptions import ConnectTimeoutError # Django @@ -81,7 +83,8 @@ getattrd, get_pk_from_dict, schedule_task_manager, - ignore_inventory_computed_fields + ignore_inventory_computed_fields, + set_environ ) from awx.main.utils.encryption import encrypt_value from awx.main.utils.filters import SmartFilter @@ -110,6 +113,7 @@ OrganizationUsersList, OrganizationAdminsList, OrganizationProjectsList, + OrganizationJobTemplatesList, OrganizationWorkflowJobTemplatesList, OrganizationTeamsList, OrganizationActivityStreamList, @@ -120,6 +124,7 @@ OrganizationNotificationTemplatesSuccessList, OrganizationNotificationTemplatesApprovalList, OrganizationInstanceGroupsList, + OrganizationGalaxyCredentialsList, OrganizationAccessList, OrganizationObjectRolesList, ) @@ -148,6 +153,7 @@ ApiV2PingView, ApiV2ConfigView, ApiV2SubscriptionView, + ApiV2AttachView, ) from awx.api.views.webhooks import ( # noqa WebhookKeyView, @@ -169,6 +175,15 @@ def api_exception_handler(exc, context): exc = ParseError(exc.args[0]) if isinstance(context['view'], UnifiedJobStdout): context['view'].renderer_classes = [renderers.BrowsableAPIRenderer, JSONRenderer] + if isinstance(exc, APIException): + req = context['request']._request + if 'awx.named_url_rewritten' in req.environ and not str(getattr(exc, 'status_code', 0)).startswith('2'): + # if the URL was rewritten, and it's not a 2xx level status code, + # revert the request.path to its original value to avoid leaking + # any context about the existance of resources + req.path = req.environ['awx.named_url_rewritten'] + if exc.status_code == 403: + exc = NotFound(detail=_('Not found.')) return exception_handler(exc, context) @@ -204,20 +219,15 @@ def get(self, request, format=None): 'failed': ec2_inventory_failed.count()} user_groups = get_user_queryset(request.user, models.Group) - groups_job_failed = ( - models.Group.objects.filter(hosts_with_active_failures__gt=0) | models.Group.objects.filter(groups_with_active_failures__gt=0) - ).count() groups_inventory_failed = models.Group.objects.filter(inventory_sources__last_job_failed=True).count() data['groups'] = {'url': reverse('api:group_list', request=request), - 'failures_url': reverse('api:group_list', request=request) + "?has_active_failures=True", 'total': user_groups.count(), - 'job_failed': groups_job_failed, 'inventory_failed': groups_inventory_failed} user_hosts = get_user_queryset(request.user, models.Host) - user_hosts_failed = user_hosts.filter(has_active_failures=True) + user_hosts_failed = user_hosts.filter(last_job_host_summary__failed=True) data['hosts'] = {'url': reverse('api:host_list', request=request), - 'failures_url': reverse('api:host_list', request=request) + "?has_active_failures=True", + 'failures_url': reverse('api:host_list', request=request) + "?last_job_host_summary__failed=True", 'total': user_hosts.count(), 'failed': user_hosts_failed.count()} @@ -232,8 +242,8 @@ def get(self, request, format=None): git_failed_projects = git_projects.filter(last_job_failed=True) svn_projects = user_projects.filter(scm_type='svn') svn_failed_projects = svn_projects.filter(last_job_failed=True) - hg_projects = user_projects.filter(scm_type='hg') - hg_failed_projects = hg_projects.filter(last_job_failed=True) + archive_projects = user_projects.filter(scm_type='archive') + archive_failed_projects = archive_projects.filter(last_job_failed=True) data['scm_types'] = {} data['scm_types']['git'] = {'url': reverse('api:project_list', request=request) + "?scm_type=git", 'label': 'Git', @@ -245,11 +255,11 @@ def get(self, request, format=None): 'failures_url': reverse('api:project_list', request=request) + "?scm_type=svn&last_job_failed=True", 'total': svn_projects.count(), 'failed': svn_failed_projects.count()} - data['scm_types']['hg'] = {'url': reverse('api:project_list', request=request) + "?scm_type=hg", - 'label': 'Mercurial', - 'failures_url': reverse('api:project_list', request=request) + "?scm_type=hg&last_job_failed=True", - 'total': hg_projects.count(), - 'failed': hg_failed_projects.count()} + data['scm_types']['archive'] = {'url': reverse('api:project_list', request=request) + "?scm_type=archive", + 'label': 'Remote Archive', + 'failures_url': reverse('api:project_list', request=request) + "?scm_type=archive&last_job_failed=True", + 'total': archive_projects.count(), + 'failed': archive_failed_projects.count()} user_list = get_user_queryset(request.user, models.User) team_list = get_user_queryset(request.user, models.Team) @@ -300,6 +310,9 @@ def get(self, request, format=None): if period == 'month': end_date = start_date - dateutil.relativedelta.relativedelta(months=1) interval = 'days' + elif period == 'two_weeks': + end_date = start_date - dateutil.relativedelta.relativedelta(weeks=2) + interval = 'days' elif period == 'week': end_date = start_date - dateutil.relativedelta.relativedelta(weeks=1) interval = 'days' @@ -1095,7 +1108,7 @@ def post(self, request, *args, **kwargs): credential_content_type = ContentType.objects.get_for_model(models.Credential) if role.content_type == credential_content_type: - if role.content_object.organization and user not in role.content_object.organization.member_role: + if 'disassociate' not in request.data and role.content_object.organization and user not in role.content_object.organization.member_role: data = dict(msg=_("You cannot grant credential access to a user not in the credentials' organization")) return Response(data, status=status.HTTP_400_BAD_REQUEST) @@ -1340,6 +1353,13 @@ class CredentialDetail(RetrieveUpdateDestroyAPIView): model = models.Credential serializer_class = serializers.CredentialSerializer + def destroy(self, request, *args, **kwargs): + instance = self.get_object() + if instance.managed_by_tower: + raise PermissionDenied(detail=_("Deletion not allowed for managed credentials")) + return super(CredentialDetail, self).destroy(request, *args, **kwargs) + + class CredentialActivityStreamList(SubListAPIView): @@ -1400,10 +1420,18 @@ def post(self, request, *args, **kwargs): obj.credential_type.plugin.backend(**backend_kwargs) return Response({}, status=status.HTTP_202_ACCEPTED) except requests.exceptions.HTTPError as exc: - message = 'HTTP {}\n{}'.format(exc.response.status_code, exc.response.text) + message = 'HTTP {}'.format(exc.response.status_code) return Response({'inputs': message}, status=status.HTTP_400_BAD_REQUEST) except Exception as exc: - return Response({'inputs': str(exc)}, status=status.HTTP_400_BAD_REQUEST) + message = exc.__class__.__name__ + args = getattr(exc, 'args', []) + for a in args: + if isinstance( + getattr(a, 'reason', None), + ConnectTimeoutError + ): + message = str(a.reason) + return Response({'inputs': message}, status=status.HTTP_400_BAD_REQUEST) class CredentialInputSourceDetail(RetrieveUpdateDestroyAPIView): @@ -1452,10 +1480,18 @@ def post(self, request, *args, **kwargs): obj.plugin.backend(**backend_kwargs) return Response({}, status=status.HTTP_202_ACCEPTED) except requests.exceptions.HTTPError as exc: - message = 'HTTP {}\n{}'.format(exc.response.status_code, exc.response.text) + message = 'HTTP {}'.format(exc.response.status_code) return Response({'inputs': message}, status=status.HTTP_400_BAD_REQUEST) except Exception as exc: - return Response({'inputs': str(exc)}, status=status.HTTP_400_BAD_REQUEST) + message = exc.__class__.__name__ + args = getattr(exc, 'args', []) + for a in args: + if isinstance( + getattr(a, 'reason', None), + ConnectTimeoutError + ): + message = str(a.reason) + return Response({'inputs': message}, status=status.HTTP_400_BAD_REQUEST) class HostRelatedSearchMixin(object): @@ -1611,7 +1647,8 @@ class HostInsights(GenericAPIView): def _call_insights_api(self, url, session, headers): try: - res = session.get(url, headers=headers, timeout=120) + with set_environ(**settings.AWX_TASK_ENV): + res = session.get(url, headers=headers, timeout=120) except requests.exceptions.SSLError: raise BadGateway(_('SSLError while trying to connect to {}').format(url)) except requests.exceptions.Timeout: @@ -2150,7 +2187,7 @@ def perform_list_destroy(self, instance_list): host__inventory_sources=inv_source ).delete() r = super(InventorySourceHostsList, self).perform_list_destroy(instance_list) - update_inventory_computed_fields.delay(inv_source.inventory_id, True) + update_inventory_computed_fields.delay(inv_source.inventory_id) return r @@ -2177,7 +2214,7 @@ def perform_list_destroy(self, instance_list): group__inventory_sources=inv_source ).delete() r = super(InventorySourceGroupsList, self).perform_list_destroy(instance_list) - update_inventory_computed_fields.delay(inv_source.inventory_id, True) + update_inventory_computed_fields.delay(inv_source.inventory_id) return r @@ -2339,70 +2376,24 @@ def modernize_launch_payload(self, data, obj): old field structure to launch endpoint TODO: delete this method with future API version changes ''' - ignored_fields = {} modern_data = data.copy() id_fd = '{}_id'.format('inventory') if 'inventory' not in modern_data and id_fd in modern_data: modern_data['inventory'] = modern_data[id_fd] - # Automatically convert legacy launch credential arguments into a list of `.credentials` - if 'credentials' in modern_data and 'extra_credentials' in modern_data: - raise ParseError({"error": _( - "'credentials' cannot be used in combination with 'extra_credentials'." - )}) - - if 'extra_credentials' in modern_data: - # make a list of the current credentials - existing_credentials = obj.credentials.all() - template_credentials = list(existing_credentials) # save copy of existing - new_credentials = [] - if 'extra_credentials' in modern_data: - existing_credentials = [ - cred for cred in existing_credentials - if cred.credential_type.kind not in ('cloud', 'net') - ] - prompted_value = modern_data.pop('extra_credentials') - - # validate type, since these are not covered by a serializer - if not isinstance(prompted_value, Iterable): - msg = _( - "Incorrect type. Expected a list received {}." - ).format(prompted_value.__class__.__name__) - raise ParseError({'extra_credentials': [msg], 'credentials': [msg]}) - - # add the deprecated credential specified in the request - if not isinstance(prompted_value, Iterable) or isinstance(prompted_value, str): - prompted_value = [prompted_value] - - # If user gave extra_credentials, special case to use exactly - # the given list without merging with JT credentials - if prompted_value: - obj._deprecated_credential_launch = True # signal to not merge credentials - new_credentials.extend(prompted_value) - - # combine the list of "new" and the filtered list of "old" - new_credentials.extend([cred.pk for cred in existing_credentials]) - if new_credentials: - # If provided list doesn't contain the pre-existing credentials - # defined on the template, add them back here - for cred_obj in template_credentials: - if cred_obj.pk not in new_credentials: - new_credentials.append(cred_obj.pk) - modern_data['credentials'] = new_credentials - # credential passwords were historically provided as top-level attributes if 'credential_passwords' not in modern_data: modern_data['credential_passwords'] = data.copy() - return (modern_data, ignored_fields) + return modern_data def post(self, request, *args, **kwargs): obj = self.get_object() try: - modern_data, ignored_fields = self.modernize_launch_payload( + modern_data = self.modernize_launch_payload( data=request.data, obj=obj ) except ParseError as exc: @@ -2412,8 +2403,6 @@ def post(self, request, *args, **kwargs): if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - ignored_fields.update(serializer._ignored_fields) - if not request.user.can_access(models.JobLaunchConfig, 'add', serializer.validated_data, template=obj): raise PermissionDenied() @@ -2429,11 +2418,11 @@ def post(self, request, *args, **kwargs): data = OrderedDict() if isinstance(new_job, models.WorkflowJob): data['workflow_job'] = new_job.id - data['ignored_fields'] = self.sanitize_for_response(ignored_fields) + data['ignored_fields'] = self.sanitize_for_response(serializer._ignored_fields) data.update(serializers.WorkflowJobSerializer(new_job, context=self.get_serializer_context()).to_representation(new_job)) else: data['job'] = new_job.id - data['ignored_fields'] = self.sanitize_for_response(ignored_fields) + data['ignored_fields'] = self.sanitize_for_response(serializer._ignored_fields) data.update(serializers.JobSerializer(new_job, context=self.get_serializer_context()).to_representation(new_job)) headers = {'Location': new_job.get_absolute_url(request)} return Response(data, status=status.HTTP_201_CREATED, headers=headers) @@ -2707,28 +2696,12 @@ def is_valid_relation(self, parent, sub, created=False): return {"error": _("Cannot assign multiple {credential_type} credentials.").format( credential_type=sub.unique_hash(display=True))} kind = sub.credential_type.kind - if kind not in ('ssh', 'vault', 'cloud', 'net'): + if kind not in ('ssh', 'vault', 'cloud', 'net', 'kubernetes'): return {'error': _('Cannot assign a Credential of kind `{}`.').format(kind)} return super(JobTemplateCredentialsList, self).is_valid_relation(parent, sub, created) -class JobTemplateExtraCredentialsList(JobTemplateCredentialsList): - - deprecated = True - - def get_queryset(self): - sublist_qs = super(JobTemplateExtraCredentialsList, self).get_queryset() - sublist_qs = sublist_qs.filter(credential_type__kind__in=['cloud', 'net']) - return sublist_qs - - def is_valid_relation(self, parent, sub, created=False): - valid = super(JobTemplateExtraCredentialsList, self).is_valid_relation(parent, sub, created) - if sub.credential_type.kind not in ('cloud', 'net'): - return {'error': _('Extra credentials must be network or cloud.')} - return valid - - class JobTemplateLabelList(DeleteLastUnattachLabelMixin, SubListCreateAttachDetachAPIView): model = models.Label @@ -3067,7 +3040,7 @@ def post(self, request, *args, **kwargs): approval_template, context=self.get_serializer_context() ).data - return Response(data, status=status.HTTP_200_OK) + return Response(data, status=status.HTTP_201_CREATED) def check_permissions(self, request): obj = self.get_object().workflow_job_template @@ -3268,7 +3241,7 @@ def post(self, request, *args, **kwargs): jt = obj.job_template if not jt: raise ParseError(_('Cannot relaunch slice workflow job orphaned from job template.')) - elif not jt.inventory or min(jt.inventory.hosts.count(), jt.job_slice_count) != obj.workflow_nodes.count(): + elif not obj.inventory or min(obj.inventory.hosts.count(), jt.job_slice_count) != obj.workflow_nodes.count(): raise ParseError(_('Cannot relaunch sliced workflow job after slice count has changed.')) new_workflow_job = obj.create_relaunch_workflow_job() new_workflow_job.signal_start() @@ -3545,16 +3518,6 @@ class JobCredentialsList(SubListAPIView): relationship = 'credentials' -class JobExtraCredentialsList(JobCredentialsList): - - deprecated = True - - def get_queryset(self): - sublist_qs = super(JobExtraCredentialsList, self).get_queryset() - sublist_qs = sublist_qs.filter(credential_type__kind__in=['cloud', 'net']) - return sublist_qs - - class JobLabelList(SubListAPIView): model = models.Label @@ -3819,6 +3782,12 @@ class JobEventHostsList(HostRelatedSearchMixin, SubListAPIView): relationship = 'hosts' name = _('Job Event Hosts List') + def get_queryset(self): + parent_event = self.get_parent_object() + self.check_parent_access(parent_event) + qs = self.request.user.get_queryset(self.model).filter(job_events_as_primary_host=parent_event) + return qs + class BaseJobEventsList(NoTruncateMixin, SubListAPIView): @@ -3841,8 +3810,7 @@ class HostJobEventsList(BaseJobEventsList): def get_queryset(self): parent_obj = self.get_parent_object() self.check_parent_access(parent_obj) - qs = self.request.user.get_queryset(self.model).filter( - Q(host=parent_obj) | Q(hosts=parent_obj)).distinct() + qs = self.request.user.get_queryset(self.model).filter(host=parent_obj) return qs @@ -3858,9 +3826,7 @@ class JobJobEventsList(BaseJobEventsList): def get_queryset(self): job = self.get_parent_object() self.check_parent_access(job) - qs = job.job_events - qs = qs.select_related('host') - qs = qs.prefetch_related('hosts', 'children') + qs = job.job_events.select_related('host').order_by('start_line') return qs.all() @@ -4284,7 +4250,9 @@ def delete(self, request, *args, **kwargs): obj = self.get_object() if not request.user.can_access(self.model, 'delete', obj): return Response(status=status.HTTP_404_NOT_FOUND) - if obj.notifications.filter(status='pending').exists(): + + hours_old = now() - dateutil.relativedelta.relativedelta(hours=8) + if obj.notifications.filter(status='pending', created__gt=hours_old).exists(): return Response({"error": _("Delete not allowed while there are pending notifications")}, status=status.HTTP_405_METHOD_NOT_ALLOWED) return super(NotificationTemplateDetail, self).delete(request, *args, **kwargs) @@ -4303,7 +4271,7 @@ def post(self, request, *args, **kwargs): msg = "Tower Notification Test {} {}".format(obj.id, settings.TOWER_URL_BASE) if obj.notification_type in ('email', 'pagerduty'): body = "Ansible Tower Test Notification {} {}".format(obj.id, settings.TOWER_URL_BASE) - elif obj.notification_type == 'webhook': + elif obj.notification_type in ('webhook', 'grafana'): body = '{{"body": "Ansible Tower Test Notification {} {}"}}'.format(obj.id, settings.TOWER_URL_BASE) else: body = {"body": "Ansible Tower Test Notification {} {}".format(obj.id, settings.TOWER_URL_BASE)} @@ -4414,7 +4382,7 @@ def post(self, request, *args, **kwargs): credential_content_type = ContentType.objects.get_for_model(models.Credential) if role.content_type == credential_content_type: - if role.content_object.organization and user not in role.content_object.organization.member_role: + if 'disassociate' not in request.data and role.content_object.organization and user not in role.content_object.organization.member_role: data = dict(msg=_("You cannot grant credential access to a user not in the credentials' organization")) return Response(data, status=status.HTTP_400_BAD_REQUEST) diff --git a/awx/api/views/inventory.py b/awx/api/views/inventory.py index 987f5467b423..607a71c6d5bc 100644 --- a/awx/api/views/inventory.py +++ b/awx/api/views/inventory.py @@ -134,7 +134,8 @@ def update(self, request, *args, **kwargs): # Do not allow changes to an Inventory kind. if kind is not None and obj.kind != kind: - return self.http_method_not_allowed(request, *args, **kwargs) + return Response(dict(error=_('You cannot turn a regular inventory into a "smart" inventory.')), + status=status.HTTP_405_METHOD_NOT_ALLOWED) return super(InventoryDetail, self).update(request, *args, **kwargs) def destroy(self, request, *args, **kwargs): diff --git a/awx/api/views/metrics.py b/awx/api/views/metrics.py index 092e36efde1b..39744e1bcd79 100644 --- a/awx/api/views/metrics.py +++ b/awx/api/views/metrics.py @@ -22,7 +22,7 @@ ) -logger = logging.getLogger('awx.main.analytics') +logger = logging.getLogger('awx.analytics') class MetricsView(APIView): @@ -39,3 +39,4 @@ def get(self, request): if (request.user.is_superuser or request.user.is_system_auditor): return Response(metrics().decode('UTF-8')) raise PermissionDenied() + diff --git a/awx/api/views/mixin.py b/awx/api/views/mixin.py index e7d4959dfcde..9b57278e2e71 100644 --- a/awx/api/views/mixin.py +++ b/awx/api/views/mixin.py @@ -4,10 +4,7 @@ import dateutil import logging -from django.db.models import ( - Count, - F, -) +from django.db.models import Count from django.db import transaction from django.shortcuts import get_object_or_404 from django.utils.timezone import now @@ -175,28 +172,18 @@ def get_serializer_context(self, *args, **kwargs): inv_qs = Inventory.accessible_objects(self.request.user, 'read_role') project_qs = Project.accessible_objects(self.request.user, 'read_role') + jt_qs = JobTemplate.accessible_objects(self.request.user, 'read_role') # Produce counts of Foreign Key relationships - db_results['inventories'] = inv_qs\ - .values('organization').annotate(Count('organization')).order_by('organization') + db_results['inventories'] = inv_qs.values('organization').annotate(Count('organization')).order_by('organization') db_results['teams'] = Team.accessible_objects( self.request.user, 'read_role').values('organization').annotate( Count('organization')).order_by('organization') - JT_project_reference = 'project__organization' - JT_inventory_reference = 'inventory__organization' - db_results['job_templates_project'] = JobTemplate.accessible_objects( - self.request.user, 'read_role').exclude( - project__organization=F(JT_inventory_reference)).values(JT_project_reference).annotate( - Count(JT_project_reference)).order_by(JT_project_reference) - - db_results['job_templates_inventory'] = JobTemplate.accessible_objects( - self.request.user, 'read_role').values(JT_inventory_reference).annotate( - Count(JT_inventory_reference)).order_by(JT_inventory_reference) + db_results['job_templates'] = jt_qs.values('organization').annotate(Count('organization')).order_by('organization') - db_results['projects'] = project_qs\ - .values('organization').annotate(Count('organization')).order_by('organization') + db_results['projects'] = project_qs.values('organization').annotate(Count('organization')).order_by('organization') # Other members and admins of organization are always viewable db_results['users'] = org_qs.annotate( @@ -212,11 +199,7 @@ def get_serializer_context(self, *args, **kwargs): 'admins': 0, 'projects': 0} for res, count_qs in db_results.items(): - if res == 'job_templates_project': - org_reference = JT_project_reference - elif res == 'job_templates_inventory': - org_reference = JT_inventory_reference - elif res == 'users': + if res == 'users': org_reference = 'id' else: org_reference = 'organization' @@ -229,14 +212,6 @@ def get_serializer_context(self, *args, **kwargs): continue count_context[org_id][res] = entry['%s__count' % org_reference] - # Combine the counts for job templates by project and inventory - for org in org_id_list: - org_id = org['id'] - count_context[org_id]['job_templates'] = 0 - for related_path in ['job_templates_project', 'job_templates_inventory']: - if related_path in count_context[org_id]: - count_context[org_id]['job_templates'] += count_context[org_id].pop(related_path) - full_context['related_field_counts'] = count_context return full_context diff --git a/awx/api/views/organization.py b/awx/api/views/organization.py index e1af4c67b14a..d03dfcc86fc8 100644 --- a/awx/api/views/organization.py +++ b/awx/api/views/organization.py @@ -7,11 +7,13 @@ # Django from django.db.models import Count from django.contrib.contenttypes.models import ContentType +from django.utils.translation import ugettext_lazy as _ # AWX from awx.main.models import ( ActivityStream, Inventory, + Host, Project, JobTemplate, WorkflowJobTemplate, @@ -21,6 +23,7 @@ User, Team, InstanceGroup, + Credential ) from awx.api.generics import ( ListCreateAPIView, @@ -28,6 +31,7 @@ SubListAPIView, SubListCreateAttachDetachAPIView, SubListAttachDetachAPIView, + SubListCreateAPIView, ResourceAccessList, BaseUsersList, ) @@ -35,14 +39,14 @@ from awx.api.serializers import ( OrganizationSerializer, InventorySerializer, - ProjectSerializer, UserSerializer, TeamSerializer, ActivityStreamSerializer, RoleSerializer, NotificationTemplateSerializer, - WorkflowJobTemplateSerializer, InstanceGroupSerializer, + ProjectSerializer, JobTemplateSerializer, WorkflowJobTemplateSerializer, + CredentialSerializer ) from awx.api.views.mixin import ( RelatedJobsPreventDeleteMixin, @@ -94,7 +98,8 @@ def get_serializer_context(self, *args, **kwargs): org_counts['projects'] = Project.accessible_objects(**access_kwargs).filter( organization__id=org_id).count() org_counts['job_templates'] = JobTemplate.accessible_objects(**access_kwargs).filter( - project__organization__id=org_id).count() + organization__id=org_id).count() + org_counts['hosts'] = Host.objects.org_active_count(org_id) full_context['related_field_counts'] = {} full_context['related_field_counts'][org_id] = org_counts @@ -128,21 +133,27 @@ class OrganizationAdminsList(BaseUsersList): ordering = ('username',) -class OrganizationProjectsList(SubListCreateAttachDetachAPIView): +class OrganizationProjectsList(SubListCreateAPIView): model = Project serializer_class = ProjectSerializer parent_model = Organization - relationship = 'projects' parent_key = 'organization' -class OrganizationWorkflowJobTemplatesList(SubListCreateAttachDetachAPIView): +class OrganizationJobTemplatesList(SubListCreateAPIView): + + model = JobTemplate + serializer_class = JobTemplateSerializer + parent_model = Organization + parent_key = 'organization' + + +class OrganizationWorkflowJobTemplatesList(SubListCreateAPIView): model = WorkflowJobTemplate serializer_class = WorkflowJobTemplateSerializer parent_model = Organization - relationship = 'workflows' parent_key = 'organization' @@ -208,6 +219,20 @@ class OrganizationInstanceGroupsList(SubListAttachDetachAPIView): relationship = 'instance_groups' +class OrganizationGalaxyCredentialsList(SubListAttachDetachAPIView): + + model = Credential + serializer_class = CredentialSerializer + parent_model = Organization + relationship = 'galaxy_credentials' + + def is_valid_relation(self, parent, sub, created=False): + if sub.kind != 'galaxy_api_token': + return {'msg': _( + f"Credential must be a Galaxy credential, not {sub.credential_type.name}." + )} + + class OrganizationAccessList(ResourceAccessList): model = User # needs to be User for AccessLists's diff --git a/awx/api/views/root.py b/awx/api/views/root.py index e4e065765250..0f5e7e6cddc5 100644 --- a/awx/api/views/root.py +++ b/awx/api/views/root.py @@ -1,9 +1,10 @@ # Copyright (c) 2018 Ansible, Inc. # All Rights Reserved. +import base64 +import json import logging import operator -import json from collections import OrderedDict from django.conf import settings @@ -20,6 +21,8 @@ import requests from awx.api.generics import APIView +from awx.conf.registry import settings_registry +from awx.main.analytics import all_collectors from awx.main.ha import is_ha_environment from awx.main.utils import ( get_awx_version, @@ -27,8 +30,8 @@ get_custom_venv_choices, to_python_boolean, ) +from awx.main.utils.licensing import validate_entitlement_manifest from awx.api.versioning import reverse, drf_reverse -from awx.conf.license import get_license from awx.main.constants import PRIVILEGE_ESCALATION_METHODS from awx.main.models import ( Project, @@ -37,6 +40,7 @@ InstanceGroup, JobTemplate, ) +from awx.main.utils import set_environ logger = logging.getLogger('awx.api.views.root') @@ -175,7 +179,7 @@ def get(self, request, format=None): class ApiV2SubscriptionView(APIView): permission_classes = (IsAuthenticated,) - name = _('Configuration') + name = _('Subscriptions') swagger_topic = 'System Configuration' def check_permissions(self, request): @@ -186,31 +190,87 @@ def check_permissions(self, request): def post(self, request): from awx.main.utils.common import get_licenser data = request.data.copy() - if data.get('rh_password') == '$encrypted$': - data['rh_password'] = settings.REDHAT_PASSWORD + if data.get('subscriptions_password') == '$encrypted$': + data['subscriptions_password'] = settings.SUBSCRIPTIONS_PASSWORD try: - user, pw = data.get('rh_username'), data.get('rh_password') - validated = get_licenser().validate_rh(user, pw) + user, pw = data.get('subscriptions_username'), data.get('subscriptions_password') + with set_environ(**settings.AWX_TASK_ENV): + validated = get_licenser().validate_rh(user, pw) if user: - settings.REDHAT_USERNAME = data['rh_username'] + settings.SUBSCRIPTIONS_USERNAME = data['subscriptions_username'] if pw: - settings.REDHAT_PASSWORD = data['rh_password'] + settings.SUBSCRIPTIONS_PASSWORD = data['subscriptions_password'] except Exception as exc: - msg = _("Invalid License") + msg = _("Invalid Subscription") if ( isinstance(exc, requests.exceptions.HTTPError) and getattr(getattr(exc, 'response', None), 'status_code', None) == 401 ): msg = _("The provided credentials are invalid (HTTP 401).") - if isinstance(exc, (ValueError, OSError)) and exc.args: + elif isinstance(exc, requests.exceptions.ProxyError): + msg = _("Unable to connect to proxy server.") + elif isinstance(exc, requests.exceptions.ConnectionError): + msg = _("Could not connect to subscription service.") + elif isinstance(exc, (ValueError, OSError)) and exc.args: msg = exc.args[0] - logger.exception(smart_text(u"Invalid license submitted."), - extra=dict(actor=request.user.username)) + else: + logger.exception(smart_text(u"Invalid subscription submitted."), + extra=dict(actor=request.user.username)) return Response({"error": msg}, status=status.HTTP_400_BAD_REQUEST) return Response(validated) +class ApiV2AttachView(APIView): + + permission_classes = (IsAuthenticated,) + name = _('Attach Subscription') + swagger_topic = 'System Configuration' + + def check_permissions(self, request): + super(ApiV2AttachView, self).check_permissions(request) + if not request.user.is_superuser and request.method.lower() not in {'options', 'head'}: + self.permission_denied(request) # Raises PermissionDenied exception. + + def post(self, request): + data = request.data.copy() + pool_id = data.get('pool_id', None) + if not pool_id: + return Response({"error": _("No subscription pool ID provided.")}, status=status.HTTP_400_BAD_REQUEST) + user = getattr(settings, 'SUBSCRIPTIONS_USERNAME', None) + pw = getattr(settings, 'SUBSCRIPTIONS_PASSWORD', None) + if pool_id and user and pw: + from awx.main.utils.common import get_licenser + data = request.data.copy() + try: + with set_environ(**settings.AWX_TASK_ENV): + validated = get_licenser().validate_rh(user, pw) + except Exception as exc: + msg = _("Invalid Subscription") + if ( + isinstance(exc, requests.exceptions.HTTPError) and + getattr(getattr(exc, 'response', None), 'status_code', None) == 401 + ): + msg = _("The provided credentials are invalid (HTTP 401).") + elif isinstance(exc, requests.exceptions.ProxyError): + msg = _("Unable to connect to proxy server.") + elif isinstance(exc, requests.exceptions.ConnectionError): + msg = _("Could not connect to subscription service.") + elif isinstance(exc, (ValueError, OSError)) and exc.args: + msg = exc.args[0] + else: + logger.exception(smart_text(u"Invalid subscription submitted."), + extra=dict(actor=request.user.username)) + return Response({"error": msg}, status=status.HTTP_400_BAD_REQUEST) + for sub in validated: + if sub['pool_id'] == pool_id: + sub['valid_key'] = True + settings.LICENSE = sub + return Response(sub) + + return Response({"error": _("Error processing subscription metadata.")}, status=status.HTTP_400_BAD_REQUEST) + + class ApiV2ConfigView(APIView): permission_classes = (IsAuthenticated,) @@ -225,15 +285,11 @@ def check_permissions(self, request): def get(self, request, format=None): '''Return various sitewide configuration settings''' - if request.user.is_superuser or request.user.is_system_auditor: - license_data = get_license(show_key=True) - else: - license_data = get_license(show_key=False) + from awx.main.utils.common import get_licenser + license_data = get_licenser().validate() + if not license_data.get('valid_key', False): license_data = {} - if license_data and 'features' in license_data and 'activity_streams' in license_data['features']: - # FIXME: Make the final setting value dependent on the feature? - license_data['features']['activity_streams'] &= settings.ACTIVITY_STREAM_ENABLED pendo_state = settings.PENDO_TRACKING_STATE if settings.PENDO_TRACKING_STATE in ('off', 'anonymous', 'detailed') else 'off' @@ -244,6 +300,7 @@ def get(self, request, format=None): ansible_version=get_ansible_version(), eula=render_to_string("eula.md") if license_data.get('license_type', 'UNLICENSED') != 'open' else '', analytics_status=pendo_state, + analytics_collectors=all_collectors(), become_methods=PRIVILEGE_ESCALATION_METHODS, ) @@ -271,9 +328,10 @@ def get(self, request, format=None): return Response(data) + def post(self, request): if not isinstance(request.data, dict): - return Response({"error": _("Invalid license data")}, status=status.HTTP_400_BAD_REQUEST) + return Response({"error": _("Invalid subscription data")}, status=status.HTTP_400_BAD_REQUEST) if "eula_accepted" not in request.data: return Response({"error": _("Missing 'eula_accepted' property")}, status=status.HTTP_400_BAD_REQUEST) try: @@ -290,24 +348,47 @@ def post(self, request): logger.info(smart_text(u"Invalid JSON submitted for license."), extra=dict(actor=request.user.username)) return Response({"error": _("Invalid JSON")}, status=status.HTTP_400_BAD_REQUEST) - try: - from awx.main.utils.common import get_licenser - license_data = json.loads(data_actual) - license_data_validated = get_licenser(**license_data).validate() - except Exception: - logger.warning(smart_text(u"Invalid license submitted."), - extra=dict(actor=request.user.username)) - return Response({"error": _("Invalid License")}, status=status.HTTP_400_BAD_REQUEST) + + from awx.main.utils.common import get_licenser + license_data = json.loads(data_actual) + if 'license_key' in license_data: + return Response({"error": _('Legacy license submitted. A subscription manifest is now required.')}, status=status.HTTP_400_BAD_REQUEST) + if 'manifest' in license_data: + try: + json_actual = json.loads(base64.b64decode(license_data['manifest'])) + if 'license_key' in json_actual: + return Response( + {"error": _('Legacy license submitted. A subscription manifest is now required.')}, + status=status.HTTP_400_BAD_REQUEST + ) + except Exception: + pass + try: + license_data = validate_entitlement_manifest(license_data['manifest']) + except ValueError as e: + return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST) + except Exception: + logger.exception('Invalid manifest submitted. {}') + return Response({"error": _('Invalid manifest submitted.')}, status=status.HTTP_400_BAD_REQUEST) + + try: + license_data_validated = get_licenser().license_from_manifest(license_data) + except Exception: + logger.warning(smart_text(u"Invalid subscription submitted."), + extra=dict(actor=request.user.username)) + return Response({"error": _("Invalid License")}, status=status.HTTP_400_BAD_REQUEST) + else: + license_data_validated = get_licenser().validate() # If the license is valid, write it to the database. if license_data_validated['valid_key']: - settings.LICENSE = license_data - settings.TOWER_URL_BASE = "{}://{}".format(request.scheme, request.get_host()) + if not settings_registry.is_setting_read_only('TOWER_URL_BASE'): + settings.TOWER_URL_BASE = "{}://{}".format(request.scheme, request.get_host()) return Response(license_data_validated) - logger.warning(smart_text(u"Invalid license submitted."), + logger.warning(smart_text(u"Invalid subscription submitted."), extra=dict(actor=request.user.username)) - return Response({"error": _("Invalid license")}, status=status.HTTP_400_BAD_REQUEST) + return Response({"error": _("Invalid subscription")}, status=status.HTTP_400_BAD_REQUEST) def delete(self, request): try: diff --git a/awx/asgi.py b/awx/asgi.py index 3190a7032cac..eb141aabdbf0 100644 --- a/awx/asgi.py +++ b/awx/asgi.py @@ -2,14 +2,14 @@ # All Rights Reserved. import os import logging +import django from awx import __version__ as tower_version - # Prepare the AWX environment. from awx import prepare_env, MODE +from channels.routing import get_default_application # noqa prepare_env() # NOQA -from django.core.wsgi import get_wsgi_application # NOQA -from channels.asgi import get_channel_layer + """ ASGI config for AWX project. @@ -25,13 +25,14 @@ try: fd = open("/var/lib/awx/.tower_version", "r") if fd.read().strip() != tower_version: - raise Exception() - except Exception: + raise ValueError() + except FileNotFoundError: + pass + except ValueError as e: logger.error("Missing or incorrect metadata for Tower version. Ensure Tower was installed using the setup playbook.") - raise Exception("Missing or incorrect metadata for Tower version. Ensure Tower was installed using the setup playbook.") + raise Exception("Missing or incorrect metadata for Tower version. Ensure Tower was installed using the setup playbook.") from e os.environ.setdefault("DJANGO_SETTINGS_MODULE", "awx.settings") - - -channel_layer = get_channel_layer() +django.setup() +channel_layer = get_default_application() diff --git a/awx/conf/fields.py b/awx/conf/fields.py index 82a1e2b8a337..7c9a94969dfa 100644 --- a/awx/conf/fields.py +++ b/awx/conf/fields.py @@ -11,7 +11,7 @@ # Django REST Framework from rest_framework.fields import ( # noqa - BooleanField, CharField, ChoiceField, DictField, EmailField, + BooleanField, CharField, ChoiceField, DictField, DateTimeField, EmailField, IntegerField, ListField, NullBooleanField ) @@ -172,9 +172,9 @@ def run_validators(self, value): netloc = '{}:{}'.format(netloc, url_parts.port) if url_parts.username: if url_parts.password: - netloc = '{}:{}@{}' % (url_parts.username, url_parts.password, netloc) + netloc = '{}:{}@{}'.format(url_parts.username, url_parts.password, netloc) else: - netloc = '{}@{}' % (url_parts.username, netloc) + netloc = '{}@{}'.format(url_parts.username, netloc) value = urlparse.urlunsplit([url_parts.scheme, netloc, url_parts.path, url_parts.query, url_parts.fragment]) except Exception: raise # If something fails here, just fall through and let the validators check it. diff --git a/awx/conf/license.py b/awx/conf/license.py index 6ad1042f9a32..3929c3792156 100644 --- a/awx/conf/license.py +++ b/awx/conf/license.py @@ -1,18 +1,14 @@ # Copyright (c) 2016 Ansible, Inc. # All Rights Reserved. - __all__ = ['get_license'] def _get_validated_license_data(): - from awx.main.utils.common import get_licenser + from awx.main.utils import get_licenser return get_licenser().validate() -def get_license(show_key=False): +def get_license(): """Return a dictionary representing the active license on this Tower instance.""" - license_data = _get_validated_license_data() - if not show_key: - license_data.pop('license_key', None) - return license_data + return _get_validated_license_data() diff --git a/awx/conf/migrations/0007_v380_rename_more_settings.py b/awx/conf/migrations/0007_v380_rename_more_settings.py new file mode 100644 index 000000000000..a57b7ec4bcfb --- /dev/null +++ b/awx/conf/migrations/0007_v380_rename_more_settings.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from django.db import migrations +from awx.conf.migrations import _rename_setting + + +def copy_allowed_ips(apps, schema_editor): + _rename_setting.rename_setting(apps, schema_editor, old_key='PROXY_IP_WHITELIST', new_key='PROXY_IP_ALLOWED_LIST') + + +class Migration(migrations.Migration): + + dependencies = [ + ('conf', '0006_v331_ldap_group_type'), + ] + + operations = [ + migrations.RunPython(copy_allowed_ips), + ] diff --git a/awx/conf/migrations/0008_subscriptions.py b/awx/conf/migrations/0008_subscriptions.py new file mode 100644 index 000000000000..dacd066b4deb --- /dev/null +++ b/awx/conf/migrations/0008_subscriptions.py @@ -0,0 +1,26 @@ +# Generated by Django 2.2.11 on 2020-08-04 15:19 + +import logging + +from django.db import migrations + +from awx.conf.migrations._subscriptions import clear_old_license, prefill_rh_credentials + +logger = logging.getLogger('awx.conf.migrations') + + +def _noop(apps, schema_editor): + pass + + +class Migration(migrations.Migration): + + dependencies = [ + ('conf', '0007_v380_rename_more_settings'), + ] + + + operations = [ + migrations.RunPython(clear_old_license, _noop), + migrations.RunPython(prefill_rh_credentials, _noop) + ] diff --git a/awx/conf/migrations/_subscriptions.py b/awx/conf/migrations/_subscriptions.py new file mode 100644 index 000000000000..2b979fb68eff --- /dev/null +++ b/awx/conf/migrations/_subscriptions.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +import logging +from django.utils.timezone import now +from awx.main.utils.encryption import decrypt_field, encrypt_field + +logger = logging.getLogger('awx.conf.settings') + +__all__ = ['clear_old_license', 'prefill_rh_credentials'] + + +def clear_old_license(apps, schema_editor): + Setting = apps.get_model('conf', 'Setting') + Setting.objects.filter(key='LICENSE').delete() + + +def _migrate_setting(apps, old_key, new_key, encrypted=False): + Setting = apps.get_model('conf', 'Setting') + if not Setting.objects.filter(key=old_key).exists(): + return + new_setting = Setting.objects.create(key=new_key, + created=now(), + modified=now() + ) + if encrypted: + new_setting.value = decrypt_field(Setting.objects.filter(key=old_key).first(), 'value') + new_setting.value = encrypt_field(new_setting, 'value') + else: + new_setting.value = getattr(Setting.objects.filter(key=old_key).first(), 'value') + new_setting.save() + + +def prefill_rh_credentials(apps, schema_editor): + _migrate_setting(apps, 'REDHAT_USERNAME', 'SUBSCRIPTIONS_USERNAME', encrypted=False) + _migrate_setting(apps, 'REDHAT_PASSWORD', 'SUBSCRIPTIONS_PASSWORD', encrypted=True) diff --git a/awx/conf/models.py b/awx/conf/models.py index 2859650f5471..fe28fd89a8e7 100644 --- a/awx/conf/models.py +++ b/awx/conf/models.py @@ -78,14 +78,6 @@ def get_cache_key(self, key): def get_cache_id_key(self, key): return '{}_ID'.format(key) - def display_value(self): - if self.key == 'LICENSE' and 'license_key' in self.value: - # don't log the license key in activity stream - value = self.value.copy() - value['license_key'] = '********' - return value - return self.value - import awx.conf.signals # noqa diff --git a/awx/conf/registry.py b/awx/conf/registry.py index 63336fc55eac..e8e52fe69505 100644 --- a/awx/conf/registry.py +++ b/awx/conf/registry.py @@ -129,12 +129,14 @@ def get_setting_field(self, setting, mixin_class=None, for_user=False, **kwargs) placeholder = field_kwargs.pop('placeholder', empty) encrypted = bool(field_kwargs.pop('encrypted', False)) defined_in_file = bool(field_kwargs.pop('defined_in_file', False)) + unit = field_kwargs.pop('unit', None) if getattr(field_kwargs.get('child', None), 'source', None) is not None: field_kwargs['child'].source = None field_instance = field_class(**field_kwargs) field_instance.category_slug = category_slug field_instance.category = category field_instance.depends_on = depends_on + field_instance.unit = unit if placeholder is not empty: field_instance.placeholder = placeholder field_instance.defined_in_file = defined_in_file diff --git a/awx/conf/settings.py b/awx/conf/settings.py index a35a5354c869..4b18e3d9f657 100644 --- a/awx/conf/settings.py +++ b/awx/conf/settings.py @@ -1,14 +1,10 @@ # Python -from collections import namedtuple import contextlib import logging -import re import sys import threading import time -import traceback -import urllib.parse -from io import StringIO +import os # Django from django.conf import LazySettings @@ -16,40 +12,42 @@ from django.core.cache import cache as django_cache from django.core.exceptions import ImproperlyConfigured from django.db import transaction, connection -from django.db.utils import Error as DBError +from django.db.utils import Error as DBError, ProgrammingError from django.utils.functional import cached_property # Django REST Framework from rest_framework.fields import empty, SkipField +import cachetools + # Tower from awx.main.utils import encrypt_field, decrypt_field from awx.conf import settings_registry from awx.conf.models import Setting from awx.conf.migrations._reencrypt import decrypt_field as old_decrypt_field -import cachetools - # FIXME: Gracefully handle when settings are accessed before the database is # ready (or during migrations). logger = logging.getLogger('awx.conf.settings') +SETTING_MEMORY_TTL = 5 if 'callback_receiver' in ' '.join(sys.argv) else 0 + # Store a special value to indicate when a setting is not set in the database. SETTING_CACHE_NOTSET = '___notset___' -# Cannot store None in memcached; use a special value instead to indicate None. +# Cannot store None in cache; use a special value instead to indicate None. # If the special value for None is the same as the "not set" value, then a value # of None will be equivalent to the setting not being set (and will raise an # AttributeError if there is no other default defined). # SETTING_CACHE_NONE = '___none___' SETTING_CACHE_NONE = SETTING_CACHE_NOTSET -# Cannot store empty list/tuple in memcached; use a special value instead to +# Cannot store empty list/tuple in cache; use a special value instead to # indicate an empty list. SETTING_CACHE_EMPTY_LIST = '___[]___' -# Cannot store empty dict in memcached; use a special value instead to indicate +# Cannot store empty dict in cache; use a special value instead to indicate # an empty dict. SETTING_CACHE_EMPTY_DICT = '___{}___' @@ -62,15 +60,6 @@ __all__ = ['SettingsWrapper', 'get_settings_to_cache', 'SETTING_CACHE_NOTSET'] -def normalize_broker_url(value): - parts = value.rsplit('@', 1) - match = re.search('(amqp://[^:]+:)(.*)', parts[0]) - if match: - prefix, password = match.group(1), match.group(2) - parts[0] = prefix + urllib.parse.quote(password) - return '@'.join(parts) - - @contextlib.contextmanager def _ctit_db_wrapper(trans_safe=False): ''' @@ -90,43 +79,21 @@ def _ctit_db_wrapper(trans_safe=False): logger.debug('Obtaining database settings in spite of broken transaction.') transaction.set_rollback(False) yield - except DBError: - # We want the _full_ traceback with the context - # First we get the current call stack, which constitutes the "top", - # it has the context up to the point where the context manager is used - top_stack = StringIO() - traceback.print_stack(file=top_stack) - top_lines = top_stack.getvalue().strip('\n').split('\n') - top_stack.close() - # Get "bottom" stack from the local error that happened - # inside of the "with" block this wraps - exc_type, exc_value, exc_traceback = sys.exc_info() - bottom_stack = StringIO() - traceback.print_tb(exc_traceback, file=bottom_stack) - bottom_lines = bottom_stack.getvalue().strip('\n').split('\n') - # Glue together top and bottom where overlap is found - bottom_cutoff = 0 - for i, line in enumerate(bottom_lines): - if line in top_lines: - # start of overlapping section, take overlap from bottom - top_lines = top_lines[:top_lines.index(line)] - bottom_cutoff = i - break - bottom_lines = bottom_lines[bottom_cutoff:] - tb_lines = top_lines + bottom_lines - - tb_string = '\n'.join( - ['Traceback (most recent call last):'] + - tb_lines + - ['{}: {}'.format(exc_type.__name__, str(exc_value))] - ) - bottom_stack.close() - # Log the combined stack + except DBError as exc: if trans_safe: - if 'check_migrations' not in sys.argv: - logger.debug('Database settings are not available, using defaults, error:\n{}'.format(tb_string)) + if 'migrate' not in sys.argv and 'check_migrations' not in sys.argv: + level = logger.exception + if isinstance(exc, ProgrammingError): + if 'relation' in str(exc) and 'does not exist' in str(exc): + # this generally means we can't fetch Tower configuration + # because the database hasn't actually finished migrating yet; + # this is usually a sign that a service in a container (such as ws_broadcast) + # has come up *before* the database has finished migrating, and + # especially that the conf.settings table doesn't exist yet + level = logger.debug + level('Database settings are not available, using defaults.') else: - logger.debug('Error modifying something related to database settings.\n{}'.format(tb_string)) + logger.exception('Error modifying something related to database settings.') finally: if trans_safe and is_atomic and rollback_set: transaction.set_rollback(rollback_set) @@ -138,12 +105,13 @@ def filter_sensitive(registry, key, value): return value -# settings.__getattr__ is called *constantly*, and the LOG_AGGREGATOR_ ones are -# so ubiquitous when external logging is enabled that they should kept in memory -# with a short TTL to avoid even having to contact memcached -# the primary use case for this optimization is the callback receiver -# when external logging is enabled -LOGGING_SETTINGS_CACHE = cachetools.TTLCache(maxsize=50, ttl=1) +class TransientSetting(object): + + __slots__ = ('pk', 'value') + + def __init__(self, pk, value): + self.pk = pk + self.value = value class EncryptedCacheProxy(object): @@ -173,7 +141,6 @@ def __init__(self, cache, registry, encrypter=None, decrypter=None): def get(self, key, **kwargs): value = self.cache.get(key, **kwargs) value = self._handle_encryption(self.decrypter, key, value) - logger.debug('cache get(%r, %r) -> %r', key, empty, filter_sensitive(self.registry, key, value)) return value def set(self, key, value, log=True, **kwargs): @@ -196,8 +163,6 @@ def set_many(self, data, **kwargs): self.set(key, value, log=False, **kwargs) def _handle_encryption(self, method, key, value): - TransientSetting = namedtuple('TransientSetting', ['pk', 'value']) - if value is not empty and self.registry.is_setting_encrypted(key): # If the setting exists in the database, we'll use its primary key # as part of the AES key when encrypting/decrypting @@ -283,6 +248,7 @@ def __init__(self, default_settings, cache, registry): # These values have to be stored via self.__dict__ in this way to get # around the magic __setattr__ method on this class (which is used to # store API-assigned settings in the database). + self.__dict__['__forks__'] = {} self.__dict__['default_settings'] = default_settings self.__dict__['_awx_conf_settings'] = self self.__dict__['_awx_conf_preload_expires'] = None @@ -291,6 +257,26 @@ def __init__(self, default_settings, cache, registry): self.__dict__['cache'] = EncryptedCacheProxy(cache, registry) self.__dict__['registry'] = registry + # record the current pid so we compare it post-fork for + # processes like the dispatcher and callback receiver + self.__dict__['pid'] = os.getpid() + + def __clean_on_fork__(self): + pid = os.getpid() + # if the current pid does *not* match the value on self, it means + # that value was copied on fork, and we're now in a *forked* process; + # the *first* time we enter this code path (on setting access), + # forcibly close DB/cache sockets and set a marker so we don't run + # this code again _in this process_ + # + if pid != self.__dict__['pid'] and pid not in self.__dict__['__forks__']: + self.__dict__['__forks__'][pid] = True + # It's important to close these post-fork, because we + # don't want the forked processes to inherit the open sockets + # for the DB and cache connections (that way lies race conditions) + connection.close() + django_cache.close() + @cached_property def all_supported_settings(self): return self.registry.get_registered_settings() @@ -366,6 +352,7 @@ def _preload_cache(self): self.cache.set_many(settings_to_cache, timeout=SETTING_CACHE_TIMEOUT) def _get_local(self, name, validate=True): + self.__clean_on_fork__() self._preload_cache() cache_key = Setting.get_cache_key(name) try: @@ -446,35 +433,21 @@ def _get_default(self, name): def SETTINGS_MODULE(self): return self._get_default('SETTINGS_MODULE') + @cachetools.cached(cache=cachetools.TTLCache(maxsize=2048, ttl=SETTING_MEMORY_TTL)) def __getattr__(self, name): - if name.startswith('LOG_AGGREGATOR_'): - cached = LOGGING_SETTINGS_CACHE.get(name) - if cached: - return cached value = empty if name in self.all_supported_settings: with _ctit_db_wrapper(trans_safe=True): value = self._get_local(name) if value is not empty: - if name.startswith('LOG_AGGREGATOR_'): - LOGGING_SETTINGS_CACHE[name] = value return value - value = self._get_default(name) - # sometimes users specify RabbitMQ passwords that contain - # unescaped : and @ characters that confused urlparse, e.g., - # amqp://guest:a@ns:ibl3#@localhost:5672// - # - # detect these scenarios, and automatically escape the user's - # password so it just works - if name == 'BROKER_URL': - value = normalize_broker_url(value) - return value + return self._get_default(name) def _set_local(self, name, value): field = self.registry.get_setting_field(name) if field.read_only: logger.warning('Attempt to set read only setting "%s".', name) - raise ImproperlyConfigured('Setting "%s" is read only.'.format(name)) + raise ImproperlyConfigured('Setting "{}" is read only.'.format(name)) try: data = field.to_representation(value) @@ -505,7 +478,7 @@ def _del_local(self, name): field = self.registry.get_setting_field(name) if field.read_only: logger.warning('Attempt to delete read only setting "%s".', name) - raise ImproperlyConfigured('Setting "%s" is read only.'.format(name)) + raise ImproperlyConfigured('Setting "{}" is read only.'.format(name)) for setting in Setting.objects.filter(key=name, user__isnull=True): setting.delete() # pre_delete handler will delete from cache. diff --git a/awx/conf/tests/functional/test_api.py b/awx/conf/tests/functional/test_api.py index d7bb06a1bffc..869627878a0d 100644 --- a/awx/conf/tests/functional/test_api.py +++ b/awx/conf/tests/functional/test_api.py @@ -325,17 +325,3 @@ def test_setting_singleton_delete_no_read_only_fields(api_request, dummy_setting ) assert response.data['FOO_BAR'] == 23 - -@pytest.mark.django_db -def test_setting_logging_test(api_request): - with mock.patch('awx.conf.views.AWXProxyHandler.perform_test') as mock_func: - api_request( - 'post', - reverse('api:setting_logging_test'), - data={'LOG_AGGREGATOR_HOST': 'http://foobar', 'LOG_AGGREGATOR_TYPE': 'logstash'} - ) - call = mock_func.call_args_list[0] - args, kwargs = call - given_settings = kwargs['custom_settings'] - assert given_settings.LOG_AGGREGATOR_HOST == 'http://foobar' - assert given_settings.LOG_AGGREGATOR_TYPE == 'logstash' diff --git a/awx/conf/tests/unit/test_registry.py b/awx/conf/tests/unit/test_registry.py index c25ea0072485..ea5c66375f71 100644 --- a/awx/conf/tests/unit/test_registry.py +++ b/awx/conf/tests/unit/test_registry.py @@ -29,9 +29,10 @@ def reg(request): # as "defined in a settings file". This is analogous to manually # specifying a setting on the filesystem (e.g., in a local_settings.py in # development, or in /etc/tower/conf.d/.py) - defaults = request.node.get_marker('defined_in_file') - if defaults: - settings.configure(**defaults.kwargs) + for marker in request.node.own_markers: + if marker.name == 'defined_in_file': + settings.configure(**marker.kwargs) + settings._wrapped = SettingsWrapper(settings._wrapped, cache, registry) diff --git a/awx/conf/tests/unit/test_settings.py b/awx/conf/tests/unit/test_settings.py index a95cbe54f7db..7e3058e344d7 100644 --- a/awx/conf/tests/unit/test_settings.py +++ b/awx/conf/tests/unit/test_settings.py @@ -41,13 +41,16 @@ def settings(request): cache = LocMemCache(str(uuid4()), {}) # make a new random cache each time settings = LazySettings() registry = SettingsRegistry(settings) + defaults = {} # @pytest.mark.defined_in_file can be used to mark specific setting values # as "defined in a settings file". This is analogous to manually # specifying a setting on the filesystem (e.g., in a local_settings.py in # development, or in /etc/tower/conf.d/.py) - in_file_marker = request.node.get_marker('defined_in_file') - defaults = in_file_marker.kwargs if in_file_marker else {} + for marker in request.node.own_markers: + if marker.name == 'defined_in_file': + defaults = marker.kwargs + defaults['DEFAULTS_SNAPSHOT'] = {} settings.configure(**defaults) settings._wrapped = SettingsWrapper(settings._wrapped, @@ -63,15 +66,6 @@ def test_unregistered_setting(settings): assert settings.cache.get('DEBUG') is None -def test_cached_settings_unicode_is_auto_decoded(settings): - # https://github.com/linsomniac/python-memcached/issues/79 - # https://github.com/linsomniac/python-memcached/blob/288c159720eebcdf667727a859ef341f1e908308/memcache.py#L961 - - value = 'Iñtërnâtiônàlizætiøn' # this simulates what python-memcached does on cache.set() - settings.cache.set('DEBUG', value) - assert settings.cache.get('DEBUG') == 'Iñtërnâtiônàlizætiøn' - - def test_read_only_setting(settings): settings.registry.register( 'AWX_READ_ONLY', @@ -251,31 +245,6 @@ def test_setting_from_db(settings, mocker): assert settings.cache.get('AWX_SOME_SETTING') == 'FROM_DB' -@pytest.mark.parametrize('encrypted', (True, False)) -def test_setting_from_db_with_unicode(settings, mocker, encrypted): - settings.registry.register( - 'AWX_SOME_SETTING', - field_class=fields.CharField, - category=_('System'), - category_slug='system', - default='DEFAULT', - encrypted=encrypted - ) - # this simulates a bug in python-memcached; see https://github.com/linsomniac/python-memcached/issues/79 - value = 'Iñtërnâtiônàlizætiøn' - - setting_from_db = mocker.Mock(id=1, key='AWX_SOME_SETTING', value=value) - mocks = mocker.Mock(**{ - 'order_by.return_value': mocker.Mock(**{ - '__iter__': lambda self: iter([setting_from_db]), - 'first.return_value': setting_from_db - }), - }) - with mocker.patch('awx.conf.models.Setting.objects.filter', return_value=mocks): - assert settings.AWX_SOME_SETTING == 'Iñtërnâtiônàlizætiøn' - assert settings.cache.get('AWX_SOME_SETTING') == 'Iñtërnâtiônàlizætiøn' - - @pytest.mark.defined_in_file(AWX_SOME_SETTING='DEFAULT') def test_read_only_setting_assignment(settings): "read-only settings cannot be overwritten" diff --git a/awx/conf/views.py b/awx/conf/views.py index 13b72a926f5c..18f8a6d2d539 100644 --- a/awx/conf/views.py +++ b/awx/conf/views.py @@ -3,15 +3,20 @@ # Python import collections +import logging +import subprocess import sys +import socket +from socket import SHUT_RDWR # Django +from django.db import connection from django.conf import settings from django.http import Http404 from django.utils.translation import ugettext_lazy as _ # Django REST Framework -from rest_framework.exceptions import PermissionDenied, ValidationError +from rest_framework.exceptions import PermissionDenied from rest_framework.response import Response from rest_framework import serializers from rest_framework import status @@ -26,7 +31,6 @@ from awx.api.permissions import IsSuperUser from awx.api.versioning import reverse from awx.main.utils import camelcase_to_underscore -from awx.main.utils.handlers import AWXProxyHandler, LoggingConnectivityException from awx.main.tasks import handle_setting_changes from awx.conf.models import Setting from awx.conf.serializers import SettingCategorySerializer, SettingSingletonSerializer @@ -127,7 +131,8 @@ def perform_update(self, serializer): setting.save(update_fields=['value']) settings_change_list.append(key) if settings_change_list: - handle_setting_changes.delay(settings_change_list) + connection.on_commit(lambda: handle_setting_changes.delay(settings_change_list)) + def destroy(self, request, *args, **kwargs): instance = self.get_object() @@ -142,7 +147,7 @@ def perform_destroy(self, instance): setting.delete() settings_change_list.append(setting.key) if settings_change_list: - handle_setting_changes.delay(settings_change_list) + connection.on_commit(lambda: handle_setting_changes.delay(settings_change_list)) # When TOWER_URL_BASE is deleted from the API, reset it to the hostname # used to make the request as a default. @@ -161,40 +166,53 @@ class SettingLoggingTest(GenericAPIView): filter_backends = [] def post(self, request, *args, **kwargs): - defaults = dict() - for key in settings_registry.get_registered_settings(category_slug='logging'): - try: - defaults[key] = settings_registry.get_setting_field(key).get_default() - except serializers.SkipField: - defaults[key] = None - obj = type('Settings', (object,), defaults)() - serializer = self.get_serializer(obj, data=request.data) - serializer.is_valid(raise_exception=True) - # Special validation specific to logging test. - errors = {} - for key in ['LOG_AGGREGATOR_TYPE', 'LOG_AGGREGATOR_HOST']: - if not request.data.get(key, ''): - errors[key] = 'This field is required.' - if errors: - raise ValidationError(errors) - - if request.data.get('LOG_AGGREGATOR_PASSWORD', '').startswith('$encrypted$'): - serializer.validated_data['LOG_AGGREGATOR_PASSWORD'] = getattr( - settings, 'LOG_AGGREGATOR_PASSWORD', '' + # Error if logging is not enabled + enabled = getattr(settings, 'LOG_AGGREGATOR_ENABLED', False) + if not enabled: + return Response({'error': 'Logging not enabled'}, status=status.HTTP_409_CONFLICT) + + # Send test message to configured logger based on db settings + try: + default_logger = settings.LOG_AGGREGATOR_LOGGERS[0] + if default_logger != 'awx': + default_logger = f'awx.analytics.{default_logger}' + except IndexError: + default_logger = 'awx' + logging.getLogger(default_logger).error('AWX Connection Test Message') + + hostname = getattr(settings, 'LOG_AGGREGATOR_HOST', None) + protocol = getattr(settings, 'LOG_AGGREGATOR_PROTOCOL', None) + + try: + subprocess.check_output( + ['rsyslogd', '-N1', '-f', '/var/lib/awx/rsyslog/rsyslog.conf'], + stderr=subprocess.STDOUT ) + except subprocess.CalledProcessError as exc: + return Response({'error': exc.output}, status=status.HTTP_400_BAD_REQUEST) + + # Check to ensure port is open at host + if protocol in ['udp', 'tcp']: + port = getattr(settings, 'LOG_AGGREGATOR_PORT', None) + # Error if port is not set when using UDP/TCP + if not port: + return Response({'error': 'Port required for ' + protocol}, status=status.HTTP_400_BAD_REQUEST) + else: + # if http/https by this point, domain is reacheable + return Response(status=status.HTTP_202_ACCEPTED) + if protocol == 'udp': + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + else: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: - class MockSettings: - pass - mock_settings = MockSettings() - for k, v in serializer.validated_data.items(): - setattr(mock_settings, k, v) - AWXProxyHandler().perform_test(custom_settings=mock_settings) - if mock_settings.LOG_AGGREGATOR_PROTOCOL.upper() == 'UDP': - return Response(status=status.HTTP_201_CREATED) - except LoggingConnectivityException as e: - return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) - return Response(status=status.HTTP_200_OK) + s.settimeout(.5) + s.connect((hostname, int(port))) + s.shutdown(SHUT_RDWR) + s.close() + return Response(status=status.HTTP_202_ACCEPTED) + except Exception as e: + return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST) # Create view functions for all of the class-based views to simplify inclusion diff --git a/awx/locale/django.pot b/awx/locale/django.pot index a07c8e72ecb9..e5fbe0539082 100644 --- a/awx/locale/django.pot +++ b/awx/locale/django.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-11-27 17:28+0000\n" +"POT-Creation-Date: 2020-10-05 19:43+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -27,47 +27,56 @@ msgid "" "again." msgstr "" -#: awx/api/conf.py:17 awx/api/conf.py:26 awx/api/conf.py:34 awx/api/conf.py:47 -#: awx/api/conf.py:59 awx/sso/conf.py:85 awx/sso/conf.py:96 awx/sso/conf.py:108 -#: awx/sso/conf.py:123 +#: awx/api/conf.py:17 awx/api/conf.py:27 awx/api/conf.py:35 awx/api/conf.py:51 +#: awx/api/conf.py:64 awx/api/conf.py:76 awx/sso/conf.py:97 awx/sso/conf.py:108 +#: awx/sso/conf.py:120 awx/sso/conf.py:135 msgid "Authentication" msgstr "" -#: awx/api/conf.py:24 -msgid "Maximum number of simultaneous logged in sessions" +#: awx/api/conf.py:19 awx/api/conf.py:53 awx/main/conf.py:256 +#: awx/main/conf.py:268 awx/main/conf.py:281 awx/main/conf.py:503 +#: awx/main/conf.py:516 awx/main/conf.py:529 awx/main/conf.py:544 +#: awx/main/conf.py:682 awx/main/conf.py:764 awx/sso/conf.py:518 +msgid "seconds" msgstr "" #: awx/api/conf.py:25 +msgid "Maximum number of simultaneous logged in sessions" +msgstr "" + +#: awx/api/conf.py:26 msgid "" "Maximum number of simultaneous logged in sessions a user may have. To " "disable enter -1." msgstr "" -#: awx/api/conf.py:32 +#: awx/api/conf.py:33 msgid "Enable HTTP Basic Auth" msgstr "" -#: awx/api/conf.py:33 +#: awx/api/conf.py:34 msgid "Enable HTTP Basic Auth for the API Browser." msgstr "" -#: awx/api/conf.py:42 +#: awx/api/conf.py:44 msgid "OAuth 2 Timeout Settings" msgstr "" -#: awx/api/conf.py:43 +#: awx/api/conf.py:45 msgid "" "Dictionary for customizing OAuth 2 timeouts, available items are " "`ACCESS_TOKEN_EXPIRE_SECONDS`, the duration of access tokens in the number " -"of seconds, and `AUTHORIZATION_CODE_EXPIRE_SECONDS`, the duration of " -"authorization grants in the number of seconds." +"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." msgstr "" -#: awx/api/conf.py:54 +#: awx/api/conf.py:59 msgid "Allow External Users to Create OAuth2 Tokens" msgstr "" -#: awx/api/conf.py:55 +#: awx/api/conf.py:60 msgid "" "For security reasons, users from external auth providers (LDAP, SAML, SSO, " "Radius, and others) are not allowed to create OAuth2 tokens. To change this " @@ -75,6 +84,16 @@ msgid "" "setting is toggled off." msgstr "" +#: awx/api/conf.py:73 +msgid "Login redirect override URL" +msgstr "" + +#: awx/api/conf.py:74 +msgid "" +"URL to which unauthorized users will be redirected to log in. If blank, " +"users will be sent to the Tower login page." +msgstr "" + #: awx/api/exceptions.py:20 msgid "Resource is being used by running jobs." msgstr "" @@ -84,98 +103,95 @@ msgstr "" msgid "Invalid key names: {invalid_key_names}" msgstr "" -#: awx/api/fields.py:107 +#: awx/api/fields.py:111 msgid "Credential {} does not exist" msgstr "" -#: awx/api/filters.py:96 +#: awx/api/filters.py:82 msgid "No related model for field {}." msgstr "" -#: awx/api/filters.py:113 +#: awx/api/filters.py:99 msgid "Filtering on password fields is not allowed." msgstr "" -#: awx/api/filters.py:125 awx/api/filters.py:127 +#: awx/api/filters.py:111 awx/api/filters.py:113 #, python-format msgid "Filtering on %s is not allowed." msgstr "" -#: awx/api/filters.py:130 +#: awx/api/filters.py:116 msgid "Loops not allowed in filters, detected on field {}." msgstr "" -#: awx/api/filters.py:159 +#: awx/api/filters.py:160 msgid "Query string field name not provided." msgstr "" -#: awx/api/filters.py:186 +#: awx/api/filters.py:192 #, python-brace-format msgid "Invalid {field_name} id: {field_id}" msgstr "" -#: awx/api/filters.py:325 -#, python-format -msgid "cannot filter on kind %s" -msgstr "" - -#: awx/api/filters.py:351 +#: awx/api/filters.py:338 msgid "" "Cannot apply role_level filter to this list because its model does not use " "roles for access control." msgstr "" -#: awx/api/generics.py:196 +#: awx/api/generics.py:183 msgid "" "You did not use correct Content-Type in your HTTP request. If you are using " "our REST API, the Content-Type must be application/json" msgstr "" -#: awx/api/generics.py:632 awx/api/generics.py:694 +#: awx/api/generics.py:647 awx/api/generics.py:709 msgid "\"id\" field must be an integer." msgstr "" -#: awx/api/generics.py:691 +#: awx/api/generics.py:706 msgid "\"id\" is required to disassociate" msgstr "" -#: awx/api/generics.py:742 +#: awx/api/generics.py:757 msgid "{} 'id' field is missing." msgstr "" -#: awx/api/metadata.py:51 +#: awx/api/metadata.py:58 msgid "Database ID for this {}." msgstr "" -#: awx/api/metadata.py:52 +#: awx/api/metadata.py:59 msgid "Name of this {}." msgstr "" -#: awx/api/metadata.py:53 +#: awx/api/metadata.py:60 msgid "Optional description of this {}." msgstr "" -#: awx/api/metadata.py:54 +#: awx/api/metadata.py:61 msgid "Data type for this {}." msgstr "" -#: awx/api/metadata.py:55 +#: awx/api/metadata.py:62 msgid "URL for this {}." msgstr "" -#: awx/api/metadata.py:56 +#: awx/api/metadata.py:63 msgid "Data structure with URLs of related resources." msgstr "" -#: awx/api/metadata.py:57 -msgid "Data structure with name/description for related resources." +#: awx/api/metadata.py:64 +msgid "" +"Data structure with name/description for related resources. The output for " +"some objects may be limited for performance reasons." msgstr "" -#: awx/api/metadata.py:58 +#: awx/api/metadata.py:66 msgid "Timestamp when this {} was created." msgstr "" -#: awx/api/metadata.py:59 +#: awx/api/metadata.py:67 msgid "Timestamp when this {} was last modified." msgstr "" @@ -190,1197 +206,1327 @@ msgid "" "Possible cause: trailing comma." msgstr "" -#: awx/api/serializers.py:155 +#: awx/api/serializers.py:169 msgid "" "The original object is already named {}, a copy from it cannot have the same " "name." msgstr "" -#: awx/api/serializers.py:290 +#: awx/api/serializers.py:302 #, python-format msgid "Cannot use dictionary for %s" msgstr "" -#: awx/api/serializers.py:307 +#: awx/api/serializers.py:316 msgid "Playbook Run" msgstr "" -#: awx/api/serializers.py:308 +#: awx/api/serializers.py:317 msgid "Command" msgstr "" -#: awx/api/serializers.py:309 awx/main/models/unified_jobs.py:550 +#: awx/api/serializers.py:318 awx/main/models/unified_jobs.py:546 msgid "SCM Update" msgstr "" -#: awx/api/serializers.py:310 +#: awx/api/serializers.py:319 msgid "Inventory Sync" msgstr "" -#: awx/api/serializers.py:311 +#: awx/api/serializers.py:320 msgid "Management Job" msgstr "" -#: awx/api/serializers.py:312 +#: awx/api/serializers.py:321 msgid "Workflow Job" msgstr "" -#: awx/api/serializers.py:313 +#: awx/api/serializers.py:322 msgid "Workflow Template" msgstr "" -#: awx/api/serializers.py:314 +#: awx/api/serializers.py:323 msgid "Job Template" msgstr "" -#: awx/api/serializers.py:714 +#: awx/api/serializers.py:709 msgid "" "Indicates whether all of the events generated by this unified job have been " "saved to the database." msgstr "" -#: awx/api/serializers.py:879 +#: awx/api/serializers.py:880 msgid "Write-only field used to change the password." msgstr "" -#: awx/api/serializers.py:881 +#: awx/api/serializers.py:882 msgid "Set if the account is managed by an external service" msgstr "" -#: awx/api/serializers.py:905 +#: awx/api/serializers.py:909 msgid "Password required for new User." msgstr "" -#: awx/api/serializers.py:980 +#: awx/api/serializers.py:994 #, python-format msgid "Unable to change %s on user managed by LDAP." msgstr "" -#: awx/api/serializers.py:1066 +#: awx/api/serializers.py:1090 msgid "Must be a simple space-separated string with allowed scopes {}." msgstr "" -#: awx/api/serializers.py:1164 +#: awx/api/serializers.py:1188 msgid "Authorization Grant Type" msgstr "" -#: awx/api/serializers.py:1166 awx/main/models/credential/__init__.py:1061 +#: awx/api/serializers.py:1190 awx/main/credential_plugins/azure_kv.py:30 +#: awx/main/models/credential/__init__.py:972 msgid "Client Secret" msgstr "" -#: awx/api/serializers.py:1169 +#: awx/api/serializers.py:1193 msgid "Client Type" msgstr "" -#: awx/api/serializers.py:1172 +#: awx/api/serializers.py:1196 msgid "Redirect URIs" msgstr "" -#: awx/api/serializers.py:1175 +#: awx/api/serializers.py:1199 msgid "Skip Authorization" msgstr "" -#: awx/api/serializers.py:1290 +#: awx/api/serializers.py:1306 +msgid "Cannot change max_hosts." +msgstr "" + +#: awx/api/serializers.py:1339 msgid "This path is already being used by another manual project." msgstr "" -#: awx/api/serializers.py:1371 -msgid "Organization is missing" +#: awx/api/serializers.py:1341 +msgid "SCM branch cannot be used with archive projects." +msgstr "" + +#: awx/api/serializers.py:1343 +msgid "SCM refspec can only be used with git projects." +msgstr "" + +#: awx/api/serializers.py:1420 +msgid "" +"One or more job templates depend on branch override behavior for this " +"project (ids: {})." msgstr "" -#: awx/api/serializers.py:1375 +#: awx/api/serializers.py:1427 msgid "Update options must be set to false for manual projects." msgstr "" -#: awx/api/serializers.py:1381 +#: awx/api/serializers.py:1433 msgid "Array of playbooks available within this project." msgstr "" -#: awx/api/serializers.py:1400 +#: awx/api/serializers.py:1452 msgid "" "Array of inventory files and directories available within this project, not " "comprehensive." msgstr "" -#: awx/api/serializers.py:1448 awx/api/serializers.py:3291 -#: awx/api/serializers.py:3498 +#: awx/api/serializers.py:1500 awx/api/serializers.py:3089 +#: awx/api/serializers.py:3301 msgid "A count of hosts uniquely assigned to each status." msgstr "" -#: awx/api/serializers.py:1451 awx/api/serializers.py:3294 +#: awx/api/serializers.py:1503 awx/api/serializers.py:3092 msgid "A count of all plays and tasks for the job run." msgstr "" -#: awx/api/serializers.py:1505 awx/api/serializers.py:1732 -#: awx/api/serializers.py:3135 awx/api/serializers.py:3138 -#: awx/api/serializers.py:3141 awx/api/serializers.py:3144 -#: awx/api/serializers.py:3147 awx/api/serializers.py:3150 -#: awx/api/serializers.py:3153 awx/api/serializers.py:3156 -#: awx/api/serializers.py:3159 -msgid "This field has been deprecated and will be removed in a future release" -msgstr "" - -#: awx/api/serializers.py:1572 +#: awx/api/serializers.py:1630 msgid "Smart inventories must specify host_filter" msgstr "" -#: awx/api/serializers.py:1676 +#: awx/api/serializers.py:1722 #, python-format msgid "Invalid port specification: %s" msgstr "" -#: awx/api/serializers.py:1687 +#: awx/api/serializers.py:1733 msgid "Cannot create Host for Smart Inventory" msgstr "" -#: awx/api/serializers.py:1804 +#: awx/api/serializers.py:1751 +msgid "A Group with that name already exists." +msgstr "" + +#: awx/api/serializers.py:1822 +msgid "A Host with that name already exists." +msgstr "" + +#: awx/api/serializers.py:1827 msgid "Invalid group name." msgstr "" -#: awx/api/serializers.py:1809 +#: awx/api/serializers.py:1832 msgid "Cannot create Group for Smart Inventory" msgstr "" -#: awx/api/serializers.py:1884 +#: awx/api/serializers.py:1907 msgid "" "Script must begin with a hashbang sequence: i.e.... #!/usr/bin/env python" msgstr "" -#: awx/api/serializers.py:1914 +#: awx/api/serializers.py:1936 msgid "Cloud credential to use for inventory updates." msgstr "" -#: awx/api/serializers.py:1935 +#: awx/api/serializers.py:1957 msgid "`{}` is a prohibited environment variable" msgstr "" -#: awx/api/serializers.py:1946 +#: awx/api/serializers.py:1968 msgid "If 'source' is 'custom', 'source_script' must be provided." msgstr "" -#: awx/api/serializers.py:1952 +#: awx/api/serializers.py:1974 msgid "Must provide an inventory." msgstr "" -#: awx/api/serializers.py:1956 +#: awx/api/serializers.py:1978 msgid "" "The 'source_script' does not belong to the same organization as the " "inventory." msgstr "" -#: awx/api/serializers.py:1958 +#: awx/api/serializers.py:1980 msgid "'source_script' doesn't exist." msgstr "" -#: awx/api/serializers.py:1994 -msgid "Automatic group relationship, will be removed in 3.3" -msgstr "" - -#: awx/api/serializers.py:2081 +#: awx/api/serializers.py:2082 msgid "Cannot use manual project for SCM-based inventory." msgstr "" #: awx/api/serializers.py:2087 -msgid "" -"Manual inventory sources are created automatically when a group is created " -"in the v1 API." +msgid "Setting not compatible with existing schedules." msgstr "" #: awx/api/serializers.py:2092 -msgid "Setting not compatible with existing schedules." +msgid "Cannot create Inventory Source for Smart Inventory" msgstr "" -#: awx/api/serializers.py:2097 -msgid "Cannot create Inventory Source for Smart Inventory" +#: awx/api/serializers.py:2140 +msgid "Project required for scm type sources." msgstr "" -#: awx/api/serializers.py:2148 +#: awx/api/serializers.py:2149 #, python-format msgid "Cannot set %s if not SCM type." msgstr "" -#: awx/api/serializers.py:2423 +#: awx/api/serializers.py:2219 +msgid "The project used for this job." +msgstr "" + +#: awx/api/serializers.py:2475 msgid "Modifications not allowed for managed credential types" msgstr "" -#: awx/api/serializers.py:2428 +#: awx/api/serializers.py:2487 msgid "" "Modifications to inputs are not allowed for credential types that are in use" msgstr "" -#: awx/api/serializers.py:2434 +#: awx/api/serializers.py:2492 #, python-format msgid "Must be 'cloud' or 'net', not %s" msgstr "" -#: awx/api/serializers.py:2440 +#: awx/api/serializers.py:2498 msgid "'ask_at_runtime' is not supported for custom credentials." msgstr "" -#: awx/api/serializers.py:2511 +#: awx/api/serializers.py:2547 msgid "Credential Type" msgstr "" -#: awx/api/serializers.py:2626 -#, python-format -msgid "\"%s\" is not a valid choice" +#: awx/api/serializers.py:2611 +msgid "Modifications not allowed for managed credentials" msgstr "" -#: awx/api/serializers.py:2645 -#, python-brace-format -msgid "'{field_name}' is not a valid field for {credential_type_name}" +#: awx/api/serializers.py:2629 awx/api/serializers.py:2703 +msgid "Galaxy credentials must be owned by an Organization." msgstr "" -#: awx/api/serializers.py:2666 +#: awx/api/serializers.py:2646 msgid "" "You cannot change the credential type of the credential, as it may break the " "functionality of the resources using it." msgstr "" -#: awx/api/serializers.py:2678 +#: awx/api/serializers.py:2658 msgid "" "Write-only field used to add user to owner role. If provided, do not give " "either team or organization. Only valid for creation." msgstr "" -#: awx/api/serializers.py:2683 +#: awx/api/serializers.py:2663 msgid "" "Write-only field used to add team to owner role. If provided, do not give " "either user or organization. Only valid for creation." msgstr "" -#: awx/api/serializers.py:2688 +#: awx/api/serializers.py:2668 msgid "" "Inherit permissions from organization roles. If provided on creation, do not " "give either user or team." msgstr "" -#: awx/api/serializers.py:2704 +#: awx/api/serializers.py:2685 msgid "Missing 'user', 'team', or 'organization'." msgstr "" -#: awx/api/serializers.py:2744 +#: awx/api/serializers.py:2690 msgid "" -"Credential organization must be set and match before assigning to a team" +"Only one of 'user', 'team', or 'organization' should be provided, received " +"{} fields." msgstr "" -#: awx/api/serializers.py:2945 -msgid "You must provide a cloud credential." +#: awx/api/serializers.py:2718 +msgid "" +"Credential organization must be set and match before assigning to a team" msgstr "" -#: awx/api/serializers.py:2946 -msgid "You must provide a network credential." +#: awx/api/serializers.py:2844 +msgid "This field is required." msgstr "" -#: awx/api/serializers.py:2947 awx/main/models/jobs.py:154 -msgid "You must provide an SSH credential." +#: awx/api/serializers.py:2853 +msgid "Playbook not found for project." msgstr "" -#: awx/api/serializers.py:2948 -msgid "You must provide a vault credential." +#: awx/api/serializers.py:2855 +msgid "Must select playbook for project." msgstr "" -#: awx/api/serializers.py:2967 -msgid "This field is required." +#: awx/api/serializers.py:2857 awx/api/serializers.py:2859 +msgid "Project does not allow overriding branch." msgstr "" -#: awx/api/serializers.py:2969 awx/api/serializers.py:2971 -msgid "Playbook not found for project." +#: awx/api/serializers.py:2896 +msgid "Must be a Personal Access Token." msgstr "" -#: awx/api/serializers.py:2973 -msgid "Must select playbook for project." +#: awx/api/serializers.py:2899 +msgid "Must match the selected webhook service." msgstr "" -#: awx/api/serializers.py:3055 +#: awx/api/serializers.py:2970 msgid "Cannot enable provisioning callback without an inventory set." msgstr "" -#: awx/api/serializers.py:3058 +#: awx/api/serializers.py:2973 msgid "Must either set a default value or ask to prompt on launch." msgstr "" -#: awx/api/serializers.py:3060 awx/main/models/jobs.py:317 +#: awx/api/serializers.py:2975 awx/main/models/jobs.py:299 msgid "Job Templates must have a project assigned." msgstr "" -#: awx/api/serializers.py:3072 -msgid "" -"Job slicing is a workflows-based feature and your license does not allow use " -"of workflows." -msgstr "" - -#: awx/api/serializers.py:3213 -msgid "Invalid job template." -msgstr "" - -#: awx/api/serializers.py:3334 +#: awx/api/serializers.py:3133 msgid "No change to job limit" msgstr "" -#: awx/api/serializers.py:3335 +#: awx/api/serializers.py:3134 msgid "All failed and unreachable hosts" msgstr "" -#: awx/api/serializers.py:3350 +#: awx/api/serializers.py:3149 msgid "Missing passwords needed to start: {}" msgstr "" -#: awx/api/serializers.py:3369 +#: awx/api/serializers.py:3168 msgid "Relaunch by host status not available until job finishes running." msgstr "" -#: awx/api/serializers.py:3383 +#: awx/api/serializers.py:3182 msgid "Job Template Project is missing or undefined." msgstr "" -#: awx/api/serializers.py:3385 +#: awx/api/serializers.py:3184 msgid "Job Template Inventory is missing or undefined." msgstr "" -#: awx/api/serializers.py:3423 +#: awx/api/serializers.py:3222 msgid "Unknown, job may have been ran before launch configurations were saved." msgstr "" -#: awx/api/serializers.py:3490 awx/main/tasks.py:2302 +#: awx/api/serializers.py:3293 awx/main/tasks.py:2838 awx/main/tasks.py:2856 msgid "{} are prohibited from use in ad hoc commands." msgstr "" -#: awx/api/serializers.py:3578 awx/api/views/__init__.py:4186 +#: awx/api/serializers.py:3381 awx/api/views/__init__.py:4211 #, python-brace-format msgid "" "Standard Output too large to display ({text_size} bytes), only download " "supported for sizes over {supported_size} bytes." msgstr "" -#: awx/api/serializers.py:3785 +#: awx/api/serializers.py:3694 msgid "Provided variable {} has no database value to replace with." msgstr "" -#: awx/api/serializers.py:3803 -#, python-brace-format -msgid "\"$encrypted$ is a reserved keyword, may not be used for {var_name}.\"" +#: awx/api/serializers.py:3712 +msgid "\"$encrypted$ is a reserved keyword, may not be used for {}.\"" msgstr "" -#: awx/api/serializers.py:3877 awx/api/views/__init__.py:478 -msgid "Related template is not configured to accept credentials on launch." +#: awx/api/serializers.py:4119 +msgid "A project is required to run a job." +msgstr "" + +#: awx/api/serializers.py:4121 +msgid "Missing a revision to run due to failed project update." msgstr "" -#: awx/api/serializers.py:4353 +#: awx/api/serializers.py:4125 msgid "The inventory associated with this Job Template is being deleted." msgstr "" -#: awx/api/serializers.py:4355 awx/api/serializers.py:4467 +#: awx/api/serializers.py:4127 awx/api/serializers.py:4247 msgid "The provided inventory is being deleted." msgstr "" -#: awx/api/serializers.py:4363 +#: awx/api/serializers.py:4135 msgid "Cannot assign multiple {} credentials." msgstr "" -#: awx/api/serializers.py:4367 +#: awx/api/serializers.py:4140 msgid "Cannot assign a Credential of kind `{}`" msgstr "" -#: awx/api/serializers.py:4380 +#: awx/api/serializers.py:4153 msgid "" "Removing {} credential at launch time without replacement is not supported. " "Provided list lacked credential(s): {}." msgstr "" -#: awx/api/serializers.py:4465 +#: awx/api/serializers.py:4245 msgid "The inventory associated with this Workflow is being deleted." msgstr "" -#: awx/api/serializers.py:4532 +#: awx/api/serializers.py:4316 +msgid "Message type '{}' invalid, must be either 'message' or 'body'" +msgstr "" + +#: awx/api/serializers.py:4322 +msgid "Expected string for '{}', found {}, " +msgstr "" + +#: awx/api/serializers.py:4326 +msgid "Messages cannot contain newlines (found newline in {} event)" +msgstr "" + +#: awx/api/serializers.py:4332 +msgid "Expected dict for 'messages' field, found {}" +msgstr "" + +#: awx/api/serializers.py:4336 +msgid "" +"Event '{}' invalid, must be one of 'started', 'success', 'error', or " +"'workflow_approval'" +msgstr "" + +#: awx/api/serializers.py:4342 +msgid "Expected dict for event '{}', found {}" +msgstr "" + +#: awx/api/serializers.py:4347 +msgid "" +"Workflow Approval event '{}' invalid, must be one of 'running', 'approved', " +"'timed_out', or 'denied'" +msgstr "" + +#: awx/api/serializers.py:4354 +msgid "Expected dict for workflow approval event '{}', found {}" +msgstr "" + +#: awx/api/serializers.py:4381 +msgid "Unable to render message '{}': {}" +msgstr "" + +#: awx/api/serializers.py:4383 +msgid "Field '{}' unavailable" +msgstr "" + +#: awx/api/serializers.py:4385 +msgid "Security error due to field '{}'" +msgstr "" + +#: awx/api/serializers.py:4405 +msgid "Webhook body for '{}' should be a json dictionary. Found type '{}'." +msgstr "" + +#: awx/api/serializers.py:4408 +msgid "Webhook body for '{}' is not a valid json dictionary ({})." +msgstr "" + +#: awx/api/serializers.py:4426 msgid "" "Missing required fields for Notification Configuration: notification_type" msgstr "" -#: awx/api/serializers.py:4555 +#: awx/api/serializers.py:4453 msgid "No values specified for field '{}'" msgstr "" -#: awx/api/serializers.py:4560 +#: awx/api/serializers.py:4458 +msgid "HTTP method must be either 'POST' or 'PUT'." +msgstr "" + +#: awx/api/serializers.py:4460 msgid "Missing required fields for Notification Configuration: {}." msgstr "" -#: awx/api/serializers.py:4563 +#: awx/api/serializers.py:4463 msgid "Configuration field '{}' incorrect type, expected {}." msgstr "" -#: awx/api/serializers.py:4625 +#: awx/api/serializers.py:4480 +msgid "Notification body" +msgstr "" + +#: awx/api/serializers.py:4560 msgid "" "Valid DTSTART required in rrule. Value should start with: DTSTART:" "YYYYMMDDTHHMMSSZ" msgstr "" -#: awx/api/serializers.py:4627 +#: awx/api/serializers.py:4562 msgid "" "DTSTART cannot be a naive datetime. Specify ;TZINFO= or YYYYMMDDTHHMMSSZZ." msgstr "" -#: awx/api/serializers.py:4629 +#: awx/api/serializers.py:4564 msgid "Multiple DTSTART is not supported." msgstr "" -#: awx/api/serializers.py:4631 +#: awx/api/serializers.py:4566 msgid "RRULE required in rrule." msgstr "" -#: awx/api/serializers.py:4633 +#: awx/api/serializers.py:4568 msgid "Multiple RRULE is not supported." msgstr "" -#: awx/api/serializers.py:4635 +#: awx/api/serializers.py:4570 msgid "INTERVAL required in rrule." msgstr "" -#: awx/api/serializers.py:4637 +#: awx/api/serializers.py:4572 msgid "SECONDLY is not supported." msgstr "" -#: awx/api/serializers.py:4639 +#: awx/api/serializers.py:4574 msgid "Multiple BYMONTHDAYs not supported." msgstr "" -#: awx/api/serializers.py:4641 +#: awx/api/serializers.py:4576 msgid "Multiple BYMONTHs not supported." msgstr "" -#: awx/api/serializers.py:4643 +#: awx/api/serializers.py:4578 msgid "BYDAY with numeric prefix not supported." msgstr "" -#: awx/api/serializers.py:4645 +#: awx/api/serializers.py:4580 msgid "BYYEARDAY not supported." msgstr "" -#: awx/api/serializers.py:4647 +#: awx/api/serializers.py:4582 msgid "BYWEEKNO not supported." msgstr "" -#: awx/api/serializers.py:4649 +#: awx/api/serializers.py:4584 msgid "RRULE may not contain both COUNT and UNTIL" msgstr "" -#: awx/api/serializers.py:4653 +#: awx/api/serializers.py:4588 msgid "COUNT > 999 is unsupported." msgstr "" -#: awx/api/serializers.py:4657 +#: awx/api/serializers.py:4594 msgid "rrule parsing failed validation: {}" msgstr "" -#: awx/api/serializers.py:4715 +#: awx/api/serializers.py:4656 msgid "Inventory Source must be a cloud resource." msgstr "" -#: awx/api/serializers.py:4717 +#: awx/api/serializers.py:4658 msgid "Manual Project cannot have a schedule set." msgstr "" -#: awx/api/serializers.py:4730 +#: awx/api/serializers.py:4661 +msgid "" +"Inventory sources with `update_on_project_update` cannot be scheduled. " +"Schedule its source project `{}` instead." +msgstr "" + +#: awx/api/serializers.py:4671 msgid "" "Count of jobs in the running or waiting state that are targeted for this " "instance" msgstr "" -#: awx/api/serializers.py:4735 +#: awx/api/serializers.py:4676 msgid "Count of all jobs that target this instance" msgstr "" -#: awx/api/serializers.py:4768 +#: awx/api/serializers.py:4711 msgid "" "Count of jobs in the running or waiting state that are targeted for this " "instance group" msgstr "" -#: awx/api/serializers.py:4773 +#: awx/api/serializers.py:4716 msgid "Count of all jobs that target this instance group" msgstr "" -#: awx/api/serializers.py:4781 +#: awx/api/serializers.py:4721 +msgid "Indicates whether instance group controls any other group" +msgstr "" + +#: awx/api/serializers.py:4725 +msgid "" +"Indicates whether instances in this group are isolated.Isolated groups have " +"a designated controller group." +msgstr "" + +#: awx/api/serializers.py:4730 +msgid "" +"Indicates whether instances in this group are containerized.Containerized " +"groups have a designated Openshift or Kubernetes cluster." +msgstr "" + +#: awx/api/serializers.py:4738 msgid "Policy Instance Percentage" msgstr "" -#: awx/api/serializers.py:4782 +#: awx/api/serializers.py:4739 msgid "" "Minimum percentage of all instances that will be automatically assigned to " "this group when new instances come online." msgstr "" -#: awx/api/serializers.py:4787 +#: awx/api/serializers.py:4744 msgid "Policy Instance Minimum" msgstr "" -#: awx/api/serializers.py:4788 +#: awx/api/serializers.py:4745 msgid "" "Static minimum number of Instances that will be automatically assign to this " "group when new instances come online." msgstr "" -#: awx/api/serializers.py:4793 +#: awx/api/serializers.py:4750 msgid "Policy Instance List" msgstr "" -#: awx/api/serializers.py:4794 +#: awx/api/serializers.py:4751 msgid "List of exact-match Instances that will be assigned to this group" msgstr "" -#: awx/api/serializers.py:4816 +#: awx/api/serializers.py:4777 msgid "Duplicate entry {}." msgstr "" -#: awx/api/serializers.py:4818 +#: awx/api/serializers.py:4779 msgid "{} is not a valid hostname of an existing instance." msgstr "" -#: awx/api/serializers.py:4820 awx/api/views/mixin.py:138 +#: awx/api/serializers.py:4781 awx/api/views/mixin.py:98 msgid "" "Isolated instances may not be added or removed from instances groups via the " "API." msgstr "" -#: awx/api/serializers.py:4822 awx/api/views/mixin.py:142 +#: awx/api/serializers.py:4783 awx/api/views/mixin.py:102 msgid "Isolated instance group membership may not be managed via the API." msgstr "" -#: awx/api/serializers.py:4827 +#: awx/api/serializers.py:4785 awx/api/serializers.py:4790 +#: awx/api/serializers.py:4795 +msgid "Containerized instances may not be managed via the API" +msgstr "" + +#: awx/api/serializers.py:4800 msgid "tower instance group name may not be changed." msgstr "" -#: awx/api/serializers.py:4897 +#: awx/api/serializers.py:4805 +msgid "Only Kubernetes credentials can be associated with an Instance Group" +msgstr "" + +#: awx/api/serializers.py:4844 +msgid "" +"When present, shows the field name of the role or relationship that changed." +msgstr "" + +#: awx/api/serializers.py:4846 +msgid "" +"When present, shows the model on which the role or relationship was defined." +msgstr "" + +#: awx/api/serializers.py:4879 msgid "" "A summary of the new and changed values when an object is created, updated, " "or deleted" msgstr "" -#: awx/api/serializers.py:4899 +#: awx/api/serializers.py:4881 msgid "" "For create, update, and delete events this is the object type that was " "affected. For associate and disassociate events this is the object type " "associated or disassociated with object2." msgstr "" -#: awx/api/serializers.py:4902 +#: awx/api/serializers.py:4884 msgid "" "Unpopulated for create, update, and delete events. For associate and " "disassociate events this is the object type that object1 is being associated " "with." msgstr "" -#: awx/api/serializers.py:4905 +#: awx/api/serializers.py:4887 msgid "The action taken with respect to the given object(s)." msgstr "" -#: awx/api/views/__init__.py:161 +#: awx/api/views/__init__.py:185 +msgid "Not found." +msgstr "" + +#: awx/api/views/__init__.py:193 msgid "Dashboard" msgstr "" -#: awx/api/views/__init__.py:260 +#: awx/api/views/__init__.py:290 msgid "Dashboard Jobs Graphs" msgstr "" -#: awx/api/views/__init__.py:296 +#: awx/api/views/__init__.py:326 #, python-format msgid "Unknown period \"%s\"" msgstr "" -#: awx/api/views/__init__.py:310 +#: awx/api/views/__init__.py:340 msgid "Instances" msgstr "" -#: awx/api/views/__init__.py:318 +#: awx/api/views/__init__.py:348 msgid "Instance Detail" msgstr "" -#: awx/api/views/__init__.py:338 +#: awx/api/views/__init__.py:365 msgid "Instance Jobs" msgstr "" -#: awx/api/views/__init__.py:352 +#: awx/api/views/__init__.py:379 msgid "Instance's Instance Groups" msgstr "" -#: awx/api/views/__init__.py:361 +#: awx/api/views/__init__.py:388 msgid "Instance Groups" msgstr "" -#: awx/api/views/__init__.py:369 +#: awx/api/views/__init__.py:396 msgid "Instance Group Detail" msgstr "" -#: awx/api/views/__init__.py:377 +#: awx/api/views/__init__.py:411 msgid "Isolated Groups can not be removed from the API" msgstr "" -#: awx/api/views/__init__.py:379 +#: awx/api/views/__init__.py:413 msgid "" "Instance Groups acting as a controller for an Isolated Group can not be " "removed from the API" msgstr "" -#: awx/api/views/__init__.py:385 +#: awx/api/views/__init__.py:419 msgid "Instance Group Running Jobs" msgstr "" -#: awx/api/views/__init__.py:394 +#: awx/api/views/__init__.py:428 msgid "Instance Group's Instances" msgstr "" -#: awx/api/views/__init__.py:404 +#: awx/api/views/__init__.py:438 msgid "Schedules" msgstr "" -#: awx/api/views/__init__.py:418 +#: awx/api/views/__init__.py:452 msgid "Schedule Recurrence Rule Preview" msgstr "" -#: awx/api/views/__init__.py:465 +#: awx/api/views/__init__.py:499 msgid "Cannot assign credential when related template is null." msgstr "" -#: awx/api/views/__init__.py:470 +#: awx/api/views/__init__.py:504 msgid "Related template cannot accept {} on launch." msgstr "" -#: awx/api/views/__init__.py:472 +#: awx/api/views/__init__.py:506 msgid "" "Credential that requires user input on launch cannot be used in saved launch " "configuration." msgstr "" -#: awx/api/views/__init__.py:480 +#: awx/api/views/__init__.py:512 +msgid "Related template is not configured to accept credentials on launch." +msgstr "" + +#: awx/api/views/__init__.py:514 #, python-brace-format msgid "" "This launch configuration already provides a {credential_type} credential." msgstr "" -#: awx/api/views/__init__.py:483 +#: awx/api/views/__init__.py:517 #, python-brace-format msgid "Related template already uses {credential_type} credential." msgstr "" -#: awx/api/views/__init__.py:501 +#: awx/api/views/__init__.py:535 msgid "Schedule Jobs List" msgstr "" -#: awx/api/views/__init__.py:590 awx/api/views/__init__.py:4399 +#: awx/api/views/__init__.py:619 awx/api/views/__init__.py:4420 msgid "" "You cannot assign an Organization participation role as a child role for a " "Team." msgstr "" -#: awx/api/views/__init__.py:594 awx/api/views/__init__.py:4413 +#: awx/api/views/__init__.py:623 awx/api/views/__init__.py:4434 msgid "You cannot grant system-level permissions to a team." msgstr "" -#: awx/api/views/__init__.py:601 awx/api/views/__init__.py:4405 +#: awx/api/views/__init__.py:630 awx/api/views/__init__.py:4426 msgid "" "You cannot grant credential access to a team when the Organization field " "isn't set, or belongs to a different organization" msgstr "" -#: awx/api/views/__init__.py:715 +#: awx/api/views/__init__.py:732 msgid "Project Schedules" msgstr "" -#: awx/api/views/__init__.py:726 +#: awx/api/views/__init__.py:743 msgid "Project SCM Inventory Sources" msgstr "" -#: awx/api/views/__init__.py:827 +#: awx/api/views/__init__.py:844 msgid "Project Update Events List" msgstr "" -#: awx/api/views/__init__.py:841 +#: awx/api/views/__init__.py:858 msgid "System Job Events List" msgstr "" -#: awx/api/views/__init__.py:877 +#: awx/api/views/__init__.py:892 msgid "Project Update SCM Inventory Updates" msgstr "" -#: awx/api/views/__init__.py:936 +#: awx/api/views/__init__.py:937 msgid "Me" msgstr "" -#: awx/api/views/__init__.py:944 +#: awx/api/views/__init__.py:946 msgid "OAuth 2 Applications" msgstr "" -#: awx/api/views/__init__.py:953 +#: awx/api/views/__init__.py:955 msgid "OAuth 2 Application Detail" msgstr "" -#: awx/api/views/__init__.py:966 +#: awx/api/views/__init__.py:968 msgid "OAuth 2 Application Tokens" msgstr "" -#: awx/api/views/__init__.py:988 +#: awx/api/views/__init__.py:990 msgid "OAuth2 Tokens" msgstr "" -#: awx/api/views/__init__.py:997 +#: awx/api/views/__init__.py:999 msgid "OAuth2 User Tokens" msgstr "" -#: awx/api/views/__init__.py:1009 +#: awx/api/views/__init__.py:1011 msgid "OAuth2 User Authorized Access Tokens" msgstr "" -#: awx/api/views/__init__.py:1024 +#: awx/api/views/__init__.py:1026 msgid "Organization OAuth2 Applications" msgstr "" -#: awx/api/views/__init__.py:1036 +#: awx/api/views/__init__.py:1038 msgid "OAuth2 Personal Access Tokens" msgstr "" -#: awx/api/views/__init__.py:1051 +#: awx/api/views/__init__.py:1053 msgid "OAuth Token Detail" msgstr "" -#: awx/api/views/__init__.py:1112 awx/api/views/__init__.py:4366 +#: awx/api/views/__init__.py:1115 awx/api/views/__init__.py:4387 msgid "" "You cannot grant credential access to a user not in the credentials' " "organization" msgstr "" -#: awx/api/views/__init__.py:1116 awx/api/views/__init__.py:4370 +#: awx/api/views/__init__.py:1119 awx/api/views/__init__.py:4391 msgid "You cannot grant private credential access to another user" msgstr "" -#: awx/api/views/__init__.py:1214 +#: awx/api/views/__init__.py:1217 #, python-format msgid "Cannot change %s." msgstr "" -#: awx/api/views/__init__.py:1220 +#: awx/api/views/__init__.py:1223 msgid "Cannot delete user." msgstr "" -#: awx/api/views/__init__.py:1244 +#: awx/api/views/__init__.py:1247 msgid "Deletion not allowed for managed credential types" msgstr "" -#: awx/api/views/__init__.py:1246 +#: awx/api/views/__init__.py:1249 msgid "Credential types that are in use cannot be deleted" msgstr "" -#: awx/api/views/__init__.py:1445 -msgid "The inventory for this host is already being deleted." +#: awx/api/views/__init__.py:1362 +msgid "Deletion not allowed for managed credentials" msgstr "" -#: awx/api/views/__init__.py:1580 -msgid "Fact not found." +#: awx/api/views/__init__.py:1407 +msgid "External Credential Test" msgstr "" -#: awx/api/views/__init__.py:1610 +#: awx/api/views/__init__.py:1442 +msgid "Credential Input Source Detail" +msgstr "" + +#: awx/api/views/__init__.py:1450 awx/api/views/__init__.py:1458 +msgid "Credential Input Sources" +msgstr "" + +#: awx/api/views/__init__.py:1473 +msgid "External Credential Type Test" +msgstr "" + +#: awx/api/views/__init__.py:1539 +msgid "The inventory for this host is already being deleted." +msgstr "" + +#: awx/api/views/__init__.py:1656 msgid "SSLError while trying to connect to {}" msgstr "" -#: awx/api/views/__init__.py:1612 +#: awx/api/views/__init__.py:1658 msgid "Request to {} timed out." msgstr "" -#: awx/api/views/__init__.py:1614 +#: awx/api/views/__init__.py:1660 msgid "Unknown exception {} while trying to GET {}" msgstr "" -#: awx/api/views/__init__.py:1617 +#: awx/api/views/__init__.py:1664 msgid "" "Unauthorized access. Please check your Insights Credential username and " "password." msgstr "" -#: awx/api/views/__init__.py:1620 +#: awx/api/views/__init__.py:1668 msgid "" -"Failed to gather reports and maintenance plans from Insights API at URL {}. " -"Server responded with {} status code and message {}" +"Failed to access the Insights API at URL {}. Server responded with {} status " +"code and message {}" +msgstr "" + +#: awx/api/views/__init__.py:1677 +msgid "Expected JSON response from Insights at URL {} but instead got {}" msgstr "" -#: awx/api/views/__init__.py:1627 -msgid "Expected JSON response from Insights but instead got {}" +#: awx/api/views/__init__.py:1695 +msgid "Could not translate Insights system ID {} into an Insights platform ID." msgstr "" -#: awx/api/views/__init__.py:1634 +#: awx/api/views/__init__.py:1737 msgid "This host is not recognized as an Insights host." msgstr "" -#: awx/api/views/__init__.py:1639 +#: awx/api/views/__init__.py:1745 msgid "The Insights Credential for \"{}\" was not found." msgstr "" -#: awx/api/views/__init__.py:1707 +#: awx/api/views/__init__.py:1824 msgid "Cyclical Group association." msgstr "" -#: awx/api/views/__init__.py:1878 +#: awx/api/views/__init__.py:1990 msgid "Inventory subset argument must be a string." msgstr "" -#: awx/api/views/__init__.py:1882 +#: awx/api/views/__init__.py:1994 msgid "Subset does not use any supported syntax." msgstr "" -#: awx/api/views/__init__.py:1932 +#: awx/api/views/__init__.py:2044 msgid "Inventory Source List" msgstr "" -#: awx/api/views/__init__.py:1944 +#: awx/api/views/__init__.py:2056 msgid "Inventory Sources Update" msgstr "" -#: awx/api/views/__init__.py:1977 +#: awx/api/views/__init__.py:2089 msgid "Could not start because `can_update` returned False" msgstr "" -#: awx/api/views/__init__.py:1985 +#: awx/api/views/__init__.py:2097 msgid "No inventory sources to update." msgstr "" -#: awx/api/views/__init__.py:2014 +#: awx/api/views/__init__.py:2119 msgid "Inventory Source Schedules" msgstr "" -#: awx/api/views/__init__.py:2042 +#: awx/api/views/__init__.py:2146 msgid "Notification Templates can only be assigned when source is one of {}." msgstr "" -#: awx/api/views/__init__.py:2111 +#: awx/api/views/__init__.py:2244 msgid "Source already has credential assigned." msgstr "" -#: awx/api/views/__init__.py:2264 -msgid "Field is not allowed for use with v1 API." -msgstr "" - -#: awx/api/views/__init__.py:2274 -msgid "" -"'credentials' cannot be used in combination with 'credential', " -"'vault_credential', or 'extra_credentials'." -msgstr "" - -#: awx/api/views/__init__.py:2301 -msgid "Incorrect type. Expected {}, received {}." -msgstr "" - -#: awx/api/views/__init__.py:2399 +#: awx/api/views/__init__.py:2460 msgid "Job Template Schedules" msgstr "" -#: awx/api/views/__init__.py:2427 awx/api/views/__init__.py:2438 -msgid "Your license does not allow adding surveys." -msgstr "" - -#: awx/api/views/__init__.py:2458 +#: awx/api/views/__init__.py:2509 msgid "Field '{}' is missing from survey spec." msgstr "" -#: awx/api/views/__init__.py:2460 +#: awx/api/views/__init__.py:2511 msgid "Expected {} for field '{}', received {} type." msgstr "" -#: awx/api/views/__init__.py:2464 +#: awx/api/views/__init__.py:2515 msgid "'spec' doesn't contain any items." msgstr "" -#: awx/api/views/__init__.py:2478 +#: awx/api/views/__init__.py:2529 #, python-format msgid "Survey question %s is not a json object." msgstr "" -#: awx/api/views/__init__.py:2481 +#: awx/api/views/__init__.py:2532 #, python-brace-format msgid "'{field_name}' missing from survey question {idx}" msgstr "" -#: awx/api/views/__init__.py:2491 +#: awx/api/views/__init__.py:2542 #, python-brace-format msgid "'{field_name}' in survey question {idx} expected to be {type_label}." msgstr "" -#: awx/api/views/__init__.py:2495 +#: awx/api/views/__init__.py:2546 #, python-format msgid "'variable' '%(item)s' duplicated in survey question %(survey)s." msgstr "" -#: awx/api/views/__init__.py:2505 +#: awx/api/views/__init__.py:2556 #, python-brace-format msgid "" "'{survey_item[type]}' in survey question {idx} is not one of " "'{allowed_types}' allowed question types." msgstr "" -#: awx/api/views/__init__.py:2515 +#: awx/api/views/__init__.py:2566 #, python-brace-format msgid "" "Default value {survey_item[default]} in survey question {idx} expected to be " "{type_label}." msgstr "" -#: awx/api/views/__init__.py:2525 +#: awx/api/views/__init__.py:2576 #, python-brace-format msgid "The {min_or_max} limit in survey question {idx} expected to be integer." msgstr "" -#: awx/api/views/__init__.py:2529 +#: awx/api/views/__init__.py:2586 #, python-brace-format msgid "Survey question {idx} of type {survey_item[type]} must specify choices." msgstr "" -#: awx/api/views/__init__.py:2538 +#: awx/api/views/__init__.py:2600 +msgid "Multiple Choice (Single Select) can only have one default value." +msgstr "" + +#: awx/api/views/__init__.py:2604 +msgid "Default choice must be answered from the choices listed." +msgstr "" + +#: awx/api/views/__init__.py:2613 #, python-brace-format msgid "" "$encrypted$ is a reserved keyword for password question defaults, survey " "question {idx} is type {survey_item[type]}." msgstr "" -#: awx/api/views/__init__.py:2552 +#: awx/api/views/__init__.py:2627 #, python-brace-format msgid "" "$encrypted$ is a reserved keyword, may not be used for new default in " "position {idx}." msgstr "" -#: awx/api/views/__init__.py:2626 +#: awx/api/views/__init__.py:2699 #, python-brace-format msgid "Cannot assign multiple {credential_type} credentials." msgstr "" -#: awx/api/views/__init__.py:2630 +#: awx/api/views/__init__.py:2703 msgid "Cannot assign a Credential of kind `{}`." msgstr "" -#: awx/api/views/__init__.py:2647 -msgid "Extra credentials must be network or cloud." -msgstr "" - -#: awx/api/views/__init__.py:2669 +#: awx/api/views/__init__.py:2726 msgid "Maximum number of labels for {} reached." msgstr "" -#: awx/api/views/__init__.py:2792 +#: awx/api/views/__init__.py:2849 msgid "No matching host could be found!" msgstr "" -#: awx/api/views/__init__.py:2795 +#: awx/api/views/__init__.py:2852 msgid "Multiple hosts matched the request!" msgstr "" -#: awx/api/views/__init__.py:2800 +#: awx/api/views/__init__.py:2857 msgid "Cannot start automatically, user input required!" msgstr "" -#: awx/api/views/__init__.py:2807 +#: awx/api/views/__init__.py:2865 msgid "Host callback job already pending." msgstr "" -#: awx/api/views/__init__.py:2823 awx/api/views/__init__.py:3629 +#: awx/api/views/__init__.py:2881 awx/api/views/__init__.py:3632 msgid "Error starting job!" msgstr "" -#: awx/api/views/__init__.py:2973 -msgid "Multiple parent relationship not allowed." +#: awx/api/views/__init__.py:3005 awx/api/views/__init__.py:3025 +msgid "Cycle detected." msgstr "" -#: awx/api/views/__init__.py:2978 -msgid "Cycle detected." +#: awx/api/views/__init__.py:3017 +msgid "Relationship not allowed." msgstr "" -#: awx/api/views/__init__.py:3158 +#: awx/api/views/__init__.py:3246 msgid "Cannot relaunch slice workflow job orphaned from job template." msgstr "" -#: awx/api/views/__init__.py:3191 +#: awx/api/views/__init__.py:3248 +msgid "Cannot relaunch sliced workflow job after slice count has changed." +msgstr "" + +#: awx/api/views/__init__.py:3281 msgid "Workflow Job Template Schedules" msgstr "" -#: awx/api/views/__init__.py:3326 awx/api/views/__init__.py:4033 +#: awx/api/views/__init__.py:3424 awx/api/views/__init__.py:4055 msgid "Superuser privileges needed." msgstr "" -#: awx/api/views/__init__.py:3359 +#: awx/api/views/__init__.py:3457 msgid "System Job Template Schedules" msgstr "" -#: awx/api/views/__init__.py:3417 -msgid "POST not allowed for Job launching in version 2 of the api" -msgstr "" - -#: awx/api/views/__init__.py:3441 awx/api/views/__init__.py:3447 -msgid "PUT not allowed for Job Details in version 2 of the API" -msgstr "" - -#: awx/api/views/__init__.py:3607 +#: awx/api/views/__init__.py:3615 #, python-brace-format msgid "Wait until job finishes before retrying on {status_value} hosts." msgstr "" -#: awx/api/views/__init__.py:3612 +#: awx/api/views/__init__.py:3620 #, python-brace-format msgid "Cannot retry on {status_value} hosts, playbook stats not available." msgstr "" -#: awx/api/views/__init__.py:3617 +#: awx/api/views/__init__.py:3625 #, python-brace-format msgid "Cannot relaunch because previous job had 0 {status_value} hosts." msgstr "" -#: awx/api/views/__init__.py:3623 -#, python-brace-format -msgid "" -"Cannot relaunch because the limit length {limit_length} exceeds the max of " -"{limit_max}." -msgstr "" - -#: awx/api/views/__init__.py:3651 +#: awx/api/views/__init__.py:3654 msgid "Cannot create schedule because job requires credential passwords." msgstr "" -#: awx/api/views/__init__.py:3656 +#: awx/api/views/__init__.py:3659 msgid "Cannot create schedule because job was launched by legacy method." msgstr "" -#: awx/api/views/__init__.py:3658 +#: awx/api/views/__init__.py:3661 msgid "Cannot create schedule because a related resource is missing." msgstr "" -#: awx/api/views/__init__.py:3713 +#: awx/api/views/__init__.py:3716 msgid "Job Host Summaries List" msgstr "" -#: awx/api/views/__init__.py:3762 +#: awx/api/views/__init__.py:3770 msgid "Job Event Children List" msgstr "" -#: awx/api/views/__init__.py:3772 +#: awx/api/views/__init__.py:3786 msgid "Job Event Hosts List" msgstr "" -#: awx/api/views/__init__.py:3781 +#: awx/api/views/__init__.py:3801 msgid "Job Events List" msgstr "" -#: awx/api/views/__init__.py:3990 +#: awx/api/views/__init__.py:4012 msgid "Ad Hoc Command Events List" msgstr "" -#: awx/api/views/__init__.py:4232 +#: awx/api/views/__init__.py:4257 msgid "Delete not allowed while there are pending notifications" msgstr "" -#: awx/api/views/__init__.py:4240 +#: awx/api/views/__init__.py:4265 msgid "Notification Template Test" msgstr "" -#: awx/api/views/inventory.py:65 -msgid "Inventory Update Events List" +#: awx/api/views/__init__.py:4525 awx/api/views/__init__.py:4540 +msgid "User does not have permission to approve or deny this workflow." msgstr "" -#: awx/api/views/inventory.py:88 -msgid "Cannot delete inventory script." +#: awx/api/views/__init__.py:4527 awx/api/views/__init__.py:4542 +msgid "This workflow step has already been approved or denied." msgstr "" -#: awx/api/views/inventory.py:149 -#, python-brace-format -msgid "{0}" +#: awx/api/views/inventory.py:63 +msgid "Inventory Update Events List" +msgstr "" + +#: awx/api/views/inventory.py:90 +msgid "Cannot delete inventory script." msgstr "" -#: awx/api/views/mixin.py:50 -msgid "Your license does not allow use of the activity stream." +#: awx/api/views/inventory.py:137 +msgid "You cannot turn a regular inventory into a \"smart\" inventory." msgstr "" -#: awx/api/views/mixin.py:61 -msgid "Your license does not permit use of system tracking." +#: awx/api/views/inventory.py:150 +#, python-brace-format +msgid "{0}" msgstr "" -#: awx/api/views/mixin.py:72 -msgid "Your license does not allow use of workflows." +#: awx/api/views/metrics.py:30 +msgid "Metrics" msgstr "" -#: awx/api/views/mixin.py:86 +#: awx/api/views/mixin.py:46 msgid "Cannot delete job resource when associated workflow job is running." msgstr "" -#: awx/api/views/mixin.py:91 +#: awx/api/views/mixin.py:51 msgid "Cannot delete running job resource." msgstr "" -#: awx/api/views/mixin.py:96 +#: awx/api/views/mixin.py:56 msgid "Job has not finished processing events." msgstr "" -#: awx/api/views/mixin.py:193 +#: awx/api/views/mixin.py:153 msgid "Related job {} is still processing events." msgstr "" -#: awx/api/views/organization.py:84 -msgid "Your license only permits a single organization to exist." +#: awx/api/views/organization.py:230 +#, python-brace-format +msgid "Credential must be a Galaxy credential, not {sub.credential_type.name}." msgstr "" -#: awx/api/views/root.py:43 awx/templates/rest_framework/api.html:28 +#: awx/api/views/root.py:50 awx/templates/rest_framework/api.html:28 msgid "REST API" msgstr "" -#: awx/api/views/root.py:54 awx/templates/rest_framework/api.html:4 +#: awx/api/views/root.py:60 awx/templates/rest_framework/api.html:4 msgid "AWX REST API" msgstr "" -#: awx/api/views/root.py:67 +#: awx/api/views/root.py:73 msgid "API OAuth 2 Authorization Root" msgstr "" -#: awx/api/views/root.py:132 -msgid "Version 1" -msgstr "" - -#: awx/api/views/root.py:136 +#: awx/api/views/root.py:140 msgid "Version 2" msgstr "" -#: awx/api/views/root.py:145 +#: awx/api/views/root.py:149 msgid "Ping" msgstr "" -#: awx/api/views/root.py:176 awx/conf/apps.py:10 +#: awx/api/views/root.py:181 awx/api/views/root.py:226 awx/conf/apps.py:10 msgid "Configuration" msgstr "" -#: awx/api/views/root.py:233 +#: awx/api/views/root.py:203 awx/api/views/root.py:310 +msgid "Invalid License" +msgstr "" + +#: awx/api/views/root.py:208 +msgid "The provided credentials are invalid (HTTP 401)." +msgstr "" + +#: awx/api/views/root.py:210 +msgid "Unable to connect to proxy server." +msgstr "" + +#: awx/api/views/root.py:212 +msgid "Could not connect to subscription service." +msgstr "" + +#: awx/api/views/root.py:286 msgid "Invalid license data" msgstr "" -#: awx/api/views/root.py:235 +#: awx/api/views/root.py:288 msgid "Missing 'eula_accepted' property" msgstr "" -#: awx/api/views/root.py:239 +#: awx/api/views/root.py:292 msgid "'eula_accepted' value is invalid" msgstr "" -#: awx/api/views/root.py:242 +#: awx/api/views/root.py:295 msgid "'eula_accepted' must be True" msgstr "" -#: awx/api/views/root.py:249 +#: awx/api/views/root.py:302 msgid "Invalid JSON" msgstr "" -#: awx/api/views/root.py:257 -msgid "Invalid License" -msgstr "" - -#: awx/api/views/root.py:267 +#: awx/api/views/root.py:321 msgid "Invalid license" msgstr "" -#: awx/api/views/root.py:275 +#: awx/api/views/root.py:329 msgid "Failed to remove license." msgstr "" +#: awx/api/views/webhooks.py:143 +msgid "Webhook previously received, aborting." +msgstr "" + #: awx/conf/conf.py:20 msgid "Bud Frogs" msgstr "" @@ -1521,19 +1667,19 @@ msgstr "" msgid "Example setting that cannot be changed." msgstr "" -#: awx/conf/conf.py:93 +#: awx/conf/conf.py:90 msgid "Example Setting" msgstr "" -#: awx/conf/conf.py:94 +#: awx/conf/conf.py:91 msgid "Example setting which can be different for each user." msgstr "" -#: awx/conf/conf.py:95 awx/conf/registry.py:85 awx/conf/views.py:55 +#: awx/conf/conf.py:92 awx/conf/registry.py:81 awx/conf/views.py:56 msgid "User" msgstr "" -#: awx/conf/fields.py:60 awx/sso/fields.py:595 +#: awx/conf/fields.py:63 awx/sso/fields.py:595 #, python-brace-format msgid "" "Expected None, True, False, a string or list of strings but got {input_type} " @@ -1541,370 +1687,437 @@ msgid "" msgstr "" #: awx/conf/fields.py:104 -msgid "Enter a valid URL" -msgstr "" - -#: awx/conf/fields.py:136 #, python-brace-format -msgid "\"{input}\" is not a valid string." +msgid "Expected list of strings but got {input_type} instead." msgstr "" -#: awx/conf/fields.py:151 +#: awx/conf/fields.py:105 #, python-brace-format -msgid "Expected a list of tuples of max length 2 but got {input_type} instead." -msgstr "" - -#: awx/conf/license.py:22 -msgid "Your Tower license does not allow that." -msgstr "" - -#: awx/conf/management/commands/migrate_to_database_settings.py:41 -msgid "Only show which settings would be commented/migrated." +msgid "{path} is not a valid path choice." msgstr "" -#: awx/conf/management/commands/migrate_to_database_settings.py:48 -msgid "Skip over settings that would raise an error when commenting/migrating." -msgstr "" - -#: awx/conf/management/commands/migrate_to_database_settings.py:55 -msgid "Skip commenting out settings in files." +#: awx/conf/fields.py:149 +msgid "Enter a valid URL" msgstr "" -#: awx/conf/management/commands/migrate_to_database_settings.py:62 -msgid "Skip migrating and only comment out settings in files." +#: awx/conf/fields.py:187 +#, python-brace-format +msgid "\"{input}\" is not a valid string." msgstr "" -#: awx/conf/management/commands/migrate_to_database_settings.py:68 -msgid "Backup existing settings files with this suffix." +#: awx/conf/fields.py:202 +#, python-brace-format +msgid "Expected a list of tuples of max length 2 but got {input_type} instead." msgstr "" -#: awx/conf/registry.py:73 awx/conf/tests/unit/test_registry.py:169 -#: awx/conf/tests/unit/test_registry.py:192 -#: awx/conf/tests/unit/test_registry.py:196 -#: awx/conf/tests/unit/test_registry.py:201 -#: awx/conf/tests/unit/test_registry.py:208 +#: awx/conf/registry.py:73 awx/conf/tests/unit/test_registry.py:156 msgid "All" msgstr "" -#: awx/conf/registry.py:74 awx/conf/tests/unit/test_registry.py:170 -#: awx/conf/tests/unit/test_registry.py:193 -#: awx/conf/tests/unit/test_registry.py:197 -#: awx/conf/tests/unit/test_registry.py:202 -#: awx/conf/tests/unit/test_registry.py:209 +#: awx/conf/registry.py:74 awx/conf/tests/unit/test_registry.py:157 msgid "Changed" msgstr "" -#: awx/conf/registry.py:86 +#: awx/conf/registry.py:82 msgid "User-Defaults" msgstr "" -#: awx/conf/registry.py:154 +#: awx/conf/registry.py:145 msgid "This value has been set manually in a settings file." msgstr "" -#: awx/conf/tests/unit/test_registry.py:46 -#: awx/conf/tests/unit/test_registry.py:56 -#: awx/conf/tests/unit/test_registry.py:72 -#: awx/conf/tests/unit/test_registry.py:87 -#: awx/conf/tests/unit/test_registry.py:100 -#: awx/conf/tests/unit/test_registry.py:106 -#: awx/conf/tests/unit/test_registry.py:126 -#: awx/conf/tests/unit/test_registry.py:140 +#: awx/conf/tests/unit/test_registry.py:47 +#: awx/conf/tests/unit/test_registry.py:57 +#: awx/conf/tests/unit/test_registry.py:73 +#: awx/conf/tests/unit/test_registry.py:88 +#: awx/conf/tests/unit/test_registry.py:101 +#: awx/conf/tests/unit/test_registry.py:107 +#: awx/conf/tests/unit/test_registry.py:127 +#: awx/conf/tests/unit/test_registry.py:133 #: awx/conf/tests/unit/test_registry.py:146 -#: awx/conf/tests/unit/test_registry.py:159 -#: awx/conf/tests/unit/test_registry.py:171 -#: awx/conf/tests/unit/test_registry.py:180 -#: awx/conf/tests/unit/test_registry.py:198 -#: awx/conf/tests/unit/test_registry.py:210 -#: awx/conf/tests/unit/test_registry.py:219 -#: awx/conf/tests/unit/test_registry.py:225 -#: awx/conf/tests/unit/test_registry.py:237 -#: awx/conf/tests/unit/test_registry.py:245 -#: awx/conf/tests/unit/test_registry.py:288 -#: awx/conf/tests/unit/test_registry.py:306 -#: awx/conf/tests/unit/test_settings.py:79 -#: awx/conf/tests/unit/test_settings.py:97 -#: awx/conf/tests/unit/test_settings.py:112 -#: awx/conf/tests/unit/test_settings.py:127 -#: awx/conf/tests/unit/test_settings.py:143 -#: awx/conf/tests/unit/test_settings.py:156 -#: awx/conf/tests/unit/test_settings.py:173 -#: awx/conf/tests/unit/test_settings.py:189 -#: awx/conf/tests/unit/test_settings.py:200 -#: awx/conf/tests/unit/test_settings.py:216 -#: awx/conf/tests/unit/test_settings.py:237 -#: awx/conf/tests/unit/test_settings.py:259 -#: awx/conf/tests/unit/test_settings.py:285 -#: awx/conf/tests/unit/test_settings.py:299 -#: awx/conf/tests/unit/test_settings.py:323 +#: awx/conf/tests/unit/test_registry.py:158 +#: awx/conf/tests/unit/test_registry.py:167 +#: awx/conf/tests/unit/test_registry.py:173 +#: awx/conf/tests/unit/test_registry.py:185 +#: awx/conf/tests/unit/test_registry.py:192 +#: awx/conf/tests/unit/test_registry.py:234 +#: awx/conf/tests/unit/test_registry.py:252 +#: awx/conf/tests/unit/test_settings.py:73 +#: awx/conf/tests/unit/test_settings.py:91 +#: awx/conf/tests/unit/test_settings.py:106 +#: awx/conf/tests/unit/test_settings.py:121 +#: awx/conf/tests/unit/test_settings.py:137 +#: awx/conf/tests/unit/test_settings.py:150 +#: awx/conf/tests/unit/test_settings.py:167 +#: awx/conf/tests/unit/test_settings.py:183 +#: awx/conf/tests/unit/test_settings.py:194 +#: awx/conf/tests/unit/test_settings.py:210 +#: awx/conf/tests/unit/test_settings.py:231 +#: awx/conf/tests/unit/test_settings.py:254 +#: awx/conf/tests/unit/test_settings.py:268 +#: awx/conf/tests/unit/test_settings.py:292 +#: awx/conf/tests/unit/test_settings.py:312 +#: awx/conf/tests/unit/test_settings.py:329 #: awx/conf/tests/unit/test_settings.py:343 -#: awx/conf/tests/unit/test_settings.py:360 -#: awx/conf/tests/unit/test_settings.py:374 -#: awx/conf/tests/unit/test_settings.py:398 -#: awx/conf/tests/unit/test_settings.py:411 -#: awx/conf/tests/unit/test_settings.py:430 -#: awx/conf/tests/unit/test_settings.py:466 awx/main/conf.py:22 -#: awx/main/conf.py:32 awx/main/conf.py:43 awx/main/conf.py:53 -#: awx/main/conf.py:62 awx/main/conf.py:74 awx/main/conf.py:87 -#: awx/main/conf.py:100 awx/main/conf.py:125 +#: awx/conf/tests/unit/test_settings.py:367 +#: awx/conf/tests/unit/test_settings.py:380 +#: awx/conf/tests/unit/test_settings.py:399 +#: awx/conf/tests/unit/test_settings.py:435 awx/main/conf.py:23 +#: awx/main/conf.py:32 awx/main/conf.py:42 awx/main/conf.py:52 +#: awx/main/conf.py:64 awx/main/conf.py:77 awx/main/conf.py:90 +#: awx/main/conf.py:115 awx/main/conf.py:128 awx/main/conf.py:141 +#: awx/main/conf.py:153 awx/main/conf.py:161 awx/main/conf.py:172 +#: awx/main/conf.py:395 awx/main/conf.py:750 awx/main/conf.py:762 msgid "System" msgstr "" -#: awx/conf/tests/unit/test_registry.py:165 -#: awx/conf/tests/unit/test_registry.py:172 -#: awx/conf/tests/unit/test_registry.py:187 -#: awx/conf/tests/unit/test_registry.py:203 -#: awx/conf/tests/unit/test_registry.py:211 +#: awx/conf/tests/unit/test_registry.py:152 +#: awx/conf/tests/unit/test_registry.py:159 msgid "OtherSystem" msgstr "" -#: awx/conf/views.py:47 +#: awx/conf/views.py:48 msgid "Setting Categories" msgstr "" -#: awx/conf/views.py:71 +#: awx/conf/views.py:70 msgid "Setting Detail" msgstr "" -#: awx/conf/views.py:166 +#: awx/conf/views.py:162 msgid "Logging Connectivity Test" msgstr "" -#: awx/main/access.py:59 +#: awx/main/access.py:66 #, python-format msgid "Required related field %s for permission check." msgstr "" -#: awx/main/access.py:75 +#: awx/main/access.py:82 #, python-format msgid "Bad data found in related field %s." msgstr "" -#: awx/main/access.py:302 +#: awx/main/access.py:331 msgid "License is missing." msgstr "" -#: awx/main/access.py:304 +#: awx/main/access.py:333 msgid "License has expired." msgstr "" -#: awx/main/access.py:312 +#: awx/main/access.py:341 #, python-format msgid "License count of %s instances has been reached." msgstr "" -#: awx/main/access.py:314 +#: awx/main/access.py:343 #, python-format msgid "License count of %s instances has been exceeded." msgstr "" -#: awx/main/access.py:316 +#: awx/main/access.py:345 msgid "Host count exceeds available instances." msgstr "" -#: awx/main/access.py:320 +#: awx/main/access.py:363 awx/main/access.py:372 #, python-format -msgid "Feature %s is not enabled in the active license." -msgstr "" - -#: awx/main/access.py:322 -msgid "Features not found in active license." +msgid "" +"You have already reached the maximum number of %s hosts allowed for your " +"organization. Contact your System Administrator for assistance." msgstr "" -#: awx/main/access.py:836 +#: awx/main/access.py:927 msgid "Unable to change inventory on a host." msgstr "" -#: awx/main/access.py:853 awx/main/access.py:898 +#: awx/main/access.py:948 awx/main/access.py:990 msgid "Cannot associate two items from different inventories." msgstr "" -#: awx/main/access.py:886 +#: awx/main/access.py:978 msgid "Unable to change inventory on a group." msgstr "" -#: awx/main/access.py:1147 +#: awx/main/access.py:1261 msgid "Unable to change organization on a team." msgstr "" -#: awx/main/access.py:1163 +#: awx/main/access.py:1277 msgid "The {} role cannot be assigned to a team" msgstr "" -#: awx/main/access.py:1527 awx/main/access.py:1970 -msgid "Job was launched with prompts provided by another user." -msgstr "" - -#: awx/main/access.py:1547 -msgid "Job has been orphaned from its job template." +#: awx/main/access.py:1471 +msgid "Insufficient access to Job Template credentials." msgstr "" -#: awx/main/access.py:1549 -msgid "Job was launched with unknown prompted fields." +#: awx/main/access.py:1635 awx/main/access.py:2059 +msgid "Job was launched with secret prompts provided by another user." msgstr "" -#: awx/main/access.py:1551 -msgid "Job was launched with prompted fields." +#: awx/main/access.py:1644 +msgid "Job has been orphaned from its job template and organization." msgstr "" -#: awx/main/access.py:1553 -msgid " Organization level permissions required." +#: awx/main/access.py:1646 +msgid "Job was launched with prompted fields you do not have access to." msgstr "" -#: awx/main/access.py:1555 -msgid " You do not have permission to related resources." +#: awx/main/access.py:1648 +msgid "" +"Job was launched with unknown prompted fields. Organization admin " +"permissions required." msgstr "" -#: awx/main/access.py:1963 +#: awx/main/access.py:2049 msgid "Workflow Job was launched with unknown prompts." msgstr "" -#: awx/main/access.py:1974 +#: awx/main/access.py:2061 msgid "Job was launched with prompts you lack access to." msgstr "" -#: awx/main/access.py:1978 +#: awx/main/access.py:2063 msgid "Job was launched with prompts no longer accepted." msgstr "" -#: awx/main/access.py:1992 +#: awx/main/access.py:2075 msgid "" "You do not have permission to the workflow job resources required for " "relaunch." msgstr "" -#: awx/main/apps.py:8 -msgid "Main" +#: awx/main/analytics/collectors.py:36 +msgid "General platform configuration." msgstr "" -#: awx/main/conf.py:20 -msgid "Enable Activity Stream" +#: awx/main/analytics/collectors.py:68 +msgid "Counts of objects such as organizations, inventories, and projects" msgstr "" -#: awx/main/conf.py:21 -msgid "Enable capturing activity for the activity stream." +#: awx/main/analytics/collectors.py:103 +msgid "Counts of users and teams by organization" msgstr "" -#: awx/main/conf.py:30 -msgid "Enable Activity Stream for Inventory Sync" +#: awx/main/analytics/collectors.py:115 +msgid "Counts of credentials by credential type" msgstr "" -#: awx/main/conf.py:31 -msgid "" -"Enable capturing activity for the activity stream when running inventory " -"sync." +#: awx/main/analytics/collectors.py:127 +msgid "Inventories, their inventory sources, and host counts" msgstr "" -#: awx/main/conf.py:40 -msgid "All Users Visible to Organization Admins" +#: awx/main/analytics/collectors.py:152 +msgid "Counts of projects by source control type" msgstr "" -#: awx/main/conf.py:41 -msgid "" -"Controls whether any Organization Admin can view all users and teams, even " -"those not associated with their Organization." +#: awx/main/analytics/collectors.py:171 +msgid "Cluster topology and capacity" msgstr "" -#: awx/main/conf.py:50 -msgid "Organization Admins Can Manage Users and Teams" +#: awx/main/analytics/collectors.py:197 +msgid "Counts of jobs by status" msgstr "" -#: awx/main/conf.py:51 -msgid "" -"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." +#: awx/main/analytics/collectors.py:207 +msgid "Counts of jobs by execution node" msgstr "" -#: awx/main/conf.py:60 -msgid "Enable Administrator Alerts" +#: awx/main/analytics/collectors.py:222 +msgid "Metadata about the analytics collected" msgstr "" -#: awx/main/conf.py:61 -msgid "Email Admin users for system events that may require attention." +#: awx/main/analytics/collectors.py:285 +msgid "Automation task records" msgstr "" -#: awx/main/conf.py:71 -msgid "Base URL of the Tower host" +#: awx/main/analytics/collectors.py:314 +msgid "Data on jobs run" msgstr "" -#: awx/main/conf.py:72 -msgid "" -"This setting is used by services like notifications to render a valid url to " -"the Tower host." +#: awx/main/analytics/collectors.py:351 +msgid "Data on job templates" msgstr "" -#: awx/main/conf.py:81 -msgid "Remote Host Headers" +#: awx/main/analytics/collectors.py:374 +msgid "Data on workflow runs" msgstr "" -#: awx/main/conf.py:82 -msgid "" +#: awx/main/analytics/collectors.py:410 +msgid "Data on workflows" +msgstr "" + +#: awx/main/apps.py:8 +msgid "Main" +msgstr "" + +#: awx/main/conf.py:21 +msgid "Enable Activity Stream" +msgstr "" + +#: awx/main/conf.py:22 +msgid "Enable capturing activity for the activity stream." +msgstr "" + +#: awx/main/conf.py:30 +msgid "Enable Activity Stream for Inventory Sync" +msgstr "" + +#: awx/main/conf.py:31 +msgid "" +"Enable capturing activity for the activity stream when running inventory " +"sync." +msgstr "" + +#: awx/main/conf.py:39 +msgid "All Users Visible to Organization Admins" +msgstr "" + +#: awx/main/conf.py:40 +msgid "" +"Controls whether any Organization Admin can view all users and teams, even " +"those not associated with their Organization." +msgstr "" + +#: awx/main/conf.py:49 +msgid "Organization Admins Can Manage Users and Teams" +msgstr "" + +#: awx/main/conf.py:50 +msgid "" +"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." +msgstr "" + +#: awx/main/conf.py:61 +msgid "Base URL of the Tower host" +msgstr "" + +#: awx/main/conf.py:62 +msgid "" +"This setting is used by services like notifications to render a valid url to " +"the Tower host." +msgstr "" + +#: awx/main/conf.py:71 +msgid "Remote Host Headers" +msgstr "" + +#: awx/main/conf.py:72 +msgid "" "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." msgstr "" -#: awx/main/conf.py:94 -msgid "Proxy IP Whitelist" +#: awx/main/conf.py:84 +msgid "Proxy IP Allowed List" msgstr "" -#: awx/main/conf.py:95 +#: awx/main/conf.py:85 msgid "" "If Tower is behind a reverse proxy/load balancer, use this setting to " -"whitelist the proxy IP addresses from which Tower should trust custom " +"configure the proxy IP addresses from which Tower 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')" msgstr "" -#: awx/main/conf.py:121 +#: awx/main/conf.py:111 msgid "License" msgstr "" -#: awx/main/conf.py:122 +#: awx/main/conf.py:112 msgid "" "The license controls which features and functionality are enabled. Use /api/" -"v1/config/ to update or change the license." +"v2/config/ to update or change the license." +msgstr "" + +#: awx/main/conf.py:126 +msgid "Red Hat customer username" +msgstr "" + +#: awx/main/conf.py:127 +msgid "" +"This username is used to retrieve license information and to send Automation " +"Analytics" +msgstr "" + +#: awx/main/conf.py:139 +msgid "Red Hat customer password" msgstr "" -#: awx/main/conf.py:132 +#: awx/main/conf.py:140 +msgid "" +"This password is used to retrieve license information and to send Automation " +"Analytics" +msgstr "" + +#: awx/main/conf.py:151 +msgid "Automation Analytics upload URL" +msgstr "" + +#: awx/main/conf.py:152 +msgid "" +"This setting is used to to configure data collection for the Automation " +"Analytics dashboard" +msgstr "" + +#: awx/main/conf.py:160 +msgid "Unique identifier for an AWX/Tower installation" +msgstr "" + +#: awx/main/conf.py:169 +msgid "Custom virtual environment paths" +msgstr "" + +#: awx/main/conf.py:170 +msgid "" +"Paths where Tower will look for custom virtual environments (in addition to /" +"var/lib/awx/venv/). Enter one path per line." +msgstr "" + +#: awx/main/conf.py:180 msgid "Ansible Modules Allowed for Ad Hoc Jobs" msgstr "" -#: awx/main/conf.py:133 +#: awx/main/conf.py:181 msgid "List of modules allowed to be used by ad-hoc jobs." msgstr "" -#: awx/main/conf.py:134 awx/main/conf.py:156 awx/main/conf.py:165 -#: awx/main/conf.py:176 awx/main/conf.py:186 awx/main/conf.py:196 -#: awx/main/conf.py:206 awx/main/conf.py:217 awx/main/conf.py:229 -#: awx/main/conf.py:241 awx/main/conf.py:254 awx/main/conf.py:266 -#: awx/main/conf.py:276 awx/main/conf.py:287 awx/main/conf.py:297 -#: awx/main/conf.py:308 awx/main/conf.py:318 awx/main/conf.py:328 -#: awx/main/conf.py:340 awx/main/conf.py:352 awx/main/conf.py:364 -#: awx/main/conf.py:378 +#: awx/main/conf.py:182 awx/main/conf.py:204 awx/main/conf.py:213 +#: awx/main/conf.py:224 awx/main/conf.py:234 awx/main/conf.py:244 +#: awx/main/conf.py:254 awx/main/conf.py:266 awx/main/conf.py:279 +#: awx/main/conf.py:289 awx/main/conf.py:302 awx/main/conf.py:315 +#: awx/main/conf.py:327 awx/main/conf.py:338 awx/main/conf.py:349 +#: awx/main/conf.py:361 awx/main/conf.py:373 awx/main/conf.py:384 +#: awx/main/conf.py:404 awx/main/conf.py:414 awx/main/conf.py:424 +#: awx/main/conf.py:437 awx/main/conf.py:448 awx/main/conf.py:458 +#: awx/main/conf.py:469 awx/main/conf.py:479 awx/main/conf.py:489 +#: awx/main/conf.py:501 awx/main/conf.py:514 awx/main/conf.py:527 +#: awx/main/conf.py:542 awx/main/conf.py:555 msgid "Jobs" msgstr "" -#: awx/main/conf.py:143 +#: awx/main/conf.py:191 msgid "Always" msgstr "" -#: awx/main/conf.py:144 +#: awx/main/conf.py:192 msgid "Never" msgstr "" -#: awx/main/conf.py:145 +#: awx/main/conf.py:193 msgid "Only On Job Template Definitions" msgstr "" -#: awx/main/conf.py:148 +#: awx/main/conf.py:196 msgid "When can extra variables contain Jinja templates?" msgstr "" -#: awx/main/conf.py:150 +#: awx/main/conf.py:198 msgid "" "Ansible allows variable substitution via the Jinja2 templating language for " "--extra-vars. This poses a potential security risk where Tower users with " @@ -1913,195 +2126,294 @@ msgid "" "to \"template\" or \"never\"." msgstr "" -#: awx/main/conf.py:163 +#: awx/main/conf.py:211 msgid "Enable job isolation" msgstr "" -#: awx/main/conf.py:164 +#: awx/main/conf.py:212 msgid "" "Isolates an Ansible job from protected parts of the system to prevent " "exposing sensitive information." msgstr "" -#: awx/main/conf.py:172 +#: awx/main/conf.py:220 msgid "Job execution path" msgstr "" -#: awx/main/conf.py:173 +#: awx/main/conf.py:221 msgid "" "The directory in which Tower will create new temporary directories for job " "execution and isolation (such as credential files and custom inventory " "scripts)." msgstr "" -#: awx/main/conf.py:184 +#: awx/main/conf.py:232 msgid "Paths to hide from isolated jobs" msgstr "" -#: awx/main/conf.py:185 +#: awx/main/conf.py:233 msgid "" "Additional paths to hide from isolated processes. Enter one path per line." msgstr "" -#: awx/main/conf.py:194 +#: awx/main/conf.py:242 msgid "Paths to expose to isolated jobs" msgstr "" -#: awx/main/conf.py:195 +#: awx/main/conf.py:243 msgid "" -"Whitelist of paths that would otherwise be hidden to expose to isolated " -"jobs. Enter one path per line." +"List of paths that would otherwise be hidden to expose to isolated jobs. " +"Enter one path per line." msgstr "" -#: awx/main/conf.py:204 +#: awx/main/conf.py:252 msgid "Isolated status check interval" msgstr "" -#: awx/main/conf.py:205 +#: awx/main/conf.py:253 msgid "" "The number of seconds to sleep between status checks for jobs running on " "isolated instances." msgstr "" -#: awx/main/conf.py:214 +#: awx/main/conf.py:263 msgid "Isolated launch timeout" msgstr "" -#: awx/main/conf.py:215 +#: awx/main/conf.py:264 msgid "" "The timeout (in seconds) for launching jobs on isolated instances. This " "includes the time needed to copy source control files (playbooks) to the " "isolated instance." msgstr "" -#: awx/main/conf.py:226 +#: awx/main/conf.py:276 msgid "Isolated connection timeout" msgstr "" -#: awx/main/conf.py:227 +#: awx/main/conf.py:277 msgid "" "Ansible SSH connection timeout (in seconds) to use when communicating with " "isolated instances. Value should be substantially greater than expected " "network latency." msgstr "" -#: awx/main/conf.py:237 +#: awx/main/conf.py:287 +msgid "Isolated host key checking" +msgstr "" + +#: awx/main/conf.py:288 +msgid "" +"When set to True, AWX will enforce strict host key checking for " +"communication with isolated nodes." +msgstr "" + +#: awx/main/conf.py:298 msgid "Generate RSA keys for isolated instances" msgstr "" -#: awx/main/conf.py:238 +#: awx/main/conf.py:299 msgid "" "If set, a random RSA key will be generated and distributed to isolated " "instances. To disable this behavior and manage authentication for isolated " "instances outside of Tower, disable this setting." msgstr "" -#: awx/main/conf.py:252 awx/main/conf.py:253 +#: awx/main/conf.py:313 awx/main/conf.py:314 msgid "The RSA private key for SSH traffic to isolated instances" msgstr "" -#: awx/main/conf.py:264 awx/main/conf.py:265 +#: awx/main/conf.py:325 awx/main/conf.py:326 msgid "The RSA public key for SSH traffic to isolated instances" msgstr "" -#: awx/main/conf.py:274 +#: awx/main/conf.py:335 +msgid "Enable detailed resource profiling on all playbook runs" +msgstr "" + +#: awx/main/conf.py:336 +msgid "" +"If set, detailed resource profiling data will be collected on all jobs. This " +"data can be gathered with `sosreport`." +msgstr "" + +#: awx/main/conf.py:346 +msgid "Interval (in seconds) between polls for cpu usage." +msgstr "" + +#: awx/main/conf.py:347 +msgid "" +"Interval (in seconds) between polls for cpu usage. Setting this lower than " +"the default will affect playbook performance." +msgstr "" + +#: awx/main/conf.py:358 +msgid "Interval (in seconds) between polls for memory usage." +msgstr "" + +#: awx/main/conf.py:359 +msgid "" +"Interval (in seconds) between polls for memory usage. Setting this lower " +"than the default will affect playbook performance." +msgstr "" + +#: awx/main/conf.py:370 +msgid "Interval (in seconds) between polls for PID count." +msgstr "" + +#: awx/main/conf.py:371 +msgid "" +"Interval (in seconds) between polls for PID count. Setting this lower than " +"the default will affect playbook performance." +msgstr "" + +#: awx/main/conf.py:382 msgid "Extra Environment Variables" msgstr "" -#: awx/main/conf.py:275 +#: awx/main/conf.py:383 msgid "" "Additional environment variables set for playbook runs, inventory updates, " "project updates, and notification sending." msgstr "" -#: awx/main/conf.py:285 +#: awx/main/conf.py:393 +msgid "Gather data for Automation Analytics" +msgstr "" + +#: awx/main/conf.py:394 +msgid "Enables Tower to gather data on automation and send it to Red Hat." +msgstr "" + +#: awx/main/conf.py:402 +msgid "Run Project Updates With Higher Verbosity" +msgstr "" + +#: awx/main/conf.py:403 +msgid "" +"Adds the CLI -vvv flag to ansible-playbook runs of project_update.yml used " +"for project updates." +msgstr "" + +#: awx/main/conf.py:412 msgid "Enable Role Download" msgstr "" -#: awx/main/conf.py:286 +#: awx/main/conf.py:413 msgid "" "Allows roles to be dynamically downloaded from a requirements.yml file for " "SCM projects." msgstr "" -#: awx/main/conf.py:295 +#: awx/main/conf.py:422 +msgid "Enable Collection(s) Download" +msgstr "" + +#: awx/main/conf.py:423 +msgid "" +"Allows collections to be dynamically downloaded from a requirements.yml file " +"for SCM projects." +msgstr "" + +#: awx/main/conf.py:432 +msgid "Follow symlinks" +msgstr "" + +#: awx/main/conf.py:434 +msgid "" +"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." +msgstr "" + +#: awx/main/conf.py:445 +msgid "Ignore Ansible Galaxy SSL Certificate Verification" +msgstr "" + +#: awx/main/conf.py:446 +msgid "" +"If set to true, certificate validation will not be done when installing " +"content from any Galaxy server." +msgstr "" + +#: awx/main/conf.py:456 msgid "Standard Output Maximum Display Size" msgstr "" -#: awx/main/conf.py:296 +#: awx/main/conf.py:457 msgid "" "Maximum Size of Standard Output in bytes to display before requiring the " "output be downloaded." msgstr "" -#: awx/main/conf.py:305 +#: awx/main/conf.py:466 msgid "Job Event Standard Output Maximum Display Size" msgstr "" -#: awx/main/conf.py:307 +#: awx/main/conf.py:468 msgid "" "Maximum Size of Standard Output in bytes to display for a single job or ad " "hoc command event. `stdout` will end with `…` when truncated." msgstr "" -#: awx/main/conf.py:316 +#: awx/main/conf.py:477 msgid "Maximum Scheduled Jobs" msgstr "" -#: awx/main/conf.py:317 +#: awx/main/conf.py:478 msgid "" "Maximum number of the same job template that can be waiting to run when " "launching from a schedule before no more are created." msgstr "" -#: awx/main/conf.py:326 +#: awx/main/conf.py:487 msgid "Ansible Callback Plugins" msgstr "" -#: awx/main/conf.py:327 +#: awx/main/conf.py:488 msgid "" "List of paths to search for extra callback plugins to be used when running " "jobs. Enter one path per line." msgstr "" -#: awx/main/conf.py:337 +#: awx/main/conf.py:498 msgid "Default Job Timeout" msgstr "" -#: awx/main/conf.py:338 +#: awx/main/conf.py:499 msgid "" "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." msgstr "" -#: awx/main/conf.py:349 +#: awx/main/conf.py:511 msgid "Default Inventory Update Timeout" msgstr "" -#: awx/main/conf.py:350 +#: awx/main/conf.py:512 msgid "" "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." msgstr "" -#: awx/main/conf.py:361 +#: awx/main/conf.py:524 msgid "Default Project Update Timeout" msgstr "" -#: awx/main/conf.py:362 +#: awx/main/conf.py:525 msgid "" "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." msgstr "" -#: awx/main/conf.py:373 +#: awx/main/conf.py:537 msgid "Per-Host Ansible Fact Cache Timeout" msgstr "" -#: awx/main/conf.py:374 +#: awx/main/conf.py:538 msgid "" "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 " @@ -2110,62 +2422,74 @@ msgid "" "timeout should be imposed." msgstr "" -#: awx/main/conf.py:387 +#: awx/main/conf.py:552 +msgid "Maximum number of forks per job" +msgstr "" + +#: awx/main/conf.py:553 +msgid "" +"Saving a Job Template with more than this number of forks will result in an " +"error. When set to 0, no limit is applied." +msgstr "" + +#: awx/main/conf.py:564 msgid "Logging Aggregator" msgstr "" -#: awx/main/conf.py:388 +#: awx/main/conf.py:565 msgid "Hostname/IP where external logs will be sent to." msgstr "" -#: awx/main/conf.py:389 awx/main/conf.py:400 awx/main/conf.py:412 -#: awx/main/conf.py:422 awx/main/conf.py:434 awx/main/conf.py:449 -#: awx/main/conf.py:461 awx/main/conf.py:470 awx/main/conf.py:480 -#: awx/main/conf.py:492 awx/main/conf.py:503 awx/main/conf.py:515 -#: awx/main/conf.py:528 +#: awx/main/conf.py:566 awx/main/conf.py:577 awx/main/conf.py:589 +#: awx/main/conf.py:599 awx/main/conf.py:611 awx/main/conf.py:626 +#: awx/main/conf.py:638 awx/main/conf.py:647 awx/main/conf.py:657 +#: awx/main/conf.py:669 awx/main/conf.py:680 awx/main/conf.py:693 +#: awx/main/conf.py:706 awx/main/conf.py:718 awx/main/conf.py:729 +#: awx/main/conf.py:739 msgid "Logging" msgstr "" -#: awx/main/conf.py:397 +#: awx/main/conf.py:574 msgid "Logging Aggregator Port" msgstr "" -#: awx/main/conf.py:398 +#: awx/main/conf.py:575 msgid "" "Port on Logging Aggregator to send logs to (if required and not provided in " "Logging Aggregator)." msgstr "" -#: awx/main/conf.py:410 +#: awx/main/conf.py:587 msgid "Logging Aggregator Type" msgstr "" -#: awx/main/conf.py:411 +#: awx/main/conf.py:588 msgid "Format messages for the chosen log aggregator." msgstr "" -#: awx/main/conf.py:420 +#: awx/main/conf.py:597 msgid "Logging Aggregator Username" msgstr "" -#: awx/main/conf.py:421 -msgid "Username for external log aggregator (if required)." +#: awx/main/conf.py:598 +msgid "Username for external log aggregator (if required; HTTP/s only)." msgstr "" -#: awx/main/conf.py:432 +#: awx/main/conf.py:609 msgid "Logging Aggregator Password/Token" msgstr "" -#: awx/main/conf.py:433 +#: awx/main/conf.py:610 msgid "" -"Password or authentication token for external log aggregator (if required)." +"Password or authentication token for external log aggregator (if required; " +"HTTP/s only)." msgstr "" -#: awx/main/conf.py:442 +#: awx/main/conf.py:619 msgid "Loggers Sending Data to Log Aggregator Form" msgstr "" -#: awx/main/conf.py:443 +#: awx/main/conf.py:620 msgid "" "List of loggers that will send HTTP logs to the collector, these can include " "any or all of: \n" @@ -2175,11 +2499,11 @@ msgid "" "system_tracking - facts gathered from scan jobs." msgstr "" -#: awx/main/conf.py:456 +#: awx/main/conf.py:633 msgid "Log System Tracking Facts Individually" msgstr "" -#: awx/main/conf.py:457 +#: awx/main/conf.py:634 msgid "" "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. " @@ -2187,47 +2511,47 @@ msgid "" "efficiency in fact processing." msgstr "" -#: awx/main/conf.py:468 +#: awx/main/conf.py:645 msgid "Enable External Logging" msgstr "" -#: awx/main/conf.py:469 +#: awx/main/conf.py:646 msgid "Enable sending logs to external log aggregator." msgstr "" -#: awx/main/conf.py:478 +#: awx/main/conf.py:655 msgid "Cluster-wide Tower unique identifier." msgstr "" -#: awx/main/conf.py:479 +#: awx/main/conf.py:656 msgid "Useful to uniquely identify Tower instances." msgstr "" -#: awx/main/conf.py:488 +#: awx/main/conf.py:665 msgid "Logging Aggregator Protocol" msgstr "" -#: awx/main/conf.py:489 +#: awx/main/conf.py:666 msgid "" "Protocol used to communicate with log aggregator. HTTPS/HTTP assumes HTTPS " "unless http:// is explicitly used in the Logging Aggregator hostname." msgstr "" -#: awx/main/conf.py:499 +#: awx/main/conf.py:676 msgid "TCP Connection Timeout" msgstr "" -#: awx/main/conf.py:500 +#: awx/main/conf.py:677 msgid "" "Number of seconds for a TCP connection to external log aggregator to " "timeout. Applies to HTTPS and TCP log aggregator protocols." msgstr "" -#: awx/main/conf.py:510 +#: awx/main/conf.py:688 msgid "Enable/disable HTTPS certificate verification" msgstr "" -#: awx/main/conf.py:511 +#: awx/main/conf.py:689 msgid "" "Flag to control enable/disable of certificate verification when " "LOG_AGGREGATOR_PROTOCOL is \"https\". If enabled, Tower's log handler will " @@ -2235,11 +2559,11 @@ msgid "" "connection." msgstr "" -#: awx/main/conf.py:523 +#: awx/main/conf.py:701 msgid "Logging Aggregator Level Threshold" msgstr "" -#: awx/main/conf.py:524 +#: awx/main/conf.py:702 msgid "" "Level threshold used by log handler. Severities from lowest to highest are " "DEBUG, INFO, WARNING, ERROR, CRITICAL. Messages less severe than the " @@ -2247,1294 +2571,1537 @@ msgid "" "anlytics ignore this setting)" msgstr "" -#: awx/main/conf.py:547 awx/sso/conf.py:1264 +#: awx/main/conf.py:714 +msgid "Maximum disk persistance for external log aggregation (in GB)" +msgstr "" + +#: awx/main/conf.py:715 +msgid "" +"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." +msgstr "" + +#: awx/main/conf.py:725 +msgid "File system location for rsyslogd disk persistence" +msgstr "" + +#: awx/main/conf.py:726 +msgid "" +"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." +msgstr "" + +#: awx/main/conf.py:736 +msgid "Enable rsyslogd debugging" +msgstr "" + +#: awx/main/conf.py:737 +msgid "" +"Enabled high verbosity debugging for rsyslogd. Useful for debugging " +"connection issues for external log aggregation." +msgstr "" + +#: awx/main/conf.py:748 +msgid "Last gather date for Automation Analytics." +msgstr "" + +#: awx/main/conf.py:758 +msgid "Automation Analytics Gather Interval" +msgstr "" + +#: awx/main/conf.py:759 +msgid "Interval (in seconds) between data gathering." +msgstr "" + +#: awx/main/conf.py:782 awx/sso/conf.py:1251 msgid "\n" msgstr "" -#: awx/main/constants.py:17 +#: awx/main/constants.py:16 msgid "Sudo" msgstr "" -#: awx/main/constants.py:17 +#: awx/main/constants.py:16 msgid "Su" msgstr "" -#: awx/main/constants.py:17 +#: awx/main/constants.py:16 msgid "Pbrun" msgstr "" -#: awx/main/constants.py:17 +#: awx/main/constants.py:16 msgid "Pfexec" msgstr "" -#: awx/main/constants.py:18 +#: awx/main/constants.py:17 msgid "DZDO" msgstr "" -#: awx/main/constants.py:18 +#: awx/main/constants.py:17 msgid "Pmrun" msgstr "" -#: awx/main/constants.py:18 +#: awx/main/constants.py:17 msgid "Runas" msgstr "" -#: awx/main/constants.py:19 +#: awx/main/constants.py:18 msgid "Enable" msgstr "" -#: awx/main/constants.py:19 +#: awx/main/constants.py:18 msgid "Doas" msgstr "" +#: awx/main/constants.py:18 +msgid "Ksu" +msgstr "" + +#: awx/main/constants.py:19 +msgid "Machinectl" +msgstr "" + +#: awx/main/constants.py:19 +msgid "Sesu" +msgstr "" + #: awx/main/constants.py:21 msgid "None" msgstr "" -#: awx/main/fields.py:62 -#, python-brace-format -msgid "'{value}' is not one of ['{allowed_values}']" +#: awx/main/credential_plugins/aim.py:11 +msgid "CyberArk AIM URL" msgstr "" -#: awx/main/fields.py:421 -#, python-brace-format -msgid "{type} provided in relative path {path}, expected {expected_type}" +#: awx/main/credential_plugins/aim.py:16 +msgid "Application ID" msgstr "" -#: awx/main/fields.py:426 -#, python-brace-format -msgid "{type} provided, expected {expected_type}" +#: awx/main/credential_plugins/aim.py:21 +msgid "Client Key" msgstr "" -#: awx/main/fields.py:431 -#, python-brace-format -msgid "Schema validation error in relative path {path} ({error})" +#: awx/main/credential_plugins/aim.py:27 +msgid "Client Certificate" msgstr "" -#: awx/main/fields.py:552 -msgid "secret values must be of type string, not {}" +#: awx/main/credential_plugins/aim.py:33 +msgid "Verify SSL Certificates" msgstr "" -#: awx/main/fields.py:587 -#, python-format -msgid "cannot be set unless \"%s\" is set" +#: awx/main/credential_plugins/aim.py:39 +msgid "Object Query" msgstr "" -#: awx/main/fields.py:603 -#, python-format -msgid "required for %s" +#: awx/main/credential_plugins/aim.py:41 +msgid "" +"Lookup query for the object. Ex: Safe=TestSafe;Object=testAccountName123" msgstr "" -#: awx/main/fields.py:627 -msgid "must be set when SSH key is encrypted." +#: awx/main/credential_plugins/aim.py:44 +msgid "Object Query Format" msgstr "" -#: awx/main/fields.py:633 -msgid "should not be set when SSH key is not encrypted." +#: awx/main/credential_plugins/aim.py:50 +msgid "Reason" msgstr "" -#: awx/main/fields.py:691 -msgid "'dependencies' is not supported for custom credentials." +#: awx/main/credential_plugins/aim.py:52 +msgid "" +"Object request reason. This is only needed if it is required by the object's " +"policy." msgstr "" -#: awx/main/fields.py:705 -msgid "\"tower\" is a reserved field name" +#: awx/main/credential_plugins/azure_kv.py:21 +msgid "Vault URL (DNS Name)" msgstr "" -#: awx/main/fields.py:712 -#, python-format -msgid "field IDs must be unique (%s)" +#: awx/main/credential_plugins/azure_kv.py:26 +#: awx/main/models/credential/__init__.py:968 +msgid "Client ID" msgstr "" -#: awx/main/fields.py:725 -msgid "become_method is a reserved type name" +#: awx/main/credential_plugins/azure_kv.py:35 +#: awx/main/models/credential/__init__.py:977 +msgid "Tenant ID" msgstr "" -#: awx/main/fields.py:736 -#, python-brace-format -msgid "{sub_key} not allowed for {element_type} type ({element_id})" +#: awx/main/credential_plugins/azure_kv.py:39 +msgid "Cloud Environment" msgstr "" -#: awx/main/fields.py:794 -msgid "" -"Environment variable {} may affect Ansible configuration so its use is not " -"allowed in credentials." +#: awx/main/credential_plugins/azure_kv.py:40 +msgid "Specify which azure cloud environment to use." +msgstr "" + +#: awx/main/credential_plugins/azure_kv.py:46 +msgid "Secret Name" +msgstr "" + +#: awx/main/credential_plugins/azure_kv.py:48 +msgid "The name of the secret to look up." msgstr "" -#: awx/main/fields.py:800 -msgid "Environment variable {} is blacklisted from use in credentials." +#: awx/main/credential_plugins/azure_kv.py:51 +#: awx/main/credential_plugins/conjur.py:42 +msgid "Secret Version" msgstr "" -#: awx/main/fields.py:828 +#: awx/main/credential_plugins/azure_kv.py:53 +#: awx/main/credential_plugins/conjur.py:44 +#: awx/main/credential_plugins/hashivault.py:89 msgid "" -"Must define unnamed file injector in order to reference `tower.filename`." +"Used to specify a specific secret version (if left empty, the latest version " +"will be used)." msgstr "" -#: awx/main/fields.py:835 -msgid "Cannot directly reference reserved `tower` namespace container." +#: awx/main/credential_plugins/conjur.py:13 +msgid "Conjur URL" msgstr "" -#: awx/main/fields.py:845 -msgid "Must use multi-file syntax when injecting multiple files" +#: awx/main/credential_plugins/conjur.py:18 +msgid "API Key" msgstr "" -#: awx/main/fields.py:865 -#, python-brace-format -msgid "{sub_key} uses an undefined field ({error_msg})" +#: awx/main/credential_plugins/conjur.py:23 +#: awx/main/migrations/_inventory_source_vars.py:142 +msgid "Account" msgstr "" -#: awx/main/fields.py:872 -#, python-brace-format -msgid "" -"Syntax error rendering template for {sub_key} inside of {type} ({error_msg})" +#: awx/main/credential_plugins/conjur.py:27 +#: awx/main/models/credential/__init__.py:606 +#: awx/main/models/credential/__init__.py:662 +#: awx/main/models/credential/__init__.py:720 +#: awx/main/models/credential/__init__.py:793 +#: awx/main/models/credential/__init__.py:846 +#: awx/main/models/credential/__init__.py:872 +#: awx/main/models/credential/__init__.py:899 +#: awx/main/models/credential/__init__.py:959 +#: awx/main/models/credential/__init__.py:1032 +#: awx/main/models/credential/__init__.py:1063 +#: awx/main/models/credential/__init__.py:1113 +msgid "Username" msgstr "" -#: awx/main/middleware.py:161 -msgid "Formats of all available named urls" +#: awx/main/credential_plugins/conjur.py:31 +msgid "Public Key Certificate" +msgstr "" + +#: awx/main/credential_plugins/conjur.py:37 +msgid "Secret Identifier" +msgstr "" + +#: awx/main/credential_plugins/conjur.py:39 +msgid "The identifier for the secret e.g., /some/identifier" +msgstr "" + +#: awx/main/credential_plugins/hashivault.py:14 +msgid "Server URL" +msgstr "" + +#: awx/main/credential_plugins/hashivault.py:17 +msgid "The URL to the HashiCorp Vault" +msgstr "" + +#: awx/main/credential_plugins/hashivault.py:20 +#: awx/main/models/credential/__init__.py:998 +#: awx/main/models/credential/__init__.py:1015 +msgid "Token" +msgstr "" + +#: awx/main/credential_plugins/hashivault.py:23 +msgid "The access token used to authenticate to the Vault server" +msgstr "" + +#: awx/main/credential_plugins/hashivault.py:26 +msgid "CA Certificate" msgstr "" -#: awx/main/middleware.py:162 +#: awx/main/credential_plugins/hashivault.py:29 msgid "" -"Read-only list of key-value pairs that shows the standard format of all " -"available named URLs." +"The CA certificate used to verify the SSL certificate of the Vault server" msgstr "" -#: awx/main/middleware.py:164 awx/main/middleware.py:174 -msgid "Named URL" +#: awx/main/credential_plugins/hashivault.py:32 +msgid "AppRole role_id" msgstr "" -#: awx/main/middleware.py:171 -msgid "List of all named url graph nodes." +#: awx/main/credential_plugins/hashivault.py:35 +msgid "The Role ID for AppRole Authentication" +msgstr "" + +#: awx/main/credential_plugins/hashivault.py:38 +msgid "AppRole secret_id" +msgstr "" + +#: awx/main/credential_plugins/hashivault.py:42 +msgid "The Secret ID for AppRole Authentication" +msgstr "" + +#: awx/main/credential_plugins/hashivault.py:45 +msgid "Path to Approle Auth" msgstr "" -#: awx/main/middleware.py:172 +#: awx/main/credential_plugins/hashivault.py:49 msgid "" -"Read-only list of key-value pairs that exposes named URL graph topology. Use " -"this list to programmatically generate named URLs for resources" +"The AppRole Authentication path to use if one isn't provided in the metadata " +"when linking to an input field. Defaults to 'approle'" msgstr "" -#: awx/main/migrations/_reencrypt.py:26 awx/main/models/notifications.py:35 -msgid "Email" +#: awx/main/credential_plugins/hashivault.py:54 +msgid "Path to Secret" msgstr "" -#: awx/main/migrations/_reencrypt.py:27 awx/main/models/notifications.py:36 -msgid "Slack" +#: awx/main/credential_plugins/hashivault.py:56 +msgid "The path to the secret stored in the secret backend e.g, /some/secret/" msgstr "" -#: awx/main/migrations/_reencrypt.py:28 awx/main/models/notifications.py:37 -msgid "Twilio" +#: awx/main/credential_plugins/hashivault.py:59 +msgid "Path to Auth" msgstr "" -#: awx/main/migrations/_reencrypt.py:29 awx/main/models/notifications.py:38 -msgid "Pagerduty" +#: awx/main/credential_plugins/hashivault.py:62 +msgid "The path where the Authentication method is mounted e.g, approle" msgstr "" -#: awx/main/migrations/_reencrypt.py:30 awx/main/models/notifications.py:39 -msgid "HipChat" +#: awx/main/credential_plugins/hashivault.py:70 +msgid "API Version" msgstr "" -#: awx/main/migrations/_reencrypt.py:31 awx/main/models/notifications.py:41 -msgid "Mattermost" +#: awx/main/credential_plugins/hashivault.py:72 +msgid "" +"API v1 is for static key/value lookups. API v2 is for versioned key/value " +"lookups." msgstr "" -#: awx/main/migrations/_reencrypt.py:32 awx/main/models/notifications.py:40 -msgid "Webhook" +#: awx/main/credential_plugins/hashivault.py:77 +msgid "Name of Secret Backend" msgstr "" -#: awx/main/migrations/_reencrypt.py:33 awx/main/models/notifications.py:43 -msgid "IRC" +#: awx/main/credential_plugins/hashivault.py:79 +msgid "" +"The name of the kv secret backend (if left empty, the first segment of the " +"secret path will be used)." msgstr "" -#: awx/main/models/activity_stream.py:25 -msgid "Entity Created" +#: awx/main/credential_plugins/hashivault.py:82 +#: awx/main/migrations/_inventory_source_vars.py:147 +msgid "Key Name" msgstr "" -#: awx/main/models/activity_stream.py:26 -msgid "Entity Updated" +#: awx/main/credential_plugins/hashivault.py:84 +msgid "The name of the key to look up in the secret." msgstr "" -#: awx/main/models/activity_stream.py:27 -msgid "Entity Deleted" +#: awx/main/credential_plugins/hashivault.py:87 +msgid "Secret Version (v2 only)" msgstr "" -#: awx/main/models/activity_stream.py:28 -msgid "Entity Associated with another Entity" +#: awx/main/credential_plugins/hashivault.py:96 +msgid "Unsigned Public Key" msgstr "" -#: awx/main/models/activity_stream.py:29 -msgid "Entity was Disassociated with another Entity" +#: awx/main/credential_plugins/hashivault.py:101 +msgid "Role Name" msgstr "" -#: awx/main/models/ad_hoc_commands.py:95 -msgid "No valid inventory." +#: awx/main/credential_plugins/hashivault.py:103 +msgid "The name of the role used to sign." msgstr "" -#: awx/main/models/ad_hoc_commands.py:102 -msgid "You must provide a machine / SSH credential." +#: awx/main/credential_plugins/hashivault.py:106 +msgid "Valid Principals" msgstr "" -#: awx/main/models/ad_hoc_commands.py:113 -#: awx/main/models/ad_hoc_commands.py:121 -msgid "Invalid type for ad hoc command" +#: awx/main/credential_plugins/hashivault.py:108 +msgid "" +"Valid principals (either usernames or hostnames) that the certificate should " +"be signed for." msgstr "" -#: awx/main/models/ad_hoc_commands.py:116 -msgid "Unsupported module for ad hoc commands." +#: awx/main/fields.py:67 +#, python-brace-format +msgid "'{value}' is not one of ['{allowed_values}']" +msgstr "" + +#: awx/main/fields.py:439 +#, python-brace-format +msgid "{type} provided in relative path {path}, expected {expected_type}" +msgstr "" + +#: awx/main/fields.py:444 +#, python-brace-format +msgid "{type} provided, expected {expected_type}" +msgstr "" + +#: awx/main/fields.py:449 +#, python-brace-format +msgid "Schema validation error in relative path {path} ({error})" msgstr "" -#: awx/main/models/ad_hoc_commands.py:124 +#: awx/main/fields.py:558 #, python-format -msgid "No argument passed to %s module." +msgid "required for %s" msgstr "" -#: awx/main/models/base.py:33 awx/main/models/base.py:39 -#: awx/main/models/base.py:44 awx/main/models/base.py:49 -msgid "Run" +#: awx/main/fields.py:632 +msgid "secret values must be of type string, not {}" msgstr "" -#: awx/main/models/base.py:34 awx/main/models/base.py:40 -#: awx/main/models/base.py:45 awx/main/models/base.py:50 -msgid "Check" +#: awx/main/fields.py:675 +#, python-format +msgid "cannot be set unless \"%s\" is set" msgstr "" -#: awx/main/models/base.py:35 -msgid "Scan" +#: awx/main/fields.py:710 +msgid "must be set when SSH key is encrypted." msgstr "" -#: awx/main/models/credential/__init__.py:110 -msgid "Host" +#: awx/main/fields.py:718 +msgid "should not be set when SSH key is not encrypted." msgstr "" -#: awx/main/models/credential/__init__.py:111 -msgid "The hostname or IP address to use." +#: awx/main/fields.py:777 +msgid "'dependencies' is not supported for custom credentials." msgstr "" -#: awx/main/models/credential/__init__.py:117 -#: awx/main/models/credential/__init__.py:683 -#: awx/main/models/credential/__init__.py:738 -#: awx/main/models/credential/__init__.py:803 -#: awx/main/models/credential/__init__.py:881 -#: awx/main/models/credential/__init__.py:927 -#: awx/main/models/credential/__init__.py:955 -#: awx/main/models/credential/__init__.py:984 -#: awx/main/models/credential/__init__.py:1048 -#: awx/main/models/credential/__init__.py:1089 -#: awx/main/models/credential/__init__.py:1122 -#: awx/main/models/credential/__init__.py:1174 -msgid "Username" +#: awx/main/fields.py:791 +msgid "\"tower\" is a reserved field name" msgstr "" -#: awx/main/models/credential/__init__.py:118 -msgid "Username for this credential." +#: awx/main/fields.py:798 +#, python-format +msgid "field IDs must be unique (%s)" msgstr "" -#: awx/main/models/credential/__init__.py:124 -#: awx/main/models/credential/__init__.py:687 -#: awx/main/models/credential/__init__.py:742 -#: awx/main/models/credential/__init__.py:807 -#: awx/main/models/credential/__init__.py:931 -#: awx/main/models/credential/__init__.py:959 -#: awx/main/models/credential/__init__.py:988 -#: awx/main/models/credential/__init__.py:1052 -#: awx/main/models/credential/__init__.py:1093 -#: awx/main/models/credential/__init__.py:1126 -#: awx/main/models/credential/__init__.py:1178 -msgid "Password" +#: awx/main/fields.py:813 +msgid "{} is not a {}" +msgstr "" + +#: awx/main/fields.py:819 +#, python-brace-format +msgid "{sub_key} not allowed for {element_type} type ({element_id})" msgstr "" -#: awx/main/models/credential/__init__.py:125 +#: awx/main/fields.py:877 msgid "" -"Password for this credential (or \"ASK\" to prompt the user for machine " -"credentials)." +"Environment variable {} may affect Ansible configuration so its use is not " +"allowed in credentials." +msgstr "" + +#: awx/main/fields.py:883 +msgid "Environment variable {} is not allowed to be used in credentials." +msgstr "" + +#: awx/main/fields.py:911 +msgid "" +"Must define unnamed file injector in order to reference `tower.filename`." msgstr "" -#: awx/main/models/credential/__init__.py:132 -msgid "Security Token" +#: awx/main/fields.py:918 +msgid "Cannot directly reference reserved `tower` namespace container." msgstr "" -#: awx/main/models/credential/__init__.py:133 -msgid "Security Token for this credential" +#: awx/main/fields.py:928 +msgid "Must use multi-file syntax when injecting multiple files" msgstr "" -#: awx/main/models/credential/__init__.py:139 -msgid "Project" +#: awx/main/fields.py:948 +#, python-brace-format +msgid "{sub_key} uses an undefined field ({error_msg})" msgstr "" -#: awx/main/models/credential/__init__.py:140 -msgid "The identifier for the project." +#: awx/main/fields.py:955 +msgid "Encountered unsafe code execution: {}" msgstr "" -#: awx/main/models/credential/__init__.py:146 -msgid "Domain" +#: awx/main/fields.py:959 +#, python-brace-format +msgid "" +"Syntax error rendering template for {sub_key} inside of {type} ({error_msg})" msgstr "" -#: awx/main/models/credential/__init__.py:147 -msgid "The identifier for the domain." +#: awx/main/middleware.py:118 +msgid "Formats of all available named urls" msgstr "" -#: awx/main/models/credential/__init__.py:152 -msgid "SSH private key" +#: awx/main/middleware.py:119 +msgid "" +"Read-only list of key-value pairs that shows the standard format of all " +"available named URLs." msgstr "" -#: awx/main/models/credential/__init__.py:153 -msgid "RSA or DSA private key to be used instead of password." +#: awx/main/middleware.py:121 awx/main/middleware.py:131 +msgid "Named URL" msgstr "" -#: awx/main/models/credential/__init__.py:159 -msgid "SSH key unlock" +#: awx/main/middleware.py:128 +msgid "List of all named url graph nodes." msgstr "" -#: awx/main/models/credential/__init__.py:160 +#: awx/main/middleware.py:129 msgid "" -"Passphrase to unlock SSH private key if encrypted (or \"ASK\" to prompt the " -"user for machine credentials)." +"Read-only list of key-value pairs that exposes named URL graph topology. Use " +"this list to programmatically generate named URLs for resources" +msgstr "" + +#: awx/main/migrations/_inventory_source_vars.py:140 +msgid "Image ID" +msgstr "" + +#: awx/main/migrations/_inventory_source_vars.py:141 +msgid "Availability Zone" +msgstr "" + +#: awx/main/migrations/_inventory_source_vars.py:143 +msgid "Instance ID" +msgstr "" + +#: awx/main/migrations/_inventory_source_vars.py:144 +msgid "Instance State" +msgstr "" + +#: awx/main/migrations/_inventory_source_vars.py:145 +msgid "Platform" +msgstr "" + +#: awx/main/migrations/_inventory_source_vars.py:146 +msgid "Instance Type" +msgstr "" + +#: awx/main/migrations/_inventory_source_vars.py:148 +msgid "Region" msgstr "" -#: awx/main/models/credential/__init__.py:168 -msgid "Privilege escalation method." +#: awx/main/migrations/_inventory_source_vars.py:149 +msgid "Security Group" msgstr "" -#: awx/main/models/credential/__init__.py:174 -msgid "Privilege escalation username." +#: awx/main/migrations/_inventory_source_vars.py:150 +msgid "Tags" msgstr "" -#: awx/main/models/credential/__init__.py:180 -msgid "Password for privilege escalation method." +#: awx/main/migrations/_inventory_source_vars.py:151 +msgid "Tag None" msgstr "" -#: awx/main/models/credential/__init__.py:186 -msgid "Vault password (or \"ASK\" to prompt the user)." +#: awx/main/migrations/_inventory_source_vars.py:152 +msgid "VPC ID" msgstr "" -#: awx/main/models/credential/__init__.py:190 -msgid "Whether to use the authorize mechanism." +#: awx/main/models/activity_stream.py:28 +msgid "Entity Created" msgstr "" -#: awx/main/models/credential/__init__.py:196 -msgid "Password used by the authorize mechanism." +#: awx/main/models/activity_stream.py:29 +msgid "Entity Updated" msgstr "" -#: awx/main/models/credential/__init__.py:202 -msgid "Client Id or Application Id for the credential" +#: awx/main/models/activity_stream.py:30 +msgid "Entity Deleted" msgstr "" -#: awx/main/models/credential/__init__.py:208 -msgid "Secret Token for this credential" +#: awx/main/models/activity_stream.py:31 +msgid "Entity Associated with another Entity" msgstr "" -#: awx/main/models/credential/__init__.py:214 -msgid "Subscription identifier for this credential" +#: awx/main/models/activity_stream.py:32 +msgid "Entity was Disassociated with another Entity" msgstr "" -#: awx/main/models/credential/__init__.py:220 -msgid "Tenant identifier for this credential" +#: awx/main/models/activity_stream.py:45 +msgid "The cluster node the activity took place on." msgstr "" -#: awx/main/models/credential/__init__.py:244 +#: awx/main/models/ad_hoc_commands.py:97 +msgid "No valid inventory." +msgstr "" + +#: awx/main/models/ad_hoc_commands.py:104 +msgid "You must provide a machine / SSH credential." +msgstr "" + +#: awx/main/models/ad_hoc_commands.py:115 +#: awx/main/models/ad_hoc_commands.py:123 +msgid "Invalid type for ad hoc command" +msgstr "" + +#: awx/main/models/ad_hoc_commands.py:118 +msgid "Unsupported module for ad hoc commands." +msgstr "" + +#: awx/main/models/ad_hoc_commands.py:126 +#, python-format +msgid "No argument passed to %s module." +msgstr "" + +#: awx/main/models/base.py:34 awx/main/models/base.py:40 +#: awx/main/models/base.py:45 awx/main/models/base.py:50 +msgid "Run" +msgstr "" + +#: awx/main/models/base.py:35 awx/main/models/base.py:41 +#: awx/main/models/base.py:46 awx/main/models/base.py:51 +msgid "Check" +msgstr "" + +#: awx/main/models/base.py:36 +msgid "Scan" +msgstr "" + +#: awx/main/models/credential/__init__.py:96 msgid "" "Specify the type of credential you want to create. Refer to the Ansible " "Tower documentation for details on each type." msgstr "" -#: awx/main/models/credential/__init__.py:258 -#: awx/main/models/credential/__init__.py:467 +#: awx/main/models/credential/__init__.py:114 +#: awx/main/models/credential/__init__.py:358 msgid "" -"Enter inputs using either JSON or YAML syntax. Use the radio button to " -"toggle between the two. Refer to the Ansible Tower documentation for example " -"syntax." +"Enter inputs using either JSON or YAML syntax. Refer to the Ansible Tower " +"documentation for example syntax." msgstr "" -#: awx/main/models/credential/__init__.py:448 -#: awx/main/models/credential/__init__.py:678 +#: awx/main/models/credential/__init__.py:329 +#: awx/main/models/credential/__init__.py:602 msgid "Machine" msgstr "" -#: awx/main/models/credential/__init__.py:449 -#: awx/main/models/credential/__init__.py:769 +#: awx/main/models/credential/__init__.py:330 +#: awx/main/models/credential/__init__.py:688 msgid "Vault" msgstr "" -#: awx/main/models/credential/__init__.py:450 -#: awx/main/models/credential/__init__.py:798 +#: awx/main/models/credential/__init__.py:331 +#: awx/main/models/credential/__init__.py:715 msgid "Network" msgstr "" -#: awx/main/models/credential/__init__.py:451 -#: awx/main/models/credential/__init__.py:733 +#: awx/main/models/credential/__init__.py:332 +#: awx/main/models/credential/__init__.py:657 msgid "Source Control" msgstr "" -#: awx/main/models/credential/__init__.py:452 +#: awx/main/models/credential/__init__.py:333 msgid "Cloud" msgstr "" -#: awx/main/models/credential/__init__.py:453 -#: awx/main/models/credential/__init__.py:1084 +#: awx/main/models/credential/__init__.py:334 +msgid "Personal Access Token" +msgstr "" + +#: awx/main/models/credential/__init__.py:335 +#: awx/main/models/credential/__init__.py:1027 msgid "Insights" msgstr "" -#: awx/main/models/credential/__init__.py:474 +#: awx/main/models/credential/__init__.py:336 +msgid "External" +msgstr "" + +#: awx/main/models/credential/__init__.py:337 +msgid "Kubernetes" +msgstr "" + +#: awx/main/models/credential/__init__.py:338 +msgid "Galaxy/Automation Hub" +msgstr "" + +#: awx/main/models/credential/__init__.py:364 msgid "" -"Enter injectors using either JSON or YAML syntax. Use the radio button to " -"toggle between the two. Refer to the Ansible Tower documentation for example " -"syntax." +"Enter injectors using either JSON or YAML syntax. Refer to the Ansible Tower " +"documentation for example syntax." msgstr "" -#: awx/main/models/credential/__init__.py:525 +#: awx/main/models/credential/__init__.py:433 #, python-format msgid "adding %s credential type" msgstr "" -#: awx/main/models/credential/__init__.py:693 -#: awx/main/models/credential/__init__.py:812 +#: awx/main/models/credential/__init__.py:610 +#: awx/main/models/credential/__init__.py:666 +#: awx/main/models/credential/__init__.py:724 +#: awx/main/models/credential/__init__.py:850 +#: awx/main/models/credential/__init__.py:876 +#: awx/main/models/credential/__init__.py:903 +#: awx/main/models/credential/__init__.py:963 +#: awx/main/models/credential/__init__.py:1036 +#: awx/main/models/credential/__init__.py:1067 +#: awx/main/models/credential/__init__.py:1119 +msgid "Password" +msgstr "" + +#: awx/main/models/credential/__init__.py:616 +#: awx/main/models/credential/__init__.py:729 msgid "SSH Private Key" msgstr "" -#: awx/main/models/credential/__init__.py:700 -#: awx/main/models/credential/__init__.py:754 -#: awx/main/models/credential/__init__.py:819 +#: awx/main/models/credential/__init__.py:623 +msgid "Signed SSH Certificate" +msgstr "" + +#: awx/main/models/credential/__init__.py:629 +#: awx/main/models/credential/__init__.py:678 +#: awx/main/models/credential/__init__.py:736 msgid "Private Key Passphrase" msgstr "" -#: awx/main/models/credential/__init__.py:706 +#: awx/main/models/credential/__init__.py:635 msgid "Privilege Escalation Method" msgstr "" -#: awx/main/models/credential/__init__.py:708 +#: awx/main/models/credential/__init__.py:637 msgid "" "Specify a method for \"become\" operations. This is equivalent to specifying " "the --become-method Ansible parameter." msgstr "" -#: awx/main/models/credential/__init__.py:713 +#: awx/main/models/credential/__init__.py:642 msgid "Privilege Escalation Username" msgstr "" -#: awx/main/models/credential/__init__.py:717 +#: awx/main/models/credential/__init__.py:646 msgid "Privilege Escalation Password" msgstr "" -#: awx/main/models/credential/__init__.py:747 +#: awx/main/models/credential/__init__.py:671 msgid "SCM Private Key" msgstr "" -#: awx/main/models/credential/__init__.py:774 +#: awx/main/models/credential/__init__.py:693 msgid "Vault Password" msgstr "" -#: awx/main/models/credential/__init__.py:780 +#: awx/main/models/credential/__init__.py:699 msgid "Vault Identifier" msgstr "" -#: awx/main/models/credential/__init__.py:783 +#: awx/main/models/credential/__init__.py:702 msgid "" "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+." msgstr "" -#: awx/main/models/credential/__init__.py:824 +#: awx/main/models/credential/__init__.py:741 msgid "Authorize" msgstr "" -#: awx/main/models/credential/__init__.py:828 +#: awx/main/models/credential/__init__.py:745 msgid "Authorize Password" msgstr "" -#: awx/main/models/credential/__init__.py:845 +#: awx/main/models/credential/__init__.py:759 msgid "Amazon Web Services" msgstr "" -#: awx/main/models/credential/__init__.py:850 +#: awx/main/models/credential/__init__.py:764 msgid "Access Key" msgstr "" -#: awx/main/models/credential/__init__.py:854 +#: awx/main/models/credential/__init__.py:768 msgid "Secret Key" msgstr "" -#: awx/main/models/credential/__init__.py:859 +#: awx/main/models/credential/__init__.py:773 msgid "STS Token" msgstr "" -#: awx/main/models/credential/__init__.py:862 +#: awx/main/models/credential/__init__.py:776 msgid "" "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." msgstr "" -#: awx/main/models/credential/__init__.py:876 awx/main/models/inventory.py:1014 +#: awx/main/models/credential/__init__.py:788 awx/main/models/inventory.py:826 msgid "OpenStack" msgstr "" -#: awx/main/models/credential/__init__.py:885 +#: awx/main/models/credential/__init__.py:797 msgid "Password (API Key)" msgstr "" -#: awx/main/models/credential/__init__.py:890 -#: awx/main/models/credential/__init__.py:1117 +#: awx/main/models/credential/__init__.py:802 +#: awx/main/models/credential/__init__.py:1058 msgid "Host (Authentication URL)" msgstr "" -#: awx/main/models/credential/__init__.py:892 +#: awx/main/models/credential/__init__.py:804 msgid "" "The host to authenticate with. For example, https://openstack.business.com/" "v2.0/" msgstr "" -#: awx/main/models/credential/__init__.py:896 +#: awx/main/models/credential/__init__.py:808 msgid "Project (Tenant Name)" msgstr "" -#: awx/main/models/credential/__init__.py:900 +#: awx/main/models/credential/__init__.py:812 +msgid "Project (Domain Name)" +msgstr "" + +#: awx/main/models/credential/__init__.py:816 msgid "Domain Name" msgstr "" -#: awx/main/models/credential/__init__.py:902 +#: awx/main/models/credential/__init__.py:818 msgid "" "OpenStack domains define administrative boundaries. It is only needed for " "Keystone v3 authentication URLs. Refer to Ansible Tower documentation for " "common scenarios." msgstr "" -#: awx/main/models/credential/__init__.py:916 awx/main/models/inventory.py:1011 +#: awx/main/models/credential/__init__.py:824 +msgid "Region Name" +msgstr "" + +#: awx/main/models/credential/__init__.py:826 +msgid "" +"For some cloud providers, like OVH, region must be specified." +msgstr "" + +#: awx/main/models/credential/__init__.py:824 +#: awx/main/models/credential/__init__.py:1131 +#: awx/main/models/credential/__init__.py:1166 +msgid "Verify SSL" +msgstr "" + +#: awx/main/models/credential/__init__.py:835 awx/main/models/inventory.py:824 msgid "VMware vCenter" msgstr "" -#: awx/main/models/credential/__init__.py:921 +#: awx/main/models/credential/__init__.py:840 msgid "VCenter Host" msgstr "" -#: awx/main/models/credential/__init__.py:923 +#: awx/main/models/credential/__init__.py:842 msgid "" "Enter the hostname or IP address that corresponds to your VMware vCenter." msgstr "" -#: awx/main/models/credential/__init__.py:944 awx/main/models/inventory.py:1012 +#: awx/main/models/credential/__init__.py:861 awx/main/models/inventory.py:825 msgid "Red Hat Satellite 6" msgstr "" -#: awx/main/models/credential/__init__.py:949 +#: awx/main/models/credential/__init__.py:866 msgid "Satellite 6 URL" msgstr "" -#: awx/main/models/credential/__init__.py:951 +#: awx/main/models/credential/__init__.py:868 msgid "" "Enter the URL that corresponds to your Red Hat Satellite 6 server. For " "example, https://satellite.example.org" msgstr "" -#: awx/main/models/credential/__init__.py:972 awx/main/models/inventory.py:1013 +#: awx/main/models/credential/__init__.py:887 msgid "Red Hat CloudForms" msgstr "" -#: awx/main/models/credential/__init__.py:977 +#: awx/main/models/credential/__init__.py:892 msgid "CloudForms URL" msgstr "" -#: awx/main/models/credential/__init__.py:979 +#: awx/main/models/credential/__init__.py:894 msgid "" "Enter the URL for the virtual machine that corresponds to your CloudForms " "instance. For example, https://cloudforms.example.org" msgstr "" -#: awx/main/models/credential/__init__.py:1001 -#: awx/main/models/inventory.py:1009 +#: awx/main/models/credential/__init__.py:914 awx/main/models/inventory.py:822 msgid "Google Compute Engine" msgstr "" -#: awx/main/models/credential/__init__.py:1006 +#: awx/main/models/credential/__init__.py:919 msgid "Service Account Email Address" msgstr "" -#: awx/main/models/credential/__init__.py:1008 +#: awx/main/models/credential/__init__.py:921 msgid "" "The email address assigned to the Google Compute Engine service account." msgstr "" -#: awx/main/models/credential/__init__.py:1014 +#: awx/main/models/credential/__init__.py:927 msgid "" "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" msgstr "" -#: awx/main/models/credential/__init__.py:1020 +#: awx/main/models/credential/__init__.py:933 msgid "RSA Private Key" msgstr "" -#: awx/main/models/credential/__init__.py:1025 +#: awx/main/models/credential/__init__.py:938 msgid "" "Paste the contents of the PEM file associated with the service account email." msgstr "" -#: awx/main/models/credential/__init__.py:1037 -#: awx/main/models/inventory.py:1010 +#: awx/main/models/credential/__init__.py:948 awx/main/models/inventory.py:823 msgid "Microsoft Azure Resource Manager" msgstr "" -#: awx/main/models/credential/__init__.py:1042 +#: awx/main/models/credential/__init__.py:953 msgid "Subscription ID" msgstr "" -#: awx/main/models/credential/__init__.py:1044 +#: awx/main/models/credential/__init__.py:955 msgid "Subscription ID is an Azure construct, which is mapped to a username." msgstr "" -#: awx/main/models/credential/__init__.py:1057 -msgid "Client ID" +#: awx/main/models/credential/__init__.py:981 +msgid "Azure Cloud Environment" msgstr "" -#: awx/main/models/credential/__init__.py:1066 -msgid "Tenant ID" +#: awx/main/models/credential/__init__.py:983 +msgid "" +"Environment variable AZURE_CLOUD_ENVIRONMENT when using Azure GovCloud or " +"Azure stack." msgstr "" -#: awx/main/models/credential/__init__.py:1070 -msgid "Azure Cloud Environment" +#: awx/main/models/credential/__init__.py:993 +msgid "GitHub Personal Access Token" +msgstr "" + +#: awx/main/models/credential/__init__.py:1001 +msgid "This token needs to come from your profile settings in GitHub" +msgstr "" + +#: awx/main/models/credential/__init__.py:1010 +msgid "GitLab Personal Access Token" +msgstr "" + +#: awx/main/models/credential/__init__.py:1018 +msgid "This token needs to come from your profile settings in GitLab" +msgstr "" + +#: awx/main/models/credential/__init__.py:1053 awx/main/models/inventory.py:827 +msgid "Red Hat Virtualization" +msgstr "" + +#: awx/main/models/credential/__init__.py:1060 +msgid "The host to authenticate with." msgstr "" #: awx/main/models/credential/__init__.py:1072 +msgid "CA File" +msgstr "" + +#: awx/main/models/credential/__init__.py:1074 +msgid "Absolute file path to the CA file to use (optional)" +msgstr "" + +#: awx/main/models/credential/__init__.py:1103 awx/main/models/inventory.py:828 +msgid "Ansible Tower" +msgstr "" + +#: awx/main/models/credential/__init__.py:1108 +msgid "Ansible Tower Hostname" +msgstr "" + +#: awx/main/models/credential/__init__.py:1110 +msgid "The Ansible Tower base URL to authenticate with." +msgstr "" + +#: awx/main/models/credential/__init__.py:1115 msgid "" -"Environment variable AZURE_CLOUD_ENVIRONMENT when using Azure GovCloud or " -"Azure stack." +"The Ansible Tower user to authenticate as.This should not be set if an OAuth " +"token is being used." +msgstr "" + +#: awx/main/models/credential/__init__.py:1124 +msgid "OAuth Token" +msgstr "" + +#: awx/main/models/credential/__init__.py:1127 +msgid "" +"An OAuth token to use to authenticate to Tower with.This should not be set " +"if username/password are being used." +msgstr "" + +#: awx/main/models/credential/__init__.py:1152 +msgid "OpenShift or Kubernetes API Bearer Token" +msgstr "" + +#: awx/main/models/credential/__init__.py:1156 +msgid "OpenShift or Kubernetes API Endpoint" +msgstr "" + +#: awx/main/models/credential/__init__.py:1158 +msgid "The OpenShift or Kubernetes API Endpoint to authenticate with." +msgstr "" + +#: awx/main/models/credential/__init__.py:1161 +msgid "API authentication bearer token" +msgstr "" + +#: awx/main/models/credential/__init__.py:1171 +msgid "Certificate Authority data" +msgstr "" + +#: awx/main/models/credential/__init__.py:1184 +msgid "Ansible Galaxy/Automation Hub API Token" +msgstr "" + +#: awx/main/models/credential/__init__.py:1188 +msgid "Galaxy Server URL" msgstr "" -#: awx/main/models/credential/__init__.py:1112 -#: awx/main/models/inventory.py:1015 -msgid "Red Hat Virtualization" +#: awx/main/models/credential/__init__.py:1190 +msgid "The URL of the Galaxy instance to connect to." msgstr "" -#: awx/main/models/credential/__init__.py:1119 -msgid "The host to authenticate with." +#: awx/main/models/credential/__init__.py:1193 +msgid "Auth Server URL" msgstr "" -#: awx/main/models/credential/__init__.py:1131 -msgid "CA File" +#: awx/main/models/credential/__init__.py:1196 +msgid "The URL of a Keycloak server token_endpoint, if using SSO auth." msgstr "" -#: awx/main/models/credential/__init__.py:1133 -msgid "Absolute file path to the CA file to use (optional)" +#: awx/main/models/credential/__init__.py:1201 +msgid "API Token" msgstr "" -#: awx/main/models/credential/__init__.py:1164 -#: awx/main/models/inventory.py:1016 -msgid "Ansible Tower" +#: awx/main/models/credential/__init__.py:1205 +msgid "A token to use for authentication against the Galaxy instance." msgstr "" -#: awx/main/models/credential/__init__.py:1169 -msgid "Ansible Tower Hostname" +#: awx/main/models/credential/__init__.py:1244 +msgid "Target must be a non-external credential" msgstr "" -#: awx/main/models/credential/__init__.py:1171 -msgid "The Ansible Tower base URL to authenticate with." +#: awx/main/models/credential/__init__.py:1249 +msgid "Source must be an external credential" msgstr "" -#: awx/main/models/credential/__init__.py:1183 -msgid "Verify SSL" +#: awx/main/models/credential/__init__.py:1256 +msgid "Input field must be defined on target credential (options are {})." msgstr "" -#: awx/main/models/events.py:105 awx/main/models/events.py:630 +#: awx/main/models/events.py:165 awx/main/models/events.py:707 msgid "Host Failed" msgstr "" -#: awx/main/models/events.py:106 awx/main/models/events.py:631 +#: awx/main/models/events.py:166 +msgid "Host Started" +msgstr "" + +#: awx/main/models/events.py:167 awx/main/models/events.py:708 msgid "Host OK" msgstr "" -#: awx/main/models/events.py:107 +#: awx/main/models/events.py:168 msgid "Host Failure" msgstr "" -#: awx/main/models/events.py:108 awx/main/models/events.py:637 +#: awx/main/models/events.py:169 awx/main/models/events.py:714 msgid "Host Skipped" msgstr "" -#: awx/main/models/events.py:109 awx/main/models/events.py:632 +#: awx/main/models/events.py:170 awx/main/models/events.py:709 msgid "Host Unreachable" msgstr "" -#: awx/main/models/events.py:110 awx/main/models/events.py:124 +#: awx/main/models/events.py:171 awx/main/models/events.py:185 msgid "No Hosts Remaining" msgstr "" -#: awx/main/models/events.py:111 +#: awx/main/models/events.py:172 msgid "Host Polling" msgstr "" -#: awx/main/models/events.py:112 +#: awx/main/models/events.py:173 msgid "Host Async OK" msgstr "" -#: awx/main/models/events.py:113 +#: awx/main/models/events.py:174 msgid "Host Async Failure" msgstr "" -#: awx/main/models/events.py:114 +#: awx/main/models/events.py:175 msgid "Item OK" msgstr "" -#: awx/main/models/events.py:115 +#: awx/main/models/events.py:176 msgid "Item Failed" msgstr "" -#: awx/main/models/events.py:116 +#: awx/main/models/events.py:177 msgid "Item Skipped" msgstr "" -#: awx/main/models/events.py:117 +#: awx/main/models/events.py:178 msgid "Host Retry" msgstr "" -#: awx/main/models/events.py:119 +#: awx/main/models/events.py:180 msgid "File Difference" msgstr "" -#: awx/main/models/events.py:120 +#: awx/main/models/events.py:181 msgid "Playbook Started" msgstr "" -#: awx/main/models/events.py:121 +#: awx/main/models/events.py:182 msgid "Running Handlers" msgstr "" -#: awx/main/models/events.py:122 +#: awx/main/models/events.py:183 msgid "Including File" msgstr "" -#: awx/main/models/events.py:123 +#: awx/main/models/events.py:184 msgid "No Hosts Matched" msgstr "" -#: awx/main/models/events.py:125 +#: awx/main/models/events.py:186 msgid "Task Started" msgstr "" -#: awx/main/models/events.py:127 +#: awx/main/models/events.py:188 msgid "Variables Prompted" msgstr "" -#: awx/main/models/events.py:128 +#: awx/main/models/events.py:189 msgid "Gathering Facts" msgstr "" -#: awx/main/models/events.py:129 +#: awx/main/models/events.py:190 msgid "internal: on Import for Host" msgstr "" -#: awx/main/models/events.py:130 +#: awx/main/models/events.py:191 msgid "internal: on Not Import for Host" msgstr "" -#: awx/main/models/events.py:131 +#: awx/main/models/events.py:192 msgid "Play Started" msgstr "" -#: awx/main/models/events.py:132 +#: awx/main/models/events.py:193 msgid "Playbook Complete" msgstr "" -#: awx/main/models/events.py:136 awx/main/models/events.py:647 +#: awx/main/models/events.py:197 awx/main/models/events.py:724 msgid "Debug" msgstr "" -#: awx/main/models/events.py:137 awx/main/models/events.py:648 +#: awx/main/models/events.py:198 awx/main/models/events.py:725 msgid "Verbose" msgstr "" -#: awx/main/models/events.py:138 awx/main/models/events.py:649 +#: awx/main/models/events.py:199 awx/main/models/events.py:726 msgid "Deprecated" msgstr "" -#: awx/main/models/events.py:139 awx/main/models/events.py:650 +#: awx/main/models/events.py:200 awx/main/models/events.py:727 msgid "Warning" msgstr "" -#: awx/main/models/events.py:140 awx/main/models/events.py:651 +#: awx/main/models/events.py:201 awx/main/models/events.py:728 msgid "System Warning" msgstr "" -#: awx/main/models/events.py:141 awx/main/models/events.py:652 -#: awx/main/models/unified_jobs.py:73 +#: awx/main/models/events.py:202 awx/main/models/events.py:729 +#: awx/main/models/unified_jobs.py:75 msgid "Error" msgstr "" -#: awx/main/models/fact.py:25 -msgid "Host for the facts that the fact scan captured." -msgstr "" - -#: awx/main/models/fact.py:30 -msgid "Date and time of the corresponding fact scan gathering time." -msgstr "" - -#: awx/main/models/fact.py:33 -msgid "" -"Arbitrary JSON structure of module facts captured at timestamp for a single " -"host." -msgstr "" - -#: awx/main/models/ha.py:183 +#: awx/main/models/ha.py:184 msgid "Instances that are members of this InstanceGroup" msgstr "" -#: awx/main/models/ha.py:188 +#: awx/main/models/ha.py:189 msgid "Instance Group to remotely control this group." msgstr "" -#: awx/main/models/ha.py:195 +#: awx/main/models/ha.py:209 msgid "Percentage of Instances to automatically assign to this group" msgstr "" -#: awx/main/models/ha.py:199 +#: awx/main/models/ha.py:213 msgid "" "Static minimum number of Instances to automatically assign to this group" msgstr "" -#: awx/main/models/ha.py:204 +#: awx/main/models/ha.py:218 msgid "" "List of exact-match Instances that will always be automatically assigned to " "this group" msgstr "" -#: awx/main/models/inventory.py:63 +#: awx/main/models/inventory.py:74 msgid "Hosts have a direct link to this inventory." msgstr "" -#: awx/main/models/inventory.py:64 +#: awx/main/models/inventory.py:75 msgid "Hosts for inventory generated using the host_filter property." msgstr "" -#: awx/main/models/inventory.py:69 +#: awx/main/models/inventory.py:80 msgid "inventories" msgstr "" -#: awx/main/models/inventory.py:76 +#: awx/main/models/inventory.py:87 msgid "Organization containing this inventory." msgstr "" -#: awx/main/models/inventory.py:83 +#: awx/main/models/inventory.py:94 msgid "Inventory variables in JSON or YAML format." msgstr "" -#: awx/main/models/inventory.py:88 -msgid "Flag indicating whether any hosts in this inventory have failed." -msgstr "" - -#: awx/main/models/inventory.py:93 -msgid "Total number of hosts in this inventory." +#: awx/main/models/inventory.py:99 +msgid "" +"This field is deprecated and will be removed in a future release. Flag " +"indicating whether any hosts in this inventory have failed." msgstr "" -#: awx/main/models/inventory.py:98 -msgid "Number of hosts in this inventory with active failures." +#: awx/main/models/inventory.py:105 +msgid "" +"This field is deprecated and will be removed in a future release. Total " +"number of hosts in this inventory." msgstr "" -#: awx/main/models/inventory.py:103 -msgid "Total number of groups in this inventory." +#: awx/main/models/inventory.py:111 +msgid "" +"This field is deprecated and will be removed in a future release. Number of " +"hosts in this inventory with active failures." msgstr "" -#: awx/main/models/inventory.py:108 -msgid "Number of groups in this inventory with active failures." +#: awx/main/models/inventory.py:117 +msgid "" +"This field is deprecated and will be removed in a future release. Total " +"number of groups in this inventory." msgstr "" -#: awx/main/models/inventory.py:113 +#: awx/main/models/inventory.py:123 msgid "" -"Flag indicating whether this inventory has any external inventory sources." +"This field is deprecated and will be removed in a future release. Flag " +"indicating whether this inventory has any external inventory sources." msgstr "" -#: awx/main/models/inventory.py:118 +#: awx/main/models/inventory.py:129 msgid "" "Total number of external inventory sources configured within this inventory." msgstr "" -#: awx/main/models/inventory.py:123 +#: awx/main/models/inventory.py:134 msgid "Number of external inventory sources in this inventory with failures." msgstr "" -#: awx/main/models/inventory.py:130 +#: awx/main/models/inventory.py:141 msgid "Kind of inventory being represented." msgstr "" -#: awx/main/models/inventory.py:136 +#: awx/main/models/inventory.py:147 msgid "Filter that will be applied to the hosts of this inventory." msgstr "" -#: awx/main/models/inventory.py:163 +#: awx/main/models/inventory.py:175 msgid "" "Credentials to be used by hosts belonging to this inventory when accessing " "Red Hat Insights API." msgstr "" -#: awx/main/models/inventory.py:172 +#: awx/main/models/inventory.py:184 msgid "Flag indicating the inventory is being deleted." msgstr "" -#: awx/main/models/inventory.py:227 +#: awx/main/models/inventory.py:239 msgid "Could not parse subset as slice specification." msgstr "" -#: awx/main/models/inventory.py:231 +#: awx/main/models/inventory.py:243 msgid "Slice number must be less than total number of slices." msgstr "" -#: awx/main/models/inventory.py:233 +#: awx/main/models/inventory.py:245 msgid "Slice number must be 1 or higher." msgstr "" -#: awx/main/models/inventory.py:483 +#: awx/main/models/inventory.py:382 msgid "Assignment not allowed for Smart Inventory" msgstr "" -#: awx/main/models/inventory.py:485 awx/main/models/projects.py:159 +#: awx/main/models/inventory.py:384 awx/main/models/projects.py:167 msgid "Credential kind must be 'insights'." msgstr "" -#: awx/main/models/inventory.py:570 +#: awx/main/models/inventory.py:469 msgid "Is this host online and available for running jobs?" msgstr "" -#: awx/main/models/inventory.py:576 +#: awx/main/models/inventory.py:475 msgid "" "The value used by the remote inventory source to uniquely identify the host" msgstr "" -#: awx/main/models/inventory.py:581 +#: awx/main/models/inventory.py:480 msgid "Host variables in JSON or YAML format." msgstr "" -#: awx/main/models/inventory.py:603 -msgid "Flag indicating whether the last job failed for this host." -msgstr "" - -#: awx/main/models/inventory.py:608 -msgid "" -"Flag indicating whether this host was created/updated from any external " -"inventory sources." -msgstr "" - -#: awx/main/models/inventory.py:614 +#: awx/main/models/inventory.py:503 msgid "Inventory source(s) that created or modified this host." msgstr "" -#: awx/main/models/inventory.py:619 +#: awx/main/models/inventory.py:508 msgid "Arbitrary JSON structure of most recent ansible_facts, per-host." msgstr "" -#: awx/main/models/inventory.py:625 +#: awx/main/models/inventory.py:514 msgid "The date and time ansible_facts was last modified." msgstr "" -#: awx/main/models/inventory.py:632 +#: awx/main/models/inventory.py:521 msgid "Red Hat Insights host unique identifier." msgstr "" -#: awx/main/models/inventory.py:767 +#: awx/main/models/inventory.py:635 msgid "Group variables in JSON or YAML format." msgstr "" -#: awx/main/models/inventory.py:773 +#: awx/main/models/inventory.py:641 msgid "Hosts associated directly with this group." msgstr "" -#: awx/main/models/inventory.py:778 -msgid "Total number of hosts directly or indirectly in this group." -msgstr "" - -#: awx/main/models/inventory.py:783 -msgid "Flag indicating whether this group has any hosts with active failures." -msgstr "" - -#: awx/main/models/inventory.py:788 -msgid "Number of hosts in this group with active failures." -msgstr "" - -#: awx/main/models/inventory.py:793 -msgid "Total number of child groups contained within this group." -msgstr "" - -#: awx/main/models/inventory.py:798 -msgid "Number of child groups within this group that have active failures." -msgstr "" - -#: awx/main/models/inventory.py:803 -msgid "" -"Flag indicating whether this group was created/updated from any external " -"inventory sources." -msgstr "" - -#: awx/main/models/inventory.py:809 +#: awx/main/models/inventory.py:647 msgid "Inventory source(s) that created or modified this group." msgstr "" -#: awx/main/models/inventory.py:1005 awx/main/models/projects.py:53 -#: awx/main/models/unified_jobs.py:543 -msgid "Manual" -msgstr "" - -#: awx/main/models/inventory.py:1006 +#: awx/main/models/inventory.py:819 msgid "File, Directory or Script" msgstr "" -#: awx/main/models/inventory.py:1007 +#: awx/main/models/inventory.py:820 msgid "Sourced from a Project" msgstr "" -#: awx/main/models/inventory.py:1008 +#: awx/main/models/inventory.py:821 msgid "Amazon EC2" msgstr "" -#: awx/main/models/inventory.py:1017 +#: awx/main/models/inventory.py:829 msgid "Custom Script" msgstr "" -#: awx/main/models/inventory.py:1134 +#: awx/main/models/inventory.py:863 msgid "Inventory source variables in YAML or JSON format." msgstr "" -#: awx/main/models/inventory.py:1145 +#: awx/main/models/inventory.py:868 +msgid "" +"Retrieve the enabled state from the given dict of host variables. The " +"enabled variable may be specified as \"foo.bar\", in which case the lookup " +"will traverse into nested dicts, equivalent to: from_dict.get(\"foo\", {})." +"get(\"bar\", default)" +msgstr "" + +#: awx/main/models/inventory.py:876 msgid "" -"Comma-separated list of filter expressions (EC2 only). Hosts are imported " -"when ANY of the filters match." +"Only used when enabled_var is set. Value when the host is considered " +"enabled. For example if enabled_var=\"status.power_state\"and enabled_value=" +"\"powered_on\" with host variables:{ \"status\": { \"power_state\": " +"\"powered_on\", \"created\": \"2020-08-04T18:13:04+00:00\", \"healthy" +"\": true }, \"name\": \"foobar\", \"ip_address\": \"192.168.2.1\"}" +"The host would be marked enabled. If power_state where any value other than " +"powered_on then the host would be disabled when imported into Tower. If the " +"key is not found then the host will be enabled" msgstr "" -#: awx/main/models/inventory.py:1151 -msgid "Limit groups automatically created from inventory source (EC2 only)." +#: awx/main/models/inventory.py:896 +msgid "Regex where only matching hosts will be imported into Tower." msgstr "" -#: awx/main/models/inventory.py:1155 +#: awx/main/models/inventory.py:900 msgid "Overwrite local groups and hosts from remote inventory source." msgstr "" -#: awx/main/models/inventory.py:1159 +#: awx/main/models/inventory.py:904 msgid "Overwrite local variables from remote inventory source." msgstr "" -#: awx/main/models/inventory.py:1164 awx/main/models/jobs.py:139 -#: awx/main/models/projects.py:128 +#: awx/main/models/inventory.py:909 awx/main/models/jobs.py:154 +#: awx/main/models/projects.py:136 msgid "The amount of time (in seconds) to run before the task is canceled." msgstr "" -#: awx/main/models/inventory.py:1197 -msgid "Image ID" -msgstr "" - -#: awx/main/models/inventory.py:1198 -msgid "Availability Zone" -msgstr "" - -#: awx/main/models/inventory.py:1199 -msgid "Account" -msgstr "" - -#: awx/main/models/inventory.py:1200 -msgid "Instance ID" -msgstr "" - -#: awx/main/models/inventory.py:1201 -msgid "Instance State" -msgstr "" - -#: awx/main/models/inventory.py:1202 -msgid "Platform" -msgstr "" - -#: awx/main/models/inventory.py:1203 -msgid "Instance Type" -msgstr "" - -#: awx/main/models/inventory.py:1204 -msgid "Key Name" -msgstr "" - -#: awx/main/models/inventory.py:1205 -msgid "Region" -msgstr "" - -#: awx/main/models/inventory.py:1206 -msgid "Security Group" -msgstr "" - -#: awx/main/models/inventory.py:1207 -msgid "Tags" -msgstr "" - -#: awx/main/models/inventory.py:1208 -msgid "Tag None" -msgstr "" - -#: awx/main/models/inventory.py:1209 -msgid "VPC ID" -msgstr "" - -#: awx/main/models/inventory.py:1277 +#: awx/main/models/inventory.py:926 #, python-format msgid "" "Cloud-based inventory sources (such as %s) require credentials for the " "matching cloud service." msgstr "" -#: awx/main/models/inventory.py:1283 +#: awx/main/models/inventory.py:932 msgid "Credential is required for a cloud source." msgstr "" -#: awx/main/models/inventory.py:1286 +#: awx/main/models/inventory.py:935 msgid "" "Credentials of type machine, source control, insights and vault are " "disallowed for custom inventory sources." msgstr "" -#: awx/main/models/inventory.py:1291 +#: awx/main/models/inventory.py:940 msgid "" "Credentials of type insights and vault are disallowed for scm inventory " "sources." msgstr "" -#: awx/main/models/inventory.py:1343 -#, python-format -msgid "Invalid %(source)s region: %(region)s" -msgstr "" - -#: awx/main/models/inventory.py:1367 -#, python-format -msgid "Invalid filter expression: %(filter)s" -msgstr "" - -#: awx/main/models/inventory.py:1388 -#, python-format -msgid "Invalid group by choice: %(choice)s" -msgstr "" - -#: awx/main/models/inventory.py:1423 +#: awx/main/models/inventory.py:1004 msgid "Project containing inventory file used as source." msgstr "" -#: awx/main/models/inventory.py:1584 -#, python-format -msgid "" -"Unable to configure this item for cloud sync. It is already managed by %s." -msgstr "" - -#: awx/main/models/inventory.py:1594 +#: awx/main/models/inventory.py:1177 msgid "" "More than one SCM-based inventory source with update on project update per-" "inventory not allowed." msgstr "" -#: awx/main/models/inventory.py:1601 +#: awx/main/models/inventory.py:1184 msgid "" "Cannot update SCM-based inventory source on launch if set to update on " "project update. Instead, configure the corresponding source project to " "update on launch." msgstr "" -#: awx/main/models/inventory.py:1607 +#: awx/main/models/inventory.py:1190 msgid "Cannot set source_path if not SCM type." msgstr "" -#: awx/main/models/inventory.py:1645 +#: awx/main/models/inventory.py:1233 msgid "" "Inventory files from this Project Update were used for the inventory update." msgstr "" -#: awx/main/models/inventory.py:1754 +#: awx/main/models/inventory.py:1344 msgid "Inventory script contents" msgstr "" -#: awx/main/models/inventory.py:1759 +#: awx/main/models/inventory.py:1349 msgid "Organization owning this inventory script" msgstr "" -#: awx/main/models/jobs.py:65 +#: awx/main/models/jobs.py:74 msgid "" "If enabled, textual changes made to any templated files on the host are " "shown in the standard output" msgstr "" -#: awx/main/models/jobs.py:144 +#: awx/main/models/jobs.py:106 +msgid "" +"Branch to use in job run. Project default used if blank. Only allowed if " +"project allow_override field is set to true." +msgstr "" + +#: awx/main/models/jobs.py:159 msgid "" "If enabled, Tower will act as an Ansible Fact Cache Plugin; persisting facts " "at the end of a playbook run to the database and caching facts for use by " "Ansible." msgstr "" -#: awx/main/models/jobs.py:162 -msgid "You must provide a Vault credential." -msgstr "" - -#: awx/main/models/jobs.py:282 +#: awx/main/models/jobs.py:260 msgid "" "The number of jobs to slice into at runtime. Will cause the Job Template to " "launch a workflow if value is greater than 1." msgstr "" -#: awx/main/models/jobs.py:315 +#: awx/main/models/jobs.py:297 msgid "Job Template must provide 'inventory' or allow prompting for it." msgstr "" -#: awx/main/models/jobs.py:433 awx/main/models/workflow.py:461 +#: awx/main/models/jobs.py:308 +#, python-brace-format +msgid "Maximum number of forks ({settings.MAX_FORKS}) exceeded." +msgstr "" + +#: awx/main/models/jobs.py:459 +msgid "Project is missing." +msgstr "" + +#: awx/main/models/jobs.py:463 +msgid "Project does not allow override of branch." +msgstr "" + +#: awx/main/models/jobs.py:473 awx/main/models/workflow.py:545 msgid "Field is not configured to prompt on launch." msgstr "" -#: awx/main/models/jobs.py:439 +#: awx/main/models/jobs.py:479 msgid "Saved launch configurations cannot provide passwords needed to start." msgstr "" -#: awx/main/models/jobs.py:447 +#: awx/main/models/jobs.py:487 msgid "Job Template {} is missing or undefined." msgstr "" -#: awx/main/models/jobs.py:528 awx/main/models/projects.py:273 +#: awx/main/models/jobs.py:570 awx/main/models/projects.py:284 +#: awx/main/models/projects.py:508 msgid "SCM Revision" msgstr "" -#: awx/main/models/jobs.py:529 +#: awx/main/models/jobs.py:571 msgid "The SCM Revision from the Project used for this job, if available" msgstr "" -#: awx/main/models/jobs.py:537 +#: awx/main/models/jobs.py:579 msgid "" "The SCM Refresh task used to make sure the playbooks were available for the " "job run" msgstr "" -#: awx/main/models/jobs.py:542 +#: awx/main/models/jobs.py:584 msgid "" "If part of a sliced job, the ID of the inventory slice operated on. If not " "part of sliced job, parameter is not used." msgstr "" -#: awx/main/models/jobs.py:548 +#: awx/main/models/jobs.py:590 msgid "" "If ran as part of sliced jobs, the total number of slices. If 1, job is not " "part of a sliced job." msgstr "" -#: awx/main/models/jobs.py:684 +#: awx/main/models/jobs.py:672 #, python-brace-format msgid "{status_value} is not a valid status option." msgstr "" -#: awx/main/models/jobs.py:1090 +#: awx/main/models/jobs.py:922 +msgid "" +"Inventory applied as a prompt, assuming job template prompts for inventory" +msgstr "" + +#: awx/main/models/jobs.py:1081 msgid "job host summaries" msgstr "" -#: awx/main/models/jobs.py:1162 +#: awx/main/models/jobs.py:1140 msgid "Remove jobs older than a certain number of days" msgstr "" -#: awx/main/models/jobs.py:1163 +#: awx/main/models/jobs.py:1141 msgid "Remove activity stream entries older than a certain number of days" msgstr "" -#: awx/main/models/jobs.py:1164 -msgid "Purge and/or reduce the granularity of system tracking data" +#: awx/main/models/jobs.py:1142 +msgid "Removes expired browser sessions from the database" msgstr "" -#: awx/main/models/jobs.py:1234 +#: awx/main/models/jobs.py:1143 +msgid "Removes expired OAuth 2 access tokens and refresh tokens" +msgstr "" + +#: awx/main/models/jobs.py:1213 #, python-brace-format msgid "Variables {list_of_keys} are not allowed for system jobs." msgstr "" -#: awx/main/models/jobs.py:1249 +#: awx/main/models/jobs.py:1229 msgid "days must be a positive integer." msgstr "" @@ -3542,110 +4109,166 @@ msgstr "" msgid "Organization this label belongs to." msgstr "" -#: awx/main/models/mixins.py:316 +#: awx/main/models/mixins.py:321 #, python-brace-format msgid "" "Variables {list_of_keys} are not allowed on launch. Check the Prompt on " "Launch setting on the {model_name} to include Extra Variables." msgstr "" -#: awx/main/models/mixins.py:448 +#: awx/main/models/mixins.py:453 msgid "Local absolute file path containing a custom Python virtualenv to use" msgstr "" -#: awx/main/models/mixins.py:455 +#: awx/main/models/mixins.py:460 msgid "{} is not a valid virtualenv in {}" msgstr "" +#: awx/main/models/mixins.py:507 awx/main/models/mixins.py:551 +msgid "Service that webhook requests will be accepted from" +msgstr "" + +#: awx/main/models/mixins.py:512 +msgid "Shared secret that the webhook service will use to sign requests" +msgstr "" + +#: awx/main/models/mixins.py:520 awx/main/models/mixins.py:559 +msgid "Personal Access Token for posting back the status to the service API" +msgstr "" + +#: awx/main/models/mixins.py:564 +msgid "Unique identifier of the event that triggered this webhook" +msgstr "" + +#: awx/main/models/notifications.py:41 +msgid "Email" +msgstr "" + #: awx/main/models/notifications.py:42 +msgid "Slack" +msgstr "" + +#: awx/main/models/notifications.py:43 +msgid "Twilio" +msgstr "" + +#: awx/main/models/notifications.py:44 +msgid "Pagerduty" +msgstr "" + +#: awx/main/models/notifications.py:45 +msgid "Grafana" +msgstr "" + +#: awx/main/models/notifications.py:46 awx/main/models/unified_jobs.py:544 +msgid "Webhook" +msgstr "" + +#: awx/main/models/notifications.py:47 +msgid "Mattermost" +msgstr "" + +#: awx/main/models/notifications.py:48 msgid "Rocket.Chat" msgstr "" -#: awx/main/models/notifications.py:142 awx/main/models/unified_jobs.py:68 +#: awx/main/models/notifications.py:49 +msgid "IRC" +msgstr "" + +#: awx/main/models/notifications.py:80 +msgid "Optional custom messages for notification template." +msgstr "" + +#: awx/main/models/notifications.py:210 awx/main/models/unified_jobs.py:70 msgid "Pending" msgstr "" -#: awx/main/models/notifications.py:143 awx/main/models/unified_jobs.py:71 +#: awx/main/models/notifications.py:211 awx/main/models/unified_jobs.py:73 msgid "Successful" msgstr "" -#: awx/main/models/notifications.py:144 awx/main/models/unified_jobs.py:72 +#: awx/main/models/notifications.py:212 awx/main/models/unified_jobs.py:74 msgid "Failed" msgstr "" -#: awx/main/models/notifications.py:218 -msgid "status_str must be either succeeded or failed" +#: awx/main/models/notifications.py:470 +msgid "status must be either running, succeeded or failed" msgstr "" -#: awx/main/models/oauth.py:29 +#: awx/main/models/oauth.py:33 msgid "application" msgstr "" -#: awx/main/models/oauth.py:35 +#: awx/main/models/oauth.py:40 msgid "Confidential" msgstr "" -#: awx/main/models/oauth.py:36 +#: awx/main/models/oauth.py:41 msgid "Public" msgstr "" -#: awx/main/models/oauth.py:43 +#: awx/main/models/oauth.py:47 msgid "Authorization code" msgstr "" -#: awx/main/models/oauth.py:44 -msgid "Implicit" -msgstr "" - -#: awx/main/models/oauth.py:45 +#: awx/main/models/oauth.py:48 msgid "Resource owner password-based" msgstr "" -#: awx/main/models/oauth.py:60 +#: awx/main/models/oauth.py:63 msgid "Organization containing this application." msgstr "" -#: awx/main/models/oauth.py:69 +#: awx/main/models/oauth.py:72 msgid "" "Used for more stringent verification of access to an application when " "creating a token." msgstr "" -#: awx/main/models/oauth.py:74 +#: awx/main/models/oauth.py:77 msgid "" "Set to Public or Confidential depending on how secure the client device is." msgstr "" -#: awx/main/models/oauth.py:78 +#: awx/main/models/oauth.py:81 msgid "" "Set True to skip authorization step for completely trusted applications." msgstr "" -#: awx/main/models/oauth.py:83 +#: awx/main/models/oauth.py:86 msgid "" "The Grant type the user must use for acquire tokens for this application." msgstr "" -#: awx/main/models/oauth.py:91 +#: awx/main/models/oauth.py:94 msgid "access token" msgstr "" -#: awx/main/models/oauth.py:99 +#: awx/main/models/oauth.py:103 msgid "The user representing the token owner" msgstr "" -#: awx/main/models/oauth.py:114 +#: awx/main/models/oauth.py:117 msgid "" "Allowed scopes, further restricts user's permissions. Must be a simple space-" "separated string with allowed scopes ['read', 'write']." msgstr "" -#: awx/main/models/oauth.py:133 +#: awx/main/models/oauth.py:140 msgid "" "OAuth2 Tokens cannot be created by users associated with an external " "authentication provider ({})" msgstr "" +#: awx/main/models/organization.py:57 +msgid "Maximum number of hosts allowed to be managed by this organization." +msgstr "" + +#: awx/main/models/projects.py:53 awx/main/models/unified_jobs.py:538 +msgid "Manual" +msgstr "" + #: awx/main/models/projects.py:54 msgid "Git" msgstr "" @@ -3662,169 +4285,205 @@ msgstr "" msgid "Red Hat Insights" msgstr "" -#: awx/main/models/projects.py:83 +#: awx/main/models/projects.py:58 +msgid "Remote Archive" +msgstr "" + +#: awx/main/models/projects.py:84 msgid "" "Local path (relative to PROJECTS_ROOT) containing playbooks and related " "files for this project." msgstr "" -#: awx/main/models/projects.py:92 +#: awx/main/models/projects.py:93 msgid "SCM Type" msgstr "" -#: awx/main/models/projects.py:93 +#: awx/main/models/projects.py:94 msgid "Specifies the source control system used to store the project." msgstr "" -#: awx/main/models/projects.py:99 +#: awx/main/models/projects.py:100 msgid "SCM URL" msgstr "" -#: awx/main/models/projects.py:100 +#: awx/main/models/projects.py:101 msgid "The location where the project is stored." msgstr "" -#: awx/main/models/projects.py:106 +#: awx/main/models/projects.py:107 msgid "SCM Branch" msgstr "" -#: awx/main/models/projects.py:107 +#: awx/main/models/projects.py:108 msgid "Specific branch, tag or commit to checkout." msgstr "" -#: awx/main/models/projects.py:111 -msgid "Discard any local changes before syncing the project." +#: awx/main/models/projects.py:114 +msgid "SCM refspec" msgstr "" #: awx/main/models/projects.py:115 +msgid "For git projects, an additional refspec to fetch." +msgstr "" + +#: awx/main/models/projects.py:119 +msgid "Discard any local changes before syncing the project." +msgstr "" + +#: awx/main/models/projects.py:123 msgid "Delete the project before syncing." msgstr "" -#: awx/main/models/projects.py:144 +#: awx/main/models/projects.py:152 msgid "Invalid SCM URL." msgstr "" -#: awx/main/models/projects.py:147 +#: awx/main/models/projects.py:155 msgid "SCM URL is required." msgstr "" -#: awx/main/models/projects.py:155 +#: awx/main/models/projects.py:163 msgid "Insights Credential is required for an Insights Project." msgstr "" -#: awx/main/models/projects.py:161 +#: awx/main/models/projects.py:169 msgid "Credential kind must be 'scm'." msgstr "" -#: awx/main/models/projects.py:178 +#: awx/main/models/projects.py:186 msgid "Invalid credential." msgstr "" -#: awx/main/models/projects.py:259 +#: awx/main/models/projects.py:265 msgid "Update the project when a job is launched that uses the project." msgstr "" -#: awx/main/models/projects.py:264 +#: awx/main/models/projects.py:270 msgid "" -"The number of seconds after the last project update ran that a newproject " +"The number of seconds after the last project update ran that a new project " "update will be launched as a job dependency." msgstr "" -#: awx/main/models/projects.py:274 +#: awx/main/models/projects.py:275 +msgid "" +"Allow changing the SCM branch or revision in a job template that uses this " +"project." +msgstr "" + +#: awx/main/models/projects.py:285 msgid "The last revision fetched by a project update" msgstr "" -#: awx/main/models/projects.py:281 +#: awx/main/models/projects.py:292 msgid "Playbook Files" msgstr "" -#: awx/main/models/projects.py:282 +#: awx/main/models/projects.py:293 msgid "List of playbooks found in the project" msgstr "" -#: awx/main/models/projects.py:289 +#: awx/main/models/projects.py:300 msgid "Inventory Files" msgstr "" -#: awx/main/models/projects.py:290 +#: awx/main/models/projects.py:301 msgid "" "Suggested list of content that could be Ansible inventory in the project" msgstr "" -#: awx/main/models/rbac.py:36 +#: awx/main/models/projects.py:338 +msgid "Organization cannot be changed when in use by job templates." +msgstr "" + +#: awx/main/models/projects.py:501 +msgid "Parts of the project update playbook that will be run." +msgstr "" + +#: awx/main/models/projects.py:509 +msgid "" +"The SCM Revision discovered by this update for the given project and branch." +msgstr "" + +#: awx/main/models/rbac.py:35 msgid "System Administrator" msgstr "" -#: awx/main/models/rbac.py:37 +#: awx/main/models/rbac.py:36 msgid "System Auditor" msgstr "" -#: awx/main/models/rbac.py:38 +#: awx/main/models/rbac.py:37 msgid "Ad Hoc" msgstr "" -#: awx/main/models/rbac.py:39 +#: awx/main/models/rbac.py:38 msgid "Admin" msgstr "" -#: awx/main/models/rbac.py:40 +#: awx/main/models/rbac.py:39 msgid "Project Admin" msgstr "" -#: awx/main/models/rbac.py:41 +#: awx/main/models/rbac.py:40 msgid "Inventory Admin" msgstr "" -#: awx/main/models/rbac.py:42 +#: awx/main/models/rbac.py:41 msgid "Credential Admin" msgstr "" -#: awx/main/models/rbac.py:43 +#: awx/main/models/rbac.py:42 msgid "Job Template Admin" msgstr "" -#: awx/main/models/rbac.py:44 +#: awx/main/models/rbac.py:43 msgid "Workflow Admin" msgstr "" -#: awx/main/models/rbac.py:45 +#: awx/main/models/rbac.py:44 msgid "Notification Admin" msgstr "" -#: awx/main/models/rbac.py:46 +#: awx/main/models/rbac.py:45 msgid "Auditor" msgstr "" -#: awx/main/models/rbac.py:47 +#: awx/main/models/rbac.py:46 msgid "Execute" msgstr "" -#: awx/main/models/rbac.py:48 +#: awx/main/models/rbac.py:47 msgid "Member" msgstr "" -#: awx/main/models/rbac.py:49 +#: awx/main/models/rbac.py:48 msgid "Read" msgstr "" -#: awx/main/models/rbac.py:50 +#: awx/main/models/rbac.py:49 msgid "Update" msgstr "" -#: awx/main/models/rbac.py:51 +#: awx/main/models/rbac.py:50 msgid "Use" msgstr "" +#: awx/main/models/rbac.py:51 +msgid "Approve" +msgstr "" + #: awx/main/models/rbac.py:55 msgid "Can manage all aspects of the system" msgstr "" #: awx/main/models/rbac.py:56 -msgid "Can view all settings on the system" +msgid "Can view all aspects of the system" msgstr "" #: awx/main/models/rbac.py:57 -msgid "May run ad hoc commands on an inventory" +#, python-format +msgid "May run ad hoc commands on the %s" msgstr "" #: awx/main/models/rbac.py:58 @@ -3864,7 +4523,7 @@ msgstr "" #: awx/main/models/rbac.py:65 #, python-format -msgid "Can view all settings for the %s" +msgid "Can view all aspects of the %s" msgstr "" #: awx/main/models/rbac.py:67 @@ -3887,9 +4546,8 @@ msgid "May view settings for the %s" msgstr "" #: awx/main/models/rbac.py:72 -msgid "" -"May update project or inventory or group using the configured source update " -"system" +#, python-format +msgid "May update the %s" msgstr "" #: awx/main/models/rbac.py:73 @@ -3897,137 +4555,184 @@ msgstr "" msgid "Can use the %s in a job template" msgstr "" -#: awx/main/models/rbac.py:137 +#: awx/main/models/rbac.py:74 +msgid "Can approve or deny a workflow approval node" +msgstr "" + +#: awx/main/models/rbac.py:138 msgid "roles" msgstr "" -#: awx/main/models/rbac.py:443 +#: awx/main/models/rbac.py:445 msgid "role_ancestors" msgstr "" -#: awx/main/models/schedules.py:79 +#: awx/main/models/schedules.py:83 msgid "Enables processing of this schedule." msgstr "" -#: awx/main/models/schedules.py:85 +#: awx/main/models/schedules.py:89 msgid "The first occurrence of the schedule occurs on or after this time." msgstr "" -#: awx/main/models/schedules.py:91 +#: awx/main/models/schedules.py:95 msgid "" "The last occurrence of the schedule occurs before this time, aftewards the " "schedule expires." msgstr "" -#: awx/main/models/schedules.py:95 +#: awx/main/models/schedules.py:99 msgid "A value representing the schedules iCal recurrence rule." msgstr "" -#: awx/main/models/schedules.py:101 +#: awx/main/models/schedules.py:105 msgid "The next time that the scheduled action will run." msgstr "" -#: awx/main/models/unified_jobs.py:67 +#: awx/main/models/unified_jobs.py:69 msgid "New" msgstr "" -#: awx/main/models/unified_jobs.py:69 +#: awx/main/models/unified_jobs.py:71 msgid "Waiting" msgstr "" -#: awx/main/models/unified_jobs.py:70 +#: awx/main/models/unified_jobs.py:72 msgid "Running" msgstr "" -#: awx/main/models/unified_jobs.py:74 +#: awx/main/models/unified_jobs.py:76 msgid "Canceled" msgstr "" -#: awx/main/models/unified_jobs.py:78 +#: awx/main/models/unified_jobs.py:80 msgid "Never Updated" msgstr "" -#: awx/main/models/unified_jobs.py:82 +#: awx/main/models/unified_jobs.py:84 msgid "OK" msgstr "" -#: awx/main/models/unified_jobs.py:83 +#: awx/main/models/unified_jobs.py:85 msgid "Missing" msgstr "" -#: awx/main/models/unified_jobs.py:87 +#: awx/main/models/unified_jobs.py:89 msgid "No External Source" msgstr "" -#: awx/main/models/unified_jobs.py:94 +#: awx/main/models/unified_jobs.py:96 msgid "Updating" msgstr "" -#: awx/main/models/unified_jobs.py:451 +#: awx/main/models/unified_jobs.py:167 +msgid "The organization used to determine access to this template." +msgstr "" + +#: awx/main/models/unified_jobs.py:465 msgid "Field is not allowed on launch." msgstr "" -#: awx/main/models/unified_jobs.py:479 +#: awx/main/models/unified_jobs.py:493 #, python-brace-format msgid "" "Variables {list_of_keys} provided, but this template cannot accept variables." msgstr "" -#: awx/main/models/unified_jobs.py:544 +#: awx/main/models/unified_jobs.py:539 msgid "Relaunch" msgstr "" -#: awx/main/models/unified_jobs.py:545 +#: awx/main/models/unified_jobs.py:540 msgid "Callback" msgstr "" -#: awx/main/models/unified_jobs.py:546 +#: awx/main/models/unified_jobs.py:541 msgid "Scheduled" msgstr "" -#: awx/main/models/unified_jobs.py:547 +#: awx/main/models/unified_jobs.py:542 msgid "Dependency" msgstr "" -#: awx/main/models/unified_jobs.py:548 -msgid "Workflow" +#: awx/main/models/unified_jobs.py:543 +msgid "Workflow" +msgstr "" + +#: awx/main/models/unified_jobs.py:545 +msgid "Sync" +msgstr "" + +#: awx/main/models/unified_jobs.py:600 +msgid "The node the job executed on." +msgstr "" + +#: awx/main/models/unified_jobs.py:606 +msgid "The instance that managed the isolated execution environment." +msgstr "" + +#: awx/main/models/unified_jobs.py:633 +msgid "The date and time the job was queued for starting." +msgstr "" + +#: awx/main/models/unified_jobs.py:638 +msgid "" +"If True, the task manager has already processed potential dependencies for " +"this job." +msgstr "" + +#: awx/main/models/unified_jobs.py:644 +msgid "The date and time the job finished execution." +msgstr "" + +#: awx/main/models/unified_jobs.py:651 +msgid "The date and time when the cancel request was sent." msgstr "" -#: awx/main/models/unified_jobs.py:549 -msgid "Sync" +#: awx/main/models/unified_jobs.py:658 +msgid "Elapsed time in seconds that the job ran." msgstr "" -#: awx/main/models/unified_jobs.py:597 -msgid "The node the job executed on." +#: awx/main/models/unified_jobs.py:680 +msgid "" +"A status field to indicate the state of the job if it wasn't able to run and " +"capture stdout" msgstr "" -#: awx/main/models/unified_jobs.py:603 -msgid "The instance that managed the isolated execution environment." +#: awx/main/models/unified_jobs.py:709 +msgid "The Instance group the job was run under" msgstr "" -#: awx/main/models/unified_jobs.py:629 -msgid "The date and time the job was queued for starting." +#: awx/main/models/unified_jobs.py:717 +msgid "The organization used to determine access to this unified job." msgstr "" -#: awx/main/models/unified_jobs.py:635 -msgid "The date and time the job finished execution." +#: awx/main/models/workflow.py:85 +msgid "" +"If enabled then the node will only run if all of the parent nodes have met " +"the criteria to reach this node" msgstr "" -#: awx/main/models/unified_jobs.py:641 -msgid "Elapsed time in seconds that the job ran." +#: awx/main/models/workflow.py:154 +msgid "" +"An identifier for this node that is unique within its workflow. It is copied " +"to workflow job nodes corresponding to this node." msgstr "" -#: awx/main/models/unified_jobs.py:663 +#: awx/main/models/workflow.py:229 msgid "" -"A status field to indicate the state of the job if it wasn't able to run and " -"capture stdout" +"Indicates that a job will not be created when True. Workflow runtime " +"semantics will mark this True if the node is in a path that will decidedly " +"not be ran. A value of False means the node may not run." msgstr "" -#: awx/main/models/unified_jobs.py:692 -msgid "The Rampart/Instance group the job was run under" +#: awx/main/models/workflow.py:236 +msgid "" +"An identifier coresponding to the workflow job template node that this node " +"was created from." msgstr "" -#: awx/main/models/workflow.py:221 +#: awx/main/models/workflow.py:282 #, python-brace-format msgid "" "Bad launch configuration starting template {template_pk} as part of workflow " @@ -4035,155 +4740,192 @@ msgid "" "{error_text}" msgstr "" -#: awx/main/models/workflow.py:363 +#: awx/main/models/workflow.py:595 msgid "" -"Inventory applied to all job templates in workflow that prompt for inventory." +"If automatically created for a sliced job run, the job template the workflow " +"job was created from." msgstr "" -#: awx/main/models/workflow.py:511 +#: awx/main/models/workflow.py:687 awx/main/models/workflow.py:721 msgid "" -"If automatically created for a sliced job run, the job template the workflow " -"job was created from." +"The amount of time (in seconds) before the approval node expires and fails." msgstr "" -#: awx/main/notifications/base.py:17 awx/main/notifications/email_backend.py:28 +#: awx/main/models/workflow.py:725 msgid "" -"{} #{} had status {}, view details at {}\n" -"\n" +"Shows when an approval node (with a timeout assigned to it) has timed out." +msgstr "" + +#: awx/main/notifications/grafana_backend.py:81 +msgid "Error converting time {} or timeEnd {} to int." msgstr "" -#: awx/main/notifications/hipchat_backend.py:48 -msgid "Error sending messages: {}" +#: awx/main/notifications/grafana_backend.py:83 +msgid "Error converting time {} and/or timeEnd {} to int." msgstr "" -#: awx/main/notifications/hipchat_backend.py:50 -msgid "Error sending message to hipchat: {}" +#: awx/main/notifications/grafana_backend.py:97 +#: awx/main/notifications/grafana_backend.py:99 +msgid "Error sending notification grafana: {}" msgstr "" -#: awx/main/notifications/irc_backend.py:54 +#: awx/main/notifications/irc_backend.py:56 msgid "Exception connecting to irc server: {}" msgstr "" -#: awx/main/notifications/mattermost_backend.py:48 -#: awx/main/notifications/mattermost_backend.py:50 +#: awx/main/notifications/mattermost_backend.py:49 +#: awx/main/notifications/mattermost_backend.py:51 msgid "Error sending notification mattermost: {}" msgstr "" -#: awx/main/notifications/pagerduty_backend.py:39 +#: awx/main/notifications/pagerduty_backend.py:75 msgid "Exception connecting to PagerDuty: {}" msgstr "" -#: awx/main/notifications/pagerduty_backend.py:48 -#: awx/main/notifications/slack_backend.py:55 -#: awx/main/notifications/twilio_backend.py:46 +#: awx/main/notifications/pagerduty_backend.py:84 +#: awx/main/notifications/slack_backend.py:58 +#: awx/main/notifications/twilio_backend.py:48 msgid "Exception sending messages: {}" msgstr "" -#: awx/main/notifications/rocketchat_backend.py:46 #: awx/main/notifications/rocketchat_backend.py:49 +#: awx/main/notifications/rocketchat_backend.py:52 msgid "Error sending notification rocket.chat: {}" msgstr "" -#: awx/main/notifications/twilio_backend.py:36 +#: awx/main/notifications/twilio_backend.py:38 msgid "Exception connecting to Twilio: {}" msgstr "" -#: awx/main/notifications/webhook_backend.py:38 -#: awx/main/notifications/webhook_backend.py:40 +#: awx/main/notifications/webhook_backend.py:75 +#: awx/main/notifications/webhook_backend.py:77 msgid "Error sending notification webhook: {}" msgstr "" -#: awx/main/scheduler/task_manager.py:133 +#: awx/main/scheduler/dag_workflow.py:170 +#, python-brace-format +msgid "" +"No error handling path for workflow job node(s) [{node_status}]. Workflow " +"job node(s) missing unified job template and error handling path [{no_ufjt}]." +msgstr "" + +#: awx/main/scheduler/task_manager.py:127 msgid "" "Workflow Job spawned from workflow could not start because it would result " "in recursion (spawn order, most recent first: {})" msgstr "" -#: awx/main/scheduler/task_manager.py:141 +#: awx/main/scheduler/task_manager.py:135 msgid "" "Job spawned from workflow could not start because it was missing a related " "resource such as project or inventory" msgstr "" -#: awx/main/scheduler/task_manager.py:150 +#: awx/main/scheduler/task_manager.py:144 msgid "" "Job spawned from workflow could not start because it was not in the right " "state or required manual credentials" msgstr "" -#: awx/main/signals.py:646 -msgid "limit_reached" +#: awx/main/scheduler/task_manager.py:185 +msgid "No error handling paths found, marking workflow as failed" +msgstr "" + +#: awx/main/scheduler/task_manager.py:523 +#, python-brace-format +msgid "The approval node {name} ({pk}) has expired after {timeout} seconds." msgstr "" -#: awx/main/tasks.py:313 -msgid "Ansible Tower host usage over 90%" +#: awx/main/tasks.py:599 +msgid "" +"Scheduled job could not start because it was not in the " +"right state or required manual credentials" msgstr "" -#: awx/main/tasks.py:318 -msgid "Ansible Tower license will expire soon" +#: awx/main/tasks.py:1070 +msgid "Invalid virtual environment selected: {}" msgstr "" -#: awx/main/tasks.py:1375 +#: awx/main/tasks.py:1857 msgid "Job could not start because it does not have a valid inventory." msgstr "" -#: awx/main/tasks.py:1386 +#: awx/main/tasks.py:1861 +msgid "Job could not start because it does not have a valid project." +msgstr "" + +#: awx/main/tasks.py:1866 msgid "" "The project revision for this job template is unknown due to a failed update." msgstr "" -#: awx/main/utils/common.py:95 +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:470 +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:498 +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:505 +msgid "" +"No error handling path for workflow job node(s) [({},{})]. Workflow job " +"node(s) missing unified job template and error handling path []." +msgstr "" + +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:480 +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:491 +msgid "" +"No error handling path for workflow job node(s) []. Workflow job node(s) " +"missing unified job template and error handling path [{}]." +msgstr "" + +#: awx/main/utils/common.py:87 #, python-format msgid "Unable to convert \"%s\" to boolean" msgstr "" -#: awx/main/utils/common.py:256 +#: awx/main/utils/common.py:248 #, python-format msgid "Unsupported SCM type \"%s\"" msgstr "" -#: awx/main/utils/common.py:263 awx/main/utils/common.py:275 -#: awx/main/utils/common.py:294 +#: awx/main/utils/common.py:255 awx/main/utils/common.py:267 +#: awx/main/utils/common.py:286 #, python-format msgid "Invalid %s URL" msgstr "" -#: awx/main/utils/common.py:265 awx/main/utils/common.py:304 +#: awx/main/utils/common.py:257 awx/main/utils/common.py:297 #, python-format msgid "Unsupported %s URL" msgstr "" -#: awx/main/utils/common.py:306 +#: awx/main/utils/common.py:299 #, python-format msgid "Unsupported host \"%s\" for file:// URL" msgstr "" -#: awx/main/utils/common.py:308 +#: awx/main/utils/common.py:301 #, python-format msgid "Host is required for %s URL" msgstr "" -#: awx/main/utils/common.py:326 +#: awx/main/utils/common.py:319 #, python-format msgid "Username must be \"git\" for SSH access to %s." msgstr "" -#: awx/main/utils/common.py:332 +#: awx/main/utils/common.py:325 #, python-format msgid "Username must be \"hg\" for SSH access to %s." msgstr "" -#: awx/main/utils/common.py:613 +#: awx/main/utils/common.py:656 #, python-brace-format msgid "Input type `{data_type}` is not a dictionary" msgstr "" -#: awx/main/utils/common.py:646 +#: awx/main/utils/common.py:689 #, python-brace-format msgid "Variables not compatible with JSON standard (error: {json_error})" msgstr "" -#: awx/main/utils/common.py:652 +#: awx/main/utils/common.py:695 #, python-brace-format msgid "" "Cannot parse as JSON (error: {json_error}) or YAML (error: {yaml_error})." @@ -4259,331 +5001,47 @@ msgid "" "No more than %(max_certs)d certificates are allowed, %(cert_count)d provided." msgstr "" -#: awx/main/views.py:23 +#: awx/main/views.py:30 msgid "API Error" msgstr "" -#: awx/main/views.py:61 +#: awx/main/views.py:65 msgid "Bad Request" msgstr "" -#: awx/main/views.py:62 +#: awx/main/views.py:66 msgid "The request could not be understood by the server." msgstr "" -#: awx/main/views.py:69 +#: awx/main/views.py:73 msgid "Forbidden" msgstr "" -#: awx/main/views.py:70 +#: awx/main/views.py:74 msgid "You don't have permission to access the requested resource." msgstr "" -#: awx/main/views.py:77 +#: awx/main/views.py:81 msgid "Not Found" msgstr "" -#: awx/main/views.py:78 +#: awx/main/views.py:82 msgid "The requested resource could not be found." msgstr "" -#: awx/main/views.py:85 +#: awx/main/views.py:89 msgid "Server Error" msgstr "" -#: awx/main/views.py:86 +#: awx/main/views.py:90 msgid "A server error has occurred." msgstr "" -#: awx/settings/defaults.py:698 -msgid "US East (Northern Virginia)" -msgstr "" - -#: awx/settings/defaults.py:699 -msgid "US East (Ohio)" -msgstr "" - -#: awx/settings/defaults.py:700 -msgid "US West (Oregon)" -msgstr "" - -#: awx/settings/defaults.py:701 -msgid "US West (Northern California)" -msgstr "" - -#: awx/settings/defaults.py:702 -msgid "Canada (Central)" -msgstr "" - -#: awx/settings/defaults.py:703 -msgid "EU (Frankfurt)" -msgstr "" - -#: awx/settings/defaults.py:704 -msgid "EU (Ireland)" -msgstr "" - -#: awx/settings/defaults.py:705 -msgid "EU (London)" -msgstr "" - -#: awx/settings/defaults.py:706 -msgid "Asia Pacific (Singapore)" -msgstr "" - -#: awx/settings/defaults.py:707 -msgid "Asia Pacific (Sydney)" -msgstr "" - -#: awx/settings/defaults.py:708 -msgid "Asia Pacific (Tokyo)" -msgstr "" - -#: awx/settings/defaults.py:709 -msgid "Asia Pacific (Seoul)" -msgstr "" - -#: awx/settings/defaults.py:710 -msgid "Asia Pacific (Mumbai)" -msgstr "" - -#: awx/settings/defaults.py:711 -msgid "South America (Sao Paulo)" -msgstr "" - -#: awx/settings/defaults.py:712 -msgid "US West (GovCloud)" -msgstr "" - -#: awx/settings/defaults.py:713 -msgid "China (Beijing)" -msgstr "" - -#: awx/settings/defaults.py:762 -msgid "US East 1 (B)" -msgstr "" - -#: awx/settings/defaults.py:763 -msgid "US East 1 (C)" -msgstr "" - -#: awx/settings/defaults.py:764 -msgid "US East 1 (D)" -msgstr "" - -#: awx/settings/defaults.py:765 -msgid "US East 4 (A)" -msgstr "" - -#: awx/settings/defaults.py:766 -msgid "US East 4 (B)" -msgstr "" - -#: awx/settings/defaults.py:767 -msgid "US East 4 (C)" -msgstr "" - -#: awx/settings/defaults.py:768 -msgid "US Central (A)" -msgstr "" - -#: awx/settings/defaults.py:769 -msgid "US Central (B)" -msgstr "" - -#: awx/settings/defaults.py:770 -msgid "US Central (C)" -msgstr "" - -#: awx/settings/defaults.py:771 -msgid "US Central (F)" -msgstr "" - -#: awx/settings/defaults.py:772 -msgid "US West (A)" -msgstr "" - -#: awx/settings/defaults.py:773 -msgid "US West (B)" -msgstr "" - -#: awx/settings/defaults.py:774 -msgid "US West (C)" -msgstr "" - -#: awx/settings/defaults.py:775 -msgid "Europe West 1 (B)" -msgstr "" - -#: awx/settings/defaults.py:776 -msgid "Europe West 1 (C)" -msgstr "" - -#: awx/settings/defaults.py:777 -msgid "Europe West 1 (D)" -msgstr "" - -#: awx/settings/defaults.py:778 -msgid "Europe West 2 (A)" -msgstr "" - -#: awx/settings/defaults.py:779 -msgid "Europe West 2 (B)" -msgstr "" - -#: awx/settings/defaults.py:780 -msgid "Europe West 2 (C)" -msgstr "" - -#: awx/settings/defaults.py:781 -msgid "Asia East (A)" -msgstr "" - -#: awx/settings/defaults.py:782 -msgid "Asia East (B)" -msgstr "" - -#: awx/settings/defaults.py:783 -msgid "Asia East (C)" -msgstr "" - -#: awx/settings/defaults.py:784 -msgid "Asia Southeast (A)" -msgstr "" - -#: awx/settings/defaults.py:785 -msgid "Asia Southeast (B)" -msgstr "" - -#: awx/settings/defaults.py:786 -msgid "Asia Northeast (A)" -msgstr "" - -#: awx/settings/defaults.py:787 -msgid "Asia Northeast (B)" -msgstr "" - -#: awx/settings/defaults.py:788 -msgid "Asia Northeast (C)" -msgstr "" - -#: awx/settings/defaults.py:789 -msgid "Australia Southeast (A)" -msgstr "" - -#: awx/settings/defaults.py:790 -msgid "Australia Southeast (B)" -msgstr "" - -#: awx/settings/defaults.py:791 -msgid "Australia Southeast (C)" -msgstr "" - -#: awx/settings/defaults.py:813 -msgid "US East" -msgstr "" - -#: awx/settings/defaults.py:814 -msgid "US East 2" -msgstr "" - -#: awx/settings/defaults.py:815 -msgid "US Central" -msgstr "" - -#: awx/settings/defaults.py:816 -msgid "US North Central" -msgstr "" - -#: awx/settings/defaults.py:817 -msgid "US South Central" -msgstr "" - -#: awx/settings/defaults.py:818 -msgid "US West Central" -msgstr "" - -#: awx/settings/defaults.py:819 -msgid "US West" -msgstr "" - -#: awx/settings/defaults.py:820 -msgid "US West 2" -msgstr "" - -#: awx/settings/defaults.py:821 -msgid "Canada East" -msgstr "" - -#: awx/settings/defaults.py:822 -msgid "Canada Central" -msgstr "" - -#: awx/settings/defaults.py:823 -msgid "Brazil South" -msgstr "" - -#: awx/settings/defaults.py:824 -msgid "Europe North" -msgstr "" - -#: awx/settings/defaults.py:825 -msgid "Europe West" -msgstr "" - -#: awx/settings/defaults.py:826 -msgid "UK West" -msgstr "" - -#: awx/settings/defaults.py:827 -msgid "UK South" -msgstr "" - -#: awx/settings/defaults.py:828 -msgid "Asia East" -msgstr "" - -#: awx/settings/defaults.py:829 -msgid "Asia Southeast" -msgstr "" - -#: awx/settings/defaults.py:830 -msgid "Australia East" -msgstr "" - -#: awx/settings/defaults.py:831 -msgid "Australia Southeast" -msgstr "" - -#: awx/settings/defaults.py:832 -msgid "India West" -msgstr "" - -#: awx/settings/defaults.py:833 -msgid "India South" -msgstr "" - -#: awx/settings/defaults.py:834 -msgid "Japan East" -msgstr "" - -#: awx/settings/defaults.py:835 -msgid "Japan West" -msgstr "" - -#: awx/settings/defaults.py:836 -msgid "Korea Central" -msgstr "" - -#: awx/settings/defaults.py:837 -msgid "Korea South" -msgstr "" - #: awx/sso/apps.py:9 msgid "Single Sign-On" msgstr "" -#: awx/sso/conf.py:30 +#: awx/sso/conf.py:41 msgid "" "Mapping to organization admins/users from social auth accounts. This " "setting\n" @@ -4594,46 +5052,46 @@ msgid "" "Tower documentation." msgstr "" -#: awx/sso/conf.py:55 +#: awx/sso/conf.py:67 msgid "" "Mapping of team members (users) from social auth accounts. Configuration\n" "details are available in Tower documentation." msgstr "" -#: awx/sso/conf.py:80 +#: awx/sso/conf.py:92 msgid "Authentication Backends" msgstr "" -#: awx/sso/conf.py:81 +#: awx/sso/conf.py:93 msgid "" "List of authentication backends that are enabled based on license features " "and other authentication settings." msgstr "" -#: awx/sso/conf.py:94 +#: awx/sso/conf.py:106 msgid "Social Auth Organization Map" msgstr "" -#: awx/sso/conf.py:106 +#: awx/sso/conf.py:118 msgid "Social Auth Team Map" msgstr "" -#: awx/sso/conf.py:118 +#: awx/sso/conf.py:130 msgid "Social Auth User Fields" msgstr "" -#: awx/sso/conf.py:119 +#: awx/sso/conf.py:131 msgid "" "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." msgstr "" -#: awx/sso/conf.py:141 +#: awx/sso/conf.py:153 msgid "LDAP Server URI" msgstr "" -#: awx/sso/conf.py:142 +#: awx/sso/conf.py:154 msgid "" "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 " @@ -4641,47 +5099,47 @@ msgid "" "disabled if this parameter is empty." msgstr "" -#: awx/sso/conf.py:146 awx/sso/conf.py:162 awx/sso/conf.py:174 -#: awx/sso/conf.py:186 awx/sso/conf.py:202 awx/sso/conf.py:222 -#: awx/sso/conf.py:244 awx/sso/conf.py:259 awx/sso/conf.py:277 -#: awx/sso/conf.py:294 awx/sso/conf.py:306 awx/sso/conf.py:332 -#: awx/sso/conf.py:348 awx/sso/conf.py:362 awx/sso/conf.py:380 -#: awx/sso/conf.py:406 +#: awx/sso/conf.py:158 awx/sso/conf.py:173 awx/sso/conf.py:184 +#: awx/sso/conf.py:195 awx/sso/conf.py:210 awx/sso/conf.py:229 +#: awx/sso/conf.py:250 awx/sso/conf.py:264 awx/sso/conf.py:281 +#: awx/sso/conf.py:297 awx/sso/conf.py:308 awx/sso/conf.py:333 +#: awx/sso/conf.py:348 awx/sso/conf.py:361 awx/sso/conf.py:378 +#: awx/sso/conf.py:404 msgid "LDAP" msgstr "" -#: awx/sso/conf.py:158 +#: awx/sso/conf.py:169 msgid "LDAP Bind DN" msgstr "" -#: awx/sso/conf.py:159 +#: awx/sso/conf.py:170 msgid "" "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 Ansible Tower documentation for example syntax." msgstr "" -#: awx/sso/conf.py:172 +#: awx/sso/conf.py:182 msgid "LDAP Bind Password" msgstr "" -#: awx/sso/conf.py:173 +#: awx/sso/conf.py:183 msgid "Password used to bind LDAP user account." msgstr "" -#: awx/sso/conf.py:184 +#: awx/sso/conf.py:193 msgid "LDAP Start TLS" msgstr "" -#: awx/sso/conf.py:185 +#: awx/sso/conf.py:194 msgid "Whether to enable TLS when the LDAP connection is not using SSL." msgstr "" -#: awx/sso/conf.py:195 +#: awx/sso/conf.py:203 msgid "LDAP Connection Options" msgstr "" -#: awx/sso/conf.py:196 +#: awx/sso/conf.py:204 msgid "" "Additional options to set for the LDAP connection. LDAP referrals are " "disabled by default (to prevent certain LDAP queries from hanging with AD). " @@ -4690,11 +5148,11 @@ msgid "" "values that can be set." msgstr "" -#: awx/sso/conf.py:215 +#: awx/sso/conf.py:222 msgid "LDAP User Search" msgstr "" -#: awx/sso/conf.py:216 +#: awx/sso/conf.py:223 msgid "" "LDAP search query to find users. Any user that matches the given pattern " "will be able to login to Tower. The user should also be mapped into a Tower " @@ -4703,11 +5161,11 @@ msgid "" "possible. See Tower documentation for details." msgstr "" -#: awx/sso/conf.py:238 +#: awx/sso/conf.py:244 msgid "LDAP User DN Template" msgstr "" -#: awx/sso/conf.py:239 +#: awx/sso/conf.py:245 msgid "" "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 " @@ -4715,11 +5173,11 @@ msgid "" "used instead of AUTH_LDAP_USER_SEARCH." msgstr "" -#: awx/sso/conf.py:254 +#: awx/sso/conf.py:259 msgid "LDAP User Attribute Map" msgstr "" -#: awx/sso/conf.py:255 +#: awx/sso/conf.py:260 msgid "" "Mapping of LDAP user schema to Tower API user attributes. The default " "setting is valid for ActiveDirectory but users with other LDAP " @@ -4727,41 +5185,41 @@ msgid "" "documentation for additional details." msgstr "" -#: awx/sso/conf.py:273 +#: awx/sso/conf.py:277 msgid "LDAP Group Search" msgstr "" -#: awx/sso/conf.py:274 +#: awx/sso/conf.py:278 msgid "" "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." msgstr "" -#: awx/sso/conf.py:290 +#: awx/sso/conf.py:293 msgid "LDAP Group Type" msgstr "" -#: awx/sso/conf.py:291 +#: awx/sso/conf.py:294 msgid "" "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" msgstr "" -#: awx/sso/conf.py:304 +#: awx/sso/conf.py:306 msgid "LDAP Group Type Parameters" msgstr "" -#: awx/sso/conf.py:305 +#: awx/sso/conf.py:307 msgid "Key value parameters to send the chosen group type init method." msgstr "" -#: awx/sso/conf.py:327 +#: awx/sso/conf.py:328 msgid "LDAP Require Group" msgstr "" -#: awx/sso/conf.py:328 +#: awx/sso/conf.py:329 msgid "" "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 " @@ -4778,22 +5236,22 @@ msgid "" "if a member of this group. Only one deny group is supported." msgstr "" -#: awx/sso/conf.py:358 +#: awx/sso/conf.py:357 msgid "LDAP User Flags By Group" msgstr "" -#: awx/sso/conf.py:359 +#: awx/sso/conf.py:358 msgid "" "Retrieve users from a given group. At this time, superuser and system " "auditors are the only groups supported. Refer to the Ansible Tower " "documentation for more detail." msgstr "" -#: awx/sso/conf.py:375 +#: awx/sso/conf.py:373 msgid "LDAP Organization Map" msgstr "" -#: awx/sso/conf.py:376 +#: awx/sso/conf.py:374 msgid "" "Mapping between organization admins/users and LDAP groups. This controls " "which users are placed into which Tower organizations relative to their LDAP " @@ -4801,237 +5259,237 @@ msgid "" "documentation." msgstr "" -#: awx/sso/conf.py:403 +#: awx/sso/conf.py:401 msgid "LDAP Team Map" msgstr "" -#: awx/sso/conf.py:404 +#: awx/sso/conf.py:402 msgid "" "Mapping between team members (users) and LDAP groups. Configuration details " "are available in the Ansible Tower documentation." msgstr "" -#: awx/sso/conf.py:440 +#: awx/sso/conf.py:437 msgid "RADIUS Server" msgstr "" -#: awx/sso/conf.py:441 +#: awx/sso/conf.py:438 msgid "" "Hostname/IP of RADIUS server. RADIUS authentication is disabled if this " "setting is empty." msgstr "" -#: awx/sso/conf.py:443 awx/sso/conf.py:457 awx/sso/conf.py:469 +#: awx/sso/conf.py:440 awx/sso/conf.py:453 awx/sso/conf.py:464 #: awx/sso/models.py:14 msgid "RADIUS" msgstr "" -#: awx/sso/conf.py:455 +#: awx/sso/conf.py:451 msgid "RADIUS Port" msgstr "" -#: awx/sso/conf.py:456 +#: awx/sso/conf.py:452 msgid "Port of RADIUS server." msgstr "" -#: awx/sso/conf.py:467 +#: awx/sso/conf.py:462 msgid "RADIUS Secret" msgstr "" -#: awx/sso/conf.py:468 +#: awx/sso/conf.py:463 msgid "Shared secret for authenticating to RADIUS server." msgstr "" -#: awx/sso/conf.py:484 +#: awx/sso/conf.py:478 msgid "TACACS+ Server" msgstr "" -#: awx/sso/conf.py:485 +#: awx/sso/conf.py:479 msgid "Hostname of TACACS+ server." msgstr "" -#: awx/sso/conf.py:486 awx/sso/conf.py:499 awx/sso/conf.py:512 -#: awx/sso/conf.py:525 awx/sso/conf.py:537 awx/sso/models.py:15 +#: awx/sso/conf.py:480 awx/sso/conf.py:492 awx/sso/conf.py:504 +#: awx/sso/conf.py:516 awx/sso/conf.py:528 awx/sso/models.py:15 msgid "TACACS+" msgstr "" -#: awx/sso/conf.py:497 +#: awx/sso/conf.py:490 msgid "TACACS+ Port" msgstr "" -#: awx/sso/conf.py:498 +#: awx/sso/conf.py:491 msgid "Port number of TACACS+ server." msgstr "" -#: awx/sso/conf.py:510 +#: awx/sso/conf.py:502 msgid "TACACS+ Secret" msgstr "" -#: awx/sso/conf.py:511 +#: awx/sso/conf.py:503 msgid "Shared secret for authenticating to TACACS+ server." msgstr "" -#: awx/sso/conf.py:523 +#: awx/sso/conf.py:514 msgid "TACACS+ Auth Session Timeout" msgstr "" -#: awx/sso/conf.py:524 +#: awx/sso/conf.py:515 msgid "TACACS+ session timeout value in seconds, 0 disables timeout." msgstr "" -#: awx/sso/conf.py:535 +#: awx/sso/conf.py:526 msgid "TACACS+ Authentication Protocol" msgstr "" -#: awx/sso/conf.py:536 +#: awx/sso/conf.py:527 msgid "Choose the authentication protocol used by TACACS+ client." msgstr "" -#: awx/sso/conf.py:551 +#: awx/sso/conf.py:541 msgid "Google OAuth2 Callback URL" msgstr "" -#: awx/sso/conf.py:552 awx/sso/conf.py:645 awx/sso/conf.py:710 +#: awx/sso/conf.py:542 awx/sso/conf.py:635 awx/sso/conf.py:700 msgid "" "Provide this URL as the callback URL for your application as part of your " "registration process. Refer to the Ansible Tower documentation for more " "detail." msgstr "" -#: awx/sso/conf.py:555 awx/sso/conf.py:567 awx/sso/conf.py:579 -#: awx/sso/conf.py:592 awx/sso/conf.py:606 awx/sso/conf.py:618 -#: awx/sso/conf.py:630 +#: awx/sso/conf.py:545 awx/sso/conf.py:557 awx/sso/conf.py:569 +#: awx/sso/conf.py:582 awx/sso/conf.py:596 awx/sso/conf.py:608 +#: awx/sso/conf.py:620 msgid "Google OAuth2" msgstr "" -#: awx/sso/conf.py:565 +#: awx/sso/conf.py:555 msgid "Google OAuth2 Key" msgstr "" -#: awx/sso/conf.py:566 +#: awx/sso/conf.py:556 msgid "The OAuth2 key from your web application." msgstr "" -#: awx/sso/conf.py:577 +#: awx/sso/conf.py:567 msgid "Google OAuth2 Secret" msgstr "" -#: awx/sso/conf.py:578 +#: awx/sso/conf.py:568 msgid "The OAuth2 secret from your web application." msgstr "" -#: awx/sso/conf.py:589 -msgid "Google OAuth2 Whitelisted Domains" +#: awx/sso/conf.py:579 +msgid "Google OAuth2 Allowed Domains" msgstr "" -#: awx/sso/conf.py:590 +#: awx/sso/conf.py:580 msgid "" "Update this setting to restrict the domains who are allowed to login using " "Google OAuth2." msgstr "" -#: awx/sso/conf.py:601 +#: awx/sso/conf.py:591 msgid "Google OAuth2 Extra Arguments" msgstr "" -#: awx/sso/conf.py:602 +#: awx/sso/conf.py:592 msgid "" "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 Ansible Tower documentation for more detail." msgstr "" -#: awx/sso/conf.py:616 +#: awx/sso/conf.py:606 msgid "Google OAuth2 Organization Map" msgstr "" -#: awx/sso/conf.py:628 +#: awx/sso/conf.py:618 msgid "Google OAuth2 Team Map" msgstr "" -#: awx/sso/conf.py:644 +#: awx/sso/conf.py:634 msgid "GitHub OAuth2 Callback URL" msgstr "" -#: awx/sso/conf.py:648 awx/sso/conf.py:660 awx/sso/conf.py:671 -#: awx/sso/conf.py:683 awx/sso/conf.py:695 +#: awx/sso/conf.py:638 awx/sso/conf.py:650 awx/sso/conf.py:661 +#: awx/sso/conf.py:673 awx/sso/conf.py:685 msgid "GitHub OAuth2" msgstr "" -#: awx/sso/conf.py:658 +#: awx/sso/conf.py:648 msgid "GitHub OAuth2 Key" msgstr "" -#: awx/sso/conf.py:659 +#: awx/sso/conf.py:649 msgid "The OAuth2 key (Client ID) from your GitHub developer application." msgstr "" -#: awx/sso/conf.py:669 +#: awx/sso/conf.py:659 msgid "GitHub OAuth2 Secret" msgstr "" -#: awx/sso/conf.py:670 +#: awx/sso/conf.py:660 msgid "" "The OAuth2 secret (Client Secret) from your GitHub developer application." msgstr "" -#: awx/sso/conf.py:681 +#: awx/sso/conf.py:671 msgid "GitHub OAuth2 Organization Map" msgstr "" -#: awx/sso/conf.py:693 +#: awx/sso/conf.py:683 msgid "GitHub OAuth2 Team Map" msgstr "" -#: awx/sso/conf.py:709 +#: awx/sso/conf.py:699 msgid "GitHub Organization OAuth2 Callback URL" msgstr "" -#: awx/sso/conf.py:713 awx/sso/conf.py:725 awx/sso/conf.py:736 -#: awx/sso/conf.py:749 awx/sso/conf.py:760 awx/sso/conf.py:772 +#: awx/sso/conf.py:703 awx/sso/conf.py:715 awx/sso/conf.py:726 +#: awx/sso/conf.py:739 awx/sso/conf.py:750 awx/sso/conf.py:762 msgid "GitHub Organization OAuth2" msgstr "" -#: awx/sso/conf.py:723 +#: awx/sso/conf.py:713 msgid "GitHub Organization OAuth2 Key" msgstr "" -#: awx/sso/conf.py:724 awx/sso/conf.py:802 +#: awx/sso/conf.py:714 awx/sso/conf.py:792 msgid "The OAuth2 key (Client ID) from your GitHub organization application." msgstr "" -#: awx/sso/conf.py:734 +#: awx/sso/conf.py:724 msgid "GitHub Organization OAuth2 Secret" msgstr "" -#: awx/sso/conf.py:735 awx/sso/conf.py:813 +#: awx/sso/conf.py:725 awx/sso/conf.py:803 msgid "" "The OAuth2 secret (Client Secret) from your GitHub organization application." msgstr "" -#: awx/sso/conf.py:746 +#: awx/sso/conf.py:736 msgid "GitHub Organization Name" msgstr "" -#: awx/sso/conf.py:747 +#: awx/sso/conf.py:737 msgid "" "The name of your GitHub organization, as used in your organization's URL: " "https://github.com//." msgstr "" -#: awx/sso/conf.py:758 +#: awx/sso/conf.py:748 msgid "GitHub Organization OAuth2 Organization Map" msgstr "" -#: awx/sso/conf.py:770 +#: awx/sso/conf.py:760 msgid "GitHub Organization OAuth2 Team Map" msgstr "" -#: awx/sso/conf.py:786 +#: awx/sso/conf.py:776 msgid "GitHub Team OAuth2 Callback URL" msgstr "" -#: awx/sso/conf.py:787 +#: awx/sso/conf.py:777 msgid "" "Create an organization-owned application at https://github.com/organizations/" "/settings/applications and obtain an OAuth2 key (Client ID) and " @@ -5039,97 +5497,107 @@ msgid "" "application." msgstr "" -#: awx/sso/conf.py:791 awx/sso/conf.py:803 awx/sso/conf.py:814 -#: awx/sso/conf.py:827 awx/sso/conf.py:838 awx/sso/conf.py:850 +#: awx/sso/conf.py:781 awx/sso/conf.py:793 awx/sso/conf.py:804 +#: awx/sso/conf.py:817 awx/sso/conf.py:828 awx/sso/conf.py:840 msgid "GitHub Team OAuth2" msgstr "" -#: awx/sso/conf.py:801 +#: awx/sso/conf.py:791 msgid "GitHub Team OAuth2 Key" msgstr "" -#: awx/sso/conf.py:812 +#: awx/sso/conf.py:802 msgid "GitHub Team OAuth2 Secret" msgstr "" -#: awx/sso/conf.py:824 +#: awx/sso/conf.py:814 msgid "GitHub Team ID" msgstr "" -#: awx/sso/conf.py:825 +#: awx/sso/conf.py:815 msgid "" "Find the numeric team ID using the Github API: http://fabian-kostadinov." "github.io/2015/01/16/how-to-find-a-github-team-id/." msgstr "" -#: awx/sso/conf.py:836 +#: awx/sso/conf.py:826 msgid "GitHub Team OAuth2 Organization Map" msgstr "" -#: awx/sso/conf.py:848 +#: awx/sso/conf.py:838 msgid "GitHub Team OAuth2 Team Map" msgstr "" -#: awx/sso/conf.py:864 +#: awx/sso/conf.py:854 msgid "Azure AD OAuth2 Callback URL" msgstr "" -#: awx/sso/conf.py:865 +#: awx/sso/conf.py:855 msgid "" "Provide this URL as the callback URL for your application as part of your " "registration process. Refer to the Ansible Tower documentation for more " "detail. " msgstr "" -#: awx/sso/conf.py:868 awx/sso/conf.py:880 awx/sso/conf.py:891 -#: awx/sso/conf.py:903 awx/sso/conf.py:915 +#: awx/sso/conf.py:858 awx/sso/conf.py:870 awx/sso/conf.py:881 +#: awx/sso/conf.py:893 awx/sso/conf.py:905 msgid "Azure AD OAuth2" msgstr "" -#: awx/sso/conf.py:878 +#: awx/sso/conf.py:868 msgid "Azure AD OAuth2 Key" msgstr "" -#: awx/sso/conf.py:879 +#: awx/sso/conf.py:869 msgid "The OAuth2 key (Client ID) from your Azure AD application." msgstr "" -#: awx/sso/conf.py:889 +#: awx/sso/conf.py:879 msgid "Azure AD OAuth2 Secret" msgstr "" -#: awx/sso/conf.py:890 +#: awx/sso/conf.py:880 msgid "The OAuth2 secret (Client Secret) from your Azure AD application." msgstr "" -#: awx/sso/conf.py:901 +#: awx/sso/conf.py:891 msgid "Azure AD OAuth2 Organization Map" msgstr "" -#: awx/sso/conf.py:913 +#: awx/sso/conf.py:903 msgid "Azure AD OAuth2 Team Map" msgstr "" -#: awx/sso/conf.py:938 -msgid "SAML Assertion Consumer Service (ACS) URL" +#: awx/sso/conf.py:927 +msgid "Automatically Create Organizations and Teams on SAML Login" +msgstr "" + +#: awx/sso/conf.py:928 +msgid "" +"When enabled (the default), mapped Organizations and Teams will be created " +"automatically on successful SAML login." +msgstr "" + +#: awx/sso/conf.py:930 awx/sso/conf.py:943 awx/sso/conf.py:956 +#: awx/sso/conf.py:969 awx/sso/conf.py:983 awx/sso/conf.py:996 +#: awx/sso/conf.py:1008 awx/sso/conf.py:1028 awx/sso/conf.py:1045 +#: awx/sso/conf.py:1063 awx/sso/conf.py:1098 awx/sso/conf.py:1129 +#: awx/sso/conf.py:1142 awx/sso/conf.py:1158 awx/sso/conf.py:1170 +#: awx/sso/conf.py:1182 awx/sso/conf.py:1201 awx/sso/models.py:16 +msgid "SAML" msgstr "" #: awx/sso/conf.py:939 +msgid "SAML Assertion Consumer Service (ACS) URL" +msgstr "" + +#: awx/sso/conf.py:940 msgid "" "Register Tower 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." msgstr "" -#: awx/sso/conf.py:942 awx/sso/conf.py:956 awx/sso/conf.py:970 -#: awx/sso/conf.py:985 awx/sso/conf.py:999 awx/sso/conf.py:1012 -#: awx/sso/conf.py:1033 awx/sso/conf.py:1051 awx/sso/conf.py:1070 -#: awx/sso/conf.py:1106 awx/sso/conf.py:1138 awx/sso/conf.py:1152 -#: awx/sso/conf.py:1169 awx/sso/conf.py:1182 awx/sso/conf.py:1195 -#: awx/sso/conf.py:1213 awx/sso/models.py:16 -msgid "SAML" -msgstr "" - #: awx/sso/conf.py:953 msgid "SAML Service Provider Metadata URL" msgstr "" @@ -5140,71 +5608,71 @@ msgid "" "can download one from this URL." msgstr "" -#: awx/sso/conf.py:966 +#: awx/sso/conf.py:965 msgid "SAML Service Provider Entity ID" msgstr "" -#: awx/sso/conf.py:967 +#: awx/sso/conf.py:966 msgid "" "The application-defined unique identifier used as the audience of the SAML " "service provider (SP) configuration. This is usually the URL for Tower." msgstr "" -#: awx/sso/conf.py:982 +#: awx/sso/conf.py:980 msgid "SAML Service Provider Public Certificate" msgstr "" -#: awx/sso/conf.py:983 +#: awx/sso/conf.py:981 msgid "" "Create a keypair for Tower to use as a service provider (SP) and include the " "certificate content here." msgstr "" -#: awx/sso/conf.py:996 +#: awx/sso/conf.py:993 msgid "SAML Service Provider Private Key" msgstr "" -#: awx/sso/conf.py:997 +#: awx/sso/conf.py:994 msgid "" "Create a keypair for Tower to use as a service provider (SP) and include the " "private key content here." msgstr "" -#: awx/sso/conf.py:1009 +#: awx/sso/conf.py:1005 msgid "SAML Service Provider Organization Info" msgstr "" -#: awx/sso/conf.py:1010 +#: awx/sso/conf.py:1006 msgid "" "Provide the URL, display name, and the name of your app. Refer to the " "Ansible Tower documentation for example syntax." msgstr "" -#: awx/sso/conf.py:1029 +#: awx/sso/conf.py:1024 msgid "SAML Service Provider Technical Contact" msgstr "" -#: awx/sso/conf.py:1030 +#: awx/sso/conf.py:1025 msgid "" "Provide the name and email address of the technical contact for your service " "provider. Refer to the Ansible Tower documentation for example syntax." msgstr "" -#: awx/sso/conf.py:1047 +#: awx/sso/conf.py:1041 msgid "SAML Service Provider Support Contact" msgstr "" -#: awx/sso/conf.py:1048 +#: awx/sso/conf.py:1042 msgid "" "Provide the name and email address of the support contact for your service " "provider. Refer to the Ansible Tower documentation for example syntax." msgstr "" -#: awx/sso/conf.py:1064 +#: awx/sso/conf.py:1057 msgid "SAML Enabled Identity Providers" msgstr "" -#: awx/sso/conf.py:1065 +#: awx/sso/conf.py:1058 msgid "" "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 " @@ -5213,165 +5681,129 @@ msgid "" "additional details and syntax." msgstr "" -#: awx/sso/conf.py:1102 +#: awx/sso/conf.py:1094 msgid "SAML Security Config" msgstr "" -#: awx/sso/conf.py:1103 +#: awx/sso/conf.py:1095 msgid "" "A dict of key value pairs that are passed to the underlying python-saml " "security setting https://github.com/onelogin/python-saml#settings" msgstr "" -#: awx/sso/conf.py:1135 +#: awx/sso/conf.py:1126 msgid "SAML Service Provider extra configuration data" msgstr "" -#: awx/sso/conf.py:1136 +#: awx/sso/conf.py:1127 msgid "" "A dict of key value pairs to be passed to the underlying python-saml Service " "Provider configuration setting." msgstr "" -#: awx/sso/conf.py:1149 +#: awx/sso/conf.py:1139 msgid "SAML IDP to extra_data attribute mapping" msgstr "" -#: awx/sso/conf.py:1150 +#: awx/sso/conf.py:1140 msgid "" "A list of tuples that maps IDP attributes to extra_attributes. Each " "attribute will be a list of values, even if only 1 value." msgstr "" -#: awx/sso/conf.py:1167 +#: awx/sso/conf.py:1156 msgid "SAML Organization Map" msgstr "" -#: awx/sso/conf.py:1180 +#: awx/sso/conf.py:1168 msgid "SAML Team Map" msgstr "" -#: awx/sso/conf.py:1193 +#: awx/sso/conf.py:1180 msgid "SAML Organization Attribute Mapping" msgstr "" -#: awx/sso/conf.py:1194 +#: awx/sso/conf.py:1181 msgid "Used to translate user organization membership into Tower." msgstr "" -#: awx/sso/conf.py:1211 +#: awx/sso/conf.py:1199 msgid "SAML Team Attribute Mapping" msgstr "" -#: awx/sso/conf.py:1212 +#: awx/sso/conf.py:1200 msgid "Used to translate user team membership into Tower." msgstr "" -#: awx/sso/fields.py:183 +#: awx/sso/fields.py:81 +msgid "Invalid field." +msgstr "" + +#: awx/sso/fields.py:250 #, python-brace-format msgid "Invalid connection option(s): {invalid_options}." msgstr "" -#: awx/sso/fields.py:266 +#: awx/sso/fields.py:334 msgid "Base" msgstr "" -#: awx/sso/fields.py:267 +#: awx/sso/fields.py:335 msgid "One Level" msgstr "" -#: awx/sso/fields.py:268 +#: awx/sso/fields.py:336 msgid "Subtree" msgstr "" -#: awx/sso/fields.py:286 +#: awx/sso/fields.py:354 #, python-brace-format msgid "Expected a list of three items but got {length} instead." msgstr "" -#: awx/sso/fields.py:287 +#: awx/sso/fields.py:355 #, python-brace-format msgid "Expected an instance of LDAPSearch but got {input_type} instead." msgstr "" -#: awx/sso/fields.py:323 +#: awx/sso/fields.py:391 #, python-brace-format msgid "" "Expected an instance of LDAPSearch or LDAPSearchUnion but got {input_type} " "instead." msgstr "" -#: awx/sso/fields.py:361 +#: awx/sso/fields.py:429 #, python-brace-format msgid "Invalid user attribute(s): {invalid_attrs}." msgstr "" -#: awx/sso/fields.py:378 +#: awx/sso/fields.py:447 #, python-brace-format msgid "Expected an instance of LDAPGroupType but got {input_type} instead." msgstr "" -#: awx/sso/fields.py:418 awx/sso/fields.py:465 +#: awx/sso/fields.py:487 #, python-brace-format msgid "Invalid key(s): {invalid_keys}." msgstr "" -#: awx/sso/fields.py:443 +#: awx/sso/fields.py:513 #, python-brace-format msgid "Invalid user flag: \"{invalid_flag}\"." msgstr "" -#: awx/sso/fields.py:464 -#, python-brace-format -msgid "Missing key(s): {missing_keys}." -msgstr "" - -#: awx/sso/fields.py:514 awx/sso/fields.py:631 -#, python-brace-format -msgid "Invalid key(s) for organization map: {invalid_keys}." -msgstr "" - -#: awx/sso/fields.py:532 -#, python-brace-format -msgid "Missing required key for team map: {invalid_keys}." -msgstr "" - -#: awx/sso/fields.py:533 awx/sso/fields.py:650 -#, python-brace-format -msgid "Invalid key(s) for team map: {invalid_keys}." -msgstr "" - -#: awx/sso/fields.py:649 -#, python-brace-format -msgid "Missing required key for team map: {missing_keys}." -msgstr "" - #: awx/sso/fields.py:667 #, python-brace-format -msgid "Missing required key(s) for org info record: {missing_keys}." -msgstr "" - -#: awx/sso/fields.py:680 -#, python-brace-format msgid "Invalid language code(s) for org info: {invalid_lang_codes}." msgstr "" -#: awx/sso/fields.py:699 -#, python-brace-format -msgid "Missing required key(s) for contact: {missing_keys}." -msgstr "" - -#: awx/sso/fields.py:711 -#, python-brace-format -msgid "Missing required key(s) for IdP: {missing_keys}." -msgstr "" - -#: awx/sso/pipeline.py:31 +#: awx/sso/pipeline.py:28 #, python-brace-format msgid "An account cannot be found for {0}" msgstr "" -#: awx/sso/pipeline.py:37 +#: awx/sso/pipeline.py:34 msgid "Your account is inactive" msgstr "" @@ -5410,36 +5842,8 @@ msgstr "" msgid "Resize" msgstr "" -#: awx/templates/rest_framework/base.html:37 -msgid "navbar" -msgstr "" - -#: awx/templates/rest_framework/base.html:75 -msgid "content" -msgstr "" - -#: awx/templates/rest_framework/base.html:78 -msgid "request form" -msgstr "" - -#: awx/templates/rest_framework/base.html:134 -msgid "Filters" -msgstr "" - -#: awx/templates/rest_framework/base.html:139 -msgid "main content" -msgstr "" - -#: awx/templates/rest_framework/base.html:155 -msgid "request info" -msgstr "" - -#: awx/templates/rest_framework/base.html:159 -msgid "response info" -msgstr "" - -#: awx/ui/apps.py:9 awx/ui/conf.py:22 awx/ui/conf.py:36 awx/ui/conf.py:51 -#: awx/ui/conf.py:63 awx/ui/conf.py:73 +#: awx/ui/apps.py:9 awx/ui/conf.py:22 awx/ui/conf.py:36 awx/ui/conf.py:50 +#: awx/ui/conf.py:61 awx/ui/conf.py:71 msgid "UI" msgstr "" @@ -5456,11 +5860,11 @@ msgid "Detailed" msgstr "" #: awx/ui/conf.py:20 -msgid "Analytics Tracking State" +msgid "User Analytics Tracking State" msgstr "" #: awx/ui/conf.py:21 -msgid "Enable or Disable Analytics Tracking." +msgid "Enable or Disable User Analytics Tracking." msgstr "" #: awx/ui/conf.py:31 @@ -5471,46 +5875,46 @@ msgstr "" msgid "" "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, as custom HTML or other markup languages are " -"not supported." +"added must be in plain text or an HTML fragment, as other markup languages " +"are not supported." msgstr "" -#: awx/ui/conf.py:46 +#: awx/ui/conf.py:45 msgid "Custom Logo" msgstr "" -#: awx/ui/conf.py:47 +#: awx/ui/conf.py:46 msgid "" "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." msgstr "" -#: awx/ui/conf.py:60 +#: awx/ui/conf.py:58 msgid "Max Job Events Retrieved by UI" msgstr "" -#: awx/ui/conf.py:61 +#: awx/ui/conf.py:59 msgid "" "Maximum number of job events for the UI to retrieve within a single request." msgstr "" -#: awx/ui/conf.py:70 +#: awx/ui/conf.py:68 msgid "Enable Live Updates in the UI" msgstr "" -#: awx/ui/conf.py:71 +#: awx/ui/conf.py:69 msgid "" "If disabled, the page will not refresh when events are received. Reloading " "the page will be required to get the latest details." msgstr "" -#: awx/ui/fields.py:29 +#: awx/ui/fields.py:30 msgid "" "Invalid format for custom logo. Must be a data URL with a base64-encoded " "GIF, PNG or JPEG image." msgstr "" -#: awx/ui/fields.py:30 +#: awx/ui/fields.py:31 msgid "Invalid base64-encoded data in data URL." msgstr "" diff --git a/awx/locale/en-us/LC_MESSAGES/django.po b/awx/locale/en-us/LC_MESSAGES/django.po index a07c8e72ecb9..e5fbe0539082 100644 --- a/awx/locale/en-us/LC_MESSAGES/django.po +++ b/awx/locale/en-us/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-11-27 17:28+0000\n" +"POT-Creation-Date: 2020-10-05 19:43+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -27,47 +27,56 @@ msgid "" "again." msgstr "" -#: awx/api/conf.py:17 awx/api/conf.py:26 awx/api/conf.py:34 awx/api/conf.py:47 -#: awx/api/conf.py:59 awx/sso/conf.py:85 awx/sso/conf.py:96 awx/sso/conf.py:108 -#: awx/sso/conf.py:123 +#: awx/api/conf.py:17 awx/api/conf.py:27 awx/api/conf.py:35 awx/api/conf.py:51 +#: awx/api/conf.py:64 awx/api/conf.py:76 awx/sso/conf.py:97 awx/sso/conf.py:108 +#: awx/sso/conf.py:120 awx/sso/conf.py:135 msgid "Authentication" msgstr "" -#: awx/api/conf.py:24 -msgid "Maximum number of simultaneous logged in sessions" +#: awx/api/conf.py:19 awx/api/conf.py:53 awx/main/conf.py:256 +#: awx/main/conf.py:268 awx/main/conf.py:281 awx/main/conf.py:503 +#: awx/main/conf.py:516 awx/main/conf.py:529 awx/main/conf.py:544 +#: awx/main/conf.py:682 awx/main/conf.py:764 awx/sso/conf.py:518 +msgid "seconds" msgstr "" #: awx/api/conf.py:25 +msgid "Maximum number of simultaneous logged in sessions" +msgstr "" + +#: awx/api/conf.py:26 msgid "" "Maximum number of simultaneous logged in sessions a user may have. To " "disable enter -1." msgstr "" -#: awx/api/conf.py:32 +#: awx/api/conf.py:33 msgid "Enable HTTP Basic Auth" msgstr "" -#: awx/api/conf.py:33 +#: awx/api/conf.py:34 msgid "Enable HTTP Basic Auth for the API Browser." msgstr "" -#: awx/api/conf.py:42 +#: awx/api/conf.py:44 msgid "OAuth 2 Timeout Settings" msgstr "" -#: awx/api/conf.py:43 +#: awx/api/conf.py:45 msgid "" "Dictionary for customizing OAuth 2 timeouts, available items are " "`ACCESS_TOKEN_EXPIRE_SECONDS`, the duration of access tokens in the number " -"of seconds, and `AUTHORIZATION_CODE_EXPIRE_SECONDS`, the duration of " -"authorization grants in the number of seconds." +"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." msgstr "" -#: awx/api/conf.py:54 +#: awx/api/conf.py:59 msgid "Allow External Users to Create OAuth2 Tokens" msgstr "" -#: awx/api/conf.py:55 +#: awx/api/conf.py:60 msgid "" "For security reasons, users from external auth providers (LDAP, SAML, SSO, " "Radius, and others) are not allowed to create OAuth2 tokens. To change this " @@ -75,6 +84,16 @@ msgid "" "setting is toggled off." msgstr "" +#: awx/api/conf.py:73 +msgid "Login redirect override URL" +msgstr "" + +#: awx/api/conf.py:74 +msgid "" +"URL to which unauthorized users will be redirected to log in. If blank, " +"users will be sent to the Tower login page." +msgstr "" + #: awx/api/exceptions.py:20 msgid "Resource is being used by running jobs." msgstr "" @@ -84,98 +103,95 @@ msgstr "" msgid "Invalid key names: {invalid_key_names}" msgstr "" -#: awx/api/fields.py:107 +#: awx/api/fields.py:111 msgid "Credential {} does not exist" msgstr "" -#: awx/api/filters.py:96 +#: awx/api/filters.py:82 msgid "No related model for field {}." msgstr "" -#: awx/api/filters.py:113 +#: awx/api/filters.py:99 msgid "Filtering on password fields is not allowed." msgstr "" -#: awx/api/filters.py:125 awx/api/filters.py:127 +#: awx/api/filters.py:111 awx/api/filters.py:113 #, python-format msgid "Filtering on %s is not allowed." msgstr "" -#: awx/api/filters.py:130 +#: awx/api/filters.py:116 msgid "Loops not allowed in filters, detected on field {}." msgstr "" -#: awx/api/filters.py:159 +#: awx/api/filters.py:160 msgid "Query string field name not provided." msgstr "" -#: awx/api/filters.py:186 +#: awx/api/filters.py:192 #, python-brace-format msgid "Invalid {field_name} id: {field_id}" msgstr "" -#: awx/api/filters.py:325 -#, python-format -msgid "cannot filter on kind %s" -msgstr "" - -#: awx/api/filters.py:351 +#: awx/api/filters.py:338 msgid "" "Cannot apply role_level filter to this list because its model does not use " "roles for access control." msgstr "" -#: awx/api/generics.py:196 +#: awx/api/generics.py:183 msgid "" "You did not use correct Content-Type in your HTTP request. If you are using " "our REST API, the Content-Type must be application/json" msgstr "" -#: awx/api/generics.py:632 awx/api/generics.py:694 +#: awx/api/generics.py:647 awx/api/generics.py:709 msgid "\"id\" field must be an integer." msgstr "" -#: awx/api/generics.py:691 +#: awx/api/generics.py:706 msgid "\"id\" is required to disassociate" msgstr "" -#: awx/api/generics.py:742 +#: awx/api/generics.py:757 msgid "{} 'id' field is missing." msgstr "" -#: awx/api/metadata.py:51 +#: awx/api/metadata.py:58 msgid "Database ID for this {}." msgstr "" -#: awx/api/metadata.py:52 +#: awx/api/metadata.py:59 msgid "Name of this {}." msgstr "" -#: awx/api/metadata.py:53 +#: awx/api/metadata.py:60 msgid "Optional description of this {}." msgstr "" -#: awx/api/metadata.py:54 +#: awx/api/metadata.py:61 msgid "Data type for this {}." msgstr "" -#: awx/api/metadata.py:55 +#: awx/api/metadata.py:62 msgid "URL for this {}." msgstr "" -#: awx/api/metadata.py:56 +#: awx/api/metadata.py:63 msgid "Data structure with URLs of related resources." msgstr "" -#: awx/api/metadata.py:57 -msgid "Data structure with name/description for related resources." +#: awx/api/metadata.py:64 +msgid "" +"Data structure with name/description for related resources. The output for " +"some objects may be limited for performance reasons." msgstr "" -#: awx/api/metadata.py:58 +#: awx/api/metadata.py:66 msgid "Timestamp when this {} was created." msgstr "" -#: awx/api/metadata.py:59 +#: awx/api/metadata.py:67 msgid "Timestamp when this {} was last modified." msgstr "" @@ -190,1197 +206,1327 @@ msgid "" "Possible cause: trailing comma." msgstr "" -#: awx/api/serializers.py:155 +#: awx/api/serializers.py:169 msgid "" "The original object is already named {}, a copy from it cannot have the same " "name." msgstr "" -#: awx/api/serializers.py:290 +#: awx/api/serializers.py:302 #, python-format msgid "Cannot use dictionary for %s" msgstr "" -#: awx/api/serializers.py:307 +#: awx/api/serializers.py:316 msgid "Playbook Run" msgstr "" -#: awx/api/serializers.py:308 +#: awx/api/serializers.py:317 msgid "Command" msgstr "" -#: awx/api/serializers.py:309 awx/main/models/unified_jobs.py:550 +#: awx/api/serializers.py:318 awx/main/models/unified_jobs.py:546 msgid "SCM Update" msgstr "" -#: awx/api/serializers.py:310 +#: awx/api/serializers.py:319 msgid "Inventory Sync" msgstr "" -#: awx/api/serializers.py:311 +#: awx/api/serializers.py:320 msgid "Management Job" msgstr "" -#: awx/api/serializers.py:312 +#: awx/api/serializers.py:321 msgid "Workflow Job" msgstr "" -#: awx/api/serializers.py:313 +#: awx/api/serializers.py:322 msgid "Workflow Template" msgstr "" -#: awx/api/serializers.py:314 +#: awx/api/serializers.py:323 msgid "Job Template" msgstr "" -#: awx/api/serializers.py:714 +#: awx/api/serializers.py:709 msgid "" "Indicates whether all of the events generated by this unified job have been " "saved to the database." msgstr "" -#: awx/api/serializers.py:879 +#: awx/api/serializers.py:880 msgid "Write-only field used to change the password." msgstr "" -#: awx/api/serializers.py:881 +#: awx/api/serializers.py:882 msgid "Set if the account is managed by an external service" msgstr "" -#: awx/api/serializers.py:905 +#: awx/api/serializers.py:909 msgid "Password required for new User." msgstr "" -#: awx/api/serializers.py:980 +#: awx/api/serializers.py:994 #, python-format msgid "Unable to change %s on user managed by LDAP." msgstr "" -#: awx/api/serializers.py:1066 +#: awx/api/serializers.py:1090 msgid "Must be a simple space-separated string with allowed scopes {}." msgstr "" -#: awx/api/serializers.py:1164 +#: awx/api/serializers.py:1188 msgid "Authorization Grant Type" msgstr "" -#: awx/api/serializers.py:1166 awx/main/models/credential/__init__.py:1061 +#: awx/api/serializers.py:1190 awx/main/credential_plugins/azure_kv.py:30 +#: awx/main/models/credential/__init__.py:972 msgid "Client Secret" msgstr "" -#: awx/api/serializers.py:1169 +#: awx/api/serializers.py:1193 msgid "Client Type" msgstr "" -#: awx/api/serializers.py:1172 +#: awx/api/serializers.py:1196 msgid "Redirect URIs" msgstr "" -#: awx/api/serializers.py:1175 +#: awx/api/serializers.py:1199 msgid "Skip Authorization" msgstr "" -#: awx/api/serializers.py:1290 +#: awx/api/serializers.py:1306 +msgid "Cannot change max_hosts." +msgstr "" + +#: awx/api/serializers.py:1339 msgid "This path is already being used by another manual project." msgstr "" -#: awx/api/serializers.py:1371 -msgid "Organization is missing" +#: awx/api/serializers.py:1341 +msgid "SCM branch cannot be used with archive projects." +msgstr "" + +#: awx/api/serializers.py:1343 +msgid "SCM refspec can only be used with git projects." +msgstr "" + +#: awx/api/serializers.py:1420 +msgid "" +"One or more job templates depend on branch override behavior for this " +"project (ids: {})." msgstr "" -#: awx/api/serializers.py:1375 +#: awx/api/serializers.py:1427 msgid "Update options must be set to false for manual projects." msgstr "" -#: awx/api/serializers.py:1381 +#: awx/api/serializers.py:1433 msgid "Array of playbooks available within this project." msgstr "" -#: awx/api/serializers.py:1400 +#: awx/api/serializers.py:1452 msgid "" "Array of inventory files and directories available within this project, not " "comprehensive." msgstr "" -#: awx/api/serializers.py:1448 awx/api/serializers.py:3291 -#: awx/api/serializers.py:3498 +#: awx/api/serializers.py:1500 awx/api/serializers.py:3089 +#: awx/api/serializers.py:3301 msgid "A count of hosts uniquely assigned to each status." msgstr "" -#: awx/api/serializers.py:1451 awx/api/serializers.py:3294 +#: awx/api/serializers.py:1503 awx/api/serializers.py:3092 msgid "A count of all plays and tasks for the job run." msgstr "" -#: awx/api/serializers.py:1505 awx/api/serializers.py:1732 -#: awx/api/serializers.py:3135 awx/api/serializers.py:3138 -#: awx/api/serializers.py:3141 awx/api/serializers.py:3144 -#: awx/api/serializers.py:3147 awx/api/serializers.py:3150 -#: awx/api/serializers.py:3153 awx/api/serializers.py:3156 -#: awx/api/serializers.py:3159 -msgid "This field has been deprecated and will be removed in a future release" -msgstr "" - -#: awx/api/serializers.py:1572 +#: awx/api/serializers.py:1630 msgid "Smart inventories must specify host_filter" msgstr "" -#: awx/api/serializers.py:1676 +#: awx/api/serializers.py:1722 #, python-format msgid "Invalid port specification: %s" msgstr "" -#: awx/api/serializers.py:1687 +#: awx/api/serializers.py:1733 msgid "Cannot create Host for Smart Inventory" msgstr "" -#: awx/api/serializers.py:1804 +#: awx/api/serializers.py:1751 +msgid "A Group with that name already exists." +msgstr "" + +#: awx/api/serializers.py:1822 +msgid "A Host with that name already exists." +msgstr "" + +#: awx/api/serializers.py:1827 msgid "Invalid group name." msgstr "" -#: awx/api/serializers.py:1809 +#: awx/api/serializers.py:1832 msgid "Cannot create Group for Smart Inventory" msgstr "" -#: awx/api/serializers.py:1884 +#: awx/api/serializers.py:1907 msgid "" "Script must begin with a hashbang sequence: i.e.... #!/usr/bin/env python" msgstr "" -#: awx/api/serializers.py:1914 +#: awx/api/serializers.py:1936 msgid "Cloud credential to use for inventory updates." msgstr "" -#: awx/api/serializers.py:1935 +#: awx/api/serializers.py:1957 msgid "`{}` is a prohibited environment variable" msgstr "" -#: awx/api/serializers.py:1946 +#: awx/api/serializers.py:1968 msgid "If 'source' is 'custom', 'source_script' must be provided." msgstr "" -#: awx/api/serializers.py:1952 +#: awx/api/serializers.py:1974 msgid "Must provide an inventory." msgstr "" -#: awx/api/serializers.py:1956 +#: awx/api/serializers.py:1978 msgid "" "The 'source_script' does not belong to the same organization as the " "inventory." msgstr "" -#: awx/api/serializers.py:1958 +#: awx/api/serializers.py:1980 msgid "'source_script' doesn't exist." msgstr "" -#: awx/api/serializers.py:1994 -msgid "Automatic group relationship, will be removed in 3.3" -msgstr "" - -#: awx/api/serializers.py:2081 +#: awx/api/serializers.py:2082 msgid "Cannot use manual project for SCM-based inventory." msgstr "" #: awx/api/serializers.py:2087 -msgid "" -"Manual inventory sources are created automatically when a group is created " -"in the v1 API." +msgid "Setting not compatible with existing schedules." msgstr "" #: awx/api/serializers.py:2092 -msgid "Setting not compatible with existing schedules." +msgid "Cannot create Inventory Source for Smart Inventory" msgstr "" -#: awx/api/serializers.py:2097 -msgid "Cannot create Inventory Source for Smart Inventory" +#: awx/api/serializers.py:2140 +msgid "Project required for scm type sources." msgstr "" -#: awx/api/serializers.py:2148 +#: awx/api/serializers.py:2149 #, python-format msgid "Cannot set %s if not SCM type." msgstr "" -#: awx/api/serializers.py:2423 +#: awx/api/serializers.py:2219 +msgid "The project used for this job." +msgstr "" + +#: awx/api/serializers.py:2475 msgid "Modifications not allowed for managed credential types" msgstr "" -#: awx/api/serializers.py:2428 +#: awx/api/serializers.py:2487 msgid "" "Modifications to inputs are not allowed for credential types that are in use" msgstr "" -#: awx/api/serializers.py:2434 +#: awx/api/serializers.py:2492 #, python-format msgid "Must be 'cloud' or 'net', not %s" msgstr "" -#: awx/api/serializers.py:2440 +#: awx/api/serializers.py:2498 msgid "'ask_at_runtime' is not supported for custom credentials." msgstr "" -#: awx/api/serializers.py:2511 +#: awx/api/serializers.py:2547 msgid "Credential Type" msgstr "" -#: awx/api/serializers.py:2626 -#, python-format -msgid "\"%s\" is not a valid choice" +#: awx/api/serializers.py:2611 +msgid "Modifications not allowed for managed credentials" msgstr "" -#: awx/api/serializers.py:2645 -#, python-brace-format -msgid "'{field_name}' is not a valid field for {credential_type_name}" +#: awx/api/serializers.py:2629 awx/api/serializers.py:2703 +msgid "Galaxy credentials must be owned by an Organization." msgstr "" -#: awx/api/serializers.py:2666 +#: awx/api/serializers.py:2646 msgid "" "You cannot change the credential type of the credential, as it may break the " "functionality of the resources using it." msgstr "" -#: awx/api/serializers.py:2678 +#: awx/api/serializers.py:2658 msgid "" "Write-only field used to add user to owner role. If provided, do not give " "either team or organization. Only valid for creation." msgstr "" -#: awx/api/serializers.py:2683 +#: awx/api/serializers.py:2663 msgid "" "Write-only field used to add team to owner role. If provided, do not give " "either user or organization. Only valid for creation." msgstr "" -#: awx/api/serializers.py:2688 +#: awx/api/serializers.py:2668 msgid "" "Inherit permissions from organization roles. If provided on creation, do not " "give either user or team." msgstr "" -#: awx/api/serializers.py:2704 +#: awx/api/serializers.py:2685 msgid "Missing 'user', 'team', or 'organization'." msgstr "" -#: awx/api/serializers.py:2744 +#: awx/api/serializers.py:2690 msgid "" -"Credential organization must be set and match before assigning to a team" +"Only one of 'user', 'team', or 'organization' should be provided, received " +"{} fields." msgstr "" -#: awx/api/serializers.py:2945 -msgid "You must provide a cloud credential." +#: awx/api/serializers.py:2718 +msgid "" +"Credential organization must be set and match before assigning to a team" msgstr "" -#: awx/api/serializers.py:2946 -msgid "You must provide a network credential." +#: awx/api/serializers.py:2844 +msgid "This field is required." msgstr "" -#: awx/api/serializers.py:2947 awx/main/models/jobs.py:154 -msgid "You must provide an SSH credential." +#: awx/api/serializers.py:2853 +msgid "Playbook not found for project." msgstr "" -#: awx/api/serializers.py:2948 -msgid "You must provide a vault credential." +#: awx/api/serializers.py:2855 +msgid "Must select playbook for project." msgstr "" -#: awx/api/serializers.py:2967 -msgid "This field is required." +#: awx/api/serializers.py:2857 awx/api/serializers.py:2859 +msgid "Project does not allow overriding branch." msgstr "" -#: awx/api/serializers.py:2969 awx/api/serializers.py:2971 -msgid "Playbook not found for project." +#: awx/api/serializers.py:2896 +msgid "Must be a Personal Access Token." msgstr "" -#: awx/api/serializers.py:2973 -msgid "Must select playbook for project." +#: awx/api/serializers.py:2899 +msgid "Must match the selected webhook service." msgstr "" -#: awx/api/serializers.py:3055 +#: awx/api/serializers.py:2970 msgid "Cannot enable provisioning callback without an inventory set." msgstr "" -#: awx/api/serializers.py:3058 +#: awx/api/serializers.py:2973 msgid "Must either set a default value or ask to prompt on launch." msgstr "" -#: awx/api/serializers.py:3060 awx/main/models/jobs.py:317 +#: awx/api/serializers.py:2975 awx/main/models/jobs.py:299 msgid "Job Templates must have a project assigned." msgstr "" -#: awx/api/serializers.py:3072 -msgid "" -"Job slicing is a workflows-based feature and your license does not allow use " -"of workflows." -msgstr "" - -#: awx/api/serializers.py:3213 -msgid "Invalid job template." -msgstr "" - -#: awx/api/serializers.py:3334 +#: awx/api/serializers.py:3133 msgid "No change to job limit" msgstr "" -#: awx/api/serializers.py:3335 +#: awx/api/serializers.py:3134 msgid "All failed and unreachable hosts" msgstr "" -#: awx/api/serializers.py:3350 +#: awx/api/serializers.py:3149 msgid "Missing passwords needed to start: {}" msgstr "" -#: awx/api/serializers.py:3369 +#: awx/api/serializers.py:3168 msgid "Relaunch by host status not available until job finishes running." msgstr "" -#: awx/api/serializers.py:3383 +#: awx/api/serializers.py:3182 msgid "Job Template Project is missing or undefined." msgstr "" -#: awx/api/serializers.py:3385 +#: awx/api/serializers.py:3184 msgid "Job Template Inventory is missing or undefined." msgstr "" -#: awx/api/serializers.py:3423 +#: awx/api/serializers.py:3222 msgid "Unknown, job may have been ran before launch configurations were saved." msgstr "" -#: awx/api/serializers.py:3490 awx/main/tasks.py:2302 +#: awx/api/serializers.py:3293 awx/main/tasks.py:2838 awx/main/tasks.py:2856 msgid "{} are prohibited from use in ad hoc commands." msgstr "" -#: awx/api/serializers.py:3578 awx/api/views/__init__.py:4186 +#: awx/api/serializers.py:3381 awx/api/views/__init__.py:4211 #, python-brace-format msgid "" "Standard Output too large to display ({text_size} bytes), only download " "supported for sizes over {supported_size} bytes." msgstr "" -#: awx/api/serializers.py:3785 +#: awx/api/serializers.py:3694 msgid "Provided variable {} has no database value to replace with." msgstr "" -#: awx/api/serializers.py:3803 -#, python-brace-format -msgid "\"$encrypted$ is a reserved keyword, may not be used for {var_name}.\"" +#: awx/api/serializers.py:3712 +msgid "\"$encrypted$ is a reserved keyword, may not be used for {}.\"" msgstr "" -#: awx/api/serializers.py:3877 awx/api/views/__init__.py:478 -msgid "Related template is not configured to accept credentials on launch." +#: awx/api/serializers.py:4119 +msgid "A project is required to run a job." +msgstr "" + +#: awx/api/serializers.py:4121 +msgid "Missing a revision to run due to failed project update." msgstr "" -#: awx/api/serializers.py:4353 +#: awx/api/serializers.py:4125 msgid "The inventory associated with this Job Template is being deleted." msgstr "" -#: awx/api/serializers.py:4355 awx/api/serializers.py:4467 +#: awx/api/serializers.py:4127 awx/api/serializers.py:4247 msgid "The provided inventory is being deleted." msgstr "" -#: awx/api/serializers.py:4363 +#: awx/api/serializers.py:4135 msgid "Cannot assign multiple {} credentials." msgstr "" -#: awx/api/serializers.py:4367 +#: awx/api/serializers.py:4140 msgid "Cannot assign a Credential of kind `{}`" msgstr "" -#: awx/api/serializers.py:4380 +#: awx/api/serializers.py:4153 msgid "" "Removing {} credential at launch time without replacement is not supported. " "Provided list lacked credential(s): {}." msgstr "" -#: awx/api/serializers.py:4465 +#: awx/api/serializers.py:4245 msgid "The inventory associated with this Workflow is being deleted." msgstr "" -#: awx/api/serializers.py:4532 +#: awx/api/serializers.py:4316 +msgid "Message type '{}' invalid, must be either 'message' or 'body'" +msgstr "" + +#: awx/api/serializers.py:4322 +msgid "Expected string for '{}', found {}, " +msgstr "" + +#: awx/api/serializers.py:4326 +msgid "Messages cannot contain newlines (found newline in {} event)" +msgstr "" + +#: awx/api/serializers.py:4332 +msgid "Expected dict for 'messages' field, found {}" +msgstr "" + +#: awx/api/serializers.py:4336 +msgid "" +"Event '{}' invalid, must be one of 'started', 'success', 'error', or " +"'workflow_approval'" +msgstr "" + +#: awx/api/serializers.py:4342 +msgid "Expected dict for event '{}', found {}" +msgstr "" + +#: awx/api/serializers.py:4347 +msgid "" +"Workflow Approval event '{}' invalid, must be one of 'running', 'approved', " +"'timed_out', or 'denied'" +msgstr "" + +#: awx/api/serializers.py:4354 +msgid "Expected dict for workflow approval event '{}', found {}" +msgstr "" + +#: awx/api/serializers.py:4381 +msgid "Unable to render message '{}': {}" +msgstr "" + +#: awx/api/serializers.py:4383 +msgid "Field '{}' unavailable" +msgstr "" + +#: awx/api/serializers.py:4385 +msgid "Security error due to field '{}'" +msgstr "" + +#: awx/api/serializers.py:4405 +msgid "Webhook body for '{}' should be a json dictionary. Found type '{}'." +msgstr "" + +#: awx/api/serializers.py:4408 +msgid "Webhook body for '{}' is not a valid json dictionary ({})." +msgstr "" + +#: awx/api/serializers.py:4426 msgid "" "Missing required fields for Notification Configuration: notification_type" msgstr "" -#: awx/api/serializers.py:4555 +#: awx/api/serializers.py:4453 msgid "No values specified for field '{}'" msgstr "" -#: awx/api/serializers.py:4560 +#: awx/api/serializers.py:4458 +msgid "HTTP method must be either 'POST' or 'PUT'." +msgstr "" + +#: awx/api/serializers.py:4460 msgid "Missing required fields for Notification Configuration: {}." msgstr "" -#: awx/api/serializers.py:4563 +#: awx/api/serializers.py:4463 msgid "Configuration field '{}' incorrect type, expected {}." msgstr "" -#: awx/api/serializers.py:4625 +#: awx/api/serializers.py:4480 +msgid "Notification body" +msgstr "" + +#: awx/api/serializers.py:4560 msgid "" "Valid DTSTART required in rrule. Value should start with: DTSTART:" "YYYYMMDDTHHMMSSZ" msgstr "" -#: awx/api/serializers.py:4627 +#: awx/api/serializers.py:4562 msgid "" "DTSTART cannot be a naive datetime. Specify ;TZINFO= or YYYYMMDDTHHMMSSZZ." msgstr "" -#: awx/api/serializers.py:4629 +#: awx/api/serializers.py:4564 msgid "Multiple DTSTART is not supported." msgstr "" -#: awx/api/serializers.py:4631 +#: awx/api/serializers.py:4566 msgid "RRULE required in rrule." msgstr "" -#: awx/api/serializers.py:4633 +#: awx/api/serializers.py:4568 msgid "Multiple RRULE is not supported." msgstr "" -#: awx/api/serializers.py:4635 +#: awx/api/serializers.py:4570 msgid "INTERVAL required in rrule." msgstr "" -#: awx/api/serializers.py:4637 +#: awx/api/serializers.py:4572 msgid "SECONDLY is not supported." msgstr "" -#: awx/api/serializers.py:4639 +#: awx/api/serializers.py:4574 msgid "Multiple BYMONTHDAYs not supported." msgstr "" -#: awx/api/serializers.py:4641 +#: awx/api/serializers.py:4576 msgid "Multiple BYMONTHs not supported." msgstr "" -#: awx/api/serializers.py:4643 +#: awx/api/serializers.py:4578 msgid "BYDAY with numeric prefix not supported." msgstr "" -#: awx/api/serializers.py:4645 +#: awx/api/serializers.py:4580 msgid "BYYEARDAY not supported." msgstr "" -#: awx/api/serializers.py:4647 +#: awx/api/serializers.py:4582 msgid "BYWEEKNO not supported." msgstr "" -#: awx/api/serializers.py:4649 +#: awx/api/serializers.py:4584 msgid "RRULE may not contain both COUNT and UNTIL" msgstr "" -#: awx/api/serializers.py:4653 +#: awx/api/serializers.py:4588 msgid "COUNT > 999 is unsupported." msgstr "" -#: awx/api/serializers.py:4657 +#: awx/api/serializers.py:4594 msgid "rrule parsing failed validation: {}" msgstr "" -#: awx/api/serializers.py:4715 +#: awx/api/serializers.py:4656 msgid "Inventory Source must be a cloud resource." msgstr "" -#: awx/api/serializers.py:4717 +#: awx/api/serializers.py:4658 msgid "Manual Project cannot have a schedule set." msgstr "" -#: awx/api/serializers.py:4730 +#: awx/api/serializers.py:4661 +msgid "" +"Inventory sources with `update_on_project_update` cannot be scheduled. " +"Schedule its source project `{}` instead." +msgstr "" + +#: awx/api/serializers.py:4671 msgid "" "Count of jobs in the running or waiting state that are targeted for this " "instance" msgstr "" -#: awx/api/serializers.py:4735 +#: awx/api/serializers.py:4676 msgid "Count of all jobs that target this instance" msgstr "" -#: awx/api/serializers.py:4768 +#: awx/api/serializers.py:4711 msgid "" "Count of jobs in the running or waiting state that are targeted for this " "instance group" msgstr "" -#: awx/api/serializers.py:4773 +#: awx/api/serializers.py:4716 msgid "Count of all jobs that target this instance group" msgstr "" -#: awx/api/serializers.py:4781 +#: awx/api/serializers.py:4721 +msgid "Indicates whether instance group controls any other group" +msgstr "" + +#: awx/api/serializers.py:4725 +msgid "" +"Indicates whether instances in this group are isolated.Isolated groups have " +"a designated controller group." +msgstr "" + +#: awx/api/serializers.py:4730 +msgid "" +"Indicates whether instances in this group are containerized.Containerized " +"groups have a designated Openshift or Kubernetes cluster." +msgstr "" + +#: awx/api/serializers.py:4738 msgid "Policy Instance Percentage" msgstr "" -#: awx/api/serializers.py:4782 +#: awx/api/serializers.py:4739 msgid "" "Minimum percentage of all instances that will be automatically assigned to " "this group when new instances come online." msgstr "" -#: awx/api/serializers.py:4787 +#: awx/api/serializers.py:4744 msgid "Policy Instance Minimum" msgstr "" -#: awx/api/serializers.py:4788 +#: awx/api/serializers.py:4745 msgid "" "Static minimum number of Instances that will be automatically assign to this " "group when new instances come online." msgstr "" -#: awx/api/serializers.py:4793 +#: awx/api/serializers.py:4750 msgid "Policy Instance List" msgstr "" -#: awx/api/serializers.py:4794 +#: awx/api/serializers.py:4751 msgid "List of exact-match Instances that will be assigned to this group" msgstr "" -#: awx/api/serializers.py:4816 +#: awx/api/serializers.py:4777 msgid "Duplicate entry {}." msgstr "" -#: awx/api/serializers.py:4818 +#: awx/api/serializers.py:4779 msgid "{} is not a valid hostname of an existing instance." msgstr "" -#: awx/api/serializers.py:4820 awx/api/views/mixin.py:138 +#: awx/api/serializers.py:4781 awx/api/views/mixin.py:98 msgid "" "Isolated instances may not be added or removed from instances groups via the " "API." msgstr "" -#: awx/api/serializers.py:4822 awx/api/views/mixin.py:142 +#: awx/api/serializers.py:4783 awx/api/views/mixin.py:102 msgid "Isolated instance group membership may not be managed via the API." msgstr "" -#: awx/api/serializers.py:4827 +#: awx/api/serializers.py:4785 awx/api/serializers.py:4790 +#: awx/api/serializers.py:4795 +msgid "Containerized instances may not be managed via the API" +msgstr "" + +#: awx/api/serializers.py:4800 msgid "tower instance group name may not be changed." msgstr "" -#: awx/api/serializers.py:4897 +#: awx/api/serializers.py:4805 +msgid "Only Kubernetes credentials can be associated with an Instance Group" +msgstr "" + +#: awx/api/serializers.py:4844 +msgid "" +"When present, shows the field name of the role or relationship that changed." +msgstr "" + +#: awx/api/serializers.py:4846 +msgid "" +"When present, shows the model on which the role or relationship was defined." +msgstr "" + +#: awx/api/serializers.py:4879 msgid "" "A summary of the new and changed values when an object is created, updated, " "or deleted" msgstr "" -#: awx/api/serializers.py:4899 +#: awx/api/serializers.py:4881 msgid "" "For create, update, and delete events this is the object type that was " "affected. For associate and disassociate events this is the object type " "associated or disassociated with object2." msgstr "" -#: awx/api/serializers.py:4902 +#: awx/api/serializers.py:4884 msgid "" "Unpopulated for create, update, and delete events. For associate and " "disassociate events this is the object type that object1 is being associated " "with." msgstr "" -#: awx/api/serializers.py:4905 +#: awx/api/serializers.py:4887 msgid "The action taken with respect to the given object(s)." msgstr "" -#: awx/api/views/__init__.py:161 +#: awx/api/views/__init__.py:185 +msgid "Not found." +msgstr "" + +#: awx/api/views/__init__.py:193 msgid "Dashboard" msgstr "" -#: awx/api/views/__init__.py:260 +#: awx/api/views/__init__.py:290 msgid "Dashboard Jobs Graphs" msgstr "" -#: awx/api/views/__init__.py:296 +#: awx/api/views/__init__.py:326 #, python-format msgid "Unknown period \"%s\"" msgstr "" -#: awx/api/views/__init__.py:310 +#: awx/api/views/__init__.py:340 msgid "Instances" msgstr "" -#: awx/api/views/__init__.py:318 +#: awx/api/views/__init__.py:348 msgid "Instance Detail" msgstr "" -#: awx/api/views/__init__.py:338 +#: awx/api/views/__init__.py:365 msgid "Instance Jobs" msgstr "" -#: awx/api/views/__init__.py:352 +#: awx/api/views/__init__.py:379 msgid "Instance's Instance Groups" msgstr "" -#: awx/api/views/__init__.py:361 +#: awx/api/views/__init__.py:388 msgid "Instance Groups" msgstr "" -#: awx/api/views/__init__.py:369 +#: awx/api/views/__init__.py:396 msgid "Instance Group Detail" msgstr "" -#: awx/api/views/__init__.py:377 +#: awx/api/views/__init__.py:411 msgid "Isolated Groups can not be removed from the API" msgstr "" -#: awx/api/views/__init__.py:379 +#: awx/api/views/__init__.py:413 msgid "" "Instance Groups acting as a controller for an Isolated Group can not be " "removed from the API" msgstr "" -#: awx/api/views/__init__.py:385 +#: awx/api/views/__init__.py:419 msgid "Instance Group Running Jobs" msgstr "" -#: awx/api/views/__init__.py:394 +#: awx/api/views/__init__.py:428 msgid "Instance Group's Instances" msgstr "" -#: awx/api/views/__init__.py:404 +#: awx/api/views/__init__.py:438 msgid "Schedules" msgstr "" -#: awx/api/views/__init__.py:418 +#: awx/api/views/__init__.py:452 msgid "Schedule Recurrence Rule Preview" msgstr "" -#: awx/api/views/__init__.py:465 +#: awx/api/views/__init__.py:499 msgid "Cannot assign credential when related template is null." msgstr "" -#: awx/api/views/__init__.py:470 +#: awx/api/views/__init__.py:504 msgid "Related template cannot accept {} on launch." msgstr "" -#: awx/api/views/__init__.py:472 +#: awx/api/views/__init__.py:506 msgid "" "Credential that requires user input on launch cannot be used in saved launch " "configuration." msgstr "" -#: awx/api/views/__init__.py:480 +#: awx/api/views/__init__.py:512 +msgid "Related template is not configured to accept credentials on launch." +msgstr "" + +#: awx/api/views/__init__.py:514 #, python-brace-format msgid "" "This launch configuration already provides a {credential_type} credential." msgstr "" -#: awx/api/views/__init__.py:483 +#: awx/api/views/__init__.py:517 #, python-brace-format msgid "Related template already uses {credential_type} credential." msgstr "" -#: awx/api/views/__init__.py:501 +#: awx/api/views/__init__.py:535 msgid "Schedule Jobs List" msgstr "" -#: awx/api/views/__init__.py:590 awx/api/views/__init__.py:4399 +#: awx/api/views/__init__.py:619 awx/api/views/__init__.py:4420 msgid "" "You cannot assign an Organization participation role as a child role for a " "Team." msgstr "" -#: awx/api/views/__init__.py:594 awx/api/views/__init__.py:4413 +#: awx/api/views/__init__.py:623 awx/api/views/__init__.py:4434 msgid "You cannot grant system-level permissions to a team." msgstr "" -#: awx/api/views/__init__.py:601 awx/api/views/__init__.py:4405 +#: awx/api/views/__init__.py:630 awx/api/views/__init__.py:4426 msgid "" "You cannot grant credential access to a team when the Organization field " "isn't set, or belongs to a different organization" msgstr "" -#: awx/api/views/__init__.py:715 +#: awx/api/views/__init__.py:732 msgid "Project Schedules" msgstr "" -#: awx/api/views/__init__.py:726 +#: awx/api/views/__init__.py:743 msgid "Project SCM Inventory Sources" msgstr "" -#: awx/api/views/__init__.py:827 +#: awx/api/views/__init__.py:844 msgid "Project Update Events List" msgstr "" -#: awx/api/views/__init__.py:841 +#: awx/api/views/__init__.py:858 msgid "System Job Events List" msgstr "" -#: awx/api/views/__init__.py:877 +#: awx/api/views/__init__.py:892 msgid "Project Update SCM Inventory Updates" msgstr "" -#: awx/api/views/__init__.py:936 +#: awx/api/views/__init__.py:937 msgid "Me" msgstr "" -#: awx/api/views/__init__.py:944 +#: awx/api/views/__init__.py:946 msgid "OAuth 2 Applications" msgstr "" -#: awx/api/views/__init__.py:953 +#: awx/api/views/__init__.py:955 msgid "OAuth 2 Application Detail" msgstr "" -#: awx/api/views/__init__.py:966 +#: awx/api/views/__init__.py:968 msgid "OAuth 2 Application Tokens" msgstr "" -#: awx/api/views/__init__.py:988 +#: awx/api/views/__init__.py:990 msgid "OAuth2 Tokens" msgstr "" -#: awx/api/views/__init__.py:997 +#: awx/api/views/__init__.py:999 msgid "OAuth2 User Tokens" msgstr "" -#: awx/api/views/__init__.py:1009 +#: awx/api/views/__init__.py:1011 msgid "OAuth2 User Authorized Access Tokens" msgstr "" -#: awx/api/views/__init__.py:1024 +#: awx/api/views/__init__.py:1026 msgid "Organization OAuth2 Applications" msgstr "" -#: awx/api/views/__init__.py:1036 +#: awx/api/views/__init__.py:1038 msgid "OAuth2 Personal Access Tokens" msgstr "" -#: awx/api/views/__init__.py:1051 +#: awx/api/views/__init__.py:1053 msgid "OAuth Token Detail" msgstr "" -#: awx/api/views/__init__.py:1112 awx/api/views/__init__.py:4366 +#: awx/api/views/__init__.py:1115 awx/api/views/__init__.py:4387 msgid "" "You cannot grant credential access to a user not in the credentials' " "organization" msgstr "" -#: awx/api/views/__init__.py:1116 awx/api/views/__init__.py:4370 +#: awx/api/views/__init__.py:1119 awx/api/views/__init__.py:4391 msgid "You cannot grant private credential access to another user" msgstr "" -#: awx/api/views/__init__.py:1214 +#: awx/api/views/__init__.py:1217 #, python-format msgid "Cannot change %s." msgstr "" -#: awx/api/views/__init__.py:1220 +#: awx/api/views/__init__.py:1223 msgid "Cannot delete user." msgstr "" -#: awx/api/views/__init__.py:1244 +#: awx/api/views/__init__.py:1247 msgid "Deletion not allowed for managed credential types" msgstr "" -#: awx/api/views/__init__.py:1246 +#: awx/api/views/__init__.py:1249 msgid "Credential types that are in use cannot be deleted" msgstr "" -#: awx/api/views/__init__.py:1445 -msgid "The inventory for this host is already being deleted." +#: awx/api/views/__init__.py:1362 +msgid "Deletion not allowed for managed credentials" msgstr "" -#: awx/api/views/__init__.py:1580 -msgid "Fact not found." +#: awx/api/views/__init__.py:1407 +msgid "External Credential Test" msgstr "" -#: awx/api/views/__init__.py:1610 +#: awx/api/views/__init__.py:1442 +msgid "Credential Input Source Detail" +msgstr "" + +#: awx/api/views/__init__.py:1450 awx/api/views/__init__.py:1458 +msgid "Credential Input Sources" +msgstr "" + +#: awx/api/views/__init__.py:1473 +msgid "External Credential Type Test" +msgstr "" + +#: awx/api/views/__init__.py:1539 +msgid "The inventory for this host is already being deleted." +msgstr "" + +#: awx/api/views/__init__.py:1656 msgid "SSLError while trying to connect to {}" msgstr "" -#: awx/api/views/__init__.py:1612 +#: awx/api/views/__init__.py:1658 msgid "Request to {} timed out." msgstr "" -#: awx/api/views/__init__.py:1614 +#: awx/api/views/__init__.py:1660 msgid "Unknown exception {} while trying to GET {}" msgstr "" -#: awx/api/views/__init__.py:1617 +#: awx/api/views/__init__.py:1664 msgid "" "Unauthorized access. Please check your Insights Credential username and " "password." msgstr "" -#: awx/api/views/__init__.py:1620 +#: awx/api/views/__init__.py:1668 msgid "" -"Failed to gather reports and maintenance plans from Insights API at URL {}. " -"Server responded with {} status code and message {}" +"Failed to access the Insights API at URL {}. Server responded with {} status " +"code and message {}" +msgstr "" + +#: awx/api/views/__init__.py:1677 +msgid "Expected JSON response from Insights at URL {} but instead got {}" msgstr "" -#: awx/api/views/__init__.py:1627 -msgid "Expected JSON response from Insights but instead got {}" +#: awx/api/views/__init__.py:1695 +msgid "Could not translate Insights system ID {} into an Insights platform ID." msgstr "" -#: awx/api/views/__init__.py:1634 +#: awx/api/views/__init__.py:1737 msgid "This host is not recognized as an Insights host." msgstr "" -#: awx/api/views/__init__.py:1639 +#: awx/api/views/__init__.py:1745 msgid "The Insights Credential for \"{}\" was not found." msgstr "" -#: awx/api/views/__init__.py:1707 +#: awx/api/views/__init__.py:1824 msgid "Cyclical Group association." msgstr "" -#: awx/api/views/__init__.py:1878 +#: awx/api/views/__init__.py:1990 msgid "Inventory subset argument must be a string." msgstr "" -#: awx/api/views/__init__.py:1882 +#: awx/api/views/__init__.py:1994 msgid "Subset does not use any supported syntax." msgstr "" -#: awx/api/views/__init__.py:1932 +#: awx/api/views/__init__.py:2044 msgid "Inventory Source List" msgstr "" -#: awx/api/views/__init__.py:1944 +#: awx/api/views/__init__.py:2056 msgid "Inventory Sources Update" msgstr "" -#: awx/api/views/__init__.py:1977 +#: awx/api/views/__init__.py:2089 msgid "Could not start because `can_update` returned False" msgstr "" -#: awx/api/views/__init__.py:1985 +#: awx/api/views/__init__.py:2097 msgid "No inventory sources to update." msgstr "" -#: awx/api/views/__init__.py:2014 +#: awx/api/views/__init__.py:2119 msgid "Inventory Source Schedules" msgstr "" -#: awx/api/views/__init__.py:2042 +#: awx/api/views/__init__.py:2146 msgid "Notification Templates can only be assigned when source is one of {}." msgstr "" -#: awx/api/views/__init__.py:2111 +#: awx/api/views/__init__.py:2244 msgid "Source already has credential assigned." msgstr "" -#: awx/api/views/__init__.py:2264 -msgid "Field is not allowed for use with v1 API." -msgstr "" - -#: awx/api/views/__init__.py:2274 -msgid "" -"'credentials' cannot be used in combination with 'credential', " -"'vault_credential', or 'extra_credentials'." -msgstr "" - -#: awx/api/views/__init__.py:2301 -msgid "Incorrect type. Expected {}, received {}." -msgstr "" - -#: awx/api/views/__init__.py:2399 +#: awx/api/views/__init__.py:2460 msgid "Job Template Schedules" msgstr "" -#: awx/api/views/__init__.py:2427 awx/api/views/__init__.py:2438 -msgid "Your license does not allow adding surveys." -msgstr "" - -#: awx/api/views/__init__.py:2458 +#: awx/api/views/__init__.py:2509 msgid "Field '{}' is missing from survey spec." msgstr "" -#: awx/api/views/__init__.py:2460 +#: awx/api/views/__init__.py:2511 msgid "Expected {} for field '{}', received {} type." msgstr "" -#: awx/api/views/__init__.py:2464 +#: awx/api/views/__init__.py:2515 msgid "'spec' doesn't contain any items." msgstr "" -#: awx/api/views/__init__.py:2478 +#: awx/api/views/__init__.py:2529 #, python-format msgid "Survey question %s is not a json object." msgstr "" -#: awx/api/views/__init__.py:2481 +#: awx/api/views/__init__.py:2532 #, python-brace-format msgid "'{field_name}' missing from survey question {idx}" msgstr "" -#: awx/api/views/__init__.py:2491 +#: awx/api/views/__init__.py:2542 #, python-brace-format msgid "'{field_name}' in survey question {idx} expected to be {type_label}." msgstr "" -#: awx/api/views/__init__.py:2495 +#: awx/api/views/__init__.py:2546 #, python-format msgid "'variable' '%(item)s' duplicated in survey question %(survey)s." msgstr "" -#: awx/api/views/__init__.py:2505 +#: awx/api/views/__init__.py:2556 #, python-brace-format msgid "" "'{survey_item[type]}' in survey question {idx} is not one of " "'{allowed_types}' allowed question types." msgstr "" -#: awx/api/views/__init__.py:2515 +#: awx/api/views/__init__.py:2566 #, python-brace-format msgid "" "Default value {survey_item[default]} in survey question {idx} expected to be " "{type_label}." msgstr "" -#: awx/api/views/__init__.py:2525 +#: awx/api/views/__init__.py:2576 #, python-brace-format msgid "The {min_or_max} limit in survey question {idx} expected to be integer." msgstr "" -#: awx/api/views/__init__.py:2529 +#: awx/api/views/__init__.py:2586 #, python-brace-format msgid "Survey question {idx} of type {survey_item[type]} must specify choices." msgstr "" -#: awx/api/views/__init__.py:2538 +#: awx/api/views/__init__.py:2600 +msgid "Multiple Choice (Single Select) can only have one default value." +msgstr "" + +#: awx/api/views/__init__.py:2604 +msgid "Default choice must be answered from the choices listed." +msgstr "" + +#: awx/api/views/__init__.py:2613 #, python-brace-format msgid "" "$encrypted$ is a reserved keyword for password question defaults, survey " "question {idx} is type {survey_item[type]}." msgstr "" -#: awx/api/views/__init__.py:2552 +#: awx/api/views/__init__.py:2627 #, python-brace-format msgid "" "$encrypted$ is a reserved keyword, may not be used for new default in " "position {idx}." msgstr "" -#: awx/api/views/__init__.py:2626 +#: awx/api/views/__init__.py:2699 #, python-brace-format msgid "Cannot assign multiple {credential_type} credentials." msgstr "" -#: awx/api/views/__init__.py:2630 +#: awx/api/views/__init__.py:2703 msgid "Cannot assign a Credential of kind `{}`." msgstr "" -#: awx/api/views/__init__.py:2647 -msgid "Extra credentials must be network or cloud." -msgstr "" - -#: awx/api/views/__init__.py:2669 +#: awx/api/views/__init__.py:2726 msgid "Maximum number of labels for {} reached." msgstr "" -#: awx/api/views/__init__.py:2792 +#: awx/api/views/__init__.py:2849 msgid "No matching host could be found!" msgstr "" -#: awx/api/views/__init__.py:2795 +#: awx/api/views/__init__.py:2852 msgid "Multiple hosts matched the request!" msgstr "" -#: awx/api/views/__init__.py:2800 +#: awx/api/views/__init__.py:2857 msgid "Cannot start automatically, user input required!" msgstr "" -#: awx/api/views/__init__.py:2807 +#: awx/api/views/__init__.py:2865 msgid "Host callback job already pending." msgstr "" -#: awx/api/views/__init__.py:2823 awx/api/views/__init__.py:3629 +#: awx/api/views/__init__.py:2881 awx/api/views/__init__.py:3632 msgid "Error starting job!" msgstr "" -#: awx/api/views/__init__.py:2973 -msgid "Multiple parent relationship not allowed." +#: awx/api/views/__init__.py:3005 awx/api/views/__init__.py:3025 +msgid "Cycle detected." msgstr "" -#: awx/api/views/__init__.py:2978 -msgid "Cycle detected." +#: awx/api/views/__init__.py:3017 +msgid "Relationship not allowed." msgstr "" -#: awx/api/views/__init__.py:3158 +#: awx/api/views/__init__.py:3246 msgid "Cannot relaunch slice workflow job orphaned from job template." msgstr "" -#: awx/api/views/__init__.py:3191 +#: awx/api/views/__init__.py:3248 +msgid "Cannot relaunch sliced workflow job after slice count has changed." +msgstr "" + +#: awx/api/views/__init__.py:3281 msgid "Workflow Job Template Schedules" msgstr "" -#: awx/api/views/__init__.py:3326 awx/api/views/__init__.py:4033 +#: awx/api/views/__init__.py:3424 awx/api/views/__init__.py:4055 msgid "Superuser privileges needed." msgstr "" -#: awx/api/views/__init__.py:3359 +#: awx/api/views/__init__.py:3457 msgid "System Job Template Schedules" msgstr "" -#: awx/api/views/__init__.py:3417 -msgid "POST not allowed for Job launching in version 2 of the api" -msgstr "" - -#: awx/api/views/__init__.py:3441 awx/api/views/__init__.py:3447 -msgid "PUT not allowed for Job Details in version 2 of the API" -msgstr "" - -#: awx/api/views/__init__.py:3607 +#: awx/api/views/__init__.py:3615 #, python-brace-format msgid "Wait until job finishes before retrying on {status_value} hosts." msgstr "" -#: awx/api/views/__init__.py:3612 +#: awx/api/views/__init__.py:3620 #, python-brace-format msgid "Cannot retry on {status_value} hosts, playbook stats not available." msgstr "" -#: awx/api/views/__init__.py:3617 +#: awx/api/views/__init__.py:3625 #, python-brace-format msgid "Cannot relaunch because previous job had 0 {status_value} hosts." msgstr "" -#: awx/api/views/__init__.py:3623 -#, python-brace-format -msgid "" -"Cannot relaunch because the limit length {limit_length} exceeds the max of " -"{limit_max}." -msgstr "" - -#: awx/api/views/__init__.py:3651 +#: awx/api/views/__init__.py:3654 msgid "Cannot create schedule because job requires credential passwords." msgstr "" -#: awx/api/views/__init__.py:3656 +#: awx/api/views/__init__.py:3659 msgid "Cannot create schedule because job was launched by legacy method." msgstr "" -#: awx/api/views/__init__.py:3658 +#: awx/api/views/__init__.py:3661 msgid "Cannot create schedule because a related resource is missing." msgstr "" -#: awx/api/views/__init__.py:3713 +#: awx/api/views/__init__.py:3716 msgid "Job Host Summaries List" msgstr "" -#: awx/api/views/__init__.py:3762 +#: awx/api/views/__init__.py:3770 msgid "Job Event Children List" msgstr "" -#: awx/api/views/__init__.py:3772 +#: awx/api/views/__init__.py:3786 msgid "Job Event Hosts List" msgstr "" -#: awx/api/views/__init__.py:3781 +#: awx/api/views/__init__.py:3801 msgid "Job Events List" msgstr "" -#: awx/api/views/__init__.py:3990 +#: awx/api/views/__init__.py:4012 msgid "Ad Hoc Command Events List" msgstr "" -#: awx/api/views/__init__.py:4232 +#: awx/api/views/__init__.py:4257 msgid "Delete not allowed while there are pending notifications" msgstr "" -#: awx/api/views/__init__.py:4240 +#: awx/api/views/__init__.py:4265 msgid "Notification Template Test" msgstr "" -#: awx/api/views/inventory.py:65 -msgid "Inventory Update Events List" +#: awx/api/views/__init__.py:4525 awx/api/views/__init__.py:4540 +msgid "User does not have permission to approve or deny this workflow." msgstr "" -#: awx/api/views/inventory.py:88 -msgid "Cannot delete inventory script." +#: awx/api/views/__init__.py:4527 awx/api/views/__init__.py:4542 +msgid "This workflow step has already been approved or denied." msgstr "" -#: awx/api/views/inventory.py:149 -#, python-brace-format -msgid "{0}" +#: awx/api/views/inventory.py:63 +msgid "Inventory Update Events List" +msgstr "" + +#: awx/api/views/inventory.py:90 +msgid "Cannot delete inventory script." msgstr "" -#: awx/api/views/mixin.py:50 -msgid "Your license does not allow use of the activity stream." +#: awx/api/views/inventory.py:137 +msgid "You cannot turn a regular inventory into a \"smart\" inventory." msgstr "" -#: awx/api/views/mixin.py:61 -msgid "Your license does not permit use of system tracking." +#: awx/api/views/inventory.py:150 +#, python-brace-format +msgid "{0}" msgstr "" -#: awx/api/views/mixin.py:72 -msgid "Your license does not allow use of workflows." +#: awx/api/views/metrics.py:30 +msgid "Metrics" msgstr "" -#: awx/api/views/mixin.py:86 +#: awx/api/views/mixin.py:46 msgid "Cannot delete job resource when associated workflow job is running." msgstr "" -#: awx/api/views/mixin.py:91 +#: awx/api/views/mixin.py:51 msgid "Cannot delete running job resource." msgstr "" -#: awx/api/views/mixin.py:96 +#: awx/api/views/mixin.py:56 msgid "Job has not finished processing events." msgstr "" -#: awx/api/views/mixin.py:193 +#: awx/api/views/mixin.py:153 msgid "Related job {} is still processing events." msgstr "" -#: awx/api/views/organization.py:84 -msgid "Your license only permits a single organization to exist." +#: awx/api/views/organization.py:230 +#, python-brace-format +msgid "Credential must be a Galaxy credential, not {sub.credential_type.name}." msgstr "" -#: awx/api/views/root.py:43 awx/templates/rest_framework/api.html:28 +#: awx/api/views/root.py:50 awx/templates/rest_framework/api.html:28 msgid "REST API" msgstr "" -#: awx/api/views/root.py:54 awx/templates/rest_framework/api.html:4 +#: awx/api/views/root.py:60 awx/templates/rest_framework/api.html:4 msgid "AWX REST API" msgstr "" -#: awx/api/views/root.py:67 +#: awx/api/views/root.py:73 msgid "API OAuth 2 Authorization Root" msgstr "" -#: awx/api/views/root.py:132 -msgid "Version 1" -msgstr "" - -#: awx/api/views/root.py:136 +#: awx/api/views/root.py:140 msgid "Version 2" msgstr "" -#: awx/api/views/root.py:145 +#: awx/api/views/root.py:149 msgid "Ping" msgstr "" -#: awx/api/views/root.py:176 awx/conf/apps.py:10 +#: awx/api/views/root.py:181 awx/api/views/root.py:226 awx/conf/apps.py:10 msgid "Configuration" msgstr "" -#: awx/api/views/root.py:233 +#: awx/api/views/root.py:203 awx/api/views/root.py:310 +msgid "Invalid License" +msgstr "" + +#: awx/api/views/root.py:208 +msgid "The provided credentials are invalid (HTTP 401)." +msgstr "" + +#: awx/api/views/root.py:210 +msgid "Unable to connect to proxy server." +msgstr "" + +#: awx/api/views/root.py:212 +msgid "Could not connect to subscription service." +msgstr "" + +#: awx/api/views/root.py:286 msgid "Invalid license data" msgstr "" -#: awx/api/views/root.py:235 +#: awx/api/views/root.py:288 msgid "Missing 'eula_accepted' property" msgstr "" -#: awx/api/views/root.py:239 +#: awx/api/views/root.py:292 msgid "'eula_accepted' value is invalid" msgstr "" -#: awx/api/views/root.py:242 +#: awx/api/views/root.py:295 msgid "'eula_accepted' must be True" msgstr "" -#: awx/api/views/root.py:249 +#: awx/api/views/root.py:302 msgid "Invalid JSON" msgstr "" -#: awx/api/views/root.py:257 -msgid "Invalid License" -msgstr "" - -#: awx/api/views/root.py:267 +#: awx/api/views/root.py:321 msgid "Invalid license" msgstr "" -#: awx/api/views/root.py:275 +#: awx/api/views/root.py:329 msgid "Failed to remove license." msgstr "" +#: awx/api/views/webhooks.py:143 +msgid "Webhook previously received, aborting." +msgstr "" + #: awx/conf/conf.py:20 msgid "Bud Frogs" msgstr "" @@ -1521,19 +1667,19 @@ msgstr "" msgid "Example setting that cannot be changed." msgstr "" -#: awx/conf/conf.py:93 +#: awx/conf/conf.py:90 msgid "Example Setting" msgstr "" -#: awx/conf/conf.py:94 +#: awx/conf/conf.py:91 msgid "Example setting which can be different for each user." msgstr "" -#: awx/conf/conf.py:95 awx/conf/registry.py:85 awx/conf/views.py:55 +#: awx/conf/conf.py:92 awx/conf/registry.py:81 awx/conf/views.py:56 msgid "User" msgstr "" -#: awx/conf/fields.py:60 awx/sso/fields.py:595 +#: awx/conf/fields.py:63 awx/sso/fields.py:595 #, python-brace-format msgid "" "Expected None, True, False, a string or list of strings but got {input_type} " @@ -1541,370 +1687,437 @@ msgid "" msgstr "" #: awx/conf/fields.py:104 -msgid "Enter a valid URL" -msgstr "" - -#: awx/conf/fields.py:136 #, python-brace-format -msgid "\"{input}\" is not a valid string." +msgid "Expected list of strings but got {input_type} instead." msgstr "" -#: awx/conf/fields.py:151 +#: awx/conf/fields.py:105 #, python-brace-format -msgid "Expected a list of tuples of max length 2 but got {input_type} instead." -msgstr "" - -#: awx/conf/license.py:22 -msgid "Your Tower license does not allow that." -msgstr "" - -#: awx/conf/management/commands/migrate_to_database_settings.py:41 -msgid "Only show which settings would be commented/migrated." +msgid "{path} is not a valid path choice." msgstr "" -#: awx/conf/management/commands/migrate_to_database_settings.py:48 -msgid "Skip over settings that would raise an error when commenting/migrating." -msgstr "" - -#: awx/conf/management/commands/migrate_to_database_settings.py:55 -msgid "Skip commenting out settings in files." +#: awx/conf/fields.py:149 +msgid "Enter a valid URL" msgstr "" -#: awx/conf/management/commands/migrate_to_database_settings.py:62 -msgid "Skip migrating and only comment out settings in files." +#: awx/conf/fields.py:187 +#, python-brace-format +msgid "\"{input}\" is not a valid string." msgstr "" -#: awx/conf/management/commands/migrate_to_database_settings.py:68 -msgid "Backup existing settings files with this suffix." +#: awx/conf/fields.py:202 +#, python-brace-format +msgid "Expected a list of tuples of max length 2 but got {input_type} instead." msgstr "" -#: awx/conf/registry.py:73 awx/conf/tests/unit/test_registry.py:169 -#: awx/conf/tests/unit/test_registry.py:192 -#: awx/conf/tests/unit/test_registry.py:196 -#: awx/conf/tests/unit/test_registry.py:201 -#: awx/conf/tests/unit/test_registry.py:208 +#: awx/conf/registry.py:73 awx/conf/tests/unit/test_registry.py:156 msgid "All" msgstr "" -#: awx/conf/registry.py:74 awx/conf/tests/unit/test_registry.py:170 -#: awx/conf/tests/unit/test_registry.py:193 -#: awx/conf/tests/unit/test_registry.py:197 -#: awx/conf/tests/unit/test_registry.py:202 -#: awx/conf/tests/unit/test_registry.py:209 +#: awx/conf/registry.py:74 awx/conf/tests/unit/test_registry.py:157 msgid "Changed" msgstr "" -#: awx/conf/registry.py:86 +#: awx/conf/registry.py:82 msgid "User-Defaults" msgstr "" -#: awx/conf/registry.py:154 +#: awx/conf/registry.py:145 msgid "This value has been set manually in a settings file." msgstr "" -#: awx/conf/tests/unit/test_registry.py:46 -#: awx/conf/tests/unit/test_registry.py:56 -#: awx/conf/tests/unit/test_registry.py:72 -#: awx/conf/tests/unit/test_registry.py:87 -#: awx/conf/tests/unit/test_registry.py:100 -#: awx/conf/tests/unit/test_registry.py:106 -#: awx/conf/tests/unit/test_registry.py:126 -#: awx/conf/tests/unit/test_registry.py:140 +#: awx/conf/tests/unit/test_registry.py:47 +#: awx/conf/tests/unit/test_registry.py:57 +#: awx/conf/tests/unit/test_registry.py:73 +#: awx/conf/tests/unit/test_registry.py:88 +#: awx/conf/tests/unit/test_registry.py:101 +#: awx/conf/tests/unit/test_registry.py:107 +#: awx/conf/tests/unit/test_registry.py:127 +#: awx/conf/tests/unit/test_registry.py:133 #: awx/conf/tests/unit/test_registry.py:146 -#: awx/conf/tests/unit/test_registry.py:159 -#: awx/conf/tests/unit/test_registry.py:171 -#: awx/conf/tests/unit/test_registry.py:180 -#: awx/conf/tests/unit/test_registry.py:198 -#: awx/conf/tests/unit/test_registry.py:210 -#: awx/conf/tests/unit/test_registry.py:219 -#: awx/conf/tests/unit/test_registry.py:225 -#: awx/conf/tests/unit/test_registry.py:237 -#: awx/conf/tests/unit/test_registry.py:245 -#: awx/conf/tests/unit/test_registry.py:288 -#: awx/conf/tests/unit/test_registry.py:306 -#: awx/conf/tests/unit/test_settings.py:79 -#: awx/conf/tests/unit/test_settings.py:97 -#: awx/conf/tests/unit/test_settings.py:112 -#: awx/conf/tests/unit/test_settings.py:127 -#: awx/conf/tests/unit/test_settings.py:143 -#: awx/conf/tests/unit/test_settings.py:156 -#: awx/conf/tests/unit/test_settings.py:173 -#: awx/conf/tests/unit/test_settings.py:189 -#: awx/conf/tests/unit/test_settings.py:200 -#: awx/conf/tests/unit/test_settings.py:216 -#: awx/conf/tests/unit/test_settings.py:237 -#: awx/conf/tests/unit/test_settings.py:259 -#: awx/conf/tests/unit/test_settings.py:285 -#: awx/conf/tests/unit/test_settings.py:299 -#: awx/conf/tests/unit/test_settings.py:323 +#: awx/conf/tests/unit/test_registry.py:158 +#: awx/conf/tests/unit/test_registry.py:167 +#: awx/conf/tests/unit/test_registry.py:173 +#: awx/conf/tests/unit/test_registry.py:185 +#: awx/conf/tests/unit/test_registry.py:192 +#: awx/conf/tests/unit/test_registry.py:234 +#: awx/conf/tests/unit/test_registry.py:252 +#: awx/conf/tests/unit/test_settings.py:73 +#: awx/conf/tests/unit/test_settings.py:91 +#: awx/conf/tests/unit/test_settings.py:106 +#: awx/conf/tests/unit/test_settings.py:121 +#: awx/conf/tests/unit/test_settings.py:137 +#: awx/conf/tests/unit/test_settings.py:150 +#: awx/conf/tests/unit/test_settings.py:167 +#: awx/conf/tests/unit/test_settings.py:183 +#: awx/conf/tests/unit/test_settings.py:194 +#: awx/conf/tests/unit/test_settings.py:210 +#: awx/conf/tests/unit/test_settings.py:231 +#: awx/conf/tests/unit/test_settings.py:254 +#: awx/conf/tests/unit/test_settings.py:268 +#: awx/conf/tests/unit/test_settings.py:292 +#: awx/conf/tests/unit/test_settings.py:312 +#: awx/conf/tests/unit/test_settings.py:329 #: awx/conf/tests/unit/test_settings.py:343 -#: awx/conf/tests/unit/test_settings.py:360 -#: awx/conf/tests/unit/test_settings.py:374 -#: awx/conf/tests/unit/test_settings.py:398 -#: awx/conf/tests/unit/test_settings.py:411 -#: awx/conf/tests/unit/test_settings.py:430 -#: awx/conf/tests/unit/test_settings.py:466 awx/main/conf.py:22 -#: awx/main/conf.py:32 awx/main/conf.py:43 awx/main/conf.py:53 -#: awx/main/conf.py:62 awx/main/conf.py:74 awx/main/conf.py:87 -#: awx/main/conf.py:100 awx/main/conf.py:125 +#: awx/conf/tests/unit/test_settings.py:367 +#: awx/conf/tests/unit/test_settings.py:380 +#: awx/conf/tests/unit/test_settings.py:399 +#: awx/conf/tests/unit/test_settings.py:435 awx/main/conf.py:23 +#: awx/main/conf.py:32 awx/main/conf.py:42 awx/main/conf.py:52 +#: awx/main/conf.py:64 awx/main/conf.py:77 awx/main/conf.py:90 +#: awx/main/conf.py:115 awx/main/conf.py:128 awx/main/conf.py:141 +#: awx/main/conf.py:153 awx/main/conf.py:161 awx/main/conf.py:172 +#: awx/main/conf.py:395 awx/main/conf.py:750 awx/main/conf.py:762 msgid "System" msgstr "" -#: awx/conf/tests/unit/test_registry.py:165 -#: awx/conf/tests/unit/test_registry.py:172 -#: awx/conf/tests/unit/test_registry.py:187 -#: awx/conf/tests/unit/test_registry.py:203 -#: awx/conf/tests/unit/test_registry.py:211 +#: awx/conf/tests/unit/test_registry.py:152 +#: awx/conf/tests/unit/test_registry.py:159 msgid "OtherSystem" msgstr "" -#: awx/conf/views.py:47 +#: awx/conf/views.py:48 msgid "Setting Categories" msgstr "" -#: awx/conf/views.py:71 +#: awx/conf/views.py:70 msgid "Setting Detail" msgstr "" -#: awx/conf/views.py:166 +#: awx/conf/views.py:162 msgid "Logging Connectivity Test" msgstr "" -#: awx/main/access.py:59 +#: awx/main/access.py:66 #, python-format msgid "Required related field %s for permission check." msgstr "" -#: awx/main/access.py:75 +#: awx/main/access.py:82 #, python-format msgid "Bad data found in related field %s." msgstr "" -#: awx/main/access.py:302 +#: awx/main/access.py:331 msgid "License is missing." msgstr "" -#: awx/main/access.py:304 +#: awx/main/access.py:333 msgid "License has expired." msgstr "" -#: awx/main/access.py:312 +#: awx/main/access.py:341 #, python-format msgid "License count of %s instances has been reached." msgstr "" -#: awx/main/access.py:314 +#: awx/main/access.py:343 #, python-format msgid "License count of %s instances has been exceeded." msgstr "" -#: awx/main/access.py:316 +#: awx/main/access.py:345 msgid "Host count exceeds available instances." msgstr "" -#: awx/main/access.py:320 +#: awx/main/access.py:363 awx/main/access.py:372 #, python-format -msgid "Feature %s is not enabled in the active license." -msgstr "" - -#: awx/main/access.py:322 -msgid "Features not found in active license." +msgid "" +"You have already reached the maximum number of %s hosts allowed for your " +"organization. Contact your System Administrator for assistance." msgstr "" -#: awx/main/access.py:836 +#: awx/main/access.py:927 msgid "Unable to change inventory on a host." msgstr "" -#: awx/main/access.py:853 awx/main/access.py:898 +#: awx/main/access.py:948 awx/main/access.py:990 msgid "Cannot associate two items from different inventories." msgstr "" -#: awx/main/access.py:886 +#: awx/main/access.py:978 msgid "Unable to change inventory on a group." msgstr "" -#: awx/main/access.py:1147 +#: awx/main/access.py:1261 msgid "Unable to change organization on a team." msgstr "" -#: awx/main/access.py:1163 +#: awx/main/access.py:1277 msgid "The {} role cannot be assigned to a team" msgstr "" -#: awx/main/access.py:1527 awx/main/access.py:1970 -msgid "Job was launched with prompts provided by another user." -msgstr "" - -#: awx/main/access.py:1547 -msgid "Job has been orphaned from its job template." +#: awx/main/access.py:1471 +msgid "Insufficient access to Job Template credentials." msgstr "" -#: awx/main/access.py:1549 -msgid "Job was launched with unknown prompted fields." +#: awx/main/access.py:1635 awx/main/access.py:2059 +msgid "Job was launched with secret prompts provided by another user." msgstr "" -#: awx/main/access.py:1551 -msgid "Job was launched with prompted fields." +#: awx/main/access.py:1644 +msgid "Job has been orphaned from its job template and organization." msgstr "" -#: awx/main/access.py:1553 -msgid " Organization level permissions required." +#: awx/main/access.py:1646 +msgid "Job was launched with prompted fields you do not have access to." msgstr "" -#: awx/main/access.py:1555 -msgid " You do not have permission to related resources." +#: awx/main/access.py:1648 +msgid "" +"Job was launched with unknown prompted fields. Organization admin " +"permissions required." msgstr "" -#: awx/main/access.py:1963 +#: awx/main/access.py:2049 msgid "Workflow Job was launched with unknown prompts." msgstr "" -#: awx/main/access.py:1974 +#: awx/main/access.py:2061 msgid "Job was launched with prompts you lack access to." msgstr "" -#: awx/main/access.py:1978 +#: awx/main/access.py:2063 msgid "Job was launched with prompts no longer accepted." msgstr "" -#: awx/main/access.py:1992 +#: awx/main/access.py:2075 msgid "" "You do not have permission to the workflow job resources required for " "relaunch." msgstr "" -#: awx/main/apps.py:8 -msgid "Main" +#: awx/main/analytics/collectors.py:36 +msgid "General platform configuration." msgstr "" -#: awx/main/conf.py:20 -msgid "Enable Activity Stream" +#: awx/main/analytics/collectors.py:68 +msgid "Counts of objects such as organizations, inventories, and projects" msgstr "" -#: awx/main/conf.py:21 -msgid "Enable capturing activity for the activity stream." +#: awx/main/analytics/collectors.py:103 +msgid "Counts of users and teams by organization" msgstr "" -#: awx/main/conf.py:30 -msgid "Enable Activity Stream for Inventory Sync" +#: awx/main/analytics/collectors.py:115 +msgid "Counts of credentials by credential type" msgstr "" -#: awx/main/conf.py:31 -msgid "" -"Enable capturing activity for the activity stream when running inventory " -"sync." +#: awx/main/analytics/collectors.py:127 +msgid "Inventories, their inventory sources, and host counts" msgstr "" -#: awx/main/conf.py:40 -msgid "All Users Visible to Organization Admins" +#: awx/main/analytics/collectors.py:152 +msgid "Counts of projects by source control type" msgstr "" -#: awx/main/conf.py:41 -msgid "" -"Controls whether any Organization Admin can view all users and teams, even " -"those not associated with their Organization." +#: awx/main/analytics/collectors.py:171 +msgid "Cluster topology and capacity" msgstr "" -#: awx/main/conf.py:50 -msgid "Organization Admins Can Manage Users and Teams" +#: awx/main/analytics/collectors.py:197 +msgid "Counts of jobs by status" msgstr "" -#: awx/main/conf.py:51 -msgid "" -"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." +#: awx/main/analytics/collectors.py:207 +msgid "Counts of jobs by execution node" msgstr "" -#: awx/main/conf.py:60 -msgid "Enable Administrator Alerts" +#: awx/main/analytics/collectors.py:222 +msgid "Metadata about the analytics collected" msgstr "" -#: awx/main/conf.py:61 -msgid "Email Admin users for system events that may require attention." +#: awx/main/analytics/collectors.py:285 +msgid "Automation task records" msgstr "" -#: awx/main/conf.py:71 -msgid "Base URL of the Tower host" +#: awx/main/analytics/collectors.py:314 +msgid "Data on jobs run" msgstr "" -#: awx/main/conf.py:72 -msgid "" -"This setting is used by services like notifications to render a valid url to " -"the Tower host." +#: awx/main/analytics/collectors.py:351 +msgid "Data on job templates" msgstr "" -#: awx/main/conf.py:81 -msgid "Remote Host Headers" +#: awx/main/analytics/collectors.py:374 +msgid "Data on workflow runs" msgstr "" -#: awx/main/conf.py:82 -msgid "" +#: awx/main/analytics/collectors.py:410 +msgid "Data on workflows" +msgstr "" + +#: awx/main/apps.py:8 +msgid "Main" +msgstr "" + +#: awx/main/conf.py:21 +msgid "Enable Activity Stream" +msgstr "" + +#: awx/main/conf.py:22 +msgid "Enable capturing activity for the activity stream." +msgstr "" + +#: awx/main/conf.py:30 +msgid "Enable Activity Stream for Inventory Sync" +msgstr "" + +#: awx/main/conf.py:31 +msgid "" +"Enable capturing activity for the activity stream when running inventory " +"sync." +msgstr "" + +#: awx/main/conf.py:39 +msgid "All Users Visible to Organization Admins" +msgstr "" + +#: awx/main/conf.py:40 +msgid "" +"Controls whether any Organization Admin can view all users and teams, even " +"those not associated with their Organization." +msgstr "" + +#: awx/main/conf.py:49 +msgid "Organization Admins Can Manage Users and Teams" +msgstr "" + +#: awx/main/conf.py:50 +msgid "" +"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." +msgstr "" + +#: awx/main/conf.py:61 +msgid "Base URL of the Tower host" +msgstr "" + +#: awx/main/conf.py:62 +msgid "" +"This setting is used by services like notifications to render a valid url to " +"the Tower host." +msgstr "" + +#: awx/main/conf.py:71 +msgid "Remote Host Headers" +msgstr "" + +#: awx/main/conf.py:72 +msgid "" "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." msgstr "" -#: awx/main/conf.py:94 -msgid "Proxy IP Whitelist" +#: awx/main/conf.py:84 +msgid "Proxy IP Allowed List" msgstr "" -#: awx/main/conf.py:95 +#: awx/main/conf.py:85 msgid "" "If Tower is behind a reverse proxy/load balancer, use this setting to " -"whitelist the proxy IP addresses from which Tower should trust custom " +"configure the proxy IP addresses from which Tower 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')" msgstr "" -#: awx/main/conf.py:121 +#: awx/main/conf.py:111 msgid "License" msgstr "" -#: awx/main/conf.py:122 +#: awx/main/conf.py:112 msgid "" "The license controls which features and functionality are enabled. Use /api/" -"v1/config/ to update or change the license." +"v2/config/ to update or change the license." +msgstr "" + +#: awx/main/conf.py:126 +msgid "Red Hat customer username" +msgstr "" + +#: awx/main/conf.py:127 +msgid "" +"This username is used to retrieve license information and to send Automation " +"Analytics" +msgstr "" + +#: awx/main/conf.py:139 +msgid "Red Hat customer password" msgstr "" -#: awx/main/conf.py:132 +#: awx/main/conf.py:140 +msgid "" +"This password is used to retrieve license information and to send Automation " +"Analytics" +msgstr "" + +#: awx/main/conf.py:151 +msgid "Automation Analytics upload URL" +msgstr "" + +#: awx/main/conf.py:152 +msgid "" +"This setting is used to to configure data collection for the Automation " +"Analytics dashboard" +msgstr "" + +#: awx/main/conf.py:160 +msgid "Unique identifier for an AWX/Tower installation" +msgstr "" + +#: awx/main/conf.py:169 +msgid "Custom virtual environment paths" +msgstr "" + +#: awx/main/conf.py:170 +msgid "" +"Paths where Tower will look for custom virtual environments (in addition to /" +"var/lib/awx/venv/). Enter one path per line." +msgstr "" + +#: awx/main/conf.py:180 msgid "Ansible Modules Allowed for Ad Hoc Jobs" msgstr "" -#: awx/main/conf.py:133 +#: awx/main/conf.py:181 msgid "List of modules allowed to be used by ad-hoc jobs." msgstr "" -#: awx/main/conf.py:134 awx/main/conf.py:156 awx/main/conf.py:165 -#: awx/main/conf.py:176 awx/main/conf.py:186 awx/main/conf.py:196 -#: awx/main/conf.py:206 awx/main/conf.py:217 awx/main/conf.py:229 -#: awx/main/conf.py:241 awx/main/conf.py:254 awx/main/conf.py:266 -#: awx/main/conf.py:276 awx/main/conf.py:287 awx/main/conf.py:297 -#: awx/main/conf.py:308 awx/main/conf.py:318 awx/main/conf.py:328 -#: awx/main/conf.py:340 awx/main/conf.py:352 awx/main/conf.py:364 -#: awx/main/conf.py:378 +#: awx/main/conf.py:182 awx/main/conf.py:204 awx/main/conf.py:213 +#: awx/main/conf.py:224 awx/main/conf.py:234 awx/main/conf.py:244 +#: awx/main/conf.py:254 awx/main/conf.py:266 awx/main/conf.py:279 +#: awx/main/conf.py:289 awx/main/conf.py:302 awx/main/conf.py:315 +#: awx/main/conf.py:327 awx/main/conf.py:338 awx/main/conf.py:349 +#: awx/main/conf.py:361 awx/main/conf.py:373 awx/main/conf.py:384 +#: awx/main/conf.py:404 awx/main/conf.py:414 awx/main/conf.py:424 +#: awx/main/conf.py:437 awx/main/conf.py:448 awx/main/conf.py:458 +#: awx/main/conf.py:469 awx/main/conf.py:479 awx/main/conf.py:489 +#: awx/main/conf.py:501 awx/main/conf.py:514 awx/main/conf.py:527 +#: awx/main/conf.py:542 awx/main/conf.py:555 msgid "Jobs" msgstr "" -#: awx/main/conf.py:143 +#: awx/main/conf.py:191 msgid "Always" msgstr "" -#: awx/main/conf.py:144 +#: awx/main/conf.py:192 msgid "Never" msgstr "" -#: awx/main/conf.py:145 +#: awx/main/conf.py:193 msgid "Only On Job Template Definitions" msgstr "" -#: awx/main/conf.py:148 +#: awx/main/conf.py:196 msgid "When can extra variables contain Jinja templates?" msgstr "" -#: awx/main/conf.py:150 +#: awx/main/conf.py:198 msgid "" "Ansible allows variable substitution via the Jinja2 templating language for " "--extra-vars. This poses a potential security risk where Tower users with " @@ -1913,195 +2126,294 @@ msgid "" "to \"template\" or \"never\"." msgstr "" -#: awx/main/conf.py:163 +#: awx/main/conf.py:211 msgid "Enable job isolation" msgstr "" -#: awx/main/conf.py:164 +#: awx/main/conf.py:212 msgid "" "Isolates an Ansible job from protected parts of the system to prevent " "exposing sensitive information." msgstr "" -#: awx/main/conf.py:172 +#: awx/main/conf.py:220 msgid "Job execution path" msgstr "" -#: awx/main/conf.py:173 +#: awx/main/conf.py:221 msgid "" "The directory in which Tower will create new temporary directories for job " "execution and isolation (such as credential files and custom inventory " "scripts)." msgstr "" -#: awx/main/conf.py:184 +#: awx/main/conf.py:232 msgid "Paths to hide from isolated jobs" msgstr "" -#: awx/main/conf.py:185 +#: awx/main/conf.py:233 msgid "" "Additional paths to hide from isolated processes. Enter one path per line." msgstr "" -#: awx/main/conf.py:194 +#: awx/main/conf.py:242 msgid "Paths to expose to isolated jobs" msgstr "" -#: awx/main/conf.py:195 +#: awx/main/conf.py:243 msgid "" -"Whitelist of paths that would otherwise be hidden to expose to isolated " -"jobs. Enter one path per line." +"List of paths that would otherwise be hidden to expose to isolated jobs. " +"Enter one path per line." msgstr "" -#: awx/main/conf.py:204 +#: awx/main/conf.py:252 msgid "Isolated status check interval" msgstr "" -#: awx/main/conf.py:205 +#: awx/main/conf.py:253 msgid "" "The number of seconds to sleep between status checks for jobs running on " "isolated instances." msgstr "" -#: awx/main/conf.py:214 +#: awx/main/conf.py:263 msgid "Isolated launch timeout" msgstr "" -#: awx/main/conf.py:215 +#: awx/main/conf.py:264 msgid "" "The timeout (in seconds) for launching jobs on isolated instances. This " "includes the time needed to copy source control files (playbooks) to the " "isolated instance." msgstr "" -#: awx/main/conf.py:226 +#: awx/main/conf.py:276 msgid "Isolated connection timeout" msgstr "" -#: awx/main/conf.py:227 +#: awx/main/conf.py:277 msgid "" "Ansible SSH connection timeout (in seconds) to use when communicating with " "isolated instances. Value should be substantially greater than expected " "network latency." msgstr "" -#: awx/main/conf.py:237 +#: awx/main/conf.py:287 +msgid "Isolated host key checking" +msgstr "" + +#: awx/main/conf.py:288 +msgid "" +"When set to True, AWX will enforce strict host key checking for " +"communication with isolated nodes." +msgstr "" + +#: awx/main/conf.py:298 msgid "Generate RSA keys for isolated instances" msgstr "" -#: awx/main/conf.py:238 +#: awx/main/conf.py:299 msgid "" "If set, a random RSA key will be generated and distributed to isolated " "instances. To disable this behavior and manage authentication for isolated " "instances outside of Tower, disable this setting." msgstr "" -#: awx/main/conf.py:252 awx/main/conf.py:253 +#: awx/main/conf.py:313 awx/main/conf.py:314 msgid "The RSA private key for SSH traffic to isolated instances" msgstr "" -#: awx/main/conf.py:264 awx/main/conf.py:265 +#: awx/main/conf.py:325 awx/main/conf.py:326 msgid "The RSA public key for SSH traffic to isolated instances" msgstr "" -#: awx/main/conf.py:274 +#: awx/main/conf.py:335 +msgid "Enable detailed resource profiling on all playbook runs" +msgstr "" + +#: awx/main/conf.py:336 +msgid "" +"If set, detailed resource profiling data will be collected on all jobs. This " +"data can be gathered with `sosreport`." +msgstr "" + +#: awx/main/conf.py:346 +msgid "Interval (in seconds) between polls for cpu usage." +msgstr "" + +#: awx/main/conf.py:347 +msgid "" +"Interval (in seconds) between polls for cpu usage. Setting this lower than " +"the default will affect playbook performance." +msgstr "" + +#: awx/main/conf.py:358 +msgid "Interval (in seconds) between polls for memory usage." +msgstr "" + +#: awx/main/conf.py:359 +msgid "" +"Interval (in seconds) between polls for memory usage. Setting this lower " +"than the default will affect playbook performance." +msgstr "" + +#: awx/main/conf.py:370 +msgid "Interval (in seconds) between polls for PID count." +msgstr "" + +#: awx/main/conf.py:371 +msgid "" +"Interval (in seconds) between polls for PID count. Setting this lower than " +"the default will affect playbook performance." +msgstr "" + +#: awx/main/conf.py:382 msgid "Extra Environment Variables" msgstr "" -#: awx/main/conf.py:275 +#: awx/main/conf.py:383 msgid "" "Additional environment variables set for playbook runs, inventory updates, " "project updates, and notification sending." msgstr "" -#: awx/main/conf.py:285 +#: awx/main/conf.py:393 +msgid "Gather data for Automation Analytics" +msgstr "" + +#: awx/main/conf.py:394 +msgid "Enables Tower to gather data on automation and send it to Red Hat." +msgstr "" + +#: awx/main/conf.py:402 +msgid "Run Project Updates With Higher Verbosity" +msgstr "" + +#: awx/main/conf.py:403 +msgid "" +"Adds the CLI -vvv flag to ansible-playbook runs of project_update.yml used " +"for project updates." +msgstr "" + +#: awx/main/conf.py:412 msgid "Enable Role Download" msgstr "" -#: awx/main/conf.py:286 +#: awx/main/conf.py:413 msgid "" "Allows roles to be dynamically downloaded from a requirements.yml file for " "SCM projects." msgstr "" -#: awx/main/conf.py:295 +#: awx/main/conf.py:422 +msgid "Enable Collection(s) Download" +msgstr "" + +#: awx/main/conf.py:423 +msgid "" +"Allows collections to be dynamically downloaded from a requirements.yml file " +"for SCM projects." +msgstr "" + +#: awx/main/conf.py:432 +msgid "Follow symlinks" +msgstr "" + +#: awx/main/conf.py:434 +msgid "" +"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." +msgstr "" + +#: awx/main/conf.py:445 +msgid "Ignore Ansible Galaxy SSL Certificate Verification" +msgstr "" + +#: awx/main/conf.py:446 +msgid "" +"If set to true, certificate validation will not be done when installing " +"content from any Galaxy server." +msgstr "" + +#: awx/main/conf.py:456 msgid "Standard Output Maximum Display Size" msgstr "" -#: awx/main/conf.py:296 +#: awx/main/conf.py:457 msgid "" "Maximum Size of Standard Output in bytes to display before requiring the " "output be downloaded." msgstr "" -#: awx/main/conf.py:305 +#: awx/main/conf.py:466 msgid "Job Event Standard Output Maximum Display Size" msgstr "" -#: awx/main/conf.py:307 +#: awx/main/conf.py:468 msgid "" "Maximum Size of Standard Output in bytes to display for a single job or ad " "hoc command event. `stdout` will end with `…` when truncated." msgstr "" -#: awx/main/conf.py:316 +#: awx/main/conf.py:477 msgid "Maximum Scheduled Jobs" msgstr "" -#: awx/main/conf.py:317 +#: awx/main/conf.py:478 msgid "" "Maximum number of the same job template that can be waiting to run when " "launching from a schedule before no more are created." msgstr "" -#: awx/main/conf.py:326 +#: awx/main/conf.py:487 msgid "Ansible Callback Plugins" msgstr "" -#: awx/main/conf.py:327 +#: awx/main/conf.py:488 msgid "" "List of paths to search for extra callback plugins to be used when running " "jobs. Enter one path per line." msgstr "" -#: awx/main/conf.py:337 +#: awx/main/conf.py:498 msgid "Default Job Timeout" msgstr "" -#: awx/main/conf.py:338 +#: awx/main/conf.py:499 msgid "" "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." msgstr "" -#: awx/main/conf.py:349 +#: awx/main/conf.py:511 msgid "Default Inventory Update Timeout" msgstr "" -#: awx/main/conf.py:350 +#: awx/main/conf.py:512 msgid "" "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." msgstr "" -#: awx/main/conf.py:361 +#: awx/main/conf.py:524 msgid "Default Project Update Timeout" msgstr "" -#: awx/main/conf.py:362 +#: awx/main/conf.py:525 msgid "" "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." msgstr "" -#: awx/main/conf.py:373 +#: awx/main/conf.py:537 msgid "Per-Host Ansible Fact Cache Timeout" msgstr "" -#: awx/main/conf.py:374 +#: awx/main/conf.py:538 msgid "" "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 " @@ -2110,62 +2422,74 @@ msgid "" "timeout should be imposed." msgstr "" -#: awx/main/conf.py:387 +#: awx/main/conf.py:552 +msgid "Maximum number of forks per job" +msgstr "" + +#: awx/main/conf.py:553 +msgid "" +"Saving a Job Template with more than this number of forks will result in an " +"error. When set to 0, no limit is applied." +msgstr "" + +#: awx/main/conf.py:564 msgid "Logging Aggregator" msgstr "" -#: awx/main/conf.py:388 +#: awx/main/conf.py:565 msgid "Hostname/IP where external logs will be sent to." msgstr "" -#: awx/main/conf.py:389 awx/main/conf.py:400 awx/main/conf.py:412 -#: awx/main/conf.py:422 awx/main/conf.py:434 awx/main/conf.py:449 -#: awx/main/conf.py:461 awx/main/conf.py:470 awx/main/conf.py:480 -#: awx/main/conf.py:492 awx/main/conf.py:503 awx/main/conf.py:515 -#: awx/main/conf.py:528 +#: awx/main/conf.py:566 awx/main/conf.py:577 awx/main/conf.py:589 +#: awx/main/conf.py:599 awx/main/conf.py:611 awx/main/conf.py:626 +#: awx/main/conf.py:638 awx/main/conf.py:647 awx/main/conf.py:657 +#: awx/main/conf.py:669 awx/main/conf.py:680 awx/main/conf.py:693 +#: awx/main/conf.py:706 awx/main/conf.py:718 awx/main/conf.py:729 +#: awx/main/conf.py:739 msgid "Logging" msgstr "" -#: awx/main/conf.py:397 +#: awx/main/conf.py:574 msgid "Logging Aggregator Port" msgstr "" -#: awx/main/conf.py:398 +#: awx/main/conf.py:575 msgid "" "Port on Logging Aggregator to send logs to (if required and not provided in " "Logging Aggregator)." msgstr "" -#: awx/main/conf.py:410 +#: awx/main/conf.py:587 msgid "Logging Aggregator Type" msgstr "" -#: awx/main/conf.py:411 +#: awx/main/conf.py:588 msgid "Format messages for the chosen log aggregator." msgstr "" -#: awx/main/conf.py:420 +#: awx/main/conf.py:597 msgid "Logging Aggregator Username" msgstr "" -#: awx/main/conf.py:421 -msgid "Username for external log aggregator (if required)." +#: awx/main/conf.py:598 +msgid "Username for external log aggregator (if required; HTTP/s only)." msgstr "" -#: awx/main/conf.py:432 +#: awx/main/conf.py:609 msgid "Logging Aggregator Password/Token" msgstr "" -#: awx/main/conf.py:433 +#: awx/main/conf.py:610 msgid "" -"Password or authentication token for external log aggregator (if required)." +"Password or authentication token for external log aggregator (if required; " +"HTTP/s only)." msgstr "" -#: awx/main/conf.py:442 +#: awx/main/conf.py:619 msgid "Loggers Sending Data to Log Aggregator Form" msgstr "" -#: awx/main/conf.py:443 +#: awx/main/conf.py:620 msgid "" "List of loggers that will send HTTP logs to the collector, these can include " "any or all of: \n" @@ -2175,11 +2499,11 @@ msgid "" "system_tracking - facts gathered from scan jobs." msgstr "" -#: awx/main/conf.py:456 +#: awx/main/conf.py:633 msgid "Log System Tracking Facts Individually" msgstr "" -#: awx/main/conf.py:457 +#: awx/main/conf.py:634 msgid "" "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. " @@ -2187,47 +2511,47 @@ msgid "" "efficiency in fact processing." msgstr "" -#: awx/main/conf.py:468 +#: awx/main/conf.py:645 msgid "Enable External Logging" msgstr "" -#: awx/main/conf.py:469 +#: awx/main/conf.py:646 msgid "Enable sending logs to external log aggregator." msgstr "" -#: awx/main/conf.py:478 +#: awx/main/conf.py:655 msgid "Cluster-wide Tower unique identifier." msgstr "" -#: awx/main/conf.py:479 +#: awx/main/conf.py:656 msgid "Useful to uniquely identify Tower instances." msgstr "" -#: awx/main/conf.py:488 +#: awx/main/conf.py:665 msgid "Logging Aggregator Protocol" msgstr "" -#: awx/main/conf.py:489 +#: awx/main/conf.py:666 msgid "" "Protocol used to communicate with log aggregator. HTTPS/HTTP assumes HTTPS " "unless http:// is explicitly used in the Logging Aggregator hostname." msgstr "" -#: awx/main/conf.py:499 +#: awx/main/conf.py:676 msgid "TCP Connection Timeout" msgstr "" -#: awx/main/conf.py:500 +#: awx/main/conf.py:677 msgid "" "Number of seconds for a TCP connection to external log aggregator to " "timeout. Applies to HTTPS and TCP log aggregator protocols." msgstr "" -#: awx/main/conf.py:510 +#: awx/main/conf.py:688 msgid "Enable/disable HTTPS certificate verification" msgstr "" -#: awx/main/conf.py:511 +#: awx/main/conf.py:689 msgid "" "Flag to control enable/disable of certificate verification when " "LOG_AGGREGATOR_PROTOCOL is \"https\". If enabled, Tower's log handler will " @@ -2235,11 +2559,11 @@ msgid "" "connection." msgstr "" -#: awx/main/conf.py:523 +#: awx/main/conf.py:701 msgid "Logging Aggregator Level Threshold" msgstr "" -#: awx/main/conf.py:524 +#: awx/main/conf.py:702 msgid "" "Level threshold used by log handler. Severities from lowest to highest are " "DEBUG, INFO, WARNING, ERROR, CRITICAL. Messages less severe than the " @@ -2247,1294 +2571,1537 @@ msgid "" "anlytics ignore this setting)" msgstr "" -#: awx/main/conf.py:547 awx/sso/conf.py:1264 +#: awx/main/conf.py:714 +msgid "Maximum disk persistance for external log aggregation (in GB)" +msgstr "" + +#: awx/main/conf.py:715 +msgid "" +"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." +msgstr "" + +#: awx/main/conf.py:725 +msgid "File system location for rsyslogd disk persistence" +msgstr "" + +#: awx/main/conf.py:726 +msgid "" +"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." +msgstr "" + +#: awx/main/conf.py:736 +msgid "Enable rsyslogd debugging" +msgstr "" + +#: awx/main/conf.py:737 +msgid "" +"Enabled high verbosity debugging for rsyslogd. Useful for debugging " +"connection issues for external log aggregation." +msgstr "" + +#: awx/main/conf.py:748 +msgid "Last gather date for Automation Analytics." +msgstr "" + +#: awx/main/conf.py:758 +msgid "Automation Analytics Gather Interval" +msgstr "" + +#: awx/main/conf.py:759 +msgid "Interval (in seconds) between data gathering." +msgstr "" + +#: awx/main/conf.py:782 awx/sso/conf.py:1251 msgid "\n" msgstr "" -#: awx/main/constants.py:17 +#: awx/main/constants.py:16 msgid "Sudo" msgstr "" -#: awx/main/constants.py:17 +#: awx/main/constants.py:16 msgid "Su" msgstr "" -#: awx/main/constants.py:17 +#: awx/main/constants.py:16 msgid "Pbrun" msgstr "" -#: awx/main/constants.py:17 +#: awx/main/constants.py:16 msgid "Pfexec" msgstr "" -#: awx/main/constants.py:18 +#: awx/main/constants.py:17 msgid "DZDO" msgstr "" -#: awx/main/constants.py:18 +#: awx/main/constants.py:17 msgid "Pmrun" msgstr "" -#: awx/main/constants.py:18 +#: awx/main/constants.py:17 msgid "Runas" msgstr "" -#: awx/main/constants.py:19 +#: awx/main/constants.py:18 msgid "Enable" msgstr "" -#: awx/main/constants.py:19 +#: awx/main/constants.py:18 msgid "Doas" msgstr "" +#: awx/main/constants.py:18 +msgid "Ksu" +msgstr "" + +#: awx/main/constants.py:19 +msgid "Machinectl" +msgstr "" + +#: awx/main/constants.py:19 +msgid "Sesu" +msgstr "" + #: awx/main/constants.py:21 msgid "None" msgstr "" -#: awx/main/fields.py:62 -#, python-brace-format -msgid "'{value}' is not one of ['{allowed_values}']" +#: awx/main/credential_plugins/aim.py:11 +msgid "CyberArk AIM URL" msgstr "" -#: awx/main/fields.py:421 -#, python-brace-format -msgid "{type} provided in relative path {path}, expected {expected_type}" +#: awx/main/credential_plugins/aim.py:16 +msgid "Application ID" msgstr "" -#: awx/main/fields.py:426 -#, python-brace-format -msgid "{type} provided, expected {expected_type}" +#: awx/main/credential_plugins/aim.py:21 +msgid "Client Key" msgstr "" -#: awx/main/fields.py:431 -#, python-brace-format -msgid "Schema validation error in relative path {path} ({error})" +#: awx/main/credential_plugins/aim.py:27 +msgid "Client Certificate" msgstr "" -#: awx/main/fields.py:552 -msgid "secret values must be of type string, not {}" +#: awx/main/credential_plugins/aim.py:33 +msgid "Verify SSL Certificates" msgstr "" -#: awx/main/fields.py:587 -#, python-format -msgid "cannot be set unless \"%s\" is set" +#: awx/main/credential_plugins/aim.py:39 +msgid "Object Query" msgstr "" -#: awx/main/fields.py:603 -#, python-format -msgid "required for %s" +#: awx/main/credential_plugins/aim.py:41 +msgid "" +"Lookup query for the object. Ex: Safe=TestSafe;Object=testAccountName123" msgstr "" -#: awx/main/fields.py:627 -msgid "must be set when SSH key is encrypted." +#: awx/main/credential_plugins/aim.py:44 +msgid "Object Query Format" msgstr "" -#: awx/main/fields.py:633 -msgid "should not be set when SSH key is not encrypted." +#: awx/main/credential_plugins/aim.py:50 +msgid "Reason" msgstr "" -#: awx/main/fields.py:691 -msgid "'dependencies' is not supported for custom credentials." +#: awx/main/credential_plugins/aim.py:52 +msgid "" +"Object request reason. This is only needed if it is required by the object's " +"policy." msgstr "" -#: awx/main/fields.py:705 -msgid "\"tower\" is a reserved field name" +#: awx/main/credential_plugins/azure_kv.py:21 +msgid "Vault URL (DNS Name)" msgstr "" -#: awx/main/fields.py:712 -#, python-format -msgid "field IDs must be unique (%s)" +#: awx/main/credential_plugins/azure_kv.py:26 +#: awx/main/models/credential/__init__.py:968 +msgid "Client ID" msgstr "" -#: awx/main/fields.py:725 -msgid "become_method is a reserved type name" +#: awx/main/credential_plugins/azure_kv.py:35 +#: awx/main/models/credential/__init__.py:977 +msgid "Tenant ID" msgstr "" -#: awx/main/fields.py:736 -#, python-brace-format -msgid "{sub_key} not allowed for {element_type} type ({element_id})" +#: awx/main/credential_plugins/azure_kv.py:39 +msgid "Cloud Environment" msgstr "" -#: awx/main/fields.py:794 -msgid "" -"Environment variable {} may affect Ansible configuration so its use is not " -"allowed in credentials." +#: awx/main/credential_plugins/azure_kv.py:40 +msgid "Specify which azure cloud environment to use." +msgstr "" + +#: awx/main/credential_plugins/azure_kv.py:46 +msgid "Secret Name" +msgstr "" + +#: awx/main/credential_plugins/azure_kv.py:48 +msgid "The name of the secret to look up." msgstr "" -#: awx/main/fields.py:800 -msgid "Environment variable {} is blacklisted from use in credentials." +#: awx/main/credential_plugins/azure_kv.py:51 +#: awx/main/credential_plugins/conjur.py:42 +msgid "Secret Version" msgstr "" -#: awx/main/fields.py:828 +#: awx/main/credential_plugins/azure_kv.py:53 +#: awx/main/credential_plugins/conjur.py:44 +#: awx/main/credential_plugins/hashivault.py:89 msgid "" -"Must define unnamed file injector in order to reference `tower.filename`." +"Used to specify a specific secret version (if left empty, the latest version " +"will be used)." msgstr "" -#: awx/main/fields.py:835 -msgid "Cannot directly reference reserved `tower` namespace container." +#: awx/main/credential_plugins/conjur.py:13 +msgid "Conjur URL" msgstr "" -#: awx/main/fields.py:845 -msgid "Must use multi-file syntax when injecting multiple files" +#: awx/main/credential_plugins/conjur.py:18 +msgid "API Key" msgstr "" -#: awx/main/fields.py:865 -#, python-brace-format -msgid "{sub_key} uses an undefined field ({error_msg})" +#: awx/main/credential_plugins/conjur.py:23 +#: awx/main/migrations/_inventory_source_vars.py:142 +msgid "Account" msgstr "" -#: awx/main/fields.py:872 -#, python-brace-format -msgid "" -"Syntax error rendering template for {sub_key} inside of {type} ({error_msg})" +#: awx/main/credential_plugins/conjur.py:27 +#: awx/main/models/credential/__init__.py:606 +#: awx/main/models/credential/__init__.py:662 +#: awx/main/models/credential/__init__.py:720 +#: awx/main/models/credential/__init__.py:793 +#: awx/main/models/credential/__init__.py:846 +#: awx/main/models/credential/__init__.py:872 +#: awx/main/models/credential/__init__.py:899 +#: awx/main/models/credential/__init__.py:959 +#: awx/main/models/credential/__init__.py:1032 +#: awx/main/models/credential/__init__.py:1063 +#: awx/main/models/credential/__init__.py:1113 +msgid "Username" msgstr "" -#: awx/main/middleware.py:161 -msgid "Formats of all available named urls" +#: awx/main/credential_plugins/conjur.py:31 +msgid "Public Key Certificate" +msgstr "" + +#: awx/main/credential_plugins/conjur.py:37 +msgid "Secret Identifier" +msgstr "" + +#: awx/main/credential_plugins/conjur.py:39 +msgid "The identifier for the secret e.g., /some/identifier" +msgstr "" + +#: awx/main/credential_plugins/hashivault.py:14 +msgid "Server URL" +msgstr "" + +#: awx/main/credential_plugins/hashivault.py:17 +msgid "The URL to the HashiCorp Vault" +msgstr "" + +#: awx/main/credential_plugins/hashivault.py:20 +#: awx/main/models/credential/__init__.py:998 +#: awx/main/models/credential/__init__.py:1015 +msgid "Token" +msgstr "" + +#: awx/main/credential_plugins/hashivault.py:23 +msgid "The access token used to authenticate to the Vault server" +msgstr "" + +#: awx/main/credential_plugins/hashivault.py:26 +msgid "CA Certificate" msgstr "" -#: awx/main/middleware.py:162 +#: awx/main/credential_plugins/hashivault.py:29 msgid "" -"Read-only list of key-value pairs that shows the standard format of all " -"available named URLs." +"The CA certificate used to verify the SSL certificate of the Vault server" msgstr "" -#: awx/main/middleware.py:164 awx/main/middleware.py:174 -msgid "Named URL" +#: awx/main/credential_plugins/hashivault.py:32 +msgid "AppRole role_id" msgstr "" -#: awx/main/middleware.py:171 -msgid "List of all named url graph nodes." +#: awx/main/credential_plugins/hashivault.py:35 +msgid "The Role ID for AppRole Authentication" +msgstr "" + +#: awx/main/credential_plugins/hashivault.py:38 +msgid "AppRole secret_id" +msgstr "" + +#: awx/main/credential_plugins/hashivault.py:42 +msgid "The Secret ID for AppRole Authentication" +msgstr "" + +#: awx/main/credential_plugins/hashivault.py:45 +msgid "Path to Approle Auth" msgstr "" -#: awx/main/middleware.py:172 +#: awx/main/credential_plugins/hashivault.py:49 msgid "" -"Read-only list of key-value pairs that exposes named URL graph topology. Use " -"this list to programmatically generate named URLs for resources" +"The AppRole Authentication path to use if one isn't provided in the metadata " +"when linking to an input field. Defaults to 'approle'" msgstr "" -#: awx/main/migrations/_reencrypt.py:26 awx/main/models/notifications.py:35 -msgid "Email" +#: awx/main/credential_plugins/hashivault.py:54 +msgid "Path to Secret" msgstr "" -#: awx/main/migrations/_reencrypt.py:27 awx/main/models/notifications.py:36 -msgid "Slack" +#: awx/main/credential_plugins/hashivault.py:56 +msgid "The path to the secret stored in the secret backend e.g, /some/secret/" msgstr "" -#: awx/main/migrations/_reencrypt.py:28 awx/main/models/notifications.py:37 -msgid "Twilio" +#: awx/main/credential_plugins/hashivault.py:59 +msgid "Path to Auth" msgstr "" -#: awx/main/migrations/_reencrypt.py:29 awx/main/models/notifications.py:38 -msgid "Pagerduty" +#: awx/main/credential_plugins/hashivault.py:62 +msgid "The path where the Authentication method is mounted e.g, approle" msgstr "" -#: awx/main/migrations/_reencrypt.py:30 awx/main/models/notifications.py:39 -msgid "HipChat" +#: awx/main/credential_plugins/hashivault.py:70 +msgid "API Version" msgstr "" -#: awx/main/migrations/_reencrypt.py:31 awx/main/models/notifications.py:41 -msgid "Mattermost" +#: awx/main/credential_plugins/hashivault.py:72 +msgid "" +"API v1 is for static key/value lookups. API v2 is for versioned key/value " +"lookups." msgstr "" -#: awx/main/migrations/_reencrypt.py:32 awx/main/models/notifications.py:40 -msgid "Webhook" +#: awx/main/credential_plugins/hashivault.py:77 +msgid "Name of Secret Backend" msgstr "" -#: awx/main/migrations/_reencrypt.py:33 awx/main/models/notifications.py:43 -msgid "IRC" +#: awx/main/credential_plugins/hashivault.py:79 +msgid "" +"The name of the kv secret backend (if left empty, the first segment of the " +"secret path will be used)." msgstr "" -#: awx/main/models/activity_stream.py:25 -msgid "Entity Created" +#: awx/main/credential_plugins/hashivault.py:82 +#: awx/main/migrations/_inventory_source_vars.py:147 +msgid "Key Name" msgstr "" -#: awx/main/models/activity_stream.py:26 -msgid "Entity Updated" +#: awx/main/credential_plugins/hashivault.py:84 +msgid "The name of the key to look up in the secret." msgstr "" -#: awx/main/models/activity_stream.py:27 -msgid "Entity Deleted" +#: awx/main/credential_plugins/hashivault.py:87 +msgid "Secret Version (v2 only)" msgstr "" -#: awx/main/models/activity_stream.py:28 -msgid "Entity Associated with another Entity" +#: awx/main/credential_plugins/hashivault.py:96 +msgid "Unsigned Public Key" msgstr "" -#: awx/main/models/activity_stream.py:29 -msgid "Entity was Disassociated with another Entity" +#: awx/main/credential_plugins/hashivault.py:101 +msgid "Role Name" msgstr "" -#: awx/main/models/ad_hoc_commands.py:95 -msgid "No valid inventory." +#: awx/main/credential_plugins/hashivault.py:103 +msgid "The name of the role used to sign." msgstr "" -#: awx/main/models/ad_hoc_commands.py:102 -msgid "You must provide a machine / SSH credential." +#: awx/main/credential_plugins/hashivault.py:106 +msgid "Valid Principals" msgstr "" -#: awx/main/models/ad_hoc_commands.py:113 -#: awx/main/models/ad_hoc_commands.py:121 -msgid "Invalid type for ad hoc command" +#: awx/main/credential_plugins/hashivault.py:108 +msgid "" +"Valid principals (either usernames or hostnames) that the certificate should " +"be signed for." msgstr "" -#: awx/main/models/ad_hoc_commands.py:116 -msgid "Unsupported module for ad hoc commands." +#: awx/main/fields.py:67 +#, python-brace-format +msgid "'{value}' is not one of ['{allowed_values}']" +msgstr "" + +#: awx/main/fields.py:439 +#, python-brace-format +msgid "{type} provided in relative path {path}, expected {expected_type}" +msgstr "" + +#: awx/main/fields.py:444 +#, python-brace-format +msgid "{type} provided, expected {expected_type}" +msgstr "" + +#: awx/main/fields.py:449 +#, python-brace-format +msgid "Schema validation error in relative path {path} ({error})" msgstr "" -#: awx/main/models/ad_hoc_commands.py:124 +#: awx/main/fields.py:558 #, python-format -msgid "No argument passed to %s module." +msgid "required for %s" msgstr "" -#: awx/main/models/base.py:33 awx/main/models/base.py:39 -#: awx/main/models/base.py:44 awx/main/models/base.py:49 -msgid "Run" +#: awx/main/fields.py:632 +msgid "secret values must be of type string, not {}" msgstr "" -#: awx/main/models/base.py:34 awx/main/models/base.py:40 -#: awx/main/models/base.py:45 awx/main/models/base.py:50 -msgid "Check" +#: awx/main/fields.py:675 +#, python-format +msgid "cannot be set unless \"%s\" is set" msgstr "" -#: awx/main/models/base.py:35 -msgid "Scan" +#: awx/main/fields.py:710 +msgid "must be set when SSH key is encrypted." msgstr "" -#: awx/main/models/credential/__init__.py:110 -msgid "Host" +#: awx/main/fields.py:718 +msgid "should not be set when SSH key is not encrypted." msgstr "" -#: awx/main/models/credential/__init__.py:111 -msgid "The hostname or IP address to use." +#: awx/main/fields.py:777 +msgid "'dependencies' is not supported for custom credentials." msgstr "" -#: awx/main/models/credential/__init__.py:117 -#: awx/main/models/credential/__init__.py:683 -#: awx/main/models/credential/__init__.py:738 -#: awx/main/models/credential/__init__.py:803 -#: awx/main/models/credential/__init__.py:881 -#: awx/main/models/credential/__init__.py:927 -#: awx/main/models/credential/__init__.py:955 -#: awx/main/models/credential/__init__.py:984 -#: awx/main/models/credential/__init__.py:1048 -#: awx/main/models/credential/__init__.py:1089 -#: awx/main/models/credential/__init__.py:1122 -#: awx/main/models/credential/__init__.py:1174 -msgid "Username" +#: awx/main/fields.py:791 +msgid "\"tower\" is a reserved field name" msgstr "" -#: awx/main/models/credential/__init__.py:118 -msgid "Username for this credential." +#: awx/main/fields.py:798 +#, python-format +msgid "field IDs must be unique (%s)" msgstr "" -#: awx/main/models/credential/__init__.py:124 -#: awx/main/models/credential/__init__.py:687 -#: awx/main/models/credential/__init__.py:742 -#: awx/main/models/credential/__init__.py:807 -#: awx/main/models/credential/__init__.py:931 -#: awx/main/models/credential/__init__.py:959 -#: awx/main/models/credential/__init__.py:988 -#: awx/main/models/credential/__init__.py:1052 -#: awx/main/models/credential/__init__.py:1093 -#: awx/main/models/credential/__init__.py:1126 -#: awx/main/models/credential/__init__.py:1178 -msgid "Password" +#: awx/main/fields.py:813 +msgid "{} is not a {}" +msgstr "" + +#: awx/main/fields.py:819 +#, python-brace-format +msgid "{sub_key} not allowed for {element_type} type ({element_id})" msgstr "" -#: awx/main/models/credential/__init__.py:125 +#: awx/main/fields.py:877 msgid "" -"Password for this credential (or \"ASK\" to prompt the user for machine " -"credentials)." +"Environment variable {} may affect Ansible configuration so its use is not " +"allowed in credentials." +msgstr "" + +#: awx/main/fields.py:883 +msgid "Environment variable {} is not allowed to be used in credentials." +msgstr "" + +#: awx/main/fields.py:911 +msgid "" +"Must define unnamed file injector in order to reference `tower.filename`." msgstr "" -#: awx/main/models/credential/__init__.py:132 -msgid "Security Token" +#: awx/main/fields.py:918 +msgid "Cannot directly reference reserved `tower` namespace container." msgstr "" -#: awx/main/models/credential/__init__.py:133 -msgid "Security Token for this credential" +#: awx/main/fields.py:928 +msgid "Must use multi-file syntax when injecting multiple files" msgstr "" -#: awx/main/models/credential/__init__.py:139 -msgid "Project" +#: awx/main/fields.py:948 +#, python-brace-format +msgid "{sub_key} uses an undefined field ({error_msg})" msgstr "" -#: awx/main/models/credential/__init__.py:140 -msgid "The identifier for the project." +#: awx/main/fields.py:955 +msgid "Encountered unsafe code execution: {}" msgstr "" -#: awx/main/models/credential/__init__.py:146 -msgid "Domain" +#: awx/main/fields.py:959 +#, python-brace-format +msgid "" +"Syntax error rendering template for {sub_key} inside of {type} ({error_msg})" msgstr "" -#: awx/main/models/credential/__init__.py:147 -msgid "The identifier for the domain." +#: awx/main/middleware.py:118 +msgid "Formats of all available named urls" msgstr "" -#: awx/main/models/credential/__init__.py:152 -msgid "SSH private key" +#: awx/main/middleware.py:119 +msgid "" +"Read-only list of key-value pairs that shows the standard format of all " +"available named URLs." msgstr "" -#: awx/main/models/credential/__init__.py:153 -msgid "RSA or DSA private key to be used instead of password." +#: awx/main/middleware.py:121 awx/main/middleware.py:131 +msgid "Named URL" msgstr "" -#: awx/main/models/credential/__init__.py:159 -msgid "SSH key unlock" +#: awx/main/middleware.py:128 +msgid "List of all named url graph nodes." msgstr "" -#: awx/main/models/credential/__init__.py:160 +#: awx/main/middleware.py:129 msgid "" -"Passphrase to unlock SSH private key if encrypted (or \"ASK\" to prompt the " -"user for machine credentials)." +"Read-only list of key-value pairs that exposes named URL graph topology. Use " +"this list to programmatically generate named URLs for resources" +msgstr "" + +#: awx/main/migrations/_inventory_source_vars.py:140 +msgid "Image ID" +msgstr "" + +#: awx/main/migrations/_inventory_source_vars.py:141 +msgid "Availability Zone" +msgstr "" + +#: awx/main/migrations/_inventory_source_vars.py:143 +msgid "Instance ID" +msgstr "" + +#: awx/main/migrations/_inventory_source_vars.py:144 +msgid "Instance State" +msgstr "" + +#: awx/main/migrations/_inventory_source_vars.py:145 +msgid "Platform" +msgstr "" + +#: awx/main/migrations/_inventory_source_vars.py:146 +msgid "Instance Type" +msgstr "" + +#: awx/main/migrations/_inventory_source_vars.py:148 +msgid "Region" msgstr "" -#: awx/main/models/credential/__init__.py:168 -msgid "Privilege escalation method." +#: awx/main/migrations/_inventory_source_vars.py:149 +msgid "Security Group" msgstr "" -#: awx/main/models/credential/__init__.py:174 -msgid "Privilege escalation username." +#: awx/main/migrations/_inventory_source_vars.py:150 +msgid "Tags" msgstr "" -#: awx/main/models/credential/__init__.py:180 -msgid "Password for privilege escalation method." +#: awx/main/migrations/_inventory_source_vars.py:151 +msgid "Tag None" msgstr "" -#: awx/main/models/credential/__init__.py:186 -msgid "Vault password (or \"ASK\" to prompt the user)." +#: awx/main/migrations/_inventory_source_vars.py:152 +msgid "VPC ID" msgstr "" -#: awx/main/models/credential/__init__.py:190 -msgid "Whether to use the authorize mechanism." +#: awx/main/models/activity_stream.py:28 +msgid "Entity Created" msgstr "" -#: awx/main/models/credential/__init__.py:196 -msgid "Password used by the authorize mechanism." +#: awx/main/models/activity_stream.py:29 +msgid "Entity Updated" msgstr "" -#: awx/main/models/credential/__init__.py:202 -msgid "Client Id or Application Id for the credential" +#: awx/main/models/activity_stream.py:30 +msgid "Entity Deleted" msgstr "" -#: awx/main/models/credential/__init__.py:208 -msgid "Secret Token for this credential" +#: awx/main/models/activity_stream.py:31 +msgid "Entity Associated with another Entity" msgstr "" -#: awx/main/models/credential/__init__.py:214 -msgid "Subscription identifier for this credential" +#: awx/main/models/activity_stream.py:32 +msgid "Entity was Disassociated with another Entity" msgstr "" -#: awx/main/models/credential/__init__.py:220 -msgid "Tenant identifier for this credential" +#: awx/main/models/activity_stream.py:45 +msgid "The cluster node the activity took place on." msgstr "" -#: awx/main/models/credential/__init__.py:244 +#: awx/main/models/ad_hoc_commands.py:97 +msgid "No valid inventory." +msgstr "" + +#: awx/main/models/ad_hoc_commands.py:104 +msgid "You must provide a machine / SSH credential." +msgstr "" + +#: awx/main/models/ad_hoc_commands.py:115 +#: awx/main/models/ad_hoc_commands.py:123 +msgid "Invalid type for ad hoc command" +msgstr "" + +#: awx/main/models/ad_hoc_commands.py:118 +msgid "Unsupported module for ad hoc commands." +msgstr "" + +#: awx/main/models/ad_hoc_commands.py:126 +#, python-format +msgid "No argument passed to %s module." +msgstr "" + +#: awx/main/models/base.py:34 awx/main/models/base.py:40 +#: awx/main/models/base.py:45 awx/main/models/base.py:50 +msgid "Run" +msgstr "" + +#: awx/main/models/base.py:35 awx/main/models/base.py:41 +#: awx/main/models/base.py:46 awx/main/models/base.py:51 +msgid "Check" +msgstr "" + +#: awx/main/models/base.py:36 +msgid "Scan" +msgstr "" + +#: awx/main/models/credential/__init__.py:96 msgid "" "Specify the type of credential you want to create. Refer to the Ansible " "Tower documentation for details on each type." msgstr "" -#: awx/main/models/credential/__init__.py:258 -#: awx/main/models/credential/__init__.py:467 +#: awx/main/models/credential/__init__.py:114 +#: awx/main/models/credential/__init__.py:358 msgid "" -"Enter inputs using either JSON or YAML syntax. Use the radio button to " -"toggle between the two. Refer to the Ansible Tower documentation for example " -"syntax." +"Enter inputs using either JSON or YAML syntax. Refer to the Ansible Tower " +"documentation for example syntax." msgstr "" -#: awx/main/models/credential/__init__.py:448 -#: awx/main/models/credential/__init__.py:678 +#: awx/main/models/credential/__init__.py:329 +#: awx/main/models/credential/__init__.py:602 msgid "Machine" msgstr "" -#: awx/main/models/credential/__init__.py:449 -#: awx/main/models/credential/__init__.py:769 +#: awx/main/models/credential/__init__.py:330 +#: awx/main/models/credential/__init__.py:688 msgid "Vault" msgstr "" -#: awx/main/models/credential/__init__.py:450 -#: awx/main/models/credential/__init__.py:798 +#: awx/main/models/credential/__init__.py:331 +#: awx/main/models/credential/__init__.py:715 msgid "Network" msgstr "" -#: awx/main/models/credential/__init__.py:451 -#: awx/main/models/credential/__init__.py:733 +#: awx/main/models/credential/__init__.py:332 +#: awx/main/models/credential/__init__.py:657 msgid "Source Control" msgstr "" -#: awx/main/models/credential/__init__.py:452 +#: awx/main/models/credential/__init__.py:333 msgid "Cloud" msgstr "" -#: awx/main/models/credential/__init__.py:453 -#: awx/main/models/credential/__init__.py:1084 +#: awx/main/models/credential/__init__.py:334 +msgid "Personal Access Token" +msgstr "" + +#: awx/main/models/credential/__init__.py:335 +#: awx/main/models/credential/__init__.py:1027 msgid "Insights" msgstr "" -#: awx/main/models/credential/__init__.py:474 +#: awx/main/models/credential/__init__.py:336 +msgid "External" +msgstr "" + +#: awx/main/models/credential/__init__.py:337 +msgid "Kubernetes" +msgstr "" + +#: awx/main/models/credential/__init__.py:338 +msgid "Galaxy/Automation Hub" +msgstr "" + +#: awx/main/models/credential/__init__.py:364 msgid "" -"Enter injectors using either JSON or YAML syntax. Use the radio button to " -"toggle between the two. Refer to the Ansible Tower documentation for example " -"syntax." +"Enter injectors using either JSON or YAML syntax. Refer to the Ansible Tower " +"documentation for example syntax." msgstr "" -#: awx/main/models/credential/__init__.py:525 +#: awx/main/models/credential/__init__.py:433 #, python-format msgid "adding %s credential type" msgstr "" -#: awx/main/models/credential/__init__.py:693 -#: awx/main/models/credential/__init__.py:812 +#: awx/main/models/credential/__init__.py:610 +#: awx/main/models/credential/__init__.py:666 +#: awx/main/models/credential/__init__.py:724 +#: awx/main/models/credential/__init__.py:850 +#: awx/main/models/credential/__init__.py:876 +#: awx/main/models/credential/__init__.py:903 +#: awx/main/models/credential/__init__.py:963 +#: awx/main/models/credential/__init__.py:1036 +#: awx/main/models/credential/__init__.py:1067 +#: awx/main/models/credential/__init__.py:1119 +msgid "Password" +msgstr "" + +#: awx/main/models/credential/__init__.py:616 +#: awx/main/models/credential/__init__.py:729 msgid "SSH Private Key" msgstr "" -#: awx/main/models/credential/__init__.py:700 -#: awx/main/models/credential/__init__.py:754 -#: awx/main/models/credential/__init__.py:819 +#: awx/main/models/credential/__init__.py:623 +msgid "Signed SSH Certificate" +msgstr "" + +#: awx/main/models/credential/__init__.py:629 +#: awx/main/models/credential/__init__.py:678 +#: awx/main/models/credential/__init__.py:736 msgid "Private Key Passphrase" msgstr "" -#: awx/main/models/credential/__init__.py:706 +#: awx/main/models/credential/__init__.py:635 msgid "Privilege Escalation Method" msgstr "" -#: awx/main/models/credential/__init__.py:708 +#: awx/main/models/credential/__init__.py:637 msgid "" "Specify a method for \"become\" operations. This is equivalent to specifying " "the --become-method Ansible parameter." msgstr "" -#: awx/main/models/credential/__init__.py:713 +#: awx/main/models/credential/__init__.py:642 msgid "Privilege Escalation Username" msgstr "" -#: awx/main/models/credential/__init__.py:717 +#: awx/main/models/credential/__init__.py:646 msgid "Privilege Escalation Password" msgstr "" -#: awx/main/models/credential/__init__.py:747 +#: awx/main/models/credential/__init__.py:671 msgid "SCM Private Key" msgstr "" -#: awx/main/models/credential/__init__.py:774 +#: awx/main/models/credential/__init__.py:693 msgid "Vault Password" msgstr "" -#: awx/main/models/credential/__init__.py:780 +#: awx/main/models/credential/__init__.py:699 msgid "Vault Identifier" msgstr "" -#: awx/main/models/credential/__init__.py:783 +#: awx/main/models/credential/__init__.py:702 msgid "" "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+." msgstr "" -#: awx/main/models/credential/__init__.py:824 +#: awx/main/models/credential/__init__.py:741 msgid "Authorize" msgstr "" -#: awx/main/models/credential/__init__.py:828 +#: awx/main/models/credential/__init__.py:745 msgid "Authorize Password" msgstr "" -#: awx/main/models/credential/__init__.py:845 +#: awx/main/models/credential/__init__.py:759 msgid "Amazon Web Services" msgstr "" -#: awx/main/models/credential/__init__.py:850 +#: awx/main/models/credential/__init__.py:764 msgid "Access Key" msgstr "" -#: awx/main/models/credential/__init__.py:854 +#: awx/main/models/credential/__init__.py:768 msgid "Secret Key" msgstr "" -#: awx/main/models/credential/__init__.py:859 +#: awx/main/models/credential/__init__.py:773 msgid "STS Token" msgstr "" -#: awx/main/models/credential/__init__.py:862 +#: awx/main/models/credential/__init__.py:776 msgid "" "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." msgstr "" -#: awx/main/models/credential/__init__.py:876 awx/main/models/inventory.py:1014 +#: awx/main/models/credential/__init__.py:788 awx/main/models/inventory.py:826 msgid "OpenStack" msgstr "" -#: awx/main/models/credential/__init__.py:885 +#: awx/main/models/credential/__init__.py:797 msgid "Password (API Key)" msgstr "" -#: awx/main/models/credential/__init__.py:890 -#: awx/main/models/credential/__init__.py:1117 +#: awx/main/models/credential/__init__.py:802 +#: awx/main/models/credential/__init__.py:1058 msgid "Host (Authentication URL)" msgstr "" -#: awx/main/models/credential/__init__.py:892 +#: awx/main/models/credential/__init__.py:804 msgid "" "The host to authenticate with. For example, https://openstack.business.com/" "v2.0/" msgstr "" -#: awx/main/models/credential/__init__.py:896 +#: awx/main/models/credential/__init__.py:808 msgid "Project (Tenant Name)" msgstr "" -#: awx/main/models/credential/__init__.py:900 +#: awx/main/models/credential/__init__.py:812 +msgid "Project (Domain Name)" +msgstr "" + +#: awx/main/models/credential/__init__.py:816 msgid "Domain Name" msgstr "" -#: awx/main/models/credential/__init__.py:902 +#: awx/main/models/credential/__init__.py:818 msgid "" "OpenStack domains define administrative boundaries. It is only needed for " "Keystone v3 authentication URLs. Refer to Ansible Tower documentation for " "common scenarios." msgstr "" -#: awx/main/models/credential/__init__.py:916 awx/main/models/inventory.py:1011 +#: awx/main/models/credential/__init__.py:824 +msgid "Region Name" +msgstr "" + +#: awx/main/models/credential/__init__.py:826 +msgid "" +"For some cloud providers, like OVH, region must be specified." +msgstr "" + +#: awx/main/models/credential/__init__.py:824 +#: awx/main/models/credential/__init__.py:1131 +#: awx/main/models/credential/__init__.py:1166 +msgid "Verify SSL" +msgstr "" + +#: awx/main/models/credential/__init__.py:835 awx/main/models/inventory.py:824 msgid "VMware vCenter" msgstr "" -#: awx/main/models/credential/__init__.py:921 +#: awx/main/models/credential/__init__.py:840 msgid "VCenter Host" msgstr "" -#: awx/main/models/credential/__init__.py:923 +#: awx/main/models/credential/__init__.py:842 msgid "" "Enter the hostname or IP address that corresponds to your VMware vCenter." msgstr "" -#: awx/main/models/credential/__init__.py:944 awx/main/models/inventory.py:1012 +#: awx/main/models/credential/__init__.py:861 awx/main/models/inventory.py:825 msgid "Red Hat Satellite 6" msgstr "" -#: awx/main/models/credential/__init__.py:949 +#: awx/main/models/credential/__init__.py:866 msgid "Satellite 6 URL" msgstr "" -#: awx/main/models/credential/__init__.py:951 +#: awx/main/models/credential/__init__.py:868 msgid "" "Enter the URL that corresponds to your Red Hat Satellite 6 server. For " "example, https://satellite.example.org" msgstr "" -#: awx/main/models/credential/__init__.py:972 awx/main/models/inventory.py:1013 +#: awx/main/models/credential/__init__.py:887 msgid "Red Hat CloudForms" msgstr "" -#: awx/main/models/credential/__init__.py:977 +#: awx/main/models/credential/__init__.py:892 msgid "CloudForms URL" msgstr "" -#: awx/main/models/credential/__init__.py:979 +#: awx/main/models/credential/__init__.py:894 msgid "" "Enter the URL for the virtual machine that corresponds to your CloudForms " "instance. For example, https://cloudforms.example.org" msgstr "" -#: awx/main/models/credential/__init__.py:1001 -#: awx/main/models/inventory.py:1009 +#: awx/main/models/credential/__init__.py:914 awx/main/models/inventory.py:822 msgid "Google Compute Engine" msgstr "" -#: awx/main/models/credential/__init__.py:1006 +#: awx/main/models/credential/__init__.py:919 msgid "Service Account Email Address" msgstr "" -#: awx/main/models/credential/__init__.py:1008 +#: awx/main/models/credential/__init__.py:921 msgid "" "The email address assigned to the Google Compute Engine service account." msgstr "" -#: awx/main/models/credential/__init__.py:1014 +#: awx/main/models/credential/__init__.py:927 msgid "" "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" msgstr "" -#: awx/main/models/credential/__init__.py:1020 +#: awx/main/models/credential/__init__.py:933 msgid "RSA Private Key" msgstr "" -#: awx/main/models/credential/__init__.py:1025 +#: awx/main/models/credential/__init__.py:938 msgid "" "Paste the contents of the PEM file associated with the service account email." msgstr "" -#: awx/main/models/credential/__init__.py:1037 -#: awx/main/models/inventory.py:1010 +#: awx/main/models/credential/__init__.py:948 awx/main/models/inventory.py:823 msgid "Microsoft Azure Resource Manager" msgstr "" -#: awx/main/models/credential/__init__.py:1042 +#: awx/main/models/credential/__init__.py:953 msgid "Subscription ID" msgstr "" -#: awx/main/models/credential/__init__.py:1044 +#: awx/main/models/credential/__init__.py:955 msgid "Subscription ID is an Azure construct, which is mapped to a username." msgstr "" -#: awx/main/models/credential/__init__.py:1057 -msgid "Client ID" +#: awx/main/models/credential/__init__.py:981 +msgid "Azure Cloud Environment" msgstr "" -#: awx/main/models/credential/__init__.py:1066 -msgid "Tenant ID" +#: awx/main/models/credential/__init__.py:983 +msgid "" +"Environment variable AZURE_CLOUD_ENVIRONMENT when using Azure GovCloud or " +"Azure stack." msgstr "" -#: awx/main/models/credential/__init__.py:1070 -msgid "Azure Cloud Environment" +#: awx/main/models/credential/__init__.py:993 +msgid "GitHub Personal Access Token" +msgstr "" + +#: awx/main/models/credential/__init__.py:1001 +msgid "This token needs to come from your profile settings in GitHub" +msgstr "" + +#: awx/main/models/credential/__init__.py:1010 +msgid "GitLab Personal Access Token" +msgstr "" + +#: awx/main/models/credential/__init__.py:1018 +msgid "This token needs to come from your profile settings in GitLab" +msgstr "" + +#: awx/main/models/credential/__init__.py:1053 awx/main/models/inventory.py:827 +msgid "Red Hat Virtualization" +msgstr "" + +#: awx/main/models/credential/__init__.py:1060 +msgid "The host to authenticate with." msgstr "" #: awx/main/models/credential/__init__.py:1072 +msgid "CA File" +msgstr "" + +#: awx/main/models/credential/__init__.py:1074 +msgid "Absolute file path to the CA file to use (optional)" +msgstr "" + +#: awx/main/models/credential/__init__.py:1103 awx/main/models/inventory.py:828 +msgid "Ansible Tower" +msgstr "" + +#: awx/main/models/credential/__init__.py:1108 +msgid "Ansible Tower Hostname" +msgstr "" + +#: awx/main/models/credential/__init__.py:1110 +msgid "The Ansible Tower base URL to authenticate with." +msgstr "" + +#: awx/main/models/credential/__init__.py:1115 msgid "" -"Environment variable AZURE_CLOUD_ENVIRONMENT when using Azure GovCloud or " -"Azure stack." +"The Ansible Tower user to authenticate as.This should not be set if an OAuth " +"token is being used." +msgstr "" + +#: awx/main/models/credential/__init__.py:1124 +msgid "OAuth Token" +msgstr "" + +#: awx/main/models/credential/__init__.py:1127 +msgid "" +"An OAuth token to use to authenticate to Tower with.This should not be set " +"if username/password are being used." +msgstr "" + +#: awx/main/models/credential/__init__.py:1152 +msgid "OpenShift or Kubernetes API Bearer Token" +msgstr "" + +#: awx/main/models/credential/__init__.py:1156 +msgid "OpenShift or Kubernetes API Endpoint" +msgstr "" + +#: awx/main/models/credential/__init__.py:1158 +msgid "The OpenShift or Kubernetes API Endpoint to authenticate with." +msgstr "" + +#: awx/main/models/credential/__init__.py:1161 +msgid "API authentication bearer token" +msgstr "" + +#: awx/main/models/credential/__init__.py:1171 +msgid "Certificate Authority data" +msgstr "" + +#: awx/main/models/credential/__init__.py:1184 +msgid "Ansible Galaxy/Automation Hub API Token" +msgstr "" + +#: awx/main/models/credential/__init__.py:1188 +msgid "Galaxy Server URL" msgstr "" -#: awx/main/models/credential/__init__.py:1112 -#: awx/main/models/inventory.py:1015 -msgid "Red Hat Virtualization" +#: awx/main/models/credential/__init__.py:1190 +msgid "The URL of the Galaxy instance to connect to." msgstr "" -#: awx/main/models/credential/__init__.py:1119 -msgid "The host to authenticate with." +#: awx/main/models/credential/__init__.py:1193 +msgid "Auth Server URL" msgstr "" -#: awx/main/models/credential/__init__.py:1131 -msgid "CA File" +#: awx/main/models/credential/__init__.py:1196 +msgid "The URL of a Keycloak server token_endpoint, if using SSO auth." msgstr "" -#: awx/main/models/credential/__init__.py:1133 -msgid "Absolute file path to the CA file to use (optional)" +#: awx/main/models/credential/__init__.py:1201 +msgid "API Token" msgstr "" -#: awx/main/models/credential/__init__.py:1164 -#: awx/main/models/inventory.py:1016 -msgid "Ansible Tower" +#: awx/main/models/credential/__init__.py:1205 +msgid "A token to use for authentication against the Galaxy instance." msgstr "" -#: awx/main/models/credential/__init__.py:1169 -msgid "Ansible Tower Hostname" +#: awx/main/models/credential/__init__.py:1244 +msgid "Target must be a non-external credential" msgstr "" -#: awx/main/models/credential/__init__.py:1171 -msgid "The Ansible Tower base URL to authenticate with." +#: awx/main/models/credential/__init__.py:1249 +msgid "Source must be an external credential" msgstr "" -#: awx/main/models/credential/__init__.py:1183 -msgid "Verify SSL" +#: awx/main/models/credential/__init__.py:1256 +msgid "Input field must be defined on target credential (options are {})." msgstr "" -#: awx/main/models/events.py:105 awx/main/models/events.py:630 +#: awx/main/models/events.py:165 awx/main/models/events.py:707 msgid "Host Failed" msgstr "" -#: awx/main/models/events.py:106 awx/main/models/events.py:631 +#: awx/main/models/events.py:166 +msgid "Host Started" +msgstr "" + +#: awx/main/models/events.py:167 awx/main/models/events.py:708 msgid "Host OK" msgstr "" -#: awx/main/models/events.py:107 +#: awx/main/models/events.py:168 msgid "Host Failure" msgstr "" -#: awx/main/models/events.py:108 awx/main/models/events.py:637 +#: awx/main/models/events.py:169 awx/main/models/events.py:714 msgid "Host Skipped" msgstr "" -#: awx/main/models/events.py:109 awx/main/models/events.py:632 +#: awx/main/models/events.py:170 awx/main/models/events.py:709 msgid "Host Unreachable" msgstr "" -#: awx/main/models/events.py:110 awx/main/models/events.py:124 +#: awx/main/models/events.py:171 awx/main/models/events.py:185 msgid "No Hosts Remaining" msgstr "" -#: awx/main/models/events.py:111 +#: awx/main/models/events.py:172 msgid "Host Polling" msgstr "" -#: awx/main/models/events.py:112 +#: awx/main/models/events.py:173 msgid "Host Async OK" msgstr "" -#: awx/main/models/events.py:113 +#: awx/main/models/events.py:174 msgid "Host Async Failure" msgstr "" -#: awx/main/models/events.py:114 +#: awx/main/models/events.py:175 msgid "Item OK" msgstr "" -#: awx/main/models/events.py:115 +#: awx/main/models/events.py:176 msgid "Item Failed" msgstr "" -#: awx/main/models/events.py:116 +#: awx/main/models/events.py:177 msgid "Item Skipped" msgstr "" -#: awx/main/models/events.py:117 +#: awx/main/models/events.py:178 msgid "Host Retry" msgstr "" -#: awx/main/models/events.py:119 +#: awx/main/models/events.py:180 msgid "File Difference" msgstr "" -#: awx/main/models/events.py:120 +#: awx/main/models/events.py:181 msgid "Playbook Started" msgstr "" -#: awx/main/models/events.py:121 +#: awx/main/models/events.py:182 msgid "Running Handlers" msgstr "" -#: awx/main/models/events.py:122 +#: awx/main/models/events.py:183 msgid "Including File" msgstr "" -#: awx/main/models/events.py:123 +#: awx/main/models/events.py:184 msgid "No Hosts Matched" msgstr "" -#: awx/main/models/events.py:125 +#: awx/main/models/events.py:186 msgid "Task Started" msgstr "" -#: awx/main/models/events.py:127 +#: awx/main/models/events.py:188 msgid "Variables Prompted" msgstr "" -#: awx/main/models/events.py:128 +#: awx/main/models/events.py:189 msgid "Gathering Facts" msgstr "" -#: awx/main/models/events.py:129 +#: awx/main/models/events.py:190 msgid "internal: on Import for Host" msgstr "" -#: awx/main/models/events.py:130 +#: awx/main/models/events.py:191 msgid "internal: on Not Import for Host" msgstr "" -#: awx/main/models/events.py:131 +#: awx/main/models/events.py:192 msgid "Play Started" msgstr "" -#: awx/main/models/events.py:132 +#: awx/main/models/events.py:193 msgid "Playbook Complete" msgstr "" -#: awx/main/models/events.py:136 awx/main/models/events.py:647 +#: awx/main/models/events.py:197 awx/main/models/events.py:724 msgid "Debug" msgstr "" -#: awx/main/models/events.py:137 awx/main/models/events.py:648 +#: awx/main/models/events.py:198 awx/main/models/events.py:725 msgid "Verbose" msgstr "" -#: awx/main/models/events.py:138 awx/main/models/events.py:649 +#: awx/main/models/events.py:199 awx/main/models/events.py:726 msgid "Deprecated" msgstr "" -#: awx/main/models/events.py:139 awx/main/models/events.py:650 +#: awx/main/models/events.py:200 awx/main/models/events.py:727 msgid "Warning" msgstr "" -#: awx/main/models/events.py:140 awx/main/models/events.py:651 +#: awx/main/models/events.py:201 awx/main/models/events.py:728 msgid "System Warning" msgstr "" -#: awx/main/models/events.py:141 awx/main/models/events.py:652 -#: awx/main/models/unified_jobs.py:73 +#: awx/main/models/events.py:202 awx/main/models/events.py:729 +#: awx/main/models/unified_jobs.py:75 msgid "Error" msgstr "" -#: awx/main/models/fact.py:25 -msgid "Host for the facts that the fact scan captured." -msgstr "" - -#: awx/main/models/fact.py:30 -msgid "Date and time of the corresponding fact scan gathering time." -msgstr "" - -#: awx/main/models/fact.py:33 -msgid "" -"Arbitrary JSON structure of module facts captured at timestamp for a single " -"host." -msgstr "" - -#: awx/main/models/ha.py:183 +#: awx/main/models/ha.py:184 msgid "Instances that are members of this InstanceGroup" msgstr "" -#: awx/main/models/ha.py:188 +#: awx/main/models/ha.py:189 msgid "Instance Group to remotely control this group." msgstr "" -#: awx/main/models/ha.py:195 +#: awx/main/models/ha.py:209 msgid "Percentage of Instances to automatically assign to this group" msgstr "" -#: awx/main/models/ha.py:199 +#: awx/main/models/ha.py:213 msgid "" "Static minimum number of Instances to automatically assign to this group" msgstr "" -#: awx/main/models/ha.py:204 +#: awx/main/models/ha.py:218 msgid "" "List of exact-match Instances that will always be automatically assigned to " "this group" msgstr "" -#: awx/main/models/inventory.py:63 +#: awx/main/models/inventory.py:74 msgid "Hosts have a direct link to this inventory." msgstr "" -#: awx/main/models/inventory.py:64 +#: awx/main/models/inventory.py:75 msgid "Hosts for inventory generated using the host_filter property." msgstr "" -#: awx/main/models/inventory.py:69 +#: awx/main/models/inventory.py:80 msgid "inventories" msgstr "" -#: awx/main/models/inventory.py:76 +#: awx/main/models/inventory.py:87 msgid "Organization containing this inventory." msgstr "" -#: awx/main/models/inventory.py:83 +#: awx/main/models/inventory.py:94 msgid "Inventory variables in JSON or YAML format." msgstr "" -#: awx/main/models/inventory.py:88 -msgid "Flag indicating whether any hosts in this inventory have failed." -msgstr "" - -#: awx/main/models/inventory.py:93 -msgid "Total number of hosts in this inventory." +#: awx/main/models/inventory.py:99 +msgid "" +"This field is deprecated and will be removed in a future release. Flag " +"indicating whether any hosts in this inventory have failed." msgstr "" -#: awx/main/models/inventory.py:98 -msgid "Number of hosts in this inventory with active failures." +#: awx/main/models/inventory.py:105 +msgid "" +"This field is deprecated and will be removed in a future release. Total " +"number of hosts in this inventory." msgstr "" -#: awx/main/models/inventory.py:103 -msgid "Total number of groups in this inventory." +#: awx/main/models/inventory.py:111 +msgid "" +"This field is deprecated and will be removed in a future release. Number of " +"hosts in this inventory with active failures." msgstr "" -#: awx/main/models/inventory.py:108 -msgid "Number of groups in this inventory with active failures." +#: awx/main/models/inventory.py:117 +msgid "" +"This field is deprecated and will be removed in a future release. Total " +"number of groups in this inventory." msgstr "" -#: awx/main/models/inventory.py:113 +#: awx/main/models/inventory.py:123 msgid "" -"Flag indicating whether this inventory has any external inventory sources." +"This field is deprecated and will be removed in a future release. Flag " +"indicating whether this inventory has any external inventory sources." msgstr "" -#: awx/main/models/inventory.py:118 +#: awx/main/models/inventory.py:129 msgid "" "Total number of external inventory sources configured within this inventory." msgstr "" -#: awx/main/models/inventory.py:123 +#: awx/main/models/inventory.py:134 msgid "Number of external inventory sources in this inventory with failures." msgstr "" -#: awx/main/models/inventory.py:130 +#: awx/main/models/inventory.py:141 msgid "Kind of inventory being represented." msgstr "" -#: awx/main/models/inventory.py:136 +#: awx/main/models/inventory.py:147 msgid "Filter that will be applied to the hosts of this inventory." msgstr "" -#: awx/main/models/inventory.py:163 +#: awx/main/models/inventory.py:175 msgid "" "Credentials to be used by hosts belonging to this inventory when accessing " "Red Hat Insights API." msgstr "" -#: awx/main/models/inventory.py:172 +#: awx/main/models/inventory.py:184 msgid "Flag indicating the inventory is being deleted." msgstr "" -#: awx/main/models/inventory.py:227 +#: awx/main/models/inventory.py:239 msgid "Could not parse subset as slice specification." msgstr "" -#: awx/main/models/inventory.py:231 +#: awx/main/models/inventory.py:243 msgid "Slice number must be less than total number of slices." msgstr "" -#: awx/main/models/inventory.py:233 +#: awx/main/models/inventory.py:245 msgid "Slice number must be 1 or higher." msgstr "" -#: awx/main/models/inventory.py:483 +#: awx/main/models/inventory.py:382 msgid "Assignment not allowed for Smart Inventory" msgstr "" -#: awx/main/models/inventory.py:485 awx/main/models/projects.py:159 +#: awx/main/models/inventory.py:384 awx/main/models/projects.py:167 msgid "Credential kind must be 'insights'." msgstr "" -#: awx/main/models/inventory.py:570 +#: awx/main/models/inventory.py:469 msgid "Is this host online and available for running jobs?" msgstr "" -#: awx/main/models/inventory.py:576 +#: awx/main/models/inventory.py:475 msgid "" "The value used by the remote inventory source to uniquely identify the host" msgstr "" -#: awx/main/models/inventory.py:581 +#: awx/main/models/inventory.py:480 msgid "Host variables in JSON or YAML format." msgstr "" -#: awx/main/models/inventory.py:603 -msgid "Flag indicating whether the last job failed for this host." -msgstr "" - -#: awx/main/models/inventory.py:608 -msgid "" -"Flag indicating whether this host was created/updated from any external " -"inventory sources." -msgstr "" - -#: awx/main/models/inventory.py:614 +#: awx/main/models/inventory.py:503 msgid "Inventory source(s) that created or modified this host." msgstr "" -#: awx/main/models/inventory.py:619 +#: awx/main/models/inventory.py:508 msgid "Arbitrary JSON structure of most recent ansible_facts, per-host." msgstr "" -#: awx/main/models/inventory.py:625 +#: awx/main/models/inventory.py:514 msgid "The date and time ansible_facts was last modified." msgstr "" -#: awx/main/models/inventory.py:632 +#: awx/main/models/inventory.py:521 msgid "Red Hat Insights host unique identifier." msgstr "" -#: awx/main/models/inventory.py:767 +#: awx/main/models/inventory.py:635 msgid "Group variables in JSON or YAML format." msgstr "" -#: awx/main/models/inventory.py:773 +#: awx/main/models/inventory.py:641 msgid "Hosts associated directly with this group." msgstr "" -#: awx/main/models/inventory.py:778 -msgid "Total number of hosts directly or indirectly in this group." -msgstr "" - -#: awx/main/models/inventory.py:783 -msgid "Flag indicating whether this group has any hosts with active failures." -msgstr "" - -#: awx/main/models/inventory.py:788 -msgid "Number of hosts in this group with active failures." -msgstr "" - -#: awx/main/models/inventory.py:793 -msgid "Total number of child groups contained within this group." -msgstr "" - -#: awx/main/models/inventory.py:798 -msgid "Number of child groups within this group that have active failures." -msgstr "" - -#: awx/main/models/inventory.py:803 -msgid "" -"Flag indicating whether this group was created/updated from any external " -"inventory sources." -msgstr "" - -#: awx/main/models/inventory.py:809 +#: awx/main/models/inventory.py:647 msgid "Inventory source(s) that created or modified this group." msgstr "" -#: awx/main/models/inventory.py:1005 awx/main/models/projects.py:53 -#: awx/main/models/unified_jobs.py:543 -msgid "Manual" -msgstr "" - -#: awx/main/models/inventory.py:1006 +#: awx/main/models/inventory.py:819 msgid "File, Directory or Script" msgstr "" -#: awx/main/models/inventory.py:1007 +#: awx/main/models/inventory.py:820 msgid "Sourced from a Project" msgstr "" -#: awx/main/models/inventory.py:1008 +#: awx/main/models/inventory.py:821 msgid "Amazon EC2" msgstr "" -#: awx/main/models/inventory.py:1017 +#: awx/main/models/inventory.py:829 msgid "Custom Script" msgstr "" -#: awx/main/models/inventory.py:1134 +#: awx/main/models/inventory.py:863 msgid "Inventory source variables in YAML or JSON format." msgstr "" -#: awx/main/models/inventory.py:1145 +#: awx/main/models/inventory.py:868 +msgid "" +"Retrieve the enabled state from the given dict of host variables. The " +"enabled variable may be specified as \"foo.bar\", in which case the lookup " +"will traverse into nested dicts, equivalent to: from_dict.get(\"foo\", {})." +"get(\"bar\", default)" +msgstr "" + +#: awx/main/models/inventory.py:876 msgid "" -"Comma-separated list of filter expressions (EC2 only). Hosts are imported " -"when ANY of the filters match." +"Only used when enabled_var is set. Value when the host is considered " +"enabled. For example if enabled_var=\"status.power_state\"and enabled_value=" +"\"powered_on\" with host variables:{ \"status\": { \"power_state\": " +"\"powered_on\", \"created\": \"2020-08-04T18:13:04+00:00\", \"healthy" +"\": true }, \"name\": \"foobar\", \"ip_address\": \"192.168.2.1\"}" +"The host would be marked enabled. If power_state where any value other than " +"powered_on then the host would be disabled when imported into Tower. If the " +"key is not found then the host will be enabled" msgstr "" -#: awx/main/models/inventory.py:1151 -msgid "Limit groups automatically created from inventory source (EC2 only)." +#: awx/main/models/inventory.py:896 +msgid "Regex where only matching hosts will be imported into Tower." msgstr "" -#: awx/main/models/inventory.py:1155 +#: awx/main/models/inventory.py:900 msgid "Overwrite local groups and hosts from remote inventory source." msgstr "" -#: awx/main/models/inventory.py:1159 +#: awx/main/models/inventory.py:904 msgid "Overwrite local variables from remote inventory source." msgstr "" -#: awx/main/models/inventory.py:1164 awx/main/models/jobs.py:139 -#: awx/main/models/projects.py:128 +#: awx/main/models/inventory.py:909 awx/main/models/jobs.py:154 +#: awx/main/models/projects.py:136 msgid "The amount of time (in seconds) to run before the task is canceled." msgstr "" -#: awx/main/models/inventory.py:1197 -msgid "Image ID" -msgstr "" - -#: awx/main/models/inventory.py:1198 -msgid "Availability Zone" -msgstr "" - -#: awx/main/models/inventory.py:1199 -msgid "Account" -msgstr "" - -#: awx/main/models/inventory.py:1200 -msgid "Instance ID" -msgstr "" - -#: awx/main/models/inventory.py:1201 -msgid "Instance State" -msgstr "" - -#: awx/main/models/inventory.py:1202 -msgid "Platform" -msgstr "" - -#: awx/main/models/inventory.py:1203 -msgid "Instance Type" -msgstr "" - -#: awx/main/models/inventory.py:1204 -msgid "Key Name" -msgstr "" - -#: awx/main/models/inventory.py:1205 -msgid "Region" -msgstr "" - -#: awx/main/models/inventory.py:1206 -msgid "Security Group" -msgstr "" - -#: awx/main/models/inventory.py:1207 -msgid "Tags" -msgstr "" - -#: awx/main/models/inventory.py:1208 -msgid "Tag None" -msgstr "" - -#: awx/main/models/inventory.py:1209 -msgid "VPC ID" -msgstr "" - -#: awx/main/models/inventory.py:1277 +#: awx/main/models/inventory.py:926 #, python-format msgid "" "Cloud-based inventory sources (such as %s) require credentials for the " "matching cloud service." msgstr "" -#: awx/main/models/inventory.py:1283 +#: awx/main/models/inventory.py:932 msgid "Credential is required for a cloud source." msgstr "" -#: awx/main/models/inventory.py:1286 +#: awx/main/models/inventory.py:935 msgid "" "Credentials of type machine, source control, insights and vault are " "disallowed for custom inventory sources." msgstr "" -#: awx/main/models/inventory.py:1291 +#: awx/main/models/inventory.py:940 msgid "" "Credentials of type insights and vault are disallowed for scm inventory " "sources." msgstr "" -#: awx/main/models/inventory.py:1343 -#, python-format -msgid "Invalid %(source)s region: %(region)s" -msgstr "" - -#: awx/main/models/inventory.py:1367 -#, python-format -msgid "Invalid filter expression: %(filter)s" -msgstr "" - -#: awx/main/models/inventory.py:1388 -#, python-format -msgid "Invalid group by choice: %(choice)s" -msgstr "" - -#: awx/main/models/inventory.py:1423 +#: awx/main/models/inventory.py:1004 msgid "Project containing inventory file used as source." msgstr "" -#: awx/main/models/inventory.py:1584 -#, python-format -msgid "" -"Unable to configure this item for cloud sync. It is already managed by %s." -msgstr "" - -#: awx/main/models/inventory.py:1594 +#: awx/main/models/inventory.py:1177 msgid "" "More than one SCM-based inventory source with update on project update per-" "inventory not allowed." msgstr "" -#: awx/main/models/inventory.py:1601 +#: awx/main/models/inventory.py:1184 msgid "" "Cannot update SCM-based inventory source on launch if set to update on " "project update. Instead, configure the corresponding source project to " "update on launch." msgstr "" -#: awx/main/models/inventory.py:1607 +#: awx/main/models/inventory.py:1190 msgid "Cannot set source_path if not SCM type." msgstr "" -#: awx/main/models/inventory.py:1645 +#: awx/main/models/inventory.py:1233 msgid "" "Inventory files from this Project Update were used for the inventory update." msgstr "" -#: awx/main/models/inventory.py:1754 +#: awx/main/models/inventory.py:1344 msgid "Inventory script contents" msgstr "" -#: awx/main/models/inventory.py:1759 +#: awx/main/models/inventory.py:1349 msgid "Organization owning this inventory script" msgstr "" -#: awx/main/models/jobs.py:65 +#: awx/main/models/jobs.py:74 msgid "" "If enabled, textual changes made to any templated files on the host are " "shown in the standard output" msgstr "" -#: awx/main/models/jobs.py:144 +#: awx/main/models/jobs.py:106 +msgid "" +"Branch to use in job run. Project default used if blank. Only allowed if " +"project allow_override field is set to true." +msgstr "" + +#: awx/main/models/jobs.py:159 msgid "" "If enabled, Tower will act as an Ansible Fact Cache Plugin; persisting facts " "at the end of a playbook run to the database and caching facts for use by " "Ansible." msgstr "" -#: awx/main/models/jobs.py:162 -msgid "You must provide a Vault credential." -msgstr "" - -#: awx/main/models/jobs.py:282 +#: awx/main/models/jobs.py:260 msgid "" "The number of jobs to slice into at runtime. Will cause the Job Template to " "launch a workflow if value is greater than 1." msgstr "" -#: awx/main/models/jobs.py:315 +#: awx/main/models/jobs.py:297 msgid "Job Template must provide 'inventory' or allow prompting for it." msgstr "" -#: awx/main/models/jobs.py:433 awx/main/models/workflow.py:461 +#: awx/main/models/jobs.py:308 +#, python-brace-format +msgid "Maximum number of forks ({settings.MAX_FORKS}) exceeded." +msgstr "" + +#: awx/main/models/jobs.py:459 +msgid "Project is missing." +msgstr "" + +#: awx/main/models/jobs.py:463 +msgid "Project does not allow override of branch." +msgstr "" + +#: awx/main/models/jobs.py:473 awx/main/models/workflow.py:545 msgid "Field is not configured to prompt on launch." msgstr "" -#: awx/main/models/jobs.py:439 +#: awx/main/models/jobs.py:479 msgid "Saved launch configurations cannot provide passwords needed to start." msgstr "" -#: awx/main/models/jobs.py:447 +#: awx/main/models/jobs.py:487 msgid "Job Template {} is missing or undefined." msgstr "" -#: awx/main/models/jobs.py:528 awx/main/models/projects.py:273 +#: awx/main/models/jobs.py:570 awx/main/models/projects.py:284 +#: awx/main/models/projects.py:508 msgid "SCM Revision" msgstr "" -#: awx/main/models/jobs.py:529 +#: awx/main/models/jobs.py:571 msgid "The SCM Revision from the Project used for this job, if available" msgstr "" -#: awx/main/models/jobs.py:537 +#: awx/main/models/jobs.py:579 msgid "" "The SCM Refresh task used to make sure the playbooks were available for the " "job run" msgstr "" -#: awx/main/models/jobs.py:542 +#: awx/main/models/jobs.py:584 msgid "" "If part of a sliced job, the ID of the inventory slice operated on. If not " "part of sliced job, parameter is not used." msgstr "" -#: awx/main/models/jobs.py:548 +#: awx/main/models/jobs.py:590 msgid "" "If ran as part of sliced jobs, the total number of slices. If 1, job is not " "part of a sliced job." msgstr "" -#: awx/main/models/jobs.py:684 +#: awx/main/models/jobs.py:672 #, python-brace-format msgid "{status_value} is not a valid status option." msgstr "" -#: awx/main/models/jobs.py:1090 +#: awx/main/models/jobs.py:922 +msgid "" +"Inventory applied as a prompt, assuming job template prompts for inventory" +msgstr "" + +#: awx/main/models/jobs.py:1081 msgid "job host summaries" msgstr "" -#: awx/main/models/jobs.py:1162 +#: awx/main/models/jobs.py:1140 msgid "Remove jobs older than a certain number of days" msgstr "" -#: awx/main/models/jobs.py:1163 +#: awx/main/models/jobs.py:1141 msgid "Remove activity stream entries older than a certain number of days" msgstr "" -#: awx/main/models/jobs.py:1164 -msgid "Purge and/or reduce the granularity of system tracking data" +#: awx/main/models/jobs.py:1142 +msgid "Removes expired browser sessions from the database" msgstr "" -#: awx/main/models/jobs.py:1234 +#: awx/main/models/jobs.py:1143 +msgid "Removes expired OAuth 2 access tokens and refresh tokens" +msgstr "" + +#: awx/main/models/jobs.py:1213 #, python-brace-format msgid "Variables {list_of_keys} are not allowed for system jobs." msgstr "" -#: awx/main/models/jobs.py:1249 +#: awx/main/models/jobs.py:1229 msgid "days must be a positive integer." msgstr "" @@ -3542,110 +4109,166 @@ msgstr "" msgid "Organization this label belongs to." msgstr "" -#: awx/main/models/mixins.py:316 +#: awx/main/models/mixins.py:321 #, python-brace-format msgid "" "Variables {list_of_keys} are not allowed on launch. Check the Prompt on " "Launch setting on the {model_name} to include Extra Variables." msgstr "" -#: awx/main/models/mixins.py:448 +#: awx/main/models/mixins.py:453 msgid "Local absolute file path containing a custom Python virtualenv to use" msgstr "" -#: awx/main/models/mixins.py:455 +#: awx/main/models/mixins.py:460 msgid "{} is not a valid virtualenv in {}" msgstr "" +#: awx/main/models/mixins.py:507 awx/main/models/mixins.py:551 +msgid "Service that webhook requests will be accepted from" +msgstr "" + +#: awx/main/models/mixins.py:512 +msgid "Shared secret that the webhook service will use to sign requests" +msgstr "" + +#: awx/main/models/mixins.py:520 awx/main/models/mixins.py:559 +msgid "Personal Access Token for posting back the status to the service API" +msgstr "" + +#: awx/main/models/mixins.py:564 +msgid "Unique identifier of the event that triggered this webhook" +msgstr "" + +#: awx/main/models/notifications.py:41 +msgid "Email" +msgstr "" + #: awx/main/models/notifications.py:42 +msgid "Slack" +msgstr "" + +#: awx/main/models/notifications.py:43 +msgid "Twilio" +msgstr "" + +#: awx/main/models/notifications.py:44 +msgid "Pagerduty" +msgstr "" + +#: awx/main/models/notifications.py:45 +msgid "Grafana" +msgstr "" + +#: awx/main/models/notifications.py:46 awx/main/models/unified_jobs.py:544 +msgid "Webhook" +msgstr "" + +#: awx/main/models/notifications.py:47 +msgid "Mattermost" +msgstr "" + +#: awx/main/models/notifications.py:48 msgid "Rocket.Chat" msgstr "" -#: awx/main/models/notifications.py:142 awx/main/models/unified_jobs.py:68 +#: awx/main/models/notifications.py:49 +msgid "IRC" +msgstr "" + +#: awx/main/models/notifications.py:80 +msgid "Optional custom messages for notification template." +msgstr "" + +#: awx/main/models/notifications.py:210 awx/main/models/unified_jobs.py:70 msgid "Pending" msgstr "" -#: awx/main/models/notifications.py:143 awx/main/models/unified_jobs.py:71 +#: awx/main/models/notifications.py:211 awx/main/models/unified_jobs.py:73 msgid "Successful" msgstr "" -#: awx/main/models/notifications.py:144 awx/main/models/unified_jobs.py:72 +#: awx/main/models/notifications.py:212 awx/main/models/unified_jobs.py:74 msgid "Failed" msgstr "" -#: awx/main/models/notifications.py:218 -msgid "status_str must be either succeeded or failed" +#: awx/main/models/notifications.py:470 +msgid "status must be either running, succeeded or failed" msgstr "" -#: awx/main/models/oauth.py:29 +#: awx/main/models/oauth.py:33 msgid "application" msgstr "" -#: awx/main/models/oauth.py:35 +#: awx/main/models/oauth.py:40 msgid "Confidential" msgstr "" -#: awx/main/models/oauth.py:36 +#: awx/main/models/oauth.py:41 msgid "Public" msgstr "" -#: awx/main/models/oauth.py:43 +#: awx/main/models/oauth.py:47 msgid "Authorization code" msgstr "" -#: awx/main/models/oauth.py:44 -msgid "Implicit" -msgstr "" - -#: awx/main/models/oauth.py:45 +#: awx/main/models/oauth.py:48 msgid "Resource owner password-based" msgstr "" -#: awx/main/models/oauth.py:60 +#: awx/main/models/oauth.py:63 msgid "Organization containing this application." msgstr "" -#: awx/main/models/oauth.py:69 +#: awx/main/models/oauth.py:72 msgid "" "Used for more stringent verification of access to an application when " "creating a token." msgstr "" -#: awx/main/models/oauth.py:74 +#: awx/main/models/oauth.py:77 msgid "" "Set to Public or Confidential depending on how secure the client device is." msgstr "" -#: awx/main/models/oauth.py:78 +#: awx/main/models/oauth.py:81 msgid "" "Set True to skip authorization step for completely trusted applications." msgstr "" -#: awx/main/models/oauth.py:83 +#: awx/main/models/oauth.py:86 msgid "" "The Grant type the user must use for acquire tokens for this application." msgstr "" -#: awx/main/models/oauth.py:91 +#: awx/main/models/oauth.py:94 msgid "access token" msgstr "" -#: awx/main/models/oauth.py:99 +#: awx/main/models/oauth.py:103 msgid "The user representing the token owner" msgstr "" -#: awx/main/models/oauth.py:114 +#: awx/main/models/oauth.py:117 msgid "" "Allowed scopes, further restricts user's permissions. Must be a simple space-" "separated string with allowed scopes ['read', 'write']." msgstr "" -#: awx/main/models/oauth.py:133 +#: awx/main/models/oauth.py:140 msgid "" "OAuth2 Tokens cannot be created by users associated with an external " "authentication provider ({})" msgstr "" +#: awx/main/models/organization.py:57 +msgid "Maximum number of hosts allowed to be managed by this organization." +msgstr "" + +#: awx/main/models/projects.py:53 awx/main/models/unified_jobs.py:538 +msgid "Manual" +msgstr "" + #: awx/main/models/projects.py:54 msgid "Git" msgstr "" @@ -3662,169 +4285,205 @@ msgstr "" msgid "Red Hat Insights" msgstr "" -#: awx/main/models/projects.py:83 +#: awx/main/models/projects.py:58 +msgid "Remote Archive" +msgstr "" + +#: awx/main/models/projects.py:84 msgid "" "Local path (relative to PROJECTS_ROOT) containing playbooks and related " "files for this project." msgstr "" -#: awx/main/models/projects.py:92 +#: awx/main/models/projects.py:93 msgid "SCM Type" msgstr "" -#: awx/main/models/projects.py:93 +#: awx/main/models/projects.py:94 msgid "Specifies the source control system used to store the project." msgstr "" -#: awx/main/models/projects.py:99 +#: awx/main/models/projects.py:100 msgid "SCM URL" msgstr "" -#: awx/main/models/projects.py:100 +#: awx/main/models/projects.py:101 msgid "The location where the project is stored." msgstr "" -#: awx/main/models/projects.py:106 +#: awx/main/models/projects.py:107 msgid "SCM Branch" msgstr "" -#: awx/main/models/projects.py:107 +#: awx/main/models/projects.py:108 msgid "Specific branch, tag or commit to checkout." msgstr "" -#: awx/main/models/projects.py:111 -msgid "Discard any local changes before syncing the project." +#: awx/main/models/projects.py:114 +msgid "SCM refspec" msgstr "" #: awx/main/models/projects.py:115 +msgid "For git projects, an additional refspec to fetch." +msgstr "" + +#: awx/main/models/projects.py:119 +msgid "Discard any local changes before syncing the project." +msgstr "" + +#: awx/main/models/projects.py:123 msgid "Delete the project before syncing." msgstr "" -#: awx/main/models/projects.py:144 +#: awx/main/models/projects.py:152 msgid "Invalid SCM URL." msgstr "" -#: awx/main/models/projects.py:147 +#: awx/main/models/projects.py:155 msgid "SCM URL is required." msgstr "" -#: awx/main/models/projects.py:155 +#: awx/main/models/projects.py:163 msgid "Insights Credential is required for an Insights Project." msgstr "" -#: awx/main/models/projects.py:161 +#: awx/main/models/projects.py:169 msgid "Credential kind must be 'scm'." msgstr "" -#: awx/main/models/projects.py:178 +#: awx/main/models/projects.py:186 msgid "Invalid credential." msgstr "" -#: awx/main/models/projects.py:259 +#: awx/main/models/projects.py:265 msgid "Update the project when a job is launched that uses the project." msgstr "" -#: awx/main/models/projects.py:264 +#: awx/main/models/projects.py:270 msgid "" -"The number of seconds after the last project update ran that a newproject " +"The number of seconds after the last project update ran that a new project " "update will be launched as a job dependency." msgstr "" -#: awx/main/models/projects.py:274 +#: awx/main/models/projects.py:275 +msgid "" +"Allow changing the SCM branch or revision in a job template that uses this " +"project." +msgstr "" + +#: awx/main/models/projects.py:285 msgid "The last revision fetched by a project update" msgstr "" -#: awx/main/models/projects.py:281 +#: awx/main/models/projects.py:292 msgid "Playbook Files" msgstr "" -#: awx/main/models/projects.py:282 +#: awx/main/models/projects.py:293 msgid "List of playbooks found in the project" msgstr "" -#: awx/main/models/projects.py:289 +#: awx/main/models/projects.py:300 msgid "Inventory Files" msgstr "" -#: awx/main/models/projects.py:290 +#: awx/main/models/projects.py:301 msgid "" "Suggested list of content that could be Ansible inventory in the project" msgstr "" -#: awx/main/models/rbac.py:36 +#: awx/main/models/projects.py:338 +msgid "Organization cannot be changed when in use by job templates." +msgstr "" + +#: awx/main/models/projects.py:501 +msgid "Parts of the project update playbook that will be run." +msgstr "" + +#: awx/main/models/projects.py:509 +msgid "" +"The SCM Revision discovered by this update for the given project and branch." +msgstr "" + +#: awx/main/models/rbac.py:35 msgid "System Administrator" msgstr "" -#: awx/main/models/rbac.py:37 +#: awx/main/models/rbac.py:36 msgid "System Auditor" msgstr "" -#: awx/main/models/rbac.py:38 +#: awx/main/models/rbac.py:37 msgid "Ad Hoc" msgstr "" -#: awx/main/models/rbac.py:39 +#: awx/main/models/rbac.py:38 msgid "Admin" msgstr "" -#: awx/main/models/rbac.py:40 +#: awx/main/models/rbac.py:39 msgid "Project Admin" msgstr "" -#: awx/main/models/rbac.py:41 +#: awx/main/models/rbac.py:40 msgid "Inventory Admin" msgstr "" -#: awx/main/models/rbac.py:42 +#: awx/main/models/rbac.py:41 msgid "Credential Admin" msgstr "" -#: awx/main/models/rbac.py:43 +#: awx/main/models/rbac.py:42 msgid "Job Template Admin" msgstr "" -#: awx/main/models/rbac.py:44 +#: awx/main/models/rbac.py:43 msgid "Workflow Admin" msgstr "" -#: awx/main/models/rbac.py:45 +#: awx/main/models/rbac.py:44 msgid "Notification Admin" msgstr "" -#: awx/main/models/rbac.py:46 +#: awx/main/models/rbac.py:45 msgid "Auditor" msgstr "" -#: awx/main/models/rbac.py:47 +#: awx/main/models/rbac.py:46 msgid "Execute" msgstr "" -#: awx/main/models/rbac.py:48 +#: awx/main/models/rbac.py:47 msgid "Member" msgstr "" -#: awx/main/models/rbac.py:49 +#: awx/main/models/rbac.py:48 msgid "Read" msgstr "" -#: awx/main/models/rbac.py:50 +#: awx/main/models/rbac.py:49 msgid "Update" msgstr "" -#: awx/main/models/rbac.py:51 +#: awx/main/models/rbac.py:50 msgid "Use" msgstr "" +#: awx/main/models/rbac.py:51 +msgid "Approve" +msgstr "" + #: awx/main/models/rbac.py:55 msgid "Can manage all aspects of the system" msgstr "" #: awx/main/models/rbac.py:56 -msgid "Can view all settings on the system" +msgid "Can view all aspects of the system" msgstr "" #: awx/main/models/rbac.py:57 -msgid "May run ad hoc commands on an inventory" +#, python-format +msgid "May run ad hoc commands on the %s" msgstr "" #: awx/main/models/rbac.py:58 @@ -3864,7 +4523,7 @@ msgstr "" #: awx/main/models/rbac.py:65 #, python-format -msgid "Can view all settings for the %s" +msgid "Can view all aspects of the %s" msgstr "" #: awx/main/models/rbac.py:67 @@ -3887,9 +4546,8 @@ msgid "May view settings for the %s" msgstr "" #: awx/main/models/rbac.py:72 -msgid "" -"May update project or inventory or group using the configured source update " -"system" +#, python-format +msgid "May update the %s" msgstr "" #: awx/main/models/rbac.py:73 @@ -3897,137 +4555,184 @@ msgstr "" msgid "Can use the %s in a job template" msgstr "" -#: awx/main/models/rbac.py:137 +#: awx/main/models/rbac.py:74 +msgid "Can approve or deny a workflow approval node" +msgstr "" + +#: awx/main/models/rbac.py:138 msgid "roles" msgstr "" -#: awx/main/models/rbac.py:443 +#: awx/main/models/rbac.py:445 msgid "role_ancestors" msgstr "" -#: awx/main/models/schedules.py:79 +#: awx/main/models/schedules.py:83 msgid "Enables processing of this schedule." msgstr "" -#: awx/main/models/schedules.py:85 +#: awx/main/models/schedules.py:89 msgid "The first occurrence of the schedule occurs on or after this time." msgstr "" -#: awx/main/models/schedules.py:91 +#: awx/main/models/schedules.py:95 msgid "" "The last occurrence of the schedule occurs before this time, aftewards the " "schedule expires." msgstr "" -#: awx/main/models/schedules.py:95 +#: awx/main/models/schedules.py:99 msgid "A value representing the schedules iCal recurrence rule." msgstr "" -#: awx/main/models/schedules.py:101 +#: awx/main/models/schedules.py:105 msgid "The next time that the scheduled action will run." msgstr "" -#: awx/main/models/unified_jobs.py:67 +#: awx/main/models/unified_jobs.py:69 msgid "New" msgstr "" -#: awx/main/models/unified_jobs.py:69 +#: awx/main/models/unified_jobs.py:71 msgid "Waiting" msgstr "" -#: awx/main/models/unified_jobs.py:70 +#: awx/main/models/unified_jobs.py:72 msgid "Running" msgstr "" -#: awx/main/models/unified_jobs.py:74 +#: awx/main/models/unified_jobs.py:76 msgid "Canceled" msgstr "" -#: awx/main/models/unified_jobs.py:78 +#: awx/main/models/unified_jobs.py:80 msgid "Never Updated" msgstr "" -#: awx/main/models/unified_jobs.py:82 +#: awx/main/models/unified_jobs.py:84 msgid "OK" msgstr "" -#: awx/main/models/unified_jobs.py:83 +#: awx/main/models/unified_jobs.py:85 msgid "Missing" msgstr "" -#: awx/main/models/unified_jobs.py:87 +#: awx/main/models/unified_jobs.py:89 msgid "No External Source" msgstr "" -#: awx/main/models/unified_jobs.py:94 +#: awx/main/models/unified_jobs.py:96 msgid "Updating" msgstr "" -#: awx/main/models/unified_jobs.py:451 +#: awx/main/models/unified_jobs.py:167 +msgid "The organization used to determine access to this template." +msgstr "" + +#: awx/main/models/unified_jobs.py:465 msgid "Field is not allowed on launch." msgstr "" -#: awx/main/models/unified_jobs.py:479 +#: awx/main/models/unified_jobs.py:493 #, python-brace-format msgid "" "Variables {list_of_keys} provided, but this template cannot accept variables." msgstr "" -#: awx/main/models/unified_jobs.py:544 +#: awx/main/models/unified_jobs.py:539 msgid "Relaunch" msgstr "" -#: awx/main/models/unified_jobs.py:545 +#: awx/main/models/unified_jobs.py:540 msgid "Callback" msgstr "" -#: awx/main/models/unified_jobs.py:546 +#: awx/main/models/unified_jobs.py:541 msgid "Scheduled" msgstr "" -#: awx/main/models/unified_jobs.py:547 +#: awx/main/models/unified_jobs.py:542 msgid "Dependency" msgstr "" -#: awx/main/models/unified_jobs.py:548 -msgid "Workflow" +#: awx/main/models/unified_jobs.py:543 +msgid "Workflow" +msgstr "" + +#: awx/main/models/unified_jobs.py:545 +msgid "Sync" +msgstr "" + +#: awx/main/models/unified_jobs.py:600 +msgid "The node the job executed on." +msgstr "" + +#: awx/main/models/unified_jobs.py:606 +msgid "The instance that managed the isolated execution environment." +msgstr "" + +#: awx/main/models/unified_jobs.py:633 +msgid "The date and time the job was queued for starting." +msgstr "" + +#: awx/main/models/unified_jobs.py:638 +msgid "" +"If True, the task manager has already processed potential dependencies for " +"this job." +msgstr "" + +#: awx/main/models/unified_jobs.py:644 +msgid "The date and time the job finished execution." +msgstr "" + +#: awx/main/models/unified_jobs.py:651 +msgid "The date and time when the cancel request was sent." msgstr "" -#: awx/main/models/unified_jobs.py:549 -msgid "Sync" +#: awx/main/models/unified_jobs.py:658 +msgid "Elapsed time in seconds that the job ran." msgstr "" -#: awx/main/models/unified_jobs.py:597 -msgid "The node the job executed on." +#: awx/main/models/unified_jobs.py:680 +msgid "" +"A status field to indicate the state of the job if it wasn't able to run and " +"capture stdout" msgstr "" -#: awx/main/models/unified_jobs.py:603 -msgid "The instance that managed the isolated execution environment." +#: awx/main/models/unified_jobs.py:709 +msgid "The Instance group the job was run under" msgstr "" -#: awx/main/models/unified_jobs.py:629 -msgid "The date and time the job was queued for starting." +#: awx/main/models/unified_jobs.py:717 +msgid "The organization used to determine access to this unified job." msgstr "" -#: awx/main/models/unified_jobs.py:635 -msgid "The date and time the job finished execution." +#: awx/main/models/workflow.py:85 +msgid "" +"If enabled then the node will only run if all of the parent nodes have met " +"the criteria to reach this node" msgstr "" -#: awx/main/models/unified_jobs.py:641 -msgid "Elapsed time in seconds that the job ran." +#: awx/main/models/workflow.py:154 +msgid "" +"An identifier for this node that is unique within its workflow. It is copied " +"to workflow job nodes corresponding to this node." msgstr "" -#: awx/main/models/unified_jobs.py:663 +#: awx/main/models/workflow.py:229 msgid "" -"A status field to indicate the state of the job if it wasn't able to run and " -"capture stdout" +"Indicates that a job will not be created when True. Workflow runtime " +"semantics will mark this True if the node is in a path that will decidedly " +"not be ran. A value of False means the node may not run." msgstr "" -#: awx/main/models/unified_jobs.py:692 -msgid "The Rampart/Instance group the job was run under" +#: awx/main/models/workflow.py:236 +msgid "" +"An identifier coresponding to the workflow job template node that this node " +"was created from." msgstr "" -#: awx/main/models/workflow.py:221 +#: awx/main/models/workflow.py:282 #, python-brace-format msgid "" "Bad launch configuration starting template {template_pk} as part of workflow " @@ -4035,155 +4740,192 @@ msgid "" "{error_text}" msgstr "" -#: awx/main/models/workflow.py:363 +#: awx/main/models/workflow.py:595 msgid "" -"Inventory applied to all job templates in workflow that prompt for inventory." +"If automatically created for a sliced job run, the job template the workflow " +"job was created from." msgstr "" -#: awx/main/models/workflow.py:511 +#: awx/main/models/workflow.py:687 awx/main/models/workflow.py:721 msgid "" -"If automatically created for a sliced job run, the job template the workflow " -"job was created from." +"The amount of time (in seconds) before the approval node expires and fails." msgstr "" -#: awx/main/notifications/base.py:17 awx/main/notifications/email_backend.py:28 +#: awx/main/models/workflow.py:725 msgid "" -"{} #{} had status {}, view details at {}\n" -"\n" +"Shows when an approval node (with a timeout assigned to it) has timed out." +msgstr "" + +#: awx/main/notifications/grafana_backend.py:81 +msgid "Error converting time {} or timeEnd {} to int." msgstr "" -#: awx/main/notifications/hipchat_backend.py:48 -msgid "Error sending messages: {}" +#: awx/main/notifications/grafana_backend.py:83 +msgid "Error converting time {} and/or timeEnd {} to int." msgstr "" -#: awx/main/notifications/hipchat_backend.py:50 -msgid "Error sending message to hipchat: {}" +#: awx/main/notifications/grafana_backend.py:97 +#: awx/main/notifications/grafana_backend.py:99 +msgid "Error sending notification grafana: {}" msgstr "" -#: awx/main/notifications/irc_backend.py:54 +#: awx/main/notifications/irc_backend.py:56 msgid "Exception connecting to irc server: {}" msgstr "" -#: awx/main/notifications/mattermost_backend.py:48 -#: awx/main/notifications/mattermost_backend.py:50 +#: awx/main/notifications/mattermost_backend.py:49 +#: awx/main/notifications/mattermost_backend.py:51 msgid "Error sending notification mattermost: {}" msgstr "" -#: awx/main/notifications/pagerduty_backend.py:39 +#: awx/main/notifications/pagerduty_backend.py:75 msgid "Exception connecting to PagerDuty: {}" msgstr "" -#: awx/main/notifications/pagerduty_backend.py:48 -#: awx/main/notifications/slack_backend.py:55 -#: awx/main/notifications/twilio_backend.py:46 +#: awx/main/notifications/pagerduty_backend.py:84 +#: awx/main/notifications/slack_backend.py:58 +#: awx/main/notifications/twilio_backend.py:48 msgid "Exception sending messages: {}" msgstr "" -#: awx/main/notifications/rocketchat_backend.py:46 #: awx/main/notifications/rocketchat_backend.py:49 +#: awx/main/notifications/rocketchat_backend.py:52 msgid "Error sending notification rocket.chat: {}" msgstr "" -#: awx/main/notifications/twilio_backend.py:36 +#: awx/main/notifications/twilio_backend.py:38 msgid "Exception connecting to Twilio: {}" msgstr "" -#: awx/main/notifications/webhook_backend.py:38 -#: awx/main/notifications/webhook_backend.py:40 +#: awx/main/notifications/webhook_backend.py:75 +#: awx/main/notifications/webhook_backend.py:77 msgid "Error sending notification webhook: {}" msgstr "" -#: awx/main/scheduler/task_manager.py:133 +#: awx/main/scheduler/dag_workflow.py:170 +#, python-brace-format +msgid "" +"No error handling path for workflow job node(s) [{node_status}]. Workflow " +"job node(s) missing unified job template and error handling path [{no_ufjt}]." +msgstr "" + +#: awx/main/scheduler/task_manager.py:127 msgid "" "Workflow Job spawned from workflow could not start because it would result " "in recursion (spawn order, most recent first: {})" msgstr "" -#: awx/main/scheduler/task_manager.py:141 +#: awx/main/scheduler/task_manager.py:135 msgid "" "Job spawned from workflow could not start because it was missing a related " "resource such as project or inventory" msgstr "" -#: awx/main/scheduler/task_manager.py:150 +#: awx/main/scheduler/task_manager.py:144 msgid "" "Job spawned from workflow could not start because it was not in the right " "state or required manual credentials" msgstr "" -#: awx/main/signals.py:646 -msgid "limit_reached" +#: awx/main/scheduler/task_manager.py:185 +msgid "No error handling paths found, marking workflow as failed" +msgstr "" + +#: awx/main/scheduler/task_manager.py:523 +#, python-brace-format +msgid "The approval node {name} ({pk}) has expired after {timeout} seconds." msgstr "" -#: awx/main/tasks.py:313 -msgid "Ansible Tower host usage over 90%" +#: awx/main/tasks.py:599 +msgid "" +"Scheduled job could not start because it was not in the " +"right state or required manual credentials" msgstr "" -#: awx/main/tasks.py:318 -msgid "Ansible Tower license will expire soon" +#: awx/main/tasks.py:1070 +msgid "Invalid virtual environment selected: {}" msgstr "" -#: awx/main/tasks.py:1375 +#: awx/main/tasks.py:1857 msgid "Job could not start because it does not have a valid inventory." msgstr "" -#: awx/main/tasks.py:1386 +#: awx/main/tasks.py:1861 +msgid "Job could not start because it does not have a valid project." +msgstr "" + +#: awx/main/tasks.py:1866 msgid "" "The project revision for this job template is unknown due to a failed update." msgstr "" -#: awx/main/utils/common.py:95 +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:470 +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:498 +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:505 +msgid "" +"No error handling path for workflow job node(s) [({},{})]. Workflow job " +"node(s) missing unified job template and error handling path []." +msgstr "" + +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:480 +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:491 +msgid "" +"No error handling path for workflow job node(s) []. Workflow job node(s) " +"missing unified job template and error handling path [{}]." +msgstr "" + +#: awx/main/utils/common.py:87 #, python-format msgid "Unable to convert \"%s\" to boolean" msgstr "" -#: awx/main/utils/common.py:256 +#: awx/main/utils/common.py:248 #, python-format msgid "Unsupported SCM type \"%s\"" msgstr "" -#: awx/main/utils/common.py:263 awx/main/utils/common.py:275 -#: awx/main/utils/common.py:294 +#: awx/main/utils/common.py:255 awx/main/utils/common.py:267 +#: awx/main/utils/common.py:286 #, python-format msgid "Invalid %s URL" msgstr "" -#: awx/main/utils/common.py:265 awx/main/utils/common.py:304 +#: awx/main/utils/common.py:257 awx/main/utils/common.py:297 #, python-format msgid "Unsupported %s URL" msgstr "" -#: awx/main/utils/common.py:306 +#: awx/main/utils/common.py:299 #, python-format msgid "Unsupported host \"%s\" for file:// URL" msgstr "" -#: awx/main/utils/common.py:308 +#: awx/main/utils/common.py:301 #, python-format msgid "Host is required for %s URL" msgstr "" -#: awx/main/utils/common.py:326 +#: awx/main/utils/common.py:319 #, python-format msgid "Username must be \"git\" for SSH access to %s." msgstr "" -#: awx/main/utils/common.py:332 +#: awx/main/utils/common.py:325 #, python-format msgid "Username must be \"hg\" for SSH access to %s." msgstr "" -#: awx/main/utils/common.py:613 +#: awx/main/utils/common.py:656 #, python-brace-format msgid "Input type `{data_type}` is not a dictionary" msgstr "" -#: awx/main/utils/common.py:646 +#: awx/main/utils/common.py:689 #, python-brace-format msgid "Variables not compatible with JSON standard (error: {json_error})" msgstr "" -#: awx/main/utils/common.py:652 +#: awx/main/utils/common.py:695 #, python-brace-format msgid "" "Cannot parse as JSON (error: {json_error}) or YAML (error: {yaml_error})." @@ -4259,331 +5001,47 @@ msgid "" "No more than %(max_certs)d certificates are allowed, %(cert_count)d provided." msgstr "" -#: awx/main/views.py:23 +#: awx/main/views.py:30 msgid "API Error" msgstr "" -#: awx/main/views.py:61 +#: awx/main/views.py:65 msgid "Bad Request" msgstr "" -#: awx/main/views.py:62 +#: awx/main/views.py:66 msgid "The request could not be understood by the server." msgstr "" -#: awx/main/views.py:69 +#: awx/main/views.py:73 msgid "Forbidden" msgstr "" -#: awx/main/views.py:70 +#: awx/main/views.py:74 msgid "You don't have permission to access the requested resource." msgstr "" -#: awx/main/views.py:77 +#: awx/main/views.py:81 msgid "Not Found" msgstr "" -#: awx/main/views.py:78 +#: awx/main/views.py:82 msgid "The requested resource could not be found." msgstr "" -#: awx/main/views.py:85 +#: awx/main/views.py:89 msgid "Server Error" msgstr "" -#: awx/main/views.py:86 +#: awx/main/views.py:90 msgid "A server error has occurred." msgstr "" -#: awx/settings/defaults.py:698 -msgid "US East (Northern Virginia)" -msgstr "" - -#: awx/settings/defaults.py:699 -msgid "US East (Ohio)" -msgstr "" - -#: awx/settings/defaults.py:700 -msgid "US West (Oregon)" -msgstr "" - -#: awx/settings/defaults.py:701 -msgid "US West (Northern California)" -msgstr "" - -#: awx/settings/defaults.py:702 -msgid "Canada (Central)" -msgstr "" - -#: awx/settings/defaults.py:703 -msgid "EU (Frankfurt)" -msgstr "" - -#: awx/settings/defaults.py:704 -msgid "EU (Ireland)" -msgstr "" - -#: awx/settings/defaults.py:705 -msgid "EU (London)" -msgstr "" - -#: awx/settings/defaults.py:706 -msgid "Asia Pacific (Singapore)" -msgstr "" - -#: awx/settings/defaults.py:707 -msgid "Asia Pacific (Sydney)" -msgstr "" - -#: awx/settings/defaults.py:708 -msgid "Asia Pacific (Tokyo)" -msgstr "" - -#: awx/settings/defaults.py:709 -msgid "Asia Pacific (Seoul)" -msgstr "" - -#: awx/settings/defaults.py:710 -msgid "Asia Pacific (Mumbai)" -msgstr "" - -#: awx/settings/defaults.py:711 -msgid "South America (Sao Paulo)" -msgstr "" - -#: awx/settings/defaults.py:712 -msgid "US West (GovCloud)" -msgstr "" - -#: awx/settings/defaults.py:713 -msgid "China (Beijing)" -msgstr "" - -#: awx/settings/defaults.py:762 -msgid "US East 1 (B)" -msgstr "" - -#: awx/settings/defaults.py:763 -msgid "US East 1 (C)" -msgstr "" - -#: awx/settings/defaults.py:764 -msgid "US East 1 (D)" -msgstr "" - -#: awx/settings/defaults.py:765 -msgid "US East 4 (A)" -msgstr "" - -#: awx/settings/defaults.py:766 -msgid "US East 4 (B)" -msgstr "" - -#: awx/settings/defaults.py:767 -msgid "US East 4 (C)" -msgstr "" - -#: awx/settings/defaults.py:768 -msgid "US Central (A)" -msgstr "" - -#: awx/settings/defaults.py:769 -msgid "US Central (B)" -msgstr "" - -#: awx/settings/defaults.py:770 -msgid "US Central (C)" -msgstr "" - -#: awx/settings/defaults.py:771 -msgid "US Central (F)" -msgstr "" - -#: awx/settings/defaults.py:772 -msgid "US West (A)" -msgstr "" - -#: awx/settings/defaults.py:773 -msgid "US West (B)" -msgstr "" - -#: awx/settings/defaults.py:774 -msgid "US West (C)" -msgstr "" - -#: awx/settings/defaults.py:775 -msgid "Europe West 1 (B)" -msgstr "" - -#: awx/settings/defaults.py:776 -msgid "Europe West 1 (C)" -msgstr "" - -#: awx/settings/defaults.py:777 -msgid "Europe West 1 (D)" -msgstr "" - -#: awx/settings/defaults.py:778 -msgid "Europe West 2 (A)" -msgstr "" - -#: awx/settings/defaults.py:779 -msgid "Europe West 2 (B)" -msgstr "" - -#: awx/settings/defaults.py:780 -msgid "Europe West 2 (C)" -msgstr "" - -#: awx/settings/defaults.py:781 -msgid "Asia East (A)" -msgstr "" - -#: awx/settings/defaults.py:782 -msgid "Asia East (B)" -msgstr "" - -#: awx/settings/defaults.py:783 -msgid "Asia East (C)" -msgstr "" - -#: awx/settings/defaults.py:784 -msgid "Asia Southeast (A)" -msgstr "" - -#: awx/settings/defaults.py:785 -msgid "Asia Southeast (B)" -msgstr "" - -#: awx/settings/defaults.py:786 -msgid "Asia Northeast (A)" -msgstr "" - -#: awx/settings/defaults.py:787 -msgid "Asia Northeast (B)" -msgstr "" - -#: awx/settings/defaults.py:788 -msgid "Asia Northeast (C)" -msgstr "" - -#: awx/settings/defaults.py:789 -msgid "Australia Southeast (A)" -msgstr "" - -#: awx/settings/defaults.py:790 -msgid "Australia Southeast (B)" -msgstr "" - -#: awx/settings/defaults.py:791 -msgid "Australia Southeast (C)" -msgstr "" - -#: awx/settings/defaults.py:813 -msgid "US East" -msgstr "" - -#: awx/settings/defaults.py:814 -msgid "US East 2" -msgstr "" - -#: awx/settings/defaults.py:815 -msgid "US Central" -msgstr "" - -#: awx/settings/defaults.py:816 -msgid "US North Central" -msgstr "" - -#: awx/settings/defaults.py:817 -msgid "US South Central" -msgstr "" - -#: awx/settings/defaults.py:818 -msgid "US West Central" -msgstr "" - -#: awx/settings/defaults.py:819 -msgid "US West" -msgstr "" - -#: awx/settings/defaults.py:820 -msgid "US West 2" -msgstr "" - -#: awx/settings/defaults.py:821 -msgid "Canada East" -msgstr "" - -#: awx/settings/defaults.py:822 -msgid "Canada Central" -msgstr "" - -#: awx/settings/defaults.py:823 -msgid "Brazil South" -msgstr "" - -#: awx/settings/defaults.py:824 -msgid "Europe North" -msgstr "" - -#: awx/settings/defaults.py:825 -msgid "Europe West" -msgstr "" - -#: awx/settings/defaults.py:826 -msgid "UK West" -msgstr "" - -#: awx/settings/defaults.py:827 -msgid "UK South" -msgstr "" - -#: awx/settings/defaults.py:828 -msgid "Asia East" -msgstr "" - -#: awx/settings/defaults.py:829 -msgid "Asia Southeast" -msgstr "" - -#: awx/settings/defaults.py:830 -msgid "Australia East" -msgstr "" - -#: awx/settings/defaults.py:831 -msgid "Australia Southeast" -msgstr "" - -#: awx/settings/defaults.py:832 -msgid "India West" -msgstr "" - -#: awx/settings/defaults.py:833 -msgid "India South" -msgstr "" - -#: awx/settings/defaults.py:834 -msgid "Japan East" -msgstr "" - -#: awx/settings/defaults.py:835 -msgid "Japan West" -msgstr "" - -#: awx/settings/defaults.py:836 -msgid "Korea Central" -msgstr "" - -#: awx/settings/defaults.py:837 -msgid "Korea South" -msgstr "" - #: awx/sso/apps.py:9 msgid "Single Sign-On" msgstr "" -#: awx/sso/conf.py:30 +#: awx/sso/conf.py:41 msgid "" "Mapping to organization admins/users from social auth accounts. This " "setting\n" @@ -4594,46 +5052,46 @@ msgid "" "Tower documentation." msgstr "" -#: awx/sso/conf.py:55 +#: awx/sso/conf.py:67 msgid "" "Mapping of team members (users) from social auth accounts. Configuration\n" "details are available in Tower documentation." msgstr "" -#: awx/sso/conf.py:80 +#: awx/sso/conf.py:92 msgid "Authentication Backends" msgstr "" -#: awx/sso/conf.py:81 +#: awx/sso/conf.py:93 msgid "" "List of authentication backends that are enabled based on license features " "and other authentication settings." msgstr "" -#: awx/sso/conf.py:94 +#: awx/sso/conf.py:106 msgid "Social Auth Organization Map" msgstr "" -#: awx/sso/conf.py:106 +#: awx/sso/conf.py:118 msgid "Social Auth Team Map" msgstr "" -#: awx/sso/conf.py:118 +#: awx/sso/conf.py:130 msgid "Social Auth User Fields" msgstr "" -#: awx/sso/conf.py:119 +#: awx/sso/conf.py:131 msgid "" "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." msgstr "" -#: awx/sso/conf.py:141 +#: awx/sso/conf.py:153 msgid "LDAP Server URI" msgstr "" -#: awx/sso/conf.py:142 +#: awx/sso/conf.py:154 msgid "" "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 " @@ -4641,47 +5099,47 @@ msgid "" "disabled if this parameter is empty." msgstr "" -#: awx/sso/conf.py:146 awx/sso/conf.py:162 awx/sso/conf.py:174 -#: awx/sso/conf.py:186 awx/sso/conf.py:202 awx/sso/conf.py:222 -#: awx/sso/conf.py:244 awx/sso/conf.py:259 awx/sso/conf.py:277 -#: awx/sso/conf.py:294 awx/sso/conf.py:306 awx/sso/conf.py:332 -#: awx/sso/conf.py:348 awx/sso/conf.py:362 awx/sso/conf.py:380 -#: awx/sso/conf.py:406 +#: awx/sso/conf.py:158 awx/sso/conf.py:173 awx/sso/conf.py:184 +#: awx/sso/conf.py:195 awx/sso/conf.py:210 awx/sso/conf.py:229 +#: awx/sso/conf.py:250 awx/sso/conf.py:264 awx/sso/conf.py:281 +#: awx/sso/conf.py:297 awx/sso/conf.py:308 awx/sso/conf.py:333 +#: awx/sso/conf.py:348 awx/sso/conf.py:361 awx/sso/conf.py:378 +#: awx/sso/conf.py:404 msgid "LDAP" msgstr "" -#: awx/sso/conf.py:158 +#: awx/sso/conf.py:169 msgid "LDAP Bind DN" msgstr "" -#: awx/sso/conf.py:159 +#: awx/sso/conf.py:170 msgid "" "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 Ansible Tower documentation for example syntax." msgstr "" -#: awx/sso/conf.py:172 +#: awx/sso/conf.py:182 msgid "LDAP Bind Password" msgstr "" -#: awx/sso/conf.py:173 +#: awx/sso/conf.py:183 msgid "Password used to bind LDAP user account." msgstr "" -#: awx/sso/conf.py:184 +#: awx/sso/conf.py:193 msgid "LDAP Start TLS" msgstr "" -#: awx/sso/conf.py:185 +#: awx/sso/conf.py:194 msgid "Whether to enable TLS when the LDAP connection is not using SSL." msgstr "" -#: awx/sso/conf.py:195 +#: awx/sso/conf.py:203 msgid "LDAP Connection Options" msgstr "" -#: awx/sso/conf.py:196 +#: awx/sso/conf.py:204 msgid "" "Additional options to set for the LDAP connection. LDAP referrals are " "disabled by default (to prevent certain LDAP queries from hanging with AD). " @@ -4690,11 +5148,11 @@ msgid "" "values that can be set." msgstr "" -#: awx/sso/conf.py:215 +#: awx/sso/conf.py:222 msgid "LDAP User Search" msgstr "" -#: awx/sso/conf.py:216 +#: awx/sso/conf.py:223 msgid "" "LDAP search query to find users. Any user that matches the given pattern " "will be able to login to Tower. The user should also be mapped into a Tower " @@ -4703,11 +5161,11 @@ msgid "" "possible. See Tower documentation for details." msgstr "" -#: awx/sso/conf.py:238 +#: awx/sso/conf.py:244 msgid "LDAP User DN Template" msgstr "" -#: awx/sso/conf.py:239 +#: awx/sso/conf.py:245 msgid "" "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 " @@ -4715,11 +5173,11 @@ msgid "" "used instead of AUTH_LDAP_USER_SEARCH." msgstr "" -#: awx/sso/conf.py:254 +#: awx/sso/conf.py:259 msgid "LDAP User Attribute Map" msgstr "" -#: awx/sso/conf.py:255 +#: awx/sso/conf.py:260 msgid "" "Mapping of LDAP user schema to Tower API user attributes. The default " "setting is valid for ActiveDirectory but users with other LDAP " @@ -4727,41 +5185,41 @@ msgid "" "documentation for additional details." msgstr "" -#: awx/sso/conf.py:273 +#: awx/sso/conf.py:277 msgid "LDAP Group Search" msgstr "" -#: awx/sso/conf.py:274 +#: awx/sso/conf.py:278 msgid "" "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." msgstr "" -#: awx/sso/conf.py:290 +#: awx/sso/conf.py:293 msgid "LDAP Group Type" msgstr "" -#: awx/sso/conf.py:291 +#: awx/sso/conf.py:294 msgid "" "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" msgstr "" -#: awx/sso/conf.py:304 +#: awx/sso/conf.py:306 msgid "LDAP Group Type Parameters" msgstr "" -#: awx/sso/conf.py:305 +#: awx/sso/conf.py:307 msgid "Key value parameters to send the chosen group type init method." msgstr "" -#: awx/sso/conf.py:327 +#: awx/sso/conf.py:328 msgid "LDAP Require Group" msgstr "" -#: awx/sso/conf.py:328 +#: awx/sso/conf.py:329 msgid "" "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 " @@ -4778,22 +5236,22 @@ msgid "" "if a member of this group. Only one deny group is supported." msgstr "" -#: awx/sso/conf.py:358 +#: awx/sso/conf.py:357 msgid "LDAP User Flags By Group" msgstr "" -#: awx/sso/conf.py:359 +#: awx/sso/conf.py:358 msgid "" "Retrieve users from a given group. At this time, superuser and system " "auditors are the only groups supported. Refer to the Ansible Tower " "documentation for more detail." msgstr "" -#: awx/sso/conf.py:375 +#: awx/sso/conf.py:373 msgid "LDAP Organization Map" msgstr "" -#: awx/sso/conf.py:376 +#: awx/sso/conf.py:374 msgid "" "Mapping between organization admins/users and LDAP groups. This controls " "which users are placed into which Tower organizations relative to their LDAP " @@ -4801,237 +5259,237 @@ msgid "" "documentation." msgstr "" -#: awx/sso/conf.py:403 +#: awx/sso/conf.py:401 msgid "LDAP Team Map" msgstr "" -#: awx/sso/conf.py:404 +#: awx/sso/conf.py:402 msgid "" "Mapping between team members (users) and LDAP groups. Configuration details " "are available in the Ansible Tower documentation." msgstr "" -#: awx/sso/conf.py:440 +#: awx/sso/conf.py:437 msgid "RADIUS Server" msgstr "" -#: awx/sso/conf.py:441 +#: awx/sso/conf.py:438 msgid "" "Hostname/IP of RADIUS server. RADIUS authentication is disabled if this " "setting is empty." msgstr "" -#: awx/sso/conf.py:443 awx/sso/conf.py:457 awx/sso/conf.py:469 +#: awx/sso/conf.py:440 awx/sso/conf.py:453 awx/sso/conf.py:464 #: awx/sso/models.py:14 msgid "RADIUS" msgstr "" -#: awx/sso/conf.py:455 +#: awx/sso/conf.py:451 msgid "RADIUS Port" msgstr "" -#: awx/sso/conf.py:456 +#: awx/sso/conf.py:452 msgid "Port of RADIUS server." msgstr "" -#: awx/sso/conf.py:467 +#: awx/sso/conf.py:462 msgid "RADIUS Secret" msgstr "" -#: awx/sso/conf.py:468 +#: awx/sso/conf.py:463 msgid "Shared secret for authenticating to RADIUS server." msgstr "" -#: awx/sso/conf.py:484 +#: awx/sso/conf.py:478 msgid "TACACS+ Server" msgstr "" -#: awx/sso/conf.py:485 +#: awx/sso/conf.py:479 msgid "Hostname of TACACS+ server." msgstr "" -#: awx/sso/conf.py:486 awx/sso/conf.py:499 awx/sso/conf.py:512 -#: awx/sso/conf.py:525 awx/sso/conf.py:537 awx/sso/models.py:15 +#: awx/sso/conf.py:480 awx/sso/conf.py:492 awx/sso/conf.py:504 +#: awx/sso/conf.py:516 awx/sso/conf.py:528 awx/sso/models.py:15 msgid "TACACS+" msgstr "" -#: awx/sso/conf.py:497 +#: awx/sso/conf.py:490 msgid "TACACS+ Port" msgstr "" -#: awx/sso/conf.py:498 +#: awx/sso/conf.py:491 msgid "Port number of TACACS+ server." msgstr "" -#: awx/sso/conf.py:510 +#: awx/sso/conf.py:502 msgid "TACACS+ Secret" msgstr "" -#: awx/sso/conf.py:511 +#: awx/sso/conf.py:503 msgid "Shared secret for authenticating to TACACS+ server." msgstr "" -#: awx/sso/conf.py:523 +#: awx/sso/conf.py:514 msgid "TACACS+ Auth Session Timeout" msgstr "" -#: awx/sso/conf.py:524 +#: awx/sso/conf.py:515 msgid "TACACS+ session timeout value in seconds, 0 disables timeout." msgstr "" -#: awx/sso/conf.py:535 +#: awx/sso/conf.py:526 msgid "TACACS+ Authentication Protocol" msgstr "" -#: awx/sso/conf.py:536 +#: awx/sso/conf.py:527 msgid "Choose the authentication protocol used by TACACS+ client." msgstr "" -#: awx/sso/conf.py:551 +#: awx/sso/conf.py:541 msgid "Google OAuth2 Callback URL" msgstr "" -#: awx/sso/conf.py:552 awx/sso/conf.py:645 awx/sso/conf.py:710 +#: awx/sso/conf.py:542 awx/sso/conf.py:635 awx/sso/conf.py:700 msgid "" "Provide this URL as the callback URL for your application as part of your " "registration process. Refer to the Ansible Tower documentation for more " "detail." msgstr "" -#: awx/sso/conf.py:555 awx/sso/conf.py:567 awx/sso/conf.py:579 -#: awx/sso/conf.py:592 awx/sso/conf.py:606 awx/sso/conf.py:618 -#: awx/sso/conf.py:630 +#: awx/sso/conf.py:545 awx/sso/conf.py:557 awx/sso/conf.py:569 +#: awx/sso/conf.py:582 awx/sso/conf.py:596 awx/sso/conf.py:608 +#: awx/sso/conf.py:620 msgid "Google OAuth2" msgstr "" -#: awx/sso/conf.py:565 +#: awx/sso/conf.py:555 msgid "Google OAuth2 Key" msgstr "" -#: awx/sso/conf.py:566 +#: awx/sso/conf.py:556 msgid "The OAuth2 key from your web application." msgstr "" -#: awx/sso/conf.py:577 +#: awx/sso/conf.py:567 msgid "Google OAuth2 Secret" msgstr "" -#: awx/sso/conf.py:578 +#: awx/sso/conf.py:568 msgid "The OAuth2 secret from your web application." msgstr "" -#: awx/sso/conf.py:589 -msgid "Google OAuth2 Whitelisted Domains" +#: awx/sso/conf.py:579 +msgid "Google OAuth2 Allowed Domains" msgstr "" -#: awx/sso/conf.py:590 +#: awx/sso/conf.py:580 msgid "" "Update this setting to restrict the domains who are allowed to login using " "Google OAuth2." msgstr "" -#: awx/sso/conf.py:601 +#: awx/sso/conf.py:591 msgid "Google OAuth2 Extra Arguments" msgstr "" -#: awx/sso/conf.py:602 +#: awx/sso/conf.py:592 msgid "" "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 Ansible Tower documentation for more detail." msgstr "" -#: awx/sso/conf.py:616 +#: awx/sso/conf.py:606 msgid "Google OAuth2 Organization Map" msgstr "" -#: awx/sso/conf.py:628 +#: awx/sso/conf.py:618 msgid "Google OAuth2 Team Map" msgstr "" -#: awx/sso/conf.py:644 +#: awx/sso/conf.py:634 msgid "GitHub OAuth2 Callback URL" msgstr "" -#: awx/sso/conf.py:648 awx/sso/conf.py:660 awx/sso/conf.py:671 -#: awx/sso/conf.py:683 awx/sso/conf.py:695 +#: awx/sso/conf.py:638 awx/sso/conf.py:650 awx/sso/conf.py:661 +#: awx/sso/conf.py:673 awx/sso/conf.py:685 msgid "GitHub OAuth2" msgstr "" -#: awx/sso/conf.py:658 +#: awx/sso/conf.py:648 msgid "GitHub OAuth2 Key" msgstr "" -#: awx/sso/conf.py:659 +#: awx/sso/conf.py:649 msgid "The OAuth2 key (Client ID) from your GitHub developer application." msgstr "" -#: awx/sso/conf.py:669 +#: awx/sso/conf.py:659 msgid "GitHub OAuth2 Secret" msgstr "" -#: awx/sso/conf.py:670 +#: awx/sso/conf.py:660 msgid "" "The OAuth2 secret (Client Secret) from your GitHub developer application." msgstr "" -#: awx/sso/conf.py:681 +#: awx/sso/conf.py:671 msgid "GitHub OAuth2 Organization Map" msgstr "" -#: awx/sso/conf.py:693 +#: awx/sso/conf.py:683 msgid "GitHub OAuth2 Team Map" msgstr "" -#: awx/sso/conf.py:709 +#: awx/sso/conf.py:699 msgid "GitHub Organization OAuth2 Callback URL" msgstr "" -#: awx/sso/conf.py:713 awx/sso/conf.py:725 awx/sso/conf.py:736 -#: awx/sso/conf.py:749 awx/sso/conf.py:760 awx/sso/conf.py:772 +#: awx/sso/conf.py:703 awx/sso/conf.py:715 awx/sso/conf.py:726 +#: awx/sso/conf.py:739 awx/sso/conf.py:750 awx/sso/conf.py:762 msgid "GitHub Organization OAuth2" msgstr "" -#: awx/sso/conf.py:723 +#: awx/sso/conf.py:713 msgid "GitHub Organization OAuth2 Key" msgstr "" -#: awx/sso/conf.py:724 awx/sso/conf.py:802 +#: awx/sso/conf.py:714 awx/sso/conf.py:792 msgid "The OAuth2 key (Client ID) from your GitHub organization application." msgstr "" -#: awx/sso/conf.py:734 +#: awx/sso/conf.py:724 msgid "GitHub Organization OAuth2 Secret" msgstr "" -#: awx/sso/conf.py:735 awx/sso/conf.py:813 +#: awx/sso/conf.py:725 awx/sso/conf.py:803 msgid "" "The OAuth2 secret (Client Secret) from your GitHub organization application." msgstr "" -#: awx/sso/conf.py:746 +#: awx/sso/conf.py:736 msgid "GitHub Organization Name" msgstr "" -#: awx/sso/conf.py:747 +#: awx/sso/conf.py:737 msgid "" "The name of your GitHub organization, as used in your organization's URL: " "https://github.com//." msgstr "" -#: awx/sso/conf.py:758 +#: awx/sso/conf.py:748 msgid "GitHub Organization OAuth2 Organization Map" msgstr "" -#: awx/sso/conf.py:770 +#: awx/sso/conf.py:760 msgid "GitHub Organization OAuth2 Team Map" msgstr "" -#: awx/sso/conf.py:786 +#: awx/sso/conf.py:776 msgid "GitHub Team OAuth2 Callback URL" msgstr "" -#: awx/sso/conf.py:787 +#: awx/sso/conf.py:777 msgid "" "Create an organization-owned application at https://github.com/organizations/" "/settings/applications and obtain an OAuth2 key (Client ID) and " @@ -5039,97 +5497,107 @@ msgid "" "application." msgstr "" -#: awx/sso/conf.py:791 awx/sso/conf.py:803 awx/sso/conf.py:814 -#: awx/sso/conf.py:827 awx/sso/conf.py:838 awx/sso/conf.py:850 +#: awx/sso/conf.py:781 awx/sso/conf.py:793 awx/sso/conf.py:804 +#: awx/sso/conf.py:817 awx/sso/conf.py:828 awx/sso/conf.py:840 msgid "GitHub Team OAuth2" msgstr "" -#: awx/sso/conf.py:801 +#: awx/sso/conf.py:791 msgid "GitHub Team OAuth2 Key" msgstr "" -#: awx/sso/conf.py:812 +#: awx/sso/conf.py:802 msgid "GitHub Team OAuth2 Secret" msgstr "" -#: awx/sso/conf.py:824 +#: awx/sso/conf.py:814 msgid "GitHub Team ID" msgstr "" -#: awx/sso/conf.py:825 +#: awx/sso/conf.py:815 msgid "" "Find the numeric team ID using the Github API: http://fabian-kostadinov." "github.io/2015/01/16/how-to-find-a-github-team-id/." msgstr "" -#: awx/sso/conf.py:836 +#: awx/sso/conf.py:826 msgid "GitHub Team OAuth2 Organization Map" msgstr "" -#: awx/sso/conf.py:848 +#: awx/sso/conf.py:838 msgid "GitHub Team OAuth2 Team Map" msgstr "" -#: awx/sso/conf.py:864 +#: awx/sso/conf.py:854 msgid "Azure AD OAuth2 Callback URL" msgstr "" -#: awx/sso/conf.py:865 +#: awx/sso/conf.py:855 msgid "" "Provide this URL as the callback URL for your application as part of your " "registration process. Refer to the Ansible Tower documentation for more " "detail. " msgstr "" -#: awx/sso/conf.py:868 awx/sso/conf.py:880 awx/sso/conf.py:891 -#: awx/sso/conf.py:903 awx/sso/conf.py:915 +#: awx/sso/conf.py:858 awx/sso/conf.py:870 awx/sso/conf.py:881 +#: awx/sso/conf.py:893 awx/sso/conf.py:905 msgid "Azure AD OAuth2" msgstr "" -#: awx/sso/conf.py:878 +#: awx/sso/conf.py:868 msgid "Azure AD OAuth2 Key" msgstr "" -#: awx/sso/conf.py:879 +#: awx/sso/conf.py:869 msgid "The OAuth2 key (Client ID) from your Azure AD application." msgstr "" -#: awx/sso/conf.py:889 +#: awx/sso/conf.py:879 msgid "Azure AD OAuth2 Secret" msgstr "" -#: awx/sso/conf.py:890 +#: awx/sso/conf.py:880 msgid "The OAuth2 secret (Client Secret) from your Azure AD application." msgstr "" -#: awx/sso/conf.py:901 +#: awx/sso/conf.py:891 msgid "Azure AD OAuth2 Organization Map" msgstr "" -#: awx/sso/conf.py:913 +#: awx/sso/conf.py:903 msgid "Azure AD OAuth2 Team Map" msgstr "" -#: awx/sso/conf.py:938 -msgid "SAML Assertion Consumer Service (ACS) URL" +#: awx/sso/conf.py:927 +msgid "Automatically Create Organizations and Teams on SAML Login" +msgstr "" + +#: awx/sso/conf.py:928 +msgid "" +"When enabled (the default), mapped Organizations and Teams will be created " +"automatically on successful SAML login." +msgstr "" + +#: awx/sso/conf.py:930 awx/sso/conf.py:943 awx/sso/conf.py:956 +#: awx/sso/conf.py:969 awx/sso/conf.py:983 awx/sso/conf.py:996 +#: awx/sso/conf.py:1008 awx/sso/conf.py:1028 awx/sso/conf.py:1045 +#: awx/sso/conf.py:1063 awx/sso/conf.py:1098 awx/sso/conf.py:1129 +#: awx/sso/conf.py:1142 awx/sso/conf.py:1158 awx/sso/conf.py:1170 +#: awx/sso/conf.py:1182 awx/sso/conf.py:1201 awx/sso/models.py:16 +msgid "SAML" msgstr "" #: awx/sso/conf.py:939 +msgid "SAML Assertion Consumer Service (ACS) URL" +msgstr "" + +#: awx/sso/conf.py:940 msgid "" "Register Tower 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." msgstr "" -#: awx/sso/conf.py:942 awx/sso/conf.py:956 awx/sso/conf.py:970 -#: awx/sso/conf.py:985 awx/sso/conf.py:999 awx/sso/conf.py:1012 -#: awx/sso/conf.py:1033 awx/sso/conf.py:1051 awx/sso/conf.py:1070 -#: awx/sso/conf.py:1106 awx/sso/conf.py:1138 awx/sso/conf.py:1152 -#: awx/sso/conf.py:1169 awx/sso/conf.py:1182 awx/sso/conf.py:1195 -#: awx/sso/conf.py:1213 awx/sso/models.py:16 -msgid "SAML" -msgstr "" - #: awx/sso/conf.py:953 msgid "SAML Service Provider Metadata URL" msgstr "" @@ -5140,71 +5608,71 @@ msgid "" "can download one from this URL." msgstr "" -#: awx/sso/conf.py:966 +#: awx/sso/conf.py:965 msgid "SAML Service Provider Entity ID" msgstr "" -#: awx/sso/conf.py:967 +#: awx/sso/conf.py:966 msgid "" "The application-defined unique identifier used as the audience of the SAML " "service provider (SP) configuration. This is usually the URL for Tower." msgstr "" -#: awx/sso/conf.py:982 +#: awx/sso/conf.py:980 msgid "SAML Service Provider Public Certificate" msgstr "" -#: awx/sso/conf.py:983 +#: awx/sso/conf.py:981 msgid "" "Create a keypair for Tower to use as a service provider (SP) and include the " "certificate content here." msgstr "" -#: awx/sso/conf.py:996 +#: awx/sso/conf.py:993 msgid "SAML Service Provider Private Key" msgstr "" -#: awx/sso/conf.py:997 +#: awx/sso/conf.py:994 msgid "" "Create a keypair for Tower to use as a service provider (SP) and include the " "private key content here." msgstr "" -#: awx/sso/conf.py:1009 +#: awx/sso/conf.py:1005 msgid "SAML Service Provider Organization Info" msgstr "" -#: awx/sso/conf.py:1010 +#: awx/sso/conf.py:1006 msgid "" "Provide the URL, display name, and the name of your app. Refer to the " "Ansible Tower documentation for example syntax." msgstr "" -#: awx/sso/conf.py:1029 +#: awx/sso/conf.py:1024 msgid "SAML Service Provider Technical Contact" msgstr "" -#: awx/sso/conf.py:1030 +#: awx/sso/conf.py:1025 msgid "" "Provide the name and email address of the technical contact for your service " "provider. Refer to the Ansible Tower documentation for example syntax." msgstr "" -#: awx/sso/conf.py:1047 +#: awx/sso/conf.py:1041 msgid "SAML Service Provider Support Contact" msgstr "" -#: awx/sso/conf.py:1048 +#: awx/sso/conf.py:1042 msgid "" "Provide the name and email address of the support contact for your service " "provider. Refer to the Ansible Tower documentation for example syntax." msgstr "" -#: awx/sso/conf.py:1064 +#: awx/sso/conf.py:1057 msgid "SAML Enabled Identity Providers" msgstr "" -#: awx/sso/conf.py:1065 +#: awx/sso/conf.py:1058 msgid "" "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 " @@ -5213,165 +5681,129 @@ msgid "" "additional details and syntax." msgstr "" -#: awx/sso/conf.py:1102 +#: awx/sso/conf.py:1094 msgid "SAML Security Config" msgstr "" -#: awx/sso/conf.py:1103 +#: awx/sso/conf.py:1095 msgid "" "A dict of key value pairs that are passed to the underlying python-saml " "security setting https://github.com/onelogin/python-saml#settings" msgstr "" -#: awx/sso/conf.py:1135 +#: awx/sso/conf.py:1126 msgid "SAML Service Provider extra configuration data" msgstr "" -#: awx/sso/conf.py:1136 +#: awx/sso/conf.py:1127 msgid "" "A dict of key value pairs to be passed to the underlying python-saml Service " "Provider configuration setting." msgstr "" -#: awx/sso/conf.py:1149 +#: awx/sso/conf.py:1139 msgid "SAML IDP to extra_data attribute mapping" msgstr "" -#: awx/sso/conf.py:1150 +#: awx/sso/conf.py:1140 msgid "" "A list of tuples that maps IDP attributes to extra_attributes. Each " "attribute will be a list of values, even if only 1 value." msgstr "" -#: awx/sso/conf.py:1167 +#: awx/sso/conf.py:1156 msgid "SAML Organization Map" msgstr "" -#: awx/sso/conf.py:1180 +#: awx/sso/conf.py:1168 msgid "SAML Team Map" msgstr "" -#: awx/sso/conf.py:1193 +#: awx/sso/conf.py:1180 msgid "SAML Organization Attribute Mapping" msgstr "" -#: awx/sso/conf.py:1194 +#: awx/sso/conf.py:1181 msgid "Used to translate user organization membership into Tower." msgstr "" -#: awx/sso/conf.py:1211 +#: awx/sso/conf.py:1199 msgid "SAML Team Attribute Mapping" msgstr "" -#: awx/sso/conf.py:1212 +#: awx/sso/conf.py:1200 msgid "Used to translate user team membership into Tower." msgstr "" -#: awx/sso/fields.py:183 +#: awx/sso/fields.py:81 +msgid "Invalid field." +msgstr "" + +#: awx/sso/fields.py:250 #, python-brace-format msgid "Invalid connection option(s): {invalid_options}." msgstr "" -#: awx/sso/fields.py:266 +#: awx/sso/fields.py:334 msgid "Base" msgstr "" -#: awx/sso/fields.py:267 +#: awx/sso/fields.py:335 msgid "One Level" msgstr "" -#: awx/sso/fields.py:268 +#: awx/sso/fields.py:336 msgid "Subtree" msgstr "" -#: awx/sso/fields.py:286 +#: awx/sso/fields.py:354 #, python-brace-format msgid "Expected a list of three items but got {length} instead." msgstr "" -#: awx/sso/fields.py:287 +#: awx/sso/fields.py:355 #, python-brace-format msgid "Expected an instance of LDAPSearch but got {input_type} instead." msgstr "" -#: awx/sso/fields.py:323 +#: awx/sso/fields.py:391 #, python-brace-format msgid "" "Expected an instance of LDAPSearch or LDAPSearchUnion but got {input_type} " "instead." msgstr "" -#: awx/sso/fields.py:361 +#: awx/sso/fields.py:429 #, python-brace-format msgid "Invalid user attribute(s): {invalid_attrs}." msgstr "" -#: awx/sso/fields.py:378 +#: awx/sso/fields.py:447 #, python-brace-format msgid "Expected an instance of LDAPGroupType but got {input_type} instead." msgstr "" -#: awx/sso/fields.py:418 awx/sso/fields.py:465 +#: awx/sso/fields.py:487 #, python-brace-format msgid "Invalid key(s): {invalid_keys}." msgstr "" -#: awx/sso/fields.py:443 +#: awx/sso/fields.py:513 #, python-brace-format msgid "Invalid user flag: \"{invalid_flag}\"." msgstr "" -#: awx/sso/fields.py:464 -#, python-brace-format -msgid "Missing key(s): {missing_keys}." -msgstr "" - -#: awx/sso/fields.py:514 awx/sso/fields.py:631 -#, python-brace-format -msgid "Invalid key(s) for organization map: {invalid_keys}." -msgstr "" - -#: awx/sso/fields.py:532 -#, python-brace-format -msgid "Missing required key for team map: {invalid_keys}." -msgstr "" - -#: awx/sso/fields.py:533 awx/sso/fields.py:650 -#, python-brace-format -msgid "Invalid key(s) for team map: {invalid_keys}." -msgstr "" - -#: awx/sso/fields.py:649 -#, python-brace-format -msgid "Missing required key for team map: {missing_keys}." -msgstr "" - #: awx/sso/fields.py:667 #, python-brace-format -msgid "Missing required key(s) for org info record: {missing_keys}." -msgstr "" - -#: awx/sso/fields.py:680 -#, python-brace-format msgid "Invalid language code(s) for org info: {invalid_lang_codes}." msgstr "" -#: awx/sso/fields.py:699 -#, python-brace-format -msgid "Missing required key(s) for contact: {missing_keys}." -msgstr "" - -#: awx/sso/fields.py:711 -#, python-brace-format -msgid "Missing required key(s) for IdP: {missing_keys}." -msgstr "" - -#: awx/sso/pipeline.py:31 +#: awx/sso/pipeline.py:28 #, python-brace-format msgid "An account cannot be found for {0}" msgstr "" -#: awx/sso/pipeline.py:37 +#: awx/sso/pipeline.py:34 msgid "Your account is inactive" msgstr "" @@ -5410,36 +5842,8 @@ msgstr "" msgid "Resize" msgstr "" -#: awx/templates/rest_framework/base.html:37 -msgid "navbar" -msgstr "" - -#: awx/templates/rest_framework/base.html:75 -msgid "content" -msgstr "" - -#: awx/templates/rest_framework/base.html:78 -msgid "request form" -msgstr "" - -#: awx/templates/rest_framework/base.html:134 -msgid "Filters" -msgstr "" - -#: awx/templates/rest_framework/base.html:139 -msgid "main content" -msgstr "" - -#: awx/templates/rest_framework/base.html:155 -msgid "request info" -msgstr "" - -#: awx/templates/rest_framework/base.html:159 -msgid "response info" -msgstr "" - -#: awx/ui/apps.py:9 awx/ui/conf.py:22 awx/ui/conf.py:36 awx/ui/conf.py:51 -#: awx/ui/conf.py:63 awx/ui/conf.py:73 +#: awx/ui/apps.py:9 awx/ui/conf.py:22 awx/ui/conf.py:36 awx/ui/conf.py:50 +#: awx/ui/conf.py:61 awx/ui/conf.py:71 msgid "UI" msgstr "" @@ -5456,11 +5860,11 @@ msgid "Detailed" msgstr "" #: awx/ui/conf.py:20 -msgid "Analytics Tracking State" +msgid "User Analytics Tracking State" msgstr "" #: awx/ui/conf.py:21 -msgid "Enable or Disable Analytics Tracking." +msgid "Enable or Disable User Analytics Tracking." msgstr "" #: awx/ui/conf.py:31 @@ -5471,46 +5875,46 @@ msgstr "" msgid "" "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, as custom HTML or other markup languages are " -"not supported." +"added must be in plain text or an HTML fragment, as other markup languages " +"are not supported." msgstr "" -#: awx/ui/conf.py:46 +#: awx/ui/conf.py:45 msgid "Custom Logo" msgstr "" -#: awx/ui/conf.py:47 +#: awx/ui/conf.py:46 msgid "" "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." msgstr "" -#: awx/ui/conf.py:60 +#: awx/ui/conf.py:58 msgid "Max Job Events Retrieved by UI" msgstr "" -#: awx/ui/conf.py:61 +#: awx/ui/conf.py:59 msgid "" "Maximum number of job events for the UI to retrieve within a single request." msgstr "" -#: awx/ui/conf.py:70 +#: awx/ui/conf.py:68 msgid "Enable Live Updates in the UI" msgstr "" -#: awx/ui/conf.py:71 +#: awx/ui/conf.py:69 msgid "" "If disabled, the page will not refresh when events are received. Reloading " "the page will be required to get the latest details." msgstr "" -#: awx/ui/fields.py:29 +#: awx/ui/fields.py:30 msgid "" "Invalid format for custom logo. Must be a data URL with a base64-encoded " "GIF, PNG or JPEG image." msgstr "" -#: awx/ui/fields.py:30 +#: awx/ui/fields.py:31 msgid "Invalid base64-encoded data in data URL." msgstr "" diff --git a/awx/locale/es/LC_MESSAGES/django.po b/awx/locale/es/LC_MESSAGES/django.po index be81d26dc299..4cba6a5d5585 100644 --- a/awx/locale/es/LC_MESSAGES/django.po +++ b/awx/locale/es/LC_MESSAGES/django.po @@ -2,28 +2,20 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. -# Carlos Munoz , 2017. #zanata -# Froebel Flores , 2017. #zanata -# edrh01 , 2017. #zanata -# mkim , 2017. #zanata -# plocatelli , 2017. #zanata -# trh01 , 2017. #zanata -# edrh01 , 2018. #zanata -# trh01 , 2018. #zanata +# +#, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-08-14 13:52+0000\n" -"PO-Revision-Date: 2018-08-16 04:11+0000\n" -"Last-Translator: trh01 \n" -"Language-Team: \n" +"POT-Creation-Date: 2020-05-28 21:45+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: es \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: es\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Zanata 4.6.0\n" #: awx/api/conf.py:15 msgid "Idle Time Force Log Out" @@ -33,15 +25,13 @@ msgstr "Tiempo de inactividad fuerza desconexión" msgid "" "Number of seconds that a user is inactive before they will need to login " "again." -msgstr "" -"Número de segundos que un usuario es inactivo antes de que ellos vuelvan a " -"conectarse de nuevo." +msgstr "Número de segundos que un usuario es inactivo antes de que ellos vuelvan a conectarse de nuevo." -#: awx/api/conf.py:17 awx/api/conf.py:26 awx/api/conf.py:34 awx/api/conf.py:47 -#: awx/api/conf.py:59 awx/sso/conf.py:85 awx/sso/conf.py:96 -#: awx/sso/conf.py:108 awx/sso/conf.py:123 +#: awx/api/conf.py:17 awx/api/conf.py:26 awx/api/conf.py:34 awx/api/conf.py:50 +#: awx/api/conf.py:62 awx/api/conf.py:74 awx/sso/conf.py:97 awx/sso/conf.py:108 +#: awx/sso/conf.py:120 awx/sso/conf.py:135 msgid "Authentication" -msgstr "Autenticación" +msgstr "Identificación" #: awx/api/conf.py:24 msgid "Maximum number of simultaneous logged in sessions" @@ -51,151 +41,152 @@ msgstr "Número máximo de sesiones activas en simultáneo" msgid "" "Maximum number of simultaneous logged in sessions a user may have. To " "disable enter -1." -msgstr "" -"Número máximo de sesiones activas en simultáneo que un usuario puede tener. " -"Para deshabilitar, introduzca -1." +msgstr "Número máximo de sesiones activas en simultáneo que un usuario puede tener. Para deshabilitar, introduzca -1." #: awx/api/conf.py:32 msgid "Enable HTTP Basic Auth" -msgstr "Habilitar autenticación básica HTTP" +msgstr "Habilitar autentificación básica HTTP" #: awx/api/conf.py:33 msgid "Enable HTTP Basic Auth for the API Browser." -msgstr "Habilitar autenticación básica HTTP para la navegación API." +msgstr "Habilitar autentificación básica HTTP para la navegación API." -#: awx/api/conf.py:42 +#: awx/api/conf.py:43 msgid "OAuth 2 Timeout Settings" msgstr "Configuración de tiempo de expiración OAuth 2" -#: awx/api/conf.py:43 +#: awx/api/conf.py:44 msgid "" "Dictionary for customizing OAuth 2 timeouts, available items are " "`ACCESS_TOKEN_EXPIRE_SECONDS`, the duration of access tokens in the number " -"of seconds, and `AUTHORIZATION_CODE_EXPIRE_SECONDS`, the duration of " -"authorization grants in the number of seconds." -msgstr "" -"Diccionario para personalizar los tiempos de expiración OAuth 2; los " -"elementos disponibles son `ACCESS_TOKEN_EXPIRE_SECONDS`: duración de los " -"tokens de acceso en cantidad de segundos y " -"`AUTHORIZATION_CODE_EXPIRE_SECONDS`: duración de las autorizaciones " -"otorgadas en cantidad de segundos." +"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." +msgstr "Diccionario para personalizar los tiempos de espera de OAuth2; los elementos disponibles son `ACCESS_TOKEN_EXPIRE_SECONDS`, la duración de los tokens de acceso en cantidad de segundos; `AUTHORIZATION_CODE_EXPIRE_SECONDS`, la duración de los códigos de autorización en cantidad de segundos; y `REFRESH_TOKEN_EXPIRE_SECONDS`, la duración de los tokens de actualización, después de los tokens de acceso expirados, en cantidad de segundos." -#: awx/api/conf.py:54 +#: awx/api/conf.py:57 msgid "Allow External Users to Create OAuth2 Tokens" msgstr "Permitir que los usuarios externos creen tokens OAuth2" -#: awx/api/conf.py:55 +#: awx/api/conf.py:58 msgid "" "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." -msgstr "" -"Por motivos de seguridad, los usuarios de proveedores de autenticación " -"externos (LDAP, SAML, SSO, Radius y otros) no pueden crear tokens OAuth2. " -"Habilite este ajuste para cambiar este comportamiento. Los tokens existentes" -" no se eliminarán cuando desactive este ajuste." +"behavior, enable this setting. Existing tokens will not be deleted when this " +"setting is toggled off." +msgstr "Por motivos de seguridad, los usuarios de proveedores de autenticación externos (LDAP, SAML, SSO, Radius y otros) no tienen permitido crear tokens de OAuth2. Habilite este ajuste para cambiar este comportamiento. Los tokens existentes no se eliminarán cuando se desactive este ajuste." + +#: awx/api/conf.py:71 +msgid "Login redirect override URL" +msgstr "URL de invalidación de redireccionamiento de inicio de sesión" + +#: awx/api/conf.py:72 +msgid "" +"URL to which unauthorized users will be redirected to log in. If blank, " +"users will be sent to the Tower login page." +msgstr "URL a la que los usuarios no autorizados serán redirigidos para iniciar sesión. Si está en blanco, los usuarios serán enviados a la página de inicio de sesión de Tower." #: awx/api/exceptions.py:20 msgid "Resource is being used by running jobs." -msgstr "El recurso está siendo usado por tareas en ejecución." +msgstr "El recurso está siendo usado por trabajos en ejecución." #: awx/api/fields.py:81 #, python-brace-format msgid "Invalid key names: {invalid_key_names}" msgstr "Nombres de claves no válidos: {invalid_key_names}" -#: awx/api/fields.py:107 +#: awx/api/fields.py:111 msgid "Credential {} does not exist" msgstr "La credencial {} no existe" -#: awx/api/filters.py:97 +#: awx/api/filters.py:82 msgid "No related model for field {}." msgstr "Sin modelo relacionado para el campo {}." -#: awx/api/filters.py:114 +#: awx/api/filters.py:99 msgid "Filtering on password fields is not allowed." msgstr "Filtrar sobre campos de contraseña no está permitido." -#: awx/api/filters.py:126 awx/api/filters.py:128 +#: awx/api/filters.py:111 awx/api/filters.py:113 #, python-format msgid "Filtering on %s is not allowed." msgstr "Filtrar sobre %s no está permitido." -#: awx/api/filters.py:131 +#: awx/api/filters.py:116 msgid "Loops not allowed in filters, detected on field {}." -msgstr "Bucles no permitidos en los filtros; detectados en el campo {}." +msgstr "Bucles no permitidos en los filtros, detectados en el campo {}." #: awx/api/filters.py:160 msgid "Query string field name not provided." msgstr "Nombre de campo de la cadena de petición no provisto." -#: awx/api/filters.py:187 +#: awx/api/filters.py:192 #, python-brace-format msgid "Invalid {field_name} id: {field_id}" -msgstr "ID de {field_name} no válida: {field_id}" +msgstr "ID{field_name} no válido: {field_id}" -#: awx/api/filters.py:326 -#, python-format -msgid "cannot filter on kind %s" -msgstr "no se puede filtrar en el tipo %s" +#: awx/api/filters.py:333 +msgid "" +"Cannot apply role_level filter to this list because its model does not use " +"roles for access control." +msgstr "No se puede aplicar el filtro role_level a esta lista debido a que su modelo no usa roles para el control de acceso." -#: awx/api/generics.py:197 +#: awx/api/generics.py:182 msgid "" "You did not use correct Content-Type in your HTTP request. If you are using " "our REST API, the Content-Type must be application/json" -msgstr "" -"No utilizó el Tipo de contenido correcto en su solicitud HTTP. Si está " -"usando nuestra API REST, el Tipo de contenido debe ser aplicación/json." +msgstr "No utilizó el Tipo de contenido correcto en su solicitud HTTP. Si está usando nuestra API REST, el Tipo de contenido debe ser aplicación/json." -#: awx/api/generics.py:635 awx/api/generics.py:697 +#: awx/api/generics.py:623 awx/api/generics.py:685 msgid "\"id\" field must be an integer." msgstr "El campo \"id\" debe ser un número entero." -#: awx/api/generics.py:694 +#: awx/api/generics.py:682 msgid "\"id\" is required to disassociate" msgstr "\"id\" es necesario para desasociar" -#: awx/api/generics.py:745 +#: awx/api/generics.py:733 msgid "{} 'id' field is missing." msgstr "Falta el campo {} 'id'." -#: awx/api/metadata.py:51 +#: awx/api/metadata.py:58 msgid "Database ID for this {}." -msgstr "ID de la base de datos para esto {}." +msgstr "ID de la base de datos para esto {}" -#: awx/api/metadata.py:52 +#: awx/api/metadata.py:59 msgid "Name of this {}." -msgstr "Nombre de esto {}." +msgstr "Nombre de esto {}" -#: awx/api/metadata.py:53 +#: awx/api/metadata.py:60 msgid "Optional description of this {}." -msgstr "Descripción opcional de esto {}." +msgstr "Descripción opcional de esto {}" -#: awx/api/metadata.py:54 +#: awx/api/metadata.py:61 msgid "Data type for this {}." -msgstr "Tipo de datos para esto {}." +msgstr "Tipo de datos para esto {}" -#: awx/api/metadata.py:55 +#: awx/api/metadata.py:62 msgid "URL for this {}." -msgstr "URL para esto {}." +msgstr "URL para esto {}" -#: awx/api/metadata.py:56 +#: awx/api/metadata.py:63 msgid "Data structure with URLs of related resources." -msgstr "Estructura de datos con URL de recursos relacionados." +msgstr "Estructura de datos con URLs de recursos relacionados." -#: awx/api/metadata.py:57 -msgid "Data structure with name/description for related resources." -msgstr "" -"Estructura de datos con nombre/descripción para recursos relacionados." +#: awx/api/metadata.py:64 +msgid "" +"Data structure with name/description for related resources. The output for " +"some objects may be limited for performance reasons." +msgstr "Estructura de datos con nombre/descripción de los recursos relacionados. La salida de algunos objetos puede estar limitada por motivos de rendimiento." -#: awx/api/metadata.py:58 +#: awx/api/metadata.py:66 msgid "Timestamp when this {} was created." -msgstr "Fecha y hora en que se creó esto {}." +msgstr "Fecha y hora cuando este {} fue creado." -#: awx/api/metadata.py:59 +#: awx/api/metadata.py:67 msgid "Timestamp when this {} was last modified." -msgstr "Fecha y hora en que se modificó esto {} más recientemente." +msgstr "Fecha y hora cuando este {} fue modificado más recientemente." #: awx/api/parsers.py:33 msgid "JSON parse error - not a JSON object" @@ -206,1307 +197,1313 @@ msgstr "Error de análisis JSON; no es un objeto JSON" msgid "" "JSON parse error - %s\n" "Possible cause: trailing comma." -msgstr "" -"Error de análisis JSON - %s\n" +msgstr "Error de análisis JSON - %s\n" "Posible causa: coma final." -#: awx/api/serializers.py:155 +#: awx/api/serializers.py:169 msgid "" -"The original object is already named {}, a copy from it cannot have the same" -" name." -msgstr "" -"El objeto original ya tiene el nombre {}, por lo que una copia de este no " -"puede tener el mismo nombre." +"The original object is already named {}, a copy from it cannot have the same " +"name." +msgstr "El objeto original ya tiene el nombre {}, por lo que una copia de este no puede tener el mismo nombre." -#: awx/api/serializers.py:290 +#: awx/api/serializers.py:302 #, python-format msgid "Cannot use dictionary for %s" msgstr "No se puede usar el diccionario para %s" -#: awx/api/serializers.py:307 +#: awx/api/serializers.py:316 msgid "Playbook Run" -msgstr "Ejecutar Playbook" +msgstr "Ejecución de playbook" -#: awx/api/serializers.py:308 +#: awx/api/serializers.py:317 msgid "Command" msgstr "Comando" -#: awx/api/serializers.py:309 awx/main/models/unified_jobs.py:526 +#: awx/api/serializers.py:318 awx/main/models/unified_jobs.py:547 msgid "SCM Update" -msgstr "Actualización SCM" +msgstr "Actualizar SCM" -#: awx/api/serializers.py:310 +#: awx/api/serializers.py:319 msgid "Inventory Sync" -msgstr "Sincronizar inventario" +msgstr "Sincronización de inventario" -#: awx/api/serializers.py:311 +#: awx/api/serializers.py:320 msgid "Management Job" msgstr "Trabajo de gestión" -#: awx/api/serializers.py:312 +#: awx/api/serializers.py:321 msgid "Workflow Job" msgstr "Tarea en flujo de trabajo" -#: awx/api/serializers.py:313 +#: awx/api/serializers.py:322 msgid "Workflow Template" msgstr "Plantilla de flujo de trabajo" -#: awx/api/serializers.py:314 +#: awx/api/serializers.py:323 msgid "Job Template" msgstr "Plantilla de trabajo" -#: awx/api/serializers.py:714 +#: awx/api/serializers.py:709 msgid "" "Indicates whether all of the events generated by this unified job have been " "saved to the database." -msgstr "" -"Indica si todos los eventos generados por esta tarea unificada se guardaron " -"en la base de datos." +msgstr "Indica si todos los eventos generados por esta tarea unificada se guardaron en la base de datos." -#: awx/api/serializers.py:879 +#: awx/api/serializers.py:878 msgid "Write-only field used to change the password." -msgstr "Campo de solo escritura utilizado para cambiar la contraseña." +msgstr "Campo de sólo escritura utilizado para cambiar la contraseña." -#: awx/api/serializers.py:881 +#: awx/api/serializers.py:880 msgid "Set if the account is managed by an external service" msgstr "Establecer si la cuenta es administrada por un servicio externo" -#: awx/api/serializers.py:905 +#: awx/api/serializers.py:907 msgid "Password required for new User." msgstr "Contraseña requerida para un usuario nuevo." -#: awx/api/serializers.py:981 +#: awx/api/serializers.py:992 #, python-format msgid "Unable to change %s on user managed by LDAP." -msgstr "Incapaz de cambiar %s en usuario gestionado por LDAP." +msgstr "No se puede cambiar %s en el usuario gestionado por LDAP." -#: awx/api/serializers.py:1067 +#: awx/api/serializers.py:1088 msgid "Must be a simple space-separated string with allowed scopes {}." -msgstr "" -"Debe ser una cadena simple separada por espacios con alcances permitidos {}." +msgstr "Debe ser una cadena simple separada por espacios con alcances permitidos {}." -#: awx/api/serializers.py:1167 +#: awx/api/serializers.py:1186 msgid "Authorization Grant Type" msgstr "Tipo de autorización" -#: awx/api/serializers.py:1169 awx/main/models/credential/__init__.py:1064 +#: awx/api/serializers.py:1188 awx/main/credential_plugins/azure_kv.py:30 +#: awx/main/models/credential/__init__.py:960 msgid "Client Secret" -msgstr "Secreto del cliente" +msgstr "Pregunta secreta del cliente" -#: awx/api/serializers.py:1172 +#: awx/api/serializers.py:1191 msgid "Client Type" msgstr "Tipo de cliente" -#: awx/api/serializers.py:1175 +#: awx/api/serializers.py:1194 msgid "Redirect URIs" msgstr "Redirigir URI" -#: awx/api/serializers.py:1178 +#: awx/api/serializers.py:1197 msgid "Skip Authorization" msgstr "Omitir autorización" -#: awx/api/serializers.py:1290 +#: awx/api/serializers.py:1303 +msgid "Cannot change max_hosts." +msgstr "No se puede modificar max_hosts." + +#: awx/api/serializers.py:1336 msgid "This path is already being used by another manual project." msgstr "Esta ruta ya está siendo usada por otro proyecto manual." -#: awx/api/serializers.py:1316 -msgid "This field has been deprecated and will be removed in a future release" -msgstr "Este campo ya no se utiliza y será retirado en un futuro lanzamiento." +#: awx/api/serializers.py:1338 +msgid "SCM refspec can only be used with git projects." +msgstr "SCM refspec solo puede usarse con proyectos git." -#: awx/api/serializers.py:1375 -msgid "Organization is missing" -msgstr "Organización no encontrada" +#: awx/api/serializers.py:1415 +msgid "" +"One or more job templates depend on branch override behavior for this " +"project (ids: {})." +msgstr "Una o más plantillas de trabajo dependen del comportamiento de invalidación de ramas para este proyecto (ids: {})." -#: awx/api/serializers.py:1379 +#: awx/api/serializers.py:1422 msgid "Update options must be set to false for manual projects." -msgstr "" -"Las opciones de actualización se deben establecer en false para proyectos " -"manuales." +msgstr "Opciones de actualización deben ser establecidas a false para proyectos manuales." -#: awx/api/serializers.py:1385 +#: awx/api/serializers.py:1428 msgid "Array of playbooks available within this project." msgstr "Colección de playbooks disponibles dentro de este proyecto." -#: awx/api/serializers.py:1404 +#: awx/api/serializers.py:1447 msgid "" "Array of inventory files and directories available within this project, not " "comprehensive." -msgstr "" -"Colección de archivos de inventario y directorios disponibles dentro de este" -" proyecto, no global." +msgstr "Colección de archivos de inventario y directorios disponibles dentro de este proyecto, no global." -#: awx/api/serializers.py:1452 awx/api/serializers.py:3247 -#: awx/api/serializers.py:3454 +#: awx/api/serializers.py:1495 awx/api/serializers.py:3048 +#: awx/api/serializers.py:3260 msgid "A count of hosts uniquely assigned to each status." msgstr "Un número de hosts asignados de manera única a cada estado." -#: awx/api/serializers.py:1455 awx/api/serializers.py:3250 +#: awx/api/serializers.py:1498 awx/api/serializers.py:3051 msgid "A count of all plays and tasks for the job run." msgstr "La cantidad de reproducciones y tareas para la ejecución del trabajo." -#: awx/api/serializers.py:1570 +#: awx/api/serializers.py:1625 msgid "Smart inventories must specify host_filter" msgstr "Los inventarios inteligentes deben especificar host_filter" -#: awx/api/serializers.py:1674 +#: awx/api/serializers.py:1713 #, python-format msgid "Invalid port specification: %s" -msgstr "Especificación de puerto no válida: %s" +msgstr "Especificación de puerto no válido: %s" -#: awx/api/serializers.py:1685 +#: awx/api/serializers.py:1724 msgid "Cannot create Host for Smart Inventory" msgstr "No es posible crear un host para el Inventario inteligente" -#: awx/api/serializers.py:1797 +#: awx/api/serializers.py:1808 msgid "Invalid group name." -msgstr "Nombre de grupo no válido." +msgstr "Nombre de grupo inválido." -#: awx/api/serializers.py:1802 +#: awx/api/serializers.py:1813 msgid "Cannot create Group for Smart Inventory" msgstr "No es posible crear un grupo para el Inventario inteligente" -#: awx/api/serializers.py:1877 +#: awx/api/serializers.py:1888 msgid "" "Script must begin with a hashbang sequence: i.e.... #!/usr/bin/env python" -msgstr "" -"El script debe empezar con una secuencia hashbang, es decir.... " -"#!/usr/bin/env python" +msgstr "El script debe empezar con una secuencia hashbang, p.e.... #!/usr/bin/env python" + +#: awx/api/serializers.py:1917 +msgid "Cloud credential to use for inventory updates." +msgstr "Credencial de la nube que se usa para actualizaciones de inventario." -#: awx/api/serializers.py:1926 +#: awx/api/serializers.py:1938 msgid "`{}` is a prohibited environment variable" msgstr "`{}` es una variable de entorno prohibida" -#: awx/api/serializers.py:1937 +#: awx/api/serializers.py:1949 msgid "If 'source' is 'custom', 'source_script' must be provided." -msgstr "Si 'source' es 'custom', se debe especificar 'source_script'." +msgstr "Si 'source' es 'custom', 'source_script' debe ser especificado." -#: awx/api/serializers.py:1943 +#: awx/api/serializers.py:1955 msgid "Must provide an inventory." msgstr "Debe proporcionar un inventario." -#: awx/api/serializers.py:1947 +#: awx/api/serializers.py:1959 msgid "" "The 'source_script' does not belong to the same organization as the " "inventory." -msgstr "" -"El 'source_script' no pertenece a la misma organización que el inventario." +msgstr "El 'source_script' no pertenece a la misma organización que el inventario." -#: awx/api/serializers.py:1949 +#: awx/api/serializers.py:1961 msgid "'source_script' doesn't exist." msgstr "'source_script' no existe." -#: awx/api/serializers.py:1985 -msgid "Automatic group relationship, will be removed in 3.3" -msgstr "Relación de grupo automática; se eliminará en 3.3" - -#: awx/api/serializers.py:2072 +#: awx/api/serializers.py:2063 msgid "Cannot use manual project for SCM-based inventory." msgstr "No se puede usar el proyecto manual para el inventario basado en SCM." -#: awx/api/serializers.py:2078 -msgid "" -"Manual inventory sources are created automatically when a group is created " -"in the v1 API." -msgstr "" -"Las fuentes de inventario manuales se crean automáticamente cuando se crea " -"un grupo en la API v1." - -#: awx/api/serializers.py:2083 +#: awx/api/serializers.py:2068 msgid "Setting not compatible with existing schedules." msgstr "Configuración no compatible con programaciones existentes." -#: awx/api/serializers.py:2088 +#: awx/api/serializers.py:2073 msgid "Cannot create Inventory Source for Smart Inventory" -msgstr "" -"No es posible crear una fuente de inventarios para el Inventario inteligente" +msgstr "No es posible crear una fuente de inventarios para el Inventario inteligente" -#: awx/api/serializers.py:2139 +#: awx/api/serializers.py:2121 +msgid "Project required for scm type sources." +msgstr "Se requiere un proyecto para las fuentes de tipo scm." + +#: awx/api/serializers.py:2130 #, python-format msgid "Cannot set %s if not SCM type." msgstr "No es posible definir %s si no es de tipo SCM." -#: awx/api/serializers.py:2414 +#: awx/api/serializers.py:2200 +msgid "The project used for this job." +msgstr "El proyecto utilizado para este trabajo." + +#: awx/api/serializers.py:2455 msgid "Modifications not allowed for managed credential types" -msgstr "" -"Modificaciones no permitidas para los tipos de credenciales administradas" +msgstr "Modificaciones no permitidas para los tipos de credenciales administradas" -#: awx/api/serializers.py:2419 +#: awx/api/serializers.py:2467 msgid "" "Modifications to inputs are not allowed for credential types that are in use" -msgstr "" -"No se permiten las modificaciones a entradas para los tipos de credenciales " -"que están en uso" +msgstr "No se permiten las modificaciones a entradas para los tipos de credenciales que están en uso" -#: awx/api/serializers.py:2425 +#: awx/api/serializers.py:2472 #, python-format msgid "Must be 'cloud' or 'net', not %s" msgstr "Debe ser 'cloud' o 'net', no %s" -#: awx/api/serializers.py:2431 +#: awx/api/serializers.py:2478 msgid "'ask_at_runtime' is not supported for custom credentials." -msgstr "" -"'ask_at_runtime' no es compatible con las credenciales personalizadas." +msgstr "'ask_at_runtime' no es compatible con las credenciales personalizadas." -#: awx/api/serializers.py:2502 +#: awx/api/serializers.py:2526 msgid "Credential Type" msgstr "Tipo de credencial" -#: awx/api/serializers.py:2617 -#, python-format -msgid "\"%s\" is not a valid choice" -msgstr "\"%s\" no es una opción válida" - -#: awx/api/serializers.py:2636 -#, python-brace-format -msgid "'{field_name}' is not a valid field for {credential_type_name}" -msgstr "'{field_name}' no es un campo válido para {credential_type_name}" - -#: awx/api/serializers.py:2657 +#: awx/api/serializers.py:2607 msgid "" -"You cannot change the credential type of the credential, as it may break the" -" functionality of the resources using it." -msgstr "" -"No puede cambiar el tipo de credencial, ya que puede interrumpir la " -"funcionalidad de los recursos que la usan." +"You cannot change the credential type of the credential, as it may break the " +"functionality of the resources using it." +msgstr "No puede cambiar el tipo de credencial, ya que puede interrumpir la funcionalidad de los recursos que la usan." -#: awx/api/serializers.py:2669 +#: awx/api/serializers.py:2619 msgid "" "Write-only field used to add user to owner role. If provided, do not give " "either team or organization. Only valid for creation." -msgstr "" -"Campo de solo escritura utilizado para añadir usuario a rol de propietario. " -"Si se indica, no otorgar equipo u organización. Solo válido para creación." +msgstr "Campo de sólo escritura utilizado para añadir usuario a rol de propietario. Si se indica, no otorgar equipo u organización. Sólo válido para creación." -#: awx/api/serializers.py:2674 +#: awx/api/serializers.py:2624 msgid "" "Write-only field used to add team to owner role. If provided, do not give " "either user or organization. Only valid for creation." -msgstr "" -"Campo de solo escritura para añadir equipo a un rol propietario. Si se " -"indica, no otorgar usuario u organización. Solo válido para creación." +msgstr "Campo de sólo escritura para añadir equipo a un rol propietario.Si se indica, no otorgar usuario u organización. Sólo válido para creación." -#: awx/api/serializers.py:2679 +#: awx/api/serializers.py:2629 msgid "" -"Inherit permissions from organization roles. If provided on creation, do not" -" give either user or team." -msgstr "" -"Permisos heredados desde roles de organización. Si se indica, no otorgar " -"usuario o equipo." +"Inherit permissions from organization roles. If provided on creation, do not " +"give either user or team." +msgstr "Permisos heredados desde roles de organización. Si se indica, no otorgar usuario o equipo." -#: awx/api/serializers.py:2695 +#: awx/api/serializers.py:2645 msgid "Missing 'user', 'team', or 'organization'." -msgstr "'User', 'team' u 'organization' no encontrados." +msgstr "No encontrado 'user', 'team' u 'organization'" -#: awx/api/serializers.py:2735 +#: awx/api/serializers.py:2662 msgid "" "Credential organization must be set and match before assigning to a team" -msgstr "" -"Se debe establecer y corresponder la organización de credenciales antes de " -"asignarlas a un equipo" - -#: awx/api/serializers.py:2936 -msgid "You must provide a cloud credential." -msgstr "Debe proporcionar una credencial de nube." - -#: awx/api/serializers.py:2937 -msgid "You must provide a network credential." -msgstr "Debe indicar un credencial de red." +msgstr "Credenciales de organización deben ser establecidas y coincidir antes de ser asignadas a un equipo" -#: awx/api/serializers.py:2938 awx/main/models/jobs.py:155 -msgid "You must provide an SSH credential." -msgstr "Debe proporcionar una credencial SSH." - -#: awx/api/serializers.py:2939 -msgid "You must provide a vault credential." -msgstr "Debe proporcionar una credencial de Vault." - -#: awx/api/serializers.py:2958 +#: awx/api/serializers.py:2793 msgid "This field is required." msgstr "Este campo es obligatorio." -#: awx/api/serializers.py:2960 awx/api/serializers.py:2962 +#: awx/api/serializers.py:2802 msgid "Playbook not found for project." msgstr "Playbook no encontrado para el proyecto." -#: awx/api/serializers.py:2964 +#: awx/api/serializers.py:2804 msgid "Must select playbook for project." msgstr "Debe seleccionar un playbook para el proyecto." -#: awx/api/serializers.py:3045 +#: awx/api/serializers.py:2806 awx/api/serializers.py:2808 +msgid "Project does not allow overriding branch." +msgstr "El proyecto no permite la invalidación de la rama." + +#: awx/api/serializers.py:2845 +msgid "Must be a Personal Access Token." +msgstr "Debe ser un Token de acceso personal." + +#: awx/api/serializers.py:2848 +msgid "Must match the selected webhook service." +msgstr "Debe coincidir con el servicio de webhook seleccionado." + +#: awx/api/serializers.py:2919 msgid "Cannot enable provisioning callback without an inventory set." -msgstr "" -"No puede habilitar la callback de aprovisionamiento sin un conjunto de " -"inventario." +msgstr "No puede habilitar la callback de aprovisionamiento sin un conjunto de inventario." -#: awx/api/serializers.py:3048 +#: awx/api/serializers.py:2922 msgid "Must either set a default value or ask to prompt on launch." -msgstr "" -"Debe establecer un valor por defecto o preguntar por valor al ejecutar." - -#: awx/api/serializers.py:3050 awx/main/models/jobs.py:310 -msgid "Job types 'run' and 'check' must have assigned a project." -msgstr "Tipos de trabajo 'run' y 'check' deben tener asignado un proyecto." +msgstr "Debe establecer un valor por defecto o preguntar por valor al ejecutar." -#: awx/api/serializers.py:3169 -msgid "Invalid job template." -msgstr "Plantilla de trabajo no válida." +#: awx/api/serializers.py:2924 awx/main/models/jobs.py:299 +msgid "Job Templates must have a project assigned." +msgstr "Las plantillas de trabajo deben tener un proyecto asignado." -#: awx/api/serializers.py:3290 +#: awx/api/serializers.py:3092 msgid "No change to job limit" msgstr "Sin cambios en el límite de tareas" -#: awx/api/serializers.py:3291 +#: awx/api/serializers.py:3093 msgid "All failed and unreachable hosts" msgstr "Todos los hosts fallidos y sin comunicación" -#: awx/api/serializers.py:3306 +#: awx/api/serializers.py:3108 msgid "Missing passwords needed to start: {}" msgstr "Se necesitan las contraseñas faltantes para iniciar: {}" -#: awx/api/serializers.py:3325 +#: awx/api/serializers.py:3127 msgid "Relaunch by host status not available until job finishes running." -msgstr "" -"Relanzamiento por estado de host no disponible hasta que la tarea termine de" -" ejecutarse." +msgstr "Relanzamiento por estado de host no disponible hasta que la tarea termine de ejecutarse." -#: awx/api/serializers.py:3339 +#: awx/api/serializers.py:3141 msgid "Job Template Project is missing or undefined." msgstr "Proyecto en la plantilla de trabajo no encontrado o no definido." -#: awx/api/serializers.py:3341 +#: awx/api/serializers.py:3143 msgid "Job Template Inventory is missing or undefined." msgstr "Inventario en la plantilla de trabajo no encontrado o no definido." -#: awx/api/serializers.py:3379 -msgid "" -"Unknown, job may have been ran before launch configurations were saved." -msgstr "" -"Desconocido; este trabajo pudo haberse ejecutado antes de guardar la " -"configuración de lanzamiento." +#: awx/api/serializers.py:3181 +msgid "Unknown, job may have been ran before launch configurations were saved." +msgstr "Desconocido; este trabajo pudo haberse ejecutado antes de guardar la configuración de lanzamiento." -#: awx/api/serializers.py:3446 awx/main/tasks.py:2297 +#: awx/api/serializers.py:3252 awx/main/tasks.py:2800 awx/main/tasks.py:2818 msgid "{} are prohibited from use in ad hoc commands." msgstr "{} tienen uso prohibido en comandos ad hoc." -#: awx/api/serializers.py:3534 awx/api/views.py:4893 +#: awx/api/serializers.py:3340 awx/api/views/__init__.py:4243 #, python-brace-format msgid "" "Standard Output too large to display ({text_size} bytes), only download " "supported for sizes over {supported_size} bytes." -msgstr "" -"La salida estándar es demasiado larga para visualizarse ({text_size} bytes);" -" la descarga solo se admite para tamaños por encima de {supported_size} " -"bytes." +msgstr "La salida estándar es demasiado larga para visualizarse ({text_size} bytes); solo se admite la descarga para tamaños superiores a {supported_size} bytes." -#: awx/api/serializers.py:3727 +#: awx/api/serializers.py:3653 msgid "Provided variable {} has no database value to replace with." -msgstr "" -"La variable {} provista no tiene un valor de base de datos con qué " -"reemplazarla." +msgstr "La variable {} provista no tiene un valor de base de datos con qué reemplazarla." -#: awx/api/serializers.py:3745 -#, python-brace-format -msgid "\"$encrypted$ is a reserved keyword, may not be used for {var_name}.\"" -msgstr "" -"\"$encrypted$ es una palabra clave reservada y no puede utilizarse para " -"{var_name}\"." +#: awx/api/serializers.py:3671 +msgid "\"$encrypted$ is a reserved keyword, may not be used for {}.\"" +msgstr "\"$encrypted$ es una palabra clave reservada y no puede usarse para {}\"." -#: awx/api/serializers.py:3815 -#, python-format -msgid "Cannot nest a %s inside a WorkflowJobTemplate" -msgstr "No es posible anidar un %s dentro de un WorkflowJobTemplate" +#: awx/api/serializers.py:4078 +msgid "A project is required to run a job." +msgstr "Se requiere un proyecto para ejecutar una tarea." -#: awx/api/serializers.py:3822 awx/api/views.py:818 -msgid "Related template is not configured to accept credentials on launch." -msgstr "" -"La plantilla relacionada no está configurada para aceptar credenciales " -"durante el lanzamiento." +#: awx/api/serializers.py:4080 +msgid "Missing a revision to run due to failed project update." +msgstr "Falta una revisión para ejecutar debido a un error en la actualización del proyecto." -#: awx/api/serializers.py:4282 +#: awx/api/serializers.py:4084 msgid "The inventory associated with this Job Template is being deleted." -msgstr "" -"Se está eliminando el inventario asociado con esta plantilla de trabajo." +msgstr "Se está eliminando el inventario asociado con esta plantilla de trabajo." -#: awx/api/serializers.py:4284 +#: awx/api/serializers.py:4086 awx/api/serializers.py:4202 msgid "The provided inventory is being deleted." msgstr "El inventario provisto se está eliminando." -#: awx/api/serializers.py:4292 +#: awx/api/serializers.py:4094 msgid "Cannot assign multiple {} credentials." msgstr "No se pueden asignar múltiples credenciales {}." -#: awx/api/serializers.py:4296 +#: awx/api/serializers.py:4098 msgid "Cannot assign a Credential of kind `{}`" msgstr "No puede asignar una credencial del tipo `{}`" -#: awx/api/serializers.py:4309 +#: awx/api/serializers.py:4111 msgid "" "Removing {} credential at launch time without replacement is not supported. " "Provided list lacked credential(s): {}." -msgstr "" -"No se admite quitar la credencial {} en el momento de lanzamiento sin " -"reemplazo. La lista provista no contaba con credencial(es): {}." +msgstr "No se admite quitar la credencial {} en el momento de lanzamiento sin reemplazo. La lista provista no contaba con credencial(es): {}." -#: awx/api/serializers.py:4435 +#: awx/api/serializers.py:4200 +msgid "The inventory associated with this Workflow is being deleted." +msgstr "Se está eliminando el inventario asociado con este flujo de trabajo." + +#: awx/api/serializers.py:4271 +msgid "Message type '{}' invalid, must be either 'message' or 'body'" +msgstr "El tipo de mensaje '{}' no es válido, debe ser 'mensaje' o 'cuerpo'." + +#: awx/api/serializers.py:4277 +msgid "Expected string for '{}', found {}, " +msgstr "Cadena esperada para '{}', se encontró {}," + +#: awx/api/serializers.py:4281 +msgid "Messages cannot contain newlines (found newline in {} event)" +msgstr "Los mensajes no pueden contener nuevas líneas (se encontró una nueva línea en el evento {})" + +#: awx/api/serializers.py:4287 +msgid "Expected dict for 'messages' field, found {}" +msgstr "Dict esperado para el campo 'mensajes', se encontró {}" + +#: awx/api/serializers.py:4291 +msgid "" +"Event '{}' invalid, must be one of 'started', 'success', 'error', or " +"'workflow_approval'" +msgstr "El evento '{}' no es válido, debe ser uno de 'iniciado', 'éxito', 'error' o 'aprobación_de_flujo de trabajo'" + +#: awx/api/serializers.py:4297 +msgid "Expected dict for event '{}', found {}" +msgstr "Dict esperado para el evento '{}', se encontró {}" + +#: awx/api/serializers.py:4302 +msgid "" +"Workflow Approval event '{}' invalid, must be one of 'running', 'approved', " +"'timed_out', or 'denied'" +msgstr "El evento de aprobación del flujo de trabajo '{}' no es válido, debe ser uno de 'en ejecución', 'aprobado', 'tiempo de espera agotado' o 'denegado'" + +#: awx/api/serializers.py:4309 +msgid "Expected dict for workflow approval event '{}', found {}" +msgstr "Dict esperado para el evento de aprobación del flujo de trabajo '{}', se encontró {}" + +#: awx/api/serializers.py:4336 +msgid "Unable to render message '{}': {}" +msgstr "No se puede procesar el mensaje '{}': {}" + +#: awx/api/serializers.py:4338 +msgid "Field '{}' unavailable" +msgstr "Campo '{}' no disponible" + +#: awx/api/serializers.py:4340 +msgid "Security error due to field '{}'" +msgstr "Error de seguridad debido al campo '{}'" + +#: awx/api/serializers.py:4360 +msgid "Webhook body for '{}' should be a json dictionary. Found type '{}'." +msgstr "El cuerpo de Webhook para '{}' debería ser un diccionario json. Se encontró el tipo '{}'." + +#: awx/api/serializers.py:4363 +msgid "Webhook body for '{}' is not a valid json dictionary ({})." +msgstr "El cuerpo de Webhook para '{}' no es un diccionario json válido ({})." + +#: awx/api/serializers.py:4381 msgid "" "Missing required fields for Notification Configuration: notification_type" -msgstr "" -"Campos obligatorios no definidos para la configuración de notificación: " -"notification_type" +msgstr "Campos obligatorios no definidos para la configuración de notificación: notification_type" -#: awx/api/serializers.py:4458 +#: awx/api/serializers.py:4408 msgid "No values specified for field '{}'" msgstr "Ningún valor especificado para el campo '{}'" -#: awx/api/serializers.py:4463 +#: awx/api/serializers.py:4413 +msgid "HTTP method must be either 'POST' or 'PUT'." +msgstr "El método HTTP debe ser 'POST' o 'PUT'." + +#: awx/api/serializers.py:4415 msgid "Missing required fields for Notification Configuration: {}." -msgstr "" -"Campos obligatorios no definidos para la configuración de notificación: {}." +msgstr "Campos no definidos para la configuración de notificación: {}." -#: awx/api/serializers.py:4466 +#: awx/api/serializers.py:4418 msgid "Configuration field '{}' incorrect type, expected {}." -msgstr "Tipo incorrecto en el campo de configuración '{}'; esperado {}." +msgstr "Tipo incorrecto en la configuración del campo '{} ', esperado {}." + +#: awx/api/serializers.py:4435 +msgid "Notification body" +msgstr "Cuerpo de la notificación" -#: awx/api/serializers.py:4528 +#: awx/api/serializers.py:4515 msgid "" -"Valid DTSTART required in rrule. Value should start with: " -"DTSTART:YYYYMMDDTHHMMSSZ" -msgstr "" -"DTSTART válido necesario en rrule. El valor debe empezar con: " -"DTSTART:YYYYMMDDTHHMMSSZ" +"Valid DTSTART required in rrule. Value should start with: DTSTART:" +"YYYYMMDDTHHMMSSZ" +msgstr "DTSTART válido necesario en rrule. El valor debe empezar con: DTSTART:YYYYMMDDTHHMMSSZ" -#: awx/api/serializers.py:4530 +#: awx/api/serializers.py:4517 msgid "" "DTSTART cannot be a naive datetime. Specify ;TZINFO= or YYYYMMDDTHHMMSSZZ." -msgstr "" -"DTSTART no puede ser una fecha/hora ingenua. Especifique ;TZINFO= o " -"YYYYMMDDTHHMMSSZZ." +msgstr "DTSTART no puede ser una fecha/hora ingenua. Especifique ;TZINFO= o YYYYMMDDTHHMMSSZZ." -#: awx/api/serializers.py:4532 +#: awx/api/serializers.py:4519 msgid "Multiple DTSTART is not supported." msgstr "Múltiple DTSTART no está soportado." -#: awx/api/serializers.py:4534 +#: awx/api/serializers.py:4521 msgid "RRULE required in rrule." msgstr "RRULE requerido en rrule." -#: awx/api/serializers.py:4536 +#: awx/api/serializers.py:4523 msgid "Multiple RRULE is not supported." msgstr "Múltiple RRULE no está soportado." -#: awx/api/serializers.py:4538 +#: awx/api/serializers.py:4525 msgid "INTERVAL required in rrule." -msgstr "INTERVAL requerido en rrule." +msgstr "INTERVAL requerido en 'rrule'." -#: awx/api/serializers.py:4540 +#: awx/api/serializers.py:4527 msgid "SECONDLY is not supported." msgstr "SECONDLY no está soportado." -#: awx/api/serializers.py:4542 +#: awx/api/serializers.py:4529 msgid "Multiple BYMONTHDAYs not supported." msgstr "Multiple BYMONTHDAYs no soportado." -#: awx/api/serializers.py:4544 +#: awx/api/serializers.py:4531 msgid "Multiple BYMONTHs not supported." msgstr "Multiple BYMONTHs no soportado." -#: awx/api/serializers.py:4546 +#: awx/api/serializers.py:4533 msgid "BYDAY with numeric prefix not supported." msgstr "BYDAY con prefijo numérico no soportado." -#: awx/api/serializers.py:4548 +#: awx/api/serializers.py:4535 msgid "BYYEARDAY not supported." msgstr "BYYEARDAY no soportado." -#: awx/api/serializers.py:4550 +#: awx/api/serializers.py:4537 msgid "BYWEEKNO not supported." msgstr "BYWEEKNO no soportado." -#: awx/api/serializers.py:4552 +#: awx/api/serializers.py:4539 msgid "RRULE may not contain both COUNT and UNTIL" msgstr "RRULE no puede contener ambos COUNT y UNTIL" -#: awx/api/serializers.py:4556 +#: awx/api/serializers.py:4543 msgid "COUNT > 999 is unsupported." msgstr "COUNT > 999 no está soportada." -#: awx/api/serializers.py:4560 +#: awx/api/serializers.py:4549 msgid "rrule parsing failed validation: {}" msgstr "validación fallida analizando rrule: {}" -#: awx/api/serializers.py:4601 +#: awx/api/serializers.py:4611 msgid "Inventory Source must be a cloud resource." msgstr "Fuente del inventario debe ser un recurso cloud." -#: awx/api/serializers.py:4603 +#: awx/api/serializers.py:4613 msgid "Manual Project cannot have a schedule set." msgstr "El proyecto manual no puede tener una programación establecida." #: awx/api/serializers.py:4616 msgid "" +"Inventory sources with `update_on_project_update` cannot be scheduled. " +"Schedule its source project `{}` instead." +msgstr "No se pueden programar las fuentes de inventario con `update_on_project_update. En su lugar, programe su proyecto fuente `{}`." + +#: awx/api/serializers.py:4626 +msgid "" "Count of jobs in the running or waiting state that are targeted for this " "instance" -msgstr "" -"Cantidad de tareas en estado de ejecución o espera que están destinadas para" -" esta instancia" +msgstr "Cantidad de tareas en estado de ejecución o espera que están destinadas para esta instancia" -#: awx/api/serializers.py:4621 +#: awx/api/serializers.py:4631 msgid "Count of all jobs that target this instance" msgstr "Todos los trabajos que abordan esta instancia" -#: awx/api/serializers.py:4654 +#: awx/api/serializers.py:4664 msgid "" "Count of jobs in the running or waiting state that are targeted for this " "instance group" -msgstr "" -"Cantidad de tareas en estado de ejecución o espera que están destinadas para" -" este grupo de instancia" +msgstr "Cantidad de tareas en estado de ejecución o espera que están destinadas para este grupo de instancia" -#: awx/api/serializers.py:4659 +#: awx/api/serializers.py:4669 msgid "Count of all jobs that target this instance group" msgstr "Todos los trabajos que abordan este grupo de instancias" -#: awx/api/serializers.py:4667 +#: awx/api/serializers.py:4674 +msgid "Indicates whether instance group controls any other group" +msgstr "Indica si el grupo de instancias controla algún otro grupo" + +#: awx/api/serializers.py:4678 +msgid "" +"Indicates whether instances in this group are isolated.Isolated groups have " +"a designated controller group." +msgstr "Indica si las instancias de este grupo están aisladas. Los grupos aislados tienen un grupo controlador designado." + +#: awx/api/serializers.py:4683 +msgid "" +"Indicates whether instances in this group are containerized.Containerized " +"groups have a designated Openshift or Kubernetes cluster." +msgstr "Indica si las instancias de este grupo son contenedorizadas. Los grupos contenedorizados tienen un clúster Openshift o Kubernetes designado." + +#: awx/api/serializers.py:4691 msgid "Policy Instance Percentage" msgstr "Porcentaje de instancias de políticas" -#: awx/api/serializers.py:4668 +#: awx/api/serializers.py:4692 msgid "" "Minimum percentage of all instances that will be automatically assigned to " "this group when new instances come online." -msgstr "" -"Porcentaje mínimo de todas las instancias que se asignarán automáticamente a" -" este grupo cuando nuevas instancias aparezcan en línea." +msgstr "Porcentaje mínimo de todas las instancias que se asignarán automáticamente a este grupo cuando nuevas instancias aparezcan en línea." -#: awx/api/serializers.py:4673 +#: awx/api/serializers.py:4697 msgid "Policy Instance Minimum" msgstr "Mínimo de instancias de políticas" -#: awx/api/serializers.py:4674 +#: awx/api/serializers.py:4698 msgid "" -"Static minimum number of Instances that will be automatically assign to this" -" group when new instances come online." -msgstr "" -"Número mínimo estático de instancias que se asignarán automáticamente a este" -" grupo cuando aparezcan nuevas instancias en línea." +"Static minimum number of Instances that will be automatically assign to this " +"group when new instances come online." +msgstr "Número mínimo estático de instancias que se asignarán automáticamente a este grupo cuando aparezcan nuevas instancias en línea." -#: awx/api/serializers.py:4679 +#: awx/api/serializers.py:4703 msgid "Policy Instance List" msgstr "Lista de instancias de políticas" -#: awx/api/serializers.py:4680 +#: awx/api/serializers.py:4704 msgid "List of exact-match Instances that will be assigned to this group" -msgstr "" -"Lista de instancias con coincidencia exacta que se asignarán a este grupo" +msgstr "Lista de instancias con coincidencia exacta que se asignarán a este grupo" -#: awx/api/serializers.py:4702 +#: awx/api/serializers.py:4730 msgid "Duplicate entry {}." msgstr "Entrada por duplicado {}." -#: awx/api/serializers.py:4704 +#: awx/api/serializers.py:4732 msgid "{} is not a valid hostname of an existing instance." msgstr "{} no es un nombre de host válido de una instancia existente." -#: awx/api/serializers.py:4706 awx/api/views.py:202 +#: awx/api/serializers.py:4734 awx/api/views/mixin.py:98 msgid "" -"Isolated instances may not be added or removed from instances groups via the" -" API." -msgstr "" -"No se pueden agregar ni eliminar instancias aisladas de los grupos de " -"instancias a través de la API." +"Isolated instances may not be added or removed from instances groups via the " +"API." +msgstr "No se pueden agregar ni eliminar instancias aisladas de los grupos de instancias a través de la API." -#: awx/api/serializers.py:4708 awx/api/views.py:206 +#: awx/api/serializers.py:4736 awx/api/views/mixin.py:102 msgid "Isolated instance group membership may not be managed via the API." -msgstr "" -"La membresía del grupo de instancias aisladas no puede administrarse a " -"través de la API." +msgstr "La membresía del grupo de instancias aisladas no puede administrarse a través de la API." -#: awx/api/serializers.py:4713 +#: awx/api/serializers.py:4738 awx/api/serializers.py:4743 +#: awx/api/serializers.py:4748 +msgid "Containerized instances may not be managed via the API" +msgstr "Las instancias contenedorizadas no pueden ser gestionadas a través de la API." + +#: awx/api/serializers.py:4753 msgid "tower instance group name may not be changed." msgstr "No se puede cambiar el nombre del grupo de la instancia de tower." -#: awx/api/serializers.py:4783 +#: awx/api/serializers.py:4758 +msgid "Only Kubernetes credentials can be associated with an Instance Group" +msgstr "Solo las credenciales de Kubernetes pueden asociarse a un grupo de instancias." + +#: awx/api/serializers.py:4797 +msgid "" +"When present, shows the field name of the role or relationship that changed." +msgstr "Cuando está presente, muestra el nombre de campo de la función o relación que cambió." + +#: awx/api/serializers.py:4799 +msgid "" +"When present, shows the model on which the role or relationship was defined." +msgstr "Cuando está presente, muestra el modelo sobre el cual se definió el rol o la relación." + +#: awx/api/serializers.py:4832 msgid "" "A summary of the new and changed values when an object is created, updated, " "or deleted" -msgstr "" -"Un resumen de los valores nuevos y cambiados cuando se crea, se actualiza o " -"se elimina un objeto." +msgstr "Un resumen de los valores nuevos y cambiados cuando un objeto es creado, actualizado o eliminado." -#: awx/api/serializers.py:4785 +#: awx/api/serializers.py:4834 msgid "" "For create, update, and delete events this is the object type that was " "affected. For associate and disassociate events this is the object type " "associated or disassociated with object2." -msgstr "" -"Para crear, actualizar y eliminar eventos, este es el tipo de objeto que fue" -" afectado. Para asociar o desasociar eventos, este es el tipo de objeto " -"asociado o desasociado con object2." +msgstr "Para crear, actualizar y eliminar eventos éste es el tipo de objeto que fue afectado. Para asociar o desasociar eventos éste es el tipo de objeto asociado o desasociado con object2." -#: awx/api/serializers.py:4788 +#: awx/api/serializers.py:4837 msgid "" "Unpopulated for create, update, and delete events. For associate and " -"disassociate events this is the object type that object1 is being associated" -" with." -msgstr "" -"Vacío para crear, actualizar y eliminar eventos. Para asociar y desasociar " -"eventos, este es el tipo de objetos con el que object1 está asociado." +"disassociate events this is the object type that object1 is being associated " +"with." +msgstr "Vacío para crear, actualizar y eliminar eventos. Para asociar y desasociar eventos éste es el tipo de objetos que object1 con el está asociado." -#: awx/api/serializers.py:4791 +#: awx/api/serializers.py:4840 msgid "The action taken with respect to the given object(s)." -msgstr "La acción tomada con respeto al/a los objeto(s) especificado(s)." - -#: awx/api/views.py:119 -msgid "Your license does not allow use of the activity stream." -msgstr "Su licencia no permite el uso de flujo de actividad." - -#: awx/api/views.py:129 -msgid "Your license does not permit use of system tracking." -msgstr "Su licencia no permite el uso de sistema de rastreo." - -#: awx/api/views.py:139 -msgid "Your license does not allow use of workflows." -msgstr "Su licencia no permite el uso de flujos de trabajo." +msgstr "La acción tomada al respeto al/los especificado(s) objeto(s)." -#: awx/api/views.py:153 -msgid "Cannot delete job resource when associated workflow job is running." -msgstr "" -"No es posible eliminar un recurso de trabajo cuando la tarea del flujo de " -"trabajo está en ejecución." - -#: awx/api/views.py:158 -msgid "Cannot delete running job resource." -msgstr "No es posible eliminar el recurso de trabajo en ejecución." - -#: awx/api/views.py:163 -msgid "Job has not finished processing events." -msgstr "La tarea no terminó de procesar eventos." - -#: awx/api/views.py:257 -msgid "Related job {} is still processing events." -msgstr "La tarea {} relacionada aún está procesando eventos." - -#: awx/api/views.py:264 awx/templates/rest_framework/api.html:28 -msgid "REST API" -msgstr "API REST" - -#: awx/api/views.py:275 awx/templates/rest_framework/api.html:4 -msgid "AWX REST API" -msgstr "API REST de AWX" - -#: awx/api/views.py:288 -msgid "API OAuth 2 Authorization Root" -msgstr "Raíz de autorización de API OAuth 2" - -#: awx/api/views.py:353 -msgid "Version 1" -msgstr "Version 1" - -#: awx/api/views.py:357 -msgid "Version 2" -msgstr "Versión 2" - -#: awx/api/views.py:366 -msgid "Ping" -msgstr "Ping" - -#: awx/api/views.py:397 awx/conf/apps.py:10 -msgid "Configuration" -msgstr "Configuración" - -#: awx/api/views.py:454 -msgid "Invalid license data" -msgstr "Datos de licencia no válidos" - -#: awx/api/views.py:456 -msgid "Missing 'eula_accepted' property" -msgstr "Propiedad 'eula_accepted' no encontrada" - -#: awx/api/views.py:460 -msgid "'eula_accepted' value is invalid" -msgstr "Valor 'eula_accepted' no válido" - -#: awx/api/views.py:463 -msgid "'eula_accepted' must be True" -msgstr "'eula_accepted' debe ser True" - -#: awx/api/views.py:470 -msgid "Invalid JSON" -msgstr "JSON no válido" - -#: awx/api/views.py:478 -msgid "Invalid License" -msgstr "Licencia no válida" - -#: awx/api/views.py:488 -msgid "Invalid license" -msgstr "Licencia no válida" - -#: awx/api/views.py:496 -#, python-format -msgid "Failed to remove license (%s)" -msgstr "Error al eliminar licencia (%s)" - -#: awx/api/views.py:501 +#: awx/api/views/__init__.py:181 msgid "Dashboard" msgstr "Panel de control" -#: awx/api/views.py:600 +#: awx/api/views/__init__.py:271 msgid "Dashboard Jobs Graphs" msgstr "Panel de control de gráficas de trabajo" -#: awx/api/views.py:636 +#: awx/api/views/__init__.py:307 #, python-format msgid "Unknown period \"%s\"" -msgstr "Período desconocido \"%s\"" +msgstr "Periodo desconocido \"%s\"" -#: awx/api/views.py:650 +#: awx/api/views/__init__.py:321 msgid "Instances" msgstr "Instancias" -#: awx/api/views.py:658 +#: awx/api/views/__init__.py:329 msgid "Instance Detail" msgstr "Detalle de la instancia" -#: awx/api/views.py:678 +#: awx/api/views/__init__.py:346 msgid "Instance Jobs" msgstr "Tareas de instancia" -#: awx/api/views.py:692 +#: awx/api/views/__init__.py:360 msgid "Instance's Instance Groups" msgstr "Grupos de instancias de la instancia" -#: awx/api/views.py:701 +#: awx/api/views/__init__.py:369 msgid "Instance Groups" msgstr "Grupos de instancias" -#: awx/api/views.py:709 +#: awx/api/views/__init__.py:377 msgid "Instance Group Detail" msgstr "Detalle del grupo de instancias" -#: awx/api/views.py:717 +#: awx/api/views/__init__.py:392 msgid "Isolated Groups can not be removed from the API" msgstr "Grupos aislados no se pueden remover de la API" -#: awx/api/views.py:719 +#: awx/api/views/__init__.py:394 msgid "" "Instance Groups acting as a controller for an Isolated Group can not be " "removed from the API" -msgstr "" -"Los Grupos de instancias que actúan como un controlador para un Grupo " -"aislado no se pueden eliminar de la API" +msgstr "Los Grupos de instancias que actúan como un controlador para un Grupo aislado no se pueden eliminar de la API" -#: awx/api/views.py:725 +#: awx/api/views/__init__.py:400 msgid "Instance Group Running Jobs" msgstr "Tareas en ejecución del grupo de instancias" -#: awx/api/views.py:734 +#: awx/api/views/__init__.py:409 msgid "Instance Group's Instances" msgstr "Instancias del grupo de instancias" -#: awx/api/views.py:744 +#: awx/api/views/__init__.py:419 msgid "Schedules" msgstr "Programaciones" -#: awx/api/views.py:758 +#: awx/api/views/__init__.py:433 msgid "Schedule Recurrence Rule Preview" msgstr "Programe la vista previa de la regla de recurrencia" -#: awx/api/views.py:805 +#: awx/api/views/__init__.py:480 msgid "Cannot assign credential when related template is null." -msgstr "" -"No se puede asignar la credencial cuando la plantilla relacionada es nula." +msgstr "No se puede asignar la credencial cuando la plantilla relacionada es nula." -#: awx/api/views.py:810 +#: awx/api/views/__init__.py:485 msgid "Related template cannot accept {} on launch." msgstr "La plantilla relacionada no puede aceptar {} durante el lanzamiento." -#: awx/api/views.py:812 +#: awx/api/views/__init__.py:487 msgid "" -"Credential that requires user input on launch cannot be used in saved launch" -" configuration." -msgstr "" -"Una credencial que requiere ingreso de datos del usuario durante el " -"lanzamiento no se puede utilizar en una configuración de lanzamiento " -"guardada." +"Credential that requires user input on launch cannot be used in saved launch " +"configuration." +msgstr "Una credencial que requiere ingreso de datos del usuario durante el lanzamiento no se puede utilizar en una configuración de lanzamiento guardada." + +#: awx/api/views/__init__.py:493 +msgid "Related template is not configured to accept credentials on launch." +msgstr "La plantilla relacionada no está configurada para aceptar credenciales durante el lanzamiento." -#: awx/api/views.py:820 +#: awx/api/views/__init__.py:495 #, python-brace-format msgid "" "This launch configuration already provides a {credential_type} credential." -msgstr "" -"Esta configuración de lanzamiento ya proporciona una credencial " -"{credential_type}." +msgstr "Esta configuración de lanzamiento ya proporciona una credencial {credential_type}." -#: awx/api/views.py:823 +#: awx/api/views/__init__.py:498 #, python-brace-format msgid "Related template already uses {credential_type} credential." msgstr "La plantilla relacionada ya usa la credencial {credential_type}." -#: awx/api/views.py:841 +#: awx/api/views/__init__.py:516 msgid "Schedule Jobs List" msgstr "Lista de trabajos programados" -#: awx/api/views.py:996 -msgid "Your license only permits a single organization to exist." -msgstr "Su licencia solo permite que exista una organización." - -#: awx/api/views.py:1223 awx/api/views.py:5106 +#: awx/api/views/__init__.py:600 awx/api/views/__init__.py:4452 msgid "" "You cannot assign an Organization participation role as a child role for a " "Team." -msgstr "" -"No puede asignar un rol de participación de organización como rol secundario" -" para un equipo." +msgstr "No puede asignar un rol de participación de organización como rol secundario para un equipo." -#: awx/api/views.py:1227 awx/api/views.py:5120 +#: awx/api/views/__init__.py:604 awx/api/views/__init__.py:4466 msgid "You cannot grant system-level permissions to a team." msgstr "No puede asignar permisos de nivel de sistema a un equipo." -#: awx/api/views.py:1234 awx/api/views.py:5112 +#: awx/api/views/__init__.py:611 awx/api/views/__init__.py:4458 msgid "" "You cannot grant credential access to a team when the Organization field " "isn't set, or belongs to a different organization" -msgstr "" -"No puede asignar acceso con credencial a un equipo cuando el campo " -"Organización no está establecido o pertenece a una organización diferente." +msgstr "No puede asignar credenciales de acceso a un equipo cuando el campo de organización no está establecido o pertenezca a una organización diferente." -#: awx/api/views.py:1348 +#: awx/api/views/__init__.py:713 msgid "Project Schedules" msgstr "Programación del proyecto" -#: awx/api/views.py:1359 +#: awx/api/views/__init__.py:724 msgid "Project SCM Inventory Sources" msgstr "Fuentes de inventario SCM del proyecto" -#: awx/api/views.py:1460 +#: awx/api/views/__init__.py:825 msgid "Project Update Events List" msgstr "Lista de eventos de actualización de proyectos" -#: awx/api/views.py:1474 +#: awx/api/views/__init__.py:839 msgid "System Job Events List" msgstr "Lista de eventos de tareas del sistema" -#: awx/api/views.py:1488 -msgid "Inventory Update Events List" -msgstr "Lista de eventos de actualización de inventarios" - -#: awx/api/views.py:1522 +#: awx/api/views/__init__.py:873 msgid "Project Update SCM Inventory Updates" msgstr "Actualizaciones de inventario SCM de la actualización del proyecto" -#: awx/api/views.py:1581 +#: awx/api/views/__init__.py:918 msgid "Me" msgstr "Yo" -#: awx/api/views.py:1589 +#: awx/api/views/__init__.py:927 msgid "OAuth 2 Applications" msgstr "Aplicaciones OAuth 2" -#: awx/api/views.py:1598 +#: awx/api/views/__init__.py:936 msgid "OAuth 2 Application Detail" msgstr "Detalle de aplicaciones OAuth 2" -#: awx/api/views.py:1607 +#: awx/api/views/__init__.py:949 msgid "OAuth 2 Application Tokens" msgstr "Tokens de aplicaciones OAuth 2" -#: awx/api/views.py:1629 +#: awx/api/views/__init__.py:971 msgid "OAuth2 Tokens" msgstr "Tokens OAuth2" -#: awx/api/views.py:1638 +#: awx/api/views/__init__.py:980 msgid "OAuth2 User Tokens" msgstr "Tokens de usuario OAuth2" -#: awx/api/views.py:1650 +#: awx/api/views/__init__.py:992 msgid "OAuth2 User Authorized Access Tokens" msgstr "Tokens de acceso autorizado de usuario OAuth2" -#: awx/api/views.py:1665 +#: awx/api/views/__init__.py:1007 msgid "Organization OAuth2 Applications" msgstr "Aplicaciones OAuth2 de la organización " -#: awx/api/views.py:1677 +#: awx/api/views/__init__.py:1019 msgid "OAuth2 Personal Access Tokens" msgstr "Tokens de acceso personal OAuth2" -#: awx/api/views.py:1692 +#: awx/api/views/__init__.py:1034 msgid "OAuth Token Detail" msgstr "Detalle del token OAuth" -#: awx/api/views.py:1752 awx/api/views.py:5073 +#: awx/api/views/__init__.py:1096 awx/api/views/__init__.py:4419 msgid "" "You cannot grant credential access to a user not in the credentials' " "organization" -msgstr "" -"No puede conceder acceso con credencial a un usuario que no está en la " -"organización de credenciales" +msgstr "No puede conceder credenciales de acceso a un usuario que no está en la organización del credencial." -#: awx/api/views.py:1756 awx/api/views.py:5077 +#: awx/api/views/__init__.py:1100 awx/api/views/__init__.py:4423 msgid "You cannot grant private credential access to another user" -msgstr "No puede conceder acceso con credencial privado a otro usuario" +msgstr "No puede conceder acceso a un credencial privado a otro usuario" -#: awx/api/views.py:1854 +#: awx/api/views/__init__.py:1198 #, python-format msgid "Cannot change %s." -msgstr "No se puede cambiar %s." +msgstr "No se puede cambiar%s." -#: awx/api/views.py:1860 +#: awx/api/views/__init__.py:1204 msgid "Cannot delete user." -msgstr "No se puede eliminar el usuario." +msgstr "No se puede eliminar usuario." -#: awx/api/views.py:1884 +#: awx/api/views/__init__.py:1228 msgid "Deletion not allowed for managed credential types" -msgstr "" -"No se permite la eliminación para los tipos de credenciales administradas" +msgstr "No se permite la eliminación para los tipos de credenciales administradas" -#: awx/api/views.py:1886 +#: awx/api/views/__init__.py:1230 msgid "Credential types that are in use cannot be deleted" msgstr "No se pueden eliminar los tipos de credenciales en uso" -#: awx/api/views.py:2061 -msgid "Cannot delete inventory script." -msgstr "No se puede eliminar el script de inventario." +#: awx/api/views/__init__.py:1381 +msgid "External Credential Test" +msgstr "Prueba de credencial externa" -#: awx/api/views.py:2152 -#, python-brace-format -msgid "{0}" -msgstr "{0}" +#: awx/api/views/__init__.py:1408 +msgid "Credential Input Source Detail" +msgstr "Detalle de la fuente de entrada de la credencial" + +#: awx/api/views/__init__.py:1416 awx/api/views/__init__.py:1424 +msgid "Credential Input Sources" +msgstr "Fuentes de entrada de la credencial" + +#: awx/api/views/__init__.py:1439 +msgid "External Credential Type Test" +msgstr "Prueba del tipo de credencial externa" -#: awx/api/views.py:2256 +#: awx/api/views/__init__.py:1497 msgid "The inventory for this host is already being deleted." msgstr "Ya se está eliminando el inventario de este host." -#: awx/api/views.py:2389 -msgid "Fact not found." -msgstr "Hecho no encontrado." - -#: awx/api/views.py:2411 +#: awx/api/views/__init__.py:1614 msgid "SSLError while trying to connect to {}" msgstr "SSLError al intentar conectarse a {}" -#: awx/api/views.py:2413 +#: awx/api/views/__init__.py:1616 msgid "Request to {} timed out." -msgstr "Caducó el tiempo de solicitud para {}." +msgstr "El tiempo de solicitud {} caducó." -#: awx/api/views.py:2415 +#: awx/api/views/__init__.py:1618 msgid "Unknown exception {} while trying to GET {}" msgstr "Excepción desconocida {} al intentar usar GET {}" -#: awx/api/views.py:2418 +#: awx/api/views/__init__.py:1622 msgid "" "Unauthorized access. Please check your Insights Credential username and " "password." -msgstr "" -"Acceso no autorizado. Verifique su nombre de usuario y contraseña de " -"credencial de Insights." +msgstr "Acceso no autorizado. Verifique su nombre de usuario y contraseña de Insights." -#: awx/api/views.py:2421 +#: awx/api/views/__init__.py:1626 msgid "" -"Failed to gather reports and maintenance plans from Insights API at URL {}. " -"Server responded with {} status code and message {}" -msgstr "" -"No se pudieron recopilar los informes y planes de mantenimiento desde la API" -" de Insights en la URL {}. El servidor respondió con el código de estado {} " -"y el mensaje {}" +"Failed to access the Insights API at URL {}. Server responded with {} status " +"code and message {}" +msgstr "No se pudo acceder a la API de Insights en la URL {}. El servidor respondió con el código de estado {} y el mensaje {}" + +#: awx/api/views/__init__.py:1635 +msgid "Expected JSON response from Insights at URL {} but instead got {}" +msgstr "Respuesta JSON esperada de Insights en la URL {}; en cambio, se recibió {}" -#: awx/api/views.py:2428 -msgid "Expected JSON response from Insights but instead got {}" -msgstr "Respuesta JSON esperada de Insights; en cambio, se recibió {}" +#: awx/api/views/__init__.py:1653 +msgid "Could not translate Insights system ID {} into an Insights platform ID." +msgstr "No se pudo traducir el ID del sistema Insights {} en un ID de plataforma de Insights." -#: awx/api/views.py:2435 +#: awx/api/views/__init__.py:1695 msgid "This host is not recognized as an Insights host." msgstr "Este host no se reconoce como un host de Insights." -#: awx/api/views.py:2440 +#: awx/api/views/__init__.py:1703 msgid "The Insights Credential for \"{}\" was not found." msgstr "No se encontró la credencial de Insights para \"{}\"." -#: awx/api/views.py:2508 +#: awx/api/views/__init__.py:1782 msgid "Cyclical Group association." msgstr "Asociación de grupos cíclica." -#: awx/api/views.py:2722 +#: awx/api/views/__init__.py:1948 +msgid "Inventory subset argument must be a string." +msgstr "El argumento del subconjunto de inventario debe ser una cadena." + +#: awx/api/views/__init__.py:1952 +msgid "Subset does not use any supported syntax." +msgstr "El subconjunto no usa una sintaxis compatible." + +#: awx/api/views/__init__.py:2002 msgid "Inventory Source List" msgstr "Listado de fuentes del inventario" -#: awx/api/views.py:2734 +#: awx/api/views/__init__.py:2014 msgid "Inventory Sources Update" msgstr "Actualización de fuentes de inventario" -#: awx/api/views.py:2767 +#: awx/api/views/__init__.py:2047 msgid "Could not start because `can_update` returned False" msgstr "No se pudo iniciar porque `can_update` devolvió False" -#: awx/api/views.py:2775 +#: awx/api/views/__init__.py:2055 msgid "No inventory sources to update." msgstr "No hay fuentes de inventario para actualizar." -#: awx/api/views.py:2804 +#: awx/api/views/__init__.py:2077 msgid "Inventory Source Schedules" -msgstr "Programaciones de fuente de inventario" +msgstr "Programaciones de la fuente del inventario" -#: awx/api/views.py:2832 +#: awx/api/views/__init__.py:2104 msgid "Notification Templates can only be assigned when source is one of {}." -msgstr "" -"Plantillas de notificación solo se pueden asignar cuando la fuente es una de" -" {}." - -#: awx/api/views.py:2887 -msgid "Vault credentials are not yet supported for inventory sources." -msgstr "Las credenciales Vault aún no se admiten para fuentes de inventario." +msgstr "Plantillas de notificación pueden ser sólo asignadas cuando la fuente es una de estas {}." -#: awx/api/views.py:2892 -msgid "Source already has cloud credential assigned." -msgstr "La fuente ya tiene asignada la credencial de nube." +#: awx/api/views/__init__.py:2202 +msgid "Source already has credential assigned." +msgstr "La fuente ya tiene asignada una credencial." -#: awx/api/views.py:3042 -msgid "Field is not allowed for use with v1 API." -msgstr "No se puede usar el campo con la API v1." +#: awx/api/views/__init__.py:2350 +msgid "'credentials' cannot be used in combination with 'extra_credentials'." +msgstr "'credentials' no se puede utilizar en combinación con 'extra_credentials'." -#: awx/api/views.py:3052 -msgid "" -"'credentials' cannot be used in combination with 'credential', " -"'vault_credential', or 'extra_credentials'." -msgstr "" -"'credentials' no se puede utilizar en combinación con 'credential', " -"'vault_credential' o 'extra_credentials'." +#: awx/api/views/__init__.py:2368 +msgid "Incorrect type. Expected a list received {}." +msgstr "Tipo incorrecto. Se esperaba una lista recibida {}." -#: awx/api/views.py:3079 -msgid "Incorrect type. Expected {}, received {}." -msgstr "Tipo incorrecto. Esperado {}, recibido {}." - -#: awx/api/views.py:3172 +#: awx/api/views/__init__.py:2466 msgid "Job Template Schedules" -msgstr "Programaciones de plantilla de trabajo" - -#: awx/api/views.py:3190 awx/api/views.py:3201 -msgid "Your license does not allow adding surveys." -msgstr "Su licencia no permite añadir cuestionarios." +msgstr "Programación plantilla de trabajo" -#: awx/api/views.py:3220 +#: awx/api/views/__init__.py:2515 msgid "Field '{}' is missing from survey spec." msgstr "El campo '{}' no se encuentra en el cuestionario identificado." -#: awx/api/views.py:3222 +#: awx/api/views/__init__.py:2517 msgid "Expected {} for field '{}', received {} type." msgstr "{} esperado para el campo '{}'; tipo {} recibido." -#: awx/api/views.py:3226 +#: awx/api/views/__init__.py:2521 msgid "'spec' doesn't contain any items." msgstr "'spec' no contiene ningún elemento." -#: awx/api/views.py:3235 +#: awx/api/views/__init__.py:2535 #, python-format msgid "Survey question %s is not a json object." -msgstr "Pregunta de cuestionario %s no es un objeto JSON." +msgstr "La pregunta de la encuesta %s no es un objeto json." -#: awx/api/views.py:3237 -#, python-format -msgid "'type' missing from survey question %s." -msgstr "'type' no encontrado en la pregunta de cuestionario %s." - -#: awx/api/views.py:3239 -#, python-format -msgid "'question_name' missing from survey question %s." -msgstr "'question_name' no encontrado en la pregunta de cuestionario %s." +#: awx/api/views/__init__.py:2538 +#, python-brace-format +msgid "'{field_name}' missing from survey question {idx}" +msgstr "Falta '{field_name}' en la pregunta de la encuesta {idx}" -#: awx/api/views.py:3241 -#, python-format -msgid "'variable' missing from survey question %s." -msgstr "'variable' no encontrada en la pregunta de cuestionario %s." +#: awx/api/views/__init__.py:2548 +#, python-brace-format +msgid "'{field_name}' in survey question {idx} expected to be {type_label}." +msgstr "'{field_name}' en la pregunta de la encuesta {idx} se espera que sea {type_label}." -#: awx/api/views.py:3243 +#: awx/api/views/__init__.py:2552 #, python-format msgid "'variable' '%(item)s' duplicated in survey question %(survey)s." -msgstr "" -"'variable' '%(item)s' repetida en la pregunta de cuestionario %(survey)s." +msgstr "'variable' '%(item)s' duplicada en la pregunta de la encuesta %(survey)s." -#: awx/api/views.py:3248 -#, python-format -msgid "'required' missing from survey question %s." -msgstr "'required' no encontrado en la pregunta de cuestionario %s." +#: awx/api/views/__init__.py:2562 +#, python-brace-format +msgid "" +"'{survey_item[type]}' in survey question {idx} is not one of " +"'{allowed_types}' allowed question types." +msgstr "'{survey_item[type]}' en la pregunta de la encuesta {idx} no es uno de los tipos de preguntas permitidas de '{allowed_types}'." -#: awx/api/views.py:3253 +#: awx/api/views/__init__.py:2572 #, python-brace-format msgid "" -"Value {question_default} for '{variable_name}' expected to be a string." -msgstr "" -"Se espera que el valor {question_default} para '{variable_name}' sea una " -"cadena." +"Default value {survey_item[default]} in survey question {idx} expected to be " +"{type_label}." +msgstr "El valor por defecto {survey_item[default]} en la pregunta de la encuesta {idx} se espera que sea {type_label}." + +#: awx/api/views/__init__.py:2582 +#, python-brace-format +msgid "The {min_or_max} limit in survey question {idx} expected to be integer." +msgstr "El límite {min_or_max} en la pregunta de la encuesta {idx} debe ser un número entero." + +#: awx/api/views/__init__.py:2592 +#, python-brace-format +msgid "Survey question {idx} of type {survey_item[type]} must specify choices." +msgstr "La pregunta de la encuesta {idx} del tipo {survey_item[type]} debe especificar opciones." + +#: awx/api/views/__init__.py:2606 +msgid "Multiple Choice (Single Select) can only have one default value." +msgstr "La opción múltiple (selección simple) solo puede tener un valor predeterminado." -#: awx/api/views.py:3263 +#: awx/api/views/__init__.py:2610 +msgid "Default choice must be answered from the choices listed." +msgstr "La opción predeterminada responderse de las opciones enumeradas." + +#: awx/api/views/__init__.py:2619 #, python-brace-format msgid "" "$encrypted$ is a reserved keyword for password question defaults, survey " -"question {question_position} is type {question_type}." -msgstr "" -"$encrypted$ es una palabra clave reservada para valores predeterminados de " -"la pregunta de la contraseña; la pregunta del cuestionario " -"{question_position} es de tipo {question_type}." +"question {idx} is type {survey_item[type]}." +msgstr "$encrypted$ es una palabra clave reservada para valores predeterminados de preguntas de contraseña; la pregunta de la encuesta {idx} es del tipo {survey_item[type]}." -#: awx/api/views.py:3279 +#: awx/api/views/__init__.py:2633 #, python-brace-format msgid "" "$encrypted$ is a reserved keyword, may not be used for new default in " -"position {question_position}." -msgstr "" -"$encrypted$ es una palabra clave reservada y no puede utilizarse como un " -"nuevo valor predeterminado en la posición {question_position}." +"position {idx}." +msgstr "$encrypted$ es una palabra clave reservada y no puede utilizarse para un nuevo valor predeterminado en la posición {idx}." -#: awx/api/views.py:3353 +#: awx/api/views/__init__.py:2705 #, python-brace-format msgid "Cannot assign multiple {credential_type} credentials." msgstr "No se pueden asignar múltiples credenciales {credential_type}." -#: awx/api/views.py:3357 +#: awx/api/views/__init__.py:2709 msgid "Cannot assign a Credential of kind `{}`." msgstr "No se puede asignar una credencial del tipo `{}`." -#: awx/api/views.py:3374 +#: awx/api/views/__init__.py:2726 msgid "Extra credentials must be network or cloud." msgstr "Las credenciales adicionales deben ser red o nube." -#: awx/api/views.py:3396 +#: awx/api/views/__init__.py:2748 msgid "Maximum number of labels for {} reached." msgstr "Número máximo de etiquetas para {} alcanzado." -#: awx/api/views.py:3519 +#: awx/api/views/__init__.py:2871 msgid "No matching host could be found!" -msgstr "No se encontró ningún host coincidente." +msgstr "¡Ningún servidor indicado pudo ser encontrado!" -#: awx/api/views.py:3522 +#: awx/api/views/__init__.py:2874 msgid "Multiple hosts matched the request!" -msgstr "Varios hosts corresponden a la petición." +msgstr "¡Varios servidores corresponden a la petición!" -#: awx/api/views.py:3527 +#: awx/api/views/__init__.py:2879 msgid "Cannot start automatically, user input required!" -msgstr "" -"No se puede iniciar automáticamente; entrada de datos de usuario necesaria." +msgstr "No se puede iniciar automáticamente, !Entrada de datos de usuario necesaria!" -#: awx/api/views.py:3534 +#: awx/api/views/__init__.py:2887 msgid "Host callback job already pending." -msgstr "Trabajo de callback para el host ya está pendiente." +msgstr "Trabajo de callback para el servidor ya está pendiente." -#: awx/api/views.py:3549 awx/api/views.py:4336 +#: awx/api/views/__init__.py:2903 awx/api/views/__init__.py:3664 msgid "Error starting job!" -msgstr "Error al iniciar trabajo." +msgstr "¡Error iniciando trabajo!" -#: awx/api/views.py:3669 -#, python-brace-format -msgid "Cannot associate {0} when {1} have been associated." -msgstr "No se puede asociar {0} cuando se ha asociado {1}." - -#: awx/api/views.py:3694 -msgid "Multiple parent relationship not allowed." -msgstr "No se permiten múltiples relaciones primarias." - -#: awx/api/views.py:3699 +#: awx/api/views/__init__.py:3027 awx/api/views/__init__.py:3047 msgid "Cycle detected." msgstr "Ciclo detectado." -#: awx/api/views.py:3902 +#: awx/api/views/__init__.py:3039 +msgid "Relationship not allowed." +msgstr "Relación no permitida." + +#: awx/api/views/__init__.py:3268 +msgid "Cannot relaunch slice workflow job orphaned from job template." +msgstr "No se puede volver a ejecutar el trabajo del flujo de trabajo de fraccionamiento eliminado de la plantilla de trabajo." + +#: awx/api/views/__init__.py:3270 +msgid "Cannot relaunch sliced workflow job after slice count has changed." +msgstr "No se puede volver a ejecutar el trabajo del flujo de trabajo después de que cambia el conteo de fraccionamiento." + +#: awx/api/views/__init__.py:3303 msgid "Workflow Job Template Schedules" msgstr "Programaciones de plantilla de tareas de flujo de trabajo" -#: awx/api/views.py:4038 awx/api/views.py:4740 +#: awx/api/views/__init__.py:3446 awx/api/views/__init__.py:4087 msgid "Superuser privileges needed." msgstr "Privilegios de superusuario necesarios." -#: awx/api/views.py:4071 +#: awx/api/views/__init__.py:3479 msgid "System Job Template Schedules" -msgstr "Programaciones de plantilla de trabajos de sistema" - -#: awx/api/views.py:4129 -msgid "POST not allowed for Job launching in version 2 of the api" -msgstr "" -"POST no permitido para una tarea que se lanza en la versión 2 de la API" +msgstr "Programación de plantilla de trabajos de sistema." -#: awx/api/views.py:4153 awx/api/views.py:4159 -msgid "PUT not allowed for Job Details in version 2 of the API" -msgstr "PUT no permitido para Detalles de la tarea en la versión 2 de la API" - -#: awx/api/views.py:4319 +#: awx/api/views/__init__.py:3647 #, python-brace-format msgid "Wait until job finishes before retrying on {status_value} hosts." -msgstr "" -"Espere a que termine el trabajo antes de intentar nuevamente en hosts " -"{status_value}." +msgstr "Espere hasta que termine el trabajo antes de intentar nuevamente en hosts {status_value}." -#: awx/api/views.py:4324 +#: awx/api/views/__init__.py:3652 #, python-brace-format msgid "Cannot retry on {status_value} hosts, playbook stats not available." -msgstr "" -"No se puede volver a intentar en hosts {status_value}; las estadísticas de " -"playbook no están disponibles." +msgstr "No se puede volver a intentar en hosts {status_value}; las estadísticas del cuaderno de estrategias no están disponibles." -#: awx/api/views.py:4329 +#: awx/api/views/__init__.py:3657 #, python-brace-format msgid "Cannot relaunch because previous job had 0 {status_value} hosts." -msgstr "" -"No se puede volver a lanzar porque la tarea anterior tuvo 0 hosts " -"{status_value}." +msgstr "No se puede volver a lanzar porque la tarea anterior tuvo 0 hosts {status_value}." -#: awx/api/views.py:4358 +#: awx/api/views/__init__.py:3686 msgid "Cannot create schedule because job requires credential passwords." -msgstr "" -"No se puede crear la programación porque la tarea requiere contraseñas de " -"credenciales." +msgstr "No se puede crear la programación porque la tarea requiere contraseñas de credenciales." -#: awx/api/views.py:4363 +#: awx/api/views/__init__.py:3691 msgid "Cannot create schedule because job was launched by legacy method." -msgstr "" -"No se puede crear una programación porque la tarea se lanzó por un método de" -" legado." +msgstr "No se puede crear una programación porque la tarea se lanzó por un método de legado." -#: awx/api/views.py:4365 +#: awx/api/views/__init__.py:3693 msgid "Cannot create schedule because a related resource is missing." -msgstr "" -"No se puede crear la programación porque falta un recurso relacionado." +msgstr "No se puede crear la programación porque falta un recurso relacionado." -#: awx/api/views.py:4420 +#: awx/api/views/__init__.py:3748 msgid "Job Host Summaries List" -msgstr "Lista resumida de hosts de trabajo" +msgstr "Lista resumida de trabajos de servidor" -#: awx/api/views.py:4469 +#: awx/api/views/__init__.py:3802 msgid "Job Event Children List" -msgstr "Lista de hijos de eventos de trabajo" +msgstr "LIsta de hijos de eventos de trabajo" -#: awx/api/views.py:4479 +#: awx/api/views/__init__.py:3818 msgid "Job Event Hosts List" -msgstr "Lista de hosts de eventos de trabajo" +msgstr "Lista de eventos de trabajos de servidor." -#: awx/api/views.py:4488 +#: awx/api/views/__init__.py:3833 msgid "Job Events List" msgstr "Lista de eventos de trabajo" -#: awx/api/views.py:4697 +#: awx/api/views/__init__.py:4044 msgid "Ad Hoc Command Events List" msgstr "Lista de eventos para comando Ad Hoc" -#: awx/api/views.py:4939 +#: awx/api/views/__init__.py:4289 msgid "Delete not allowed while there are pending notifications" msgstr "Eliminar no está permitido mientras existan notificaciones pendientes" -#: awx/api/views.py:4947 +#: awx/api/views/__init__.py:4297 msgid "Notification Template Test" msgstr "Prueba de plantilla de notificación" -#: awx/conf/conf.py:20 -msgid "Bud Frogs" -msgstr "Bud Frogs" +#: awx/api/views/__init__.py:4557 awx/api/views/__init__.py:4572 +msgid "User does not have permission to approve or deny this workflow." +msgstr "El usuario no tiene permiso para aprobar o denegar este flujo de trabajo." -#: awx/conf/conf.py:21 -msgid "Bunny" -msgstr "Bunny" +#: awx/api/views/__init__.py:4559 awx/api/views/__init__.py:4574 +msgid "This workflow step has already been approved or denied." +msgstr "Este paso de flujo de trabajo ya ha sido autorizado o denegado." -#: awx/conf/conf.py:22 -msgid "Cheese" -msgstr "Cheese" +#: awx/api/views/inventory.py:63 +msgid "Inventory Update Events List" +msgstr "Lista de eventos de actualización de inventarios" -#: awx/conf/conf.py:23 -msgid "Daemon" +#: awx/api/views/inventory.py:90 +msgid "Cannot delete inventory script." +msgstr "No se puede eliminar script de inventario." + +#: awx/api/views/inventory.py:149 +#, python-brace-format +msgid "{0}" +msgstr "{0}" + +#: awx/api/views/metrics.py:30 +msgid "Metrics" +msgstr "Métrica" + +#: awx/api/views/mixin.py:46 +msgid "Cannot delete job resource when associated workflow job is running." +msgstr "No es posible eliminar un recurso de trabajo cuando la tarea del flujo de trabajo está en ejecución." + +#: awx/api/views/mixin.py:51 +msgid "Cannot delete running job resource." +msgstr "No es posible eliminar el recurso de trabajo en ejecución." + +#: awx/api/views/mixin.py:56 +msgid "Job has not finished processing events." +msgstr "La tarea no terminó de procesar eventos." + +#: awx/api/views/mixin.py:153 +msgid "Related job {} is still processing events." +msgstr "La tarea {} relacionada aún está procesando eventos." + +#: awx/api/views/root.py:49 awx/templates/rest_framework/api.html:28 +msgid "REST API" +msgstr "REST API" + +#: awx/api/views/root.py:59 awx/templates/rest_framework/api.html:4 +msgid "AWX REST API" +msgstr "API REST de AWX" + +#: awx/api/views/root.py:72 +msgid "API OAuth 2 Authorization Root" +msgstr "Raíz de autorización de API OAuth 2" + +#: awx/api/views/root.py:139 +msgid "Version 2" +msgstr "Versión 2" + +#: awx/api/views/root.py:148 +msgid "Ping" +msgstr "Ping" + +#: awx/api/views/root.py:180 awx/api/views/root.py:225 awx/conf/apps.py:10 +msgid "Configuration" +msgstr "Configuración" + +#: awx/api/views/root.py:202 awx/api/views/root.py:308 +msgid "Invalid License" +msgstr "Licencia no valida" + +#: awx/api/views/root.py:207 +msgid "The provided credentials are invalid (HTTP 401)." +msgstr "Las credenciales proporcionadas no son válidas (HTTP 401)." + +#: awx/api/views/root.py:209 +msgid "Unable to connect to proxy server." +msgstr "No se puede conectar al servidor proxy." + +#: awx/api/views/root.py:211 +msgid "Could not connect to subscription service." +msgstr "No se pudo conectar al servicio de suscripción." + +#: awx/api/views/root.py:284 +msgid "Invalid license data" +msgstr "Datos de licencia inválidos." + +#: awx/api/views/root.py:286 +msgid "Missing 'eula_accepted' property" +msgstr "Propiedad 'eula_accepted' no encontrada" + +#: awx/api/views/root.py:290 +msgid "'eula_accepted' value is invalid" +msgstr "Valor 'eula_accepted' no es válido" + +#: awx/api/views/root.py:293 +msgid "'eula_accepted' must be True" +msgstr "'eula_accepted' debe ser True" + +#: awx/api/views/root.py:300 +msgid "Invalid JSON" +msgstr "JSON inválido" + +#: awx/api/views/root.py:319 +msgid "Invalid license" +msgstr "Licencia inválida" + +#: awx/api/views/root.py:327 +msgid "Failed to remove license." +msgstr "Se produjo un error al eliminar la licencia." + +#: awx/api/views/webhooks.py:143 +msgid "Webhook previously received, aborting." +msgstr "Webhook previamente recibido, anulando." + +#: awx/conf/conf.py:20 +msgid "Bud Frogs" +msgstr "Bud Frogs" + +#: awx/conf/conf.py:21 +msgid "Bunny" +msgstr "Bunny" + +#: awx/conf/conf.py:22 +msgid "Cheese" +msgstr "Cheese" + +#: awx/conf/conf.py:23 +msgid "Daemon" msgstr "Daemon" #: awx/conf/conf.py:24 @@ -1619,9 +1616,7 @@ msgstr "Cow Selection" #: awx/conf/conf.py:53 msgid "Select which cow to use with cowsay when running jobs." -msgstr "" -"Seleccione con cual 'cow' debe ser utilizado cowsay mientras ejecuta " -"trabajos." +msgstr "Seleccione con cual 'cow' debe ser utilizado cowsay mientras ejecuta trabajos." #: awx/conf/conf.py:54 awx/conf/conf.py:75 msgid "Cows" @@ -1629,101 +1624,70 @@ msgstr "Cows" #: awx/conf/conf.py:73 msgid "Example Read-Only Setting" -msgstr "Ejemplo de ajuste de solo lectura" +msgstr "Ejemplo de ajuste de sólo lectura.f" #: awx/conf/conf.py:74 msgid "Example setting that cannot be changed." -msgstr "Ejemplo de ajuste que no se puede cambiar" +msgstr "Ejemplo de ajuste que no puede ser cambiado." -#: awx/conf/conf.py:93 +#: awx/conf/conf.py:90 msgid "Example Setting" msgstr "Ejemplo de ajuste" -#: awx/conf/conf.py:94 +#: awx/conf/conf.py:91 msgid "Example setting which can be different for each user." -msgstr "Ejemplo de ajuste que puede ser diferente para cada usuario." +msgstr "Ejemplo de configuración que puede ser diferente para cada usuario." -#: awx/conf/conf.py:95 awx/conf/registry.py:85 awx/conf/views.py:55 +#: awx/conf/conf.py:92 awx/conf/registry.py:81 awx/conf/views.py:56 msgid "User" msgstr "Usuario" -#: awx/conf/fields.py:60 awx/sso/fields.py:595 +#: awx/conf/fields.py:63 awx/sso/fields.py:595 #, python-brace-format msgid "" -"Expected None, True, False, a string or list of strings but got {input_type}" -" instead." -msgstr "" -"Se esperaba None, True, False, una cadena de texto o una lista de cadenas de" -" texto; pero, en cambio, se obtuvo {input_type}." +"Expected None, True, False, a string or list of strings but got {input_type} " +"instead." +msgstr "Se esperaba None, True, False, una cadena o una lista de cadenas, pero, en cambio, se obtuvo {input_type}." #: awx/conf/fields.py:104 +#, python-brace-format +msgid "Expected list of strings but got {input_type} instead." +msgstr "Se esperaba la lista de cadenas; pero, en cambio, se obtuvo {input_type}." + +#: awx/conf/fields.py:105 +#, python-brace-format +msgid "{path} is not a valid path choice." +msgstr "{path} no es una opción de ruta válida." + +#: awx/conf/fields.py:149 msgid "Enter a valid URL" msgstr "Introduzca una URL válida" -#: awx/conf/fields.py:136 +#: awx/conf/fields.py:187 #, python-brace-format msgid "\"{input}\" is not a valid string." msgstr "\"{input}\" no es una cadena válida." -#: awx/conf/fields.py:151 +#: awx/conf/fields.py:202 #, python-brace-format -msgid "" -"Expected a list of tuples of max length 2 but got {input_type} instead." -msgstr "" -"Se esperaba una lista de tuplas de longitud máxima 2, pero, en cambio, se " -"obtuvo {input_type}." - -#: awx/conf/license.py:22 -msgid "Your Tower license does not allow that." -msgstr "Su licencia Tower no permite eso." - -#: awx/conf/management/commands/migrate_to_database_settings.py:41 -msgid "Only show which settings would be commented/migrated." -msgstr "Solo mostrar los ajustes que serán comentados/migrados." - -#: awx/conf/management/commands/migrate_to_database_settings.py:48 -msgid "" -"Skip over settings that would raise an error when commenting/migrating." -msgstr "Omitir los ajustes que causarán un error al comentar/migrar." - -#: awx/conf/management/commands/migrate_to_database_settings.py:55 -msgid "Skip commenting out settings in files." -msgstr "Omitir los comentarios sobre ajustes en archivos." - -#: awx/conf/management/commands/migrate_to_database_settings.py:62 -msgid "Skip migrating and only comment out settings in files." -msgstr "Omita la migración y solo comente sobre ajustes en archivos." +msgid "Expected a list of tuples of max length 2 but got {input_type} instead." +msgstr "Se esperaba una lista de tuplas de 2 como longitud máxima, pero, en cambio, se obtuvo {input_type}." -#: awx/conf/management/commands/migrate_to_database_settings.py:68 -msgid "Backup existing settings files with this suffix." -msgstr "" -"Hacer copia de seguridad de todos los archivos de configuración existentes " -"con este sufijo." - -#: awx/conf/registry.py:73 awx/conf/tests/unit/test_registry.py:169 -#: awx/conf/tests/unit/test_registry.py:192 -#: awx/conf/tests/unit/test_registry.py:196 -#: awx/conf/tests/unit/test_registry.py:201 -#: awx/conf/tests/unit/test_registry.py:208 +#: awx/conf/registry.py:73 awx/conf/tests/unit/test_registry.py:155 msgid "All" msgstr "Todos" -#: awx/conf/registry.py:74 awx/conf/tests/unit/test_registry.py:170 -#: awx/conf/tests/unit/test_registry.py:193 -#: awx/conf/tests/unit/test_registry.py:197 -#: awx/conf/tests/unit/test_registry.py:202 -#: awx/conf/tests/unit/test_registry.py:209 +#: awx/conf/registry.py:74 awx/conf/tests/unit/test_registry.py:156 msgid "Changed" msgstr "Cambiado" -#: awx/conf/registry.py:86 +#: awx/conf/registry.py:82 msgid "User-Defaults" -msgstr "Valores predeterminados del usuario" +msgstr "Parámetros de usuario por defecto" -#: awx/conf/registry.py:154 +#: awx/conf/registry.py:143 msgid "This value has been set manually in a settings file." -msgstr "" -"Este valor se ha establecido manualmente en el archivo de configuración." +msgstr "Este valor ha sido establecido manualmente en el fichero de configuración." #: awx/conf/tests/unit/test_registry.py:46 #: awx/conf/tests/unit/test_registry.py:56 @@ -1732,19 +1696,15 @@ msgstr "" #: awx/conf/tests/unit/test_registry.py:100 #: awx/conf/tests/unit/test_registry.py:106 #: awx/conf/tests/unit/test_registry.py:126 -#: awx/conf/tests/unit/test_registry.py:140 -#: awx/conf/tests/unit/test_registry.py:146 -#: awx/conf/tests/unit/test_registry.py:159 -#: awx/conf/tests/unit/test_registry.py:171 -#: awx/conf/tests/unit/test_registry.py:180 -#: awx/conf/tests/unit/test_registry.py:198 -#: awx/conf/tests/unit/test_registry.py:210 -#: awx/conf/tests/unit/test_registry.py:219 -#: awx/conf/tests/unit/test_registry.py:225 -#: awx/conf/tests/unit/test_registry.py:237 -#: awx/conf/tests/unit/test_registry.py:245 -#: awx/conf/tests/unit/test_registry.py:288 -#: awx/conf/tests/unit/test_registry.py:306 +#: awx/conf/tests/unit/test_registry.py:132 +#: awx/conf/tests/unit/test_registry.py:145 +#: awx/conf/tests/unit/test_registry.py:157 +#: awx/conf/tests/unit/test_registry.py:166 +#: awx/conf/tests/unit/test_registry.py:172 +#: awx/conf/tests/unit/test_registry.py:184 +#: awx/conf/tests/unit/test_registry.py:191 +#: awx/conf/tests/unit/test_registry.py:233 +#: awx/conf/tests/unit/test_registry.py:251 #: awx/conf/tests/unit/test_settings.py:79 #: awx/conf/tests/unit/test_settings.py:97 #: awx/conf/tests/unit/test_settings.py:112 @@ -1766,711 +1726,928 @@ msgstr "" #: awx/conf/tests/unit/test_settings.py:398 #: awx/conf/tests/unit/test_settings.py:411 #: awx/conf/tests/unit/test_settings.py:430 -#: awx/conf/tests/unit/test_settings.py:466 awx/main/conf.py:22 -#: awx/main/conf.py:32 awx/main/conf.py:43 awx/main/conf.py:53 -#: awx/main/conf.py:62 awx/main/conf.py:74 awx/main/conf.py:87 -#: awx/main/conf.py:100 awx/main/conf.py:125 +#: awx/conf/tests/unit/test_settings.py:466 awx/main/conf.py:24 +#: awx/main/conf.py:33 awx/main/conf.py:43 awx/main/conf.py:53 +#: awx/main/conf.py:65 awx/main/conf.py:78 awx/main/conf.py:91 +#: awx/main/conf.py:116 awx/main/conf.py:129 awx/main/conf.py:142 +#: awx/main/conf.py:154 awx/main/conf.py:162 awx/main/conf.py:173 +#: awx/main/conf.py:405 awx/main/conf.py:830 awx/main/conf.py:840 +#: awx/main/conf.py:852 msgid "System" msgstr "Sistema" -#: awx/conf/tests/unit/test_registry.py:165 -#: awx/conf/tests/unit/test_registry.py:172 -#: awx/conf/tests/unit/test_registry.py:187 -#: awx/conf/tests/unit/test_registry.py:203 -#: awx/conf/tests/unit/test_registry.py:211 +#: awx/conf/tests/unit/test_registry.py:151 +#: awx/conf/tests/unit/test_registry.py:158 msgid "OtherSystem" msgstr "Otro sistema" -#: awx/conf/views.py:47 +#: awx/conf/views.py:48 msgid "Setting Categories" msgstr "Categorías de ajustes" -#: awx/conf/views.py:71 +#: awx/conf/views.py:70 msgid "Setting Detail" msgstr "Detalles del ajuste" -#: awx/conf/views.py:166 +#: awx/conf/views.py:162 msgid "Logging Connectivity Test" msgstr "Registrando prueba de conectividad" -#: awx/main/access.py:59 +#: awx/main/access.py:66 #, python-format msgid "Required related field %s for permission check." msgstr "Campo relacionado %s requerido para verificación de permisos." -#: awx/main/access.py:75 +#: awx/main/access.py:82 #, python-format msgid "Bad data found in related field %s." msgstr "Dato incorrecto encontrado en el campo relacionado %s." -#: awx/main/access.py:302 +#: awx/main/access.py:331 msgid "License is missing." msgstr "Licencia no encontrada." -#: awx/main/access.py:304 +#: awx/main/access.py:333 msgid "License has expired." msgstr "La licencia ha expirado." -#: awx/main/access.py:312 +#: awx/main/access.py:341 #, python-format msgid "License count of %s instances has been reached." -msgstr "Se alcanzó el número de licencias de instancias %s." +msgstr "Se ha alcanzado el número de licencias de %s instancias." -#: awx/main/access.py:314 +#: awx/main/access.py:343 #, python-format msgid "License count of %s instances has been exceeded." -msgstr "Se superó el número de licencias de instancias %s." +msgstr "Se ha excedido el número de licencias de %s instancias." -#: awx/main/access.py:316 +#: awx/main/access.py:345 msgid "Host count exceeds available instances." -msgstr "El número de hosts excede las instancias disponibles." +msgstr "El número de servidores excede las instancias disponibles." -#: awx/main/access.py:320 +#: awx/main/access.py:363 awx/main/access.py:372 #, python-format -msgid "Feature %s is not enabled in the active license." -msgstr "Funcionalidad %s no está habilitada en la licencia activa." - -#: awx/main/access.py:322 -msgid "Features not found in active license." -msgstr "Funcionalidades no encontradas en la licencia activa." +msgid "" +"You have already reached the maximum number of %s hosts allowed for your " +"organization. Contact your System Administrator for assistance." +msgstr "Ya ha alcanzado la cantidad máxima de %s hosts permitidos para su organización. Contacte al administrador del sistema para obtener ayuda." -#: awx/main/access.py:835 +#: awx/main/access.py:927 msgid "Unable to change inventory on a host." msgstr "Imposible modificar el inventario en un servidor." -#: awx/main/access.py:852 awx/main/access.py:897 +#: awx/main/access.py:948 awx/main/access.py:990 msgid "Cannot associate two items from different inventories." msgstr "No es posible asociar dos elementos de diferentes inventarios." -#: awx/main/access.py:885 +#: awx/main/access.py:978 msgid "Unable to change inventory on a group." msgstr "Imposible cambiar el inventario en un grupo." -#: awx/main/access.py:1146 +#: awx/main/access.py:1264 msgid "Unable to change organization on a team." msgstr "Imposible cambiar la organización en un equipo." -#: awx/main/access.py:1163 +#: awx/main/access.py:1280 msgid "The {} role cannot be assigned to a team" -msgstr "El rol {} no se puede asignar a un equipo" +msgstr "El rol {} no puede ser asignado a un equipo." -#: awx/main/access.py:1165 -msgid "The admin_role for a User cannot be assigned to a team" -msgstr "El admin_role para un usuario no se puede asignar a un equipo" +#: awx/main/access.py:1474 +msgid "Insufficient access to Job Template credentials." +msgstr "Acceso insuficiente a las credenciales de la plantilla de trabajo." -#: awx/main/access.py:1531 awx/main/access.py:1965 -msgid "Job was launched with prompts provided by another user." -msgstr "La tarea se inició con avisos provistos por otro usuario." +#: awx/main/access.py:1639 awx/main/access.py:2063 +msgid "Job was launched with secret prompts provided by another user." +msgstr "El trabajo se inició con avisos secretos provistos por otro usuario." -#: awx/main/access.py:1551 -msgid "Job has been orphaned from its job template." -msgstr "Se eliminó la plantilla de trabajo de la tarea." +#: awx/main/access.py:1648 +msgid "Job has been orphaned from its job template and organization." +msgstr "La tarea quedó huérfana de su plantilla de tarea y organización." -#: awx/main/access.py:1553 -msgid "Job was launched with unknown prompted fields." -msgstr "La tarea se inició con campos completados desconocidos." +#: awx/main/access.py:1650 +msgid "Job was launched with prompted fields you do not have access to." +msgstr "La tarea se lanzó con campos de consulta a los que no tiene acceso." -#: awx/main/access.py:1555 -msgid "Job was launched with prompted fields." -msgstr "La tarea se ejecutó con campos completados." +#: awx/main/access.py:1652 +msgid "" +"Job was launched with unknown prompted fields. Organization admin " +"permissions required." +msgstr "La tarea se lanzó con campos de consulta desconocidos. Se requieren permisos de administración de la organización." + +#: awx/main/access.py:2053 +msgid "Workflow Job was launched with unknown prompts." +msgstr "El trabajo del flujo de trabajo se ejecutó con avisos desconocidos." -#: awx/main/access.py:1557 -msgid " Organization level permissions required." -msgstr "Se requieren permisos de nivel de organización." +#: awx/main/access.py:2065 +msgid "Job was launched with prompts you lack access to." +msgstr "El trabajo se ejecutó con avisos a los que no tiene acceso." -#: awx/main/access.py:1559 -msgid " You do not have permission to related resources." -msgstr "No tiene permisos para los recursos relacionados." +#: awx/main/access.py:2067 +msgid "Job was launched with prompts no longer accepted." +msgstr "El trabajo se ejecutó con avisos que ya no se aceptan." -#: awx/main/access.py:1979 +#: awx/main/access.py:2079 msgid "" "You do not have permission to the workflow job resources required for " "relaunch." -msgstr "" -"Usted no tiene el permiso a los recursos de la tarea de flujo de trabajo " -"necesarios para relanzar. " +msgstr "Usted no tiene el permiso a los recursos de la tarea de flujo de trabajo necesarios para relanzar. " #: awx/main/apps.py:8 msgid "Main" msgstr "Principal" -#: awx/main/conf.py:20 +#: awx/main/conf.py:22 msgid "Enable Activity Stream" msgstr "Activar flujo de actividad" -#: awx/main/conf.py:21 +#: awx/main/conf.py:23 msgid "Enable capturing activity for the activity stream." msgstr "Habilite la captura de actividades para el flujo de actividad." -#: awx/main/conf.py:30 +#: awx/main/conf.py:31 msgid "Enable Activity Stream for Inventory Sync" -msgstr "Habilitar el flujo de actividad para la sincronización de inventarios" +msgstr "Habilitar el flujo de actividad para la sincronización de inventarios." -#: awx/main/conf.py:31 +#: awx/main/conf.py:32 msgid "" "Enable capturing activity for the activity stream when running inventory " "sync." -msgstr "" -"Habilite la captura de actividades para el flujo de actividad cuando ejecute" -" la sincronización del inventario." +msgstr "Habilite la captura de actividades para el flujo de actividad cuando ejecute la sincronización del inventario." #: awx/main/conf.py:40 msgid "All Users Visible to Organization Admins" -msgstr "" -"Todos los usuarios visibles para los administradores de la organización" +msgstr "Todos los usuarios visibles para los administradores de la organización." #: awx/main/conf.py:41 msgid "" "Controls whether any Organization Admin can view all users and teams, even " "those not associated with their Organization." -msgstr "" -"Controla si cualquier administrador de organización puede ver todos los " -"usuarios y equipos, incluso aquellos no están asociados a su organización." +msgstr "Controla si cualquier administrador de organización puede ver todos los usuarios y equipos, incluso aquellos no están asociados a su organización." #: awx/main/conf.py:50 msgid "Organization Admins Can Manage Users and Teams" -msgstr "" -"Los administradores de la organización pueden gestionar usuarios y equipos" +msgstr "Los administradores de la organización pueden gestionar usuarios y equipos" #: awx/main/conf.py:51 msgid "" "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." -msgstr "" -"Controla si algún Administrador de la organización tiene los privilegios " -"para crear y gestionar usuarios y equipos. Recomendamos deshabilitar esta " -"capacidad si está usando una integración de LDAP o SAML." - -#: awx/main/conf.py:60 -msgid "Enable Administrator Alerts" -msgstr "Habilitar alertas de administrador" - -#: awx/main/conf.py:61 -msgid "Email Admin users for system events that may require attention." -msgstr "" -"Enviar correo electrónico a los usuarios administradores para los eventos " -"del sistema que requieren atención." +msgstr "Controla si algún Administrador de la organización tiene los privilegios para crear y gestionar usuarios y equipos. Recomendamos deshabilitar esta capacidad si está usando una integración de LDAP o SAML." -#: awx/main/conf.py:71 +#: awx/main/conf.py:62 msgid "Base URL of the Tower host" msgstr "Dirección base URL para el servidor Tower" -#: awx/main/conf.py:72 +#: awx/main/conf.py:63 msgid "" -"This setting is used by services like notifications to render a valid url to" -" the Tower host." -msgstr "" -"Esta configuración es utilizada por servicios cono notificaciones para " -"mostrar una URL válida al host Tower." +"This setting is used by services like notifications to render a valid url to " +"the Tower host." +msgstr "Este configuración es utilizada por servicios cono notificaciones para mostrar una url válida al servidor Tower." -#: awx/main/conf.py:81 +#: awx/main/conf.py:72 msgid "Remote Host Headers" -msgstr "Encabezados de host remoto" +msgstr "Cabeceras de servidor remoto" -#: awx/main/conf.py:82 +#: awx/main/conf.py:73 msgid "" "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." -msgstr "" -"Los encabezados HTTP y las llaves de activación para buscar y determinar el " -"nombre de host remoto o IP. Añada elementos adicionales a esta lista, como " -"\"HTTP_X_FORWARDED_FOR\", si está detrás de un proxy inverso. Consulte la " -"sección \"Soporte de proxy\" de la guía del adminstrador para obtener más " -"información." +msgstr "Los encabezados HTTP y las llaves de activación para buscar y determinar el nombre de host remoto o IP. Añada elementos adicionales a esta lista, como \"HTTP_X_FORWARDED_FOR\", si está detrás de un proxy inverso. Consulte la sección \"Soporte de proxy\" de la guía del adminstrador para obtener más información." -#: awx/main/conf.py:94 +#: awx/main/conf.py:85 msgid "Proxy IP Whitelist" msgstr "Lista blanca de IP de proxy" -#: awx/main/conf.py:95 +#: awx/main/conf.py:86 msgid "" "If Tower is behind a reverse proxy/load balancer, use this setting to " "whitelist the proxy IP addresses from which Tower 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')" -msgstr "" -"Si Tower está detrás de un equilibrador de carga/proxy inverso, use este " -"ajuste para colocar las direcciones IP del proxy en la lista blanca desde la" -" cual Tower debe confiar en los valores personalizados del encabezado " -"REMOTE_HOST_HEADERS. Si este ajuste es una lista vacía (valor " -"predeterminado), se confiará en los encabezados especificados por " -"REMOTE_HOST_HEADERS de manera incondicional')" - -#: awx/main/conf.py:121 +msgstr "Si Tower está detrás de un equilibrador de carga/proxy inverso, use este ajuste para colocar las direcciones IP del proxy en la lista blanca desde la cual Tower debe confiar en los valores personalizados del encabezado REMOTE_HOST_HEADERS. Si este ajuste es una lista vacía (valor predeterminado), se confiará en los encabezados especificados por REMOTE_HOST_HEADERS de manera incondicional')" + +#: awx/main/conf.py:112 msgid "License" msgstr "Licencia" -#: awx/main/conf.py:122 +#: awx/main/conf.py:113 msgid "" -"The license controls which features and functionality are enabled. Use " -"/api/v1/config/ to update or change the license." -msgstr "" -"Los controles de licencia cuyas funciones y funcionalidades están " -"habilitadas. Use /api/v1/config/ para actualizar o cambiar la licencia." +"The license controls which features and functionality are enabled. Use /api/" +"v2/config/ to update or change the license." +msgstr "Los controles de licencia cuyas funciones y funcionalidades están habilitadas. Use /api/v2/config/ para actualizar o cambiar la licencia." + +#: awx/main/conf.py:127 +msgid "Red Hat customer username" +msgstr "Nombre de usuario del cliente de Red Hat" + +#: awx/main/conf.py:128 +msgid "" +"This username is used to retrieve license information and to send Automation " +"Analytics" +msgstr "Este nombre de usuario se utiliza para recuperar la información de la licencia y para enviar a Automation Analytics" + +#: awx/main/conf.py:140 +msgid "Red Hat customer password" +msgstr "Contraseña de cliente de Red Hat" + +#: awx/main/conf.py:141 +msgid "" +"This password is used to retrieve license information and to send Automation " +"Analytics" +msgstr "Esta contraseña se utiliza para recuperar la información de la licencia y para enviar a Automation Analytics" -#: awx/main/conf.py:132 +#: awx/main/conf.py:152 +msgid "Automation Analytics upload URL." +msgstr "URL de carga de Automation Analytics." + +#: awx/main/conf.py:153 +msgid "" +"This setting is used to to configure data collection for the Automation " +"Analytics dashboard" +msgstr "Este ajuste se utiliza para configurar la recopilación de datos para el panel de Automation Analytics" + +#: awx/main/conf.py:161 +msgid "Unique identifier for an AWX/Tower installation" +msgstr "Identificador único para una instalación de AWX/Tower" + +#: awx/main/conf.py:170 +msgid "Custom virtual environment paths" +msgstr "Rutas de entorno virtual personalizadas" + +#: awx/main/conf.py:171 +msgid "" +"Paths where Tower will look for custom virtual environments (in addition to /" +"var/lib/awx/venv/). Enter one path per line." +msgstr "Las rutas donde Tower buscará entornos virtuales personalizados (además de /var/lib/awx/venv/). Ingrese una ruta por línea." + +#: awx/main/conf.py:181 msgid "Ansible Modules Allowed for Ad Hoc Jobs" -msgstr "Módulos Ansible autorizados para trabajos Ad Hoc" +msgstr "Módulos Ansible autorizados para ejecutar trabajos Ad Hoc" -#: awx/main/conf.py:133 +#: awx/main/conf.py:182 msgid "List of modules allowed to be used by ad-hoc jobs." msgstr "Lista de módulos permitidos para su uso con trabajos ad-hoc." -#: awx/main/conf.py:134 awx/main/conf.py:156 awx/main/conf.py:165 -#: awx/main/conf.py:176 awx/main/conf.py:186 awx/main/conf.py:196 -#: awx/main/conf.py:206 awx/main/conf.py:217 awx/main/conf.py:229 -#: awx/main/conf.py:241 awx/main/conf.py:254 awx/main/conf.py:266 -#: awx/main/conf.py:276 awx/main/conf.py:287 awx/main/conf.py:298 -#: awx/main/conf.py:308 awx/main/conf.py:318 awx/main/conf.py:330 -#: awx/main/conf.py:342 awx/main/conf.py:354 awx/main/conf.py:368 +#: awx/main/conf.py:183 awx/main/conf.py:205 awx/main/conf.py:214 +#: awx/main/conf.py:225 awx/main/conf.py:235 awx/main/conf.py:245 +#: awx/main/conf.py:256 awx/main/conf.py:267 awx/main/conf.py:278 +#: awx/main/conf.py:290 awx/main/conf.py:299 awx/main/conf.py:312 +#: awx/main/conf.py:325 awx/main/conf.py:337 awx/main/conf.py:348 +#: awx/main/conf.py:359 awx/main/conf.py:371 awx/main/conf.py:383 +#: awx/main/conf.py:394 awx/main/conf.py:414 awx/main/conf.py:424 +#: awx/main/conf.py:434 awx/main/conf.py:450 awx/main/conf.py:463 +#: awx/main/conf.py:477 awx/main/conf.py:491 awx/main/conf.py:503 +#: awx/main/conf.py:513 awx/main/conf.py:524 awx/main/conf.py:534 +#: awx/main/conf.py:545 awx/main/conf.py:555 awx/main/conf.py:565 +#: awx/main/conf.py:577 awx/main/conf.py:589 awx/main/conf.py:601 +#: awx/main/conf.py:615 awx/main/conf.py:627 msgid "Jobs" msgstr "Trabajos" -#: awx/main/conf.py:143 +#: awx/main/conf.py:192 msgid "Always" msgstr "Siempre" -#: awx/main/conf.py:144 +#: awx/main/conf.py:193 msgid "Never" msgstr "Nunca" -#: awx/main/conf.py:145 +#: awx/main/conf.py:194 msgid "Only On Job Template Definitions" msgstr "Solo en definiciones de plantilla de tareas" -#: awx/main/conf.py:148 +#: awx/main/conf.py:197 msgid "When can extra variables contain Jinja templates?" msgstr "¿Cuándo pueden las variables adicionales contener plantillas Jinja?" -#: awx/main/conf.py:150 +#: awx/main/conf.py:199 msgid "" "Ansible allows variable substitution via the Jinja2 templating language for " "--extra-vars. This poses a potential security risk where Tower 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\"." -msgstr "" -"Ansible permite la sustitución de variables a través del lenguaje de " -"plantillas Jinja2 para --extra-vars. Esto presenta un potencial de riesgo a " -"la seguridad, en donde los usuarios de Tower con la capacidad de especificar" -" vars adicionales en el momento de lanzamiento de la tarea pueden usar las " -"plantillas Jinja2 para ejecutar Python arbitrario. Se recomienda que este " -"valor se establezca como \"plantilla\" o \"nunca\"." - -#: awx/main/conf.py:163 +"templates to run arbitrary Python. It is recommended that this value be set " +"to \"template\" or \"never\"." +msgstr "Ansible permite la sustitución de variables a través del lenguaje de plantillas Jinja2 para --extra-vars. Esto presenta un potencial de riesgo a la seguridad, en donde los usuarios de Tower con la capacidad de especificar vars adicionales en el momento de lanzamiento de la tarea pueden usar las plantillas Jinja2 para ejecutar Python arbitrario. Se recomienda que este valor se establezca como \"plantilla\" o \"nunca\"." + +#: awx/main/conf.py:212 msgid "Enable job isolation" msgstr "Habilitar aislamiento de trabajo" -#: awx/main/conf.py:164 +#: awx/main/conf.py:213 msgid "" "Isolates an Ansible job from protected parts of the system to prevent " "exposing sensitive information." -msgstr "" -"Aísla un trabajo Ansible de partes protegidas del sistema para prevenir la " -"exposición de información confidencial." +msgstr "Aísla un trabajo Ansible de partes protegidas del sistema para prevenir la exposición de información confidencial." -#: awx/main/conf.py:172 +#: awx/main/conf.py:221 msgid "Job execution path" msgstr "Ruta de ejecución de una tarea" -#: awx/main/conf.py:173 +#: awx/main/conf.py:222 msgid "" "The directory in which Tower will create new temporary directories for job " "execution and isolation (such as credential files and custom inventory " "scripts)." -msgstr "" -"El directorio en el que Tower creará nuevos directorios temporales para la " -"ejecución y el aislamiento de tareas (como archivos de credenciales y " -"scripts de inventario personalizados)." +msgstr "El directorio en el que Tower creará nuevos directorios temporales para la ejecución y el aislamiento de tareas (como archivos de credenciales y scripts de inventario personalizados)." -#: awx/main/conf.py:184 +#: awx/main/conf.py:233 msgid "Paths to hide from isolated jobs" -msgstr "Rutas a ocultar para los trabajos aislados" +msgstr "Rutas a ocultar para los trabajos aislados." -#: awx/main/conf.py:185 +#: awx/main/conf.py:234 msgid "" "Additional paths to hide from isolated processes. Enter one path per line." -msgstr "" -"Rutas adicionales para ocultar de los procesos aislados. Ingrese una ruta " -"por línea." +msgstr "Rutas adicionales para ocultar de los procesos aislados. Ingrese una ruta por línea." -#: awx/main/conf.py:194 +#: awx/main/conf.py:243 msgid "Paths to expose to isolated jobs" -msgstr "Rutas por exponer para los trabajos aislados" +msgstr "Rutas a exponer para los trabajos aislados." -#: awx/main/conf.py:195 +#: awx/main/conf.py:244 msgid "" "Whitelist of paths that would otherwise be hidden to expose to isolated " "jobs. Enter one path per line." -msgstr "" -"Lista blanca de rutas que de otra manera se esconderían para exponer a " -"trabajos aislados. Ingrese una ruta por línea." +msgstr "Lista blanca de rutas que de otra manera se esconderían para exponer a trabajos aislados. Ingrese una ruta por línea." -#: awx/main/conf.py:204 +#: awx/main/conf.py:254 +msgid "Verbosity level for isolated node management tasks" +msgstr "Nivel de detalle para tareas de administración de nodos aisladas" + +#: awx/main/conf.py:255 +msgid "" +"This can be raised to aid in debugging connection issues for isolated task " +"execution" +msgstr "Este se puede elevar para asistir en la depuración de problemas de conexión para la ejecución de tareas aisladas" + +#: awx/main/conf.py:265 msgid "Isolated status check interval" msgstr "Intervalo de verificación de estado aislado" -#: awx/main/conf.py:205 +#: awx/main/conf.py:266 msgid "" "The number of seconds to sleep between status checks for jobs running on " "isolated instances." -msgstr "" -"La cantidad de segundos de inactividad entre las verificaciones de estado " -"para las tareas que se ejecutan en instancias aisladas." +msgstr "La cantidad de segundos de inactividad entre las verificaciones de estado para las tareas que se ejecutan en instancias aisladas." -#: awx/main/conf.py:214 +#: awx/main/conf.py:275 msgid "Isolated launch timeout" msgstr "Tiempo de espera para la ejecución aislada" -#: awx/main/conf.py:215 +#: awx/main/conf.py:276 msgid "" "The timeout (in seconds) for launching jobs on isolated instances. This " "includes the time needed to copy source control files (playbooks) to the " "isolated instance." -msgstr "" -"El tiempo de espera (en segundos) para ejecutar trabajos en instancias " -"aisladas. Esto incluye el tiempo que se necesita para copiar archivos de " -"control de código fuente (playbooks) en la instancia aislada." +msgstr "El tiempo de espera (en segundos) para ejecutar trabajos en instancias aisladas. Esto incluye el tiempo que se necesita para copiar archivos de control de código fuente (playbooks) en la instancia aislada." -#: awx/main/conf.py:226 +#: awx/main/conf.py:287 msgid "Isolated connection timeout" msgstr "Tiempo de espera para la conexión aislada" -#: awx/main/conf.py:227 +#: awx/main/conf.py:288 msgid "" "Ansible SSH connection timeout (in seconds) to use when communicating with " "isolated instances. Value should be substantially greater than expected " "network latency." -msgstr "" -"El tiempo de espera de conexión SSH de Ansible (en segundos) para usar " -"cuando se comunica con instancias aisladas. El valor debe ser " -"considerablemente superior a la latencia de red esperada." +msgstr "El tiempo de espera de conexión SSH de Ansible (en segundos) para usar cuando se comunica con instancias aisladas. El valor debe ser considerablemente superior a la latencia de red esperada." + +#: awx/main/conf.py:297 +msgid "Isolated host key checking" +msgstr "Verificación de clave de host aislada" + +#: awx/main/conf.py:298 +msgid "" +"When set to True, AWX will enforce strict host key checking for " +"communication with isolated nodes." +msgstr "Cuando se establezca en True, AWX aplicará una verificación de clave de host estricta para la comunicación con los nodos aislados." -#: awx/main/conf.py:237 +#: awx/main/conf.py:308 msgid "Generate RSA keys for isolated instances" -msgstr "Generar claves de RSA para instancias aisladas" +msgstr "Genere claves de RSA para instancias aisladas" -#: awx/main/conf.py:238 +#: awx/main/conf.py:309 msgid "" "If set, a random RSA key will be generated and distributed to isolated " "instances. To disable this behavior and manage authentication for isolated " "instances outside of Tower, disable this setting." -msgstr "" -"En caso de estar definida, se generará una clave de RSA y se distribuirá a " -"instancias aisladas. Para desactivar este comportamiento y administrar la " -"autenticación para instancias aisladas fuera de Tower, desactive esta " -"configuración." +msgstr "En caso de estar definida, se generará una clave de RSA y se distribuirá a instancias aisladas. Para desactivar este comportamiento y administrar la autenticación para instancias aisladas fuera de Tower, desactive esta configuración." -#: awx/main/conf.py:252 awx/main/conf.py:253 +#: awx/main/conf.py:323 awx/main/conf.py:324 msgid "The RSA private key for SSH traffic to isolated instances" msgstr "La clave privada RSA para el tráfico SSH en instancias aisladas" -#: awx/main/conf.py:264 awx/main/conf.py:265 +#: awx/main/conf.py:335 awx/main/conf.py:336 msgid "The RSA public key for SSH traffic to isolated instances" msgstr "La clave pública RSA para el tráfico SSH en instancias aisladas" -#: awx/main/conf.py:274 +#: awx/main/conf.py:345 +msgid "Enable detailed resource profiling on all playbook runs" +msgstr "Habilitar la creación de perfiles de recursos detallados en todas las ejecuciones del cuaderno de estrategias" + +#: awx/main/conf.py:346 +msgid "" +"If set, detailed resource profiling data will be collected on all jobs. This " +"data can be gathered with `sosreport`." +msgstr "Si se establece, se recopilarán datos de la creación de perfiles de recursos detallados en todos los trabajos. Estos datos pueden recopilarse con `sosreport'." + +#: awx/main/conf.py:356 +msgid "Interval (in seconds) between polls for cpu usage." +msgstr "Intervalo (en segundos) entre encuestas para el uso de la cpu." + +#: awx/main/conf.py:357 +msgid "" +"Interval (in seconds) between polls for cpu usage. Setting this lower than " +"the default will affect playbook performance." +msgstr "Intervalo (en segundos) entre encuestas para el uso de la cpu. Si se ajusta a un valor más bajo que el predeterminado, el rendimiento del cuaderno de estrategias se verá afectado." + +#: awx/main/conf.py:368 +msgid "Interval (in seconds) between polls for memory usage." +msgstr "Intervalo (en segundos) entre encuestas para el uso de la memoria." + +#: awx/main/conf.py:369 +msgid "" +"Interval (in seconds) between polls for memory usage. Setting this lower " +"than the default will affect playbook performance." +msgstr "Intervalo (en segundos) entre encuestas para el uso de la memoria. Si se ajusta a un valor más bajo que el predeterminado, el rendimiento del cuaderno de estrategias se verá afectado." + +#: awx/main/conf.py:380 +msgid "Interval (in seconds) between polls for PID count." +msgstr "Intervalo (en segundos) entre encuestas para el recuento de PID." + +#: awx/main/conf.py:381 +msgid "" +"Interval (in seconds) between polls for PID count. Setting this lower than " +"the default will affect playbook performance." +msgstr "Intervalo (en segundos) entre encuestas para el recuento de PID. Si se ajusta a un valor más bajo que el predeterminado, el rendimiento del cuaderno de estrategias se verá afectado." + +#: awx/main/conf.py:392 msgid "Extra Environment Variables" msgstr "Variables de entorno adicionales" -#: awx/main/conf.py:275 +#: awx/main/conf.py:393 msgid "" "Additional environment variables set for playbook runs, inventory updates, " "project updates, and notification sending." -msgstr "" -"Las variables de entorno adicionales establecidas para ejecuciones de " -"playbook, actualizaciones de inventarios, actualizaciones de proyectos y " -"envío de notificaciones." +msgstr "Las variables de entorno adicionales establecidas para ejecuciones de playbook, actualizaciones de inventarios, actualizaciones de proyectos y envío de notificaciones." + +#: awx/main/conf.py:403 +msgid "Gather data for Automation Analytics" +msgstr "Recopile datos para Automation Analytics" + +#: awx/main/conf.py:404 +msgid "Enables Tower to gather data on automation and send it to Red Hat." +msgstr "Permite que Tower recopile datos sobre automatización y los envíe a Red Hat." + +#: awx/main/conf.py:412 +msgid "Run Project Updates With Higher Verbosity" +msgstr "Ejecutar actualizaciones de proyectos con más detalles" + +#: awx/main/conf.py:413 +msgid "" +"Adds the CLI -vvv flag to ansible-playbook runs of project_update.yml used " +"for project updates." +msgstr "Añade el indicador CLI -vvv a las ejecuciones del cuaderno de estrategias de Ansible de project_update.yml utilizado para las actualizaciones del proyecto." + +#: awx/main/conf.py:422 +msgid "Enable Role Download" +msgstr "Habilitar descarga de roles" + +#: awx/main/conf.py:423 +msgid "" +"Allows roles to be dynamically downloaded from a requirements.yml file for " +"SCM projects." +msgstr "Permite que se descarguen los roles de forma dinámica desde un archivo requirements.yml para proyectos de SCM." + +#: awx/main/conf.py:432 +msgid "Enable Collection(s) Download" +msgstr "Habilitar la descarga de la(s) recopilación(es)" + +#: awx/main/conf.py:433 +msgid "" +"Allows collections to be dynamically downloaded from a requirements.yml file " +"for SCM projects." +msgstr "Permite que se descarguen las recopilaciones de forma dinámica desde un archivo requirements.yml para proyectos de SCM." + +#: awx/main/conf.py:443 +msgid "Primary Galaxy Server URL" +msgstr "URL del servidor principal de Galaxy" + +#: awx/main/conf.py:445 +msgid "" +"For organizations that run their own Galaxy service, this gives the option " +"to specify a host as the primary galaxy server. Requirements will be " +"downloaded from the primary if the specific role or collection is available " +"there. If the content is not avilable in the primary, or if this field is " +"left blank, it will default to galaxy.ansible.com." +msgstr "Para las organizaciones que ejecutan su propio servicio Galaxy, esto da la opción de especificar un host como el servidor de Galaxy principal. Los requisitos se descargarán del principal si la función o recopilación específica está disponible allí. Si el contenido no está disponible en el principal, o si este campo se deja en blanco, de forma predeterminada será galaxy.ansible.com." -#: awx/main/conf.py:285 +#: awx/main/conf.py:459 +msgid "Primary Galaxy Server Username" +msgstr "Nombre de usuario del servidor principal Galaxy" + +#: awx/main/conf.py:460 +msgid "" +"For using a galaxy server at higher precedence than the public Ansible " +"Galaxy. The username to use for basic authentication against the Galaxy " +"instance, this is mutually exclusive with PRIMARY_GALAXY_TOKEN." +msgstr "Para usar un servidor Galaxy con mayor prioridad que el Ansible Galaxy público. El nombre de usuario para usar para la autenticación básica frente a la instancia de Galaxy, esto se excluye mutuamente con PRIMARY_GALAXY_TOKEN." + +#: awx/main/conf.py:473 +msgid "Primary Galaxy Server Password" +msgstr "Contraseña del servidor principal de Galaxy" + +#: awx/main/conf.py:474 +msgid "" +"For using a galaxy server at higher precedence than the public Ansible " +"Galaxy. The password to use for basic authentication against the Galaxy " +"instance, this is mutually exclusive with PRIMARY_GALAXY_TOKEN." +msgstr "Para usar un servidor de Galaxy con mayor prioridad que el Ansible Galaxy público. La contraseña para usar para la autenticación básica frente la instancia de Galaxy, esto se excluye mutuamente con PRIMARY_GALAXY_TOKEN." + +#: awx/main/conf.py:487 +msgid "Primary Galaxy Server Token" +msgstr "Token del servidor principal de Galaxy" + +#: awx/main/conf.py:488 +msgid "" +"For using a galaxy server at higher precedence than the public Ansible " +"Galaxy. The token to use for connecting with the Galaxy instance, this is " +"mutually exclusive with corresponding username and password settings." +msgstr "Para usar un servidor de Galaxy con mayor prioridad que el Ansible Galaxy público. El token para usar para conectarse con la instancia de Galaxy, esto se excluye mutuamente con los correspondientes ajustes de nombre de usuario y contraseña." + +#: awx/main/conf.py:500 +msgid "Primary Galaxy Authentication URL" +msgstr "URL de autenticación principal de Galaxy" + +#: awx/main/conf.py:501 +msgid "" +"For using a galaxy server at higher precedence than the public Ansible " +"Galaxy. The token_endpoint of a Keycloak server." +msgstr "Para Por usar un servidor de Galaxy con mayor prioridad que el Ansible Galaxy público. El token_endpoint de un servidor Keycloak." + +#: awx/main/conf.py:511 +msgid "Allow Access to Public Galaxy" +msgstr "Permitir el acceso a Galaxy público" + +#: awx/main/conf.py:512 +msgid "" +"Allow or deny access to the public Ansible Galaxy during project updates." +msgstr "Permitir o denegar el acceso Ansible Galaxy público durante las actualizaciones de proyectos." + +#: awx/main/conf.py:521 +msgid "Ignore Ansible Galaxy SSL Certificate Verification" +msgstr "Ignore la verificación de certificados SSL de Ansible Galaxy" + +#: awx/main/conf.py:522 +msgid "" +"If set to true, certificate validation will not be done wheninstalling " +"content from any Galaxy server." +msgstr "Si se establece en true, la validación del certificado no se realizará al instalar contenido de cualquier servidor de Galaxy." + +#: awx/main/conf.py:532 msgid "Standard Output Maximum Display Size" -msgstr "Tamaño de visualización máximo para la salida estándar" +msgstr "Tamaño máximo a mostrar para la salida estándar." -#: awx/main/conf.py:286 +#: awx/main/conf.py:533 msgid "" "Maximum Size of Standard Output in bytes to display before requiring the " "output be downloaded." -msgstr "" -"Tamaño máximo de la salida estándar en bytes para mostrar antes de obligar a" -" que la salida sea descargada." +msgstr "Tamaño máximo de la salida estándar en bytes para mostrar antes de obligar a que la salida sea descargada." -#: awx/main/conf.py:295 +#: awx/main/conf.py:542 msgid "Job Event Standard Output Maximum Display Size" -msgstr "" -"Tamaño de visualización máximo de la salida estándar del evento del trabajo" +msgstr "Tamaño máximo de la salida estándar para mostrar del evento del trabajo." -#: awx/main/conf.py:297 +#: awx/main/conf.py:544 msgid "" "Maximum Size of Standard Output in bytes to display for a single job or ad " "hoc command event. `stdout` will end with `…` when truncated." -msgstr "" -"Tamaño máximo de la salida estándar en bytes por mostrar para un único " -"trabajo o evento del comando ad hoc. `stdout` terminará con `...` cuando se " -"trunque." +msgstr "Tamaño máximo de la salida estándar en bytes a mostrar para un único trabajo o evento del comando ad hoc. `stdout` terminará con `...` cuando sea truncado." -#: awx/main/conf.py:306 +#: awx/main/conf.py:553 msgid "Maximum Scheduled Jobs" -msgstr "Número máximo de trabajos programados" +msgstr "Máximo número de trabajos programados." -#: awx/main/conf.py:307 +#: awx/main/conf.py:554 msgid "" "Maximum number of the same job template that can be waiting to run when " "launching from a schedule before no more are created." -msgstr "" -"Número máximo de la misma plantilla de trabajo que puede estar esperando " -"para ejecutarse cuando se lanza desde una programación antes de que no se " -"creen más." +msgstr "Número máximo de la misma plantilla de trabajo que pueden estar esperando para ser ejecutado cuando se lanzan desde una programación antes de que no se creen más." -#: awx/main/conf.py:316 +#: awx/main/conf.py:563 msgid "Ansible Callback Plugins" msgstr "Plugins de Ansible callback" -#: awx/main/conf.py:317 +#: awx/main/conf.py:564 msgid "" "List of paths to search for extra callback plugins to be used when running " "jobs. Enter one path per line." -msgstr "" -"Lista de rutas para buscar complementos adicionales de retorno de llamada " -"que se utilizan cuando se ejecutan tareas. Ingrese una ruta por línea." +msgstr "Lista de rutas para buscar complementos adicionales de retorno de llamada que se utilizan cuando se ejecutan tareas. Ingrese una ruta por línea." -#: awx/main/conf.py:327 +#: awx/main/conf.py:574 msgid "Default Job Timeout" -msgstr "Tiempo de espera predeterminado para el trabajo" +msgstr "Tiempo de espera por defecto para el trabajo" -#: awx/main/conf.py:328 +#: awx/main/conf.py:575 msgid "" "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." -msgstr "" -"Tiempo máximo en segundos para permitir que se ejecuten tareas. Utilice el " -"valor 0 para indicar que no se impondrá ningún tiempo de espera. Un tiempo " -"de espera establecido en una plantilla de trabajo individual reemplazará " -"este." +msgstr "Tiempo máximo en segundos para permitir que se ejecuten tareas. Utilice el valor 0 para indicar que no se impondrá ningún tiempo de espera. Un tiempo de espera establecido en una plantilla de trabajo individual reemplazará este." -#: awx/main/conf.py:339 +#: awx/main/conf.py:586 msgid "Default Inventory Update Timeout" -msgstr "Tiempo de espera por defecto para la actualización del inventario" +msgstr "Tiempo de espera por defecto para la actualización del inventario." -#: awx/main/conf.py:340 +#: awx/main/conf.py:587 msgid "" -"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 " +"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." -msgstr "" -"Tiempo máximo en segundos para permitir la ejecución de actualizaciones de " -"inventario. Utilice el valor 0 para indicar que no debería señalarse ningún " -"tipo de tiempo de expiración. El tiempo de expiración definido para una " -"fuente de inventario individual debería anularlo." +msgstr "Tiempo máximo en segundos para permitir la ejecución de actualizaciones de inventario. Utilice el valor 0 para indicar que no debería señalarse ningún tipo de tiempo de expiración. El tiempo de expiración definido para una fuente de inventario individual debería anularlo." -#: awx/main/conf.py:351 +#: awx/main/conf.py:598 msgid "Default Project Update Timeout" -msgstr "Tiempo de espera predeterminado para la actualización de un proyecto" +msgstr "Tiempo de espera por defecto para la actualización de un proyecto" -#: awx/main/conf.py:352 +#: awx/main/conf.py:599 msgid "" "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." -msgstr "" -"Tiempo máximo en segundos para permitir que se ejecuten las actualizaciones " -"de los proyectos. Utilice un valor de 0 para indicar que no debería " -"definirse ningún tipo de tiempo de expiración. El tiempo de expiración " -"definido para una fuente de inventario individual debería anularlo." +msgstr "Tiempo máximo en segundos para permitir que se ejecuten las actualizaciones de los proyectos. Utilice un valor de 0 para indicar que no debería definirse ningún tipo de tiempo de expiración. El tiempo de expiración definido para una fuente de inventario individual debería anularlo." -#: awx/main/conf.py:363 +#: awx/main/conf.py:610 msgid "Per-Host Ansible Fact Cache Timeout" msgstr "Tiempo de espera de la caché de eventos Ansible por host" -#: awx/main/conf.py:364 +#: awx/main/conf.py:611 msgid "" "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 " +"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." -msgstr "" -"Tiempo máximo, en segundos, en que los datos almacenados de Ansible se " -"consideran válidos desde la última vez que fueron modificados. Solo se podrá" -" acceder a datos válidos y actualizados mediante la playbook. Observe que " -"esto no influye en la eliminación de datos de Ansible de la base de datos. " -"Use un valor de 0 para indicar que no se debe imponer ningún tiempo de " -"expiración." - -#: awx/main/conf.py:377 +msgstr "Tiempo máximo, en segundos, en que los datos almacenados de Ansible se consideran válidos desde la última vez que fueron modificados. Solo se podrá acceder a datos válidos y actualizados mediante la playbook. Observe que esto no influye en la eliminación de datos de Ansible de la base de datos. Use un valor de 0 para indicar que no se debe imponer ningún tiempo de expiración." + +#: awx/main/conf.py:624 +msgid "Maximum number of forks per job." +msgstr "Número máximo de bifurcaciones por tarea." + +#: awx/main/conf.py:625 +msgid "" +"Saving a Job Template with more than this number of forks will result in an " +"error. When set to 0, no limit is applied." +msgstr "Guardar una plantilla de tarea con un número de bifurcaciones superior a este resultará en un error. Cuando se establece en 0, no se aplica ningún límite." + +#: awx/main/conf.py:636 msgid "Logging Aggregator" -msgstr "Agregador de registros" +msgstr "Agregación de registros" -#: awx/main/conf.py:378 +#: awx/main/conf.py:637 msgid "Hostname/IP where external logs will be sent to." -msgstr "Nombre de host/IP donde se enviarán los registros externos." - -#: awx/main/conf.py:379 awx/main/conf.py:390 awx/main/conf.py:402 -#: awx/main/conf.py:412 awx/main/conf.py:424 awx/main/conf.py:439 -#: awx/main/conf.py:451 awx/main/conf.py:460 awx/main/conf.py:470 -#: awx/main/conf.py:482 awx/main/conf.py:493 awx/main/conf.py:505 -#: awx/main/conf.py:518 +msgstr "Hostname/IP donde los logs externos serán enviados." + +#: awx/main/conf.py:638 awx/main/conf.py:649 awx/main/conf.py:661 +#: awx/main/conf.py:671 awx/main/conf.py:683 awx/main/conf.py:698 +#: awx/main/conf.py:710 awx/main/conf.py:719 awx/main/conf.py:729 +#: awx/main/conf.py:741 awx/main/conf.py:752 awx/main/conf.py:764 +#: awx/main/conf.py:777 awx/main/conf.py:787 awx/main/conf.py:799 +#: awx/main/conf.py:810 awx/main/conf.py:820 msgid "Logging" msgstr "Registros" -#: awx/main/conf.py:387 +#: awx/main/conf.py:646 msgid "Logging Aggregator Port" msgstr "Puerto de agregación de registros" -#: awx/main/conf.py:388 +#: awx/main/conf.py:647 msgid "" "Port on Logging Aggregator to send logs to (if required and not provided in " "Logging Aggregator)." -msgstr "" -"El puerto del Agregador de registros al cual enviar registros (si se " -"requiere y no está definido en el Agregador de registros)." +msgstr "El puerto del Agregador de Logs al cual enviar logs (si es requerido y no está definido en el Agregador de Logs)." -#: awx/main/conf.py:400 +#: awx/main/conf.py:659 msgid "Logging Aggregator Type" -msgstr "Tipo de agregador de registros" +msgstr "Tipo de agregación de registros." -#: awx/main/conf.py:401 +#: awx/main/conf.py:660 msgid "Format messages for the chosen log aggregator." -msgstr "Mensajes de formato para el agregador de registros escogido." +msgstr "Formato de mensajes para el agregador de registros escogidos." -#: awx/main/conf.py:410 +#: awx/main/conf.py:669 msgid "Logging Aggregator Username" msgstr "Usuario del agregador de registros" -#: awx/main/conf.py:411 -msgid "Username for external log aggregator (if required)." -msgstr "Usuario para el agregador de registros externo (si es necesario)." +#: awx/main/conf.py:670 +msgid "Username for external log aggregator (if required; HTTP/s only)." +msgstr "Nombre de usuario para el agregador de registros externos (si es necesario; solo HTTP/s)." -#: awx/main/conf.py:422 +#: awx/main/conf.py:681 msgid "Logging Aggregator Password/Token" msgstr "Contraseña/Token del agregador de registros" -#: awx/main/conf.py:423 +#: awx/main/conf.py:682 msgid "" -"Password or authentication token for external log aggregator (if required)." -msgstr "" -"Contraseña o token de autenticación para el agregador de registros externo " -"(si es necesario)." +"Password or authentication token for external log aggregator (if required; " +"HTTP/s only)." +msgstr "Contraseña o token de autentificación para el agregador de registros externos (si es necesario; solo HTTP/s)." -#: awx/main/conf.py:432 +#: awx/main/conf.py:691 msgid "Loggers Sending Data to Log Aggregator Form" -msgstr "" -"Registradores que envían datos al formulario del agregador de registros" +msgstr "Registradores que envían datos al formulario de agregadores de registros" -#: awx/main/conf.py:433 +#: awx/main/conf.py:692 msgid "" -"List of loggers that will send HTTP logs to the collector, these can include any or all of: \n" +"List of loggers that will send HTTP logs to the collector, these can include " +"any or all of: \n" "awx - service logs\n" "activity_stream - activity stream records\n" "job_events - callback data from Ansible job events\n" "system_tracking - facts gathered from scan jobs." -msgstr "" -"Lista de registradores que enviarán registros HTTP al colector. Estos pueden incluir cualquiera o todos los siguientes: \n" +msgstr "Lista de registradores que enviarán registros HTTP al colector. Estos pueden incluir cualquiera o todos los siguientes: \n" "awx - registros de servicio\n" "activity_stream - registros de flujo de actividades\n" "job_events - datos de retorno de llamada de eventos de tareas Ansible\n" "system_tracking - información obtenida de tareas de análisis" -#: awx/main/conf.py:446 +#: awx/main/conf.py:705 msgid "Log System Tracking Facts Individually" -msgstr "Sistema de registros rastrea los hechos individualmente" +msgstr "Sistema de registros tratará los facts individualmente." -#: awx/main/conf.py:447 +#: awx/main/conf.py:706 msgid "" "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." -msgstr "" -"Si se establece, los datos del sistema de rastreo se enviarán para cada " -"paquete, servicio u otro elemento encontrado en el escaneo, lo que permite " -"mayor granularidad en la consulta de búsqueda. Si no se establece, los datos" -" se enviarán como un único diccionario, lo que permite mayor eficiencia en " -"el proceso de datos." +msgstr "Si se establece, los datos del sistema de rastreo se enviarán para cada paquete, servicio u otro elemento encontrado en el escaneo, lo que permite mayor granularidad en la consulta de búsqueda. Si no se establece, los datos se enviarán como un único diccionario, lo que permite mayor eficiencia en el proceso de datos." -#: awx/main/conf.py:458 +#: awx/main/conf.py:717 msgid "Enable External Logging" msgstr "Habilitar registro externo" -#: awx/main/conf.py:459 +#: awx/main/conf.py:718 msgid "Enable sending logs to external log aggregator." msgstr "Habilitar el envío de registros a un agregador de registros externo." -#: awx/main/conf.py:468 +#: awx/main/conf.py:727 msgid "Cluster-wide Tower unique identifier." msgstr "Indentificador de Torre único a través del cluster." -#: awx/main/conf.py:469 +#: awx/main/conf.py:728 msgid "Useful to uniquely identify Tower instances." -msgstr "Útil para identificar instancias de Torre de manera única." +msgstr "Útil para identificar instancias de Torre." -#: awx/main/conf.py:478 +#: awx/main/conf.py:737 msgid "Logging Aggregator Protocol" msgstr "Registrando protocolo de agregador" -#: awx/main/conf.py:479 +#: awx/main/conf.py:738 msgid "" "Protocol used to communicate with log aggregator. HTTPS/HTTP assumes HTTPS " "unless http:// is explicitly used in the Logging Aggregator hostname." -msgstr "" -"Protocolo utilizado para comunicarse con el agregador de registros. " -"HTTPS/HTTP asume HTTPS a menos que se utilice http:// explícitamente en el " -"nombre de host del agregador de registros." +msgstr "Protocolo utilizado para comunicarse con el agregador de registros. HTTPS/HTTP asume HTTPS a menos que se utilice http:// explícitamente en el nombre de host del agregador de registros." -#: awx/main/conf.py:489 +#: awx/main/conf.py:748 msgid "TCP Connection Timeout" msgstr "Tiempo de espera para la conexión TCP" -#: awx/main/conf.py:490 +#: awx/main/conf.py:749 msgid "" "Number of seconds for a TCP connection to external log aggregator to " "timeout. Applies to HTTPS and TCP log aggregator protocols." -msgstr "" -"Cantidad de segundos para que una conexión TCP a un agregador de registros " -"externo caduque. Aplica a protocolos de agregadores de registros HTTPS y " -"TCP." +msgstr "Cantidad de segundos para que una conexión TCP a un agregador de registros externo caduque. Aplica a protocolos de agregadores de registros HTTPS y TCP." -#: awx/main/conf.py:500 +#: awx/main/conf.py:759 msgid "Enable/disable HTTPS certificate verification" msgstr "Habilitar/deshabilitar verificación de certificado HTTPS" -#: awx/main/conf.py:501 +#: awx/main/conf.py:760 msgid "" "Flag to control enable/disable of certificate verification when " "LOG_AGGREGATOR_PROTOCOL is \"https\". If enabled, Tower's log handler will " "verify certificate sent by external log aggregator before establishing " "connection." -msgstr "" -"Indicador para controlar la habilitación/deshabilitación de la verificación " -"del certificado cuando LOG_AGGREGATOR_PROTOCOL es \"https\". Si está " -"habilitado, el controlador de registros de Tower verificará que el agregador" -" de registros externo haya enviado el certificado antes de establecer la " -"conexión." +msgstr "Indicador para controlar la habilitación/deshabilitación de la verificación del certificado cuando LOG_AGGREGATOR_PROTOCOL es \"https\". Si está habilitado, el controlador de registros de Tower verificará que el agregador de registros externo haya enviado el certificado antes de establecer la conexión." -#: awx/main/conf.py:513 +#: awx/main/conf.py:772 msgid "Logging Aggregator Level Threshold" msgstr "Umbral de nivel del agregador de registros" -#: awx/main/conf.py:514 +#: awx/main/conf.py:773 msgid "" "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)" -msgstr "" -"El umbral de nivel utilizado por el controlador de registros. Los niveles de" -" gravedad desde el menor hasta el mayor son DEBUG, INFO, WARNING, ERROR, " -"CRITICAL. El controlador de registros ignorará los mensajes menos graves que" -" el umbral (los mensajes de la categoría awx.anlytics omiten este ajuste)." +"threshold will be ignored by log handler. (messages under category awx." +"anlytics ignore this setting)" +msgstr "El umbral de nivel utilizado por el controlador de registros. Los niveles de gravedad desde el menor hasta el mayor son DEBUG, INFO, WARNING, ERROR, CRITICAL. El controlador de registros ignorará los mensajes menos graves que el umbral (los mensajes de la categoría awx.anlytics omiten este ajuste)." + +#: awx/main/conf.py:785 +msgid "Enabled external log aggregation auditing" +msgstr "Auditoría de agregación de registros externos habilitada " + +#: awx/main/conf.py:786 +msgid "" +"When enabled, all external logs emitted by Tower will also be written to /" +"var/log/tower/external.log. This is an experimental setting intended to be " +"used for debugging external log aggregation issues (and may be subject to " +"change in the future)." +msgstr "Cuando está habilitada, todos los registros externos emitidos por Tower también se escribirán en /var/log/tower/external.log. Se trata de un entorno experimental destinado a ser utilizado para depurar problemas de agregación de registros externos (y puede estar sujeto a cambios en el futuro)." + +#: awx/main/conf.py:795 +msgid "Maximum disk persistance for external log aggregation (in GB)" +msgstr "Máxima persistencia del disco para la agregación de registros externos (en GB)" -#: awx/main/conf.py:537 awx/sso/conf.py:1264 +#: awx/main/conf.py:796 +msgid "" +"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." +msgstr "Cantidad de datos para almacenar (en gigabytes) durante una interrupción del agregador de registros externos (el valor predeterminado es 1). Equivalente a la configuración de rsyslogd queue.maxdiskspace." + +#: awx/main/conf.py:806 +msgid "File system location for rsyslogd disk persistence" +msgstr "Ubicación del sistema de archivos para la persistencia del disco rsyslogd" + +#: awx/main/conf.py:807 +msgid "" +"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." +msgstr "Ubicación para persistir los registros que se deben volver a intentar después de una interrupción del agregador de registros externos (el valor predeterminado es /var/lib/awx). Equivalente a la configuración de rsyslogd queue.spoolDirectory." + +#: awx/main/conf.py:817 +msgid "Enable rsyslogd debugging" +msgstr "Habilitar la depuración de rsyslogd" + +#: awx/main/conf.py:818 +msgid "" +"Enabled high verbosity debugging for rsyslogd. Useful for debugging " +"connection issues for external log aggregation." +msgstr "Habilitó la depuración con nivel de detalle alto para rsyslogd. Útil para depurar problemas de conexión para la agregación de registros externos." + +#: awx/main/conf.py:828 +msgid "Message Durability" +msgstr "Durabilidad del mensaje" + +#: awx/main/conf.py:829 +msgid "" +"When set (the default), underlying queues will be persisted to disk. " +"Disable this to enable higher message bus throughput." +msgstr "Cuando se establece (el valor predeterminado), las colas subyacentes persistirán en el disco. Desactívelo para habilitar un mayor rendimiento del bus de mensajes." + +#: awx/main/conf.py:838 +msgid "Last gather date for Automation Analytics." +msgstr "Última fecha de reunión para el Análisis de Automatización." + +#: awx/main/conf.py:848 +msgid "Automation Analytics Gather Interval" +msgstr "Los análisis de automatización recogen el intervalo" + +#: awx/main/conf.py:849 +msgid "Interval (in seconds) between data gathering." +msgstr "Intervalo (en segundos) entre la recolección de datos." + +#: awx/main/conf.py:871 awx/sso/conf.py:1239 msgid "\n" msgstr "\n" +#: awx/main/conf.py:892 +msgid "" +"A URL for Primary Galaxy must be defined before disabling public Galaxy." +msgstr "Se debe definir un URL para el servidor principal de Galaxy antes de deshabilitar el servidor público de Galaxy." + +#: awx/main/conf.py:912 +msgid "Cannot provide field if PRIMARY_GALAXY_URL is not set." +msgstr "No puede proporcionar el campo si PRIMARY_GALAXY_URL no está establecido." + +#: awx/main/conf.py:925 +#, python-brace-format +msgid "" +"Galaxy server settings are not available until Ansible {min_version}, you " +"are running {current_version}." +msgstr "La configuración del servidor de Galaxy no está disponible hasta Ansible {min_version}; usted está ejecutando{current_version}." + +#: awx/main/conf.py:934 +msgid "" +"Setting Galaxy token and authentication URL is mutually exclusive with " +"username and password." +msgstr "La configuración del token de Galaxy y la URL de autenticación se excluyen mutuamente con el nombre de usuario y la contraseña." + +#: awx/main/conf.py:937 +msgid "If authenticating via username and password, both must be provided." +msgstr "Si se autentica con un nombre de usuario y una contraseña, se deben proporcionar ambos." + +#: awx/main/conf.py:943 +msgid "" +"If authenticating via token, both token and authentication URL must be " +"provided." +msgstr "Si la autenticación se realiza mediante un token, se debe proporcionar tanto el token como la URL de autenticación." + #: awx/main/constants.py:17 msgid "Sudo" msgstr "Sudo" @@ -2507,207 +2684,404 @@ msgstr "Habilitar" msgid "Doas" msgstr "Doas" -#: awx/main/constants.py:21 +#: awx/main/constants.py:19 +msgid "Ksu" +msgstr "Ksu" + +#: awx/main/constants.py:20 +msgid "Machinectl" +msgstr "Machinectl" + +#: awx/main/constants.py:20 +msgid "Sesu" +msgstr "Sesu" + +#: awx/main/constants.py:22 msgid "None" msgstr "Ninguno" -#: awx/main/fields.py:62 +#: awx/main/credential_plugins/aim.py:16 +msgid "CyberArk AIM URL" +msgstr "CyberArk AIM URL" + +#: awx/main/credential_plugins/aim.py:21 +msgid "Application ID" +msgstr "ID de aplicación" + +#: awx/main/credential_plugins/aim.py:26 +msgid "Client Key" +msgstr "Clave de cliente" + +#: awx/main/credential_plugins/aim.py:32 +msgid "Client Certificate" +msgstr "Certificado de cliente" + +#: awx/main/credential_plugins/aim.py:38 +msgid "Verify SSL Certificates" +msgstr "Verificar certificados SSL" + +#: awx/main/credential_plugins/aim.py:44 +msgid "Object Query" +msgstr "Consulta de objetos" + +#: awx/main/credential_plugins/aim.py:46 +msgid "" +"Lookup query for the object. Ex: Safe=TestSafe;Object=testAccountName123" +msgstr "Consulta de búsqueda para el objeto. Ej.: Safe=TestSafe;Object=testAccountName123" + +#: awx/main/credential_plugins/aim.py:49 +msgid "Object Query Format" +msgstr "Formato de la consulta de objetos" + +#: awx/main/credential_plugins/aim.py:55 +msgid "Reason" +msgstr "Razón" + +#: awx/main/credential_plugins/aim.py:57 +msgid "" +"Object request reason. This is only needed if it is required by the object's " +"policy." +msgstr "Razón de la solicitud de objetos. Esta solo es necesaria si lo requiere la política del objeto." + +#: awx/main/credential_plugins/azure_kv.py:21 +msgid "Vault URL (DNS Name)" +msgstr "URL de Vault (Nombre de DNS)" + +#: awx/main/credential_plugins/azure_kv.py:26 +#: awx/main/models/credential/__init__.py:956 +msgid "Client ID" +msgstr "ID del cliente" + +#: awx/main/credential_plugins/azure_kv.py:35 +#: awx/main/models/credential/__init__.py:965 +msgid "Tenant ID" +msgstr "ID inquilino [Tenant]" + +#: awx/main/credential_plugins/azure_kv.py:39 +msgid "Cloud Environment" +msgstr "Entorno de nube" + +#: awx/main/credential_plugins/azure_kv.py:40 +msgid "Specify which azure cloud environment to use." +msgstr "Especifique el entorno de nube de Azure que se debe usar." + +#: awx/main/credential_plugins/azure_kv.py:46 +msgid "Secret Name" +msgstr "Nombre del secreto" + +#: awx/main/credential_plugins/azure_kv.py:48 +msgid "The name of the secret to look up." +msgstr "El nombre del secreto para buscar." + +#: awx/main/credential_plugins/azure_kv.py:51 +#: awx/main/credential_plugins/conjur.py:47 +msgid "Secret Version" +msgstr "Versión del secreto" + +#: awx/main/credential_plugins/azure_kv.py:53 +#: awx/main/credential_plugins/conjur.py:49 +#: awx/main/credential_plugins/hashivault.py:67 +msgid "" +"Used to specify a specific secret version (if left empty, the latest version " +"will be used)." +msgstr "Se utiliza para especificar una versión específica del secreto (si se deja vacía, se utilizará la última versión)." + +#: awx/main/credential_plugins/conjur.py:18 +msgid "Conjur URL" +msgstr "URL de Conjur" + +#: awx/main/credential_plugins/conjur.py:23 +msgid "API Key" +msgstr "Clave API" + +#: awx/main/credential_plugins/conjur.py:28 awx/main/models/inventory.py:1018 +msgid "Account" +msgstr "Cuenta" + +#: awx/main/credential_plugins/conjur.py:32 +#: awx/main/models/credential/__init__.py:598 +#: awx/main/models/credential/__init__.py:654 +#: awx/main/models/credential/__init__.py:712 +#: awx/main/models/credential/__init__.py:785 +#: awx/main/models/credential/__init__.py:834 +#: awx/main/models/credential/__init__.py:860 +#: awx/main/models/credential/__init__.py:887 +#: awx/main/models/credential/__init__.py:947 +#: awx/main/models/credential/__init__.py:1020 +#: awx/main/models/credential/__init__.py:1051 +#: awx/main/models/credential/__init__.py:1101 +msgid "Username" +msgstr "Usuario" + +#: awx/main/credential_plugins/conjur.py:36 +msgid "Public Key Certificate" +msgstr "Certificado de clave pública" + +#: awx/main/credential_plugins/conjur.py:42 +msgid "Secret Identifier" +msgstr "Identificador del secreto" + +#: awx/main/credential_plugins/conjur.py:44 +msgid "The identifier for the secret e.g., /some/identifier" +msgstr "El identificador para el secreto; por ejemplo, /some/identifier" + +#: awx/main/credential_plugins/hashivault.py:19 +msgid "Server URL" +msgstr "URL del servidor" + +#: awx/main/credential_plugins/hashivault.py:22 +msgid "The URL to the HashiCorp Vault" +msgstr "La URL para HashiCorp Vault" + +#: awx/main/credential_plugins/hashivault.py:25 +#: awx/main/models/credential/__init__.py:986 +#: awx/main/models/credential/__init__.py:1003 +msgid "Token" +msgstr "Token" + +#: awx/main/credential_plugins/hashivault.py:28 +msgid "The access token used to authenticate to the Vault server" +msgstr "El token de acceso utilizado para autenticar el servidor de Vault" + +#: awx/main/credential_plugins/hashivault.py:31 +msgid "CA Certificate" +msgstr "Certificado de CA" + +#: awx/main/credential_plugins/hashivault.py:34 +msgid "" +"The CA certificate used to verify the SSL certificate of the Vault server" +msgstr "El certificado de CA utilizado para verificar el certificado SSL del servidor de almacén" + +#: awx/main/credential_plugins/hashivault.py:38 +msgid "Path to Secret" +msgstr "Ruta al secreto" + +#: awx/main/credential_plugins/hashivault.py:40 +msgid "The path to the secret stored in the secret backend e.g, /some/secret/" +msgstr "La ruta al secreto almacenado en el backend de secretos; por ejemplo, /some/secret/" + +#: awx/main/credential_plugins/hashivault.py:48 +msgid "API Version" +msgstr "Versión de la API" + +#: awx/main/credential_plugins/hashivault.py:50 +msgid "" +"API v1 is for static key/value lookups. API v2 is for versioned key/value " +"lookups." +msgstr "API v1 es para búsquedas de valores/claves estáticos. API v2 es para búsquedas de valores/claves con versiones." + +#: awx/main/credential_plugins/hashivault.py:55 +msgid "Name of Secret Backend" +msgstr "Nombre del backend de secretos" + +#: awx/main/credential_plugins/hashivault.py:57 +msgid "" +"The name of the kv secret backend (if left empty, the first segment of the " +"secret path will be used)." +msgstr "El nombre del backend de secretos kv (si deja vacío, se utilizará el primer segmento de la ruta del secreto)." + +#: awx/main/credential_plugins/hashivault.py:60 +#: awx/main/models/inventory.py:1023 +msgid "Key Name" +msgstr "Nombre clave" + +#: awx/main/credential_plugins/hashivault.py:62 +msgid "The name of the key to look up in the secret." +msgstr "El nombre de la clave para buscar en el secreto." + +#: awx/main/credential_plugins/hashivault.py:65 +msgid "Secret Version (v2 only)" +msgstr "Versión del secreto (solo v2)" + +#: awx/main/credential_plugins/hashivault.py:74 +msgid "Unsigned Public Key" +msgstr "Clave pública sin signo" + +#: awx/main/credential_plugins/hashivault.py:79 +msgid "Role Name" +msgstr "Nombre del rol" + +#: awx/main/credential_plugins/hashivault.py:81 +msgid "The name of the role used to sign." +msgstr "El nombre del rol utilizado para firmar." + +#: awx/main/credential_plugins/hashivault.py:84 +msgid "Valid Principals" +msgstr "Principales válidos" + +#: awx/main/credential_plugins/hashivault.py:86 +msgid "" +"Valid principals (either usernames or hostnames) that the certificate should " +"be signed for." +msgstr "Principales válidos (ya sea nombres de usuario o nombres de host) para los que se debería firmar el certificado:" + +#: awx/main/fields.py:67 #, python-brace-format msgid "'{value}' is not one of ['{allowed_values}']" msgstr "'{value}' no es uno de ['{allowed_values}']" -#: awx/main/fields.py:421 +#: awx/main/fields.py:439 #, python-brace-format msgid "{type} provided in relative path {path}, expected {expected_type}" -msgstr "{type} provisto en la ruta relativa {path}; se espera {expected_type}" +msgstr "{type} proporcionado en la ruta de acceso relativa {path}, se esperada {expected_type}" -#: awx/main/fields.py:426 +#: awx/main/fields.py:444 #, python-brace-format msgid "{type} provided, expected {expected_type}" -msgstr "{type} provisto; se espera {expected_type}" +msgstr "se proporcionó {type}, se esperaba {expected_type}" -#: awx/main/fields.py:431 +#: awx/main/fields.py:449 #, python-brace-format msgid "Schema validation error in relative path {path} ({error})" -msgstr "Error de validación del esquema en ruta relativa {path} ({error})" +msgstr "Error de validación del esquema en ruta de acceso relativa {path} ({error})" -#: awx/main/fields.py:552 +#: awx/main/fields.py:558 +#, python-format +msgid "required for %s" +msgstr "requerido para %s" + +#: awx/main/fields.py:632 msgid "secret values must be of type string, not {}" msgstr "los valores secretos deben ser de tipo cadena, no {}" -#: awx/main/fields.py:587 +#: awx/main/fields.py:667 #, python-format msgid "cannot be set unless \"%s\" is set" -msgstr "no puede establecerse, a menos que se defina \"%s\"" +msgstr "no puede establecerse, excepto que se defina \"%s\"" -#: awx/main/fields.py:603 -#, python-format -msgid "required for %s" -msgstr "requerido para %s" - -#: awx/main/fields.py:627 +#: awx/main/fields.py:702 msgid "must be set when SSH key is encrypted." msgstr "se debe establecer cuando la clave SSH está cifrada." -#: awx/main/fields.py:633 +#: awx/main/fields.py:710 msgid "should not be set when SSH key is not encrypted." msgstr "no se debe establecer cuando la clave SSH no está cifrada." -#: awx/main/fields.py:691 +#: awx/main/fields.py:769 msgid "'dependencies' is not supported for custom credentials." msgstr "'dependencias' no es compatible con las credenciales personalizadas." -#: awx/main/fields.py:705 +#: awx/main/fields.py:783 msgid "\"tower\" is a reserved field name" msgstr "\"tower\" es un nombre de campo reservado" -#: awx/main/fields.py:712 +#: awx/main/fields.py:790 #, python-format msgid "field IDs must be unique (%s)" msgstr "los ID de campo deben ser únicos (%s)" -#: awx/main/fields.py:725 -msgid "become_method is a reserved type name" -msgstr "become_method es un nombre de tipo reservado" +#: awx/main/fields.py:805 +msgid "{} is not a {}" +msgstr "{} no es un {}" -#: awx/main/fields.py:736 +#: awx/main/fields.py:811 #, python-brace-format msgid "{sub_key} not allowed for {element_type} type ({element_id})" -msgstr "{sub_key} no permitido para el tipo {element_type} ({element_id})" +msgstr "{sub_key} no está permitido para el tipo {element_type} ({element_id})" + +#: awx/main/fields.py:869 +msgid "" +"Environment variable {} may affect Ansible configuration so its use is not " +"allowed in credentials." +msgstr "Es posible que la variable de entorno {} pueda afectar la configuración de Ansible, de modo que no se permite su uso en credenciales." + +#: awx/main/fields.py:875 +msgid "Environment variable {} is blacklisted from use in credentials." +msgstr "El uso de la variable de entorno {} está prohibido en las credenciales." -#: awx/main/fields.py:810 +#: awx/main/fields.py:903 msgid "" "Must define unnamed file injector in order to reference `tower.filename`." -msgstr "" -"Se debe definir el inyector del archivo sin nombre para hacer referencia a " -"`tower.filename`." +msgstr "Se debe definir el inyector del archivo sin nombre para hacer referencia a `tower.filename`." -#: awx/main/fields.py:817 +#: awx/main/fields.py:910 msgid "Cannot directly reference reserved `tower` namespace container." -msgstr "" -"No se puede hacer referencia directa al contenedor del espacio de nombres " -"`tower`." +msgstr "No se puede hacer referencia directa al contenedor del espacio de nombres `tower`." -#: awx/main/fields.py:827 +#: awx/main/fields.py:920 msgid "Must use multi-file syntax when injecting multiple files" -msgstr "" -"Debe usar una sintaxis de archivos múltiples al inyectar archivos múltiples" +msgstr "Debe usar una sintaxis de archivos múltiples al inyectar archivos múltiples" -#: awx/main/fields.py:844 +#: awx/main/fields.py:940 #, python-brace-format msgid "{sub_key} uses an undefined field ({error_msg})" msgstr "{sub_key} usa un campo indefinido ({error_msg})" -#: awx/main/fields.py:851 +#: awx/main/fields.py:947 #, python-brace-format msgid "" "Syntax error rendering template for {sub_key} inside of {type} ({error_msg})" -msgstr "" -"Plantilla que arroja un error de sintaxis para {sub_key} dentro de {type} " -"({error_msg})" +msgstr "Plantilla que arroja un error de sintaxis para {sub_key} dentro de {type} ({error_msg})" -#: awx/main/middleware.py:160 +#: awx/main/middleware.py:118 msgid "Formats of all available named urls" msgstr "Formatos de todas las URL con nombre disponibles" -#: awx/main/middleware.py:161 +#: awx/main/middleware.py:119 msgid "" "Read-only list of key-value pairs that shows the standard format of all " "available named URLs." -msgstr "" -"Lista de solo lectura de los pares clave-valor que muestra el formato " -"estándar de todas las URL con nombre disponibles." +msgstr "Lista de solo lectura de los pares clave-valor que muestra el formato estándar de todas las URL con nombre disponibles." -#: awx/main/middleware.py:163 awx/main/middleware.py:173 +#: awx/main/middleware.py:121 awx/main/middleware.py:131 msgid "Named URL" msgstr "URL con nombre" -#: awx/main/middleware.py:170 +#: awx/main/middleware.py:128 msgid "List of all named url graph nodes." msgstr "Lista de todos los nodos gráficos de URL con nombre." -#: awx/main/middleware.py:171 +#: awx/main/middleware.py:129 msgid "" -"Read-only list of key-value pairs that exposes named URL graph topology. Use" -" this list to programmatically generate named URLs for resources" -msgstr "" -"Lista de solo lectura de los pares clave-valor que expone la topología " -"gráfica de URL con nombre. Use esta lista para generar URL con nombre para " -"recursos mediante programación." - -#: awx/main/migrations/_reencrypt.py:26 awx/main/models/notifications.py:35 -msgid "Email" -msgstr "Correo electrónico" - -#: awx/main/migrations/_reencrypt.py:27 awx/main/models/notifications.py:36 -msgid "Slack" -msgstr "Slack" - -#: awx/main/migrations/_reencrypt.py:28 awx/main/models/notifications.py:37 -msgid "Twilio" -msgstr "Twilio" +"Read-only list of key-value pairs that exposes named URL graph topology. Use " +"this list to programmatically generate named URLs for resources" +msgstr "Lista de solo lectura de los pares clave-valor que expone la topología gráfica de URL con nombre. Use esta lista para generar URL con nombre para recursos mediante programación." -#: awx/main/migrations/_reencrypt.py:29 awx/main/models/notifications.py:38 -msgid "Pagerduty" -msgstr "Pagerduty" - -#: awx/main/migrations/_reencrypt.py:30 awx/main/models/notifications.py:39 -msgid "HipChat" -msgstr "HipChat" - -#: awx/main/migrations/_reencrypt.py:31 awx/main/models/notifications.py:41 -msgid "Mattermost" -msgstr "Mattermost" - -#: awx/main/migrations/_reencrypt.py:32 awx/main/models/notifications.py:40 -msgid "Webhook" -msgstr "Webhook" - -#: awx/main/migrations/_reencrypt.py:33 awx/main/models/notifications.py:43 -msgid "IRC" -msgstr "IRC" - -#: awx/main/models/activity_stream.py:25 +#: awx/main/models/activity_stream.py:28 msgid "Entity Created" msgstr "Entidad creada" -#: awx/main/models/activity_stream.py:26 +#: awx/main/models/activity_stream.py:29 msgid "Entity Updated" msgstr "Entidad actualizada" -#: awx/main/models/activity_stream.py:27 +#: awx/main/models/activity_stream.py:30 msgid "Entity Deleted" msgstr "Entidad eliminada" -#: awx/main/models/activity_stream.py:28 +#: awx/main/models/activity_stream.py:31 msgid "Entity Associated with another Entity" msgstr "Entidad asociada con otra entidad" -#: awx/main/models/activity_stream.py:29 +#: awx/main/models/activity_stream.py:32 msgid "Entity was Disassociated with another Entity" msgstr "La entidad fue desasociada de otra entidad" -#: awx/main/models/ad_hoc_commands.py:95 +#: awx/main/models/activity_stream.py:45 +msgid "The cluster node the activity took place on." +msgstr "El nodo de clúster en el que tuvo lugar la actividad." + +#: awx/main/models/ad_hoc_commands.py:97 msgid "No valid inventory." -msgstr "Inventario no válido." +msgstr "Inventario no válido" -#: awx/main/models/ad_hoc_commands.py:102 +#: awx/main/models/ad_hoc_commands.py:104 msgid "You must provide a machine / SSH credential." -msgstr "Debe proporcionar una credencial de máquina/SSH." +msgstr "Debe proporcionar un credencial de máquina / SSH." -#: awx/main/models/ad_hoc_commands.py:113 -#: awx/main/models/ad_hoc_commands.py:121 +#: awx/main/models/ad_hoc_commands.py:115 +#: awx/main/models/ad_hoc_commands.py:123 msgid "Invalid type for ad hoc command" -msgstr "Tipo no válido para comando ad hoc" +msgstr "Tipo inválido para comando ad hoc" -#: awx/main/models/ad_hoc_commands.py:116 +#: awx/main/models/ad_hoc_commands.py:118 msgid "Unsupported module for ad hoc commands." msgstr "Módulo no soportado para comandos ad hoc." -#: awx/main/models/ad_hoc_commands.py:124 +#: awx/main/models/ad_hoc_commands.py:126 #, python-format msgid "No argument passed to %s module." -msgstr "Ningún argumento pasado al módulo %s." +msgstr "Ningún argumento pasó al módulo %s." #: awx/main/models/base.py:33 awx/main/models/base.py:39 #: awx/main/models/base.py:44 awx/main/models/base.py:49 @@ -2719,1124 +3093,945 @@ msgstr "Ejecutar" msgid "Check" msgstr "Comprobar" -#: awx/main/models/base.py:35 -msgid "Scan" -msgstr "Escanear" - -#: awx/main/models/credential/__init__.py:110 -msgid "Host" -msgstr "Host" - -#: awx/main/models/credential/__init__.py:111 -msgid "The hostname or IP address to use." -msgstr "El nombre de host o la dirección IP por utilizar." - -#: awx/main/models/credential/__init__.py:117 -#: awx/main/models/credential/__init__.py:686 -#: awx/main/models/credential/__init__.py:741 -#: awx/main/models/credential/__init__.py:806 -#: awx/main/models/credential/__init__.py:884 -#: awx/main/models/credential/__init__.py:930 -#: awx/main/models/credential/__init__.py:958 -#: awx/main/models/credential/__init__.py:987 -#: awx/main/models/credential/__init__.py:1051 -#: awx/main/models/credential/__init__.py:1092 -#: awx/main/models/credential/__init__.py:1125 -#: awx/main/models/credential/__init__.py:1177 -msgid "Username" -msgstr "Usuario" - -#: awx/main/models/credential/__init__.py:118 -msgid "Username for this credential." -msgstr "Usuario para esta credencial." - -#: awx/main/models/credential/__init__.py:124 -#: awx/main/models/credential/__init__.py:690 -#: awx/main/models/credential/__init__.py:745 -#: awx/main/models/credential/__init__.py:810 -#: awx/main/models/credential/__init__.py:934 -#: awx/main/models/credential/__init__.py:962 -#: awx/main/models/credential/__init__.py:991 -#: awx/main/models/credential/__init__.py:1055 -#: awx/main/models/credential/__init__.py:1096 -#: awx/main/models/credential/__init__.py:1129 -#: awx/main/models/credential/__init__.py:1181 -msgid "Password" -msgstr "Contraseña" - -#: awx/main/models/credential/__init__.py:125 -msgid "" -"Password for this credential (or \"ASK\" to prompt the user for machine " -"credentials)." -msgstr "" -"Contraseña para esta credencial (o \"ASK\" para solicitar al usuario las " -"credenciales de máquina)." - -#: awx/main/models/credential/__init__.py:132 -msgid "Security Token" -msgstr "Token de seguridad" - -#: awx/main/models/credential/__init__.py:133 -msgid "Security Token for this credential" -msgstr "Token de seguridad para esta credencial" - -#: awx/main/models/credential/__init__.py:139 -msgid "Project" -msgstr "Proyecto" - -#: awx/main/models/credential/__init__.py:140 -msgid "The identifier for the project." -msgstr "El identificador para el proyecto." - -#: awx/main/models/credential/__init__.py:146 -msgid "Domain" -msgstr "Dominio" - -#: awx/main/models/credential/__init__.py:147 -msgid "The identifier for the domain." -msgstr "El identificador para el dominio." - -#: awx/main/models/credential/__init__.py:152 -msgid "SSH private key" -msgstr "Clave privada SSH" - -#: awx/main/models/credential/__init__.py:153 -msgid "RSA or DSA private key to be used instead of password." -msgstr "Clave privada RSA o DSA para utilizar en lugar de una contraseña." - -#: awx/main/models/credential/__init__.py:159 -msgid "SSH key unlock" -msgstr "Desbloqueo de clave SSH" - -#: awx/main/models/credential/__init__.py:160 -msgid "" -"Passphrase to unlock SSH private key if encrypted (or \"ASK\" to prompt the " -"user for machine credentials)." -msgstr "" -"Frase de contraseña para desbloquear la clave privada SSH si está cifrada (o" -" \"ASK\" para solicitar al usuario las credenciales de máquina)." - -#: awx/main/models/credential/__init__.py:168 -msgid "Privilege escalation method." -msgstr "Método de elevación de privilegios." - -#: awx/main/models/credential/__init__.py:174 -msgid "Privilege escalation username." -msgstr "Nombre de usuario para la elevación de privilegios." - -#: awx/main/models/credential/__init__.py:180 -msgid "Password for privilege escalation method." -msgstr "Contraseña para el método de elevación de privilegios." - -#: awx/main/models/credential/__init__.py:186 -msgid "Vault password (or \"ASK\" to prompt the user)." -msgstr "Contraseña de Vault (o \"ASK\" para solicitar al usuario)." - -#: awx/main/models/credential/__init__.py:190 -msgid "Whether to use the authorize mechanism." -msgstr "Si se utilizará el mecanismo de autenticación." - -#: awx/main/models/credential/__init__.py:196 -msgid "Password used by the authorize mechanism." -msgstr "Contraseña utilizada por el mecanismo de autenticación." - -#: awx/main/models/credential/__init__.py:202 -msgid "Client Id or Application Id for the credential" -msgstr "Id del cliente o Id de aplicación para el credencial" - -#: awx/main/models/credential/__init__.py:208 -msgid "Secret Token for this credential" -msgstr "Token secreto para esta credencial" - -#: awx/main/models/credential/__init__.py:214 -msgid "Subscription identifier for this credential" -msgstr "Identificador de suscripción para esta credencial" - -#: awx/main/models/credential/__init__.py:220 -msgid "Tenant identifier for this credential" -msgstr "Identificador de inquilino [Tenant] para esta credencial" +#: awx/main/models/base.py:35 +msgid "Scan" +msgstr "Escanear" -#: awx/main/models/credential/__init__.py:244 +#: awx/main/models/credential/__init__.py:96 msgid "" "Specify the type of credential you want to create. Refer to the Ansible " "Tower documentation for details on each type." -msgstr "" -"Especifique el tipo de credencial que desea crear. Consulte la documentación" -" de Ansible Tower para obtener información sobre cada tipo." +msgstr "Especifique el tipo de credencial que desea crear. Consulte la documentación de Ansible Tower para obtener información sobre cada tipo." -#: awx/main/models/credential/__init__.py:258 -#: awx/main/models/credential/__init__.py:476 +#: awx/main/models/credential/__init__.py:110 +#: awx/main/models/credential/__init__.py:353 msgid "" -"Enter inputs using either JSON or YAML syntax. Use the radio button to " -"toggle between the two. Refer to the Ansible Tower documentation for example" -" syntax." -msgstr "" -"Ingrese entradas a través de la sintaxis JSON o YAML. Use el botón de " -"selección para alternar entre las dos opciones. Consulte la documentación de" -" Ansible Tower para ver sintaxis de ejemplo." +"Enter inputs using either JSON or YAML syntax. Refer to the Ansible Tower " +"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." -#: awx/main/models/credential/__init__.py:457 -#: awx/main/models/credential/__init__.py:681 +#: awx/main/models/credential/__init__.py:325 +#: awx/main/models/credential/__init__.py:594 msgid "Machine" msgstr "Máquina" -#: awx/main/models/credential/__init__.py:458 -#: awx/main/models/credential/__init__.py:772 +#: awx/main/models/credential/__init__.py:326 +#: awx/main/models/credential/__init__.py:680 msgid "Vault" msgstr "Vault" -#: awx/main/models/credential/__init__.py:459 -#: awx/main/models/credential/__init__.py:801 +#: awx/main/models/credential/__init__.py:327 +#: awx/main/models/credential/__init__.py:707 msgid "Network" msgstr "Red" -#: awx/main/models/credential/__init__.py:460 -#: awx/main/models/credential/__init__.py:736 +#: awx/main/models/credential/__init__.py:328 +#: awx/main/models/credential/__init__.py:649 msgid "Source Control" msgstr "Fuente de control" -#: awx/main/models/credential/__init__.py:461 +#: awx/main/models/credential/__init__.py:329 msgid "Cloud" msgstr "Nube" -#: awx/main/models/credential/__init__.py:462 -#: awx/main/models/credential/__init__.py:1087 +#: awx/main/models/credential/__init__.py:330 +msgid "Personal Access Token" +msgstr "Token de acceso personal" + +#: awx/main/models/credential/__init__.py:331 +#: awx/main/models/credential/__init__.py:1015 msgid "Insights" msgstr "Insights" -#: awx/main/models/credential/__init__.py:483 +#: awx/main/models/credential/__init__.py:332 +msgid "External" +msgstr "Externo" + +#: awx/main/models/credential/__init__.py:333 +msgid "Kubernetes" +msgstr "Kubernetes" + +#: awx/main/models/credential/__init__.py:359 msgid "" -"Enter injectors using either JSON or YAML syntax. Use the radio button to " -"toggle between the two. Refer to the Ansible Tower documentation for example" -" syntax." -msgstr "" -"Ingrese inyectores a través de la sintaxis JSON o YAML. Use el botón de " -"selección para alternar entre las dos opciones. Consulte la documentación de" -" Ansible Tower para ver sintaxis de ejemplo." +"Enter injectors using either JSON or YAML syntax. Refer to the Ansible Tower " +"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." -#: awx/main/models/credential/__init__.py:534 +#: awx/main/models/credential/__init__.py:428 #, python-format msgid "adding %s credential type" -msgstr "añadir el tipo de credencial %s" +msgstr "agregar el tipo de credencial %s" + +#: awx/main/models/credential/__init__.py:602 +#: awx/main/models/credential/__init__.py:658 +#: awx/main/models/credential/__init__.py:716 +#: awx/main/models/credential/__init__.py:838 +#: awx/main/models/credential/__init__.py:864 +#: awx/main/models/credential/__init__.py:891 +#: awx/main/models/credential/__init__.py:951 +#: awx/main/models/credential/__init__.py:1024 +#: awx/main/models/credential/__init__.py:1055 +#: awx/main/models/credential/__init__.py:1105 +msgid "Password" +msgstr "Contraseña" -#: awx/main/models/credential/__init__.py:696 -#: awx/main/models/credential/__init__.py:815 +#: awx/main/models/credential/__init__.py:608 +#: awx/main/models/credential/__init__.py:721 msgid "SSH Private Key" msgstr "Clave privada SSH" -#: awx/main/models/credential/__init__.py:703 -#: awx/main/models/credential/__init__.py:757 -#: awx/main/models/credential/__init__.py:822 +#: awx/main/models/credential/__init__.py:615 +msgid "Signed SSH Certificate" +msgstr "Certificado SSH firmado" + +#: awx/main/models/credential/__init__.py:621 +#: awx/main/models/credential/__init__.py:670 +#: awx/main/models/credential/__init__.py:728 msgid "Private Key Passphrase" -msgstr "Frase de contraseña para clave privada" +msgstr "Frase de contraseña para la clave privada" -#: awx/main/models/credential/__init__.py:709 +#: awx/main/models/credential/__init__.py:627 msgid "Privilege Escalation Method" msgstr "Método de escalación de privilegios" -#: awx/main/models/credential/__init__.py:711 +#: awx/main/models/credential/__init__.py:629 msgid "" -"Specify a method for \"become\" operations. This is equivalent to specifying" -" the --become-method Ansible parameter." -msgstr "" -"Especifique un método para operaciones \"become\". Esto equivale a " -"especificar el parámetro --become-method de Ansible." +"Specify a method for \"become\" operations. This is equivalent to specifying " +"the --become-method Ansible parameter." +msgstr "Especifique un método para operaciones \"become\". Esto equivale a especificar el parámetro --become-method de Ansible." -#: awx/main/models/credential/__init__.py:716 +#: awx/main/models/credential/__init__.py:634 msgid "Privilege Escalation Username" -msgstr "Nombre de usuario de escalación de privilegios" +msgstr "Usuario para la elevación de privilegios" -#: awx/main/models/credential/__init__.py:720 +#: awx/main/models/credential/__init__.py:638 msgid "Privilege Escalation Password" -msgstr "Contraseña de escalación de privilegios" +msgstr "Contraseña para la elevación de privilegios" -#: awx/main/models/credential/__init__.py:750 +#: awx/main/models/credential/__init__.py:663 msgid "SCM Private Key" msgstr "Clave privada SCM" -#: awx/main/models/credential/__init__.py:777 +#: awx/main/models/credential/__init__.py:685 msgid "Vault Password" msgstr "Contraseña Vault" -#: awx/main/models/credential/__init__.py:783 +#: awx/main/models/credential/__init__.py:691 msgid "Vault Identifier" msgstr "Identificador de Vault" -#: awx/main/models/credential/__init__.py:786 +#: awx/main/models/credential/__init__.py:694 msgid "" -"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+." -msgstr "" -"Especifique una ID de Vault (opcional). Esto es equivalente a especificar el" -" parámetro --vault-id de Ansible para ofrecer múltiples contraseñas Vault. " -"Observe: esta función solo funciona en Ansible 2.4+." +"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+." +msgstr "Especifique una ID de Vault (opcional). Esto es equivalente a especificar el parámetro --vault-id de Ansible para ofrecer múltiples contraseñas Vault. Observe: esta función solo funciona en Ansible 2.4+." -#: awx/main/models/credential/__init__.py:827 +#: awx/main/models/credential/__init__.py:733 msgid "Authorize" msgstr "Autorizar" -#: awx/main/models/credential/__init__.py:831 +#: awx/main/models/credential/__init__.py:737 msgid "Authorize Password" msgstr "Contraseña de autorización" -#: awx/main/models/credential/__init__.py:848 +#: awx/main/models/credential/__init__.py:751 msgid "Amazon Web Services" msgstr "Amazon Web Services" -#: awx/main/models/credential/__init__.py:853 +#: awx/main/models/credential/__init__.py:756 msgid "Access Key" msgstr "Clave de acceso" -#: awx/main/models/credential/__init__.py:857 +#: awx/main/models/credential/__init__.py:760 msgid "Secret Key" msgstr "Clave secreta" -#: awx/main/models/credential/__init__.py:862 +#: awx/main/models/credential/__init__.py:765 msgid "STS Token" msgstr "Token STS" -#: awx/main/models/credential/__init__.py:865 +#: awx/main/models/credential/__init__.py:768 msgid "" "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." -msgstr "" -"Security Token Service (STS) es un servicio web que le permite solicitar " -"credenciales temporales, con privilegio limitado, para usuarios de AWS " -"Identity y Access Management (IAM)." +msgstr "El Security Token Service (STS) es un servicio web que habilita su solicitud temporalmente y con credenciales con privilegio limitado para usuarios de AWS Identity y Access Management (IAM)." -#: awx/main/models/credential/__init__.py:879 awx/main/models/inventory.py:990 +#: awx/main/models/credential/__init__.py:780 awx/main/models/inventory.py:833 msgid "OpenStack" msgstr "OpenStack" -#: awx/main/models/credential/__init__.py:888 +#: awx/main/models/credential/__init__.py:789 msgid "Password (API Key)" msgstr "Contraseña (clave API)" -#: awx/main/models/credential/__init__.py:893 -#: awx/main/models/credential/__init__.py:1120 +#: awx/main/models/credential/__init__.py:794 +#: awx/main/models/credential/__init__.py:1046 msgid "Host (Authentication URL)" -msgstr "Host (URL de autenticación)" +msgstr "Servidor (URL de autenticación)" -#: awx/main/models/credential/__init__.py:895 +#: awx/main/models/credential/__init__.py:796 msgid "" -"The host to authenticate with. For example, " -"https://openstack.business.com/v2.0/" -msgstr "" -"El host con el cual autenticar. Por ejemplo, " -"https://openstack.business.com/v2.0/" +"The host to authenticate with. For example, https://openstack.business.com/" +"v2.0/" +msgstr "El host con el cual autenticar. Por ejemplo, https://openstack.business.com/v2.0/" -#: awx/main/models/credential/__init__.py:899 +#: awx/main/models/credential/__init__.py:800 msgid "Project (Tenant Name)" msgstr "Proyecto (Nombre del inquilino [Tenant])" -#: awx/main/models/credential/__init__.py:903 +#: awx/main/models/credential/__init__.py:804 msgid "Domain Name" msgstr "Nombre de dominio" -#: awx/main/models/credential/__init__.py:905 +#: awx/main/models/credential/__init__.py:806 msgid "" "OpenStack domains define administrative boundaries. It is only needed for " "Keystone v3 authentication URLs. Refer to Ansible Tower documentation for " "common scenarios." -msgstr "" -"Los dominios OpenStack definen los límites administrativos. Solo es " -"necesario para las URL de autenticación para KeyStone v3. Consulte la " -"documentación de Ansible Tower para conocer los escenarios comunes." +msgstr "Los dominios OpenStack definen los límites administrativos. Solo es necesario para las URL de autenticación para KeyStone v3. Consulte la documentación de Ansible Tower para conocer los escenarios comunes." + +#: awx/main/models/credential/__init__.py:812 +#: awx/main/models/credential/__init__.py:1110 +#: awx/main/models/credential/__init__.py:1144 +msgid "Verify SSL" +msgstr "Verificar SSL" -#: awx/main/models/credential/__init__.py:919 awx/main/models/inventory.py:987 +#: awx/main/models/credential/__init__.py:823 awx/main/models/inventory.py:830 msgid "VMware vCenter" msgstr "VMware vCenter" -#: awx/main/models/credential/__init__.py:924 +#: awx/main/models/credential/__init__.py:828 msgid "VCenter Host" msgstr "Host de vCenter" -#: awx/main/models/credential/__init__.py:926 +#: awx/main/models/credential/__init__.py:830 msgid "" "Enter the hostname or IP address that corresponds to your VMware vCenter." -msgstr "" -"Introduzca el nombre de host o la dirección IP que corresponda a su VMWare " -"vCenter." +msgstr "Introduzca el nombre de host o la dirección IP que corresponda a su VMWare vCenter." -#: awx/main/models/credential/__init__.py:947 awx/main/models/inventory.py:988 +#: awx/main/models/credential/__init__.py:849 awx/main/models/inventory.py:831 msgid "Red Hat Satellite 6" msgstr "Red Hat Satellite 6" -#: awx/main/models/credential/__init__.py:952 +#: awx/main/models/credential/__init__.py:854 msgid "Satellite 6 URL" msgstr "URL Satellite 6" -#: awx/main/models/credential/__init__.py:954 +#: awx/main/models/credential/__init__.py:856 msgid "" "Enter the URL that corresponds to your Red Hat Satellite 6 server. For " "example, https://satellite.example.org" -msgstr "" -"Introduzca la URL que corresponda a su servidor Red Hat Satellite 6. Por " -"ejemplo, https://satellite.example.org" +msgstr "Introduzca la URL que corresponda a su servidor Red Hat Satellite 6. Por ejemplo, https://satellite.example.org" -#: awx/main/models/credential/__init__.py:975 awx/main/models/inventory.py:989 +#: awx/main/models/credential/__init__.py:875 awx/main/models/inventory.py:832 msgid "Red Hat CloudForms" msgstr "Red Hat CloudForms" -#: awx/main/models/credential/__init__.py:980 +#: awx/main/models/credential/__init__.py:880 msgid "CloudForms URL" msgstr "URL CloudForms" -#: awx/main/models/credential/__init__.py:982 +#: awx/main/models/credential/__init__.py:882 msgid "" "Enter the URL for the virtual machine that corresponds to your CloudForms " "instance. For example, https://cloudforms.example.org" -msgstr "" -"Introduzca la URL para la máquina virtual que corresponda a su instancia " -"CloudForm. Por ejemplo, https://cloudforms.example.org" +msgstr "Introduzca la URL para la máquina virtual que corresponda a su instancia de CloudForm. Por ejemplo, https://cloudforms.example.org" -#: awx/main/models/credential/__init__.py:1004 -#: awx/main/models/inventory.py:985 +#: awx/main/models/credential/__init__.py:902 awx/main/models/inventory.py:828 msgid "Google Compute Engine" msgstr "Google Compute Engine" -#: awx/main/models/credential/__init__.py:1009 +#: awx/main/models/credential/__init__.py:907 msgid "Service Account Email Address" -msgstr "Dirección de correo electrónico de cuenta de servicio" +msgstr "Dirección de correo de cuenta de servicio" -#: awx/main/models/credential/__init__.py:1011 +#: awx/main/models/credential/__init__.py:909 msgid "" "The email address assigned to the Google Compute Engine service account." -msgstr "" -"La dirección de correo electrónico asignada a la cuenta de servicio de " -"Google Compute Engine." +msgstr "La dirección de correo electrónico asignada a la cuenta de servicio de Google Compute Engine." -#: awx/main/models/credential/__init__.py:1017 +#: awx/main/models/credential/__init__.py:915 msgid "" "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" -msgstr "" -"La ID de proyecto es la identificación asignada por GCE. Por lo general, " -"está formada por dos o tres palabras seguidas por un número de tres dígitos." -" Ejemplos: project-id-000 y another-project-id" +msgstr "La ID de proyecto es la identificación asignada por GCE. Por lo general, está formada por dos o tres palabras seguidas por un número de tres dígitos. Ejemplos: project-id-000 y another-project-id" -#: awx/main/models/credential/__init__.py:1023 +#: awx/main/models/credential/__init__.py:921 msgid "RSA Private Key" msgstr "Clave privada RSA" -#: awx/main/models/credential/__init__.py:1028 +#: awx/main/models/credential/__init__.py:926 msgid "" -"Paste the contents of the PEM file associated with the service account " -"email." -msgstr "" -"Pegue el contenido del archivo PEM asociado al correo electrónico de la " -"cuenta de servicio." +"Paste the contents of the PEM file associated with the service account email." +msgstr "Pegue el contenido del fichero PEM asociado al correo de la cuenta de servicio." -#: awx/main/models/credential/__init__.py:1040 -#: awx/main/models/inventory.py:986 +#: awx/main/models/credential/__init__.py:936 awx/main/models/inventory.py:829 msgid "Microsoft Azure Resource Manager" msgstr "Microsoft Azure Resource Manager" -#: awx/main/models/credential/__init__.py:1045 +#: awx/main/models/credential/__init__.py:941 msgid "Subscription ID" msgstr "ID de suscripción" -#: awx/main/models/credential/__init__.py:1047 +#: awx/main/models/credential/__init__.py:943 msgid "Subscription ID is an Azure construct, which is mapped to a username." -msgstr "" -"La ID de suscripción es un elemento de Azure, que está asignado a un nombre " -"de usuario." - -#: awx/main/models/credential/__init__.py:1060 -msgid "Client ID" -msgstr "ID de cliente" - -#: awx/main/models/credential/__init__.py:1069 -msgid "Tenant ID" -msgstr "ID de inquilino" +msgstr "El ID de subscripción es un elemento Azure, el cual está asociado al usuario." -#: awx/main/models/credential/__init__.py:1073 +#: awx/main/models/credential/__init__.py:969 msgid "Azure Cloud Environment" msgstr "Entorno de nube de Azure" -#: awx/main/models/credential/__init__.py:1075 +#: awx/main/models/credential/__init__.py:971 msgid "" "Environment variable AZURE_CLOUD_ENVIRONMENT when using Azure GovCloud or " "Azure stack." -msgstr "" -"Variable AZURE_CLOUD_ENVIRONMENT del entorno al usar Azure GovCloud o la " -"pila de Azure." +msgstr "Variable AZURE_CLOUD_ENVIRONMENT del entorno al usar Azure GovCloud o la pila de Azure." -#: awx/main/models/credential/__init__.py:1115 -#: awx/main/models/inventory.py:991 +#: awx/main/models/credential/__init__.py:981 +msgid "GitHub Personal Access Token" +msgstr "Token de acceso personal de GitHub" + +#: awx/main/models/credential/__init__.py:989 +msgid "This token needs to come from your profile settings in GitHub" +msgstr "Esta token debe provenir de la configuración de su perfil en GitHub" + +#: awx/main/models/credential/__init__.py:998 +msgid "GitLab Personal Access Token" +msgstr "Token de acceso personal de GitLab" + +#: awx/main/models/credential/__init__.py:1006 +msgid "This token needs to come from your profile settings in GitLab" +msgstr "Este token debe provenir de la configuración de su perfil en GitLab" + +#: awx/main/models/credential/__init__.py:1041 awx/main/models/inventory.py:834 msgid "Red Hat Virtualization" msgstr "Virtualización de Red Hat" -#: awx/main/models/credential/__init__.py:1122 +#: awx/main/models/credential/__init__.py:1048 msgid "The host to authenticate with." -msgstr "El host con el cual autenticarse." +msgstr "El servidor al que autentificarse." -#: awx/main/models/credential/__init__.py:1134 +#: awx/main/models/credential/__init__.py:1060 msgid "CA File" msgstr "Archivo CA" -#: awx/main/models/credential/__init__.py:1136 +#: awx/main/models/credential/__init__.py:1062 msgid "Absolute file path to the CA file to use (optional)" msgstr "Ruta de archivo absoluta al archivo CA por usar (opcional)" -#: awx/main/models/credential/__init__.py:1167 -#: awx/main/models/inventory.py:992 +#: awx/main/models/credential/__init__.py:1091 awx/main/models/inventory.py:835 msgid "Ansible Tower" msgstr "Ansible Tower" -#: awx/main/models/credential/__init__.py:1172 +#: awx/main/models/credential/__init__.py:1096 msgid "Ansible Tower Hostname" msgstr "Nombre de host de Ansible Tower" -#: awx/main/models/credential/__init__.py:1174 +#: awx/main/models/credential/__init__.py:1098 msgid "The Ansible Tower base URL to authenticate with." msgstr "La URL de base de Ansible Tower con la cual autenticarse." -#: awx/main/models/credential/__init__.py:1186 -msgid "Verify SSL" -msgstr "Verificar SSL " +#: awx/main/models/credential/__init__.py:1130 +msgid "OpenShift or Kubernetes API Bearer Token" +msgstr "Token portador de la API de OpenShift o Kubernetes" + +#: awx/main/models/credential/__init__.py:1134 +msgid "OpenShift or Kubernetes API Endpoint" +msgstr "Punto final de la API de OpenShift o Kubernetes" + +#: awx/main/models/credential/__init__.py:1136 +msgid "The OpenShift or Kubernetes API Endpoint to authenticate with." +msgstr "El punto final de la API de OpenShift o Kubernetes con el cual autenticarse." + +#: awx/main/models/credential/__init__.py:1139 +msgid "API authentication bearer token" +msgstr "Token de portador de autenticación de API" + +#: awx/main/models/credential/__init__.py:1149 +msgid "Certificate Authority data" +msgstr "Datos de la Entidad de certificación" + +#: awx/main/models/credential/__init__.py:1190 +msgid "Target must be a non-external credential" +msgstr "El destino debe ser una credencial no externa" -#: awx/main/models/events.py:105 awx/main/models/events.py:630 +#: awx/main/models/credential/__init__.py:1195 +msgid "Source must be an external credential" +msgstr "El oriden debe ser una credencial externa" + +#: awx/main/models/credential/__init__.py:1202 +msgid "Input field must be defined on target credential (options are {})." +msgstr "El campo de entrada debe definirse en la credencial de destino (las opciones son {})." + +#: awx/main/models/events.py:152 awx/main/models/events.py:674 msgid "Host Failed" -msgstr "Host fallido" +msgstr "Servidor fallido" -#: awx/main/models/events.py:106 awx/main/models/events.py:631 +#: awx/main/models/events.py:153 +msgid "Host Started" +msgstr "Host iniciado" + +#: awx/main/models/events.py:154 awx/main/models/events.py:675 msgid "Host OK" -msgstr "Host OK" +msgstr "Servidor OK" -#: awx/main/models/events.py:107 +#: awx/main/models/events.py:155 msgid "Host Failure" -msgstr "Error del host" +msgstr "Fallo del servidor" -#: awx/main/models/events.py:108 awx/main/models/events.py:637 +#: awx/main/models/events.py:156 awx/main/models/events.py:681 msgid "Host Skipped" -msgstr "Host omitido" +msgstr "Servidor omitido" -#: awx/main/models/events.py:109 awx/main/models/events.py:632 +#: awx/main/models/events.py:157 awx/main/models/events.py:676 msgid "Host Unreachable" -msgstr "Host no alcanzable" +msgstr "Servidor no alcanzable" -#: awx/main/models/events.py:110 awx/main/models/events.py:124 +#: awx/main/models/events.py:158 awx/main/models/events.py:172 msgid "No Hosts Remaining" -msgstr "No más hosts" +msgstr "No más servidores" -#: awx/main/models/events.py:111 +#: awx/main/models/events.py:159 msgid "Host Polling" -msgstr "Sondeo al host" +msgstr "Sondeo al servidor" -#: awx/main/models/events.py:112 +#: awx/main/models/events.py:160 msgid "Host Async OK" -msgstr "Host Async OK" +msgstr "Servidor Async OK" -#: awx/main/models/events.py:113 +#: awx/main/models/events.py:161 msgid "Host Async Failure" -msgstr "Host Async fallido" +msgstr "Servidor Async fallido" -#: awx/main/models/events.py:114 +#: awx/main/models/events.py:162 msgid "Item OK" msgstr "Elemento OK" -#: awx/main/models/events.py:115 +#: awx/main/models/events.py:163 msgid "Item Failed" msgstr "Elemento fallido" -#: awx/main/models/events.py:116 +#: awx/main/models/events.py:164 msgid "Item Skipped" msgstr "Elemento omitido" -#: awx/main/models/events.py:117 +#: awx/main/models/events.py:165 msgid "Host Retry" -msgstr "Reintentar host" +msgstr "Reintentar servidor" -#: awx/main/models/events.py:119 +#: awx/main/models/events.py:167 msgid "File Difference" -msgstr "Diferencia entre archivos" +msgstr "Diferencias del fichero" -#: awx/main/models/events.py:120 +#: awx/main/models/events.py:168 msgid "Playbook Started" msgstr "Playbook iniciado" -#: awx/main/models/events.py:121 +#: awx/main/models/events.py:169 msgid "Running Handlers" msgstr "Handlers ejecutándose" -#: awx/main/models/events.py:122 +#: awx/main/models/events.py:170 msgid "Including File" -msgstr "Incluyendo archivo" +msgstr "Incluyendo fichero" -#: awx/main/models/events.py:123 +#: awx/main/models/events.py:171 msgid "No Hosts Matched" -msgstr "Ningún host corresponde" +msgstr "Ningún servidor corresponde" -#: awx/main/models/events.py:125 +#: awx/main/models/events.py:173 msgid "Task Started" msgstr "Tarea iniciada" -#: awx/main/models/events.py:127 +#: awx/main/models/events.py:175 msgid "Variables Prompted" msgstr "Variables solicitadas" -#: awx/main/models/events.py:128 +#: awx/main/models/events.py:176 msgid "Gathering Facts" -msgstr "Obteniendo hechos" +msgstr "Obteniendo facts" -#: awx/main/models/events.py:129 +#: awx/main/models/events.py:177 msgid "internal: on Import for Host" -msgstr "interno: en la importación para el host" +msgstr "internal: en la importación para el servidor" -#: awx/main/models/events.py:130 +#: awx/main/models/events.py:178 msgid "internal: on Not Import for Host" -msgstr "interno: en la no importación para el host" +msgstr "internal: en la no importación para el servidor" -#: awx/main/models/events.py:131 +#: awx/main/models/events.py:179 msgid "Play Started" msgstr "Jugada iniciada" -#: awx/main/models/events.py:132 +#: awx/main/models/events.py:180 msgid "Playbook Complete" msgstr "Playbook terminado" -#: awx/main/models/events.py:136 awx/main/models/events.py:647 +#: awx/main/models/events.py:184 awx/main/models/events.py:691 msgid "Debug" -msgstr "Depurar" +msgstr "Debug" -#: awx/main/models/events.py:137 awx/main/models/events.py:648 +#: awx/main/models/events.py:185 awx/main/models/events.py:692 msgid "Verbose" msgstr "Nivel de detalle" -#: awx/main/models/events.py:138 awx/main/models/events.py:649 +#: awx/main/models/events.py:186 awx/main/models/events.py:693 msgid "Deprecated" msgstr "Obsoleto" -#: awx/main/models/events.py:139 awx/main/models/events.py:650 +#: awx/main/models/events.py:187 awx/main/models/events.py:694 msgid "Warning" msgstr "Advertencia" -#: awx/main/models/events.py:140 awx/main/models/events.py:651 +#: awx/main/models/events.py:188 awx/main/models/events.py:695 msgid "System Warning" msgstr "Advertencia del sistema" -#: awx/main/models/events.py:141 awx/main/models/events.py:652 -#: awx/main/models/unified_jobs.py:67 +#: awx/main/models/events.py:189 awx/main/models/events.py:696 +#: awx/main/models/unified_jobs.py:75 msgid "Error" msgstr "Error" -#: awx/main/models/fact.py:25 -msgid "Host for the facts that the fact scan captured." -msgstr "Host para los hechos que el escaneo de hechos capture." - -#: awx/main/models/fact.py:30 -msgid "Date and time of the corresponding fact scan gathering time." -msgstr "" -"Fecha y hora que corresponden al escaneo de hechos en el tiempo que fueron " -"obtenidos." - -#: awx/main/models/fact.py:33 -msgid "" -"Arbitrary JSON structure of module facts captured at timestamp for a single " -"host." -msgstr "" -"Estructura de JSON arbitraria de hechos de módulos capturados en la fecha y " -"hora para un único host." - -#: awx/main/models/ha.py:181 +#: awx/main/models/ha.py:175 msgid "Instances that are members of this InstanceGroup" msgstr "Las instancias que son miembros de este grupo de instancias" -#: awx/main/models/ha.py:186 +#: awx/main/models/ha.py:180 msgid "Instance Group to remotely control this group." msgstr "Grupo de instancias para controlar remotamente este grupo." -#: awx/main/models/ha.py:193 +#: awx/main/models/ha.py:200 msgid "Percentage of Instances to automatically assign to this group" -msgstr "" -"Porcentaje de instancias que se asignarán automáticamente a este grupo" +msgstr "Porcentaje de instancias que se asignarán automáticamente a este grupo" -#: awx/main/models/ha.py:197 +#: awx/main/models/ha.py:204 msgid "" "Static minimum number of Instances to automatically assign to this group" -msgstr "" -"Número mínimo estático de instancias que se asignarán automáticamente a este" -" grupo" +msgstr "Número mínimo estático de instancias que se asignarán automáticamente a este grupo" -#: awx/main/models/ha.py:202 +#: awx/main/models/ha.py:209 msgid "" "List of exact-match Instances that will always be automatically assigned to " "this group" -msgstr "" -"Lista de instancias con coincidencia exacta que se asignarán siempre " -"automáticamente a este grupo" +msgstr "Lista de instancias con coincidencia exacta que se asignarán siempre automáticamente a este grupo" -#: awx/main/models/inventory.py:61 +#: awx/main/models/inventory.py:80 msgid "Hosts have a direct link to this inventory." msgstr "Los hosts tienen un enlace directo a este inventario." -#: awx/main/models/inventory.py:62 +#: awx/main/models/inventory.py:81 msgid "Hosts for inventory generated using the host_filter property." msgstr "Hosts para inventario generados a través de la propiedad host_filter." -#: awx/main/models/inventory.py:67 +#: awx/main/models/inventory.py:86 msgid "inventories" msgstr "inventarios" -#: awx/main/models/inventory.py:74 +#: awx/main/models/inventory.py:93 msgid "Organization containing this inventory." msgstr "Organización que contiene este inventario." -#: awx/main/models/inventory.py:81 +#: awx/main/models/inventory.py:100 msgid "Inventory variables in JSON or YAML format." msgstr "Variables de inventario en formato JSON o YAML." -#: awx/main/models/inventory.py:86 -msgid "Flag indicating whether any hosts in this inventory have failed." -msgstr "" -"Indicador que establece si algún servidor en este inventario ha fallado." - -#: awx/main/models/inventory.py:91 -msgid "Total number of hosts in this inventory." -msgstr "Número total de hosts en este inventario." +#: awx/main/models/inventory.py:105 +msgid "" +"This field is deprecated and will be removed in a future release. Flag " +"indicating whether any hosts in this inventory have failed." +msgstr "Este campo es obsoleto y se eliminará en un lanzamiento futuro. Indicador que establece si algún host en este inventario ha fallado." -#: awx/main/models/inventory.py:96 -msgid "Number of hosts in this inventory with active failures." -msgstr "Número de hosts en este inventario con errores activos." +#: awx/main/models/inventory.py:111 +msgid "" +"This field is deprecated and will be removed in a future release. Total " +"number of hosts in this inventory." +msgstr "Este campo es obsoleto y se eliminará en un lanzamiento futuro. Cantidad total de hosts en este inventario." -#: awx/main/models/inventory.py:101 -msgid "Total number of groups in this inventory." -msgstr "Número total de grupos en este inventario." +#: awx/main/models/inventory.py:117 +msgid "" +"This field is deprecated and will be removed in a future release. Number of " +"hosts in this inventory with active failures." +msgstr "Este campo es obsoleto y se eliminará en un lanzamiento futuro. Cantidad de hosts en este inventario con fallas activas." -#: awx/main/models/inventory.py:106 -msgid "Number of groups in this inventory with active failures." -msgstr "Número de grupos en este inventario con errores activos." +#: awx/main/models/inventory.py:123 +msgid "" +"This field is deprecated and will be removed in a future release. Total " +"number of groups in this inventory." +msgstr "Este campo es obsoleto y se eliminará en un lanzamiento futuro. Cantidad total de grupos en este inventario." -#: awx/main/models/inventory.py:111 +#: awx/main/models/inventory.py:129 msgid "" -"Flag indicating whether this inventory has any external inventory sources." -msgstr "" -"Indicador que establece si este inventario tiene alguna fuente de externa " -"de inventario." +"This field is deprecated and will be removed in a future release. Flag " +"indicating whether this inventory has any external inventory sources." +msgstr "Este campo es obsoleto y se eliminará en un lanzamiento futuro. Indicador que establece si este inventario tiene algúna fuente de inventario externa." -#: awx/main/models/inventory.py:116 +#: awx/main/models/inventory.py:135 msgid "" "Total number of external inventory sources configured within this inventory." -msgstr "" -"Número total de inventarios de origen externo configurado dentro de este " -"inventario." +msgstr "Número total de inventarios de origen externo configurado dentro de este inventario." -#: awx/main/models/inventory.py:121 +#: awx/main/models/inventory.py:140 msgid "Number of external inventory sources in this inventory with failures." -msgstr "" -"Número de inventarios de origen externo en este inventario con errores." +msgstr "Número de inventarios de origen externo en este inventario con errores." -#: awx/main/models/inventory.py:128 +#: awx/main/models/inventory.py:147 msgid "Kind of inventory being represented." msgstr "Tipo de inventario que se representa." -#: awx/main/models/inventory.py:134 +#: awx/main/models/inventory.py:153 msgid "Filter that will be applied to the hosts of this inventory." msgstr "Filtro que se aplicará a los hosts de este inventario." -#: awx/main/models/inventory.py:161 +#: awx/main/models/inventory.py:181 msgid "" "Credentials to be used by hosts belonging to this inventory when accessing " "Red Hat Insights API." -msgstr "" -"Credenciales que utilizarán los hosts que pertenecen a este inventario " -"cuando accedan a la API de Red Hat Insights." +msgstr "Credenciales que utilizarán los hosts que pertenecen a este inventario cuando accedan a la API de Red Hat Insights." -#: awx/main/models/inventory.py:170 +#: awx/main/models/inventory.py:190 msgid "Flag indicating the inventory is being deleted." msgstr "Indicador que muestra que el inventario se eliminará." -#: awx/main/models/inventory.py:459 +#: awx/main/models/inventory.py:245 +msgid "Could not parse subset as slice specification." +msgstr "No se pudo analizar el subconjunto según las especificaciones de fraccionamiento." + +#: awx/main/models/inventory.py:249 +msgid "Slice number must be less than total number of slices." +msgstr "El número de fraccionamiento debe ser inferior al número total de fraccionamientos." + +#: awx/main/models/inventory.py:251 +msgid "Slice number must be 1 or higher." +msgstr "El número de fraccionamiento debe ser 1 o superior." + +#: awx/main/models/inventory.py:388 msgid "Assignment not allowed for Smart Inventory" msgstr "Tarea no permitida para el inventario inteligente" -#: awx/main/models/inventory.py:461 awx/main/models/projects.py:159 +#: awx/main/models/inventory.py:390 awx/main/models/projects.py:166 msgid "Credential kind must be 'insights'." msgstr "Tipo de credencial debe ser 'insights'." -#: awx/main/models/inventory.py:546 +#: awx/main/models/inventory.py:475 msgid "Is this host online and available for running jobs?" msgstr "¿Está este servidor funcionando y disponible para ejecutar trabajos?" -#: awx/main/models/inventory.py:552 +#: awx/main/models/inventory.py:481 msgid "" "The value used by the remote inventory source to uniquely identify the host" -msgstr "" -"El valor usado por el inventario de fuente remota para identificar de forma " -"única el host" +msgstr "El valor usado por el inventario de fuente remota para identificar de forma única el servidor" -#: awx/main/models/inventory.py:557 +#: awx/main/models/inventory.py:486 msgid "Host variables in JSON or YAML format." -msgstr "Variables de host en formato JSON o YAML." +msgstr "Variables del servidor en formato JSON o YAML." -#: awx/main/models/inventory.py:579 -msgid "Flag indicating whether the last job failed for this host." -msgstr "Indicador que establece si el último trabajo falló para este host." - -#: awx/main/models/inventory.py:584 -msgid "" -"Flag indicating whether this host was created/updated from any external " -"inventory sources." -msgstr "" -"Indicador que establece si este host se creó/se actualizó desde algún " -"inventario de fuente externa." - -#: awx/main/models/inventory.py:590 +#: awx/main/models/inventory.py:509 msgid "Inventory source(s) that created or modified this host." -msgstr "Fuente(s) del inventario que crearon o modificaron este host." +msgstr "Fuente(s) del inventario que crearon o modificaron este servidor." -#: awx/main/models/inventory.py:595 +#: awx/main/models/inventory.py:514 msgid "Arbitrary JSON structure of most recent ansible_facts, per-host." msgstr "Estructura de JSON arbitraria de ansible_facts más reciente por host." -#: awx/main/models/inventory.py:601 +#: awx/main/models/inventory.py:520 msgid "The date and time ansible_facts was last modified." msgstr "La fecha y hora en las que se modificó ansible_facts por última vez." -#: awx/main/models/inventory.py:608 +#: awx/main/models/inventory.py:527 msgid "Red Hat Insights host unique identifier." msgstr "Identificador único de host de Red Hat Insights." -#: awx/main/models/inventory.py:743 +#: awx/main/models/inventory.py:641 msgid "Group variables in JSON or YAML format." msgstr "Grupo de variables en formato JSON o YAML." -#: awx/main/models/inventory.py:749 +#: awx/main/models/inventory.py:647 msgid "Hosts associated directly with this group." -msgstr "Hosts asociados directamente con este grupo." - -#: awx/main/models/inventory.py:754 -msgid "Total number of hosts directly or indirectly in this group." -msgstr "Número total de hosts directamente o indirectamente en este grupo." - -#: awx/main/models/inventory.py:759 -msgid "Flag indicating whether this group has any hosts with active failures." -msgstr "" -"Indicador que establece si este grupo tiene algunos hosts con errores " -"activos." - -#: awx/main/models/inventory.py:764 -msgid "Number of hosts in this group with active failures." -msgstr "Número de hosts en este grupo con errores activos." +msgstr "Hosts associated directly with this group." -#: awx/main/models/inventory.py:769 -msgid "Total number of child groups contained within this group." -msgstr "Número total de grupos hijo dentro de este grupo." - -#: awx/main/models/inventory.py:774 -msgid "Number of child groups within this group that have active failures." -msgstr "" -"Número de grupos hijo dentro de este grupo que tienen errores activos." - -#: awx/main/models/inventory.py:779 -msgid "" -"Flag indicating whether this group was created/updated from any external " -"inventory sources." -msgstr "" -"Indicador que establece si este grupo se creó/se actualizó desde un " -"inventario de fuente externa." - -#: awx/main/models/inventory.py:785 +#: awx/main/models/inventory.py:653 msgid "Inventory source(s) that created or modified this group." -msgstr "Fuentes de inventario que crearon o modificaron este grupo." - -#: awx/main/models/inventory.py:981 awx/main/models/projects.py:53 -#: awx/main/models/unified_jobs.py:519 -msgid "Manual" -msgstr "Manual" +msgstr "Fuente(s) de inventario que crearon o modificaron este grupo." -#: awx/main/models/inventory.py:982 +#: awx/main/models/inventory.py:825 msgid "File, Directory or Script" msgstr "Archivo, directorio o script" -#: awx/main/models/inventory.py:983 +#: awx/main/models/inventory.py:826 msgid "Sourced from a Project" msgstr "Extraído de un proyecto" -#: awx/main/models/inventory.py:984 +#: awx/main/models/inventory.py:827 msgid "Amazon EC2" msgstr "Amazon EC2" -#: awx/main/models/inventory.py:993 +#: awx/main/models/inventory.py:836 msgid "Custom Script" msgstr "Script personalizado" -#: awx/main/models/inventory.py:1110 +#: awx/main/models/inventory.py:953 msgid "Inventory source variables in YAML or JSON format." msgstr "Variables para la fuente del inventario en formato YAML o JSON." -#: awx/main/models/inventory.py:1121 +#: awx/main/models/inventory.py:964 msgid "" "Comma-separated list of filter expressions (EC2 only). Hosts are imported " "when ANY of the filters match." -msgstr "" -"Lista de expresiones de filtrado separadas por comas (solo EC2). Los hosts " -"se importan cuando ALGUNO de los filtros coincide." +msgstr "Lista de expresiones de filtrado separadas por coma (sólo EC2). Servidores son importados cuando ALGÚN filtro coincide." -#: awx/main/models/inventory.py:1127 +#: awx/main/models/inventory.py:970 msgid "Limit groups automatically created from inventory source (EC2 only)." -msgstr "" -"Limitar grupos creados automáticamente desde la fuente del inventario (solo " -"EC2)." +msgstr "Limitar grupos creados automáticamente desde la fuente del inventario (sólo EC2)" -#: awx/main/models/inventory.py:1131 +#: awx/main/models/inventory.py:974 msgid "Overwrite local groups and hosts from remote inventory source." -msgstr "" -"Sobrescribir grupos y hosts locales desde una fuente de inventario remota." +msgstr "Sobrescribir grupos locales y servidores desde una fuente remota del inventario." -#: awx/main/models/inventory.py:1135 +#: awx/main/models/inventory.py:978 msgid "Overwrite local variables from remote inventory source." -msgstr "" -"Sobrescribir las variables locales desde una fuente de inventario remota." +msgstr "Sobrescribir las variables locales desde una fuente remota del inventario." -#: awx/main/models/inventory.py:1140 awx/main/models/jobs.py:140 -#: awx/main/models/projects.py:128 +#: awx/main/models/inventory.py:983 awx/main/models/jobs.py:154 +#: awx/main/models/projects.py:135 msgid "The amount of time (in seconds) to run before the task is canceled." -msgstr "" -"La cantidad de tiempo (en segundos) para ejecutar antes de que se cancele la" -" tarea." +msgstr "La cantidad de tiempo (en segundos) para ejecutar antes de que se cancele la tarea." -#: awx/main/models/inventory.py:1173 +#: awx/main/models/inventory.py:1016 msgid "Image ID" -msgstr "ID de imagen" +msgstr "Id de imagen" -#: awx/main/models/inventory.py:1174 +#: awx/main/models/inventory.py:1017 msgid "Availability Zone" msgstr "Zona de disponibilidad" -#: awx/main/models/inventory.py:1175 -msgid "Account" -msgstr "Cuenta" - -#: awx/main/models/inventory.py:1176 +#: awx/main/models/inventory.py:1019 msgid "Instance ID" msgstr "ID de instancia" -#: awx/main/models/inventory.py:1177 +#: awx/main/models/inventory.py:1020 msgid "Instance State" msgstr "Estado de instancia" -#: awx/main/models/inventory.py:1178 +#: awx/main/models/inventory.py:1021 msgid "Platform" msgstr "Plataforma" -#: awx/main/models/inventory.py:1179 +#: awx/main/models/inventory.py:1022 msgid "Instance Type" msgstr "Tipo de instancia" -#: awx/main/models/inventory.py:1180 -msgid "Key Name" -msgstr "Nombre clave" - -#: awx/main/models/inventory.py:1181 +#: awx/main/models/inventory.py:1024 msgid "Region" msgstr "Región" -#: awx/main/models/inventory.py:1182 +#: awx/main/models/inventory.py:1025 msgid "Security Group" msgstr "Grupo de seguridad" -#: awx/main/models/inventory.py:1183 +#: awx/main/models/inventory.py:1026 msgid "Tags" msgstr "Etiquetas" -#: awx/main/models/inventory.py:1184 +#: awx/main/models/inventory.py:1027 msgid "Tag None" msgstr "Etiqueta ninguna" -#: awx/main/models/inventory.py:1185 +#: awx/main/models/inventory.py:1028 msgid "VPC ID" msgstr "VPC ID" -#: awx/main/models/inventory.py:1253 +#: awx/main/models/inventory.py:1096 #, python-format msgid "" "Cloud-based inventory sources (such as %s) require credentials for the " "matching cloud service." -msgstr "" -"Fuentes de inventario basados en Cloud (como %s) requieren credenciales para" -" identificar el servicio cloud correspondiente." +msgstr "Las fuentes de inventario basados en la nube (como %s) requieren credenciales para el servicio en la nube coincidente." -#: awx/main/models/inventory.py:1259 +#: awx/main/models/inventory.py:1102 msgid "Credential is required for a cloud source." -msgstr "Se requiere una credencial para una fuente cloud." +msgstr "Un credencial es necesario para una fuente cloud." -#: awx/main/models/inventory.py:1262 +#: awx/main/models/inventory.py:1105 msgid "" "Credentials of type machine, source control, insights and vault are " "disallowed for custom inventory sources." -msgstr "" -"Credenciales de tipo de máquina, control de fuentes, conocimientos y vault " -"no están permitidas para las fuentes de inventario personalizado." +msgstr "Credenciales de tipo de máquina, control de fuentes, conocimientos y vault no están permitidas para las fuentes de inventario personalizado." + +#: awx/main/models/inventory.py:1110 +msgid "" +"Credentials of type insights and vault are disallowed for scm inventory " +"sources." +msgstr "Las credenciales de tipo de Insights y Vault no están permitidas para fuentes de inventario de SCM." -#: awx/main/models/inventory.py:1314 +#: awx/main/models/inventory.py:1170 #, python-format msgid "Invalid %(source)s region: %(region)s" msgstr "Región %(source)s no válida: %(region)s" -#: awx/main/models/inventory.py:1338 +#: awx/main/models/inventory.py:1194 #, python-format msgid "Invalid filter expression: %(filter)s" msgstr "Expresión de filtro no válida: %(filter)s" -#: awx/main/models/inventory.py:1359 +#: awx/main/models/inventory.py:1215 #, python-format msgid "Invalid group by choice: %(choice)s" -msgstr "Grupo escogido no válido: %(choice)s" +msgstr "Grupo por elección no válido: %(choice)s" -#: awx/main/models/inventory.py:1394 +#: awx/main/models/inventory.py:1243 msgid "Project containing inventory file used as source." msgstr "Proyecto que contiene el archivo de inventario usado como fuente." -#: awx/main/models/inventory.py:1555 -#, python-format -msgid "" -"Unable to configure this item for cloud sync. It is already managed by %s." -msgstr "" -"Imposible configurar este elemento para sincronización cloud. Ya es " -"administrado por %s." - -#: awx/main/models/inventory.py:1565 +#: awx/main/models/inventory.py:1416 msgid "" "More than one SCM-based inventory source with update on project update per-" "inventory not allowed." -msgstr "" -"No se permite más de una fuente de inventario basada en SCM con " -"actualización en la actualización del proyecto por inventario." +msgstr "No se permite más de una fuente de inventario basada en SCM con actualización en la actualización del proyecto por inventario." -#: awx/main/models/inventory.py:1572 +#: awx/main/models/inventory.py:1423 msgid "" "Cannot update SCM-based inventory source on launch if set to update on " "project update. Instead, configure the corresponding source project to " "update on launch." -msgstr "" -"No se puede actualizar la fuente de inventario basada en SCM en la ejecución" -" si está configurada para actualizarse en la actualización del proyecto. En " -"su lugar, configure el proyecto de fuente correspondiente para actualizar en" -" la ejecución." +msgstr "No se puede actualizar la fuente de inventario basada en SCM en la ejecución si está configurada para actualizarse en la actualización del proyecto. En su lugar, configure el proyecto de fuente correspondiente para actualizar en la ejecución." -#: awx/main/models/inventory.py:1579 -msgid "" -"SCM type sources must set `overwrite_vars` to `true` until Ansible 2.5." -msgstr "" -"Las fuentes de tipo SCM deben configurar `overwrite_vars` como `true` hasta " -"Ansible 2.5." - -#: awx/main/models/inventory.py:1584 +#: awx/main/models/inventory.py:1429 msgid "Cannot set source_path if not SCM type." msgstr "No se puede configurar source_path si no es de tipo SCM." -#: awx/main/models/inventory.py:1622 +#: awx/main/models/inventory.py:1472 msgid "" "Inventory files from this Project Update were used for the inventory update." -msgstr "" -"Los archivos de inventario de esta actualización de proyecto se utilizaron " -"para la actualización del inventario." +msgstr "Los archivos de inventario de esta actualización de proyecto se utilizaron para la actualización del inventario." -#: awx/main/models/inventory.py:1732 +#: awx/main/models/inventory.py:1583 msgid "Inventory script contents" msgstr "Contenido del script de inventario" -#: awx/main/models/inventory.py:1737 +#: awx/main/models/inventory.py:1588 msgid "Organization owning this inventory script" -msgstr "Organización propietaria de este script de inventario" +msgstr "Organización propietario de este script de inventario" -#: awx/main/models/jobs.py:66 +#: awx/main/models/jobs.py:74 msgid "" "If enabled, textual changes made to any templated files on the host are " "shown in the standard output" -msgstr "" -"Si se habilita, los cambios de texto realizados en cualquier archivo de " -"plantilla en el host se muestran en la salida estándar" +msgstr "Si se habilita, los cambios de texto realizados en cualquier archivo de plantilla en el host se muestran en la salida estándar" -#: awx/main/models/jobs.py:145 +#: awx/main/models/jobs.py:106 msgid "" -"If enabled, Tower will act as an Ansible Fact Cache Plugin; persisting facts" -" at the end of a playbook run to the database and caching facts for use by " -"Ansible." -msgstr "" -"Si se habilita, Tower funcionará como un complemento de caché de eventos " -"Ansible, continuará los eventos al final de una ejecución de playbook en la " -"base de datos y almacenará en caché los eventos que son utilizados por " +"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." + +#: awx/main/models/jobs.py:159 +msgid "" +"If enabled, Tower will act as an Ansible Fact Cache Plugin; persisting facts " +"at the end of a playbook run to the database and caching facts for use by " "Ansible." +msgstr "Si se habilita, Tower funcionará como un complemento de caché de eventos Ansible, continuará los eventos al final de una ejecución de playbook en la base de datos y almacenará en caché los eventos que son utilizados por Ansible." -#: awx/main/models/jobs.py:163 -msgid "You must provide a Vault credential." -msgstr "Debe proporcionar una credencial de Vault." +#: awx/main/models/jobs.py:260 +msgid "" +"The number of jobs to slice into at runtime. Will cause the Job Template to " +"launch a workflow if value is greater than 1." +msgstr "La cantidad de trabajos por fragmentar en el tiempo de ejecución. Hará que la plantilla de trabajo ejecute un flujo de trabajo si el valor es mayor a 1." -#: awx/main/models/jobs.py:308 +#: awx/main/models/jobs.py:297 msgid "Job Template must provide 'inventory' or allow prompting for it." -msgstr "" -"La plantilla de trabajo debe proporcionar 'inventory' o permitir " -"solicitarlo." +msgstr "La plantilla de trabajo debe proporcionar 'inventory' o permitir solicitarlo." + +#: awx/main/models/jobs.py:308 +#, python-brace-format +msgid "Maximum number of forks ({settings.MAX_FORKS}) exceeded." +msgstr "Se superó el número máximo de bifurcaciones ({settings.MAX_FORKS})." + +#: awx/main/models/jobs.py:463 +msgid "Project is missing." +msgstr "Falta el proyecto." -#: awx/main/models/jobs.py:398 +#: awx/main/models/jobs.py:467 +msgid "Project does not allow override of branch." +msgstr "El proyecto no permite la anulación de la rama." + +#: awx/main/models/jobs.py:477 awx/main/models/workflow.py:545 msgid "Field is not configured to prompt on launch." -msgstr "" -"El campo no está configurado para emitir avisos durante el lanzamiento." +msgstr "El campo no está configurado para emitir avisos durante el lanzamiento." -#: awx/main/models/jobs.py:404 +#: awx/main/models/jobs.py:483 msgid "Saved launch configurations cannot provide passwords needed to start." -msgstr "" -"Las opciones de configuración de lanzamiento guardadas no pueden brindar las" -" contraseñas necesarias para el inicio." +msgstr "Las opciones de configuración de lanzamiento guardadas no pueden brindar las contraseñas necesarias para el inicio." -#: awx/main/models/jobs.py:412 +#: awx/main/models/jobs.py:491 msgid "Job Template {} is missing or undefined." msgstr "Plantilla de tareas {} no encontrada o no definida." -#: awx/main/models/jobs.py:493 awx/main/models/projects.py:277 +#: awx/main/models/jobs.py:574 awx/main/models/projects.py:278 +#: awx/main/models/projects.py:497 msgid "SCM Revision" msgstr "Revisión SCM" -#: awx/main/models/jobs.py:494 +#: awx/main/models/jobs.py:575 msgid "The SCM Revision from the Project used for this job, if available" -msgstr "" -"La revisión SCM desde el proyecto usado para este trabajo, si está " -"disponible" +msgstr "La revisión SCM desde el proyecto usado para este trabajo, si está disponible" -#: awx/main/models/jobs.py:502 +#: awx/main/models/jobs.py:583 msgid "" "The SCM Refresh task used to make sure the playbooks were available for the " "job run" -msgstr "" -"La tarea de actualización de SCM utilizada para asegurarse de que los " -"playbooks estaban disponibles para la ejecución del trabajo" +msgstr "La tarea de actualización de SCM utilizado para asegurarse que los playbooks estaban disponibles para la ejecución del trabajo" + +#: awx/main/models/jobs.py:588 +msgid "" +"If part of a sliced job, the ID of the inventory slice operated on. If not " +"part of sliced job, parameter is not used." +msgstr "Si forma parte de un trabajo fraccionado, el ID del fraccionamiento de inventario en el que se realiza. Si no es parte de un trabajo fraccionado, no se usa el parámetro." + +#: awx/main/models/jobs.py:594 +msgid "" +"If ran as part of sliced jobs, the total number of slices. If 1, job is not " +"part of a sliced job." +msgstr "Si se ejecuta como parte de trabajos fraccionados, la cantidad total de fraccionamientos. Si es 1, el trabajo no forma parte de un trabajo fraccionado." -#: awx/main/models/jobs.py:629 +#: awx/main/models/jobs.py:676 #, python-brace-format msgid "{status_value} is not a valid status option." msgstr "{status_value} no es una opción de estado válida." -#: awx/main/models/jobs.py:1005 +#: awx/main/models/jobs.py:926 +msgid "" +"Inventory applied as a prompt, assuming job template prompts for inventory" +msgstr "Inventario aplicado como un aviso, asumiendo que la plantilla de trabajo solicita el inventario" + +#: awx/main/models/jobs.py:1085 msgid "job host summaries" -msgstr "resumen de hosts de trabajo" +msgstr "Resumen de trabajos de servidor" -#: awx/main/models/jobs.py:1077 +#: awx/main/models/jobs.py:1144 msgid "Remove jobs older than a certain number of days" -msgstr "Eliminar trabajos más antiguos que el número de días especificado" +msgstr "Eliminar trabajos más antiguos que el ńumero de días especificado" -#: awx/main/models/jobs.py:1078 +#: awx/main/models/jobs.py:1145 msgid "Remove activity stream entries older than a certain number of days" -msgstr "" -"Eliminar entradas del flujo de actividad más antiguos que el número de días " -"especificado" +msgstr "Eliminar entradas del flujo de actividad más antiguos que el número de días especificado" -#: awx/main/models/jobs.py:1079 -msgid "Purge and/or reduce the granularity of system tracking data" -msgstr "Limpiar o reducir la granularidad de los datos del sistema de rastreo" +#: awx/main/models/jobs.py:1146 +msgid "Removes expired browser sessions from the database" +msgstr "Elimina las sesiones de navegador expiradas de la base de datos" -#: awx/main/models/jobs.py:1149 +#: awx/main/models/jobs.py:1147 +msgid "Removes expired OAuth 2 access tokens and refresh tokens" +msgstr "Elimina los tokens de acceso OAuth2 expirados y los tokens de actualización" + +#: awx/main/models/jobs.py:1217 #, python-brace-format msgid "Variables {list_of_keys} are not allowed for system jobs." -msgstr "" -"Las variables {list_of_keys} no están permitidas para tareas del sistema." +msgstr "Las variables {list_of_keys} no están permitidas para los trabajos del sistema." -#: awx/main/models/jobs.py:1164 +#: awx/main/models/jobs.py:1233 msgid "days must be a positive integer." msgstr "días debe ser un número entero." @@ -3844,127 +4039,169 @@ msgstr "días debe ser un número entero." msgid "Organization this label belongs to." msgstr "Organización a la que esta etiqueta pertenece." -#: awx/main/models/mixins.py:309 +#: awx/main/models/mixins.py:321 #, python-brace-format msgid "" "Variables {list_of_keys} are not allowed on launch. Check the Prompt on " -"Launch setting on the Job Template to include Extra Variables." -msgstr "" -"Las variables {list_of_keys} no están permitidas durante el lanzamiento. " -"Verifique la configuración de Aviso durante el lanzamiento en la Plantilla " -"de tareas para incluir las Variables adicionales." +"Launch setting on the {model_name} to include Extra Variables." +msgstr "Las variables {list_of_keys} no están permitidas en el lanzamiento. Verifique la configuración de Aviso en el lanzamiento en el {model_name} para incluir variables adicionales." -#: awx/main/models/mixins.py:440 +#: awx/main/models/mixins.py:453 msgid "Local absolute file path containing a custom Python virtualenv to use" -msgstr "" -"La ruta de archivos absoluta local que contiene un Python virtualenv para " -"uso" +msgstr "La ruta de archivos absoluta local que contiene un Python virtualenv personalizado para uso" -#: awx/main/models/mixins.py:447 +#: awx/main/models/mixins.py:460 msgid "{} is not a valid virtualenv in {}" msgstr "{} no es un virtualenv válido en {}" +#: awx/main/models/mixins.py:507 awx/main/models/mixins.py:551 +msgid "Service that webhook requests will be accepted from" +msgstr "El servicio que webhook solicita será aceptado desde" + +#: awx/main/models/mixins.py:512 +msgid "Shared secret that the webhook service will use to sign requests" +msgstr "Secreto compartido que el servicio webhook utilizará para firmar solicitudes" + +#: awx/main/models/mixins.py:520 awx/main/models/mixins.py:559 +msgid "Personal Access Token for posting back the status to the service API" +msgstr "Token de acceso personal para devolver el estado a la API de servicio" + +#: awx/main/models/mixins.py:564 +msgid "Unique identifier of the event that triggered this webhook" +msgstr "Identificador único del evento que activó este webhook" + #: awx/main/models/notifications.py:42 +msgid "Email" +msgstr "Correo electrónico" + +#: awx/main/models/notifications.py:43 +msgid "Slack" +msgstr "Slack" + +#: awx/main/models/notifications.py:44 +msgid "Twilio" +msgstr "Twilio" + +#: awx/main/models/notifications.py:45 +msgid "Pagerduty" +msgstr "Pagerduty" + +#: awx/main/models/notifications.py:46 +msgid "Grafana" +msgstr "Grafana" + +#: awx/main/models/notifications.py:47 +msgid "HipChat" +msgstr "HipChat" + +#: awx/main/models/notifications.py:48 awx/main/models/unified_jobs.py:545 +msgid "Webhook" +msgstr "Webhook" + +#: awx/main/models/notifications.py:49 +msgid "Mattermost" +msgstr "Mattermost" + +#: awx/main/models/notifications.py:50 msgid "Rocket.Chat" msgstr "Rocket.Chat" -#: awx/main/models/notifications.py:142 awx/main/models/unified_jobs.py:62 +#: awx/main/models/notifications.py:51 +msgid "IRC" +msgstr "IRC" + +#: awx/main/models/notifications.py:82 +msgid "Optional custom messages for notification template." +msgstr "Mensajes personalizados opcionales para la plantilla de notificación." + +#: awx/main/models/notifications.py:212 awx/main/models/unified_jobs.py:70 msgid "Pending" msgstr "Pendiente" -#: awx/main/models/notifications.py:143 awx/main/models/unified_jobs.py:65 +#: awx/main/models/notifications.py:213 awx/main/models/unified_jobs.py:73 msgid "Successful" msgstr "Correctamente" -#: awx/main/models/notifications.py:144 awx/main/models/unified_jobs.py:66 +#: awx/main/models/notifications.py:214 awx/main/models/unified_jobs.py:74 msgid "Failed" msgstr "Fallido" -#: awx/main/models/notifications.py:218 -msgid "status_str must be either succeeded or failed" -msgstr "status_str debe ser 'succeeded' o 'failed'" +#: awx/main/models/notifications.py:467 +msgid "status must be either running, succeeded or failed" +msgstr "el estado debe ser en ejecución, correcto o falló" -#: awx/main/models/oauth.py:29 +#: awx/main/models/oauth.py:33 msgid "application" msgstr "aplicación" -#: awx/main/models/oauth.py:35 +#: awx/main/models/oauth.py:40 msgid "Confidential" msgstr "Confidencial" -#: awx/main/models/oauth.py:36 +#: awx/main/models/oauth.py:41 msgid "Public" msgstr "Público" -#: awx/main/models/oauth.py:43 +#: awx/main/models/oauth.py:47 msgid "Authorization code" msgstr "Código de autorización" -#: awx/main/models/oauth.py:44 -msgid "Implicit" -msgstr "Implícito" - -#: awx/main/models/oauth.py:45 +#: awx/main/models/oauth.py:48 msgid "Resource owner password-based" msgstr "Basado en contraseña del propietario de recursos" -#: awx/main/models/oauth.py:60 +#: awx/main/models/oauth.py:63 msgid "Organization containing this application." msgstr "Organización que contiene esta aplicación." -#: awx/main/models/oauth.py:69 +#: awx/main/models/oauth.py:72 msgid "" "Used for more stringent verification of access to an application when " "creating a token." -msgstr "" -"Utilizado para una verificación más estricta de acceso a una aplicación al " -"crear un token." +msgstr "Usado para una verificación más estricta de acceso a una aplicación al crear un token." -#: awx/main/models/oauth.py:74 +#: awx/main/models/oauth.py:77 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." +msgstr "Establecer como Público o Confidencial según cuán seguro sea el dispositivo del cliente." -#: awx/main/models/oauth.py:78 +#: awx/main/models/oauth.py:81 msgid "" "Set True to skip authorization step for completely trusted applications." -msgstr "" -"Se debe establecer como True para omitir el paso de autorización para " -"aplicaciones completamente confiables." +msgstr "Se debe establecer como True para omitir el paso de autorización para aplicaciones completamente confiables." -#: awx/main/models/oauth.py:83 +#: awx/main/models/oauth.py:86 msgid "" "The Grant type the user must use for acquire tokens for this application." -msgstr "" -"El tipo de Permiso que debe usar el usuario para adquirir tokens para esta " -"aplicación." +msgstr "El tipo de Permiso que debe usar el usuario para adquirir tokens para esta aplicación." -#: awx/main/models/oauth.py:91 +#: awx/main/models/oauth.py:94 msgid "access token" -msgstr "Token de acceso" +msgstr "token de acceso" -#: awx/main/models/oauth.py:99 +#: awx/main/models/oauth.py:103 msgid "The user representing the token owner" msgstr "El usuario que representa al propietario del token" -#: awx/main/models/oauth.py:114 +#: awx/main/models/oauth.py:117 msgid "" -"Allowed scopes, further restricts user's permissions. Must be a simple " -"space-separated string with allowed scopes ['read', 'write']." -msgstr "" -"Los alcances permitidos limitan aún más los permisos de los usuarios. Debe " -"ser una cadena simple y separada por espacios, con alcances permitidos " -"['lectura', 'escritura']." +"Allowed scopes, further restricts user's permissions. Must be a simple space-" +"separated string with allowed scopes ['read', 'write']." +msgstr "Los alcances permitidos limitan aún más los permisos de los usuarios. Debe ser una cadena simple y separada por espacios, con alcances permitidos ['lectura', 'escritura']." -#: awx/main/models/oauth.py:133 +#: awx/main/models/oauth.py:140 msgid "" "OAuth2 Tokens cannot be created by users associated with an external " "authentication provider ({})" -msgstr "" -"Los usuarios asociados con un proveedor de autenticación externo ({}) no " -"pueden crear tokens OAuth2" +msgstr "Los usuarios asociados con un proveedor de autenticación externo ({}) no pueden crear tokens OAuth2" + +#: awx/main/models/organization.py:51 +msgid "Maximum number of hosts allowed to be managed by this organization." +msgstr "Cantidad máxima de hosts que puede administrar esta organización." + +#: awx/main/models/projects.py:53 awx/main/models/unified_jobs.py:539 +msgid "Manual" +msgstr "Manual" #: awx/main/models/projects.py:54 msgid "Git" @@ -3986,9 +4223,7 @@ msgstr "Red Hat Insights" msgid "" "Local path (relative to PROJECTS_ROOT) containing playbooks and related " "files for this project." -msgstr "" -"Ruta local (relativa a PROJECTS_ROOT) que contiene playbooks y archivos " -"relacionados para este proyecto." +msgstr "Ruta local (relativa a PROJECTS_ROOT) que contiene playbooks y ficheros relacionados para este proyecto." #: awx/main/models/projects.py:92 msgid "SCM Type" @@ -3996,17 +4231,15 @@ msgstr "Tipo SCM" #: awx/main/models/projects.py:93 msgid "Specifies the source control system used to store the project." -msgstr "" -"Especifica el sistema de control de fuentes utilizado para almacenar el " -"proyecto." +msgstr "Especifica el sistema de control de fuentes utilizado para almacenar el proyecto." #: awx/main/models/projects.py:99 msgid "SCM URL" -msgstr "URL de SCM" +msgstr "SCM URL" #: awx/main/models/projects.py:100 msgid "The location where the project is stored." -msgstr "La ubicación donde está alojado el proyecto." +msgstr "La ubicación donde el proyecto está alojado." #: awx/main/models/projects.py:106 msgid "SCM Branch" @@ -4016,187 +4249,212 @@ msgstr "Rama SCM" msgid "Specific branch, tag or commit to checkout." msgstr "Especificar rama, etiqueta o commit para checkout." -#: awx/main/models/projects.py:111 +#: awx/main/models/projects.py:113 +msgid "SCM refspec" +msgstr "SCM refspec" + +#: awx/main/models/projects.py:114 +msgid "For git projects, an additional refspec to fetch." +msgstr "Para los proyectos de git, un refspec adicional para obtener." + +#: awx/main/models/projects.py:118 msgid "Discard any local changes before syncing the project." -msgstr "" -"Descartar cualquier cambio local antes de la sincronización del proyecto." +msgstr "Descartar cualquier cambio local antes de la sincronización del proyecto." -#: awx/main/models/projects.py:115 +#: awx/main/models/projects.py:122 msgid "Delete the project before syncing." msgstr "Eliminar el proyecto antes de la sincronización." -#: awx/main/models/projects.py:144 +#: awx/main/models/projects.py:151 msgid "Invalid SCM URL." -msgstr "URL de SCM no válida." +msgstr "SCM URL inválida." -#: awx/main/models/projects.py:147 +#: awx/main/models/projects.py:154 msgid "SCM URL is required." -msgstr "URL de SCM es obligatoria." +msgstr "SCM URL es obligatoria." -#: awx/main/models/projects.py:155 +#: awx/main/models/projects.py:162 msgid "Insights Credential is required for an Insights Project." msgstr "Se requiere una credencial de Insights para un proyecto de Insights." -#: awx/main/models/projects.py:161 +#: awx/main/models/projects.py:168 msgid "Credential kind must be 'scm'." -msgstr "Tipo de credenciales debe ser 'scm'." +msgstr "TIpo de credenciales deben ser 'scm'." -#: awx/main/models/projects.py:178 +#: awx/main/models/projects.py:185 msgid "Invalid credential." -msgstr "Credencial no válida." +msgstr "Credencial inválida." -#: awx/main/models/projects.py:263 +#: awx/main/models/projects.py:259 msgid "Update the project when a job is launched that uses the project." -msgstr "" -"Actualizar el proyecto mientras se ejecuta un trabajo que usa este proyecto." +msgstr "Actualizar el proyecto mientras un trabajo es ejecutado que usa este proyecto." -#: awx/main/models/projects.py:268 +#: awx/main/models/projects.py:264 msgid "" -"The number of seconds after the last project update ran that a newproject " +"The number of seconds after the last project update ran that a new project " "update will be launched as a job dependency." -msgstr "" -"El número de segundos desde que se ejecutó la última actualización del " -"proyecto para que la nueva actualización del proyecto se ejecute como un " -"trabajo dependiente." +msgstr "El número de segundos desde de que la última actualización del proyecto se ejecutó, que una nueva actualización de proyecto se iniciará como una dependencia de trabajo." -#: awx/main/models/projects.py:278 +#: awx/main/models/projects.py:269 +msgid "" +"Allow changing the SCM branch or revision in a job template that uses this " +"project." +msgstr "Permitir el cambio de la rama o revisión de SCM en una plantilla de trabajo que utilice este proyecto." + +#: awx/main/models/projects.py:279 msgid "The last revision fetched by a project update" msgstr "La última revisión obtenida por una actualización del proyecto." -#: awx/main/models/projects.py:285 +#: awx/main/models/projects.py:286 msgid "Playbook Files" -msgstr "Archivos Playbook" +msgstr "Ficheros Playbook" -#: awx/main/models/projects.py:286 +#: awx/main/models/projects.py:287 msgid "List of playbooks found in the project" msgstr "Lista de playbooks encontrados en este proyecto" -#: awx/main/models/projects.py:293 +#: awx/main/models/projects.py:294 msgid "Inventory Files" msgstr "Archivos de inventario" -#: awx/main/models/projects.py:294 +#: awx/main/models/projects.py:295 msgid "" "Suggested list of content that could be Ansible inventory in the project" -msgstr "" -"Lista sugerida de contenido que podría ser inventario de Ansible en el " -"proyecto" +msgstr "Lista sugerida de contenido que podría ser inventario de Ansible en el proyecto" -#: awx/main/models/rbac.py:36 +#: awx/main/models/projects.py:332 +msgid "Organization cannot be changed when in use by job templates." +msgstr "La organización no se puede cambiar cuando es usada por plantillas de tareas." + +#: awx/main/models/projects.py:490 +msgid "Parts of the project update playbook that will be run." +msgstr "Partes de la guía de actualización del proyecto que se ejecutará." + +#: awx/main/models/projects.py:498 +msgid "" +"The SCM Revision discovered by this update for the given project and branch." +msgstr "La revisión del SCM descubierta por esta actualización para la rama y el proyecto dados." + +#: awx/main/models/rbac.py:35 msgid "System Administrator" msgstr "Administrador del sistema" -#: awx/main/models/rbac.py:37 +#: awx/main/models/rbac.py:36 msgid "System Auditor" msgstr "Auditor del sistema" -#: awx/main/models/rbac.py:38 +#: awx/main/models/rbac.py:37 msgid "Ad Hoc" msgstr "Ad Hoc" -#: awx/main/models/rbac.py:39 +#: awx/main/models/rbac.py:38 msgid "Admin" msgstr "Admin" -#: awx/main/models/rbac.py:40 +#: awx/main/models/rbac.py:39 msgid "Project Admin" msgstr "Administrador de proyectos" -#: awx/main/models/rbac.py:41 +#: awx/main/models/rbac.py:40 msgid "Inventory Admin" msgstr "Administrador de inventarios" -#: awx/main/models/rbac.py:42 +#: awx/main/models/rbac.py:41 msgid "Credential Admin" msgstr "Administrador de credenciales" -#: awx/main/models/rbac.py:43 +#: awx/main/models/rbac.py:42 msgid "Job Template Admin" msgstr "Administrador de plantillas de trabajo" -#: awx/main/models/rbac.py:44 +#: awx/main/models/rbac.py:43 msgid "Workflow Admin" msgstr "Administrador de flujos de trabajo" -#: awx/main/models/rbac.py:45 +#: awx/main/models/rbac.py:44 msgid "Notification Admin" msgstr "Administrador de notificaciones" -#: awx/main/models/rbac.py:46 +#: awx/main/models/rbac.py:45 msgid "Auditor" msgstr "Auditor" -#: awx/main/models/rbac.py:47 +#: awx/main/models/rbac.py:46 msgid "Execute" msgstr "Ejecutar" -#: awx/main/models/rbac.py:48 +#: awx/main/models/rbac.py:47 msgid "Member" msgstr "Miembro" -#: awx/main/models/rbac.py:49 +#: awx/main/models/rbac.py:48 msgid "Read" msgstr "Lectura" -#: awx/main/models/rbac.py:50 +#: awx/main/models/rbac.py:49 msgid "Update" msgstr "Actualización" -#: awx/main/models/rbac.py:51 +#: awx/main/models/rbac.py:50 msgid "Use" msgstr "Uso" +#: awx/main/models/rbac.py:51 +msgid "Approve" +msgstr "Aprobar" + #: awx/main/models/rbac.py:55 msgid "Can manage all aspects of the system" msgstr "Puede gestionar todos los aspectos del sistema" #: awx/main/models/rbac.py:56 -msgid "Can view all settings on the system" -msgstr "Puede ver todos los ajustes del sistema" +msgid "Can view all aspects of the system" +msgstr "Puede ver todos los aspectos del sistema" #: awx/main/models/rbac.py:57 -msgid "May run ad hoc commands on an inventory" -msgstr "Puede ejecutar comandos ad-hoc en un inventario" +#, python-format +msgid "May run ad hoc commands on the %s" +msgstr "Puede ejecutar comandos ad hoc en %s" #: awx/main/models/rbac.py:58 #, python-format msgid "Can manage all aspects of the %s" -msgstr "Puede gestionar todos los aspectos de %s" +msgstr "Puede gestionar todos los aspectos del %s" #: awx/main/models/rbac.py:59 #, python-format msgid "Can manage all projects of the %s" -msgstr "Puede gestionar todos los proyectos de %s" +msgstr "Puede gestionar todos los proyectos del %s" #: awx/main/models/rbac.py:60 #, python-format msgid "Can manage all inventories of the %s" -msgstr "Puede gestionar todos los inventarios de %s" +msgstr "Puede gestionar todos los inventarios del %s" #: awx/main/models/rbac.py:61 #, python-format msgid "Can manage all credentials of the %s" -msgstr "Puede gestionar todas las credenciales de %s" +msgstr "Puede gestionar todas las credenciales del %s" #: awx/main/models/rbac.py:62 #, python-format msgid "Can manage all job templates of the %s" -msgstr "Puede administrar todas las plantillas de trabajo de %s" +msgstr "Puede administrar todas las plantillas de trabajo del %s" #: awx/main/models/rbac.py:63 #, python-format msgid "Can manage all workflows of the %s" -msgstr "Puede gestionar todos los flujos de trabajo de %s" +msgstr "Puede gestionar todos los flujos de trabajo del %s" #: awx/main/models/rbac.py:64 #, python-format msgid "Can manage all notifications of the %s" -msgstr "Puede gestionar todas las notificaciones de %s" +msgstr "Puede gestionar todas las notificaciones del %s" #: awx/main/models/rbac.py:65 #, python-format -msgid "Can view all settings for the %s" -msgstr "Puede ver todos los ajustes de %s" +msgid "Can view all aspects of the %s" +msgstr "Puede todos los aspectos de %s" #: awx/main/models/rbac.py:67 msgid "May run any executable resources in the organization" @@ -4205,721 +4463,794 @@ msgstr "Puede ejecutar cualquier recurso ejecutable en la organización" #: awx/main/models/rbac.py:68 #, python-format msgid "May run the %s" -msgstr "Puede ejecutar %s" +msgstr "Puede ejecutar el %s" #: awx/main/models/rbac.py:70 #, python-format msgid "User is a member of the %s" -msgstr "Usuario es miembro de %s" +msgstr "El usuario es miembro del %s" #: awx/main/models/rbac.py:71 #, python-format msgid "May view settings for the %s" -msgstr "Podría ver ajustes para %s" +msgstr "Podría ver ajustes para el %s" #: awx/main/models/rbac.py:72 -msgid "" -"May update project or inventory or group using the configured source update " -"system" -msgstr "" -"Podría actualizar el proyecto o el inventario, así como el grupo utilizando " -"el sistema de actualización configurado en la fuente" +#, python-format +msgid "May update the %s" +msgstr "Puede actualizar el %s" #: awx/main/models/rbac.py:73 #, python-format msgid "Can use the %s in a job template" -msgstr "Puede usar %s en una plantilla de trabajo" +msgstr "Puede usar el %s en una plantilla de trabajo" -#: awx/main/models/rbac.py:137 +#: awx/main/models/rbac.py:74 +msgid "Can approve or deny a workflow approval node" +msgstr "Puede aprobar o denegar un nodo de aprobación de flujo de trabajo" + +#: awx/main/models/rbac.py:138 msgid "roles" msgstr "roles" -#: awx/main/models/rbac.py:443 +#: awx/main/models/rbac.py:445 msgid "role_ancestors" msgstr "role_ancestors" -#: awx/main/models/schedules.py:79 +#: awx/main/models/schedules.py:83 msgid "Enables processing of this schedule." msgstr "Habilita el procesamiento de esta programación." -#: awx/main/models/schedules.py:85 +#: awx/main/models/schedules.py:89 msgid "The first occurrence of the schedule occurs on or after this time." -msgstr "La primera ocurrencia del programador sucede en esta fecha o después." +msgstr "La primera ocurrencia del programador sucede en o después de esta fecha." -#: awx/main/models/schedules.py:91 +#: awx/main/models/schedules.py:95 msgid "" "The last occurrence of the schedule occurs before this time, aftewards the " "schedule expires." -msgstr "" -"La última ocurrencia del planificador sucede antes de esta fecha, justo " -"después de que la planificación expire." +msgstr "La última ocurrencia del planificador sucede antes de esta fecha, justo después de que la planificación expire." -#: awx/main/models/schedules.py:95 +#: awx/main/models/schedules.py:99 msgid "A value representing the schedules iCal recurrence rule." msgstr "Un valor representando la regla de programación recurrente iCal." -#: awx/main/models/schedules.py:101 +#: awx/main/models/schedules.py:105 msgid "The next time that the scheduled action will run." -msgstr "La próxima vez que se ejecutará la acción programa." +msgstr "La siguiente vez que la acción programa se ejecutará." -#: awx/main/models/unified_jobs.py:61 +#: awx/main/models/unified_jobs.py:69 msgid "New" msgstr "Nuevo" -#: awx/main/models/unified_jobs.py:63 +#: awx/main/models/unified_jobs.py:71 msgid "Waiting" msgstr "Esperando" -#: awx/main/models/unified_jobs.py:64 +#: awx/main/models/unified_jobs.py:72 msgid "Running" msgstr "Ejecutándose" -#: awx/main/models/unified_jobs.py:68 +#: awx/main/models/unified_jobs.py:76 msgid "Canceled" msgstr "Cancelado" -#: awx/main/models/unified_jobs.py:72 +#: awx/main/models/unified_jobs.py:80 msgid "Never Updated" msgstr "Nunca actualizado" -#: awx/main/models/unified_jobs.py:76 +#: awx/main/models/unified_jobs.py:84 msgid "OK" -msgstr "Correcto" +msgstr "OK" -#: awx/main/models/unified_jobs.py:77 +#: awx/main/models/unified_jobs.py:85 msgid "Missing" msgstr "No encontrado" -#: awx/main/models/unified_jobs.py:81 +#: awx/main/models/unified_jobs.py:89 msgid "No External Source" msgstr "Sin fuente externa" -#: awx/main/models/unified_jobs.py:88 +#: awx/main/models/unified_jobs.py:96 msgid "Updating" msgstr "Actualizando" -#: awx/main/models/unified_jobs.py:427 +#: awx/main/models/unified_jobs.py:167 +msgid "The organization used to determine access to this template." +msgstr "La organización usada para determinar el acceso a esta plantilla." + +#: awx/main/models/unified_jobs.py:466 msgid "Field is not allowed on launch." msgstr "El campo no está permitido durante el lanzamiento." -#: awx/main/models/unified_jobs.py:455 +#: awx/main/models/unified_jobs.py:494 #, python-brace-format msgid "" -"Variables {list_of_keys} provided, but this template cannot accept " -"variables." -msgstr "" -"Variables {list_of_keys} provistas, aunque esta plantilla no puede aceptar " -"variables." +"Variables {list_of_keys} provided, but this template cannot accept variables." +msgstr "Se proporcionaron las variables {list_of_keys}, aunque esta plantilla no puede aceptar variables." -#: awx/main/models/unified_jobs.py:520 +#: awx/main/models/unified_jobs.py:540 msgid "Relaunch" msgstr "Relanzar" -#: awx/main/models/unified_jobs.py:521 +#: awx/main/models/unified_jobs.py:541 msgid "Callback" msgstr "Callback" -#: awx/main/models/unified_jobs.py:522 +#: awx/main/models/unified_jobs.py:542 msgid "Scheduled" msgstr "Programado" -#: awx/main/models/unified_jobs.py:523 +#: awx/main/models/unified_jobs.py:543 msgid "Dependency" msgstr "Dependencia" -#: awx/main/models/unified_jobs.py:524 +#: awx/main/models/unified_jobs.py:544 msgid "Workflow" msgstr "Flujo de trabajo" -#: awx/main/models/unified_jobs.py:525 +#: awx/main/models/unified_jobs.py:546 msgid "Sync" msgstr "Sincronizar" -#: awx/main/models/unified_jobs.py:573 +#: awx/main/models/unified_jobs.py:601 msgid "The node the job executed on." msgstr "El nodo en el que se ejecutó la tarea." -#: awx/main/models/unified_jobs.py:579 +#: awx/main/models/unified_jobs.py:607 msgid "The instance that managed the isolated execution environment." msgstr "La instancia que gestionó el entorno de ejecución aislado." -#: awx/main/models/unified_jobs.py:605 +#: awx/main/models/unified_jobs.py:634 msgid "The date and time the job was queued for starting." -msgstr "" -"La fecha y hora en que el trabajo se colocó en la cola para iniciarse." +msgstr "La fecha y hora que el trabajo fue puesto en la cola para iniciarse." + +#: awx/main/models/unified_jobs.py:639 +msgid "" +"If True, the task manager has already processed potential dependencies for " +"this job." +msgstr "Si es verdadero (True), el gerente de tareas ya ha procesado las posibles dependencias para esta tarea." -#: awx/main/models/unified_jobs.py:611 +#: awx/main/models/unified_jobs.py:645 msgid "The date and time the job finished execution." -msgstr "La fecha y hora en que el trabajo finalizó su ejecución." +msgstr "La fecha y hora en la que el trabajo finalizó su ejecución." -#: awx/main/models/unified_jobs.py:617 +#: awx/main/models/unified_jobs.py:652 +msgid "The date and time when the cancel request was sent." +msgstr "La fecha y la hora en que se envió la solicitud de cancelación." + +#: awx/main/models/unified_jobs.py:659 msgid "Elapsed time in seconds that the job ran." -msgstr "Tiempo transcurrido en segundos en que se ejecutó el trabajo." +msgstr "Tiempo transcurrido en segundos que el trabajo se ejecutó. " -#: awx/main/models/unified_jobs.py:639 +#: awx/main/models/unified_jobs.py:681 msgid "" -"A status field to indicate the state of the job if it wasn't able to run and" -" capture stdout" -msgstr "" -"Un campo de estado que indica el estado del trabajo si este no fue capaz de " -"ejecutarse y obtener la salida estándar." +"A status field to indicate the state of the job if it wasn't able to run and " +"capture stdout" +msgstr "Un campo de estado que indica el estado del trabajo si éste no fue capaz de ejecutarse y obtener la salida estándar." + +#: awx/main/models/unified_jobs.py:710 +msgid "The Instance group the job was run under" +msgstr "El grupo Instance en el que se ejecutó la tarea" + +#: awx/main/models/unified_jobs.py:718 +msgid "The organization used to determine access to this unified job." +msgstr "La organización usada para determinar el acceso a esta tarea unificada." + +#: awx/main/models/workflow.py:85 +msgid "" +"If enabled then the node will only run if all of the parent nodes have met " +"the criteria to reach this node" +msgstr "Si está habilitado, el nodo solo se ejecutará si todos los nodos padres han cumplido los criterios para alcanzar este nodo" + +#: awx/main/models/workflow.py:154 +msgid "" +"An identifier for this node that is unique within its workflow. It is copied " +"to workflow job nodes corresponding to this node." +msgstr "Un identificador para este nodo que es único dentro de su flujo de trabajo. Se copia a los nodos de tareas del flujo de trabajo correspondientes a este nodo." + +#: awx/main/models/workflow.py:229 +msgid "" +"Indicates that a job will not be created when True. Workflow runtime " +"semantics will mark this True if the node is in a path that will decidedly " +"not be ran. A value of False means the node may not run." +msgstr "Indica que un trabajo no se creará cuando es sea True. La semántica del tiempo de ejecución del flujo de trabajo marcará esto como True si el nodo está en una ruta de acceso que indudablemente no se ejecutará. Un valor False significa que es posible que el nodo no se ejecute." -#: awx/main/models/unified_jobs.py:668 -msgid "The Rampart/Instance group the job was run under" -msgstr "El grupo Rampart/Instancia en el que se ejecutó la tarea" +#: awx/main/models/workflow.py:236 +msgid "" +"An identifier coresponding to the workflow job template node that this node " +"was created from." +msgstr "Un identificador que corresponde al nodo de plantilla de tarea del flujo de trabajo a partir del cual se creó este nodo." -#: awx/main/models/workflow.py:203 +#: awx/main/models/workflow.py:282 #, python-brace-format msgid "" -"Bad launch configuration starting template {template_pk} as part of workflow {workflow_pk}. Errors:\n" +"Bad launch configuration starting template {template_pk} as part of workflow " +"{workflow_pk}. Errors:\n" "{error_text}" -msgstr "" -"Plantilla de inicio de la configuración de mal lanzamiento {template_pk} como parte del flujo de trabajo {workflow_pk}. Errores:\n" +msgstr "Configuración de lanzamiento incorrecta iniciando la plantilla {template_pk} como parte del flujo de trabajo {workflow_pk}. Errores:\n" "{error_text}" -#: awx/main/models/workflow.py:393 -msgid "Field is not allowed for use in workflows." -msgstr "El campo no se permite para el uso en flujos de trabajo." +#: awx/main/models/workflow.py:595 +msgid "" +"If automatically created for a sliced job run, the job template the workflow " +"job was created from." +msgstr "Si se crea automáticamente para la ejecución de un trabajo fraccionado, la plantilla de trabajo desde la que se creó el trabajo del flujo de trabajo." + +#: awx/main/models/workflow.py:687 awx/main/models/workflow.py:721 +msgid "" +"The amount of time (in seconds) before the approval node expires and fails." +msgstr "La cantidad de tiempo (en segundos) antes de que el nodo de aprobación expire y falle." -#: awx/main/notifications/base.py:17 -#: awx/main/notifications/email_backend.py:28 +#: awx/main/models/workflow.py:725 msgid "" -"{} #{} had status {}, view details at {}\n" -"\n" -msgstr "" -"{} #{} tenía el estado {}; ver detalles en {}\n" -"\n" +"Shows when an approval node (with a timeout assigned to it) has timed out." +msgstr "Muestra cuando un nodo de aprobación (con un tiempo de espera asignado a él) ha agotado el tiempo de espera." -#: awx/main/notifications/hipchat_backend.py:48 -msgid "Error sending messages: {}" -msgstr "Error al enviar mensajes: {}" +#: awx/main/notifications/grafana_backend.py:86 +msgid "Error converting time {} or timeEnd {} to int." +msgstr "Error al convertir la hora {} o timeEnd {} en int." + +#: awx/main/notifications/grafana_backend.py:88 +msgid "Error converting time {} and/or timeEnd {} to int." +msgstr "Error al convertir la hora {} y/o timeEnd {} en int." + +#: awx/main/notifications/grafana_backend.py:102 +#: awx/main/notifications/grafana_backend.py:104 +msgid "Error sending notification grafana: {}" +msgstr "Error al enviar Grafana de notificación: {}" #: awx/main/notifications/hipchat_backend.py:50 +msgid "Error sending messages: {}" +msgstr "Error enviando mensajes: {}" + +#: awx/main/notifications/hipchat_backend.py:52 msgid "Error sending message to hipchat: {}" -msgstr "Error al enviar mensaje a hipchat: {}" +msgstr "Error enviando mensaje a hipchat: {}" -#: awx/main/notifications/irc_backend.py:54 +#: awx/main/notifications/irc_backend.py:56 msgid "Exception connecting to irc server: {}" -msgstr "Excepción al conectarse al servidor irc: {}" +msgstr "Excepción conectando al servidor de ir: {}" -#: awx/main/notifications/mattermost_backend.py:48 #: awx/main/notifications/mattermost_backend.py:50 +#: awx/main/notifications/mattermost_backend.py:52 msgid "Error sending notification mattermost: {}" msgstr "Error al enviar la notificación mattermost: {}" -#: awx/main/notifications/pagerduty_backend.py:39 +#: awx/main/notifications/pagerduty_backend.py:64 msgid "Exception connecting to PagerDuty: {}" -msgstr "Excepción al conectarse a PagerDuty: {}" +msgstr "Excepción conectando a PagerDuty: {}" -#: awx/main/notifications/pagerduty_backend.py:48 -#: awx/main/notifications/slack_backend.py:82 -#: awx/main/notifications/slack_backend.py:99 -#: awx/main/notifications/twilio_backend.py:46 +#: awx/main/notifications/pagerduty_backend.py:73 +#: awx/main/notifications/slack_backend.py:58 +#: awx/main/notifications/twilio_backend.py:48 msgid "Exception sending messages: {}" -msgstr "Excepción al enviar mensajes: {}" +msgstr "Excepción enviando mensajes: {}" -#: awx/main/notifications/rocketchat_backend.py:46 #: awx/main/notifications/rocketchat_backend.py:49 +#: awx/main/notifications/rocketchat_backend.py:52 msgid "Error sending notification rocket.chat: {}" msgstr "Error al enviar la notificación rocket.chat: {}" -#: awx/main/notifications/twilio_backend.py:36 +#: awx/main/notifications/twilio_backend.py:38 msgid "Exception connecting to Twilio: {}" -msgstr "Excepción al conectarse a Twilio: {}" +msgstr "Excepción conectando a Twilio: {}" -#: awx/main/notifications/webhook_backend.py:38 -#: awx/main/notifications/webhook_backend.py:40 +#: awx/main/notifications/webhook_backend.py:75 +#: awx/main/notifications/webhook_backend.py:77 msgid "Error sending notification webhook: {}" -msgstr "Error al enviar la notificación weebhook: {}" +msgstr "Error enviando notificación weebhook: {}" -#: awx/main/scheduler/task_manager.py:201 +#: awx/main/scheduler/dag_workflow.py:170 +#, python-brace-format msgid "" -"Job spawned from workflow could not start because it was not in the right " -"state or required manual credentials" -msgstr "" -"No se pudo iniciar el trabajo generado desde un flujo de trabajo porque no " -"tenía el estado correcto o se requerían credenciales manuales." +"No error handling path for workflow job node(s) [{node_status}]. Workflow " +"job node(s) missing unified job template and error handling path [{no_ufjt}]." +msgstr "No hay una ruta de acceso de control de errores para los nodos de tarea del flujo de trabajo [{node_status}]. Los nodos de tarea del flujo de trabajo carecen de una plantilla de tarea y una ruta de acceso de control de errores unificadas [{no_ufjt}]." -#: awx/main/scheduler/task_manager.py:205 +#: awx/main/scheduler/task_manager.py:118 +msgid "" +"Workflow Job spawned from workflow could not start because it would result " +"in recursion (spawn order, most recent first: {})" +msgstr "No se pudo iniciar el trabajo generado desde un flujo de trabajo porque generaría una recurrencia (orden de generación; el más reciente primero: {})" + +#: awx/main/scheduler/task_manager.py:126 msgid "" "Job spawned from workflow could not start because it was missing a related " "resource such as project or inventory" -msgstr "" -"No se pudo iniciar un trabajo generado desde un flujo de trabajo porque no " -"se encontraron los recursos relacionados como un proyecto o un inventario." +msgstr "Trabajo generado desde un flujo de trabajo no pudo ser iniciado porque no se encontraron los recursos relacionados como un proyecto o un inventario." + +#: awx/main/scheduler/task_manager.py:135 +msgid "" +"Job spawned from workflow could not start because it was not in the right " +"state or required manual credentials" +msgstr "Trabajo generado desde un flujo de trabajo no pudo ser iniciado porque no tenía el estado correcto o credenciales manuales eran solicitados." -#: awx/main/signals.py:632 -msgid "limit_reached" -msgstr "limit_reached" +#: awx/main/scheduler/task_manager.py:176 +msgid "No error handling paths found, marking workflow as failed" +msgstr "No se encontraron errores al manejar las rutas, el flujo de trabajo se marcó como fallado" -#: awx/main/tasks.py:305 -msgid "Ansible Tower host usage over 90%" -msgstr "Uso de hosts de Ansible Tower por encima de 90 %" +#: awx/main/scheduler/task_manager.py:508 +#, python-brace-format +msgid "The approval node {name} ({pk}) has expired after {timeout} seconds." +msgstr "El nodo de autorización {name} ({pk}) ha expirado después de {timeout} segundos." -#: awx/main/tasks.py:310 -msgid "Ansible Tower license will expire soon" -msgstr "La licencia de Ansible Tower expirará pronto" +#: awx/main/tasks.py:1049 +msgid "Invalid virtual environment selected: {}" +msgstr "Entorno virtual seleccionado no válido: {}" -#: awx/main/tasks.py:1358 +#: awx/main/tasks.py:1853 msgid "Job could not start because it does not have a valid inventory." msgstr "La tarea no se pudo iniciar por no tener un inventario válido." -#: awx/main/utils/common.py:97 +#: awx/main/tasks.py:1857 +msgid "Job could not start because it does not have a valid project." +msgstr "La tarea no se pudo iniciar por no tener un proyecto válido." + +#: awx/main/tasks.py:1862 +msgid "" +"The project revision for this job template is unknown due to a failed update." +msgstr "La revisión del proyecto para esta plantilla de trabajo es desconocida debido a una actualización fallida." + +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:470 +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:498 +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:505 +msgid "" +"No error handling path for workflow job node(s) [({},{})]. Workflow job " +"node(s) missing unified job template and error handling path []." +msgstr "No hay una ruta de acceso de control de errores para los nodos de tarea del flujo de trabajo [({},{})]. Los nodos de tarea del flujo de trabajo carecen de una plantilla de tarea y una ruta de acceso de control de errores unificadas []." + +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:480 +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:491 +msgid "" +"No error handling path for workflow job node(s) []. Workflow job node(s) " +"missing unified job template and error handling path [{}]." +msgstr "No hay una ruta de acceso de control de errores para los nodos de tarea del flujo de trabajo []. Los nodos de tarea del flujo de trabajo carecen de una plantilla de tarea y una ruta de acceso de control de errores unificadas [{}]." + +#: awx/main/utils/common.py:87 #, python-format msgid "Unable to convert \"%s\" to boolean" -msgstr "Imposible convertir \"%s\" a booleano" +msgstr "No puede convertir \"%s\" a booleano" -#: awx/main/utils/common.py:254 +#: awx/main/utils/common.py:261 #, python-format msgid "Unsupported SCM type \"%s\"" -msgstr "Tipo de SCM no soportado \"%s\"" +msgstr "Tipo de SCM \"%s\" no admitido" -#: awx/main/utils/common.py:261 awx/main/utils/common.py:273 -#: awx/main/utils/common.py:292 +#: awx/main/utils/common.py:268 awx/main/utils/common.py:280 +#: awx/main/utils/common.py:299 #, python-format msgid "Invalid %s URL" msgstr "URL %s no válida" -#: awx/main/utils/common.py:263 awx/main/utils/common.py:302 +#: awx/main/utils/common.py:270 awx/main/utils/common.py:309 #, python-format msgid "Unsupported %s URL" -msgstr "URL %s no soportada" +msgstr "URL %s no admitida" -#: awx/main/utils/common.py:304 +#: awx/main/utils/common.py:311 #, python-format msgid "Unsupported host \"%s\" for file:// URL" -msgstr "Host \"%s\" no soportado para URL file://" +msgstr "Host \"%s\" no admitido para URL de file://" -#: awx/main/utils/common.py:306 +#: awx/main/utils/common.py:313 #, python-format msgid "Host is required for %s URL" -msgstr "Servidor es obligatorio para URL %s" +msgstr "El host es obligatorio para URL %s" -#: awx/main/utils/common.py:324 +#: awx/main/utils/common.py:331 #, python-format msgid "Username must be \"git\" for SSH access to %s." -msgstr "Usuario debe ser \"git\" para acceso SSH a %s." +msgstr "El nombre de usuario debe ser \"git\" para el acceso de SSH a %s." -#: awx/main/utils/common.py:330 +#: awx/main/utils/common.py:337 #, python-format msgid "Username must be \"hg\" for SSH access to %s." -msgstr "Usuario debe ser \"hg\" para acceso SSH a %s." +msgstr "El nombre de usuario debe ser \"hg\" para el acceso de SSH a %s." -#: awx/main/utils/common.py:611 +#: awx/main/utils/common.py:668 #, python-brace-format msgid "Input type `{data_type}` is not a dictionary" msgstr "El tipo de entrada `{data_type}` no está en el diccionario" -#: awx/main/utils/common.py:644 +#: awx/main/utils/common.py:701 #, python-brace-format msgid "Variables not compatible with JSON standard (error: {json_error})" -msgstr "Variables no compatibles con el estándar JSON (error: {json_error})" +msgstr "Variables no compatibles con el estándar de JSON (error: {json_error})" -#: awx/main/utils/common.py:650 +#: awx/main/utils/common.py:707 #, python-brace-format msgid "" "Cannot parse as JSON (error: {json_error}) or YAML (error: {yaml_error})." -msgstr "" -"No se puede analizar como JSON (error: {json_error}) o YAML (error: " -"{yaml_error})." +msgstr "No se puede analizar como JSON (error: {json_error}) o YAML (error: {yaml_error})." #: awx/main/validators.py:67 #, python-format msgid "Invalid certificate or key: %s..." -msgstr "Clave o certificado no válido: %s…" +msgstr "Clave o certificado no válido: %s..." #: awx/main/validators.py:83 #, python-format msgid "Invalid private key: unsupported type \"%s\"" -msgstr "Clave privada no válida: tipo no soportado \"%s\"" +msgstr "Clave privada no válida: tipo \"%s\" no admitido" #: awx/main/validators.py:87 #, python-format msgid "Unsupported PEM object type: \"%s\"" -msgstr "Tipo de objeto PEM no soportado: \"%s\"" +msgstr "Tipo de objeto PEM no admitido: \"%s\"" #: awx/main/validators.py:112 msgid "Invalid base64-encoded data" -msgstr "Datos codificados en base64 no válidos" +msgstr "Datos codificados en base64 inválidos" -#: awx/main/validators.py:131 +#: awx/main/validators.py:133 msgid "Exactly one private key is required." -msgstr "Se requiere exactamente una clave privada." +msgstr "Exactamente una clave privada es necesaria." -#: awx/main/validators.py:133 +#: awx/main/validators.py:135 msgid "At least one private key is required." -msgstr "Se requiere al menos una clave privada." +msgstr "Al menos una clave privada es necesaria." -#: awx/main/validators.py:135 +#: awx/main/validators.py:137 #, python-format msgid "" -"At least %(min_keys)d private keys are required, only %(key_count)d " -"provided." -msgstr "" -"Se requieren al menos %(min_keys)d claves privadas; solo se proporcionaron " -"%(key_count)d." +"At least %(min_keys)d private keys are required, only %(key_count)d provided." +msgstr "Al menos %(min_keys)d claves privadas son necesarias, solo se proporciona %(key_count)d." -#: awx/main/validators.py:138 +#: awx/main/validators.py:140 #, python-format msgid "Only one private key is allowed, %(key_count)d provided." -msgstr "Solo se permite una clave privada; se proporcionaron %(key_count)d." +msgstr "Solo se permite una clave privada, se proporcionó %(key_count)d." -#: awx/main/validators.py:140 +#: awx/main/validators.py:142 #, python-format msgid "" "No more than %(max_keys)d private keys are allowed, %(key_count)d provided." -msgstr "" -"No se permiten más de %(max_keys)d claves privadas; se proporcionaron " -"%(key_count)d." +msgstr "No se permiten más de %(max_keys)d claves privadas, se proporcionó %(key_count)d." -#: awx/main/validators.py:145 +#: awx/main/validators.py:147 msgid "Exactly one certificate is required." -msgstr "Se requiere exactamente un certificado." +msgstr "Exactamente un certificado es necesario." -#: awx/main/validators.py:147 +#: awx/main/validators.py:149 msgid "At least one certificate is required." -msgstr "Se requiere al menos un certificado." +msgstr "Al menos un certificado es necesario." -#: awx/main/validators.py:149 +#: awx/main/validators.py:151 #, python-format msgid "" "At least %(min_certs)d certificates are required, only %(cert_count)d " "provided." -msgstr "" -"Se requieren al menos %(min_certs)d certificados; solo se proporcionaron " -"%(cert_count)d." +msgstr "Al menos %(min_certs)d certificados son necesarios, solo se proporcionó %(cert_count)d." -#: awx/main/validators.py:152 +#: awx/main/validators.py:154 #, python-format msgid "Only one certificate is allowed, %(cert_count)d provided." -msgstr "Solo se permite un certificado; se proporcionaron %(cert_count)d." +msgstr "Solo se permite un certificado, se proporcionó %(cert_count)d." -#: awx/main/validators.py:154 +#: awx/main/validators.py:156 #, python-format msgid "" -"No more than %(max_certs)d certificates are allowed, %(cert_count)d " -"provided." -msgstr "" -"No se permiten más de %(max_certs)d certificados; se proporcionaron " -"%(cert_count)d." +"No more than %(max_certs)d certificates are allowed, %(cert_count)d provided." +msgstr "No se permiten más de %(max_certs)d certificados, se proporcionó %(cert_count)d." -#: awx/main/views.py:23 +#: awx/main/views.py:30 msgid "API Error" msgstr "Error de API" -#: awx/main/views.py:61 +#: awx/main/views.py:65 msgid "Bad Request" msgstr "Solicitud incorrecta" -#: awx/main/views.py:62 +#: awx/main/views.py:66 msgid "The request could not be understood by the server." -msgstr "El servidor no puede entender la petición." +msgstr "La petición no puede ser entendida por el servidor." -#: awx/main/views.py:69 +#: awx/main/views.py:73 msgid "Forbidden" msgstr "Prohibido" -#: awx/main/views.py:70 +#: awx/main/views.py:74 msgid "You don't have permission to access the requested resource." msgstr "Usted no tiene permisos para acceder al recurso solicitado." -#: awx/main/views.py:77 +#: awx/main/views.py:81 msgid "Not Found" msgstr "No encontrado" -#: awx/main/views.py:78 +#: awx/main/views.py:82 msgid "The requested resource could not be found." -msgstr "No se pudo encontrar el recurso solicitado." +msgstr "El recurso solicitado no pudo ser encontrado." -#: awx/main/views.py:85 +#: awx/main/views.py:89 msgid "Server Error" msgstr "Error de servidor" -#: awx/main/views.py:86 +#: awx/main/views.py:90 msgid "A server error has occurred." -msgstr "Se produjo un error de servidor." +msgstr "Un error en el servidor ha ocurrido." -#: awx/settings/defaults.py:725 +#: awx/settings/defaults.py:683 msgid "US East (Northern Virginia)" -msgstr "Este de EE. UU. (Virginia del norte)" +msgstr "Este de EE.UU. (Virginia del norte)" -#: awx/settings/defaults.py:726 +#: awx/settings/defaults.py:684 msgid "US East (Ohio)" -msgstr "Este de EE. UU. (Ohio)" +msgstr "Este de EE.UU. (Ohio)" -#: awx/settings/defaults.py:727 +#: awx/settings/defaults.py:685 msgid "US West (Oregon)" -msgstr "Oeste de EE. UU. (Oregón)" +msgstr "Oeste de EE.UU. (Oregón)" -#: awx/settings/defaults.py:728 +#: awx/settings/defaults.py:686 msgid "US West (Northern California)" -msgstr "Oeste de EE. UU (California del norte)" +msgstr "Oeste de EE.UU (California del norte)" -#: awx/settings/defaults.py:729 +#: awx/settings/defaults.py:687 msgid "Canada (Central)" -msgstr "Canadá (Central)" +msgstr "Canada (Central)" -#: awx/settings/defaults.py:730 +#: awx/settings/defaults.py:688 msgid "EU (Frankfurt)" msgstr "UE (Fráncfort)" -#: awx/settings/defaults.py:731 +#: awx/settings/defaults.py:689 msgid "EU (Ireland)" msgstr "UE (Irlanda)" -#: awx/settings/defaults.py:732 +#: awx/settings/defaults.py:690 msgid "EU (London)" msgstr "UE (Londres)" -#: awx/settings/defaults.py:733 +#: awx/settings/defaults.py:691 msgid "Asia Pacific (Singapore)" msgstr "Asia Pacífico (Singapur)" -#: awx/settings/defaults.py:734 +#: awx/settings/defaults.py:692 msgid "Asia Pacific (Sydney)" msgstr "Asia Pacífico (Sídney)" -#: awx/settings/defaults.py:735 +#: awx/settings/defaults.py:693 msgid "Asia Pacific (Tokyo)" msgstr "Asia Pacífico (Tokio)" -#: awx/settings/defaults.py:736 +#: awx/settings/defaults.py:694 msgid "Asia Pacific (Seoul)" msgstr "Asia Pacífico (Seúl)" -#: awx/settings/defaults.py:737 +#: awx/settings/defaults.py:695 msgid "Asia Pacific (Mumbai)" msgstr "Asia Pacífico (Bombay)" -#: awx/settings/defaults.py:738 +#: awx/settings/defaults.py:696 msgid "South America (Sao Paulo)" msgstr "América del sur (São Paulo)" -#: awx/settings/defaults.py:739 +#: awx/settings/defaults.py:697 msgid "US West (GovCloud)" -msgstr "Oeste de EE. UU. (GovCloud)" +msgstr "Oeste de EE.UU. (GovCloud)" -#: awx/settings/defaults.py:740 +#: awx/settings/defaults.py:698 msgid "China (Beijing)" msgstr "China (Pekín)" -#: awx/settings/defaults.py:789 +#: awx/settings/defaults.py:747 msgid "US East 1 (B)" msgstr "Este de EE. UU. 1 (B)" -#: awx/settings/defaults.py:790 +#: awx/settings/defaults.py:748 msgid "US East 1 (C)" msgstr "Este de EE. UU. 1 (C)" -#: awx/settings/defaults.py:791 +#: awx/settings/defaults.py:749 msgid "US East 1 (D)" msgstr "Este de EE. UU. 1 (D)" -#: awx/settings/defaults.py:792 +#: awx/settings/defaults.py:750 msgid "US East 4 (A)" msgstr "Este de EE. UU. 4 (A)" -#: awx/settings/defaults.py:793 +#: awx/settings/defaults.py:751 msgid "US East 4 (B)" msgstr "Este de EE. UU. 4 (B)" -#: awx/settings/defaults.py:794 +#: awx/settings/defaults.py:752 msgid "US East 4 (C)" msgstr "Este de EE. UU. 4 (C)" -#: awx/settings/defaults.py:795 +#: awx/settings/defaults.py:753 msgid "US Central (A)" -msgstr "EE. UU. Central (A)" +msgstr "EE.UU. Central (A)" -#: awx/settings/defaults.py:796 +#: awx/settings/defaults.py:754 msgid "US Central (B)" -msgstr "EE. UU. Central (B)" +msgstr "EE.UU. Central (B)" -#: awx/settings/defaults.py:797 +#: awx/settings/defaults.py:755 msgid "US Central (C)" -msgstr "EE. UU. Central (C)" +msgstr "EE.UU. Central (C)" -#: awx/settings/defaults.py:798 +#: awx/settings/defaults.py:756 msgid "US Central (F)" -msgstr "EE. UU. Central (F)" +msgstr "EE.UU. Central (F)" -#: awx/settings/defaults.py:799 +#: awx/settings/defaults.py:757 msgid "US West (A)" msgstr "Oeste de EE. UU. (A)" -#: awx/settings/defaults.py:800 +#: awx/settings/defaults.py:758 msgid "US West (B)" msgstr "Oeste de EE. UU. (B)" -#: awx/settings/defaults.py:801 +#: awx/settings/defaults.py:759 msgid "US West (C)" msgstr "Oeste de EE. UU. (C)" -#: awx/settings/defaults.py:802 +#: awx/settings/defaults.py:760 msgid "Europe West 1 (B)" msgstr "Oeste de Europa 1 (B)" -#: awx/settings/defaults.py:803 +#: awx/settings/defaults.py:761 msgid "Europe West 1 (C)" msgstr "Oeste de Europa 1 (C)" -#: awx/settings/defaults.py:804 +#: awx/settings/defaults.py:762 msgid "Europe West 1 (D)" msgstr "Oeste de Europa 1 (D)" -#: awx/settings/defaults.py:805 +#: awx/settings/defaults.py:763 msgid "Europe West 2 (A)" msgstr "Oeste de Europa 2 (A)" -#: awx/settings/defaults.py:806 +#: awx/settings/defaults.py:764 msgid "Europe West 2 (B)" msgstr "Oeste de Europa 2 (B)" -#: awx/settings/defaults.py:807 +#: awx/settings/defaults.py:765 msgid "Europe West 2 (C)" msgstr "Oeste de Europa 2 (C)" -#: awx/settings/defaults.py:808 +#: awx/settings/defaults.py:766 msgid "Asia East (A)" msgstr "Este de Asia (A)" -#: awx/settings/defaults.py:809 +#: awx/settings/defaults.py:767 msgid "Asia East (B)" msgstr "Este de Asia (B)" -#: awx/settings/defaults.py:810 +#: awx/settings/defaults.py:768 msgid "Asia East (C)" msgstr "Este de Asia (C)" -#: awx/settings/defaults.py:811 +#: awx/settings/defaults.py:769 msgid "Asia Southeast (A)" msgstr "Sudeste de Asia (A)" -#: awx/settings/defaults.py:812 +#: awx/settings/defaults.py:770 msgid "Asia Southeast (B)" msgstr "Sudeste de Asia (B)" -#: awx/settings/defaults.py:813 +#: awx/settings/defaults.py:771 msgid "Asia Northeast (A)" msgstr "Noreste de Asia (A)" -#: awx/settings/defaults.py:814 +#: awx/settings/defaults.py:772 msgid "Asia Northeast (B)" msgstr "Noreste de Asia (B)" -#: awx/settings/defaults.py:815 +#: awx/settings/defaults.py:773 msgid "Asia Northeast (C)" msgstr "Noreste de Asia (C)" -#: awx/settings/defaults.py:816 +#: awx/settings/defaults.py:774 msgid "Australia Southeast (A)" msgstr "Sudeste de Australia (A)" -#: awx/settings/defaults.py:817 +#: awx/settings/defaults.py:775 msgid "Australia Southeast (B)" msgstr "Sudeste de Australia (B)" -#: awx/settings/defaults.py:818 +#: awx/settings/defaults.py:776 msgid "Australia Southeast (C)" msgstr "Sudeste de Australia (C)" -#: awx/settings/defaults.py:840 +#: awx/settings/defaults.py:798 msgid "US East" -msgstr "Este de EE. UU." +msgstr "Este de EE.UU." -#: awx/settings/defaults.py:841 +#: awx/settings/defaults.py:799 msgid "US East 2" -msgstr "Este de EE. UU. 2" +msgstr "Este de EE.UU. 2" -#: awx/settings/defaults.py:842 +#: awx/settings/defaults.py:800 msgid "US Central" -msgstr "EE. UU. Central" +msgstr "EE.UU. Central" -#: awx/settings/defaults.py:843 +#: awx/settings/defaults.py:801 msgid "US North Central" -msgstr "Norte-Centro de EE. UU." +msgstr "Norte-centro de EE.UU." -#: awx/settings/defaults.py:844 +#: awx/settings/defaults.py:802 msgid "US South Central" -msgstr "Sur-Centro de EE. UU." +msgstr "Sur-Centro de EE.UU." -#: awx/settings/defaults.py:845 +#: awx/settings/defaults.py:803 msgid "US West Central" msgstr "Oeste central de EE. UU." -#: awx/settings/defaults.py:846 +#: awx/settings/defaults.py:804 msgid "US West" -msgstr "Oeste de EE. UU." +msgstr "Oeste de EE.UU." -#: awx/settings/defaults.py:847 +#: awx/settings/defaults.py:805 msgid "US West 2" msgstr "Oeste de EE. UU. 2" -#: awx/settings/defaults.py:848 +#: awx/settings/defaults.py:806 msgid "Canada East" msgstr "Este de Canadá" -#: awx/settings/defaults.py:849 +#: awx/settings/defaults.py:807 msgid "Canada Central" msgstr "Canadá Central" -#: awx/settings/defaults.py:850 +#: awx/settings/defaults.py:808 msgid "Brazil South" msgstr "Sur de Brasil" -#: awx/settings/defaults.py:851 +#: awx/settings/defaults.py:809 msgid "Europe North" msgstr "Norte de Europa" -#: awx/settings/defaults.py:852 +#: awx/settings/defaults.py:810 msgid "Europe West" msgstr "Oeste de Europa" -#: awx/settings/defaults.py:853 +#: awx/settings/defaults.py:811 msgid "UK West" msgstr "Oeste del Reino Unido" -#: awx/settings/defaults.py:854 +#: awx/settings/defaults.py:812 msgid "UK South" msgstr "Sur del Reino Unido" -#: awx/settings/defaults.py:855 +#: awx/settings/defaults.py:813 msgid "Asia East" msgstr "Este de Asia" -#: awx/settings/defaults.py:856 +#: awx/settings/defaults.py:814 msgid "Asia Southeast" msgstr "Sudeste de Asia" -#: awx/settings/defaults.py:857 +#: awx/settings/defaults.py:815 msgid "Australia East" msgstr "Este de Australia" -#: awx/settings/defaults.py:858 +#: awx/settings/defaults.py:816 msgid "Australia Southeast" msgstr "Sudeste de Australia" -#: awx/settings/defaults.py:859 +#: awx/settings/defaults.py:817 msgid "India West" msgstr "Oeste de India" -#: awx/settings/defaults.py:860 +#: awx/settings/defaults.py:818 msgid "India South" msgstr "Sur de India" -#: awx/settings/defaults.py:861 +#: awx/settings/defaults.py:819 msgid "Japan East" msgstr "Este de Japón" -#: awx/settings/defaults.py:862 +#: awx/settings/defaults.py:820 msgid "Japan West" msgstr "Oeste de Japón" -#: awx/settings/defaults.py:863 +#: awx/settings/defaults.py:821 msgid "Korea Central" msgstr "Corea central" -#: awx/settings/defaults.py:864 +#: awx/settings/defaults.py:822 msgid "Korea South" msgstr "Sur de Corea" @@ -4927,247 +5258,191 @@ msgstr "Sur de Corea" msgid "Single Sign-On" msgstr "Inicio de sesión único (SSO)" -#: awx/sso/conf.py:30 +#: awx/sso/conf.py:41 msgid "" -"Mapping to organization admins/users from social auth accounts. This setting\n" -"controls which users are placed into which Tower organizations based on their\n" -"username and email address. Configuration details are available in the Ansible\n" +"Mapping to organization admins/users from social auth accounts. This " +"setting\n" +"controls which users are placed into which Tower organizations based on " +"their\n" +"username and email address. Configuration details are available in the " +"Ansible\n" "Tower documentation." -msgstr "" -"Asignación a administradores o usuarios de la organización desde cuentas de " -"autorización social. Esta configuración controla qué usuarios se ubican en " -"qué organizaciones de Tower en función de su nombre de usuario y dirección " -"de correo electrónico. Detalles de configuración disponibles en la " -"documentación de Ansible Tower." +msgstr "Asignación a administradores o usuarios de la organización desde cuentas de autorización social. Esta configuración controla qué usuarios se ubican en qué organizaciones de Tower en función de su nombre de usuario y dirección de correo electrónico. Detalles de configuración disponibles en la documentación de Ansible Tower." -#: awx/sso/conf.py:55 +#: awx/sso/conf.py:67 msgid "" "Mapping of team members (users) from social auth accounts. Configuration\n" "details are available in Tower documentation." -msgstr "" -"Asignación de miembros del equipo (usuarios) desde cuentas de autenticación social. Los detalles\n" +msgstr "Asignación de miembros del equipo (usuarios) desde cuentas de autenticación social. Los detalles\n" "de la configuración están disponibles en la documentación de Tower." -#: awx/sso/conf.py:80 +#: awx/sso/conf.py:92 msgid "Authentication Backends" -msgstr "Backends para autenticación" +msgstr "Backends para autentificación" -#: awx/sso/conf.py:81 +#: awx/sso/conf.py:93 msgid "" "List of authentication backends that are enabled based on license features " "and other authentication settings." -msgstr "" -"Listado de backends de autenticación que están habilitados basados en " -"funcionalidades de la licencia y otros ajustes de autenticación." +msgstr "Listado de backends de autentificación que están habilitados basados en funcionalidades de la licencia y otros ajustes de autentificación." -#: awx/sso/conf.py:94 +#: awx/sso/conf.py:106 msgid "Social Auth Organization Map" -msgstr "Autenticación social - Mapa de organización" +msgstr "Autentificación social - Mapa de organización" -#: awx/sso/conf.py:106 +#: awx/sso/conf.py:118 msgid "Social Auth Team Map" -msgstr "Autenticación social - Mapa de equipos" +msgstr "Autentificación social - Mapa de equipos" -#: awx/sso/conf.py:118 +#: awx/sso/conf.py:130 msgid "Social Auth User Fields" -msgstr "Autenticación social - Campos de usuario" +msgstr "Autentificación social - Campos usuario" -#: awx/sso/conf.py:119 +#: awx/sso/conf.py:131 msgid "" -"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." -msgstr "" -"Cuando se establece una lista vacía `[]`, esta configuración impide que se " -"puedan crear nuevos usuarios. Solo los usuarios que han iniciado sesión " -"previamente usando autenticación social o que tengan una cuenta de usuario " -"que corresponda con la dirección de correo electrónico podrán iniciar " -"sesión." +"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." +msgstr "Cuando se establece una lista vacía `[]`, esta configuración previene que nuevos usuarios puedan ser creados. Sólo usuarios que previamente han iniciado sesión usando autentificación social o tengan una cuenta de usuario que corresponda con la dirección de correcto podrán iniciar sesión." -#: awx/sso/conf.py:141 +#: awx/sso/conf.py:153 msgid "LDAP Server URI" -msgstr "URI de servidor LDAP" +msgstr "URI servidor LDAP" -#: awx/sso/conf.py:142 +#: awx/sso/conf.py:154 msgid "" "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 " +"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." -msgstr "" -"URI para conectarse a un servidor LDAP. Por ejemplo, " -"\"ldap://ldap.ejemplo.com:389\" (no SSL) o \"ldaps://ldap.ejemplo.com:636\" " -"(SSL). Se pueden especificar varios servidores LDAP separados por espacios o" -" comas. La autenticación LDAP está deshabilitada si este parámetro está " -"vacío." - -#: awx/sso/conf.py:146 awx/sso/conf.py:162 awx/sso/conf.py:174 -#: awx/sso/conf.py:186 awx/sso/conf.py:202 awx/sso/conf.py:222 -#: awx/sso/conf.py:244 awx/sso/conf.py:259 awx/sso/conf.py:277 -#: awx/sso/conf.py:294 awx/sso/conf.py:306 awx/sso/conf.py:332 -#: awx/sso/conf.py:348 awx/sso/conf.py:362 awx/sso/conf.py:380 -#: awx/sso/conf.py:406 +msgstr "URI para conectar a un servidor LDAP. Por ejemplo \"ldap://ldap.ejemplo.com:389\" (no SSL) or \"ldaps://ldap.ejemplo.com:636\" (SSL). Varios servidores LDAP pueden ser especificados separados por espacios o comandos. La autentificación LDAP está deshabilitado si este parámetro está vacío." + +#: awx/sso/conf.py:158 awx/sso/conf.py:173 awx/sso/conf.py:184 +#: awx/sso/conf.py:195 awx/sso/conf.py:210 awx/sso/conf.py:229 +#: awx/sso/conf.py:250 awx/sso/conf.py:264 awx/sso/conf.py:281 +#: awx/sso/conf.py:297 awx/sso/conf.py:308 awx/sso/conf.py:333 +#: awx/sso/conf.py:348 awx/sso/conf.py:361 awx/sso/conf.py:378 +#: awx/sso/conf.py:404 msgid "LDAP" msgstr "LDAP" -#: awx/sso/conf.py:158 +#: awx/sso/conf.py:169 msgid "LDAP Bind DN" msgstr "DN para enlazar con LDAP" -#: awx/sso/conf.py:159 +#: awx/sso/conf.py:170 msgid "" "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 Ansible Tower documentation for example syntax." -msgstr "" -"DN (Distinguished Name) del usuario para vincular todas las búsquedas. Esta " -"es la cuenta de usuario del sistema que utilizaremos para iniciar sesión " -"para consultar a LDAP y obtener otro tipo de información sobre el usuario. " -"Consulte la documentación de Ansible Tower para acceder a ejemplos de " -"sintaxis." +msgstr "El nombre distintivo (Distinguished Name, DN) del usuario para vincular todas las búsquedas. Esta es la cuenta de usuario del sistema que utilizaremos para iniciar sesión con el fin de consultar a LDAP y obtener otro tipo de información sobre el usuario. Consulte la documentación de Ansible Tower para acceder a ejemplos de sintaxis." -#: awx/sso/conf.py:172 +#: awx/sso/conf.py:182 msgid "LDAP Bind Password" msgstr "Contraseña para enlazar con LDAP" -#: awx/sso/conf.py:173 +#: awx/sso/conf.py:183 msgid "Password used to bind LDAP user account." msgstr "Contraseña usada para enlazar a LDAP con la cuenta de usuario." -#: awx/sso/conf.py:184 +#: awx/sso/conf.py:193 msgid "LDAP Start TLS" msgstr "LDAP - Iniciar TLS" -#: awx/sso/conf.py:185 +#: awx/sso/conf.py:194 msgid "Whether to enable TLS when the LDAP connection is not using SSL." -msgstr "Para activar o no TLS cuando la conexión LDAP no utilice SSL." +msgstr "Para activar o no TLS cuando la conexión LDAP no utilice SSL" -#: awx/sso/conf.py:195 +#: awx/sso/conf.py:203 msgid "LDAP Connection Options" msgstr "Opciones de conexión a LDAP" -#: awx/sso/conf.py:196 +#: awx/sso/conf.py:204 msgid "" "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." -msgstr "" -"Opciones adicionales por establecer para la conexión LDAP. Las referencias " -"LDAP están deshabilitadas por defecto (para prevenir que ciertas consultas " -"LDAP se bloqueen con AD). Los nombres de las opciones deben ser cadenas de " -"texto (p. ej., \"OPT_REFERRALS\"). Consulte https://www.python-" -"ldap.org/doc/html/ldap.html#options para conocer posibles opciones y valores" -" que se pueden establecer." - -#: awx/sso/conf.py:215 +"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." +msgstr "Opciones adicionales a establecer para la conexión LDAP. Referenciadores LDAP están deshabilitados por defecto (para prevenir que ciertas consultas LDAP se bloqueen con AD). Los nombres de las opciones deben ser cadenas de texto (p.e. \"OPT_REFERRALS\"). Acuda a https://www.python-ldap.org/doc/html/ldap.html#options para posibles opciones y valores que pueden ser establecidos." + +#: awx/sso/conf.py:222 msgid "LDAP User Search" msgstr "Búsqueda de usuarios LDAP" -#: awx/sso/conf.py:216 +#: awx/sso/conf.py:223 msgid "" "LDAP search query to find users. Any user that matches the given pattern " -"will be able to login to Tower. The user should also be mapped into a Tower" -" organization (as defined in the AUTH_LDAP_ORGANIZATION_MAP setting). If " +"will be able to login to Tower. The user should also be mapped into a Tower " +"organization (as defined in the AUTH_LDAP_ORGANIZATION_MAP setting). If " "multiple search queries need to be supported use of \"LDAPUnion\" is " "possible. See Tower documentation for details." -msgstr "" -"Búsqueda en LDAP para encontrar usuarios. Cualquier usuario que se ajuste a " -"un patrón determinado podrá iniciar sesión en Tower. El usuario también " -"debería asignarse en una organización de Tower (conforme se define en la " -"configuración AUTH_LDAP_ORGANIZATION_MAP). Si es necesario respaldar varias " -"búsquedas, es posible utilizar \"LDAPUnion\". Consulte la documentación de " -"Tower para acceder a información detallada." - -#: awx/sso/conf.py:238 +msgstr "Búsqueda en LDAP para encontrar usuarios. Cualquier usuario que se ajuste a un patrón determinado podrá iniciar sesión en Tower. El usuario también debería asignarse en una organización de Tower (conforme se define en la configuración AUTH_LDAP_ORGANIZATION_MAP). Si es necesario respaldar varias búsquedas, es posible utilizar \"LDAPUnion\". Consulte la documentación de Tower para acceder a información detallada." + +#: awx/sso/conf.py:244 msgid "LDAP User DN Template" msgstr "Plantilla de DN para el usuario LDAP" -#: awx/sso/conf.py:239 +#: awx/sso/conf.py:245 msgid "" "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." -msgstr "" -"Alternativa a la búsqueda de usuarios, en caso de que los DN de los usuarios" -" tengan todos el mismo formato. Este enfoque es más efectivo para buscar " -"usuarios que las búsquedas, en caso de poder utilizarse en el entorno de su " -"organización. Si esta configuración tiene un valor, se utilizará en lugar de" -" AUTH_LDAP_USER_SEARCH." +msgstr "Alternativa a la búsqueda de usuarios, en caso de que los DN de los usuarios tengan todos el mismo formato. Este enfoque es más efectivo para buscar usuarios que las búsquedas, en caso de poder utilizarse en el entorno de su organización. Si esta configuración tiene un valor, se utilizará en lugar de AUTH_LDAP_USER_SEARCH." -#: awx/sso/conf.py:254 +#: awx/sso/conf.py:259 msgid "LDAP User Attribute Map" msgstr "Mapa de atributos de usuario LDAP" -#: awx/sso/conf.py:255 +#: awx/sso/conf.py:260 msgid "" "Mapping of LDAP user schema to Tower 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 Ansible Tower " "documentation for additional details." -msgstr "" -"Asignación de esquemas de usuarios de LDAP con los atributos de usuario API " -"de Tower. La configuración predeterminada es válida para ActiveDirectory. " -"Sin embargo, los usuarios con otras configuraciones de LDAP tal vez " -"necesiten modificar los valores. Consulte la documentación de Ansible Tower " -"para acceder a información detallada." +msgstr "Asignación de esquemas de usuarios de LDAP a los atributos de usuario API de Tower. La configuración predeterminada es válida para ActiveDirectory. Sin embargo, los usuarios con otras configuraciones de LDAP tal vez necesiten modificar los valores. Consulte la documentación de Ansible Tower para acceder a información detallada." -#: awx/sso/conf.py:273 +#: awx/sso/conf.py:277 msgid "LDAP Group Search" msgstr "Búsqueda de grupos LDAP" -#: awx/sso/conf.py:274 +#: awx/sso/conf.py:278 msgid "" "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." -msgstr "" -"Se asigna a los usuarios en las organizaciones en función de su membresía en" -" los grupos LDAP. Esta configuración define la búsqueda de LDAP para " -"encontrar grupos. A diferencia de la búsqueda de usuarios, la búsqueda de " -"grupos no es compatible con LDAPSearchUnion." +msgstr "Se asigna a los usuarios en las organizaciones en función de su membresía en los grupos LDAP. Esta configuración define la búsqueda de LDAP para encontrar grupos. A diferencia de la búsqueda de usuarios, la búsqueda de grupos no es compatible con LDAPSearchUnion." -#: awx/sso/conf.py:290 +#: awx/sso/conf.py:293 msgid "LDAP Group Type" msgstr "Tipo de grupo LDAP" -#: awx/sso/conf.py:291 +#: awx/sso/conf.py:294 msgid "" -"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" -msgstr "" -"Puede tener que cambiarse el tipo de grupo en función del tipo de servidor " -"de LDAP. Los valores se enumeran en: https://django-auth-" -"ldap.readthedocs.io/en/stable/groups.html#types-of-groups" +"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" +msgstr "Puede tener que cambiarse el tipo de grupo en función del tipo de servidor de LDAP. Los valores se enumeran en: https://django-auth-ldap.readthedocs.io/en/stable/groups.html#types-of-groups" -#: awx/sso/conf.py:304 +#: awx/sso/conf.py:306 msgid "LDAP Group Type Parameters" msgstr "Parámetros del tipo de grupo LDAP" -#: awx/sso/conf.py:305 +#: awx/sso/conf.py:307 msgid "Key value parameters to send the chosen group type init method." -msgstr "" -"Parámetros de valor clave para enviar al método de inicio del tipo de grupo " -"elegido." +msgstr "Parámetros de valor clave para enviar el método de inicio del tipo de grupo elegido." -#: awx/sso/conf.py:327 +#: awx/sso/conf.py:328 msgid "LDAP Require Group" msgstr "Grupo LDAP requerido" -#: awx/sso/conf.py:328 +#: awx/sso/conf.py:329 msgid "" "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 via Tower. Only one require group is supported." -msgstr "" -"Grupo DN necesario para iniciar sesión. Si se especifica, el usuario debe " -"ser miembro de este grupo para iniciar sesión mediante LDAP. Si no se " -"establece, cualquiera en LDAP que corresponda con la búsqueda de usuario " -"será capaz de iniciar sesión en Tower. No es posible especificar varios " -"grupos." +msgstr "Grupo DN necesario para iniciar sesión. Si se especifica, el usuario debe ser miembro de este grupo para iniciar sesión usando LDAP. SI no se establece, cualquiera en LDAP que corresponda con la búsqueda de usuario será capaz de iniciar sesión en Tower. No es posible especificar varios grupos." #: awx/sso/conf.py:344 msgid "LDAP Deny Group" @@ -5177,711 +5452,583 @@ msgstr "Grupo LDAP no permitido" msgid "" "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." -msgstr "" -"Grupo DN no permitido para iniciar sesión. Si se especifica, el usuario no " -"podrá iniciar sesión si es miembro de este grupo. Solo un grupo no permitido" -" está soportado." +msgstr "Grupo DN no permitido para iniciar sesión. SI se especifica, el usuario no podrá iniciar sesión si es miembro de este grupo. Sólo un grupo no permitido está soportado." -#: awx/sso/conf.py:358 +#: awx/sso/conf.py:357 msgid "LDAP User Flags By Group" msgstr "Indicadores de usuario LDAP por grupo" -#: awx/sso/conf.py:359 +#: awx/sso/conf.py:358 msgid "" "Retrieve users from a given group. At this time, superuser and system " "auditors are the only groups supported. Refer to the Ansible Tower " "documentation for more detail." -msgstr "" -"Recuperar usuarios de un grupo determinado. En este momento, el superusuario" -" y los auditores del sistema son los únicos grupos que se admiten. Consulte " -"la documentación de Ansible Tower para obtener información detallada." +msgstr "Recuperar usuarios de un grupo determinado. En este momento, el superusuario y los auditores del sistema son los únicos grupos que se admiten. Consulte la documentación de Ansible Tower para obtener información detallada." -#: awx/sso/conf.py:375 +#: awx/sso/conf.py:373 msgid "LDAP Organization Map" msgstr "Mapa de organización LDAP" -#: awx/sso/conf.py:376 +#: awx/sso/conf.py:374 msgid "" "Mapping between organization admins/users and LDAP groups. This controls " -"which users are placed into which Tower organizations relative to their LDAP" -" group memberships. Configuration details are available in the Ansible Tower" -" documentation." -msgstr "" -"Mapear entre administradores/usuarios de las organizaciones y grupos de " -"LDAP. De esta manera, se controla qué usuarios se ubican en qué " -"organizaciones de Tower en función de sus membresías en grupos de LDAP. " -"Detalles de configuración disponibles en la documentación de Ansible Tower." +"which users are placed into which Tower organizations relative to their LDAP " +"group memberships. Configuration details are available in the Ansible Tower " +"documentation." +msgstr "Mapear entre administradores/usuarios de las organizaciones y grupos de LDAP. De esta manera, se controla qué usuarios se ubican en qué organizaciones de Tower en función de sus membresías en grupos de LDAP. Detalles de configuración disponibles en la documentación de Ansible Tower." -#: awx/sso/conf.py:403 +#: awx/sso/conf.py:401 msgid "LDAP Team Map" msgstr "Mapa de equipos LDAP" -#: awx/sso/conf.py:404 +#: awx/sso/conf.py:402 msgid "" "Mapping between team members (users) and LDAP groups. Configuration details " "are available in the Ansible Tower documentation." -msgstr "" -"Mapear entre los miembros de equipos (usuarios) y grupos de LDAP. Detalles " -"de configuración disponibles en la documentación de Ansible Tower." +msgstr "Mapear entre los miembros de equipos (usuarios) y grupos de LDAP. Detalles de configuración disponibles en la documentación de Ansible Tower." -#: awx/sso/conf.py:440 +#: awx/sso/conf.py:437 msgid "RADIUS Server" msgstr "Servidor RADIUS" -#: awx/sso/conf.py:441 +#: awx/sso/conf.py:438 msgid "" "Hostname/IP of RADIUS server. RADIUS authentication is disabled if this " "setting is empty." -msgstr "" -"Hostname/IP del servidor RADIUS. La autenticación de RADIUS se desactiva si " -"esta configuración está vacía." +msgstr "Nombre de host/IP del servidor RADIUS. La autenticación de RADIUS se deshabilita si esta configuración está vacía." -#: awx/sso/conf.py:443 awx/sso/conf.py:457 awx/sso/conf.py:469 +#: awx/sso/conf.py:440 awx/sso/conf.py:453 awx/sso/conf.py:464 #: awx/sso/models.py:14 msgid "RADIUS" msgstr "RADIUS" -#: awx/sso/conf.py:455 +#: awx/sso/conf.py:451 msgid "RADIUS Port" msgstr "Puerto RADIUS" -#: awx/sso/conf.py:456 +#: awx/sso/conf.py:452 msgid "Port of RADIUS server." msgstr "Puerto del servidor RADIUS" -#: awx/sso/conf.py:467 +#: awx/sso/conf.py:462 msgid "RADIUS Secret" msgstr "Clave secreta RADIUS" -#: awx/sso/conf.py:468 +#: awx/sso/conf.py:463 msgid "Shared secret for authenticating to RADIUS server." -msgstr "Clave secreta compartida para autenticación a RADIUS." +msgstr "Clave secreta compartida para autentificación a RADIUS." -#: awx/sso/conf.py:484 +#: awx/sso/conf.py:478 msgid "TACACS+ Server" msgstr "Servidor TACACS+" -#: awx/sso/conf.py:485 +#: awx/sso/conf.py:479 msgid "Hostname of TACACS+ server." msgstr "Nombre de host del servidor TACACS+." -#: awx/sso/conf.py:486 awx/sso/conf.py:499 awx/sso/conf.py:512 -#: awx/sso/conf.py:525 awx/sso/conf.py:537 awx/sso/models.py:15 +#: awx/sso/conf.py:480 awx/sso/conf.py:492 awx/sso/conf.py:504 +#: awx/sso/conf.py:516 awx/sso/conf.py:527 awx/sso/models.py:15 msgid "TACACS+" msgstr "TACACS+" -#: awx/sso/conf.py:497 +#: awx/sso/conf.py:490 msgid "TACACS+ Port" msgstr "Puerto TACACS+" -#: awx/sso/conf.py:498 +#: awx/sso/conf.py:491 msgid "Port number of TACACS+ server." msgstr "Número de puerto del servidor TACACS+." -#: awx/sso/conf.py:510 +#: awx/sso/conf.py:502 msgid "TACACS+ Secret" msgstr "Clave secreta TACACS+" -#: awx/sso/conf.py:511 +#: awx/sso/conf.py:503 msgid "Shared secret for authenticating to TACACS+ server." -msgstr "" -"Clave secreta compartida para la autenticación en el servidor TACACS+." +msgstr "Clave secreta compartida para la autenticación en el servidor TACACS+." -#: awx/sso/conf.py:523 +#: awx/sso/conf.py:514 msgid "TACACS+ Auth Session Timeout" msgstr "Tiempo de espera para la sesión de autenticación de TACACS+" -#: awx/sso/conf.py:524 +#: awx/sso/conf.py:515 msgid "TACACS+ session timeout value in seconds, 0 disables timeout." -msgstr "" -"Valor de tiempo de espera para la sesión TACACS+ en segundos. El valor 0 " -"deshabilita el tiempo de espera." +msgstr "Valor de tiempo de espera para la sesión TACACS+ en segundos. El valor 0 deshabilita el tiempo de espera." -#: awx/sso/conf.py:535 +#: awx/sso/conf.py:525 msgid "TACACS+ Authentication Protocol" msgstr "Protocolo de autenticación de TACACS+" -#: awx/sso/conf.py:536 +#: awx/sso/conf.py:526 msgid "Choose the authentication protocol used by TACACS+ client." msgstr "Elija el protocolo de autenticación utilizado por el cliente TACACS+." -#: awx/sso/conf.py:551 +#: awx/sso/conf.py:540 msgid "Google OAuth2 Callback URL" msgstr "Google OAuth2 Callback URL" -#: awx/sso/conf.py:552 awx/sso/conf.py:645 awx/sso/conf.py:710 +#: awx/sso/conf.py:541 awx/sso/conf.py:634 awx/sso/conf.py:699 msgid "" "Provide this URL as the callback URL for your application as part of your " "registration process. Refer to the Ansible Tower documentation for more " "detail." -msgstr "" -"Indique esta URL como URL de callback para su aplicación como parte del " -"proceso de registro. Consulte la documentación de Ansible Tower para obtener" -" información detallada." +msgstr "Indique esta URL como URL de callback para su aplicación como parte del proceso de registro. Consulte la documentación de Ansible Tower para obtener información detallada." -#: awx/sso/conf.py:555 awx/sso/conf.py:567 awx/sso/conf.py:579 -#: awx/sso/conf.py:592 awx/sso/conf.py:606 awx/sso/conf.py:618 -#: awx/sso/conf.py:630 +#: awx/sso/conf.py:544 awx/sso/conf.py:556 awx/sso/conf.py:568 +#: awx/sso/conf.py:581 awx/sso/conf.py:595 awx/sso/conf.py:607 +#: awx/sso/conf.py:619 msgid "Google OAuth2" msgstr "Google OAuth2" -#: awx/sso/conf.py:565 +#: awx/sso/conf.py:554 msgid "Google OAuth2 Key" msgstr "Clave Google OAuth2" -#: awx/sso/conf.py:566 +#: awx/sso/conf.py:555 msgid "The OAuth2 key from your web application." msgstr "Clave OAuth2 de su aplicación web." -#: awx/sso/conf.py:577 +#: awx/sso/conf.py:566 msgid "Google OAuth2 Secret" msgstr "Clave secreta para Google OAuth2" -#: awx/sso/conf.py:578 +#: awx/sso/conf.py:567 msgid "The OAuth2 secret from your web application." msgstr "Secreto OAuth2 de su aplicación web." -#: awx/sso/conf.py:589 +#: awx/sso/conf.py:578 msgid "Google OAuth2 Whitelisted Domains" msgstr "Lista blanca de dominios para Google OAuth2" -#: awx/sso/conf.py:590 +#: awx/sso/conf.py:579 msgid "" "Update this setting to restrict the domains who are allowed to login using " "Google OAuth2." -msgstr "" -"Actualizar esta configuración para restringir los dominios que están " -"permitidos para iniciar sesión utilizando Google OAuth2." +msgstr "Actualizar esta configuración para restringir los dominios que están permitidos para iniciar sesión utilizando Google OAuth2." -#: awx/sso/conf.py:601 +#: awx/sso/conf.py:590 msgid "Google OAuth2 Extra Arguments" msgstr "Argumentos adicionales para Google OAuth2" -#: awx/sso/conf.py:602 +#: awx/sso/conf.py:591 msgid "" -"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 " +"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 Ansible Tower documentation for more detail." -msgstr "" -"Argumentos adicionales para el inicio de sesión en Google OAuth2. Puede " -"limitarlo para permitir la autenticación de un solo dominio, incluso si el " -"usuario ha iniciado sesión con varias cuentas de Google. Consulte la " -"documentación de Ansible Tower para obtener información detallada." +msgstr "Argumentos adicionales para el inicio de sesión en Google OAuth2. Puede limitarlo para permitir la autenticación de un solo dominio, incluso si el usuario ha iniciado sesión con varias cuentas de Google. Consulte la documentación de Ansible Tower para obtener información detallada." -#: awx/sso/conf.py:616 +#: awx/sso/conf.py:605 msgid "Google OAuth2 Organization Map" msgstr "Mapa de organización para Google OAuth2" -#: awx/sso/conf.py:628 +#: awx/sso/conf.py:617 msgid "Google OAuth2 Team Map" msgstr "Mapa de equipo para Google OAuth2" -#: awx/sso/conf.py:644 +#: awx/sso/conf.py:633 msgid "GitHub OAuth2 Callback URL" msgstr "GitHub OAuth2 Callback URL" -#: awx/sso/conf.py:648 awx/sso/conf.py:660 awx/sso/conf.py:671 -#: awx/sso/conf.py:683 awx/sso/conf.py:695 +#: awx/sso/conf.py:637 awx/sso/conf.py:649 awx/sso/conf.py:660 +#: awx/sso/conf.py:672 awx/sso/conf.py:684 msgid "GitHub OAuth2" msgstr "GitHub OAuth2" -#: awx/sso/conf.py:658 +#: awx/sso/conf.py:647 msgid "GitHub OAuth2 Key" msgstr "Clave para Github OAuth2" -#: awx/sso/conf.py:659 +#: awx/sso/conf.py:648 msgid "The OAuth2 key (Client ID) from your GitHub developer application." -msgstr "" -"La clave OAuth2 (ID del cliente) de su aplicación de desarrollo GitHub." +msgstr "La clave OAuth2 (ID del cliente) de su aplicación de desarrollo GitHub." -#: awx/sso/conf.py:669 +#: awx/sso/conf.py:658 msgid "GitHub OAuth2 Secret" msgstr "Clave secreta para GitHub OAuth2" -#: awx/sso/conf.py:670 +#: awx/sso/conf.py:659 msgid "" "The OAuth2 secret (Client Secret) from your GitHub developer application." -msgstr "" -"La clave secreta OAuth2 (Clave secreta del cliente) de su aplicación de " -"desarrollo GitHub." +msgstr "La clave secreta OAuth2 (Clave secreta del cliente) de su aplicación de desarrollo GitHub." -#: awx/sso/conf.py:681 +#: awx/sso/conf.py:670 msgid "GitHub OAuth2 Organization Map" msgstr "Mapa de organización para GitHub OAuth2" -#: awx/sso/conf.py:693 +#: awx/sso/conf.py:682 msgid "GitHub OAuth2 Team Map" msgstr "Mapa de equipo para GitHub OAuth2" -#: awx/sso/conf.py:709 +#: awx/sso/conf.py:698 msgid "GitHub Organization OAuth2 Callback URL" msgstr "OAuth2 URL Callback para organización Github" -#: awx/sso/conf.py:713 awx/sso/conf.py:725 awx/sso/conf.py:736 -#: awx/sso/conf.py:749 awx/sso/conf.py:760 awx/sso/conf.py:772 +#: awx/sso/conf.py:702 awx/sso/conf.py:714 awx/sso/conf.py:725 +#: awx/sso/conf.py:738 awx/sso/conf.py:749 awx/sso/conf.py:761 msgid "GitHub Organization OAuth2" msgstr "OAuth2 para la organización Github" -#: awx/sso/conf.py:723 +#: awx/sso/conf.py:712 msgid "GitHub Organization OAuth2 Key" msgstr "Clave OAuth2 para la organización Github" -#: awx/sso/conf.py:724 awx/sso/conf.py:802 +#: awx/sso/conf.py:713 awx/sso/conf.py:791 msgid "The OAuth2 key (Client ID) from your GitHub organization application." -msgstr "" -"La clave OAuth2 (ID del cliente) de su aplicación de organización GitHub." +msgstr "La clave OAuth2 (ID del cliente) de su aplicación de organización GitHub." -#: awx/sso/conf.py:734 +#: awx/sso/conf.py:723 msgid "GitHub Organization OAuth2 Secret" msgstr "Clave secreta OAuth2 para la organización GitHub" -#: awx/sso/conf.py:735 awx/sso/conf.py:813 +#: awx/sso/conf.py:724 awx/sso/conf.py:802 msgid "" "The OAuth2 secret (Client Secret) from your GitHub organization application." -msgstr "" -"La clave secreta OAuth2 (Clave secreta del cliente) from your GitHub " -"organization application." +msgstr "La clave secreta OAuth2 (Clave secreta del cliente) from your GitHub organization application." -#: awx/sso/conf.py:746 +#: awx/sso/conf.py:735 msgid "GitHub Organization Name" -msgstr "Nombre para la organización GitHub" +msgstr "Nombre de la organización de GitHub" -#: awx/sso/conf.py:747 +#: awx/sso/conf.py:736 msgid "" "The name of your GitHub organization, as used in your organization's URL: " "https://github.com//." -msgstr "" -"El nombre de su organización de su GitHub, el utilizado en su URL de " -"organización: https://github.com//." +msgstr "El nombre de su organización de GitHub, como se utiliza en la URL de su organización: https://github.com//." -#: awx/sso/conf.py:758 +#: awx/sso/conf.py:747 msgid "GitHub Organization OAuth2 Organization Map" -msgstr "Mapa de organización OAuth2 para organizaciones GitHub" +msgstr "Mapa de organización de OAuth2 de la organización de GitHub" -#: awx/sso/conf.py:770 +#: awx/sso/conf.py:759 msgid "GitHub Organization OAuth2 Team Map" msgstr "Mapa de equipos OAuth2 para equipos GitHub" -#: awx/sso/conf.py:786 +#: awx/sso/conf.py:775 msgid "GitHub Team OAuth2 Callback URL" -msgstr "URL callback OAuth2 para los equipos GitHub" +msgstr "URL de devolución de OAuth2 del equipo de GitHub" -#: awx/sso/conf.py:787 +#: awx/sso/conf.py:776 msgid "" -"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." -msgstr "" -"Crear una aplicación propia de la organización en " -"https://github.com/organizations//settings/applications y " -"obtener una clave OAuth2 (ID del cliente ) y clave secreta (Clave secreta " -"del cliente). Proporcione esta URL como URL callback para su aplicación." +"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." +msgstr "Cree una aplicación propiedad de la organización en https://github.com/organizations//settings/applications y obtenga una clave de OAuth2 (ID del cliente) y secreta (clave secreta de cliente). Proporcione esta URL como URL de devolución para su aplicación." -#: awx/sso/conf.py:791 awx/sso/conf.py:803 awx/sso/conf.py:814 -#: awx/sso/conf.py:827 awx/sso/conf.py:838 awx/sso/conf.py:850 +#: awx/sso/conf.py:780 awx/sso/conf.py:792 awx/sso/conf.py:803 +#: awx/sso/conf.py:816 awx/sso/conf.py:827 awx/sso/conf.py:839 msgid "GitHub Team OAuth2" -msgstr "OAuth2 para equipos GitHub" +msgstr "OAuth2 de equipo de GitHub" -#: awx/sso/conf.py:801 +#: awx/sso/conf.py:790 msgid "GitHub Team OAuth2 Key" msgstr "Clave OAuth2 para equipos GitHub" -#: awx/sso/conf.py:812 +#: awx/sso/conf.py:801 msgid "GitHub Team OAuth2 Secret" msgstr "Clave secreta OAuth2 para equipos GitHub" -#: awx/sso/conf.py:824 +#: awx/sso/conf.py:813 msgid "GitHub Team ID" msgstr "ID de equipo GitHub" -#: awx/sso/conf.py:825 +#: awx/sso/conf.py:814 msgid "" -"Find the numeric team ID using the Github API: http://fabian-" -"kostadinov.github.io/2015/01/16/how-to-find-a-github-team-id/." -msgstr "" -"Encuentre su identificador numérico de equipo utilizando la API de GitHub: " -"http://fabian-kostadinov.github.io/2015/01/16/how-to-find-a-github-team-id/." +"Find the numeric team ID using the Github API: http://fabian-kostadinov." +"github.io/2015/01/16/how-to-find-a-github-team-id/." +msgstr "Encuentre su identificador numérico de equipo utilizando la API de GitHub: http://fabian-kostadinov.github.io/2015/01/16/how-to-find-a-github-team-id/." -#: awx/sso/conf.py:836 +#: awx/sso/conf.py:825 msgid "GitHub Team OAuth2 Organization Map" msgstr "Mapa de organizaciones OAuth2 para los equipos GitHub" -#: awx/sso/conf.py:848 +#: awx/sso/conf.py:837 msgid "GitHub Team OAuth2 Team Map" msgstr "Mapa de equipos OAuth2 para equipos GitHub" -#: awx/sso/conf.py:864 +#: awx/sso/conf.py:853 msgid "Azure AD OAuth2 Callback URL" msgstr "URL callback OAuth2 para Azure AD" -#: awx/sso/conf.py:865 +#: awx/sso/conf.py:854 msgid "" "Provide this URL as the callback URL for your application as part of your " "registration process. Refer to the Ansible Tower documentation for more " "detail. " -msgstr "" -"Indique esta URL como URL de callback para su aplicación como parte del " -"proceso de registro. Consulte la documentación de Ansible Tower para obtener" -" información detallada." +msgstr "Indique esta URL como URL de callback para su aplicación como parte del proceso de registro. Consulte la documentación de Ansible Tower para obtener información detallada." -#: awx/sso/conf.py:868 awx/sso/conf.py:880 awx/sso/conf.py:891 -#: awx/sso/conf.py:903 awx/sso/conf.py:915 +#: awx/sso/conf.py:857 awx/sso/conf.py:869 awx/sso/conf.py:880 +#: awx/sso/conf.py:892 awx/sso/conf.py:904 msgid "Azure AD OAuth2" msgstr "Azure AD OAuth2" -#: awx/sso/conf.py:878 +#: awx/sso/conf.py:867 msgid "Azure AD OAuth2 Key" msgstr "Clave OAuth2 para Azure AD" -#: awx/sso/conf.py:879 +#: awx/sso/conf.py:868 msgid "The OAuth2 key (Client ID) from your Azure AD application." msgstr "La clave OAuth2 (ID del cliente) de su aplicación en Azure AD." -#: awx/sso/conf.py:889 +#: awx/sso/conf.py:878 msgid "Azure AD OAuth2 Secret" msgstr "Clave secreta OAuth2 para Azure AD" -#: awx/sso/conf.py:890 +#: awx/sso/conf.py:879 msgid "The OAuth2 secret (Client Secret) from your Azure AD application." -msgstr "" -"La clave secreta OAuth2 (Clave secreta del cliente) de su aplicación Azure " -"AD." +msgstr "La clave secreta OAuth2 (Clave secreta del cliente) de su aplicación Azure AD." -#: awx/sso/conf.py:901 +#: awx/sso/conf.py:890 msgid "Azure AD OAuth2 Organization Map" msgstr "Mapa de organizaciones OAuth2 para Azure AD" -#: awx/sso/conf.py:913 +#: awx/sso/conf.py:902 msgid "Azure AD OAuth2 Team Map" msgstr "Mapa de equipos OAuth2 para Azure AD" -#: awx/sso/conf.py:938 +#: awx/sso/conf.py:927 msgid "SAML Assertion Consumer Service (ACS) URL" msgstr "URL del Servicio de consumidor de aserciones (ACS) SAML" -#: awx/sso/conf.py:939 +#: awx/sso/conf.py:928 msgid "" "Register Tower 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." -msgstr "" -"Registre Tower como un proveedor de servicio (SP) con cada proveedor de " -"identidad (IdP) que ha configurado. Proporcione su ID de entidad SP y esta " -"dirección URL ACS para su aplicación." - -#: awx/sso/conf.py:942 awx/sso/conf.py:956 awx/sso/conf.py:970 -#: awx/sso/conf.py:985 awx/sso/conf.py:999 awx/sso/conf.py:1012 -#: awx/sso/conf.py:1033 awx/sso/conf.py:1051 awx/sso/conf.py:1070 -#: awx/sso/conf.py:1106 awx/sso/conf.py:1138 awx/sso/conf.py:1152 -#: awx/sso/conf.py:1169 awx/sso/conf.py:1182 awx/sso/conf.py:1195 -#: awx/sso/conf.py:1213 awx/sso/models.py:16 +msgstr "Registre Tower como un proveedor de servicio (SP) con cada proveedor de identidad (IdP) que ha configurado. Proporcione su ID de entidad SP y esta dirección URL ACS para su aplicación." + +#: awx/sso/conf.py:931 awx/sso/conf.py:944 awx/sso/conf.py:957 +#: awx/sso/conf.py:971 awx/sso/conf.py:984 awx/sso/conf.py:996 +#: awx/sso/conf.py:1016 awx/sso/conf.py:1033 awx/sso/conf.py:1051 +#: awx/sso/conf.py:1086 awx/sso/conf.py:1117 awx/sso/conf.py:1130 +#: awx/sso/conf.py:1146 awx/sso/conf.py:1158 awx/sso/conf.py:1170 +#: awx/sso/conf.py:1189 awx/sso/models.py:16 msgid "SAML" msgstr "SAML" -#: awx/sso/conf.py:953 +#: awx/sso/conf.py:941 msgid "SAML Service Provider Metadata URL" msgstr "URL de metadatos para el proveedor de servicios SAML" -#: awx/sso/conf.py:954 +#: awx/sso/conf.py:942 msgid "" "If your identity provider (IdP) allows uploading an XML metadata file, you " "can download one from this URL." -msgstr "" -"Si su proveedor de identidad (IdP) permite subir ficheros de metadatos en " -"XML, puede descargarlo desde esta dirección." +msgstr "Si su proveedor de identidad (IdP) permite subir ficheros de metadatos en XML, puede descargarlo desde esta dirección." -#: awx/sso/conf.py:966 +#: awx/sso/conf.py:953 msgid "SAML Service Provider Entity ID" msgstr "ID de la entidad del proveedor de servicio SAML" -#: awx/sso/conf.py:967 +#: awx/sso/conf.py:954 msgid "" "The application-defined unique identifier used as the audience of the SAML " "service provider (SP) configuration. This is usually the URL for Tower." -msgstr "" -"El identificador único definido por la aplicación utilizado como " -"configuración para la audiencia del proveedor de servicio (SP) SAML. Por lo " -"general, es la URL para Tower." +msgstr "El identificador único definido por la aplicación utilizado como configuración para la audiencia del proveedor de servicio (SP) SAML. Por lo general, es la URL para Tower." -#: awx/sso/conf.py:982 +#: awx/sso/conf.py:968 msgid "SAML Service Provider Public Certificate" msgstr "Certificado público del proveedor de servicio SAML" -#: awx/sso/conf.py:983 +#: awx/sso/conf.py:969 msgid "" -"Create a keypair for Tower to use as a service provider (SP) and include the" -" certificate content here." -msgstr "" -"Crear par de claves para Tower a usar como proveedor de servicio (SP) e " -"incluir el contenido del certificado aquí." +"Create a keypair for Tower to use as a service provider (SP) and include the " +"certificate content here." +msgstr "Crear par de claves para Tower a usar como proveedor de servicio (SP) e incluir el contenido del certificado aquí." -#: awx/sso/conf.py:996 +#: awx/sso/conf.py:981 msgid "SAML Service Provider Private Key" msgstr "Clave privada del proveedor de servicio SAML" -#: awx/sso/conf.py:997 +#: awx/sso/conf.py:982 msgid "" -"Create a keypair for Tower to use as a service provider (SP) and include the" -" private key content here." -msgstr "" -"Crear par de claves para Tower a usar como proveedor de servicio (SP) e " -"incluir el contenido de la clave privada aquí." +"Create a keypair for Tower to use as a service provider (SP) and include the " +"private key content here." +msgstr "Crear par de claves para Tower a usar como proveedor de servicio (SP) e incluir el contenido de la clave privada aquí." -#: awx/sso/conf.py:1009 +#: awx/sso/conf.py:993 msgid "SAML Service Provider Organization Info" msgstr "Información organizacional del proveedor de servicio SAML" -#: awx/sso/conf.py:1010 +#: awx/sso/conf.py:994 msgid "" "Provide the URL, display name, and the name of your app. Refer to the " "Ansible Tower documentation for example syntax." -msgstr "" -"Indique la URL, el nombre de la pantalla y el nombre de la aplicación. " -"Consulte la documentación de Ansible Tower para acceder a ejemplos de " -"sintaxis." +msgstr "Indique la URL, el nombre de la pantalla y el nombre de la aplicación. Consulte la documentación de Ansible Tower para acceder a ejemplos de sintaxis." -#: awx/sso/conf.py:1029 +#: awx/sso/conf.py:1012 msgid "SAML Service Provider Technical Contact" msgstr "Contacto técnico del proveedor de servicio SAML" -#: awx/sso/conf.py:1030 +#: awx/sso/conf.py:1013 msgid "" -"Provide the name and email address of the technical contact for your service" -" provider. Refer to the Ansible Tower documentation for example syntax." -msgstr "" -"Indique el nombre y la dirección de correo electrónico del contacto técnico" -" de su proveedor de servicios. Consulte la documentación de Ansible Tower " -"para obtener ejemplos de sintaxis." +"Provide the name and email address of the technical contact for your service " +"provider. Refer to the Ansible Tower documentation for example syntax." +msgstr "Indique el nombre y la dirección de correo electrónico del contacto técnico de su proveedor de servicios. Consulte la documentación de Ansible Tower para obtener ejemplos de sintaxis." -#: awx/sso/conf.py:1047 +#: awx/sso/conf.py:1029 msgid "SAML Service Provider Support Contact" msgstr "Contacto de soporte del proveedor de servicio SAML" -#: awx/sso/conf.py:1048 +#: awx/sso/conf.py:1030 msgid "" "Provide the name and email address of the support contact for your service " "provider. Refer to the Ansible Tower documentation for example syntax." -msgstr "" -"Indique el nombre y la dirección de correo electrónico del contacto de " -"soporte técnico de su proveedor de servicios. Consulte la documentación de " -"Ansible Tower para obtener ejemplos de sintaxis." +msgstr "Indique el nombre y la dirección de correo electrónico del contacto de soporte de su proveedor de servicios. Consulte la documentación de Ansible Tower para obtener ejemplos de sintaxis." -#: awx/sso/conf.py:1064 +#: awx/sso/conf.py:1045 msgid "SAML Enabled Identity Providers" msgstr "Proveedores de identidad habilitados SAML" -#: awx/sso/conf.py:1065 +#: awx/sso/conf.py:1046 msgid "" "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." -msgstr "" -"Configure la ID de la entidad, la URL del acceso SSO y el certificado de " -"cada proveedor de identidad (IdP) en uso. Se admiten varios IdP de SAML. " -"Algunos IdP pueden proporcionar datos sobre los usuarios por medio del uso " -"de nombres de atributos que difieren de los OID predeterminados. Pueden " -"anularse los nombres de los atributos para cada IdP. Consulte la " -"documentación de Ansible Tower para obtener información detallada adicional " -"y ejemplos de sintaxis." - -#: awx/sso/conf.py:1102 +"names may be overridden for each IdP. Refer to the Ansible documentation for " +"additional details and syntax." +msgstr "Configure la ID de la entidad, la URL del acceso SSO y el certificado de cada proveedor de identidad (IdP) en uso. Se admiten varios IdP de SAML. Algunos IdP pueden proporcionar datos sobre los usuarios por medio del uso de nombres de atributos que difieren de los OID predeterminados. Pueden anularse los nombres de los atributos para cada IdP. Consulte la documentación de Ansible Tower para obtener información detallada adicional y ejemplos de sintaxis." + +#: awx/sso/conf.py:1082 msgid "SAML Security Config" msgstr "Configuración de seguridad SAML" -#: awx/sso/conf.py:1103 +#: awx/sso/conf.py:1083 msgid "" "A dict of key value pairs that are passed to the underlying python-saml " "security setting https://github.com/onelogin/python-saml#settings" -msgstr "" -"Un diccionario de pares de valores clave que se envían a la configuración de" -" seguridad python-saml subyacente https://github.com/onelogin/python-" -"saml#settings" +msgstr "Un diccionario de pares de valores clave que se envían a la configuración de seguridad python-saml subyacente https://github.com/onelogin/python-saml#settings" -#: awx/sso/conf.py:1135 +#: awx/sso/conf.py:1114 msgid "SAML Service Provider extra configuration data" msgstr "Datos de configuración adicionales del proveedor de servicio SAML" -#: awx/sso/conf.py:1136 +#: awx/sso/conf.py:1115 msgid "" -"A dict of key value pairs to be passed to the underlying python-saml Service" -" Provider configuration setting." -msgstr "" -"Un diccionario de pares de valores clave que se envían a los valores de " -"configuración del proveedor de servicio python-saml subyacente" +"A dict of key value pairs to be passed to the underlying python-saml Service " +"Provider configuration setting." +msgstr "Un diccionario de pares de valores clave que se envían a los valores de configuración del proveedor de servicio python-saml subyacente." -#: awx/sso/conf.py:1149 +#: awx/sso/conf.py:1127 msgid "SAML IDP to extra_data attribute mapping" msgstr "Asignación de atributos de SAML IDP a extra_data" -#: awx/sso/conf.py:1150 +#: awx/sso/conf.py:1128 msgid "" "A list of tuples that maps IDP attributes to extra_attributes. Each " "attribute will be a list of values, even if only 1 value." -msgstr "" -"Una lista de tuplas que asigna atributos IDP a extra_attributes. Cada " -"atributo será una lista de valores, aunque sea un solo valor." +msgstr "Una lista de tuplas que asigna atributos IDP a extra_attributes. Cada atributo será una lista de valores, aunque sea un solo valor." -#: awx/sso/conf.py:1167 +#: awx/sso/conf.py:1144 msgid "SAML Organization Map" msgstr "Mapa de organización SAML" -#: awx/sso/conf.py:1180 +#: awx/sso/conf.py:1156 msgid "SAML Team Map" msgstr "Mapa de equipo SAML" -#: awx/sso/conf.py:1193 +#: awx/sso/conf.py:1168 msgid "SAML Organization Attribute Mapping" msgstr "Asignación de atributos de la Organización SAML" -#: awx/sso/conf.py:1194 +#: awx/sso/conf.py:1169 msgid "Used to translate user organization membership into Tower." -msgstr "" -"Utilizado para traducir la membresía a Tower de la organización del usuario." +msgstr "Usado para traducir la membresía de la organización del usuario a Tower." -#: awx/sso/conf.py:1211 +#: awx/sso/conf.py:1187 msgid "SAML Team Attribute Mapping" -msgstr "Asignación de atributos del Equipo SAML" +msgstr "Asignación de atributos del equipo SAML" -#: awx/sso/conf.py:1212 +#: awx/sso/conf.py:1188 msgid "Used to translate user team membership into Tower." -msgstr "Utilizado para traducir la membresía a Tower del equipo del usuario." +msgstr "Usado para traducir la membresía del equipo del usuario a Tower." -#: awx/sso/fields.py:183 +#: awx/sso/fields.py:81 +msgid "Invalid field." +msgstr "Campo no válido." + +#: awx/sso/fields.py:250 #, python-brace-format msgid "Invalid connection option(s): {invalid_options}." -msgstr "Opción(es) de conexión inválida(s): {invalid_options}." +msgstr "Opción(es) de conexión no válida(s): {invalid_options}." -#: awx/sso/fields.py:266 +#: awx/sso/fields.py:334 msgid "Base" msgstr "Base" -#: awx/sso/fields.py:267 +#: awx/sso/fields.py:335 msgid "One Level" msgstr "Un nivel" -#: awx/sso/fields.py:268 +#: awx/sso/fields.py:336 msgid "Subtree" msgstr "Árbol hijo" -#: awx/sso/fields.py:286 +#: awx/sso/fields.py:354 #, python-brace-format msgid "Expected a list of three items but got {length} instead." -msgstr "" -"Esperado una lista de tres elementos pero en cambio se proporcionaron " -"{length}" +msgstr "Se esperaba una lista de tres elementos, pero en cambio se obtuvo {length}." -#: awx/sso/fields.py:287 +#: awx/sso/fields.py:355 #, python-brace-format msgid "Expected an instance of LDAPSearch but got {input_type} instead." -msgstr "" -"Esperado una instancia de LDAPSearch pero en cambio se obtuvo {input_type}" +msgstr "Se esperaba una instancia de LDAPSearch, pero en cambio se obtuvo {input_type}." -#: awx/sso/fields.py:323 +#: awx/sso/fields.py:391 #, python-brace-format msgid "" "Expected an instance of LDAPSearch or LDAPSearchUnion but got {input_type} " "instead." -msgstr "" -"Esperado una instancia de LDAPSearch o LDAPSearchUnion pero en cambio se " -"obtuvo {input_type}" +msgstr "Se esperaba una instancia de LDAPSearch o LDAPSearchUnion, pero en cambio se obtuvo {input_type}." -#: awx/sso/fields.py:361 +#: awx/sso/fields.py:429 #, python-brace-format msgid "Invalid user attribute(s): {invalid_attrs}." -msgstr "Atributo(s) de usuario inválido(s): {invalid_attrs}." +msgstr "Atributo(s) de usuario no válido(s): {invalid_attrs}." -#: awx/sso/fields.py:378 +#: awx/sso/fields.py:447 #, python-brace-format msgid "Expected an instance of LDAPGroupType but got {input_type} instead." -msgstr "" -"Se esperaba una instancia de LDAPGroupType pero en cambio se obtuvo " -"{input_type}." +msgstr "Se esperaba una instancia de LDAPGroupType, pero en cambio se obtuvo {input_type}." -#: awx/sso/fields.py:418 awx/sso/fields.py:465 +#: awx/sso/fields.py:487 #, python-brace-format msgid "Invalid key(s): {invalid_keys}." -msgstr "Clave(s) inválida(s): {invalid_keys}." +msgstr "Clave(s) no válida(s): {invalid_keys}." -#: awx/sso/fields.py:443 +#: awx/sso/fields.py:513 #, python-brace-format msgid "Invalid user flag: \"{invalid_flag}\"." -msgstr "Indicador de usuario inválido: \"{invalid_flag}\"." - -#: awx/sso/fields.py:464 -#, python-brace-format -msgid "Missing key(s): {missing_keys}." -msgstr "Clave(s) no encontradas: {missing_keys}." - -#: awx/sso/fields.py:514 awx/sso/fields.py:631 -#, python-brace-format -msgid "Invalid key(s) for organization map: {invalid_keys}." -msgstr "Clave(s) inválida(s) para map de organización: {invalid_keys}." - -#: awx/sso/fields.py:532 -#, python-brace-format -msgid "Missing required key for team map: {invalid_keys}." -msgstr "Clave necesario no encontrada para mapa de equipo: {invalid_keys}." - -#: awx/sso/fields.py:533 awx/sso/fields.py:650 -#, python-brace-format -msgid "Invalid key(s) for team map: {invalid_keys}." -msgstr "Clave(s) inválida(s) para mapa de equipo: {invalid_keys}." - -#: awx/sso/fields.py:649 -#, python-brace-format -msgid "Missing required key for team map: {missing_keys}." -msgstr "Clave necesaria no encontrada para mapa de equipo: {missing_keys}." +msgstr "Marcador de usuario no válido: \"{invalid_flag}\"." #: awx/sso/fields.py:667 #, python-brace-format -msgid "Missing required key(s) for org info record: {missing_keys}." -msgstr "" -"Clave(s) necesaria(s) no encontrada(s) para el registro informacional de la " -"organización: {missing_keys}." - -#: awx/sso/fields.py:680 -#, python-brace-format msgid "Invalid language code(s) for org info: {invalid_lang_codes}." -msgstr "" -"Código(s) de lenguaje(s) inválido(s) para información organizacional: " -"{invalid_lang_codes}." - -#: awx/sso/fields.py:699 -#, python-brace-format -msgid "Missing required key(s) for contact: {missing_keys}." -msgstr "Clave(s) necesaria(s) no encontrada(s) para contacto: {missing_keys}." - -#: awx/sso/fields.py:711 -#, python-brace-format -msgid "Missing required key(s) for IdP: {missing_keys}." -msgstr "Clave(s) necesaria(s) no encontrada(s) para IdP: {missing_keys}." +msgstr "Código(s) de lenguaje(s) no válido(s) para obtener información de la org.: {invalid_lang_codes}." -#: awx/sso/pipeline.py:31 +#: awx/sso/pipeline.py:27 #, python-brace-format msgid "An account cannot be found for {0}" -msgstr "Una cuenta no puede ser encontrada para {0}" +msgstr "No se puede encontrar una cuenta para {0}" -#: awx/sso/pipeline.py:37 +#: awx/sso/pipeline.py:33 msgid "Your account is inactive" msgstr "Su cuenta está inactiva" #: awx/sso/validators.py:20 awx/sso/validators.py:46 #, python-format msgid "DN must include \"%%(user)s\" placeholder for username: %s" -msgstr "DN debe incluir el marcador \"%%(user)s\" para el usuario: %s" +msgstr "DN debe incluir el marcador de posición \"%%(user)s\" para el nombre de usuario: %s" #: awx/sso/validators.py:27 #, python-format msgid "Invalid DN: %s" -msgstr "DN inválido: %s" +msgstr "DN no válido: %s" #: awx/sso/validators.py:58 #, python-format msgid "Invalid filter: %s" -msgstr "Filtro inválido: %s" +msgstr "Filtro no válido: %s" #: awx/sso/validators.py:69 msgid "TACACS+ secret does not allow non-ascii characters" @@ -5903,36 +6050,8 @@ msgstr "Volver a Ansible Tower" msgid "Resize" msgstr "Redimensionar" -#: awx/templates/rest_framework/base.html:37 -msgid "navbar" -msgstr "navbar" - -#: awx/templates/rest_framework/base.html:75 -msgid "content" -msgstr "contenido" - -#: awx/templates/rest_framework/base.html:78 -msgid "request form" -msgstr "solicitar formulario" - -#: awx/templates/rest_framework/base.html:134 -msgid "Filters" -msgstr "Filtros" - -#: awx/templates/rest_framework/base.html:139 -msgid "main content" -msgstr "contenido principal" - -#: awx/templates/rest_framework/base.html:155 -msgid "request info" -msgstr "solicitar información" - -#: awx/templates/rest_framework/base.html:159 -msgid "response info" -msgstr "información de respuesta" - -#: awx/ui/apps.py:9 awx/ui/conf.py:22 awx/ui/conf.py:36 awx/ui/conf.py:51 -#: awx/ui/conf.py:63 awx/ui/conf.py:73 +#: awx/ui/apps.py:9 awx/ui/conf.py:22 awx/ui/conf.py:36 awx/ui/conf.py:50 +#: awx/ui/conf.py:61 awx/ui/conf.py:71 msgid "UI" msgstr "UI" @@ -5949,12 +6068,12 @@ msgid "Detailed" msgstr "Detallado" #: awx/ui/conf.py:20 -msgid "Analytics Tracking State" -msgstr "Estado de seguimiento analítico" +msgid "User Analytics Tracking State" +msgstr "Estado de seguimiento analítico del usuario" #: awx/ui/conf.py:21 -msgid "Enable or Disable Analytics Tracking." -msgstr "Habilitar o deshabilitar el seguimiento analítico" +msgid "Enable or Disable User Analytics Tracking." +msgstr "Habilitar o deshabilitar el seguimiento analítico del usuario" #: awx/ui/conf.py:31 msgid "Custom Login Info" @@ -5963,61 +6082,48 @@ msgstr "Información personalizada de inicio de sesión" #: awx/ui/conf.py:32 msgid "" "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, as custom HTML or other markup languages are " +"disclaimer) to a text box in the login modal using this setting. Any content " +"added must be in plain text, as custom HTML or other markup languages are " "not supported." -msgstr "" -"En caso de ser necesario, puede agregar información específica (como avisos " -"legales o exenciones de responsabilidad) en un cuadro de texto en el modal " -"de inicio de sesión con esta configuración. Los contenidos que se agreguen " -"deberán ser texto simple, ya que las HTML personalizadas u otros lenguajes " -"de marcado no son compatibles." +msgstr "En caso de ser necesario, puede agregar información específica (como avisos legales o exenciones de responsabilidad) en un cuadro de texto en el modal de inicio de sesión con esta configuración. El contenido que se agregue deberá ser texto simple, ya que las HTML personalizadas u otros lenguajes de marcado no son compatibles." -#: awx/ui/conf.py:46 +#: awx/ui/conf.py:45 msgid "Custom Logo" msgstr "Logo personalizado" -#: awx/ui/conf.py:47 +#: awx/ui/conf.py:46 msgid "" -"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 " +"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." -msgstr "" -"Para configurar un logo personalizado, proporcione un fichero que ha creado." -" Para que los logos personalizados se vean mejor, utilice un fichero .png " -"con fondo transparente. Formatos GIF, PNG y JPEG están soportados." +msgstr "Para configurar un logo personalizado, proporcione un fichero que ha creado. Para que los logos personalizados se vean mejor, utilice un fichero .png con fondo transparente. Formatos GIF, PNG y JPEG están soportados." -#: awx/ui/conf.py:60 +#: awx/ui/conf.py:58 msgid "Max Job Events Retrieved by UI" msgstr "Máxima cantidad de eventos de tareas recuperados por UI" -#: awx/ui/conf.py:61 +#: awx/ui/conf.py:59 msgid "" "Maximum number of job events for the UI to retrieve within a single request." -msgstr "" -"Máxima cantidad de eventos de tareas para que la UI recupere dentro de una " -"sola solicitud." +msgstr "Máxima cantidad de eventos de tareas para que la UI recupere dentro de una sola solicitud." -#: awx/ui/conf.py:70 +#: awx/ui/conf.py:68 msgid "Enable Live Updates in the UI" msgstr "Habilite las actualizaciones en directo en la UI" -#: awx/ui/conf.py:71 +#: awx/ui/conf.py:69 msgid "" "If disabled, the page will not refresh when events are received. Reloading " "the page will be required to get the latest details." -msgstr "" -"Si está deshabilitada, la página no se actualizará al recibir eventos. Se " -"deberá volver a cargar la página para obtener la información más reciente." +msgstr "Si está deshabilitada, la página no se actualizará al recibir eventos. Se deberá volver a cargar la página para obtener la información más reciente." -#: awx/ui/fields.py:29 +#: awx/ui/fields.py:30 msgid "" "Invalid format for custom logo. Must be a data URL with a base64-encoded " "GIF, PNG or JPEG image." -msgstr "" -"Formato inválido para el logo personalizado. Debe ser una URL de datos con " -"una imagen GIF codificada en base64, PNG o JPEG." +msgstr "Formato inválido para el logo personalizado. Debe ser una URL de datos con una imagen GIF codificada en base64, PNG o JPEG." -#: awx/ui/fields.py:30 +#: awx/ui/fields.py:31 msgid "Invalid base64-encoded data in data URL." msgstr "Dato codificado en base64 inválido en la URL de datos" + diff --git a/awx/locale/fr/LC_MESSAGES/django.po b/awx/locale/fr/LC_MESSAGES/django.po index 84c3f4ff51d4..bcb54c548b45 100644 --- a/awx/locale/fr/LC_MESSAGES/django.po +++ b/awx/locale/fr/LC_MESSAGES/django.po @@ -1,21 +1,21 @@ -# aude_stoquart , 2017. #zanata -# croe , 2017. #zanata -# mkim , 2017. #zanata -# croe , 2018. #zanata +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-08-14 13:52+0000\n" -"PO-Revision-Date: 2018-08-16 12:04+0000\n" -"Last-Translator: croe \n" -"Language-Team: French\n" +"POT-Creation-Date: 2020-05-28 21:45+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: fr \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: fr\n" -"Plural-Forms: nplurals=2; plural=(n > 1)\n" -"X-Generator: Zanata 4.6.0\n" #: awx/api/conf.py:15 msgid "Idle Time Force Log Out" @@ -25,13 +25,11 @@ msgstr "Temps d'inactivité - Forcer la déconnexion" msgid "" "Number of seconds that a user is inactive before they will need to login " "again." -msgstr "" -"Délai en secondes pendant lequel un utilisateur peut rester inactif avant de" -" devoir se reconnecter." +msgstr "Délai en secondes pendant lequel un utilisateur peut rester inactif avant de devoir se reconnecter." -#: awx/api/conf.py:17 awx/api/conf.py:26 awx/api/conf.py:34 awx/api/conf.py:47 -#: awx/api/conf.py:59 awx/sso/conf.py:85 awx/sso/conf.py:96 -#: awx/sso/conf.py:108 awx/sso/conf.py:123 +#: awx/api/conf.py:17 awx/api/conf.py:26 awx/api/conf.py:34 awx/api/conf.py:50 +#: awx/api/conf.py:62 awx/api/conf.py:74 awx/sso/conf.py:97 awx/sso/conf.py:108 +#: awx/sso/conf.py:120 awx/sso/conf.py:135 msgid "Authentication" msgstr "Authentification" @@ -43,9 +41,7 @@ msgstr "Le nombre maximum de sessions actives en simultané" msgid "" "Maximum number of simultaneous logged in sessions a user may have. To " "disable enter -1." -msgstr "" -"Nombre maximal de connexions actives simultanées dont un utilisateur peut " -"disposer. Pour désactiver cette option, entrez -1." +msgstr "Nombre maximal de connexions actives simultanées dont un utilisateur peut disposer. Pour désactiver cette option, entrez -1." #: awx/api/conf.py:32 msgid "Enable HTTP Basic Auth" @@ -55,38 +51,41 @@ msgstr "Activer l'authentification HTTP de base" msgid "Enable HTTP Basic Auth for the API Browser." msgstr "Activer l'authentification HTTP de base pour le navigateur d'API." -#: awx/api/conf.py:42 +#: awx/api/conf.py:43 msgid "OAuth 2 Timeout Settings" msgstr "OAuth 2 Config Timeout" -#: awx/api/conf.py:43 +#: awx/api/conf.py:44 msgid "" "Dictionary for customizing OAuth 2 timeouts, available items are " "`ACCESS_TOKEN_EXPIRE_SECONDS`, the duration of access tokens in the number " -"of seconds, and `AUTHORIZATION_CODE_EXPIRE_SECONDS`, the duration of " -"authorization grants in the number of seconds." -msgstr "" -"Dictionnaire pour personnaliser les timeouts OAuth 2; les éléments " -"disponibles sont `ACCESS_TOKEN_EXPIRE_SECONDS`, la durée des jetons d'accès " -"en nombre de secondes, et`AUTHORIZATION_CODE_EXPIRE_SECONDS`, la durée des " -"autorisations données en nombre de secondes." +"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." +msgstr "Dictionnaire pour la personnalisation des timeouts OAuth 2, les éléments disponibles sont `ACCESS_TOKEN_EXPIRE_SECONDS`, la durée des jetons d'accès en nombre de secondes, `AUTHORIZATION_CODE_EXPIRE_SECONDS`, la durée des codes d'autorisation en nombre de secondes, et `REFRESH_TOKEN_EXPIRE_SECONDS`, la durée des jetons d’actualisation, après l’expiration des jetons d'accès, en nombre de secondes." -#: awx/api/conf.py:54 +#: awx/api/conf.py:57 msgid "Allow External Users to Create OAuth2 Tokens" msgstr "Autoriser les utilisateurs extérieurs à créer des Jetons OAuth2" -#: awx/api/conf.py:55 +#: awx/api/conf.py:58 msgid "" "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." -msgstr "" -"Pour des raisons de sécurité, les utilisateurs de fournisseurs " -"d'authentification externes (LDAP, SAML, SSO, Radius et autres) ne sont pas " -"autorisés à créer des jetons OAuth2. Pour modifier ce comportement, activez " -"ce paramètre. Les jetons existants ne sont pas supprimés lorsque ce " -"paramètre est désactivé." +"behavior, enable this setting. Existing tokens will not be deleted when this " +"setting is toggled off." +msgstr "Pour des raisons de sécurité, les utilisateurs de fournisseurs d'authentification externes (LDAP, SAML, SSO, Radius et autres) ne sont pas autorisés à créer des jetons OAuth2. Pour modifier ce comportement, activez ce paramètre. Les jetons existants ne sont pas supprimés lorsque ce paramètre est désactivé." + +#: awx/api/conf.py:71 +msgid "Login redirect override URL" +msgstr "URL de remplacement de redirection de connexion" + +#: awx/api/conf.py:72 +msgid "" +"URL to which unauthorized users will be redirected to log in. If blank, " +"users will be sent to the Tower login page." +msgstr "URL vers laquelle les utilisateurs non autorisés sont redirigés pour se connecter. Si cette valeur est vide, les utilisateurs sont renvoyés vers la page de connexion de Tower." #: awx/api/exceptions.py:20 msgid "Resource is being used by running jobs." @@ -95,26 +94,26 @@ msgstr "Ressource utilisée par les tâches en cours." #: awx/api/fields.py:81 #, python-brace-format msgid "Invalid key names: {invalid_key_names}" -msgstr "Noms de clés non valides : {invalid_key_names}" +msgstr "Noms de clés invalides : {invalid_key_names}" -#: awx/api/fields.py:107 +#: awx/api/fields.py:111 msgid "Credential {} does not exist" msgstr "L'identifiant {} n'existe pas" -#: awx/api/filters.py:97 +#: awx/api/filters.py:82 msgid "No related model for field {}." msgstr "Aucun modèle qui y soit lié pour le champ '{}'" -#: awx/api/filters.py:114 +#: awx/api/filters.py:99 msgid "Filtering on password fields is not allowed." msgstr "Le filtrage des champs de mot de passe n'est pas autorisé." -#: awx/api/filters.py:126 awx/api/filters.py:128 +#: awx/api/filters.py:111 awx/api/filters.py:113 #, python-format msgid "Filtering on %s is not allowed." -msgstr "Le filtrage de %s n'est pas autorisé." +msgstr "Le filtrage sur %s n'est pas autorisé." -#: awx/api/filters.py:131 +#: awx/api/filters.py:116 msgid "Loops not allowed in filters, detected on field {}." msgstr "Boucles non autorisés dans les filtres, détectées sur le champ {}." @@ -122,69 +121,70 @@ msgstr "Boucles non autorisés dans les filtres, détectées sur le champ {}." msgid "Query string field name not provided." msgstr "Nom de champ du string de requête non fourni." -#: awx/api/filters.py:187 +#: awx/api/filters.py:192 #, python-brace-format msgid "Invalid {field_name} id: {field_id}" -msgstr "Id {field_name} non valide : {field_id}" +msgstr "ID {field_name} non valide : {field_id}" -#: awx/api/filters.py:326 -#, python-format -msgid "cannot filter on kind %s" -msgstr "impossible de filtrer sur le genre %s" +#: awx/api/filters.py:333 +msgid "" +"Cannot apply role_level filter to this list because its model does not use " +"roles for access control." +msgstr "N'a pas pu appliquer le filtre role_level à cette liste car son modèle n'utilise pas de rôles pour le contrôle d'accès." -#: awx/api/generics.py:197 +#: awx/api/generics.py:182 msgid "" "You did not use correct Content-Type in your HTTP request. If you are using " "our REST API, the Content-Type must be application/json" -msgstr "" -"Vous n'avez pas utilisé le bon type de contenu dans votre requête HTTP. Si " -"vous utilisez notre API REST, le type de contenu doit être application/json." +msgstr "Vous n'avez pas utilisé le bon type de contenu dans votre requête HTTP. Si vous utilisez notre API REST, le type de contenu doit être application/json." -#: awx/api/generics.py:635 awx/api/generics.py:697 +#: awx/api/generics.py:623 awx/api/generics.py:685 msgid "\"id\" field must be an integer." msgstr "Le champ \"id\" doit être un nombre entier." -#: awx/api/generics.py:694 +#: awx/api/generics.py:682 msgid "\"id\" is required to disassociate" msgstr "\"id\" est nécessaire pour dissocier" -#: awx/api/generics.py:745 +#: awx/api/generics.py:733 msgid "{} 'id' field is missing." msgstr "Le champ {} 'id' est manquant." -#: awx/api/metadata.py:51 +#: awx/api/metadata.py:58 msgid "Database ID for this {}." msgstr "ID de base de données pour ce {}." -#: awx/api/metadata.py:52 +#: awx/api/metadata.py:59 msgid "Name of this {}." msgstr "Nom de ce {}." -#: awx/api/metadata.py:53 +#: awx/api/metadata.py:60 msgid "Optional description of this {}." msgstr "Description facultative de ce {}." -#: awx/api/metadata.py:54 +#: awx/api/metadata.py:61 msgid "Data type for this {}." msgstr "Type de données pour ce {}." -#: awx/api/metadata.py:55 +#: awx/api/metadata.py:62 msgid "URL for this {}." msgstr "URL de ce {}." -#: awx/api/metadata.py:56 +#: awx/api/metadata.py:63 msgid "Data structure with URLs of related resources." msgstr "Structure de données avec URL des ressources associées." -#: awx/api/metadata.py:57 -msgid "Data structure with name/description for related resources." -msgstr "Structure de données avec nom/description des ressources associées." +#: awx/api/metadata.py:64 +msgid "" +"Data structure with name/description for related resources. The output for " +"some objects may be limited for performance reasons." +msgstr "Structure des données avec nom/description des ressources connexes. La sortie de certains objets peut être limitée pour des raisons de performance." -#: awx/api/metadata.py:58 +#: awx/api/metadata.py:66 msgid "Timestamp when this {} was created." msgstr "Horodatage lors de la création de ce {}." -#: awx/api/metadata.py:59 +#: awx/api/metadata.py:67 msgid "Timestamp when this {} was last modified." msgstr "Horodatage lors de la modification de ce {}." @@ -197,1310 +197,1306 @@ msgstr "Erreur d'analyse JSON - pas un objet JSON" msgid "" "JSON parse error - %s\n" "Possible cause: trailing comma." -msgstr "" -"Erreur d'analyse JSON - %s\n" -"Cause possible : virgule de fin." +msgstr "Erreur d'analyse JSON - %s\n" +"Cause possible : virgule de fin." -#: awx/api/serializers.py:155 +#: awx/api/serializers.py:169 msgid "" -"The original object is already named {}, a copy from it cannot have the same" -" name." -msgstr "" -"L'objet d'origine s'appelle déjà {}, une copie de cet objet ne peut pas " -"avoir le même nom." +"The original object is already named {}, a copy from it cannot have the same " +"name." +msgstr "L'objet d'origine s'appelle déjà {}, une copie de cet objet ne peut pas avoir le même nom." -#: awx/api/serializers.py:290 +#: awx/api/serializers.py:302 #, python-format msgid "Cannot use dictionary for %s" msgstr "Impossible d'utiliser le dictionnaire pour %s" -#: awx/api/serializers.py:307 +#: awx/api/serializers.py:316 msgid "Playbook Run" msgstr "Exécution du playbook" -#: awx/api/serializers.py:308 +#: awx/api/serializers.py:317 msgid "Command" msgstr "Commande" -#: awx/api/serializers.py:309 awx/main/models/unified_jobs.py:526 +#: awx/api/serializers.py:318 awx/main/models/unified_jobs.py:547 msgid "SCM Update" msgstr "Mise à jour SCM" -#: awx/api/serializers.py:310 +#: awx/api/serializers.py:319 msgid "Inventory Sync" msgstr "Synchronisation des inventaires" -#: awx/api/serializers.py:311 +#: awx/api/serializers.py:320 msgid "Management Job" msgstr "Tâche de gestion" -#: awx/api/serializers.py:312 +#: awx/api/serializers.py:321 msgid "Workflow Job" msgstr "Tâche de workflow" -#: awx/api/serializers.py:313 +#: awx/api/serializers.py:322 msgid "Workflow Template" msgstr "Modèle de workflow" -#: awx/api/serializers.py:314 +#: awx/api/serializers.py:323 msgid "Job Template" -msgstr "Modèle de job" +msgstr "Modèle de tâche" -#: awx/api/serializers.py:714 +#: awx/api/serializers.py:709 msgid "" "Indicates whether all of the events generated by this unified job have been " "saved to the database." -msgstr "" -"Indique si tous les événements générés par ce job unifié ont été enregistrés" -" dans la base de données." +msgstr "Indique si tous les événements générés par ce job unifié ont été enregistrés dans la base de données." -#: awx/api/serializers.py:879 +#: awx/api/serializers.py:878 msgid "Write-only field used to change the password." msgstr "Champ en écriture seule servant à modifier le mot de passe." -#: awx/api/serializers.py:881 +#: awx/api/serializers.py:880 msgid "Set if the account is managed by an external service" msgstr "À définir si le compte est géré par un service externe" -#: awx/api/serializers.py:905 +#: awx/api/serializers.py:907 msgid "Password required for new User." msgstr "Mot de passe requis pour le nouvel utilisateur." -#: awx/api/serializers.py:981 +#: awx/api/serializers.py:992 #, python-format msgid "Unable to change %s on user managed by LDAP." msgstr "Impossible de redéfinir %s sur un utilisateur géré par LDAP." -#: awx/api/serializers.py:1067 +#: awx/api/serializers.py:1088 msgid "Must be a simple space-separated string with allowed scopes {}." -msgstr "" -"Doit correspondre à une simple chaîne de caractères séparés par des espaces " -"avec dans les limites autorisées {}." +msgstr "Doit correspondre à une simple chaîne de caractères séparés par des espaces avec dans les limites autorisées {}." -#: awx/api/serializers.py:1167 +#: awx/api/serializers.py:1186 msgid "Authorization Grant Type" msgstr "Type d'autorisation" -#: awx/api/serializers.py:1169 awx/main/models/credential/__init__.py:1064 +#: awx/api/serializers.py:1188 awx/main/credential_plugins/azure_kv.py:30 +#: awx/main/models/credential/__init__.py:960 msgid "Client Secret" msgstr "Question secrète du client" -#: awx/api/serializers.py:1172 +#: awx/api/serializers.py:1191 msgid "Client Type" msgstr "Type de client" -#: awx/api/serializers.py:1175 +#: awx/api/serializers.py:1194 msgid "Redirect URIs" msgstr "Redirection d'URIs." -#: awx/api/serializers.py:1178 +#: awx/api/serializers.py:1197 msgid "Skip Authorization" msgstr "Sauter l'étape d'autorisation" -#: awx/api/serializers.py:1290 +#: awx/api/serializers.py:1303 +msgid "Cannot change max_hosts." +msgstr "Impossible de modifier max_hosts." + +#: awx/api/serializers.py:1336 msgid "This path is already being used by another manual project." msgstr "Ce chemin est déjà utilisé par un autre projet manuel." -#: awx/api/serializers.py:1316 -msgid "This field has been deprecated and will be removed in a future release" -msgstr "Ce champ obsolète et sera supprimé dans une prochaine version." +#: awx/api/serializers.py:1338 +msgid "SCM refspec can only be used with git projects." +msgstr "SCM refspec ne peut être utilisé qu'avec les projets git." -#: awx/api/serializers.py:1375 -msgid "Organization is missing" -msgstr "L'organisation est manquante" +#: awx/api/serializers.py:1415 +msgid "" +"One or more job templates depend on branch override behavior for this " +"project (ids: {})." +msgstr "Un ou plusieurs modèles de tâches dépendent du comportement de remplacement de branche pour ce projet (ids : {})." -#: awx/api/serializers.py:1379 +#: awx/api/serializers.py:1422 msgid "Update options must be set to false for manual projects." -msgstr "" -"La Mise à jour des options doit être définie à false pour les projets " -"manuels." +msgstr "La Mise à jour des options doit être définie à false pour les projets manuels." -#: awx/api/serializers.py:1385 +#: awx/api/serializers.py:1428 msgid "Array of playbooks available within this project." msgstr "Tableau des playbooks disponibles dans ce projet." -#: awx/api/serializers.py:1404 +#: awx/api/serializers.py:1447 msgid "" "Array of inventory files and directories available within this project, not " "comprehensive." -msgstr "" -"Tableau des fichiers d'inventaires et des répertoires disponibles dans ce " -"projet : incomplet." +msgstr "Tableau des fichiers d'inventaires et des répertoires disponibles dans ce projet : incomplet." -#: awx/api/serializers.py:1452 awx/api/serializers.py:3247 -#: awx/api/serializers.py:3454 +#: awx/api/serializers.py:1495 awx/api/serializers.py:3048 +#: awx/api/serializers.py:3260 msgid "A count of hosts uniquely assigned to each status." msgstr "Un nombre d'hôtes assignés exclusivement à chaque statut." -#: awx/api/serializers.py:1455 awx/api/serializers.py:3250 +#: awx/api/serializers.py:1498 awx/api/serializers.py:3051 msgid "A count of all plays and tasks for the job run." msgstr "Le nombre de lectures et de tâches liées à l'exécution d'un job." -#: awx/api/serializers.py:1570 +#: awx/api/serializers.py:1625 msgid "Smart inventories must specify host_filter" msgstr "Les inventaires smart doivent spécifier le host_filter" -#: awx/api/serializers.py:1674 +#: awx/api/serializers.py:1713 #, python-format msgid "Invalid port specification: %s" -msgstr "Spécification de port non valide : %s" +msgstr "Spécification de port non valide : %s" -#: awx/api/serializers.py:1685 +#: awx/api/serializers.py:1724 msgid "Cannot create Host for Smart Inventory" msgstr "Impossible de créer un Hôte pour l' Inventaire Smart" -#: awx/api/serializers.py:1797 +#: awx/api/serializers.py:1808 msgid "Invalid group name." msgstr "Nom de groupe incorrect." -#: awx/api/serializers.py:1802 +#: awx/api/serializers.py:1813 msgid "Cannot create Group for Smart Inventory" msgstr "Impossible de créer de Groupe pour l' Inventaire Smart" -#: awx/api/serializers.py:1877 +#: awx/api/serializers.py:1888 msgid "" "Script must begin with a hashbang sequence: i.e.... #!/usr/bin/env python" -msgstr "" -"Le script doit commencer par une séquence hashbang : c.-à-d. ... " -"#!/usr/bin/env python" +msgstr "Le script doit commencer par une séquence hashbang : c.-à-d. ... #!/usr/bin/env python" -#: awx/api/serializers.py:1926 +#: awx/api/serializers.py:1917 +msgid "Cloud credential to use for inventory updates." +msgstr "Informations d’identification cloud à utiliser pour les mises à jour d'inventaire." + +#: awx/api/serializers.py:1938 msgid "`{}` is a prohibited environment variable" msgstr "`{}`est une variable d'environnement interdite" -#: awx/api/serializers.py:1937 +#: awx/api/serializers.py:1949 msgid "If 'source' is 'custom', 'source_script' must be provided." msgstr "Si la valeur 'source' est 'custom', 'source_script' doit être défini." -#: awx/api/serializers.py:1943 +#: awx/api/serializers.py:1955 msgid "Must provide an inventory." msgstr "Vous devez fournir un inventaire." -#: awx/api/serializers.py:1947 +#: awx/api/serializers.py:1959 msgid "" "The 'source_script' does not belong to the same organization as the " "inventory." -msgstr "" -"Le 'source_script' n'appartient pas à la même organisation que l'inventaire." +msgstr "Le 'source_script' n'appartient pas à la même organisation que l'inventaire." -#: awx/api/serializers.py:1949 +#: awx/api/serializers.py:1961 msgid "'source_script' doesn't exist." msgstr "'source_script' n'existe pas." -#: awx/api/serializers.py:1985 -msgid "Automatic group relationship, will be removed in 3.3" -msgstr "Relation de groupe automatique, sera supprimée dans la version 3.3" - -#: awx/api/serializers.py:2072 +#: awx/api/serializers.py:2063 msgid "Cannot use manual project for SCM-based inventory." msgstr "Impossible d'utiliser un projet manuel pour un inventaire SCM." -#: awx/api/serializers.py:2078 -msgid "" -"Manual inventory sources are created automatically when a group is created " -"in the v1 API." -msgstr "" -"Les sources d'inventaire manuel sont créées automatiquement lorsqu'un groupe" -" est créé dans l'API v1." - -#: awx/api/serializers.py:2083 +#: awx/api/serializers.py:2068 msgid "Setting not compatible with existing schedules." msgstr "Paramètre incompatible avec les planifications existantes." -#: awx/api/serializers.py:2088 +#: awx/api/serializers.py:2073 msgid "Cannot create Inventory Source for Smart Inventory" msgstr "Impossible de créer une Source d'inventaire pour l' Inventaire Smart" -#: awx/api/serializers.py:2139 +#: awx/api/serializers.py:2121 +msgid "Project required for scm type sources." +msgstr "Projet requis pour les sources de type scm." + +#: awx/api/serializers.py:2130 #, python-format msgid "Cannot set %s if not SCM type." -msgstr "Impossible de définir %s si pas du type SCM ." +msgstr "Impossible de définir %s si pas du type SCM." + +#: awx/api/serializers.py:2200 +msgid "The project used for this job." +msgstr "Le projet utilisé pour ce job." -#: awx/api/serializers.py:2414 +#: awx/api/serializers.py:2455 msgid "Modifications not allowed for managed credential types" -msgstr "" -"Modifications non autorisées pour les types d'informations d'identification " -"gérés" +msgstr "Modifications non autorisées pour les types d'informations d'identification gérés" -#: awx/api/serializers.py:2419 +#: awx/api/serializers.py:2467 msgid "" "Modifications to inputs are not allowed for credential types that are in use" -msgstr "" -"Modifications apportées aux entrées non autorisées pour les types " -"d'informations d'identification en cours d'utilisation" +msgstr "Modifications apportées aux entrées non autorisées pour les types d'informations d'identification en cours d'utilisation" -#: awx/api/serializers.py:2425 +#: awx/api/serializers.py:2472 #, python-format msgid "Must be 'cloud' or 'net', not %s" msgstr "Doit être 'cloud' ou 'net', pas %s" -#: awx/api/serializers.py:2431 +#: awx/api/serializers.py:2478 msgid "'ask_at_runtime' is not supported for custom credentials." -msgstr "" -"'ask_at_runtime' n'est pas pris en charge pour les informations " -"d'identification personnalisées." +msgstr "'ask_at_runtime' n'est pas pris en charge pour les informations d'identification personnalisées." -#: awx/api/serializers.py:2502 +#: awx/api/serializers.py:2526 msgid "Credential Type" msgstr "Type d'informations d’identification" -#: awx/api/serializers.py:2617 -#, python-format -msgid "\"%s\" is not a valid choice" -msgstr "\"%s\" n'est pas un choix valide." - -#: awx/api/serializers.py:2636 -#, python-brace-format -msgid "'{field_name}' is not a valid field for {credential_type_name}" -msgstr "'{field_name}' n'est pas un champ valide pour {credential_type_name}" - -#: awx/api/serializers.py:2657 +#: awx/api/serializers.py:2607 msgid "" -"You cannot change the credential type of the credential, as it may break the" -" functionality of the resources using it." -msgstr "" -"Vous ne pouvez pas modifier le type d'identifiants, car cela peut " -"compromettre la fonctionnalité de la ressource les utilisant." +"You cannot change the credential type of the credential, as it may break the " +"functionality of the resources using it." +msgstr "Vous ne pouvez pas modifier le type d'identifiants, car cela peut compromettre la fonctionnalité de la ressource les utilisant." -#: awx/api/serializers.py:2669 +#: awx/api/serializers.py:2619 msgid "" "Write-only field used to add user to owner role. If provided, do not give " "either team or organization. Only valid for creation." -msgstr "" -"Champ en écriture seule qui sert à ajouter un utilisateur au rôle de " -"propriétaire. Si vous le définissez, n'entrez ni équipe ni organisation. " -"Seulement valable pour la création." +msgstr "Champ en écriture seule qui sert à ajouter un utilisateur au rôle de propriétaire. Si vous le définissez, n'entrez ni équipe ni organisation. Seulement valable pour la création." -#: awx/api/serializers.py:2674 +#: awx/api/serializers.py:2624 msgid "" "Write-only field used to add team to owner role. If provided, do not give " "either user or organization. Only valid for creation." -msgstr "" -"Champ en écriture seule qui sert à ajouter une équipe au rôle de " -"propriétaire. Si vous le définissez, n'entrez ni utilisateur ni " -"organisation. Seulement valable pour la création." +msgstr "Champ en écriture seule qui sert à ajouter une équipe au rôle de propriétaire. Si vous le définissez, n'entrez ni utilisateur ni organisation. Seulement valable pour la création." -#: awx/api/serializers.py:2679 +#: awx/api/serializers.py:2629 msgid "" -"Inherit permissions from organization roles. If provided on creation, do not" -" give either user or team." -msgstr "" -"Hériter des permissions à partir des rôles d'organisation. Si vous le " -"définissez lors de la création, n'entrez ni utilisateur ni équipe." +"Inherit permissions from organization roles. If provided on creation, do not " +"give either user or team." +msgstr "Hériter des permissions à partir des rôles d'organisation. Si vous le définissez lors de la création, n'entrez ni utilisateur ni équipe." -#: awx/api/serializers.py:2695 +#: awx/api/serializers.py:2645 msgid "Missing 'user', 'team', or 'organization'." msgstr "Valeur 'utilisateur', 'équipe' ou 'organisation' manquante." -#: awx/api/serializers.py:2735 +#: awx/api/serializers.py:2662 msgid "" "Credential organization must be set and match before assigning to a team" -msgstr "" -"L'organisation des informations d'identification doit être définie et mise " -"en correspondance avant de l'attribuer à une équipe" +msgstr "L'organisation des informations d'identification doit être définie et mise en correspondance avant de l'attribuer à une équipe" -#: awx/api/serializers.py:2936 -msgid "You must provide a cloud credential." -msgstr "Vous devez fournir une information d'identification cloud." - -#: awx/api/serializers.py:2937 -msgid "You must provide a network credential." -msgstr "Vous devez fournir des informations d'identification réseau." - -#: awx/api/serializers.py:2938 awx/main/models/jobs.py:155 -msgid "You must provide an SSH credential." -msgstr "Vous devez fournir des informations d'identification SSH." - -#: awx/api/serializers.py:2939 -msgid "You must provide a vault credential." -msgstr "Vous devez fournir un archivage sécurisé." - -#: awx/api/serializers.py:2958 +#: awx/api/serializers.py:2793 msgid "This field is required." msgstr "Ce champ est obligatoire." -#: awx/api/serializers.py:2960 awx/api/serializers.py:2962 +#: awx/api/serializers.py:2802 msgid "Playbook not found for project." msgstr "Playbook introuvable pour le projet." -#: awx/api/serializers.py:2964 +#: awx/api/serializers.py:2804 msgid "Must select playbook for project." msgstr "Un playbook doit être sélectionné pour le project." -#: awx/api/serializers.py:3045 +#: awx/api/serializers.py:2806 awx/api/serializers.py:2808 +msgid "Project does not allow overriding branch." +msgstr "Le projet ne permet pas de branche de remplacement." + +#: awx/api/serializers.py:2845 +msgid "Must be a Personal Access Token." +msgstr "Doit être un jeton d'accès personnel." + +#: awx/api/serializers.py:2848 +msgid "Must match the selected webhook service." +msgstr "Doit correspondre au service de webhook sélectionné." + +#: awx/api/serializers.py:2919 msgid "Cannot enable provisioning callback without an inventory set." -msgstr "" -"Impossible d'activer le rappel de provisioning sans inventaire défini." +msgstr "Impossible d'activer le rappel de provisioning sans inventaire défini." -#: awx/api/serializers.py:3048 +#: awx/api/serializers.py:2922 msgid "Must either set a default value or ask to prompt on launch." -msgstr "" -"Une valeur par défaut doit être définie ou bien demander une invite au " -"moment du lancement." - -#: awx/api/serializers.py:3050 awx/main/models/jobs.py:310 -msgid "Job types 'run' and 'check' must have assigned a project." -msgstr "Un projet doit être assigné aux types de tâche 'run' et 'check'." +msgstr "Une valeur par défaut doit être définie ou bien demander une invite au moment du lancement." -#: awx/api/serializers.py:3169 -msgid "Invalid job template." -msgstr "Modèle de tâche non valide." +#: awx/api/serializers.py:2924 awx/main/models/jobs.py:299 +msgid "Job Templates must have a project assigned." +msgstr "Les Modèles de job doivent avoir un projet attribué." -#: awx/api/serializers.py:3290 +#: awx/api/serializers.py:3092 msgid "No change to job limit" msgstr "Aucun changement à la limite du job" -#: awx/api/serializers.py:3291 +#: awx/api/serializers.py:3093 msgid "All failed and unreachable hosts" msgstr "Tous les hôtes inaccessibles ou ayant échoué" -#: awx/api/serializers.py:3306 +#: awx/api/serializers.py:3108 msgid "Missing passwords needed to start: {}" msgstr "Mots de passe manquants indispensables pour commencer : {}" -#: awx/api/serializers.py:3325 +#: awx/api/serializers.py:3127 msgid "Relaunch by host status not available until job finishes running." -msgstr "" -"Relance par statut d'hôte non disponible jusqu'à la fin de l'exécution du " -"job en cours." +msgstr "Relance par statut d'hôte non disponible jusqu'à la fin de l'exécution du job en cours." -#: awx/api/serializers.py:3339 +#: awx/api/serializers.py:3141 msgid "Job Template Project is missing or undefined." msgstr "Le projet de modèle de tâche est manquant ou non défini." -#: awx/api/serializers.py:3341 +#: awx/api/serializers.py:3143 msgid "Job Template Inventory is missing or undefined." msgstr "Le projet de modèle d'inventaire est manquant ou non défini." -#: awx/api/serializers.py:3379 -msgid "" -"Unknown, job may have been ran before launch configurations were saved." -msgstr "" -"Inconnu, il se peut que le job ait été exécuté avant que les configurations " -"de lancement ne soient sauvegardées." +#: awx/api/serializers.py:3181 +msgid "Unknown, job may have been ran before launch configurations were saved." +msgstr "Inconnu, il se peut que le job ait été exécuté avant que les configurations de lancement ne soient sauvegardées." -#: awx/api/serializers.py:3446 awx/main/tasks.py:2297 +#: awx/api/serializers.py:3252 awx/main/tasks.py:2800 awx/main/tasks.py:2818 msgid "{} are prohibited from use in ad hoc commands." msgstr "{} ne sont pas autorisés à utiliser les commandes ad hoc." -#: awx/api/serializers.py:3534 awx/api/views.py:4893 +#: awx/api/serializers.py:3340 awx/api/views/__init__.py:4243 #, python-brace-format msgid "" "Standard Output too large to display ({text_size} bytes), only download " "supported for sizes over {supported_size} bytes." -msgstr "" -"Sortie standard trop grande pour pouvoir s'afficher ({text_size} octets). Le" -" téléchargement est pris en charge seulement pour une taille supérieure à " -"{supported_size} octets." +msgstr "Sortie standard trop grande pour pouvoir s'afficher ({text_size} octets). Le téléchargement est pris en charge seulement pour une taille supérieure à {supported_size} octets." -#: awx/api/serializers.py:3727 +#: awx/api/serializers.py:3653 msgid "Provided variable {} has no database value to replace with." -msgstr "" -"La variable fournie {} n'a pas de valeur de base de données de remplaçage." +msgstr "La variable fournie {} n'a pas de valeur de base de données de remplaçage." -#: awx/api/serializers.py:3745 -#, python-brace-format -msgid "\"$encrypted$ is a reserved keyword, may not be used for {var_name}.\"" -msgstr "" -"\"$encrypted$ est un mot clé réservé et ne peut pas être utilisé comme " -"{var_name}.\"" +#: awx/api/serializers.py:3671 +msgid "\"$encrypted$ is a reserved keyword, may not be used for {}.\"" +msgstr "\"$encrypted$ est un mot clé réservé et ne peut pas être utilisé comme {}.\"" -#: awx/api/serializers.py:3815 -#, python-format -msgid "Cannot nest a %s inside a WorkflowJobTemplate" -msgstr "Impossible d'imbriquer %s dans un modèle de tâche Workflow." +#: awx/api/serializers.py:4078 +msgid "A project is required to run a job." +msgstr "Un projet est nécessaire pour exécuter une tâche." -#: awx/api/serializers.py:3822 awx/api/views.py:818 -msgid "Related template is not configured to accept credentials on launch." -msgstr "" -"Le modèle associé n'est pas configuré pour pouvoir accepter les identifiants" -" au lancement." +#: awx/api/serializers.py:4080 +msgid "Missing a revision to run due to failed project update." +msgstr "Une révision n'a pas été exécutée en raison de l'échec de la mise à jour du projet." -#: awx/api/serializers.py:4282 +#: awx/api/serializers.py:4084 msgid "The inventory associated with this Job Template is being deleted." -msgstr "" -"L'inventaire associé à ce modèle de tâche est en cours de suppression." +msgstr "L'inventaire associé à ce modèle de tâche est en cours de suppression." -#: awx/api/serializers.py:4284 +#: awx/api/serializers.py:4086 awx/api/serializers.py:4202 msgid "The provided inventory is being deleted." msgstr "L'inventaire fourni est en cours de suppression." -#: awx/api/serializers.py:4292 +#: awx/api/serializers.py:4094 msgid "Cannot assign multiple {} credentials." msgstr "Ne peut pas attribuer plusieurs identifiants {}." -#: awx/api/serializers.py:4296 +#: awx/api/serializers.py:4098 msgid "Cannot assign a Credential of kind `{}`" msgstr "Ne peut pas attribuer d'information d'identification de type `{}`" -#: awx/api/serializers.py:4309 +#: awx/api/serializers.py:4111 msgid "" "Removing {} credential at launch time without replacement is not supported. " "Provided list lacked credential(s): {}." -msgstr "" -"Le retrait des identifiants {} au moment du lancement sans procurer de " -"valeurs de remplacement n'est pas pris en charge. La liste fournie manquait " -"d'identifiant(s): {}." +msgstr "Le retrait des identifiants {} au moment du lancement sans procurer de valeurs de remplacement n'est pas pris en charge. La liste fournie manquait d'identifiant(s): {}." -#: awx/api/serializers.py:4435 +#: awx/api/serializers.py:4200 +msgid "The inventory associated with this Workflow is being deleted." +msgstr "L'inventaire associé à ce flux de travail est en cours de suppression." + +#: awx/api/serializers.py:4271 +msgid "Message type '{}' invalid, must be either 'message' or 'body'" +msgstr "Type de message '{}' invalide, doit être soit 'message' soit 'body'" + +#: awx/api/serializers.py:4277 +msgid "Expected string for '{}', found {}, " +msgstr "Chaîne attendue pour '{}', trouvé {}, " + +#: awx/api/serializers.py:4281 +msgid "Messages cannot contain newlines (found newline in {} event)" +msgstr "Les messages ne peuvent pas contenir de nouvelles lignes (trouvé nouvelle ligne dans l'événement {})" + +#: awx/api/serializers.py:4287 +msgid "Expected dict for 'messages' field, found {}" +msgstr "Dict attendu pour le champ 'messages', trouvé {}" + +#: awx/api/serializers.py:4291 +msgid "" +"Event '{}' invalid, must be one of 'started', 'success', 'error', or " +"'workflow_approval'" +msgstr "L'événement '{}' est invalide, il doit être de type 'started', 'success', 'error' ou 'workflow_approval'" + +#: awx/api/serializers.py:4297 +msgid "Expected dict for event '{}', found {}" +msgstr "Dict attendu pour l'événement '{}', trouvé {}" + +#: awx/api/serializers.py:4302 +msgid "" +"Workflow Approval event '{}' invalid, must be one of 'running', 'approved', " +"'timed_out', or 'denied'" +msgstr "L'événement d'approbation de workflow '{}' n'est pas valide, il doit être 'running', 'approved', 'timed_out' ou 'denied'" + +#: awx/api/serializers.py:4309 +msgid "Expected dict for workflow approval event '{}', found {}" +msgstr "Dict attendu pour l'événement d'approbation du workflow '{}', trouvé {}" + +#: awx/api/serializers.py:4336 +msgid "Unable to render message '{}': {}" +msgstr "Impossible de rendre le message '{}' : {}" + +#: awx/api/serializers.py:4338 +msgid "Field '{}' unavailable" +msgstr "Champ '{}' non disponible" + +#: awx/api/serializers.py:4340 +msgid "Security error due to field '{}'" +msgstr "Erreur de sécurité due au champ '{}'" + +#: awx/api/serializers.py:4360 +msgid "Webhook body for '{}' should be a json dictionary. Found type '{}'." +msgstr "Le corps du webhook pour '{}' doit être un dictionnaire json. Trouvé le type '{}'." + +#: awx/api/serializers.py:4363 +msgid "Webhook body for '{}' is not a valid json dictionary ({})." +msgstr "Le corps du webhook pour '{}' n'est pas un dictionnaire json valide ({})." + +#: awx/api/serializers.py:4381 msgid "" "Missing required fields for Notification Configuration: notification_type" -msgstr "" -"Champs obligatoires manquants pour la configuration des notifications : " -"notification_type" +msgstr "Champs obligatoires manquants pour la configuration des notifications : notification_type" -#: awx/api/serializers.py:4458 +#: awx/api/serializers.py:4408 msgid "No values specified for field '{}'" msgstr "Aucune valeur spécifiée pour le champ '{}'" -#: awx/api/serializers.py:4463 +#: awx/api/serializers.py:4413 +msgid "HTTP method must be either 'POST' or 'PUT'." +msgstr "La méthode HTTP doit être soit 'POST' soit 'PUT'." + +#: awx/api/serializers.py:4415 msgid "Missing required fields for Notification Configuration: {}." -msgstr "" -"Champs obligatoires manquants pour la configuration des notifications : {}." +msgstr "Champs obligatoires manquants pour la configuration des notifications : {}." -#: awx/api/serializers.py:4466 +#: awx/api/serializers.py:4418 msgid "Configuration field '{}' incorrect type, expected {}." msgstr "Type de champ de configuration '{}' incorrect, {} attendu." -#: awx/api/serializers.py:4528 +#: awx/api/serializers.py:4435 +msgid "Notification body" +msgstr "Corps de notification" + +#: awx/api/serializers.py:4515 msgid "" -"Valid DTSTART required in rrule. Value should start with: " -"DTSTART:YYYYMMDDTHHMMSSZ" -msgstr "" -"DTSTART valide obligatoire dans rrule. La valeur doit commencer par : " -"DTSTART:YYYYMMDDTHHMMSSZ" +"Valid DTSTART required in rrule. Value should start with: DTSTART:" +"YYYYMMDDTHHMMSSZ" +msgstr "DTSTART valide obligatoire dans rrule. La valeur doit commencer par : DTSTART:YYYYMMDDTHHMMSSZ" -#: awx/api/serializers.py:4530 +#: awx/api/serializers.py:4517 msgid "" "DTSTART cannot be a naive datetime. Specify ;TZINFO= or YYYYMMDDTHHMMSSZZ." -msgstr "" -"DTSTART ne peut correspondre à une DateHeure naïve. Spécifier ;TZINFO= ou " -"YYYYMMDDTHHMMSSZZ." +msgstr "DTSTART ne peut correspondre à une DateHeure naïve. Spécifier ;TZINFO= ou YYYYMMDDTHHMMSSZZ." -#: awx/api/serializers.py:4532 +#: awx/api/serializers.py:4519 msgid "Multiple DTSTART is not supported." msgstr "Une seule valeur DTSTART est prise en charge." -#: awx/api/serializers.py:4534 +#: awx/api/serializers.py:4521 msgid "RRULE required in rrule." msgstr "RRULE obligatoire dans rrule." -#: awx/api/serializers.py:4536 +#: awx/api/serializers.py:4523 msgid "Multiple RRULE is not supported." msgstr "Une seule valeur RRULE est prise en charge." -#: awx/api/serializers.py:4538 +#: awx/api/serializers.py:4525 msgid "INTERVAL required in rrule." msgstr "INTERVAL obligatoire dans rrule." -#: awx/api/serializers.py:4540 +#: awx/api/serializers.py:4527 msgid "SECONDLY is not supported." msgstr "SECONDLY n'est pas pris en charge." -#: awx/api/serializers.py:4542 +#: awx/api/serializers.py:4529 msgid "Multiple BYMONTHDAYs not supported." msgstr "Une seule valeur BYMONTHDAY est prise en charge." -#: awx/api/serializers.py:4544 +#: awx/api/serializers.py:4531 msgid "Multiple BYMONTHs not supported." msgstr "Une seule valeur BYMONTH est prise en charge." -#: awx/api/serializers.py:4546 +#: awx/api/serializers.py:4533 msgid "BYDAY with numeric prefix not supported." msgstr "BYDAY avec un préfixe numérique non pris en charge." -#: awx/api/serializers.py:4548 +#: awx/api/serializers.py:4535 msgid "BYYEARDAY not supported." msgstr "BYYEARDAY non pris en charge." -#: awx/api/serializers.py:4550 +#: awx/api/serializers.py:4537 msgid "BYWEEKNO not supported." msgstr "BYWEEKNO non pris en charge." -#: awx/api/serializers.py:4552 +#: awx/api/serializers.py:4539 msgid "RRULE may not contain both COUNT and UNTIL" msgstr "RRULE peut contenir à la fois COUNT et UNTIL" -#: awx/api/serializers.py:4556 +#: awx/api/serializers.py:4543 msgid "COUNT > 999 is unsupported." msgstr "COUNT > 999 non pris en charge." -#: awx/api/serializers.py:4560 +#: awx/api/serializers.py:4549 msgid "rrule parsing failed validation: {}" msgstr "L'analyse rrule n'a pas pu être validée : {}" -#: awx/api/serializers.py:4601 +#: awx/api/serializers.py:4611 msgid "Inventory Source must be a cloud resource." msgstr "La source d'inventaire doit être une ressource cloud." -#: awx/api/serializers.py:4603 +#: awx/api/serializers.py:4613 msgid "Manual Project cannot have a schedule set." msgstr "Le projet manuel ne peut pas avoir de calendrier défini." #: awx/api/serializers.py:4616 msgid "" +"Inventory sources with `update_on_project_update` cannot be scheduled. " +"Schedule its source project `{}` instead." +msgstr "Impossible de planifier les sources d'inventaire avec `update_on_project_update`. Planifiez plutôt son projet source`{}`." + +#: awx/api/serializers.py:4626 +msgid "" "Count of jobs in the running or waiting state that are targeted for this " "instance" -msgstr "" -"Le nombre de jobs en cours d'exécution ou en attente qui sont ciblés pour " -"cette instance." +msgstr "Le nombre de jobs en cours d'exécution ou en attente qui sont ciblés pour cette instance." -#: awx/api/serializers.py:4621 +#: awx/api/serializers.py:4631 msgid "Count of all jobs that target this instance" msgstr "Le nombre de jobs qui ciblent cette instance." -#: awx/api/serializers.py:4654 +#: awx/api/serializers.py:4664 msgid "" "Count of jobs in the running or waiting state that are targeted for this " "instance group" -msgstr "" -"Le nombre de jobs en cours d'exécution ou en attente qui sont ciblés pour ce" -" groupe d'instances." +msgstr "Le nombre de jobs en cours d'exécution ou en attente qui sont ciblés pour ce groupe d'instances." -#: awx/api/serializers.py:4659 +#: awx/api/serializers.py:4669 msgid "Count of all jobs that target this instance group" msgstr "Le nombre de jobs qui ciblent ce groupe d'instances" -#: awx/api/serializers.py:4667 +#: awx/api/serializers.py:4674 +msgid "Indicates whether instance group controls any other group" +msgstr "Indique si le groupe d'instances contrôle un autre groupe" + +#: awx/api/serializers.py:4678 +msgid "" +"Indicates whether instances in this group are isolated.Isolated groups have " +"a designated controller group." +msgstr "Indique si les instances de ce groupe sont isolées. Les groupes isolés ont un groupe de contrôleurs désigné." + +#: awx/api/serializers.py:4683 +msgid "" +"Indicates whether instances in this group are containerized.Containerized " +"groups have a designated Openshift or Kubernetes cluster." +msgstr "Indique si les instances de ce groupe sont conteneurisées. Les groupes conteneurisés ont un groupe Openshift ou Kubernetes désigné." + +#: awx/api/serializers.py:4691 msgid "Policy Instance Percentage" msgstr "Pourcentage d'instances de stratégie" -#: awx/api/serializers.py:4668 +#: awx/api/serializers.py:4692 msgid "" "Minimum percentage of all instances that will be automatically 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." +msgstr "Le pourcentage minimum de toutes les instances qui seront automatiquement assignées à ce groupe lorsque de nouvelles instances seront mises en ligne." -#: awx/api/serializers.py:4673 +#: awx/api/serializers.py:4697 msgid "Policy Instance Minimum" msgstr "Instances de stratégies minimum" -#: awx/api/serializers.py:4674 +#: awx/api/serializers.py:4698 msgid "" -"Static minimum number of Instances that will be automatically assign 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." +"Static minimum number of Instances that will be automatically assign 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." -#: awx/api/serializers.py:4679 +#: awx/api/serializers.py:4703 msgid "Policy Instance List" msgstr "Listes d'instances de stratégie" -#: awx/api/serializers.py:4680 +#: awx/api/serializers.py:4704 msgid "List of exact-match Instances that will be assigned to this group" msgstr "Liste des cas de concordance exacte qui seront assignés à ce groupe." -#: awx/api/serializers.py:4702 +#: awx/api/serializers.py:4730 msgid "Duplicate entry {}." msgstr "Entrée dupliquée {}." -#: awx/api/serializers.py:4704 +#: awx/api/serializers.py:4732 msgid "{} is not a valid hostname of an existing instance." msgstr "{} n'est pas un nom d'hôte valide d'instance existante." -#: awx/api/serializers.py:4706 awx/api/views.py:202 +#: awx/api/serializers.py:4734 awx/api/views/mixin.py:98 msgid "" -"Isolated instances may not be added or removed from instances groups via the" -" API." -msgstr "" -"Des instances isolées ne peuvent pas être ajoutées ou supprimées de groupes " -"d'instances via l'API." +"Isolated instances may not be added or removed from instances groups via the " +"API." +msgstr "Des instances isolées ne peuvent pas être ajoutées ou supprimées de groupes d'instances via l'API." -#: awx/api/serializers.py:4708 awx/api/views.py:206 +#: awx/api/serializers.py:4736 awx/api/views/mixin.py:102 msgid "Isolated instance group membership may not be managed via the API." -msgstr "" -"L'appartenance à un groupe d'instances isolées n'est sans doute pas gérée " -"par l'API." +msgstr "L'appartenance à un groupe d'instances isolées n'est sans doute pas gérée par l'API." + +#: awx/api/serializers.py:4738 awx/api/serializers.py:4743 +#: awx/api/serializers.py:4748 +msgid "Containerized instances may not be managed via the API" +msgstr "Les instances conteneurisées ne peuvent pas être gérées via l'API" -#: awx/api/serializers.py:4713 +#: awx/api/serializers.py:4753 msgid "tower instance group name may not be changed." msgstr "Le nom de groupe de l'instance Tower ne peut pas être modifié." -#: awx/api/serializers.py:4783 +#: awx/api/serializers.py:4758 +msgid "Only Kubernetes credentials can be associated with an Instance Group" +msgstr "Seuls les identifiants Kubernetes peuvent être associés à un groupe d'instances" + +#: awx/api/serializers.py:4797 +msgid "" +"When present, shows the field name of the role or relationship that changed." +msgstr "Le cas échéant, affiche le nom de champ du rôle ou de la relation qui a changé." + +#: awx/api/serializers.py:4799 +msgid "" +"When present, shows the model on which the role or relationship was defined." +msgstr "Le cas échéant, affiche le modèle sur lequel le rôle ou la relation a été défini." + +#: awx/api/serializers.py:4832 msgid "" "A summary of the new and changed values when an object is created, updated, " "or deleted" -msgstr "" -"Un récapitulatif des valeurs nouvelles et modifiées lorsqu'un objet est " -"créé, mis à jour ou supprimé" +msgstr "Un récapitulatif des valeurs nouvelles et modifiées lorsqu'un objet est créé, mis à jour ou supprimé" -#: awx/api/serializers.py:4785 +#: awx/api/serializers.py:4834 msgid "" "For create, update, and delete events this is the object type that was " "affected. For associate and disassociate events this is the object type " "associated or disassociated with object2." -msgstr "" -"Pour créer, mettre à jour et supprimer des événements, il s'agit du type " -"d'objet qui a été affecté. Pour associer et dissocier des événements, il " -"s'agit du type d'objet associé à ou dissocié de object2." +msgstr "Pour créer, mettre à jour et supprimer des événements, il s'agit du type d'objet qui a été affecté. Pour associer et dissocier des événements, il s'agit du type d'objet associé à ou dissocié de object2." -#: awx/api/serializers.py:4788 +#: awx/api/serializers.py:4837 msgid "" "Unpopulated for create, update, and delete events. For associate and " -"disassociate events this is the object type that object1 is being associated" -" with." -msgstr "" -"Laisser vide pour créer, mettre à jour et supprimer des événements. Pour " -"associer et dissocier des événements, il s'agit du type d'objet auquel " -"object1 est associé." +"disassociate events this is the object type that object1 is being associated " +"with." +msgstr "Laisser vide pour créer, mettre à jour et supprimer des événements. Pour associer et dissocier des événements, il s'agit du type d'objet auquel object1 est associé." -#: awx/api/serializers.py:4791 +#: awx/api/serializers.py:4840 msgid "The action taken with respect to the given object(s)." msgstr "Action appliquée par rapport à l'objet ou aux objets donnés." -#: awx/api/views.py:119 -msgid "Your license does not allow use of the activity stream." -msgstr "Votre licence ne permet pas l'utilisation du flux d'activité." - -#: awx/api/views.py:129 -msgid "Your license does not permit use of system tracking." -msgstr "Votre licence ne permet pas l'utilisation du suivi du système." - -#: awx/api/views.py:139 -msgid "Your license does not allow use of workflows." -msgstr "Votre licence ne permet pas l'utilisation de workflows." - -#: awx/api/views.py:153 -msgid "Cannot delete job resource when associated workflow job is running." -msgstr "" -"Impossible de supprimer les ressources de tâche lorsqu'une tâche de workflow" -" associée est en cours d'exécution." - -#: awx/api/views.py:158 -msgid "Cannot delete running job resource." -msgstr "Impossible de supprimer la ressource de la tâche en cours." - -#: awx/api/views.py:163 -msgid "Job has not finished processing events." -msgstr "Job n'ayant pas terminé de traiter les événements." - -#: awx/api/views.py:257 -msgid "Related job {} is still processing events." -msgstr "Le job associé {} est toujours en cours de traitement des événements." - -#: awx/api/views.py:264 awx/templates/rest_framework/api.html:28 -msgid "REST API" -msgstr "API REST" - -#: awx/api/views.py:275 awx/templates/rest_framework/api.html:4 -msgid "AWX REST API" -msgstr "API REST AWX" - -#: awx/api/views.py:288 -msgid "API OAuth 2 Authorization Root" -msgstr "API OAuth 2 Authorization Root" - -#: awx/api/views.py:353 -msgid "Version 1" -msgstr "Version 1" - -#: awx/api/views.py:357 -msgid "Version 2" -msgstr "Version 2" - -#: awx/api/views.py:366 -msgid "Ping" -msgstr "Ping" - -#: awx/api/views.py:397 awx/conf/apps.py:10 -msgid "Configuration" -msgstr "Configuration" - -#: awx/api/views.py:454 -msgid "Invalid license data" -msgstr "Données de licence non valides" - -#: awx/api/views.py:456 -msgid "Missing 'eula_accepted' property" -msgstr "Propriété 'eula_accepted' manquante" - -#: awx/api/views.py:460 -msgid "'eula_accepted' value is invalid" -msgstr "La valeur 'eula_accepted' n'est pas valide" - -#: awx/api/views.py:463 -msgid "'eula_accepted' must be True" -msgstr "La valeur 'eula_accepted' doit être True" - -#: awx/api/views.py:470 -msgid "Invalid JSON" -msgstr "Syntaxe JSON non valide" - -#: awx/api/views.py:478 -msgid "Invalid License" -msgstr "Licence non valide" - -#: awx/api/views.py:488 -msgid "Invalid license" -msgstr "Licence non valide" - -#: awx/api/views.py:496 -#, python-format -msgid "Failed to remove license (%s)" -msgstr "Suppression de la licence (%s) impossible" - -#: awx/api/views.py:501 +#: awx/api/views/__init__.py:181 msgid "Dashboard" msgstr "Tableau de bord" -#: awx/api/views.py:600 +#: awx/api/views/__init__.py:271 msgid "Dashboard Jobs Graphs" msgstr "Graphiques de tâches du tableau de bord" -#: awx/api/views.py:636 +#: awx/api/views/__init__.py:307 #, python-format msgid "Unknown period \"%s\"" msgstr "Période \"%s\" inconnue" -#: awx/api/views.py:650 +#: awx/api/views/__init__.py:321 msgid "Instances" msgstr "Instances" -#: awx/api/views.py:658 +#: awx/api/views/__init__.py:329 msgid "Instance Detail" msgstr "Détail de l'instance" -#: awx/api/views.py:678 +#: awx/api/views/__init__.py:346 msgid "Instance Jobs" msgstr "Jobs d'instance" -#: awx/api/views.py:692 +#: awx/api/views/__init__.py:360 msgid "Instance's Instance Groups" msgstr "Groupes d'instances de l'instance" -#: awx/api/views.py:701 +#: awx/api/views/__init__.py:369 msgid "Instance Groups" msgstr "Groupes d'instances" -#: awx/api/views.py:709 +#: awx/api/views/__init__.py:377 msgid "Instance Group Detail" msgstr "Détail du groupe d'instances" -#: awx/api/views.py:717 +#: awx/api/views/__init__.py:392 msgid "Isolated Groups can not be removed from the API" msgstr "Les groupes isolés ne peuvent pas être supprimés des l'API" -#: awx/api/views.py:719 +#: awx/api/views/__init__.py:394 msgid "" "Instance Groups acting as a controller for an Isolated Group can not be " "removed from the API" -msgstr "" -"Les groupes d'instance agissant en tant que contrôleur d'un groupe isolé ne " -"peuvent pas être retirés de l'API" +msgstr "Les groupes d'instance agissant en tant que contrôleur d'un groupe isolé ne peuvent pas être retirés de l'API" -#: awx/api/views.py:725 +#: awx/api/views/__init__.py:400 msgid "Instance Group Running Jobs" msgstr "Groupe d'instances exécutant les tâches" -#: awx/api/views.py:734 +#: awx/api/views/__init__.py:409 msgid "Instance Group's Instances" msgstr "Instances du groupe d'instances" -#: awx/api/views.py:744 +#: awx/api/views/__init__.py:419 msgid "Schedules" msgstr "Calendriers" -#: awx/api/views.py:758 +#: awx/api/views/__init__.py:433 msgid "Schedule Recurrence Rule Preview" msgstr "Prévisualisation Règle récurrente de planning" -#: awx/api/views.py:805 +#: awx/api/views/__init__.py:480 msgid "Cannot assign credential when related template is null." -msgstr "" -"Impossible d'attribuer des identifiants lorsque le modèle associé est nul." +msgstr "Impossible d'attribuer des identifiants lorsque le modèle associé est nul." -#: awx/api/views.py:810 +#: awx/api/views/__init__.py:485 msgid "Related template cannot accept {} on launch." msgstr "Le modèle associé ne peut pas accepter {} au lancement." -#: awx/api/views.py:812 +#: awx/api/views/__init__.py:487 msgid "" -"Credential that requires user input on launch cannot be used in saved launch" -" configuration." -msgstr "" -"Les identifiants qui nécessitent l'entrée de l'utilisateur au lancement ne " -"peuvent pas être utilisés dans la configuration de lancement sauvegardée." +"Credential that requires user input on launch cannot be used in saved launch " +"configuration." +msgstr "Les identifiants qui nécessitent l'entrée de l'utilisateur au lancement ne peuvent pas être utilisés dans la configuration de lancement sauvegardée." + +#: awx/api/views/__init__.py:493 +msgid "Related template is not configured to accept credentials on launch." +msgstr "Le modèle associé n'est pas configuré pour pouvoir accepter les identifiants au lancement." -#: awx/api/views.py:820 +#: awx/api/views/__init__.py:495 #, python-brace-format msgid "" "This launch configuration already provides a {credential_type} credential." -msgstr "" -"Cette configuration de lancement fournit déjà un credential " -"{credential_type}." +msgstr "Cette configuration de lancement fournit déjà un identifiant {credential_type}." -#: awx/api/views.py:823 +#: awx/api/views/__init__.py:498 #, python-brace-format msgid "Related template already uses {credential_type} credential." msgstr "Le modèle associé utilise déjà l'identifiant {credential_type}." -#: awx/api/views.py:841 +#: awx/api/views/__init__.py:516 msgid "Schedule Jobs List" msgstr "Listes des tâches de planification" -#: awx/api/views.py:996 -msgid "Your license only permits a single organization to exist." -msgstr "Votre licence permet uniquement l'existence d'une seule organisation." - -#: awx/api/views.py:1223 awx/api/views.py:5106 +#: awx/api/views/__init__.py:600 awx/api/views/__init__.py:4452 msgid "" "You cannot assign an Organization participation role as a child role for a " "Team." -msgstr "" -"Vous ne pouvez pas attribuer un rôle de Participation à une organisation en " -"tant que rôle enfant pour une Équipe." +msgstr "Vous ne pouvez pas attribuer un rôle de Participation à une organisation en tant que rôle enfant pour une Équipe." -#: awx/api/views.py:1227 awx/api/views.py:5120 +#: awx/api/views/__init__.py:604 awx/api/views/__init__.py:4466 msgid "You cannot grant system-level permissions to a team." -msgstr "" -"Vous ne pouvez pas accorder de permissions au niveau système à une équipe." +msgstr "Vous ne pouvez pas accorder de permissions au niveau système à une équipe." -#: awx/api/views.py:1234 awx/api/views.py:5112 +#: awx/api/views/__init__.py:611 awx/api/views/__init__.py:4458 msgid "" "You cannot grant credential access to a team when the Organization field " "isn't set, or belongs to a different organization" -msgstr "" -"Vous ne pouvez pas accorder d'accès par informations d'identification à une " -"équipe lorsque le champ Organisation n'est pas défini ou qu'elle appartient " -"à une organisation différente" +msgstr "Vous ne pouvez pas accorder d'accès par informations d'identification à une équipe lorsque le champ Organisation n'est pas défini ou qu'elle appartient à une organisation différente" -#: awx/api/views.py:1348 +#: awx/api/views/__init__.py:713 msgid "Project Schedules" msgstr "Calendriers des projets" -#: awx/api/views.py:1359 +#: awx/api/views/__init__.py:724 msgid "Project SCM Inventory Sources" msgstr "Sources d'inventaire SCM du projet" -#: awx/api/views.py:1460 +#: awx/api/views/__init__.py:825 msgid "Project Update Events List" msgstr "Liste des événements de mise à jour du projet" -#: awx/api/views.py:1474 +#: awx/api/views/__init__.py:839 msgid "System Job Events List" msgstr "Liste des événements d'un job système" -#: awx/api/views.py:1488 -msgid "Inventory Update Events List" -msgstr "Liste des événements de mise à jour de l'inventaire" - -#: awx/api/views.py:1522 +#: awx/api/views/__init__.py:873 msgid "Project Update SCM Inventory Updates" msgstr "Mises à jour de l'inventaire SCM de la mise à jour du projet" -#: awx/api/views.py:1581 +#: awx/api/views/__init__.py:918 msgid "Me" msgstr "Moi-même" -#: awx/api/views.py:1589 +#: awx/api/views/__init__.py:927 msgid "OAuth 2 Applications" msgstr "OAuth 2 Applications" -#: awx/api/views.py:1598 +#: awx/api/views/__init__.py:936 msgid "OAuth 2 Application Detail" msgstr "OAuth 2 Détails Application" -#: awx/api/views.py:1607 +#: awx/api/views/__init__.py:949 msgid "OAuth 2 Application Tokens" msgstr "OAuth 2 Jetons Application" -#: awx/api/views.py:1629 +#: awx/api/views/__init__.py:971 msgid "OAuth2 Tokens" msgstr "OAuth2 Jetons" -#: awx/api/views.py:1638 +#: awx/api/views/__init__.py:980 msgid "OAuth2 User Tokens" msgstr "OAuth2 Jetons Utilisateur" -#: awx/api/views.py:1650 +#: awx/api/views/__init__.py:992 msgid "OAuth2 User Authorized Access Tokens" msgstr "OAuth2 Jetons d'accès Utilisateur autorisé" -#: awx/api/views.py:1665 +#: awx/api/views/__init__.py:1007 msgid "Organization OAuth2 Applications" msgstr "Organisation OAuth2 Applications" -#: awx/api/views.py:1677 +#: awx/api/views/__init__.py:1019 msgid "OAuth2 Personal Access Tokens" msgstr "OAuth2 Jetons d'accès personnels" -#: awx/api/views.py:1692 +#: awx/api/views/__init__.py:1034 msgid "OAuth Token Detail" msgstr "OAuth 2 Détails Jeton" -#: awx/api/views.py:1752 awx/api/views.py:5073 +#: awx/api/views/__init__.py:1096 awx/api/views/__init__.py:4419 msgid "" "You cannot grant credential access to a user not in the credentials' " "organization" -msgstr "" -"Vous ne pouvez pas accorder d'accès par informations d'identification à un " -"utilisateur ne figurant pas dans l'organisation d'informations " -"d'identification." +msgstr "Vous ne pouvez pas accorder d'accès par informations d'identification à un utilisateur ne figurant pas dans l'organisation d'informations d'identification." -#: awx/api/views.py:1756 awx/api/views.py:5077 +#: awx/api/views/__init__.py:1100 awx/api/views/__init__.py:4423 msgid "You cannot grant private credential access to another user" -msgstr "" -"Vous ne pouvez pas accorder d'accès privé par informations d'identification " -"à un autre utilisateur" +msgstr "Vous ne pouvez pas accorder d'accès privé par informations d'identification à un autre utilisateur" -#: awx/api/views.py:1854 +#: awx/api/views/__init__.py:1198 #, python-format msgid "Cannot change %s." msgstr "Impossible de modifier %s." -#: awx/api/views.py:1860 +#: awx/api/views/__init__.py:1204 msgid "Cannot delete user." msgstr "Impossible de supprimer l'utilisateur." -#: awx/api/views.py:1884 +#: awx/api/views/__init__.py:1228 msgid "Deletion not allowed for managed credential types" -msgstr "" -"Suppression non autorisée pour les types d'informations d'identification " -"gérés." +msgstr "Suppression non autorisée pour les types d'informations d'identification gérés." -#: awx/api/views.py:1886 +#: awx/api/views/__init__.py:1230 msgid "Credential types that are in use cannot be deleted" -msgstr "" -"Les types d'informations d'identification utilisés ne peuvent pas être " -"supprimés." +msgstr "Les types d'informations d'identification utilisés ne peuvent pas être supprimés." -#: awx/api/views.py:2061 -msgid "Cannot delete inventory script." -msgstr "Impossible de supprimer le script d'inventaire." +#: awx/api/views/__init__.py:1381 +msgid "External Credential Test" +msgstr "Test des informations d'identification externes" -#: awx/api/views.py:2152 -#, python-brace-format -msgid "{0}" -msgstr "{0}" +#: awx/api/views/__init__.py:1408 +msgid "Credential Input Source Detail" +msgstr "Détail de la source en entrée des informations d'identification" + +#: awx/api/views/__init__.py:1416 awx/api/views/__init__.py:1424 +msgid "Credential Input Sources" +msgstr "Sources en entrée des informations d'identification" -#: awx/api/views.py:2256 +#: awx/api/views/__init__.py:1439 +msgid "External Credential Type Test" +msgstr "Test du type d'informations d'identification externes" + +#: awx/api/views/__init__.py:1497 msgid "The inventory for this host is already being deleted." msgstr "L'inventaire associé à cet hôte est en cours de suppression." -#: awx/api/views.py:2389 -msgid "Fact not found." -msgstr "Fait introuvable." - -#: awx/api/views.py:2411 +#: awx/api/views/__init__.py:1614 msgid "SSLError while trying to connect to {}" msgstr "ErreurSSL lors de la tentative de connexion au {} " -#: awx/api/views.py:2413 +#: awx/api/views/__init__.py:1616 msgid "Request to {} timed out." msgstr "Délai de requête {} expiré." -#: awx/api/views.py:2415 +#: awx/api/views/__init__.py:1618 msgid "Unknown exception {} while trying to GET {}" msgstr "Exception inconnue {} lors de la tentative GET {}" -#: awx/api/views.py:2418 +#: awx/api/views/__init__.py:1622 msgid "" "Unauthorized access. Please check your Insights Credential username and " "password." msgstr "Accès non autorisé. Veuillez vérifier vos identifiants pour Insights." -#: awx/api/views.py:2421 +#: awx/api/views/__init__.py:1626 msgid "" -"Failed to gather reports and maintenance plans from Insights API at URL {}. " -"Server responded with {} status code and message {}" -msgstr "" -"N'a pas pu collecter les rapports et plans de maintenance de l'API Insights " -"sur l'URL {}. Le serveur a répondu avec un code de statut {} et un message " -"{} " +"Failed to access the Insights API at URL {}. Server responded with {} status " +"code and message {}" +msgstr "Impossible d'accéder à l'API Insights sur l'URL {}. Le serveur a répondu avec le code de statut {} et le message {}" -#: awx/api/views.py:2428 -msgid "Expected JSON response from Insights but instead got {}" -msgstr "" -"Réponse JSON attendue de la part d'Insights, mais {} a été reçu à la place" +#: awx/api/views/__init__.py:1635 +msgid "Expected JSON response from Insights at URL {} but instead got {}" +msgstr "Réponse JSON attendue de la part d'Insights sur l'URL {}, mais {} a été reçu à la place" -#: awx/api/views.py:2435 +#: awx/api/views/__init__.py:1653 +msgid "Could not translate Insights system ID {} into an Insights platform ID." +msgstr "Impossible de convertir l'ID de système Insights ID {} en ID de plateforme Insights." + +#: awx/api/views/__init__.py:1695 msgid "This host is not recognized as an Insights host." msgstr "Cet hôte n'est pas reconnu comme hôte Insights." -#: awx/api/views.py:2440 +#: awx/api/views/__init__.py:1703 msgid "The Insights Credential for \"{}\" was not found." msgstr "Informations d'identification Insights pour \"{}\" introuvables." -#: awx/api/views.py:2508 +#: awx/api/views/__init__.py:1782 msgid "Cyclical Group association." msgstr "Association de groupe cyclique." -#: awx/api/views.py:2722 +#: awx/api/views/__init__.py:1948 +msgid "Inventory subset argument must be a string." +msgstr "L'argument de sous-ensemble d'inventaire doit correspondre à une chaîne de caractères." + +#: awx/api/views/__init__.py:1952 +msgid "Subset does not use any supported syntax." +msgstr "Le sous-ensemble n'utilise pas de syntaxe prise en charge." + +#: awx/api/views/__init__.py:2002 msgid "Inventory Source List" msgstr "Liste des sources d'inventaire" -#: awx/api/views.py:2734 +#: awx/api/views/__init__.py:2014 msgid "Inventory Sources Update" msgstr "Mise à jour des sources d'inventaire" -#: awx/api/views.py:2767 +#: awx/api/views/__init__.py:2047 msgid "Could not start because `can_update` returned False" msgstr "Démarrage impossible car `can_update` a renvoyé False" -#: awx/api/views.py:2775 +#: awx/api/views/__init__.py:2055 msgid "No inventory sources to update." msgstr "Aucune source d'inventaire à actualiser." -#: awx/api/views.py:2804 +#: awx/api/views/__init__.py:2077 msgid "Inventory Source Schedules" msgstr "Calendriers des sources d'inventaire" -#: awx/api/views.py:2832 +#: awx/api/views/__init__.py:2104 msgid "Notification Templates can only be assigned when source is one of {}." -msgstr "" -"Les modèles de notification ne peuvent être attribués que lorsque la source " -"est l'une des {}." - -#: awx/api/views.py:2887 -msgid "Vault credentials are not yet supported for inventory sources." -msgstr "" -"Les identifiants d'archivage sécurisé ne sont pas encore pris en charge pour" -" les sources d'inventaire." +msgstr "Les modèles de notification ne peuvent être attribués que lorsque la source est l'une des {}." -#: awx/api/views.py:2892 -msgid "Source already has cloud credential assigned." -msgstr "La source a déjà un identifiant cloud d'attribué." +#: awx/api/views/__init__.py:2202 +msgid "Source already has credential assigned." +msgstr "La source a déjà des informations d'identification attribuées." -#: awx/api/views.py:3042 -msgid "Field is not allowed for use with v1 API." -msgstr "Champ non autorisé avec l'API v1" +#: awx/api/views/__init__.py:2350 +msgid "'credentials' cannot be used in combination with 'extra_credentials'." +msgstr "Les 'credentials' (identifiants) ne peuvent pas être utilisés en combinaison à des 'extra_credentials'." -#: awx/api/views.py:3052 -msgid "" -"'credentials' cannot be used in combination with 'credential', " -"'vault_credential', or 'extra_credentials'." -msgstr "" -"Les 'credentials' (identifiants) ne peuvent pas être utilisés en combinaison" -" à des 'credential', 'vault_credential', ou 'extra_credentials'." +#: awx/api/views/__init__.py:2368 +msgid "Incorrect type. Expected a list received {}." +msgstr "Type non valide. Liste attendue, {} reçu." -#: awx/api/views.py:3079 -msgid "Incorrect type. Expected {}, received {}." -msgstr "Type non valide. Attendu {}, reçu {}." - -#: awx/api/views.py:3172 +#: awx/api/views/__init__.py:2466 msgid "Job Template Schedules" msgstr "Calendriers des modèles de tâche" -#: awx/api/views.py:3190 awx/api/views.py:3201 -msgid "Your license does not allow adding surveys." -msgstr "Votre licence ne permet pas l'ajout de questionnaires." - -#: awx/api/views.py:3220 +#: awx/api/views/__init__.py:2515 msgid "Field '{}' is missing from survey spec." msgstr "Le champ '{}' manque dans la specification du questionnaire." -#: awx/api/views.py:3222 +#: awx/api/views/__init__.py:2517 msgid "Expected {} for field '{}', received {} type." msgstr "{} attendu pour le champ '{}', type {} reçu." -#: awx/api/views.py:3226 +#: awx/api/views/__init__.py:2521 msgid "'spec' doesn't contain any items." msgstr "'spec' ne contient aucun élément." -#: awx/api/views.py:3235 +#: awx/api/views/__init__.py:2535 #, python-format msgid "Survey question %s is not a json object." -msgstr "La question %s n'est pas un objet json." +msgstr "La question d’enquête %s n'est pas un objet json." -#: awx/api/views.py:3237 -#, python-format -msgid "'type' missing from survey question %s." -msgstr "'type' est manquant dans la question %s." - -#: awx/api/views.py:3239 -#, python-format -msgid "'question_name' missing from survey question %s." -msgstr "'question_name' est manquant dans la question %s." +#: awx/api/views/__init__.py:2538 +#, python-brace-format +msgid "'{field_name}' missing from survey question {idx}" +msgstr "'{field_name}' est manquant dans la question d’enquête {idx}" -#: awx/api/views.py:3241 -#, python-format -msgid "'variable' missing from survey question %s." -msgstr "'variable' est manquant dans la question %s." +#: awx/api/views/__init__.py:2548 +#, python-brace-format +msgid "'{field_name}' in survey question {idx} expected to be {type_label}." +msgstr "'{field_name}' dans la question d’enquête {idx} doit être {type_label}." -#: awx/api/views.py:3243 +#: awx/api/views/__init__.py:2552 #, python-format msgid "'variable' '%(item)s' duplicated in survey question %(survey)s." -msgstr "'variable' '%(item)s' en double dans la question %(survey)s." +msgstr "'variable' '%(item)s' en double dans la question d’enquête %(survey)s." -#: awx/api/views.py:3248 -#, python-format -msgid "'required' missing from survey question %s." -msgstr "'required' est manquant dans la question %s." +#: awx/api/views/__init__.py:2562 +#, python-brace-format +msgid "" +"'{survey_item[type]}' in survey question {idx} is not one of " +"'{allowed_types}' allowed question types." +msgstr "'{survey_item[type]}' dans la question d’enquête {idx} n'est pas un des '{allowed_types}' types de questions autorisés." -#: awx/api/views.py:3253 +#: awx/api/views/__init__.py:2572 #, python-brace-format msgid "" -"Value {question_default} for '{variable_name}' expected to be a string." -msgstr "Valeur {question_default} de '{variable_name}' de chaîne attendue. " +"Default value {survey_item[default]} in survey question {idx} expected to be " +"{type_label}." +msgstr "La valeur par défaut {survey_item[default]} dans la question d’enquête {idx} doit être {type_label}." + +#: awx/api/views/__init__.py:2582 +#, python-brace-format +msgid "The {min_or_max} limit in survey question {idx} expected to be integer." +msgstr "La limite {min_or_max} dans la question d'enquête {idx} doit correspondre à un nombre entier." + +#: awx/api/views/__init__.py:2592 +#, python-brace-format +msgid "Survey question {idx} of type {survey_item[type]} must specify choices." +msgstr "La question d'enquête {idx} de type {survey_item[type]} doit spécifier des choix." + +#: awx/api/views/__init__.py:2606 +msgid "Multiple Choice (Single Select) can only have one default value." +msgstr "Les options à choix multiples (une seule sélection) ne peuvent avoir qu'une seule valeur par défaut." -#: awx/api/views.py:3263 +#: awx/api/views/__init__.py:2610 +msgid "Default choice must be answered from the choices listed." +msgstr "Une réponse doit être apportée au choix par défaut parmi les choix énumérés." + +#: awx/api/views/__init__.py:2619 #, python-brace-format msgid "" "$encrypted$ is a reserved keyword for password question defaults, survey " -"question {question_position} is type {question_type}." -msgstr "" -"$encrypted$ est un mot clé réservé pour les questions de mots de passe par " -"défaut, la questions de questionnaire {question_position} est de type " -"{question_type}." +"question {idx} is type {survey_item[type]}." +msgstr "$encrypted$ est un mot de passe réservé pour les questions (valeurs par défaut) de mots de passe, la question d'enquête {idx} est de type {survey_item[type]}." -#: awx/api/views.py:3279 +#: awx/api/views/__init__.py:2633 #, python-brace-format msgid "" "$encrypted$ is a reserved keyword, may not be used for new default in " -"position {question_position}." -msgstr "" -"$encrypted$ est un mot clé réservé, et ne peut pas être utilisé comme valeur" -" pour la position {question_position}." +"position {idx}." +msgstr "$encrypted$ est un mot de passe réservé; il ne peut pas être utilisé comme nouvelle valeur par défaut à l'emplacement {idx}." -#: awx/api/views.py:3353 +#: awx/api/views/__init__.py:2705 #, python-brace-format msgid "Cannot assign multiple {credential_type} credentials." msgstr "Ne peut pas attribuer plusieurs identifiants {credential_type}." -#: awx/api/views.py:3357 +#: awx/api/views/__init__.py:2709 msgid "Cannot assign a Credential of kind `{}`." msgstr "Ne peut pas attribuer d'information d'identification de type `{}`" -#: awx/api/views.py:3374 +#: awx/api/views/__init__.py:2726 msgid "Extra credentials must be network or cloud." -msgstr "" -"Les informations d'identification supplémentaires doivent être des " -"identifiants réseau ou cloud." +msgstr "Les informations d'identification supplémentaires doivent être des identifiants réseau ou cloud." -#: awx/api/views.py:3396 +#: awx/api/views/__init__.py:2748 msgid "Maximum number of labels for {} reached." msgstr "Nombre maximum d'étiquettes {} atteint." -#: awx/api/views.py:3519 +#: awx/api/views/__init__.py:2871 msgid "No matching host could be found!" msgstr "Aucun hôte correspondant n'a été trouvé." -#: awx/api/views.py:3522 +#: awx/api/views/__init__.py:2874 msgid "Multiple hosts matched the request!" msgstr "Plusieurs hôtes correspondent à la requête." -#: awx/api/views.py:3527 +#: awx/api/views/__init__.py:2879 msgid "Cannot start automatically, user input required!" -msgstr "" -"Impossible de démarrer automatiquement, saisie de l'utilisateur obligatoire." +msgstr "Impossible de démarrer automatiquement, saisie de l'utilisateur obligatoire." -#: awx/api/views.py:3534 +#: awx/api/views/__init__.py:2887 msgid "Host callback job already pending." msgstr "La tâche de rappel de l'hôte est déjà en attente." -#: awx/api/views.py:3549 awx/api/views.py:4336 +#: awx/api/views/__init__.py:2903 awx/api/views/__init__.py:3664 msgid "Error starting job!" msgstr "Erreur lors du démarrage de la tâche." -#: awx/api/views.py:3669 -#, python-brace-format -msgid "Cannot associate {0} when {1} have been associated." -msgstr " {0} ne peut pas être associé lorsque {1} est associé." - -#: awx/api/views.py:3694 -msgid "Multiple parent relationship not allowed." -msgstr "Relation de parents multiples non autorisée." - -#: awx/api/views.py:3699 +#: awx/api/views/__init__.py:3027 awx/api/views/__init__.py:3047 msgid "Cycle detected." msgstr "Cycle détecté." -#: awx/api/views.py:3902 +#: awx/api/views/__init__.py:3039 +msgid "Relationship not allowed." +msgstr "Relation non autorisée." + +#: awx/api/views/__init__.py:3268 +msgid "Cannot relaunch slice workflow job orphaned from job template." +msgstr "Ne peut pas relancer le découpage de job de flux de travail rendu orphelin à partir d'un modèle de job." + +#: awx/api/views/__init__.py:3270 +msgid "Cannot relaunch sliced workflow job after slice count has changed." +msgstr "Ne peut pas relancer le découpage de job de flux de travail une fois que le nombre de tranches a été modifié." + +#: awx/api/views/__init__.py:3303 msgid "Workflow Job Template Schedules" msgstr "Calendriers des modèles de tâche Workflow" -#: awx/api/views.py:4038 awx/api/views.py:4740 +#: awx/api/views/__init__.py:3446 awx/api/views/__init__.py:4087 msgid "Superuser privileges needed." msgstr "Privilèges de superutilisateur requis." -#: awx/api/views.py:4071 +#: awx/api/views/__init__.py:3479 msgid "System Job Template Schedules" msgstr "Calendriers des modèles de tâche Système" -#: awx/api/views.py:4129 -msgid "POST not allowed for Job launching in version 2 of the api" -msgstr "" -"POST non autorisé pour le lancement de tâches dans la version 2 de l'api" - -#: awx/api/views.py:4153 awx/api/views.py:4159 -msgid "PUT not allowed for Job Details in version 2 of the API" -msgstr "PUT non autorisé pour le détail des tâches dans la version 2 de l'API" - -#: awx/api/views.py:4319 +#: awx/api/views/__init__.py:3647 #, python-brace-format msgid "Wait until job finishes before retrying on {status_value} hosts." -msgstr "" -"Patientez jusqu'à ce que le job se termine avant d'essayer à nouveau les " -"hôtes {status_value}." +msgstr "Patientez jusqu'à ce que la tâche se termine avant d'essayer à nouveau les hôtes {status_value}." -#: awx/api/views.py:4324 +#: awx/api/views/__init__.py:3652 #, python-brace-format msgid "Cannot retry on {status_value} hosts, playbook stats not available." -msgstr "" -"Impossible d'essayer à nouveau sur les hôtes {status_value}, les stats de " -"playbook ne sont pas disponibles." +msgstr "Impossible d'essayer à nouveau sur les hôtes {status_value}, les stats de playbook ne sont pas disponibles." -#: awx/api/views.py:4329 +#: awx/api/views/__init__.py:3657 #, python-brace-format msgid "Cannot relaunch because previous job had 0 {status_value} hosts." -msgstr "" -"Impossible de relancer car le job précédent possède 0 {status_value} hôtes." +msgstr "Impossible de relancer car le job précédent possède 0 {status_value} hôtes." -#: awx/api/views.py:4358 +#: awx/api/views/__init__.py:3686 msgid "Cannot create schedule because job requires credential passwords." -msgstr "" -"Impossible de créer un planning parce que le job exige des mots de passe " -"d'authentification." +msgstr "Impossible de créer un planning parce que le job exige des mots de passe d'authentification." -#: awx/api/views.py:4363 +#: awx/api/views/__init__.py:3691 msgid "Cannot create schedule because job was launched by legacy method." -msgstr "" -"Impossible de créer un planning parce que le travail a été lancé par la " -"méthode héritée." +msgstr "Impossible de créer un planning parce que le travail a été lancé par la méthode héritée." -#: awx/api/views.py:4365 +#: awx/api/views/__init__.py:3693 msgid "Cannot create schedule because a related resource is missing." -msgstr "" -"Impossible de créer un planning car une ressource associée est manquante." +msgstr "Impossible de créer un planning car une ressource associée est manquante." -#: awx/api/views.py:4420 +#: awx/api/views/__init__.py:3748 msgid "Job Host Summaries List" msgstr "Liste récapitulative des hôtes de la tâche" -#: awx/api/views.py:4469 +#: awx/api/views/__init__.py:3802 msgid "Job Event Children List" msgstr "Liste des enfants d'événement de la tâche" -#: awx/api/views.py:4479 +#: awx/api/views/__init__.py:3818 msgid "Job Event Hosts List" msgstr "Liste des hôtes d'événement de la tâche" -#: awx/api/views.py:4488 +#: awx/api/views/__init__.py:3833 msgid "Job Events List" msgstr "Liste des événements de la tâche" -#: awx/api/views.py:4697 +#: awx/api/views/__init__.py:4044 msgid "Ad Hoc Command Events List" msgstr "Liste d'événements de la commande ad hoc" -#: awx/api/views.py:4939 +#: awx/api/views/__init__.py:4289 msgid "Delete not allowed while there are pending notifications" msgstr "Suppression non autorisée tant que des notifications sont en attente" -#: awx/api/views.py:4947 +#: awx/api/views/__init__.py:4297 msgid "Notification Template Test" msgstr "Test de modèle de notification" -#: awx/conf/conf.py:20 -msgid "Bud Frogs" -msgstr "Bud Frogs" +#: awx/api/views/__init__.py:4557 awx/api/views/__init__.py:4572 +msgid "User does not have permission to approve or deny this workflow." +msgstr "L'utilisateur n'a pas la permission d'approuver ou de refuser ce workflow." -#: awx/conf/conf.py:21 -msgid "Bunny" -msgstr "Bunny" +#: awx/api/views/__init__.py:4559 awx/api/views/__init__.py:4574 +msgid "This workflow step has already been approved or denied." +msgstr "Cette étape de workflow a déjà été approuvée ou refusée." + +#: awx/api/views/inventory.py:63 +msgid "Inventory Update Events List" +msgstr "Liste des événements de mise à jour de l'inventaire" + +#: awx/api/views/inventory.py:90 +msgid "Cannot delete inventory script." +msgstr "Impossible de supprimer le script d'inventaire." + +#: awx/api/views/inventory.py:149 +#, python-brace-format +msgid "{0}" +msgstr "{0}" + +#: awx/api/views/metrics.py:30 +msgid "Metrics" +msgstr "Métriques" + +#: awx/api/views/mixin.py:46 +msgid "Cannot delete job resource when associated workflow job is running." +msgstr "Impossible de supprimer les ressources de tâche lorsqu'une tâche de workflow associée est en cours d'exécution." + +#: awx/api/views/mixin.py:51 +msgid "Cannot delete running job resource." +msgstr "Impossible de supprimer la ressource de la tâche en cours." + +#: awx/api/views/mixin.py:56 +msgid "Job has not finished processing events." +msgstr "Job n'ayant pas terminé de traiter les événements." + +#: awx/api/views/mixin.py:153 +msgid "Related job {} is still processing events." +msgstr "Le job associé {} est toujours en cours de traitement des événements." + +#: awx/api/views/root.py:49 awx/templates/rest_framework/api.html:28 +msgid "REST API" +msgstr "API REST" + +#: awx/api/views/root.py:59 awx/templates/rest_framework/api.html:4 +msgid "AWX REST API" +msgstr "API REST AWX" + +#: awx/api/views/root.py:72 +msgid "API OAuth 2 Authorization Root" +msgstr "API OAuth 2 Authorization Root" + +#: awx/api/views/root.py:139 +msgid "Version 2" +msgstr "Version 2" + +#: awx/api/views/root.py:148 +msgid "Ping" +msgstr "Ping" + +#: awx/api/views/root.py:180 awx/api/views/root.py:225 awx/conf/apps.py:10 +msgid "Configuration" +msgstr "Configuration" + +#: awx/api/views/root.py:202 awx/api/views/root.py:308 +msgid "Invalid License" +msgstr "Licence non valide" + +#: awx/api/views/root.py:207 +msgid "The provided credentials are invalid (HTTP 401)." +msgstr "Les informations d'identification fournies ne sont pas valides (HTTP 401)." + +#: awx/api/views/root.py:209 +msgid "Unable to connect to proxy server." +msgstr "Impossible de se connecter au serveur mandateur." + +#: awx/api/views/root.py:211 +msgid "Could not connect to subscription service." +msgstr "Impossible de se connecter au service d'abonnement." + +#: awx/api/views/root.py:284 +msgid "Invalid license data" +msgstr "Données de licence non valides" + +#: awx/api/views/root.py:286 +msgid "Missing 'eula_accepted' property" +msgstr "Propriété 'eula_accepted' manquante" + +#: awx/api/views/root.py:290 +msgid "'eula_accepted' value is invalid" +msgstr "La valeur 'eula_accepted' n'est pas valide" + +#: awx/api/views/root.py:293 +msgid "'eula_accepted' must be True" +msgstr "La valeur 'eula_accepted' doit être True" + +#: awx/api/views/root.py:300 +msgid "Invalid JSON" +msgstr "Syntaxe JSON non valide" + +#: awx/api/views/root.py:319 +msgid "Invalid license" +msgstr "Licence non valide" + +#: awx/api/views/root.py:327 +msgid "Failed to remove license." +msgstr "N'a pas pu supprimer la licence." + +#: awx/api/views/webhooks.py:143 +msgid "Webhook previously received, aborting." +msgstr "Webhook reçu précédemment, abandon." + +#: awx/conf/conf.py:20 +msgid "Bud Frogs" +msgstr "Bud Frogs" + +#: awx/conf/conf.py:21 +msgid "Bunny" +msgstr "Bunny" #: awx/conf/conf.py:22 msgid "Cheese" @@ -1620,8 +1616,7 @@ msgstr "Sélection cow" #: awx/conf/conf.py:53 msgid "Select which cow to use with cowsay when running jobs." -msgstr "" -"Sélectionnez quel cow utiliser avec cowsay lors de l'exécution de tâches." +msgstr "Sélectionnez quel cow utiliser avec cowsay lors de l'exécution de tâches." #: awx/conf/conf.py:54 awx/conf/conf.py:75 msgid "Cows" @@ -1635,97 +1630,64 @@ msgstr "Exemple de paramètre en lecture seule" msgid "Example setting that cannot be changed." msgstr "L'exemple de paramètre ne peut pas être modifié." -#: awx/conf/conf.py:93 +#: awx/conf/conf.py:90 msgid "Example Setting" msgstr "Exemple de paramètre" -#: awx/conf/conf.py:94 +#: awx/conf/conf.py:91 msgid "Example setting which can be different for each user." msgstr "Exemple de paramètre qui peut être différent pour chaque utilisateur." -#: awx/conf/conf.py:95 awx/conf/registry.py:85 awx/conf/views.py:55 +#: awx/conf/conf.py:92 awx/conf/registry.py:81 awx/conf/views.py:56 msgid "User" msgstr "Utilisateur" -#: awx/conf/fields.py:60 awx/sso/fields.py:595 +#: awx/conf/fields.py:63 awx/sso/fields.py:595 #, python-brace-format msgid "" -"Expected None, True, False, a string or list of strings but got {input_type}" -" instead." -msgstr "" -"Les valeurs None, True, False, une chaîne ou une liste de chaînes étaient " -"attendues, mais {input_type} a été obtenu à la place." +"Expected None, True, False, a string or list of strings but got {input_type} " +"instead." +msgstr "Les valeurs None, True, False, une chaîne ou une liste de chaînes étaient attendues, mais {input_type} a été obtenu à la place." #: awx/conf/fields.py:104 +#, python-brace-format +msgid "Expected list of strings but got {input_type} instead." +msgstr "Liste de chaînes attendue mais {input_type} reçu à la place." + +#: awx/conf/fields.py:105 +#, python-brace-format +msgid "{path} is not a valid path choice." +msgstr "{path} n'est pas un choix de chemin d'accès valide." + +#: awx/conf/fields.py:149 msgid "Enter a valid URL" -msgstr "Entez une URL valide" +msgstr "Entrez une URL valide" -#: awx/conf/fields.py:136 +#: awx/conf/fields.py:187 #, python-brace-format msgid "\"{input}\" is not a valid string." msgstr "\"{input}\" n'est pas une chaîne valide." -#: awx/conf/fields.py:151 +#: awx/conf/fields.py:202 #, python-brace-format -msgid "" -"Expected a list of tuples of max length 2 but got {input_type} instead." -msgstr "" -"Liste de tuples de longueur max 2 attendue mais a obtenu {input_type} à la " -"place." +msgid "Expected a list of tuples of max length 2 but got {input_type} instead." +msgstr "Liste de tuples de longueur max 2 attendue mais a obtenu {input_type} à la place." -#: awx/conf/license.py:22 -msgid "Your Tower license does not allow that." -msgstr "Votre licence Tower ne vous y autorise pas." - -#: awx/conf/management/commands/migrate_to_database_settings.py:41 -msgid "Only show which settings would be commented/migrated." -msgstr "" -"Afficher seulement les paramètres qui pourraient être commentés/migrés." - -#: awx/conf/management/commands/migrate_to_database_settings.py:48 -msgid "" -"Skip over settings that would raise an error when commenting/migrating." -msgstr "" -"Ignorer les paramètres qui pourraient provoquer une erreur lors de la saisie" -" de commentaires/de la migration." - -#: awx/conf/management/commands/migrate_to_database_settings.py:55 -msgid "Skip commenting out settings in files." -msgstr "Ignorer la saisie de commentaires de paramètres dans les fichiers." - -#: awx/conf/management/commands/migrate_to_database_settings.py:62 -msgid "Skip migrating and only comment out settings in files." -msgstr "" -"Ignorer la migration et ne dé-commenter que les configuration des fichiers." - -#: awx/conf/management/commands/migrate_to_database_settings.py:68 -msgid "Backup existing settings files with this suffix." -msgstr "Sauvegardez les fichiers de paramètres existants avec ce suffixe." - -#: awx/conf/registry.py:73 awx/conf/tests/unit/test_registry.py:169 -#: awx/conf/tests/unit/test_registry.py:192 -#: awx/conf/tests/unit/test_registry.py:196 -#: awx/conf/tests/unit/test_registry.py:201 -#: awx/conf/tests/unit/test_registry.py:208 +#: awx/conf/registry.py:73 awx/conf/tests/unit/test_registry.py:155 msgid "All" msgstr "Tous" -#: awx/conf/registry.py:74 awx/conf/tests/unit/test_registry.py:170 -#: awx/conf/tests/unit/test_registry.py:193 -#: awx/conf/tests/unit/test_registry.py:197 -#: awx/conf/tests/unit/test_registry.py:202 -#: awx/conf/tests/unit/test_registry.py:209 +#: awx/conf/registry.py:74 awx/conf/tests/unit/test_registry.py:156 msgid "Changed" msgstr "Modifié" -#: awx/conf/registry.py:86 +#: awx/conf/registry.py:82 msgid "User-Defaults" msgstr "Paramètres utilisateur par défaut" -#: awx/conf/registry.py:154 +#: awx/conf/registry.py:143 msgid "This value has been set manually in a settings file." -msgstr "" -"Cette valeur a été définie manuellement dans un fichier de configuration." +msgstr "Cette valeur a été définie manuellement dans un fichier de configuration." #: awx/conf/tests/unit/test_registry.py:46 #: awx/conf/tests/unit/test_registry.py:56 @@ -1734,19 +1696,15 @@ msgstr "" #: awx/conf/tests/unit/test_registry.py:100 #: awx/conf/tests/unit/test_registry.py:106 #: awx/conf/tests/unit/test_registry.py:126 -#: awx/conf/tests/unit/test_registry.py:140 -#: awx/conf/tests/unit/test_registry.py:146 -#: awx/conf/tests/unit/test_registry.py:159 -#: awx/conf/tests/unit/test_registry.py:171 -#: awx/conf/tests/unit/test_registry.py:180 -#: awx/conf/tests/unit/test_registry.py:198 -#: awx/conf/tests/unit/test_registry.py:210 -#: awx/conf/tests/unit/test_registry.py:219 -#: awx/conf/tests/unit/test_registry.py:225 -#: awx/conf/tests/unit/test_registry.py:237 -#: awx/conf/tests/unit/test_registry.py:245 -#: awx/conf/tests/unit/test_registry.py:288 -#: awx/conf/tests/unit/test_registry.py:306 +#: awx/conf/tests/unit/test_registry.py:132 +#: awx/conf/tests/unit/test_registry.py:145 +#: awx/conf/tests/unit/test_registry.py:157 +#: awx/conf/tests/unit/test_registry.py:166 +#: awx/conf/tests/unit/test_registry.py:172 +#: awx/conf/tests/unit/test_registry.py:184 +#: awx/conf/tests/unit/test_registry.py:191 +#: awx/conf/tests/unit/test_registry.py:233 +#: awx/conf/tests/unit/test_registry.py:251 #: awx/conf/tests/unit/test_settings.py:79 #: awx/conf/tests/unit/test_settings.py:97 #: awx/conf/tests/unit/test_settings.py:112 @@ -1768,716 +1726,929 @@ msgstr "" #: awx/conf/tests/unit/test_settings.py:398 #: awx/conf/tests/unit/test_settings.py:411 #: awx/conf/tests/unit/test_settings.py:430 -#: awx/conf/tests/unit/test_settings.py:466 awx/main/conf.py:22 -#: awx/main/conf.py:32 awx/main/conf.py:43 awx/main/conf.py:53 -#: awx/main/conf.py:62 awx/main/conf.py:74 awx/main/conf.py:87 -#: awx/main/conf.py:100 awx/main/conf.py:125 +#: awx/conf/tests/unit/test_settings.py:466 awx/main/conf.py:24 +#: awx/main/conf.py:33 awx/main/conf.py:43 awx/main/conf.py:53 +#: awx/main/conf.py:65 awx/main/conf.py:78 awx/main/conf.py:91 +#: awx/main/conf.py:116 awx/main/conf.py:129 awx/main/conf.py:142 +#: awx/main/conf.py:154 awx/main/conf.py:162 awx/main/conf.py:173 +#: awx/main/conf.py:405 awx/main/conf.py:830 awx/main/conf.py:840 +#: awx/main/conf.py:852 msgid "System" msgstr "Système" -#: awx/conf/tests/unit/test_registry.py:165 -#: awx/conf/tests/unit/test_registry.py:172 -#: awx/conf/tests/unit/test_registry.py:187 -#: awx/conf/tests/unit/test_registry.py:203 -#: awx/conf/tests/unit/test_registry.py:211 +#: awx/conf/tests/unit/test_registry.py:151 +#: awx/conf/tests/unit/test_registry.py:158 msgid "OtherSystem" msgstr "Autre Système" -#: awx/conf/views.py:47 +#: awx/conf/views.py:48 msgid "Setting Categories" msgstr "Catégories de paramètre" -#: awx/conf/views.py:71 +#: awx/conf/views.py:70 msgid "Setting Detail" msgstr "Détails du paramètre" -#: awx/conf/views.py:166 +#: awx/conf/views.py:162 msgid "Logging Connectivity Test" msgstr "Journalisation du test de connectivité" -#: awx/main/access.py:59 +#: awx/main/access.py:66 #, python-format msgid "Required related field %s for permission check." -msgstr "Champs associés % requis pour la vérification des permissions." +msgstr "Champ associé %s requis pour la vérification des permissions." -#: awx/main/access.py:75 +#: awx/main/access.py:82 #, python-format msgid "Bad data found in related field %s." msgstr "Données incorrectes trouvées dans le champ %s associé." -#: awx/main/access.py:302 +#: awx/main/access.py:331 msgid "License is missing." msgstr "La licence est manquante." -#: awx/main/access.py:304 +#: awx/main/access.py:333 msgid "License has expired." msgstr "La licence est arrivée à expiration." -#: awx/main/access.py:312 +#: awx/main/access.py:341 #, python-format msgid "License count of %s instances has been reached." msgstr "Le nombre de licences d'instances %s a été atteint." -#: awx/main/access.py:314 +#: awx/main/access.py:343 #, python-format msgid "License count of %s instances has been exceeded." msgstr "Le nombre de licences d'instances %s a été dépassé." -#: awx/main/access.py:316 +#: awx/main/access.py:345 msgid "Host count exceeds available instances." msgstr "Le nombre d'hôtes dépasse celui des instances disponibles." -#: awx/main/access.py:320 +#: awx/main/access.py:363 awx/main/access.py:372 #, python-format -msgid "Feature %s is not enabled in the active license." -msgstr "La fonctionnalité %s n'est pas activée dans la licence active." - -#: awx/main/access.py:322 -msgid "Features not found in active license." -msgstr "Fonctionnalités introuvables dans la licence active." +msgid "" +"You have already reached the maximum number of %s hosts allowed for your " +"organization. Contact your System Administrator for assistance." +msgstr "Vous avez atteint le nombre maximum d'hôtes %s autorisés pour votre organisation. Contactez votre administrateur système pour obtenir de l'aide." -#: awx/main/access.py:835 +#: awx/main/access.py:927 msgid "Unable to change inventory on a host." msgstr "Impossible de modifier l'inventaire sur un hôte." -#: awx/main/access.py:852 awx/main/access.py:897 +#: awx/main/access.py:948 awx/main/access.py:990 msgid "Cannot associate two items from different inventories." msgstr "Impossible d'associer deux éléments d'inventaires différents." -#: awx/main/access.py:885 +#: awx/main/access.py:978 msgid "Unable to change inventory on a group." msgstr "Impossible de modifier l'inventaire sur un groupe." -#: awx/main/access.py:1146 +#: awx/main/access.py:1264 msgid "Unable to change organization on a team." msgstr "Impossible de modifier l'organisation d'une équipe." -#: awx/main/access.py:1163 +#: awx/main/access.py:1280 msgid "The {} role cannot be assigned to a team" msgstr "Le rôle {} ne peut pas être attribué à une équipe" -#: awx/main/access.py:1165 -msgid "The admin_role for a User cannot be assigned to a team" -msgstr "L'admin_role d'un utilisateur ne peut pas être attribué à une équipe" +#: awx/main/access.py:1474 +msgid "Insufficient access to Job Template credentials." +msgstr "Accès insuffisant aux informations d'identification du modèle de tâche." -#: awx/main/access.py:1531 awx/main/access.py:1965 -msgid "Job was launched with prompts provided by another user." -msgstr "" -"Le job a été lancé par des champs d'invite provenant d'un autre utilisateur." +#: awx/main/access.py:1639 awx/main/access.py:2063 +msgid "Job was launched with secret prompts provided by another user." +msgstr "La tâche a été lancée avec des invites secrètes fournies par un autre utilisateur." + +#: awx/main/access.py:1648 +msgid "Job has been orphaned from its job template and organization." +msgstr "La tâche est orpheline de son modèle de tâche et de son organisation." -#: awx/main/access.py:1551 -msgid "Job has been orphaned from its job template." -msgstr "La tâche est orpheline de son modèle de tâche." +#: awx/main/access.py:1650 +msgid "Job was launched with prompted fields you do not have access to." +msgstr "La tâche a été lancée avec des champs d'invite auxquels vous n'avez pas accès." -#: awx/main/access.py:1553 -msgid "Job was launched with unknown prompted fields." -msgstr "Le job été lancé par des champs d'invite inconnus." +#: awx/main/access.py:1652 +msgid "" +"Job was launched with unknown prompted fields. Organization admin " +"permissions required." +msgstr "La tâche a été lancée avec des champs d'invite inconnus. Les autorisations d'administration de l'organisation sont requises." -#: awx/main/access.py:1555 -msgid "Job was launched with prompted fields." -msgstr "La tâche a été lancée avec les champs d'invite." +#: awx/main/access.py:2053 +msgid "Workflow Job was launched with unknown prompts." +msgstr "Le job de flux de travail a été lancé par des instructions inconnues." -#: awx/main/access.py:1557 -msgid " Organization level permissions required." -msgstr "Permissions au niveau de l'organisation requises." +#: awx/main/access.py:2065 +msgid "Job was launched with prompts you lack access to." +msgstr "Le job a été lancé par des instructions auxquelles vous n'avez pas accès." -#: awx/main/access.py:1559 -msgid " You do not have permission to related resources." -msgstr "Vous n'avez pas de permission vers les ressources liées." +#: awx/main/access.py:2067 +msgid "Job was launched with prompts no longer accepted." +msgstr "Le job a été lancé par des instructions qui ne sont plus acceptées." -#: awx/main/access.py:1979 +#: awx/main/access.py:2079 msgid "" "You do not have permission to the workflow job resources required for " "relaunch." -msgstr "" -"Vous n'avez pas la permission pour accéder aux permissions de tâche de " -"workflow requises pour le second lancement." +msgstr "Vous n'avez pas la permission pour accéder aux permissions de tâche de workflow requises pour le second lancement." #: awx/main/apps.py:8 msgid "Main" msgstr "Principal" -#: awx/main/conf.py:20 +#: awx/main/conf.py:22 msgid "Enable Activity Stream" msgstr "Activer le flux d'activité" -#: awx/main/conf.py:21 +#: awx/main/conf.py:23 msgid "Enable capturing activity for the activity stream." msgstr "Activer la capture d'activités pour le flux d'activité." -#: awx/main/conf.py:30 +#: awx/main/conf.py:31 msgid "Enable Activity Stream for Inventory Sync" msgstr "Activer le flux d'activité pour la synchronisation des inventaires" -#: awx/main/conf.py:31 +#: awx/main/conf.py:32 msgid "" "Enable capturing activity for the activity stream when running inventory " "sync." -msgstr "" -"Activer la capture d'activités pour le flux d'activité lors de la " -"synchronisation des inventaires en cours d'exécution." +msgstr "Activer la capture d'activités pour le flux d'activité lors de la synchronisation des inventaires en cours d'exécution." #: awx/main/conf.py:40 msgid "All Users Visible to Organization Admins" -msgstr "" -"Tous les utilisateurs visibles pour les administrateurs de l'organisation" +msgstr "Tous les utilisateurs visibles pour les administrateurs de l'organisation" #: awx/main/conf.py:41 msgid "" "Controls whether any Organization Admin can view all users and teams, even " "those not associated with their Organization." -msgstr "" -"Contrôle si un administrateur d'organisation peut ou non afficher tous les " -"utilisateurs et les équipes, même ceux qui ne sont pas associés à son " -"organisation." +msgstr "Contrôle si un administrateur d'organisation peut ou non afficher tous les utilisateurs et les équipes, même ceux qui ne sont pas associés à son organisation." #: awx/main/conf.py:50 msgid "Organization Admins Can Manage Users and Teams" -msgstr "" -"Les administrateurs de l'organisation peuvent gérer les utilisateurs et les " -"équipes." +msgstr "Les administrateurs de l'organisation peuvent gérer les utilisateurs et les équipes." #: awx/main/conf.py:51 msgid "" "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." -msgstr "" -"Contrôle si l'administrateur de l'organisation dispose des privilèges " -"nécessaires pour créer et gérer les utilisateurs et les équipes. Vous pouvez" -" désactiver cette fonctionnalité si vous utilisez une intégration LDAP ou " -"SAML." +msgstr "Contrôle si l'administrateur de l'organisation dispose des privilèges nécessaires pour créer et gérer les utilisateurs et les équipes. Vous pouvez désactiver cette fonctionnalité si vous utilisez une intégration LDAP ou SAML." -#: awx/main/conf.py:60 -msgid "Enable Administrator Alerts" -msgstr "Activer les alertes administrateur" - -#: awx/main/conf.py:61 -msgid "Email Admin users for system events that may require attention." -msgstr "" -"Alerter les administrateurs par email concernant des événements système " -"susceptibles de mériter leur attention." - -#: awx/main/conf.py:71 +#: awx/main/conf.py:62 msgid "Base URL of the Tower host" msgstr "URL de base pour l'hôte Tower" -#: awx/main/conf.py:72 +#: awx/main/conf.py:63 msgid "" -"This setting is used by services like notifications to render a valid url to" -" the Tower host." -msgstr "" -"Ce paramètre est utilisé par des services sous la forme de notifications " -"permettant de rendre valide une URL pour l'hôte Tower." +"This setting is used by services like notifications to render a valid url to " +"the Tower host." +msgstr "Ce paramètre est utilisé par des services sous la forme de notifications permettant de rendre valide une URL pour l'hôte Tower." -#: awx/main/conf.py:81 +#: awx/main/conf.py:72 msgid "Remote Host Headers" msgstr "En-têtes d'hôte distant" -#: awx/main/conf.py:82 +#: awx/main/conf.py:73 msgid "" "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." -msgstr "" -"En-têtes HTTP et méta-clés à rechercher afin de déterminer le nom ou " -"l'adresse IP d'un hôte distant. Ajoutez des éléments supplémentaires à cette" -" liste, tels que \"HTTP_X_FORWARDED_FOR\", en présence d'un proxy inverse. " -"Voir la section \"Support Proxy\" du Guide de l'administrateur pour obtenir " -"des détails supplémentaires." +msgstr "En-têtes HTTP et méta-clés à rechercher afin de déterminer le nom ou l'adresse IP d'un hôte distant. Ajoutez des éléments supplémentaires à cette liste, tels que \"HTTP_X_FORWARDED_FOR\", en présence d'un proxy inverse. Voir la section \"Support Proxy\" du Guide de l'administrateur pour obtenir des détails supplémentaires." -#: awx/main/conf.py:94 +#: awx/main/conf.py:85 msgid "Proxy IP Whitelist" msgstr "Liste blanche des IP proxy" -#: awx/main/conf.py:95 +#: awx/main/conf.py:86 msgid "" "If Tower is behind a reverse proxy/load balancer, use this setting to " "whitelist the proxy IP addresses from which Tower 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')" -msgstr "" -"Si Tower se trouve derrière un proxy inverse/équilibreur de charge, utilisez ce paramètre pour mettre sur liste blanche les adresses IP du proxy en provenance desquelles Tower devra approuver les valeurs d'en-tête REMOTE_HOST_HEADERS personnalisées. Si ce paramètre correspond à une liste vide (valeur par défaut), les en-têtes spécifiés par\n" +msgstr "Si Tower se trouve derrière un proxy inverse/équilibreur de charge, utilisez ce paramètre pour mettre sur liste blanche les adresses IP du proxy en provenance desquelles Tower devra approuver les valeurs d'en-tête REMOTE_HOST_HEADERS personnalisées. Si ce paramètre correspond à une liste vide (valeur par défaut), les en-têtes spécifiés par\n" "REMOTE_HOST_HEADERS seront approuvés sans condition)" -#: awx/main/conf.py:121 +#: awx/main/conf.py:112 msgid "License" msgstr "Licence" -#: awx/main/conf.py:122 +#: awx/main/conf.py:113 msgid "" -"The license controls which features and functionality are enabled. Use " -"/api/v1/config/ to update or change the license." -msgstr "" -"La licence détermine les fonctionnalités et les fonctions activées. Utilisez" -" /api/v1/config/ pour mettre à jour ou modifier la licence." +"The license controls which features and functionality are enabled. Use /api/" +"v2/config/ to update or change the license." +msgstr "La licence détermine les fonctionnalités et les fonctions activées. Utilisez /api/v2/config/ pour mettre à jour ou modifier la licence." + +#: awx/main/conf.py:127 +msgid "Red Hat customer username" +msgstr "Nom d'utilisateur du client Red Hat" + +#: awx/main/conf.py:128 +msgid "" +"This username is used to retrieve license information and to send Automation " +"Analytics" +msgstr "Ce nom d'utilisateur est utilisé pour récupérer les informations de licence à envoyer à Automation Analytics" -#: awx/main/conf.py:132 +#: awx/main/conf.py:140 +msgid "Red Hat customer password" +msgstr "Mot de passe client Red Hat" + +#: awx/main/conf.py:141 +msgid "" +"This password is used to retrieve license information and to send Automation " +"Analytics" +msgstr "Ce mot de passe est utilisé pour récupérer les informations de licence à envoyer à Automation Analytics" + +#: awx/main/conf.py:152 +msgid "Automation Analytics upload URL." +msgstr "URL de téléchargement d’Automation Analytics." + +#: awx/main/conf.py:153 +msgid "" +"This setting is used to to configure data collection for the Automation " +"Analytics dashboard" +msgstr "Ce paramètre est utilisé pour configurer la collecte de données pour le tableau de bord d'Automation Analytics" + +#: awx/main/conf.py:161 +msgid "Unique identifier for an AWX/Tower installation" +msgstr "Identifiant unique pour une installation AWX/Tower" + +#: awx/main/conf.py:170 +msgid "Custom virtual environment paths" +msgstr "Chemins d'environnement virtuels personnalisés" + +#: awx/main/conf.py:171 +msgid "" +"Paths where Tower will look for custom virtual environments (in addition to /" +"var/lib/awx/venv/). Enter one path per line." +msgstr "Chemins d'accès où Tower recherchera des environnements virtuels personnalisés (en plus de /var/lib/awx/venv/). Saisissez un chemin par ligne." + +#: awx/main/conf.py:181 msgid "Ansible Modules Allowed for Ad Hoc Jobs" msgstr "Modules Ansible autorisés pour des tâches ad hoc" -#: awx/main/conf.py:133 +#: awx/main/conf.py:182 msgid "List of modules allowed to be used by ad-hoc jobs." msgstr "Liste des modules que des tâches ad hoc sont autorisées à utiliser." -#: awx/main/conf.py:134 awx/main/conf.py:156 awx/main/conf.py:165 -#: awx/main/conf.py:176 awx/main/conf.py:186 awx/main/conf.py:196 -#: awx/main/conf.py:206 awx/main/conf.py:217 awx/main/conf.py:229 -#: awx/main/conf.py:241 awx/main/conf.py:254 awx/main/conf.py:266 -#: awx/main/conf.py:276 awx/main/conf.py:287 awx/main/conf.py:298 -#: awx/main/conf.py:308 awx/main/conf.py:318 awx/main/conf.py:330 -#: awx/main/conf.py:342 awx/main/conf.py:354 awx/main/conf.py:368 +#: awx/main/conf.py:183 awx/main/conf.py:205 awx/main/conf.py:214 +#: awx/main/conf.py:225 awx/main/conf.py:235 awx/main/conf.py:245 +#: awx/main/conf.py:256 awx/main/conf.py:267 awx/main/conf.py:278 +#: awx/main/conf.py:290 awx/main/conf.py:299 awx/main/conf.py:312 +#: awx/main/conf.py:325 awx/main/conf.py:337 awx/main/conf.py:348 +#: awx/main/conf.py:359 awx/main/conf.py:371 awx/main/conf.py:383 +#: awx/main/conf.py:394 awx/main/conf.py:414 awx/main/conf.py:424 +#: awx/main/conf.py:434 awx/main/conf.py:450 awx/main/conf.py:463 +#: awx/main/conf.py:477 awx/main/conf.py:491 awx/main/conf.py:503 +#: awx/main/conf.py:513 awx/main/conf.py:524 awx/main/conf.py:534 +#: awx/main/conf.py:545 awx/main/conf.py:555 awx/main/conf.py:565 +#: awx/main/conf.py:577 awx/main/conf.py:589 awx/main/conf.py:601 +#: awx/main/conf.py:615 awx/main/conf.py:627 msgid "Jobs" msgstr "Tâches" -#: awx/main/conf.py:143 +#: awx/main/conf.py:192 msgid "Always" msgstr "Toujours" -#: awx/main/conf.py:144 +#: awx/main/conf.py:193 msgid "Never" msgstr "Jamais" -#: awx/main/conf.py:145 +#: awx/main/conf.py:194 msgid "Only On Job Template Definitions" msgstr "Définitions de Modèle de job uniquement" -#: awx/main/conf.py:148 +#: awx/main/conf.py:197 msgid "When can extra variables contain Jinja templates?" -msgstr "" -"Quand des variables supplémentaires peuvent-elles contenir des modèles Jinja" -" ?" +msgstr "Quand des variables supplémentaires peuvent-elles contenir des modèles Jinja ?" -#: awx/main/conf.py:150 +#: awx/main/conf.py:199 msgid "" "Ansible allows variable substitution via the Jinja2 templating language for " "--extra-vars. This poses a potential security risk where Tower 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\"." -msgstr "" -"Ansible permet la substitution de variables via le langage de modèle Jinja2 " -"pour --extra-vars. Cela pose un risque potentiel de sécurité où les " -"utilisateurs de Tower ayant la possibilité de spécifier des vars " -"supplémentaires au moment du lancement du job peuvent utiliser les modèles " -"Jinja2 pour exécuter arbitrairement Python. Il est recommandé de définir " -"cette valeur à \"template\" (modèle) ou \"never\" (jamais)." - -#: awx/main/conf.py:163 +"templates to run arbitrary Python. It is recommended that this value be set " +"to \"template\" or \"never\"." +msgstr "Ansible permet la substitution de variables via le langage de modèle Jinja2 pour --extra-vars. Cela pose un risque potentiel de sécurité où les utilisateurs de Tower ayant la possibilité de spécifier des vars supplémentaires au moment du lancement du job peuvent utiliser les modèles Jinja2 pour exécuter arbitrairement Python. Il est recommandé de définir cette valeur à \"template\" (modèle) ou \"never\" (jamais)." + +#: awx/main/conf.py:212 msgid "Enable job isolation" msgstr "Activer l'isolement des tâches" -#: awx/main/conf.py:164 +#: awx/main/conf.py:213 msgid "" "Isolates an Ansible job from protected parts of the system to prevent " "exposing sensitive information." -msgstr "" -"Permet d'isoler une tâche Ansible des parties protégées du système pour " -"éviter l'exposition d'informations sensibles." +msgstr "Permet d'isoler une tâche Ansible des parties protégées du système pour éviter l'exposition d'informations sensibles." -#: awx/main/conf.py:172 +#: awx/main/conf.py:221 msgid "Job execution path" msgstr "Chemin d'exécution de la tâche" -#: awx/main/conf.py:173 +#: awx/main/conf.py:222 msgid "" "The directory in which Tower will create new temporary directories for job " "execution and isolation (such as credential files and custom inventory " "scripts)." -msgstr "" -"Répertoire dans lequel Tower créera des répertoires temporaires pour " -"l'exécution et l'isolation de la tâche (comme les fichiers des informations " -"d'identification et les scripts d'inventaire personnalisés)." +msgstr "Répertoire dans lequel Tower créera des répertoires temporaires pour l'exécution et l'isolation de la tâche (comme les fichiers des informations d'identification et les scripts d'inventaire personnalisés)." -#: awx/main/conf.py:184 +#: awx/main/conf.py:233 msgid "Paths to hide from isolated jobs" msgstr "Chemins à dissimuler des tâches isolées" -#: awx/main/conf.py:185 +#: awx/main/conf.py:234 msgid "" "Additional paths to hide from isolated processes. Enter one path per line." -msgstr "" -"Chemins supplémentaires à dissimuler des processus isolés. Saisissez un " -"chemin par ligne." +msgstr "Chemins supplémentaires à dissimuler des processus isolés. Saisissez un chemin par ligne." -#: awx/main/conf.py:194 +#: awx/main/conf.py:243 msgid "Paths to expose to isolated jobs" msgstr "Chemins à exposer aux tâches isolées" -#: awx/main/conf.py:195 +#: awx/main/conf.py:244 msgid "" "Whitelist of paths that would otherwise be hidden to expose to isolated " "jobs. Enter one path per line." -msgstr "" -"Liste blanche des chemins qui seraient autrement dissimulés de façon à ne " -"pas être exposés aux tâches isolées. Saisissez un chemin par ligne." +msgstr "Liste blanche des chemins qui seraient autrement dissimulés de façon à ne pas être exposés aux tâches isolées. Saisissez un chemin par ligne." + +#: awx/main/conf.py:254 +msgid "Verbosity level for isolated node management tasks" +msgstr "Niveau de verbosité pour les tâches de gestion de nœuds isolés" -#: awx/main/conf.py:204 +#: awx/main/conf.py:255 +msgid "" +"This can be raised to aid in debugging connection issues for isolated task " +"execution" +msgstr "Cela peut être soulevé pour aider à déboguer les problèmes de connexion pour l'exécution de tâches isolées." + +#: awx/main/conf.py:265 msgid "Isolated status check interval" msgstr "Intervalle de vérification du statut isolé" -#: awx/main/conf.py:205 +#: awx/main/conf.py:266 msgid "" "The number of seconds to sleep between status checks for jobs running on " "isolated instances." -msgstr "" -"Nombre de secondes de veille entre les vérifications de statut pour les " -"tâches s'exécutant sur des instances isolées." +msgstr "Nombre de secondes de veille entre les vérifications de statut pour les tâches s'exécutant sur des instances isolées." -#: awx/main/conf.py:214 +#: awx/main/conf.py:275 msgid "Isolated launch timeout" msgstr "Délai d'attente du lancement isolé" -#: awx/main/conf.py:215 +#: awx/main/conf.py:276 msgid "" "The timeout (in seconds) for launching jobs on isolated instances. This " "includes the time needed to copy source control files (playbooks) to the " "isolated instance." -msgstr "" -"Délai d'attente (en secondes) pour lancer des tâches sur des instances " -"isolées. Cela inclut la durée nécessaire pour copier les fichiers de " -"contrôle de la source (playbook) sur l'instance isolée." +msgstr "Délai d'attente (en secondes) pour lancer des tâches sur des instances isolées. Cela inclut la durée nécessaire pour copier les fichiers de contrôle de la source (playbook) sur l'instance isolée." -#: awx/main/conf.py:226 +#: awx/main/conf.py:287 msgid "Isolated connection timeout" msgstr "Délai d'attente de connexion isolée" -#: awx/main/conf.py:227 +#: awx/main/conf.py:288 msgid "" "Ansible SSH connection timeout (in seconds) to use when communicating with " "isolated instances. Value should be substantially greater than expected " "network latency." -msgstr "" -"Délai d'attente de connexion SSH Ansible (en secondes) à utiliser lors de la" -" communication avec des instances isolées. La valeur doit être nettement " -"supérieure à la latence du réseau attendue." +msgstr "Délai d'attente de connexion SSH Ansible (en secondes) à utiliser lors de la communication avec des instances isolées. La valeur doit être nettement supérieure à la latence du réseau attendue." -#: awx/main/conf.py:237 +#: awx/main/conf.py:297 +msgid "Isolated host key checking" +msgstr "Vérification de la clé d’hôte isolée" + +#: awx/main/conf.py:298 +msgid "" +"When set to True, AWX will enforce strict host key checking for " +"communication with isolated nodes." +msgstr "Si cette valeur est définie sur True, AWX applique une vérification stricte de la clé d’hôte pour la communication avec les nœuds isolés." + +#: awx/main/conf.py:308 msgid "Generate RSA keys for isolated instances" msgstr "Génère des clés RSA pour les instances isolées" -#: awx/main/conf.py:238 +#: awx/main/conf.py:309 msgid "" "If set, a random RSA key will be generated and distributed to isolated " "instances. To disable this behavior and manage authentication for isolated " "instances outside of Tower, disable this setting." -msgstr "" -"Si définie, une clé RSA sera générée de manière aléatoire et distribuée aux " -"instances isolées. Pour désactiver ce comportement, et gérer " -"l'authentification pour les instances isolées en dehors de Tower, désactiver" -" ce paramètre." +msgstr "Si définie, une clé RSA sera générée de manière aléatoire et distribuée aux instances isolées. Pour désactiver ce comportement, et gérer l'authentification pour les instances isolées en dehors de Tower, désactiver ce paramètre." -#: awx/main/conf.py:252 awx/main/conf.py:253 +#: awx/main/conf.py:323 awx/main/conf.py:324 msgid "The RSA private key for SSH traffic to isolated instances" msgstr "Clé privée RSA pour le trafic SSH vers des instances isolées" -#: awx/main/conf.py:264 awx/main/conf.py:265 +#: awx/main/conf.py:335 awx/main/conf.py:336 msgid "The RSA public key for SSH traffic to isolated instances" msgstr "Clé publique RSA pour le trafic SSH vers des instances isolées" -#: awx/main/conf.py:274 +#: awx/main/conf.py:345 +msgid "Enable detailed resource profiling on all playbook runs" +msgstr "Activer le profilage détaillé des ressources sur toutes les exécutions du playbook" + +#: awx/main/conf.py:346 +msgid "" +"If set, detailed resource profiling data will be collected on all jobs. This " +"data can be gathered with `sosreport`." +msgstr "Si cette valeur est définie, des données détaillées sur le profilage des ressources seront recueillies pour toutes les tâches. Ces données peuvent être collectées avec `sosreport`." + +#: awx/main/conf.py:356 +msgid "Interval (in seconds) between polls for cpu usage." +msgstr "Intervalle (en secondes) entre les interrogations pour l'utilisation du cpu." + +#: awx/main/conf.py:357 +msgid "" +"Interval (in seconds) between polls for cpu usage. Setting this lower than " +"the default will affect playbook performance." +msgstr "Intervalle (en secondes) entre les interrogations pour l'utilisation du cpu. La définition de ce paramètre sur un niveau inférieur à celui par défaut affecte les performances du playbook." + +#: awx/main/conf.py:368 +msgid "Interval (in seconds) between polls for memory usage." +msgstr "Intervalle (en secondes) entre les interrogations pour l'utilisation de la mémoire." + +#: awx/main/conf.py:369 +msgid "" +"Interval (in seconds) between polls for memory usage. Setting this lower " +"than the default will affect playbook performance." +msgstr "Intervalle (en secondes) entre les interrogations pour l'utilisation de la mémoire. La définition de ce paramètre sur un niveau inférieur à celui par défaut affectera les performances du playbook." + +#: awx/main/conf.py:380 +msgid "Interval (in seconds) between polls for PID count." +msgstr "Intervalle (en secondes) entre les interrogations pour le comptage PID." + +#: awx/main/conf.py:381 +msgid "" +"Interval (in seconds) between polls for PID count. Setting this lower than " +"the default will affect playbook performance." +msgstr "Intervalle (en secondes) entre les interrogations pour le comptage PID. La définition de ce paramètre sur un niveau inférieur à celui par défaut affecte les performances du playbook." + +#: awx/main/conf.py:392 msgid "Extra Environment Variables" msgstr "Variables d'environnement supplémentaires" -#: awx/main/conf.py:275 +#: awx/main/conf.py:393 msgid "" "Additional environment variables set for playbook runs, inventory updates, " "project updates, and notification sending." -msgstr "" -"Variables d'environnement supplémentaires définies pour les exécutions de " -"Playbook, les mises à jour de projet et l'envoi de notifications." +msgstr "Variables d'environnement supplémentaires définies pour les exécutions de Playbook, les mises à jour de projet et l'envoi de notifications." + +#: awx/main/conf.py:403 +msgid "Gather data for Automation Analytics" +msgstr "Recueillir des données pour Automation Analytics" -#: awx/main/conf.py:285 +#: awx/main/conf.py:404 +msgid "Enables Tower to gather data on automation and send it to Red Hat." +msgstr "Permet à Tower de recueillir des données sur l'automatisation et de les envoyer à Red Hat." + +#: awx/main/conf.py:412 +msgid "Run Project Updates With Higher Verbosity" +msgstr "Exécuter des mises à jour de projet avec une plus grande verbosité" + +#: awx/main/conf.py:413 +msgid "" +"Adds the CLI -vvv flag to ansible-playbook runs of project_update.yml used " +"for project updates." +msgstr "Ajoute le drapeau CLI -vvv aux exécutions ansible-playbook de project_update.yml utilisées pour les mises à jour du projet." + +#: awx/main/conf.py:422 +msgid "Enable Role Download" +msgstr "Active le téléchargement du rôle" + +#: awx/main/conf.py:423 +msgid "" +"Allows roles to be dynamically downloaded from a requirements.yml file for " +"SCM projects." +msgstr "Permet aux rôles d'être téléchargés dynamiquement à partir du fichier requirements.yml pour les projets SCM." + +#: awx/main/conf.py:432 +msgid "Enable Collection(s) Download" +msgstr "Activer le téléchargement de la ou des collections" + +#: awx/main/conf.py:433 +msgid "" +"Allows collections to be dynamically downloaded from a requirements.yml file " +"for SCM projects." +msgstr "Permet aux collections d'être téléchargés dynamiquement à partir du fichier requirements.yml pour les projets SCM." + +#: awx/main/conf.py:443 +msgid "Primary Galaxy Server URL" +msgstr "URL du serveur Galaxy primaire" + +#: awx/main/conf.py:445 +msgid "" +"For organizations that run their own Galaxy service, this gives the option " +"to specify a host as the primary galaxy server. Requirements will be " +"downloaded from the primary if the specific role or collection is available " +"there. If the content is not avilable in the primary, or if this field is " +"left blank, it will default to galaxy.ansible.com." +msgstr "Pour les organisations qui exploitent leur propre service Galaxy, cela donne la possibilité de spécifier un hôte comme serveur Galaxy primaire. Les exigences seront téléchargées à partir du primaire si le rôle ou la collection spécifique y est disponible. Si le contenu n'est pas disponible dans le primaire, ou si ce champ est laissé vide, il sera par défaut galaxy.ansible.com." + +#: awx/main/conf.py:459 +msgid "Primary Galaxy Server Username" +msgstr "Nom d'utilisateur du serveur Galaxy primaire" + +#: awx/main/conf.py:460 +msgid "" +"For using a galaxy server at higher precedence than the public Ansible " +"Galaxy. The username to use for basic authentication against the Galaxy " +"instance, this is mutually exclusive with PRIMARY_GALAXY_TOKEN." +msgstr "Permet d’utiliser un serveur Galaxy à une priorité plus élevée que le Galaxy Ansible public. Le nom d'utilisateur à utiliser pour l'authentification de base sur la base de l'instance Galaxy, ceci est mutuellement exclusif avec PRIMARY_GALAXY_TOKEN." + +#: awx/main/conf.py:473 +msgid "Primary Galaxy Server Password" +msgstr "Mot de passe du serveur Galaxy primaire" + +#: awx/main/conf.py:474 +msgid "" +"For using a galaxy server at higher precedence than the public Ansible " +"Galaxy. The password to use for basic authentication against the Galaxy " +"instance, this is mutually exclusive with PRIMARY_GALAXY_TOKEN." +msgstr "Permet d’utiliser un serveur Galaxy à une priorité plus élevée que le Galaxy Ansible public. Le mot de passe à utiliser pour l'authentification de base sur la base de l'instance Galaxy, ceci est mutuellement exclusif avec PRIMARY_GALAXY_TOKEN." + +#: awx/main/conf.py:487 +msgid "Primary Galaxy Server Token" +msgstr "Jeton du serveur Galaxy primaire" + +#: awx/main/conf.py:488 +msgid "" +"For using a galaxy server at higher precedence than the public Ansible " +"Galaxy. The token to use for connecting with the Galaxy instance, this is " +"mutually exclusive with corresponding username and password settings." +msgstr "Permet d’utiliser un serveur Galaxy à une priorité plus élevée que le Galaxy Ansible publique. Le jeton à utiliser pour se connecter avec l'instance Galaxy, ceci est mutuellement exclusif avec les paramètres de nom d'utilisateur et de mot de passe correspondants." + +#: awx/main/conf.py:500 +msgid "Primary Galaxy Authentication URL" +msgstr "URL d'authentification du Galaxy primaire" + +#: awx/main/conf.py:501 +msgid "" +"For using a galaxy server at higher precedence than the public Ansible " +"Galaxy. The token_endpoint of a Keycloak server." +msgstr "Permet d’utiliser un serveur Galaxy à une priorité plus élevée que le Galaxy Ansible public. Le token_endpoint d'un serveur Keycloak." + +#: awx/main/conf.py:511 +msgid "Allow Access to Public Galaxy" +msgstr "Permettre l'accès au Galaxy public" + +#: awx/main/conf.py:512 +msgid "" +"Allow or deny access to the public Ansible Galaxy during project updates." +msgstr "Autoriser ou refuser l'accès au Galaxy Ansible public pendant les mises à jour du projet." + +#: awx/main/conf.py:521 +msgid "Ignore Ansible Galaxy SSL Certificate Verification" +msgstr "Ignorer la vérification du certificat SSL Galaxy Ansible" + +#: awx/main/conf.py:522 +msgid "" +"If set to true, certificate validation will not be done wheninstalling " +"content from any Galaxy server." +msgstr "S'il est défini à true, la validation du certificat ne sera pas effectuée lors de l'installation de contenu à partir d'un serveur Galaxy." + +#: awx/main/conf.py:532 msgid "Standard Output Maximum Display Size" msgstr "Taille d'affichage maximale pour une sortie standard" -#: awx/main/conf.py:286 +#: awx/main/conf.py:533 msgid "" "Maximum Size of Standard Output in bytes to display before requiring the " "output be downloaded." -msgstr "" -"Taille maximale d'une sortie standard en octets à afficher avant de demander" -" le téléchargement de la sortie." +msgstr "Taille maximale d'une sortie standard en octets à afficher avant de demander le téléchargement de la sortie." -#: awx/main/conf.py:295 +#: awx/main/conf.py:542 msgid "Job Event Standard Output Maximum Display Size" -msgstr "" -"Taille d'affichage maximale pour une sortie standard d'événement de tâche" +msgstr "Taille d'affichage maximale pour une sortie standard d'événement de tâche" -#: awx/main/conf.py:297 +#: awx/main/conf.py:544 msgid "" "Maximum Size of Standard Output in bytes to display for a single job or ad " "hoc command event. `stdout` will end with `…` when truncated." -msgstr "" -"Taille maximale de la sortie standard en octets à afficher pour une seule " -"tâche ou pour un seul événement de commande ad hoc. `stdout` se terminera " -"par `...` quand il sera tronqué." +msgstr "Taille maximale de la sortie standard en octets à afficher pour une seule tâche ou pour un seul événement de commande ad hoc. `stdout` se terminera par `...` quand il sera tronqué." -#: awx/main/conf.py:306 +#: awx/main/conf.py:553 msgid "Maximum Scheduled Jobs" msgstr "Nombre max. de tâches planifiées" -#: awx/main/conf.py:307 +#: awx/main/conf.py:554 msgid "" "Maximum number of the same job template that can be waiting to run when " "launching from a schedule before no more are created." -msgstr "" -"Nombre maximal du même modèle de tâche qui peut être mis en attente " -"d'exécution lors de son lancement à partir d'un calendrier, avant que " -"d'autres ne soient créés." +msgstr "Nombre maximal du même modèle de tâche qui peut être mis en attente d'exécution lors de son lancement à partir d'un calendrier, avant que d'autres ne soient créés." -#: awx/main/conf.py:316 +#: awx/main/conf.py:563 msgid "Ansible Callback Plugins" msgstr "Plug-ins de rappel Ansible" -#: awx/main/conf.py:317 +#: awx/main/conf.py:564 msgid "" "List of paths to search for extra callback plugins to be used when running " "jobs. Enter one path per line." -msgstr "" -"Liste des chemins servant à rechercher d'autres plug-ins de rappel qui " -"serviront lors de l'exécution de tâches. Saisissez un chemin par ligne." +msgstr "Liste des chemins servant à rechercher d'autres plug-ins de rappel qui serviront lors de l'exécution de tâches. Saisissez un chemin par ligne." -#: awx/main/conf.py:327 +#: awx/main/conf.py:574 msgid "Default Job Timeout" msgstr "Délai d'attente par défaut des tâches" -#: awx/main/conf.py:328 +#: awx/main/conf.py:575 msgid "" "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." -msgstr "" -"Délai maximal (en secondes) d'exécution des tâches. Utilisez la valeur 0 " -"pour indiquer qu'aucun délai ne doit être imposé. Un délai d'attente défini " -"sur celui d'un modèle de tâche précis écrasera cette valeur." +msgstr "Délai maximal (en secondes) d'exécution des tâches. Utilisez la valeur 0 pour indiquer qu'aucun délai ne doit être imposé. Un délai d'attente défini sur celui d'un modèle de tâche précis écrasera cette valeur." -#: awx/main/conf.py:339 +#: awx/main/conf.py:586 msgid "Default Inventory Update Timeout" msgstr "Délai d'attente par défaut pour la mise à jour d'inventaire" -#: awx/main/conf.py:340 +#: awx/main/conf.py:587 msgid "" -"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 " +"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." -msgstr "" -"Délai maximal en secondes d'exécution des mises à jour d'inventaire. " -"Utilisez la valeur 0 pour indiquer qu'aucun délai ne doit être imposé. Un " -"délai d'attente défini sur celui d'une source d'inventaire précise écrasera " -"cette valeur." +msgstr "Délai maximal en secondes d'exécution des mises à jour d'inventaire. Utilisez la valeur 0 pour indiquer qu'aucun délai ne doit être imposé. Un délai d'attente défini sur celui d'une source d'inventaire précise écrasera cette valeur." -#: awx/main/conf.py:351 +#: awx/main/conf.py:598 msgid "Default Project Update Timeout" msgstr "Délai d'attente par défaut pour la mise à jour de projet" -#: awx/main/conf.py:352 +#: awx/main/conf.py:599 msgid "" "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." -msgstr "" -"Délai maximal en secondes d'exécution des mises à jour de projet. Utilisez " -"la valeur 0 pour indiquer qu'aucun délai ne doit être imposé. Un délai " -"d'attente défini sur celui d'un projet précis écrasera cette valeur." +msgstr "Délai maximal en secondes d'exécution des mises à jour de projet. Utilisez la valeur 0 pour indiquer qu'aucun délai ne doit être imposé. Un délai d'attente défini sur celui d'un projet précis écrasera cette valeur." -#: awx/main/conf.py:363 +#: awx/main/conf.py:610 msgid "Per-Host Ansible Fact Cache Timeout" msgstr "Expiration du délai d’attente du cache Ansible Fact Cache par hôte" -#: awx/main/conf.py:364 +#: awx/main/conf.py:611 msgid "" "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 " +"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." -msgstr "" -"Durée maximale, en secondes, pendant laquelle les faits Ansible sont " -"considérés comme valides depuis leur dernière modification. Seuls les faits " -"valides - non périmés - seront accessibles par un Playbook. Remarquez que " -"cela n'a aucune incidence sur la suppression d'ansible_facts de la base de " -"données. Utiliser une valeur de 0 pour indiquer qu'aucun timeout ne soit " -"imposé." - -#: awx/main/conf.py:377 +msgstr "Durée maximale, en secondes, pendant laquelle les faits Ansible sont considérés comme valides depuis leur dernière modification. Seuls les faits valides - non périmés - seront accessibles par un Playbook. Remarquez que cela n'a aucune incidence sur la suppression d'ansible_facts de la base de données. Utiliser une valeur de 0 pour indiquer qu'aucun timeout ne soit imposé." + +#: awx/main/conf.py:624 +msgid "Maximum number of forks per job." +msgstr "Nombre maximum de fourches par tâche." + +#: awx/main/conf.py:625 +msgid "" +"Saving a Job Template with more than this number of forks will result in an " +"error. When set to 0, no limit is applied." +msgstr "L'enregistrement d'un modèle de tâche avec un nombre de fourches supérieur à ce nombre entraînera une erreur. Lorsqu'il est défini sur 0, aucune limite n'est appliquée." + +#: awx/main/conf.py:636 msgid "Logging Aggregator" msgstr "Agrégateur de journalisation" -#: awx/main/conf.py:378 +#: awx/main/conf.py:637 msgid "Hostname/IP where external logs will be sent to." msgstr "Nom d'hôte / IP où les journaux externes seront envoyés." -#: awx/main/conf.py:379 awx/main/conf.py:390 awx/main/conf.py:402 -#: awx/main/conf.py:412 awx/main/conf.py:424 awx/main/conf.py:439 -#: awx/main/conf.py:451 awx/main/conf.py:460 awx/main/conf.py:470 -#: awx/main/conf.py:482 awx/main/conf.py:493 awx/main/conf.py:505 -#: awx/main/conf.py:518 +#: awx/main/conf.py:638 awx/main/conf.py:649 awx/main/conf.py:661 +#: awx/main/conf.py:671 awx/main/conf.py:683 awx/main/conf.py:698 +#: awx/main/conf.py:710 awx/main/conf.py:719 awx/main/conf.py:729 +#: awx/main/conf.py:741 awx/main/conf.py:752 awx/main/conf.py:764 +#: awx/main/conf.py:777 awx/main/conf.py:787 awx/main/conf.py:799 +#: awx/main/conf.py:810 awx/main/conf.py:820 msgid "Logging" msgstr "Journalisation" -#: awx/main/conf.py:387 +#: awx/main/conf.py:646 msgid "Logging Aggregator Port" msgstr "Port d'agrégateur de journalisation" -#: awx/main/conf.py:388 +#: awx/main/conf.py:647 msgid "" "Port on Logging Aggregator to send logs to (if required and not provided in " "Logging Aggregator)." -msgstr "" -"Port d'agrégateur de journalisation où envoyer les journaux (le cas échéant " -"ou si non fourni dans l'agrégateur de journalisation)." +msgstr "Port d'agrégateur de journalisation où envoyer les journaux (le cas échéant ou si non fourni dans l'agrégateur de journalisation)." -#: awx/main/conf.py:400 +#: awx/main/conf.py:659 msgid "Logging Aggregator Type" msgstr "Type d'agrégateur de journalisation" -#: awx/main/conf.py:401 +#: awx/main/conf.py:660 msgid "Format messages for the chosen log aggregator." -msgstr "" -"Formater les messages pour l'agrégateur de journalisation que vous aurez " -"choisi." +msgstr "Formater les messages pour l'agrégateur de journalisation que vous aurez choisi." -#: awx/main/conf.py:410 +#: awx/main/conf.py:669 msgid "Logging Aggregator Username" msgstr "Nom d'utilisateur de l'agrégateur de journalisation" -#: awx/main/conf.py:411 -msgid "Username for external log aggregator (if required)." -msgstr "" -"Nom d'utilisateur pour agrégateur de journalisation externe (le cas " -"échéant)." +#: awx/main/conf.py:670 +msgid "Username for external log aggregator (if required; HTTP/s only)." +msgstr "Nom d'utilisateur pour l'agrégateur de journalisation externe (le cas échéant ; HTTP/s uniquement)." -#: awx/main/conf.py:422 +#: awx/main/conf.py:681 msgid "Logging Aggregator Password/Token" msgstr "Mot de passe / Jeton d'agrégateur de journalisation" -#: awx/main/conf.py:423 +#: awx/main/conf.py:682 msgid "" -"Password or authentication token for external log aggregator (if required)." -msgstr "" -"Mot de passe ou jeton d'authentification d'agrégateur de journalisation " -"externe (le cas échéant)." +"Password or authentication token for external log aggregator (if required; " +"HTTP/s only)." +msgstr "Mot de passe ou jeton d'authentification d'agrégateur de journalisation externe (le cas échéant ; HTTP/s uniquement)." -#: awx/main/conf.py:432 +#: awx/main/conf.py:691 msgid "Loggers Sending Data to Log Aggregator Form" -msgstr "" -"Journaliseurs à l'origine des envois de données à l'agrégateur de journaux" +msgstr "Journaliseurs à l'origine des envois de données à l'agrégateur de journaux" -#: awx/main/conf.py:433 +#: awx/main/conf.py:692 msgid "" -"List of loggers that will send HTTP logs to the collector, these can include any or all of: \n" +"List of loggers that will send HTTP logs to the collector, these can include " +"any or all of: \n" "awx - service logs\n" "activity_stream - activity stream records\n" "job_events - callback data from Ansible job events\n" "system_tracking - facts gathered from scan jobs." -msgstr "" -"Liste des journaliseurs qui enverront des journaux HTTP au collecteur notamment (tous les types ou certains seulement) : \n" +msgstr "Liste des journaliseurs qui enverront des journaux HTTP au collecteur notamment (tous les types ou certains seulement) : \n" "awx - journaux de service\n" "activity_stream - enregistrements de flux d'activité \n" "job_events - données de rappel issues d'événements de tâche Ansible\n" "system_tracking - données générées par des tâches de scan." -#: awx/main/conf.py:446 +#: awx/main/conf.py:705 msgid "Log System Tracking Facts Individually" msgstr "Système de journalisation traçant des facts individuellement" -#: awx/main/conf.py:447 +#: awx/main/conf.py:706 msgid "" "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." -msgstr "" -"Si défini, les facts de traçage de système seront envoyés pour chaque " -"package, service, ou autre item se trouvant dans un scan, ce qui permet une " -"meilleure granularité de recherche. Si non définis, les facts seront envoyés" -" sous forme de dictionnaire unique, ce qui permet une meilleure efficacité " -"du processus pour les facts." +msgstr "Si défini, les facts de traçage de système seront envoyés pour chaque package, service, ou autre item se trouvant dans un scan, ce qui permet une meilleure granularité de recherche. Si non définis, les facts seront envoyés sous forme de dictionnaire unique, ce qui permet une meilleure efficacité du processus pour les facts." -#: awx/main/conf.py:458 +#: awx/main/conf.py:717 msgid "Enable External Logging" msgstr "Activer la journalisation externe" -#: awx/main/conf.py:459 +#: awx/main/conf.py:718 msgid "Enable sending logs to external log aggregator." msgstr "Activer l'envoi de journaux à un agrégateur de journaux externe." -#: awx/main/conf.py:468 +#: awx/main/conf.py:727 msgid "Cluster-wide Tower unique identifier." msgstr "Identificateur unique Tower" -#: awx/main/conf.py:469 +#: awx/main/conf.py:728 msgid "Useful to uniquely identify Tower instances." msgstr "Utile pour identifier les instances Tower spécifiquement." -#: awx/main/conf.py:478 +#: awx/main/conf.py:737 msgid "Logging Aggregator Protocol" msgstr "Protocole de l'agrégateur de journalisation" -#: awx/main/conf.py:479 +#: awx/main/conf.py:738 msgid "" "Protocol used to communicate with log aggregator. HTTPS/HTTP assumes HTTPS " "unless http:// is explicitly used in the Logging Aggregator hostname." -msgstr "" -"Le protocole utilisé pour communiquer avec l'agrégateur de journalisation. " -"HTTPS/HTTP assume HTTPS à moins que http:// ne soit explicitement utilisé " -"dans le nom d'hôte de l'agrégateur de journalisation." +msgstr "Le protocole utilisé pour communiquer avec l'agrégateur de journalisation. HTTPS/HTTP assume HTTPS à moins que http:// ne soit explicitement utilisé dans le nom d'hôte de l'agrégateur de journalisation." -#: awx/main/conf.py:489 +#: awx/main/conf.py:748 msgid "TCP Connection Timeout" msgstr "Expiration du délai d'attente de connexion TCP" -#: awx/main/conf.py:490 +#: awx/main/conf.py:749 msgid "" "Number of seconds for a TCP connection to external log aggregator to " "timeout. Applies to HTTPS and TCP log aggregator protocols." -msgstr "" -"Délai d'attente (nombre de secondes) entre une connexion TCP et un " -"agrégateur de journalisation externe. S'applique aux protocoles des " -"agrégateurs de journalisation HTTPS et TCP." +msgstr "Délai d'attente (nombre de secondes) entre une connexion TCP et un agrégateur de journalisation externe. S'applique aux protocoles des agrégateurs de journalisation HTTPS et TCP." -#: awx/main/conf.py:500 +#: awx/main/conf.py:759 msgid "Enable/disable HTTPS certificate verification" msgstr "Activer/désactiver la vérification de certificat HTTPS" -#: awx/main/conf.py:501 +#: awx/main/conf.py:760 msgid "" "Flag to control enable/disable of certificate verification when " "LOG_AGGREGATOR_PROTOCOL is \"https\". If enabled, Tower's log handler will " "verify certificate sent by external log aggregator before establishing " "connection." -msgstr "" -"Indicateur permettant de contrôler l'activation/désactivation de la " -"vérification des certificats lorsque LOG_AGGREGATOR_PROTOCOL est \"https\". " -"S'il est activé, le gestionnaire de journal de Tower vérifiera le certificat" -" envoyé par l'agrégateur de journalisation externe avant d'établir la " -"connexion." +msgstr "Indicateur permettant de contrôler l'activation/désactivation de la vérification des certificats lorsque LOG_AGGREGATOR_PROTOCOL est \"https\". S'il est activé, le gestionnaire de journal de Tower vérifiera le certificat envoyé par l'agrégateur de journalisation externe avant d'établir la connexion." -#: awx/main/conf.py:513 +#: awx/main/conf.py:772 msgid "Logging Aggregator Level Threshold" msgstr "Seuil du niveau de l'agrégateur de journalisation" -#: awx/main/conf.py:514 +#: awx/main/conf.py:773 msgid "" "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)" -msgstr "" -"Seuil de niveau utilisé par le gestionnaire de journal. Les niveaux de " -"gravité sont les suivants (de la plus faible à la plus grave) : DEBUG " -"(débogage), INFO, WARNING (avertissement), ERROR (erreur), CRITICAL " -"(critique). Les messages moins graves que le seuil seront ignorés par le " -"gestionnaire de journal. (les messages sous la catégorie awx.anlytics " -"ignorent ce paramètre)" - -#: awx/main/conf.py:537 awx/sso/conf.py:1264 +"threshold will be ignored by log handler. (messages under category awx." +"anlytics ignore this setting)" +msgstr "Seuil de niveau utilisé par le gestionnaire de journal. Les niveaux de gravité sont les suivants (de la plus faible à la plus grave) : DEBUG (débogage), INFO, WARNING (avertissement), ERROR (erreur), CRITICAL (critique). Les messages moins graves que le seuil seront ignorés par le gestionnaire de journal. (les messages sous la catégorie awx.anlytics ignorent ce paramètre)" + +#: awx/main/conf.py:785 +msgid "Enabled external log aggregation auditing" +msgstr "Activation de l’audit d’agrégation des journaux externes" + +#: awx/main/conf.py:786 +msgid "" +"When enabled, all external logs emitted by Tower will also be written to /" +"var/log/tower/external.log. This is an experimental setting intended to be " +"used for debugging external log aggregation issues (and may be subject to " +"change in the future)." +msgstr "Lorsque cette option est activée, tous les journaux externes émis par Tower seront également écrits dans /var/log/tower/external.log. Il s'agit d'un paramètre expérimental destiné à être utilisé pour le débogage de problèmes d'agrégation de journaux externes (et qui pourrait être modifié à l'avenir)." + +#: awx/main/conf.py:795 +msgid "Maximum disk persistance for external log aggregation (in GB)" +msgstr "Persistance maximale du disque pour l'agrégation de journalisation externe (en Go)" + +#: awx/main/conf.py:796 +msgid "" +"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." +msgstr "Quantité de données à stocker (en gigaoctets) lors d'une panne de l'agrégateur de journalisation externe (valeur par défaut : 1). Équivalent au paramètre rsyslogd queue.maxdiskspace." + +#: awx/main/conf.py:806 +msgid "File system location for rsyslogd disk persistence" +msgstr "Emplacement du système de fichiers pour la persistance du disque rsyslogd" + +#: awx/main/conf.py:807 +msgid "" +"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." +msgstr "Emplacement de persistance des journaux qui doivent être réessayés après une panne de l'agrégateur de journalisation externe (par défaut : /var/lib/awx). Équivalent au paramètre rsyslogd queue.spoolDirectory." + +#: awx/main/conf.py:817 +msgid "Enable rsyslogd debugging" +msgstr "Activer le débogage de rsyslogd" + +#: awx/main/conf.py:818 +msgid "" +"Enabled high verbosity debugging for rsyslogd. Useful for debugging " +"connection issues for external log aggregation." +msgstr "Activation du débogage de la verbosité élevée de rsyslogd. Utile pour le débogage des problèmes de connexion pour l'agrégation de journalisation externe." + +#: awx/main/conf.py:828 +msgid "Message Durability" +msgstr "Durabilité des messages" + +#: awx/main/conf.py:829 +msgid "" +"When set (the default), underlying queues will be persisted to disk. " +"Disable this to enable higher message bus throughput." +msgstr "Lorsque cette option est définie (valeur par défaut), les files d'attente sous-jacentes sont conservées sur le disque. Désactivez cette option pour permettre un débit de bus de message plus élevé." + +#: awx/main/conf.py:838 +msgid "Last gather date for Automation Analytics." +msgstr "Dernière date de rassemblement pour Automation Analytics." + +#: awx/main/conf.py:848 +msgid "Automation Analytics Gather Interval" +msgstr "Intervalle de collecte des données d'automatisation" + +#: awx/main/conf.py:849 +msgid "Interval (in seconds) between data gathering." +msgstr "Intervalle (en secondes) entre les collectes de données." + +#: awx/main/conf.py:871 awx/sso/conf.py:1239 msgid "\n" msgstr "\n" +#: awx/main/conf.py:892 +msgid "" +"A URL for Primary Galaxy must be defined before disabling public Galaxy." +msgstr "Une URL pour le Galaxy primaire doit être définie avant de désactiver le Galaxy public." + +#: awx/main/conf.py:912 +msgid "Cannot provide field if PRIMARY_GALAXY_URL is not set." +msgstr "Ne peut pas fournir de champ si PRIMARY_GALAXY_URL n'est pas défini." + +#: awx/main/conf.py:925 +#, python-brace-format +msgid "" +"Galaxy server settings are not available until Ansible {min_version}, you " +"are running {current_version}." +msgstr "Les paramètres du serveur Galaxy ne sont disponibles qu’à partir d’Ansible {min_version}, vous exécutez la version {current_version}." + +#: awx/main/conf.py:934 +msgid "" +"Setting Galaxy token and authentication URL is mutually exclusive with " +"username and password." +msgstr "La définition du jeton Galaxy et de l'URL d'authentification est mutuellement exclusive avec le nom d'utilisateur et le mot de passe." + +#: awx/main/conf.py:937 +msgid "If authenticating via username and password, both must be provided." +msgstr "En cas d'authentification par nom d'utilisateur et mot de passe, les deux doivent être fournis." + +#: awx/main/conf.py:943 +msgid "" +"If authenticating via token, both token and authentication URL must be " +"provided." +msgstr "En cas d'authentification par jeton, le jeton et l'URL d'authentification doivent tous deux être fournis." + #: awx/main/constants.py:17 msgid "Sudo" msgstr "Sudo" @@ -2514,208 +2685,401 @@ msgstr "Activer" msgid "Doas" msgstr "Comme" -#: awx/main/constants.py:21 +#: awx/main/constants.py:19 +msgid "Ksu" +msgstr "Ksu" + +#: awx/main/constants.py:20 +msgid "Machinectl" +msgstr "Machinectl" + +#: awx/main/constants.py:20 +msgid "Sesu" +msgstr "Sesu" + +#: awx/main/constants.py:22 msgid "None" msgstr "Aucun" -#: awx/main/fields.py:62 +#: awx/main/credential_plugins/aim.py:16 +msgid "CyberArk AIM URL" +msgstr "URL CyberArk AIM" + +#: awx/main/credential_plugins/aim.py:21 +msgid "Application ID" +msgstr "ID d'application" + +#: awx/main/credential_plugins/aim.py:26 +msgid "Client Key" +msgstr "Clé du client" + +#: awx/main/credential_plugins/aim.py:32 +msgid "Client Certificate" +msgstr "Certificat client" + +#: awx/main/credential_plugins/aim.py:38 +msgid "Verify SSL Certificates" +msgstr "Vérifier les certificats SSL" + +#: awx/main/credential_plugins/aim.py:44 +msgid "Object Query" +msgstr "Requête d'objet" + +#: awx/main/credential_plugins/aim.py:46 +msgid "" +"Lookup query for the object. Ex: Safe=TestSafe;Object=testAccountName123" +msgstr "Requête de recherche pour l'objet. Ex. : Safe=TestSafe;Object=testAccountName123" + +#: awx/main/credential_plugins/aim.py:49 +msgid "Object Query Format" +msgstr "Format de requête d'objet" + +#: awx/main/credential_plugins/aim.py:55 +msgid "Reason" +msgstr "Raison" + +#: awx/main/credential_plugins/aim.py:57 +msgid "" +"Object request reason. This is only needed if it is required by the object's " +"policy." +msgstr "Raison de la requête d'objet. Cela n'est nécessaire que si cela est requis par la stratégie de l'objet." + +#: awx/main/credential_plugins/azure_kv.py:21 +msgid "Vault URL (DNS Name)" +msgstr "URL du coffre (nom DNS)" + +#: awx/main/credential_plugins/azure_kv.py:26 +#: awx/main/models/credential/__init__.py:956 +msgid "Client ID" +msgstr "ID du client" + +#: awx/main/credential_plugins/azure_kv.py:35 +#: awx/main/models/credential/__init__.py:965 +msgid "Tenant ID" +msgstr "ID Client" + +#: awx/main/credential_plugins/azure_kv.py:39 +msgid "Cloud Environment" +msgstr "Environnement Cloud" + +#: awx/main/credential_plugins/azure_kv.py:40 +msgid "Specify which azure cloud environment to use." +msgstr "Précisez quel environnement Cloud Azure utiliser." + +#: awx/main/credential_plugins/azure_kv.py:46 +msgid "Secret Name" +msgstr "Nom secret" + +#: awx/main/credential_plugins/azure_kv.py:48 +msgid "The name of the secret to look up." +msgstr "Nom du secret à rechercher." + +#: awx/main/credential_plugins/azure_kv.py:51 +#: awx/main/credential_plugins/conjur.py:47 +msgid "Secret Version" +msgstr "Version secrète" + +#: awx/main/credential_plugins/azure_kv.py:53 +#: awx/main/credential_plugins/conjur.py:49 +#: awx/main/credential_plugins/hashivault.py:67 +msgid "" +"Used to specify a specific secret version (if left empty, the latest version " +"will be used)." +msgstr "Utilisé pour spécifier une version secrète spécifique (si elle est laissée vide, la dernière version sera utilisée)." + +#: awx/main/credential_plugins/conjur.py:18 +msgid "Conjur URL" +msgstr "URL Conjur" + +#: awx/main/credential_plugins/conjur.py:23 +msgid "API Key" +msgstr "Clé API" + +#: awx/main/credential_plugins/conjur.py:28 awx/main/models/inventory.py:1018 +msgid "Account" +msgstr "Compte" + +#: awx/main/credential_plugins/conjur.py:32 +#: awx/main/models/credential/__init__.py:598 +#: awx/main/models/credential/__init__.py:654 +#: awx/main/models/credential/__init__.py:712 +#: awx/main/models/credential/__init__.py:785 +#: awx/main/models/credential/__init__.py:834 +#: awx/main/models/credential/__init__.py:860 +#: awx/main/models/credential/__init__.py:887 +#: awx/main/models/credential/__init__.py:947 +#: awx/main/models/credential/__init__.py:1020 +#: awx/main/models/credential/__init__.py:1051 +#: awx/main/models/credential/__init__.py:1101 +msgid "Username" +msgstr "Nom d'utilisateur" + +#: awx/main/credential_plugins/conjur.py:36 +msgid "Public Key Certificate" +msgstr "Certificat de clé publique" + +#: awx/main/credential_plugins/conjur.py:42 +msgid "Secret Identifier" +msgstr "Identifiant secret" + +#: awx/main/credential_plugins/conjur.py:44 +msgid "The identifier for the secret e.g., /some/identifier" +msgstr "Identifiant du secret, par exemple : /un/identifiant" + +#: awx/main/credential_plugins/hashivault.py:19 +msgid "Server URL" +msgstr "URL du serveur" + +#: awx/main/credential_plugins/hashivault.py:22 +msgid "The URL to the HashiCorp Vault" +msgstr "URL du coffre HashiCorp" + +#: awx/main/credential_plugins/hashivault.py:25 +#: awx/main/models/credential/__init__.py:986 +#: awx/main/models/credential/__init__.py:1003 +msgid "Token" +msgstr "Token" + +#: awx/main/credential_plugins/hashivault.py:28 +msgid "The access token used to authenticate to the Vault server" +msgstr "Jeton d'accès utilisé pour s'authentifier auprès du serveur Vault" + +#: awx/main/credential_plugins/hashivault.py:31 +msgid "CA Certificate" +msgstr "Certificat CA" + +#: awx/main/credential_plugins/hashivault.py:34 +msgid "" +"The CA certificate used to verify the SSL certificate of the Vault server" +msgstr "Le certificat CA utilisé pour vérifier le certificat SSL du serveur Vault" + +#: awx/main/credential_plugins/hashivault.py:38 +msgid "Path to Secret" +msgstr "Chemin d'accès au secret" + +#: awx/main/credential_plugins/hashivault.py:40 +msgid "The path to the secret stored in the secret backend e.g, /some/secret/" +msgstr "Chemin d'accès au secret stocké dans le backend du secret, par exemple /un/secret/" + +#: awx/main/credential_plugins/hashivault.py:48 +msgid "API Version" +msgstr "Version de l'API" + +#: awx/main/credential_plugins/hashivault.py:50 +msgid "" +"API v1 is for static key/value lookups. API v2 is for versioned key/value " +"lookups." +msgstr "API v1 convient aux recherches de clé statique/valeur. API v2 convient aux recherches clé/valeur versionnées." + +#: awx/main/credential_plugins/hashivault.py:55 +msgid "Name of Secret Backend" +msgstr "Nom du backend secret" + +#: awx/main/credential_plugins/hashivault.py:57 +msgid "" +"The name of the kv secret backend (if left empty, the first segment of the " +"secret path will be used)." +msgstr "Nom du backend secret (s'il est laissé vide, le premier segment du chemin secret sera utilisé)." + +#: awx/main/credential_plugins/hashivault.py:60 +#: awx/main/models/inventory.py:1023 +msgid "Key Name" +msgstr "Nom de la clé" + +#: awx/main/credential_plugins/hashivault.py:62 +msgid "The name of the key to look up in the secret." +msgstr "Nom de la clé à rechercher dans le secret." + +#: awx/main/credential_plugins/hashivault.py:65 +msgid "Secret Version (v2 only)" +msgstr "Version secrète (v2 uniquement)" + +#: awx/main/credential_plugins/hashivault.py:74 +msgid "Unsigned Public Key" +msgstr "Clé publique non signée" + +#: awx/main/credential_plugins/hashivault.py:79 +msgid "Role Name" +msgstr "Nom du rôle" + +#: awx/main/credential_plugins/hashivault.py:81 +msgid "The name of the role used to sign." +msgstr "Le nom du rôle utilisé pour signer." + +#: awx/main/credential_plugins/hashivault.py:84 +msgid "Valid Principals" +msgstr "Principaux valides" + +#: awx/main/credential_plugins/hashivault.py:86 +msgid "" +"Valid principals (either usernames or hostnames) that the certificate should " +"be signed for." +msgstr "Principaux valides (noms d'utilisateur ou noms d'hôte) pour lesquels le certificat doit être signé." + +#: awx/main/fields.py:67 #, python-brace-format msgid "'{value}' is not one of ['{allowed_values}']" -msgstr "'{value}' n'appartient pas aux valeurs ['{allowed_values}']" +msgstr "'{value}' ne fait pas partie de ['{allowed_values}']" -#: awx/main/fields.py:421 +#: awx/main/fields.py:439 #, python-brace-format msgid "{type} provided in relative path {path}, expected {expected_type}" -msgstr "" -"{type} donné dans le chemin d'accès relatif {path}, {expected_type} attendu" +msgstr "{type} fourni dans le chemin d’accès relatif {path}, {expected_type} attendu" -#: awx/main/fields.py:426 +#: awx/main/fields.py:444 #, python-brace-format msgid "{type} provided, expected {expected_type}" -msgstr "{type} donné, {expected_type} attendu" +msgstr "{type} fourni, {expected_type} attendu" -#: awx/main/fields.py:431 +#: awx/main/fields.py:449 #, python-brace-format msgid "Schema validation error in relative path {path} ({error})" -msgstr "" -"Erreur de validation de schéma dans le chemin relatif {path} ({error})" +msgstr "Erreur de validation de schéma dans le chemin d’accès relatif {path} ({error})" -#: awx/main/fields.py:552 +#: awx/main/fields.py:558 +#, python-format +msgid "required for %s" +msgstr "requis pour %s" + +#: awx/main/fields.py:632 msgid "secret values must be of type string, not {}" msgstr "les valeurs secrètes doivent être sous forme de string, et non pas {}" -#: awx/main/fields.py:587 +#: awx/main/fields.py:667 #, python-format msgid "cannot be set unless \"%s\" is set" -msgstr "ne peut être défini à moins que \"%s\" soit défini" - -#: awx/main/fields.py:603 -#, python-format -msgid "required for %s" -msgstr "requis pour %s" +msgstr "ne peut être défini à moins que \"%s\" ne soit défini" -#: awx/main/fields.py:627 +#: awx/main/fields.py:702 msgid "must be set when SSH key is encrypted." msgstr "doit être défini lorsque la clé SSH est chiffrée." -#: awx/main/fields.py:633 +#: awx/main/fields.py:710 msgid "should not be set when SSH key is not encrypted." msgstr "ne doit pas être défini lorsque la clé SSH n'est pas chiffrée." -#: awx/main/fields.py:691 +#: awx/main/fields.py:769 msgid "'dependencies' is not supported for custom credentials." -msgstr "" -"les dépendances ne sont pas prises en charge pour les identifiants " -"personnalisés." +msgstr "les dépendances ne sont pas prises en charge pour les identifiants personnalisés." -#: awx/main/fields.py:705 +#: awx/main/fields.py:783 msgid "\"tower\" is a reserved field name" msgstr "\"tower\" est un nom de champ réservé" -#: awx/main/fields.py:712 +#: awx/main/fields.py:790 #, python-format msgid "field IDs must be unique (%s)" -msgstr "Les ID de champ doivent être uniques (%s)" +msgstr "les ID de champ doivent être uniques (%s)" -#: awx/main/fields.py:725 -msgid "become_method is a reserved type name" -msgstr "become_method est un type de nom réservé" +#: awx/main/fields.py:805 +msgid "{} is not a {}" +msgstr "{} n'est pas un(e) {}" -#: awx/main/fields.py:736 +#: awx/main/fields.py:811 #, python-brace-format msgid "{sub_key} not allowed for {element_type} type ({element_id})" msgstr "{sub_key} non autorisé pour le type {element_type} ({element_id})" -#: awx/main/fields.py:810 +#: awx/main/fields.py:869 +msgid "" +"Environment variable {} may affect Ansible configuration so its use is not " +"allowed in credentials." +msgstr "La variable d'environnement {} peut affecter la configuration Ansible, donc son utilisation n'est pas autorisée pour les identifiants." + +#: awx/main/fields.py:875 +msgid "Environment variable {} is blacklisted from use in credentials." +msgstr "La variable d'environnement {} n'est pas autorisée pour les identifiants." + +#: awx/main/fields.py:903 msgid "" "Must define unnamed file injector in order to reference `tower.filename`." -msgstr "" -"Doit définir l'injecteur de fichier sans nom pour pouvoir référencer « " -"tower.filename »." +msgstr "Doit définir l'injecteur de fichier sans nom pour pouvoir référencer « tower.filename »." -#: awx/main/fields.py:817 +#: awx/main/fields.py:910 msgid "Cannot directly reference reserved `tower` namespace container." -msgstr "" -"Impossible de référencer directement le conteneur d'espace de nommage " -"réservé « Tower »." +msgstr "Impossible de référencer directement le conteneur d'espace de nommage réservé « Tower »." -#: awx/main/fields.py:827 +#: awx/main/fields.py:920 msgid "Must use multi-file syntax when injecting multiple files" -msgstr "" -"Doit utiliser la syntaxe multi-fichier lors de l'injection de plusieurs " -"fichiers." +msgstr "Doit utiliser la syntaxe multi-fichier lors de l'injection de plusieurs fichiers." -#: awx/main/fields.py:844 +#: awx/main/fields.py:940 #, python-brace-format msgid "{sub_key} uses an undefined field ({error_msg})" msgstr "{sub_key} utilise un champ non défini ({error_msg})" -#: awx/main/fields.py:851 +#: awx/main/fields.py:947 #, python-brace-format msgid "" "Syntax error rendering template for {sub_key} inside of {type} ({error_msg})" -msgstr "" -"Il y a une erreur de rendu de modèle {sub_key} dans {type} ({error_msg})" +msgstr "Il y a une erreur de rendu de modèle pour {sub_key} dans {type} ({error_msg})" -#: awx/main/middleware.py:160 +#: awx/main/middleware.py:118 msgid "Formats of all available named urls" msgstr "Formats de toutes les URL nommées disponibles" -#: awx/main/middleware.py:161 +#: awx/main/middleware.py:119 msgid "" "Read-only list of key-value pairs that shows the standard format of all " "available named URLs." -msgstr "" -"Liste en lecture seule de paires clé/valeur qui affiche le format standard " -"de toutes les URL nommées disponibles." +msgstr "Liste en lecture seule de paires clé/valeur qui affiche le format standard de toutes les URL nommées disponibles." -#: awx/main/middleware.py:163 awx/main/middleware.py:173 +#: awx/main/middleware.py:121 awx/main/middleware.py:131 msgid "Named URL" msgstr "URL nommée" -#: awx/main/middleware.py:170 +#: awx/main/middleware.py:128 msgid "List of all named url graph nodes." msgstr "Liste de tous les noeuds de graphique des URL nommées." -#: awx/main/middleware.py:171 +#: awx/main/middleware.py:129 msgid "" -"Read-only list of key-value pairs that exposes named URL graph topology. Use" -" this list to programmatically generate named URLs for resources" -msgstr "" -"Liste en lecture seule de paires clé/valeur qui affiche la topologie des " -"graphiques des URL nommées. Utilisez cette liste pour générer via un " -"programme des URL nommées pour les ressources" - -#: awx/main/migrations/_reencrypt.py:26 awx/main/models/notifications.py:35 -msgid "Email" -msgstr "Email" - -#: awx/main/migrations/_reencrypt.py:27 awx/main/models/notifications.py:36 -msgid "Slack" -msgstr "Slack" - -#: awx/main/migrations/_reencrypt.py:28 awx/main/models/notifications.py:37 -msgid "Twilio" -msgstr "Twilio" - -#: awx/main/migrations/_reencrypt.py:29 awx/main/models/notifications.py:38 -msgid "Pagerduty" -msgstr "Pagerduty" - -#: awx/main/migrations/_reencrypt.py:30 awx/main/models/notifications.py:39 -msgid "HipChat" -msgstr "HipChat" - -#: awx/main/migrations/_reencrypt.py:31 awx/main/models/notifications.py:41 -msgid "Mattermost" -msgstr "Mattermost" - -#: awx/main/migrations/_reencrypt.py:32 awx/main/models/notifications.py:40 -msgid "Webhook" -msgstr "Webhook" - -#: awx/main/migrations/_reencrypt.py:33 awx/main/models/notifications.py:43 -msgid "IRC" -msgstr "IRC" +"Read-only list of key-value pairs that exposes named URL graph topology. Use " +"this list to programmatically generate named URLs for resources" +msgstr "Liste en lecture seule de paires clé/valeur qui affiche la topologie des graphiques des URL nommées. Utilisez cette liste pour générer via un programme des URL nommées pour les ressources" -#: awx/main/models/activity_stream.py:25 +#: awx/main/models/activity_stream.py:28 msgid "Entity Created" msgstr "Entité créée" -#: awx/main/models/activity_stream.py:26 +#: awx/main/models/activity_stream.py:29 msgid "Entity Updated" msgstr "Entité mise à jour" -#: awx/main/models/activity_stream.py:27 +#: awx/main/models/activity_stream.py:30 msgid "Entity Deleted" msgstr "Entité supprimée" -#: awx/main/models/activity_stream.py:28 +#: awx/main/models/activity_stream.py:31 msgid "Entity Associated with another Entity" msgstr "Entité associée à une autre entité" -#: awx/main/models/activity_stream.py:29 +#: awx/main/models/activity_stream.py:32 msgid "Entity was Disassociated with another Entity" msgstr "Entité dissociée d'une autre entité" -#: awx/main/models/ad_hoc_commands.py:95 +#: awx/main/models/activity_stream.py:45 +msgid "The cluster node the activity took place on." +msgstr "Le nœud de cluster sur lequel l'activité a eu lieu." + +#: awx/main/models/ad_hoc_commands.py:97 msgid "No valid inventory." msgstr "Aucun inventaire valide." -#: awx/main/models/ad_hoc_commands.py:102 +#: awx/main/models/ad_hoc_commands.py:104 msgid "You must provide a machine / SSH credential." msgstr "Vous devez fournir des informations d'identification machine / SSH." -#: awx/main/models/ad_hoc_commands.py:113 -#: awx/main/models/ad_hoc_commands.py:121 +#: awx/main/models/ad_hoc_commands.py:115 +#: awx/main/models/ad_hoc_commands.py:123 msgid "Invalid type for ad hoc command" msgstr "Type non valide pour la commande ad hoc" -#: awx/main/models/ad_hoc_commands.py:116 +#: awx/main/models/ad_hoc_commands.py:118 msgid "Unsupported module for ad hoc commands." msgstr "Module non pris en charge pour les commandes ad hoc." -#: awx/main/models/ad_hoc_commands.py:124 +#: awx/main/models/ad_hoc_commands.py:126 #, python-format msgid "No argument passed to %s module." msgstr "Aucun argument transmis au module %s." @@ -2732,1116 +3096,954 @@ msgstr "Vérifier" #: awx/main/models/base.py:35 msgid "Scan" -msgstr "Scanner" - -#: awx/main/models/credential/__init__.py:110 -msgid "Host" -msgstr "Hôte" - -#: awx/main/models/credential/__init__.py:111 -msgid "The hostname or IP address to use." -msgstr "Nom d'hôte ou adresse IP à utiliser." - -#: awx/main/models/credential/__init__.py:117 -#: awx/main/models/credential/__init__.py:686 -#: awx/main/models/credential/__init__.py:741 -#: awx/main/models/credential/__init__.py:806 -#: awx/main/models/credential/__init__.py:884 -#: awx/main/models/credential/__init__.py:930 -#: awx/main/models/credential/__init__.py:958 -#: awx/main/models/credential/__init__.py:987 -#: awx/main/models/credential/__init__.py:1051 -#: awx/main/models/credential/__init__.py:1092 -#: awx/main/models/credential/__init__.py:1125 -#: awx/main/models/credential/__init__.py:1177 -msgid "Username" -msgstr "Nom d'utilisateur" - -#: awx/main/models/credential/__init__.py:118 -msgid "Username for this credential." -msgstr "Nom d'utilisateur pour ces informations d'identification." - -#: awx/main/models/credential/__init__.py:124 -#: awx/main/models/credential/__init__.py:690 -#: awx/main/models/credential/__init__.py:745 -#: awx/main/models/credential/__init__.py:810 -#: awx/main/models/credential/__init__.py:934 -#: awx/main/models/credential/__init__.py:962 -#: awx/main/models/credential/__init__.py:991 -#: awx/main/models/credential/__init__.py:1055 -#: awx/main/models/credential/__init__.py:1096 -#: awx/main/models/credential/__init__.py:1129 -#: awx/main/models/credential/__init__.py:1181 -msgid "Password" -msgstr "Mot de passe" - -#: awx/main/models/credential/__init__.py:125 -msgid "" -"Password for this credential (or \"ASK\" to prompt the user for machine " -"credentials)." -msgstr "" -"Mot de passe pour ces informations d'identification (ou \"ASK\" pour " -"demander à l'utilisateur les informations d'identification de la machine)." - -#: awx/main/models/credential/__init__.py:132 -msgid "Security Token" -msgstr "Token de sécurité" - -#: awx/main/models/credential/__init__.py:133 -msgid "Security Token for this credential" -msgstr "Token de sécurité pour ces informations d'identification" - -#: awx/main/models/credential/__init__.py:139 -msgid "Project" -msgstr "Projet" - -#: awx/main/models/credential/__init__.py:140 -msgid "The identifier for the project." -msgstr "Identifiant du projet." - -#: awx/main/models/credential/__init__.py:146 -msgid "Domain" -msgstr "Domaine" - -#: awx/main/models/credential/__init__.py:147 -msgid "The identifier for the domain." -msgstr "Identifiant du domaine." - -#: awx/main/models/credential/__init__.py:152 -msgid "SSH private key" -msgstr "Clé privée SSH" - -#: awx/main/models/credential/__init__.py:153 -msgid "RSA or DSA private key to be used instead of password." -msgstr "Clé privée RSA ou DSA à utiliser au lieu du mot de passe." - -#: awx/main/models/credential/__init__.py:159 -msgid "SSH key unlock" -msgstr "Déverrouillage de la clé SSH" - -#: awx/main/models/credential/__init__.py:160 -msgid "" -"Passphrase to unlock SSH private key if encrypted (or \"ASK\" to prompt the " -"user for machine credentials)." -msgstr "" -"Phrase de passe servant à déverrouiller la clé privée SSH si elle est " -"chiffrée (ou \"ASK\" pour demander à l'utilisateur les informations " -"d'identification de la machine)." - -#: awx/main/models/credential/__init__.py:168 -msgid "Privilege escalation method." -msgstr "Méthode d'élévation des privilèges." - -#: awx/main/models/credential/__init__.py:174 -msgid "Privilege escalation username." -msgstr "Nom d'utilisateur pour l'élévation des privilèges" - -#: awx/main/models/credential/__init__.py:180 -msgid "Password for privilege escalation method." -msgstr "Mot de passe pour la méthode d'élévation des privilèges." - -#: awx/main/models/credential/__init__.py:186 -msgid "Vault password (or \"ASK\" to prompt the user)." -msgstr "Mot de passe Vault (ou \"ASK\" pour le demander à l'utilisateur)." - -#: awx/main/models/credential/__init__.py:190 -msgid "Whether to use the authorize mechanism." -msgstr "Indique s'il faut ou non utiliser le mécanisme d'autorisation." - -#: awx/main/models/credential/__init__.py:196 -msgid "Password used by the authorize mechanism." -msgstr "Mot de passe utilisé par le mécanisme d'autorisation." - -#: awx/main/models/credential/__init__.py:202 -msgid "Client Id or Application Id for the credential" -msgstr "" -"ID du client ou de l'application pour les informations d'identification" - -#: awx/main/models/credential/__init__.py:208 -msgid "Secret Token for this credential" -msgstr "Token secret pour ces informations d'identification" - -#: awx/main/models/credential/__init__.py:214 -msgid "Subscription identifier for this credential" -msgstr "ID d'abonnement pour ces informations d'identification" - -#: awx/main/models/credential/__init__.py:220 -msgid "Tenant identifier for this credential" -msgstr "ID de tenant pour ces informations d'identification" +msgstr "Scanner" -#: awx/main/models/credential/__init__.py:244 +#: awx/main/models/credential/__init__.py:96 msgid "" "Specify the type of credential you want to create. Refer to the Ansible " "Tower documentation for details on each type." -msgstr "" -"Spécifiez le type d'information d'identification à créer. Consultez la " -"documentation d’Ansible Tower pour en savoir plus sur chaque type." +msgstr "Spécifiez le type d'information d'identification à créer. Consultez la documentation d’Ansible Tower pour en savoir plus sur chaque type." -#: awx/main/models/credential/__init__.py:258 -#: awx/main/models/credential/__init__.py:476 +#: awx/main/models/credential/__init__.py:110 +#: awx/main/models/credential/__init__.py:353 msgid "" -"Enter inputs using either JSON or YAML syntax. Use the radio button to " -"toggle between the two. Refer to the Ansible Tower documentation for example" -" syntax." -msgstr "" -"Entrez les variables avec la syntaxe JSON ou YAML. Utilisez le bouton radio " -"pour basculer entre les deux. Consultez la documentation d’Ansible Tower " -"pour avoir un exemple de syntaxe." +"Enter inputs using either JSON or YAML syntax. Refer to the Ansible Tower " +"documentation for example syntax." +msgstr "Entrez les variables avec la syntaxe JSON ou YAML. Consultez la documentation d’Ansible Tower pour avoir un exemple de syntaxe." -#: awx/main/models/credential/__init__.py:457 -#: awx/main/models/credential/__init__.py:681 +#: awx/main/models/credential/__init__.py:325 +#: awx/main/models/credential/__init__.py:594 msgid "Machine" msgstr "Machine" -#: awx/main/models/credential/__init__.py:458 -#: awx/main/models/credential/__init__.py:772 +#: awx/main/models/credential/__init__.py:326 +#: awx/main/models/credential/__init__.py:680 msgid "Vault" msgstr "Coffre-fort" -#: awx/main/models/credential/__init__.py:459 -#: awx/main/models/credential/__init__.py:801 +#: awx/main/models/credential/__init__.py:327 +#: awx/main/models/credential/__init__.py:707 msgid "Network" msgstr "Réseau" -#: awx/main/models/credential/__init__.py:460 -#: awx/main/models/credential/__init__.py:736 +#: awx/main/models/credential/__init__.py:328 +#: awx/main/models/credential/__init__.py:649 msgid "Source Control" msgstr "Contrôle de la source" -#: awx/main/models/credential/__init__.py:461 +#: awx/main/models/credential/__init__.py:329 msgid "Cloud" msgstr "Cloud" -#: awx/main/models/credential/__init__.py:462 -#: awx/main/models/credential/__init__.py:1087 +#: awx/main/models/credential/__init__.py:330 +msgid "Personal Access Token" +msgstr "Jeton d'accès personnel" + +#: awx/main/models/credential/__init__.py:331 +#: awx/main/models/credential/__init__.py:1015 msgid "Insights" msgstr "Insights" -#: awx/main/models/credential/__init__.py:483 +#: awx/main/models/credential/__init__.py:332 +msgid "External" +msgstr "Externe" + +#: awx/main/models/credential/__init__.py:333 +msgid "Kubernetes" +msgstr "Kubernetes" + +#: awx/main/models/credential/__init__.py:359 msgid "" -"Enter injectors using either JSON or YAML syntax. Use the radio button to " -"toggle between the two. Refer to the Ansible Tower documentation for example" -" syntax." -msgstr "" -"Entrez les injecteurs avec la syntaxe JSON ou YAML. Utilisez le bouton radio" -" pour basculer entre les deux. Consultez la documentation Ansible Tower pour" -" avoir un exemple de syntaxe." +"Enter injectors using either JSON or YAML syntax. Refer to the Ansible Tower " +"documentation for example syntax." +msgstr "Entrez les injecteurs avec la syntaxe JSON ou YAML. Consultez la documentation d’Ansible Tower pour avoir un exemple de syntaxe." -#: awx/main/models/credential/__init__.py:534 +#: awx/main/models/credential/__init__.py:428 #, python-format msgid "adding %s credential type" msgstr "ajout type d'identifiants %s" -#: awx/main/models/credential/__init__.py:696 -#: awx/main/models/credential/__init__.py:815 +#: awx/main/models/credential/__init__.py:602 +#: awx/main/models/credential/__init__.py:658 +#: awx/main/models/credential/__init__.py:716 +#: awx/main/models/credential/__init__.py:838 +#: awx/main/models/credential/__init__.py:864 +#: awx/main/models/credential/__init__.py:891 +#: awx/main/models/credential/__init__.py:951 +#: awx/main/models/credential/__init__.py:1024 +#: awx/main/models/credential/__init__.py:1055 +#: awx/main/models/credential/__init__.py:1105 +msgid "Password" +msgstr "Mot de passe" + +#: awx/main/models/credential/__init__.py:608 +#: awx/main/models/credential/__init__.py:721 msgid "SSH Private Key" msgstr "Clé privée SSH" -#: awx/main/models/credential/__init__.py:703 -#: awx/main/models/credential/__init__.py:757 -#: awx/main/models/credential/__init__.py:822 +#: awx/main/models/credential/__init__.py:615 +msgid "Signed SSH Certificate" +msgstr "Certificat SSH signé" + +#: awx/main/models/credential/__init__.py:621 +#: awx/main/models/credential/__init__.py:670 +#: awx/main/models/credential/__init__.py:728 msgid "Private Key Passphrase" msgstr "Phrase de passe pour la clé privée" -#: awx/main/models/credential/__init__.py:709 +#: awx/main/models/credential/__init__.py:627 msgid "Privilege Escalation Method" msgstr "Méthode d'escalade privilégiée" -#: awx/main/models/credential/__init__.py:711 +#: awx/main/models/credential/__init__.py:629 msgid "" -"Specify a method for \"become\" operations. This is equivalent to specifying" -" the --become-method Ansible parameter." -msgstr "" -"Spécifiez une méthode pour les opérations « become ». Cela équivaut à " -"définir le paramètre Ansible --become-method." +"Specify a method for \"become\" operations. This is equivalent to specifying " +"the --become-method Ansible parameter." +msgstr "Spécifiez une méthode pour les opérations « become ». Cela équivaut à définir le paramètre Ansible --become-method." -#: awx/main/models/credential/__init__.py:716 +#: awx/main/models/credential/__init__.py:634 msgid "Privilege Escalation Username" msgstr "Nom d’utilisateur pour l’élévation des privilèges" -#: awx/main/models/credential/__init__.py:720 +#: awx/main/models/credential/__init__.py:638 msgid "Privilege Escalation Password" msgstr "Mot de passe pour l’élévation des privilèges" -#: awx/main/models/credential/__init__.py:750 +#: awx/main/models/credential/__init__.py:663 msgid "SCM Private Key" msgstr "Clé privée SCM" -#: awx/main/models/credential/__init__.py:777 +#: awx/main/models/credential/__init__.py:685 msgid "Vault Password" -msgstr "Mot de passe de l'archivage sécurisé" +msgstr "Mot de passe Vault" -#: awx/main/models/credential/__init__.py:783 +#: awx/main/models/credential/__init__.py:691 msgid "Vault Identifier" msgstr "Identifiant Archivage sécurisé" -#: awx/main/models/credential/__init__.py:786 +#: awx/main/models/credential/__init__.py:694 msgid "" -"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+." -msgstr "" -"Spécifiez un ID d'archivage sécurisé (facultatif). Ceci équivaut à spécifier" -" le paramètre --vault-id d'Ansible pour fournir plusieurs mots de passe " -"d'archivage sécurisé. Remarque : cette fonctionnalité ne fonctionne que " -"dans Ansible 2.4+." +"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+." +msgstr "Spécifiez un ID d'archivage sécurisé (facultatif). Ceci équivaut à spécifier le paramètre --vault-id d'Ansible pour fournir plusieurs mots de passe d'archivage sécurisé. Remarque : cette fonctionnalité ne fonctionne que dans Ansible 2.4+." -#: awx/main/models/credential/__init__.py:827 +#: awx/main/models/credential/__init__.py:733 msgid "Authorize" msgstr "Autoriser" -#: awx/main/models/credential/__init__.py:831 +#: awx/main/models/credential/__init__.py:737 msgid "Authorize Password" msgstr "Mot de passe d’autorisation" -#: awx/main/models/credential/__init__.py:848 +#: awx/main/models/credential/__init__.py:751 msgid "Amazon Web Services" msgstr "Amazon Web Services" -#: awx/main/models/credential/__init__.py:853 +#: awx/main/models/credential/__init__.py:756 msgid "Access Key" msgstr "Clé d’accès" -#: awx/main/models/credential/__init__.py:857 +#: awx/main/models/credential/__init__.py:760 msgid "Secret Key" msgstr "Clé secrète" -#: awx/main/models/credential/__init__.py:862 +#: awx/main/models/credential/__init__.py:765 msgid "STS Token" -msgstr "Jeton STS" +msgstr "Token STS" -#: awx/main/models/credential/__init__.py:865 +#: awx/main/models/credential/__init__.py:768 msgid "" "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." -msgstr "" -"Le service de jeton de sécurité (STS) est un service Web qui permet de " -"demander des informations d’identification provisoires avec des privilèges " -"limités pour les utilisateurs d’AWS Identity and Access Management (IAM)." +msgstr "Le service de jeton de sécurité (STS) est un service Web qui permet de demander des informations d’identification provisoires avec des privilèges limités pour les utilisateurs d’AWS Identity and Access Management (IAM)." -#: awx/main/models/credential/__init__.py:879 awx/main/models/inventory.py:990 +#: awx/main/models/credential/__init__.py:780 awx/main/models/inventory.py:833 msgid "OpenStack" msgstr "OpenStack" -#: awx/main/models/credential/__init__.py:888 +#: awx/main/models/credential/__init__.py:789 msgid "Password (API Key)" msgstr "Mot de passe (clé API)" -#: awx/main/models/credential/__init__.py:893 -#: awx/main/models/credential/__init__.py:1120 +#: awx/main/models/credential/__init__.py:794 +#: awx/main/models/credential/__init__.py:1046 msgid "Host (Authentication URL)" msgstr "Hôte (URL d’authentification)" -#: awx/main/models/credential/__init__.py:895 +#: awx/main/models/credential/__init__.py:796 msgid "" -"The host to authenticate with. For example, " -"https://openstack.business.com/v2.0/" -msgstr "" -"Hôte avec lequel s’authentifier. Exemple,\n" +"The host to authenticate with. For example, https://openstack.business.com/" +"v2.0/" +msgstr "Hôte avec lequel s’authentifier. Exemple,\n" "https://openstack.business.com/v2.0/" -#: awx/main/models/credential/__init__.py:899 +#: awx/main/models/credential/__init__.py:800 msgid "Project (Tenant Name)" msgstr "Projet (nom du client)" -#: awx/main/models/credential/__init__.py:903 +#: awx/main/models/credential/__init__.py:804 msgid "Domain Name" msgstr "Nom de domaine" -#: awx/main/models/credential/__init__.py:905 +#: awx/main/models/credential/__init__.py:806 msgid "" "OpenStack domains define administrative boundaries. It is only needed for " "Keystone v3 authentication URLs. Refer to Ansible Tower documentation for " "common scenarios." +msgstr "Les domaines OpenStack définissent les limites administratives. Ils sont nécessaires uniquement pour les URL d’authentification Keystone v3. Voir la documentation Ansible Tower pour les scénarios courants." + +#: awx/main/models/credential/__init__.py:824 +msgid "Region Name" +msgstr "Nom de la region" + +#: awx/main/models/credential/__init__.py:826 +msgid "" +"For some cloud providers, like OVH, region must be specified." msgstr "" -"Les domaines OpenStack définissent les limites administratives. Ils sont " -"nécessaires uniquement pour les URL d’authentification Keystone v3. Voir la " -"documentation Ansible Tower pour les scénarios courants." +"Chez certains fournisseurs, comme OVH, vous devez spécifier le nom de la région" -#: awx/main/models/credential/__init__.py:919 awx/main/models/inventory.py:987 +#: awx/main/models/credential/__init__.py:812 +#: awx/main/models/credential/__init__.py:1110 +#: awx/main/models/credential/__init__.py:1144 +msgid "Verify SSL" +msgstr "Vérifier SSL" + +#: awx/main/models/credential/__init__.py:823 awx/main/models/inventory.py:830 msgid "VMware vCenter" msgstr "VMware vCenter" -#: awx/main/models/credential/__init__.py:924 +#: awx/main/models/credential/__init__.py:828 msgid "VCenter Host" msgstr "Hôte vCenter" -#: awx/main/models/credential/__init__.py:926 +#: awx/main/models/credential/__init__.py:830 msgid "" "Enter the hostname or IP address that corresponds to your VMware vCenter." -msgstr "" -"Saisir le nom d’hôte ou l’adresse IP qui correspond à votre VMware vCenter." +msgstr "Saisir le nom d’hôte ou l’adresse IP qui correspond à votre VMware vCenter." -#: awx/main/models/credential/__init__.py:947 awx/main/models/inventory.py:988 +#: awx/main/models/credential/__init__.py:849 awx/main/models/inventory.py:831 msgid "Red Hat Satellite 6" msgstr "Red Hat Satellite 6" -#: awx/main/models/credential/__init__.py:952 +#: awx/main/models/credential/__init__.py:854 msgid "Satellite 6 URL" msgstr "URL Satellite 6" -#: awx/main/models/credential/__init__.py:954 +#: awx/main/models/credential/__init__.py:856 msgid "" "Enter the URL that corresponds to your Red Hat Satellite 6 server. For " "example, https://satellite.example.org" -msgstr "" -"Veuillez saisir l’URL qui correspond à votre serveur Red Hat Satellite 6. " -"Par exemple, https://satellite.example.org" +msgstr "Veuillez saisir l’URL qui correspond à votre serveur Red Hat Satellite 6. Par exemple, https://satellite.example.org" -#: awx/main/models/credential/__init__.py:975 awx/main/models/inventory.py:989 +#: awx/main/models/credential/__init__.py:875 awx/main/models/inventory.py:832 msgid "Red Hat CloudForms" msgstr "Red Hat CloudForms" -#: awx/main/models/credential/__init__.py:980 +#: awx/main/models/credential/__init__.py:880 msgid "CloudForms URL" msgstr "URL CloudForms" -#: awx/main/models/credential/__init__.py:982 +#: awx/main/models/credential/__init__.py:882 msgid "" "Enter the URL for the virtual machine that corresponds to your CloudForms " "instance. For example, https://cloudforms.example.org" -msgstr "" -"Veuillez saisir l’URL de la machine virtuelle qui correspond à votre " -"instance de CloudForm. Par exemple, https://cloudforms.example.org" +msgstr "Veuillez saisir l’URL de la machine virtuelle qui correspond à votre instance de CloudForm. Par exemple, https://cloudforms.example.org" -#: awx/main/models/credential/__init__.py:1004 -#: awx/main/models/inventory.py:985 +#: awx/main/models/credential/__init__.py:902 awx/main/models/inventory.py:828 msgid "Google Compute Engine" msgstr "Google Compute Engine" -#: awx/main/models/credential/__init__.py:1009 +#: awx/main/models/credential/__init__.py:907 msgid "Service Account Email Address" msgstr "Adresse électronique du compte de service" -#: awx/main/models/credential/__init__.py:1011 +#: awx/main/models/credential/__init__.py:909 msgid "" "The email address assigned to the Google Compute Engine service account." -msgstr "" -"Adresse électronique attribuée au compte de service Google Compute Engine." +msgstr "Adresse électronique attribuée au compte de service Google Compute Engine." -#: awx/main/models/credential/__init__.py:1017 +#: awx/main/models/credential/__init__.py:915 msgid "" "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" -msgstr "" -"L’ID du projet est l’identifiant attribué par GCE. Il se compose souvent de " -"deux ou trois mots suivis d’un nombre à trois chiffres. Exemples : project-" -"id-000 and another-project-id" +msgstr "L’ID du projet est l’identifiant attribué par GCE. Il se compose souvent de deux ou trois mots suivis d’un nombre à trois chiffres. Exemples : project-id-000 and another-project-id" -#: awx/main/models/credential/__init__.py:1023 +#: awx/main/models/credential/__init__.py:921 msgid "RSA Private Key" msgstr "Clé privée RSA" -#: awx/main/models/credential/__init__.py:1028 +#: awx/main/models/credential/__init__.py:926 msgid "" -"Paste the contents of the PEM file associated with the service account " -"email." -msgstr "" -"Collez le contenu du fichier PEM associé à l’adresse électronique du compte " -"de service." +"Paste the contents of the PEM file associated with the service account email." +msgstr "Collez le contenu du fichier PEM associé à l’adresse électronique du compte de service." -#: awx/main/models/credential/__init__.py:1040 -#: awx/main/models/inventory.py:986 +#: awx/main/models/credential/__init__.py:936 awx/main/models/inventory.py:829 msgid "Microsoft Azure Resource Manager" msgstr "Microsoft Azure Resource Manager" -#: awx/main/models/credential/__init__.py:1045 +#: awx/main/models/credential/__init__.py:941 msgid "Subscription ID" msgstr "ID d’abonnement" -#: awx/main/models/credential/__init__.py:1047 +#: awx/main/models/credential/__init__.py:943 msgid "Subscription ID is an Azure construct, which is mapped to a username." -msgstr "" -"L’ID d’abonnement est une construction Azure mappée à un nom d’utilisateur." - -#: awx/main/models/credential/__init__.py:1060 -msgid "Client ID" -msgstr "ID du client" +msgstr "L’ID d’abonnement est une construction Azure mappée à un nom d’utilisateur." -#: awx/main/models/credential/__init__.py:1069 -msgid "Tenant ID" -msgstr "ID Tenant" - -#: awx/main/models/credential/__init__.py:1073 +#: awx/main/models/credential/__init__.py:969 msgid "Azure Cloud Environment" msgstr "Environnement Cloud Azure" -#: awx/main/models/credential/__init__.py:1075 +#: awx/main/models/credential/__init__.py:971 msgid "" "Environment variable AZURE_CLOUD_ENVIRONMENT when using Azure GovCloud or " "Azure stack." -msgstr "" -"Variable d'environnement AZURE_CLOUD_ENVIRONMENT avec Azure GovCloud ou une " -"pile Azure." +msgstr "Variable d'environnement AZURE_CLOUD_ENVIRONMENT avec Azure GovCloud ou une pile Azure." + +#: awx/main/models/credential/__init__.py:981 +msgid "GitHub Personal Access Token" +msgstr "Jeton d'accès personnel GitHub" -#: awx/main/models/credential/__init__.py:1115 -#: awx/main/models/inventory.py:991 +#: awx/main/models/credential/__init__.py:989 +msgid "This token needs to come from your profile settings in GitHub" +msgstr "Ce jeton doit provenir de vos paramètres de profil dans GitHub" + +#: awx/main/models/credential/__init__.py:998 +msgid "GitLab Personal Access Token" +msgstr "Jeton d'accès personnel GitLab" + +#: awx/main/models/credential/__init__.py:1006 +msgid "This token needs to come from your profile settings in GitLab" +msgstr "Ce jeton doit provenir de vos paramètres de profil dans GitLab" + +#: awx/main/models/credential/__init__.py:1041 awx/main/models/inventory.py:834 msgid "Red Hat Virtualization" msgstr "Red Hat Virtualization" -#: awx/main/models/credential/__init__.py:1122 +#: awx/main/models/credential/__init__.py:1048 msgid "The host to authenticate with." msgstr "Hôte avec lequel s’authentifier." -#: awx/main/models/credential/__init__.py:1134 +#: awx/main/models/credential/__init__.py:1060 msgid "CA File" msgstr "Fichier CA" -#: awx/main/models/credential/__init__.py:1136 +#: awx/main/models/credential/__init__.py:1062 msgid "Absolute file path to the CA file to use (optional)" msgstr "Chemin d'accès absolu vers le fichier CA à utiliser (en option)" -#: awx/main/models/credential/__init__.py:1167 -#: awx/main/models/inventory.py:992 +#: awx/main/models/credential/__init__.py:1091 awx/main/models/inventory.py:835 msgid "Ansible Tower" msgstr "Ansible Tower" -#: awx/main/models/credential/__init__.py:1172 +#: awx/main/models/credential/__init__.py:1096 msgid "Ansible Tower Hostname" msgstr "Nom d'hôte Ansible Tower" -#: awx/main/models/credential/__init__.py:1174 +#: awx/main/models/credential/__init__.py:1098 msgid "The Ansible Tower base URL to authenticate with." msgstr "L'URL basé Ansible Tower avec lequel s'authentifier." -#: awx/main/models/credential/__init__.py:1186 -msgid "Verify SSL" -msgstr "Vérifier SSL" +#: awx/main/models/credential/__init__.py:1130 +msgid "OpenShift or Kubernetes API Bearer Token" +msgstr "Jeton du porteur d’API OpenShift ou Kubernetes" + +#: awx/main/models/credential/__init__.py:1134 +msgid "OpenShift or Kubernetes API Endpoint" +msgstr "Point d'accès d’API OpenShift ou Kubernetes" + +#: awx/main/models/credential/__init__.py:1136 +msgid "The OpenShift or Kubernetes API Endpoint to authenticate with." +msgstr "Point d'accès de l’API OpenShift ou Kubernetes auprès duquel s’authentifier." + +#: awx/main/models/credential/__init__.py:1139 +msgid "API authentication bearer token" +msgstr "Token du porteur d'authentification d'API" + +#: awx/main/models/credential/__init__.py:1149 +msgid "Certificate Authority data" +msgstr "Données de l'autorité de certification" + +#: awx/main/models/credential/__init__.py:1190 +msgid "Target must be a non-external credential" +msgstr "La cible doit être une information d'identification non externe" -#: awx/main/models/events.py:105 awx/main/models/events.py:630 +#: awx/main/models/credential/__init__.py:1195 +msgid "Source must be an external credential" +msgstr "La source doit être une information d'identification externe" + +#: awx/main/models/credential/__init__.py:1202 +msgid "Input field must be defined on target credential (options are {})." +msgstr "Le champ de saisie doit être défini sur des informations d'identification externes (les options sont {})." + +#: awx/main/models/events.py:152 awx/main/models/events.py:674 msgid "Host Failed" msgstr "Échec de l'hôte" -#: awx/main/models/events.py:106 awx/main/models/events.py:631 +#: awx/main/models/events.py:153 +msgid "Host Started" +msgstr "Hôte démarré" + +#: awx/main/models/events.py:154 awx/main/models/events.py:675 msgid "Host OK" msgstr "Hôte OK" -#: awx/main/models/events.py:107 +#: awx/main/models/events.py:155 msgid "Host Failure" msgstr "Échec de l'hôte" -#: awx/main/models/events.py:108 awx/main/models/events.py:637 +#: awx/main/models/events.py:156 awx/main/models/events.py:681 msgid "Host Skipped" msgstr "Hôte ignoré" -#: awx/main/models/events.py:109 awx/main/models/events.py:632 +#: awx/main/models/events.py:157 awx/main/models/events.py:676 msgid "Host Unreachable" msgstr "Hôte inaccessible" -#: awx/main/models/events.py:110 awx/main/models/events.py:124 +#: awx/main/models/events.py:158 awx/main/models/events.py:172 msgid "No Hosts Remaining" msgstr "Aucun hôte restant" -#: awx/main/models/events.py:111 +#: awx/main/models/events.py:159 msgid "Host Polling" msgstr "Interrogation de l'hôte" -#: awx/main/models/events.py:112 +#: awx/main/models/events.py:160 msgid "Host Async OK" msgstr "Désynchronisation des hôtes OK" -#: awx/main/models/events.py:113 +#: awx/main/models/events.py:161 msgid "Host Async Failure" msgstr "Échec de désynchronisation des hôtes" -#: awx/main/models/events.py:114 +#: awx/main/models/events.py:162 msgid "Item OK" msgstr "Élément OK" -#: awx/main/models/events.py:115 +#: awx/main/models/events.py:163 msgid "Item Failed" msgstr "Échec de l'élément" -#: awx/main/models/events.py:116 +#: awx/main/models/events.py:164 msgid "Item Skipped" msgstr "Élément ignoré" -#: awx/main/models/events.py:117 +#: awx/main/models/events.py:165 msgid "Host Retry" msgstr "Nouvel essai de l'hôte" -#: awx/main/models/events.py:119 +#: awx/main/models/events.py:167 msgid "File Difference" msgstr "Écart entre les fichiers" -#: awx/main/models/events.py:120 +#: awx/main/models/events.py:168 msgid "Playbook Started" msgstr "Playbook démarré" -#: awx/main/models/events.py:121 +#: awx/main/models/events.py:169 msgid "Running Handlers" msgstr "Descripteurs d'exécution" -#: awx/main/models/events.py:122 +#: awx/main/models/events.py:170 msgid "Including File" msgstr "Ajout de fichier" -#: awx/main/models/events.py:123 +#: awx/main/models/events.py:171 msgid "No Hosts Matched" msgstr "Aucun hôte correspondant" -#: awx/main/models/events.py:125 +#: awx/main/models/events.py:173 msgid "Task Started" msgstr "Tâche démarrée" -#: awx/main/models/events.py:127 +#: awx/main/models/events.py:175 msgid "Variables Prompted" msgstr "Variables demandées" -#: awx/main/models/events.py:128 +#: awx/main/models/events.py:176 msgid "Gathering Facts" msgstr "Collecte des faits" -#: awx/main/models/events.py:129 +#: awx/main/models/events.py:177 msgid "internal: on Import for Host" msgstr "interne : à l'importation pour l'hôte" -#: awx/main/models/events.py:130 +#: awx/main/models/events.py:178 msgid "internal: on Not Import for Host" msgstr "interne : à la non-importation pour l'hôte" -#: awx/main/models/events.py:131 +#: awx/main/models/events.py:179 msgid "Play Started" msgstr "Scène démarrée" -#: awx/main/models/events.py:132 +#: awx/main/models/events.py:180 msgid "Playbook Complete" msgstr "Playbook terminé" -#: awx/main/models/events.py:136 awx/main/models/events.py:647 +#: awx/main/models/events.py:184 awx/main/models/events.py:691 msgid "Debug" msgstr "Déboguer" -#: awx/main/models/events.py:137 awx/main/models/events.py:648 +#: awx/main/models/events.py:185 awx/main/models/events.py:692 msgid "Verbose" msgstr "Verbeux" -#: awx/main/models/events.py:138 awx/main/models/events.py:649 +#: awx/main/models/events.py:186 awx/main/models/events.py:693 msgid "Deprecated" msgstr "Obsolète" -#: awx/main/models/events.py:139 awx/main/models/events.py:650 +#: awx/main/models/events.py:187 awx/main/models/events.py:694 msgid "Warning" msgstr "Avertissement" -#: awx/main/models/events.py:140 awx/main/models/events.py:651 +#: awx/main/models/events.py:188 awx/main/models/events.py:695 msgid "System Warning" msgstr "Avertissement système" -#: awx/main/models/events.py:141 awx/main/models/events.py:652 -#: awx/main/models/unified_jobs.py:67 +#: awx/main/models/events.py:189 awx/main/models/events.py:696 +#: awx/main/models/unified_jobs.py:75 msgid "Error" msgstr "Erreur" -#: awx/main/models/fact.py:25 -msgid "Host for the facts that the fact scan captured." -msgstr "Hôte pour les faits que le scan de faits a capturés." - -#: awx/main/models/fact.py:30 -msgid "Date and time of the corresponding fact scan gathering time." -msgstr "" -"Date et heure du scan de faits correspondant au moment de la collecte des " -"faits." - -#: awx/main/models/fact.py:33 -msgid "" -"Arbitrary JSON structure of module facts captured at timestamp for a single " -"host." -msgstr "" -"Structure JSON arbitraire des faits de module capturés au moment de " -"l'horodatage pour un seul hôte." - -#: awx/main/models/ha.py:181 +#: awx/main/models/ha.py:175 msgid "Instances that are members of this InstanceGroup" msgstr "Instances membres de ce GroupeInstances." -#: awx/main/models/ha.py:186 +#: awx/main/models/ha.py:180 msgid "Instance Group to remotely control this group." msgstr "Groupe d'instances pour contrôler ce groupe à distance." -#: awx/main/models/ha.py:193 +#: awx/main/models/ha.py:200 msgid "Percentage of Instances to automatically assign to this group" -msgstr "" -"Le pourcentage d'instances qui seront automatiquement assignées à ce groupe" +msgstr "Le pourcentage d'instances qui seront automatiquement assignées à ce groupe" -#: awx/main/models/ha.py:197 +#: awx/main/models/ha.py:204 msgid "" "Static minimum number of Instances to automatically assign to this group" -msgstr "" -"Nombre minimum statique d'instances qui seront automatiquement assignées à " -"ce groupe." +msgstr "Nombre minimum statique d'instances qui seront automatiquement assignées à ce groupe." -#: awx/main/models/ha.py:202 +#: awx/main/models/ha.py:209 msgid "" "List of exact-match Instances that will always be automatically assigned to " "this group" -msgstr "" -"Liste des cas de concordance exacte qui seront toujours assignés " -"automatiquement à ce groupe." +msgstr "Liste des cas de concordance exacte qui seront toujours assignés automatiquement à ce groupe." -#: awx/main/models/inventory.py:61 +#: awx/main/models/inventory.py:80 msgid "Hosts have a direct link to this inventory." msgstr "Les hôtes ont un lien direct vers cet inventaire." -#: awx/main/models/inventory.py:62 +#: awx/main/models/inventory.py:81 msgid "Hosts for inventory generated using the host_filter property." msgstr "Hôtes pour inventaire générés avec la propriété host_filter." -#: awx/main/models/inventory.py:67 +#: awx/main/models/inventory.py:86 msgid "inventories" msgstr "inventaires" -#: awx/main/models/inventory.py:74 +#: awx/main/models/inventory.py:93 msgid "Organization containing this inventory." msgstr "Organisation contenant cet inventaire." -#: awx/main/models/inventory.py:81 +#: awx/main/models/inventory.py:100 msgid "Inventory variables in JSON or YAML format." msgstr "Variables d'inventaire au format JSON ou YAML." -#: awx/main/models/inventory.py:86 -msgid "Flag indicating whether any hosts in this inventory have failed." -msgstr "Marqueur indiquant si les hôtes de cet inventaire ont échoué." - -#: awx/main/models/inventory.py:91 -msgid "Total number of hosts in this inventory." -msgstr "Nombre total d'hôtes dans cet inventaire." +#: awx/main/models/inventory.py:105 +msgid "" +"This field is deprecated and will be removed in a future release. Flag " +"indicating whether any hosts in this inventory have failed." +msgstr "Ce champ est obsolète et sera supprimé dans une prochaine version. Indicateur signalant si des hôtes de cet inventaire ont échoué." -#: awx/main/models/inventory.py:96 -msgid "Number of hosts in this inventory with active failures." -msgstr "Nombre d'hôtes dans cet inventaire avec des échecs non résolus." +#: awx/main/models/inventory.py:111 +msgid "" +"This field is deprecated and will be removed in a future release. Total " +"number of hosts in this inventory." +msgstr "Ce champ est obsolète et sera supprimé dans une prochaine version. Nombre total d'hôtes dans cet inventaire." -#: awx/main/models/inventory.py:101 -msgid "Total number of groups in this inventory." -msgstr "Nombre total de groupes dans cet inventaire." +#: awx/main/models/inventory.py:117 +msgid "" +"This field is deprecated and will be removed in a future release. Number of " +"hosts in this inventory with active failures." +msgstr "Ce champ est obsolète et sera supprimé dans une prochaine version. Nombre d'hôtes dans cet inventaire avec des échecs actifs." -#: awx/main/models/inventory.py:106 -msgid "Number of groups in this inventory with active failures." -msgstr "Nombre de groupes dans cet inventaire avec des échecs non résolus." +#: awx/main/models/inventory.py:123 +msgid "" +"This field is deprecated and will be removed in a future release. Total " +"number of groups in this inventory." +msgstr "Ce champ est obsolète et sera supprimé dans une prochaine version. Nombre total de groupes dans cet inventaire." -#: awx/main/models/inventory.py:111 +#: awx/main/models/inventory.py:129 msgid "" -"Flag indicating whether this inventory has any external inventory sources." -msgstr "" -"Marqueur indiquant si cet inventaire contient des sources d'inventaire " -"externes." +"This field is deprecated and will be removed in a future release. Flag " +"indicating whether this inventory has any external inventory sources." +msgstr "Ce champ est obsolète et sera supprimé dans une prochaine version. Indicateur signalant si cet inventaire a des sources d’inventaire externes." -#: awx/main/models/inventory.py:116 +#: awx/main/models/inventory.py:135 msgid "" "Total number of external inventory sources configured within this inventory." -msgstr "" -"Nombre total de sources d'inventaire externes configurées dans cet " -"inventaire." +msgstr "Nombre total de sources d'inventaire externes configurées dans cet inventaire." -#: awx/main/models/inventory.py:121 +#: awx/main/models/inventory.py:140 msgid "Number of external inventory sources in this inventory with failures." -msgstr "" -"Nombre total de sources d'inventaire externes en échec dans cet inventaire." +msgstr "Nombre total de sources d'inventaire externes en échec dans cet inventaire." -#: awx/main/models/inventory.py:128 +#: awx/main/models/inventory.py:147 msgid "Kind of inventory being represented." msgstr "Genre d'inventaire représenté." -#: awx/main/models/inventory.py:134 +#: awx/main/models/inventory.py:153 msgid "Filter that will be applied to the hosts of this inventory." msgstr "Filtre appliqué aux hôtes de cet inventaire." -#: awx/main/models/inventory.py:161 +#: awx/main/models/inventory.py:181 msgid "" "Credentials to be used by hosts belonging to this inventory when accessing " "Red Hat Insights API." -msgstr "" -"Informations d'identification à utiliser par les hôtes appartenant à cet " -"inventaire lors de l'accès à l'API Red Hat Insights ." +msgstr "Informations d'identification à utiliser par les hôtes appartenant à cet inventaire lors de l'accès à l'API Red Hat Insights ." -#: awx/main/models/inventory.py:170 +#: awx/main/models/inventory.py:190 msgid "Flag indicating the inventory is being deleted." msgstr "Marqueur indiquant que cet inventaire est en cours de suppression." -#: awx/main/models/inventory.py:459 +#: awx/main/models/inventory.py:245 +msgid "Could not parse subset as slice specification." +msgstr "N'a pas pu traiter les sous-ensembles en tant que spécification de découpage." + +#: awx/main/models/inventory.py:249 +msgid "Slice number must be less than total number of slices." +msgstr "Le nombre de tranches doit être inférieur au nombre total de tranches." + +#: awx/main/models/inventory.py:251 +msgid "Slice number must be 1 or higher." +msgstr "Le nombre de tranches doit être 1 ou valeur supérieure." + +#: awx/main/models/inventory.py:388 msgid "Assignment not allowed for Smart Inventory" msgstr "Attribution non autorisée pour un inventaire Smart" -#: awx/main/models/inventory.py:461 awx/main/models/projects.py:159 +#: awx/main/models/inventory.py:390 awx/main/models/projects.py:166 msgid "Credential kind must be 'insights'." msgstr "Le genre d'informations d'identification doit être 'insights'." -#: awx/main/models/inventory.py:546 +#: awx/main/models/inventory.py:475 msgid "Is this host online and available for running jobs?" msgstr "Cet hôte est-il en ligne et disponible pour exécuter des tâches ?" -#: awx/main/models/inventory.py:552 +#: awx/main/models/inventory.py:481 msgid "" "The value used by the remote inventory source to uniquely identify the host" -msgstr "" -"Valeur utilisée par la source d'inventaire distante pour identifier l'hôte " -"de façon unique" +msgstr "Valeur utilisée par la source d'inventaire distante pour identifier l'hôte de façon unique" -#: awx/main/models/inventory.py:557 +#: awx/main/models/inventory.py:486 msgid "Host variables in JSON or YAML format." msgstr "Variables d'hôte au format JSON ou YAML." -#: awx/main/models/inventory.py:579 -msgid "Flag indicating whether the last job failed for this host." -msgstr "Marqueur indiquant si la dernière tâche a échoué pour cet hôte." - -#: awx/main/models/inventory.py:584 -msgid "" -"Flag indicating whether this host was created/updated from any external " -"inventory sources." -msgstr "" -"Marqueur indiquant si cet hôte a été créé/mis à jour à partir de sources " -"d'inventaire externes." - -#: awx/main/models/inventory.py:590 +#: awx/main/models/inventory.py:509 msgid "Inventory source(s) that created or modified this host." msgstr "Sources d'inventaire qui ont créé ou modifié cet hôte." -#: awx/main/models/inventory.py:595 +#: awx/main/models/inventory.py:514 msgid "Arbitrary JSON structure of most recent ansible_facts, per-host." -msgstr "" -"Structure JSON arbitraire des faits ansible les plus récents, par hôte." +msgstr "Structure JSON arbitraire des faits ansible les plus récents, par hôte." -#: awx/main/models/inventory.py:601 +#: awx/main/models/inventory.py:520 msgid "The date and time ansible_facts was last modified." msgstr "Date et heure de la dernière modification apportée à ansible_facts." -#: awx/main/models/inventory.py:608 +#: awx/main/models/inventory.py:527 msgid "Red Hat Insights host unique identifier." msgstr "Identifiant unique de l'hôte de Red Hat Insights." -#: awx/main/models/inventory.py:743 +#: awx/main/models/inventory.py:641 msgid "Group variables in JSON or YAML format." msgstr "Variables de groupe au format JSON ou YAML." -#: awx/main/models/inventory.py:749 +#: awx/main/models/inventory.py:647 msgid "Hosts associated directly with this group." msgstr "Hôtes associés directement à ce groupe." -#: awx/main/models/inventory.py:754 -msgid "Total number of hosts directly or indirectly in this group." -msgstr "" -"Nombre total d'hôtes associés directement ou indirectement à ce groupe." - -#: awx/main/models/inventory.py:759 -msgid "Flag indicating whether this group has any hosts with active failures." -msgstr "" -"Marqueur indiquant si ce groupe possède ou non des hôtes avec des échecs non" -" résolus." - -#: awx/main/models/inventory.py:764 -msgid "Number of hosts in this group with active failures." -msgstr "Nombre d'hôtes dans ce groupe avec des échecs non résolus." - -#: awx/main/models/inventory.py:769 -msgid "Total number of child groups contained within this group." -msgstr "Nombre total de groupes enfants compris dans ce groupe." - -#: awx/main/models/inventory.py:774 -msgid "Number of child groups within this group that have active failures." -msgstr "Nombre de groupes enfants dans ce groupe avec des échecs non résolus." - -#: awx/main/models/inventory.py:779 -msgid "" -"Flag indicating whether this group was created/updated from any external " -"inventory sources." -msgstr "" -"Marqueur indiquant si ce groupe a été créé/mis à jour à partir de sources " -"d'inventaire externes." - -#: awx/main/models/inventory.py:785 +#: awx/main/models/inventory.py:653 msgid "Inventory source(s) that created or modified this group." msgstr "Sources d'inventaire qui ont créé ou modifié ce groupe." -#: awx/main/models/inventory.py:981 awx/main/models/projects.py:53 -#: awx/main/models/unified_jobs.py:519 -msgid "Manual" -msgstr "Manuel" - -#: awx/main/models/inventory.py:982 +#: awx/main/models/inventory.py:825 msgid "File, Directory or Script" msgstr "Fichier, répertoire ou script" -#: awx/main/models/inventory.py:983 +#: awx/main/models/inventory.py:826 msgid "Sourced from a Project" msgstr "Provenance d'un projet" -#: awx/main/models/inventory.py:984 +#: awx/main/models/inventory.py:827 msgid "Amazon EC2" msgstr "Amazon EC2" -#: awx/main/models/inventory.py:993 +#: awx/main/models/inventory.py:836 msgid "Custom Script" msgstr "Script personnalisé" -#: awx/main/models/inventory.py:1110 +#: awx/main/models/inventory.py:953 msgid "Inventory source variables in YAML or JSON format." msgstr "Variables de source d'inventaire au format JSON ou YAML." -#: awx/main/models/inventory.py:1121 +#: awx/main/models/inventory.py:964 msgid "" "Comma-separated list of filter expressions (EC2 only). Hosts are imported " "when ANY of the filters match." -msgstr "" -"Liste d'expressions de filtre séparées par des virgules (EC2 uniquement). " -"Les hôtes sont importés lorsque l'UN des filtres correspondent." +msgstr "Liste d'expressions de filtre séparées par des virgules (EC2 uniquement). Les hôtes sont importés lorsque l'UN des filtres correspondent." -#: awx/main/models/inventory.py:1127 +#: awx/main/models/inventory.py:970 msgid "Limit groups automatically created from inventory source (EC2 only)." -msgstr "" -"Limiter automatiquement les groupes créés à partir de la source d'inventaire" -" (EC2 uniquement)." +msgstr "Limiter automatiquement les groupes créés à partir de la source d'inventaire (EC2 uniquement)." -#: awx/main/models/inventory.py:1131 +#: awx/main/models/inventory.py:974 msgid "Overwrite local groups and hosts from remote inventory source." -msgstr "" -"Écraser les groupes locaux et les hôtes de la source d'inventaire distante." +msgstr "Écraser les groupes locaux et les hôtes de la source d'inventaire distante." -#: awx/main/models/inventory.py:1135 +#: awx/main/models/inventory.py:978 msgid "Overwrite local variables from remote inventory source." msgstr "Écraser les variables locales de la source d'inventaire distante." -#: awx/main/models/inventory.py:1140 awx/main/models/jobs.py:140 -#: awx/main/models/projects.py:128 +#: awx/main/models/inventory.py:983 awx/main/models/jobs.py:154 +#: awx/main/models/projects.py:135 msgid "The amount of time (in seconds) to run before the task is canceled." msgstr "Délai écoulé (en secondes) avant que la tâche ne soit annulée." -#: awx/main/models/inventory.py:1173 +#: awx/main/models/inventory.py:1016 msgid "Image ID" msgstr "ID d'image" -#: awx/main/models/inventory.py:1174 +#: awx/main/models/inventory.py:1017 msgid "Availability Zone" msgstr "Zone de disponibilité" -#: awx/main/models/inventory.py:1175 -msgid "Account" -msgstr "Compte" - -#: awx/main/models/inventory.py:1176 +#: awx/main/models/inventory.py:1019 msgid "Instance ID" msgstr "ID d'instance" -#: awx/main/models/inventory.py:1177 +#: awx/main/models/inventory.py:1020 msgid "Instance State" msgstr "État de l'instance" -#: awx/main/models/inventory.py:1178 +#: awx/main/models/inventory.py:1021 msgid "Platform" msgstr "Plateforme " -#: awx/main/models/inventory.py:1179 +#: awx/main/models/inventory.py:1022 msgid "Instance Type" msgstr "Type d'instance" -#: awx/main/models/inventory.py:1180 -msgid "Key Name" -msgstr "Nom de la clé" - -#: awx/main/models/inventory.py:1181 +#: awx/main/models/inventory.py:1024 msgid "Region" msgstr "Région" -#: awx/main/models/inventory.py:1182 +#: awx/main/models/inventory.py:1025 msgid "Security Group" msgstr "Groupe de sécurité" -#: awx/main/models/inventory.py:1183 +#: awx/main/models/inventory.py:1026 msgid "Tags" msgstr "Balises" -#: awx/main/models/inventory.py:1184 +#: awx/main/models/inventory.py:1027 msgid "Tag None" msgstr "Ne rien baliser" -#: awx/main/models/inventory.py:1185 +#: awx/main/models/inventory.py:1028 msgid "VPC ID" msgstr "ID VPC" -#: awx/main/models/inventory.py:1253 +#: awx/main/models/inventory.py:1096 #, python-format msgid "" "Cloud-based inventory sources (such as %s) require credentials for the " "matching cloud service." -msgstr "" -"Les sources d'inventaire cloud (telles que %s) requièrent des informations " -"d'identification pour le service cloud correspondant." +msgstr "Les sources d'inventaire cloud (telles que %s) requièrent des informations d'identification pour le service cloud correspondant." -#: awx/main/models/inventory.py:1259 +#: awx/main/models/inventory.py:1102 msgid "Credential is required for a cloud source." -msgstr "" -"Les informations d'identification sont requises pour une source cloud." +msgstr "Les informations d'identification sont requises pour une source cloud." -#: awx/main/models/inventory.py:1262 +#: awx/main/models/inventory.py:1105 msgid "" "Credentials of type machine, source control, insights and vault are " "disallowed for custom inventory sources." -msgstr "" -"Les identifiants de type machine, contrôle de la source, insights ou " -"archivage sécurisé ne sont pas autorisés par les sources d'inventaire " -"personnalisées." +msgstr "Les identifiants de type machine, contrôle de la source, insights ou archivage sécurisé ne sont pas autorisés par les sources d'inventaire personnalisées." + +#: awx/main/models/inventory.py:1110 +msgid "" +"Credentials of type insights and vault are disallowed for scm inventory " +"sources." +msgstr "Les identifiants de type insights ou archivage sécurisé ne sont pas autorisés pour les sources d'inventaire scm." -#: awx/main/models/inventory.py:1314 +#: awx/main/models/inventory.py:1170 #, python-format msgid "Invalid %(source)s region: %(region)s" msgstr "Région %(source)s non valide : %(region)s" -#: awx/main/models/inventory.py:1338 +#: awx/main/models/inventory.py:1194 #, python-format msgid "Invalid filter expression: %(filter)s" msgstr "Expression de filtre non valide : %(filter)s" -#: awx/main/models/inventory.py:1359 +#: awx/main/models/inventory.py:1215 #, python-format msgid "Invalid group by choice: %(choice)s" msgstr "Choix de regroupement non valide : %(choice)s" -#: awx/main/models/inventory.py:1394 +#: awx/main/models/inventory.py:1243 msgid "Project containing inventory file used as source." msgstr "Projet contenant le fichier d'inventaire utilisé comme source." -#: awx/main/models/inventory.py:1555 -#, python-format -msgid "" -"Unable to configure this item for cloud sync. It is already managed by %s." -msgstr "" -"Impossible de configurer cet élément pour la synchronisation dans le cloud. " -"Il est déjà géré par %s." - -#: awx/main/models/inventory.py:1565 +#: awx/main/models/inventory.py:1416 msgid "" "More than one SCM-based inventory source with update on project update per-" "inventory not allowed." -msgstr "" -"On n'autorise pas plus d'une source d'inventaire basé SCM avec mise à jour " -"pré-inventaire ou mise à jour projet." +msgstr "On n'autorise pas plus d'une source d'inventaire basé SCM avec mise à jour pré-inventaire ou mise à jour projet." -#: awx/main/models/inventory.py:1572 +#: awx/main/models/inventory.py:1423 msgid "" "Cannot update SCM-based inventory source on launch if set to update on " "project update. Instead, configure the corresponding source project to " "update on launch." -msgstr "" -"Impossible de mettre à jour une source d'inventaire SCM lors du lancement si" -" elle est définie pour se mettre à jour lors de l'actualisation du projet. À" -" la place, configurez le projet source correspondant pour qu'il se mette à " -"jour au moment du lancement." - -#: awx/main/models/inventory.py:1579 -msgid "" -"SCM type sources must set `overwrite_vars` to `true` until Ansible 2.5." -msgstr "" -"Les sources de type SCM doivent définir les « overwrite_vars » sur « true »." +msgstr "Impossible de mettre à jour une source d'inventaire SCM lors du lancement si elle est définie pour se mettre à jour lors de l'actualisation du projet. À la place, configurez le projet source correspondant pour qu'il se mette à jour au moment du lancement." -#: awx/main/models/inventory.py:1584 +#: awx/main/models/inventory.py:1429 msgid "Cannot set source_path if not SCM type." msgstr "Impossible de définir chemin_source si pas du type SCM." -#: awx/main/models/inventory.py:1622 +#: awx/main/models/inventory.py:1472 msgid "" "Inventory files from this Project Update were used for the inventory update." -msgstr "" -"Les fichiers d'inventaire de cette mise à jour de projet ont été utilisés " -"pour la mise à jour de l'inventaire." +msgstr "Les fichiers d'inventaire de cette mise à jour de projet ont été utilisés pour la mise à jour de l'inventaire." -#: awx/main/models/inventory.py:1732 +#: awx/main/models/inventory.py:1583 msgid "Inventory script contents" msgstr "Contenus des scripts d'inventaire" -#: awx/main/models/inventory.py:1737 +#: awx/main/models/inventory.py:1588 msgid "Organization owning this inventory script" msgstr "Organisation propriétaire de ce script d'inventaire." -#: awx/main/models/jobs.py:66 +#: awx/main/models/jobs.py:74 msgid "" "If enabled, textual changes made to any templated files on the host are " "shown in the standard output" -msgstr "" -"Si cette option est activée, les modifications textuelles apportées aux " -"fichiers basés sur un modèle de l'hôte sont affichées dans la sortie " -"standard out" +msgstr "Si cette option est activée, les modifications textuelles apportées aux fichiers basés sur un modèle de l'hôte sont affichées dans la sortie standard out" -#: awx/main/models/jobs.py:145 +#: awx/main/models/jobs.py:106 msgid "" -"If enabled, Tower will act as an Ansible Fact Cache Plugin; persisting facts" -" at the end of a playbook run to the database and caching facts for use by " +"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." + +#: awx/main/models/jobs.py:159 +msgid "" +"If enabled, Tower will act as an Ansible Fact Cache Plugin; persisting facts " +"at the end of a playbook run to the database and caching facts for use by " "Ansible." -msgstr "" -"Si cette option est activée, Tower agira comme plug-in de fact cache Ansible" -" ; les faits persistants à la fin d'un playbook s'exécutent vers la base de " -"données et les faits mis en cache pour être utilisés par Ansible." +msgstr "Si cette option est activée, Tower agira comme plug-in de fact cache Ansible ; les faits persistants à la fin d'un playbook s'exécutent vers la base de données et les faits mis en cache pour être utilisés par Ansible." + +#: awx/main/models/jobs.py:260 +msgid "" +"The number of jobs to slice into at runtime. Will cause the Job Template to " +"launch a workflow if value is greater than 1." +msgstr "Le nombre de jobs à découper au moment de l'exécution. Amènera le Modèle de job à lancer un flux de travail si la valeur est supérieure à 1." -#: awx/main/models/jobs.py:163 -msgid "You must provide a Vault credential." -msgstr "Vous devez fournir des informations d'identification du coffre-fort." +#: awx/main/models/jobs.py:297 +msgid "Job Template must provide 'inventory' or allow prompting for it." +msgstr "Le modèle de tâche doit fournir un inventaire ou permettre d'en demander un." #: awx/main/models/jobs.py:308 -msgid "Job Template must provide 'inventory' or allow prompting for it." -msgstr "" -"Le modèle de tâche doit fournir un inventaire ou permettre d'en demander un." +#, python-brace-format +msgid "Maximum number of forks ({settings.MAX_FORKS}) exceeded." +msgstr "Nombre maximum de fourches ({settings.MAX_FORKS}) dépassé." + +#: awx/main/models/jobs.py:463 +msgid "Project is missing." +msgstr "Le projet est manquant." -#: awx/main/models/jobs.py:398 +#: awx/main/models/jobs.py:467 +msgid "Project does not allow override of branch." +msgstr "Le projet ne permet pas de remplacer la branche." + +#: awx/main/models/jobs.py:477 awx/main/models/workflow.py:545 msgid "Field is not configured to prompt on launch." msgstr "Le champ n'est pas configuré pour être invité au lancement." -#: awx/main/models/jobs.py:404 +#: awx/main/models/jobs.py:483 msgid "Saved launch configurations cannot provide passwords needed to start." -msgstr "" -"Les configurations de lancement sauvegardées ne peuvent pas fournir les mots" -" de passe nécessaires au démarrage." +msgstr "Les configurations de lancement sauvegardées ne peuvent pas fournir les mots de passe nécessaires au démarrage." -#: awx/main/models/jobs.py:412 +#: awx/main/models/jobs.py:491 msgid "Job Template {} is missing or undefined." msgstr "Modèle de Job {} manquant ou non défini." -#: awx/main/models/jobs.py:493 awx/main/models/projects.py:277 +#: awx/main/models/jobs.py:574 awx/main/models/projects.py:278 +#: awx/main/models/projects.py:497 msgid "SCM Revision" msgstr "Révision SCM" -#: awx/main/models/jobs.py:494 +#: awx/main/models/jobs.py:575 msgid "The SCM Revision from the Project used for this job, if available" msgstr "Révision SCM du projet utilisé pour cette tâche, le cas échéant" -#: awx/main/models/jobs.py:502 +#: awx/main/models/jobs.py:583 msgid "" "The SCM Refresh task used to make sure the playbooks were available for the " "job run" -msgstr "" -"Activité d'actualisation du SCM qui permet de s'assurer que les playbooks " -"étaient disponibles pour l'exécution de la tâche" +msgstr "Activité d'actualisation du SCM qui permet de s'assurer que les playbooks étaient disponibles pour l'exécution de la tâche" + +#: awx/main/models/jobs.py:588 +msgid "" +"If part of a sliced job, the ID of the inventory slice operated on. If not " +"part of sliced job, parameter is not used." +msgstr "Si faisant partie d'un job découpé, l'ID de l'inventaire sur lequel l'opération de découpage a eu lieu. Si ne fait pas partie d'un job découpé, le paramètre ne sera pas utilisé." + +#: awx/main/models/jobs.py:594 +msgid "" +"If ran as part of sliced jobs, the total number of slices. If 1, job is not " +"part of a sliced job." +msgstr "Si exécuté en tant que job découpé, le nombre total de tranches. Si égal à 1, le job ne fait pas partie d'un job découpé." -#: awx/main/models/jobs.py:629 +#: awx/main/models/jobs.py:676 #, python-brace-format msgid "{status_value} is not a valid status option." msgstr "{status_value} ne correspond pas à une option de statut valide." -#: awx/main/models/jobs.py:1005 +#: awx/main/models/jobs.py:926 +msgid "" +"Inventory applied as a prompt, assuming job template prompts for inventory" +msgstr "Inventaire appliqué en tant qu'invite, en supposant que le modèle de tâche demande un inventaire" + +#: awx/main/models/jobs.py:1085 msgid "job host summaries" msgstr "récapitulatifs des hôtes pour la tâche" -#: awx/main/models/jobs.py:1077 +#: awx/main/models/jobs.py:1144 msgid "Remove jobs older than a certain number of days" msgstr "Supprimer les tâches plus anciennes qu'un certain nombre de jours" -#: awx/main/models/jobs.py:1078 +#: awx/main/models/jobs.py:1145 msgid "Remove activity stream entries older than a certain number of days" -msgstr "" -"Supprimer les entrées du flux d'activité plus anciennes qu'un certain nombre" -" de jours" +msgstr "Supprimer les entrées du flux d'activité plus anciennes qu'un certain nombre de jours" -#: awx/main/models/jobs.py:1079 -msgid "Purge and/or reduce the granularity of system tracking data" -msgstr "Purger et/ou réduire la granularité des données de suivi du système" +#: awx/main/models/jobs.py:1146 +msgid "Removes expired browser sessions from the database" +msgstr "Supprime les sessions de navigateur expirées dans la base de données" -#: awx/main/models/jobs.py:1149 +#: awx/main/models/jobs.py:1147 +msgid "Removes expired OAuth 2 access tokens and refresh tokens" +msgstr "Supprime les jetons d'accès OAuth 2 et les jetons d’actualisation arrivés à expiration" + +#: awx/main/models/jobs.py:1217 #, python-brace-format msgid "Variables {list_of_keys} are not allowed for system jobs." -msgstr "" -"Les variables {list_of_keys} ne sont pas autorisées pour les jobs système." +msgstr "Les variables {list_of_keys} ne sont pas autorisées pour les tâches système." -#: awx/main/models/jobs.py:1164 +#: awx/main/models/jobs.py:1233 msgid "days must be a positive integer." msgstr "jours doit être un entier positif." @@ -3849,127 +4051,169 @@ msgstr "jours doit être un entier positif." msgid "Organization this label belongs to." msgstr "Organisation à laquelle appartient ce libellé." -#: awx/main/models/mixins.py:309 +#: awx/main/models/mixins.py:321 #, python-brace-format msgid "" "Variables {list_of_keys} are not allowed on launch. Check the Prompt on " -"Launch setting on the Job Template to include Extra Variables." -msgstr "" -"Les variables {list_of_keys} ne sont pas autorisées au lancement. Vérifiez " -"le paramètre Invite au lancement sur le Modèle de job pour inclure des " -"Variables supplémentaires." +"Launch setting on the {model_name} to include Extra Variables." +msgstr "Les variables {list_of_keys} ne sont pas autorisées au lancement. Vérifiez le paramètre Invite au lancement sur {model_name} pour inclure des Variables supplémentaires." -#: awx/main/models/mixins.py:440 +#: awx/main/models/mixins.py:453 msgid "Local absolute file path containing a custom Python virtualenv to use" -msgstr "" -"Chemin d'accès au fichier local absolu contenant un virtualenv Python " -"personnalisé à utiliser" +msgstr "Chemin d'accès au fichier local absolu contenant un virtualenv Python personnalisé à utiliser" -#: awx/main/models/mixins.py:447 +#: awx/main/models/mixins.py:460 msgid "{} is not a valid virtualenv in {}" msgstr "{} n'est pas un virtualenv dans {}" +#: awx/main/models/mixins.py:507 awx/main/models/mixins.py:551 +msgid "Service that webhook requests will be accepted from" +msgstr "Service à partir duquel les demandes de webhook seront acceptées" + +#: awx/main/models/mixins.py:512 +msgid "Shared secret that the webhook service will use to sign requests" +msgstr "Secret partagé que le service de webhook utilisera pour signer les demandes" + +#: awx/main/models/mixins.py:520 awx/main/models/mixins.py:559 +msgid "Personal Access Token for posting back the status to the service API" +msgstr "Jeton d'accès personnel pour la restitution du statut à l'API du service" + +#: awx/main/models/mixins.py:564 +msgid "Unique identifier of the event that triggered this webhook" +msgstr "Identifiant unique de l'événement qui a déclenché ce webhook" + #: awx/main/models/notifications.py:42 +msgid "Email" +msgstr "Email" + +#: awx/main/models/notifications.py:43 +msgid "Slack" +msgstr "Slack" + +#: awx/main/models/notifications.py:44 +msgid "Twilio" +msgstr "Twilio" + +#: awx/main/models/notifications.py:45 +msgid "Pagerduty" +msgstr "Pagerduty" + +#: awx/main/models/notifications.py:46 +msgid "Grafana" +msgstr "Grafana" + +#: awx/main/models/notifications.py:47 +msgid "HipChat" +msgstr "HipChat" + +#: awx/main/models/notifications.py:48 awx/main/models/unified_jobs.py:545 +msgid "Webhook" +msgstr "Webhook" + +#: awx/main/models/notifications.py:49 +msgid "Mattermost" +msgstr "Mattermost" + +#: awx/main/models/notifications.py:50 msgid "Rocket.Chat" msgstr "Rocket.Chat" -#: awx/main/models/notifications.py:142 awx/main/models/unified_jobs.py:62 +#: awx/main/models/notifications.py:51 +msgid "IRC" +msgstr "IRC" + +#: awx/main/models/notifications.py:82 +msgid "Optional custom messages for notification template." +msgstr "Messages personnalisés optionnels pour le modèle de notification." + +#: awx/main/models/notifications.py:212 awx/main/models/unified_jobs.py:70 msgid "Pending" msgstr "En attente" -#: awx/main/models/notifications.py:143 awx/main/models/unified_jobs.py:65 +#: awx/main/models/notifications.py:213 awx/main/models/unified_jobs.py:73 msgid "Successful" msgstr "Réussi" -#: awx/main/models/notifications.py:144 awx/main/models/unified_jobs.py:66 +#: awx/main/models/notifications.py:214 awx/main/models/unified_jobs.py:74 msgid "Failed" msgstr "Échec" -#: awx/main/models/notifications.py:218 -msgid "status_str must be either succeeded or failed" -msgstr "status_str doit être une réussite ou un échec" +#: awx/main/models/notifications.py:467 +msgid "status must be either running, succeeded or failed" +msgstr "le statut doit être soit en cours, soit réussi, soit échoué" -#: awx/main/models/oauth.py:29 +#: awx/main/models/oauth.py:33 msgid "application" msgstr "application" -#: awx/main/models/oauth.py:35 +#: awx/main/models/oauth.py:40 msgid "Confidential" msgstr "Confidentiel" -#: awx/main/models/oauth.py:36 +#: awx/main/models/oauth.py:41 msgid "Public" msgstr "Public" -#: awx/main/models/oauth.py:43 +#: awx/main/models/oauth.py:47 msgid "Authorization code" msgstr "Code d'autorisation" -#: awx/main/models/oauth.py:44 -msgid "Implicit" -msgstr "Implicite" - -#: awx/main/models/oauth.py:45 +#: awx/main/models/oauth.py:48 msgid "Resource owner password-based" msgstr "Ressource basée mot de passe de propriétaire" -#: awx/main/models/oauth.py:60 +#: awx/main/models/oauth.py:63 msgid "Organization containing this application." msgstr "Organisation contenant cette application." -#: awx/main/models/oauth.py:69 +#: awx/main/models/oauth.py:72 msgid "" "Used for more stringent verification of access to an application when " "creating a token." -msgstr "" -"Utilisé pour une vérification plus rigoureuse de l'accès à une application " -"lors de la création d'un jeton." +msgstr "Utilisé pour une vérification plus rigoureuse de l'accès à une application lors de la création d'un jeton." -#: awx/main/models/oauth.py:74 +#: awx/main/models/oauth.py:77 msgid "" "Set to Public or Confidential depending on how secure the client device is." -msgstr "" -"Défini sur sur Public ou Confidentiel selon le degré de sécurité du " -"périphérique client." +msgstr "Défini sur sur Public ou Confidentiel selon le degré de sécurité du périphérique client." -#: awx/main/models/oauth.py:78 +#: awx/main/models/oauth.py:81 msgid "" "Set True to skip authorization step for completely trusted applications." -msgstr "" -"Définir sur True pour sauter l'étape d'autorisation pour les applications " -"totalement fiables." +msgstr "Définir sur True pour sauter l'étape d'autorisation pour les applications totalement fiables." -#: awx/main/models/oauth.py:83 +#: awx/main/models/oauth.py:86 msgid "" "The Grant type the user must use for acquire tokens for this application." -msgstr "" -"Le type de permission que l'utilisateur doit utiliser pour acquérir des " -"jetons pour cette application." +msgstr "Le type de permission que l'utilisateur doit utiliser pour acquérir des jetons pour cette application." -#: awx/main/models/oauth.py:91 +#: awx/main/models/oauth.py:94 msgid "access token" msgstr "jeton d'accès" -#: awx/main/models/oauth.py:99 +#: awx/main/models/oauth.py:103 msgid "The user representing the token owner" msgstr "L'utilisateur représentant le propriétaire du jeton." -#: awx/main/models/oauth.py:114 +#: awx/main/models/oauth.py:117 msgid "" -"Allowed scopes, further restricts user's permissions. Must be a simple " -"space-separated string with allowed scopes ['read', 'write']." -msgstr "" -"Limites autorisées, restreint encore plus les permissions de l'utilisateur. " -"Doit correspondre à une simple chaîne de caractères séparés par des espaces " -"avec des champs d'application autorisés ('read','write')." +"Allowed scopes, further restricts user's permissions. Must be a simple space-" +"separated string with allowed scopes ['read', 'write']." +msgstr "Limites autorisées, restreint encore plus les permissions de l'utilisateur. Doit correspondre à une simple chaîne de caractères séparés par des espaces avec des champs d'application autorisés ('read','write')." -#: awx/main/models/oauth.py:133 +#: awx/main/models/oauth.py:140 msgid "" "OAuth2 Tokens cannot be created by users associated with an external " "authentication provider ({})" -msgstr "" -"Les jetons OAuth2 ne peuvent pas être créés par des utilisateurs associés à " -"un fournisseur d'authentification externe." +msgstr "Les jetons OAuth2 ne peuvent pas être créés par des utilisateurs associés à un fournisseur d'authentification externe." + +#: awx/main/models/organization.py:51 +msgid "Maximum number of hosts allowed to be managed by this organization." +msgstr "Nombre maximum d'hôtes autorisés à être gérés par cette organisation." + +#: awx/main/models/projects.py:53 awx/main/models/unified_jobs.py:539 +msgid "Manual" +msgstr "Manuel" #: awx/main/models/projects.py:54 msgid "Git" @@ -3991,9 +4235,7 @@ msgstr "Red Hat Insights" msgid "" "Local path (relative to PROJECTS_ROOT) containing playbooks and related " "files for this project." -msgstr "" -"Chemin local (relatif à PROJECTS_ROOT) contenant des playbooks et des " -"fichiers associés pour ce projet." +msgstr "Chemin local (relatif à PROJECTS_ROOT) contenant des playbooks et des fichiers associés pour ce projet." #: awx/main/models/projects.py:92 msgid "SCM Type" @@ -4001,8 +4243,7 @@ msgstr "Type de SCM" #: awx/main/models/projects.py:93 msgid "Specifies the source control system used to store the project." -msgstr "" -"Spécifie le système de contrôle des sources utilisé pour stocker le projet." +msgstr "Spécifie le système de contrôle des sources utilisé pour stocker le projet." #: awx/main/models/projects.py:99 msgid "SCM URL" @@ -4020,146 +4261,172 @@ msgstr "Branche SCM" msgid "Specific branch, tag or commit to checkout." msgstr "Branche, balise ou validation spécifique à valider." -#: awx/main/models/projects.py:111 +#: awx/main/models/projects.py:113 +msgid "SCM refspec" +msgstr "SCM refspec" + +#: awx/main/models/projects.py:114 +msgid "For git projects, an additional refspec to fetch." +msgstr "Pour les projets Git, une refspec supplémentaire à obtenir." + +#: awx/main/models/projects.py:118 msgid "Discard any local changes before syncing the project." msgstr "Ignorez les modifications locales avant de synchroniser le projet." -#: awx/main/models/projects.py:115 +#: awx/main/models/projects.py:122 msgid "Delete the project before syncing." msgstr "Supprimez le projet avant la synchronisation." -#: awx/main/models/projects.py:144 +#: awx/main/models/projects.py:151 msgid "Invalid SCM URL." msgstr "URL du SCM incorrecte." -#: awx/main/models/projects.py:147 +#: awx/main/models/projects.py:154 msgid "SCM URL is required." msgstr "L'URL du SCM est requise." -#: awx/main/models/projects.py:155 +#: awx/main/models/projects.py:162 msgid "Insights Credential is required for an Insights Project." -msgstr "" -"Des informations d'identification Insights sont requises pour un projet " -"Insights." +msgstr "Des informations d'identification Insights sont requises pour un projet Insights." -#: awx/main/models/projects.py:161 +#: awx/main/models/projects.py:168 msgid "Credential kind must be 'scm'." msgstr "Le type d'informations d'identification doit être 'scm'." -#: awx/main/models/projects.py:178 +#: awx/main/models/projects.py:185 msgid "Invalid credential." msgstr "Informations d'identification non valides." -#: awx/main/models/projects.py:263 +#: awx/main/models/projects.py:259 msgid "Update the project when a job is launched that uses the project." msgstr "Mettez à jour le projet lorsqu'une tâche qui l'utilise est lancée." -#: awx/main/models/projects.py:268 +#: awx/main/models/projects.py:264 msgid "" -"The number of seconds after the last project update ran that a newproject " +"The number of seconds after the last project update ran that a new project " "update will be launched as a job dependency." -msgstr "" -"Délai écoulé (en secondes) entre la dernière mise à jour du projet et le " -"lancement d'une nouvelle mise à jour en tant que dépendance de la tâche." +msgstr "Délai écoulé (en secondes) entre la dernière mise à jour du projet et le lancement d'une nouvelle mise à jour de projet en tant que dépendance de la tâche." + +#: awx/main/models/projects.py:269 +msgid "" +"Allow changing the SCM branch or revision in a job template that uses this " +"project." +msgstr "Permet de modifier la branche ou la révision SCM dans un modèle de tâche qui utilise ce projet." -#: awx/main/models/projects.py:278 +#: awx/main/models/projects.py:279 msgid "The last revision fetched by a project update" msgstr "Dernière révision récupérée par une mise à jour du projet" -#: awx/main/models/projects.py:285 +#: awx/main/models/projects.py:286 msgid "Playbook Files" msgstr "Fichiers de playbook" -#: awx/main/models/projects.py:286 +#: awx/main/models/projects.py:287 msgid "List of playbooks found in the project" msgstr "Liste des playbooks trouvés dans le projet" -#: awx/main/models/projects.py:293 +#: awx/main/models/projects.py:294 msgid "Inventory Files" msgstr "Fichiers d'inventaire" -#: awx/main/models/projects.py:294 +#: awx/main/models/projects.py:295 msgid "" "Suggested list of content that could be Ansible inventory in the project" -msgstr "" -"Suggestion de liste de contenu qui pourrait être un inventaire Ansible dans " -"le projet" +msgstr "Suggestion de liste de contenu qui pourrait être un inventaire Ansible dans le projet" -#: awx/main/models/rbac.py:36 +#: awx/main/models/projects.py:332 +msgid "Organization cannot be changed when in use by job templates." +msgstr "L'organisation ne peut pas être modifiée lorsqu'elle est utilisée par les modèles de tâche." + +#: awx/main/models/projects.py:490 +msgid "Parts of the project update playbook that will be run." +msgstr "Certaines parties du projet mettent à jour le playbook qui sera exécuté." + +#: awx/main/models/projects.py:498 +msgid "" +"The SCM Revision discovered by this update for the given project and branch." +msgstr "Révision SCM découverte par cette mise à jour pour le projet et la branche donnés." + +#: awx/main/models/rbac.py:35 msgid "System Administrator" msgstr "Administrateur du système" -#: awx/main/models/rbac.py:37 +#: awx/main/models/rbac.py:36 msgid "System Auditor" msgstr "Auditeur système" -#: awx/main/models/rbac.py:38 +#: awx/main/models/rbac.py:37 msgid "Ad Hoc" msgstr "Ad Hoc" -#: awx/main/models/rbac.py:39 +#: awx/main/models/rbac.py:38 msgid "Admin" msgstr "Admin" -#: awx/main/models/rbac.py:40 +#: awx/main/models/rbac.py:39 msgid "Project Admin" msgstr "Admin Projet" -#: awx/main/models/rbac.py:41 +#: awx/main/models/rbac.py:40 msgid "Inventory Admin" msgstr "Admin Inventaire" -#: awx/main/models/rbac.py:42 +#: awx/main/models/rbac.py:41 msgid "Credential Admin" msgstr "Admin Identifiants" -#: awx/main/models/rbac.py:43 +#: awx/main/models/rbac.py:42 msgid "Job Template Admin" msgstr "Admin Modèle de job" -#: awx/main/models/rbac.py:44 +#: awx/main/models/rbac.py:43 msgid "Workflow Admin" -msgstr "Admin Workflow" +msgstr "Admin Flux de travail" -#: awx/main/models/rbac.py:45 +#: awx/main/models/rbac.py:44 msgid "Notification Admin" msgstr "Admin Notification" -#: awx/main/models/rbac.py:46 +#: awx/main/models/rbac.py:45 msgid "Auditor" msgstr "Auditeur" -#: awx/main/models/rbac.py:47 +#: awx/main/models/rbac.py:46 msgid "Execute" msgstr "Execution" -#: awx/main/models/rbac.py:48 +#: awx/main/models/rbac.py:47 msgid "Member" msgstr "Membre" -#: awx/main/models/rbac.py:49 +#: awx/main/models/rbac.py:48 msgid "Read" msgstr "Lecture" -#: awx/main/models/rbac.py:50 +#: awx/main/models/rbac.py:49 msgid "Update" msgstr "Mise à jour" -#: awx/main/models/rbac.py:51 +#: awx/main/models/rbac.py:50 msgid "Use" msgstr "Utilisation" +#: awx/main/models/rbac.py:51 +msgid "Approve" +msgstr "Approbation" + #: awx/main/models/rbac.py:55 msgid "Can manage all aspects of the system" msgstr "Peut gérer tous les aspects du système" #: awx/main/models/rbac.py:56 -msgid "Can view all settings on the system" -msgstr "Peut afficher tous les paramètres de configuration du système" +msgid "Can view all aspects of the system" +msgstr "Peut afficher tous les aspects du système" #: awx/main/models/rbac.py:57 -msgid "May run ad hoc commands on an inventory" -msgstr "Peut exécuter des commandes ad hoc sur un inventaire" +#, python-format +msgid "May run ad hoc commands on the %s" +msgstr "Peut exécuter des commandes ad hoc sur le %s" #: awx/main/models/rbac.py:58 #, python-format @@ -4184,12 +4451,12 @@ msgstr "Peut gérer tous les identifiants de %s" #: awx/main/models/rbac.py:62 #, python-format msgid "Can manage all job templates of the %s" -msgstr "Peut gérer tous les modèles de job de %s" +msgstr "Peut gérer tous les modèles de tâche de %s" #: awx/main/models/rbac.py:63 #, python-format msgid "Can manage all workflows of the %s" -msgstr "Peut gérer tous les workflows de %s" +msgstr "Peut gérer tous les flux de travail de %s" #: awx/main/models/rbac.py:64 #, python-format @@ -4198,18 +4465,17 @@ msgstr "Peut gérer toutes les notifications de %s" #: awx/main/models/rbac.py:65 #, python-format -msgid "Can view all settings for the %s" -msgstr "Peut afficher tous les paramètres de configuration du %s" +msgid "Can view all aspects of the %s" +msgstr "Peut afficher tous les aspects de %s" #: awx/main/models/rbac.py:67 msgid "May run any executable resources in the organization" -msgstr "" -"Peut exécuter n'importe quelle ressource exécutable dans l'organisation" +msgstr "Peut exécuter n'importe quelle ressource exécutable dans l'organisation" #: awx/main/models/rbac.py:68 #, python-format msgid "May run the %s" -msgstr "Peut exécuter %s" +msgstr "Peut exécuter le %s" #: awx/main/models/rbac.py:70 #, python-format @@ -4219,322 +4485,401 @@ msgstr "L'utilisateur est un membre de %s" #: awx/main/models/rbac.py:71 #, python-format msgid "May view settings for the %s" -msgstr "Peut afficher les paramètres de configuration de %s" +msgstr "Peut afficher les paramètres de %s" #: awx/main/models/rbac.py:72 -msgid "" -"May update project or inventory or group using the configured source update " -"system" -msgstr "" -"Peut mettre un projet, un inventaire, ou un groupe à jour en utilisant le " -"système de mise à jour de la source configuré." +#, python-format +msgid "May update the %s" +msgstr "Peut mettre à jour le %s" #: awx/main/models/rbac.py:73 #, python-format msgid "Can use the %s in a job template" msgstr "Peut utiliser %s dans un modèle de tâche" -#: awx/main/models/rbac.py:137 +#: awx/main/models/rbac.py:74 +msgid "Can approve or deny a workflow approval node" +msgstr "Peut approuver ou refuser un nœud d'approbation de workflow" + +#: awx/main/models/rbac.py:138 msgid "roles" msgstr "rôles" -#: awx/main/models/rbac.py:443 +#: awx/main/models/rbac.py:445 msgid "role_ancestors" msgstr "role_ancestors" -#: awx/main/models/schedules.py:79 +#: awx/main/models/schedules.py:83 msgid "Enables processing of this schedule." msgstr "Active le traitement de ce calendrier." -#: awx/main/models/schedules.py:85 +#: awx/main/models/schedules.py:89 msgid "The first occurrence of the schedule occurs on or after this time." -msgstr "" -"La première occurrence du calendrier se produit à ce moment précis ou " -"ultérieurement." +msgstr "La première occurrence du calendrier se produit à ce moment précis ou ultérieurement." -#: awx/main/models/schedules.py:91 +#: awx/main/models/schedules.py:95 msgid "" "The last occurrence of the schedule occurs before this time, aftewards the " "schedule expires." -msgstr "" -"La dernière occurrence du calendrier se produit avant ce moment précis. " -"Passé ce délai, le calendrier arrive à expiration." +msgstr "La dernière occurrence du calendrier se produit avant ce moment précis. Passé ce délai, le calendrier arrive à expiration." -#: awx/main/models/schedules.py:95 +#: awx/main/models/schedules.py:99 msgid "A value representing the schedules iCal recurrence rule." msgstr "Valeur représentant la règle de récurrence iCal des calendriers." -#: awx/main/models/schedules.py:101 +#: awx/main/models/schedules.py:105 msgid "The next time that the scheduled action will run." msgstr "La prochaine fois que l'action planifiée s'exécutera." -#: awx/main/models/unified_jobs.py:61 +#: awx/main/models/unified_jobs.py:69 msgid "New" msgstr "Nouveau" -#: awx/main/models/unified_jobs.py:63 +#: awx/main/models/unified_jobs.py:71 msgid "Waiting" msgstr "En attente" -#: awx/main/models/unified_jobs.py:64 +#: awx/main/models/unified_jobs.py:72 msgid "Running" msgstr "En cours d'exécution" -#: awx/main/models/unified_jobs.py:68 +#: awx/main/models/unified_jobs.py:76 msgid "Canceled" msgstr "Annulé" -#: awx/main/models/unified_jobs.py:72 +#: awx/main/models/unified_jobs.py:80 msgid "Never Updated" msgstr "Jamais mis à jour" -#: awx/main/models/unified_jobs.py:76 +#: awx/main/models/unified_jobs.py:84 msgid "OK" msgstr "OK" -#: awx/main/models/unified_jobs.py:77 +#: awx/main/models/unified_jobs.py:85 msgid "Missing" msgstr "Manquant" -#: awx/main/models/unified_jobs.py:81 +#: awx/main/models/unified_jobs.py:89 msgid "No External Source" msgstr "Aucune source externe" -#: awx/main/models/unified_jobs.py:88 +#: awx/main/models/unified_jobs.py:96 msgid "Updating" msgstr "Mise à jour en cours" -#: awx/main/models/unified_jobs.py:427 +#: awx/main/models/unified_jobs.py:167 +msgid "The organization used to determine access to this template." +msgstr "Organisation utilisée pour déterminer l'accès à ce modèle." + +#: awx/main/models/unified_jobs.py:466 msgid "Field is not allowed on launch." msgstr "Champ non autorisé au lancement." -#: awx/main/models/unified_jobs.py:455 +#: awx/main/models/unified_jobs.py:494 #, python-brace-format msgid "" -"Variables {list_of_keys} provided, but this template cannot accept " -"variables." -msgstr "" -"Variables {list_of_keys} fournies, mais ce modèle ne peut pas accepter de " -"variables." +"Variables {list_of_keys} provided, but this template cannot accept variables." +msgstr "Variables {list_of_keys} fournies, mais ce modèle ne peut pas accepter de variables." -#: awx/main/models/unified_jobs.py:520 +#: awx/main/models/unified_jobs.py:540 msgid "Relaunch" msgstr "Relancer" -#: awx/main/models/unified_jobs.py:521 +#: awx/main/models/unified_jobs.py:541 msgid "Callback" msgstr "Rappeler" -#: awx/main/models/unified_jobs.py:522 +#: awx/main/models/unified_jobs.py:542 msgid "Scheduled" msgstr "Planifié" -#: awx/main/models/unified_jobs.py:523 +#: awx/main/models/unified_jobs.py:543 msgid "Dependency" msgstr "Dépendance" -#: awx/main/models/unified_jobs.py:524 +#: awx/main/models/unified_jobs.py:544 msgid "Workflow" msgstr "Workflow" -#: awx/main/models/unified_jobs.py:525 +#: awx/main/models/unified_jobs.py:546 msgid "Sync" msgstr "Sync" -#: awx/main/models/unified_jobs.py:573 +#: awx/main/models/unified_jobs.py:601 msgid "The node the job executed on." msgstr "Nœud sur lequel la tâche s'est exécutée." -#: awx/main/models/unified_jobs.py:579 +#: awx/main/models/unified_jobs.py:607 msgid "The instance that managed the isolated execution environment." msgstr "L'instance qui gère l'environnement d'exécution isolé." -#: awx/main/models/unified_jobs.py:605 +#: awx/main/models/unified_jobs.py:634 msgid "The date and time the job was queued for starting." -msgstr "" -"Date et heure auxquelles la tâche a été mise en file d'attente pour le " -"démarrage." +msgstr "Date et heure auxquelles la tâche a été mise en file d'attente pour le démarrage." -#: awx/main/models/unified_jobs.py:611 +#: awx/main/models/unified_jobs.py:639 +msgid "" +"If True, the task manager has already processed potential dependencies for " +"this job." +msgstr "Si la valeur True est définie, le gestionnaire de tâches a déjà traité les dépendances potentielles de cette tâche." + +#: awx/main/models/unified_jobs.py:645 msgid "The date and time the job finished execution." msgstr "Date et heure de fin d'exécution de la tâche." -#: awx/main/models/unified_jobs.py:617 +#: awx/main/models/unified_jobs.py:652 +msgid "The date and time when the cancel request was sent." +msgstr "Date et heure d'envoi de la demande d'annulation." + +#: awx/main/models/unified_jobs.py:659 msgid "Elapsed time in seconds that the job ran." msgstr "Délai écoulé (en secondes) pendant lequel la tâche s'est exécutée." -#: awx/main/models/unified_jobs.py:639 +#: awx/main/models/unified_jobs.py:681 msgid "" -"A status field to indicate the state of the job if it wasn't able to run and" -" capture stdout" -msgstr "" -"Champ d'état indiquant l'état de la tâche si elle n'a pas pu s'exécuter et " -"capturer stdout" +"A status field to indicate the state of the job if it wasn't able to run and " +"capture stdout" +msgstr "Champ d'état indiquant l'état de la tâche si elle n'a pas pu s'exécuter et capturer stdout" + +#: awx/main/models/unified_jobs.py:710 +msgid "The Instance group the job was run under" +msgstr "Groupe d'instances sous lequel la tâche a été exécutée" + +#: awx/main/models/unified_jobs.py:718 +msgid "The organization used to determine access to this unified job." +msgstr "Organisation utilisée pour déterminer l'accès à cette tâche unifiée." + +#: awx/main/models/workflow.py:85 +msgid "" +"If enabled then the node will only run if all of the parent nodes have met " +"the criteria to reach this node" +msgstr "En cas d'activation, le nœud ne fonctionnera que si tous les nœuds parents ont respecté les critères pour atteindre ce nœud" + +#: awx/main/models/workflow.py:154 +msgid "" +"An identifier for this node that is unique within its workflow. It is copied " +"to workflow job nodes corresponding to this node." +msgstr "Identifiant pour ce nœud qui est unique dans son flux de travail. Il est copié sur les nœuds de la tâche du flux de travail correspondant à ce nœud." -#: awx/main/models/unified_jobs.py:668 -msgid "The Rampart/Instance group the job was run under" -msgstr "Groupe d'instance/Rampart sous lequel la tâche a été exécutée." +#: awx/main/models/workflow.py:229 +msgid "" +"Indicates that a job will not be created when True. Workflow runtime " +"semantics will mark this True if the node is in a path that will decidedly " +"not be ran. A value of False means the node may not run." +msgstr "Indique qu'une tâche ne sera pas créée lorsqu'elle est sur True. La sémantique d'exécution du flux de travail indiquera True si le nœud est dans un chemin qui ne sera clairement pas exécuté. Une valeur de False signifie que le nœud ne peut pas s'exécuter." + +#: awx/main/models/workflow.py:236 +msgid "" +"An identifier coresponding to the workflow job template node that this node " +"was created from." +msgstr "Identifiant correspondant au nœud du modèle de tâche de flux de travail à partir duquel ce nœud a été créé." -#: awx/main/models/workflow.py:203 +#: awx/main/models/workflow.py:282 #, python-brace-format msgid "" -"Bad launch configuration starting template {template_pk} as part of workflow {workflow_pk}. Errors:\n" +"Bad launch configuration starting template {template_pk} as part of workflow " +"{workflow_pk}. Errors:\n" "{error_text}" -msgstr "" -"Mauvais modèle de départ de configuration du lancement {template_pk} dans le workflow {workflow_pk}. Erreurs :\n" +msgstr "Modèle de démarrage de configuration de lancement incorrect {template_pk} dans le cadre du workflow {workflow_pk}. Erreurs :\n" "{error_text}" -#: awx/main/models/workflow.py:393 -msgid "Field is not allowed for use in workflows." -msgstr "Champ non autorisé dans les workflows." +#: awx/main/models/workflow.py:595 +msgid "" +"If automatically created for a sliced job run, the job template the workflow " +"job was created from." +msgstr "S'il est créé automatiquement pour l'exécution d'un job découpé, le modèle de job à partir duquel le job de flux de travail a été créé." -#: awx/main/notifications/base.py:17 -#: awx/main/notifications/email_backend.py:28 +#: awx/main/models/workflow.py:687 awx/main/models/workflow.py:721 msgid "" -"{} #{} had status {}, view details at {}\n" -"\n" -msgstr "" -"{} #{} était à l'état {}, voir les détails sur {}\n" -"\n" +"The amount of time (in seconds) before the approval node expires and fails." +msgstr "Délai (en secondes) avant que le nœud d'approbation n'expire et n'échoue." + +#: awx/main/models/workflow.py:725 +msgid "" +"Shows when an approval node (with a timeout assigned to it) has timed out." +msgstr "Indique quand un nœud d'approbation (auquel un délai d'attente a été affecté) a dépassé le délai d’attente." -#: awx/main/notifications/hipchat_backend.py:48 +#: awx/main/notifications/grafana_backend.py:86 +msgid "Error converting time {} or timeEnd {} to int." +msgstr "Erreur de conversion de time {} ou timeEnd {} en entier." + +#: awx/main/notifications/grafana_backend.py:88 +msgid "Error converting time {} and/or timeEnd {} to int." +msgstr "Erreur de conversion de time {} et/ou timeEnd {} en entier." + +#: awx/main/notifications/grafana_backend.py:102 +#: awx/main/notifications/grafana_backend.py:104 +msgid "Error sending notification grafana: {}" +msgstr "Erreur lors de l'envoi du grafana de notification : {}" + +#: awx/main/notifications/hipchat_backend.py:50 msgid "Error sending messages: {}" msgstr "Erreur lors de l'envoi de messages : {}" -#: awx/main/notifications/hipchat_backend.py:50 +#: awx/main/notifications/hipchat_backend.py:52 msgid "Error sending message to hipchat: {}" msgstr "Erreur lors de l'envoi d'un message à hipchat : {}" -#: awx/main/notifications/irc_backend.py:54 +#: awx/main/notifications/irc_backend.py:56 msgid "Exception connecting to irc server: {}" msgstr "Exception lors de la connexion au serveur irc : {}" -#: awx/main/notifications/mattermost_backend.py:48 #: awx/main/notifications/mattermost_backend.py:50 +#: awx/main/notifications/mattermost_backend.py:52 msgid "Error sending notification mattermost: {}" msgstr "Erreur d'envoi de notification mattermost: {}" -#: awx/main/notifications/pagerduty_backend.py:39 +#: awx/main/notifications/pagerduty_backend.py:64 msgid "Exception connecting to PagerDuty: {}" msgstr "Exception lors de la connexion à PagerDuty : {}" -#: awx/main/notifications/pagerduty_backend.py:48 -#: awx/main/notifications/slack_backend.py:82 -#: awx/main/notifications/slack_backend.py:99 -#: awx/main/notifications/twilio_backend.py:46 +#: awx/main/notifications/pagerduty_backend.py:73 +#: awx/main/notifications/slack_backend.py:58 +#: awx/main/notifications/twilio_backend.py:48 msgid "Exception sending messages: {}" msgstr "Exception lors de l'envoi de messages : {}" -#: awx/main/notifications/rocketchat_backend.py:46 #: awx/main/notifications/rocketchat_backend.py:49 +#: awx/main/notifications/rocketchat_backend.py:52 msgid "Error sending notification rocket.chat: {}" msgstr "Erreur d'envoi de notification rocket.chat: {}" -#: awx/main/notifications/twilio_backend.py:36 +#: awx/main/notifications/twilio_backend.py:38 msgid "Exception connecting to Twilio: {}" msgstr "Exception lors de la connexion à Twilio : {}" -#: awx/main/notifications/webhook_backend.py:38 -#: awx/main/notifications/webhook_backend.py:40 +#: awx/main/notifications/webhook_backend.py:75 +#: awx/main/notifications/webhook_backend.py:77 msgid "Error sending notification webhook: {}" msgstr "Erreur lors de l'envoi d'un webhook de notification : {}" -#: awx/main/scheduler/task_manager.py:201 +#: awx/main/scheduler/dag_workflow.py:170 +#, python-brace-format msgid "" -"Job spawned from workflow could not start because it was not in the right " -"state or required manual credentials" -msgstr "" -"Tâche, lancée à partir du workflow, ne pouvant démarrer, pour faute d'être " -"dans l'état qui convient ou nécessitant des informations d'identification " -"manuelles adéquates." +"No error handling path for workflow job node(s) [{node_status}]. Workflow " +"job node(s) missing unified job template and error handling path [{no_ufjt}]." +msgstr "Aucun chemin de traitement des erreurs pour le ou les nœuds de tâche de flux de travail [{node_status}]. Le ou les nœuds de tâche de flux de travail n'ont pas de modèle de tâche unifié ni de chemin de traitement des erreurs [{no_ufjt}]." + +#: awx/main/scheduler/task_manager.py:118 +msgid "" +"Workflow Job spawned from workflow could not start because it would result " +"in recursion (spawn order, most recent first: {})" +msgstr "Job de flux de travail lancé à partir d'un flux, ne pouvant démarrer, pour cause de récursion (ordre de génération, le plus récent d'abord : {})" -#: awx/main/scheduler/task_manager.py:205 +#: awx/main/scheduler/task_manager.py:126 msgid "" "Job spawned from workflow could not start because it was missing a related " "resource such as project or inventory" -msgstr "" -"Tâche, lancée à partir du workflow, ne pouvant démarrer, pour cause de " -"ressource manquante, tel un projet ou inventaire" +msgstr "Job lancé en provenance d'un flux de travail, ne pouvant démarrer, pour cause de ressource manquante, comme un projet ou inventaire" + +#: awx/main/scheduler/task_manager.py:135 +msgid "" +"Job spawned from workflow could not start because it was not in the right " +"state or required manual credentials" +msgstr "Tâche, lancée à partir du workflow, ne pouvant démarrer, pour faute d'être dans l'état qui convient ou nécessitant des informations d'identification manuelles adéquates." -#: awx/main/signals.py:632 -msgid "limit_reached" -msgstr "limit_reached" +#: awx/main/scheduler/task_manager.py:176 +msgid "No error handling paths found, marking workflow as failed" +msgstr "Aucun chemin de traitement des erreurs trouvé, flux de travail marqué comme étant en échec" -#: awx/main/tasks.py:305 -msgid "Ansible Tower host usage over 90%" -msgstr "Utilisation d'hôtes Ansible Tower supérieure à 90 %" +#: awx/main/scheduler/task_manager.py:508 +#, python-brace-format +msgid "The approval node {name} ({pk}) has expired after {timeout} seconds." +msgstr "Le nœud d'approbation {name} ({pk}) a expiré après {timeout} secondes." -#: awx/main/tasks.py:310 -msgid "Ansible Tower license will expire soon" -msgstr "La licence Ansible Tower expirera bientôt" +#: awx/main/tasks.py:1049 +msgid "Invalid virtual environment selected: {}" +msgstr "Environnement virtuel non valide sélectionné : {}" -#: awx/main/tasks.py:1358 +#: awx/main/tasks.py:1853 msgid "Job could not start because it does not have a valid inventory." msgstr "Le job n'a pas pu commencer parce qu'il n'a pas d'inventaire valide." -#: awx/main/utils/common.py:97 +#: awx/main/tasks.py:1857 +msgid "Job could not start because it does not have a valid project." +msgstr "La tâche n'a pas pu commencer parce qu'elle n'a pas de projet valide." + +#: awx/main/tasks.py:1862 +msgid "" +"The project revision for this job template is unknown due to a failed update." +msgstr "La révision de projet de ce modèle de job n'est pas connue en raison d'un échec de mise à jour." + +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:470 +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:498 +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:505 +msgid "" +"No error handling path for workflow job node(s) [({},{})]. Workflow job " +"node(s) missing unified job template and error handling path []." +msgstr "Aucun chemin de traitement des erreurs pour le ou les nœuds de tâche de flux de travail [({},{})]. Le ou les nœuds de tâche de flux de travail n'ont pas de modèle de tâche unifié ni de chemin de traitement des erreurs []." + +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:480 +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:491 +msgid "" +"No error handling path for workflow job node(s) []. Workflow job node(s) " +"missing unified job template and error handling path [{}]." +msgstr "Aucun chemin de traitement des erreurs pour le ou les nœuds de tâche de flux de travail []. Le ou les nœuds de tâche de flux de travail n'ont pas de modèle de tâche unifié ni de chemin de traitement des erreurs [{}]." + +#: awx/main/utils/common.py:87 #, python-format msgid "Unable to convert \"%s\" to boolean" msgstr "Impossible de convertir \"%s\" en booléen" -#: awx/main/utils/common.py:254 +#: awx/main/utils/common.py:261 #, python-format msgid "Unsupported SCM type \"%s\"" msgstr "Type de SCM \"%s\" non pris en charge" -#: awx/main/utils/common.py:261 awx/main/utils/common.py:273 -#: awx/main/utils/common.py:292 +#: awx/main/utils/common.py:268 awx/main/utils/common.py:280 +#: awx/main/utils/common.py:299 #, python-format msgid "Invalid %s URL" -msgstr "URL %s non valide." +msgstr "URL %s non valide" -#: awx/main/utils/common.py:263 awx/main/utils/common.py:302 +#: awx/main/utils/common.py:270 awx/main/utils/common.py:309 #, python-format msgid "Unsupported %s URL" msgstr "URL %s non prise en charge" -#: awx/main/utils/common.py:304 +#: awx/main/utils/common.py:311 #, python-format msgid "Unsupported host \"%s\" for file:// URL" msgstr "Hôte \"%s\" non pris en charge pour le fichier ://URL" -#: awx/main/utils/common.py:306 +#: awx/main/utils/common.py:313 #, python-format msgid "Host is required for %s URL" msgstr "L'hôte est requis pour l'URL %s" -#: awx/main/utils/common.py:324 +#: awx/main/utils/common.py:331 #, python-format msgid "Username must be \"git\" for SSH access to %s." msgstr "Le nom d'utilisateur doit être \"git\" pour l'accès SSH à %s." -#: awx/main/utils/common.py:330 +#: awx/main/utils/common.py:337 #, python-format msgid "Username must be \"hg\" for SSH access to %s." msgstr "Le nom d'utilisateur doit être \"hg\" pour l'accès SSH à %s." -#: awx/main/utils/common.py:611 +#: awx/main/utils/common.py:668 #, python-brace-format msgid "Input type `{data_type}` is not a dictionary" -msgstr "Le type d'entrée « {data_type} » ne n'est pas un dictionnaire" +msgstr "Le type d'entrée ’{data_type}’ n'est pas un dictionnaire" -#: awx/main/utils/common.py:644 +#: awx/main/utils/common.py:701 #, python-brace-format msgid "Variables not compatible with JSON standard (error: {json_error})" -msgstr "Variables non compatibles avec la syntaxe JSON (error: {json_error})" +msgstr "Variables non compatibles avec la norme JSON (error : {json_error})" -#: awx/main/utils/common.py:650 +#: awx/main/utils/common.py:707 #, python-brace-format msgid "" "Cannot parse as JSON (error: {json_error}) or YAML (error: {yaml_error})." -msgstr "" -"Impossible d'analyser avec JSON (error: {json_error}) ou YAML (error: " -"{yaml_error})." +msgstr "Impossible d'analyser avec JSON (erreur : {json_error}) ou YAML (erreur : {yaml_error})." #: awx/main/validators.py:67 #, python-format msgid "Invalid certificate or key: %s..." -msgstr "Certificat ou clé non valide : %r..." +msgstr "Certificat ou clé non valide : %s..." #: awx/main/validators.py:83 #, python-format @@ -4550,384 +4895,374 @@ msgstr "Type d'objet PEM non pris en charge : \"%s\"" msgid "Invalid base64-encoded data" msgstr "Données codées en base64 non valides" -#: awx/main/validators.py:131 +#: awx/main/validators.py:133 msgid "Exactly one private key is required." msgstr "Une clé privée uniquement est nécessaire." -#: awx/main/validators.py:133 +#: awx/main/validators.py:135 msgid "At least one private key is required." msgstr "Une clé privée au moins est nécessaire." -#: awx/main/validators.py:135 +#: awx/main/validators.py:137 #, python-format msgid "" -"At least %(min_keys)d private keys are required, only %(key_count)d " -"provided." -msgstr "" -"%(min_keys)d clés privées au moins sont requises, mais %(key_count)d " -"uniquement ont été fournies." +"At least %(min_keys)d private keys are required, only %(key_count)d provided." +msgstr "Au moins %(min_keys)d clés privées sont nécessaires, seulement %(key_count)d sont fournies." -#: awx/main/validators.py:138 +#: awx/main/validators.py:140 #, python-format msgid "Only one private key is allowed, %(key_count)d provided." msgstr "Une seule clé privée est autorisée, %(key_count)d ont été fournies." -#: awx/main/validators.py:140 +#: awx/main/validators.py:142 #, python-format msgid "" "No more than %(max_keys)d private keys are allowed, %(key_count)d provided." -msgstr "" -"Pas plus de %(max_keys)d clés privées sont autorisées, %(key_count)d ont été" -" fournies." +msgstr "Il n'est pas permis d'avoir plus que %(max_keys)d clés privées, %(key_count)d fournies." -#: awx/main/validators.py:145 +#: awx/main/validators.py:147 msgid "Exactly one certificate is required." msgstr "Un certificat uniquement est nécessaire." -#: awx/main/validators.py:147 +#: awx/main/validators.py:149 msgid "At least one certificate is required." msgstr "Un certificat au moins est nécessaire." -#: awx/main/validators.py:149 +#: awx/main/validators.py:151 #, python-format msgid "" "At least %(min_certs)d certificates are required, only %(cert_count)d " "provided." -msgstr "" -"%(min_certs)d certificats au moins sont requis, mais %(cert_count)d " -"uniquement ont été fournis." +msgstr "Au moins %(min_certs)d certificats sont requis, seulement %(cert_count)d fournis." -#: awx/main/validators.py:152 +#: awx/main/validators.py:154 #, python-format msgid "Only one certificate is allowed, %(cert_count)d provided." msgstr "Un seul certificat est autorisé, %(cert_count)d ont été fournis." -#: awx/main/validators.py:154 +#: awx/main/validators.py:156 #, python-format msgid "" -"No more than %(max_certs)d certificates are allowed, %(cert_count)d " -"provided." -msgstr "" -"Pas plus de %(max_certs)d certificats sont autorisés, %(cert_count)d ont été" -" fournis." +"No more than %(max_certs)d certificates are allowed, %(cert_count)d provided." +msgstr "Il n'est pas permis d'avoir plus que %(max_certs)d certificats, %(cert_count)d fournis." -#: awx/main/views.py:23 +#: awx/main/views.py:30 msgid "API Error" msgstr "Erreur API" -#: awx/main/views.py:61 +#: awx/main/views.py:65 msgid "Bad Request" msgstr "Requête incorrecte" -#: awx/main/views.py:62 +#: awx/main/views.py:66 msgid "The request could not be understood by the server." msgstr "La requête n'a pas pu être comprise par le serveur." -#: awx/main/views.py:69 +#: awx/main/views.py:73 msgid "Forbidden" msgstr "Interdiction" -#: awx/main/views.py:70 +#: awx/main/views.py:74 msgid "You don't have permission to access the requested resource." msgstr "Vous n'êtes pas autorisé à accéder à la ressource demandée." -#: awx/main/views.py:77 +#: awx/main/views.py:81 msgid "Not Found" msgstr "Introuvable" -#: awx/main/views.py:78 +#: awx/main/views.py:82 msgid "The requested resource could not be found." msgstr "Impossible de trouver la ressource demandée." -#: awx/main/views.py:85 +#: awx/main/views.py:89 msgid "Server Error" msgstr "Erreur serveur" -#: awx/main/views.py:86 +#: awx/main/views.py:90 msgid "A server error has occurred." msgstr "Une erreur serveur s'est produite." -#: awx/settings/defaults.py:725 +#: awx/settings/defaults.py:683 msgid "US East (Northern Virginia)" msgstr "Est des États-Unis (Virginie du Nord)" -#: awx/settings/defaults.py:726 +#: awx/settings/defaults.py:684 msgid "US East (Ohio)" msgstr "Est des États-Unis (Ohio)" -#: awx/settings/defaults.py:727 +#: awx/settings/defaults.py:685 msgid "US West (Oregon)" msgstr "Ouest des États-Unis (Oregon)" -#: awx/settings/defaults.py:728 +#: awx/settings/defaults.py:686 msgid "US West (Northern California)" msgstr "Ouest des États-Unis (Nord de la Californie)" -#: awx/settings/defaults.py:729 +#: awx/settings/defaults.py:687 msgid "Canada (Central)" msgstr "Canada (Central)" -#: awx/settings/defaults.py:730 +#: awx/settings/defaults.py:688 msgid "EU (Frankfurt)" msgstr "UE (Francfort)" -#: awx/settings/defaults.py:731 +#: awx/settings/defaults.py:689 msgid "EU (Ireland)" msgstr "UE (Irlande)" -#: awx/settings/defaults.py:732 +#: awx/settings/defaults.py:690 msgid "EU (London)" msgstr "UE (Londres)" -#: awx/settings/defaults.py:733 +#: awx/settings/defaults.py:691 msgid "Asia Pacific (Singapore)" msgstr "Asie-Pacifique (Singapour)" -#: awx/settings/defaults.py:734 +#: awx/settings/defaults.py:692 msgid "Asia Pacific (Sydney)" msgstr "Asie-Pacifique (Sydney)" -#: awx/settings/defaults.py:735 +#: awx/settings/defaults.py:693 msgid "Asia Pacific (Tokyo)" msgstr "Asie-Pacifique (Tokyo)" -#: awx/settings/defaults.py:736 +#: awx/settings/defaults.py:694 msgid "Asia Pacific (Seoul)" msgstr "Asie-Pacifique (Séoul)" -#: awx/settings/defaults.py:737 +#: awx/settings/defaults.py:695 msgid "Asia Pacific (Mumbai)" msgstr "Asie-Pacifique (Mumbai)" -#: awx/settings/defaults.py:738 +#: awx/settings/defaults.py:696 msgid "South America (Sao Paulo)" msgstr "Amérique du Sud (Sao Paulo)" -#: awx/settings/defaults.py:739 +#: awx/settings/defaults.py:697 msgid "US West (GovCloud)" msgstr "Ouest des États-Unis (GovCloud)" -#: awx/settings/defaults.py:740 +#: awx/settings/defaults.py:698 msgid "China (Beijing)" msgstr "Chine (Pékin)" -#: awx/settings/defaults.py:789 +#: awx/settings/defaults.py:747 msgid "US East 1 (B)" msgstr "Est des États-Unis 1 (B)" -#: awx/settings/defaults.py:790 +#: awx/settings/defaults.py:748 msgid "US East 1 (C)" msgstr "Est des États-Unis 1 (C)" -#: awx/settings/defaults.py:791 +#: awx/settings/defaults.py:749 msgid "US East 1 (D)" msgstr "Est des États-Unis 1 (D)" -#: awx/settings/defaults.py:792 +#: awx/settings/defaults.py:750 msgid "US East 4 (A)" msgstr "Est des États-Unis 4 (A)" -#: awx/settings/defaults.py:793 +#: awx/settings/defaults.py:751 msgid "US East 4 (B)" msgstr "Est des États-Unis 4 (B)" -#: awx/settings/defaults.py:794 +#: awx/settings/defaults.py:752 msgid "US East 4 (C)" msgstr "Est des États-Unis 4 (C)" -#: awx/settings/defaults.py:795 +#: awx/settings/defaults.py:753 msgid "US Central (A)" msgstr "Centre des États-Unis (A)" -#: awx/settings/defaults.py:796 +#: awx/settings/defaults.py:754 msgid "US Central (B)" msgstr "Centre des États-Unis (B)" -#: awx/settings/defaults.py:797 +#: awx/settings/defaults.py:755 msgid "US Central (C)" msgstr "Centre des États-Unis (C)" -#: awx/settings/defaults.py:798 +#: awx/settings/defaults.py:756 msgid "US Central (F)" msgstr "Centre des États-Unis (F)" -#: awx/settings/defaults.py:799 +#: awx/settings/defaults.py:757 msgid "US West (A)" msgstr "Ouest des États-Unis (A)" -#: awx/settings/defaults.py:800 +#: awx/settings/defaults.py:758 msgid "US West (B)" msgstr "Ouest des États-Unis (B)" -#: awx/settings/defaults.py:801 +#: awx/settings/defaults.py:759 msgid "US West (C)" msgstr "Ouest des États-Unis (C)" -#: awx/settings/defaults.py:802 +#: awx/settings/defaults.py:760 msgid "Europe West 1 (B)" msgstr "Europe de l'Ouest 1 (B)" -#: awx/settings/defaults.py:803 +#: awx/settings/defaults.py:761 msgid "Europe West 1 (C)" msgstr "Europe de l'Ouest 1 (C)" -#: awx/settings/defaults.py:804 +#: awx/settings/defaults.py:762 msgid "Europe West 1 (D)" msgstr "Europe de l'Ouest 1 (D)" -#: awx/settings/defaults.py:805 +#: awx/settings/defaults.py:763 msgid "Europe West 2 (A)" msgstr "Europe de l'Ouest 2 (A)" -#: awx/settings/defaults.py:806 +#: awx/settings/defaults.py:764 msgid "Europe West 2 (B)" msgstr "Europe de l'Ouest 2 (B)" -#: awx/settings/defaults.py:807 +#: awx/settings/defaults.py:765 msgid "Europe West 2 (C)" msgstr "Europe de l'Ouest 2 (C)" -#: awx/settings/defaults.py:808 +#: awx/settings/defaults.py:766 msgid "Asia East (A)" msgstr "Asie de l'Est (A)" -#: awx/settings/defaults.py:809 +#: awx/settings/defaults.py:767 msgid "Asia East (B)" msgstr "Asie de l'Est (B)" -#: awx/settings/defaults.py:810 +#: awx/settings/defaults.py:768 msgid "Asia East (C)" msgstr "Asie de l'Est (C)" -#: awx/settings/defaults.py:811 +#: awx/settings/defaults.py:769 msgid "Asia Southeast (A)" msgstr "Asie du Sud-Est (A)" -#: awx/settings/defaults.py:812 +#: awx/settings/defaults.py:770 msgid "Asia Southeast (B)" msgstr "Asie du Sud-Est (B)" -#: awx/settings/defaults.py:813 +#: awx/settings/defaults.py:771 msgid "Asia Northeast (A)" msgstr "Asie du Nord-Est (A)" -#: awx/settings/defaults.py:814 +#: awx/settings/defaults.py:772 msgid "Asia Northeast (B)" msgstr "Asie du Nord-Est (B)" -#: awx/settings/defaults.py:815 +#: awx/settings/defaults.py:773 msgid "Asia Northeast (C)" msgstr "Asie du Nord-Est (C)" -#: awx/settings/defaults.py:816 +#: awx/settings/defaults.py:774 msgid "Australia Southeast (A)" msgstr "Sud-est de l'Australie (A)" -#: awx/settings/defaults.py:817 +#: awx/settings/defaults.py:775 msgid "Australia Southeast (B)" msgstr "Sud-est de l'Australie (B)" -#: awx/settings/defaults.py:818 +#: awx/settings/defaults.py:776 msgid "Australia Southeast (C)" msgstr "Sud-est de l'Australie (C)" -#: awx/settings/defaults.py:840 +#: awx/settings/defaults.py:798 msgid "US East" msgstr "Est des États-Unis" -#: awx/settings/defaults.py:841 +#: awx/settings/defaults.py:799 msgid "US East 2" msgstr "Est des États-Unis 2" -#: awx/settings/defaults.py:842 +#: awx/settings/defaults.py:800 msgid "US Central" msgstr "Centre des États-Unis" -#: awx/settings/defaults.py:843 +#: awx/settings/defaults.py:801 msgid "US North Central" msgstr "Centre-Nord des États-Unis" -#: awx/settings/defaults.py:844 +#: awx/settings/defaults.py:802 msgid "US South Central" msgstr "Centre-Sud des États-Unis" -#: awx/settings/defaults.py:845 +#: awx/settings/defaults.py:803 msgid "US West Central" msgstr "Centre-Ouest des États-Unis" -#: awx/settings/defaults.py:846 +#: awx/settings/defaults.py:804 msgid "US West" msgstr "Ouest des États-Unis" -#: awx/settings/defaults.py:847 +#: awx/settings/defaults.py:805 msgid "US West 2" msgstr "Ouest des États-Unis 2" -#: awx/settings/defaults.py:848 +#: awx/settings/defaults.py:806 msgid "Canada East" msgstr "Est du Canada" -#: awx/settings/defaults.py:849 +#: awx/settings/defaults.py:807 msgid "Canada Central" msgstr "Centre du Canada" -#: awx/settings/defaults.py:850 +#: awx/settings/defaults.py:808 msgid "Brazil South" msgstr "Sud du Brésil" -#: awx/settings/defaults.py:851 +#: awx/settings/defaults.py:809 msgid "Europe North" msgstr "Europe du Nord" -#: awx/settings/defaults.py:852 +#: awx/settings/defaults.py:810 msgid "Europe West" msgstr "Europe de l'Ouest" -#: awx/settings/defaults.py:853 +#: awx/settings/defaults.py:811 msgid "UK West" msgstr "Ouest du Royaume-Uni" -#: awx/settings/defaults.py:854 +#: awx/settings/defaults.py:812 msgid "UK South" msgstr "Sud du Royaume-Uni" -#: awx/settings/defaults.py:855 +#: awx/settings/defaults.py:813 msgid "Asia East" msgstr "Asie de l'Est" -#: awx/settings/defaults.py:856 +#: awx/settings/defaults.py:814 msgid "Asia Southeast" msgstr "Asie du Sud-Est" -#: awx/settings/defaults.py:857 +#: awx/settings/defaults.py:815 msgid "Australia East" msgstr "Est de l'Australie" -#: awx/settings/defaults.py:858 +#: awx/settings/defaults.py:816 msgid "Australia Southeast" msgstr "Sud-Est de l'Australie" -#: awx/settings/defaults.py:859 +#: awx/settings/defaults.py:817 msgid "India West" msgstr "Ouest de l'Inde" -#: awx/settings/defaults.py:860 +#: awx/settings/defaults.py:818 msgid "India South" msgstr "Sud de l'Inde" -#: awx/settings/defaults.py:861 +#: awx/settings/defaults.py:819 msgid "Japan East" msgstr "Est du Japon" -#: awx/settings/defaults.py:862 +#: awx/settings/defaults.py:820 msgid "Japan West" msgstr "Ouest du Japon" -#: awx/settings/defaults.py:863 +#: awx/settings/defaults.py:821 msgid "Korea Central" msgstr "Centre de la Corée" -#: awx/settings/defaults.py:864 +#: awx/settings/defaults.py:822 msgid "Korea South" msgstr "Sud de la Corée" @@ -4935,246 +5270,192 @@ msgstr "Sud de la Corée" msgid "Single Sign-On" msgstr "Single Sign-On" -#: awx/sso/conf.py:30 +#: awx/sso/conf.py:41 msgid "" -"Mapping to organization admins/users from social auth accounts. This setting\n" -"controls which users are placed into which Tower organizations based on their\n" -"username and email address. Configuration details are available in the Ansible\n" +"Mapping to organization admins/users from social auth accounts. This " +"setting\n" +"controls which users are placed into which Tower organizations based on " +"their\n" +"username and email address. Configuration details are available in the " +"Ansible\n" "Tower documentation." -msgstr "" -"Mappage avec des administrateurs/utilisateurs d'organisation appartenant à des comptes d'authentification sociale. Ce paramètre\n" +msgstr "Mappage avec des administrateurs/utilisateurs d'organisation appartenant à des comptes d'authentification sociale. Ce paramètre\n" "contrôle les utilisateurs placés dans les organisations Tower en fonction de\n" "leur nom d'utilisateur et adresse électronique. Les informations de configuration sont disponibles dans la documentation Ansible." -#: awx/sso/conf.py:55 +#: awx/sso/conf.py:67 msgid "" "Mapping of team members (users) from social auth accounts. Configuration\n" "details are available in Tower documentation." -msgstr "" -"Mappage des membres de l'équipe (utilisateurs) appartenant à des comptes " -"d'authentification sociale. Les informations de configuration sont " -"disponibles dans la documentation Tower." +msgstr "Mappage des membres de l'équipe (utilisateurs) appartenant à des comptes d'authentification sociale. Les informations de configuration sont disponibles dans la documentation Tower." -#: awx/sso/conf.py:80 +#: awx/sso/conf.py:92 msgid "Authentication Backends" msgstr "Backends d'authentification" -#: awx/sso/conf.py:81 +#: awx/sso/conf.py:93 msgid "" "List of authentication backends that are enabled based on license features " "and other authentication settings." -msgstr "" -"Liste des backends d'authentification activés en fonction des " -"caractéristiques des licences et d'autres paramètres d'authentification." +msgstr "Liste des backends d'authentification activés en fonction des caractéristiques des licences et d'autres paramètres d'authentification." -#: awx/sso/conf.py:94 +#: awx/sso/conf.py:106 msgid "Social Auth Organization Map" msgstr "Authentification sociale - Mappage des organisations" -#: awx/sso/conf.py:106 +#: awx/sso/conf.py:118 msgid "Social Auth Team Map" msgstr "Authentification sociale - Mappage des équipes" -#: awx/sso/conf.py:118 +#: awx/sso/conf.py:130 msgid "Social Auth User Fields" msgstr "Authentification sociale - Champs d'utilisateurs" -#: awx/sso/conf.py:119 +#: awx/sso/conf.py:131 msgid "" -"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." -msgstr "" -"Lorsqu'il est défini sur une liste vide `[]`, ce paramètre empêche la " -"création de nouveaux comptes d'utilisateur. Seuls les utilisateurs ayant " -"déjà ouvert une session au moyen de l'authentification sociale ou disposant " -"d'un compte utilisateur avec une adresse électronique correspondante " -"pourront se connecter." +"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." +msgstr "Lorsqu'il est défini sur une liste vide `[]`, ce paramètre empêche la création de nouveaux comptes d'utilisateur. Seuls les utilisateurs ayant déjà ouvert une session au moyen de l'authentification sociale ou disposant d'un compte utilisateur avec une adresse électronique correspondante pourront se connecter." -#: awx/sso/conf.py:141 +#: awx/sso/conf.py:153 msgid "LDAP Server URI" msgstr "URI du serveur LDAP" -#: awx/sso/conf.py:142 +#: awx/sso/conf.py:154 msgid "" "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 " +"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." -msgstr "" -"URI de connexion au serveur LDAP, tel que \"ldap://ldap.exemple.com:389\" " -"(non SSL) ou \"ldaps://ldap.exemple.com:636\" (SSL). Plusieurs serveurs LDAP" -" peuvent être définis en les séparant par des espaces ou des virgules. " -"L'authentification LDAP est désactivée si ce paramètre est vide." - -#: awx/sso/conf.py:146 awx/sso/conf.py:162 awx/sso/conf.py:174 -#: awx/sso/conf.py:186 awx/sso/conf.py:202 awx/sso/conf.py:222 -#: awx/sso/conf.py:244 awx/sso/conf.py:259 awx/sso/conf.py:277 -#: awx/sso/conf.py:294 awx/sso/conf.py:306 awx/sso/conf.py:332 -#: awx/sso/conf.py:348 awx/sso/conf.py:362 awx/sso/conf.py:380 -#: awx/sso/conf.py:406 +msgstr "URI de connexion au serveur LDAP, tel que \"ldap://ldap.exemple.com:389\" (non SSL) ou \"ldaps://ldap.exemple.com:636\" (SSL). Plusieurs serveurs LDAP peuvent être définis en les séparant par des espaces ou des virgules. L'authentification LDAP est désactivée si ce paramètre est vide." + +#: awx/sso/conf.py:158 awx/sso/conf.py:173 awx/sso/conf.py:184 +#: awx/sso/conf.py:195 awx/sso/conf.py:210 awx/sso/conf.py:229 +#: awx/sso/conf.py:250 awx/sso/conf.py:264 awx/sso/conf.py:281 +#: awx/sso/conf.py:297 awx/sso/conf.py:308 awx/sso/conf.py:333 +#: awx/sso/conf.py:348 awx/sso/conf.py:361 awx/sso/conf.py:378 +#: awx/sso/conf.py:404 msgid "LDAP" msgstr "LDAP" -#: awx/sso/conf.py:158 +#: awx/sso/conf.py:169 msgid "LDAP Bind DN" msgstr "ND de la liaison LDAP" -#: awx/sso/conf.py:159 +#: awx/sso/conf.py:170 msgid "" "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 Ansible Tower documentation for example syntax." -msgstr "" -"ND (nom distinctif) de l'utilisateur à lier pour toutes les requêtes de " -"recherche. Il s'agit du compte utilisateur système que nous utiliserons pour" -" nous connecter afin d'interroger LDAP et obtenir d'autres informations " -"utilisateur. Voir la documentation Ansible Tower pour obtenir des exemples " -"de syntaxe." +msgstr "ND (nom distinctif) de l'utilisateur à lier pour toutes les requêtes de recherche. Il s'agit du compte utilisateur système que nous utiliserons pour nous connecter afin d'interroger LDAP et obtenir d'autres informations utilisateur. Voir la documentation Ansible Tower pour obtenir des exemples de syntaxe." -#: awx/sso/conf.py:172 +#: awx/sso/conf.py:182 msgid "LDAP Bind Password" msgstr "Mot de passe de la liaison LDAP" -#: awx/sso/conf.py:173 +#: awx/sso/conf.py:183 msgid "Password used to bind LDAP user account." msgstr "Mot de passe utilisé pour lier le compte utilisateur LDAP." -#: awx/sso/conf.py:184 +#: awx/sso/conf.py:193 msgid "LDAP Start TLS" msgstr "LDAP - Lancer TLS" -#: awx/sso/conf.py:185 +#: awx/sso/conf.py:194 msgid "Whether to enable TLS when the LDAP connection is not using SSL." msgstr "Pour activer ou non TLS lorsque la connexion LDAP n'utilise pas SSL." -#: awx/sso/conf.py:195 +#: awx/sso/conf.py:203 msgid "LDAP Connection Options" msgstr "Options de connexion à LDAP" -#: awx/sso/conf.py:196 +#: awx/sso/conf.py:204 msgid "" "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." -msgstr "" -"Options supplémentaires à définir pour la connexion LDAP. Les références " -"LDAP sont désactivées par défaut (pour empêcher certaines requêtes LDAP de " -"se bloquer avec AD). Les noms d'options doivent être des chaînes (par " -"exemple \"OPT_REFERRALS\"). Reportez-vous à https://www.python-" -"ldap.org/doc/html/ldap.html#options afin de connaître les options possibles " -"et les valeurs que vous pouvez définir." - -#: awx/sso/conf.py:215 +"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." +msgstr "Options supplémentaires à définir pour la connexion LDAP. Les références LDAP sont désactivées par défaut (pour empêcher certaines requêtes LDAP de se bloquer avec AD). Les noms d'options doivent être des chaînes (par exemple \"OPT_REFERRALS\"). Reportez-vous à https://www.python-ldap.org/doc/html/ldap.html#options afin de connaître les options possibles et les valeurs que vous pouvez définir." + +#: awx/sso/conf.py:222 msgid "LDAP User Search" msgstr "Recherche d'utilisateurs LDAP" -#: awx/sso/conf.py:216 +#: awx/sso/conf.py:223 msgid "" "LDAP search query to find users. Any user that matches the given pattern " -"will be able to login to Tower. The user should also be mapped into a Tower" -" organization (as defined in the AUTH_LDAP_ORGANIZATION_MAP setting). If " +"will be able to login to Tower. The user should also be mapped into a Tower " +"organization (as defined in the AUTH_LDAP_ORGANIZATION_MAP setting). If " "multiple search queries need to be supported use of \"LDAPUnion\" is " "possible. See Tower documentation for details." -msgstr "" -"Requête de recherche LDAP servant à retrouver des utilisateurs. Tout " -"utilisateur qui correspond au modèle donné pourra se connecter à Tower. " -"L'utilisateur doit également être mappé dans une organisation Tower (tel que" -" défini dans le paramètre AUTH_LDAP_ORGANIZATION_MAP). Si plusieurs requêtes" -" de recherche doivent être prises en charge, l'utilisation de \"LDAPUnion\" " -"est possible. Se reporter à la documentation Tower pour plus d'informations." - -#: awx/sso/conf.py:238 +msgstr "Requête de recherche LDAP servant à retrouver des utilisateurs. Tout utilisateur qui correspond au modèle donné pourra se connecter à Tower. L'utilisateur doit également être mappé dans une organisation Tower (tel que défini dans le paramètre AUTH_LDAP_ORGANIZATION_MAP). Si plusieurs requêtes de recherche doivent être prises en charge, l'utilisation de \"LDAPUnion\" est possible. Se reporter à la documentation Tower pour plus d'informations." + +#: awx/sso/conf.py:244 msgid "LDAP User DN Template" msgstr "Modèle de ND pour les utilisateurs LDAP" -#: awx/sso/conf.py:239 +#: awx/sso/conf.py:245 msgid "" "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." -msgstr "" -"Autre méthode de recherche d'utilisateurs, si les ND d'utilisateur se " -"présentent tous au même format. Cette approche est plus efficace qu'une " -"recherche d'utilisateurs si vous pouvez l'utiliser dans votre environnement " -"organisationnel. Si ce paramètre est défini, sa valeur sera utilisée à la " -"place de AUTH_LDAP_USER_SEARCH." +msgstr "Autre méthode de recherche d'utilisateurs, si les ND d'utilisateur se présentent tous au même format. Cette approche est plus efficace qu'une recherche d'utilisateurs si vous pouvez l'utiliser dans votre environnement organisationnel. Si ce paramètre est défini, sa valeur sera utilisée à la place de AUTH_LDAP_USER_SEARCH." -#: awx/sso/conf.py:254 +#: awx/sso/conf.py:259 msgid "LDAP User Attribute Map" msgstr "Mappe des attributs d'utilisateurs LDAP" -#: awx/sso/conf.py:255 +#: awx/sso/conf.py:260 msgid "" "Mapping of LDAP user schema to Tower 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 Ansible Tower " "documentation for additional details." -msgstr "" -"Mappage du schéma utilisateur LDAP avec les attributs utilisateur d'API " -"Tower. Le paramètre par défaut est valide pour ActiveDirectory, mais les " -"utilisateurs ayant d'autres configurations LDAP peuvent être amenés à " -"modifier les valeurs. Voir la documentation Ansible Tower pour obtenir des " -"détails supplémentaires." +msgstr "Mappage du schéma utilisateur LDAP avec les attributs utilisateur d'API Tower. Le paramètre par défaut est valide pour ActiveDirectory, mais les utilisateurs ayant d'autres configurations LDAP peuvent être amenés à modifier les valeurs. Voir la documentation Ansible Tower pour obtenir des détails supplémentaires." -#: awx/sso/conf.py:273 +#: awx/sso/conf.py:277 msgid "LDAP Group Search" msgstr "Recherche de groupes LDAP" -#: awx/sso/conf.py:274 +#: awx/sso/conf.py:278 msgid "" "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." -msgstr "" -"Les utilisateurs de Tower sont mappés à des organisations en fonction de " -"leur appartenance à des groupes LDAP. Ce paramètre définit la requête de " -"recherche LDAP servant à rechercher des groupes. Notez que cette méthode, " -"contrairement à la recherche d'utilisateurs LDAP, la recherche des groupes " -"ne prend pas en charge LDAPSearchUnion." +msgstr "Les utilisateurs de Tower sont mappés à des organisations en fonction de leur appartenance à des groupes LDAP. Ce paramètre définit la requête de recherche LDAP servant à rechercher des groupes. Notez que cette méthode, contrairement à la recherche d'utilisateurs LDAP, la recherche des groupes ne prend pas en charge LDAPSearchUnion." -#: awx/sso/conf.py:290 +#: awx/sso/conf.py:293 msgid "LDAP Group Type" msgstr "Type de groupe LDAP" -#: awx/sso/conf.py:291 +#: awx/sso/conf.py:294 msgid "" -"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" -msgstr "" -"Il convient parfois de modifier le type de groupe en fonction du type de " -"serveur LDAP. Les valeurs sont répertoriées à l'adresse suivante : https" -"://django-auth-ldap.readthedocs.io/en/stable/groups.html#types-of-groups" +"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" +msgstr "Il convient parfois de modifier le type de groupe en fonction du type de serveur LDAP. Les valeurs sont répertoriées à l'adresse suivante : https://django-auth-ldap.readthedocs.io/en/stable/groups.html#types-of-groups" -#: awx/sso/conf.py:304 +#: awx/sso/conf.py:306 msgid "LDAP Group Type Parameters" msgstr "Paramètres de types de groupes LDAP" -#: awx/sso/conf.py:305 +#: awx/sso/conf.py:307 msgid "Key value parameters to send the chosen group type init method." -msgstr "" -"Paramètres de valeurs-clés pour envoyer la méthode init de type de groupe " -"sélectionné." +msgstr "Paramètres de valeurs-clés pour envoyer la méthode init de type de groupe sélectionné." -#: awx/sso/conf.py:327 +#: awx/sso/conf.py:328 msgid "LDAP Require Group" msgstr "Groupe LDAP obligatoire" -#: awx/sso/conf.py:328 +#: awx/sso/conf.py:329 msgid "" "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 via Tower. Only one require group is supported." -msgstr "" -"Le ND du groupe d'utilisateurs qui doit se connecter. S'il est spécifié, " -"l'utilisateur doit être membre de ce groupe pour pouvoir se connecter via " -"LDAP. S'il n'est pas défini, tout utilisateur LDAP qui correspond à la " -"recherche d'utilisateurs pourra se connecter via Tower. Un seul groupe est " -"pris en charge." +msgstr "Le ND du groupe d'utilisateurs qui doit se connecter. S'il est spécifié, l'utilisateur doit être membre de ce groupe pour pouvoir se connecter via LDAP. S'il n'est pas défini, tout utilisateur LDAP qui correspond à la recherche d'utilisateurs pourra se connecter via Tower. Un seul groupe est pris en charge." #: awx/sso/conf.py:344 msgid "LDAP Deny Group" @@ -5184,699 +5465,574 @@ msgstr "Groupe LDAP refusé" msgid "" "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." -msgstr "" -"ND du groupe dont la connexion est refusée. S'il est spécifié, l'utilisateur" -" n'est pas autorisé à se connecter s'il est membre de ce groupe. Un seul " -"groupe refusé est pris en charge." +msgstr "ND du groupe dont la connexion est refusée. S'il est spécifié, l'utilisateur n'est pas autorisé à se connecter s'il est membre de ce groupe. Un seul groupe refusé est pris en charge." -#: awx/sso/conf.py:358 +#: awx/sso/conf.py:357 msgid "LDAP User Flags By Group" msgstr "Marqueurs d'utilisateur LDAP par groupe" -#: awx/sso/conf.py:359 +#: awx/sso/conf.py:358 msgid "" "Retrieve users from a given group. At this time, superuser and system " "auditors are the only groups supported. Refer to the Ansible Tower " "documentation for more detail." -msgstr "" -"Extraire les utilisateurs d'un groupe donné. Actuellement, le " -"superutilisateur et les auditeurs de systèmes sont les seuls groupes pris en" -" charge. Voir la documentation Ansible Tower pour obtenir plus " -"d'informations." +msgstr "Extraire les utilisateurs d'un groupe donné. Actuellement, le superutilisateur et les auditeurs de systèmes sont les seuls groupes pris en charge. Voir la documentation Ansible Tower pour obtenir plus d'informations." -#: awx/sso/conf.py:375 +#: awx/sso/conf.py:373 msgid "LDAP Organization Map" msgstr "Mappe d'organisations LDAP" -#: awx/sso/conf.py:376 +#: awx/sso/conf.py:374 msgid "" "Mapping between organization admins/users and LDAP groups. This controls " -"which users are placed into which Tower organizations relative to their LDAP" -" group memberships. Configuration details are available in the Ansible Tower" -" documentation." -msgstr "" -"Mappage entre les administrateurs/utilisateurs de l'organisation et les " -"groupes LDAP. Ce paramètre détermine les utilisateurs qui sont placés dans " -"les organisations Tower par rapport à leurs appartenances à un groupe LDAP." -" Les informations de configuration sont disponibles dans la documentation " -"Ansible Tower." +"which users are placed into which Tower organizations relative to their LDAP " +"group memberships. Configuration details are available in the Ansible Tower " +"documentation." +msgstr "Mappage entre les administrateurs/utilisateurs de l'organisation et les groupes LDAP. Ce paramètre détermine les utilisateurs qui sont placés dans les organisations Tower par rapport à leurs appartenances à un groupe LDAP. Les informations de configuration sont disponibles dans la documentation Ansible Tower." -#: awx/sso/conf.py:403 +#: awx/sso/conf.py:401 msgid "LDAP Team Map" msgstr "Mappe d'équipes LDAP" -#: awx/sso/conf.py:404 +#: awx/sso/conf.py:402 msgid "" "Mapping between team members (users) and LDAP groups. Configuration details " "are available in the Ansible Tower documentation." -msgstr "" -"Mappage entre des membres de l'équipe (utilisateurs) et des groupes LDAP. Les informations\n" +msgstr "Mappage entre des membres de l'équipe (utilisateurs) et des groupes LDAP. Les informations\n" " de configuration sont disponibles dans la documentation Ansible Tower." -#: awx/sso/conf.py:440 +#: awx/sso/conf.py:437 msgid "RADIUS Server" msgstr "Serveur RADIUS" -#: awx/sso/conf.py:441 +#: awx/sso/conf.py:438 msgid "" "Hostname/IP of RADIUS server. RADIUS authentication is disabled if this " "setting is empty." -msgstr "" -"Nom d'hôte/IP du serveur RADIUS. L'authentification RADIUS est désactivée si" -" ce paramètre est vide." +msgstr "Nom d'hôte/IP du serveur RADIUS. L'authentification RADIUS est désactivée si ce paramètre est vide." -#: awx/sso/conf.py:443 awx/sso/conf.py:457 awx/sso/conf.py:469 +#: awx/sso/conf.py:440 awx/sso/conf.py:453 awx/sso/conf.py:464 #: awx/sso/models.py:14 msgid "RADIUS" msgstr "RADIUS" -#: awx/sso/conf.py:455 +#: awx/sso/conf.py:451 msgid "RADIUS Port" msgstr "Port RADIUS" -#: awx/sso/conf.py:456 +#: awx/sso/conf.py:452 msgid "Port of RADIUS server." msgstr "Port du serveur RADIUS." -#: awx/sso/conf.py:467 +#: awx/sso/conf.py:462 msgid "RADIUS Secret" msgstr "Secret RADIUS" -#: awx/sso/conf.py:468 +#: awx/sso/conf.py:463 msgid "Shared secret for authenticating to RADIUS server." msgstr "Secret partagé pour l'authentification sur le serveur RADIUS." -#: awx/sso/conf.py:484 +#: awx/sso/conf.py:478 msgid "TACACS+ Server" msgstr "Serveur TACACS+" -#: awx/sso/conf.py:485 +#: awx/sso/conf.py:479 msgid "Hostname of TACACS+ server." msgstr "Nom d'hôte du serveur TACACS+" -#: awx/sso/conf.py:486 awx/sso/conf.py:499 awx/sso/conf.py:512 -#: awx/sso/conf.py:525 awx/sso/conf.py:537 awx/sso/models.py:15 +#: awx/sso/conf.py:480 awx/sso/conf.py:492 awx/sso/conf.py:504 +#: awx/sso/conf.py:516 awx/sso/conf.py:527 awx/sso/models.py:15 msgid "TACACS+" msgstr "TACACS+" -#: awx/sso/conf.py:497 +#: awx/sso/conf.py:490 msgid "TACACS+ Port" msgstr "Port TACACS+" -#: awx/sso/conf.py:498 +#: awx/sso/conf.py:491 msgid "Port number of TACACS+ server." msgstr "Numéro de port du serveur TACACS+" -#: awx/sso/conf.py:510 +#: awx/sso/conf.py:502 msgid "TACACS+ Secret" msgstr "Secret TACACS+" -#: awx/sso/conf.py:511 +#: awx/sso/conf.py:503 msgid "Shared secret for authenticating to TACACS+ server." msgstr "Secret partagé pour l'authentification sur le serveur TACACS+." -#: awx/sso/conf.py:523 +#: awx/sso/conf.py:514 msgid "TACACS+ Auth Session Timeout" msgstr "Expiration du délai d'attente d'autorisation de la session TACACS+." -#: awx/sso/conf.py:524 +#: awx/sso/conf.py:515 msgid "TACACS+ session timeout value in seconds, 0 disables timeout." -msgstr "" -"Valeur du délai d'attente de la session TACACS+ en secondes, 0 désactive le " -"délai d'attente." +msgstr "Valeur du délai d'attente de la session TACACS+ en secondes, 0 désactive le délai d'attente." -#: awx/sso/conf.py:535 +#: awx/sso/conf.py:525 msgid "TACACS+ Authentication Protocol" msgstr "Protocole d'autorisation TACACS+" -#: awx/sso/conf.py:536 +#: awx/sso/conf.py:526 msgid "Choose the authentication protocol used by TACACS+ client." -msgstr "" -"Choisissez le protocole d'authentification utilisé par le client TACACS+." +msgstr "Choisissez le protocole d'authentification utilisé par le client TACACS+." -#: awx/sso/conf.py:551 +#: awx/sso/conf.py:540 msgid "Google OAuth2 Callback URL" msgstr "URL de rappel OAuth2 pour Google" -#: awx/sso/conf.py:552 awx/sso/conf.py:645 awx/sso/conf.py:710 +#: awx/sso/conf.py:541 awx/sso/conf.py:634 awx/sso/conf.py:699 msgid "" "Provide this URL as the callback URL for your application as part of your " "registration process. Refer to the Ansible Tower documentation for more " "detail." -msgstr "" -"Fournir cette URL comme URL d'appel pour votre application dans le cadre de " -"votre processus d'enregistrement. Voir la documentation Ansible Tower pour " -"obtenir plus d'informations." +msgstr "Fournir cette URL comme URL d'appel pour votre application dans le cadre de votre processus d'enregistrement. Voir la documentation Ansible Tower pour obtenir plus d'informations." -#: awx/sso/conf.py:555 awx/sso/conf.py:567 awx/sso/conf.py:579 -#: awx/sso/conf.py:592 awx/sso/conf.py:606 awx/sso/conf.py:618 -#: awx/sso/conf.py:630 +#: awx/sso/conf.py:544 awx/sso/conf.py:556 awx/sso/conf.py:568 +#: awx/sso/conf.py:581 awx/sso/conf.py:595 awx/sso/conf.py:607 +#: awx/sso/conf.py:619 msgid "Google OAuth2" msgstr "OAuth2 pour Google" -#: awx/sso/conf.py:565 +#: awx/sso/conf.py:554 msgid "Google OAuth2 Key" msgstr "Clé OAuth2 pour Google" -#: awx/sso/conf.py:566 +#: awx/sso/conf.py:555 msgid "The OAuth2 key from your web application." msgstr "Clé OAuth2 de votre application Web." -#: awx/sso/conf.py:577 +#: awx/sso/conf.py:566 msgid "Google OAuth2 Secret" msgstr "Secret OAuth2 pour Google" -#: awx/sso/conf.py:578 +#: awx/sso/conf.py:567 msgid "The OAuth2 secret from your web application." msgstr "Secret OAuth2 de votre application Web." -#: awx/sso/conf.py:589 +#: awx/sso/conf.py:578 msgid "Google OAuth2 Whitelisted Domains" msgstr "Domaines sur liste blanche OAuth2 pour Google" -#: awx/sso/conf.py:590 +#: awx/sso/conf.py:579 msgid "" "Update this setting to restrict the domains who are allowed to login using " "Google OAuth2." -msgstr "" -"Mettez à jour ce paramètre pour limiter les domaines qui sont autorisés à se" -" connecter à l'aide de l'authentification OAuth2 avec un compte Google." +msgstr "Mettez à jour ce paramètre pour limiter les domaines qui sont autorisés à se connecter à l'aide de l'authentification OAuth2 avec un compte Google." -#: awx/sso/conf.py:601 +#: awx/sso/conf.py:590 msgid "Google OAuth2 Extra Arguments" msgstr "Arguments OAuth2 supplémentaires pour Google" -#: awx/sso/conf.py:602 +#: awx/sso/conf.py:591 msgid "" -"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 " +"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 Ansible Tower documentation for more detail." -msgstr "" -"Arguments supplémentaires pour l'authentification OAuth2. Vous pouvez " -"autoriser un seul domaine à s'authentifier, même si l'utilisateur est " -"connecté avec plusieurs comptes Google. Voir la documentation Ansible Tower " -"pour obtenir plus d'informations." +msgstr "Arguments supplémentaires pour l'authentification OAuth2. Vous pouvez autoriser un seul domaine à s'authentifier, même si l'utilisateur est connecté avec plusieurs comptes Google. Voir la documentation Ansible Tower pour obtenir plus d'informations." -#: awx/sso/conf.py:616 +#: awx/sso/conf.py:605 msgid "Google OAuth2 Organization Map" msgstr "Mappe d'organisations OAuth2 pour Google" -#: awx/sso/conf.py:628 +#: awx/sso/conf.py:617 msgid "Google OAuth2 Team Map" msgstr "Mappe d'équipes OAuth2 pour Google" -#: awx/sso/conf.py:644 +#: awx/sso/conf.py:633 msgid "GitHub OAuth2 Callback URL" msgstr "URL de rappel OAuth2 pour GitHub" -#: awx/sso/conf.py:648 awx/sso/conf.py:660 awx/sso/conf.py:671 -#: awx/sso/conf.py:683 awx/sso/conf.py:695 +#: awx/sso/conf.py:637 awx/sso/conf.py:649 awx/sso/conf.py:660 +#: awx/sso/conf.py:672 awx/sso/conf.py:684 msgid "GitHub OAuth2" msgstr "OAuth2 pour GitHub" -#: awx/sso/conf.py:658 +#: awx/sso/conf.py:647 msgid "GitHub OAuth2 Key" msgstr "Clé OAuth2 pour GitHub" -#: awx/sso/conf.py:659 +#: awx/sso/conf.py:648 msgid "The OAuth2 key (Client ID) from your GitHub developer application." msgstr "Clé OAuth2 (ID client) de votre application de développeur GitHub." -#: awx/sso/conf.py:669 +#: awx/sso/conf.py:658 msgid "GitHub OAuth2 Secret" msgstr "Secret OAuth2 pour GitHub" -#: awx/sso/conf.py:670 +#: awx/sso/conf.py:659 msgid "" "The OAuth2 secret (Client Secret) from your GitHub developer application." -msgstr "" -"Secret OAuth2 (secret client) de votre application de développeur GitHub." +msgstr "Secret OAuth2 (secret client) de votre application de développeur GitHub." -#: awx/sso/conf.py:681 +#: awx/sso/conf.py:670 msgid "GitHub OAuth2 Organization Map" msgstr "Mappe d'organisations OAuth2 pour GitHub" -#: awx/sso/conf.py:693 +#: awx/sso/conf.py:682 msgid "GitHub OAuth2 Team Map" msgstr "Mappe d'équipes OAuth2 pour GitHub" -#: awx/sso/conf.py:709 +#: awx/sso/conf.py:698 msgid "GitHub Organization OAuth2 Callback URL" msgstr "URL de rappel OAuth2 pour les organisations GitHub" -#: awx/sso/conf.py:713 awx/sso/conf.py:725 awx/sso/conf.py:736 -#: awx/sso/conf.py:749 awx/sso/conf.py:760 awx/sso/conf.py:772 +#: awx/sso/conf.py:702 awx/sso/conf.py:714 awx/sso/conf.py:725 +#: awx/sso/conf.py:738 awx/sso/conf.py:749 awx/sso/conf.py:761 msgid "GitHub Organization OAuth2" msgstr "OAuth2 pour les organisations GitHub" -#: awx/sso/conf.py:723 +#: awx/sso/conf.py:712 msgid "GitHub Organization OAuth2 Key" msgstr "Clé OAuth2 pour les organisations GitHub" -#: awx/sso/conf.py:724 awx/sso/conf.py:802 +#: awx/sso/conf.py:713 awx/sso/conf.py:791 msgid "The OAuth2 key (Client ID) from your GitHub organization application." msgstr "Clé OAuth2 (ID client) de votre application d'organisation GitHub." -#: awx/sso/conf.py:734 +#: awx/sso/conf.py:723 msgid "GitHub Organization OAuth2 Secret" msgstr "Secret OAuth2 pour les organisations GitHub" -#: awx/sso/conf.py:735 awx/sso/conf.py:813 +#: awx/sso/conf.py:724 awx/sso/conf.py:802 msgid "" "The OAuth2 secret (Client Secret) from your GitHub organization application." -msgstr "" -"Secret OAuth2 (secret client) de votre application d'organisation GitHub." +msgstr "Secret OAuth2 (secret client) de votre application d'organisation GitHub." -#: awx/sso/conf.py:746 +#: awx/sso/conf.py:735 msgid "GitHub Organization Name" msgstr "Nom de l'organisation GitHub" -#: awx/sso/conf.py:747 +#: awx/sso/conf.py:736 msgid "" "The name of your GitHub organization, as used in your organization's URL: " "https://github.com//." -msgstr "" -"Nom de votre organisation GitHub, tel qu'utilisé dans l'URL de votre " -"organisation : https://github.com//." +msgstr "Nom de votre organisation GitHub, tel qu'utilisé dans l'URL de votre organisation : https://github.com//." -#: awx/sso/conf.py:758 +#: awx/sso/conf.py:747 msgid "GitHub Organization OAuth2 Organization Map" msgstr "Mappe d'organisations OAuth2 pour les organisations GitHub" -#: awx/sso/conf.py:770 +#: awx/sso/conf.py:759 msgid "GitHub Organization OAuth2 Team Map" msgstr "Mappe d'équipes OAuth2 pour les organisations GitHub" -#: awx/sso/conf.py:786 +#: awx/sso/conf.py:775 msgid "GitHub Team OAuth2 Callback URL" msgstr "URL de rappel OAuth2 pour les équipes GitHub" -#: awx/sso/conf.py:787 +#: awx/sso/conf.py:776 msgid "" -"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." -msgstr "" -"Créez une application appartenant à une organisation sur " -"https://github.com/organizations//settings/applications et obtenez" -" une clé OAuth2 (ID client) et un secret (secret client). Entrez cette URL " -"comme URL de rappel de votre application." +"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." +msgstr "Créez une application appartenant à une organisation sur https://github.com/organizations//settings/applications et obtenez une clé OAuth2 (ID client) et un secret (secret client). Entrez cette URL comme URL de rappel de votre application." -#: awx/sso/conf.py:791 awx/sso/conf.py:803 awx/sso/conf.py:814 -#: awx/sso/conf.py:827 awx/sso/conf.py:838 awx/sso/conf.py:850 +#: awx/sso/conf.py:780 awx/sso/conf.py:792 awx/sso/conf.py:803 +#: awx/sso/conf.py:816 awx/sso/conf.py:827 awx/sso/conf.py:839 msgid "GitHub Team OAuth2" msgstr "OAuth2 pour les équipes GitHub" -#: awx/sso/conf.py:801 +#: awx/sso/conf.py:790 msgid "GitHub Team OAuth2 Key" msgstr "Clé OAuth2 pour les équipes GitHub" -#: awx/sso/conf.py:812 +#: awx/sso/conf.py:801 msgid "GitHub Team OAuth2 Secret" msgstr "Secret OAuth2 pour les équipes GitHub" -#: awx/sso/conf.py:824 +#: awx/sso/conf.py:813 msgid "GitHub Team ID" msgstr "ID d'équipe GitHub" -#: awx/sso/conf.py:825 +#: awx/sso/conf.py:814 msgid "" -"Find the numeric team ID using the Github API: http://fabian-" -"kostadinov.github.io/2015/01/16/how-to-find-a-github-team-id/." -msgstr "" -"Recherchez votre ID d'équipe numérique à l'aide de l'API Github : http" -"://fabian-kostadinov.github.io/2015/01/16/how-to-find-a-github-team-id/." +"Find the numeric team ID using the Github API: http://fabian-kostadinov." +"github.io/2015/01/16/how-to-find-a-github-team-id/." +msgstr "Recherchez votre ID d'équipe numérique à l'aide de l'API Github : http://fabian-kostadinov.github.io/2015/01/16/how-to-find-a-github-team-id/." -#: awx/sso/conf.py:836 +#: awx/sso/conf.py:825 msgid "GitHub Team OAuth2 Organization Map" msgstr "Mappe d'organisations OAuth2 pour les équipes GitHub" -#: awx/sso/conf.py:848 +#: awx/sso/conf.py:837 msgid "GitHub Team OAuth2 Team Map" msgstr "Mappe d'équipes OAuth2 pour les équipes GitHub" -#: awx/sso/conf.py:864 +#: awx/sso/conf.py:853 msgid "Azure AD OAuth2 Callback URL" msgstr "URL de rappel OAuth2 pour Azure AD" -#: awx/sso/conf.py:865 +#: awx/sso/conf.py:854 msgid "" "Provide this URL as the callback URL for your application as part of your " "registration process. Refer to the Ansible Tower documentation for more " "detail. " -msgstr "" -"Fournir cette URL comme URL d'appel pour votre application dans le cadre de " -"votre processus d'enregistrement. Voir la documentation Ansible Tower pour " -"plus de détails." +msgstr "Fournir cette URL comme URL d'appel pour votre application dans le cadre de votre processus d'enregistrement. Voir la documentation Ansible Tower pour plus de détails." -#: awx/sso/conf.py:868 awx/sso/conf.py:880 awx/sso/conf.py:891 -#: awx/sso/conf.py:903 awx/sso/conf.py:915 +#: awx/sso/conf.py:857 awx/sso/conf.py:869 awx/sso/conf.py:880 +#: awx/sso/conf.py:892 awx/sso/conf.py:904 msgid "Azure AD OAuth2" msgstr "OAuth2 pour Azure AD" -#: awx/sso/conf.py:878 +#: awx/sso/conf.py:867 msgid "Azure AD OAuth2 Key" msgstr "Clé OAuth2 pour Azure AD" -#: awx/sso/conf.py:879 +#: awx/sso/conf.py:868 msgid "The OAuth2 key (Client ID) from your Azure AD application." msgstr "Clé OAuth2 (ID client) de votre application Azure AD." -#: awx/sso/conf.py:889 +#: awx/sso/conf.py:878 msgid "Azure AD OAuth2 Secret" msgstr "Secret OAuth2 pour Azure AD" -#: awx/sso/conf.py:890 +#: awx/sso/conf.py:879 msgid "The OAuth2 secret (Client Secret) from your Azure AD application." msgstr "Secret OAuth2 (secret client) de votre application Azure AD." -#: awx/sso/conf.py:901 +#: awx/sso/conf.py:890 msgid "Azure AD OAuth2 Organization Map" msgstr "Mappe d'organisations OAuth2 pour Azure AD" -#: awx/sso/conf.py:913 +#: awx/sso/conf.py:902 msgid "Azure AD OAuth2 Team Map" msgstr "Mappe d'équipes OAuth2 pour Azure AD" -#: awx/sso/conf.py:938 +#: awx/sso/conf.py:927 msgid "SAML Assertion Consumer Service (ACS) URL" msgstr "URL ACS (Assertion Consumer Service) SAML" -#: awx/sso/conf.py:939 +#: awx/sso/conf.py:928 msgid "" "Register Tower 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." -msgstr "" -"Enregistrez Tower en tant que fournisseur de services (SP) auprès de chaque " -"fournisseur d'identité (IdP) configuré. Entrez votre ID d'entité SP et cette" -" URL ACS pour votre application." - -#: awx/sso/conf.py:942 awx/sso/conf.py:956 awx/sso/conf.py:970 -#: awx/sso/conf.py:985 awx/sso/conf.py:999 awx/sso/conf.py:1012 -#: awx/sso/conf.py:1033 awx/sso/conf.py:1051 awx/sso/conf.py:1070 -#: awx/sso/conf.py:1106 awx/sso/conf.py:1138 awx/sso/conf.py:1152 -#: awx/sso/conf.py:1169 awx/sso/conf.py:1182 awx/sso/conf.py:1195 -#: awx/sso/conf.py:1213 awx/sso/models.py:16 +msgstr "Enregistrez Tower en tant que fournisseur de services (SP) auprès de chaque fournisseur d'identité (IdP) configuré. Entrez votre ID d'entité SP et cette URL ACS pour votre application." + +#: awx/sso/conf.py:931 awx/sso/conf.py:944 awx/sso/conf.py:957 +#: awx/sso/conf.py:971 awx/sso/conf.py:984 awx/sso/conf.py:996 +#: awx/sso/conf.py:1016 awx/sso/conf.py:1033 awx/sso/conf.py:1051 +#: awx/sso/conf.py:1086 awx/sso/conf.py:1117 awx/sso/conf.py:1130 +#: awx/sso/conf.py:1146 awx/sso/conf.py:1158 awx/sso/conf.py:1170 +#: awx/sso/conf.py:1189 awx/sso/models.py:16 msgid "SAML" msgstr "SAML" -#: awx/sso/conf.py:953 +#: awx/sso/conf.py:941 msgid "SAML Service Provider Metadata URL" msgstr "URL de métadonnées du fournisseur de services SAML" -#: awx/sso/conf.py:954 +#: awx/sso/conf.py:942 msgid "" "If your identity provider (IdP) allows uploading an XML metadata file, you " "can download one from this URL." -msgstr "" -"Si votre fournisseur d'identité (IdP) permet de télécharger un fichier de " -"métadonnées XML, vous pouvez le faire à partir de cette URL." +msgstr "Si votre fournisseur d'identité (IdP) permet de télécharger un fichier de métadonnées XML, vous pouvez le faire à partir de cette URL." -#: awx/sso/conf.py:966 +#: awx/sso/conf.py:953 msgid "SAML Service Provider Entity ID" msgstr "ID d'entité du fournisseur de services SAML" -#: awx/sso/conf.py:967 +#: awx/sso/conf.py:954 msgid "" "The application-defined unique identifier used as the audience of the SAML " "service provider (SP) configuration. This is usually the URL for Tower." -msgstr "" -"Identifiant unique défini par l'application utilisé comme audience dans la " -"configuration du fournisseur de services (SP) SAML. Il s'agit généralement " -"de l'URL de Tower." +msgstr "Identifiant unique défini par l'application utilisé comme audience dans la configuration du fournisseur de services (SP) SAML. Il s'agit généralement de l'URL de Tower." -#: awx/sso/conf.py:982 +#: awx/sso/conf.py:968 msgid "SAML Service Provider Public Certificate" msgstr "Certificat public du fournisseur de services SAML" -#: awx/sso/conf.py:983 +#: awx/sso/conf.py:969 msgid "" -"Create a keypair for Tower to use as a service provider (SP) and include the" -" certificate content here." -msgstr "" -"Créez une paire de clés pour que Tower puisse être utilisé comme fournisseur" -" de services (SP) et entrez le contenu du certificat ici." +"Create a keypair for Tower to use as a service provider (SP) and include the " +"certificate content here." +msgstr "Créez une paire de clés pour que Tower puisse être utilisé comme fournisseur de services (SP) et entrez le contenu du certificat ici." -#: awx/sso/conf.py:996 +#: awx/sso/conf.py:981 msgid "SAML Service Provider Private Key" msgstr "Clé privée du fournisseur de services SAML" -#: awx/sso/conf.py:997 +#: awx/sso/conf.py:982 msgid "" -"Create a keypair for Tower to use as a service provider (SP) and include the" -" private key content here." -msgstr "" -"Créez une paire de clés pour que Tower puisse être utilisé comme fournisseur" -" de services (SP) et entrez le contenu de la clé privée ici." +"Create a keypair for Tower to use as a service provider (SP) and include the " +"private key content here." +msgstr "Créez une paire de clés pour que Tower puisse être utilisé comme fournisseur de services (SP) et entrez le contenu de la clé privée ici." -#: awx/sso/conf.py:1009 +#: awx/sso/conf.py:993 msgid "SAML Service Provider Organization Info" msgstr "Infos organisationnelles du fournisseur de services SAML" -#: awx/sso/conf.py:1010 +#: awx/sso/conf.py:994 msgid "" "Provide the URL, display name, and the name of your app. Refer to the " "Ansible Tower documentation for example syntax." -msgstr "" -"Fournir cette URL, le nom d'affichage, le nom de votre app. Voir la " -"documentation Ansible Tower pour obtenir des exemples de syntaxe." +msgstr "Fournir cette URL, le nom d'affichage, le nom de votre app. Voir la documentation Ansible Tower pour obtenir des exemples de syntaxe." -#: awx/sso/conf.py:1029 +#: awx/sso/conf.py:1012 msgid "SAML Service Provider Technical Contact" msgstr "Contact technique du fournisseur de services SAML" -#: awx/sso/conf.py:1030 +#: awx/sso/conf.py:1013 msgid "" -"Provide the name and email address of the technical contact for your service" -" provider. Refer to the Ansible Tower documentation for example syntax." -msgstr "" -"Fournir le nom et l'adresse email d'un contact Technique pour le fournisseur" -" de services. Voir la documentation Ansible Tower pour obtenir des exemples " -"de syntaxe." +"Provide the name and email address of the technical contact for your service " +"provider. Refer to the Ansible Tower documentation for example syntax." +msgstr "Fournir le nom et l'adresse email d'un contact Technique pour le fournisseur de services. Voir la documentation Ansible Tower pour obtenir des exemples de syntaxe." -#: awx/sso/conf.py:1047 +#: awx/sso/conf.py:1029 msgid "SAML Service Provider Support Contact" msgstr "Contact support du fournisseur de services SAML" -#: awx/sso/conf.py:1048 +#: awx/sso/conf.py:1030 msgid "" "Provide the name and email address of the support contact for your service " "provider. Refer to the Ansible Tower documentation for example syntax." -msgstr "" -"Fournir le nom et l'adresse email d'un contact Support pour le fournisseur " -"de services. Voir la documentation Ansible Tower pour obtenir des exemples " -"de syntaxe." +msgstr "Fournir le nom et l'adresse email d'un contact Support pour le fournisseur de services. Voir la documentation Ansible Tower pour obtenir des exemples de syntaxe." -#: awx/sso/conf.py:1064 +#: awx/sso/conf.py:1045 msgid "SAML Enabled Identity Providers" msgstr "Fournisseurs d'identité compatibles SAML" -#: awx/sso/conf.py:1065 +#: awx/sso/conf.py:1046 msgid "" "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." -msgstr "" -"Configurez l'ID d'entité, l'URL SSO et le certificat pour chaque fournisseur" -" d'identité (IdP) utilisé. Plusieurs IdP SAML sont pris en charge. Certains " -"IdP peuvent fournir des données utilisateur à l'aide de noms d'attributs qui" -" diffèrent des OID par défaut. Les noms d'attributs peuvent être remplacés " -"pour chaque IdP. Voir la documentation Ansible Tower pour obtenir des " -"exemples de syntaxe." - -#: awx/sso/conf.py:1102 +"names may be overridden for each IdP. Refer to the Ansible documentation for " +"additional details and syntax." +msgstr "Configurez l'ID d'entité, l'URL SSO et le certificat pour chaque fournisseur d'identité (IdP) utilisé. Plusieurs IdP SAML sont pris en charge. Certains IdP peuvent fournir des données utilisateur à l'aide de noms d'attributs qui diffèrent des OID par défaut. Les noms d'attributs peuvent être remplacés pour chaque IdP. Voir la documentation Ansible Tower pour obtenir des exemples de syntaxe." + +#: awx/sso/conf.py:1082 msgid "SAML Security Config" msgstr "Config de sécurité SAML" -#: awx/sso/conf.py:1103 +#: awx/sso/conf.py:1083 msgid "" "A dict of key value pairs that are passed to the underlying python-saml " "security setting https://github.com/onelogin/python-saml#settings" -msgstr "" -"Un dictionnaire de paires de valeurs clés qui sont passées au paramètre de " -"sécurité saus-jacent python-saml https://github.com/onelogin/python-" -"saml#settings" +msgstr "Un dictionnaire de paires de valeurs clés qui sont passées au paramètre de sécurité saus-jacent python-saml https://github.com/onelogin/python-saml#settings" -#: awx/sso/conf.py:1135 +#: awx/sso/conf.py:1114 msgid "SAML Service Provider extra configuration data" -msgstr "" -"Données de configuration supplémentaires du fournisseur du service SAML" +msgstr "Données de configuration supplémentaires du fournisseur du service SAML" -#: awx/sso/conf.py:1136 +#: awx/sso/conf.py:1115 msgid "" -"A dict of key value pairs to be passed to the underlying python-saml Service" -" Provider configuration setting." -msgstr "" -"Un dictionnaire de paires de valeurs clés qui sont passées au paramètre de " -"configuration sous-jacent du Fournisseur de service python-saml." +"A dict of key value pairs to be passed to the underlying python-saml Service " +"Provider configuration setting." +msgstr "Un dictionnaire de paires de valeurs clés qui sont passées au paramètre de configuration sous-jacent du Fournisseur de service python-saml." -#: awx/sso/conf.py:1149 +#: awx/sso/conf.py:1127 msgid "SAML IDP to extra_data attribute mapping" msgstr "IDP SAM pour la mappage d'attributs extra_data" -#: awx/sso/conf.py:1150 +#: awx/sso/conf.py:1128 msgid "" "A list of tuples that maps IDP attributes to extra_attributes. Each " "attribute will be a list of values, even if only 1 value." -msgstr "" -"Liste des tuples qui mappent les attributs IDP en extra_attributes. Chaque " -"attribut correspondra à une liste de valeurs, même si 1 seule valeur." +msgstr "Liste des tuples qui mappent les attributs IDP en extra_attributes. Chaque attribut correspondra à une liste de valeurs, même si 1 seule valeur." -#: awx/sso/conf.py:1167 +#: awx/sso/conf.py:1144 msgid "SAML Organization Map" msgstr "Mappe d'organisations SAML" -#: awx/sso/conf.py:1180 +#: awx/sso/conf.py:1156 msgid "SAML Team Map" msgstr "Mappe d'équipes SAML" -#: awx/sso/conf.py:1193 +#: awx/sso/conf.py:1168 msgid "SAML Organization Attribute Mapping" msgstr "Mappage d'attributs d'organisation SAML" -#: awx/sso/conf.py:1194 +#: awx/sso/conf.py:1169 msgid "Used to translate user organization membership into Tower." -msgstr "" -"Utilisé pour traduire l'adhésion organisation de l'utilisateur dans Tower." +msgstr "Utilisé pour traduire l'adhésion organisation de l'utilisateur dans Tower." -#: awx/sso/conf.py:1211 +#: awx/sso/conf.py:1187 msgid "SAML Team Attribute Mapping" msgstr "Mappage d'attributs d'équipe SAML" -#: awx/sso/conf.py:1212 +#: awx/sso/conf.py:1188 msgid "Used to translate user team membership into Tower." msgstr "Utilisé pour traduire l'adhésion équipe de l'utilisateur dans Tower." -#: awx/sso/fields.py:183 +#: awx/sso/fields.py:81 +msgid "Invalid field." +msgstr "Champ invalide." + +#: awx/sso/fields.py:250 #, python-brace-format msgid "Invalid connection option(s): {invalid_options}." msgstr "Option(s) de connexion non valide(s) : {invalid_options}." -#: awx/sso/fields.py:266 +#: awx/sso/fields.py:334 msgid "Base" msgstr "Base" -#: awx/sso/fields.py:267 +#: awx/sso/fields.py:335 msgid "One Level" msgstr "Un niveau" -#: awx/sso/fields.py:268 +#: awx/sso/fields.py:336 msgid "Subtree" msgstr "Sous-arborescence" -#: awx/sso/fields.py:286 +#: awx/sso/fields.py:354 #, python-brace-format msgid "Expected a list of three items but got {length} instead." -msgstr "" -"Une liste de trois éléments était attendue, mais {length} a été obtenu à la " -"place." +msgstr "Une liste de trois éléments était attendue, mais {length} a été obtenu à la place." -#: awx/sso/fields.py:287 +#: awx/sso/fields.py:355 #, python-brace-format msgid "Expected an instance of LDAPSearch but got {input_type} instead." -msgstr "" -"Une instance de LDAPSearch était attendue, mais {input_type} a été obtenu à " -"la place." +msgstr "Une instance de LDAPSearch était attendue, mais {input_type} a été obtenu à la place." -#: awx/sso/fields.py:323 +#: awx/sso/fields.py:391 #, python-brace-format msgid "" "Expected an instance of LDAPSearch or LDAPSearchUnion but got {input_type} " "instead." -msgstr "" -"Une instance de LDAPSearch ou de LDAPSearchUnion était attendue, mais " -"{input_type} a été obtenu à la place." +msgstr "Une instance de LDAPSearch ou de LDAPSearchUnion était attendue, mais {input_type} a été obtenu à la place." -#: awx/sso/fields.py:361 +#: awx/sso/fields.py:429 #, python-brace-format msgid "Invalid user attribute(s): {invalid_attrs}." msgstr "Attribut(s) d'utilisateur non valide(s) : {invalid_attrs}." -#: awx/sso/fields.py:378 +#: awx/sso/fields.py:447 #, python-brace-format msgid "Expected an instance of LDAPGroupType but got {input_type} instead." -msgstr "" -"Une instance de LDAPGroupType était attendue, mais {input_type} a été obtenu" -" à la place." +msgstr "Une instance de LDAPGroupType était attendue, mais {input_type} a été obtenu à la place." -#: awx/sso/fields.py:418 awx/sso/fields.py:465 +#: awx/sso/fields.py:487 #, python-brace-format msgid "Invalid key(s): {invalid_keys}." -msgstr "Clé(s) non valide(s) : {invalid_keys}." +msgstr "Clé(s) invalide(s) : {invalid_keys}." -#: awx/sso/fields.py:443 +#: awx/sso/fields.py:513 #, python-brace-format msgid "Invalid user flag: \"{invalid_flag}\"." -msgstr "Marqueur d'utilisateur non valide : \"{invalid_flag}\"." - -#: awx/sso/fields.py:464 -#, python-brace-format -msgid "Missing key(s): {missing_keys}." -msgstr "Clé(s) manquante(s) : {missing_keys}." - -#: awx/sso/fields.py:514 awx/sso/fields.py:631 -#, python-brace-format -msgid "Invalid key(s) for organization map: {invalid_keys}." -msgstr "Clé(s) non valide(s) pour la mappe d'organisations : {invalid_keys}." - -#: awx/sso/fields.py:532 -#, python-brace-format -msgid "Missing required key for team map: {invalid_keys}." -msgstr "Clé obligatoire manquante pour la mappe d'équipes : {invalid_keys}." - -#: awx/sso/fields.py:533 awx/sso/fields.py:650 -#, python-brace-format -msgid "Invalid key(s) for team map: {invalid_keys}." -msgstr "Clé(s) non valide(s) pour la mappe d'équipes : {invalid_keys}." - -#: awx/sso/fields.py:649 -#, python-brace-format -msgid "Missing required key for team map: {missing_keys}." -msgstr "Clé obligatoire manquante pour la mappe d'équipes : {missing_keys}." +msgstr "Drapeau d'utilisateur non valide : \"{invalid_flag}\"." #: awx/sso/fields.py:667 #, python-brace-format -msgid "Missing required key(s) for org info record: {missing_keys}." -msgstr "" -"Clé(s) obligatoire(s) manquante(s) pour l'enregistrement des infos organis. " -": {missing_keys}." - -#: awx/sso/fields.py:680 -#, python-brace-format msgid "Invalid language code(s) for org info: {invalid_lang_codes}." -msgstr "" -"Code(s) langage non valide(s) pour les infos organis. : " -"{invalid_lang_codes}." - -#: awx/sso/fields.py:699 -#, python-brace-format -msgid "Missing required key(s) for contact: {missing_keys}." -msgstr "Clé(s) obligatoire(s) manquante(s) pour le contact : {missing_keys}." +msgstr "Code(s) langage non valide(s) pour les infos organis. : {invalid_lang_codes}." -#: awx/sso/fields.py:711 -#, python-brace-format -msgid "Missing required key(s) for IdP: {missing_keys}." -msgstr "Clé(s) obligatoire(s) manquante(s) pour l'IdP : {missing_keys}." - -#: awx/sso/pipeline.py:31 +#: awx/sso/pipeline.py:27 #, python-brace-format msgid "An account cannot be found for {0}" msgstr "Impossible de trouver un compte pour {0}" -#: awx/sso/pipeline.py:37 +#: awx/sso/pipeline.py:33 msgid "Your account is inactive" msgstr "Votre compte est inactif" #: awx/sso/validators.py:20 awx/sso/validators.py:46 #, python-format msgid "DN must include \"%%(user)s\" placeholder for username: %s" -msgstr "" -"Le ND doit inclure l'espace réservé \"%%(user)s\" pour le nom d'utilisateur " -": %s" +msgstr "Le ND doit inclure l'espace réservé \"%%(user)s\" pour le nom d'utilisateur : %s" #: awx/sso/validators.py:27 #, python-format @@ -5908,36 +6064,8 @@ msgstr "Retour à Ansible Tower" msgid "Resize" msgstr "Redimensionner" -#: awx/templates/rest_framework/base.html:37 -msgid "navbar" -msgstr "barnav" - -#: awx/templates/rest_framework/base.html:75 -msgid "content" -msgstr "contenu" - -#: awx/templates/rest_framework/base.html:78 -msgid "request form" -msgstr "formulaire de demande" - -#: awx/templates/rest_framework/base.html:134 -msgid "Filters" -msgstr "Filtres" - -#: awx/templates/rest_framework/base.html:139 -msgid "main content" -msgstr "informations principales" - -#: awx/templates/rest_framework/base.html:155 -msgid "request info" -msgstr "informations demandées" - -#: awx/templates/rest_framework/base.html:159 -msgid "response info" -msgstr "Informations répondues" - -#: awx/ui/apps.py:9 awx/ui/conf.py:22 awx/ui/conf.py:36 awx/ui/conf.py:51 -#: awx/ui/conf.py:63 awx/ui/conf.py:73 +#: awx/ui/apps.py:9 awx/ui/conf.py:22 awx/ui/conf.py:36 awx/ui/conf.py:50 +#: awx/ui/conf.py:61 awx/ui/conf.py:71 msgid "UI" msgstr "IU" @@ -5954,12 +6082,12 @@ msgid "Detailed" msgstr "Détaillé" #: awx/ui/conf.py:20 -msgid "Analytics Tracking State" -msgstr "État du suivi analytique" +msgid "User Analytics Tracking State" +msgstr "État du suivi analytique de l'utilisateur" #: awx/ui/conf.py:21 -msgid "Enable or Disable Analytics Tracking." -msgstr "Activez ou désactivez le suivi analytique." +msgid "Enable or Disable User Analytics Tracking." +msgstr "Activez ou désactivez le suivi analytique de l'utilisateur." #: awx/ui/conf.py:31 msgid "Custom Login Info" @@ -5968,62 +6096,48 @@ msgstr "Infos de connexion personnalisées" #: awx/ui/conf.py:32 msgid "" "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, as custom HTML or other markup languages are " +"disclaimer) to a text box in the login modal using this setting. Any content " +"added must be in plain text, as custom HTML or other markup languages are " "not supported." -msgstr "" -"Si nécessaire, vous pouvez ajouter des informations particulières (telles " -"qu'une mention légale ou une clause de non-responsabilité) à une zone de " -"texte dans la fenêtre modale de connexion, grâce à ce paramètre. Tout " -"contenu ajouté doit l'être en texte brut, dans la mesure où le langage HTML " -"personnalisé et les autres langages de balisage ne sont pas pris en charge." +msgstr "Si nécessaire, vous pouvez ajouter des informations particulières (telles qu'une mention légale ou une clause de non-responsabilité) à une zone de texte dans la fenêtre modale de connexion, grâce à ce paramètre. Tout contenu ajouté doit l'être en texte brut, dans la mesure où le langage HTML personnalisé et les autres langages de balisage ne sont pas pris en charge." -#: awx/ui/conf.py:46 +#: awx/ui/conf.py:45 msgid "Custom Logo" msgstr "Logo personnalisé" -#: awx/ui/conf.py:47 +#: awx/ui/conf.py:46 msgid "" -"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 " +"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." -msgstr "" -"Pour créer un logo personnalisé, fournir un fichier que vos aurez créé. Pour" -" un meilleur résultat, utiliser un fichier .png avec un fond transparent. " -"Les formats GIF, PNG et JPEG sont supportés." +msgstr "Pour créer un logo personnalisé, fournir un fichier que vos aurez créé. Pour un meilleur résultat, utiliser un fichier .png avec un fond transparent. Les formats GIF, PNG et JPEG sont supportés." -#: awx/ui/conf.py:60 +#: awx/ui/conf.py:58 msgid "Max Job Events Retrieved by UI" msgstr "Max Événements Job récupérés par l'IU" -#: awx/ui/conf.py:61 +#: awx/ui/conf.py:59 msgid "" "Maximum number of job events for the UI to retrieve within a single request." -msgstr "" -"Nombre maximum d'événements liés à un Job que l'IU a extrait en une requête " -"unique." +msgstr "Nombre maximum d'événements liés à un Job que l'IU a extrait en une requête unique." -#: awx/ui/conf.py:70 +#: awx/ui/conf.py:68 msgid "Enable Live Updates in the UI" msgstr "Activer les mises à jour live dans l'IU" -#: awx/ui/conf.py:71 +#: awx/ui/conf.py:69 msgid "" "If disabled, the page will not refresh when events are received. Reloading " "the page will be required to get the latest details." -msgstr "" -"Si elle est désactivée, la page ne se rafraîchira pas lorsque des événements" -" sont reçus. Le rechargement de la page sera nécessaire pour obtenir les " -"derniers détails." +msgstr "Si elle est désactivée, la page ne se rafraîchira pas lorsque des événements sont reçus. Le rechargement de la page sera nécessaire pour obtenir les derniers détails." -#: awx/ui/fields.py:29 +#: awx/ui/fields.py:30 msgid "" "Invalid format for custom logo. Must be a data URL with a base64-encoded " "GIF, PNG or JPEG image." -msgstr "" -"Format de logo personnalisé non valide. Entrez une URL de données avec une " -"image GIF, PNG ou JPEG codée en base64." +msgstr "Format de logo personnalisé non valide. Entrez une URL de données avec une image GIF, PNG ou JPEG codée en base64." -#: awx/ui/fields.py:30 +#: awx/ui/fields.py:31 msgid "Invalid base64-encoded data in data URL." msgstr "Données codées en base64 non valides dans l'URL de données" + diff --git a/awx/locale/ja/LC_MESSAGES/django.po b/awx/locale/ja/LC_MESSAGES/django.po index dce9fb7204a5..abc2ad15aa7b 100644 --- a/awx/locale/ja/LC_MESSAGES/django.po +++ b/awx/locale/ja/LC_MESSAGES/django.po @@ -1,25 +1,25 @@ -# asasaki , 2017. #zanata -# mkim , 2017. #zanata -# myamamot , 2017. #zanata -# asasaki , 2018. #zanata +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-08-14 13:52+0000\n" -"PO-Revision-Date: 2018-08-15 03:57+0000\n" -"Last-Translator: asasaki \n" -"Language-Team: Japanese\n" +"POT-Creation-Date: 2020-09-29 15:20+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: ja \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: ja\n" -"Plural-Forms: nplurals=1; plural=0\n" -"X-Generator: Zanata 4.6.0\n" #: awx/api/conf.py:15 msgid "Idle Time Force Log Out" -msgstr "強制ログアウトまでのアイドル時間" +msgstr "アイドル時間、強制ログアウト" #: awx/api/conf.py:16 msgid "" @@ -27,9 +27,9 @@ msgid "" "again." msgstr "ユーザーが再ログインするまでに非アクティブな状態になる秒数です。" -#: awx/api/conf.py:17 awx/api/conf.py:26 awx/api/conf.py:34 awx/api/conf.py:47 -#: awx/api/conf.py:59 awx/sso/conf.py:85 awx/sso/conf.py:96 -#: awx/sso/conf.py:108 awx/sso/conf.py:123 +#: awx/api/conf.py:17 awx/api/conf.py:26 awx/api/conf.py:34 awx/api/conf.py:50 +#: awx/api/conf.py:62 awx/api/conf.py:74 awx/sso/conf.py:97 awx/sso/conf.py:108 +#: awx/sso/conf.py:120 awx/sso/conf.py:135 msgid "Authentication" msgstr "認証" @@ -51,34 +51,41 @@ msgstr "HTTP Basic 認証の有効化" msgid "Enable HTTP Basic Auth for the API Browser." msgstr "API ブラウザーの HTTP Basic 認証を有効にします。" -#: awx/api/conf.py:42 +#: awx/api/conf.py:43 msgid "OAuth 2 Timeout Settings" msgstr "OAuth 2 タイムアウト設定" -#: awx/api/conf.py:43 +#: awx/api/conf.py:44 msgid "" "Dictionary for customizing OAuth 2 timeouts, available items are " "`ACCESS_TOKEN_EXPIRE_SECONDS`, the duration of access tokens in the number " -"of seconds, and `AUTHORIZATION_CODE_EXPIRE_SECONDS`, the duration of " -"authorization grants in the number of seconds." -msgstr "" -"OAuth 2 " -"タイムアウトをカスタマイズするための辞書です。利用可能な項目は以下の通りです。`ACCESS_TOKEN_EXPIRE_SECONDS`: " -"アクセストークンの期間 (秒数)。 `AUTHORIZATION_CODE_EXPIRE_SECONDS`: 認証の付与期間 (秒数)。" +"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." +msgstr "OAuth 2 タイムアウトをカスタマイズするための辞書です。利用可能な項目は、`ACCESS_TOKEN_EXPIRE_SECONDS` (アクセストークンの期間 (秒数))、`AUTHORIZATION_CODE_EXPIRE_SECONDS` (認証コードの期間 (秒数))、`REFRESH_TOKEN_EXPIRE_SECONDS` (アクセストークンが失効した後の更新トークンの期間 (秒数)) です。" -#: awx/api/conf.py:54 +#: awx/api/conf.py:57 msgid "Allow External Users to Create OAuth2 Tokens" msgstr "外部ユーザーによる OAuth2 トークンの作成を許可" -#: awx/api/conf.py:55 +#: awx/api/conf.py:58 msgid "" "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." -msgstr "" -"セキュリティー上の理由により、外部の認証プロバイダー (LDAP、SAML、SSO、Radius など) のユーザーは OAuth2 " -"トークンの作成を許可されません。この動作を変更するには、 この設定を有効にします。この設定のトグルをオフにする場合も既存のトークンは削除されません。" +"behavior, enable this setting. Existing tokens will not be deleted when this " +"setting is toggled off." +msgstr "セキュリティー上の理由により、外部の認証プロバイダー (LDAP、SAML、SSO、Radius など) のユーザーは OAuth2 トークンを作成できません。この動作を変更するには、当設定を有効にします。この設定をオフに指定した場合は、既存のトークンは削除されません。" + +#: awx/api/conf.py:71 +msgid "Login redirect override URL" +msgstr "ログインリダイレクトオーバーライド URL" + +#: awx/api/conf.py:72 +msgid "" +"URL to which unauthorized users will be redirected to log in. If blank, " +"users will be sent to the Tower login page." +msgstr "権限のないユーザーがログインできるように、リダイレクトする URL。空白の場合は、Tower のログインページに移動します。" #: awx/api/exceptions.py:20 msgid "Resource is being used by running jobs." @@ -89,24 +96,24 @@ msgstr "リソースが実行中のジョブで使用されています。" msgid "Invalid key names: {invalid_key_names}" msgstr "無効なキー名: {invalid_key_names}" -#: awx/api/fields.py:107 +#: awx/api/fields.py:111 msgid "Credential {} does not exist" msgstr "認証情報 {} は存在しません" -#: awx/api/filters.py:97 +#: awx/api/filters.py:82 msgid "No related model for field {}." msgstr "フィールド {} の関連するモデルはありません。" -#: awx/api/filters.py:114 +#: awx/api/filters.py:99 msgid "Filtering on password fields is not allowed." msgstr "パスワードフィールドでのフィルターは許可されていません。" -#: awx/api/filters.py:126 awx/api/filters.py:128 +#: awx/api/filters.py:111 awx/api/filters.py:113 #, python-format msgid "Filtering on %s is not allowed." msgstr "%s でのフィルターは許可されていません。" -#: awx/api/filters.py:131 +#: awx/api/filters.py:116 msgid "Loops not allowed in filters, detected on field {}." msgstr "ループがフィルターで許可されていません。フィールド {} で検出されました。" @@ -114,69 +121,70 @@ msgstr "ループがフィルターで許可されていません。フィール msgid "Query string field name not provided." msgstr "クエリー文字列フィールド名は指定されていません。" -#: awx/api/filters.py:187 +#: awx/api/filters.py:192 #, python-brace-format msgid "Invalid {field_name} id: {field_id}" -msgstr "無効な {field_name} ID: {field_id}" +msgstr "無効な {field_name} id: {field_id}" -#: awx/api/filters.py:326 -#, python-format -msgid "cannot filter on kind %s" -msgstr "%s の種類でフィルターできません。" +#: awx/api/filters.py:338 +msgid "" +"Cannot apply role_level filter to this list because its model does not use " +"roles for access control." +msgstr "モデルがアクセスコントロールにロールを使用していないので、このリストに role_level フィルターを適用できません。" -#: awx/api/generics.py:197 +#: awx/api/generics.py:183 msgid "" "You did not use correct Content-Type in your HTTP request. If you are using " "our REST API, the Content-Type must be application/json" -msgstr "" -"HTTP 要求で正しい Content-Type (コンテンツタイプ) が使用されていません。REST API を使用している場合、Content-" -"Type (コンテンツタイプ) は「application/json」でなければなりません。" +msgstr "HTTP 要求で正しい Content-Type (コンテンツタイプ) が使用されていません。REST API を使用している場合、Content-Type (コンテンツタイプ) は「application/json」でなければなりません。" -#: awx/api/generics.py:635 awx/api/generics.py:697 +#: awx/api/generics.py:647 awx/api/generics.py:709 msgid "\"id\" field must be an integer." msgstr "「id」フィールドは整数でなければなりません。" -#: awx/api/generics.py:694 +#: awx/api/generics.py:706 msgid "\"id\" is required to disassociate" msgstr "関連付けを解除するには 「id」が必要です" -#: awx/api/generics.py:745 +#: awx/api/generics.py:757 msgid "{} 'id' field is missing." msgstr "{} 「id」フィールドがありません。" -#: awx/api/metadata.py:51 +#: awx/api/metadata.py:58 msgid "Database ID for this {}." msgstr "この{}のデータベース ID。" -#: awx/api/metadata.py:52 +#: awx/api/metadata.py:59 msgid "Name of this {}." msgstr "この{}の名前。" -#: awx/api/metadata.py:53 +#: awx/api/metadata.py:60 msgid "Optional description of this {}." msgstr "この{}のオプションの説明。" -#: awx/api/metadata.py:54 +#: awx/api/metadata.py:61 msgid "Data type for this {}." msgstr "この{}のデータタイプ。" -#: awx/api/metadata.py:55 +#: awx/api/metadata.py:62 msgid "URL for this {}." msgstr "この{}の URL。" -#: awx/api/metadata.py:56 +#: awx/api/metadata.py:63 msgid "Data structure with URLs of related resources." msgstr "関連リソースの URL のあるデータ構造。" -#: awx/api/metadata.py:57 -msgid "Data structure with name/description for related resources." -msgstr "関連リソースの名前/説明のあるデータ構造。" +#: awx/api/metadata.py:64 +msgid "" +"Data structure with name/description for related resources. The output for " +"some objects may be limited for performance reasons." +msgstr "関連リソースの名前/説明を含むデータ構造。一部のオブジェクトの出力は、パフォーマンス上の理由により制約がある場合があります。" -#: awx/api/metadata.py:58 +#: awx/api/metadata.py:66 msgid "Timestamp when this {} was created." msgstr "この {} の作成時のタイムスタンプ。" -#: awx/api/metadata.py:59 +#: awx/api/metadata.py:67 msgid "Timestamp when this {} was last modified." msgstr "この {} の最終変更時のタイムスタンプ。" @@ -189,1240 +197,1385 @@ msgstr "JSON パースエラー: JSON オブジェクトでありません" msgid "" "JSON parse error - %s\n" "Possible cause: trailing comma." -msgstr "" -"JSON パースエラー: %s\n" +msgstr "JSON パースエラー: %s\n" "考えられる原因: 末尾のコンマ。" -#: awx/api/serializers.py:155 +#: awx/api/serializers.py:169 msgid "" -"The original object is already named {}, a copy from it cannot have the same" -" name." +"The original object is already named {}, a copy from it cannot have the same " +"name." msgstr "元のオブジェクトにはすでに {} という名前があり、このコピーに同じ名前を使用することはできません。" -#: awx/api/serializers.py:290 +#: awx/api/serializers.py:302 #, python-format msgid "Cannot use dictionary for %s" msgstr "%s の辞書を使用できません" -#: awx/api/serializers.py:307 +#: awx/api/serializers.py:316 msgid "Playbook Run" msgstr "Playbook 実行" -#: awx/api/serializers.py:308 +#: awx/api/serializers.py:317 msgid "Command" msgstr "コマンド" -#: awx/api/serializers.py:309 awx/main/models/unified_jobs.py:526 +#: awx/api/serializers.py:318 awx/main/models/unified_jobs.py:546 msgid "SCM Update" msgstr "SCM 更新" -#: awx/api/serializers.py:310 +#: awx/api/serializers.py:319 msgid "Inventory Sync" -msgstr "インベントリーの同期" +msgstr "インベントリー同期" -#: awx/api/serializers.py:311 +#: awx/api/serializers.py:320 msgid "Management Job" msgstr "管理ジョブ" -#: awx/api/serializers.py:312 +#: awx/api/serializers.py:321 msgid "Workflow Job" msgstr "ワークフロージョブ" -#: awx/api/serializers.py:313 +#: awx/api/serializers.py:322 msgid "Workflow Template" msgstr "ワークフローテンプレート" -#: awx/api/serializers.py:314 +#: awx/api/serializers.py:323 msgid "Job Template" msgstr "ジョブテンプレート" -#: awx/api/serializers.py:714 +#: awx/api/serializers.py:709 msgid "" "Indicates whether all of the events generated by this unified job have been " "saved to the database." msgstr "この統一されたジョブで生成されるイベントすべてがデータベースに保存されているかどうかを示します。" -#: awx/api/serializers.py:879 +#: awx/api/serializers.py:880 msgid "Write-only field used to change the password." msgstr "パスワードを変更するために使用される書き込み専用フィールド。" -#: awx/api/serializers.py:881 +#: awx/api/serializers.py:882 msgid "Set if the account is managed by an external service" msgstr "アカウントが外部サービスで管理される場合に設定されます" -#: awx/api/serializers.py:905 +#: awx/api/serializers.py:909 msgid "Password required for new User." msgstr "新規ユーザーのパスワードを入力してください。" -#: awx/api/serializers.py:981 +#: awx/api/serializers.py:994 #, python-format msgid "Unable to change %s on user managed by LDAP." msgstr "LDAP で管理されたユーザーの %s を変更できません。" -#: awx/api/serializers.py:1067 +#: awx/api/serializers.py:1090 msgid "Must be a simple space-separated string with allowed scopes {}." msgstr "許可されたスコープ {} のある単純なスペースで区切られた文字列でなければなりません。" -#: awx/api/serializers.py:1167 +#: awx/api/serializers.py:1188 msgid "Authorization Grant Type" msgstr "認証付与タイプ" -#: awx/api/serializers.py:1169 awx/main/models/credential/__init__.py:1064 +#: awx/api/serializers.py:1190 awx/main/credential_plugins/azure_kv.py:30 +#: awx/main/models/credential/__init__.py:972 msgid "Client Secret" msgstr "クライアントシークレット" -#: awx/api/serializers.py:1172 +#: awx/api/serializers.py:1193 msgid "Client Type" msgstr "クライアントタイプ" -#: awx/api/serializers.py:1175 +#: awx/api/serializers.py:1196 msgid "Redirect URIs" msgstr "リダイレクト URI" -#: awx/api/serializers.py:1178 +#: awx/api/serializers.py:1199 msgid "Skip Authorization" msgstr "認証のスキップ" -#: awx/api/serializers.py:1290 +#: awx/api/serializers.py:1306 +msgid "Cannot change max_hosts." +msgstr "max_hosts を変更できません。" + +#: awx/api/serializers.py:1339 msgid "This path is already being used by another manual project." msgstr "このパスは別の手動プロジェクトですでに使用されています。" -#: awx/api/serializers.py:1316 -msgid "This field has been deprecated and will be removed in a future release" -msgstr "このフィールドは非推奨となり、今後のリリースで削除されます。" +#: awx/api/serializers.py:1341 +msgid "SCM branch cannot be used with archive projects." +msgstr "SCM ブランチはアーカイブプロジェクトでは使用できません。" + +#: awx/api/serializers.py:1343 +msgid "SCM refspec can only be used with git projects." +msgstr "SCM refspec は、git プロジェクトでのみ使用できます。" -#: awx/api/serializers.py:1375 -msgid "Organization is missing" -msgstr "組織がありません" +#: awx/api/serializers.py:1420 +msgid "" +"One or more job templates depend on branch override behavior for this " +"project (ids: {})." +msgstr "1 つまたは複数のジョブテンプレートは、このプロジェクトのブランチオーバーライドに依存しています (ids: {})。" -#: awx/api/serializers.py:1379 +#: awx/api/serializers.py:1427 msgid "Update options must be set to false for manual projects." msgstr "手動プロジェクトについては更新オプションを false に設定する必要があります。" -#: awx/api/serializers.py:1385 +#: awx/api/serializers.py:1433 msgid "Array of playbooks available within this project." msgstr "このプロジェクト内で利用可能な一連の Playbook。" -#: awx/api/serializers.py:1404 +#: awx/api/serializers.py:1452 msgid "" "Array of inventory files and directories available within this project, not " "comprehensive." msgstr "このプロジェクト内で利用可能な一連のインベントリーファイルおよびディレクトリー (包括的な一覧ではありません)。" -#: awx/api/serializers.py:1452 awx/api/serializers.py:3247 -#: awx/api/serializers.py:3454 +#: awx/api/serializers.py:1500 awx/api/serializers.py:3089 +#: awx/api/serializers.py:3301 msgid "A count of hosts uniquely assigned to each status." msgstr "各ステータスに一意に割り当てられたホスト数です。" -#: awx/api/serializers.py:1455 awx/api/serializers.py:3250 +#: awx/api/serializers.py:1503 awx/api/serializers.py:3092 msgid "A count of all plays and tasks for the job run." msgstr "ジョブ実行用のすべてのプレイおよびタスクの数です。" -#: awx/api/serializers.py:1570 +#: awx/api/serializers.py:1630 msgid "Smart inventories must specify host_filter" msgstr "スマートインベントリーは host_filter を指定する必要があります" -#: awx/api/serializers.py:1674 +#: awx/api/serializers.py:1722 #, python-format msgid "Invalid port specification: %s" msgstr "無効なポート指定: %s" -#: awx/api/serializers.py:1685 +#: awx/api/serializers.py:1733 msgid "Cannot create Host for Smart Inventory" msgstr "スマートインベントリーのホストを作成できません" -#: awx/api/serializers.py:1797 +#: awx/api/serializers.py:1751 +msgid "A Group with that name already exists." +msgstr "その名前のグループはすでに存在します。" + +#: awx/api/serializers.py:1822 +msgid "A Host with that name already exists." +msgstr "その名前のホストはすでに存在します。" + +#: awx/api/serializers.py:1827 msgid "Invalid group name." msgstr "無効なグループ名。" -#: awx/api/serializers.py:1802 +#: awx/api/serializers.py:1832 msgid "Cannot create Group for Smart Inventory" msgstr "スマートインベントリーのグループを作成できません" -#: awx/api/serializers.py:1877 +#: awx/api/serializers.py:1907 msgid "" "Script must begin with a hashbang sequence: i.e.... #!/usr/bin/env python" msgstr "スクリプトは hashbang シーケンスで開始する必要があります (例: .... #!/usr/bin/env python)" -#: awx/api/serializers.py:1926 +#: awx/api/serializers.py:1936 +msgid "Cloud credential to use for inventory updates." +msgstr "インベントリー更新に使用するクラウド認証情報" + +#: awx/api/serializers.py:1957 msgid "`{}` is a prohibited environment variable" msgstr "`{}` は禁止されている環境変数です" -#: awx/api/serializers.py:1937 +#: awx/api/serializers.py:1968 msgid "If 'source' is 'custom', 'source_script' must be provided." msgstr "「source」が「custom」である場合、「source_script」を指定する必要があります。" -#: awx/api/serializers.py:1943 +#: awx/api/serializers.py:1974 msgid "Must provide an inventory." msgstr "インベントリーを指定する必要があります。" -#: awx/api/serializers.py:1947 +#: awx/api/serializers.py:1978 msgid "" "The 'source_script' does not belong to the same organization as the " "inventory." msgstr "「source_script」はインベントリーと同じ組織に属しません。" -#: awx/api/serializers.py:1949 +#: awx/api/serializers.py:1980 msgid "'source_script' doesn't exist." msgstr "「source_script」は存在しません。" -#: awx/api/serializers.py:1985 -msgid "Automatic group relationship, will be removed in 3.3" -msgstr "自動的なグループ関係は 3.3 で削除されます" - -#: awx/api/serializers.py:2072 +#: awx/api/serializers.py:2082 msgid "Cannot use manual project for SCM-based inventory." msgstr "SCM ベースのインベントリーの手動プロジェクトを使用できません。" -#: awx/api/serializers.py:2078 -msgid "" -"Manual inventory sources are created automatically when a group is created " -"in the v1 API." -msgstr "手動のインベントリーソースは、グループが v1 API で作成される際に自動作成されます。" - -#: awx/api/serializers.py:2083 +#: awx/api/serializers.py:2087 msgid "Setting not compatible with existing schedules." msgstr "設定は既存スケジュールとの互換性がありません。" -#: awx/api/serializers.py:2088 +#: awx/api/serializers.py:2092 msgid "Cannot create Inventory Source for Smart Inventory" msgstr "スマートインベントリーのインベントリーソースを作成できません" -#: awx/api/serializers.py:2139 +#: awx/api/serializers.py:2140 +msgid "Project required for scm type sources." +msgstr "SCM タイプのソースに必要なプロジェクト。" + +#: awx/api/serializers.py:2149 #, python-format msgid "Cannot set %s if not SCM type." msgstr "SCM タイプでない場合 %s を設定できません。" -#: awx/api/serializers.py:2414 +#: awx/api/serializers.py:2219 +msgid "The project used for this job." +msgstr "このジョブに使用するプロジェクト" + +#: awx/api/serializers.py:2475 msgid "Modifications not allowed for managed credential types" msgstr "管理されている認証情報タイプで変更は許可されません" -#: awx/api/serializers.py:2419 +#: awx/api/serializers.py:2487 msgid "" "Modifications to inputs are not allowed for credential types that are in use" msgstr "入力への変更は使用中の認証情報タイプで許可されません" -#: awx/api/serializers.py:2425 +#: awx/api/serializers.py:2492 #, python-format msgid "Must be 'cloud' or 'net', not %s" msgstr "「cloud」または「net」にする必要があります (%s ではない)" -#: awx/api/serializers.py:2431 +#: awx/api/serializers.py:2498 msgid "'ask_at_runtime' is not supported for custom credentials." msgstr "「ask_at_runtime」はカスタム認証情報ではサポートされません。" -#: awx/api/serializers.py:2502 +#: awx/api/serializers.py:2547 msgid "Credential Type" msgstr "認証情報タイプ" -#: awx/api/serializers.py:2617 -#, python-format -msgid "\"%s\" is not a valid choice" -msgstr "「%s」は有効な選択ではありません" +#: awx/api/serializers.py:2611 +msgid "Modifications not allowed for managed credentials" +msgstr "管理されている認証情報では変更が許可されません" -#: awx/api/serializers.py:2636 -#, python-brace-format -msgid "'{field_name}' is not a valid field for {credential_type_name}" -msgstr "'{field_name}' は {credential_type_name} の有効なフィールドではありません" +#: awx/api/serializers.py:2629 awx/api/serializers.py:2703 +msgid "Galaxy credentials must be owned by an Organization." +msgstr "Galaxy 認証情報は組織が所有している必要があります。" -#: awx/api/serializers.py:2657 +#: awx/api/serializers.py:2646 msgid "" -"You cannot change the credential type of the credential, as it may break the" -" functionality of the resources using it." +"You cannot change the credential type of the credential, as it may break the " +"functionality of the resources using it." msgstr "認証情報の認証情報タイプを変更することはできません。これにより、認証情報を使用するリソースの機能が中断する可能性があるためです。" -#: awx/api/serializers.py:2669 +#: awx/api/serializers.py:2658 msgid "" "Write-only field used to add user to owner role. If provided, do not give " "either team or organization. Only valid for creation." -msgstr "" -"ユーザーを所有者ロールに追加するために使用される書き込み専用フィールドです。提供されている場合は、チームまたは組織のいずれも指定しないでください。作成時にのみ有効です。" +msgstr "ユーザーを所有者ロールに追加するために使用される書き込み専用フィールドです。提供されている場合は、チームまたは組織のいずれも指定しないでください。作成時にのみ有効です。" -#: awx/api/serializers.py:2674 +#: awx/api/serializers.py:2663 msgid "" "Write-only field used to add team to owner role. If provided, do not give " "either user or organization. Only valid for creation." -msgstr "" -"チームを所有者ロールに追加するために使用される書き込み専用フィールドです。提供されている場合は、ユーザーまたは組織のいずれも指定しないでください。作成時にのみ有効です。" +msgstr "チームを所有者ロールに追加するために使用される書き込み専用フィールドです。提供されている場合は、ユーザーまたは組織のいずれも指定しないでください。作成時にのみ有効です。" -#: awx/api/serializers.py:2679 +#: awx/api/serializers.py:2668 msgid "" -"Inherit permissions from organization roles. If provided on creation, do not" -" give either user or team." +"Inherit permissions from organization roles. If provided on creation, do not " +"give either user or team." msgstr "組織ロールからパーミッションを継承します。作成時に提供される場合は、ユーザーまたはチームのいずれも指定しないでください。" -#: awx/api/serializers.py:2695 +#: awx/api/serializers.py:2685 msgid "Missing 'user', 'team', or 'organization'." msgstr "「user」、「team」、または「organization」がありません。" -#: awx/api/serializers.py:2735 +#: awx/api/serializers.py:2690 +msgid "" +"Only one of 'user', 'team', or 'organization' should be provided, received " +"{} fields." +msgstr "「user」、「team」、または「organization」のいずれか 1 つのみを指定し、{} フィールドを受け取る必要があります。" + +#: awx/api/serializers.py:2718 msgid "" "Credential organization must be set and match before assigning to a team" msgstr "認証情報の組織が設定され、一致している状態でチームに割り当てる必要があります。" -#: awx/api/serializers.py:2936 -msgid "You must provide a cloud credential." -msgstr "クラウド認証情報を指定する必要があります。" - -#: awx/api/serializers.py:2937 -msgid "You must provide a network credential." -msgstr "ネットワーク認証情報を指定する必要があります。" - -#: awx/api/serializers.py:2938 awx/main/models/jobs.py:155 -msgid "You must provide an SSH credential." -msgstr "SSH 認証情報を指定する必要があります。" - -#: awx/api/serializers.py:2939 -msgid "You must provide a vault credential." -msgstr "Vault 認証情報を指定する必要があります。" - -#: awx/api/serializers.py:2958 +#: awx/api/serializers.py:2844 msgid "This field is required." msgstr "このフィールドは必須です。" -#: awx/api/serializers.py:2960 awx/api/serializers.py:2962 +#: awx/api/serializers.py:2853 msgid "Playbook not found for project." msgstr "プロジェクトの Playbook が見つかりません。" -#: awx/api/serializers.py:2964 +#: awx/api/serializers.py:2855 msgid "Must select playbook for project." msgstr "プロジェクトの Playbook を選択してください。" -#: awx/api/serializers.py:3045 +#: awx/api/serializers.py:2857 awx/api/serializers.py:2859 +msgid "Project does not allow overriding branch." +msgstr "プロジェクトは、ブランチをオーバーライドできません。" + +#: awx/api/serializers.py:2896 +msgid "Must be a Personal Access Token." +msgstr "パーソナルアクセストークンである必要があります。" + +#: awx/api/serializers.py:2899 +msgid "Must match the selected webhook service." +msgstr "選択した Webhook サービスと一致する必要があります。" + +#: awx/api/serializers.py:2970 msgid "Cannot enable provisioning callback without an inventory set." msgstr "インベントリー設定なしにプロビジョニングコールバックを有効にすることはできません。" -#: awx/api/serializers.py:3048 +#: awx/api/serializers.py:2973 msgid "Must either set a default value or ask to prompt on launch." msgstr "起動時にプロントを出すには、デフォルト値を設定するか、またはプロンプトを出すよう指定する必要があります。" -#: awx/api/serializers.py:3050 awx/main/models/jobs.py:310 -msgid "Job types 'run' and 'check' must have assigned a project." -msgstr "ジョブタイプ「run」および「check」によりプロジェクトが割り当てられている必要があります。" +#: awx/api/serializers.py:2975 awx/main/models/jobs.py:299 +msgid "Job Templates must have a project assigned." +msgstr "ジョブテンプレートにはプロジェクトを割り当てる必要があります。" -#: awx/api/serializers.py:3169 -msgid "Invalid job template." -msgstr "無効なジョブテンプレート。" - -#: awx/api/serializers.py:3290 +#: awx/api/serializers.py:3133 msgid "No change to job limit" msgstr "ジョブ制限に変更はありません" -#: awx/api/serializers.py:3291 +#: awx/api/serializers.py:3134 msgid "All failed and unreachable hosts" msgstr "失敗している、到達できないすべてのホスト" -#: awx/api/serializers.py:3306 +#: awx/api/serializers.py:3149 msgid "Missing passwords needed to start: {}" msgstr "起動に必要なパスワードが見つかりません: {}" -#: awx/api/serializers.py:3325 +#: awx/api/serializers.py:3168 msgid "Relaunch by host status not available until job finishes running." msgstr "ホストのステータス別の再起動はジョブが実行を終了するまで利用できません。" -#: awx/api/serializers.py:3339 +#: awx/api/serializers.py:3182 msgid "Job Template Project is missing or undefined." msgstr "ジョブテンプレートプロジェクトが見つからないか、または定義されていません。" -#: awx/api/serializers.py:3341 +#: awx/api/serializers.py:3184 msgid "Job Template Inventory is missing or undefined." msgstr "ジョブテンプレートインベントリーが見つからないか、または定義されていません。" -#: awx/api/serializers.py:3379 -msgid "" -"Unknown, job may have been ran before launch configurations were saved." +#: awx/api/serializers.py:3222 +msgid "Unknown, job may have been ran before launch configurations were saved." msgstr "不明です。ジョブは起動設定が保存される前に実行された可能性があります。" -#: awx/api/serializers.py:3446 awx/main/tasks.py:2297 +#: awx/api/serializers.py:3293 awx/main/tasks.py:2838 awx/main/tasks.py:2856 msgid "{} are prohibited from use in ad hoc commands." msgstr "{} の使用はアドホックコマンドで禁止されています。" -#: awx/api/serializers.py:3534 awx/api/views.py:4893 +#: awx/api/serializers.py:3381 awx/api/views/__init__.py:4211 #, python-brace-format msgid "" "Standard Output too large to display ({text_size} bytes), only download " "supported for sizes over {supported_size} bytes." -msgstr "" -"標準出力が大きすぎて表示できません ({text_size} バイト)。サイズが {supported_size} " -"バイトを超える場合はダウンロードのみがサポートされます。" +msgstr "標準出力が大きすぎて表示できません ({text_size} バイト)。サイズが {supported_size} バイトを超える場合はダウンロードのみがサポートされます。" -#: awx/api/serializers.py:3727 +#: awx/api/serializers.py:3694 msgid "Provided variable {} has no database value to replace with." msgstr "指定された変数 {} には置き換えるデータベースの値がありません。" -#: awx/api/serializers.py:3745 -#, python-brace-format -msgid "\"$encrypted$ is a reserved keyword, may not be used for {var_name}.\"" -msgstr "\"$encrypted$ は予約されたキーワードで、{var_name}には使用できません。\"" +#: awx/api/serializers.py:3712 +msgid "\"$encrypted$ is a reserved keyword, may not be used for {}.\"" +msgstr "\"$encrypted$ は予約されたキーワードで、{} には使用できません。\"" -#: awx/api/serializers.py:3815 -#, python-format -msgid "Cannot nest a %s inside a WorkflowJobTemplate" -msgstr "ワークフロージョブテンプレート内に %s をネストできません" +#: awx/api/serializers.py:4119 +msgid "A project is required to run a job." +msgstr "ジョブを実行するにはプロジェクトが必要です。" -#: awx/api/serializers.py:3822 awx/api/views.py:818 -msgid "Related template is not configured to accept credentials on launch." -msgstr "関連するテンプレートは起動時に認証情報を受け入れるよう設定されていません。" +#: awx/api/serializers.py:4121 +msgid "Missing a revision to run due to failed project update." +msgstr "プロジェクトの更新に失敗したため、実行するリビジョンがありません。" -#: awx/api/serializers.py:4282 +#: awx/api/serializers.py:4125 msgid "The inventory associated with this Job Template is being deleted." msgstr "このジョブテンプレートに関連付けられているインベントリーが削除されています。" -#: awx/api/serializers.py:4284 +#: awx/api/serializers.py:4127 awx/api/serializers.py:4244 msgid "The provided inventory is being deleted." msgstr "指定されたインベントリーが削除されています。" -#: awx/api/serializers.py:4292 +#: awx/api/serializers.py:4135 msgid "Cannot assign multiple {} credentials." msgstr "複数の {} 認証情報を割り当てることができません。" -#: awx/api/serializers.py:4296 +#: awx/api/serializers.py:4140 msgid "Cannot assign a Credential of kind `{}`" msgstr "`{}`の種類の認証情報を割り当てることができません。" -#: awx/api/serializers.py:4309 +#: awx/api/serializers.py:4153 msgid "" "Removing {} credential at launch time without replacement is not supported. " "Provided list lacked credential(s): {}." msgstr "置き換えなしで起動時に {} 認証情報を削除することはサポートされていません。指定された一覧には認証情報がありません: {}" -#: awx/api/serializers.py:4435 +#: awx/api/serializers.py:4242 +msgid "The inventory associated with this Workflow is being deleted." +msgstr "このワークフローに関連付けられているインベントリーが削除されています。" + +#: awx/api/serializers.py:4313 +msgid "Message type '{}' invalid, must be either 'message' or 'body'" +msgstr "メッセージタイプ '{}' が無効です。'メッセージ' または 'ボディー' のいずれかに指定する必要があります。" + +#: awx/api/serializers.py:4319 +msgid "Expected string for '{}', found {}, " +msgstr "'{}' の文字列が必要ですが、{} が見つかりました。 " + +#: awx/api/serializers.py:4323 +msgid "Messages cannot contain newlines (found newline in {} event)" +msgstr "メッセージでは改行を追加できません ({} イベントに改行が含まれます)" + +#: awx/api/serializers.py:4329 +msgid "Expected dict for 'messages' field, found {}" +msgstr "'messages' フィールドには辞書が必要ですが、{} が見つかりました。" + +#: awx/api/serializers.py:4333 +msgid "" +"Event '{}' invalid, must be one of 'started', 'success', 'error', or " +"'workflow_approval'" +msgstr "イベント '{}' は無効です。'started'、'success'、'error' または 'workflow_approval' のいずれかでなければなりません。" + +#: awx/api/serializers.py:4339 +msgid "Expected dict for event '{}', found {}" +msgstr "イベント '{}' には辞書が必要ですが、{} が見つかりました。" + +#: awx/api/serializers.py:4344 +msgid "" +"Workflow Approval event '{}' invalid, must be one of 'running', 'approved', " +"'timed_out', or 'denied'" +msgstr "ワークフロー承認イベント '{}' が無効です。'running'、'approved'、'timed_out' または 'denied' のいずれかでなければなりません。" + +#: awx/api/serializers.py:4351 +msgid "Expected dict for workflow approval event '{}', found {}" +msgstr "ワークフロー承認イベント '{}' には辞書が必要ですが、{} が見つかりました。" + +#: awx/api/serializers.py:4378 +msgid "Unable to render message '{}': {}" +msgstr "メッセージ '{}' のレンダリングができません: {}" + +#: awx/api/serializers.py:4380 +msgid "Field '{}' unavailable" +msgstr "フィールド '{}' が利用できません" + +#: awx/api/serializers.py:4382 +msgid "Security error due to field '{}'" +msgstr "フィールド '{}' が原因のセキュリティーエラー" + +#: awx/api/serializers.py:4402 +msgid "Webhook body for '{}' should be a json dictionary. Found type '{}'." +msgstr "'{}' の Webhook のボディーは json 辞書でなければなりません。'{}' のタイプが見つかりました。" + +#: awx/api/serializers.py:4405 +msgid "Webhook body for '{}' is not a valid json dictionary ({})." +msgstr "'{}' の Webhook ボディーは有効な json 辞書ではありません ({})。" + +#: awx/api/serializers.py:4423 msgid "" "Missing required fields for Notification Configuration: notification_type" msgstr "通知設定の必須フィールドがありません: notification_type" -#: awx/api/serializers.py:4458 +#: awx/api/serializers.py:4450 msgid "No values specified for field '{}'" msgstr "フィールド '{}' に値が指定されていません" -#: awx/api/serializers.py:4463 +#: awx/api/serializers.py:4455 +msgid "HTTP method must be either 'POST' or 'PUT'." +msgstr "HTTP メソッドは 'POST' または 'PUT' のいずれかでなければなりません。" + +#: awx/api/serializers.py:4457 msgid "Missing required fields for Notification Configuration: {}." msgstr "通知設定の必須フィールドがありません: {}。" -#: awx/api/serializers.py:4466 +#: awx/api/serializers.py:4460 msgid "Configuration field '{}' incorrect type, expected {}." msgstr "設定フィールド '{}' のタイプが正しくありません。{} が予期されました。" -#: awx/api/serializers.py:4528 +#: awx/api/serializers.py:4477 +msgid "Notification body" +msgstr "通知ボディー" + +#: awx/api/serializers.py:4557 msgid "" -"Valid DTSTART required in rrule. Value should start with: " -"DTSTART:YYYYMMDDTHHMMSSZ" +"Valid DTSTART required in rrule. Value should start with: DTSTART:" +"YYYYMMDDTHHMMSSZ" msgstr "有効な DTSTART が rrule で必要です。値は DTSTART:YYYYMMDDTHHMMSSZ で開始する必要があります。" -#: awx/api/serializers.py:4530 +#: awx/api/serializers.py:4559 msgid "" "DTSTART cannot be a naive datetime. Specify ;TZINFO= or YYYYMMDDTHHMMSSZZ." msgstr "DTSTART をネイティブの日時にすることができません。;TZINFO= or YYYYMMDDTHHMMSSZZ を指定します。" -#: awx/api/serializers.py:4532 +#: awx/api/serializers.py:4561 msgid "Multiple DTSTART is not supported." msgstr "複数の DTSTART はサポートされません。" -#: awx/api/serializers.py:4534 +#: awx/api/serializers.py:4563 msgid "RRULE required in rrule." msgstr "RRULE が rrule で必要です。" -#: awx/api/serializers.py:4536 +#: awx/api/serializers.py:4565 msgid "Multiple RRULE is not supported." msgstr "複数の RRULE はサポートされません。" -#: awx/api/serializers.py:4538 +#: awx/api/serializers.py:4567 msgid "INTERVAL required in rrule." msgstr "INTERVAL が rrule で必要です。" -#: awx/api/serializers.py:4540 +#: awx/api/serializers.py:4569 msgid "SECONDLY is not supported." msgstr "SECONDLY はサポートされません。" -#: awx/api/serializers.py:4542 +#: awx/api/serializers.py:4571 msgid "Multiple BYMONTHDAYs not supported." msgstr "複数の BYMONTHDAY はサポートされません。" -#: awx/api/serializers.py:4544 +#: awx/api/serializers.py:4573 msgid "Multiple BYMONTHs not supported." msgstr "複数の BYMONTH はサポートされません。" -#: awx/api/serializers.py:4546 +#: awx/api/serializers.py:4575 msgid "BYDAY with numeric prefix not supported." msgstr "数字の接頭辞のある BYDAY はサポートされません。" -#: awx/api/serializers.py:4548 +#: awx/api/serializers.py:4577 msgid "BYYEARDAY not supported." msgstr "BYYEARDAY はサポートされません。" -#: awx/api/serializers.py:4550 +#: awx/api/serializers.py:4579 msgid "BYWEEKNO not supported." msgstr "BYWEEKNO はサポートされません。" -#: awx/api/serializers.py:4552 +#: awx/api/serializers.py:4581 msgid "RRULE may not contain both COUNT and UNTIL" msgstr "RRULE には COUNT と UNTIL の両方を含めることができません" -#: awx/api/serializers.py:4556 +#: awx/api/serializers.py:4585 msgid "COUNT > 999 is unsupported." msgstr "COUNT > 999 はサポートされません。" -#: awx/api/serializers.py:4560 +#: awx/api/serializers.py:4591 msgid "rrule parsing failed validation: {}" msgstr "rrule の構文解析で検証に失敗しました: {}" -#: awx/api/serializers.py:4601 +#: awx/api/serializers.py:4653 msgid "Inventory Source must be a cloud resource." msgstr "インベントリーソースはクラウドリソースでなければなりません。" -#: awx/api/serializers.py:4603 +#: awx/api/serializers.py:4655 msgid "Manual Project cannot have a schedule set." msgstr "手動プロジェクトにはスケジュールを設定できません。" -#: awx/api/serializers.py:4616 +#: awx/api/serializers.py:4658 +msgid "" +"Inventory sources with `update_on_project_update` cannot be scheduled. " +"Schedule its source project `{}` instead." +msgstr "「update_on_project_update」が設定されたインベントリーソースはスケジュールできません。代わりのそのソースプロジェクト「{}」 をスケジュールします。" + +#: awx/api/serializers.py:4668 msgid "" "Count of jobs in the running or waiting state that are targeted for this " "instance" msgstr "このインスタンスにターゲット設定されている実行中または待機状態のジョブの数" -#: awx/api/serializers.py:4621 +#: awx/api/serializers.py:4673 msgid "Count of all jobs that target this instance" msgstr "このインスタンスをターゲットに設定するすべてのジョブの数" -#: awx/api/serializers.py:4654 +#: awx/api/serializers.py:4708 msgid "" "Count of jobs in the running or waiting state that are targeted for this " "instance group" msgstr "このインスタンスグループにターゲット設定されている実行中または待機状態のジョブの数" -#: awx/api/serializers.py:4659 +#: awx/api/serializers.py:4713 msgid "Count of all jobs that target this instance group" msgstr "このインスタンスグループをターゲットに設定するすべてのジョブの数" -#: awx/api/serializers.py:4667 +#: awx/api/serializers.py:4718 +msgid "Indicates whether instance group controls any other group" +msgstr "インスタンスグループが他のグループを制御するかどうかを指定します。" + +#: awx/api/serializers.py:4722 +msgid "" +"Indicates whether instances in this group are isolated.Isolated groups have " +"a designated controller group." +msgstr "このグループ内でインスタンスを分離させるかを指定します。分離されたグループには指定したコントローラーグループがあります。" + +#: awx/api/serializers.py:4727 +msgid "" +"Indicates whether instances in this group are containerized.Containerized " +"groups have a designated Openshift or Kubernetes cluster." +msgstr "このグループ内でインスタンスをコンテナー化するかを指定します。コンテナー化したグループには、指定の OpenShift または Kubernetes クラスターが含まれます。" + +#: awx/api/serializers.py:4735 msgid "Policy Instance Percentage" -msgstr "ポリシーインスタンスのパーセンテージ" +msgstr "ポリシーインスタンスの割合" -#: awx/api/serializers.py:4668 +#: awx/api/serializers.py:4736 msgid "" "Minimum percentage of all instances that will be automatically assigned to " "this group when new instances come online." -msgstr "新規インスタンスがオンライン状態になるとこのグループに自動的に割り当てられるすべてのインスタンスの最小パーセンテージです。" +msgstr "新規インスタンスがオンラインになると、このグループに自動的に最小限割り当てられるインスタンスの割合を選択します。" -#: awx/api/serializers.py:4673 +#: awx/api/serializers.py:4741 msgid "Policy Instance Minimum" msgstr "ポリシーインスタンスの最小値" -#: awx/api/serializers.py:4674 +#: awx/api/serializers.py:4742 msgid "" -"Static minimum number of Instances that will be automatically assign to this" -" group when new instances come online." -msgstr "新規インスタンスがオンライン状態になるとこのグループに自動的に割り当てられるインスタンスの静的な最小数です。" +"Static minimum number of Instances that will be automatically assign to this " +"group when new instances come online." +msgstr "新規インスタンスがオンラインになると、このグループに自動的に最小限割り当てられるインスタンス数を入力します。" -#: awx/api/serializers.py:4679 +#: awx/api/serializers.py:4747 msgid "Policy Instance List" msgstr "ポリシーインスタンスの一覧" -#: awx/api/serializers.py:4680 +#: awx/api/serializers.py:4748 msgid "List of exact-match Instances that will be assigned to this group" msgstr "このグループに割り当てられる完全一致のインスタンスの一覧" -#: awx/api/serializers.py:4702 +#: awx/api/serializers.py:4774 msgid "Duplicate entry {}." msgstr "重複するエントリー {}。" -#: awx/api/serializers.py:4704 +#: awx/api/serializers.py:4776 msgid "{} is not a valid hostname of an existing instance." msgstr "{} は既存インスタンスの有効なホスト名ではありません。" -#: awx/api/serializers.py:4706 awx/api/views.py:202 +#: awx/api/serializers.py:4778 awx/api/views/mixin.py:98 msgid "" -"Isolated instances may not be added or removed from instances groups via the" -" API." +"Isolated instances may not be added or removed from instances groups via the " +"API." msgstr "分離されたインスタンスは、API 経由でインスタンスグループから追加したり、削除したりすることができません。" -#: awx/api/serializers.py:4708 awx/api/views.py:206 +#: awx/api/serializers.py:4780 awx/api/views/mixin.py:102 msgid "Isolated instance group membership may not be managed via the API." msgstr "分離されたインスタンスグループのメンバーシップは API で管理できません。" -#: awx/api/serializers.py:4713 +#: awx/api/serializers.py:4782 awx/api/serializers.py:4787 +#: awx/api/serializers.py:4792 +msgid "Containerized instances may not be managed via the API" +msgstr "コンテナー化されたインスタンスは API で管理されないことがあります" + +#: awx/api/serializers.py:4797 msgid "tower instance group name may not be changed." -msgstr "tower インスタンスグループ名は変更できません。" +msgstr "Tower のインスタンスグループ名は変更できません。" + +#: awx/api/serializers.py:4802 +msgid "Only Kubernetes credentials can be associated with an Instance Group" +msgstr "インスタンスグループに関連付けることができる Kubernetes 認証情報のみです" -#: awx/api/serializers.py:4783 +#: awx/api/serializers.py:4841 +msgid "" +"When present, shows the field name of the role or relationship that changed." +msgstr "これがある場合には、変更された関係またはロールのフィールド名を表示します。" + +#: awx/api/serializers.py:4843 +msgid "" +"When present, shows the model on which the role or relationship was defined." +msgstr "これがある場合には、ロールまたは関係が定義されているモデルを表示します。" + +#: awx/api/serializers.py:4876 msgid "" "A summary of the new and changed values when an object is created, updated, " "or deleted" msgstr "オブジェクトの作成、更新または削除時の新規値および変更された値の概要" -#: awx/api/serializers.py:4785 +#: awx/api/serializers.py:4878 msgid "" "For create, update, and delete events this is the object type that was " "affected. For associate and disassociate events this is the object type " "associated or disassociated with object2." -msgstr "" -"作成、更新、および削除イベントの場合、これは影響を受けたオブジェクトタイプになります。関連付けおよび関連付け解除イベントの場合、これは object2 " -"に関連付けられたか、またはその関連付けが解除されたオブジェクトタイプになります。" +msgstr "作成、更新、および削除イベントの場合、これは影響を受けたオブジェクトタイプになります。関連付けおよび関連付け解除イベントの場合、これは object2 に関連付けられたか、またはその関連付けが解除されたオブジェクトタイプになります。" -#: awx/api/serializers.py:4788 +#: awx/api/serializers.py:4881 msgid "" "Unpopulated for create, update, and delete events. For associate and " -"disassociate events this is the object type that object1 is being associated" -" with." -msgstr "" -"作成、更新、および削除イベントの場合は設定されません。関連付けおよび関連付け解除イベントの場合、これは object1 " -"が関連付けられるオブジェクトタイプになります。" +"disassociate events this is the object type that object1 is being associated " +"with." +msgstr "作成、更新、および削除イベントの場合は設定されません。関連付けおよび関連付け解除イベントの場合、これは object1 が関連付けられるオブジェクトタイプになります。" -#: awx/api/serializers.py:4791 +#: awx/api/serializers.py:4884 msgid "The action taken with respect to the given object(s)." msgstr "指定されたオブジェクトについて実行されたアクション。" -#: awx/api/views.py:119 -msgid "Your license does not allow use of the activity stream." -msgstr "お使いのライセンスではアクティビティーストリームを使用できません。" - -#: awx/api/views.py:129 -msgid "Your license does not permit use of system tracking." -msgstr "お使いのライセンスではシステムトラッキングを使用できません。" - -#: awx/api/views.py:139 -msgid "Your license does not allow use of workflows." -msgstr "お使いのライセンスではワークフローを使用できません。" - -#: awx/api/views.py:153 -msgid "Cannot delete job resource when associated workflow job is running." -msgstr "関連付けられたワークフロージョブが実行中の場合、ジョブリソースを削除できません。" - -#: awx/api/views.py:158 -msgid "Cannot delete running job resource." -msgstr "実行中のジョブリソースを削除できません。" - -#: awx/api/views.py:163 -msgid "Job has not finished processing events." -msgstr "ジョブはイベント処理を終了していません。" - -#: awx/api/views.py:257 -msgid "Related job {} is still processing events." -msgstr "関連するジョブ {} は依然としてイベントを処理しています。" - -#: awx/api/views.py:264 awx/templates/rest_framework/api.html:28 -msgid "REST API" -msgstr "REST API" - -#: awx/api/views.py:275 awx/templates/rest_framework/api.html:4 -msgid "AWX REST API" -msgstr "AWX REST API" - -#: awx/api/views.py:288 -msgid "API OAuth 2 Authorization Root" -msgstr "API OAuth 2 認証ルート" - -#: awx/api/views.py:353 -msgid "Version 1" -msgstr "バージョン 1" - -#: awx/api/views.py:357 -msgid "Version 2" -msgstr "バージョン 2" - -#: awx/api/views.py:366 -msgid "Ping" -msgstr "Ping" - -#: awx/api/views.py:397 awx/conf/apps.py:10 -msgid "Configuration" -msgstr "設定" - -#: awx/api/views.py:454 -msgid "Invalid license data" -msgstr "無効なライセンスデータ" - -#: awx/api/views.py:456 -msgid "Missing 'eula_accepted' property" -msgstr "'eula_accepted' プロパティーがありません" - -#: awx/api/views.py:460 -msgid "'eula_accepted' value is invalid" -msgstr "'eula_accepted' 値は無効です。" - -#: awx/api/views.py:463 -msgid "'eula_accepted' must be True" -msgstr "'eula_accepted' は True でなければなりません" - -#: awx/api/views.py:470 -msgid "Invalid JSON" -msgstr "無効な JSON" - -#: awx/api/views.py:478 -msgid "Invalid License" -msgstr "無効なライセンス" - -#: awx/api/views.py:488 -msgid "Invalid license" -msgstr "無効なライセンス" - -#: awx/api/views.py:496 -#, python-format -msgid "Failed to remove license (%s)" -msgstr "ライセンスの削除に失敗しました (%s)" +#: awx/api/views/__init__.py:185 +msgid "Not found." +msgstr "見つかりません" -#: awx/api/views.py:501 +#: awx/api/views/__init__.py:193 msgid "Dashboard" msgstr "ダッシュボード" -#: awx/api/views.py:600 +#: awx/api/views/__init__.py:290 msgid "Dashboard Jobs Graphs" msgstr "ダッシュボードのジョブグラフ" -#: awx/api/views.py:636 +#: awx/api/views/__init__.py:326 #, python-format msgid "Unknown period \"%s\"" msgstr "不明な期間 \"%s\"" -#: awx/api/views.py:650 +#: awx/api/views/__init__.py:340 msgid "Instances" msgstr "インスタンス" -#: awx/api/views.py:658 +#: awx/api/views/__init__.py:348 msgid "Instance Detail" msgstr "インスタンスの詳細" -#: awx/api/views.py:678 +#: awx/api/views/__init__.py:365 msgid "Instance Jobs" msgstr "インスタンスジョブ" -#: awx/api/views.py:692 +#: awx/api/views/__init__.py:379 msgid "Instance's Instance Groups" msgstr "インスタンスのインスタンスグループ" -#: awx/api/views.py:701 +#: awx/api/views/__init__.py:388 msgid "Instance Groups" msgstr "インスタンスグループ" -#: awx/api/views.py:709 +#: awx/api/views/__init__.py:396 msgid "Instance Group Detail" msgstr "インスタンスグループの詳細" -#: awx/api/views.py:717 +#: awx/api/views/__init__.py:411 msgid "Isolated Groups can not be removed from the API" msgstr "分離されたグループは API から削除できません" -#: awx/api/views.py:719 +#: awx/api/views/__init__.py:413 msgid "" "Instance Groups acting as a controller for an Isolated Group can not be " "removed from the API" msgstr "分離されたグループのコントローラーとして動作するインスタンスグループは API から削除できません" -#: awx/api/views.py:725 +#: awx/api/views/__init__.py:419 msgid "Instance Group Running Jobs" msgstr "ジョブを実行しているインスタンスグループ" -#: awx/api/views.py:734 +#: awx/api/views/__init__.py:428 msgid "Instance Group's Instances" msgstr "インスタンスグループのインスタンス" -#: awx/api/views.py:744 +#: awx/api/views/__init__.py:438 msgid "Schedules" msgstr "スケジュール" -#: awx/api/views.py:758 +#: awx/api/views/__init__.py:452 msgid "Schedule Recurrence Rule Preview" msgstr "繰り返しルールプレビューのスケジュール" -#: awx/api/views.py:805 +#: awx/api/views/__init__.py:499 msgid "Cannot assign credential when related template is null." msgstr "関連するテンプレートが null の場合は認証情報を割り当てることができません。" -#: awx/api/views.py:810 +#: awx/api/views/__init__.py:504 msgid "Related template cannot accept {} on launch." msgstr "関連するテンプレートは起動時に {} を受け入れません。" -#: awx/api/views.py:812 +#: awx/api/views/__init__.py:506 msgid "" -"Credential that requires user input on launch cannot be used in saved launch" -" configuration." +"Credential that requires user input on launch cannot be used in saved launch " +"configuration." msgstr "起動時にユーザー入力を必要とする認証情報は保存された起動設定で使用できません。" -#: awx/api/views.py:820 +#: awx/api/views/__init__.py:512 +msgid "Related template is not configured to accept credentials on launch." +msgstr "関連するテンプレートは起動時に認証情報を受け入れるよう設定されていません。" + +#: awx/api/views/__init__.py:514 #, python-brace-format msgid "" "This launch configuration already provides a {credential_type} credential." msgstr "この起動設定は {credential_type} 認証情報をすでに指定しています。" -#: awx/api/views.py:823 +#: awx/api/views/__init__.py:517 #, python-brace-format msgid "Related template already uses {credential_type} credential." msgstr "関連するテンプレートは {credential_type} 認証情報をすでに使用しています。" -#: awx/api/views.py:841 +#: awx/api/views/__init__.py:535 msgid "Schedule Jobs List" msgstr "スケジュールジョブの一覧" -#: awx/api/views.py:996 -msgid "Your license only permits a single organization to exist." -msgstr "お使いのライセンスでは、単一組織のみの存在が許可されます。" - -#: awx/api/views.py:1223 awx/api/views.py:5106 +#: awx/api/views/__init__.py:619 awx/api/views/__init__.py:4420 msgid "" "You cannot assign an Organization participation role as a child role for a " "Team." msgstr "組織の参加ロールをチームの子ロールとして割り当てることができません。" -#: awx/api/views.py:1227 awx/api/views.py:5120 +#: awx/api/views/__init__.py:623 awx/api/views/__init__.py:4434 msgid "You cannot grant system-level permissions to a team." msgstr "システムレベルのパーミッションをチームに付与できません。" -#: awx/api/views.py:1234 awx/api/views.py:5112 +#: awx/api/views/__init__.py:630 awx/api/views/__init__.py:4426 msgid "" "You cannot grant credential access to a team when the Organization field " "isn't set, or belongs to a different organization" msgstr "組織フィールドが設定されていないか、または別の組織に属する場合に認証情報のアクセス権をチームに付与できません" -#: awx/api/views.py:1348 +#: awx/api/views/__init__.py:732 msgid "Project Schedules" msgstr "プロジェクトのスケジュール" -#: awx/api/views.py:1359 +#: awx/api/views/__init__.py:743 msgid "Project SCM Inventory Sources" msgstr "プロジェクト SCM のインベントリーソース" -#: awx/api/views.py:1460 +#: awx/api/views/__init__.py:844 msgid "Project Update Events List" msgstr "プロジェクト更新イベント一覧" -#: awx/api/views.py:1474 +#: awx/api/views/__init__.py:858 msgid "System Job Events List" msgstr "システムジョブイベント一覧" -#: awx/api/views.py:1488 -msgid "Inventory Update Events List" -msgstr "インベントリー更新イベント一覧" - -#: awx/api/views.py:1522 +#: awx/api/views/__init__.py:892 msgid "Project Update SCM Inventory Updates" msgstr "プロジェクト更新 SCM のインベントリー更新" -#: awx/api/views.py:1581 +#: awx/api/views/__init__.py:937 msgid "Me" msgstr "自分" -#: awx/api/views.py:1589 +#: awx/api/views/__init__.py:946 msgid "OAuth 2 Applications" msgstr "OAuth 2 アプリケーション" -#: awx/api/views.py:1598 +#: awx/api/views/__init__.py:955 msgid "OAuth 2 Application Detail" msgstr "OAuth 2 アプリケーションの詳細" -#: awx/api/views.py:1607 +#: awx/api/views/__init__.py:968 msgid "OAuth 2 Application Tokens" msgstr "OAuth 2 アプリケーショントークン" -#: awx/api/views.py:1629 +#: awx/api/views/__init__.py:990 msgid "OAuth2 Tokens" msgstr "OAuth2 トークン" -#: awx/api/views.py:1638 +#: awx/api/views/__init__.py:999 msgid "OAuth2 User Tokens" msgstr "OAuth2 ユーザートークン" -#: awx/api/views.py:1650 +#: awx/api/views/__init__.py:1011 msgid "OAuth2 User Authorized Access Tokens" msgstr "OAuth2 ユーザー認可アクセストークン" -#: awx/api/views.py:1665 +#: awx/api/views/__init__.py:1026 msgid "Organization OAuth2 Applications" msgstr "組織 OAuth2 アプリケーション" -#: awx/api/views.py:1677 +#: awx/api/views/__init__.py:1038 msgid "OAuth2 Personal Access Tokens" msgstr "OAuth2 パーソナルアクセストークン" -#: awx/api/views.py:1692 +#: awx/api/views/__init__.py:1053 msgid "OAuth Token Detail" msgstr "OAuth トークンの詳細" -#: awx/api/views.py:1752 awx/api/views.py:5073 +#: awx/api/views/__init__.py:1115 awx/api/views/__init__.py:4387 msgid "" "You cannot grant credential access to a user not in the credentials' " "organization" msgstr "認証情報の組織に属さないユーザーに認証情報のアクセス権を付与することはできません" -#: awx/api/views.py:1756 awx/api/views.py:5077 +#: awx/api/views/__init__.py:1119 awx/api/views/__init__.py:4391 msgid "You cannot grant private credential access to another user" msgstr "非公開の認証情報のアクセス権を別のユーザーに付与することはできません" -#: awx/api/views.py:1854 +#: awx/api/views/__init__.py:1217 #, python-format msgid "Cannot change %s." msgstr "%s を変更できません。" -#: awx/api/views.py:1860 +#: awx/api/views/__init__.py:1223 msgid "Cannot delete user." msgstr "ユーザーを削除できません。" -#: awx/api/views.py:1884 +#: awx/api/views/__init__.py:1247 msgid "Deletion not allowed for managed credential types" msgstr "管理されている認証情報タイプで削除は許可されません" -#: awx/api/views.py:1886 +#: awx/api/views/__init__.py:1249 msgid "Credential types that are in use cannot be deleted" msgstr "使用中の認証情報タイプを削除できません" -#: awx/api/views.py:2061 -msgid "Cannot delete inventory script." -msgstr "インベントリースクリプトを削除できません。" +#: awx/api/views/__init__.py:1362 +msgid "Deletion not allowed for managed credentials" +msgstr "管理されている認証情報では削除が許可されません" -#: awx/api/views.py:2152 -#, python-brace-format -msgid "{0}" -msgstr "{0}" +#: awx/api/views/__init__.py:1407 +msgid "External Credential Test" +msgstr "外部認証情報のテスト" + +#: awx/api/views/__init__.py:1442 +msgid "Credential Input Source Detail" +msgstr "認証情報の入力ソース詳細" + +#: awx/api/views/__init__.py:1450 awx/api/views/__init__.py:1458 +msgid "Credential Input Sources" +msgstr "認証情報の入力ソース" -#: awx/api/views.py:2256 +#: awx/api/views/__init__.py:1473 +msgid "External Credential Type Test" +msgstr "外部認証情報の種類テスト" + +#: awx/api/views/__init__.py:1539 msgid "The inventory for this host is already being deleted." msgstr "このホストのインベントリーはすでに削除されています。" -#: awx/api/views.py:2389 -msgid "Fact not found." -msgstr "ファクトが見つかりませんでした。" - -#: awx/api/views.py:2411 +#: awx/api/views/__init__.py:1656 msgid "SSLError while trying to connect to {}" msgstr "{} への接続試行中に SSL エラーが発生しました" -#: awx/api/views.py:2413 +#: awx/api/views/__init__.py:1658 msgid "Request to {} timed out." msgstr "{} の要求がタイムアウトになりました。" -#: awx/api/views.py:2415 +#: awx/api/views/__init__.py:1660 msgid "Unknown exception {} while trying to GET {}" msgstr "GET {} の試行中に不明の例外 {} が発生しました" -#: awx/api/views.py:2418 +#: awx/api/views/__init__.py:1664 msgid "" "Unauthorized access. Please check your Insights Credential username and " "password." msgstr "不正アクセスです。Insights 認証情報のユーザー名およびパスワードを確認してください。" -#: awx/api/views.py:2421 +#: awx/api/views/__init__.py:1668 msgid "" -"Failed to gather reports and maintenance plans from Insights API at URL {}. " -"Server responded with {} status code and message {}" -msgstr "" -"URL {} で Insights API からのレポートおよびメンテナンス計画を収集できませんでした。サーバーが {} " -"ステータスコードおよびメッセージ {} を出して応答しました。" +"Failed to access the Insights API at URL {}. Server responded with {} status " +"code and message {}" +msgstr "URL {} で Insights API にアクセスできませんでした。サーバーが {} ステータスコードおよびメッセージ {} を出して応答しました。" + +#: awx/api/views/__init__.py:1677 +msgid "Expected JSON response from Insights at URL {} but instead got {}" +msgstr "Insights からの JSON 応答を想定していましたが、URL {} の代わりに {} を取得しました。" -#: awx/api/views.py:2428 -msgid "Expected JSON response from Insights but instead got {}" -msgstr "Insights からの JSON 応答を予想していましたが、代わりに {} を取得しました。" +#: awx/api/views/__init__.py:1695 +msgid "Could not translate Insights system ID {} into an Insights platform ID." +msgstr "Insights システム ID {} から Insights プラットフォーム ID に変換できませんでした。" -#: awx/api/views.py:2435 +#: awx/api/views/__init__.py:1737 msgid "This host is not recognized as an Insights host." msgstr "このホストは Insights ホストとして認識されていません。" -#: awx/api/views.py:2440 +#: awx/api/views/__init__.py:1745 msgid "The Insights Credential for \"{}\" was not found." msgstr "\"{}\" の Insights 認証情報が見つかりませんでした。" -#: awx/api/views.py:2508 +#: awx/api/views/__init__.py:1824 msgid "Cyclical Group association." msgstr "循環的なグループの関連付け" -#: awx/api/views.py:2722 +#: awx/api/views/__init__.py:1990 +msgid "Inventory subset argument must be a string." +msgstr "インベントリーサブセットの引数は文字列でなければなりません。" + +#: awx/api/views/__init__.py:1994 +msgid "Subset does not use any supported syntax." +msgstr "サポートされている構文がサブセットで使用されていません。" + +#: awx/api/views/__init__.py:2044 msgid "Inventory Source List" msgstr "インベントリーソース一覧" -#: awx/api/views.py:2734 +#: awx/api/views/__init__.py:2056 msgid "Inventory Sources Update" msgstr "インベントリーソースの更新" -#: awx/api/views.py:2767 +#: awx/api/views/__init__.py:2089 msgid "Could not start because `can_update` returned False" msgstr "`can_update` が False を返したので開始できませんでした" -#: awx/api/views.py:2775 +#: awx/api/views/__init__.py:2097 msgid "No inventory sources to update." msgstr "更新するインベントリーソースがありません。" -#: awx/api/views.py:2804 +#: awx/api/views/__init__.py:2119 msgid "Inventory Source Schedules" msgstr "インベントリーソースのスケジュール" -#: awx/api/views.py:2832 +#: awx/api/views/__init__.py:2146 msgid "Notification Templates can only be assigned when source is one of {}." msgstr "ソースが {} のいずれかである場合、通知テンプレートのみを割り当てることができます。" -#: awx/api/views.py:2887 -msgid "Vault credentials are not yet supported for inventory sources." -msgstr "Vault 認証情報はインベントリーソースではまだサポートされていません。" - -#: awx/api/views.py:2892 -msgid "Source already has cloud credential assigned." -msgstr "ソースにはクラウド認証情報がすでに割り当てられています。" - -#: awx/api/views.py:3042 -msgid "Field is not allowed for use with v1 API." -msgstr "フィールドは v1 API での使用に許可されません。" - -#: awx/api/views.py:3052 -msgid "" -"'credentials' cannot be used in combination with 'credential', " -"'vault_credential', or 'extra_credentials'." -msgstr "" -"'credentials' は 'credential'、'vault_credential'、または 'extra_credentials' " -"との組み合わせで使用できません。" - -#: awx/api/views.py:3079 -msgid "Incorrect type. Expected {}, received {}." -msgstr "タイプが正しくありません。{} が予期されましたが、{} が受信されました。" +#: awx/api/views/__init__.py:2244 +msgid "Source already has credential assigned." +msgstr "ソースには認証情報がすでに割り当てられています。" -#: awx/api/views.py:3172 +#: awx/api/views/__init__.py:2460 msgid "Job Template Schedules" msgstr "ジョブテンプレートスケジュール" -#: awx/api/views.py:3190 awx/api/views.py:3201 -msgid "Your license does not allow adding surveys." -msgstr "お使いのライセンスでは Survey を追加できません。" - -#: awx/api/views.py:3220 +#: awx/api/views/__init__.py:2509 msgid "Field '{}' is missing from survey spec." msgstr "Survey の指定にフィールド '{}' がありません。" -#: awx/api/views.py:3222 +#: awx/api/views/__init__.py:2511 msgid "Expected {} for field '{}', received {} type." msgstr "フィールド '{}' の予期される {}。{} タイプを受信しました。" -#: awx/api/views.py:3226 +#: awx/api/views/__init__.py:2515 msgid "'spec' doesn't contain any items." msgstr "「spec」には項目が含まれません。" -#: awx/api/views.py:3235 +#: awx/api/views/__init__.py:2529 #, python-format msgid "Survey question %s is not a json object." msgstr "Survey の質問 %s は json オブジェクトではありません。" -#: awx/api/views.py:3237 -#, python-format -msgid "'type' missing from survey question %s." -msgstr "Survey の質問 %s に「type」がありません。" - -#: awx/api/views.py:3239 -#, python-format -msgid "'question_name' missing from survey question %s." -msgstr "Survey の質問 %s に「question_name」がありません。" +#: awx/api/views/__init__.py:2532 +#, python-brace-format +msgid "'{field_name}' missing from survey question {idx}" +msgstr "Survey の質問 {idx} に '{field_name}' がありません。" -#: awx/api/views.py:3241 -#, python-format -msgid "'variable' missing from survey question %s." -msgstr "Survey の質問 %s に「variable」がありません。" +#: awx/api/views/__init__.py:2542 +#, python-brace-format +msgid "'{field_name}' in survey question {idx} expected to be {type_label}." +msgstr "Survey の質問 {idx} の '{field_name}' は {type_label} である必要があります。" -#: awx/api/views.py:3243 +#: awx/api/views/__init__.py:2546 #, python-format msgid "'variable' '%(item)s' duplicated in survey question %(survey)s." -msgstr "Survey の質問%(survey)s で「variable」の「%(item)s」が重複しています。" +msgstr "Survey の質問 %(survey)s で '変数' '%(item)s' が重複しています。" -#: awx/api/views.py:3248 -#, python-format -msgid "'required' missing from survey question %s." -msgstr "Survey の質問 %s に「required」がありません。" +#: awx/api/views/__init__.py:2556 +#, python-brace-format +msgid "" +"'{survey_item[type]}' in survey question {idx} is not one of " +"'{allowed_types}' allowed question types." +msgstr "Survey の質問 {idx} の '{survey_item[type]}' は、'{allowed_types}' で許可されている質問タイプではありません。" -#: awx/api/views.py:3253 +#: awx/api/views/__init__.py:2566 #, python-brace-format msgid "" -"Value {question_default} for '{variable_name}' expected to be a string." -msgstr "'{variable_name}' の値 {question_default} は文字列であることが予想されます。" +"Default value {survey_item[default]} in survey question {idx} expected to be " +"{type_label}." +msgstr "Survey の質問 {idx} のデフォルト値 {survey_item[default]} は、{type_label} である必要があります。" + +#: awx/api/views/__init__.py:2576 +#, python-brace-format +msgid "The {min_or_max} limit in survey question {idx} expected to be integer." +msgstr "Survey の質問 {idx} の {min_or_max} の制限は整数である必要があります。" + +#: awx/api/views/__init__.py:2586 +#, python-brace-format +msgid "Survey question {idx} of type {survey_item[type]} must specify choices." +msgstr "タイプ {survey_item[type]} の Survey の質問 {idx} には選択肢を指定する必要があります。" + +#: awx/api/views/__init__.py:2600 +msgid "Multiple Choice (Single Select) can only have one default value." +msgstr "選択肢方式 (単一の選択) では、デフォルト値を 1 つだけ使用できます。" + +#: awx/api/views/__init__.py:2604 +msgid "Default choice must be answered from the choices listed." +msgstr "デフォルトで指定されている選択項目は、一覧から回答する必要があります。" -#: awx/api/views.py:3263 +#: awx/api/views/__init__.py:2613 #, python-brace-format msgid "" "$encrypted$ is a reserved keyword for password question defaults, survey " -"question {question_position} is type {question_type}." -msgstr "" -"$encrypted$ はパスワードの質問のデフォルトの予約されたキーワードで、Survey の質問 {question_position} はタイプ " -"{question_type} です。" +"question {idx} is type {survey_item[type]}." +msgstr "$encrypted$ は、デフォルト設定されているパスワードの質問に予約されたキーワードで、Survey の質問 {idx} は {survey_item[type]} タイプです。" -#: awx/api/views.py:3279 +#: awx/api/views/__init__.py:2627 #, python-brace-format msgid "" "$encrypted$ is a reserved keyword, may not be used for new default in " -"position {question_position}." -msgstr "$encrypted$ は予約されたキーワードで、位置 {question_position} の新規デフォルトに使用できません。" +"position {idx}." +msgstr "$encrypted$ は予約されたキーワードで、位置 {idx} の新規デフォルトに使用できません。" -#: awx/api/views.py:3353 +#: awx/api/views/__init__.py:2699 #, python-brace-format msgid "Cannot assign multiple {credential_type} credentials." msgstr "複数の {credential_type} 認証情報を割り当てることができません。" -#: awx/api/views.py:3357 +#: awx/api/views/__init__.py:2703 msgid "Cannot assign a Credential of kind `{}`." msgstr "`{}`の種類の認証情報を割り当てることができません。" -#: awx/api/views.py:3374 -msgid "Extra credentials must be network or cloud." -msgstr "追加の認証情報はネットワークまたはクラウドにする必要があります。" - -#: awx/api/views.py:3396 +#: awx/api/views/__init__.py:2726 msgid "Maximum number of labels for {} reached." msgstr "{} のラベルの最大数に達しました。" -#: awx/api/views.py:3519 +#: awx/api/views/__init__.py:2849 msgid "No matching host could be found!" msgstr "一致するホストが見つかりませんでした!" -#: awx/api/views.py:3522 +#: awx/api/views/__init__.py:2852 msgid "Multiple hosts matched the request!" msgstr "複数のホストが要求に一致しました!" -#: awx/api/views.py:3527 +#: awx/api/views/__init__.py:2857 msgid "Cannot start automatically, user input required!" msgstr "自動的に開始できません。ユーザー入力が必要です!" -#: awx/api/views.py:3534 +#: awx/api/views/__init__.py:2865 msgid "Host callback job already pending." msgstr "ホストのコールバックジョブがすでに保留中です。" -#: awx/api/views.py:3549 awx/api/views.py:4336 +#: awx/api/views/__init__.py:2881 awx/api/views/__init__.py:3632 msgid "Error starting job!" msgstr "ジョブの開始時にエラーが発生しました!" -#: awx/api/views.py:3669 -#, python-brace-format -msgid "Cannot associate {0} when {1} have been associated." -msgstr "{1} が関連付けられている場合に {0} を関連付けることはできません。" - -#: awx/api/views.py:3694 -msgid "Multiple parent relationship not allowed." -msgstr "複数の親関係は許可されません。" - -#: awx/api/views.py:3699 +#: awx/api/views/__init__.py:3005 awx/api/views/__init__.py:3025 msgid "Cycle detected." msgstr "サイクルが検出されました。" -#: awx/api/views.py:3902 +#: awx/api/views/__init__.py:3017 +msgid "Relationship not allowed." +msgstr "リレーションシップは許可されていません。" + +#: awx/api/views/__init__.py:3246 +msgid "Cannot relaunch slice workflow job orphaned from job template." +msgstr "ジョブテンプレートから孤立しているスライスされたワークフロージョブを再起動することはできません。" + +#: awx/api/views/__init__.py:3248 +msgid "Cannot relaunch sliced workflow job after slice count has changed." +msgstr "スライス数を変更した後は、スライスされたワークフロージョブを再起動することはできません。" + +#: awx/api/views/__init__.py:3281 msgid "Workflow Job Template Schedules" msgstr "ワークフロージョブテンプレートのスケジュール" -#: awx/api/views.py:4038 awx/api/views.py:4740 +#: awx/api/views/__init__.py:3424 awx/api/views/__init__.py:4055 msgid "Superuser privileges needed." msgstr "スーパーユーザー権限が必要です。" -#: awx/api/views.py:4071 +#: awx/api/views/__init__.py:3457 msgid "System Job Template Schedules" msgstr "システムジョブテンプレートのスケジュール" -#: awx/api/views.py:4129 -msgid "POST not allowed for Job launching in version 2 of the api" -msgstr "POST は API のバージョン 2 でのジョブの起動では許可されません" - -#: awx/api/views.py:4153 awx/api/views.py:4159 -msgid "PUT not allowed for Job Details in version 2 of the API" -msgstr "PUT は API のバージョン 2 でのジョブ詳細では許可されません" - -#: awx/api/views.py:4319 +#: awx/api/views/__init__.py:3615 #, python-brace-format msgid "Wait until job finishes before retrying on {status_value} hosts." msgstr "ジョブの終了を待機してから {status_value} ホストで再試行します。" -#: awx/api/views.py:4324 +#: awx/api/views/__init__.py:3620 #, python-brace-format msgid "Cannot retry on {status_value} hosts, playbook stats not available." msgstr "Playbook 統計を利用できないため、{status_value} ホストで再試行できません。" -#: awx/api/views.py:4329 +#: awx/api/views/__init__.py:3625 #, python-brace-format msgid "Cannot relaunch because previous job had 0 {status_value} hosts." -msgstr "直前のジョブにあるのが 0 {status_value} ホストであるため、再起動できません。" +msgstr "直前のジョブにあるのが 0 {status_value} ホストがあるため、再起動できません。" -#: awx/api/views.py:4358 +#: awx/api/views/__init__.py:3654 msgid "Cannot create schedule because job requires credential passwords." msgstr "ジョブには認証情報パスワードが必要なため、スケジュールを削除できません。" -#: awx/api/views.py:4363 +#: awx/api/views/__init__.py:3659 msgid "Cannot create schedule because job was launched by legacy method." msgstr "ジョブがレガシー方式で起動したため、スケジュールを作成できません。" -#: awx/api/views.py:4365 +#: awx/api/views/__init__.py:3661 msgid "Cannot create schedule because a related resource is missing." msgstr "関連するリソースがないため、スケジュールを作成できません。" -#: awx/api/views.py:4420 +#: awx/api/views/__init__.py:3716 msgid "Job Host Summaries List" msgstr "ジョブホスト概要一覧" -#: awx/api/views.py:4469 +#: awx/api/views/__init__.py:3770 msgid "Job Event Children List" msgstr "ジョブイベント子一覧" -#: awx/api/views.py:4479 +#: awx/api/views/__init__.py:3786 msgid "Job Event Hosts List" msgstr "ジョブイベントホスト一覧" -#: awx/api/views.py:4488 +#: awx/api/views/__init__.py:3801 msgid "Job Events List" msgstr "ジョブイベント一覧" -#: awx/api/views.py:4697 +#: awx/api/views/__init__.py:4012 msgid "Ad Hoc Command Events List" msgstr "アドホックコマンドイベント一覧" -#: awx/api/views.py:4939 +#: awx/api/views/__init__.py:4257 msgid "Delete not allowed while there are pending notifications" msgstr "保留中の通知がある場合に削除は許可されません" -#: awx/api/views.py:4947 +#: awx/api/views/__init__.py:4265 msgid "Notification Template Test" msgstr "通知テンプレートテスト" -#: awx/conf/conf.py:20 -msgid "Bud Frogs" -msgstr "Bud Frogs" +#: awx/api/views/__init__.py:4525 awx/api/views/__init__.py:4540 +msgid "User does not have permission to approve or deny this workflow." +msgstr "このワークフローを承認または拒否するパーミッションはありません。" -#: awx/conf/conf.py:21 -msgid "Bunny" -msgstr "Bunny" +#: awx/api/views/__init__.py:4527 awx/api/views/__init__.py:4542 +msgid "This workflow step has already been approved or denied." +msgstr "このワークフローの手順はすでに承認または拒否されています。" -#: awx/conf/conf.py:22 -msgid "Cheese" -msgstr "Cheese" +#: awx/api/views/inventory.py:63 +msgid "Inventory Update Events List" +msgstr "インベントリー更新イベント一覧" -#: awx/conf/conf.py:23 -msgid "Daemon" -msgstr "Daemon" +#: awx/api/views/inventory.py:90 +msgid "Cannot delete inventory script." +msgstr "インベントリースクリプトを削除できません。" -#: awx/conf/conf.py:24 -msgid "Default Cow" -msgstr "Default Cow" +#: awx/api/views/inventory.py:137 +msgid "You cannot turn a regular inventory into a \"smart\" inventory." +msgstr "通常の在庫を「スマート」在庫に変えることはできません。" -#: awx/conf/conf.py:25 -msgid "Dragon" -msgstr "Dragon" +#: awx/api/views/inventory.py:150 +#, python-brace-format +msgid "{0}" +msgstr "{0}" -#: awx/conf/conf.py:26 -msgid "Elephant in Snake" -msgstr "Elephant in Snake" +#: awx/api/views/metrics.py:30 +msgid "Metrics" +msgstr "メトリックス" -#: awx/conf/conf.py:27 -msgid "Elephant" -msgstr "Elephant" +#: awx/api/views/mixin.py:46 +msgid "Cannot delete job resource when associated workflow job is running." +msgstr "関連付けられたワークフロージョブが実行中の場合、ジョブリソースを削除できません。" -#: awx/conf/conf.py:28 -msgid "Eyes" -msgstr "Eyes" +#: awx/api/views/mixin.py:51 +msgid "Cannot delete running job resource." +msgstr "実行中のジョブリソースを削除できません。" -#: awx/conf/conf.py:29 -msgid "Hello Kitty" -msgstr "Hello Kitty" +#: awx/api/views/mixin.py:56 +msgid "Job has not finished processing events." +msgstr "ジョブはイベント処理を終了していません。" -#: awx/conf/conf.py:30 -msgid "Kitty" -msgstr "Kitty" +#: awx/api/views/mixin.py:153 +msgid "Related job {} is still processing events." +msgstr "関連するジョブ {} は依然としてイベントを処理しています。" -#: awx/conf/conf.py:31 -msgid "Luke Koala" -msgstr "Luke Koala" +#: awx/api/views/organization.py:230 +#, python-brace-format +msgid "Credential must be a Galaxy credential, not {sub.credential_type.name}." +msgstr "認証情報は、{sub.credential_type.name} ではなく、Galaxy 認証情報にする必要があります。" -#: awx/conf/conf.py:32 -msgid "Meow" -msgstr "Meow" +#: awx/api/views/root.py:50 awx/templates/rest_framework/api.html:28 +msgid "REST API" +msgstr "REST API" -#: awx/conf/conf.py:33 -msgid "Milk" -msgstr "Milk" +#: awx/api/views/root.py:60 awx/templates/rest_framework/api.html:4 +msgid "AWX REST API" +msgstr "AWX REST API" + +#: awx/api/views/root.py:73 +msgid "API OAuth 2 Authorization Root" +msgstr "API OAuth 2 認証ルート" + +#: awx/api/views/root.py:140 +msgid "Version 2" +msgstr "バージョン 2" + +#: awx/api/views/root.py:149 +msgid "Ping" +msgstr "Ping" + +#: awx/api/views/root.py:181 awx/api/views/root.py:226 awx/conf/apps.py:10 +msgid "Configuration" +msgstr "Configuration (構成)" + +#: awx/api/views/root.py:203 awx/api/views/root.py:310 +msgid "Invalid License" +msgstr "無効なライセンス" + +#: awx/api/views/root.py:208 +msgid "The provided credentials are invalid (HTTP 401)." +msgstr "指定した認証情報は無効 (HTTP 401) です。" + +#: awx/api/views/root.py:210 +msgid "Unable to connect to proxy server." +msgstr "プロキシサーバーに接続できません。" + +#: awx/api/views/root.py:212 +msgid "Could not connect to subscription service." +msgstr "サブスクリプションサービスに接続できませんでした。" + +#: awx/api/views/root.py:286 +msgid "Invalid license data" +msgstr "無効なライセンスデータ" + +#: awx/api/views/root.py:288 +msgid "Missing 'eula_accepted' property" +msgstr "'eula_accepted' プロパティーがありません" + +#: awx/api/views/root.py:292 +msgid "'eula_accepted' value is invalid" +msgstr "'eula_accepted' 値は無効です。" + +#: awx/api/views/root.py:295 +msgid "'eula_accepted' must be True" +msgstr "'eula_accepted' は True でなければなりません" + +#: awx/api/views/root.py:302 +msgid "Invalid JSON" +msgstr "無効な JSON" + +#: awx/api/views/root.py:321 +msgid "Invalid license" +msgstr "無効なライセンス" + +#: awx/api/views/root.py:329 +msgid "Failed to remove license." +msgstr "ライセンスを削除できませんでした。" + +#: awx/api/views/webhooks.py:143 +msgid "Webhook previously received, aborting." +msgstr "すでに Webhook を受信しているため、中止します。" + +#: awx/conf/conf.py:20 +msgid "Bud Frogs" +msgstr "Bud Frogs" + +#: awx/conf/conf.py:21 +msgid "Bunny" +msgstr "Bunny" + +#: awx/conf/conf.py:22 +msgid "Cheese" +msgstr "Cheese" + +#: awx/conf/conf.py:23 +msgid "Daemon" +msgstr "Daemon" + +#: awx/conf/conf.py:24 +msgid "Default Cow" +msgstr "Default Cow" + +#: awx/conf/conf.py:25 +msgid "Dragon" +msgstr "Dragon" + +#: awx/conf/conf.py:26 +msgid "Elephant in Snake" +msgstr "Elephant in Snake" + +#: awx/conf/conf.py:27 +msgid "Elephant" +msgstr "Elephant" + +#: awx/conf/conf.py:28 +msgid "Eyes" +msgstr "Eyes" + +#: awx/conf/conf.py:29 +msgid "Hello Kitty" +msgstr "Hello Kitty" + +#: awx/conf/conf.py:30 +msgid "Kitty" +msgstr "Kitty" + +#: awx/conf/conf.py:31 +msgid "Luke Koala" +msgstr "Luke Koala" + +#: awx/conf/conf.py:32 +msgid "Meow" +msgstr "Meow" + +#: awx/conf/conf.py:33 +msgid "Milk" +msgstr "Milk" #: awx/conf/conf.py:34 msgid "Moofasa" @@ -1508,261 +1661,295 @@ msgstr "読み取り専用設定の例" msgid "Example setting that cannot be changed." msgstr "変更不可能な設定例" -#: awx/conf/conf.py:93 +#: awx/conf/conf.py:90 msgid "Example Setting" msgstr "設定例" -#: awx/conf/conf.py:94 +#: awx/conf/conf.py:91 msgid "Example setting which can be different for each user." msgstr "ユーザーごとに異なる設定例" -#: awx/conf/conf.py:95 awx/conf/registry.py:85 awx/conf/views.py:55 +#: awx/conf/conf.py:92 awx/conf/registry.py:81 awx/conf/views.py:56 msgid "User" msgstr "ユーザー" -#: awx/conf/fields.py:60 awx/sso/fields.py:595 +#: awx/conf/fields.py:63 awx/sso/fields.py:595 #, python-brace-format msgid "" -"Expected None, True, False, a string or list of strings but got {input_type}" -" instead." +"Expected None, True, False, a string or list of strings but got {input_type} " +"instead." msgstr "None、True、False、文字列または文字列の一覧が予期されましたが、{input_type} が取得されました。" #: awx/conf/fields.py:104 +#, python-brace-format +msgid "Expected list of strings but got {input_type} instead." +msgstr "期待値は文字列の一覧でしたが、{input_type} が取得されました。" + +#: awx/conf/fields.py:105 +#, python-brace-format +msgid "{path} is not a valid path choice." +msgstr "{path} は有効なパスではありません。" + +#: awx/conf/fields.py:149 msgid "Enter a valid URL" msgstr "無効な URL の入力" -#: awx/conf/fields.py:136 +#: awx/conf/fields.py:187 #, python-brace-format msgid "\"{input}\" is not a valid string." msgstr "\"{input}\" は有効な文字列ではありません。" -#: awx/conf/fields.py:151 +#: awx/conf/fields.py:202 #, python-brace-format -msgid "" -"Expected a list of tuples of max length 2 but got {input_type} instead." +msgid "Expected a list of tuples of max length 2 but got {input_type} instead." msgstr "最大の長さが 2 のタプルの一覧が予想されましたが、代わりに {input_type} を取得しました。" -#: awx/conf/license.py:22 -msgid "Your Tower license does not allow that." -msgstr "お使いの Tower ライセンスではこれを許可しません。" - -#: awx/conf/management/commands/migrate_to_database_settings.py:41 -msgid "Only show which settings would be commented/migrated." -msgstr "コメント/移行する設定についてのみ表示します。" - -#: awx/conf/management/commands/migrate_to_database_settings.py:48 -msgid "" -"Skip over settings that would raise an error when commenting/migrating." -msgstr "コメント/移行時にエラーを発生させる設定をスキップします。" - -#: awx/conf/management/commands/migrate_to_database_settings.py:55 -msgid "Skip commenting out settings in files." -msgstr "ファイル内の設定のコメント化をスキップします。" - -#: awx/conf/management/commands/migrate_to_database_settings.py:62 -msgid "Skip migrating and only comment out settings in files." -msgstr "移行をスキップし、ファイルの設定のみをコメントアウトします。" - -#: awx/conf/management/commands/migrate_to_database_settings.py:68 -msgid "Backup existing settings files with this suffix." -msgstr "この接尾辞を持つ既存の設定ファイルをバックアップします。" - -#: awx/conf/registry.py:73 awx/conf/tests/unit/test_registry.py:169 -#: awx/conf/tests/unit/test_registry.py:192 -#: awx/conf/tests/unit/test_registry.py:196 -#: awx/conf/tests/unit/test_registry.py:201 -#: awx/conf/tests/unit/test_registry.py:208 +#: awx/conf/registry.py:73 awx/conf/tests/unit/test_registry.py:156 msgid "All" msgstr "すべて" -#: awx/conf/registry.py:74 awx/conf/tests/unit/test_registry.py:170 -#: awx/conf/tests/unit/test_registry.py:193 -#: awx/conf/tests/unit/test_registry.py:197 -#: awx/conf/tests/unit/test_registry.py:202 -#: awx/conf/tests/unit/test_registry.py:209 +#: awx/conf/registry.py:74 awx/conf/tests/unit/test_registry.py:157 msgid "Changed" msgstr "変更済み" -#: awx/conf/registry.py:86 +#: awx/conf/registry.py:82 msgid "User-Defaults" msgstr "ユーザー設定" -#: awx/conf/registry.py:154 +#: awx/conf/registry.py:143 msgid "This value has been set manually in a settings file." msgstr "この値は設定ファイルに手動で設定されました。" -#: awx/conf/tests/unit/test_registry.py:46 -#: awx/conf/tests/unit/test_registry.py:56 -#: awx/conf/tests/unit/test_registry.py:72 -#: awx/conf/tests/unit/test_registry.py:87 -#: awx/conf/tests/unit/test_registry.py:100 -#: awx/conf/tests/unit/test_registry.py:106 -#: awx/conf/tests/unit/test_registry.py:126 -#: awx/conf/tests/unit/test_registry.py:140 +#: awx/conf/tests/unit/test_registry.py:47 +#: awx/conf/tests/unit/test_registry.py:57 +#: awx/conf/tests/unit/test_registry.py:73 +#: awx/conf/tests/unit/test_registry.py:88 +#: awx/conf/tests/unit/test_registry.py:101 +#: awx/conf/tests/unit/test_registry.py:107 +#: awx/conf/tests/unit/test_registry.py:127 +#: awx/conf/tests/unit/test_registry.py:133 #: awx/conf/tests/unit/test_registry.py:146 -#: awx/conf/tests/unit/test_registry.py:159 -#: awx/conf/tests/unit/test_registry.py:171 -#: awx/conf/tests/unit/test_registry.py:180 -#: awx/conf/tests/unit/test_registry.py:198 -#: awx/conf/tests/unit/test_registry.py:210 -#: awx/conf/tests/unit/test_registry.py:219 -#: awx/conf/tests/unit/test_registry.py:225 -#: awx/conf/tests/unit/test_registry.py:237 -#: awx/conf/tests/unit/test_registry.py:245 -#: awx/conf/tests/unit/test_registry.py:288 -#: awx/conf/tests/unit/test_registry.py:306 -#: awx/conf/tests/unit/test_settings.py:79 -#: awx/conf/tests/unit/test_settings.py:97 -#: awx/conf/tests/unit/test_settings.py:112 -#: awx/conf/tests/unit/test_settings.py:127 -#: awx/conf/tests/unit/test_settings.py:143 -#: awx/conf/tests/unit/test_settings.py:156 -#: awx/conf/tests/unit/test_settings.py:173 -#: awx/conf/tests/unit/test_settings.py:189 -#: awx/conf/tests/unit/test_settings.py:200 -#: awx/conf/tests/unit/test_settings.py:216 -#: awx/conf/tests/unit/test_settings.py:237 -#: awx/conf/tests/unit/test_settings.py:259 -#: awx/conf/tests/unit/test_settings.py:285 -#: awx/conf/tests/unit/test_settings.py:299 -#: awx/conf/tests/unit/test_settings.py:323 +#: awx/conf/tests/unit/test_registry.py:158 +#: awx/conf/tests/unit/test_registry.py:167 +#: awx/conf/tests/unit/test_registry.py:173 +#: awx/conf/tests/unit/test_registry.py:185 +#: awx/conf/tests/unit/test_registry.py:192 +#: awx/conf/tests/unit/test_registry.py:234 +#: awx/conf/tests/unit/test_registry.py:252 +#: awx/conf/tests/unit/test_settings.py:73 +#: awx/conf/tests/unit/test_settings.py:91 +#: awx/conf/tests/unit/test_settings.py:106 +#: awx/conf/tests/unit/test_settings.py:121 +#: awx/conf/tests/unit/test_settings.py:137 +#: awx/conf/tests/unit/test_settings.py:150 +#: awx/conf/tests/unit/test_settings.py:167 +#: awx/conf/tests/unit/test_settings.py:183 +#: awx/conf/tests/unit/test_settings.py:194 +#: awx/conf/tests/unit/test_settings.py:210 +#: awx/conf/tests/unit/test_settings.py:231 +#: awx/conf/tests/unit/test_settings.py:254 +#: awx/conf/tests/unit/test_settings.py:268 +#: awx/conf/tests/unit/test_settings.py:292 +#: awx/conf/tests/unit/test_settings.py:312 +#: awx/conf/tests/unit/test_settings.py:329 #: awx/conf/tests/unit/test_settings.py:343 -#: awx/conf/tests/unit/test_settings.py:360 -#: awx/conf/tests/unit/test_settings.py:374 -#: awx/conf/tests/unit/test_settings.py:398 -#: awx/conf/tests/unit/test_settings.py:411 -#: awx/conf/tests/unit/test_settings.py:430 -#: awx/conf/tests/unit/test_settings.py:466 awx/main/conf.py:22 -#: awx/main/conf.py:32 awx/main/conf.py:43 awx/main/conf.py:53 -#: awx/main/conf.py:62 awx/main/conf.py:74 awx/main/conf.py:87 -#: awx/main/conf.py:100 awx/main/conf.py:125 +#: awx/conf/tests/unit/test_settings.py:367 +#: awx/conf/tests/unit/test_settings.py:380 +#: awx/conf/tests/unit/test_settings.py:399 +#: awx/conf/tests/unit/test_settings.py:435 awx/main/conf.py:23 +#: awx/main/conf.py:32 awx/main/conf.py:42 awx/main/conf.py:52 +#: awx/main/conf.py:64 awx/main/conf.py:77 awx/main/conf.py:90 +#: awx/main/conf.py:115 awx/main/conf.py:128 awx/main/conf.py:141 +#: awx/main/conf.py:153 awx/main/conf.py:161 awx/main/conf.py:172 +#: awx/main/conf.py:392 awx/main/conf.py:742 awx/main/conf.py:754 msgid "System" msgstr "システム" -#: awx/conf/tests/unit/test_registry.py:165 -#: awx/conf/tests/unit/test_registry.py:172 -#: awx/conf/tests/unit/test_registry.py:187 -#: awx/conf/tests/unit/test_registry.py:203 -#: awx/conf/tests/unit/test_registry.py:211 +#: awx/conf/tests/unit/test_registry.py:152 +#: awx/conf/tests/unit/test_registry.py:159 msgid "OtherSystem" msgstr "他のシステム" -#: awx/conf/views.py:47 +#: awx/conf/views.py:48 msgid "Setting Categories" msgstr "設定カテゴリー" -#: awx/conf/views.py:71 +#: awx/conf/views.py:70 msgid "Setting Detail" msgstr "設定の詳細" -#: awx/conf/views.py:166 +#: awx/conf/views.py:162 msgid "Logging Connectivity Test" msgstr "ロギング接続テスト" -#: awx/main/access.py:59 +#: awx/main/access.py:66 #, python-format msgid "Required related field %s for permission check." msgstr "パーミッションチェックに必要な関連フィールド %s です。" -#: awx/main/access.py:75 +#: awx/main/access.py:82 #, python-format msgid "Bad data found in related field %s." msgstr "関連フィールド %s に不正データが見つかりました。" -#: awx/main/access.py:302 +#: awx/main/access.py:331 msgid "License is missing." msgstr "ライセンスが見つかりません。" -#: awx/main/access.py:304 +#: awx/main/access.py:333 msgid "License has expired." msgstr "ライセンスの有効期限が切れました。" -#: awx/main/access.py:312 +#: awx/main/access.py:341 #, python-format msgid "License count of %s instances has been reached." msgstr "%s インスタンスのライセンス数に達しました。" -#: awx/main/access.py:314 +#: awx/main/access.py:343 #, python-format msgid "License count of %s instances has been exceeded." msgstr "%s インスタンスのライセンス数を超えました。" -#: awx/main/access.py:316 +#: awx/main/access.py:345 msgid "Host count exceeds available instances." msgstr "ホスト数が利用可能なインスタンスの上限を上回っています。" -#: awx/main/access.py:320 +#: awx/main/access.py:363 awx/main/access.py:372 #, python-format -msgid "Feature %s is not enabled in the active license." -msgstr "機能 %s はアクティブなライセンスで有効にされていません。" - -#: awx/main/access.py:322 -msgid "Features not found in active license." -msgstr "各種機能はアクティブなライセンスにありません。" +msgid "" +"You have already reached the maximum number of %s hosts allowed for your " +"organization. Contact your System Administrator for assistance." +msgstr "組織で許容できるホストの最大数 %s にすでに到達しています。システム管理者にお問い合わせください。" -#: awx/main/access.py:835 +#: awx/main/access.py:927 msgid "Unable to change inventory on a host." msgstr "ホストのインベントリーを変更できません。" -#: awx/main/access.py:852 awx/main/access.py:897 +#: awx/main/access.py:948 awx/main/access.py:990 msgid "Cannot associate two items from different inventories." msgstr "異なるインベントリーの 2 つの項目を関連付けることはできません。" -#: awx/main/access.py:885 +#: awx/main/access.py:978 msgid "Unable to change inventory on a group." msgstr "グループのインベントリーを変更できません。" -#: awx/main/access.py:1146 +#: awx/main/access.py:1261 msgid "Unable to change organization on a team." msgstr "チームの組織を変更できません。" -#: awx/main/access.py:1163 +#: awx/main/access.py:1277 msgid "The {} role cannot be assigned to a team" msgstr "{} ロールをチームに割り当てることができません" -#: awx/main/access.py:1165 -msgid "The admin_role for a User cannot be assigned to a team" -msgstr "ユーザーの admin_role をチームに割り当てることができません" +#: awx/main/access.py:1471 +msgid "Insufficient access to Job Template credentials." +msgstr "ジョブテンプレート認証情報へのアクセス権がありません。" + +#: awx/main/access.py:1635 awx/main/access.py:2059 +msgid "Job was launched with secret prompts provided by another user." +msgstr "別のユーザーのシークレットプロンプトで、ジョブが起動しました。" -#: awx/main/access.py:1531 awx/main/access.py:1965 -msgid "Job was launched with prompts provided by another user." -msgstr "ジョブは別のユーザーによって提供されるプロンプトで起動されています。" +#: awx/main/access.py:1644 +msgid "Job has been orphaned from its job template and organization." +msgstr "ジョブはジョブテンプレートおよび組織から孤立しています。" -#: awx/main/access.py:1551 -msgid "Job has been orphaned from its job template." -msgstr "ジョブはジョブテンプレートから孤立しています。" +#: awx/main/access.py:1646 +msgid "Job was launched with prompted fields you do not have access to." +msgstr "アクセス権のないプロンプトフィールドでジョブが起動されました。" -#: awx/main/access.py:1553 -msgid "Job was launched with unknown prompted fields." -msgstr "ジョブは不明のプロンプトが出されたフィールドで起動されています。" +#: awx/main/access.py:1648 +msgid "" +"Job was launched with unknown prompted fields. Organization admin " +"permissions required." +msgstr "不明なプロンプトフィールドでジョブが起動されました。組織の管理者権限が必要です。" -#: awx/main/access.py:1555 -msgid "Job was launched with prompted fields." -msgstr "ジョブはプロンプトが出されたフィールドで起動されています。" +#: awx/main/access.py:2049 +msgid "Workflow Job was launched with unknown prompts." +msgstr "ワークフロージョブは不明なプロンプトで起動されています。" -#: awx/main/access.py:1557 -msgid " Organization level permissions required." -msgstr "組織レベルのパーミッションが必要です。" +#: awx/main/access.py:2061 +msgid "Job was launched with prompts you lack access to." +msgstr "ジョブはアクセスできないプロンプトで起動されています。" -#: awx/main/access.py:1559 -msgid " You do not have permission to related resources." -msgstr "関連リソースに対するパーミッションがありません。" +#: awx/main/access.py:2063 +msgid "Job was launched with prompts no longer accepted." +msgstr "ジョブは受け入れられなくなったプロンプトで起動されています。" -#: awx/main/access.py:1979 +#: awx/main/access.py:2075 msgid "" "You do not have permission to the workflow job resources required for " "relaunch." msgstr "再起動に必要なワークフロージョブリソースへのパーミッションがありません。" +#: awx/main/analytics/collectors.py:36 +msgid "General platform configuration." +msgstr "一般的なプラットフォーム構成。" + +#: awx/main/analytics/collectors.py:68 +msgid "Counts of objects such as organizations, inventories, and projects" +msgstr "組織、在庫、プロジェクトなどのオブジェクトの数" + +#: awx/main/analytics/collectors.py:103 +msgid "Counts of users and teams by organization" +msgstr "組織別のユーザーとチームの数" + +#: awx/main/analytics/collectors.py:115 +msgid "Counts of credentials by credential type" +msgstr "認証情報タイプ別の認証情報の数" + +#: awx/main/analytics/collectors.py:127 +msgid "Inventories, their inventory sources, and host counts" +msgstr "インベントリー、そのインベントリソース、およびホスト数" + +#: awx/main/analytics/collectors.py:152 +msgid "Counts of projects by source control type" +msgstr "ソース管理タイプ別のプロジェクト数" + +#: awx/main/analytics/collectors.py:171 +msgid "Cluster topology and capacity" +msgstr "クラスターのトポロジーと容量" + +#: awx/main/analytics/collectors.py:197 +msgid "Counts of jobs by status" +msgstr "ステータス別のジョブ数" + +#: awx/main/analytics/collectors.py:207 +msgid "Counts of jobs by execution node" +msgstr "実行ノードごとのジョブ数" + +#: awx/main/analytics/collectors.py:222 +msgid "Metadata about the analytics collected" +msgstr "収集された分析に関するメタデータ" + +#: awx/main/analytics/collectors.py:285 +msgid "Automation task records" +msgstr "自動化タスクレコード" + +#: awx/main/analytics/collectors.py:314 +msgid "Data on jobs run" +msgstr "実行されたジョブに関するデータ" + +#: awx/main/analytics/collectors.py:351 +msgid "Data on job templates" +msgstr "ジョブテンプレートに関するデータ" + +#: awx/main/analytics/collectors.py:374 +msgid "Data on workflow runs" +msgstr "ワークフロー実行に関するデータ" + +#: awx/main/analytics/collectors.py:410 +msgid "Data on workflows" +msgstr "ワークフローに関するデータ" + #: awx/main/apps.py:8 msgid "Main" msgstr "メイン" -#: awx/main/conf.py:20 +#: awx/main/conf.py:21 msgid "Enable Activity Stream" msgstr "アクティビティーストリームの有効化" -#: awx/main/conf.py:21 +#: awx/main/conf.py:22 msgid "Enable capturing activity for the activity stream." msgstr "アクティビティーストリームのアクティビティーのキャプチャーを有効にします。" @@ -1776,1765 +1963,2134 @@ msgid "" "sync." msgstr "インベントリー同期の実行時にアクティビティーストリームのアクティビティーのキャプチャーを有効にします。" -#: awx/main/conf.py:40 +#: awx/main/conf.py:39 msgid "All Users Visible to Organization Admins" msgstr "組織管理者に表示されるすべてのユーザー" -#: awx/main/conf.py:41 +#: awx/main/conf.py:40 msgid "" "Controls whether any Organization Admin can view all users and teams, even " "those not associated with their Organization." msgstr "組織管理者が、それぞれの組織に関連付けられていないすべてのユーザーおよびチームを閲覧できるかどうかを制御します。" -#: awx/main/conf.py:50 +#: awx/main/conf.py:49 msgid "Organization Admins Can Manage Users and Teams" -msgstr "組織管理者はユーザーおよびチームを管理できます" +msgstr "組織管理者はユーザーおよびチームを管理できる" -#: awx/main/conf.py:51 +#: awx/main/conf.py:50 msgid "" "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." -msgstr "" -"組織管理者がユーザーおよびチームを作成し、管理する権限を持つようにするかどうかを制御します。LDAP または SAML " -"統合を使用している場合はこの機能を無効にする必要がある場合があります。" - -#: awx/main/conf.py:60 -msgid "Enable Administrator Alerts" -msgstr "管理者アラートの有効化" +msgstr "組織管理者がユーザーおよびチームを作成し、管理する権限を持つようにするかどうかを制御します。LDAP または SAML 統合を使用している場合はこの機能を無効にする必要がある場合があります。" #: awx/main/conf.py:61 -msgid "Email Admin users for system events that may require attention." -msgstr "管理者ユーザーに対し、注意が必要になる可能性のあるシステムイベントについてのメールを送信します。" - -#: awx/main/conf.py:71 msgid "Base URL of the Tower host" msgstr "Tower ホストのベース URL" -#: awx/main/conf.py:72 +#: awx/main/conf.py:62 msgid "" -"This setting is used by services like notifications to render a valid url to" -" the Tower host." +"This setting is used by services like notifications to render a valid url to " +"the Tower host." msgstr "この設定は、有効な URL を Tower ホストにレンダリングする通知などのサービスで使用されます。" -#: awx/main/conf.py:81 +#: awx/main/conf.py:71 msgid "Remote Host Headers" msgstr "リモートホストヘッダー" -#: awx/main/conf.py:82 +#: awx/main/conf.py:72 msgid "" "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." -msgstr "" -"リモートホスト名または IP を判別するために検索する HTTP " -"ヘッダーおよびメタキーです。リバースプロキシーの後ろの場合は、\"HTTP_X_FORWARDED_FOR\" " -"のように項目をこの一覧に追加します。詳細は、Administrator Guide の「Proxy Support」セクションを参照してください。" +msgstr "リモートホスト名または IP を判別するために検索する HTTP ヘッダーおよびメタキーです。リバースプロキシーの後ろの場合は、\"HTTP_X_FORWARDED_FOR\" のように項目をこの一覧に追加します。詳細は、Administrator Guide の「Proxy Support」セクションを参照してください。" -#: awx/main/conf.py:94 -msgid "Proxy IP Whitelist" -msgstr "プロキシー IP ホワイトリスト" +#: awx/main/conf.py:84 +msgid "Proxy IP Allowed List" +msgstr "プロキシ IP 許可リスト" -#: awx/main/conf.py:95 +#: awx/main/conf.py:85 msgid "" "If Tower is behind a reverse proxy/load balancer, use this setting to " -"whitelist the proxy IP addresses from which Tower should trust custom " +"configure the proxy IP addresses from which Tower 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')" -msgstr "" -"Tower がリバースプロキシー/ロードバランサーの背後にある場合、この設定を使用し、Tower がカスタム REMOTE_HOST_HEADERS " -"ヘッダーの値を信頼するのに使用するプロキシー IP アドレスをホワイトリスト化します。この設定が空のリスト (デフォルト) " -"の場合、REMOTE_HOST_HEADERS で指定されるヘッダーは条件なしに信頼されます')" +msgstr "Tower がリバースプロキシー/ロードバランサーの背後にある場合は、この設定を使用して、Tower がカスタム REMOTE_HOST_HEADERS ヘッダーの値を信頼するのに使用するプロキシー IP アドレスを設定します。この設定が空のリスト (デフォルト) の場合は、REMOTE_HOST_HEADERS で指定されるヘッダーは条件なしに信頼されます')" -#: awx/main/conf.py:121 +#: awx/main/conf.py:111 msgid "License" msgstr "ライセンス" -#: awx/main/conf.py:122 +#: awx/main/conf.py:112 msgid "" -"The license controls which features and functionality are enabled. Use " -"/api/v1/config/ to update or change the license." -msgstr "" -"ライセンスによって、有効にされる特長および機能が制御されます。ライセンスを更新または変更するには、/api/v1/config/ を使用します。" +"The license controls which features and functionality are enabled. Use /api/" +"v2/config/ to update or change the license." +msgstr "ライセンスで、どの特徴および機能を有効にするかを管理します。/api/v2/config/ を使用してライセンスを更新または変更してください。" + +#: awx/main/conf.py:126 +msgid "Red Hat customer username" +msgstr "Red Hat のお客様ユーザー名" + +#: awx/main/conf.py:127 +msgid "" +"This username is used to retrieve license information and to send Automation " +"Analytics" +msgstr "このユーザー名を使用して、ライセンス情報の受信、自動化アナリティクスの送信を行います。" -#: awx/main/conf.py:132 +#: awx/main/conf.py:139 +msgid "Red Hat customer password" +msgstr "Red Hat のお客様パスワード" + +#: awx/main/conf.py:140 +msgid "" +"This password is used to retrieve license information and to send Automation " +"Analytics" +msgstr "このパスワードを使用して、ライセンス情報の受信、自動化アナリティクスの送信を行います。" + +#: awx/main/conf.py:151 +msgid "Automation Analytics upload URL." +msgstr "自動化アナリティクスのアップロード用 URL" + +#: awx/main/conf.py:152 +msgid "" +"This setting is used to to configure data collection for the Automation " +"Analytics dashboard" +msgstr "この設定は、自動化アナリティクスダッシュボードのデータ収集を設定するのに使用します。" + +#: awx/main/conf.py:160 +msgid "Unique identifier for an AWX/Tower installation" +msgstr "AWX/Tower インストールの一意識別子" + +#: awx/main/conf.py:169 +msgid "Custom virtual environment paths" +msgstr "カスタムの仮想環境パス" + +#: awx/main/conf.py:170 +msgid "" +"Paths where Tower will look for custom virtual environments (in addition to /" +"var/lib/awx/venv/). Enter one path per line." +msgstr "Tower が (/var/lib/awx/venv/ 以外に) カスタムの仮想環境を検索するパス。1 行にパスを 1 つ入力してください。" + +#: awx/main/conf.py:180 msgid "Ansible Modules Allowed for Ad Hoc Jobs" msgstr "アドホックジョブで許可される Ansible モジュール" -#: awx/main/conf.py:133 +#: awx/main/conf.py:181 msgid "List of modules allowed to be used by ad-hoc jobs." msgstr "アドホックジョブで使用できるモジュール一覧。" -#: awx/main/conf.py:134 awx/main/conf.py:156 awx/main/conf.py:165 -#: awx/main/conf.py:176 awx/main/conf.py:186 awx/main/conf.py:196 -#: awx/main/conf.py:206 awx/main/conf.py:217 awx/main/conf.py:229 -#: awx/main/conf.py:241 awx/main/conf.py:254 awx/main/conf.py:266 -#: awx/main/conf.py:276 awx/main/conf.py:287 awx/main/conf.py:298 -#: awx/main/conf.py:308 awx/main/conf.py:318 awx/main/conf.py:330 -#: awx/main/conf.py:342 awx/main/conf.py:354 awx/main/conf.py:368 +#: awx/main/conf.py:182 awx/main/conf.py:204 awx/main/conf.py:213 +#: awx/main/conf.py:224 awx/main/conf.py:234 awx/main/conf.py:244 +#: awx/main/conf.py:254 awx/main/conf.py:265 awx/main/conf.py:277 +#: awx/main/conf.py:286 awx/main/conf.py:299 awx/main/conf.py:312 +#: awx/main/conf.py:324 awx/main/conf.py:335 awx/main/conf.py:346 +#: awx/main/conf.py:358 awx/main/conf.py:370 awx/main/conf.py:381 +#: awx/main/conf.py:401 awx/main/conf.py:411 awx/main/conf.py:421 +#: awx/main/conf.py:434 awx/main/conf.py:445 awx/main/conf.py:455 +#: awx/main/conf.py:466 awx/main/conf.py:476 awx/main/conf.py:486 +#: awx/main/conf.py:498 awx/main/conf.py:510 awx/main/conf.py:522 +#: awx/main/conf.py:536 awx/main/conf.py:548 msgid "Jobs" msgstr "ジョブ" -#: awx/main/conf.py:143 +#: awx/main/conf.py:191 msgid "Always" msgstr "常時" -#: awx/main/conf.py:144 +#: awx/main/conf.py:192 msgid "Never" msgstr "なし" -#: awx/main/conf.py:145 +#: awx/main/conf.py:193 msgid "Only On Job Template Definitions" msgstr "ジョブテンプレートの定義のみ" -#: awx/main/conf.py:148 +#: awx/main/conf.py:196 msgid "When can extra variables contain Jinja templates?" msgstr "いつ追加変数に Jinja テンプレートが含まれるか?" -#: awx/main/conf.py:150 +#: awx/main/conf.py:198 msgid "" "Ansible allows variable substitution via the Jinja2 templating language for " "--extra-vars. This poses a potential security risk where Tower 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\"." -msgstr "" -"Ansible は Jinja2 テンプレート言語を使用した --extra-vars " -"の変数置き換えを許可します。これにより、ジョブの起動時に追加変数を指定できる Tower ユーザーが Jinja2 テンプレートを使用して任意の " -"Python " -"を実行できるようになるためセキュリティー上のリスクが生じます。この値は「template」または「never」に設定することをお勧めします。" +"templates to run arbitrary Python. It is recommended that this value be set " +"to \"template\" or \"never\"." +msgstr "Ansible は Jinja2 テンプレート言語を使用した --extra-vars の変数置き換えを許可します。これにより、ジョブの起動時に追加変数を指定できる Tower ユーザーが Jinja2 テンプレートを使用して任意の Python を実行できるようになるためセキュリティー上のリスクが生じます。この値は「template」または「never」に設定することをお勧めします。" -#: awx/main/conf.py:163 +#: awx/main/conf.py:211 msgid "Enable job isolation" msgstr "ジョブの分離の有効化" -#: awx/main/conf.py:164 +#: awx/main/conf.py:212 msgid "" "Isolates an Ansible job from protected parts of the system to prevent " "exposing sensitive information." msgstr "機密情報の公開を防ぐためにシステムの保護された部分から Ansible ジョブを分離します。" -#: awx/main/conf.py:172 +#: awx/main/conf.py:220 msgid "Job execution path" msgstr "ジョブの実行パス" -#: awx/main/conf.py:173 +#: awx/main/conf.py:221 msgid "" "The directory in which Tower will create new temporary directories for job " "execution and isolation (such as credential files and custom inventory " "scripts)." -msgstr "" -"Tower がジョブの実行および分離用に新規の一時ディレクトリーを作成するディレクトリーです " -"(認証情報ファイルおよびカスタムインベントリースクリプトなど)。" +msgstr "Tower がジョブの実行および分離用に新規の一時ディレクトリーを作成するディレクトリーです (認証情報ファイルおよびカスタムインベントリースクリプトなど)。" -#: awx/main/conf.py:184 +#: awx/main/conf.py:232 msgid "Paths to hide from isolated jobs" -msgstr "分離されたジョブには公開しないパス" +msgstr "分離されたジョブから非表示にするパス" -#: awx/main/conf.py:185 +#: awx/main/conf.py:233 msgid "" "Additional paths to hide from isolated processes. Enter one path per line." msgstr "分離されたプロセスには公開しないその他のパスです。1 行に 1 つのパスを入力します。" -#: awx/main/conf.py:194 +#: awx/main/conf.py:242 msgid "Paths to expose to isolated jobs" msgstr "分離されたジョブに公開するパス" -#: awx/main/conf.py:195 +#: awx/main/conf.py:243 msgid "" -"Whitelist of paths that would otherwise be hidden to expose to isolated " -"jobs. Enter one path per line." -msgstr "分離されたジョブに公開するために非表示にされるパスのホワイトリストです。1 行に 1 つのパスを入力します。" +"List of paths that would otherwise be hidden to expose to isolated jobs. " +"Enter one path per line." +msgstr "分離されたジョブに公開するために非表示にされるパスの一覧。1 行に 1 つのパスを入力します。" -#: awx/main/conf.py:204 +#: awx/main/conf.py:252 msgid "Isolated status check interval" msgstr "分離されたステータスチェックの間隔" -#: awx/main/conf.py:205 +#: awx/main/conf.py:253 msgid "" "The number of seconds to sleep between status checks for jobs running on " "isolated instances." msgstr "分離されたインスタンスで実行されるジョブに対するステータスチェック間にスリープ状態になる期間の秒数。" -#: awx/main/conf.py:214 +#: awx/main/conf.py:262 msgid "Isolated launch timeout" msgstr "分離された起動のタイムアウト" -#: awx/main/conf.py:215 +#: awx/main/conf.py:263 msgid "" "The timeout (in seconds) for launching jobs on isolated instances. This " "includes the time needed to copy source control files (playbooks) to the " "isolated instance." -msgstr "" -"分離されたインスタンスでジョブを起動する際のタイムアウト (秒数)。これにはソースコントロールファイル (Playbook) " -"を分離されたインスタンスにコピーするために必要な時間が含まれます。" +msgstr "分離されたインスタンスでジョブを起動する際のタイムアウト (秒数)。これにはソースコントロールファイル (Playbook) を分離されたインスタンスにコピーするために必要な時間が含まれます。" -#: awx/main/conf.py:226 +#: awx/main/conf.py:274 msgid "Isolated connection timeout" msgstr "分離された接続のタイムアウト" -#: awx/main/conf.py:227 +#: awx/main/conf.py:275 msgid "" "Ansible SSH connection timeout (in seconds) to use when communicating with " "isolated instances. Value should be substantially greater than expected " "network latency." -msgstr "" -"分離されたインスタンスと通信する際に使用される Ansible SSH 接続のタイムアウト (秒数) " -"です。値は予想されるネットワークの待ち時間よりも大幅に大きな値になるはずです。" +msgstr "分離されたインスタンスと通信する際に使用される Ansible SSH 接続のタイムアウト (秒数) です。値は予想されるネットワークの待ち時間よりも大幅に大きな値になるはずです。" + +#: awx/main/conf.py:284 +msgid "Isolated host key checking" +msgstr "分離されたホストキーチェック" + +#: awx/main/conf.py:285 +msgid "" +"When set to True, AWX will enforce strict host key checking for " +"communication with isolated nodes." +msgstr "True に設定すると、AWX は、分離したノードとの通信に対するホストキーの厳密なチェックを有効にします。" -#: awx/main/conf.py:237 +#: awx/main/conf.py:295 msgid "Generate RSA keys for isolated instances" msgstr "分離されたインスタンスの RSA 鍵の生成" -#: awx/main/conf.py:238 +#: awx/main/conf.py:296 msgid "" "If set, a random RSA key will be generated and distributed to isolated " "instances. To disable this behavior and manage authentication for isolated " "instances outside of Tower, disable this setting." -msgstr "" -"設定されている場合、RSA 鍵が生成され、分離されたインスタンスに配布されます。この動作を無効にし、Tower " -"の外部にある分離されたインスタンスの認証を管理するには、この設定を無効にします。" +msgstr "設定されている場合、RSA 鍵が生成され、分離されたインスタンスに配布されます。この動作を無効にし、Tower の外部にある分離されたインスタンスの認証を管理するには、この設定を無効にします。" -#: awx/main/conf.py:252 awx/main/conf.py:253 +#: awx/main/conf.py:310 awx/main/conf.py:311 msgid "The RSA private key for SSH traffic to isolated instances" msgstr "分離されたインスタンスへの SSH トラフィック用の RSA 秘密鍵" -#: awx/main/conf.py:264 awx/main/conf.py:265 +#: awx/main/conf.py:322 awx/main/conf.py:323 msgid "The RSA public key for SSH traffic to isolated instances" msgstr "分離されたインスタンスへの SSH トラフィック用の RSA 公開鍵" -#: awx/main/conf.py:274 +#: awx/main/conf.py:332 +msgid "Enable detailed resource profiling on all playbook runs" +msgstr "全 Playbook の実行に対する詳細なリソースプロファイリングを有効にする" + +#: awx/main/conf.py:333 +msgid "" +"If set, detailed resource profiling data will be collected on all jobs. This " +"data can be gathered with `sosreport`." +msgstr "設定されている場合には、すべてのジョブに対するリソースプロファイリングデータが収集されます。このデータは、`sosreport` で収集できます。" + +#: awx/main/conf.py:343 +msgid "Interval (in seconds) between polls for cpu usage." +msgstr "CPU 使用率のポーリングの間隔 (秒単位)。" + +#: awx/main/conf.py:344 +msgid "" +"Interval (in seconds) between polls for cpu usage. Setting this lower than " +"the default will affect playbook performance." +msgstr "CPU 使用率のポーリングの間隔 (秒単位)。これをデフォルトよりも小さい値に設定すると Playbook のパフォーマンスに影響があります。" + +#: awx/main/conf.py:355 +msgid "Interval (in seconds) between polls for memory usage." +msgstr "メモリー使用率のポーリングの間隔 (秒単位)。" + +#: awx/main/conf.py:356 +msgid "" +"Interval (in seconds) between polls for memory usage. Setting this lower " +"than the default will affect playbook performance." +msgstr "メモリー使用率のポーリングの間隔 (秒単位)。これをデフォルトよりも小さい値に設定すると Playbook のパフォーマンスに影響があります。" + +#: awx/main/conf.py:367 +msgid "Interval (in seconds) between polls for PID count." +msgstr "PID 数のポーリングの間隔 (秒単位)。" + +#: awx/main/conf.py:368 +msgid "" +"Interval (in seconds) between polls for PID count. Setting this lower than " +"the default will affect playbook performance." +msgstr "PID 数のポーリングの間隔 (秒単位)。これをデフォルトよりも小さい値に設定すると Playbook のパフォーマンスに影響があります。" + +#: awx/main/conf.py:379 msgid "Extra Environment Variables" msgstr "追加の環境変数" -#: awx/main/conf.py:275 +#: awx/main/conf.py:380 msgid "" "Additional environment variables set for playbook runs, inventory updates, " "project updates, and notification sending." msgstr "Playbook 実行、インベントリー更新、プロジェクト更新および通知の送信に設定される追加の環境変数。" -#: awx/main/conf.py:285 +#: awx/main/conf.py:390 +msgid "Gather data for Automation Analytics" +msgstr "自動化アナリティクス向けにデータを収集する" + +#: awx/main/conf.py:391 +msgid "Enables Tower to gather data on automation and send it to Red Hat." +msgstr "Tower が自動化のデータを収集して Red Hat に送信できるようにします。" + +#: awx/main/conf.py:399 +msgid "Run Project Updates With Higher Verbosity" +msgstr "より詳細なプロジェクト更新を実行する" + +#: awx/main/conf.py:400 +msgid "" +"Adds the CLI -vvv flag to ansible-playbook runs of project_update.yml used " +"for project updates." +msgstr "プロジェクトの更新に使用する project_update.yml の ansible-playbook に CLI -vvv フラグを追加します。" + +#: awx/main/conf.py:409 +msgid "Enable Role Download" +msgstr "ロールのダウンロードを有効にする" + +#: awx/main/conf.py:410 +msgid "" +"Allows roles to be dynamically downloaded from a requirements.yml file for " +"SCM projects." +msgstr "ロールが SCM プロジェクトの requirements.yml ファイルから動的にダウンロードされるのを許可します。" + +#: awx/main/conf.py:419 +msgid "Enable Collection(s) Download" +msgstr "コレクションのダウンロードを有効にする" + +#: awx/main/conf.py:420 +msgid "" +"Allows collections to be dynamically downloaded from a requirements.yml file " +"for SCM projects." +msgstr "コレクションが SCM プロジェクトの requirements.yml ファイルから動的にダウンロードされるのを許可します。" + +#: awx/main/conf.py:429 +msgid "Follow symlinks" +msgstr "シンボリックリンクをたどる" + +#: awx/main/conf.py:431 +msgid "" +"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." +msgstr "Playbook をスキャンするときは、シンボリックリンクをたどってください。リンクがそれ自体の親ディレクトリーを指している場合は、これを True に設定すると、無限再帰が発生する可能性があることに注意してください。" + +#: awx/main/conf.py:442 +msgid "Ignore Ansible Galaxy SSL Certificate Verification" +msgstr "Ansible Galaxy SSL 証明書の検証を無視する" + +#: awx/main/conf.py:443 +msgid "" +"If set to true, certificate validation will not be done when installing " +"content from any Galaxy server." +msgstr "True に設定すると、任意の Galaxy サーバーからコンテンツをインストールする時に証明書は検証されません。" + +#: awx/main/conf.py:453 msgid "Standard Output Maximum Display Size" msgstr "標準出力の最大表示サイズ" -#: awx/main/conf.py:286 +#: awx/main/conf.py:454 msgid "" "Maximum Size of Standard Output in bytes to display before requiring the " "output be downloaded." msgstr "出力のダウンロードを要求する前に表示される標準出力の最大サイズ (バイト単位)。" -#: awx/main/conf.py:295 +#: awx/main/conf.py:463 msgid "Job Event Standard Output Maximum Display Size" msgstr "ジョブイベントの標準出力の最大表示サイズ" -#: awx/main/conf.py:297 +#: awx/main/conf.py:465 msgid "" "Maximum Size of Standard Output in bytes to display for a single job or ad " "hoc command event. `stdout` will end with `…` when truncated." -msgstr "" -"単一ジョブまたはアドホックコマンドイベントについて表示される標準出力の最大サイズ (バイト単位)。`stdout` は切り捨てが実行されると `…` " -"で終了します。" +msgstr "単一ジョブまたはアドホックコマンドイベントについて表示される標準出力の最大サイズ (バイト単位)。`stdout` は切り捨てが実行されると `…` で終了します。" -#: awx/main/conf.py:306 +#: awx/main/conf.py:474 msgid "Maximum Scheduled Jobs" msgstr "スケジュール済みジョブの最大数" -#: awx/main/conf.py:307 +#: awx/main/conf.py:475 msgid "" "Maximum number of the same job template that can be waiting to run when " "launching from a schedule before no more are created." msgstr "スケジュールからの起動時に実行を待機している同じジョブテンプレートの最大数です (これ以上作成されることはありません)。" -#: awx/main/conf.py:316 +#: awx/main/conf.py:484 msgid "Ansible Callback Plugins" msgstr "Ansible コールバックプラグイン" -#: awx/main/conf.py:317 +#: awx/main/conf.py:485 msgid "" "List of paths to search for extra callback plugins to be used when running " "jobs. Enter one path per line." msgstr "ジョブの実行時に使用される追加のコールバックプラグインを検索する際のパスの一覧です。1 行に 1 つのパスを入力します。" -#: awx/main/conf.py:327 +#: awx/main/conf.py:495 msgid "Default Job Timeout" msgstr "デフォルトのジョブタイムアウト" -#: awx/main/conf.py:328 +#: awx/main/conf.py:496 msgid "" "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." -msgstr "" -"ジョブの実行可能な最大時間 (秒数) です。値 0 " -"が使用されている場合はタイムアウトを設定できないことを示します。個別のジョブテンプレートに設定されるタイムアウトはこれを上書きします。" +msgstr "ジョブの実行可能な最大時間 (秒数) です。値 0 が使用されている場合はタイムアウトを設定できないことを示します。個別のジョブテンプレートに設定されるタイムアウトはこれを上書きします。" -#: awx/main/conf.py:339 +#: awx/main/conf.py:507 msgid "Default Inventory Update Timeout" msgstr "デフォルトのインベントリー更新タイムアウト" -#: awx/main/conf.py:340 +#: awx/main/conf.py:508 msgid "" -"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 " +"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." -msgstr "" -"インベントリー更新の実行可能な最大時間 (秒数)。値 0 " -"が設定されている場合はタイムアウトを設定できないことを示します。個別のインベントリーソースに設定されるタイムアウトはこれを上書きします。" +msgstr "インベントリー更新の実行可能な最大時間 (秒数)。値 0 が設定されている場合はタイムアウトを設定できないことを示します。個別のインベントリーソースに設定されるタイムアウトはこれを上書きします。" -#: awx/main/conf.py:351 +#: awx/main/conf.py:519 msgid "Default Project Update Timeout" msgstr "デフォルトのプロジェクト更新タイムアウト" -#: awx/main/conf.py:352 +#: awx/main/conf.py:520 msgid "" "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." -msgstr "" -"プロジェクト更新の実行可能な最大時間 (秒数)。値 0 " -"が設定されている場合はタイムアウトを設定できないことを示します。個別のプロジェクトに設定されるタイムアウトはこれを上書きします。" +msgstr "プロジェクト更新の実行可能な最大時間 (秒数)。値 0 が設定されている場合はタイムアウトを設定できないことを示します。個別のプロジェクトに設定されるタイムアウトはこれを上書きします。" -#: awx/main/conf.py:363 +#: awx/main/conf.py:531 msgid "Per-Host Ansible Fact Cache Timeout" msgstr "ホストあたりの Ansible ファクトキャッシュのタイムアウト" -#: awx/main/conf.py:364 +#: awx/main/conf.py:532 msgid "" "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 " +"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." -msgstr "" -"保存される Ansible ファクトが最後に変更されてから有効とみなされる最大時間 (秒数) です。有効な新規のファクトのみが Playbook " -"でアクセスできます。ansible_facts のデータベースからの削除はこれによる影響を受けません。タイムアウトが設定されないことを示すには 0 " -"の値を使用します。" +msgstr "保存される Ansible ファクトが最後に変更されてから有効とみなされる最大時間 (秒数) です。有効な新規のファクトのみが Playbook でアクセスできます。ansible_facts のデータベースからの削除はこれによる影響を受けません。タイムアウトが設定されないことを示すには 0 の値を使用します。" + +#: awx/main/conf.py:545 +msgid "Maximum number of forks per job." +msgstr "ジョブ別のフォークの最大数。" -#: awx/main/conf.py:377 +#: awx/main/conf.py:546 +msgid "" +"Saving a Job Template with more than this number of forks will result in an " +"error. When set to 0, no limit is applied." +msgstr "この数を超えるフォークを指定してジョブテンプレートを保存すると、エラーが発生します。 0 に設定すると、制限は適用されません。" + +#: awx/main/conf.py:557 msgid "Logging Aggregator" msgstr "ログアグリゲーター" -#: awx/main/conf.py:378 +#: awx/main/conf.py:558 msgid "Hostname/IP where external logs will be sent to." msgstr "外部ログの送信先のホスト名/IP" -#: awx/main/conf.py:379 awx/main/conf.py:390 awx/main/conf.py:402 -#: awx/main/conf.py:412 awx/main/conf.py:424 awx/main/conf.py:439 -#: awx/main/conf.py:451 awx/main/conf.py:460 awx/main/conf.py:470 -#: awx/main/conf.py:482 awx/main/conf.py:493 awx/main/conf.py:505 -#: awx/main/conf.py:518 +#: awx/main/conf.py:559 awx/main/conf.py:570 awx/main/conf.py:582 +#: awx/main/conf.py:592 awx/main/conf.py:604 awx/main/conf.py:619 +#: awx/main/conf.py:631 awx/main/conf.py:640 awx/main/conf.py:650 +#: awx/main/conf.py:662 awx/main/conf.py:673 awx/main/conf.py:685 +#: awx/main/conf.py:698 awx/main/conf.py:710 awx/main/conf.py:721 +#: awx/main/conf.py:731 msgid "Logging" msgstr "ロギング" -#: awx/main/conf.py:387 +#: awx/main/conf.py:567 msgid "Logging Aggregator Port" msgstr "ログアグリゲーターポート" -#: awx/main/conf.py:388 +#: awx/main/conf.py:568 msgid "" "Port on Logging Aggregator to send logs to (if required and not provided in " "Logging Aggregator)." msgstr "ログの送信先のログアグリゲーターのポート (必要な場合。ログアグリゲーターでは指定されません)。" -#: awx/main/conf.py:400 +#: awx/main/conf.py:580 msgid "Logging Aggregator Type" msgstr "ログアグリゲーターのタイプ" -#: awx/main/conf.py:401 +#: awx/main/conf.py:581 msgid "Format messages for the chosen log aggregator." msgstr "選択されたログアグリゲーターのメッセージのフォーマット。" -#: awx/main/conf.py:410 +#: awx/main/conf.py:590 msgid "Logging Aggregator Username" msgstr "ログアグリゲーターのユーザー名" -#: awx/main/conf.py:411 -msgid "Username for external log aggregator (if required)." -msgstr "外部ログアグリゲーターのユーザー名 (必要な場合)。" +#: awx/main/conf.py:591 +msgid "Username for external log aggregator (if required; HTTP/s only)." +msgstr "外部ログアグリゲーターのユーザー名 (必要な場合、HTTP/s のみ)。" -#: awx/main/conf.py:422 +#: awx/main/conf.py:602 msgid "Logging Aggregator Password/Token" msgstr "ログアグリゲーターのパスワード/トークン" -#: awx/main/conf.py:423 +#: awx/main/conf.py:603 msgid "" -"Password or authentication token for external log aggregator (if required)." -msgstr "外部ログアグリゲーターのパスワードまたは認証トークン (必要な場合)。" +"Password or authentication token for external log aggregator (if required; " +"HTTP/s only)." +msgstr "外部ログアグリゲーターのパスワードまたは認証トークン (必要な場合、HTTP/s のみ)。" -#: awx/main/conf.py:432 +#: awx/main/conf.py:612 msgid "Loggers Sending Data to Log Aggregator Form" msgstr "ログアグリゲーターフォームにデータを送信するロガー" -#: awx/main/conf.py:433 +#: awx/main/conf.py:613 msgid "" -"List of loggers that will send HTTP logs to the collector, these can include any or all of: \n" +"List of loggers that will send HTTP logs to the collector, these can include " +"any or all of: \n" "awx - service logs\n" "activity_stream - activity stream records\n" "job_events - callback data from Ansible job events\n" "system_tracking - facts gathered from scan jobs." -msgstr "" -"HTTP ログをコレクターに送信するロガーの一覧です。これらには以下のいずれか、またはすべてが含まれます。\n" +msgstr "HTTP ログをコレクターに送信するロガーの一覧です。これらには以下のいずれか、またはすべてが含まれます。\n" "awx - サービスログ\n" "activity_stream - アクティビティーストリームレコード\n" "job_events - Ansible ジョブイベントからのコールバックデータ\n" "system_tracking - スキャンジョブから生成されるファクト" -#: awx/main/conf.py:446 +#: awx/main/conf.py:626 msgid "Log System Tracking Facts Individually" -msgstr "ログシステムによるファクトの個別トラッキング" +msgstr "ログシステムトラッキングの個別ファクト" -#: awx/main/conf.py:447 +#: awx/main/conf.py:627 msgid "" "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." -msgstr "" -"設定されている場合、スキャンで見つかる各パッケージ、サービスその他の項目についてのシステムトラッキングのファクトが送信され、検索クエリーの詳細度が上がります。設定されていない場合、ファクトは単一辞書として送信され、ファクトの処理の効率が上がります。" +msgstr "設定されている場合、スキャンで見つかる各パッケージ、サービスその他の項目についてのシステムトラッキングのファクトが送信され、検索クエリーの詳細度が上がります。設定されていない場合、ファクトは単一辞書として送信され、ファクトの処理の効率が上がります。" -#: awx/main/conf.py:458 +#: awx/main/conf.py:638 msgid "Enable External Logging" msgstr "外部ログの有効化" -#: awx/main/conf.py:459 +#: awx/main/conf.py:639 msgid "Enable sending logs to external log aggregator." msgstr "外部ログアグリゲーターへのログ送信の有効化" -#: awx/main/conf.py:468 +#: awx/main/conf.py:648 msgid "Cluster-wide Tower unique identifier." msgstr "クラスター全体での Tower 固有識別子。" -#: awx/main/conf.py:469 +#: awx/main/conf.py:649 msgid "Useful to uniquely identify Tower instances." msgstr "Tower インスタンスを一意に識別するのに役立ちます。" -#: awx/main/conf.py:478 +#: awx/main/conf.py:658 msgid "Logging Aggregator Protocol" msgstr "ログアグリゲーターのプロトコル" -#: awx/main/conf.py:479 +#: awx/main/conf.py:659 msgid "" "Protocol used to communicate with log aggregator. HTTPS/HTTP assumes HTTPS " "unless http:// is explicitly used in the Logging Aggregator hostname." -msgstr "" -"ログアグリゲーターとの通信に使用されるプロトコルです。HTTPS/HTTP については、 http:// " -"がログアグリゲーターのホスト名で明示的に使用されていない限り HTTPS の使用が前提となります。" +msgstr "ログアグリゲーターとの通信に使用されるプロトコルです。HTTPS/HTTP については、 http:// がログアグリゲーターのホスト名で明示的に使用されていない限り HTTPS の使用が前提となります。" -#: awx/main/conf.py:489 +#: awx/main/conf.py:669 msgid "TCP Connection Timeout" msgstr "TCP 接続のタイムアウト" -#: awx/main/conf.py:490 +#: awx/main/conf.py:670 msgid "" "Number of seconds for a TCP connection to external log aggregator to " "timeout. Applies to HTTPS and TCP log aggregator protocols." -msgstr "" -"外部ログアグリゲーターへの TCP 接続がタイムアウトする秒数です。HTTPS および TCP ログアグリゲータープロトコルに適用されます。" +msgstr "外部ログアグリゲーターへの TCP 接続がタイムアウトする秒数です。HTTPS および TCP ログアグリゲータープロトコルに適用されます。" -#: awx/main/conf.py:500 +#: awx/main/conf.py:680 msgid "Enable/disable HTTPS certificate verification" msgstr "HTTPS 証明書の検証を有効化/無効化" -#: awx/main/conf.py:501 +#: awx/main/conf.py:681 msgid "" "Flag to control enable/disable of certificate verification when " "LOG_AGGREGATOR_PROTOCOL is \"https\". If enabled, Tower's log handler will " "verify certificate sent by external log aggregator before establishing " "connection." -msgstr "" -"LOG_AGGREGATOR_PROTOCOL " -"が「https」の場合の証明書の検証の有効化/無効化を制御するフラグです。有効にされている場合、Tower " -"のログハンドラーは接続を確立する前に外部ログアグリゲーターによって送信される証明書を検証します。" +msgstr "LOG_AGGREGATOR_PROTOCOL が「https」の場合の証明書の検証の有効化/無効化を制御するフラグです。有効にされている場合、Tower のログハンドラーは接続を確立する前に外部ログアグリゲーターによって送信される証明書を検証します。" -#: awx/main/conf.py:513 +#: awx/main/conf.py:693 msgid "Logging Aggregator Level Threshold" msgstr "ログアグリゲーターレベルのしきい値" -#: awx/main/conf.py:514 +#: awx/main/conf.py:694 msgid "" "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)" -msgstr "" -"ログハンドラーによって使用されるレベルのしきい値です。重大度は低い順から高い順に DEBUG、INFO、WARNING、ERROR、CRITICAL " -"になります。しきい値より重大度の低いメッセージはログハンドラーによって無視されます (カテゴリー awx.anlytics " -"の下にあるメッセージはこの設定を無視します)。" +"threshold will be ignored by log handler. (messages under category awx." +"anlytics ignore this setting)" +msgstr "ログハンドラーによって使用されるレベルのしきい値です。重大度は低い順から高い順に DEBUG、INFO、WARNING、ERROR、CRITICAL になります。しきい値より重大度の低いメッセージはログハンドラーによって無視されます (カテゴリー awx.anlytics の下にあるメッセージはこの設定を無視します)。" -#: awx/main/conf.py:537 awx/sso/conf.py:1264 +#: awx/main/conf.py:706 +msgid "Maximum disk persistance for external log aggregation (in GB)" +msgstr "外部ログ集計の最大ディスク永続性 (GB)" + +#: awx/main/conf.py:707 +msgid "" +"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." +msgstr "外部ログアグリゲーターの停止中に保存するデータ容量 (GB、デフォルトは 1)。rsyslogd queue.maxdiskspace 設定と同じです。" + +#: awx/main/conf.py:717 +msgid "File system location for rsyslogd disk persistence" +msgstr "rsyslogd ディスク永続性のファイルシステムの場所" + +#: awx/main/conf.py:718 +msgid "" +"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." +msgstr "外部ログアグリゲーターが停止した後に再試行する必要のあるログを永続化する場所 (デフォルト: /var/lib/awx)。rsyslogd queue.spoolDirectory の設定と同じです。" + +#: awx/main/conf.py:728 +msgid "Enable rsyslogd debugging" +msgstr "rsyslogd デバッグを有効にする" + +#: awx/main/conf.py:729 +msgid "" +"Enabled high verbosity debugging for rsyslogd. Useful for debugging " +"connection issues for external log aggregation." +msgstr "rsyslogd の詳細デバッグを有効にしました。外部ログ集計の接続問題のデバッグに役立ちます。" + +#: awx/main/conf.py:740 +msgid "Last gather date for Automation Analytics." +msgstr "自動化アナリティクス向けにデータを最後に収集した日" + +#: awx/main/conf.py:750 +msgid "Automation Analytics Gather Interval" +msgstr "自動化アナリティクスの収集間隔" + +#: awx/main/conf.py:751 +msgid "Interval (in seconds) between data gathering." +msgstr "データ収集の間隔 (秒単位)" + +#: awx/main/conf.py:773 awx/sso/conf.py:1250 msgid "\n" msgstr "\n" -#: awx/main/constants.py:17 +#: awx/main/constants.py:16 msgid "Sudo" msgstr "Sudo" -#: awx/main/constants.py:17 +#: awx/main/constants.py:16 msgid "Su" msgstr "Su" -#: awx/main/constants.py:17 +#: awx/main/constants.py:16 msgid "Pbrun" msgstr "Pbrun" -#: awx/main/constants.py:17 +#: awx/main/constants.py:16 msgid "Pfexec" msgstr "Pfexec" -#: awx/main/constants.py:18 +#: awx/main/constants.py:17 msgid "DZDO" msgstr "DZDO" -#: awx/main/constants.py:18 +#: awx/main/constants.py:17 msgid "Pmrun" msgstr "Pmrun" -#: awx/main/constants.py:18 +#: awx/main/constants.py:17 msgid "Runas" msgstr "Runas" -#: awx/main/constants.py:19 +#: awx/main/constants.py:18 msgid "Enable" msgstr "有効化" -#: awx/main/constants.py:19 +#: awx/main/constants.py:18 msgid "Doas" msgstr "Doas" +#: awx/main/constants.py:18 +msgid "Ksu" +msgstr "Ksu" + +#: awx/main/constants.py:19 +msgid "Machinectl" +msgstr "Machinectl" + +#: awx/main/constants.py:19 +msgid "Sesu" +msgstr "Sesu" + #: awx/main/constants.py:21 msgid "None" msgstr "なし" -#: awx/main/fields.py:62 +#: awx/main/credential_plugins/aim.py:11 +msgid "CyberArk AIM URL" +msgstr "CyberArk AIM URL" + +#: awx/main/credential_plugins/aim.py:16 +msgid "Application ID" +msgstr "アプリケーション ID" + +#: awx/main/credential_plugins/aim.py:21 +msgid "Client Key" +msgstr "クライアントキー" + +#: awx/main/credential_plugins/aim.py:27 +msgid "Client Certificate" +msgstr "クライアント証明書" + +#: awx/main/credential_plugins/aim.py:33 +msgid "Verify SSL Certificates" +msgstr "SSL 証明書の検証" + +#: awx/main/credential_plugins/aim.py:39 +msgid "Object Query" +msgstr "オブジェクトのクエリー" + +#: awx/main/credential_plugins/aim.py:41 +msgid "" +"Lookup query for the object. Ex: Safe=TestSafe;Object=testAccountName123" +msgstr "オブジェクトの検索クエリー。例: Safe=TestSafe;Object=testAccountName123" + +#: awx/main/credential_plugins/aim.py:44 +msgid "Object Query Format" +msgstr "オブジェクトクエリーフォーマット (必須)" + +#: awx/main/credential_plugins/aim.py:50 +msgid "Reason" +msgstr "理由" + +#: awx/main/credential_plugins/aim.py:52 +msgid "" +"Object request reason. This is only needed if it is required by the object's " +"policy." +msgstr "オブジェクト要求の理由。これは、オブジェクトのポリシーで必須の場合にのみ必要です。" + +#: awx/main/credential_plugins/azure_kv.py:21 +msgid "Vault URL (DNS Name)" +msgstr "Vault URL (DNS 名)" + +#: awx/main/credential_plugins/azure_kv.py:26 +#: awx/main/models/credential/__init__.py:968 +msgid "Client ID" +msgstr "クライアント ID" + +#: awx/main/credential_plugins/azure_kv.py:35 +#: awx/main/models/credential/__init__.py:977 +msgid "Tenant ID" +msgstr "テナント ID" + +#: awx/main/credential_plugins/azure_kv.py:39 +msgid "Cloud Environment" +msgstr "クラウド環境" + +#: awx/main/credential_plugins/azure_kv.py:40 +msgid "Specify which azure cloud environment to use." +msgstr "使用する Azure クラウド環境を指定します。" + +#: awx/main/credential_plugins/azure_kv.py:46 +msgid "Secret Name" +msgstr "シークレット名" + +#: awx/main/credential_plugins/azure_kv.py:48 +msgid "The name of the secret to look up." +msgstr "検索するシークレット名" + +#: awx/main/credential_plugins/azure_kv.py:51 +#: awx/main/credential_plugins/conjur.py:42 +msgid "Secret Version" +msgstr "シークレットのバージョン" + +#: awx/main/credential_plugins/azure_kv.py:53 +#: awx/main/credential_plugins/conjur.py:44 +#: awx/main/credential_plugins/hashivault.py:89 +msgid "" +"Used to specify a specific secret version (if left empty, the latest version " +"will be used)." +msgstr "シークレットバージョンの指定に使用します (空白の場合は、最新のバージョンが使用されます)。" + +#: awx/main/credential_plugins/conjur.py:13 +msgid "Conjur URL" +msgstr "Conjur URL" + +#: awx/main/credential_plugins/conjur.py:18 +msgid "API Key" +msgstr "API キー" + +#: awx/main/credential_plugins/conjur.py:23 +#: awx/main/migrations/_inventory_source_vars.py:142 +msgid "Account" +msgstr "アカウント" + +#: awx/main/credential_plugins/conjur.py:27 +#: awx/main/models/credential/__init__.py:606 +#: awx/main/models/credential/__init__.py:662 +#: awx/main/models/credential/__init__.py:720 +#: awx/main/models/credential/__init__.py:793 +#: awx/main/models/credential/__init__.py:846 +#: awx/main/models/credential/__init__.py:872 +#: awx/main/models/credential/__init__.py:899 +#: awx/main/models/credential/__init__.py:959 +#: awx/main/models/credential/__init__.py:1032 +#: awx/main/models/credential/__init__.py:1063 +#: awx/main/models/credential/__init__.py:1113 +msgid "Username" +msgstr "ユーザー名" + +#: awx/main/credential_plugins/conjur.py:31 +msgid "Public Key Certificate" +msgstr "公開鍵の証明書" + +#: awx/main/credential_plugins/conjur.py:37 +msgid "Secret Identifier" +msgstr "シークレット識別子" + +#: awx/main/credential_plugins/conjur.py:39 +msgid "The identifier for the secret e.g., /some/identifier" +msgstr "シークレットの識別子。例: /some/identifier" + +#: awx/main/credential_plugins/hashivault.py:14 +msgid "Server URL" +msgstr "サーバー URL" + +#: awx/main/credential_plugins/hashivault.py:17 +msgid "The URL to the HashiCorp Vault" +msgstr "HashiCorp Vault の URL" + +#: awx/main/credential_plugins/hashivault.py:20 +#: awx/main/models/credential/__init__.py:998 +#: awx/main/models/credential/__init__.py:1015 +msgid "Token" +msgstr "トークン" + +#: awx/main/credential_plugins/hashivault.py:23 +msgid "The access token used to authenticate to the Vault server" +msgstr "Vault サーバーへの認証に使用するアクセストークン" + +#: awx/main/credential_plugins/hashivault.py:26 +msgid "CA Certificate" +msgstr "CA 証明書" + +#: awx/main/credential_plugins/hashivault.py:29 +msgid "" +"The CA certificate used to verify the SSL certificate of the Vault server" +msgstr "Vault サーバーの SSL 証明書の検証に使用する CA 証明書" + +#: awx/main/credential_plugins/hashivault.py:32 +msgid "AppRole role_id" +msgstr "AppRole role_id" + +#: awx/main/credential_plugins/hashivault.py:35 +msgid "The Role ID for AppRole Authentication" +msgstr "AppRole 認証のロール ID" + +#: awx/main/credential_plugins/hashivault.py:38 +msgid "AppRole secret_id" +msgstr "AppRole secret_id" + +#: awx/main/credential_plugins/hashivault.py:42 +msgid "The Secret ID for AppRole Authentication" +msgstr "AppRole 認証のシークレット ID" + +#: awx/main/credential_plugins/hashivault.py:45 +msgid "Path to Approle Auth" +msgstr "ApproleAuth へのパス" + +#: awx/main/credential_plugins/hashivault.py:49 +msgid "" +"The AppRole Authentication path to use if one isn't provided in the metadata " +"when linking to an input field. Defaults to 'approle'" +msgstr "入力フィールドにリンクするときにメタデータで指定されていない場合に使用する AppRole 認証パス (デフォルトは「approle」)" + +#: awx/main/credential_plugins/hashivault.py:54 +msgid "Path to Secret" +msgstr "シークレットへのパス" + +#: awx/main/credential_plugins/hashivault.py:56 +msgid "The path to the secret stored in the secret backend e.g, /some/secret/" +msgstr "シークレットのバックエンドに保存されているシークレットへのパス。例: /some/secret/" + +#: awx/main/credential_plugins/hashivault.py:59 +msgid "Path to Auth" +msgstr "認証へのパス" + +#: awx/main/credential_plugins/hashivault.py:62 +msgid "The path where the Authentication method is mounted e.g, approle" +msgstr "認証方法がマウントされるパス (例: approle)" + +#: awx/main/credential_plugins/hashivault.py:70 +msgid "API Version" +msgstr "API バージョン" + +#: awx/main/credential_plugins/hashivault.py:72 +msgid "" +"API v1 is for static key/value lookups. API v2 is for versioned key/value " +"lookups." +msgstr "API v1 は静的なキー/値のルックアップ用で、API v2 はバージョン管理されたキー/値のルックアップ用です。" + +#: awx/main/credential_plugins/hashivault.py:77 +msgid "Name of Secret Backend" +msgstr "シークレットバックエンドの名前" + +#: awx/main/credential_plugins/hashivault.py:79 +msgid "" +"The name of the kv secret backend (if left empty, the first segment of the " +"secret path will be used)." +msgstr "KV シークレットバックエンド名 (空白の場合は、シークレットパスの最初のセグメントが使用されます)。" + +#: awx/main/credential_plugins/hashivault.py:82 +#: awx/main/migrations/_inventory_source_vars.py:147 +msgid "Key Name" +msgstr "キー名" + +#: awx/main/credential_plugins/hashivault.py:84 +msgid "The name of the key to look up in the secret." +msgstr "シークレットで検索するキーの名前" + +#: awx/main/credential_plugins/hashivault.py:87 +msgid "Secret Version (v2 only)" +msgstr "シークレットバージョン (v2 のみ)" + +#: awx/main/credential_plugins/hashivault.py:96 +msgid "Unsigned Public Key" +msgstr "未署名の公開鍵" + +#: awx/main/credential_plugins/hashivault.py:101 +msgid "Role Name" +msgstr "ロール名" + +#: awx/main/credential_plugins/hashivault.py:103 +msgid "The name of the role used to sign." +msgstr "署名に使用するロール名" + +#: awx/main/credential_plugins/hashivault.py:106 +msgid "Valid Principals" +msgstr "有効なプリンシパル" + +#: awx/main/credential_plugins/hashivault.py:108 +msgid "" +"Valid principals (either usernames or hostnames) that the certificate should " +"be signed for." +msgstr "証明書への署名に必要とされる、有効なプリンシパル (ユーザー名またはホスト名)" + +#: awx/main/fields.py:67 #, python-brace-format msgid "'{value}' is not one of ['{allowed_values}']" -msgstr "'{value}' は ['{allowed_values}'] のいずれでもありません" +msgstr "'{value}' は ['{allowed_values}'] のいずれでもありません。" -#: awx/main/fields.py:421 +#: awx/main/fields.py:439 #, python-brace-format msgid "{type} provided in relative path {path}, expected {expected_type}" -msgstr "相対パス {path} で指定される {type} です。{expected_type} が予想されます。" +msgstr "相対パス {path} で {type} が指定されました。{expected_type} が必要です" -#: awx/main/fields.py:426 +#: awx/main/fields.py:444 #, python-brace-format msgid "{type} provided, expected {expected_type}" -msgstr "{type} が指定されます。{expected_type} が予想されます。" +msgstr "{type} が指定されましたが、{expected_type} が必要です" -#: awx/main/fields.py:431 +#: awx/main/fields.py:449 #, python-brace-format msgid "Schema validation error in relative path {path} ({error})" msgstr "相対パス {path} でスキーマの検証エラーが生じました ({error})" -#: awx/main/fields.py:552 +#: awx/main/fields.py:558 +#, python-format +msgid "required for %s" +msgstr "%s に必須です" + +#: awx/main/fields.py:632 msgid "secret values must be of type string, not {}" msgstr "シークレットの値は文字列型にする必要があります ({}ではない)" -#: awx/main/fields.py:587 +#: awx/main/fields.py:675 #, python-format msgid "cannot be set unless \"%s\" is set" msgstr "\"%s\" が設定されていない場合は設定できません" -#: awx/main/fields.py:603 -#, python-format -msgid "required for %s" -msgstr "%s に必須です" - -#: awx/main/fields.py:627 +#: awx/main/fields.py:710 msgid "must be set when SSH key is encrypted." msgstr "SSH キーが暗号化されている場合に設定する必要があります。" -#: awx/main/fields.py:633 +#: awx/main/fields.py:718 msgid "should not be set when SSH key is not encrypted." msgstr "SSH キーが暗号化されていない場合は設定できません。" -#: awx/main/fields.py:691 +#: awx/main/fields.py:777 msgid "'dependencies' is not supported for custom credentials." msgstr "「dependencies (依存関係)」はカスタム認証情報の場合にはサポートされません。" -#: awx/main/fields.py:705 +#: awx/main/fields.py:791 msgid "\"tower\" is a reserved field name" msgstr "「tower」は予約されたフィールド名です" -#: awx/main/fields.py:712 +#: awx/main/fields.py:798 #, python-format msgid "field IDs must be unique (%s)" msgstr "フィールド ID は固有でなければなりません (%s)" -#: awx/main/fields.py:725 -msgid "become_method is a reserved type name" -msgstr "become_method は予約された型名です。" +#: awx/main/fields.py:813 +msgid "{} is not a {}" +msgstr "{} は {} ではありません" -#: awx/main/fields.py:736 +#: awx/main/fields.py:819 #, python-brace-format msgid "{sub_key} not allowed for {element_type} type ({element_id})" -msgstr "{sub_key} は {element_type} 型の ({element_id}) には許可されません" +msgstr "{element_type} タイプには {sub_key} を使用できません ({element_id})" -#: awx/main/fields.py:810 +#: awx/main/fields.py:877 +msgid "" +"Environment variable {} may affect Ansible configuration so its use is not " +"allowed in credentials." +msgstr "環境変数 {} は Ansible の設定に影響を及ぼす可能性があります。したがって認証情報で使用することはできません。" + +#: awx/main/fields.py:883 +msgid "Environment variable {} is not allowed to be used in credentials." +msgstr "環境変数 {} を認証情報で使用することは許可されていません。" + +#: awx/main/fields.py:911 msgid "" "Must define unnamed file injector in order to reference `tower.filename`." msgstr "`tower.filename`を参照するために名前が設定されていないファイルインジェクターを定義する必要があります。" -#: awx/main/fields.py:817 +#: awx/main/fields.py:918 msgid "Cannot directly reference reserved `tower` namespace container." msgstr "予約された `tower` 名前空間コンテナーを直接参照することができません。" -#: awx/main/fields.py:827 +#: awx/main/fields.py:928 msgid "Must use multi-file syntax when injecting multiple files" msgstr "複数ファイルの挿入時に複数ファイル構文を使用する必要があります。" -#: awx/main/fields.py:844 +#: awx/main/fields.py:948 #, python-brace-format msgid "{sub_key} uses an undefined field ({error_msg})" -msgstr "{sub_key} は未定義フィールド ({error_msg}) を使用します。" +msgstr "{sub_key} は未定義のフィールドを使用します ({error_msg})" + +#: awx/main/fields.py:955 +msgid "Encountered unsafe code execution: {}" +msgstr "unsafe コードの実行が発生しました: {}" -#: awx/main/fields.py:851 +#: awx/main/fields.py:959 #, python-brace-format msgid "" "Syntax error rendering template for {sub_key} inside of {type} ({error_msg})" -msgstr "{type} 内で {sub_key} のテンプレートのレンダリング中に構文エラーが発生しました({error_msg}) " +msgstr "{type} 内で {sub_key} テンプレートのレンダリング中に構文エラーが発生しました ({error_msg})" -#: awx/main/middleware.py:160 +#: awx/main/middleware.py:118 msgid "Formats of all available named urls" msgstr "利用可能なすべての名前付き url の形式" -#: awx/main/middleware.py:161 +#: awx/main/middleware.py:119 msgid "" "Read-only list of key-value pairs that shows the standard format of all " "available named URLs." msgstr "名前付き URL を持つ利用可能なすべての標準形式を表示するキーと値のペアの読み取り専用リストです。" -#: awx/main/middleware.py:163 awx/main/middleware.py:173 +#: awx/main/middleware.py:121 awx/main/middleware.py:131 msgid "Named URL" msgstr "名前付き URL" -#: awx/main/middleware.py:170 +#: awx/main/middleware.py:128 msgid "List of all named url graph nodes." msgstr "すべての名前付き URL グラフノードの一覧です。" -#: awx/main/middleware.py:171 +#: awx/main/middleware.py:129 msgid "" -"Read-only list of key-value pairs that exposes named URL graph topology. Use" -" this list to programmatically generate named URLs for resources" -msgstr "" -"名前付き URL グラフトポロジーを公開するキーと値のペアの読み取り専用一覧です。この一覧を使用してリソースの名前付き URL " -"をプログラムで生成します。" +"Read-only list of key-value pairs that exposes named URL graph topology. Use " +"this list to programmatically generate named URLs for resources" +msgstr "名前付き URL グラフトポロジーを公開するキーと値のペアの読み取り専用一覧です。この一覧を使用してリソースの名前付き URL をプログラムで生成します。" -#: awx/main/migrations/_reencrypt.py:26 awx/main/models/notifications.py:35 -msgid "Email" -msgstr "メール" +#: awx/main/migrations/_inventory_source_vars.py:140 +msgid "Image ID" +msgstr "イメージ ID" -#: awx/main/migrations/_reencrypt.py:27 awx/main/models/notifications.py:36 -msgid "Slack" -msgstr "Slack" +#: awx/main/migrations/_inventory_source_vars.py:141 +msgid "Availability Zone" +msgstr "アベイラビリティーゾーン" -#: awx/main/migrations/_reencrypt.py:28 awx/main/models/notifications.py:37 -msgid "Twilio" -msgstr "Twilio" +#: awx/main/migrations/_inventory_source_vars.py:143 +msgid "Instance ID" +msgstr "インスタンス ID" -#: awx/main/migrations/_reencrypt.py:29 awx/main/models/notifications.py:38 -msgid "Pagerduty" -msgstr "Pagerduty" +#: awx/main/migrations/_inventory_source_vars.py:144 +msgid "Instance State" +msgstr "インスタンスの状態" -#: awx/main/migrations/_reencrypt.py:30 awx/main/models/notifications.py:39 -msgid "HipChat" -msgstr "HipChat" +#: awx/main/migrations/_inventory_source_vars.py:145 +msgid "Platform" +msgstr "プラットフォーム" -#: awx/main/migrations/_reencrypt.py:31 awx/main/models/notifications.py:41 -msgid "Mattermost" -msgstr "Mattermost" +#: awx/main/migrations/_inventory_source_vars.py:146 +msgid "Instance Type" +msgstr "インスタンスタイプ" -#: awx/main/migrations/_reencrypt.py:32 awx/main/models/notifications.py:40 -msgid "Webhook" -msgstr "Webhook" +#: awx/main/migrations/_inventory_source_vars.py:148 +msgid "Region" +msgstr "リージョン" -#: awx/main/migrations/_reencrypt.py:33 awx/main/models/notifications.py:43 -msgid "IRC" -msgstr "IRC" +#: awx/main/migrations/_inventory_source_vars.py:149 +msgid "Security Group" +msgstr "セキュリティーグループ" + +#: awx/main/migrations/_inventory_source_vars.py:150 +msgid "Tags" +msgstr "タグ" + +#: awx/main/migrations/_inventory_source_vars.py:151 +msgid "Tag None" +msgstr "タグ None" + +#: awx/main/migrations/_inventory_source_vars.py:152 +msgid "VPC ID" +msgstr "VPC ID" -#: awx/main/models/activity_stream.py:25 +#: awx/main/models/activity_stream.py:28 msgid "Entity Created" msgstr "エンティティーの作成" -#: awx/main/models/activity_stream.py:26 +#: awx/main/models/activity_stream.py:29 msgid "Entity Updated" msgstr "エンティティーの更新" -#: awx/main/models/activity_stream.py:27 +#: awx/main/models/activity_stream.py:30 msgid "Entity Deleted" msgstr "エンティティーの削除" -#: awx/main/models/activity_stream.py:28 +#: awx/main/models/activity_stream.py:31 msgid "Entity Associated with another Entity" msgstr "エンティティーの別のエンティティーへの関連付け" -#: awx/main/models/activity_stream.py:29 +#: awx/main/models/activity_stream.py:32 msgid "Entity was Disassociated with another Entity" msgstr "エンティティーの別のエンティティーとの関連付けの解除" -#: awx/main/models/ad_hoc_commands.py:95 +#: awx/main/models/activity_stream.py:45 +msgid "The cluster node the activity took place on." +msgstr "アクティビティーが発生したクラスターノード。" + +#: awx/main/models/ad_hoc_commands.py:97 msgid "No valid inventory." msgstr "有効なインベントリーはありません。" -#: awx/main/models/ad_hoc_commands.py:102 +#: awx/main/models/ad_hoc_commands.py:104 msgid "You must provide a machine / SSH credential." msgstr "マシン/SSH 認証情報を入力してください。" -#: awx/main/models/ad_hoc_commands.py:113 -#: awx/main/models/ad_hoc_commands.py:121 +#: awx/main/models/ad_hoc_commands.py:115 +#: awx/main/models/ad_hoc_commands.py:123 msgid "Invalid type for ad hoc command" msgstr "アドホックコマンドの無効なタイプ" -#: awx/main/models/ad_hoc_commands.py:116 +#: awx/main/models/ad_hoc_commands.py:118 msgid "Unsupported module for ad hoc commands." msgstr "アドホックコマンドのサポートされていないモジュール。" -#: awx/main/models/ad_hoc_commands.py:124 +#: awx/main/models/ad_hoc_commands.py:126 #, python-format msgid "No argument passed to %s module." msgstr "%s モジュールに渡される引数はありません。" -#: awx/main/models/base.py:33 awx/main/models/base.py:39 -#: awx/main/models/base.py:44 awx/main/models/base.py:49 +#: awx/main/models/base.py:34 awx/main/models/base.py:40 +#: awx/main/models/base.py:45 awx/main/models/base.py:50 msgid "Run" msgstr "実行" -#: awx/main/models/base.py:34 awx/main/models/base.py:40 -#: awx/main/models/base.py:45 awx/main/models/base.py:50 +#: awx/main/models/base.py:35 awx/main/models/base.py:41 +#: awx/main/models/base.py:46 awx/main/models/base.py:51 msgid "Check" msgstr "チェック" -#: awx/main/models/base.py:35 +#: awx/main/models/base.py:36 msgid "Scan" msgstr "スキャン" -#: awx/main/models/credential/__init__.py:110 -msgid "Host" -msgstr "ホスト" - -#: awx/main/models/credential/__init__.py:111 -msgid "The hostname or IP address to use." -msgstr "使用するホスト名または IP アドレス。" - -#: awx/main/models/credential/__init__.py:117 -#: awx/main/models/credential/__init__.py:686 -#: awx/main/models/credential/__init__.py:741 -#: awx/main/models/credential/__init__.py:806 -#: awx/main/models/credential/__init__.py:884 -#: awx/main/models/credential/__init__.py:930 -#: awx/main/models/credential/__init__.py:958 -#: awx/main/models/credential/__init__.py:987 -#: awx/main/models/credential/__init__.py:1051 -#: awx/main/models/credential/__init__.py:1092 -#: awx/main/models/credential/__init__.py:1125 -#: awx/main/models/credential/__init__.py:1177 -msgid "Username" -msgstr "ユーザー名" - -#: awx/main/models/credential/__init__.py:118 -msgid "Username for this credential." -msgstr "この認証情報のユーザー名。" - -#: awx/main/models/credential/__init__.py:124 -#: awx/main/models/credential/__init__.py:690 -#: awx/main/models/credential/__init__.py:745 -#: awx/main/models/credential/__init__.py:810 -#: awx/main/models/credential/__init__.py:934 -#: awx/main/models/credential/__init__.py:962 -#: awx/main/models/credential/__init__.py:991 -#: awx/main/models/credential/__init__.py:1055 -#: awx/main/models/credential/__init__.py:1096 -#: awx/main/models/credential/__init__.py:1129 -#: awx/main/models/credential/__init__.py:1181 -msgid "Password" -msgstr "パスワード" - -#: awx/main/models/credential/__init__.py:125 -msgid "" -"Password for this credential (or \"ASK\" to prompt the user for machine " -"credentials)." -msgstr "この認証情報のパスワード (またはマシンの認証情報を求めるプロンプトを出すには 「ASK」)。" - -#: awx/main/models/credential/__init__.py:132 -msgid "Security Token" -msgstr "セキュリティートークン" - -#: awx/main/models/credential/__init__.py:133 -msgid "Security Token for this credential" -msgstr "この認証情報のセキュリティートークン" - -#: awx/main/models/credential/__init__.py:139 -msgid "Project" -msgstr "プロジェクト" - -#: awx/main/models/credential/__init__.py:140 -msgid "The identifier for the project." -msgstr "プロジェクトの識別子。" - -#: awx/main/models/credential/__init__.py:146 -msgid "Domain" -msgstr "ドメイン" - -#: awx/main/models/credential/__init__.py:147 -msgid "The identifier for the domain." -msgstr "ドメインの識別子。" - -#: awx/main/models/credential/__init__.py:152 -msgid "SSH private key" -msgstr "SSH 秘密鍵" - -#: awx/main/models/credential/__init__.py:153 -msgid "RSA or DSA private key to be used instead of password." -msgstr "パスワードの代わりに使用される RSA または DSA 秘密鍵。" - -#: awx/main/models/credential/__init__.py:159 -msgid "SSH key unlock" -msgstr "SSH キーのロック解除" - -#: awx/main/models/credential/__init__.py:160 -msgid "" -"Passphrase to unlock SSH private key if encrypted (or \"ASK\" to prompt the " -"user for machine credentials)." -msgstr "" -"暗号化されている場合は SSH 秘密鍵のロックを解除するためのパスフレーズ (またはマシンの認証情報を求めるプロンプトを出すには「ASK」)。" - -#: awx/main/models/credential/__init__.py:168 -msgid "Privilege escalation method." -msgstr "権限昇格メソッド。" - -#: awx/main/models/credential/__init__.py:174 -msgid "Privilege escalation username." -msgstr "権限昇格ユーザー名。" - -#: awx/main/models/credential/__init__.py:180 -msgid "Password for privilege escalation method." -msgstr "権限昇格メソッドのパスワード。" - -#: awx/main/models/credential/__init__.py:186 -msgid "Vault password (or \"ASK\" to prompt the user)." -msgstr "Vault パスワード (またはユーザーにプロンプトを出すには「ASK」)。" - -#: awx/main/models/credential/__init__.py:190 -msgid "Whether to use the authorize mechanism." -msgstr "認証メカニズムを使用するかどうか。" - -#: awx/main/models/credential/__init__.py:196 -msgid "Password used by the authorize mechanism." -msgstr "認証メカニズムで使用されるパスワード。" - -#: awx/main/models/credential/__init__.py:202 -msgid "Client Id or Application Id for the credential" -msgstr "認証情報のクライアント ID またはアプリケーション ID" - -#: awx/main/models/credential/__init__.py:208 -msgid "Secret Token for this credential" -msgstr "この認証情報のシークレットトークン" - -#: awx/main/models/credential/__init__.py:214 -msgid "Subscription identifier for this credential" -msgstr "この認証情報のサブスクリプション識別子" - -#: awx/main/models/credential/__init__.py:220 -msgid "Tenant identifier for this credential" -msgstr "この認証情報のテナント識別子" - -#: awx/main/models/credential/__init__.py:244 +#: awx/main/models/credential/__init__.py:96 msgid "" "Specify the type of credential you want to create. Refer to the Ansible " "Tower documentation for details on each type." -msgstr "" -"作成する必要のある証明書のタイプを指定します。それぞれのタイプの詳細については、Ansible Tower ドキュメントを参照してください。" +msgstr "作成する必要のある証明書のタイプを指定します。それぞれのタイプの詳細については、Ansible Tower ドキュメントを参照してください。" -#: awx/main/models/credential/__init__.py:258 -#: awx/main/models/credential/__init__.py:476 +#: awx/main/models/credential/__init__.py:114 +#: awx/main/models/credential/__init__.py:358 msgid "" -"Enter inputs using either JSON or YAML syntax. Use the radio button to " -"toggle between the two. Refer to the Ansible Tower documentation for example" -" syntax." -msgstr "" -"JSON または YAML 構文のいずれかを使用して入力を行います。ラジオボタンを使用してこれらの間で切り替えを行います。構文のサンプルについては " -"Ansible Tower ドキュメントを参照してください。" +"Enter inputs using either JSON or YAML syntax. Refer to the Ansible Tower " +"documentation for example syntax." +msgstr "JSON または YAML 構文のいずれかを使用して入力を行います。構文のサンプルについては Ansible Tower ドキュメントを参照してください。" -#: awx/main/models/credential/__init__.py:457 -#: awx/main/models/credential/__init__.py:681 +#: awx/main/models/credential/__init__.py:329 +#: awx/main/models/credential/__init__.py:602 msgid "Machine" msgstr "マシン" -#: awx/main/models/credential/__init__.py:458 -#: awx/main/models/credential/__init__.py:772 +#: awx/main/models/credential/__init__.py:330 +#: awx/main/models/credential/__init__.py:688 msgid "Vault" msgstr "Vault" -#: awx/main/models/credential/__init__.py:459 -#: awx/main/models/credential/__init__.py:801 +#: awx/main/models/credential/__init__.py:331 +#: awx/main/models/credential/__init__.py:715 msgid "Network" msgstr "ネットワーク" -#: awx/main/models/credential/__init__.py:460 -#: awx/main/models/credential/__init__.py:736 +#: awx/main/models/credential/__init__.py:332 +#: awx/main/models/credential/__init__.py:657 msgid "Source Control" msgstr "ソースコントロール" -#: awx/main/models/credential/__init__.py:461 +#: awx/main/models/credential/__init__.py:333 msgid "Cloud" msgstr "クラウド" -#: awx/main/models/credential/__init__.py:462 -#: awx/main/models/credential/__init__.py:1087 +#: awx/main/models/credential/__init__.py:334 +msgid "Personal Access Token" +msgstr "パーソナルアクセストークン" + +#: awx/main/models/credential/__init__.py:335 +#: awx/main/models/credential/__init__.py:1027 msgid "Insights" msgstr "Insights" -#: awx/main/models/credential/__init__.py:483 +#: awx/main/models/credential/__init__.py:336 +msgid "External" +msgstr "外部" + +#: awx/main/models/credential/__init__.py:337 +msgid "Kubernetes" +msgstr "Kubernetes" + +#: awx/main/models/credential/__init__.py:338 +msgid "Galaxy/Automation Hub" +msgstr "Galaxy / Automation Hub" + +#: awx/main/models/credential/__init__.py:364 msgid "" -"Enter injectors using either JSON or YAML syntax. Use the radio button to " -"toggle between the two. Refer to the Ansible Tower documentation for example" -" syntax." -msgstr "" -"JSON または YAML " -"構文のいずれかを使用してインジェクターを入力します。ラジオボタンを使用してこれらの間で切り替えを行います。構文のサンプルについては Ansible " -"Tower ドキュメントを参照してください。" +"Enter injectors using either JSON or YAML syntax. Refer to the Ansible Tower " +"documentation for example syntax." +msgstr "JSON または YAML 構文のいずれかを使用してインジェクターを入力します。構文のサンプルについては Ansible Tower ドキュメントを参照してください。" -#: awx/main/models/credential/__init__.py:534 +#: awx/main/models/credential/__init__.py:433 #, python-format msgid "adding %s credential type" msgstr "%s 認証情報タイプの追加" -#: awx/main/models/credential/__init__.py:696 -#: awx/main/models/credential/__init__.py:815 +#: awx/main/models/credential/__init__.py:610 +#: awx/main/models/credential/__init__.py:666 +#: awx/main/models/credential/__init__.py:724 +#: awx/main/models/credential/__init__.py:850 +#: awx/main/models/credential/__init__.py:876 +#: awx/main/models/credential/__init__.py:903 +#: awx/main/models/credential/__init__.py:963 +#: awx/main/models/credential/__init__.py:1036 +#: awx/main/models/credential/__init__.py:1067 +#: awx/main/models/credential/__init__.py:1119 +msgid "Password" +msgstr "パスワード" + +#: awx/main/models/credential/__init__.py:616 +#: awx/main/models/credential/__init__.py:729 msgid "SSH Private Key" msgstr "SSH 秘密鍵" -#: awx/main/models/credential/__init__.py:703 -#: awx/main/models/credential/__init__.py:757 -#: awx/main/models/credential/__init__.py:822 +#: awx/main/models/credential/__init__.py:623 +msgid "Signed SSH Certificate" +msgstr "署名済みの SSH 証明書" + +#: awx/main/models/credential/__init__.py:629 +#: awx/main/models/credential/__init__.py:678 +#: awx/main/models/credential/__init__.py:736 msgid "Private Key Passphrase" msgstr "秘密鍵のパスフレーズ" -#: awx/main/models/credential/__init__.py:709 +#: awx/main/models/credential/__init__.py:635 msgid "Privilege Escalation Method" msgstr "権限昇格方法" -#: awx/main/models/credential/__init__.py:711 +#: awx/main/models/credential/__init__.py:637 msgid "" -"Specify a method for \"become\" operations. This is equivalent to specifying" -" the --become-method Ansible parameter." +"Specify a method for \"become\" operations. This is equivalent to specifying " +"the --become-method Ansible parameter." msgstr "「become」操作の方式を指定します。これは --become-method Ansible パラメーターを指定することに相当します。" -#: awx/main/models/credential/__init__.py:716 +#: awx/main/models/credential/__init__.py:642 msgid "Privilege Escalation Username" -msgstr "権限昇格ユーザー名" +msgstr "権限昇格のユーザー名" -#: awx/main/models/credential/__init__.py:720 +#: awx/main/models/credential/__init__.py:646 msgid "Privilege Escalation Password" -msgstr "権限昇格パスワード" +msgstr "権限昇格のパスワード" -#: awx/main/models/credential/__init__.py:750 +#: awx/main/models/credential/__init__.py:671 msgid "SCM Private Key" msgstr "SCM 秘密鍵" -#: awx/main/models/credential/__init__.py:777 +#: awx/main/models/credential/__init__.py:693 msgid "Vault Password" msgstr "Vault パスワード" -#: awx/main/models/credential/__init__.py:783 +#: awx/main/models/credential/__init__.py:699 msgid "Vault Identifier" msgstr "Vault ID" -#: awx/main/models/credential/__init__.py:786 +#: awx/main/models/credential/__init__.py:702 msgid "" -"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+." -msgstr "" -"(オプションの) Vault ID を指定します。これは、複数の Vault パスワードを指定するために --vault-id Ansible " -"パラメーターを指定することに相当します。注: この機能は Ansible 2.4+ でのみ機能します。" +"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+." +msgstr "(オプションの) Vault ID を指定します。これは、複数の Vault パスワードを指定するために --vault-id Ansible パラメーターを指定することに相当します。注: この機能は Ansible 2.4+ でのみ機能します。" -#: awx/main/models/credential/__init__.py:827 +#: awx/main/models/credential/__init__.py:741 msgid "Authorize" -msgstr "認証" +msgstr "承認" -#: awx/main/models/credential/__init__.py:831 +#: awx/main/models/credential/__init__.py:745 msgid "Authorize Password" -msgstr "認証パスワード" +msgstr "パスワードの承認" -#: awx/main/models/credential/__init__.py:848 +#: awx/main/models/credential/__init__.py:759 msgid "Amazon Web Services" msgstr "Amazon Web Services" -#: awx/main/models/credential/__init__.py:853 +#: awx/main/models/credential/__init__.py:764 msgid "Access Key" msgstr "アクセスキー" -#: awx/main/models/credential/__init__.py:857 +#: awx/main/models/credential/__init__.py:768 msgid "Secret Key" msgstr "シークレットキー" -#: awx/main/models/credential/__init__.py:862 +#: awx/main/models/credential/__init__.py:773 msgid "STS Token" msgstr "STS トークン" -#: awx/main/models/credential/__init__.py:865 +#: awx/main/models/credential/__init__.py:776 msgid "" "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." -msgstr "" -"セキュリティートークンサービス (STS) は、AWS Identity and Access Management (IAM) " -"ユーザーの一時的な、権限の制限された認証情報を要求できる web サービスです。" +msgstr "セキュリティートークンサービス (STS) は、AWS Identity and Access Management (IAM) ユーザーの一時的な、権限の制限された認証情報を要求できる web サービスです。" -#: awx/main/models/credential/__init__.py:879 awx/main/models/inventory.py:990 +#: awx/main/models/credential/__init__.py:788 awx/main/models/inventory.py:826 msgid "OpenStack" msgstr "OpenStack" -#: awx/main/models/credential/__init__.py:888 +#: awx/main/models/credential/__init__.py:797 msgid "Password (API Key)" msgstr "パスワード (API キー)" -#: awx/main/models/credential/__init__.py:893 -#: awx/main/models/credential/__init__.py:1120 +#: awx/main/models/credential/__init__.py:802 +#: awx/main/models/credential/__init__.py:1058 msgid "Host (Authentication URL)" msgstr "ホスト (認証 URL)" -#: awx/main/models/credential/__init__.py:895 +#: awx/main/models/credential/__init__.py:804 msgid "" -"The host to authenticate with. For example, " -"https://openstack.business.com/v2.0/" +"The host to authenticate with. For example, https://openstack.business.com/" +"v2.0/" msgstr "認証に使用するホスト。例: https://openstack.business.com/v2.0/" -#: awx/main/models/credential/__init__.py:899 +#: awx/main/models/credential/__init__.py:808 msgid "Project (Tenant Name)" msgstr "プロジェクト (テナント名)" -#: awx/main/models/credential/__init__.py:903 +#: awx/main/models/credential/__init__.py:812 +msgid "Project (Domain Name)" +msgstr "プロジェクト (ドメイン名)" + +#: awx/main/models/credential/__init__.py:816 msgid "Domain Name" msgstr "ドメイン名" -#: awx/main/models/credential/__init__.py:905 +#: awx/main/models/credential/__init__.py:818 msgid "" "OpenStack domains define administrative boundaries. It is only needed for " "Keystone v3 authentication URLs. Refer to Ansible Tower documentation for " "common scenarios." -msgstr "" -"OpenStack ドメインは管理上の境界を定義します。これは Keystone v3 認証 URL にのみ必要です。共通するシナリオについては " -"Ansible Tower ドキュメントを参照してください。" +msgstr "OpenStack ドメインは管理上の境界を定義します。これは Keystone v3 認証 URL にのみ必要です。共通するシナリオについては Ansible Tower ドキュメントを参照してください。" + +#: awx/main/models/credential/__init__.py:824 +#: awx/main/models/credential/__init__.py:1131 +#: awx/main/models/credential/__init__.py:1166 +msgid "Verify SSL" +msgstr "SSL の検証" -#: awx/main/models/credential/__init__.py:919 awx/main/models/inventory.py:987 +#: awx/main/models/credential/__init__.py:835 awx/main/models/inventory.py:824 msgid "VMware vCenter" msgstr "VMware vCenter" -#: awx/main/models/credential/__init__.py:924 +#: awx/main/models/credential/__init__.py:840 msgid "VCenter Host" msgstr "vCenter ホスト" -#: awx/main/models/credential/__init__.py:926 +#: awx/main/models/credential/__init__.py:842 msgid "" "Enter the hostname or IP address that corresponds to your VMware vCenter." msgstr "VMware vCenter に対応するホスト名または IP アドレスを入力します。" -#: awx/main/models/credential/__init__.py:947 awx/main/models/inventory.py:988 +#: awx/main/models/credential/__init__.py:861 awx/main/models/inventory.py:825 msgid "Red Hat Satellite 6" msgstr "Red Hat Satellite 6" -#: awx/main/models/credential/__init__.py:952 +#: awx/main/models/credential/__init__.py:866 msgid "Satellite 6 URL" msgstr "Satellite 6 URL" -#: awx/main/models/credential/__init__.py:954 +#: awx/main/models/credential/__init__.py:868 msgid "" "Enter the URL that corresponds to your Red Hat Satellite 6 server. For " "example, https://satellite.example.org" -msgstr "" -"Red Hat Satellite 6 Server に対応する URL を入力します (例: " -"https://satellite.example.org)。" +msgstr "Red Hat Satellite 6 Server に対応する URL を入力します (例: https://satellite.example.org)。" -#: awx/main/models/credential/__init__.py:975 awx/main/models/inventory.py:989 +#: awx/main/models/credential/__init__.py:887 msgid "Red Hat CloudForms" msgstr "Red Hat CloudForms" -#: awx/main/models/credential/__init__.py:980 +#: awx/main/models/credential/__init__.py:892 msgid "CloudForms URL" msgstr "CloudForms URL" -#: awx/main/models/credential/__init__.py:982 +#: awx/main/models/credential/__init__.py:894 msgid "" "Enter the URL for the virtual machine that corresponds to your CloudForms " "instance. For example, https://cloudforms.example.org" -msgstr "" -"CloudForms インスタンスに対応する仮想マシンの URL を入力します (例: https://cloudforms.example.org)。" +msgstr "CloudForms インスタンスに対応する仮想マシンの URL を入力します (例: https://cloudforms.example.org)。" -#: awx/main/models/credential/__init__.py:1004 -#: awx/main/models/inventory.py:985 +#: awx/main/models/credential/__init__.py:914 awx/main/models/inventory.py:822 msgid "Google Compute Engine" msgstr "Google Compute Engine" -#: awx/main/models/credential/__init__.py:1009 +#: awx/main/models/credential/__init__.py:919 msgid "Service Account Email Address" msgstr "サービスアカウントのメールアドレス" -#: awx/main/models/credential/__init__.py:1011 +#: awx/main/models/credential/__init__.py:921 msgid "" "The email address assigned to the Google Compute Engine service account." msgstr "Google Compute Engine サービスアカウントに割り当てられたメールアドレス。" -#: awx/main/models/credential/__init__.py:1017 +#: awx/main/models/credential/__init__.py:927 msgid "" "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" -msgstr "" -"プロジェクト ID は GCE によって割り当てられる識別情報です。これは 3 語か、または 2 語とそれに続く 3 " -"桁の数字のいずれかで構成されます。例: project-id-000、another-project-id" +msgstr "プロジェクト ID は GCE によって割り当てられる識別情報です。これは 3 語か、または 2 語とそれに続く 3 桁の数字のいずれかで構成されます。例: project-id-000、another-project-id" -#: awx/main/models/credential/__init__.py:1023 +#: awx/main/models/credential/__init__.py:933 msgid "RSA Private Key" msgstr "RSA 秘密鍵" -#: awx/main/models/credential/__init__.py:1028 +#: awx/main/models/credential/__init__.py:938 msgid "" -"Paste the contents of the PEM file associated with the service account " -"email." +"Paste the contents of the PEM file associated with the service account email." msgstr "サービスアカウントメールに関連付けられた PEM ファイルの内容を貼り付けます。" -#: awx/main/models/credential/__init__.py:1040 -#: awx/main/models/inventory.py:986 +#: awx/main/models/credential/__init__.py:948 awx/main/models/inventory.py:823 msgid "Microsoft Azure Resource Manager" msgstr "Microsoft Azure Resource Manager" -#: awx/main/models/credential/__init__.py:1045 +#: awx/main/models/credential/__init__.py:953 msgid "Subscription ID" msgstr "サブスクリプション ID" -#: awx/main/models/credential/__init__.py:1047 +#: awx/main/models/credential/__init__.py:955 msgid "Subscription ID is an Azure construct, which is mapped to a username." msgstr "サブスクリプション ID は、ユーザー名にマップされる Azure コンストラクトです。" -#: awx/main/models/credential/__init__.py:1060 -msgid "Client ID" -msgstr "クライアント ID" - -#: awx/main/models/credential/__init__.py:1069 -msgid "Tenant ID" -msgstr "テナント ID" - -#: awx/main/models/credential/__init__.py:1073 +#: awx/main/models/credential/__init__.py:981 msgid "Azure Cloud Environment" msgstr "Azure クラウド環境" -#: awx/main/models/credential/__init__.py:1075 +#: awx/main/models/credential/__init__.py:983 msgid "" "Environment variable AZURE_CLOUD_ENVIRONMENT when using Azure GovCloud or " "Azure stack." msgstr "Azure GovCloud または Azure スタック使用時の環境変数 AZURE_CLOUD_ENVIRONMENT。" -#: awx/main/models/credential/__init__.py:1115 -#: awx/main/models/inventory.py:991 +#: awx/main/models/credential/__init__.py:993 +msgid "GitHub Personal Access Token" +msgstr "GitHub パーソナルアクセストークン" + +#: awx/main/models/credential/__init__.py:1001 +msgid "This token needs to come from your profile settings in GitHub" +msgstr "このトークンは GitHub のプロファイル設定から取得する必要があります。" + +#: awx/main/models/credential/__init__.py:1010 +msgid "GitLab Personal Access Token" +msgstr "GitLab パーソナルアクセストークン" + +#: awx/main/models/credential/__init__.py:1018 +msgid "This token needs to come from your profile settings in GitLab" +msgstr "このトークンは GitLab のプロファイル設定から取得する必要があります。" + +#: awx/main/models/credential/__init__.py:1053 awx/main/models/inventory.py:827 msgid "Red Hat Virtualization" msgstr "Red Hat Virtualization" -#: awx/main/models/credential/__init__.py:1122 +#: awx/main/models/credential/__init__.py:1060 msgid "The host to authenticate with." -msgstr "認証に使用するホスト。" +msgstr "認証するホスト。" -#: awx/main/models/credential/__init__.py:1134 +#: awx/main/models/credential/__init__.py:1072 msgid "CA File" msgstr "CA ファイル" -#: awx/main/models/credential/__init__.py:1136 +#: awx/main/models/credential/__init__.py:1074 msgid "Absolute file path to the CA file to use (optional)" msgstr "使用する CA ファイルへの絶対ファイルパス (オプション)" -#: awx/main/models/credential/__init__.py:1167 -#: awx/main/models/inventory.py:992 +#: awx/main/models/credential/__init__.py:1103 awx/main/models/inventory.py:828 msgid "Ansible Tower" msgstr "Ansible Tower" -#: awx/main/models/credential/__init__.py:1172 +#: awx/main/models/credential/__init__.py:1108 msgid "Ansible Tower Hostname" msgstr "Ansible Tower ホスト名" -#: awx/main/models/credential/__init__.py:1174 +#: awx/main/models/credential/__init__.py:1110 msgid "The Ansible Tower base URL to authenticate with." msgstr "認証で使用する Ansible Tower ベース URL。" -#: awx/main/models/credential/__init__.py:1186 -msgid "Verify SSL" -msgstr "SSL の検証" +#: awx/main/models/credential/__init__.py:1115 +msgid "" +"The Ansible Tower user to authenticate as.This should not be set if an OAuth " +"token is being used." +msgstr "認証する AnsibleTower ユーザー。OAuth トークンが使用されている場合は、これを設定しないでください。" + +#: awx/main/models/credential/__init__.py:1124 +msgid "OAuth Token" +msgstr "OAuth トークン" + +#: awx/main/models/credential/__init__.py:1127 +msgid "" +"An OAuth token to use to authenticate to Tower with.This should not be set " +"if username/password are being used." +msgstr "Tower への認証に使用する OAuth トークン。ユーザー名/パスワードが使用されている場合は設定しないでください。" + +#: awx/main/models/credential/__init__.py:1152 +msgid "OpenShift or Kubernetes API Bearer Token" +msgstr "OpenShift または Kubernetes API Bearer トークン" + +#: awx/main/models/credential/__init__.py:1156 +msgid "OpenShift or Kubernetes API Endpoint" +msgstr "OpenShift または Kubernetes API エンドポイント" + +#: awx/main/models/credential/__init__.py:1158 +msgid "The OpenShift or Kubernetes API Endpoint to authenticate with." +msgstr "認証する OpenShift または Kubernetes API エンドポイント。" + +#: awx/main/models/credential/__init__.py:1161 +msgid "API authentication bearer token" +msgstr "API 認証ベアラートークン" + +#: awx/main/models/credential/__init__.py:1171 +msgid "Certificate Authority data" +msgstr "認証局データ" + +#: awx/main/models/credential/__init__.py:1184 +msgid "Ansible Galaxy/Automation Hub API Token" +msgstr "Ansible Galaxy/Automation Hub API トークン" + +#: awx/main/models/credential/__init__.py:1188 +msgid "Galaxy Server URL" +msgstr "Galaxy Server URL" + +#: awx/main/models/credential/__init__.py:1190 +msgid "The URL of the Galaxy instance to connect to." +msgstr "接続する Galaxy インスタンスの URL。" + +#: awx/main/models/credential/__init__.py:1193 +msgid "Auth Server URL" +msgstr "認証サーバー URL" + +#: awx/main/models/credential/__init__.py:1196 +msgid "The URL of a Keycloak server token_endpoint, if using SSO auth." +msgstr "SSO 認証を使用している場合は、Keycloak サーバー token_endpoint の URL。" + +#: awx/main/models/credential/__init__.py:1201 +msgid "API Token" +msgstr "API トークン" + +#: awx/main/models/credential/__init__.py:1205 +msgid "A token to use for authentication against the Galaxy instance." +msgstr "Galaxy インスタンスに対する認証に使用するトークン。" + +#: awx/main/models/credential/__init__.py:1244 +msgid "Target must be a non-external credential" +msgstr "ターゲットには、外部の認証情報以外を使用してください。" + +#: awx/main/models/credential/__init__.py:1249 +msgid "Source must be an external credential" +msgstr "ソースは、外部の認証情報でなければなりません。" + +#: awx/main/models/credential/__init__.py:1256 +msgid "Input field must be defined on target credential (options are {})." +msgstr "入力フィールドは、ターゲットの認証情報 (オプションは {}) で定義する必要があります。" -#: awx/main/models/events.py:105 awx/main/models/events.py:630 +#: awx/main/models/events.py:165 awx/main/models/events.py:707 msgid "Host Failed" msgstr "ホストの失敗" -#: awx/main/models/events.py:106 awx/main/models/events.py:631 +#: awx/main/models/events.py:166 +msgid "Host Started" +msgstr "ホストの開始" + +#: awx/main/models/events.py:167 awx/main/models/events.py:708 msgid "Host OK" msgstr "ホスト OK" -#: awx/main/models/events.py:107 +#: awx/main/models/events.py:168 msgid "Host Failure" msgstr "ホストの失敗" -#: awx/main/models/events.py:108 awx/main/models/events.py:637 +#: awx/main/models/events.py:169 awx/main/models/events.py:714 msgid "Host Skipped" msgstr "ホストがスキップされました" -#: awx/main/models/events.py:109 awx/main/models/events.py:632 +#: awx/main/models/events.py:170 awx/main/models/events.py:709 msgid "Host Unreachable" msgstr "ホストに到達できません" -#: awx/main/models/events.py:110 awx/main/models/events.py:124 +#: awx/main/models/events.py:171 awx/main/models/events.py:185 msgid "No Hosts Remaining" msgstr "残りのホストがありません" -#: awx/main/models/events.py:111 +#: awx/main/models/events.py:172 msgid "Host Polling" msgstr "ホストのポーリング" -#: awx/main/models/events.py:112 +#: awx/main/models/events.py:173 msgid "Host Async OK" msgstr "ホストの非同期 OK" -#: awx/main/models/events.py:113 +#: awx/main/models/events.py:174 msgid "Host Async Failure" msgstr "ホストの非同期失敗" -#: awx/main/models/events.py:114 +#: awx/main/models/events.py:175 msgid "Item OK" msgstr "項目 OK" -#: awx/main/models/events.py:115 +#: awx/main/models/events.py:176 msgid "Item Failed" msgstr "項目の失敗" -#: awx/main/models/events.py:116 +#: awx/main/models/events.py:177 msgid "Item Skipped" msgstr "項目のスキップ" -#: awx/main/models/events.py:117 +#: awx/main/models/events.py:178 msgid "Host Retry" msgstr "ホストの再試行" -#: awx/main/models/events.py:119 +#: awx/main/models/events.py:180 msgid "File Difference" msgstr "ファイルの相違点" -#: awx/main/models/events.py:120 +#: awx/main/models/events.py:181 msgid "Playbook Started" msgstr "Playbook の開始" -#: awx/main/models/events.py:121 +#: awx/main/models/events.py:182 msgid "Running Handlers" msgstr "実行中のハンドラー" -#: awx/main/models/events.py:122 +#: awx/main/models/events.py:183 msgid "Including File" msgstr "組み込みファイル" -#: awx/main/models/events.py:123 +#: awx/main/models/events.py:184 msgid "No Hosts Matched" msgstr "一致するホストがありません" -#: awx/main/models/events.py:125 +#: awx/main/models/events.py:186 msgid "Task Started" msgstr "タスクの開始" -#: awx/main/models/events.py:127 +#: awx/main/models/events.py:188 msgid "Variables Prompted" msgstr "変数のプロモート" -#: awx/main/models/events.py:128 +#: awx/main/models/events.py:189 msgid "Gathering Facts" msgstr "ファクトの収集" -#: awx/main/models/events.py:129 +#: awx/main/models/events.py:190 msgid "internal: on Import for Host" msgstr "内部: ホストのインポート時" -#: awx/main/models/events.py:130 +#: awx/main/models/events.py:191 msgid "internal: on Not Import for Host" msgstr "内部: ホストの非インポート時" -#: awx/main/models/events.py:131 +#: awx/main/models/events.py:192 msgid "Play Started" msgstr "プレイの開始" -#: awx/main/models/events.py:132 +#: awx/main/models/events.py:193 msgid "Playbook Complete" msgstr "Playbook の完了" -#: awx/main/models/events.py:136 awx/main/models/events.py:647 +#: awx/main/models/events.py:197 awx/main/models/events.py:724 msgid "Debug" msgstr "デバッグ" -#: awx/main/models/events.py:137 awx/main/models/events.py:648 +#: awx/main/models/events.py:198 awx/main/models/events.py:725 msgid "Verbose" msgstr "詳細" -#: awx/main/models/events.py:138 awx/main/models/events.py:649 +#: awx/main/models/events.py:199 awx/main/models/events.py:726 msgid "Deprecated" msgstr "非推奨" -#: awx/main/models/events.py:139 awx/main/models/events.py:650 +#: awx/main/models/events.py:200 awx/main/models/events.py:727 msgid "Warning" msgstr "警告" -#: awx/main/models/events.py:140 awx/main/models/events.py:651 +#: awx/main/models/events.py:201 awx/main/models/events.py:728 msgid "System Warning" msgstr "システム警告" -#: awx/main/models/events.py:141 awx/main/models/events.py:652 -#: awx/main/models/unified_jobs.py:67 +#: awx/main/models/events.py:202 awx/main/models/events.py:729 +#: awx/main/models/unified_jobs.py:75 msgid "Error" msgstr "エラー" -#: awx/main/models/fact.py:25 -msgid "Host for the facts that the fact scan captured." -msgstr "ファクトスキャンがキャプチャーしたファクトのホスト。" - -#: awx/main/models/fact.py:30 -msgid "Date and time of the corresponding fact scan gathering time." -msgstr "対応するファクトスキャン収集時間の日時。" - -#: awx/main/models/fact.py:33 -msgid "" -"Arbitrary JSON structure of module facts captured at timestamp for a single " -"host." -msgstr "単一ホストのタイムスタンプでキャプチャーされるモジュールファクトの任意の JSON 構造。" - -#: awx/main/models/ha.py:181 +#: awx/main/models/ha.py:184 msgid "Instances that are members of this InstanceGroup" msgstr "このインスタンスグループのメンバーであるインスタンス" -#: awx/main/models/ha.py:186 +#: awx/main/models/ha.py:189 msgid "Instance Group to remotely control this group." msgstr "このグループをリモートで制御するためのインスタンスグループ" -#: awx/main/models/ha.py:193 +#: awx/main/models/ha.py:209 msgid "Percentage of Instances to automatically assign to this group" -msgstr "このグループに自動的に割り当てるインスタンスのパーセンテージ" +msgstr "このグループに自動的に割り当てるインスタンスの割合" -#: awx/main/models/ha.py:197 +#: awx/main/models/ha.py:213 msgid "" "Static minimum number of Instances to automatically assign to this group" msgstr "このグループに自動的に割り当てるインスタンスの静的な最小数。" -#: awx/main/models/ha.py:202 +#: awx/main/models/ha.py:218 msgid "" "List of exact-match Instances that will always be automatically assigned to " "this group" msgstr "このグループに常に自動的に割り当てられる完全一致のインスタンスの一覧" -#: awx/main/models/inventory.py:61 +#: awx/main/models/inventory.py:74 msgid "Hosts have a direct link to this inventory." msgstr "ホストにはこのインベントリーへの直接のリンクがあります。" -#: awx/main/models/inventory.py:62 +#: awx/main/models/inventory.py:75 msgid "Hosts for inventory generated using the host_filter property." msgstr "host_filter プロパティーを使用して生成されたインベントリーのホスト。" -#: awx/main/models/inventory.py:67 +#: awx/main/models/inventory.py:80 msgid "inventories" msgstr "インベントリー" -#: awx/main/models/inventory.py:74 +#: awx/main/models/inventory.py:87 msgid "Organization containing this inventory." msgstr "このインベントリーを含む組織。" -#: awx/main/models/inventory.py:81 +#: awx/main/models/inventory.py:94 msgid "Inventory variables in JSON or YAML format." msgstr "JSON または YAML 形式のインベントリー変数。" -#: awx/main/models/inventory.py:86 -msgid "Flag indicating whether any hosts in this inventory have failed." -msgstr "このインベントリーのホストが失敗したかどうかを示すフラグ。" - -#: awx/main/models/inventory.py:91 -msgid "Total number of hosts in this inventory." -msgstr "このインべントリー内のホストの合計数。" +#: awx/main/models/inventory.py:99 +msgid "" +"This field is deprecated and will be removed in a future release. Flag " +"indicating whether any hosts in this inventory have failed." +msgstr "このフィールドは非推奨で、今後のリリースで削除予定です。このインベントリーのホストが失敗したかどうかを示すフラグ。" -#: awx/main/models/inventory.py:96 -msgid "Number of hosts in this inventory with active failures." -msgstr "アクティブなエラーのあるこのインベントリー内のホストの数。" +#: awx/main/models/inventory.py:105 +msgid "" +"This field is deprecated and will be removed in a future release. Total " +"number of hosts in this inventory." +msgstr "このフィールドは非推奨で、今後のリリースで削除予定です。このインベントリーでの合計ホスト数。" -#: awx/main/models/inventory.py:101 -msgid "Total number of groups in this inventory." -msgstr "このインべントリー内のグループの合計数。" +#: awx/main/models/inventory.py:111 +msgid "" +"This field is deprecated and will be removed in a future release. Number of " +"hosts in this inventory with active failures." +msgstr "このフィールドは非推奨で、今後のリリースで削除予定です。このインベントリーで障害が発生中のホスト数。" -#: awx/main/models/inventory.py:106 -msgid "Number of groups in this inventory with active failures." -msgstr "アクティブなエラーのあるこのインベントリー内のグループの数。" +#: awx/main/models/inventory.py:117 +msgid "" +"This field is deprecated and will be removed in a future release. Total " +"number of groups in this inventory." +msgstr "このフィールドは非推奨で、今後のリリースで削除予定です。このインベントリーでの合計グループ数。" -#: awx/main/models/inventory.py:111 +#: awx/main/models/inventory.py:123 msgid "" -"Flag indicating whether this inventory has any external inventory sources." -msgstr "このインベントリーに外部のインベントリーソースがあるかどうかを示すフラグ。" +"This field is deprecated and will be removed in a future release. Flag " +"indicating whether this inventory has any external inventory sources." +msgstr "このフィールドは非推奨で、今後のリリースで削除予定です。このインベントリーに外部のインベントリーソースがあるかどうかを示すフラグ。" -#: awx/main/models/inventory.py:116 +#: awx/main/models/inventory.py:129 msgid "" "Total number of external inventory sources configured within this inventory." msgstr "このインベントリー内で設定される外部インベントリーソースの合計数。" -#: awx/main/models/inventory.py:121 +#: awx/main/models/inventory.py:134 msgid "Number of external inventory sources in this inventory with failures." msgstr "エラーのあるこのインベントリー内の外部インベントリーソースの数。" -#: awx/main/models/inventory.py:128 +#: awx/main/models/inventory.py:141 msgid "Kind of inventory being represented." msgstr "表示されているインベントリーの種類。" -#: awx/main/models/inventory.py:134 +#: awx/main/models/inventory.py:147 msgid "Filter that will be applied to the hosts of this inventory." msgstr "このインべントリーのホストに適用されるフィルター。" -#: awx/main/models/inventory.py:161 +#: awx/main/models/inventory.py:175 msgid "" "Credentials to be used by hosts belonging to this inventory when accessing " "Red Hat Insights API." msgstr "Red Hat Insights API へのアクセス時にこのインベントリーに属するホストによって使用される認証情報。" -#: awx/main/models/inventory.py:170 +#: awx/main/models/inventory.py:184 msgid "Flag indicating the inventory is being deleted." msgstr "このインベントリーが削除されていることを示すフラグ。" -#: awx/main/models/inventory.py:459 +#: awx/main/models/inventory.py:239 +msgid "Could not parse subset as slice specification." +msgstr "サブセットをスライスの詳細として解析できませんでした。" + +#: awx/main/models/inventory.py:243 +msgid "Slice number must be less than total number of slices." +msgstr "スライス番号はスライスの合計数より小さくなければなりません。" + +#: awx/main/models/inventory.py:245 +msgid "Slice number must be 1 or higher." +msgstr "スライス番号は 1 以上でなければなりません。" + +#: awx/main/models/inventory.py:382 msgid "Assignment not allowed for Smart Inventory" msgstr "割り当てはスマートインベントリーでは許可されません" -#: awx/main/models/inventory.py:461 awx/main/models/projects.py:159 +#: awx/main/models/inventory.py:384 awx/main/models/projects.py:167 msgid "Credential kind must be 'insights'." msgstr "認証情報の種類は「insights」である必要があります。" -#: awx/main/models/inventory.py:546 +#: awx/main/models/inventory.py:469 msgid "Is this host online and available for running jobs?" msgstr "このホストはオンラインで、ジョブを実行するために利用できますか?" -#: awx/main/models/inventory.py:552 +#: awx/main/models/inventory.py:475 msgid "" "The value used by the remote inventory source to uniquely identify the host" msgstr "ホストを一意に識別するためにリモートインベントリーソースで使用される値" -#: awx/main/models/inventory.py:557 +#: awx/main/models/inventory.py:480 msgid "Host variables in JSON or YAML format." msgstr "JSON または YAML 形式のホスト変数。" -#: awx/main/models/inventory.py:579 -msgid "Flag indicating whether the last job failed for this host." -msgstr "このホストの最後のジョブが失敗したかどうかを示すフラグ。" - -#: awx/main/models/inventory.py:584 -msgid "" -"Flag indicating whether this host was created/updated from any external " -"inventory sources." -msgstr "このホストが外部インベントリーソースから作成/更新されたかどうかを示すフラグ。" - -#: awx/main/models/inventory.py:590 +#: awx/main/models/inventory.py:503 msgid "Inventory source(s) that created or modified this host." msgstr "このホストを作成または変更したインベントリーソース。" -#: awx/main/models/inventory.py:595 +#: awx/main/models/inventory.py:508 msgid "Arbitrary JSON structure of most recent ansible_facts, per-host." msgstr "ホスト別の最新 ansible_facts の任意の JSON 構造。" -#: awx/main/models/inventory.py:601 +#: awx/main/models/inventory.py:514 msgid "The date and time ansible_facts was last modified." msgstr "ansible_facts の最終変更日時。" -#: awx/main/models/inventory.py:608 +#: awx/main/models/inventory.py:521 msgid "Red Hat Insights host unique identifier." msgstr "Red Hat Insights ホスト固有 ID。" -#: awx/main/models/inventory.py:743 +#: awx/main/models/inventory.py:635 msgid "Group variables in JSON or YAML format." msgstr "JSON または YAML 形式のグループ変数。" -#: awx/main/models/inventory.py:749 +#: awx/main/models/inventory.py:641 msgid "Hosts associated directly with this group." msgstr "このグループに直接関連付けられたホスト。" -#: awx/main/models/inventory.py:754 -msgid "Total number of hosts directly or indirectly in this group." -msgstr "このグループに直接的または間接的に属するホストの合計数。" - -#: awx/main/models/inventory.py:759 -msgid "Flag indicating whether this group has any hosts with active failures." -msgstr "このグループにアクティブなエラーのあるホストがあるかどうかを示すフラグ。" - -#: awx/main/models/inventory.py:764 -msgid "Number of hosts in this group with active failures." -msgstr "アクティブなエラーのあるこのグループ内のホストの数。" - -#: awx/main/models/inventory.py:769 -msgid "Total number of child groups contained within this group." -msgstr "このグループに含まれる子グループの合計数。" - -#: awx/main/models/inventory.py:774 -msgid "Number of child groups within this group that have active failures." -msgstr "アクティブなエラーのあるこのグループ内の子グループの数。" - -#: awx/main/models/inventory.py:779 -msgid "" -"Flag indicating whether this group was created/updated from any external " -"inventory sources." -msgstr "このグループが外部インベントリーソースから作成/更新されたかどうかを示すフラグ。" - -#: awx/main/models/inventory.py:785 +#: awx/main/models/inventory.py:647 msgid "Inventory source(s) that created or modified this group." msgstr "このグループを作成または変更したインベントリーソース。" -#: awx/main/models/inventory.py:981 awx/main/models/projects.py:53 -#: awx/main/models/unified_jobs.py:519 -msgid "Manual" -msgstr "手動" - -#: awx/main/models/inventory.py:982 +#: awx/main/models/inventory.py:819 msgid "File, Directory or Script" msgstr "ファイル、ディレクトリーまたはスクリプト" -#: awx/main/models/inventory.py:983 +#: awx/main/models/inventory.py:820 msgid "Sourced from a Project" msgstr "ソース: プロジェクト" -#: awx/main/models/inventory.py:984 +#: awx/main/models/inventory.py:821 msgid "Amazon EC2" msgstr "Amazon EC2" -#: awx/main/models/inventory.py:993 +#: awx/main/models/inventory.py:829 msgid "Custom Script" msgstr "カスタムスクリプト" -#: awx/main/models/inventory.py:1110 +#: awx/main/models/inventory.py:863 msgid "Inventory source variables in YAML or JSON format." msgstr "YAML または JSON 形式のインベントリーソース変数。" -#: awx/main/models/inventory.py:1121 +#: awx/main/models/inventory.py:868 msgid "" -"Comma-separated list of filter expressions (EC2 only). Hosts are imported " -"when ANY of the filters match." -msgstr "カンマ区切りのフィルター式の一覧 (EC2 のみ) です。ホストは、フィルターのいずれかが一致する場合にインポートされます。" +"Retrieve the enabled state from the given dict of host variables. The " +"enabled variable may be specified as \"foo.bar\", in which case the lookup " +"will traverse into nested dicts, equivalent to: from_dict.get(\"foo\", {})." +"get(\"bar\", default)" +msgstr "ホスト変数の指定された辞書から有効な状態を取得します。有効な変数は「foo.bar」として指定できます。その場合、ルックアップはネストされた辞書に移動します。これは、from_dict.get(\"foo\", {}).get(\"bar\", default) と同等です。" -#: awx/main/models/inventory.py:1127 -msgid "Limit groups automatically created from inventory source (EC2 only)." -msgstr "インベントリーソースから自動的に作成されるグループを制限します (EC2 のみ)。" +#: awx/main/models/inventory.py:876 +msgid "" +"Only used when enabled_var is set. Value when the host is considered " +"enabled. For example if enabled_var=\"status.power_state\"and enabled_value=" +"\"powered_on\" with host variables:{ \"status\": { \"power_state\": " +"\"powered_on\", \"created\": \"2020-08-04T18:13:04+00:00\", \"healthy" +"\": true }, \"name\": \"foobar\", \"ip_address\": \"192.168.2.1\"}" +"The host would be marked enabled. If power_state where any value other than " +"powered_on then the host would be disabled when imported into Tower. If the " +"key is not found then the host will be enabled" +msgstr "enabled_var が設定されている場合にのみ使用されます。ホストが有効と見なされる場合の値です。たとえば、ホスト変数 { \"status\": { \"power_state\": \"powered_on\", \"created\": \"2020-08-04T18:13:04+00:00\", \"healthy\": true }, \"name\": \"foobar\", \"ip_address\": \"192.168.2.1\"} を使用した enabled_var=\"status.power_state\" および enabled_value=\"powered_on\" の場合は、ホストは有効とマークされます。powered_on 以外の値が power_state の場合は、Tower にインポートするとホストは無効になります。キーが見つからない場合は、ホストが有効になります" + +#: awx/main/models/inventory.py:896 +msgid "Regex where only matching hosts will be imported into Tower." +msgstr "一致するホストのみが Tower にインポートされる正規表現。" -#: awx/main/models/inventory.py:1131 +#: awx/main/models/inventory.py:900 msgid "Overwrite local groups and hosts from remote inventory source." msgstr "リモートインベントリーソースからのローカルグループおよびホストを上書きします。" -#: awx/main/models/inventory.py:1135 +#: awx/main/models/inventory.py:904 msgid "Overwrite local variables from remote inventory source." msgstr "リモートインベントリーソースからのローカル変数を上書きします。" -#: awx/main/models/inventory.py:1140 awx/main/models/jobs.py:140 -#: awx/main/models/projects.py:128 +#: awx/main/models/inventory.py:909 awx/main/models/jobs.py:154 +#: awx/main/models/projects.py:136 msgid "The amount of time (in seconds) to run before the task is canceled." msgstr "タスクが取り消される前の実行時間 (秒数)。" -#: awx/main/models/inventory.py:1173 -msgid "Image ID" -msgstr "イメージ ID" - -#: awx/main/models/inventory.py:1174 -msgid "Availability Zone" -msgstr "アベイラビリティーゾーン" - -#: awx/main/models/inventory.py:1175 -msgid "Account" -msgstr "アカウント" - -#: awx/main/models/inventory.py:1176 -msgid "Instance ID" -msgstr "インスタンス ID" - -#: awx/main/models/inventory.py:1177 -msgid "Instance State" -msgstr "インスタンスの状態" - -#: awx/main/models/inventory.py:1178 -msgid "Platform" -msgstr "プラットフォーム" - -#: awx/main/models/inventory.py:1179 -msgid "Instance Type" -msgstr "インスタンスタイプ" - -#: awx/main/models/inventory.py:1180 -msgid "Key Name" -msgstr "キー名" - -#: awx/main/models/inventory.py:1181 -msgid "Region" -msgstr "リージョン" - -#: awx/main/models/inventory.py:1182 -msgid "Security Group" -msgstr "セキュリティーグループ" - -#: awx/main/models/inventory.py:1183 -msgid "Tags" -msgstr "タグ" - -#: awx/main/models/inventory.py:1184 -msgid "Tag None" -msgstr "タグ None" - -#: awx/main/models/inventory.py:1185 -msgid "VPC ID" -msgstr "VPC ID" - -#: awx/main/models/inventory.py:1253 +#: awx/main/models/inventory.py:926 #, python-format msgid "" "Cloud-based inventory sources (such as %s) require credentials for the " "matching cloud service." msgstr "クラウドベースのインベントリーソース (%s など) には一致するクラウドサービスの認証情報が必要です。" -#: awx/main/models/inventory.py:1259 +#: awx/main/models/inventory.py:932 msgid "Credential is required for a cloud source." msgstr "認証情報がクラウドソースに必要です。" -#: awx/main/models/inventory.py:1262 +#: awx/main/models/inventory.py:935 msgid "" "Credentials of type machine, source control, insights and vault are " "disallowed for custom inventory sources." msgstr "タイプがマシン、ソースコントロール、Insights および Vault の認証情報はカスタムインベントリーソースには許可されません。" -#: awx/main/models/inventory.py:1314 -#, python-format -msgid "Invalid %(source)s region: %(region)s" -msgstr "無効な %(source)s リージョン: %(region)s" - -#: awx/main/models/inventory.py:1338 -#, python-format -msgid "Invalid filter expression: %(filter)s" -msgstr "無効なフィルター式: %(filter)s" - -#: awx/main/models/inventory.py:1359 -#, python-format -msgid "Invalid group by choice: %(choice)s" -msgstr "無効なグループ (選択による): %(choice)s" +#: awx/main/models/inventory.py:940 +msgid "" +"Credentials of type insights and vault are disallowed for scm inventory " +"sources." +msgstr "タイプが Insights および Vault の認証情報は SCM のインベントリーソースには許可されません。" -#: awx/main/models/inventory.py:1394 +#: awx/main/models/inventory.py:1004 msgid "Project containing inventory file used as source." msgstr "ソースとして使用されるインベントリーファイルが含まれるプロジェクト。" -#: awx/main/models/inventory.py:1555 -#, python-format -msgid "" -"Unable to configure this item for cloud sync. It is already managed by %s." -msgstr "クラウド同期用にこの項目を設定できません。すでに %s によって管理されています。" - -#: awx/main/models/inventory.py:1565 +#: awx/main/models/inventory.py:1177 msgid "" "More than one SCM-based inventory source with update on project update per-" "inventory not allowed." msgstr "複数の SCM ベースのインベントリーソースについて、インベントリー別のプロジェクト更新時の更新は許可されません。" -#: awx/main/models/inventory.py:1572 +#: awx/main/models/inventory.py:1184 msgid "" "Cannot update SCM-based inventory source on launch if set to update on " "project update. Instead, configure the corresponding source project to " "update on launch." -msgstr "" -"プロジェクト更新時の更新に設定している場合、SCM " -"ベースのインベントリーソースを更新できません。その代わりに起動時に更新するように対応するソースプロジェクトを設定します。" - -#: awx/main/models/inventory.py:1579 -msgid "" -"SCM type sources must set `overwrite_vars` to `true` until Ansible 2.5." -msgstr "SCM タイプソースは「overwrite_vars」を「true」に設定する必要があります (Ansible 2.5 まで)。" +msgstr "プロジェクト更新時の更新に設定している場合、SCM ベースのインベントリーソースを更新できません。その代わりに起動時に更新するように対応するソースプロジェクトを設定します。" -#: awx/main/models/inventory.py:1584 +#: awx/main/models/inventory.py:1190 msgid "Cannot set source_path if not SCM type." msgstr "SCM タイプでない場合 source_path を設定できません。" -#: awx/main/models/inventory.py:1622 +#: awx/main/models/inventory.py:1233 msgid "" "Inventory files from this Project Update were used for the inventory update." msgstr "このプロジェクト更新のインベントリーファイルがインベントリー更新に使用されました。" -#: awx/main/models/inventory.py:1732 +#: awx/main/models/inventory.py:1344 msgid "Inventory script contents" msgstr "インベントリースクリプトの内容" -#: awx/main/models/inventory.py:1737 +#: awx/main/models/inventory.py:1349 msgid "Organization owning this inventory script" msgstr "このインベントリースクリプトを所有する組織" -#: awx/main/models/jobs.py:66 +#: awx/main/models/jobs.py:74 msgid "" "If enabled, textual changes made to any templated files on the host are " "shown in the standard output" msgstr "有効にされている場合、ホストのテンプレート化されたファイルに追加されるテキストの変更が標準出力に表示されます。" -#: awx/main/models/jobs.py:145 +#: awx/main/models/jobs.py:106 +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 の場合のみ許可されます。" + +#: awx/main/models/jobs.py:159 msgid "" -"If enabled, Tower will act as an Ansible Fact Cache Plugin; persisting facts" -" at the end of a playbook run to the database and caching facts for use by " +"If enabled, Tower will act as an Ansible Fact Cache Plugin; persisting facts " +"at the end of a playbook run to the database and caching facts for use by " "Ansible." -msgstr "" -"有効にされている場合、 Tower は Ansible ファクトキャッシュプラグインとして機能します。データベースに対する Playbook " -"実行の終了時にファクトが保持され、 Ansible で使用できるようにキャッシュされます。" +msgstr "有効にされている場合、 Tower は Ansible ファクトキャッシュプラグインとして機能します。データベースに対する Playbook 実行の終了時にファクトが保持され、 Ansible で使用できるようにキャッシュされます。" -#: awx/main/models/jobs.py:163 -msgid "You must provide a Vault credential." -msgstr "Vault 認証情報を指定する必要があります。" +#: awx/main/models/jobs.py:260 +msgid "" +"The number of jobs to slice into at runtime. Will cause the Job Template to " +"launch a workflow if value is greater than 1." +msgstr "実行時にスライスするジョブの数です。値が 1 を超える場合には、ジョブテンプレートはワークフローを起動します。" -#: awx/main/models/jobs.py:308 +#: awx/main/models/jobs.py:297 msgid "Job Template must provide 'inventory' or allow prompting for it." msgstr "ジョブテンプレートは「inventory」を指定するか、このプロンプトを許可する必要があります。" -#: awx/main/models/jobs.py:398 +#: awx/main/models/jobs.py:308 +#, python-brace-format +msgid "Maximum number of forks ({settings.MAX_FORKS}) exceeded." +msgstr "フォームの最大数 ({settings.MAX_FORKS}) を超えました。" + +#: awx/main/models/jobs.py:459 +msgid "Project is missing." +msgstr "プロジェクトがありません。" + +#: awx/main/models/jobs.py:463 +msgid "Project does not allow override of branch." +msgstr "プロジェクトではブランチの上書きはできません。" + +#: awx/main/models/jobs.py:473 awx/main/models/workflow.py:545 msgid "Field is not configured to prompt on launch." msgstr "フィールドは起動時にプロンプトを出すよう設定されていません。" -#: awx/main/models/jobs.py:404 +#: awx/main/models/jobs.py:479 msgid "Saved launch configurations cannot provide passwords needed to start." msgstr "保存された起動設定は、開始に必要なパスワードを提供しません。" -#: awx/main/models/jobs.py:412 +#: awx/main/models/jobs.py:487 msgid "Job Template {} is missing or undefined." msgstr "ジョブテンプレート {} が見つからないか、または定義されていません。" -#: awx/main/models/jobs.py:493 awx/main/models/projects.py:277 +#: awx/main/models/jobs.py:570 awx/main/models/projects.py:284 +#: awx/main/models/projects.py:508 msgid "SCM Revision" msgstr "SCM リビジョン" -#: awx/main/models/jobs.py:494 +#: awx/main/models/jobs.py:571 msgid "The SCM Revision from the Project used for this job, if available" msgstr "このジョブに使用されるプロジェクトからの SCM リビジョン (ある場合)" -#: awx/main/models/jobs.py:502 +#: awx/main/models/jobs.py:579 msgid "" "The SCM Refresh task used to make sure the playbooks were available for the " "job run" msgstr "SCM 更新タスクは、Playbook がジョブの実行で利用可能であったことを確認するために使用されます" -#: awx/main/models/jobs.py:629 +#: awx/main/models/jobs.py:584 +msgid "" +"If part of a sliced job, the ID of the inventory slice operated on. If not " +"part of sliced job, parameter is not used." +msgstr "スライスされたジョブの一部の場合には、スライス処理が行われたインベントリーの ID です。スライスされたジョブの一部でなければ、パラメーターは使用されません。" + +#: awx/main/models/jobs.py:590 +msgid "" +"If ran as part of sliced jobs, the total number of slices. If 1, job is not " +"part of a sliced job." +msgstr "スライスされたジョブの一部として実行された場合には、スライスの合計数です。1 であればジョブはスライスされたジョブの一部ではありません。" + +#: awx/main/models/jobs.py:672 #, python-brace-format msgid "{status_value} is not a valid status option." -msgstr "{status_value} は有効なステータスオプションではありません。" +msgstr "{status_value} は、有効なステータスオプションではありません。" + +#: awx/main/models/jobs.py:922 +msgid "" +"Inventory applied as a prompt, assuming job template prompts for inventory" +msgstr "インベントリーがプロンプトとして適用されると、ジョブテンプレートでインベントリーをプロンプトで要求することが前提となります。" -#: awx/main/models/jobs.py:1005 +#: awx/main/models/jobs.py:1081 msgid "job host summaries" msgstr "ジョブホストの概要" -#: awx/main/models/jobs.py:1077 +#: awx/main/models/jobs.py:1140 msgid "Remove jobs older than a certain number of days" msgstr "特定の日数より前のジョブを削除" -#: awx/main/models/jobs.py:1078 +#: awx/main/models/jobs.py:1141 msgid "Remove activity stream entries older than a certain number of days" msgstr "特定の日数より前のアクティビティーストリームのエントリーを削除" -#: awx/main/models/jobs.py:1079 -msgid "Purge and/or reduce the granularity of system tracking data" -msgstr "システムトラッキングデータの詳細度の削除/削減" +#: awx/main/models/jobs.py:1142 +msgid "Removes expired browser sessions from the database" +msgstr "期限切れブラウザーセッションをデータベースから削除" + +#: awx/main/models/jobs.py:1143 +msgid "Removes expired OAuth 2 access tokens and refresh tokens" +msgstr "期限切れの OAuth 2 アクセストークンを削除し、トークンを更新" -#: awx/main/models/jobs.py:1149 +#: awx/main/models/jobs.py:1213 #, python-brace-format msgid "Variables {list_of_keys} are not allowed for system jobs." -msgstr "変数 {list_of_keys} はシステムジョブに許可されていません。" +msgstr "システムジョブでは変数 {list_of_keys} を使用できません。" -#: awx/main/models/jobs.py:1164 +#: awx/main/models/jobs.py:1229 msgid "days must be a positive integer." msgstr "日数は正の整数である必要があります。" @@ -3542,113 +4098,166 @@ msgstr "日数は正の整数である必要があります。" msgid "Organization this label belongs to." msgstr "このラベルが属する組織。" -#: awx/main/models/mixins.py:309 +#: awx/main/models/mixins.py:321 #, python-brace-format msgid "" "Variables {list_of_keys} are not allowed on launch. Check the Prompt on " -"Launch setting on the Job Template to include Extra Variables." -msgstr "" -"変数 {list_of_keys} は起動時に許可されていません。ジョブテンプレートで起動時のプロンプト設定を確認し、追加変数を組み込みます。" +"Launch setting on the {model_name} to include Extra Variables." +msgstr "変数 {list_of_keys} は起動時に許可されていません。{model_name} で起動時のプロンプト設定を確認し、追加変数を組み込みます。" -#: awx/main/models/mixins.py:440 +#: awx/main/models/mixins.py:453 msgid "Local absolute file path containing a custom Python virtualenv to use" msgstr "使用するカスタム Python virtualenv を含むローカルの絶対ファイルパス" -#: awx/main/models/mixins.py:447 +#: awx/main/models/mixins.py:460 msgid "{} is not a valid virtualenv in {}" msgstr "{}は{}の有効な virtualenv ではありません" +#: awx/main/models/mixins.py:507 awx/main/models/mixins.py:551 +msgid "Service that webhook requests will be accepted from" +msgstr "Webhook 要求の受け入れ元のサービス" + +#: awx/main/models/mixins.py:512 +msgid "Shared secret that the webhook service will use to sign requests" +msgstr "Webhook サービスが要求の署名に使用する共有シークレット" + +#: awx/main/models/mixins.py:520 awx/main/models/mixins.py:559 +msgid "Personal Access Token for posting back the status to the service API" +msgstr "サービス API のステータスに戻すためのパーソナルアクセストークン" + +#: awx/main/models/mixins.py:564 +msgid "Unique identifier of the event that triggered this webhook" +msgstr "この Webhook をトリガーしたイベントの一意識別子" + +#: awx/main/models/notifications.py:41 +msgid "Email" +msgstr "メール" + #: awx/main/models/notifications.py:42 +msgid "Slack" +msgstr "Slack" + +#: awx/main/models/notifications.py:43 +msgid "Twilio" +msgstr "Twilio" + +#: awx/main/models/notifications.py:44 +msgid "Pagerduty" +msgstr "Pagerduty" + +#: awx/main/models/notifications.py:45 +msgid "Grafana" +msgstr "Grafana" + +#: awx/main/models/notifications.py:46 awx/main/models/unified_jobs.py:544 +msgid "Webhook" +msgstr "Webhook" + +#: awx/main/models/notifications.py:47 +msgid "Mattermost" +msgstr "Mattermost" + +#: awx/main/models/notifications.py:48 msgid "Rocket.Chat" msgstr "Rocket.Chat" -#: awx/main/models/notifications.py:142 awx/main/models/unified_jobs.py:62 +#: awx/main/models/notifications.py:49 +msgid "IRC" +msgstr "IRC" + +#: awx/main/models/notifications.py:80 +msgid "Optional custom messages for notification template." +msgstr "通知テンプレートのオプションのカスタムメッセージ" + +#: awx/main/models/notifications.py:210 awx/main/models/unified_jobs.py:70 msgid "Pending" msgstr "保留中" -#: awx/main/models/notifications.py:143 awx/main/models/unified_jobs.py:65 +#: awx/main/models/notifications.py:211 awx/main/models/unified_jobs.py:73 msgid "Successful" msgstr "成功" -#: awx/main/models/notifications.py:144 awx/main/models/unified_jobs.py:66 +#: awx/main/models/notifications.py:212 awx/main/models/unified_jobs.py:74 msgid "Failed" msgstr "失敗" -#: awx/main/models/notifications.py:218 -msgid "status_str must be either succeeded or failed" -msgstr "status_str は成功または失敗のいずれかである必要があります" +#: awx/main/models/notifications.py:466 +msgid "status must be either running, succeeded or failed" +msgstr "ステータスは、実行中、成功、失敗のいずれかでなければなりません。" -#: awx/main/models/oauth.py:29 +#: awx/main/models/oauth.py:33 msgid "application" msgstr "アプリケーション" -#: awx/main/models/oauth.py:35 +#: awx/main/models/oauth.py:40 msgid "Confidential" msgstr "機密" -#: awx/main/models/oauth.py:36 +#: awx/main/models/oauth.py:41 msgid "Public" msgstr "公開" -#: awx/main/models/oauth.py:43 +#: awx/main/models/oauth.py:47 msgid "Authorization code" msgstr "認証コード" -#: awx/main/models/oauth.py:44 -msgid "Implicit" -msgstr "暗黙的" - -#: awx/main/models/oauth.py:45 +#: awx/main/models/oauth.py:48 msgid "Resource owner password-based" msgstr "リソース所有者のパスワードベース" -#: awx/main/models/oauth.py:60 +#: awx/main/models/oauth.py:63 msgid "Organization containing this application." msgstr "このアプリケーションを含む組織。" -#: awx/main/models/oauth.py:69 +#: awx/main/models/oauth.py:72 msgid "" "Used for more stringent verification of access to an application when " "creating a token." msgstr "トークン作成時のアプリケーションへのアクセスのより厳しい検証に使用されます。" -#: awx/main/models/oauth.py:74 +#: awx/main/models/oauth.py:77 msgid "" "Set to Public or Confidential depending on how secure the client device is." msgstr "クライアントデバイスのセキュリティーレベルに応じて「公開」または「機密」に設定します。" -#: awx/main/models/oauth.py:78 +#: awx/main/models/oauth.py:81 msgid "" "Set True to skip authorization step for completely trusted applications." msgstr "完全に信頼されたアプリケーションの認証手順をスキップするには「True」を設定します。" -#: awx/main/models/oauth.py:83 +#: awx/main/models/oauth.py:86 msgid "" "The Grant type the user must use for acquire tokens for this application." msgstr "ユーザーがこのアプリケーションのトークンを取得するために使用する必要のある付与タイプです。" -#: awx/main/models/oauth.py:91 +#: awx/main/models/oauth.py:94 msgid "access token" msgstr "アクセストークン" -#: awx/main/models/oauth.py:99 +#: awx/main/models/oauth.py:103 msgid "The user representing the token owner" msgstr "トークンの所有者を表すユーザー" -#: awx/main/models/oauth.py:114 +#: awx/main/models/oauth.py:117 msgid "" -"Allowed scopes, further restricts user's permissions. Must be a simple " -"space-separated string with allowed scopes ['read', 'write']." -msgstr "" -"許可されたスコープで、ユーザーのパーミッションをさらに制限します。許可されたスコープ ['read', 'write'] " -"のある単純なスペースで区切られた文字列でなければなりません。" +"Allowed scopes, further restricts user's permissions. Must be a simple space-" +"separated string with allowed scopes ['read', 'write']." +msgstr "許可されたスコープで、ユーザーのパーミッションをさらに制限します。許可されたスコープ ['read', 'write'] のある単純なスペースで区切られた文字列でなければなりません。" -#: awx/main/models/oauth.py:133 +#: awx/main/models/oauth.py:140 msgid "" "OAuth2 Tokens cannot be created by users associated with an external " "authentication provider ({})" msgstr "OAuth2 トークンは、外部の認証プロバイダー ({}) に関連付けられたユーザーが作成することはできません。" +#: awx/main/models/organization.py:57 +msgid "Maximum number of hosts allowed to be managed by this organization." +msgstr "この組織が管理可能な最大ホスト数" + +#: awx/main/models/projects.py:53 awx/main/models/unified_jobs.py:538 +msgid "Manual" +msgstr "手動" + #: awx/main/models/projects.py:54 msgid "Git" msgstr "Git" @@ -3665,175 +4274,211 @@ msgstr "Subversion" msgid "Red Hat Insights" msgstr "Red Hat Insights" -#: awx/main/models/projects.py:83 +#: awx/main/models/projects.py:58 +msgid "Remote Archive" +msgstr "リモートアーカイブ" + +#: awx/main/models/projects.py:84 msgid "" "Local path (relative to PROJECTS_ROOT) containing playbooks and related " "files for this project." msgstr "このプロジェクトの Playbook および関連するファイルを含むローカルパス (PROJECTS_ROOT との相対)。" -#: awx/main/models/projects.py:92 +#: awx/main/models/projects.py:93 msgid "SCM Type" msgstr "SCM タイプ" -#: awx/main/models/projects.py:93 +#: awx/main/models/projects.py:94 msgid "Specifies the source control system used to store the project." msgstr "プロジェクトを保存するために使用されるソースコントロールシステムを指定します。" -#: awx/main/models/projects.py:99 +#: awx/main/models/projects.py:100 msgid "SCM URL" msgstr "SCM URL" -#: awx/main/models/projects.py:100 +#: awx/main/models/projects.py:101 msgid "The location where the project is stored." msgstr "プロジェクトが保存される場所。" -#: awx/main/models/projects.py:106 +#: awx/main/models/projects.py:107 msgid "SCM Branch" msgstr "SCM ブランチ" -#: awx/main/models/projects.py:107 +#: awx/main/models/projects.py:108 msgid "Specific branch, tag or commit to checkout." msgstr "チェックアウトする特定のブランチ、タグまたはコミット。" -#: awx/main/models/projects.py:111 +#: awx/main/models/projects.py:114 +msgid "SCM refspec" +msgstr "SCM refspec" + +#: awx/main/models/projects.py:115 +msgid "For git projects, an additional refspec to fetch." +msgstr "git プロジェクトについては、追加の refspec を使用して取得します。" + +#: awx/main/models/projects.py:119 msgid "Discard any local changes before syncing the project." msgstr "ローカル変更を破棄してからプロジェクトを同期します。" -#: awx/main/models/projects.py:115 +#: awx/main/models/projects.py:123 msgid "Delete the project before syncing." msgstr "プロジェクトを削除してから同期します。" -#: awx/main/models/projects.py:144 +#: awx/main/models/projects.py:152 msgid "Invalid SCM URL." msgstr "無効な SCM URL。" -#: awx/main/models/projects.py:147 +#: awx/main/models/projects.py:155 msgid "SCM URL is required." msgstr "SCM URL が必要です。" -#: awx/main/models/projects.py:155 +#: awx/main/models/projects.py:163 msgid "Insights Credential is required for an Insights Project." msgstr "Insights 認証情報が Insights プロジェクトに必要です。" -#: awx/main/models/projects.py:161 +#: awx/main/models/projects.py:169 msgid "Credential kind must be 'scm'." msgstr "認証情報の種類は 'scm' にする必要があります。" -#: awx/main/models/projects.py:178 +#: awx/main/models/projects.py:186 msgid "Invalid credential." msgstr "無効な認証情報。" -#: awx/main/models/projects.py:263 +#: awx/main/models/projects.py:265 msgid "Update the project when a job is launched that uses the project." msgstr "プロジェクトを使用するジョブの起動時にプロジェクトを更新します。" -#: awx/main/models/projects.py:268 +#: awx/main/models/projects.py:270 msgid "" -"The number of seconds after the last project update ran that a newproject " +"The number of seconds after the last project update ran that a new project " "update will be launched as a job dependency." -msgstr "新規プロジェクトの更新がジョブの依存関係として起動される最終プロジェクト更新後の秒数。" +msgstr "最終プロジェクト更新を実行して新規プロジェクトの更新をジョブの依存関係として起動した後の秒数。" + +#: awx/main/models/projects.py:275 +msgid "" +"Allow changing the SCM branch or revision in a job template that uses this " +"project." +msgstr "このプロジェクトを使用するジョブテンプレートで SCM ブランチまたはリビジョンを変更できるようにします。" -#: awx/main/models/projects.py:278 +#: awx/main/models/projects.py:285 msgid "The last revision fetched by a project update" msgstr "プロジェクト更新で取得される最新リビジョン" -#: awx/main/models/projects.py:285 +#: awx/main/models/projects.py:292 msgid "Playbook Files" msgstr "Playbook ファイル" -#: awx/main/models/projects.py:286 +#: awx/main/models/projects.py:293 msgid "List of playbooks found in the project" msgstr "プロジェクトにある Playbook の一覧" -#: awx/main/models/projects.py:293 +#: awx/main/models/projects.py:300 msgid "Inventory Files" msgstr "インベントリーファイル" -#: awx/main/models/projects.py:294 +#: awx/main/models/projects.py:301 msgid "" "Suggested list of content that could be Ansible inventory in the project" msgstr "プロジェクト内の Ansible インベントリーの可能性のあるコンテンツの一覧" -#: awx/main/models/rbac.py:36 +#: awx/main/models/projects.py:338 +msgid "Organization cannot be changed when in use by job templates." +msgstr "組織は、ジョブテンプレートで使用中の場合には変更できません。" + +#: awx/main/models/projects.py:501 +msgid "Parts of the project update playbook that will be run." +msgstr "実行予定のプロジェクト更新 Playbook の一部。" + +#: awx/main/models/projects.py:509 +msgid "" +"The SCM Revision discovered by this update for the given project and branch." +msgstr "指定のプロジェクトおよびブランチ用にこの更新が検出した SCM リビジョン" + +#: awx/main/models/rbac.py:35 msgid "System Administrator" msgstr "システム管理者" -#: awx/main/models/rbac.py:37 +#: awx/main/models/rbac.py:36 msgid "System Auditor" msgstr "システム監査者" -#: awx/main/models/rbac.py:38 +#: awx/main/models/rbac.py:37 msgid "Ad Hoc" msgstr "アドホック" -#: awx/main/models/rbac.py:39 +#: awx/main/models/rbac.py:38 msgid "Admin" msgstr "管理者" -#: awx/main/models/rbac.py:40 +#: awx/main/models/rbac.py:39 msgid "Project Admin" msgstr "プロジェクト管理者" -#: awx/main/models/rbac.py:41 +#: awx/main/models/rbac.py:40 msgid "Inventory Admin" msgstr "インベントリー管理者" -#: awx/main/models/rbac.py:42 +#: awx/main/models/rbac.py:41 msgid "Credential Admin" msgstr "認証情報管理者" -#: awx/main/models/rbac.py:43 +#: awx/main/models/rbac.py:42 msgid "Job Template Admin" msgstr "ジョブテンプレート管理者" -#: awx/main/models/rbac.py:44 +#: awx/main/models/rbac.py:43 msgid "Workflow Admin" msgstr "ワークフロー管理者" -#: awx/main/models/rbac.py:45 +#: awx/main/models/rbac.py:44 msgid "Notification Admin" msgstr "通知管理者" -#: awx/main/models/rbac.py:46 +#: awx/main/models/rbac.py:45 msgid "Auditor" msgstr "監査者" -#: awx/main/models/rbac.py:47 +#: awx/main/models/rbac.py:46 msgid "Execute" msgstr "実行" -#: awx/main/models/rbac.py:48 +#: awx/main/models/rbac.py:47 msgid "Member" msgstr "メンバー" -#: awx/main/models/rbac.py:49 +#: awx/main/models/rbac.py:48 msgid "Read" msgstr "読み込み" -#: awx/main/models/rbac.py:50 +#: awx/main/models/rbac.py:49 msgid "Update" msgstr "更新" -#: awx/main/models/rbac.py:51 +#: awx/main/models/rbac.py:50 msgid "Use" msgstr "使用" +#: awx/main/models/rbac.py:51 +msgid "Approve" +msgstr "承認" + #: awx/main/models/rbac.py:55 msgid "Can manage all aspects of the system" -msgstr "システムのすべての側面が管理可能" +msgstr "システムのすべての側面を管理可能" #: awx/main/models/rbac.py:56 -msgid "Can view all settings on the system" -msgstr "システムのすべての設定が表示可能" +msgid "Can view all aspects of the system" +msgstr "システムの全側面が表示可能" #: awx/main/models/rbac.py:57 -msgid "May run ad hoc commands on an inventory" -msgstr "インベントリーでアドホックコマンドが実行可能" +#, python-format +msgid "May run ad hoc commands on the %s" +msgstr "%s でアドホックコマンドが実行可能" #: awx/main/models/rbac.py:58 #, python-format msgid "Can manage all aspects of the %s" -msgstr "%s のすべての側面が管理可能" +msgstr "%s のすべての側面を管理可能" #: awx/main/models/rbac.py:59 #, python-format @@ -3867,8 +4512,8 @@ msgstr "%s のすべての通知が管理可能" #: awx/main/models/rbac.py:65 #, python-format -msgid "Can view all settings for the %s" -msgstr "%s のすべての設定が表示可能" +msgid "Can view all aspects of the %s" +msgstr "%s のすべての側面が表示可能" #: awx/main/models/rbac.py:67 msgid "May run any executable resources in the organization" @@ -3890,291 +4535,387 @@ msgid "May view settings for the %s" msgstr "%s の設定を表示可能" #: awx/main/models/rbac.py:72 -msgid "" -"May update project or inventory or group using the configured source update " -"system" -msgstr "設定済みのソース更新システムを使用してプロジェクト、インベントリーまたはグループを更新可能" +#, python-format +msgid "May update the %s" +msgstr "%s を更新可能" #: awx/main/models/rbac.py:73 #, python-format msgid "Can use the %s in a job template" msgstr "ジョブテンプレートで %s を使用可能" -#: awx/main/models/rbac.py:137 +#: awx/main/models/rbac.py:74 +msgid "Can approve or deny a workflow approval node" +msgstr "ワークフロー承認ノードの承認または拒否が可能" + +#: awx/main/models/rbac.py:138 msgid "roles" msgstr "ロール" -#: awx/main/models/rbac.py:443 +#: awx/main/models/rbac.py:445 msgid "role_ancestors" msgstr "role_ancestors" -#: awx/main/models/schedules.py:79 +#: awx/main/models/schedules.py:83 msgid "Enables processing of this schedule." msgstr "このスケジュールの処理を有効にします。" -#: awx/main/models/schedules.py:85 +#: awx/main/models/schedules.py:89 msgid "The first occurrence of the schedule occurs on or after this time." msgstr "スケジュールの最初のオカレンスはこの時間またはこの時間の後に生じます。" -#: awx/main/models/schedules.py:91 +#: awx/main/models/schedules.py:95 msgid "" "The last occurrence of the schedule occurs before this time, aftewards the " "schedule expires." msgstr "スケジュールの最後のオカレンスはこの時間の前に生じます。その後スケジュールが期限切れになります。" -#: awx/main/models/schedules.py:95 +#: awx/main/models/schedules.py:99 msgid "A value representing the schedules iCal recurrence rule." msgstr "スケジュールの iCal 繰り返しルールを表す値。" -#: awx/main/models/schedules.py:101 +#: awx/main/models/schedules.py:105 msgid "The next time that the scheduled action will run." msgstr "スケジュールされたアクションが次に実行される時間。" -#: awx/main/models/unified_jobs.py:61 +#: awx/main/models/unified_jobs.py:69 msgid "New" msgstr "新規" -#: awx/main/models/unified_jobs.py:63 +#: awx/main/models/unified_jobs.py:71 msgid "Waiting" msgstr "待機中" -#: awx/main/models/unified_jobs.py:64 +#: awx/main/models/unified_jobs.py:72 msgid "Running" msgstr "実行中" -#: awx/main/models/unified_jobs.py:68 +#: awx/main/models/unified_jobs.py:76 msgid "Canceled" -msgstr "取り消し" +msgstr "取り消されました" -#: awx/main/models/unified_jobs.py:72 +#: awx/main/models/unified_jobs.py:80 msgid "Never Updated" -msgstr "未更新" +msgstr "更新されていません" -#: awx/main/models/unified_jobs.py:76 +#: awx/main/models/unified_jobs.py:84 msgid "OK" msgstr "OK" -#: awx/main/models/unified_jobs.py:77 +#: awx/main/models/unified_jobs.py:85 msgid "Missing" msgstr "不明" -#: awx/main/models/unified_jobs.py:81 +#: awx/main/models/unified_jobs.py:89 msgid "No External Source" msgstr "外部ソースがありません" -#: awx/main/models/unified_jobs.py:88 +#: awx/main/models/unified_jobs.py:96 msgid "Updating" msgstr "更新中" -#: awx/main/models/unified_jobs.py:427 +#: awx/main/models/unified_jobs.py:167 +msgid "The organization used to determine access to this template." +msgstr "このテンプレートにアクセスできるかを決定する時に使用する組織" + +#: awx/main/models/unified_jobs.py:465 msgid "Field is not allowed on launch." msgstr "フィールドは起動時に許可されません。" -#: awx/main/models/unified_jobs.py:455 +#: awx/main/models/unified_jobs.py:493 #, python-brace-format msgid "" -"Variables {list_of_keys} provided, but this template cannot accept " -"variables." -msgstr "変数 {list_of_keys} が指定されますが、このテンプレートは変数を受け入れません。" +"Variables {list_of_keys} provided, but this template cannot accept variables." +msgstr "変数 {list_of_keys} が指定されましたが、このテンプレートは変数に対応していません。" -#: awx/main/models/unified_jobs.py:520 +#: awx/main/models/unified_jobs.py:539 msgid "Relaunch" msgstr "再起動" -#: awx/main/models/unified_jobs.py:521 +#: awx/main/models/unified_jobs.py:540 msgid "Callback" msgstr "コールバック" -#: awx/main/models/unified_jobs.py:522 +#: awx/main/models/unified_jobs.py:541 msgid "Scheduled" msgstr "スケジュール済み" -#: awx/main/models/unified_jobs.py:523 +#: awx/main/models/unified_jobs.py:542 msgid "Dependency" msgstr "依存関係" -#: awx/main/models/unified_jobs.py:524 +#: awx/main/models/unified_jobs.py:543 msgid "Workflow" msgstr "ワークフロー" -#: awx/main/models/unified_jobs.py:525 +#: awx/main/models/unified_jobs.py:545 msgid "Sync" msgstr "同期" -#: awx/main/models/unified_jobs.py:573 +#: awx/main/models/unified_jobs.py:600 msgid "The node the job executed on." msgstr "ジョブが実行されるノード。" -#: awx/main/models/unified_jobs.py:579 +#: awx/main/models/unified_jobs.py:606 msgid "The instance that managed the isolated execution environment." msgstr "分離された実行環境を管理したインスタンス。" -#: awx/main/models/unified_jobs.py:605 +#: awx/main/models/unified_jobs.py:633 msgid "The date and time the job was queued for starting." msgstr "ジョブが開始のために待機した日時。" -#: awx/main/models/unified_jobs.py:611 +#: awx/main/models/unified_jobs.py:638 +msgid "" +"If True, the task manager has already processed potential dependencies for " +"this job." +msgstr "True の場合には、タスクマネージャーは、このジョブの潜在的な依存関係を処理済みです。" + +#: awx/main/models/unified_jobs.py:644 msgid "The date and time the job finished execution." msgstr "ジョブが実行を完了した日時。" -#: awx/main/models/unified_jobs.py:617 +#: awx/main/models/unified_jobs.py:651 +msgid "The date and time when the cancel request was sent." +msgstr "取り消しリクエストが送信された日時。" + +#: awx/main/models/unified_jobs.py:658 msgid "Elapsed time in seconds that the job ran." msgstr "ジョブ実行の経過時間 (秒単位)" -#: awx/main/models/unified_jobs.py:639 +#: awx/main/models/unified_jobs.py:680 msgid "" -"A status field to indicate the state of the job if it wasn't able to run and" -" capture stdout" +"A status field to indicate the state of the job if it wasn't able to run and " +"capture stdout" msgstr "stdout の実行およびキャプチャーを実行できない場合のジョブの状態を示すための状態フィールド" -#: awx/main/models/unified_jobs.py:668 -msgid "The Rampart/Instance group the job was run under" -msgstr "ジョブが実行された Rampart/インスタンスグループ" +#: awx/main/models/unified_jobs.py:709 +msgid "The Instance group the job was run under" +msgstr "ジョブが実行されたインスタンスグループ" + +#: awx/main/models/unified_jobs.py:717 +msgid "The organization used to determine access to this unified job." +msgstr "この統一されたジョブにアクセスできるかを決定する時に使用する組織" + +#: awx/main/models/workflow.py:85 +msgid "" +"If enabled then the node will only run if all of the parent nodes have met " +"the criteria to reach this node" +msgstr "有効な場合には、全親ノードでこのノードに到達するための基準が満たされている場合にのみノードが実行されます" + +#: awx/main/models/workflow.py:154 +msgid "" +"An identifier for this node that is unique within its workflow. It is copied " +"to workflow job nodes corresponding to this node." +msgstr "ワークフロー内で一意のノードの ID。このノードに対応するワークフロージョブノードにコピーされます。" + +#: awx/main/models/workflow.py:229 +msgid "" +"Indicates that a job will not be created when True. Workflow runtime " +"semantics will mark this True if the node is in a path that will decidedly " +"not be ran. A value of False means the node may not run." +msgstr "True であればジョブは作成されません。ノードが絶対に実行されないパスにある場合に、ワークフロー実行時セマンティックはこれを True に設定します。値が False であればノードは実行されません。" + +#: awx/main/models/workflow.py:236 +msgid "" +"An identifier coresponding to the workflow job template node that this node " +"was created from." +msgstr "このノードの作成元のワークフロージョブテンプレートノードに対応する ID。" -#: awx/main/models/workflow.py:203 +#: awx/main/models/workflow.py:282 #, python-brace-format msgid "" -"Bad launch configuration starting template {template_pk} as part of workflow {workflow_pk}. Errors:\n" +"Bad launch configuration starting template {template_pk} as part of workflow " +"{workflow_pk}. Errors:\n" "{error_text}" -msgstr "" -"{workflow_pk} の一部としてテンプレート {template_pk} を起動する起動設定が正しくありません。エラー:\n" +msgstr "不正な起動設定が原因でワークフロー {workflow_pk} の一部としてテンプレート {template_pk} が開始されました。エラー:\n" "{error_text}" -#: awx/main/models/workflow.py:393 -msgid "Field is not allowed for use in workflows." -msgstr "フィールドはワークフローでの使用に許可されません。" +#: awx/main/models/workflow.py:595 +msgid "" +"If automatically created for a sliced job run, the job template the workflow " +"job was created from." +msgstr "スライスされたジョブの実行のために自動的に作成された場合は、ワークフロージョブが作成されたジョブテンプレートです。" -#: awx/main/notifications/base.py:17 -#: awx/main/notifications/email_backend.py:28 +#: awx/main/models/workflow.py:687 awx/main/models/workflow.py:721 msgid "" -"{} #{} had status {}, view details at {}\n" -"\n" -msgstr "" -"{} #{} のステータスは {} です。詳細を {} で確認してください。\n" -"\n" +"The amount of time (in seconds) before the approval node expires and fails." +msgstr "承認ノードが期限切れになり、失敗するまでの時間 (秒単位)。" + +#: awx/main/models/workflow.py:725 +msgid "" +"Shows when an approval node (with a timeout assigned to it) has timed out." +msgstr "承認ノード (タイムアウトが割り当てられている場合) がタイムアウトになると表示されます。" -#: awx/main/notifications/hipchat_backend.py:48 -msgid "Error sending messages: {}" -msgstr "メッセージの送信時のエラー: {}" +#: awx/main/notifications/grafana_backend.py:81 +msgid "Error converting time {} or timeEnd {} to int." +msgstr "time {} または timeEnd {} を int に変換中のエラー" -#: awx/main/notifications/hipchat_backend.py:50 -msgid "Error sending message to hipchat: {}" -msgstr "メッセージの hipchat への送信時のエラー: {}" +#: awx/main/notifications/grafana_backend.py:83 +msgid "Error converting time {} and/or timeEnd {} to int." +msgstr "time {} および/または timeEnd {} を int に変換中のエラー" -#: awx/main/notifications/irc_backend.py:54 +#: awx/main/notifications/grafana_backend.py:97 +#: awx/main/notifications/grafana_backend.py:99 +msgid "Error sending notification grafana: {}" +msgstr "通知 grafana の送信時のエラー: {}" + +#: awx/main/notifications/irc_backend.py:56 msgid "Exception connecting to irc server: {}" msgstr "irc サーバーへの接続時の例外: {}" -#: awx/main/notifications/mattermost_backend.py:48 -#: awx/main/notifications/mattermost_backend.py:50 +#: awx/main/notifications/mattermost_backend.py:49 +#: awx/main/notifications/mattermost_backend.py:51 msgid "Error sending notification mattermost: {}" msgstr "通知 mattermost の送信時のエラー: {}" -#: awx/main/notifications/pagerduty_backend.py:39 +#: awx/main/notifications/pagerduty_backend.py:75 msgid "Exception connecting to PagerDuty: {}" msgstr "PagerDuty への接続時の例外: {}" -#: awx/main/notifications/pagerduty_backend.py:48 -#: awx/main/notifications/slack_backend.py:82 -#: awx/main/notifications/slack_backend.py:99 -#: awx/main/notifications/twilio_backend.py:46 +#: awx/main/notifications/pagerduty_backend.py:84 +#: awx/main/notifications/slack_backend.py:58 +#: awx/main/notifications/twilio_backend.py:48 msgid "Exception sending messages: {}" msgstr "メッセージの送信時の例外: {}" -#: awx/main/notifications/rocketchat_backend.py:46 #: awx/main/notifications/rocketchat_backend.py:49 +#: awx/main/notifications/rocketchat_backend.py:52 msgid "Error sending notification rocket.chat: {}" msgstr "通知 rocket.chat 送信時のエラー: {}" -#: awx/main/notifications/twilio_backend.py:36 +#: awx/main/notifications/twilio_backend.py:38 msgid "Exception connecting to Twilio: {}" msgstr "Twilio への接続時の例外: {}" -#: awx/main/notifications/webhook_backend.py:38 -#: awx/main/notifications/webhook_backend.py:40 +#: awx/main/notifications/webhook_backend.py:75 +#: awx/main/notifications/webhook_backend.py:77 msgid "Error sending notification webhook: {}" msgstr "通知 webhook の送信時のエラー: {}" -#: awx/main/scheduler/task_manager.py:201 +#: awx/main/scheduler/dag_workflow.py:170 +#, python-brace-format msgid "" -"Job spawned from workflow could not start because it was not in the right " -"state or required manual credentials" -msgstr "ワークフローから起動されるジョブは、正常な状態にないか、または手動の認証が必要であるために開始できませんでした" +"No error handling path for workflow job node(s) [{node_status}]. Workflow " +"job node(s) missing unified job template and error handling path [{no_ufjt}]." +msgstr "ワークフロージョブのノードにエラー処理パスがありません [{node_status}] ワークフロージョブのノードに統一されたジョブテンプレートおよびエラー処理パスがありません [{no_ufjt}]。" + +#: awx/main/scheduler/task_manager.py:127 +msgid "" +"Workflow Job spawned from workflow could not start because it would result " +"in recursion (spawn order, most recent first: {})" +msgstr "ワークフローから起動されるワークフロージョブは、再帰が生じるために開始できませんでした (起動順、もっとも新しいものから: {})" -#: awx/main/scheduler/task_manager.py:205 +#: awx/main/scheduler/task_manager.py:135 msgid "" "Job spawned from workflow could not start because it was missing a related " "resource such as project or inventory" msgstr "ワークフローから起動されるジョブは、プロジェクトまたはインベントリーなどの関連するリソースがないために開始できませんでした" -#: awx/main/signals.py:632 -msgid "limit_reached" -msgstr "limit_reached" +#: awx/main/scheduler/task_manager.py:144 +msgid "" +"Job spawned from workflow could not start because it was not in the right " +"state or required manual credentials" +msgstr "ワークフローから起動されるジョブは、正常な状態にないか、または手動の認証が必要であるために開始できませんでした" + +#: awx/main/scheduler/task_manager.py:185 +msgid "No error handling paths found, marking workflow as failed" +msgstr "エラーの処理パスが見つかりません。ワークフローを失敗としてマークしました" -#: awx/main/tasks.py:305 -msgid "Ansible Tower host usage over 90%" -msgstr "Ansible Tower ホストの使用率が 90% を超えました" +#: awx/main/scheduler/task_manager.py:523 +#, python-brace-format +msgid "The approval node {name} ({pk}) has expired after {timeout} seconds." +msgstr "承認ノード {name} ({pk}) は {timeout} 秒後に失効しました。" + +#: awx/main/tasks.py:599 +msgid "" +"Scheduled job could not start because it was not in the " +"right state or required manual credentials" +msgstr "スケジュール済みのジョブは、正常な状態にないか、手動の認証が必要であるために開始できませんでした" -#: awx/main/tasks.py:310 -msgid "Ansible Tower license will expire soon" -msgstr "Ansible Tower ライセンスがまもなく期限切れになります" +#: awx/main/tasks.py:1070 +msgid "Invalid virtual environment selected: {}" +msgstr "無効な仮想環境が選択されました: {}" -#: awx/main/tasks.py:1358 +#: awx/main/tasks.py:1857 msgid "Job could not start because it does not have a valid inventory." msgstr "ジョブは有効なインベントリーがないために開始できませんでした。" -#: awx/main/utils/common.py:97 +#: awx/main/tasks.py:1861 +msgid "Job could not start because it does not have a valid project." +msgstr "ジョブは有効なプロジェクトがないために開始できませんでした。" + +#: awx/main/tasks.py:1866 +msgid "" +"The project revision for this job template is unknown due to a failed update." +msgstr "更新に失敗したため、このジョブテンプレートのプロジェクトリビジョンは不明です。" + +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:470 +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:498 +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:505 +msgid "" +"No error handling path for workflow job node(s) [({},{})]. Workflow job " +"node(s) missing unified job template and error handling path []." +msgstr "ワークフロージョブのノードにエラーハンドルパスがありません [({},{})] ワークフロージョブのノードに統一されたジョブテンプレートおよびエラーハンドルパスがありません []。" + +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:480 +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:491 +msgid "" +"No error handling path for workflow job node(s) []. Workflow job node(s) " +"missing unified job template and error handling path [{}]." +msgstr "ワークフロージョブのノードにエラーハンドルパスがありません [] ワークフロージョブのノードに統一されたジョブテンプレートおよびエラーハンドルパスがありません [{}]。" + +#: awx/main/utils/common.py:87 #, python-format msgid "Unable to convert \"%s\" to boolean" msgstr "\"%s\" をブール値に変換できません" -#: awx/main/utils/common.py:254 +#: awx/main/utils/common.py:248 #, python-format msgid "Unsupported SCM type \"%s\"" msgstr "サポートされない SCM タイプ \"%s\"" -#: awx/main/utils/common.py:261 awx/main/utils/common.py:273 -#: awx/main/utils/common.py:292 +#: awx/main/utils/common.py:255 awx/main/utils/common.py:267 +#: awx/main/utils/common.py:286 #, python-format msgid "Invalid %s URL" msgstr "無効な %s URL" -#: awx/main/utils/common.py:263 awx/main/utils/common.py:302 +#: awx/main/utils/common.py:257 awx/main/utils/common.py:297 #, python-format msgid "Unsupported %s URL" -msgstr "サポートされていない %s URL" +msgstr "サポートされない %s URL" -#: awx/main/utils/common.py:304 +#: awx/main/utils/common.py:299 #, python-format msgid "Unsupported host \"%s\" for file:// URL" -msgstr "ファイル:// URL のサポートされていないホスト \"%s\" " +msgstr "file:// URL でサポートされないホスト \"%s\"" -#: awx/main/utils/common.py:306 +#: awx/main/utils/common.py:301 #, python-format msgid "Host is required for %s URL" msgstr "%s URL にはホストが必要です" -#: awx/main/utils/common.py:324 +#: awx/main/utils/common.py:319 #, python-format msgid "Username must be \"git\" for SSH access to %s." msgstr "%s への SSH アクセスではユーザー名を \"git\" にする必要があります。" -#: awx/main/utils/common.py:330 +#: awx/main/utils/common.py:325 #, python-format msgid "Username must be \"hg\" for SSH access to %s." msgstr "%s への SSH アクセスではユーザー名を \"hg\" にする必要があります。" -#: awx/main/utils/common.py:611 +#: awx/main/utils/common.py:656 #, python-brace-format msgid "Input type `{data_type}` is not a dictionary" -msgstr "入力タイプ `{data_type}` は辞書ではありません" +msgstr "入力タイプ `{data_type}` は辞書ではありません" -#: awx/main/utils/common.py:644 +#: awx/main/utils/common.py:689 #, python-brace-format msgid "Variables not compatible with JSON standard (error: {json_error})" msgstr "変数には JSON 標準との互換性がありません (エラー: {json_error})" -#: awx/main/utils/common.py:650 +#: awx/main/utils/common.py:695 #, python-brace-format msgid "" "Cannot parse as JSON (error: {json_error}) or YAML (error: {yaml_error})." @@ -4199,595 +4940,281 @@ msgstr "サポートされていない PEM オブジェクトタイプ: \"%s\"" msgid "Invalid base64-encoded data" msgstr "無効な base64 エンコードされたデータ" -#: awx/main/validators.py:131 +#: awx/main/validators.py:133 msgid "Exactly one private key is required." msgstr "秘密鍵が 1 つのみ必要です。" -#: awx/main/validators.py:133 +#: awx/main/validators.py:135 msgid "At least one private key is required." msgstr "1 つ以上の秘密鍵が必要です。" -#: awx/main/validators.py:135 +#: awx/main/validators.py:137 #, python-format msgid "" -"At least %(min_keys)d private keys are required, only %(key_count)d " -"provided." -msgstr "%(min_keys)d 以上の秘密鍵が必要です。提供数: %(key_count)d のみ。" +"At least %(min_keys)d private keys are required, only %(key_count)d provided." +msgstr "最低でも %(min_keys)d の秘密鍵が必要です。提供数: %(key_count)d のみ" -#: awx/main/validators.py:138 +#: awx/main/validators.py:140 #, python-format msgid "Only one private key is allowed, %(key_count)d provided." -msgstr "秘密鍵が 1 つのみ許可されます。提供数: %(key_count)d" +msgstr "秘密鍵は 1 つのみ許可されます。提供数: %(key_count)d" -#: awx/main/validators.py:140 +#: awx/main/validators.py:142 #, python-format msgid "" "No more than %(max_keys)d private keys are allowed, %(key_count)d provided." -msgstr "%(max_keys)d を超える秘密鍵は許可されません。提供数: %(key_count)d " +msgstr "%(max_keys)d を超える秘密鍵は使用できません。提供数: %(key_count)d" -#: awx/main/validators.py:145 +#: awx/main/validators.py:147 msgid "Exactly one certificate is required." msgstr "証明書が 1 つのみ必要です。" -#: awx/main/validators.py:147 +#: awx/main/validators.py:149 msgid "At least one certificate is required." msgstr "1 つ以上の証明書が必要です。" -#: awx/main/validators.py:149 +#: awx/main/validators.py:151 #, python-format msgid "" "At least %(min_certs)d certificates are required, only %(cert_count)d " "provided." -msgstr "%(min_certs)d 以上の証明書が必要です。提供数: %(cert_count)d のみ。" +msgstr "最低でも %(min_certs)d 証明書が必要です。提供数: %(cert_count)d のみ" -#: awx/main/validators.py:152 +#: awx/main/validators.py:154 #, python-format msgid "Only one certificate is allowed, %(cert_count)d provided." -msgstr "証明書が 1 つのみ許可されます。提供数: %(cert_count)d" +msgstr "証明書は 1 つのみ許可されます。提供数: %(cert_count)d" -#: awx/main/validators.py:154 +#: awx/main/validators.py:156 #, python-format msgid "" -"No more than %(max_certs)d certificates are allowed, %(cert_count)d " -"provided." -msgstr "%(max_certs)d を超える証明書は許可されません。提供数: %(cert_count)d" +"No more than %(max_certs)d certificates are allowed, %(cert_count)d provided." +msgstr "%(max_certs)d を超えて証明書を指定できません。提供数: %(cert_count)d" -#: awx/main/views.py:23 +#: awx/main/views.py:30 msgid "API Error" msgstr "API エラー" -#: awx/main/views.py:61 +#: awx/main/views.py:65 msgid "Bad Request" msgstr "不正な要求です" -#: awx/main/views.py:62 +#: awx/main/views.py:66 msgid "The request could not be understood by the server." msgstr "要求がサーバーによって認識されませんでした。" -#: awx/main/views.py:69 +#: awx/main/views.py:73 msgid "Forbidden" msgstr "許可されていません" -#: awx/main/views.py:70 +#: awx/main/views.py:74 msgid "You don't have permission to access the requested resource." msgstr "要求されたリソースにアクセスするためのパーミッションがありません。" -#: awx/main/views.py:77 +#: awx/main/views.py:81 msgid "Not Found" msgstr "見つかりません" -#: awx/main/views.py:78 +#: awx/main/views.py:82 msgid "The requested resource could not be found." msgstr "要求されたリソースは見つかりませんでした。" -#: awx/main/views.py:85 +#: awx/main/views.py:89 msgid "Server Error" msgstr "サーバーエラー" -#: awx/main/views.py:86 +#: awx/main/views.py:90 msgid "A server error has occurred." msgstr "サーバーエラーが発生しました。" -#: awx/settings/defaults.py:725 -msgid "US East (Northern Virginia)" -msgstr "米国東部 (バージニア北部)" - -#: awx/settings/defaults.py:726 -msgid "US East (Ohio)" -msgstr "米国東部 (オハイオ)" - -#: awx/settings/defaults.py:727 -msgid "US West (Oregon)" -msgstr "米国西部 (オレゴン)" - -#: awx/settings/defaults.py:728 -msgid "US West (Northern California)" -msgstr "米国西部 (北カリフォルニア)" - -#: awx/settings/defaults.py:729 -msgid "Canada (Central)" -msgstr "カナダ (中部)" - -#: awx/settings/defaults.py:730 -msgid "EU (Frankfurt)" -msgstr "EU (フランクフルト)" - -#: awx/settings/defaults.py:731 -msgid "EU (Ireland)" -msgstr "EU (アイルランド)" - -#: awx/settings/defaults.py:732 -msgid "EU (London)" -msgstr "EU (ロンドン)" - -#: awx/settings/defaults.py:733 -msgid "Asia Pacific (Singapore)" -msgstr "アジア太平洋 (シンガポール)" - -#: awx/settings/defaults.py:734 -msgid "Asia Pacific (Sydney)" -msgstr "アジア太平洋 (シドニー)" - -#: awx/settings/defaults.py:735 -msgid "Asia Pacific (Tokyo)" -msgstr "アジア太平洋 (東京)" - -#: awx/settings/defaults.py:736 -msgid "Asia Pacific (Seoul)" -msgstr "アジア太平洋 (ソウル)" - -#: awx/settings/defaults.py:737 -msgid "Asia Pacific (Mumbai)" -msgstr "アジア太平洋 (ムンバイ)" - -#: awx/settings/defaults.py:738 -msgid "South America (Sao Paulo)" -msgstr "南アメリカ (サンパウロ)" - -#: awx/settings/defaults.py:739 -msgid "US West (GovCloud)" -msgstr "米国西部 (GovCloud)" - -#: awx/settings/defaults.py:740 -msgid "China (Beijing)" -msgstr "中国 (北京)" - -#: awx/settings/defaults.py:789 -msgid "US East 1 (B)" -msgstr "米国東部 1 (B)" - -#: awx/settings/defaults.py:790 -msgid "US East 1 (C)" -msgstr "米国東部 1 (C)" - -#: awx/settings/defaults.py:791 -msgid "US East 1 (D)" -msgstr "米国東部 1 (D)" - -#: awx/settings/defaults.py:792 -msgid "US East 4 (A)" -msgstr "米国東部 4 (A)" - -#: awx/settings/defaults.py:793 -msgid "US East 4 (B)" -msgstr "米国東部 4 (B)" - -#: awx/settings/defaults.py:794 -msgid "US East 4 (C)" -msgstr "米国東部 4 (C)" - -#: awx/settings/defaults.py:795 -msgid "US Central (A)" -msgstr "米国中部 (A)" - -#: awx/settings/defaults.py:796 -msgid "US Central (B)" -msgstr "米国中部 (B)" - -#: awx/settings/defaults.py:797 -msgid "US Central (C)" -msgstr "米国中部 (C)" - -#: awx/settings/defaults.py:798 -msgid "US Central (F)" -msgstr "米国中部 (F)" - -#: awx/settings/defaults.py:799 -msgid "US West (A)" -msgstr "米国西部 (A)" - -#: awx/settings/defaults.py:800 -msgid "US West (B)" -msgstr "米国西部 (B)" - -#: awx/settings/defaults.py:801 -msgid "US West (C)" -msgstr "米国西部 (C)" - -#: awx/settings/defaults.py:802 -msgid "Europe West 1 (B)" -msgstr "欧州西部 1 (B)" - -#: awx/settings/defaults.py:803 -msgid "Europe West 1 (C)" -msgstr "欧州西部 1 (C)" - -#: awx/settings/defaults.py:804 -msgid "Europe West 1 (D)" -msgstr "欧州西部 1 (D)" - -#: awx/settings/defaults.py:805 -msgid "Europe West 2 (A)" -msgstr "欧州西部 2 (A)" - -#: awx/settings/defaults.py:806 -msgid "Europe West 2 (B)" -msgstr "欧州西部 2 (B)" - -#: awx/settings/defaults.py:807 -msgid "Europe West 2 (C)" -msgstr "欧州西部 2 (C)" - -#: awx/settings/defaults.py:808 -msgid "Asia East (A)" -msgstr "アジア東部 (A)" - -#: awx/settings/defaults.py:809 -msgid "Asia East (B)" -msgstr "アジア東部 (B)" - -#: awx/settings/defaults.py:810 -msgid "Asia East (C)" -msgstr "アジア東部 (C)" - -#: awx/settings/defaults.py:811 -msgid "Asia Southeast (A)" -msgstr "アジア南東部 (A)" - -#: awx/settings/defaults.py:812 -msgid "Asia Southeast (B)" -msgstr "アジア南東部 (B)" - -#: awx/settings/defaults.py:813 -msgid "Asia Northeast (A)" -msgstr "アジア北東部 (A)" - -#: awx/settings/defaults.py:814 -msgid "Asia Northeast (B)" -msgstr "アジア北東部 (B)" - -#: awx/settings/defaults.py:815 -msgid "Asia Northeast (C)" -msgstr "アジア北東部 (C)" - -#: awx/settings/defaults.py:816 -msgid "Australia Southeast (A)" -msgstr "オーストラリア南東部 (A)" - -#: awx/settings/defaults.py:817 -msgid "Australia Southeast (B)" -msgstr "オーストラリア南東部 (B)" - -#: awx/settings/defaults.py:818 -msgid "Australia Southeast (C)" -msgstr "オーストラリア南東部 (C)" - -#: awx/settings/defaults.py:840 -msgid "US East" -msgstr "米国東部" - -#: awx/settings/defaults.py:841 -msgid "US East 2" -msgstr "米国東部 2" - -#: awx/settings/defaults.py:842 -msgid "US Central" -msgstr "米国中部" - -#: awx/settings/defaults.py:843 -msgid "US North Central" -msgstr "米国中北部" - -#: awx/settings/defaults.py:844 -msgid "US South Central" -msgstr "米国中南部" - -#: awx/settings/defaults.py:845 -msgid "US West Central" -msgstr "米国中西部" - -#: awx/settings/defaults.py:846 -msgid "US West" -msgstr "米国西部" - -#: awx/settings/defaults.py:847 -msgid "US West 2" -msgstr "米国西部 2" - -#: awx/settings/defaults.py:848 -msgid "Canada East" -msgstr "カナダ東部" - -#: awx/settings/defaults.py:849 -msgid "Canada Central" -msgstr "カナダ中部" - -#: awx/settings/defaults.py:850 -msgid "Brazil South" -msgstr "ブラジル南部" - -#: awx/settings/defaults.py:851 -msgid "Europe North" -msgstr "欧州北部" - -#: awx/settings/defaults.py:852 -msgid "Europe West" -msgstr "欧州西部" - -#: awx/settings/defaults.py:853 -msgid "UK West" -msgstr "英国西部" - -#: awx/settings/defaults.py:854 -msgid "UK South" -msgstr "英国南部" - -#: awx/settings/defaults.py:855 -msgid "Asia East" -msgstr "アジア東部" - -#: awx/settings/defaults.py:856 -msgid "Asia Southeast" -msgstr "アジア南東部" - -#: awx/settings/defaults.py:857 -msgid "Australia East" -msgstr "オーストラリア東部" - -#: awx/settings/defaults.py:858 -msgid "Australia Southeast" -msgstr "オーストラリア南東部 " - -#: awx/settings/defaults.py:859 -msgid "India West" -msgstr "インド西部" - -#: awx/settings/defaults.py:860 -msgid "India South" -msgstr "インド南部" - -#: awx/settings/defaults.py:861 -msgid "Japan East" -msgstr "日本東部" - -#: awx/settings/defaults.py:862 -msgid "Japan West" -msgstr "日本西部" - -#: awx/settings/defaults.py:863 -msgid "Korea Central" -msgstr "韓国中部" - -#: awx/settings/defaults.py:864 -msgid "Korea South" -msgstr "韓国南部" - #: awx/sso/apps.py:9 msgid "Single Sign-On" msgstr "シングルサインオン" -#: awx/sso/conf.py:30 +#: awx/sso/conf.py:41 msgid "" -"Mapping to organization admins/users from social auth accounts. This setting\n" -"controls which users are placed into which Tower organizations based on their\n" -"username and email address. Configuration details are available in the Ansible\n" +"Mapping to organization admins/users from social auth accounts. This " +"setting\n" +"controls which users are placed into which Tower organizations based on " +"their\n" +"username and email address. Configuration details are available in the " +"Ansible\n" "Tower documentation." -msgstr "" -"ソーシャル認証アカウントから組織管理者/ユーザーへのマッピングです。この設定は、ユーザー名およびメールアドレスに基づいてどのユーザーをどの Tower " -"組織に配置するかを管理します。設定の詳細については、Ansible Tower ドキュメントを参照してください。" +msgstr "ソーシャル認証アカウントから組織管理者/ユーザーへのマッピングです。この設定は、ユーザー名およびメールアドレスに基づいてどのユーザーをどの Tower 組織に配置するかを管理します。設定の詳細については、Ansible Tower ドキュメントを参照してください。" -#: awx/sso/conf.py:55 +#: awx/sso/conf.py:67 msgid "" "Mapping of team members (users) from social auth accounts. Configuration\n" "details are available in Tower documentation." -msgstr "" -"ソーシャル認証アカウントからチームメンバー (ユーザー) へのマッピングです。設定の詳細については、Tower ドキュメントを参照してください。" +msgstr "ソーシャル認証アカウントからチームメンバー (ユーザー) へのマッピングです。設定の詳細については、Tower ドキュメントを参照してください。" -#: awx/sso/conf.py:80 +#: awx/sso/conf.py:92 msgid "Authentication Backends" msgstr "認証バックエンド" -#: awx/sso/conf.py:81 +#: awx/sso/conf.py:93 msgid "" "List of authentication backends that are enabled based on license features " "and other authentication settings." msgstr "ライセンスの特長およびその他の認証設定に基づいて有効にされる認証バックエンドの一覧。" -#: awx/sso/conf.py:94 +#: awx/sso/conf.py:106 msgid "Social Auth Organization Map" msgstr "ソーシャル認証組織マップ" -#: awx/sso/conf.py:106 +#: awx/sso/conf.py:118 msgid "Social Auth Team Map" msgstr "ソーシャル認証チームマップ" -#: awx/sso/conf.py:118 +#: awx/sso/conf.py:130 msgid "Social Auth User Fields" msgstr "ソーシャル認証ユーザーフィールド" -#: awx/sso/conf.py:119 +#: awx/sso/conf.py:131 msgid "" -"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." -msgstr "" -"空リスト " -"`[]`に設定される場合、この設定により新規ユーザーアカウントは作成できなくなります。ソーシャル認証を使ってログインしたことのあるユーザーまたは一致するメールアドレスのユーザーアカウントを持つユーザーのみがログインできます。" +"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." +msgstr "空リスト `[]`に設定される場合、この設定により新規ユーザーアカウントは作成できなくなります。ソーシャル認証を使ってログインしたことのあるユーザーまたは一致するメールアドレスのユーザーアカウントを持つユーザーのみがログインできます。" -#: awx/sso/conf.py:141 +#: awx/sso/conf.py:153 msgid "LDAP Server URI" msgstr "LDAP サーバー URI" -#: awx/sso/conf.py:142 +#: awx/sso/conf.py:154 msgid "" "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 " +"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." -msgstr "" -"\"ldap://ldap.example.com:389\" (非 SSL) または \"ldaps://ldap.example.com:636\"" -" (SSL) などの LDAP サーバーに接続する URI です。複数の LDAP サーバーをスペースまたはカンマで区切って指定できます。LDAP " -"認証は、このパラメーターが空の場合は無効になります。" - -#: awx/sso/conf.py:146 awx/sso/conf.py:162 awx/sso/conf.py:174 -#: awx/sso/conf.py:186 awx/sso/conf.py:202 awx/sso/conf.py:222 -#: awx/sso/conf.py:244 awx/sso/conf.py:259 awx/sso/conf.py:277 -#: awx/sso/conf.py:294 awx/sso/conf.py:306 awx/sso/conf.py:332 -#: awx/sso/conf.py:348 awx/sso/conf.py:362 awx/sso/conf.py:380 -#: awx/sso/conf.py:406 +msgstr "\"ldap://ldap.example.com:389\" (非 SSL) または \"ldaps://ldap.example.com:636\" (SSL) などの LDAP サーバーに接続する URI です。複数の LDAP サーバーをスペースまたはカンマで区切って指定できます。LDAP 認証は、このパラメーターが空の場合は無効になります。" + +#: awx/sso/conf.py:158 awx/sso/conf.py:173 awx/sso/conf.py:184 +#: awx/sso/conf.py:195 awx/sso/conf.py:210 awx/sso/conf.py:229 +#: awx/sso/conf.py:250 awx/sso/conf.py:264 awx/sso/conf.py:281 +#: awx/sso/conf.py:297 awx/sso/conf.py:308 awx/sso/conf.py:333 +#: awx/sso/conf.py:348 awx/sso/conf.py:361 awx/sso/conf.py:378 +#: awx/sso/conf.py:404 msgid "LDAP" msgstr "LDAP" -#: awx/sso/conf.py:158 +#: awx/sso/conf.py:169 msgid "LDAP Bind DN" msgstr "LDAP バインド DN" -#: awx/sso/conf.py:159 +#: awx/sso/conf.py:170 msgid "" "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 Ansible Tower documentation for example syntax." -msgstr "" -"すべての検索クエリーについてバインドするユーザーの DN (識別名) です。これは、他のユーザー情報についての LDAP " -"クエリー実行時のログインに使用するシステムユーザーアカウントです。構文のサンプルについては Ansible Tower ドキュメントを参照してください。" +msgstr "すべての検索クエリーについてバインドするユーザーの DN (識別名) です。これは、他のユーザー情報についての LDAP クエリー実行時のログインに使用するシステムユーザーアカウントです。構文のサンプルについては Ansible Tower ドキュメントを参照してください。" -#: awx/sso/conf.py:172 +#: awx/sso/conf.py:182 msgid "LDAP Bind Password" msgstr "LDAP バインドパスワード" -#: awx/sso/conf.py:173 +#: awx/sso/conf.py:183 msgid "Password used to bind LDAP user account." msgstr "LDAP ユーザーアカウントをバインドするために使用されるパスワード。" -#: awx/sso/conf.py:184 +#: awx/sso/conf.py:193 msgid "LDAP Start TLS" msgstr "LDAP Start TLS" -#: awx/sso/conf.py:185 +#: awx/sso/conf.py:194 msgid "Whether to enable TLS when the LDAP connection is not using SSL." msgstr "LDAP 接続が SSL を使用していない場合に TLS を有効にするかどうか。" -#: awx/sso/conf.py:195 +#: awx/sso/conf.py:203 msgid "LDAP Connection Options" msgstr "LDAP 接続オプション" -#: awx/sso/conf.py:196 +#: awx/sso/conf.py:204 msgid "" "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." -msgstr "" -"LDAP 設定に設定する追加オプションです。LDAP 照会はデフォルトで無効にされます (特定の LDAP クエリーが AD " -"でハングすることを避けるため)。オプション名は文字列でなければなりません (例: " -"\"OPT_REFERRALS\")。可能なオプションおよび設定できる値については、https://www.python-" -"ldap.org/doc/html/ldap.html#options を参照してください。" +"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." +msgstr "LDAP 設定に設定する追加オプションです。LDAP 照会はデフォルトで無効にされます (特定の LDAP クエリーが AD でハングすることを避けるため)。オプション名は文字列でなければなりません (例: \"OPT_REFERRALS\")。可能なオプションおよび設定できる値については、https://www.python-ldap.org/doc/html/ldap.html#options を参照してください。" -#: awx/sso/conf.py:215 +#: awx/sso/conf.py:222 msgid "LDAP User Search" msgstr "LDAP ユーザー検索" -#: awx/sso/conf.py:216 +#: awx/sso/conf.py:223 msgid "" "LDAP search query to find users. Any user that matches the given pattern " -"will be able to login to Tower. The user should also be mapped into a Tower" -" organization (as defined in the AUTH_LDAP_ORGANIZATION_MAP setting). If " +"will be able to login to Tower. The user should also be mapped into a Tower " +"organization (as defined in the AUTH_LDAP_ORGANIZATION_MAP setting). If " "multiple search queries need to be supported use of \"LDAPUnion\" is " "possible. See Tower documentation for details." -msgstr "" -"ユーザーを検索するための LDAP 検索クエリーです。指定パターンに一致するユーザーは Tower にログインできます。ユーザーも Tower " -"組織にマップされている必要があります (AUTH_LDAP_ORGANIZATION_MAP " -"設定で定義)。複数の検索クエリーをサポートする必要がある場合、\"LDAPUnion\" を使用できます。詳細は、Tower " -"ドキュメントを参照してください。" +msgstr "ユーザーを検索するための LDAP 検索クエリーです。指定パターンに一致するユーザーは Tower にログインできます。ユーザーも Tower 組織にマップされている必要があります (AUTH_LDAP_ORGANIZATION_MAP 設定で定義)。複数の検索クエリーをサポートする必要がある場合、\"LDAPUnion\" を使用できます。詳細は、Tower ドキュメントを参照してください。" -#: awx/sso/conf.py:238 +#: awx/sso/conf.py:244 msgid "LDAP User DN Template" msgstr "LDAP ユーザー DN テンプレート" -#: awx/sso/conf.py:239 +#: awx/sso/conf.py:245 msgid "" "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." -msgstr "" -"ユーザー DN " -"の形式がすべて同じである場合のユーザー検索の代替法になります。この方法は、組織の環境で使用可能であるかどうかを検索する場合よりも効率的なユーザー検索方法になります。この設定に値がある場合、それが" -" AUTH_LDAP_USER_SEARCH の代わりに使用されます。" +msgstr "ユーザー DN の形式がすべて同じである場合のユーザー検索の代替法になります。この方法は、組織の環境で使用可能であるかどうかを検索する場合よりも効率的なユーザー検索方法になります。この設定に値がある場合、それが AUTH_LDAP_USER_SEARCH の代わりに使用されます。" -#: awx/sso/conf.py:254 +#: awx/sso/conf.py:259 msgid "LDAP User Attribute Map" msgstr "LDAP ユーザー属性マップ" -#: awx/sso/conf.py:255 +#: awx/sso/conf.py:260 msgid "" "Mapping of LDAP user schema to Tower 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 Ansible Tower " "documentation for additional details." -msgstr "" -"LDAP ユーザースキーマの Tower API ユーザー属性へのマッピングです 。デフォルト設定は ActiveDirectory で有効ですが、他の" -" LDAP 設定を持つユーザーは値を変更する必要が生じる場合があります。追加の詳細情報については、Ansible Tower " -"ドキュメントを参照してください。" +msgstr "LDAP ユーザースキーマの Tower API ユーザー属性へのマッピングです 。デフォルト設定は ActiveDirectory で有効ですが、他の LDAP 設定を持つユーザーは値を変更する必要が生じる場合があります。追加の詳細情報については、Ansible Tower ドキュメントを参照してください。" -#: awx/sso/conf.py:273 +#: awx/sso/conf.py:277 msgid "LDAP Group Search" msgstr "LDAP グループ検索" -#: awx/sso/conf.py:274 +#: awx/sso/conf.py:278 msgid "" "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." -msgstr "" -"ユーザーは LDAP グループのメンバーシップに基づいて組織にマップされます。この設定は、グループを検索できるように LDAP " -"検索クエリーを定義します。ユーザー検索とは異なり、グループ検索は LDAPSearchUnion をサポートしません。" +msgstr "ユーザーは LDAP グループのメンバーシップに基づいて組織にマップされます。この設定は、グループを検索できるように LDAP 検索クエリーを定義します。ユーザー検索とは異なり、グループ検索は LDAPSearchUnion をサポートしません。" -#: awx/sso/conf.py:290 +#: awx/sso/conf.py:293 msgid "LDAP Group Type" msgstr "LDAP グループタイプ" -#: awx/sso/conf.py:291 +#: awx/sso/conf.py:294 msgid "" -"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" -msgstr "" -"グループタイプは LDAP サーバーのタイプに基づいて変更する必要がある場合があります。値は以下に記載されています: https://django-" -"auth-ldap.readthedocs.io/en/stable/groups.html#types-of-groups" +"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" +msgstr "グループタイプは LDAP サーバーのタイプに基づいて変更する必要がある場合があります。値は以下に記載されています: https://django-auth-ldap.readthedocs.io/en/stable/groups.html#types-of-groups" -#: awx/sso/conf.py:304 +#: awx/sso/conf.py:306 msgid "LDAP Group Type Parameters" msgstr "LDAP グループタイプパラメーター" -#: awx/sso/conf.py:305 +#: awx/sso/conf.py:307 msgid "Key value parameters to send the chosen group type init method." msgstr "選択されたグループタイプの init メソッドを送信するためのキー値パラメーター。" -#: awx/sso/conf.py:327 +#: awx/sso/conf.py:328 msgid "LDAP Require Group" msgstr "LDAP 要求グループ" -#: awx/sso/conf.py:328 +#: awx/sso/conf.py:329 msgid "" "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 via Tower. Only one require group is supported." -msgstr "" -"ログインに必要なグループ DN。指定されている場合、LDAP " -"経由でログインするにはユーザーはこのグループのメンバーである必要があります。設定されていない場合は、ユーザー検索に一致する LDAP " -"のすべてのユーザーが Tower 経由でログインできます。1つの要求グループのみがサポートされます。" +msgstr "ログインに必要なグループ DN。指定されている場合、LDAP 経由でログインするにはユーザーはこのグループのメンバーである必要があります。設定されていない場合は、ユーザー検索に一致する LDAP のすべてのユーザーが Tower 経由でログインできます。1つの要求グループのみがサポートされます。" #: awx/sso/conf.py:344 msgid "LDAP Deny Group" @@ -4797,361 +5224,359 @@ msgstr "LDAP 拒否グループ" msgid "" "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." -msgstr "" -"グループ DN がログインで拒否されます。指定されている場合、ユーザーはこのグループのメンバーの場合にログインできません。1 " -"つの拒否グループのみがサポートされます。" +msgstr "グループ DN がログインで拒否されます。指定されている場合、ユーザーはこのグループのメンバーの場合にログインできません。1 つの拒否グループのみがサポートされます。" -#: awx/sso/conf.py:358 +#: awx/sso/conf.py:357 msgid "LDAP User Flags By Group" msgstr "LDAP ユーザーフラグ (グループ別)" -#: awx/sso/conf.py:359 +#: awx/sso/conf.py:358 msgid "" "Retrieve users from a given group. At this time, superuser and system " "auditors are the only groups supported. Refer to the Ansible Tower " "documentation for more detail." -msgstr "" -"指定されたグループからユーザーを検索します。この場合、サポートされるグループは、スーパーユーザーおよびシステム監査者のみになります。詳細は、Ansible" -" Tower ドキュメントを参照してください。" +msgstr "指定されたグループからユーザーを検索します。この場合、サポートされるグループは、スーパーユーザーおよびシステム監査者のみになります。詳細は、Ansible Tower ドキュメントを参照してください。" -#: awx/sso/conf.py:375 +#: awx/sso/conf.py:373 msgid "LDAP Organization Map" msgstr "LDAP 組織マップ" -#: awx/sso/conf.py:376 +#: awx/sso/conf.py:374 msgid "" "Mapping between organization admins/users and LDAP groups. This controls " -"which users are placed into which Tower organizations relative to their LDAP" -" group memberships. Configuration details are available in the Ansible Tower" -" documentation." -msgstr "" -"組織管理者/ユーザーと LDAP グループ間のマッピングです。この設定は、LDAP グループのメンバーシップに基づいてどのユーザーをどの Tower " -"組織に配置するかを管理します。設定の詳細については、Ansible Tower ドキュメントを参照してください。" +"which users are placed into which Tower organizations relative to their LDAP " +"group memberships. Configuration details are available in the Ansible Tower " +"documentation." +msgstr "組織管理者/ユーザーと LDAP グループ間のマッピングです。この設定は、LDAP グループのメンバーシップに基づいてどのユーザーをどの Tower 組織に配置するかを管理します。設定の詳細については、Ansible Tower ドキュメントを参照してください。" -#: awx/sso/conf.py:403 +#: awx/sso/conf.py:401 msgid "LDAP Team Map" msgstr "LDAP チームマップ" -#: awx/sso/conf.py:404 +#: awx/sso/conf.py:402 msgid "" "Mapping between team members (users) and LDAP groups. Configuration details " "are available in the Ansible Tower documentation." -msgstr "" -"チームメンバー (ユーザー) と LDAP グループ間のマッピングです。設定の詳細については、Ansible Tower " -"ドキュメントを参照してください。" +msgstr "チームメンバー (ユーザー) と LDAP グループ間のマッピングです。設定の詳細については、Ansible Tower ドキュメントを参照してください。" -#: awx/sso/conf.py:440 +#: awx/sso/conf.py:437 msgid "RADIUS Server" msgstr "RADIUS サーバー" -#: awx/sso/conf.py:441 +#: awx/sso/conf.py:438 msgid "" "Hostname/IP of RADIUS server. RADIUS authentication is disabled if this " "setting is empty." msgstr "RADIUS サーバーのホスト名/IP です。この設定が空の場合は RADIUS 認証は無効にされます。" -#: awx/sso/conf.py:443 awx/sso/conf.py:457 awx/sso/conf.py:469 +#: awx/sso/conf.py:440 awx/sso/conf.py:453 awx/sso/conf.py:464 #: awx/sso/models.py:14 msgid "RADIUS" msgstr "RADIUS" -#: awx/sso/conf.py:455 +#: awx/sso/conf.py:451 msgid "RADIUS Port" msgstr "RADIUS ポート" -#: awx/sso/conf.py:456 +#: awx/sso/conf.py:452 msgid "Port of RADIUS server." msgstr "RADIUS サーバーのポート。" -#: awx/sso/conf.py:467 +#: awx/sso/conf.py:462 msgid "RADIUS Secret" msgstr "RADIUS シークレット" -#: awx/sso/conf.py:468 +#: awx/sso/conf.py:463 msgid "Shared secret for authenticating to RADIUS server." msgstr "RADIUS サーバーに対して認証するための共有シークレット。" -#: awx/sso/conf.py:484 +#: awx/sso/conf.py:478 msgid "TACACS+ Server" msgstr "TACACS+ サーバー" -#: awx/sso/conf.py:485 +#: awx/sso/conf.py:479 msgid "Hostname of TACACS+ server." msgstr "TACACS+ サーバーのホスト名。" -#: awx/sso/conf.py:486 awx/sso/conf.py:499 awx/sso/conf.py:512 -#: awx/sso/conf.py:525 awx/sso/conf.py:537 awx/sso/models.py:15 +#: awx/sso/conf.py:480 awx/sso/conf.py:492 awx/sso/conf.py:504 +#: awx/sso/conf.py:516 awx/sso/conf.py:527 awx/sso/models.py:15 msgid "TACACS+" msgstr "TACACS+" -#: awx/sso/conf.py:497 +#: awx/sso/conf.py:490 msgid "TACACS+ Port" msgstr "TACACS+ ポート" -#: awx/sso/conf.py:498 +#: awx/sso/conf.py:491 msgid "Port number of TACACS+ server." msgstr "TACACS+ サーバーのポート番号。" -#: awx/sso/conf.py:510 +#: awx/sso/conf.py:502 msgid "TACACS+ Secret" msgstr "TACACS+ シークレット" -#: awx/sso/conf.py:511 +#: awx/sso/conf.py:503 msgid "Shared secret for authenticating to TACACS+ server." msgstr "TACACS+ サーバーに対して認証するための共有シークレット。" -#: awx/sso/conf.py:523 +#: awx/sso/conf.py:514 msgid "TACACS+ Auth Session Timeout" msgstr "TACACS+ 認証セッションタイムアウト" -#: awx/sso/conf.py:524 +#: awx/sso/conf.py:515 msgid "TACACS+ session timeout value in seconds, 0 disables timeout." msgstr "TACACS+ セッションのタイムアウト値 (秒数) です。0 はタイムアウトを無効にします。" -#: awx/sso/conf.py:535 +#: awx/sso/conf.py:525 msgid "TACACS+ Authentication Protocol" msgstr "TACACS+ 認証プロトコル" -#: awx/sso/conf.py:536 +#: awx/sso/conf.py:526 msgid "Choose the authentication protocol used by TACACS+ client." msgstr "TACACS+ クライアントによって使用される認証プロトコルを選択します。" -#: awx/sso/conf.py:551 +#: awx/sso/conf.py:540 msgid "Google OAuth2 Callback URL" msgstr "Google OAuth2 コールバック URL" -#: awx/sso/conf.py:552 awx/sso/conf.py:645 awx/sso/conf.py:710 +#: awx/sso/conf.py:541 awx/sso/conf.py:634 awx/sso/conf.py:699 msgid "" "Provide this URL as the callback URL for your application as part of your " "registration process. Refer to the Ansible Tower documentation for more " "detail." -msgstr "" -"登録プロセスの一環として、この URL をアプリケーションのコールバック URL として指定します。詳細については、Ansible Tower " -"ドキュメントを参照してください。" +msgstr "登録プロセスの一環として、この URL をアプリケーションのコールバック URL として指定します。詳細については、Ansible Tower ドキュメントを参照してください。" -#: awx/sso/conf.py:555 awx/sso/conf.py:567 awx/sso/conf.py:579 -#: awx/sso/conf.py:592 awx/sso/conf.py:606 awx/sso/conf.py:618 -#: awx/sso/conf.py:630 +#: awx/sso/conf.py:544 awx/sso/conf.py:556 awx/sso/conf.py:568 +#: awx/sso/conf.py:581 awx/sso/conf.py:595 awx/sso/conf.py:607 +#: awx/sso/conf.py:619 msgid "Google OAuth2" msgstr "Google OAuth2" -#: awx/sso/conf.py:565 +#: awx/sso/conf.py:554 msgid "Google OAuth2 Key" msgstr "Google OAuth2 キー" -#: awx/sso/conf.py:566 +#: awx/sso/conf.py:555 msgid "The OAuth2 key from your web application." msgstr "web アプリケーションの OAuth2 キー " -#: awx/sso/conf.py:577 +#: awx/sso/conf.py:566 msgid "Google OAuth2 Secret" msgstr "Google OAuth2 シークレット" -#: awx/sso/conf.py:578 +#: awx/sso/conf.py:567 msgid "The OAuth2 secret from your web application." msgstr "web アプリケーションの OAuth2 シークレット" -#: awx/sso/conf.py:589 -msgid "Google OAuth2 Whitelisted Domains" -msgstr "Google OAuth2 ホワイトリストドメイン" +#: awx/sso/conf.py:578 +msgid "Google OAuth2 Allowed Domains" +msgstr "GoogleOAuth2 で許可されているドメイン" -#: awx/sso/conf.py:590 +#: awx/sso/conf.py:579 msgid "" "Update this setting to restrict the domains who are allowed to login using " "Google OAuth2." msgstr "この設定を更新し、Google OAuth2 を使用してログインできるドメインを制限します。" -#: awx/sso/conf.py:601 +#: awx/sso/conf.py:590 msgid "Google OAuth2 Extra Arguments" msgstr "Google OAuth2 追加引数" -#: awx/sso/conf.py:602 +#: awx/sso/conf.py:591 msgid "" -"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 " +"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 Ansible Tower documentation for more detail." -msgstr "" -"Google OAuth2 ログインの追加引数です。ユーザーが複数の Google " -"アカウントでログインしている場合でも、単一ドメインの認証のみを許可するように制限できます。詳細については、Ansible Tower " -"ドキュメントを参照してください。" +msgstr "Google OAuth2 ログインの追加引数です。ユーザーが複数の Google アカウントでログインしている場合でも、単一ドメインの認証のみを許可するように制限できます。詳細については、Ansible Tower ドキュメントを参照してください。" -#: awx/sso/conf.py:616 +#: awx/sso/conf.py:605 msgid "Google OAuth2 Organization Map" msgstr "Google OAuth2 組織マップ" -#: awx/sso/conf.py:628 +#: awx/sso/conf.py:617 msgid "Google OAuth2 Team Map" msgstr "Google OAuth2 チームマップ" -#: awx/sso/conf.py:644 +#: awx/sso/conf.py:633 msgid "GitHub OAuth2 Callback URL" msgstr "GitHub OAuth2 コールバック URL" -#: awx/sso/conf.py:648 awx/sso/conf.py:660 awx/sso/conf.py:671 -#: awx/sso/conf.py:683 awx/sso/conf.py:695 +#: awx/sso/conf.py:637 awx/sso/conf.py:649 awx/sso/conf.py:660 +#: awx/sso/conf.py:672 awx/sso/conf.py:684 msgid "GitHub OAuth2" msgstr "GitHub OAuth2" -#: awx/sso/conf.py:658 +#: awx/sso/conf.py:647 msgid "GitHub OAuth2 Key" msgstr "GitHub OAuth2 キー" -#: awx/sso/conf.py:659 +#: awx/sso/conf.py:648 msgid "The OAuth2 key (Client ID) from your GitHub developer application." msgstr "GitHub 開発者アプリケーションからの OAuth2 キー (クライアント ID)。" -#: awx/sso/conf.py:669 +#: awx/sso/conf.py:658 msgid "GitHub OAuth2 Secret" msgstr "GitHub OAuth2 シークレット" -#: awx/sso/conf.py:670 +#: awx/sso/conf.py:659 msgid "" "The OAuth2 secret (Client Secret) from your GitHub developer application." msgstr "GitHub 開発者アプリケーションからの OAuth2 シークレット (クライアントシークレット)。" -#: awx/sso/conf.py:681 +#: awx/sso/conf.py:670 msgid "GitHub OAuth2 Organization Map" msgstr "GitHub OAuth2 組織マップ" -#: awx/sso/conf.py:693 +#: awx/sso/conf.py:682 msgid "GitHub OAuth2 Team Map" msgstr "GitHub OAuth2 チームマップ" -#: awx/sso/conf.py:709 +#: awx/sso/conf.py:698 msgid "GitHub Organization OAuth2 Callback URL" msgstr "GitHub 組織 OAuth2 コールバック URL" -#: awx/sso/conf.py:713 awx/sso/conf.py:725 awx/sso/conf.py:736 -#: awx/sso/conf.py:749 awx/sso/conf.py:760 awx/sso/conf.py:772 +#: awx/sso/conf.py:702 awx/sso/conf.py:714 awx/sso/conf.py:725 +#: awx/sso/conf.py:738 awx/sso/conf.py:749 awx/sso/conf.py:761 msgid "GitHub Organization OAuth2" msgstr "GitHub 組織 OAuth2" -#: awx/sso/conf.py:723 +#: awx/sso/conf.py:712 msgid "GitHub Organization OAuth2 Key" msgstr "GitHub 組織 OAuth2 キー" -#: awx/sso/conf.py:724 awx/sso/conf.py:802 +#: awx/sso/conf.py:713 awx/sso/conf.py:791 msgid "The OAuth2 key (Client ID) from your GitHub organization application." msgstr "GitHub 組織アプリケーションからの OAuth2 キー (クライアント ID)。" -#: awx/sso/conf.py:734 +#: awx/sso/conf.py:723 msgid "GitHub Organization OAuth2 Secret" msgstr "GitHub 組織 OAuth2 シークレット" -#: awx/sso/conf.py:735 awx/sso/conf.py:813 +#: awx/sso/conf.py:724 awx/sso/conf.py:802 msgid "" "The OAuth2 secret (Client Secret) from your GitHub organization application." msgstr "GitHub 組織アプリケーションからの OAuth2 シークレット (クライアントシークレット)。" -#: awx/sso/conf.py:746 +#: awx/sso/conf.py:735 msgid "GitHub Organization Name" msgstr "GitHub 組織名" -#: awx/sso/conf.py:747 +#: awx/sso/conf.py:736 msgid "" "The name of your GitHub organization, as used in your organization's URL: " "https://github.com//." msgstr "GitHub 組織の名前で、組織の URL (https://github.com//) で使用されます。" -#: awx/sso/conf.py:758 +#: awx/sso/conf.py:747 msgid "GitHub Organization OAuth2 Organization Map" msgstr "GitHub 組織 OAuth2 組織マップ" -#: awx/sso/conf.py:770 +#: awx/sso/conf.py:759 msgid "GitHub Organization OAuth2 Team Map" msgstr "GitHub 組織 OAuth2 チームマップ" -#: awx/sso/conf.py:786 +#: awx/sso/conf.py:775 msgid "GitHub Team OAuth2 Callback URL" msgstr "GitHub チーム OAuth2 コールバック URL" -#: awx/sso/conf.py:787 +#: awx/sso/conf.py:776 msgid "" -"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." -msgstr "" -"組織が所有するアプリケーションを " -"https://github.com/organizations//settings/applications に作成し、OAuth2" -" キー (クライアント ID) およびシークレット (クライアントシークレット) を取得します。この URL をアプリケーションのコールバック URL " -"として指定します。" +"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." +msgstr "組織が所有するアプリケーションを https://github.com/organizations//settings/applications に作成し、OAuth2 キー (クライアント ID) およびシークレット (クライアントシークレット) を取得します。この URL をアプリケーションのコールバック URL として指定します。" -#: awx/sso/conf.py:791 awx/sso/conf.py:803 awx/sso/conf.py:814 -#: awx/sso/conf.py:827 awx/sso/conf.py:838 awx/sso/conf.py:850 +#: awx/sso/conf.py:780 awx/sso/conf.py:792 awx/sso/conf.py:803 +#: awx/sso/conf.py:816 awx/sso/conf.py:827 awx/sso/conf.py:839 msgid "GitHub Team OAuth2" msgstr "GitHub チーム OAuth2" -#: awx/sso/conf.py:801 +#: awx/sso/conf.py:790 msgid "GitHub Team OAuth2 Key" msgstr "GitHub チーム OAuth2 キー" -#: awx/sso/conf.py:812 +#: awx/sso/conf.py:801 msgid "GitHub Team OAuth2 Secret" msgstr "GitHub チーム OAuth2 シークレット" -#: awx/sso/conf.py:824 +#: awx/sso/conf.py:813 msgid "GitHub Team ID" msgstr "GitHub チーム ID" -#: awx/sso/conf.py:825 +#: awx/sso/conf.py:814 msgid "" -"Find the numeric team ID using the Github API: http://fabian-" -"kostadinov.github.io/2015/01/16/how-to-find-a-github-team-id/." -msgstr "" -"Github API を使用して数値のチーム ID を検索します: http://fabian-" -"kostadinov.github.io/2015/01/16/how-to-find-a-github-team-id/" +"Find the numeric team ID using the Github API: http://fabian-kostadinov." +"github.io/2015/01/16/how-to-find-a-github-team-id/." +msgstr "Github API を使用して数値のチーム ID を検索します: http://fabian-kostadinov.github.io/2015/01/16/how-to-find-a-github-team-id/" -#: awx/sso/conf.py:836 +#: awx/sso/conf.py:825 msgid "GitHub Team OAuth2 Organization Map" msgstr "GitHub チーム OAuth2 組織マップ" -#: awx/sso/conf.py:848 +#: awx/sso/conf.py:837 msgid "GitHub Team OAuth2 Team Map" msgstr "GitHub チーム OAuth2 チームマップ" -#: awx/sso/conf.py:864 +#: awx/sso/conf.py:853 msgid "Azure AD OAuth2 Callback URL" msgstr "Azure AD OAuth2 コールバック URL" -#: awx/sso/conf.py:865 +#: awx/sso/conf.py:854 msgid "" "Provide this URL as the callback URL for your application as part of your " "registration process. Refer to the Ansible Tower documentation for more " "detail. " -msgstr "" -"登録プロセスの一環として、この URL をアプリケーションのコールバック URL として指定します。詳細については、Ansible Tower " -"ドキュメントを参照してください。" +msgstr "登録プロセスの一環として、この URL をアプリケーションのコールバック URL として指定します。詳細については、Ansible Tower ドキュメントを参照してください。" -#: awx/sso/conf.py:868 awx/sso/conf.py:880 awx/sso/conf.py:891 -#: awx/sso/conf.py:903 awx/sso/conf.py:915 +#: awx/sso/conf.py:857 awx/sso/conf.py:869 awx/sso/conf.py:880 +#: awx/sso/conf.py:892 awx/sso/conf.py:904 msgid "Azure AD OAuth2" msgstr "Azure AD OAuth2" -#: awx/sso/conf.py:878 +#: awx/sso/conf.py:867 msgid "Azure AD OAuth2 Key" msgstr "Azure AD OAuth2 キー" -#: awx/sso/conf.py:879 +#: awx/sso/conf.py:868 msgid "The OAuth2 key (Client ID) from your Azure AD application." msgstr "Azure AD アプリケーションからの OAuth2 キー (クライアント ID)。" -#: awx/sso/conf.py:889 +#: awx/sso/conf.py:878 msgid "Azure AD OAuth2 Secret" msgstr "Azure AD OAuth2 シークレット" -#: awx/sso/conf.py:890 +#: awx/sso/conf.py:879 msgid "The OAuth2 secret (Client Secret) from your Azure AD application." msgstr "Azure AD アプリケーションからの OAuth2 シークレット (クライアントシークレット)。" -#: awx/sso/conf.py:901 +#: awx/sso/conf.py:890 msgid "Azure AD OAuth2 Organization Map" msgstr "Azure AD OAuth2 組織マップ" -#: awx/sso/conf.py:913 +#: awx/sso/conf.py:902 msgid "Azure AD OAuth2 Team Map" msgstr "Azure AD OAuth2 チームマップ" +#: awx/sso/conf.py:926 +msgid "Automatically Create Organizations and Teams on SAML Login" +msgstr "SAML ログインで組織とチームを自動的に作成" + +#: awx/sso/conf.py:927 +msgid "" +"When enabled (the default), mapped Organizations and Teams will be created " +"automatically on successful SAML login." +msgstr "有効にすると (デフォルト)、マップされた組織とチームは、SAML ログインが成功すると自動的に作成されます。" + +#: awx/sso/conf.py:929 awx/sso/conf.py:942 awx/sso/conf.py:955 +#: awx/sso/conf.py:968 awx/sso/conf.py:982 awx/sso/conf.py:995 +#: awx/sso/conf.py:1007 awx/sso/conf.py:1027 awx/sso/conf.py:1044 +#: awx/sso/conf.py:1062 awx/sso/conf.py:1097 awx/sso/conf.py:1128 +#: awx/sso/conf.py:1141 awx/sso/conf.py:1157 awx/sso/conf.py:1169 +#: awx/sso/conf.py:1181 awx/sso/conf.py:1200 awx/sso/models.py:16 +msgid "SAML" +msgstr "SAML" + #: awx/sso/conf.py:938 msgid "SAML Assertion Consumer Service (ACS) URL" msgstr "SAML アサーションコンシューマー サービス (ACS) URL" @@ -5161,275 +5586,214 @@ msgid "" "Register Tower 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." -msgstr "" -"設定済みの各アイデンティティープロバイダー (IdP) で Tower をサービスプロバイダー (SP) として登録します。SP エンティティー ID " -"およびアプリケーションのこの ACS URL を指定します。" - -#: awx/sso/conf.py:942 awx/sso/conf.py:956 awx/sso/conf.py:970 -#: awx/sso/conf.py:985 awx/sso/conf.py:999 awx/sso/conf.py:1012 -#: awx/sso/conf.py:1033 awx/sso/conf.py:1051 awx/sso/conf.py:1070 -#: awx/sso/conf.py:1106 awx/sso/conf.py:1138 awx/sso/conf.py:1152 -#: awx/sso/conf.py:1169 awx/sso/conf.py:1182 awx/sso/conf.py:1195 -#: awx/sso/conf.py:1213 awx/sso/models.py:16 -msgid "SAML" -msgstr "SAML" +msgstr "設定済みの各アイデンティティープロバイダー (IdP) で Tower をサービスプロバイダー (SP) として登録します。SP エンティティー ID およびアプリケーションのこの ACS URL を指定します。" -#: awx/sso/conf.py:953 +#: awx/sso/conf.py:952 msgid "SAML Service Provider Metadata URL" msgstr "SAML サービスプロバイダーメタデータ URL" -#: awx/sso/conf.py:954 +#: awx/sso/conf.py:953 msgid "" "If your identity provider (IdP) allows uploading an XML metadata file, you " "can download one from this URL." -msgstr "" -"アイデンティティープロバイダー (IdP) が XML メタデータファイルのアップロードを許可する場合、この URL からダウンロードできます。" +msgstr "アイデンティティープロバイダー (IdP) が XML メタデータファイルのアップロードを許可する場合、この URL からダウンロードできます。" -#: awx/sso/conf.py:966 +#: awx/sso/conf.py:964 msgid "SAML Service Provider Entity ID" msgstr "SAML サービスプロバイダーエンティティー ID" -#: awx/sso/conf.py:967 +#: awx/sso/conf.py:965 msgid "" "The application-defined unique identifier used as the audience of the SAML " "service provider (SP) configuration. This is usually the URL for Tower." -msgstr "" -"SAML サービスプロバイダー (SP) 設定の対象として使用されるアプリケーションで定義される固有識別子です。通常これは Tower の URL " -"になります。" +msgstr "SAML サービスプロバイダー (SP) 設定の対象として使用されるアプリケーションで定義される固有識別子です。通常これは Tower の URL になります。" -#: awx/sso/conf.py:982 +#: awx/sso/conf.py:979 msgid "SAML Service Provider Public Certificate" msgstr "SAML サービスプロバイダーの公開証明書" -#: awx/sso/conf.py:983 +#: awx/sso/conf.py:980 msgid "" -"Create a keypair for Tower to use as a service provider (SP) and include the" -" certificate content here." +"Create a keypair for Tower to use as a service provider (SP) and include the " +"certificate content here." msgstr "サービスプロバイダー (SP) として使用するための Tower のキーペアを作成し、ここに証明書の内容を組み込みます。" -#: awx/sso/conf.py:996 +#: awx/sso/conf.py:992 msgid "SAML Service Provider Private Key" msgstr "SAML サービスプロバイダーの秘密鍵|" -#: awx/sso/conf.py:997 +#: awx/sso/conf.py:993 msgid "" -"Create a keypair for Tower to use as a service provider (SP) and include the" -" private key content here." +"Create a keypair for Tower to use as a service provider (SP) and include the " +"private key content here." msgstr "サービスプロバイダー (SP) として使用するための Tower のキーペアを作成し、ここに秘密鍵の内容を組み込みます。" -#: awx/sso/conf.py:1009 +#: awx/sso/conf.py:1004 msgid "SAML Service Provider Organization Info" msgstr "SAML サービスプロバイダーの組織情報" -#: awx/sso/conf.py:1010 +#: awx/sso/conf.py:1005 msgid "" "Provide the URL, display name, and the name of your app. Refer to the " "Ansible Tower documentation for example syntax." -msgstr "" -"アプリケーションの URL、表示名、および名前を指定します。構文のサンプルについては Ansible Tower ドキュメントを参照してください。" +msgstr "アプリケーションの URL、表示名、および名前を指定します。構文のサンプルについては Ansible Tower ドキュメントを参照してください。" -#: awx/sso/conf.py:1029 +#: awx/sso/conf.py:1023 msgid "SAML Service Provider Technical Contact" msgstr "SAML サービスプロバイダーテクニカルサポートの問い合わせ先" -#: awx/sso/conf.py:1030 +#: awx/sso/conf.py:1024 msgid "" -"Provide the name and email address of the technical contact for your service" -" provider. Refer to the Ansible Tower documentation for example syntax." -msgstr "" -"サービスプロバイダーのテクニカルサポート担当の名前およびメールアドレスを指定します。構文のサンプルについては Ansible Tower " -"ドキュメントを参照してください。" +"Provide the name and email address of the technical contact for your service " +"provider. Refer to the Ansible Tower documentation for example syntax." +msgstr "サービスプロバイダーのテクニカルサポート担当の名前およびメールアドレスを指定します。構文のサンプルについては Ansible Tower ドキュメントを参照してください。" -#: awx/sso/conf.py:1047 +#: awx/sso/conf.py:1040 msgid "SAML Service Provider Support Contact" msgstr "SAML サービスプロバイダーサポートの問い合わせ先" -#: awx/sso/conf.py:1048 +#: awx/sso/conf.py:1041 msgid "" "Provide the name and email address of the support contact for your service " "provider. Refer to the Ansible Tower documentation for example syntax." -msgstr "" -"サービスプロバイダーのサポート担当の名前およびメールアドレスを指定します。構文のサンプルについては Ansible Tower " -"ドキュメントを参照してください。" +msgstr "サービスプロバイダーのサポート担当の名前およびメールアドレスを指定します。構文のサンプルについては Ansible Tower ドキュメントを参照してください。" -#: awx/sso/conf.py:1064 +#: awx/sso/conf.py:1056 msgid "SAML Enabled Identity Providers" msgstr "SAML で有効にされたアイデンティティープロバイダー" -#: awx/sso/conf.py:1065 +#: awx/sso/conf.py:1057 msgid "" "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." -msgstr "" -"使用中のそれぞれのアイデンティティープロバイダー (IdP) についてのエンティティー ID、SSO URL および証明書を設定します。複数の SAML" -" IdP がサポートされます。一部の IdP はデフォルト OID とは異なる属性名を使用してユーザーデータを提供することがあります。それぞれの IdP" -" について属性名が上書きされる可能性があります。追加の詳細および構文については、Ansible ドキュメントを参照してください。" +"names may be overridden for each IdP. Refer to the Ansible documentation for " +"additional details and syntax." +msgstr "使用中のそれぞれのアイデンティティープロバイダー (IdP) についてのエンティティー ID、SSO URL および証明書を設定します。複数の SAML IdP がサポートされます。一部の IdP はデフォルト OID とは異なる属性名を使用してユーザーデータを提供することがあります。それぞれの IdP について属性名が上書きされる可能性があります。追加の詳細および構文については、Ansible ドキュメントを参照してください。" -#: awx/sso/conf.py:1102 +#: awx/sso/conf.py:1093 msgid "SAML Security Config" msgstr "SAML セキュリティー設定" -#: awx/sso/conf.py:1103 +#: awx/sso/conf.py:1094 msgid "" "A dict of key value pairs that are passed to the underlying python-saml " "security setting https://github.com/onelogin/python-saml#settings" -msgstr "" -"基礎となる python-saml セキュリティー設定に渡されるキー値ペアの辞書: https://github.com/onelogin" -"/python-saml#settings" +msgstr "基礎となる python-saml セキュリティー設定に渡されるキー値ペアの辞書: https://github.com/onelogin/python-saml#settings" -#: awx/sso/conf.py:1135 +#: awx/sso/conf.py:1125 msgid "SAML Service Provider extra configuration data" msgstr "SAML サービスプロバイダーの追加設定データ" -#: awx/sso/conf.py:1136 +#: awx/sso/conf.py:1126 msgid "" -"A dict of key value pairs to be passed to the underlying python-saml Service" -" Provider configuration setting." +"A dict of key value pairs to be passed to the underlying python-saml Service " +"Provider configuration setting." msgstr "基礎となる python-saml サービスプロバイダー設定に渡されるキー値ペアの辞書。" -#: awx/sso/conf.py:1149 +#: awx/sso/conf.py:1138 msgid "SAML IDP to extra_data attribute mapping" msgstr "SAML IDP の extra_data 属性へのマッピング" -#: awx/sso/conf.py:1150 +#: awx/sso/conf.py:1139 msgid "" "A list of tuples that maps IDP attributes to extra_attributes. Each " "attribute will be a list of values, even if only 1 value." msgstr "IDP 属性を extra_attributes にマップするタプルの一覧です。各属性は 1 つの値のみの場合も値の一覧となります。" -#: awx/sso/conf.py:1167 +#: awx/sso/conf.py:1155 msgid "SAML Organization Map" msgstr "SAML 組織マップ" -#: awx/sso/conf.py:1180 +#: awx/sso/conf.py:1167 msgid "SAML Team Map" msgstr "SAML チームマップ" -#: awx/sso/conf.py:1193 +#: awx/sso/conf.py:1179 msgid "SAML Organization Attribute Mapping" msgstr "SAML 組織属性マッピング" -#: awx/sso/conf.py:1194 +#: awx/sso/conf.py:1180 msgid "Used to translate user organization membership into Tower." msgstr "ユーザー組織メンバーシップを Tower に変換するために使用されます。" -#: awx/sso/conf.py:1211 +#: awx/sso/conf.py:1198 msgid "SAML Team Attribute Mapping" msgstr "SAML チーム属性マッピング" -#: awx/sso/conf.py:1212 +#: awx/sso/conf.py:1199 msgid "Used to translate user team membership into Tower." msgstr "ユーザーチームメンバーシップを Tower に変換するために使用されます。" -#: awx/sso/fields.py:183 +#: awx/sso/fields.py:81 +msgid "Invalid field." +msgstr "無効なフィールドです。" + +#: awx/sso/fields.py:250 #, python-brace-format msgid "Invalid connection option(s): {invalid_options}." msgstr "無効な接続オプション: {invalid_options}" -#: awx/sso/fields.py:266 +#: awx/sso/fields.py:334 msgid "Base" msgstr "ベース" -#: awx/sso/fields.py:267 +#: awx/sso/fields.py:335 msgid "One Level" msgstr "1 レベル" -#: awx/sso/fields.py:268 +#: awx/sso/fields.py:336 msgid "Subtree" msgstr "サブツリー" -#: awx/sso/fields.py:286 +#: awx/sso/fields.py:354 #, python-brace-format msgid "Expected a list of three items but got {length} instead." -msgstr "3 つの項目の一覧が予期されましが、{length} が取得されました。" +msgstr "3 つの項目の一覧が必要でしたが、{length} を取得しました。" -#: awx/sso/fields.py:287 +#: awx/sso/fields.py:355 #, python-brace-format msgid "Expected an instance of LDAPSearch but got {input_type} instead." -msgstr "LDAPSearch のインスタンスが予期されましたが、{input_type} が取得されました。" +msgstr "LDAPSearch のインスタンスが必要でしたが、{input_type} を取得しました。" -#: awx/sso/fields.py:323 +#: awx/sso/fields.py:391 #, python-brace-format msgid "" "Expected an instance of LDAPSearch or LDAPSearchUnion but got {input_type} " "instead." -msgstr "" -"LDAPSearch または LDAPSearchUnion のインスタンスが予期されましたが、{input_type} が取得されました。" +msgstr "LDAPSearch または LDAPSearchUnion のインスタンスが必要でしたが、{input_type} を取得しました。" -#: awx/sso/fields.py:361 +#: awx/sso/fields.py:429 #, python-brace-format msgid "Invalid user attribute(s): {invalid_attrs}." msgstr "無効なユーザー属性: {invalid_attrs}" -#: awx/sso/fields.py:378 +#: awx/sso/fields.py:447 #, python-brace-format msgid "Expected an instance of LDAPGroupType but got {input_type} instead." -msgstr "LDAPGroupType のインスタンスが予期されましたが、{input_type} が取得されました。" +msgstr "LDAPGroupType のインスタンスが必要でしたが、{input_type} が取得されました。" -#: awx/sso/fields.py:418 awx/sso/fields.py:465 +#: awx/sso/fields.py:487 #, python-brace-format msgid "Invalid key(s): {invalid_keys}." msgstr "無効なキー: {invalid_keys}" -#: awx/sso/fields.py:443 +#: awx/sso/fields.py:513 #, python-brace-format msgid "Invalid user flag: \"{invalid_flag}\"." msgstr "無効なユーザーフラグ: \"{invalid_flag}\"" -#: awx/sso/fields.py:464 -#, python-brace-format -msgid "Missing key(s): {missing_keys}." -msgstr "キーがありません: {missing_keys}" - -#: awx/sso/fields.py:514 awx/sso/fields.py:631 -#, python-brace-format -msgid "Invalid key(s) for organization map: {invalid_keys}." -msgstr "組織マップの無効なキー: {invalid_keys}" - -#: awx/sso/fields.py:532 -#, python-brace-format -msgid "Missing required key for team map: {invalid_keys}." -msgstr "チームマップの必要なキーがありません: {invalid_keys}" - -#: awx/sso/fields.py:533 awx/sso/fields.py:650 -#, python-brace-format -msgid "Invalid key(s) for team map: {invalid_keys}." -msgstr "チームマップの無効なキー: {invalid_keys}" - -#: awx/sso/fields.py:649 -#, python-brace-format -msgid "Missing required key for team map: {missing_keys}." -msgstr "チームマップで必要なキーがありません: {missing_keys}" - #: awx/sso/fields.py:667 #, python-brace-format -msgid "Missing required key(s) for org info record: {missing_keys}." -msgstr "組織情報レコードで必要なキーがありません: {missing_keys}" - -#: awx/sso/fields.py:680 -#, python-brace-format msgid "Invalid language code(s) for org info: {invalid_lang_codes}." msgstr "組織情報の無効な言語コード: {invalid_lang_codes}" -#: awx/sso/fields.py:699 -#, python-brace-format -msgid "Missing required key(s) for contact: {missing_keys}." -msgstr "問い合わせ先の必要なキーがありません: {missing_keys}" - -#: awx/sso/fields.py:711 -#, python-brace-format -msgid "Missing required key(s) for IdP: {missing_keys}." -msgstr "IdP で必要なキーがありません: {missing_keys}" - -#: awx/sso/pipeline.py:31 +#: awx/sso/pipeline.py:28 #, python-brace-format msgid "An account cannot be found for {0}" msgstr "{0} のアカウントが見つかりません" -#: awx/sso/pipeline.py:37 +#: awx/sso/pipeline.py:34 msgid "Your account is inactive" msgstr "アカウントが非アクティブです" @@ -5468,36 +5832,8 @@ msgstr "Ansible Tower に戻る" msgid "Resize" msgstr "サイズの変更" -#: awx/templates/rest_framework/base.html:37 -msgid "navbar" -msgstr "ナビゲーションバー" - -#: awx/templates/rest_framework/base.html:75 -msgid "content" -msgstr "コンテンツ" - -#: awx/templates/rest_framework/base.html:78 -msgid "request form" -msgstr "要求フォーム" - -#: awx/templates/rest_framework/base.html:134 -msgid "Filters" -msgstr "フィルター" - -#: awx/templates/rest_framework/base.html:139 -msgid "main content" -msgstr "主な内容" - -#: awx/templates/rest_framework/base.html:155 -msgid "request info" -msgstr "要求情報" - -#: awx/templates/rest_framework/base.html:159 -msgid "response info" -msgstr "応答情報" - -#: awx/ui/apps.py:9 awx/ui/conf.py:22 awx/ui/conf.py:36 awx/ui/conf.py:51 -#: awx/ui/conf.py:63 awx/ui/conf.py:73 +#: awx/ui/apps.py:9 awx/ui/conf.py:22 awx/ui/conf.py:36 awx/ui/conf.py:50 +#: awx/ui/conf.py:61 awx/ui/conf.py:71 msgid "UI" msgstr "UI" @@ -5514,12 +5850,12 @@ msgid "Detailed" msgstr "詳細" #: awx/ui/conf.py:20 -msgid "Analytics Tracking State" -msgstr "アナリティクストラッキングの状態" +msgid "User Analytics Tracking State" +msgstr "ユーザーアナリティクストラッキングの状態" #: awx/ui/conf.py:21 -msgid "Enable or Disable Analytics Tracking." -msgstr "アナリティクストラッキングの有効化/無効化。" +msgid "Enable or Disable User Analytics Tracking." +msgstr "ユーザーアナリティクストラッキングの有効化/無効化。" #: awx/ui/conf.py:31 msgid "Custom Login Info" @@ -5528,52 +5864,48 @@ msgstr "カスタムログイン情報" #: awx/ui/conf.py:32 msgid "" "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, as custom HTML or other markup languages are " -"not supported." -msgstr "" -"必要な場合は、この設定を使ってログインモーダルのテキストボックスに特定の情報 (法律上の通知または免責事項など) " -"を追加できます。追加されるすべてのコンテンツは、カスタム HTML や他のマークアップ言語がサポートされないため、プレーンテキストでなければなりません。" +"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." +msgstr "必要に応じて、この設定を使用して、ログインモーダルのテキストボックスに特定の情報 (法律上の通知または免責事項など) を追加できます。他のマークアップ言語はサポートされていないため、追加するコンテンツはすべてプレーンテキストまたは HTML フラグメントでなければなりません。" -#: awx/ui/conf.py:46 +#: awx/ui/conf.py:45 msgid "Custom Logo" msgstr "カスタムロゴ" -#: awx/ui/conf.py:47 +#: awx/ui/conf.py:46 msgid "" -"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 " +"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." -msgstr "" -"カスタムロゴを設定するには、作成するファイルを指定します。カスタムロゴを最適化するには、背景が透明の「.png」ファイルを使用します。GIF、PNG " -"および JPEG 形式がサポートされます。" +msgstr "カスタムロゴを設定するには、作成するファイルを指定します。カスタムロゴを最適化するには、背景が透明の「.png」ファイルを使用します。GIF、PNG および JPEG 形式がサポートされます。" -#: awx/ui/conf.py:60 +#: awx/ui/conf.py:58 msgid "Max Job Events Retrieved by UI" msgstr "UI で検索される最大ジョブイベント" -#: awx/ui/conf.py:61 +#: awx/ui/conf.py:59 msgid "" "Maximum number of job events for the UI to retrieve within a single request." msgstr "単一要求内で検索される UI についての最大ジョブイベント数。" -#: awx/ui/conf.py:70 +#: awx/ui/conf.py:68 msgid "Enable Live Updates in the UI" msgstr "UI でのライブ更新の有効化" -#: awx/ui/conf.py:71 +#: awx/ui/conf.py:69 msgid "" "If disabled, the page will not refresh when events are received. Reloading " "the page will be required to get the latest details." msgstr "無効にされている場合、ページはイベントの受信時に更新されません。最新情報の詳細を取得するには、ページをリロードする必要があります。" -#: awx/ui/fields.py:29 +#: awx/ui/fields.py:30 msgid "" "Invalid format for custom logo. Must be a data URL with a base64-encoded " "GIF, PNG or JPEG image." -msgstr "" -"カスタムロゴの無効な形式です。base64 エンコードされた GIF、PNG または JPEG イメージと共にデータ URL を指定する必要があります。" +msgstr "カスタムロゴの無効な形式です。base64 エンコードされた GIF、PNG または JPEG イメージと共にデータ URL を指定する必要があります。" -#: awx/ui/fields.py:30 +#: awx/ui/fields.py:31 msgid "Invalid base64-encoded data in data URL." msgstr "データ URL の無効な base64 エンコードされたデータ。" + diff --git a/awx/locale/nl/LC_MESSAGES/django.po b/awx/locale/nl/LC_MESSAGES/django.po index c02d8dfb29c3..7bc60ae7f6d3 100644 --- a/awx/locale/nl/LC_MESSAGES/django.po +++ b/awx/locale/nl/LC_MESSAGES/django.po @@ -1,22 +1,21 @@ -# helena , 2017. #zanata -# helena01 , 2017. #zanata -# helena02 , 2017. #zanata -# helena , 2018. #zanata -# shanemcd , 2018. #zanata +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-08-03 19:04+0000\n" -"PO-Revision-Date: 2018-08-17 09:46+0000\n" -"Last-Translator: helena \n" -"Language-Team: Dutch\n" +"POT-Creation-Date: 2020-05-28 21:45+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: nl \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: nl\n" -"Plural-Forms: nplurals=2; plural=(n != 1)\n" -"X-Generator: Zanata 4.6.0\n" #: awx/api/conf.py:15 msgid "Idle Time Force Log Out" @@ -26,13 +25,11 @@ msgstr "Niet-actieve tijd voor forceren van afmelding" msgid "" "Number of seconds that a user is inactive before they will need to login " "again." -msgstr "" -"Maximumaantal seconden dat een gebruiker niet-actief is voordat deze zich " -"opnieuw moet aanmelden." +msgstr "Maximumaantal seconden dat een gebruiker niet-actief is voordat deze zich opnieuw moet aanmelden." -#: awx/api/conf.py:17 awx/api/conf.py:26 awx/api/conf.py:34 awx/api/conf.py:47 -#: awx/api/conf.py:59 awx/sso/conf.py:85 awx/sso/conf.py:96 -#: awx/sso/conf.py:108 awx/sso/conf.py:123 +#: awx/api/conf.py:17 awx/api/conf.py:26 awx/api/conf.py:34 awx/api/conf.py:50 +#: awx/api/conf.py:62 awx/api/conf.py:74 awx/sso/conf.py:97 awx/sso/conf.py:108 +#: awx/sso/conf.py:120 awx/sso/conf.py:135 msgid "Authentication" msgstr "Authenticatie" @@ -44,9 +41,7 @@ msgstr "Maximumaantal gelijktijdige aangemelde sessies" msgid "" "Maximum number of simultaneous logged in sessions a user may have. To " "disable enter -1." -msgstr "" -"Maximumaantal gelijktijdige aangemelde sessies dat een gebruiker kan hebben." -" Voer -1 in om dit uit te schakelen." +msgstr "Maximumaantal gelijktijdige aangemelde sessies dat een gebruiker kan hebben. Voer -1 in om dit uit te schakelen." #: awx/api/conf.py:32 msgid "Enable HTTP Basic Auth" @@ -56,37 +51,41 @@ msgstr "HTTP-basisauthenticatie inschakelen" msgid "Enable HTTP Basic Auth for the API Browser." msgstr "Schakel HTTP-basisauthenticatie voor de API-browser in." -#: awx/api/conf.py:42 +#: awx/api/conf.py:43 msgid "OAuth 2 Timeout Settings" msgstr "Instellingen OAuth 2-time-out" -#: awx/api/conf.py:43 +#: awx/api/conf.py:44 msgid "" "Dictionary for customizing OAuth 2 timeouts, available items are " "`ACCESS_TOKEN_EXPIRE_SECONDS`, the duration of access tokens in the number " -"of seconds, and `AUTHORIZATION_CODE_EXPIRE_SECONDS`, the duration of " -"authorization grants in the number of seconds." -msgstr "" -"Woordenlijst voor het aanpassen van OAuth 2-time-outs. Beschikbare items " -"zijn 'ACCESS_TOKEN_EXPIRE_SECONDS', de tijdsduur van toegangstokens in het " -"aantal seconden, en 'AUTHORIZATION_CODE_EXPIRE_SECONDS', de tijdsduur van de" -" toekenning van machtigingen in het aantal seconden." +"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." +msgstr "Termenlijst voor het aanpassen van OAuth 2-time-outs. Beschikbare items zijn `ACCESS_TOKEN_EXPIRE_SECONDS`, de duur van de toegangstokens in het aantal seconden, `AUTHORIZATION_CODE_EXPIRE_SECONDS`, de duur van de autorisatiecodes in het aantal seconden. en `REFRESH_TOKEN_EXPIRE_SECONDS`, de duur van de verversingstokens na verlopen toegangstokens, in het aantal seconden." -#: awx/api/conf.py:54 +#: awx/api/conf.py:57 msgid "Allow External Users to Create OAuth2 Tokens" msgstr "Externe gebruikers in staat stellen OAuth 2-tokens aan te maken" -#: awx/api/conf.py:55 +#: awx/api/conf.py:58 msgid "" "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." -msgstr "" -"Om beveiligingsredenen mogen gebruikers van externe verificatieproviders " -"(LDAP, SAML, SSO, Radiusen anderen) geen OAuth2-tokens aanmaken. Pas deze " -"instelling aan om dit gedrag te wijzigen. Bestaande tokens worden niet " -"verwijderd wanneer deze instelling wordt uitgeschakeld." +"behavior, enable this setting. Existing tokens will not be deleted when this " +"setting is toggled off." +msgstr "Om beveiligingsredenen mogen gebruikers van externe verificatieproviders (LDAP, SAML, SSO, Radius en anderen) geen OAuth2-tokens aanmaken. Pas deze instelling aan om dit gedrag te wijzigen. Bestaande tokens worden niet verwijderd wanneer deze instelling wordt uitgeschakeld." + +#: awx/api/conf.py:71 +msgid "Login redirect override URL" +msgstr "Login doorverwijzen URL overschrijven" + +#: awx/api/conf.py:72 +msgid "" +"URL to which unauthorized users will be redirected to log in. If blank, " +"users will be sent to the Tower login page." +msgstr "URL waarnaar onbevoegde gebruikers worden doorverwezen om in te loggen. Indien deze leeg is, worden de gebruikers naar de Tower-loginpagina gestuurd." #: awx/api/exceptions.py:20 msgid "Resource is being used by running jobs." @@ -97,24 +96,24 @@ msgstr "Bron wordt gebruikt om taken uit te voeren." msgid "Invalid key names: {invalid_key_names}" msgstr "Ongeldige sleutelnamen: {invalid_key_names}" -#: awx/api/fields.py:107 +#: awx/api/fields.py:111 msgid "Credential {} does not exist" msgstr "Toegangsgegeven {} bestaat niet" -#: awx/api/filters.py:97 +#: awx/api/filters.py:82 msgid "No related model for field {}." msgstr "Geen verwant model voor veld {}." -#: awx/api/filters.py:114 +#: awx/api/filters.py:99 msgid "Filtering on password fields is not allowed." msgstr "Filteren op wachtwoordvelden is niet toegestaan." -#: awx/api/filters.py:126 awx/api/filters.py:128 +#: awx/api/filters.py:111 awx/api/filters.py:113 #, python-format msgid "Filtering on %s is not allowed." msgstr "Filteren op %s is niet toegestaan." -#: awx/api/filters.py:131 +#: awx/api/filters.py:116 msgid "Loops not allowed in filters, detected on field {}." msgstr "Lussen zijn niet toegestaan in filters, gedetecteerd in veld {}." @@ -122,69 +121,70 @@ msgstr "Lussen zijn niet toegestaan in filters, gedetecteerd in veld {}." msgid "Query string field name not provided." msgstr "Veldnaam voor queryreeks niet opgegeven." -#: awx/api/filters.py:187 +#: awx/api/filters.py:192 #, python-brace-format msgid "Invalid {field_name} id: {field_id}" -msgstr "Ongeldig {field_name} id: {field_id}" +msgstr "Ongeldige {field_name} id: {field_id}" -#: awx/api/filters.py:326 -#, python-format -msgid "cannot filter on kind %s" -msgstr "kan niet filteren op soort %s" +#: awx/api/filters.py:333 +msgid "" +"Cannot apply role_level filter to this list because its model does not use " +"roles for access control." +msgstr "Er kan geen filter op rolniveau toegepast worden op deze lijst, want het bijbehorende model gebruikt geen rollen voor de toegangscontrole." -#: awx/api/generics.py:197 +#: awx/api/generics.py:182 msgid "" "You did not use correct Content-Type in your HTTP request. If you are using " "our REST API, the Content-Type must be application/json" -msgstr "" -"U hebt geen juist inhoudstype gebruikt in uw HTTP-verzoek. Als u onze REST " -"API gebruikt, moet het inhoudstype toepassing/json zijn" +msgstr "U hebt geen juist inhoudstype gebruikt in uw HTTP-verzoek. Als u onze REST API gebruikt, moet het inhoudstype toepassing/json zijn" -#: awx/api/generics.py:635 awx/api/generics.py:697 +#: awx/api/generics.py:623 awx/api/generics.py:685 msgid "\"id\" field must be an integer." msgstr "'Id'-veld moet een geheel getal zijn." -#: awx/api/generics.py:694 +#: awx/api/generics.py:682 msgid "\"id\" is required to disassociate" msgstr "'id' is vereist om los te koppelen" -#: awx/api/generics.py:745 +#: awx/api/generics.py:733 msgid "{} 'id' field is missing." msgstr "{} 'id'-veld ontbreekt." -#: awx/api/metadata.py:51 +#: awx/api/metadata.py:58 msgid "Database ID for this {}." msgstr "Database-id voor dit {}." -#: awx/api/metadata.py:52 +#: awx/api/metadata.py:59 msgid "Name of this {}." msgstr "Naam van dit {}." -#: awx/api/metadata.py:53 +#: awx/api/metadata.py:60 msgid "Optional description of this {}." msgstr "Optionele beschrijving van dit {}." -#: awx/api/metadata.py:54 +#: awx/api/metadata.py:61 msgid "Data type for this {}." msgstr "Gegevenstype voor dit {}." -#: awx/api/metadata.py:55 +#: awx/api/metadata.py:62 msgid "URL for this {}." msgstr "URL voor dit {}." -#: awx/api/metadata.py:56 +#: awx/api/metadata.py:63 msgid "Data structure with URLs of related resources." msgstr "Gegevensstructuur met URL's van verwante bronnen." -#: awx/api/metadata.py:57 -msgid "Data structure with name/description for related resources." -msgstr "Gegevensstructuur met naam/beschrijving voor verwante bronnen." +#: awx/api/metadata.py:64 +msgid "" +"Data structure with name/description for related resources. The output for " +"some objects may be limited for performance reasons." +msgstr "Gegevensstructuur met naam/omschrijving voor gerelateerde bronnen. De uitvoer van sommige objecten kan omwille van prestatievermogen beperkt zijn." -#: awx/api/metadata.py:58 +#: awx/api/metadata.py:66 msgid "Timestamp when this {} was created." msgstr "Tijdstempel toen dit {} werd gemaakt." -#: awx/api/metadata.py:59 +#: awx/api/metadata.py:67 msgid "Timestamp when this {} was last modified." msgstr "Tijdstempel van de laatste wijziging van dit {}." @@ -197,1300 +197,1310 @@ msgstr "JSON-parseerfout - is geen JSON-object" msgid "" "JSON parse error - %s\n" "Possible cause: trailing comma." -msgstr "" -"JSON-parseerfout - %s\n" +msgstr "JSON-parseerfout - %s\n" "Mogelijke oorzaak: navolgende komma." -#: awx/api/serializers.py:155 +#: awx/api/serializers.py:169 msgid "" -"The original object is already named {}, a copy from it cannot have the same" -" name." -msgstr "" -"Het oorspronkelijke object heet al {}, een kopie hiervan kan niet dezelfde " -"naam hebben." +"The original object is already named {}, a copy from it cannot have the same " +"name." +msgstr "Het oorspronkelijke object heet al {}, een kopie hiervan kan niet dezelfde naam hebben." -#: awx/api/serializers.py:290 +#: awx/api/serializers.py:302 #, python-format msgid "Cannot use dictionary for %s" msgstr "Kan woordenlijst niet gebruiken voor %s" -#: awx/api/serializers.py:307 +#: awx/api/serializers.py:316 msgid "Playbook Run" msgstr "Draaiboek uitvoering" -#: awx/api/serializers.py:308 +#: awx/api/serializers.py:317 msgid "Command" msgstr "Opdracht" -#: awx/api/serializers.py:309 awx/main/models/unified_jobs.py:526 +#: awx/api/serializers.py:318 awx/main/models/unified_jobs.py:547 msgid "SCM Update" msgstr "SCM-update" -#: awx/api/serializers.py:310 +#: awx/api/serializers.py:319 msgid "Inventory Sync" msgstr "Inventarissynchronisatie" -#: awx/api/serializers.py:311 +#: awx/api/serializers.py:320 msgid "Management Job" msgstr "Beheertaak" -#: awx/api/serializers.py:312 +#: awx/api/serializers.py:321 msgid "Workflow Job" msgstr "Workflowtaak" -#: awx/api/serializers.py:313 +#: awx/api/serializers.py:322 msgid "Workflow Template" msgstr "Workflowsjabloon" -#: awx/api/serializers.py:314 +#: awx/api/serializers.py:323 msgid "Job Template" msgstr "Taaksjabloon" -#: awx/api/serializers.py:714 +#: awx/api/serializers.py:709 msgid "" "Indicates whether all of the events generated by this unified job have been " "saved to the database." -msgstr "" -"Geeft aan of alle evenementen die aangemaakt zijn door deze " -"gemeenschappelijke taak opgeslagen zijn in de database." +msgstr "Geeft aan of alle evenementen die aangemaakt zijn door deze gemeenschappelijke taak opgeslagen zijn in de database." -#: awx/api/serializers.py:879 +#: awx/api/serializers.py:878 msgid "Write-only field used to change the password." msgstr "Een alleen-schrijven-veld is gebruikt om het wachtwoord te wijzigen." -#: awx/api/serializers.py:881 +#: awx/api/serializers.py:880 msgid "Set if the account is managed by an external service" msgstr "Instellen als de account wordt beheerd door een externe service" -#: awx/api/serializers.py:905 +#: awx/api/serializers.py:907 msgid "Password required for new User." msgstr "Wachtwoord vereist voor een nieuwe gebruiker." -#: awx/api/serializers.py:981 +#: awx/api/serializers.py:992 #, python-format msgid "Unable to change %s on user managed by LDAP." msgstr "Kan %s niet wijzigen voor gebruiker die wordt beheerd met LDAP." -#: awx/api/serializers.py:1067 +#: awx/api/serializers.py:1088 msgid "Must be a simple space-separated string with allowed scopes {}." -msgstr "" -"Moet een reeks zijn die gescheiden is met enkele spaties en die toegestane " -"bereiken heeft {}." +msgstr "Moet een reeks zijn die gescheiden is met enkele spaties en die toegestane bereiken heeft {}." -#: awx/api/serializers.py:1167 +#: awx/api/serializers.py:1186 msgid "Authorization Grant Type" msgstr "Soort toekenning van machtiging" -#: awx/api/serializers.py:1169 awx/main/models/credential/__init__.py:1064 +#: awx/api/serializers.py:1188 awx/main/credential_plugins/azure_kv.py:30 +#: awx/main/models/credential/__init__.py:960 msgid "Client Secret" msgstr "Klant-geheim" -#: awx/api/serializers.py:1172 +#: awx/api/serializers.py:1191 msgid "Client Type" msgstr "Soort klant" -#: awx/api/serializers.py:1175 +#: awx/api/serializers.py:1194 msgid "Redirect URIs" msgstr "URI's doorverwijzen" -#: awx/api/serializers.py:1178 +#: awx/api/serializers.py:1197 msgid "Skip Authorization" msgstr "Autorisatie overslaan" -#: awx/api/serializers.py:1290 +#: awx/api/serializers.py:1303 +msgid "Cannot change max_hosts." +msgstr "Kan max_hosts niet wijzigen." + +#: awx/api/serializers.py:1336 msgid "This path is already being used by another manual project." msgstr "Dit pad wordt al gebruikt door een ander handmatig project." -#: awx/api/serializers.py:1316 -msgid "This field has been deprecated and will be removed in a future release" -msgstr "" -"Dit veld is afgeschaft en zal worden verwijderd in een toekomstige versie" +#: awx/api/serializers.py:1338 +msgid "SCM refspec can only be used with git projects." +msgstr "SCM-refspec kan alleen worden gebruikt bij git-projecten." -#: awx/api/serializers.py:1375 -msgid "Organization is missing" -msgstr "Organisatie ontbreekt" +#: awx/api/serializers.py:1415 +msgid "" +"One or more job templates depend on branch override behavior for this " +"project (ids: {})." +msgstr "Eén of meer taaksjablonen zijn bij dit project afhankelijk van het overschrijdingsgedrag van de vertakking (id's: {})." -#: awx/api/serializers.py:1379 +#: awx/api/serializers.py:1422 msgid "Update options must be set to false for manual projects." -msgstr "" -"De update-opties moeten voor handmatige projecten worden ingesteld op " -"onwaar." +msgstr "De update-opties moeten voor handmatige projecten worden ingesteld op onwaar." -#: awx/api/serializers.py:1385 +#: awx/api/serializers.py:1428 msgid "Array of playbooks available within this project." msgstr "Er is binnen dit project een draaiboekenmatrix beschikbaar." -#: awx/api/serializers.py:1404 +#: awx/api/serializers.py:1447 msgid "" "Array of inventory files and directories available within this project, not " "comprehensive." -msgstr "" -"Er is binnen dit project een niet-volledige matrix met " -"inventarisatiebestanden en -mappen beschikbaar." +msgstr "Er is binnen dit project een niet-volledige matrix met inventarisatiebestanden en -mappen beschikbaar." -#: awx/api/serializers.py:1452 awx/api/serializers.py:3247 -#: awx/api/serializers.py:3454 +#: awx/api/serializers.py:1495 awx/api/serializers.py:3048 +#: awx/api/serializers.py:3260 msgid "A count of hosts uniquely assigned to each status." -msgstr "" -"Een telling van de unieke hosts die toegewezen zijn aan iedere status." +msgstr "Een telling van de unieke hosts die toegewezen zijn aan iedere status." -#: awx/api/serializers.py:1455 awx/api/serializers.py:3250 +#: awx/api/serializers.py:1498 awx/api/serializers.py:3051 msgid "A count of all plays and tasks for the job run." -msgstr "" -"Een telling van alle draaiboekuitvoeringen en taken voor het uitvoeren van " -"de taak." +msgstr "Een telling van alle draaiboekuitvoeringen en taken voor het uitvoeren van de taak." -#: awx/api/serializers.py:1570 +#: awx/api/serializers.py:1625 msgid "Smart inventories must specify host_filter" msgstr "Smart-inventaris moet hostfilter specificeren" -#: awx/api/serializers.py:1674 +#: awx/api/serializers.py:1713 #, python-format msgid "Invalid port specification: %s" msgstr "Ongeldige poortspecificatie: %s" -#: awx/api/serializers.py:1685 +#: awx/api/serializers.py:1724 msgid "Cannot create Host for Smart Inventory" msgstr "Kan geen host aanmaken voor Smart-inventaris" -#: awx/api/serializers.py:1797 +#: awx/api/serializers.py:1808 msgid "Invalid group name." msgstr "Ongeldige groepsnaam." -#: awx/api/serializers.py:1802 +#: awx/api/serializers.py:1813 msgid "Cannot create Group for Smart Inventory" msgstr "Kan geen groep aanmaken voor Smart-inventaris" -#: awx/api/serializers.py:1877 +#: awx/api/serializers.py:1888 msgid "" "Script must begin with a hashbang sequence: i.e.... #!/usr/bin/env python" -msgstr "" -"Script moet beginnen met een hashbang-reeks, bijvoorbeeld ... #!/usr/bin/env" -" python" +msgstr "Script moet beginnen met een hashbang-reeks, bijvoorbeeld ... #!/usr/bin/env python" + +#: awx/api/serializers.py:1917 +msgid "Cloud credential to use for inventory updates." +msgstr "Cloudtoegangsgegevens te gebruiken voor inventarisupdates." -#: awx/api/serializers.py:1926 +#: awx/api/serializers.py:1938 msgid "`{}` is a prohibited environment variable" msgstr "`{}` is niet toegestaan als omgevingsvariabele" -#: awx/api/serializers.py:1937 +#: awx/api/serializers.py:1949 msgid "If 'source' is 'custom', 'source_script' must be provided." msgstr "Als 'bron' 'aangepast' is, moet 'source_script' worden geleverd." -#: awx/api/serializers.py:1943 +#: awx/api/serializers.py:1955 msgid "Must provide an inventory." msgstr "Moet een inventaris verschaffen." -#: awx/api/serializers.py:1947 +#: awx/api/serializers.py:1959 msgid "" "The 'source_script' does not belong to the same organization as the " "inventory." -msgstr "" -"Het 'source_script' behoort niet tot dezelfde categorie als de inventaris." +msgstr "Het 'source_script' behoort niet tot dezelfde categorie als de inventaris." -#: awx/api/serializers.py:1949 +#: awx/api/serializers.py:1961 msgid "'source_script' doesn't exist." msgstr "'source_script' bestaat niet." -#: awx/api/serializers.py:1985 -msgid "Automatic group relationship, will be removed in 3.3" -msgstr "Automatische groepsrelatie, wordt verwijderd in 3.3" - -#: awx/api/serializers.py:2072 +#: awx/api/serializers.py:2063 msgid "Cannot use manual project for SCM-based inventory." -msgstr "" -"Kan geen handmatig project gebruiken voor een SCM-gebaseerde inventaris." - -#: awx/api/serializers.py:2078 -msgid "" -"Manual inventory sources are created automatically when a group is created " -"in the v1 API." -msgstr "" -"Handmatige inventarisbronnen worden automatisch gemaakt wanneer er een groep" -" wordt gemaakt in de v1 API." +msgstr "Kan geen handmatig project gebruiken voor een SCM-gebaseerde inventaris." -#: awx/api/serializers.py:2083 +#: awx/api/serializers.py:2068 msgid "Setting not compatible with existing schedules." msgstr "Instelling is niet compatibel met bestaande schema's." -#: awx/api/serializers.py:2088 +#: awx/api/serializers.py:2073 msgid "Cannot create Inventory Source for Smart Inventory" msgstr "Kan geen inventarisbron aanmaken voor Smart-inventaris" -#: awx/api/serializers.py:2139 +#: awx/api/serializers.py:2121 +msgid "Project required for scm type sources." +msgstr "Project vereist voor bronnen van het type scm." + +#: awx/api/serializers.py:2130 #, python-format msgid "Cannot set %s if not SCM type." msgstr "Kan %s niet instellen als het geen SCM-type is." -#: awx/api/serializers.py:2414 +#: awx/api/serializers.py:2200 +msgid "The project used for this job." +msgstr "Het project dat voor deze taak wordt gebruikt." + +#: awx/api/serializers.py:2455 msgid "Modifications not allowed for managed credential types" msgstr "Wijzigingen zijn niet toegestaan voor beheerde referentietypen" -#: awx/api/serializers.py:2419 +#: awx/api/serializers.py:2467 msgid "" "Modifications to inputs are not allowed for credential types that are in use" -msgstr "" -"Wijzigingen in inputs zijn niet toegestaan voor referentietypen die in " -"gebruik zijn" +msgstr "Wijzigingen in inputs zijn niet toegestaan voor referentietypen die in gebruik zijn" -#: awx/api/serializers.py:2425 +#: awx/api/serializers.py:2472 #, python-format msgid "Must be 'cloud' or 'net', not %s" msgstr "Moet 'cloud' of 'net' zijn, niet %s" -#: awx/api/serializers.py:2431 +#: awx/api/serializers.py:2478 msgid "'ask_at_runtime' is not supported for custom credentials." msgstr "'ask_at_runtime' wordt niet ondersteund voor aangepaste referenties." -#: awx/api/serializers.py:2502 +#: awx/api/serializers.py:2526 msgid "Credential Type" msgstr "Soort toegangsgegevens" -#: awx/api/serializers.py:2617 -#, python-format -msgid "\"%s\" is not a valid choice" -msgstr "\"%s\" is geen geldige keuze" - -#: awx/api/serializers.py:2636 -#, python-brace-format -msgid "'{field_name}' is not a valid field for {credential_type_name}" -msgstr "'{field_name}' is geen geldig veld voor {credential_type_name}" - -#: awx/api/serializers.py:2657 +#: awx/api/serializers.py:2607 msgid "" -"You cannot change the credential type of the credential, as it may break the" -" functionality of the resources using it." -msgstr "" -"U kunt het soort toegangsgegevens niet wijzigen, omdat dan de bronnen die " -"deze gebruiken niet langer werken." +"You cannot change the credential type of the credential, as it may break the " +"functionality of the resources using it." +msgstr "U kunt het soort toegangsgegevens niet wijzigen, omdat dan de bronnen die deze gebruiken niet langer werken." -#: awx/api/serializers.py:2669 +#: awx/api/serializers.py:2619 msgid "" "Write-only field used to add user to owner role. If provided, do not give " "either team or organization. Only valid for creation." -msgstr "" -"Er is een alleen-schrijven-veld gebruikt om een gebruiker toe te voegen aan " -"de eigenaarrol. Indien verschaft, geef geen team of organisatie op. Alleen " -"geldig voor maken." +msgstr "Er is een alleen-schrijven-veld gebruikt om een gebruiker toe te voegen aan de eigenaarrol. Indien verschaft, geef geen team of organisatie op. Alleen geldig voor maken." -#: awx/api/serializers.py:2674 +#: awx/api/serializers.py:2624 msgid "" "Write-only field used to add team to owner role. If provided, do not give " "either user or organization. Only valid for creation." -msgstr "" -"Er is een alleen-schrijven-veld gebruikt om een team aan de eigenaarrol toe " -"te voegen. Indien verschaft, geef geen gebruiker of organisatie op. Alleen " -"geldig voor maken." +msgstr "Er is een alleen-schrijven-veld gebruikt om een team aan de eigenaarrol toe te voegen. Indien verschaft, geef geen gebruiker of organisatie op. Alleen geldig voor maken." -#: awx/api/serializers.py:2679 +#: awx/api/serializers.py:2629 msgid "" -"Inherit permissions from organization roles. If provided on creation, do not" -" give either user or team." -msgstr "" -"Neem machtigingen over van organisatierollen. Indien verschaft bij maken, " -"geef geen gebruiker of team op." +"Inherit permissions from organization roles. If provided on creation, do not " +"give either user or team." +msgstr "Neem machtigingen over van organisatierollen. Indien verschaft bij maken, geef geen gebruiker of team op." -#: awx/api/serializers.py:2695 +#: awx/api/serializers.py:2645 msgid "Missing 'user', 'team', or 'organization'." msgstr "'gebruiker', 'team' of 'organisatie' ontbreekt." -#: awx/api/serializers.py:2735 +#: awx/api/serializers.py:2662 msgid "" "Credential organization must be set and match before assigning to a team" -msgstr "" -"Referentieorganisatie moet worden ingesteld en moet overeenkomen vóór " -"toewijzing aan een team" - -#: awx/api/serializers.py:2936 -msgid "You must provide a cloud credential." -msgstr "U moet een cloudreferentie opgeven." - -#: awx/api/serializers.py:2937 -msgid "You must provide a network credential." -msgstr "U moet een netwerkreferentie opgeven." +msgstr "Referentieorganisatie moet worden ingesteld en moet overeenkomen vóór toewijzing aan een team" -#: awx/api/serializers.py:2938 awx/main/models/jobs.py:155 -msgid "You must provide an SSH credential." -msgstr "U moet een SSH-referentie opgeven." - -#: awx/api/serializers.py:2939 -msgid "You must provide a vault credential." -msgstr "U dient een kluistoegangsgegeven op te geven." - -#: awx/api/serializers.py:2958 +#: awx/api/serializers.py:2793 msgid "This field is required." msgstr "Dit veld is vereist." -#: awx/api/serializers.py:2960 awx/api/serializers.py:2962 +#: awx/api/serializers.py:2802 msgid "Playbook not found for project." msgstr "Draaiboek is niet gevonden voor project." -#: awx/api/serializers.py:2964 +#: awx/api/serializers.py:2804 msgid "Must select playbook for project." msgstr "Moet een draaiboek selecteren voor het project." -#: awx/api/serializers.py:3045 +#: awx/api/serializers.py:2806 awx/api/serializers.py:2808 +msgid "Project does not allow overriding branch." +msgstr "Project laat geen vervangende vertakking toe." + +#: awx/api/serializers.py:2845 +msgid "Must be a Personal Access Token." +msgstr "Moet een persoonlijke toegangstoken zijn." + +#: awx/api/serializers.py:2848 +msgid "Must match the selected webhook service." +msgstr "Moet overeenkomen met de geselecteerde webhookservice." + +#: awx/api/serializers.py:2919 msgid "Cannot enable provisioning callback without an inventory set." -msgstr "" -"Kan provisioning-terugkoppeling niet inschakelen zonder ingesteld " -"inventaris." +msgstr "Kan provisioning-terugkoppeling niet inschakelen zonder ingesteld inventaris." -#: awx/api/serializers.py:3048 +#: awx/api/serializers.py:2922 msgid "Must either set a default value or ask to prompt on launch." -msgstr "" -"Moet een standaardwaarde instellen of hierom laten vragen bij het opstarten." - -#: awx/api/serializers.py:3050 awx/main/models/jobs.py:310 -msgid "Job types 'run' and 'check' must have assigned a project." -msgstr "" -"Aan de taaktypen 'uitvoeren' en 'controleren' moet een project zijn " -"toegewezen." +msgstr "Moet een standaardwaarde instellen of hierom laten vragen bij het opstarten." -#: awx/api/serializers.py:3169 -msgid "Invalid job template." -msgstr "Ongeldige taaksjabloon." +#: awx/api/serializers.py:2924 awx/main/models/jobs.py:299 +msgid "Job Templates must have a project assigned." +msgstr "Er moet een project zijn toegewezen aan taaksjablonen." -#: awx/api/serializers.py:3290 +#: awx/api/serializers.py:3092 msgid "No change to job limit" msgstr "Geen wijzigingen in de taaklimiet" -#: awx/api/serializers.py:3291 +#: awx/api/serializers.py:3093 msgid "All failed and unreachable hosts" msgstr "Alle mislukte en onbereikbare hosts" -#: awx/api/serializers.py:3306 +#: awx/api/serializers.py:3108 msgid "Missing passwords needed to start: {}" msgstr "Ontbrekende wachtwoorden die nodig zijn om op te starten: {}" -#: awx/api/serializers.py:3325 +#: awx/api/serializers.py:3127 msgid "Relaunch by host status not available until job finishes running." -msgstr "" -"Opnieuw opstarten met hoststatus niet beschikbaar tot taak volledig " -"uitgevoerd is." +msgstr "Opnieuw opstarten met hoststatus niet beschikbaar tot taak volledig uitgevoerd is." -#: awx/api/serializers.py:3339 +#: awx/api/serializers.py:3141 msgid "Job Template Project is missing or undefined." msgstr "Het taaksjabloonproject ontbreekt of is niet gedefinieerd." -#: awx/api/serializers.py:3341 +#: awx/api/serializers.py:3143 msgid "Job Template Inventory is missing or undefined." msgstr "De taaksjablooninventaris ontbreekt of is niet gedefinieerd." -#: awx/api/serializers.py:3379 -msgid "" -"Unknown, job may have been ran before launch configurations were saved." -msgstr "" -"Onbekend, taak is mogelijk al uitgevoerd voordat opstartinstellingen " -"opgeslagen waren." +#: awx/api/serializers.py:3181 +msgid "Unknown, job may have been ran before launch configurations were saved." +msgstr "Onbekend, taak is mogelijk al uitgevoerd voordat opstartinstellingen opgeslagen waren." -#: awx/api/serializers.py:3446 awx/main/tasks.py:2297 +#: awx/api/serializers.py:3252 awx/main/tasks.py:2800 awx/main/tasks.py:2818 msgid "{} are prohibited from use in ad hoc commands." msgstr "{} kunnen niet worden gebruikt in ad-hocopdrachten." -#: awx/api/serializers.py:3534 awx/api/views.py:4893 +#: awx/api/serializers.py:3340 awx/api/views/__init__.py:4243 #, python-brace-format msgid "" "Standard Output too large to display ({text_size} bytes), only download " "supported for sizes over {supported_size} bytes." -msgstr "" -"De standaardoutput is te groot om weer te geven ({text_size} bytes), " -"download wordt alleen ondersteund voor groottes van meer dan " -"{supported_size} bytes." +msgstr "De standaardoutput is te groot om weer te geven ({text_size} bytes), download wordt alleen ondersteund voor groottes van meer dan {supported_size} bytes." -#: awx/api/serializers.py:3727 +#: awx/api/serializers.py:3653 msgid "Provided variable {} has no database value to replace with." msgstr "Opgegeven variabele {} heeft geen databasewaarde om mee te vervangen." -#: awx/api/serializers.py:3745 -#, python-brace-format -msgid "\"$encrypted$ is a reserved keyword, may not be used for {var_name}.\"" -msgstr "" -"'$encrypted$ is een gereserveerd sleutelwoord en mag niet gebruikt worden " -"voor {var_name}.'" +#: awx/api/serializers.py:3671 +msgid "\"$encrypted$ is a reserved keyword, may not be used for {}.\"" +msgstr "'$encrypted$ is een gereserveerd sleutelwoord en mag niet gebruikt worden voor {}.'" -#: awx/api/serializers.py:3815 -#, python-format -msgid "Cannot nest a %s inside a WorkflowJobTemplate" -msgstr "Kan geen a %s nesten in een WorkflowJobTemplate" +#: awx/api/serializers.py:4078 +msgid "A project is required to run a job." +msgstr "Een project is nodig om een taak kunnen uitvoeren." -#: awx/api/serializers.py:3822 awx/api/views.py:818 -msgid "Related template is not configured to accept credentials on launch." -msgstr "" -"Verwante sjabloon is niet ingesteld om toegangsgegevens bij opstarten te " -"accepteren." +#: awx/api/serializers.py:4080 +msgid "Missing a revision to run due to failed project update." +msgstr "Een revisie om uit te voeren ontbreekt vanwege een projectupdate." -#: awx/api/serializers.py:4282 +#: awx/api/serializers.py:4084 msgid "The inventory associated with this Job Template is being deleted." msgstr "De aan deze taaksjabloon gekoppelde inventaris wordt verwijderd." -#: awx/api/serializers.py:4284 +#: awx/api/serializers.py:4086 awx/api/serializers.py:4202 msgid "The provided inventory is being deleted." msgstr "Opgegeven inventaris wordt verwijderd." -#: awx/api/serializers.py:4292 +#: awx/api/serializers.py:4094 msgid "Cannot assign multiple {} credentials." msgstr "Kan niet meerdere toegangsgegevens voor {} toewijzen." -#: awx/api/serializers.py:4296 +#: awx/api/serializers.py:4098 msgid "Cannot assign a Credential of kind `{}`" msgstr "Kan geen toegangsgegevens van het type '{}' toewijzen" -#: awx/api/serializers.py:4309 +#: awx/api/serializers.py:4111 msgid "" "Removing {} credential at launch time without replacement is not supported. " "Provided list lacked credential(s): {}." -msgstr "" -"Toegangsgegevens voor {} verwijderen bij opstarten zonder vervanging wordt " -"niet ondersteund. De volgende toegangsgegevens ontbraken uit de opgegeven " -"lijst: {}." +msgstr "Toegangsgegevens voor {} verwijderen bij opstarten zonder vervanging wordt niet ondersteund. De volgende toegangsgegevens ontbraken uit de opgegeven lijst: {}." -#: awx/api/serializers.py:4435 +#: awx/api/serializers.py:4200 +msgid "The inventory associated with this Workflow is being deleted." +msgstr "De inventaris die gerelateerd is aan deze workflow wordt verwijderd." + +#: awx/api/serializers.py:4271 +msgid "Message type '{}' invalid, must be either 'message' or 'body'" +msgstr "Berichttype ‘{}‘ ongeldig, moet ‘bericht‘ ofwel ‘body‘ zijn" + +#: awx/api/serializers.py:4277 +msgid "Expected string for '{}', found {}, " +msgstr "Verwachte string voor '{}', {} gevonden, " + +#: awx/api/serializers.py:4281 +msgid "Messages cannot contain newlines (found newline in {} event)" +msgstr "Berichten kunnen geen newlines bevatten (newline gevonden in {} gebeurtenis)" + +#: awx/api/serializers.py:4287 +msgid "Expected dict for 'messages' field, found {}" +msgstr "Verwacht dictaat voor veld 'berichten', {} gevonden" + +#: awx/api/serializers.py:4291 +msgid "" +"Event '{}' invalid, must be one of 'started', 'success', 'error', or " +"'workflow_approval'" +msgstr "Gebeurtenis ‘{}‘ ongeldig, moet ‘gestart‘, ‘geslaagd‘, ‘fout‘ of ‘workflow_goedkeuring‘ zijn" + +#: awx/api/serializers.py:4297 +msgid "Expected dict for event '{}', found {}" +msgstr "Verwacht dictaat voor gebeurtenis ‘{}‘, {} gevonden" + +#: awx/api/serializers.py:4302 +msgid "" +"Workflow Approval event '{}' invalid, must be one of 'running', 'approved', " +"'timed_out', or 'denied'" +msgstr "Workflow Goedkeuringsgebeurtenis ‘{}‘ ongeldig, moet ‘uitvoerend‘, ‘goedgekeurd‘, ‘onderbroken‘ of ‘geweigerd‘ zijn" + +#: awx/api/serializers.py:4309 +msgid "Expected dict for workflow approval event '{}', found {}" +msgstr "Verwacht dictaat voor goedkeuring van de workflow ‘{}‘, {} gevonden" + +#: awx/api/serializers.py:4336 +msgid "Unable to render message '{}': {}" +msgstr "Niet in staat om bericht '{}' weer te geven: {}" + +#: awx/api/serializers.py:4338 +msgid "Field '{}' unavailable" +msgstr "Veld ‘{}‘ niet beschikbaar" + +#: awx/api/serializers.py:4340 +msgid "Security error due to field '{}'" +msgstr "Veiligheidsfout als gevolg van veld ‘{}‘" + +#: awx/api/serializers.py:4360 +msgid "Webhook body for '{}' should be a json dictionary. Found type '{}'." +msgstr "Webhook-body voor '{}' zou een json-woordenboek moeten zijn. Gevonden type '{}'." + +#: awx/api/serializers.py:4363 +msgid "Webhook body for '{}' is not a valid json dictionary ({})." +msgstr "Webhook-body voor ‘{}‘ is geen geldig json-woordenboek ({})." + +#: awx/api/serializers.py:4381 msgid "" "Missing required fields for Notification Configuration: notification_type" -msgstr "" -"Ontbrekende vereiste velden voor kennisgevingsconfiguratie: " -"notification_type" +msgstr "Ontbrekende vereiste velden voor kennisgevingsconfiguratie: notification_type" -#: awx/api/serializers.py:4458 +#: awx/api/serializers.py:4408 msgid "No values specified for field '{}'" msgstr "Geen waarden opgegeven voor veld '{}'" -#: awx/api/serializers.py:4463 +#: awx/api/serializers.py:4413 +msgid "HTTP method must be either 'POST' or 'PUT'." +msgstr "De HTTP-methode moet 'POSTEN' of 'PLAATSEN' zijn." + +#: awx/api/serializers.py:4415 msgid "Missing required fields for Notification Configuration: {}." msgstr "Ontbrekende vereiste velden voor kennisgevingsconfiguratie: {}." -#: awx/api/serializers.py:4466 +#: awx/api/serializers.py:4418 msgid "Configuration field '{}' incorrect type, expected {}." msgstr "Configuratieveld '{}' onjuist type, {} verwacht." -#: awx/api/serializers.py:4528 +#: awx/api/serializers.py:4435 +msgid "Notification body" +msgstr "Meldingsbody" + +#: awx/api/serializers.py:4515 msgid "" -"Valid DTSTART required in rrule. Value should start with: " -"DTSTART:YYYYMMDDTHHMMSSZ" -msgstr "" -"Geldige DTSTART vereist in rrule. De waarde moet beginnen met: " -"DTSTART:YYYYMMDDTHHMMSSZ" +"Valid DTSTART required in rrule. Value should start with: DTSTART:" +"YYYYMMDDTHHMMSSZ" +msgstr "Geldige DTSTART vereist in rrule. De waarde moet beginnen met: DTSTART:YYYYMMDDTHHMMSSZ" -#: awx/api/serializers.py:4530 +#: awx/api/serializers.py:4517 msgid "" "DTSTART cannot be a naive datetime. Specify ;TZINFO= or YYYYMMDDTHHMMSSZZ." -msgstr "" -"DTSTART kan geen eenvoudige datum/tijd zijn. Geef ;TZINFO= of " -"YYYYMMDDTHHMMSSZZ op." +msgstr "DTSTART kan geen eenvoudige datum/tijd zijn. Geef ;TZINFO= of YYYYMMDDTHHMMSSZZ op." -#: awx/api/serializers.py:4532 +#: awx/api/serializers.py:4519 msgid "Multiple DTSTART is not supported." msgstr "Meervoudige DTSTART wordt niet ondersteund." -#: awx/api/serializers.py:4534 +#: awx/api/serializers.py:4521 msgid "RRULE required in rrule." msgstr "RRULE vereist in rrule." -#: awx/api/serializers.py:4536 +#: awx/api/serializers.py:4523 msgid "Multiple RRULE is not supported." msgstr "Meervoudige RRULE wordt niet ondersteund." -#: awx/api/serializers.py:4538 +#: awx/api/serializers.py:4525 msgid "INTERVAL required in rrule." msgstr "INTERVAL is vereist in rrule." -#: awx/api/serializers.py:4540 +#: awx/api/serializers.py:4527 msgid "SECONDLY is not supported." msgstr "SECONDLY wordt niet ondersteund." -#: awx/api/serializers.py:4542 +#: awx/api/serializers.py:4529 msgid "Multiple BYMONTHDAYs not supported." msgstr "Meerdere BYMONTHDAY's worden niet ondersteund." -#: awx/api/serializers.py:4544 +#: awx/api/serializers.py:4531 msgid "Multiple BYMONTHs not supported." msgstr "Meerdere BYMONTH's worden niet ondersteund." -#: awx/api/serializers.py:4546 +#: awx/api/serializers.py:4533 msgid "BYDAY with numeric prefix not supported." msgstr "BYDAY met numeriek voorvoegsel wordt niet ondersteund." -#: awx/api/serializers.py:4548 +#: awx/api/serializers.py:4535 msgid "BYYEARDAY not supported." msgstr "BYYEARDAY wordt niet ondersteund." -#: awx/api/serializers.py:4550 +#: awx/api/serializers.py:4537 msgid "BYWEEKNO not supported." msgstr "BYWEEKNO wordt niet ondersteund." -#: awx/api/serializers.py:4552 +#: awx/api/serializers.py:4539 msgid "RRULE may not contain both COUNT and UNTIL" msgstr "RRULE mag niet zowel COUNT als UNTIL bevatten" -#: awx/api/serializers.py:4556 +#: awx/api/serializers.py:4543 msgid "COUNT > 999 is unsupported." msgstr "COUNT > 999 wordt niet ondersteund." -#: awx/api/serializers.py:4560 +#: awx/api/serializers.py:4549 msgid "rrule parsing failed validation: {}" msgstr "de validering van rrule-parsering is mislukt: {}" -#: awx/api/serializers.py:4601 +#: awx/api/serializers.py:4611 msgid "Inventory Source must be a cloud resource." msgstr "Inventarisbron moet een cloudresource zijn." -#: awx/api/serializers.py:4603 +#: awx/api/serializers.py:4613 msgid "Manual Project cannot have a schedule set." msgstr "Handmatig project kan geen ingesteld schema hebben." #: awx/api/serializers.py:4616 msgid "" +"Inventory sources with `update_on_project_update` cannot be scheduled. " +"Schedule its source project `{}` instead." +msgstr "Inventarisbronnen met `update_on_project_update` kan niet worden ingepland. Plan in plaats daarvan het bronproject `{}` in." + +#: awx/api/serializers.py:4626 +msgid "" "Count of jobs in the running or waiting state that are targeted for this " "instance" -msgstr "" -"Aantal taken met status 'in uitvoering' of 'wachten' die in aanmerking komen" -" voor deze instantie" +msgstr "Aantal taken met status 'in uitvoering' of 'wachten' die in aanmerking komen voor deze instantie" -#: awx/api/serializers.py:4621 +#: awx/api/serializers.py:4631 msgid "Count of all jobs that target this instance" msgstr "Aantal taken die deze instantie als doel hebben" -#: awx/api/serializers.py:4654 +#: awx/api/serializers.py:4664 msgid "" "Count of jobs in the running or waiting state that are targeted for this " "instance group" -msgstr "" -"Aantal taken met status 'in uitvoering' of 'wachten' die in aanmerking komen" -" voor deze instantiegroep" +msgstr "Aantal taken met status 'in uitvoering' of 'wachten' die in aanmerking komen voor deze instantiegroep" -#: awx/api/serializers.py:4659 +#: awx/api/serializers.py:4669 msgid "Count of all jobs that target this instance group" msgstr "Aantal van alle taken die deze instantiegroep als doel hebben" -#: awx/api/serializers.py:4667 +#: awx/api/serializers.py:4674 +msgid "Indicates whether instance group controls any other group" +msgstr "Geeft aan of de instantiegroep een andere groep regelt" + +#: awx/api/serializers.py:4678 +msgid "" +"Indicates whether instances in this group are isolated.Isolated groups have " +"a designated controller group." +msgstr "Geeft aan of instanties in deze groep geïsoleerd zijn. Geïsoleerde groepen hebben een aangewezen regelaarsgroep." + +#: awx/api/serializers.py:4683 +msgid "" +"Indicates whether instances in this group are containerized.Containerized " +"groups have a designated Openshift or Kubernetes cluster." +msgstr "Geeft aan of instanties in deze groep geclusterd zijn. Geclusterde groepen hebben een aangewezen Openshift of Kubernetes-cluster." + +#: awx/api/serializers.py:4691 msgid "Policy Instance Percentage" msgstr "Beleid instantiepercentage" -#: awx/api/serializers.py:4668 +#: awx/api/serializers.py:4692 msgid "" "Minimum percentage of all instances that will be automatically 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." +msgstr "Minimumpercentage van alle instanties die automatisch toegewezen worden aan deze groep wanneer nieuwe instanties online komen." -#: awx/api/serializers.py:4673 +#: awx/api/serializers.py:4697 msgid "Policy Instance Minimum" msgstr "Beleid instantieminimum" -#: awx/api/serializers.py:4674 +#: awx/api/serializers.py:4698 msgid "" -"Static minimum number of Instances that will be automatically assign to this" -" group when new instances come online." -msgstr "" -"Statistisch minimumaantal instanties dat automatisch toegewezen wordt aan " -"deze groep wanneer nieuwe instanties online komen." +"Static minimum number of Instances that will be automatically assign to this " +"group when new instances come online." +msgstr "Statistisch minimumaantal instanties dat automatisch toegewezen wordt aan deze groep wanneer nieuwe instanties online komen." -#: awx/api/serializers.py:4679 +#: awx/api/serializers.py:4703 msgid "Policy Instance List" msgstr "Beleid instantielijst" -#: awx/api/serializers.py:4680 +#: awx/api/serializers.py:4704 msgid "List of exact-match Instances that will be assigned to this group" -msgstr "" -"Lijst van exact overeenkomende instanties die worden toegewezen aan deze " -"groep" +msgstr "Lijst van exact overeenkomende instanties die worden toegewezen aan deze groep" -#: awx/api/serializers.py:4702 +#: awx/api/serializers.py:4730 msgid "Duplicate entry {}." msgstr "Dubbele invoer {}." -#: awx/api/serializers.py:4704 +#: awx/api/serializers.py:4732 msgid "{} is not a valid hostname of an existing instance." msgstr "{} is geen geldige hostnaam voor een bestaande instantie." -#: awx/api/serializers.py:4706 awx/api/views.py:202 +#: awx/api/serializers.py:4734 awx/api/views/mixin.py:98 msgid "" -"Isolated instances may not be added or removed from instances groups via the" -" API." -msgstr "" -"Geïsoleerde instanties mogen niet toegevoegd worden aan of verwijderd worden" -" uit instantiegroepen via de API." +"Isolated instances may not be added or removed from instances groups via the " +"API." +msgstr "Geïsoleerde instanties mogen niet toegevoegd worden aan of verwijderd worden uit instantiegroepen via de API." -#: awx/api/serializers.py:4708 awx/api/views.py:206 +#: awx/api/serializers.py:4736 awx/api/views/mixin.py:102 msgid "Isolated instance group membership may not be managed via the API." -msgstr "" -"Lidmaatschap van geïsoleerde instantiegroep mag niet beheerd worden via de " -"API." +msgstr "Lidmaatschap van geïsoleerde instantiegroep mag niet beheerd worden via de API." -#: awx/api/serializers.py:4713 +#: awx/api/serializers.py:4738 awx/api/serializers.py:4743 +#: awx/api/serializers.py:4748 +msgid "Containerized instances may not be managed via the API" +msgstr "Geclusterde instanties worden mogelijk niet beheerd via de API" + +#: awx/api/serializers.py:4753 msgid "tower instance group name may not be changed." msgstr "Naam van de tower-instantiegroep mag niet gewijzigd worden." -#: awx/api/serializers.py:4783 +#: awx/api/serializers.py:4758 +msgid "Only Kubernetes credentials can be associated with an Instance Group" +msgstr "Alleen de toegangsgegevens van Kubernetes kunnen worden geassocieerd met een Instantiegroep" + +#: awx/api/serializers.py:4797 +msgid "" +"When present, shows the field name of the role or relationship that changed." +msgstr "Geeft, indien aanwezig, de veldnaam aan van de rol of relatie die veranderd is." + +#: awx/api/serializers.py:4799 +msgid "" +"When present, shows the model on which the role or relationship was defined." +msgstr "Laat, indien aanwezig, het model zien waarvoor de rol of de relatie is gedefinieerd." + +#: awx/api/serializers.py:4832 msgid "" "A summary of the new and changed values when an object is created, updated, " "or deleted" -msgstr "" -"Een overzicht van de nieuwe en gewijzigde waarden wanneer een object wordt " -"gemaakt, bijgewerkt of verwijderd" +msgstr "Een overzicht van de nieuwe en gewijzigde waarden wanneer een object wordt gemaakt, bijgewerkt of verwijderd" -#: awx/api/serializers.py:4785 +#: awx/api/serializers.py:4834 msgid "" "For create, update, and delete events this is the object type that was " "affected. For associate and disassociate events this is the object type " "associated or disassociated with object2." -msgstr "" -"Voor maak-, update- en verwijder-gebeurtenissen is dit het betreffende " -"objecttype. Voor koppel- en ontkoppel-gebeurtenissen is dit het objecttype " -"dat wordt gekoppeld aan of ontkoppeld van object2." +msgstr "Voor maak-, update- en verwijder-gebeurtenissen is dit het betreffende objecttype. Voor koppel- en ontkoppel-gebeurtenissen is dit het objecttype dat wordt gekoppeld aan of ontkoppeld van object2." -#: awx/api/serializers.py:4788 +#: awx/api/serializers.py:4837 msgid "" "Unpopulated for create, update, and delete events. For associate and " -"disassociate events this is the object type that object1 is being associated" -" with." -msgstr "" -"Niet-ingevuld voor maak-, update- en verwijder-gebeurtenissen. Voor koppel- " -"en ontkoppel-gebeurtenissen is dit het objecttype waaraan object1 wordt " -"gekoppeld." +"disassociate events this is the object type that object1 is being associated " +"with." +msgstr "Niet-ingevuld voor maak-, update- en verwijder-gebeurtenissen. Voor koppel- en ontkoppel-gebeurtenissen is dit het objecttype waaraan object1 wordt gekoppeld." -#: awx/api/serializers.py:4791 +#: awx/api/serializers.py:4840 msgid "The action taken with respect to the given object(s)." msgstr "De actie ondernomen met betrekking tot de gegeven objecten." -#: awx/api/views.py:119 -msgid "Your license does not allow use of the activity stream." -msgstr "Uw licentie staat het gebruik van de activiteitenstroom niet toe." - -#: awx/api/views.py:129 -msgid "Your license does not permit use of system tracking." -msgstr "Uw licentie staat het gebruik van systeemtracking niet toe." - -#: awx/api/views.py:139 -msgid "Your license does not allow use of workflows." -msgstr "Uw licentie staat het gebruik van workflows niet toe." - -#: awx/api/views.py:153 -msgid "Cannot delete job resource when associated workflow job is running." -msgstr "" -"Kan taakresource niet verwijderen wanneer een gekoppelde workflowtaak wordt " -"uitgevoerd." - -#: awx/api/views.py:158 -msgid "Cannot delete running job resource." -msgstr "Kan geen taakbron in uitvoering verwijderen." - -#: awx/api/views.py:163 -msgid "Job has not finished processing events." -msgstr "Taken die nog niet klaar zijn met het verwerken van gebeurtenissen." - -#: awx/api/views.py:257 -msgid "Related job {} is still processing events." -msgstr "Verwante taak {} is nog bezig met het verwerken van gebeurtenissen." - -#: awx/api/views.py:264 awx/templates/rest_framework/api.html:28 -msgid "REST API" -msgstr "REST API" - -#: awx/api/views.py:275 awx/templates/rest_framework/api.html:4 -msgid "AWX REST API" -msgstr "AWX REST API" - -#: awx/api/views.py:288 -msgid "API OAuth 2 Authorization Root" -msgstr "Oorsprong API OAuth 2-machtiging" - -#: awx/api/views.py:353 -msgid "Version 1" -msgstr "Versie 1" - -#: awx/api/views.py:357 -msgid "Version 2" -msgstr "Versie 2" - -#: awx/api/views.py:366 -msgid "Ping" -msgstr "Ping" - -#: awx/api/views.py:397 awx/conf/apps.py:10 -msgid "Configuration" -msgstr "Configuratie" - -#: awx/api/views.py:454 -msgid "Invalid license data" -msgstr "Ongeldige licentiegegevens" - -#: awx/api/views.py:456 -msgid "Missing 'eula_accepted' property" -msgstr "Ontbrekende eigenschap 'eula_accepted'" - -#: awx/api/views.py:460 -msgid "'eula_accepted' value is invalid" -msgstr "Waarde 'eula_accepted' is ongeldig" - -#: awx/api/views.py:463 -msgid "'eula_accepted' must be True" -msgstr "'eula_accepted' moet True zijn" - -#: awx/api/views.py:470 -msgid "Invalid JSON" -msgstr "Ongeldig JSON" - -#: awx/api/views.py:478 -msgid "Invalid License" -msgstr "Ongeldige licentie" - -#: awx/api/views.py:488 -msgid "Invalid license" -msgstr "Ongeldige licentie" - -#: awx/api/views.py:496 -#, python-format -msgid "Failed to remove license (%s)" -msgstr "Kan licentie niet verwijderen (%s)" - -#: awx/api/views.py:501 +#: awx/api/views/__init__.py:181 msgid "Dashboard" msgstr "Dashboard" -#: awx/api/views.py:600 +#: awx/api/views/__init__.py:271 msgid "Dashboard Jobs Graphs" msgstr "Dashboardtaakgrafieken" -#: awx/api/views.py:636 +#: awx/api/views/__init__.py:307 #, python-format msgid "Unknown period \"%s\"" -msgstr "Onbekende periode \"%s\"" +msgstr "Onbekende periode ‘%s‘" -#: awx/api/views.py:650 +#: awx/api/views/__init__.py:321 msgid "Instances" msgstr "Instanties" -#: awx/api/views.py:658 +#: awx/api/views/__init__.py:329 msgid "Instance Detail" msgstr "Instantiedetails" -#: awx/api/views.py:678 +#: awx/api/views/__init__.py:346 msgid "Instance Jobs" msgstr "Instantietaken" -#: awx/api/views.py:692 +#: awx/api/views/__init__.py:360 msgid "Instance's Instance Groups" msgstr "Instantiegroepen van instantie" -#: awx/api/views.py:701 +#: awx/api/views/__init__.py:369 msgid "Instance Groups" msgstr "Instantiegroepen" -#: awx/api/views.py:709 +#: awx/api/views/__init__.py:377 msgid "Instance Group Detail" msgstr "Details van instantiegroep" -#: awx/api/views.py:717 +#: awx/api/views/__init__.py:392 msgid "Isolated Groups can not be removed from the API" msgstr "Geïsoleerde groepen kunnen niet verwijderd worden van de API" -#: awx/api/views.py:719 +#: awx/api/views/__init__.py:394 msgid "" "Instance Groups acting as a controller for an Isolated Group can not be " "removed from the API" -msgstr "" -"Instantiegroepen die dienen als controller voor een geïsoleerde groep kunnen" -" niet verwijderd worden van de API" +msgstr "Instantiegroepen die dienen als controller voor een geïsoleerde groep kunnen niet verwijderd worden van de API" -#: awx/api/views.py:725 +#: awx/api/views/__init__.py:400 msgid "Instance Group Running Jobs" msgstr "Taken in uitvoering van instantiegroep" -#: awx/api/views.py:734 +#: awx/api/views/__init__.py:409 msgid "Instance Group's Instances" msgstr "Instanties van instantiegroep" -#: awx/api/views.py:744 +#: awx/api/views/__init__.py:419 msgid "Schedules" msgstr "Schema's" -#: awx/api/views.py:758 +#: awx/api/views/__init__.py:433 msgid "Schedule Recurrence Rule Preview" msgstr "Voorvertoning herhalingsregel inplannen" -#: awx/api/views.py:805 +#: awx/api/views/__init__.py:480 msgid "Cannot assign credential when related template is null." msgstr "Kan geen toegangsgegevens toewijzen wanneer verwant sjabloon nul is." -#: awx/api/views.py:810 +#: awx/api/views/__init__.py:485 msgid "Related template cannot accept {} on launch." msgstr "Verwant sjabloon kan {} niet accepteren bij opstarten." -#: awx/api/views.py:812 +#: awx/api/views/__init__.py:487 msgid "" -"Credential that requires user input on launch cannot be used in saved launch" -" configuration." -msgstr "" -"Toegangsgegevens die input van de gebruiker nodig hebben bij het opstarten, " -"kunnen niet gebruikt worden in opgeslagen opstartconfiguratie." +"Credential that requires user input on launch cannot be used in saved launch " +"configuration." +msgstr "Toegangsgegevens die input van de gebruiker nodig hebben bij het opstarten, kunnen niet gebruikt worden in opgeslagen opstartconfiguratie." -#: awx/api/views.py:820 +#: awx/api/views/__init__.py:493 +msgid "Related template is not configured to accept credentials on launch." +msgstr "Verwante sjabloon is niet ingesteld om toegangsgegevens bij opstarten te accepteren." + +#: awx/api/views/__init__.py:495 #, python-brace-format msgid "" "This launch configuration already provides a {credential_type} credential." -msgstr "" -"Deze opstartconfiguratie levert al {credential_type}-toegangsgegevens." +msgstr "Deze opstartconfiguratie levert al {credential_type}-toegangsgegevens." -#: awx/api/views.py:823 +#: awx/api/views/__init__.py:498 #, python-brace-format msgid "Related template already uses {credential_type} credential." msgstr "Verwant sjabloon gebruikt al {credential_type}-toegangsgegevens." -#: awx/api/views.py:841 +#: awx/api/views/__init__.py:516 msgid "Schedule Jobs List" msgstr "Schema takenlijst" -#: awx/api/views.py:996 -msgid "Your license only permits a single organization to exist." -msgstr "Uw licentie staat het gebruik van maar één organisatie toe." - -#: awx/api/views.py:1223 awx/api/views.py:5106 +#: awx/api/views/__init__.py:600 awx/api/views/__init__.py:4452 msgid "" "You cannot assign an Organization participation role as a child role for a " "Team." -msgstr "" -"U kunt een organisatiedeelnamerol niet toewijzen als een onderliggende rol " -"voor een team." +msgstr "U kunt een organisatiedeelnamerol niet toewijzen als een onderliggende rol voor een team." -#: awx/api/views.py:1227 awx/api/views.py:5120 +#: awx/api/views/__init__.py:604 awx/api/views/__init__.py:4466 msgid "You cannot grant system-level permissions to a team." -msgstr "U kunt een team geen rechten op systeemniveau verlenen." +msgstr "U kunt een team geen rechten op systeemniveau verlenen." -#: awx/api/views.py:1234 awx/api/views.py:5112 +#: awx/api/views/__init__.py:611 awx/api/views/__init__.py:4458 msgid "" "You cannot grant credential access to a team when the Organization field " "isn't set, or belongs to a different organization" -msgstr "" -"U kunt een team geen referentietoegang verlenen wanneer het veld Organisatie" -" niet is ingesteld of behoort tot een andere organisatie" +msgstr "U kunt een team geen referentietoegang verlenen wanneer het veld Organisatie niet is ingesteld of behoort tot een andere organisatie" -#: awx/api/views.py:1348 +#: awx/api/views/__init__.py:713 msgid "Project Schedules" msgstr "Projectschema's" -#: awx/api/views.py:1359 +#: awx/api/views/__init__.py:724 msgid "Project SCM Inventory Sources" msgstr "SCM-inventarisbronnen van project" -#: awx/api/views.py:1460 +#: awx/api/views/__init__.py:825 msgid "Project Update Events List" msgstr "Lijst met projectupdategebeurtenissen" -#: awx/api/views.py:1474 +#: awx/api/views/__init__.py:839 msgid "System Job Events List" msgstr "Lijst met systeemtaakgebeurtenissen" -#: awx/api/views.py:1488 -msgid "Inventory Update Events List" -msgstr "Lijst met inventarisupdategebeurtenissen" - -#: awx/api/views.py:1522 +#: awx/api/views/__init__.py:873 msgid "Project Update SCM Inventory Updates" msgstr "SCM-inventarisupdates van projectupdate" -#: awx/api/views.py:1581 +#: awx/api/views/__init__.py:918 msgid "Me" msgstr "Mij" -#: awx/api/views.py:1589 +#: awx/api/views/__init__.py:927 msgid "OAuth 2 Applications" msgstr "OAuth 2-toepassingen" -#: awx/api/views.py:1598 +#: awx/api/views/__init__.py:936 msgid "OAuth 2 Application Detail" msgstr "Details OAuth 2-toepassing" -#: awx/api/views.py:1607 +#: awx/api/views/__init__.py:949 msgid "OAuth 2 Application Tokens" msgstr "Tokens OAuth 2-toepassing" -#: awx/api/views.py:1629 +#: awx/api/views/__init__.py:971 msgid "OAuth2 Tokens" msgstr "OAuth 2-tokens" -#: awx/api/views.py:1638 +#: awx/api/views/__init__.py:980 msgid "OAuth2 User Tokens" msgstr "OAuth2-gebruikerstokens" -#: awx/api/views.py:1650 +#: awx/api/views/__init__.py:992 msgid "OAuth2 User Authorized Access Tokens" msgstr "OAuth 2-gebruikerstokens gemachtigde toegang" -#: awx/api/views.py:1665 +#: awx/api/views/__init__.py:1007 msgid "Organization OAuth2 Applications" msgstr "Organisatie OAuth2-toepassingen" -#: awx/api/views.py:1677 +#: awx/api/views/__init__.py:1019 msgid "OAuth2 Personal Access Tokens" msgstr "OAuth2-tokens persoonlijke toegang" -#: awx/api/views.py:1692 +#: awx/api/views/__init__.py:1034 msgid "OAuth Token Detail" msgstr "Details OAuth-token" -#: awx/api/views.py:1752 awx/api/views.py:5073 +#: awx/api/views/__init__.py:1096 awx/api/views/__init__.py:4419 msgid "" "You cannot grant credential access to a user not in the credentials' " "organization" -msgstr "" -"U kunt geen referentietoegang verlenen aan een gebruiker die niet tot de " -"organisatie van de referenties behoort" +msgstr "U kunt geen referentietoegang verlenen aan een gebruiker die niet tot de organisatie van de referenties behoort" -#: awx/api/views.py:1756 awx/api/views.py:5077 +#: awx/api/views/__init__.py:1100 awx/api/views/__init__.py:4423 msgid "You cannot grant private credential access to another user" msgstr "U kunt geen privéreferentietoegang verlenen aan een andere gebruiker" -#: awx/api/views.py:1854 +#: awx/api/views/__init__.py:1198 #, python-format msgid "Cannot change %s." msgstr "Kan %s niet wijzigen." -#: awx/api/views.py:1860 +#: awx/api/views/__init__.py:1204 msgid "Cannot delete user." msgstr "Kan gebruiker niet verwijderen." -#: awx/api/views.py:1884 +#: awx/api/views/__init__.py:1228 msgid "Deletion not allowed for managed credential types" msgstr "Verwijdering is niet toegestaan voor beheerde referentietypen" -#: awx/api/views.py:1886 +#: awx/api/views/__init__.py:1230 msgid "Credential types that are in use cannot be deleted" msgstr "Referentietypen die in gebruik zijn, kunnen niet worden verwijderd" -#: awx/api/views.py:2061 -msgid "Cannot delete inventory script." -msgstr "Kan inventarisscript niet verwijderen." +#: awx/api/views/__init__.py:1381 +msgid "External Credential Test" +msgstr "Test van externe toegangsgegevens" -#: awx/api/views.py:2152 -#, python-brace-format -msgid "{0}" -msgstr "{0}" +#: awx/api/views/__init__.py:1408 +msgid "Credential Input Source Detail" +msgstr "Details van inputbron toegangsgegevens" + +#: awx/api/views/__init__.py:1416 awx/api/views/__init__.py:1424 +msgid "Credential Input Sources" +msgstr "Inputbronnen toegangsgegevens" -#: awx/api/views.py:2256 +#: awx/api/views/__init__.py:1439 +msgid "External Credential Type Test" +msgstr "Test van extern toegangsgegevenstype" + +#: awx/api/views/__init__.py:1497 msgid "The inventory for this host is already being deleted." msgstr "De inventaris voor deze host wordt al verwijderd." -#: awx/api/views.py:2389 -msgid "Fact not found." -msgstr "Feit niet gevonden." - -#: awx/api/views.py:2411 +#: awx/api/views/__init__.py:1614 msgid "SSLError while trying to connect to {}" msgstr "SSLError tijdens poging om verbinding te maken met {}" -#: awx/api/views.py:2413 +#: awx/api/views/__init__.py:1616 msgid "Request to {} timed out." msgstr "Er is een time-out opgetreden voor de aanvraag naar {}" -#: awx/api/views.py:2415 +#: awx/api/views/__init__.py:1618 msgid "Unknown exception {} while trying to GET {}" msgstr "Onbekende uitzondering {} tijdens poging tot OPHALEN {}" -#: awx/api/views.py:2418 +#: awx/api/views/__init__.py:1622 msgid "" "Unauthorized access. Please check your Insights Credential username and " "password." -msgstr "" -"Geen toegang. Controleer uw Insights Credential gebruikersnaam en " -"wachtwoord." +msgstr "Geen toegang. Controleer uw Insights Credential gebruikersnaam en wachtwoord." -#: awx/api/views.py:2421 +#: awx/api/views/__init__.py:1626 msgid "" -"Failed to gather reports and maintenance plans from Insights API at URL {}. " -"Server responded with {} status code and message {}" -msgstr "" -"Kan rapporten en onderhoudsplannen niet verzamelen uit Insights API op URL " -"{}. Server heeft gereageerd met statuscode {} en bericht {}" +"Failed to access the Insights API at URL {}. Server responded with {} status " +"code and message {}" +msgstr "Openen van Insights API via URL {} mislukt. Server reageerde met {} statuscode en de melding {}" -#: awx/api/views.py:2428 -msgid "Expected JSON response from Insights but instead got {}" -msgstr "Verwachtte JSON-reactie van Insights, maar kreeg in plaats daarvan {}" +#: awx/api/views/__init__.py:1635 +msgid "Expected JSON response from Insights at URL {} but instead got {}" +msgstr "Verwachte JSON-reactie van Insights via URL {}, maar in plaats daarvan {} verkregen." -#: awx/api/views.py:2435 +#: awx/api/views/__init__.py:1653 +msgid "Could not translate Insights system ID {} into an Insights platform ID." +msgstr "Omzetten van Insights systeem-ID {} naar een Insights platform-ID mislukt." + +#: awx/api/views/__init__.py:1695 msgid "This host is not recognized as an Insights host." msgstr "Deze host wordt niet herkend als een Insights-host." -#: awx/api/views.py:2440 +#: awx/api/views/__init__.py:1703 msgid "The Insights Credential for \"{}\" was not found." -msgstr "De Insights-referentie voor \"{}\" is niet gevonden." +msgstr "De Insights-referentie voor ‘{}‘ is niet gevonden." -#: awx/api/views.py:2508 +#: awx/api/views/__init__.py:1782 msgid "Cyclical Group association." msgstr "Cyclische groepskoppeling." -#: awx/api/views.py:2722 +#: awx/api/views/__init__.py:1948 +msgid "Inventory subset argument must be a string." +msgstr "Het argument voor de inventarissubset moet een string zijn." + +#: awx/api/views/__init__.py:1952 +msgid "Subset does not use any supported syntax." +msgstr "Subset maakt geen gebruik van een ondersteunde syntaxis." + +#: awx/api/views/__init__.py:2002 msgid "Inventory Source List" msgstr "Lijst met inventarisbronnen" -#: awx/api/views.py:2734 +#: awx/api/views/__init__.py:2014 msgid "Inventory Sources Update" msgstr "Update van inventarisbronnen" -#: awx/api/views.py:2767 +#: awx/api/views/__init__.py:2047 msgid "Could not start because `can_update` returned False" msgstr "Kan niet starten omdat 'can_update' False heeft geretourneerd" -#: awx/api/views.py:2775 +#: awx/api/views/__init__.py:2055 msgid "No inventory sources to update." msgstr "Er zijn geen inventarisbronnen om bij te werken." -#: awx/api/views.py:2804 +#: awx/api/views/__init__.py:2077 msgid "Inventory Source Schedules" msgstr "Inventarisbronschema's" -#: awx/api/views.py:2832 +#: awx/api/views/__init__.py:2104 msgid "Notification Templates can only be assigned when source is one of {}." -msgstr "" -"Berichtsjablonen kunnen alleen worden toegewezen wanneer de bron een van {} " -"is." +msgstr "Berichtsjablonen kunnen alleen worden toegewezen wanneer de bron een van {} is." -#: awx/api/views.py:2887 -msgid "Vault credentials are not yet supported for inventory sources." -msgstr "" -"Kluistoegangsgegevens zijn nog niet toegestaan voor inventarisbronnen." - -#: awx/api/views.py:2892 -msgid "Source already has cloud credential assigned." -msgstr "Bron heeft al toegewezen cloudtoegangsgegevens." +#: awx/api/views/__init__.py:2202 +msgid "Source already has credential assigned." +msgstr "Aan de bron zijn al toegangsgegevens toegewezen." -#: awx/api/views.py:3042 -msgid "Field is not allowed for use with v1 API." -msgstr "Het veld is niet toegestaan voor gebruik met v1 API." - -#: awx/api/views.py:3052 -msgid "" -"'credentials' cannot be used in combination with 'credential', " -"'vault_credential', or 'extra_credentials'." -msgstr "" -"'Toegangsgegevens' kunnen niet gebruikt worden in combinatie met " -"'referentie', 'kluistoegangsgegevens' of 'extra toegangsgegevens'." +#: awx/api/views/__init__.py:2350 +msgid "'credentials' cannot be used in combination with 'extra_credentials'." +msgstr "'Toegangsgegevens' kunnen niet gebruikt worden in combinatie met 'extra_toegangsgegevens'." -#: awx/api/views.py:3079 -msgid "Incorrect type. Expected {}, received {}." -msgstr "Onjuist type. Verwacht {}, ontvangen {}." +#: awx/api/views/__init__.py:2368 +msgid "Incorrect type. Expected a list received {}." +msgstr "Onjuist type. Verwacht een lijst, ontvangen {}." -#: awx/api/views.py:3172 +#: awx/api/views/__init__.py:2466 msgid "Job Template Schedules" msgstr "Taaksjabloonschema's" -#: awx/api/views.py:3190 awx/api/views.py:3201 -msgid "Your license does not allow adding surveys." -msgstr "Uw licentie staat toevoeging van enquêtes niet toe." - -#: awx/api/views.py:3220 +#: awx/api/views/__init__.py:2515 msgid "Field '{}' is missing from survey spec." msgstr "Veld '{}' ontbreekt in de enquêtespecificaties." -#: awx/api/views.py:3222 +#: awx/api/views/__init__.py:2517 msgid "Expected {} for field '{}', received {} type." msgstr "{} verwacht voor veld '{}', {}-soort ontvangen." -#: awx/api/views.py:3226 +#: awx/api/views/__init__.py:2521 msgid "'spec' doesn't contain any items." msgstr "'spec' bevat geen items." -#: awx/api/views.py:3235 +#: awx/api/views/__init__.py:2535 #, python-format msgid "Survey question %s is not a json object." msgstr "Enquêtevraag %s is geen json-object." -#: awx/api/views.py:3237 -#, python-format -msgid "'type' missing from survey question %s." -msgstr "'type' ontbreekt in enquêtevraag %s." - -#: awx/api/views.py:3239 -#, python-format -msgid "'question_name' missing from survey question %s." -msgstr "'question_name' ontbreekt in enquêtevraag %s." +#: awx/api/views/__init__.py:2538 +#, python-brace-format +msgid "'{field_name}' missing from survey question {idx}" +msgstr "'{field_name}' ontbreekt in enquêtevraag {idx}." -#: awx/api/views.py:3241 -#, python-format -msgid "'variable' missing from survey question %s." -msgstr "'variable' ontbreekt in enquêtevraag %s." +#: awx/api/views/__init__.py:2548 +#, python-brace-format +msgid "'{field_name}' in survey question {idx} expected to be {type_label}." +msgstr "‘{field_name}‘ in enquêtevraag {idx} is naar verwachting {type_label}." -#: awx/api/views.py:3243 +#: awx/api/views/__init__.py:2552 #, python-format msgid "'variable' '%(item)s' duplicated in survey question %(survey)s." msgstr "'variable' '%(item)s' gedupliceerd in enquêtevraag %(survey)s." -#: awx/api/views.py:3248 -#, python-format -msgid "'required' missing from survey question %s." -msgstr "'required' ontbreekt in enquêtevraag %s." +#: awx/api/views/__init__.py:2562 +#, python-brace-format +msgid "" +"'{survey_item[type]}' in survey question {idx} is not one of " +"'{allowed_types}' allowed question types." +msgstr "‘{survey_item[type]}‘ in enquêtevraag {idx} is niet een van de toegestane {allowed_types} vraagtypen." -#: awx/api/views.py:3253 +#: awx/api/views/__init__.py:2572 #, python-brace-format msgid "" -"Value {question_default} for '{variable_name}' expected to be a string." -msgstr "" -"Waarde {question_default} voor '{variable_name}' zou een string moeten zijn." +"Default value {survey_item[default]} in survey question {idx} expected to be " +"{type_label}." +msgstr "Standaardwaarde {survey_item[default]} in enquêtevraag {idx} is naar verwachting {type_label}." -#: awx/api/views.py:3263 +#: awx/api/views/__init__.py:2582 +#, python-brace-format +msgid "The {min_or_max} limit in survey question {idx} expected to be integer." +msgstr "De {min_or_max}-limiet in enquêtevraag {idx} behoort een heel getal te zijn." + +#: awx/api/views/__init__.py:2592 +#, python-brace-format +msgid "Survey question {idx} of type {survey_item[type]} must specify choices." +msgstr "Enquêtevraag {idx} van het soort {survey_item[type]} moet keuzes specificeren." + +#: awx/api/views/__init__.py:2606 +msgid "Multiple Choice (Single Select) can only have one default value." +msgstr "Meerkeuze-opties (één keuze mogelijk) kan slechts één standaardwaarde hebben." + +#: awx/api/views/__init__.py:2610 +msgid "Default choice must be answered from the choices listed." +msgstr "De standaardkeuze moet beantwoord worden aan de hand van de opgesomde keuzes." + +#: awx/api/views/__init__.py:2619 #, python-brace-format msgid "" "$encrypted$ is a reserved keyword for password question defaults, survey " -"question {question_position} is type {question_type}." -msgstr "" -"$encrypted$ is een gereserveerd sleutelwoord voor standaard\n" -"wachtwoordvragen, enquêtevraag {question_position} is van het soort {question_type}." +"question {idx} is type {survey_item[type]}." +msgstr "$encrypted$ is een gereserveerd sleutelwoord voor standaard wachtwoordvragen, enquêtevraag {idx} is van het soort {survey_item[type]}." -#: awx/api/views.py:3279 +#: awx/api/views/__init__.py:2633 #, python-brace-format msgid "" "$encrypted$ is a reserved keyword, may not be used for new default in " -"position {question_position}." -msgstr "" -"$encrypted$ is een gereserveerd sleutelwoord. Het mag niet gebruikt worden " -"als nieuwe standaard op de positie {question_position}." +"position {idx}." +msgstr "$encrypted$ is een gereserveerd sleutelwoord en mag niet worden gebruikt als nieuwe standaard in positie {idx}." -#: awx/api/views.py:3353 +#: awx/api/views/__init__.py:2705 #, python-brace-format msgid "Cannot assign multiple {credential_type} credentials." -msgstr "Kan niet meerdere {credential_type}-toegangsgegevens toewijzen." +msgstr "Kan niet meerdere toegangsgegevens voor {credential_type} toewijzen." -#: awx/api/views.py:3357 +#: awx/api/views/__init__.py:2709 msgid "Cannot assign a Credential of kind `{}`." msgstr "Kan geen toegangsgegevens van het type '{}' toewijzen." -#: awx/api/views.py:3374 +#: awx/api/views/__init__.py:2726 msgid "Extra credentials must be network or cloud." msgstr "Extra referenties moeten netwerk of cloud zijn." -#: awx/api/views.py:3396 +#: awx/api/views/__init__.py:2748 msgid "Maximum number of labels for {} reached." msgstr "Het maximumaantal labels voor {} is bereikt." -#: awx/api/views.py:3519 +#: awx/api/views/__init__.py:2871 msgid "No matching host could be found!" msgstr "Er is geen overeenkomende host gevonden." -#: awx/api/views.py:3522 +#: awx/api/views/__init__.py:2874 msgid "Multiple hosts matched the request!" msgstr "Meerdere hosts kwamen overeen met de aanvraag." -#: awx/api/views.py:3527 +#: awx/api/views/__init__.py:2879 msgid "Cannot start automatically, user input required!" msgstr "Kan niet automatisch starten. Gebruikersinput is vereist." -#: awx/api/views.py:3534 +#: awx/api/views/__init__.py:2887 msgid "Host callback job already pending." msgstr "Er is al een hostterugkoppelingstaak in afwachting." -#: awx/api/views.py:3549 awx/api/views.py:4336 +#: awx/api/views/__init__.py:2903 awx/api/views/__init__.py:3664 msgid "Error starting job!" msgstr "Fout bij starten taak." -#: awx/api/views.py:3669 -#, python-brace-format -msgid "Cannot associate {0} when {1} have been associated." -msgstr "Kan {0} niet koppelen wanneer {1} zijn gekoppeld." - -#: awx/api/views.py:3694 -msgid "Multiple parent relationship not allowed." -msgstr "Relatie met meerdere bovenliggende elementen is niet toegestaan." - -#: awx/api/views.py:3699 +#: awx/api/views/__init__.py:3027 awx/api/views/__init__.py:3047 msgid "Cycle detected." msgstr "Cyclus gedetecteerd." -#: awx/api/views.py:3902 +#: awx/api/views/__init__.py:3039 +msgid "Relationship not allowed." +msgstr "Relatie niet toegestaan." + +#: awx/api/views/__init__.py:3268 +msgid "Cannot relaunch slice workflow job orphaned from job template." +msgstr "Kan workflowtaakdeel dat is verwijderd uit de taaksjabloon niet opnieuw opstarten." + +#: awx/api/views/__init__.py:3270 +msgid "Cannot relaunch sliced workflow job after slice count has changed." +msgstr "Kan de workflowtaakdeel niet opnieuw opstarten, nadat het aantal delen is gewijzigd." + +#: awx/api/views/__init__.py:3303 msgid "Workflow Job Template Schedules" msgstr "Taaksjabloonschema's voor workflows" -#: awx/api/views.py:4038 awx/api/views.py:4740 +#: awx/api/views/__init__.py:3446 awx/api/views/__init__.py:4087 msgid "Superuser privileges needed." msgstr "Supergebruikersbevoegdheden vereist." -#: awx/api/views.py:4071 +#: awx/api/views/__init__.py:3479 msgid "System Job Template Schedules" msgstr "Taaksjabloonschema's voor systeem" -#: awx/api/views.py:4129 -msgid "POST not allowed for Job launching in version 2 of the api" -msgstr "" -"POST is niet toegestaan voor het openen van taken in versie 2 van de API" - -#: awx/api/views.py:4153 awx/api/views.py:4159 -msgid "PUT not allowed for Job Details in version 2 of the API" -msgstr "PLAATS niet toegestaan voor taakinformatie in versie 2 van de API" - -#: awx/api/views.py:4319 +#: awx/api/views/__init__.py:3647 #, python-brace-format msgid "Wait until job finishes before retrying on {status_value} hosts." -msgstr "" -"U dient te wachten tot de taak afgerond is voordat u het opnieuw probeert " -"met {status_value}-hosts." +msgstr "U dient te wachten tot de taak afgerond is voordat u het opnieuw probeert met {status_value}-hosts." -#: awx/api/views.py:4324 +#: awx/api/views/__init__.py:3652 #, python-brace-format msgid "Cannot retry on {status_value} hosts, playbook stats not available." -msgstr "" -"Kan niet opnieuw proberen met {status_value}-hosts, draaiboek niet " -"beschikbaar." +msgstr "Kan niet opnieuw proberen met {status_value}-hosts, draaiboekstatistieken niet beschikbaar." -#: awx/api/views.py:4329 +#: awx/api/views/__init__.py:3657 #, python-brace-format msgid "Cannot relaunch because previous job had 0 {status_value} hosts." -msgstr "" -"Kan niet opnieuw opstarten omdat vorige taak 0 {status_value}-hosts had." +msgstr "Kan niet opnieuw opstarten omdat vorige taak 0 {status_value}-hosts had." -#: awx/api/views.py:4358 +#: awx/api/views/__init__.py:3686 msgid "Cannot create schedule because job requires credential passwords." -msgstr "" -"Kan geen schema aanmaken omdat taak toegangsgegevens met wachtwoorden " -"vereist." +msgstr "Kan geen schema aanmaken omdat taak toegangsgegevens met wachtwoorden vereist." -#: awx/api/views.py:4363 +#: awx/api/views/__init__.py:3691 msgid "Cannot create schedule because job was launched by legacy method." -msgstr "" -"Kan geen schema aanmaken omdat taak opgestart is volgens verouderde methode." +msgstr "Kan geen schema aanmaken omdat taak opgestart is volgens verouderde methode." -#: awx/api/views.py:4365 +#: awx/api/views/__init__.py:3693 msgid "Cannot create schedule because a related resource is missing." msgstr "Kan geen schema aanmaken omdat een verwante hulpbron ontbreekt." -#: awx/api/views.py:4420 +#: awx/api/views/__init__.py:3748 msgid "Job Host Summaries List" msgstr "Lijst met taakhostoverzichten" -#: awx/api/views.py:4469 +#: awx/api/views/__init__.py:3802 msgid "Job Event Children List" msgstr "Lijst met onderliggende taakgebeurteniselementen" -#: awx/api/views.py:4479 +#: awx/api/views/__init__.py:3818 msgid "Job Event Hosts List" msgstr "Lijst met taakgebeurtenishosts" -#: awx/api/views.py:4488 +#: awx/api/views/__init__.py:3833 msgid "Job Events List" msgstr "Lijst met taakgebeurtenissen" -#: awx/api/views.py:4697 +#: awx/api/views/__init__.py:4044 msgid "Ad Hoc Command Events List" msgstr "Lijst met ad-hoc-opdrachtgebeurtenissen" -#: awx/api/views.py:4939 +#: awx/api/views/__init__.py:4289 msgid "Delete not allowed while there are pending notifications" -msgstr "" -"Verwijderen is niet toegestaan wanneer er berichten in afwachting zijn" +msgstr "Verwijderen is niet toegestaan wanneer er berichten in afwachting zijn" -#: awx/api/views.py:4947 +#: awx/api/views/__init__.py:4297 msgid "Notification Template Test" msgstr "Berichtsjabloon" -#: awx/conf/conf.py:20 -msgid "Bud Frogs" -msgstr "Budweiser-kikkers" +#: awx/api/views/__init__.py:4557 awx/api/views/__init__.py:4572 +msgid "User does not have permission to approve or deny this workflow." +msgstr "De gebruiker heeft geen toestemming om deze workflow goed te keuren of te weigeren." -#: awx/conf/conf.py:21 -msgid "Bunny" -msgstr "Konijntje" +#: awx/api/views/__init__.py:4559 awx/api/views/__init__.py:4574 +msgid "This workflow step has already been approved or denied." +msgstr "Deze workflowstap is al goedgekeurd of geweigerd." -#: awx/conf/conf.py:22 -msgid "Cheese" -msgstr "Kaas" +#: awx/api/views/inventory.py:63 +msgid "Inventory Update Events List" +msgstr "Lijst met inventarisupdategebeurtenissen" + +#: awx/api/views/inventory.py:90 +msgid "Cannot delete inventory script." +msgstr "Kan inventarisscript niet verwijderen." + +#: awx/api/views/inventory.py:149 +#, python-brace-format +msgid "{0}" +msgstr "{0}" + +#: awx/api/views/metrics.py:30 +msgid "Metrics" +msgstr "Meetwaarden" + +#: awx/api/views/mixin.py:46 +msgid "Cannot delete job resource when associated workflow job is running." +msgstr "Kan taakresource niet verwijderen wanneer een gekoppelde workflowtaak wordt uitgevoerd." + +#: awx/api/views/mixin.py:51 +msgid "Cannot delete running job resource." +msgstr "Kan geen taakbron in uitvoering verwijderen." + +#: awx/api/views/mixin.py:56 +msgid "Job has not finished processing events." +msgstr "Taken die nog niet klaar zijn met het verwerken van gebeurtenissen." + +#: awx/api/views/mixin.py:153 +msgid "Related job {} is still processing events." +msgstr "Verwante taak {} is nog bezig met het verwerken van gebeurtenissen." + +#: awx/api/views/root.py:49 awx/templates/rest_framework/api.html:28 +msgid "REST API" +msgstr "REST API" + +#: awx/api/views/root.py:59 awx/templates/rest_framework/api.html:4 +msgid "AWX REST API" +msgstr "AWX REST API" + +#: awx/api/views/root.py:72 +msgid "API OAuth 2 Authorization Root" +msgstr "Oorsprong API OAuth 2-machtiging" + +#: awx/api/views/root.py:139 +msgid "Version 2" +msgstr "Versie 2" + +#: awx/api/views/root.py:148 +msgid "Ping" +msgstr "Ping" + +#: awx/api/views/root.py:180 awx/api/views/root.py:225 awx/conf/apps.py:10 +msgid "Configuration" +msgstr "Configuratie" + +#: awx/api/views/root.py:202 awx/api/views/root.py:308 +msgid "Invalid License" +msgstr "Ongeldige licentie" + +#: awx/api/views/root.py:207 +msgid "The provided credentials are invalid (HTTP 401)." +msgstr "De verstrekte toegangsgegevens zijn ongeldig (HTTP 401)." + +#: awx/api/views/root.py:209 +msgid "Unable to connect to proxy server." +msgstr "Kan geen verbinding maken met proxyserver." + +#: awx/api/views/root.py:211 +msgid "Could not connect to subscription service." +msgstr "Kon geen verbinding maken met abonnementsdienst." + +#: awx/api/views/root.py:284 +msgid "Invalid license data" +msgstr "Ongeldige licentiegegevens" + +#: awx/api/views/root.py:286 +msgid "Missing 'eula_accepted' property" +msgstr "Ontbrekende eigenschap 'eula_accepted'" + +#: awx/api/views/root.py:290 +msgid "'eula_accepted' value is invalid" +msgstr "Waarde 'eula_accepted' is ongeldig" + +#: awx/api/views/root.py:293 +msgid "'eula_accepted' must be True" +msgstr "'eula_accepted' moet True zijn" + +#: awx/api/views/root.py:300 +msgid "Invalid JSON" +msgstr "Ongeldig JSON" + +#: awx/api/views/root.py:319 +msgid "Invalid license" +msgstr "Ongeldige licentie" + +#: awx/api/views/root.py:327 +msgid "Failed to remove license." +msgstr "Kan licentie niet verwijderen." + +#: awx/api/views/webhooks.py:143 +msgid "Webhook previously received, aborting." +msgstr "Eerder ontvangen Webhook afbreken." + +#: awx/conf/conf.py:20 +msgid "Bud Frogs" +msgstr "Budweiser-kikkers" + +#: awx/conf/conf.py:21 +msgid "Bunny" +msgstr "Konijntje" + +#: awx/conf/conf.py:22 +msgid "Cheese" +msgstr "Kaas" #: awx/conf/conf.py:23 msgid "Daemon" @@ -1606,8 +1616,7 @@ msgstr "Koe selecteren" #: awx/conf/conf.py:53 msgid "Select which cow to use with cowsay when running jobs." -msgstr "" -"Selecteer welke koe u met cowsay wilt gebruiken wanneer u taken uitvoert." +msgstr "Selecteer welke koe u met cowsay wilt gebruiken wanneer u taken uitvoert." #: awx/conf/conf.py:54 awx/conf/conf.py:75 msgid "Cows" @@ -1621,96 +1630,62 @@ msgstr "Voorbeeld alleen-lezen-instelling" msgid "Example setting that cannot be changed." msgstr "Voorbeeld van instelling die niet kan worden gewijzigd." -#: awx/conf/conf.py:93 +#: awx/conf/conf.py:90 msgid "Example Setting" msgstr "Voorbeeld van instelling" -#: awx/conf/conf.py:94 +#: awx/conf/conf.py:91 msgid "Example setting which can be different for each user." msgstr "Voorbeeld van instelling die anders kan zijn voor elke gebruiker." -#: awx/conf/conf.py:95 awx/conf/registry.py:85 awx/conf/views.py:55 +#: awx/conf/conf.py:92 awx/conf/registry.py:81 awx/conf/views.py:56 msgid "User" msgstr "Gebruiker" -#: awx/conf/fields.py:60 awx/sso/fields.py:595 +#: awx/conf/fields.py:63 awx/sso/fields.py:595 #, python-brace-format msgid "" -"Expected None, True, False, a string or list of strings but got {input_type}" -" instead." -msgstr "" -"Verwachtte None, True, False, een tekenreeks of een lijst met tekenreeksen, " -"maar kreeg in plaats daarvan {input_type}." +"Expected None, True, False, a string or list of strings but got {input_type} " +"instead." +msgstr "Verwachtte None, True, False, een tekenreeks of een lijst met tekenreeksen, maar kreeg in plaats daarvan {input_type}." #: awx/conf/fields.py:104 +#, python-brace-format +msgid "Expected list of strings but got {input_type} instead." +msgstr "Lijst met strings verwacht, maar in plaats daarvan {input_type} verkregen." + +#: awx/conf/fields.py:105 +#, python-brace-format +msgid "{path} is not a valid path choice." +msgstr "{path} is geen geldige padkeuze." + +#: awx/conf/fields.py:149 msgid "Enter a valid URL" msgstr "Geef een geldige URL op" -#: awx/conf/fields.py:136 +#: awx/conf/fields.py:187 #, python-brace-format msgid "\"{input}\" is not a valid string." -msgstr "\"{input} is geen geldige tekenreeks." +msgstr "‘{input}‘ is geen geldige tekenreeks." -#: awx/conf/fields.py:151 +#: awx/conf/fields.py:202 #, python-brace-format -msgid "" -"Expected a list of tuples of max length 2 but got {input_type} instead." -msgstr "" -"Verwachtte een lijst van tupels met maximale lengte 2 maar kreeg in plaats " -"daarvan {input_type}." - -#: awx/conf/license.py:22 -msgid "Your Tower license does not allow that." -msgstr "Uw Tower-licentie staat dat niet toe." - -#: awx/conf/management/commands/migrate_to_database_settings.py:41 -msgid "Only show which settings would be commented/migrated." -msgstr "" -"Geef alleen weer welke instellingen worden becommentarieerd/gemigreerd." +msgid "Expected a list of tuples of max length 2 but got {input_type} instead." +msgstr "Verwachtte een lijst van tupels met maximale lengte 2 maar kreeg in plaats daarvan {input_type}." -#: awx/conf/management/commands/migrate_to_database_settings.py:48 -msgid "" -"Skip over settings that would raise an error when commenting/migrating." -msgstr "" -"Sla instellingen over die een fout zouden genereren als ze worden " -"becommentarieerd/gemigreerd." - -#: awx/conf/management/commands/migrate_to_database_settings.py:55 -msgid "Skip commenting out settings in files." -msgstr "Sla het becommentariëren van instellingen in bestanden over." - -#: awx/conf/management/commands/migrate_to_database_settings.py:62 -msgid "Skip migrating and only comment out settings in files." -msgstr "" -"Sla migreren over en maak alleen opmerkingen over de instellingen in " -"bestanden." - -#: awx/conf/management/commands/migrate_to_database_settings.py:68 -msgid "Backup existing settings files with this suffix." -msgstr "" -"Maak een back-up van bestaande instellingenbestanden met dit achtervoegsel." - -#: awx/conf/registry.py:73 awx/conf/tests/unit/test_registry.py:169 -#: awx/conf/tests/unit/test_registry.py:192 -#: awx/conf/tests/unit/test_registry.py:196 -#: awx/conf/tests/unit/test_registry.py:201 -#: awx/conf/tests/unit/test_registry.py:208 +#: awx/conf/registry.py:73 awx/conf/tests/unit/test_registry.py:155 msgid "All" msgstr "Alle" -#: awx/conf/registry.py:74 awx/conf/tests/unit/test_registry.py:170 -#: awx/conf/tests/unit/test_registry.py:193 -#: awx/conf/tests/unit/test_registry.py:197 -#: awx/conf/tests/unit/test_registry.py:202 -#: awx/conf/tests/unit/test_registry.py:209 +#: awx/conf/registry.py:74 awx/conf/tests/unit/test_registry.py:156 msgid "Changed" msgstr "Gewijzigd" -#: awx/conf/registry.py:86 +#: awx/conf/registry.py:82 msgid "User-Defaults" msgstr "Standaardinstellingen voor gebruiker" -#: awx/conf/registry.py:154 +#: awx/conf/registry.py:143 msgid "This value has been set manually in a settings file." msgstr "Deze waarde is handmatig ingesteld in een instellingenbestand." @@ -1721,19 +1696,15 @@ msgstr "Deze waarde is handmatig ingesteld in een instellingenbestand." #: awx/conf/tests/unit/test_registry.py:100 #: awx/conf/tests/unit/test_registry.py:106 #: awx/conf/tests/unit/test_registry.py:126 -#: awx/conf/tests/unit/test_registry.py:140 -#: awx/conf/tests/unit/test_registry.py:146 -#: awx/conf/tests/unit/test_registry.py:159 -#: awx/conf/tests/unit/test_registry.py:171 -#: awx/conf/tests/unit/test_registry.py:180 -#: awx/conf/tests/unit/test_registry.py:198 -#: awx/conf/tests/unit/test_registry.py:210 -#: awx/conf/tests/unit/test_registry.py:219 -#: awx/conf/tests/unit/test_registry.py:225 -#: awx/conf/tests/unit/test_registry.py:237 -#: awx/conf/tests/unit/test_registry.py:245 -#: awx/conf/tests/unit/test_registry.py:288 -#: awx/conf/tests/unit/test_registry.py:306 +#: awx/conf/tests/unit/test_registry.py:132 +#: awx/conf/tests/unit/test_registry.py:145 +#: awx/conf/tests/unit/test_registry.py:157 +#: awx/conf/tests/unit/test_registry.py:166 +#: awx/conf/tests/unit/test_registry.py:172 +#: awx/conf/tests/unit/test_registry.py:184 +#: awx/conf/tests/unit/test_registry.py:191 +#: awx/conf/tests/unit/test_registry.py:233 +#: awx/conf/tests/unit/test_registry.py:251 #: awx/conf/tests/unit/test_settings.py:79 #: awx/conf/tests/unit/test_settings.py:97 #: awx/conf/tests/unit/test_settings.py:112 @@ -1755,156 +1726,153 @@ msgstr "Deze waarde is handmatig ingesteld in een instellingenbestand." #: awx/conf/tests/unit/test_settings.py:398 #: awx/conf/tests/unit/test_settings.py:411 #: awx/conf/tests/unit/test_settings.py:430 -#: awx/conf/tests/unit/test_settings.py:466 awx/main/conf.py:22 -#: awx/main/conf.py:32 awx/main/conf.py:43 awx/main/conf.py:53 -#: awx/main/conf.py:62 awx/main/conf.py:74 awx/main/conf.py:87 -#: awx/main/conf.py:100 awx/main/conf.py:125 +#: awx/conf/tests/unit/test_settings.py:466 awx/main/conf.py:24 +#: awx/main/conf.py:33 awx/main/conf.py:43 awx/main/conf.py:53 +#: awx/main/conf.py:65 awx/main/conf.py:78 awx/main/conf.py:91 +#: awx/main/conf.py:116 awx/main/conf.py:129 awx/main/conf.py:142 +#: awx/main/conf.py:154 awx/main/conf.py:162 awx/main/conf.py:173 +#: awx/main/conf.py:405 awx/main/conf.py:830 awx/main/conf.py:840 +#: awx/main/conf.py:852 msgid "System" msgstr "Systeem" -#: awx/conf/tests/unit/test_registry.py:165 -#: awx/conf/tests/unit/test_registry.py:172 -#: awx/conf/tests/unit/test_registry.py:187 -#: awx/conf/tests/unit/test_registry.py:203 -#: awx/conf/tests/unit/test_registry.py:211 +#: awx/conf/tests/unit/test_registry.py:151 +#: awx/conf/tests/unit/test_registry.py:158 msgid "OtherSystem" msgstr "OtherSystem" -#: awx/conf/views.py:47 +#: awx/conf/views.py:48 msgid "Setting Categories" msgstr "Instellingscategorieën" -#: awx/conf/views.py:71 +#: awx/conf/views.py:70 msgid "Setting Detail" msgstr "Instellingsdetail" -#: awx/conf/views.py:166 +#: awx/conf/views.py:162 msgid "Logging Connectivity Test" msgstr "Connectiviteitstest logboekregistratie" -#: awx/main/access.py:59 +#: awx/main/access.py:66 #, python-format msgid "Required related field %s for permission check." msgstr "Verwant veld %s vereist voor machtigingscontrole." -#: awx/main/access.py:75 +#: awx/main/access.py:82 #, python-format msgid "Bad data found in related field %s." msgstr "Ongeldige gegevens gevonden in gerelateerd veld %s." -#: awx/main/access.py:302 +#: awx/main/access.py:331 msgid "License is missing." msgstr "Licentie ontbreekt." -#: awx/main/access.py:304 +#: awx/main/access.py:333 msgid "License has expired." msgstr "Licentie is verlopen." -#: awx/main/access.py:312 +#: awx/main/access.py:341 #, python-format msgid "License count of %s instances has been reached." msgstr "Het aantal licenties van %s instanties is bereikt." -#: awx/main/access.py:314 +#: awx/main/access.py:343 #, python-format msgid "License count of %s instances has been exceeded." msgstr "Het aantal licenties van %s instanties is overschreden." -#: awx/main/access.py:316 +#: awx/main/access.py:345 msgid "Host count exceeds available instances." msgstr "Het aantal hosts is groter dan het aantal instanties." -#: awx/main/access.py:320 +#: awx/main/access.py:363 awx/main/access.py:372 #, python-format -msgid "Feature %s is not enabled in the active license." -msgstr "De functie %s is niet ingeschakeld in de actieve licentie." - -#: awx/main/access.py:322 -msgid "Features not found in active license." -msgstr "Functies niet gevonden in actieve licentie." +msgid "" +"You have already reached the maximum number of %s hosts allowed for your " +"organization. Contact your System Administrator for assistance." +msgstr "U hebt het maximumaantal van %s hosts dat is toegestaan voor uw organisatie al bereikt. Neem contact op met uw systeembeheerder voor hulp." -#: awx/main/access.py:835 +#: awx/main/access.py:927 msgid "Unable to change inventory on a host." msgstr "Kan inventaris op een host niet wijzigen." -#: awx/main/access.py:852 awx/main/access.py:897 +#: awx/main/access.py:948 awx/main/access.py:990 msgid "Cannot associate two items from different inventories." msgstr "Kan twee items uit verschillende inventarissen niet koppelen." -#: awx/main/access.py:885 +#: awx/main/access.py:978 msgid "Unable to change inventory on a group." msgstr "Kan inventaris van een groep niet wijzigen." -#: awx/main/access.py:1146 +#: awx/main/access.py:1264 msgid "Unable to change organization on a team." msgstr "Kan organisatie van een team niet wijzigen." -#: awx/main/access.py:1163 +#: awx/main/access.py:1280 msgid "The {} role cannot be assigned to a team" msgstr "De rol {} kan niet worden toegewezen aan een team" -#: awx/main/access.py:1165 -msgid "The admin_role for a User cannot be assigned to a team" -msgstr "" -"De admin_role voor een gebruiker kan niet worden toegewezen aan een team" +#: awx/main/access.py:1474 +msgid "Insufficient access to Job Template credentials." +msgstr "Onvoldoende toegang tot taaksjabloongegevens." -#: awx/main/access.py:1531 awx/main/access.py:1965 -msgid "Job was launched with prompts provided by another user." -msgstr "" -"Taak is opgestart met meldingen die aangeleverd zijn door een andere " -"gebruiker." +#: awx/main/access.py:1639 awx/main/access.py:2063 +msgid "Job was launched with secret prompts provided by another user." +msgstr "Taak is opgestart met geheime meldingen die aangeleverd zijn door een andere gebruiker." -#: awx/main/access.py:1551 -msgid "Job has been orphaned from its job template." -msgstr "De taak is verwijderd uit zijn taaksjabloon." +#: awx/main/access.py:1648 +msgid "Job has been orphaned from its job template and organization." +msgstr "De taak is verwijderd uit zijn taaksjabloon en organisatie." -#: awx/main/access.py:1553 -msgid "Job was launched with unknown prompted fields." -msgstr "Taak is opgestart met onbekende invoervelden." +#: awx/main/access.py:1650 +msgid "Job was launched with prompted fields you do not have access to." +msgstr "De taak is gestart met invoervelden waar u geen toegang toe hebt." -#: awx/main/access.py:1555 -msgid "Job was launched with prompted fields." -msgstr "De taak is gestart met invoervelden." +#: awx/main/access.py:1652 +msgid "" +"Job was launched with unknown prompted fields. Organization admin " +"permissions required." +msgstr "De taak is gestart met onbekende invoervelden. Beheerrechten voor de organisatie zijn vereist." + +#: awx/main/access.py:2053 +msgid "Workflow Job was launched with unknown prompts." +msgstr "Workflowtaak is opgestart via onbekende meldingen." -#: awx/main/access.py:1557 -msgid " Organization level permissions required." -msgstr "Organisatieniveaumachtigingen zijn vereist." +#: awx/main/access.py:2065 +msgid "Job was launched with prompts you lack access to." +msgstr "Taak is opgestart via meldingen waar u geen toegang toe hebt." -#: awx/main/access.py:1559 -msgid " You do not have permission to related resources." -msgstr "U hebt geen machtiging voor gerelateerde resources." +#: awx/main/access.py:2067 +msgid "Job was launched with prompts no longer accepted." +msgstr "Taak is opgestart via meldingen die niet langer worden geaccepteerd." -#: awx/main/access.py:1979 +#: awx/main/access.py:2079 msgid "" "You do not have permission to the workflow job resources required for " "relaunch." -msgstr "" -"U hebt geen machtiging voor de workflowtaakresources die vereist zijn om " -"opnieuw op te starten." +msgstr "U hebt geen machtiging voor de workflowtaakresources die vereist zijn om opnieuw op te starten." #: awx/main/apps.py:8 msgid "Main" msgstr "Hoofd" -#: awx/main/conf.py:20 +#: awx/main/conf.py:22 msgid "Enable Activity Stream" msgstr "Activiteitenstroom inschakelen" -#: awx/main/conf.py:21 +#: awx/main/conf.py:23 msgid "Enable capturing activity for the activity stream." msgstr "Vastlegactiviteit voor de activiteitenstroom inschakelen." -#: awx/main/conf.py:30 +#: awx/main/conf.py:31 msgid "Enable Activity Stream for Inventory Sync" msgstr "Activiteitenstroom voor inventarissynchronisatie inschakelen" -#: awx/main/conf.py:31 +#: awx/main/conf.py:32 msgid "" "Enable capturing activity for the activity stream when running inventory " "sync." -msgstr "" -"Vastlegactiviteit voor de activiteitenstroom inschakelen wanneer " -"inventarissynchronisatie wordt uitgevoerd." +msgstr "Vastlegactiviteit voor de activiteitenstroom inschakelen wanneer inventarissynchronisatie wordt uitgevoerd." #: awx/main/conf.py:40 msgid "All Users Visible to Organization Admins" @@ -1914,9 +1882,7 @@ msgstr "Alle gebruikers zichtbaar voor organisatiebeheerders" msgid "" "Controls whether any Organization Admin can view all users and teams, even " "those not associated with their Organization." -msgstr "" -"Regelt of een organisatiebeheerder alle gebruikers en teams kan weergeven, " -"zelfs gebruikers en teams die niet aan hun organisatie zijn gekoppeld." +msgstr "Regelt of een organisatiebeheerder alle gebruikers en teams kan weergeven, zelfs gebruikers en teams die niet aan hun organisatie zijn gekoppeld." #: awx/main/conf.py:50 msgid "Organization Admins Can Manage Users and Teams" @@ -1927,534 +1893,761 @@ msgid "" "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." -msgstr "" -"Regelt of een organisatiebeheerder gemachtigd is om gebruikers en teams aan " -"te maken en te beheren. Als u een LDAP- of SAML-integratie gebruikt, wilt u " -"deze mogelijkheid wellicht uitschakelen." +msgstr "Regelt of een organisatiebeheerder gemachtigd is om gebruikers en teams aan te maken en te beheren. Als u een LDAP- of SAML-integratie gebruikt, wilt u deze mogelijkheid wellicht uitschakelen." -#: awx/main/conf.py:60 -msgid "Enable Administrator Alerts" -msgstr "Beheerderssignalen inschakelen" - -#: awx/main/conf.py:61 -msgid "Email Admin users for system events that may require attention." -msgstr "" -"E-mails naar gebruikers met beheerdersrechten sturen over gebeurtenissen die" -" mogelijk aandacht nodig hebben." - -#: awx/main/conf.py:71 +#: awx/main/conf.py:62 msgid "Base URL of the Tower host" msgstr "Basis-URL van de Tower-host" -#: awx/main/conf.py:72 +#: awx/main/conf.py:63 msgid "" -"This setting is used by services like notifications to render a valid url to" -" the Tower host." -msgstr "" -"Deze instelling wordt gebruikt door services zoals berichten om een geldige " -"URL voor de Tower-host weer te geven." +"This setting is used by services like notifications to render a valid url to " +"the Tower host." +msgstr "Deze instelling wordt gebruikt door services zoals berichten om een geldige URL voor de Tower-host weer te geven." -#: awx/main/conf.py:81 +#: awx/main/conf.py:72 msgid "Remote Host Headers" msgstr "Externe hostheaders" -#: awx/main/conf.py:82 +#: awx/main/conf.py:73 msgid "" "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." -msgstr "" -"HTTP-headers en metasleutels om te zoeken om de naam of het IP-adres van de " -"externe host te bepalen. Voeg aan deze lijst extra items toe, zoals " -"\"HTTP_X_FORWARDED_FOR\", wanneer achter een omgekeerde proxy. Zie de sectie" -" 'proxy-ondersteuning' in de handleiding voor beheerders voor meer " -"informatie." +msgstr "HTTP-headers en metasleutels om te zoeken om de naam of het IP-adres van de externe host te bepalen. Voeg aan deze lijst extra items toe, zoals \"HTTP_X_FORWARDED_FOR\", wanneer achter een omgekeerde proxy. Zie de sectie 'proxy-ondersteuning' in de handleiding voor beheerders voor meer informatie." -#: awx/main/conf.py:94 +#: awx/main/conf.py:85 msgid "Proxy IP Whitelist" msgstr "Whitelist met proxy-IP's" -#: awx/main/conf.py:95 +#: awx/main/conf.py:86 msgid "" "If Tower is behind a reverse proxy/load balancer, use this setting to " "whitelist the proxy IP addresses from which Tower 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')" -msgstr "" -"Als Tower zich achter een omgekeerde proxy/load balancer bevindt, gebruikt u" -" deze instelling om een whitelist te maken met de proxy-IP-adressen vanwaar " -"Tower aangepaste REMOTE_HOST_HEADERS-headerwaarden moet vertrouwen. Als deze" -" instelling een lege lijst is (de standaardinstelling), dan worden de door " -"REMOTE_HOST_HEADERS opgegeven headers onvoorwaardelijk vertrouwd')" +msgstr "Als Tower zich achter een omgekeerde proxy/load balancer bevindt, gebruikt u deze instelling om een whitelist te maken met de proxy-IP-adressen vanwaar Tower aangepaste REMOTE_HOST_HEADERS-headerwaarden moet vertrouwen. Als deze instelling een lege lijst is (de standaardinstelling), dan worden de door REMOTE_HOST_HEADERS opgegeven headers onvoorwaardelijk vertrouwd')" -#: awx/main/conf.py:121 +#: awx/main/conf.py:112 msgid "License" msgstr "Licentie" -#: awx/main/conf.py:122 +#: awx/main/conf.py:113 msgid "" -"The license controls which features and functionality are enabled. Use " -"/api/v1/config/ to update or change the license." -msgstr "" -"De licentie bepaalt welke kenmerken en functionaliteiten zijn ingeschakeld. " -"Gebruik /api/v1/config/ om de licentie bij te werken of te wijzigen." +"The license controls which features and functionality are enabled. Use /api/" +"v2/config/ to update or change the license." +msgstr "De licentie bepaalt welke functies en functionaliteiten zijn ingeschakeld. Gebruik /api/v2/config/ om de licentie bij te werken of te wijzigen." + +#: awx/main/conf.py:127 +msgid "Red Hat customer username" +msgstr "Red Hat-gebruikersnaam klant" + +#: awx/main/conf.py:128 +msgid "" +"This username is used to retrieve license information and to send Automation " +"Analytics" +msgstr "Deze gebruikersnaam wordt gebruikt om licentie-informatie op te halen en om een automatiseringsanalyse te versturen" -#: awx/main/conf.py:132 +#: awx/main/conf.py:140 +msgid "Red Hat customer password" +msgstr "Red Hat klantwachtwoord" + +#: awx/main/conf.py:141 +msgid "" +"This password is used to retrieve license information and to send Automation " +"Analytics" +msgstr "Dit wachtwoord wordt gebruikt om licentie-informatie op te halen en om een automatiseringsanalyse te sturen" + +#: awx/main/conf.py:152 +msgid "Automation Analytics upload URL." +msgstr "URL automatiseringsanalyse uploaden." + +#: awx/main/conf.py:153 +msgid "" +"This setting is used to to configure data collection for the Automation " +"Analytics dashboard" +msgstr "Deze instelling wordt gebruikt om de gegevensverzameling voor het automatiseringsanalysedashboard te configureren" + +#: awx/main/conf.py:161 +msgid "Unique identifier for an AWX/Tower installation" +msgstr "Unieke identificatiecode voor installatie van een AWX/Tower" + +#: awx/main/conf.py:170 +msgid "Custom virtual environment paths" +msgstr "Paden voor aangepaste virtuele omgeving" + +#: awx/main/conf.py:171 +msgid "" +"Paths where Tower will look for custom virtual environments (in addition to /" +"var/lib/awx/venv/). Enter one path per line." +msgstr "Paden waarmee Tower naar aangepaste virtuele omgevingen zoekt (behalve /var/lib/awx/venv/). Voer één pad per regel in." + +#: awx/main/conf.py:181 msgid "Ansible Modules Allowed for Ad Hoc Jobs" msgstr "Ansible-modules toegestaan voor ad-hoctaken" -#: awx/main/conf.py:133 +#: awx/main/conf.py:182 msgid "List of modules allowed to be used by ad-hoc jobs." msgstr "Lijst met modules toegestaan voor gebruik met ad-hoctaken." -#: awx/main/conf.py:134 awx/main/conf.py:156 awx/main/conf.py:165 -#: awx/main/conf.py:176 awx/main/conf.py:186 awx/main/conf.py:196 -#: awx/main/conf.py:206 awx/main/conf.py:217 awx/main/conf.py:229 -#: awx/main/conf.py:241 awx/main/conf.py:254 awx/main/conf.py:266 -#: awx/main/conf.py:276 awx/main/conf.py:287 awx/main/conf.py:298 -#: awx/main/conf.py:308 awx/main/conf.py:318 awx/main/conf.py:330 -#: awx/main/conf.py:342 awx/main/conf.py:354 awx/main/conf.py:368 +#: awx/main/conf.py:183 awx/main/conf.py:205 awx/main/conf.py:214 +#: awx/main/conf.py:225 awx/main/conf.py:235 awx/main/conf.py:245 +#: awx/main/conf.py:256 awx/main/conf.py:267 awx/main/conf.py:278 +#: awx/main/conf.py:290 awx/main/conf.py:299 awx/main/conf.py:312 +#: awx/main/conf.py:325 awx/main/conf.py:337 awx/main/conf.py:348 +#: awx/main/conf.py:359 awx/main/conf.py:371 awx/main/conf.py:383 +#: awx/main/conf.py:394 awx/main/conf.py:414 awx/main/conf.py:424 +#: awx/main/conf.py:434 awx/main/conf.py:450 awx/main/conf.py:463 +#: awx/main/conf.py:477 awx/main/conf.py:491 awx/main/conf.py:503 +#: awx/main/conf.py:513 awx/main/conf.py:524 awx/main/conf.py:534 +#: awx/main/conf.py:545 awx/main/conf.py:555 awx/main/conf.py:565 +#: awx/main/conf.py:577 awx/main/conf.py:589 awx/main/conf.py:601 +#: awx/main/conf.py:615 awx/main/conf.py:627 msgid "Jobs" msgstr "Taken" -#: awx/main/conf.py:143 +#: awx/main/conf.py:192 msgid "Always" msgstr "Altijd" -#: awx/main/conf.py:144 +#: awx/main/conf.py:193 msgid "Never" msgstr "Nooit" -#: awx/main/conf.py:145 +#: awx/main/conf.py:194 msgid "Only On Job Template Definitions" msgstr "Alleen volgens definities taaksjabloon" -#: awx/main/conf.py:148 +#: awx/main/conf.py:197 msgid "When can extra variables contain Jinja templates?" msgstr "Wanneer kunnen extra variabelen Jinja-sjablonen bevatten?" -#: awx/main/conf.py:150 +#: awx/main/conf.py:199 msgid "" "Ansible allows variable substitution via the Jinja2 templating language for " "--extra-vars. This poses a potential security risk where Tower 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\"." -msgstr "" -"Ansible maakt het mogelijk variabelen te vervangen door --extra-vars via de " -"Jinja2-sjabloontaal. Dit brengt een mogelijk veiligheidsrisico met zich mee," -" omdat Tower-gebruikers die extra variabelen kunnen specificeren wanneer een" -" taak wordt opgestart, in staat zijn Jinja2-sjablonen te gebruiken om " -"willekeurige Python uit te voeren. Wij raden u aan deze waarde in te stellen" -" op 'sjabloon' of 'nooit'." - -#: awx/main/conf.py:163 +"templates to run arbitrary Python. It is recommended that this value be set " +"to \"template\" or \"never\"." +msgstr "Ansible maakt het mogelijk variabelen te vervangen door --extra-vars via de Jinja2-sjabloontaal. Dit brengt een mogelijk veiligheidsrisico met zich mee, omdat Tower-gebruikers die extra variabelen kunnen specificeren wanneer een taak wordt opgestart, in staat zijn Jinja2-sjablonen te gebruiken om willekeurige Python uit te voeren. Wij raden u aan deze waarde in te stellen op 'sjabloon' of 'nooit'." + +#: awx/main/conf.py:212 msgid "Enable job isolation" msgstr "Taakisolatie inschakelen" -#: awx/main/conf.py:164 +#: awx/main/conf.py:213 msgid "" "Isolates an Ansible job from protected parts of the system to prevent " "exposing sensitive information." -msgstr "" -"Isoleert een Ansible-taak van beschermde gedeeltes van het systeem om " -"blootstelling van gevoelige informatie te voorkomen." +msgstr "Isoleert een Ansible-taak van beschermde gedeeltes van het systeem om blootstelling van gevoelige informatie te voorkomen." -#: awx/main/conf.py:172 +#: awx/main/conf.py:221 msgid "Job execution path" msgstr "Taakuitvoerpad" -#: awx/main/conf.py:173 +#: awx/main/conf.py:222 msgid "" "The directory in which Tower will create new temporary directories for job " "execution and isolation (such as credential files and custom inventory " "scripts)." -msgstr "" -"De map waarin Tower nieuwe tijdelijke mappen maakt voor de uitvoering en " -"isolatie van taken (zoals referentiebestanden en aangepaste " -"inventarisscripts)." +msgstr "De map waarin Tower nieuwe tijdelijke mappen maakt voor de uitvoering en isolatie van taken (zoals referentiebestanden en aangepaste inventarisscripts)." -#: awx/main/conf.py:184 +#: awx/main/conf.py:233 msgid "Paths to hide from isolated jobs" msgstr "Paden die moeten worden verborgen voor geïsoleerde taken" -#: awx/main/conf.py:185 +#: awx/main/conf.py:234 msgid "" "Additional paths to hide from isolated processes. Enter one path per line." -msgstr "" -"Extra paden die moeten worden verborgen voor geïsoleerde processen. Geef één" -" pad per regel op." +msgstr "Extra paden die moeten worden verborgen voor geïsoleerde processen. Geef één pad per regel op." -#: awx/main/conf.py:194 +#: awx/main/conf.py:243 msgid "Paths to expose to isolated jobs" msgstr "Paden die kunnen worden blootgesteld aan geïsoleerde taken" -#: awx/main/conf.py:195 +#: awx/main/conf.py:244 msgid "" "Whitelist of paths that would otherwise be hidden to expose to isolated " "jobs. Enter one path per line." -msgstr "" -"Whitelist met paden die anders zouden zijn verborgen voor blootstelling aan " -"geïsoleerde taken. Geef één pad per regel op." +msgstr "Whitelist met paden die anders zouden zijn verborgen voor blootstelling aan geïsoleerde taken. Geef één pad per regel op." + +#: awx/main/conf.py:254 +msgid "Verbosity level for isolated node management tasks" +msgstr "Verbositeitsniveau voor geïsoleerde beheertaken voor knooppunten" + +#: awx/main/conf.py:255 +msgid "" +"This can be raised to aid in debugging connection issues for isolated task " +"execution" +msgstr "Dit kan gegenereerd worden om te helpen bij het oplossen van verbindingsfouten voor geïsoleerde taakuitvoering" -#: awx/main/conf.py:204 +#: awx/main/conf.py:265 msgid "Isolated status check interval" msgstr "Controle-interval voor isolatiestatus" -#: awx/main/conf.py:205 +#: awx/main/conf.py:266 msgid "" "The number of seconds to sleep between status checks for jobs running on " "isolated instances." -msgstr "" -"Het aantal seconden rust tussen statuscontroles voor taken die worden " -"uitgevoerd op geïsoleerde instanties." +msgstr "Het aantal seconden rust tussen statuscontroles voor taken die worden uitgevoerd op geïsoleerde instanties." -#: awx/main/conf.py:214 +#: awx/main/conf.py:275 msgid "Isolated launch timeout" msgstr "Time-out geïsoleerd opstartproces" -#: awx/main/conf.py:215 +#: awx/main/conf.py:276 msgid "" "The timeout (in seconds) for launching jobs on isolated instances. This " "includes the time needed to copy source control files (playbooks) to the " "isolated instance." -msgstr "" -"De time-out (in seconden) voor het opstarten van taken in geïsoleerde " -"instanties. Dit is met inbegrip van de tijd vereist om de controlebestanden " -"van de bron (draaiboeken) te kopiëren naar de geïsoleerde instantie." +msgstr "De time-out (in seconden) voor het opstarten van taken in geïsoleerde instanties. Dit is met inbegrip van de tijd vereist om de controlebestanden van de bron (draaiboeken) te kopiëren naar de geïsoleerde instantie." -#: awx/main/conf.py:226 +#: awx/main/conf.py:287 msgid "Isolated connection timeout" msgstr "Time-out geïsoleerde verbinding" -#: awx/main/conf.py:227 +#: awx/main/conf.py:288 msgid "" "Ansible SSH connection timeout (in seconds) to use when communicating with " "isolated instances. Value should be substantially greater than expected " "network latency." -msgstr "" -"Time-out van Ansible SSH-verbinding (in seconden) voor gebruik tijdens de " -"communicatie met geïsoleerde instanties. De waarde moet aanzienlijk groter " -"zijn dan de verwachte netwerklatentie." +msgstr "Time-out van Ansible SSH-verbinding (in seconden) voor gebruik tijdens de communicatie met geïsoleerde instanties. De waarde moet aanzienlijk groter zijn dan de verwachte netwerklatentie." + +#: awx/main/conf.py:297 +msgid "Isolated host key checking" +msgstr "Controle van de geïsoleerde hostsleutel" -#: awx/main/conf.py:237 +#: awx/main/conf.py:298 +msgid "" +"When set to True, AWX will enforce strict host key checking for " +"communication with isolated nodes." +msgstr "Wanneer deze ingesteld wordt op True, zal AWX een strikte controle van de hostsleutel voor communicatie met geïsoleerde knooppunten uitvoeren." + +#: awx/main/conf.py:308 msgid "Generate RSA keys for isolated instances" msgstr "RSA-sleutels aanmaken voor afzonderlijke instanties" -#: awx/main/conf.py:238 +#: awx/main/conf.py:309 msgid "" "If set, a random RSA key will be generated and distributed to isolated " "instances. To disable this behavior and manage authentication for isolated " "instances outside of Tower, disable this setting." -msgstr "" -"Als deze instelling ingeschakeld is, wordt er bij afzonderlijke instanties " -"een willekeurige RSA-sleutel aangemaakt en uitgedeeld. Om dit gedrag uit te " -"schakelen en authenticatie voor afzonderlijke instanties buiten Tower te " -"beheren kunt u deze instelling uitschakelen." +msgstr "Als deze instelling ingeschakeld is, wordt er bij afzonderlijke instanties een willekeurige RSA-sleutel aangemaakt en uitgedeeld. Om dit gedrag uit te schakelen en authenticatie voor afzonderlijke instanties buiten Tower te beheren kunt u deze instelling uitschakelen." -#: awx/main/conf.py:252 awx/main/conf.py:253 +#: awx/main/conf.py:323 awx/main/conf.py:324 msgid "The RSA private key for SSH traffic to isolated instances" msgstr "De RSA-privésleutel voor SSH-verkeer naar geïsoleerde instanties" -#: awx/main/conf.py:264 awx/main/conf.py:265 +#: awx/main/conf.py:335 awx/main/conf.py:336 msgid "The RSA public key for SSH traffic to isolated instances" msgstr "De openbare RSA-sleutel voor SSH-verkeer naar geïsoleerde instanties" -#: awx/main/conf.py:274 +#: awx/main/conf.py:345 +msgid "Enable detailed resource profiling on all playbook runs" +msgstr "Maak gedetailleerde bronprofilering mogelijk op alle draaiboekuitvoeringen" + +#: awx/main/conf.py:346 +msgid "" +"If set, detailed resource profiling data will be collected on all jobs. This " +"data can be gathered with `sosreport`." +msgstr "Gedetailleerde gegevens over de profilering van de bronnen worden, indien ingesteld, verzameld voor alle banen. Deze gegevens kunnen met `sosreport` worden verzameld." + +#: awx/main/conf.py:356 +msgid "Interval (in seconds) between polls for cpu usage." +msgstr "Interval (in seconden) tussen polls voor CPU-gebruik." + +#: awx/main/conf.py:357 +msgid "" +"Interval (in seconds) between polls for cpu usage. Setting this lower than " +"the default will affect playbook performance." +msgstr "Interval (in seconden) tussen polls voor CPU-gebruik. Als u dit lager instelt dan de standaardinstelling heeft dit invloed op de draaiboekprestaties." + +#: awx/main/conf.py:368 +msgid "Interval (in seconds) between polls for memory usage." +msgstr "Interval (in seconden) tussen polls voor geheugengebruik." + +#: awx/main/conf.py:369 +msgid "" +"Interval (in seconds) between polls for memory usage. Setting this lower " +"than the default will affect playbook performance." +msgstr "Interval (in seconden) tussen polls voor geheugengebruik. Als u dit lager instelt dan de standaardinstelling, heeft dit invloed op de draaiboekprestaties." + +#: awx/main/conf.py:380 +msgid "Interval (in seconds) between polls for PID count." +msgstr "Interval (in seconden) tussen de peilingen voor de PID-telling." + +#: awx/main/conf.py:381 +msgid "" +"Interval (in seconds) between polls for PID count. Setting this lower than " +"the default will affect playbook performance." +msgstr "Interval (in seconden) tussen de peilingen voor de PID-telling. Als u dit lager instelt dan de standaardinstelling, heeft dit invloed op de draaiboekprestaties." + +#: awx/main/conf.py:392 msgid "Extra Environment Variables" msgstr "Extra omgevingsvariabelen" -#: awx/main/conf.py:275 +#: awx/main/conf.py:393 msgid "" "Additional environment variables set for playbook runs, inventory updates, " "project updates, and notification sending." -msgstr "" -"Extra omgevingsvariabelen ingesteld voor draaiboekuitvoeringen, " -"inventarisupdates, projectupdates en berichtverzending." +msgstr "Extra omgevingsvariabelen ingesteld voor draaiboekuitvoeringen, inventarisupdates, projectupdates en berichtverzending." + +#: awx/main/conf.py:403 +msgid "Gather data for Automation Analytics" +msgstr "Gegevens verzamelen voor automatiseringsanalyse" + +#: awx/main/conf.py:404 +msgid "Enables Tower to gather data on automation and send it to Red Hat." +msgstr "Hiermee kan Tower automatiseringsgegevens verzamelen en naar Red Hat versturen." + +#: awx/main/conf.py:412 +msgid "Run Project Updates With Higher Verbosity" +msgstr "Project-updates met een hogere spraaklengte uitvoeren" + +#: awx/main/conf.py:413 +msgid "" +"Adds the CLI -vvv flag to ansible-playbook runs of project_update.yml used " +"for project updates." +msgstr "Voegt de CLI -vvv-vlag toe aan een draaiboekuitvoering van project_update.yml die voor projectupdates wordt gebruikt." + +#: awx/main/conf.py:422 +msgid "Enable Role Download" +msgstr "Downloaden van rol inschakelen" + +#: awx/main/conf.py:423 +msgid "" +"Allows roles to be dynamically downloaded from a requirements.yml file for " +"SCM projects." +msgstr "Toestaan dat rollen dynamisch gedownload worden vanuit een requirements.yml-bestand voor SCM-projecten." + +#: awx/main/conf.py:432 +msgid "Enable Collection(s) Download" +msgstr "Download van collectie(s) inschakelen" + +#: awx/main/conf.py:433 +msgid "" +"Allows collections to be dynamically downloaded from a requirements.yml file " +"for SCM projects." +msgstr "Toestaan dat collecties dynamisch gedownload worden vanuit een requirements.yml-bestand voor SCM-projecten." + +#: awx/main/conf.py:443 +msgid "Primary Galaxy Server URL" +msgstr "Primary Galaxy Server-URL" + +#: awx/main/conf.py:445 +msgid "" +"For organizations that run their own Galaxy service, this gives the option " +"to specify a host as the primary galaxy server. Requirements will be " +"downloaded from the primary if the specific role or collection is available " +"there. If the content is not avilable in the primary, or if this field is " +"left blank, it will default to galaxy.ansible.com." +msgstr "Voor organisaties die hun eigen Galaxy-service draaien, biedt dit de mogelijkheid om een host te specificeren als de primaire galaxy-server. Vereisten worden gedownload van de primaire server als de specifieke rol of collectie daar beschikbaar is. Als de inhoud niet beschikbaar is in de primaire server, of als dit veld wordt leeg gelaten, zal deze standaard geplaatst worden in galaxy.ansible.com." + +#: awx/main/conf.py:459 +msgid "Primary Galaxy Server Username" +msgstr "Gebruikersnaam van de primaire Galaxy-server" + +#: awx/main/conf.py:460 +msgid "" +"For using a galaxy server at higher precedence than the public Ansible " +"Galaxy. The username to use for basic authentication against the Galaxy " +"instance, this is mutually exclusive with PRIMARY_GALAXY_TOKEN." +msgstr "Voor het gebruik van een galaxy-server met een hogere prioriteit dan de publieke Ansible Galaxy. De gebruikersnaam voor de basisauthenticatie m.b.t. de Galaxy-instantie en PRIMARY_GALAXY_TOKEN sluiten elkaar uit." + +#: awx/main/conf.py:473 +msgid "Primary Galaxy Server Password" +msgstr "Wachtwoord van de primaire Galaxy-server" -#: awx/main/conf.py:285 +#: awx/main/conf.py:474 +msgid "" +"For using a galaxy server at higher precedence than the public Ansible " +"Galaxy. The password to use for basic authentication against the Galaxy " +"instance, this is mutually exclusive with PRIMARY_GALAXY_TOKEN." +msgstr "Voor het gebruik van een galaxy-server met een hogere prioriteit dan de publieke Ansible Galaxy. Het wachtwoord voor de basisverificatie tegen de Galaxy-instantie en PRIMARY_GALAXY_TOKEN sluiten elkaar uit." + +#: awx/main/conf.py:487 +msgid "Primary Galaxy Server Token" +msgstr "Primaire Galaxy-server token" + +#: awx/main/conf.py:488 +msgid "" +"For using a galaxy server at higher precedence than the public Ansible " +"Galaxy. The token to use for connecting with the Galaxy instance, this is " +"mutually exclusive with corresponding username and password settings." +msgstr "Voor het gebruik van een galaxy-server met een hogere prioriteit dan de publieke Ansible Galaxy. De token voor het verbinden met de Galaxy-instantie en de bijbehorende gebruikersnaam- en wachtwoordinstellingen sluiten elkaar uit." + +#: awx/main/conf.py:500 +msgid "Primary Galaxy Authentication URL" +msgstr "Primaire Galaxy-authenticatie-URL" + +#: awx/main/conf.py:501 +msgid "" +"For using a galaxy server at higher precedence than the public Ansible " +"Galaxy. The token_endpoint of a Keycloak server." +msgstr "Voor het gebruik van een galaxy-server met een hogere prioriteit dan de publieke Ansible Galaxy. Het token_endpoint van een Keycloak-server." + +#: awx/main/conf.py:511 +msgid "Allow Access to Public Galaxy" +msgstr "Toegang verlenen tot de openbare galaxy" + +#: awx/main/conf.py:512 +msgid "" +"Allow or deny access to the public Ansible Galaxy during project updates." +msgstr "Toegang tot het publieke Ansible Galaxy toestaan of weigeren tijdens projectupdates." + +#: awx/main/conf.py:521 +msgid "Ignore Ansible Galaxy SSL Certificate Verification" +msgstr "Ansible Galaxy SSL Certificaatverificatie negeren" + +#: awx/main/conf.py:522 +msgid "" +"If set to true, certificate validation will not be done wheninstalling " +"content from any Galaxy server." +msgstr "Indien deze ingesteld is op true, zal de certificaatvalidatie niet worden uitgevoerd bij de installatie van inhoud vanaf een Galaxy-server." + +#: awx/main/conf.py:532 msgid "Standard Output Maximum Display Size" msgstr "Maximale weergavegrootte voor standaardoutput" -#: awx/main/conf.py:286 +#: awx/main/conf.py:533 msgid "" "Maximum Size of Standard Output in bytes to display before requiring the " "output be downloaded." -msgstr "" -"De maximale weergavegrootte van standaardoutput in bytes voordat wordt " -"vereist dat de output wordt gedownload." +msgstr "De maximale weergavegrootte van standaardoutput in bytes voordat wordt vereist dat de output wordt gedownload." -#: awx/main/conf.py:295 +#: awx/main/conf.py:542 msgid "Job Event Standard Output Maximum Display Size" msgstr "Maximale weergavegrootte voor standaardoutput van taakgebeurtenissen" -#: awx/main/conf.py:297 +#: awx/main/conf.py:544 msgid "" "Maximum Size of Standard Output in bytes to display for a single job or ad " "hoc command event. `stdout` will end with `…` when truncated." -msgstr "" -"De maximale weergavegrootte van standaardoutput in bytes voor één taak of " -"ad-hoc-opdrachtgebeurtenis. `stdout` eindigt op `…` indien afgekapt." +msgstr "De maximale weergavegrootte van standaardoutput in bytes voor één taak of ad-hoc-opdrachtgebeurtenis. `stdout` eindigt op `…` indien afgekapt." -#: awx/main/conf.py:306 +#: awx/main/conf.py:553 msgid "Maximum Scheduled Jobs" msgstr "Maximumaantal geplande taken" -#: awx/main/conf.py:307 +#: awx/main/conf.py:554 msgid "" "Maximum number of the same job template that can be waiting to run when " "launching from a schedule before no more are created." -msgstr "" -"Het maximumaantal van dezelfde sjabloon dat kan wachten op uitvoering " -"wanneer wordt gestart vanuit een schema voordat er geen andere meer kunnen " -"worden gemaakt." +msgstr "Het maximumaantal van dezelfde sjabloon dat kan wachten op uitvoering wanneer wordt gestart vanuit een schema voordat er geen andere meer kunnen worden gemaakt." -#: awx/main/conf.py:316 +#: awx/main/conf.py:563 msgid "Ansible Callback Plugins" msgstr "Ansible-terugkoppelingsplugins" -#: awx/main/conf.py:317 +#: awx/main/conf.py:564 msgid "" "List of paths to search for extra callback plugins to be used when running " "jobs. Enter one path per line." -msgstr "" -"Lijst met paden om te zoeken naar extra terugkoppelingsplugins voor gebruik " -"bij het uitvoeren van taken. Geef één pad per regel op." +msgstr "Lijst met paden om te zoeken naar extra terugkoppelingsplugins voor gebruik bij het uitvoeren van taken. Geef één pad per regel op." -#: awx/main/conf.py:327 +#: awx/main/conf.py:574 msgid "Default Job Timeout" msgstr "Standaardtime-out voor taken" -#: awx/main/conf.py:328 +#: awx/main/conf.py:575 msgid "" "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." -msgstr "" -"Maximale tijd in seconden dat de uitvoering van taken mag duren. Gebruik een" -" waarde van 0 om aan te geven dat geen time-out mag worden opgelegd. Als er " -"in een individuele taaksjabloon een time-out is ingesteld, heeft deze " -"voorrang." +msgstr "Maximale tijd in seconden dat de uitvoering van taken mag duren. Gebruik een waarde van 0 om aan te geven dat geen time-out mag worden opgelegd. Als er in een individuele taaksjabloon een time-out is ingesteld, heeft deze voorrang." -#: awx/main/conf.py:339 +#: awx/main/conf.py:586 msgid "Default Inventory Update Timeout" msgstr "Standaardtime-out voor inventarisupdates" -#: awx/main/conf.py:340 +#: awx/main/conf.py:587 msgid "" -"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 " +"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." -msgstr "" -"Maximale tijd in seconden die inventarisupdates mogen duren. Gebruik een " -"waarde van 0 om aan te geven dat geen time-out mag worden opgelegd. Als er " -"in een individuele inventarisbron een time-out is ingesteld, heeft deze " -"voorrang." +msgstr "Maximale tijd in seconden die inventarisupdates mogen duren. Gebruik een waarde van 0 om aan te geven dat geen time-out mag worden opgelegd. Als er in een individuele inventarisbron een time-out is ingesteld, heeft deze voorrang." -#: awx/main/conf.py:351 +#: awx/main/conf.py:598 msgid "Default Project Update Timeout" msgstr "Standaardtime-out voor projectupdates" -#: awx/main/conf.py:352 +#: awx/main/conf.py:599 msgid "" "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." -msgstr "" -"Maximale tijd in seconden die projectupdates mogen duren. Gebruik een waarde" -" van 0 om aan te geven dat geen time-out mag worden opgelegd. Als er in een " -"individueel project een time-out is ingesteld, heeft deze voorrang." +msgstr "Maximale tijd in seconden die projectupdates mogen duren. Gebruik een waarde van 0 om aan te geven dat geen time-out mag worden opgelegd. Als er in een individueel project een time-out is ingesteld, heeft deze voorrang." -#: awx/main/conf.py:363 +#: awx/main/conf.py:610 msgid "Per-Host Ansible Fact Cache Timeout" msgstr "Time-out voor feitcache per-host Ansible" -#: awx/main/conf.py:364 +#: awx/main/conf.py:611 msgid "" "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 " +"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." -msgstr "" -"Maximale tijd in seconden dat opgeslagen Ansible-feiten als geldig worden " -"beschouwd sinds ze voor het laatst zijn gewijzigd. Alleen geldige, niet-" -"verlopen feiten zijn toegankelijk voor een draaiboek. Merk op dat dit geen " -"invloed heeft op de verwijdering van ansible_facts uit de database. Gebruik " -"een waarde van 0 om aan te geven dat er geen time-out mag worden opgelegd." +msgstr "Maximale tijd in seconden dat opgeslagen Ansible-feiten als geldig worden beschouwd sinds ze voor het laatst zijn gewijzigd. Alleen geldige, niet-verlopen feiten zijn toegankelijk voor een draaiboek. Merk op dat dit geen invloed heeft op de verwijdering van ansible_facts uit de database. Gebruik een waarde van 0 om aan te geven dat er geen time-out mag worden opgelegd." + +#: awx/main/conf.py:624 +msgid "Maximum number of forks per job." +msgstr "Maximaal aantal vorken per opdracht." -#: awx/main/conf.py:377 +#: awx/main/conf.py:625 +msgid "" +"Saving a Job Template with more than this number of forks will result in an " +"error. When set to 0, no limit is applied." +msgstr "Het opslaan van een taaksjabloon met meer vorken zal resulteren in een fout. Als dit is ingesteld op 0, wordt er geen limiet toegepast." + +#: awx/main/conf.py:636 msgid "Logging Aggregator" msgstr "Aggregator logboekregistraties" -#: awx/main/conf.py:378 +#: awx/main/conf.py:637 msgid "Hostname/IP where external logs will be sent to." msgstr "Hostnaam/IP-adres waarnaar externe logboeken worden verzonden." -#: awx/main/conf.py:379 awx/main/conf.py:390 awx/main/conf.py:402 -#: awx/main/conf.py:412 awx/main/conf.py:424 awx/main/conf.py:439 -#: awx/main/conf.py:451 awx/main/conf.py:460 awx/main/conf.py:470 -#: awx/main/conf.py:482 awx/main/conf.py:493 awx/main/conf.py:505 -#: awx/main/conf.py:518 +#: awx/main/conf.py:638 awx/main/conf.py:649 awx/main/conf.py:661 +#: awx/main/conf.py:671 awx/main/conf.py:683 awx/main/conf.py:698 +#: awx/main/conf.py:710 awx/main/conf.py:719 awx/main/conf.py:729 +#: awx/main/conf.py:741 awx/main/conf.py:752 awx/main/conf.py:764 +#: awx/main/conf.py:777 awx/main/conf.py:787 awx/main/conf.py:799 +#: awx/main/conf.py:810 awx/main/conf.py:820 msgid "Logging" msgstr "Logboekregistratie" -#: awx/main/conf.py:387 +#: awx/main/conf.py:646 msgid "Logging Aggregator Port" -msgstr "Aggregator logboekregistraties" +msgstr "Aggregator Port logboekregistraties" -#: awx/main/conf.py:388 +#: awx/main/conf.py:647 msgid "" "Port on Logging Aggregator to send logs to (if required and not provided in " "Logging Aggregator)." -msgstr "" -"Poort van aggregator logboekregistraties waarnaar logboeken worden verzonden" -" (indien vereist en niet geleverd in de aggregator logboekregistraties)." +msgstr "Poort van Aggregator logboekregistraties waarnaar logboeken worden verzonden (indien vereist en niet geleverd in de Aggregator logboekregistraties)." -#: awx/main/conf.py:400 +#: awx/main/conf.py:659 msgid "Logging Aggregator Type" msgstr "Type aggregator logboekregistraties" -#: awx/main/conf.py:401 +#: awx/main/conf.py:660 msgid "Format messages for the chosen log aggregator." msgstr "Maak berichten op voor de gekozen log aggregator." -#: awx/main/conf.py:410 +#: awx/main/conf.py:669 msgid "Logging Aggregator Username" msgstr "Gebruikersnaam aggregator logboekregistraties" -#: awx/main/conf.py:411 -msgid "Username for external log aggregator (if required)." -msgstr "Gebruikersnaam voor externe log aggregator (indien vereist)" +#: awx/main/conf.py:670 +msgid "Username for external log aggregator (if required; HTTP/s only)." +msgstr "Gebruikersnaam voor externe logboekaggregator (indien vereist; alleen HTTP/s)." -#: awx/main/conf.py:422 +#: awx/main/conf.py:681 msgid "Logging Aggregator Password/Token" msgstr "Wachtwoord/token voor aggregator logboekregistraties" -#: awx/main/conf.py:423 +#: awx/main/conf.py:682 msgid "" -"Password or authentication token for external log aggregator (if required)." -msgstr "" -"Wachtwoord of authenticatietoken voor externe log aggregator(indien " -"vereist)." +"Password or authentication token for external log aggregator (if required; " +"HTTP/s only)." +msgstr "Wachtwoord of authenticatietoken voor externe logboekaggregator (indien vereist; alleen HTTP/s)." -#: awx/main/conf.py:432 +#: awx/main/conf.py:691 msgid "Loggers Sending Data to Log Aggregator Form" -msgstr "" -"Logboekverzamelingen die gegevens verzenden naar log aggregator-formulier" +msgstr "Logboekverzamelingen die gegevens verzenden naar log aggregator-formulier" -#: awx/main/conf.py:433 +#: awx/main/conf.py:692 msgid "" -"List of loggers that will send HTTP logs to the collector, these can include any or all of: \n" +"List of loggers that will send HTTP logs to the collector, these can include " +"any or all of: \n" "awx - service logs\n" "activity_stream - activity stream records\n" "job_events - callback data from Ansible job events\n" "system_tracking - facts gathered from scan jobs." -msgstr "" -"Lijst met logboekverzamelingen die HTTP-logboeken verzenden naar de verzamelaar. Deze kunnen bestaan uit een of meer van de volgende: \n" +msgstr "Lijst met logboekverzamelingen die HTTP-logboeken verzenden naar de verzamelaar. Deze kunnen bestaan uit een of meer van de volgende: \n" "awx - servicelogboeken\n" "activity_stream - records activiteitenstroom\n" "job_events - terugkoppelgegevens van Ansible-taakgebeurtenissen\n" "system_tracking - feiten verzameld uit scantaken." -#: awx/main/conf.py:446 +#: awx/main/conf.py:705 msgid "Log System Tracking Facts Individually" msgstr "Logboeksysteem dat feiten individueel bijhoudt" -#: awx/main/conf.py:447 +#: awx/main/conf.py:706 msgid "" "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." -msgstr "" -"Indien ingesteld, worden systeemtrackingfeiten verzonden voor alle " -"pakketten, services of andere items aangetroffen in een scan, wat aan " -"zoekquery's meer gedetailleerdheid verleent. Indien niet ingesteld, worden " -"feiten verzonden als één woordenlijst, waardoor feiten sneller kunnen worden" -" verwerkt." +msgstr "Indien ingesteld, worden systeemtrackingfeiten verzonden voor alle pakketten, services of andere items aangetroffen in een scan, wat aan zoekquery's meer gedetailleerdheid verleent. Indien niet ingesteld, worden feiten verzonden als één woordenlijst, waardoor feiten sneller kunnen worden verwerkt." -#: awx/main/conf.py:458 +#: awx/main/conf.py:717 msgid "Enable External Logging" msgstr "Externe logboekregistratie inschakelen" -#: awx/main/conf.py:459 +#: awx/main/conf.py:718 msgid "Enable sending logs to external log aggregator." -msgstr "" -"Schakel de verzending in van logboeken naar een externe log aggregator." +msgstr "Schakel de verzending in van logboeken naar een externe log aggregator." -#: awx/main/conf.py:468 +#: awx/main/conf.py:727 msgid "Cluster-wide Tower unique identifier." msgstr "Clusterbrede, unieke Tower-id" -#: awx/main/conf.py:469 +#: awx/main/conf.py:728 msgid "Useful to uniquely identify Tower instances." msgstr "Handig om Tower-instanties uniek te identificeren." -#: awx/main/conf.py:478 +#: awx/main/conf.py:737 msgid "Logging Aggregator Protocol" msgstr "Protocol aggregator logboekregistraties" -#: awx/main/conf.py:479 +#: awx/main/conf.py:738 msgid "" "Protocol used to communicate with log aggregator. HTTPS/HTTP assumes HTTPS " "unless http:// is explicitly used in the Logging Aggregator hostname." -msgstr "" -"Protocol gebruikt om te communiceren met de log aggregator. HTTPS/HTTP " -"veronderstelt HTTPS tenzij http:// expliciet wordt gebruikt in de hostnaam " -"voor aggregator logboekregistraties." +msgstr "Protocol gebruikt om te communiceren met de log aggregator. HTTPS/HTTP veronderstelt HTTPS tenzij http:// expliciet wordt gebruikt in de hostnaam voor aggregator logboekregistraties." -#: awx/main/conf.py:489 +#: awx/main/conf.py:748 msgid "TCP Connection Timeout" msgstr "Time-out van TCP-verbinding" -#: awx/main/conf.py:490 +#: awx/main/conf.py:749 msgid "" "Number of seconds for a TCP connection to external log aggregator to " "timeout. Applies to HTTPS and TCP log aggregator protocols." -msgstr "" -"Aantal seconden voordat er een time-out optreedt voor een TCP-verbinding met" -" een externe log aggregator. Geldt voor HTTPS en TCP log aggregator-" -"protocollen." +msgstr "Aantal seconden voordat er een time-out optreedt voor een TCP-verbinding met een externe log aggregator. Geldt voor HTTPS en TCP log aggregator-protocollen." -#: awx/main/conf.py:500 +#: awx/main/conf.py:759 msgid "Enable/disable HTTPS certificate verification" msgstr "HTTPS-certificaatcontrole in-/uitschakelen" -#: awx/main/conf.py:501 +#: awx/main/conf.py:760 msgid "" "Flag to control enable/disable of certificate verification when " "LOG_AGGREGATOR_PROTOCOL is \"https\". If enabled, Tower's log handler will " "verify certificate sent by external log aggregator before establishing " "connection." -msgstr "" -"Vlag om certificaatcontrole in/uit te schakelen wanneer het " -"LOG_AGGREGATOR_PROTOCOL gelijk is aan \"https\". Indien ingeschakeld, " -"controleert de logboekhandler van Tower het certificaat verzonden door de " -"externe log aggregator voordat de verbinding tot stand wordt gebracht." +msgstr "Vlag om certificaatcontrole in/uit te schakelen wanneer het LOG_AGGREGATOR_PROTOCOL gelijk is aan \"https\". Indien ingeschakeld, controleert de logboekhandler van Tower het certificaat verzonden door de externe log aggregator voordat de verbinding tot stand wordt gebracht." -#: awx/main/conf.py:513 +#: awx/main/conf.py:772 msgid "Logging Aggregator Level Threshold" msgstr "Drempelwaarde aggregator logboekregistraties" -#: awx/main/conf.py:514 +#: awx/main/conf.py:773 msgid "" "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)" -msgstr "" -"Drempelwaarde gebruikt door logboekhandler. Ernstcategorieën van laag naar " -"hoog zijn DEBUG, INFO, WARNING, ERROR, CRITICAL. Berichten die minder streng" -" zijn dan de drempelwaarde, worden genegeerd door de logboekhandler. (deze " -"instelling wordt genegeerd door berichten onder de categorie awx.anlytics)" +"threshold will be ignored by log handler. (messages under category awx." +"anlytics ignore this setting)" +msgstr "Drempelwaarde gebruikt door logboekhandler. Ernstcategorieën van laag naar hoog zijn DEBUG, INFO, WARNING, ERROR, CRITICAL. Berichten die minder streng zijn dan de drempelwaarde, worden genegeerd door de logboekhandler. (deze instelling wordt genegeerd door berichten onder de categorie awx.anlytics)" + +#: awx/main/conf.py:785 +msgid "Enabled external log aggregation auditing" +msgstr "Externe logboekaggregatiecontrole ingeschakeld" + +#: awx/main/conf.py:786 +msgid "" +"When enabled, all external logs emitted by Tower will also be written to /" +"var/log/tower/external.log. This is an experimental setting intended to be " +"used for debugging external log aggregation issues (and may be subject to " +"change in the future)." +msgstr "Indien ingeschakeld, worden alle externe logs die door Tower worden uitgezonden ook naar /var/log/tower/external.log geschreven. Dit is een experimentele instelling die bedoeld is om te worden gebruikt bij het debuggen van externe logaggregatieproblemen (en kan in de toekomst worden gewijzigd)." + +#: awx/main/conf.py:795 +msgid "Maximum disk persistance for external log aggregation (in GB)" +msgstr "Maximale schijfduurzaamheid voor externe logboekaggregatie (in GB)" + +#: awx/main/conf.py:796 +msgid "" +"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." +msgstr "Hoeveelheid op te slaan gegevens (in gigabytes) tijdens een storing in de externe logboekaggregator (standaard op 1). Equivalent aan de rsyslogd wachtrij.maxdiskspace-instelling." + +#: awx/main/conf.py:806 +msgid "File system location for rsyslogd disk persistence" +msgstr "Locatie van het bestandssysteem voor rsyslogd-schijfpersistentie" -#: awx/main/conf.py:537 awx/sso/conf.py:1264 +#: awx/main/conf.py:807 +msgid "" +"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." +msgstr "Locatie om de logboeken te laten voortbestaan die moeten worden opgehaald na een storing in de externe logboekaggregator (standaard ingesteld op /var/lib/awx). Equivalent aan de rsyslogd wachtrij.spoolDirectory-instelling." + +#: awx/main/conf.py:817 +msgid "Enable rsyslogd debugging" +msgstr "Rsyslogd debugging inschakelen" + +#: awx/main/conf.py:818 +msgid "" +"Enabled high verbosity debugging for rsyslogd. Useful for debugging " +"connection issues for external log aggregation." +msgstr "Schakel hoge verbositeit debugging in voor rsyslogd. Nuttig voor het debuggen van verbindingsproblemen voor externe logboekaggregatie." + +#: awx/main/conf.py:828 +msgid "Message Durability" +msgstr "Duurzaamheid bericht" + +#: awx/main/conf.py:829 +msgid "" +"When set (the default), underlying queues will be persisted to disk. " +"Disable this to enable higher message bus throughput." +msgstr "Wanneer deze (de standaardinstelling) is ingesteld, blijven de onderliggende wachtrijen op de schijf staan. Schakel dit uit om een hogere doorvoersnelheid van de berichtenbus mogelijk te maken." + +#: awx/main/conf.py:838 +msgid "Last gather date for Automation Analytics." +msgstr "Laatste verzamelpunt voor Automation Analytics." + +#: awx/main/conf.py:848 +msgid "Automation Analytics Gather Interval" +msgstr "Automatiseringsanalyse Verzamelinterval" + +#: awx/main/conf.py:849 +msgid "Interval (in seconds) between data gathering." +msgstr "Interval (in seconden) tussen het verzamelen van gegevens." + +#: awx/main/conf.py:871 awx/sso/conf.py:1239 msgid "\n" msgstr "\n" +#: awx/main/conf.py:892 +msgid "" +"A URL for Primary Galaxy must be defined before disabling public Galaxy." +msgstr "Er moet een URL voor de primaire galaxy worden gedefinieerd voordat de openbare galaxy wordt uitgeschakeld." + +#: awx/main/conf.py:912 +msgid "Cannot provide field if PRIMARY_GALAXY_URL is not set." +msgstr "Kan geen veld invullen als PRIMARY_GALAXY_URL niet is ingesteld." + +#: awx/main/conf.py:925 +#, python-brace-format +msgid "" +"Galaxy server settings are not available until Ansible {min_version}, you " +"are running {current_version}." +msgstr "Galaxy-serverinstellingen zijn pas beschikbaar in Ansible {min_version}, u gebruik {current_version}." + +#: awx/main/conf.py:934 +msgid "" +"Setting Galaxy token and authentication URL is mutually exclusive with " +"username and password." +msgstr "Het instellen van een Galaxy-token en authenticatie-URL is niet mogelijk zonder gebruikersnaam en wachtwoord." + +#: awx/main/conf.py:937 +msgid "If authenticating via username and password, both must be provided." +msgstr "Bij authenticatie via gebruikersnaam en wachtwoord moeten beiden worden opgegeven." + +#: awx/main/conf.py:943 +msgid "" +"If authenticating via token, both token and authentication URL must be " +"provided." +msgstr "Indien de authenticatie via een token plaatsvindt, moeten zowel de token als de authenticatie-URL worden verstrekt." + #: awx/main/constants.py:17 msgid "Sudo" msgstr "Sudo" @@ -2491,1324 +2684,1354 @@ msgstr "Inschakelen" msgid "Doas" msgstr "Doas" -#: awx/main/constants.py:21 +#: awx/main/constants.py:19 +msgid "Ksu" +msgstr "Ksu" + +#: awx/main/constants.py:20 +msgid "Machinectl" +msgstr "Machinectl" + +#: awx/main/constants.py:20 +msgid "Sesu" +msgstr "Sesu" + +#: awx/main/constants.py:22 msgid "None" msgstr "Geen" -#: awx/main/fields.py:62 +#: awx/main/credential_plugins/aim.py:16 +msgid "CyberArk AIM URL" +msgstr "CyberArk AIM-URL" + +#: awx/main/credential_plugins/aim.py:21 +msgid "Application ID" +msgstr "Toepassings-ID" + +#: awx/main/credential_plugins/aim.py:26 +msgid "Client Key" +msgstr "Clientsleutel" + +#: awx/main/credential_plugins/aim.py:32 +msgid "Client Certificate" +msgstr "Clientcertificaat" + +#: awx/main/credential_plugins/aim.py:38 +msgid "Verify SSL Certificates" +msgstr "SSL-certificaten verifiëren" + +#: awx/main/credential_plugins/aim.py:44 +msgid "Object Query" +msgstr "Objectquery" + +#: awx/main/credential_plugins/aim.py:46 +msgid "" +"Lookup query for the object. Ex: Safe=TestSafe;Object=testAccountName123" +msgstr "Query opzoeken voor het object. Bijv.: \"Safe=TestSafe;Object=testAccountName123\"" + +#: awx/main/credential_plugins/aim.py:49 +msgid "Object Query Format" +msgstr "Indeling objectquery" + +#: awx/main/credential_plugins/aim.py:55 +msgid "Reason" +msgstr "Reden" + +#: awx/main/credential_plugins/aim.py:57 +msgid "" +"Object request reason. This is only needed if it is required by the object's " +"policy." +msgstr "Reden objectaanvraag. Dit is alleen noodzakelijk indien vereist volgens het objectbeleid." + +#: awx/main/credential_plugins/azure_kv.py:21 +msgid "Vault URL (DNS Name)" +msgstr "Vault-URL (DNS-naam)" + +#: awx/main/credential_plugins/azure_kv.py:26 +#: awx/main/models/credential/__init__.py:956 +msgid "Client ID" +msgstr "Klant-ID" + +#: awx/main/credential_plugins/azure_kv.py:35 +#: awx/main/models/credential/__init__.py:965 +msgid "Tenant ID" +msgstr "Huurder-ID" + +#: awx/main/credential_plugins/azure_kv.py:39 +msgid "Cloud Environment" +msgstr "Cloudomgeving" + +#: awx/main/credential_plugins/azure_kv.py:40 +msgid "Specify which azure cloud environment to use." +msgstr "Geef aan welke azure cloudomgeving gebruikt moet worden." + +#: awx/main/credential_plugins/azure_kv.py:46 +msgid "Secret Name" +msgstr "Naam van geheim" + +#: awx/main/credential_plugins/azure_kv.py:48 +msgid "The name of the secret to look up." +msgstr "De naam van het geheim dat opgezocht moet worden." + +#: awx/main/credential_plugins/azure_kv.py:51 +#: awx/main/credential_plugins/conjur.py:47 +msgid "Secret Version" +msgstr "Versie van geheim" + +#: awx/main/credential_plugins/azure_kv.py:53 +#: awx/main/credential_plugins/conjur.py:49 +#: awx/main/credential_plugins/hashivault.py:67 +msgid "" +"Used to specify a specific secret version (if left empty, the latest version " +"will be used)." +msgstr "Gebruikt om een specifieke versie van het geheim te specificeren (indien dit veld leeg wordt gelaten, wordt de nieuwste versie gebruikt)." + +#: awx/main/credential_plugins/conjur.py:18 +msgid "Conjur URL" +msgstr "Conjur-URL" + +#: awx/main/credential_plugins/conjur.py:23 +msgid "API Key" +msgstr "API-sleutel" + +#: awx/main/credential_plugins/conjur.py:28 awx/main/models/inventory.py:1018 +msgid "Account" +msgstr "Account" + +#: awx/main/credential_plugins/conjur.py:32 +#: awx/main/models/credential/__init__.py:598 +#: awx/main/models/credential/__init__.py:654 +#: awx/main/models/credential/__init__.py:712 +#: awx/main/models/credential/__init__.py:785 +#: awx/main/models/credential/__init__.py:834 +#: awx/main/models/credential/__init__.py:860 +#: awx/main/models/credential/__init__.py:887 +#: awx/main/models/credential/__init__.py:947 +#: awx/main/models/credential/__init__.py:1020 +#: awx/main/models/credential/__init__.py:1051 +#: awx/main/models/credential/__init__.py:1101 +msgid "Username" +msgstr "Gebruikersnaam" + +#: awx/main/credential_plugins/conjur.py:36 +msgid "Public Key Certificate" +msgstr "Openbare sleutel van certificaat" + +#: awx/main/credential_plugins/conjur.py:42 +msgid "Secret Identifier" +msgstr "Identificatiecode van geheim" + +#: awx/main/credential_plugins/conjur.py:44 +msgid "The identifier for the secret e.g., /some/identifier" +msgstr "De identificatiecode voor het geheim, bijv. /some/identifier" + +#: awx/main/credential_plugins/hashivault.py:19 +msgid "Server URL" +msgstr "Server-URL" + +#: awx/main/credential_plugins/hashivault.py:22 +msgid "The URL to the HashiCorp Vault" +msgstr "De URL naar de HashiCorp Vault" + +#: awx/main/credential_plugins/hashivault.py:25 +#: awx/main/models/credential/__init__.py:986 +#: awx/main/models/credential/__init__.py:1003 +msgid "Token" +msgstr "Token" + +#: awx/main/credential_plugins/hashivault.py:28 +msgid "The access token used to authenticate to the Vault server" +msgstr "De toegangstoken die wordt gebruikt om de Vault-server te authenticeren" + +#: awx/main/credential_plugins/hashivault.py:31 +msgid "CA Certificate" +msgstr "CA-certificaat" + +#: awx/main/credential_plugins/hashivault.py:34 +msgid "" +"The CA certificate used to verify the SSL certificate of the Vault server" +msgstr "Het CA-certificaat dat wordt gebruikt om het SSL-certificaat van de Vault-server te controleren" + +#: awx/main/credential_plugins/hashivault.py:38 +msgid "Path to Secret" +msgstr "Pad naar geheim" + +#: awx/main/credential_plugins/hashivault.py:40 +msgid "The path to the secret stored in the secret backend e.g, /some/secret/" +msgstr "De pad naar het geheim dat in de geheime back-end is opgeslagen, bijv. /some/secret/" + +#: awx/main/credential_plugins/hashivault.py:48 +msgid "API Version" +msgstr "API-versie" + +#: awx/main/credential_plugins/hashivault.py:50 +msgid "" +"API v1 is for static key/value lookups. API v2 is for versioned key/value " +"lookups." +msgstr "API v1 dient voor het opzoeken van statische sleutels/waarden. API v2 dient voor het opzoeken van sleutels/waarden met een bepaalde versie." + +#: awx/main/credential_plugins/hashivault.py:55 +msgid "Name of Secret Backend" +msgstr "Naam van geheime back-end" + +#: awx/main/credential_plugins/hashivault.py:57 +msgid "" +"The name of the kv secret backend (if left empty, the first segment of the " +"secret path will be used)." +msgstr "De naam van de geheime back-end (indien dit veld leeg wordt gelaten, wordt het eerste segment van het geheime pad gebruikt)." + +#: awx/main/credential_plugins/hashivault.py:60 +#: awx/main/models/inventory.py:1023 +msgid "Key Name" +msgstr "Sleutelnaam" + +#: awx/main/credential_plugins/hashivault.py:62 +msgid "The name of the key to look up in the secret." +msgstr "De naam van de sleutel die in het geheim moet worden opgezocht." + +#: awx/main/credential_plugins/hashivault.py:65 +msgid "Secret Version (v2 only)" +msgstr "Versie van geheim (alleen v2)" + +#: awx/main/credential_plugins/hashivault.py:74 +msgid "Unsigned Public Key" +msgstr "Niet-ondertekende openbare sleutel" + +#: awx/main/credential_plugins/hashivault.py:79 +msgid "Role Name" +msgstr "Naam van rol" + +#: awx/main/credential_plugins/hashivault.py:81 +msgid "The name of the role used to sign." +msgstr "De naam van de rol die wordt gebruikt om te ondertekenen." + +#: awx/main/credential_plugins/hashivault.py:84 +msgid "Valid Principals" +msgstr "Geldige principes" + +#: awx/main/credential_plugins/hashivault.py:86 +msgid "" +"Valid principals (either usernames or hostnames) that the certificate should " +"be signed for." +msgstr "Geldige principes (gebruikersnamen of hostnamen) waarvoor het certificaat moet worden ondertekend." + +#: awx/main/fields.py:67 #, python-brace-format msgid "'{value}' is not one of ['{allowed_values}']" -msgstr "'{value}' behoort niet tot ['{allowed_values}']" +msgstr "'{value}' is niet een van ['{allowed_values}']" -#: awx/main/fields.py:421 +#: awx/main/fields.py:439 #, python-brace-format msgid "{type} provided in relative path {path}, expected {expected_type}" -msgstr "" -"{type} opgegeven in relatief pad {path}, terwijl {expected_type} verwacht " -"werd" +msgstr "{type} in relatief pad {path} opgegeven, verwacht {expected_type}" -#: awx/main/fields.py:426 +#: awx/main/fields.py:444 #, python-brace-format msgid "{type} provided, expected {expected_type}" -msgstr "{type} opgegeven, terwijl {expected_type} verwacht werd" +msgstr "{type} opgegeven, {expected_type} verwacht" -#: awx/main/fields.py:431 +#: awx/main/fields.py:449 #, python-brace-format msgid "Schema validation error in relative path {path} ({error})" msgstr "Schemavalideringsfout in relatief pad {path} ({error})" -#: awx/main/fields.py:552 +#: awx/main/fields.py:558 +#, python-format +msgid "required for %s" +msgstr "vereist voor %s" + +#: awx/main/fields.py:632 msgid "secret values must be of type string, not {}" msgstr "Geheime waarden moeten van het soort reeks zijn, niet {}" -#: awx/main/fields.py:587 +#: awx/main/fields.py:667 #, python-format msgid "cannot be set unless \"%s\" is set" msgstr "kan niet ingesteld worden, tenzij '%s' ingesteld is" -#: awx/main/fields.py:603 -#, python-format -msgid "required for %s" -msgstr "vereist voor %s" - -#: awx/main/fields.py:627 +#: awx/main/fields.py:702 msgid "must be set when SSH key is encrypted." msgstr "moet worden ingesteld wanneer SSH-sleutel wordt versleuteld." -#: awx/main/fields.py:633 +#: awx/main/fields.py:710 msgid "should not be set when SSH key is not encrypted." msgstr "mag niet worden ingesteld wanneer SSH-sleutel niet is gecodeerd." -#: awx/main/fields.py:691 +#: awx/main/fields.py:769 msgid "'dependencies' is not supported for custom credentials." -msgstr "" -"'afhankelijkheden' is niet ondersteund voor aangepaste toegangsgegevens." +msgstr "'afhankelijkheden' is niet ondersteund voor aangepaste toegangsgegevens." -#: awx/main/fields.py:705 +#: awx/main/fields.py:783 msgid "\"tower\" is a reserved field name" -msgstr "\"tower\" is een gereserveerde veldnaam" +msgstr "‘tower‘ is een gereserveerde veldnaam" -#: awx/main/fields.py:712 +#: awx/main/fields.py:790 #, python-format msgid "field IDs must be unique (%s)" -msgstr "veld-id's moeten uniek zijn (%s)" +msgstr "veld-id's moeten uniek zijn (%s)" -#: awx/main/fields.py:725 -msgid "become_method is a reserved type name" -msgstr "become_method is een gereserveerde soortnaam" +#: awx/main/fields.py:805 +msgid "{} is not a {}" +msgstr "{} is geen {}" -#: awx/main/fields.py:736 +#: awx/main/fields.py:811 #, python-brace-format msgid "{sub_key} not allowed for {element_type} type ({element_id})" -msgstr "{sub_key} niet toegestaan voor {element_type}-soort ({element_id})" +msgstr "{sub_key} is niet toegestaan voor type {element_type} ({element_id})" -#: awx/main/fields.py:810 +#: awx/main/fields.py:869 +msgid "" +"Environment variable {} may affect Ansible configuration so its use is not " +"allowed in credentials." +msgstr "Omgevingsvariabele {} kan invloed hebben op de configuratie van Ansible. Daarom is het gebruik ervan niet toegestaan in toegangsgegevens." + +#: awx/main/fields.py:875 +msgid "Environment variable {} is blacklisted from use in credentials." +msgstr "Omgevingsvariabele {} mag niet worden gebruikt in toegangsgegevens." + +#: awx/main/fields.py:903 msgid "" "Must define unnamed file injector in order to reference `tower.filename`." -msgstr "" -"Bestandsinjector zonder naam moet gedefinieerd worden om te kunnen verwijzen" -" naar 'tower.filename'." +msgstr "Bestandsinjector zonder naam moet gedefinieerd worden om te kunnen verwijzen naar 'tower.filename'." -#: awx/main/fields.py:817 +#: awx/main/fields.py:910 msgid "Cannot directly reference reserved `tower` namespace container." -msgstr "" -"Kan niet direct verwijzen naar gereserveerde 'tower'-naamruimtehouder." +msgstr "Kan niet direct verwijzen naar gereserveerde 'tower'-naamruimtehouder." -#: awx/main/fields.py:827 +#: awx/main/fields.py:920 msgid "Must use multi-file syntax when injecting multiple files" -msgstr "" -"Syntaxis voor meerdere bestanden moet gebruikt worden als meerdere bestanden" -" ingevoerd worden" +msgstr "Syntaxis voor meerdere bestanden moet gebruikt worden als meerdere bestanden ingevoerd worden" -#: awx/main/fields.py:844 +#: awx/main/fields.py:940 #, python-brace-format msgid "{sub_key} uses an undefined field ({error_msg})" -msgstr "{sub_key} maakt gebruik van een niet-gedefinieerd veld ({error_msg})" +msgstr "{sub_key} gebruikt een niet-gedefinieerd veld ({error_msg})" -#: awx/main/fields.py:851 +#: awx/main/fields.py:947 #, python-brace-format msgid "" "Syntax error rendering template for {sub_key} inside of {type} ({error_msg})" -msgstr "" -"Syntaxfout bij het weergeven van de sjabloon voor {sub_key} binnen {type} " -"({error_msg})" +msgstr "Syntaxisfout bij het weergeven van de sjabloon voor {sub_key} in {type} ({error_msg})" -#: awx/main/middleware.py:160 +#: awx/main/middleware.py:118 msgid "Formats of all available named urls" msgstr "Indelingen van alle beschikbare, genoemde url's" -#: awx/main/middleware.py:161 +#: awx/main/middleware.py:119 msgid "" "Read-only list of key-value pairs that shows the standard format of all " "available named URLs." -msgstr "" -"Alleen-lezen-lijst met sleutelwaardeparen die de standaardindeling van alle " -"beschikbare, genoemde URL's toont." +msgstr "Alleen-lezen-lijst met sleutelwaardeparen die de standaardindeling van alle beschikbare, genoemde URL's toont." -#: awx/main/middleware.py:163 awx/main/middleware.py:173 +#: awx/main/middleware.py:121 awx/main/middleware.py:131 msgid "Named URL" msgstr "Genoemde URL" -#: awx/main/middleware.py:170 +#: awx/main/middleware.py:128 msgid "List of all named url graph nodes." msgstr "Lijst met alle grafische knooppunten van genoemde URL's." -#: awx/main/middleware.py:171 +#: awx/main/middleware.py:129 msgid "" -"Read-only list of key-value pairs that exposes named URL graph topology. Use" -" this list to programmatically generate named URLs for resources" -msgstr "" -"Alleen-lezen-lijst met sleutelwaardeparen die de grafische topologie van " -"genoemde URL's duidelijk maakt. Gebruik deze lijst om programmatische " -"genoemde URL's voor resources te genereren." - -#: awx/main/migrations/_reencrypt.py:26 awx/main/models/notifications.py:35 -msgid "Email" -msgstr "E-mail" - -#: awx/main/migrations/_reencrypt.py:27 awx/main/models/notifications.py:36 -msgid "Slack" -msgstr "Slack" - -#: awx/main/migrations/_reencrypt.py:28 awx/main/models/notifications.py:37 -msgid "Twilio" -msgstr "Twilio" - -#: awx/main/migrations/_reencrypt.py:29 awx/main/models/notifications.py:38 -msgid "Pagerduty" -msgstr "Pagerduty" +"Read-only list of key-value pairs that exposes named URL graph topology. Use " +"this list to programmatically generate named URLs for resources" +msgstr "Alleen-lezen-lijst met sleutelwaardeparen die de grafische topologie van genoemde URL's duidelijk maakt. Gebruik deze lijst om programmatische genoemde URL's voor bronnen te genereren." -#: awx/main/migrations/_reencrypt.py:30 awx/main/models/notifications.py:39 -msgid "HipChat" -msgstr "HipChat" - -#: awx/main/migrations/_reencrypt.py:31 awx/main/models/notifications.py:41 -msgid "Mattermost" -msgstr "Mattermost" - -#: awx/main/migrations/_reencrypt.py:32 awx/main/models/notifications.py:40 -msgid "Webhook" -msgstr "Webhook" - -#: awx/main/migrations/_reencrypt.py:33 awx/main/models/notifications.py:43 -msgid "IRC" -msgstr "IRC" - -#: awx/main/models/activity_stream.py:25 +#: awx/main/models/activity_stream.py:28 msgid "Entity Created" msgstr "Entiteit gemaakt" -#: awx/main/models/activity_stream.py:26 +#: awx/main/models/activity_stream.py:29 msgid "Entity Updated" msgstr "Entiteit bijgewerkt" -#: awx/main/models/activity_stream.py:27 +#: awx/main/models/activity_stream.py:30 msgid "Entity Deleted" msgstr "Entiteit verwijderd" -#: awx/main/models/activity_stream.py:28 +#: awx/main/models/activity_stream.py:31 msgid "Entity Associated with another Entity" msgstr "Entiteit gekoppeld aan een andere entiteit" -#: awx/main/models/activity_stream.py:29 +#: awx/main/models/activity_stream.py:32 msgid "Entity was Disassociated with another Entity" msgstr "Entiteit is losgekoppeld van een andere entiteit" -#: awx/main/models/ad_hoc_commands.py:95 +#: awx/main/models/activity_stream.py:45 +msgid "The cluster node the activity took place on." +msgstr "Het clusterknooppunt waarop de activiteit plaatsvond." + +#: awx/main/models/ad_hoc_commands.py:97 msgid "No valid inventory." msgstr "Geen geldige inventaris." -#: awx/main/models/ad_hoc_commands.py:102 +#: awx/main/models/ad_hoc_commands.py:104 msgid "You must provide a machine / SSH credential." msgstr "U moet een machine / SSH-referentie verschaffen." -#: awx/main/models/ad_hoc_commands.py:113 -#: awx/main/models/ad_hoc_commands.py:121 +#: awx/main/models/ad_hoc_commands.py:115 +#: awx/main/models/ad_hoc_commands.py:123 msgid "Invalid type for ad hoc command" msgstr "Ongeldig type voor ad-hocopdracht" -#: awx/main/models/ad_hoc_commands.py:116 +#: awx/main/models/ad_hoc_commands.py:118 msgid "Unsupported module for ad hoc commands." msgstr "Niet-ondersteunde module voor ad-hocopdrachten." -#: awx/main/models/ad_hoc_commands.py:124 +#: awx/main/models/ad_hoc_commands.py:126 #, python-format msgid "No argument passed to %s module." msgstr "Geen argument doorgegeven aan module %s." -#: awx/main/models/base.py:33 awx/main/models/base.py:39 -#: awx/main/models/base.py:44 awx/main/models/base.py:49 -msgid "Run" -msgstr "Uitvoeren" - -#: awx/main/models/base.py:34 awx/main/models/base.py:40 -#: awx/main/models/base.py:45 awx/main/models/base.py:50 -msgid "Check" -msgstr "Controleren" - -#: awx/main/models/base.py:35 -msgid "Scan" -msgstr "Scannen" - -#: awx/main/models/credential/__init__.py:110 -msgid "Host" -msgstr "Host" - -#: awx/main/models/credential/__init__.py:111 -msgid "The hostname or IP address to use." -msgstr "Te gebruiken hostnaam of IP-adres" - -#: awx/main/models/credential/__init__.py:117 -#: awx/main/models/credential/__init__.py:686 -#: awx/main/models/credential/__init__.py:741 -#: awx/main/models/credential/__init__.py:806 -#: awx/main/models/credential/__init__.py:884 -#: awx/main/models/credential/__init__.py:930 -#: awx/main/models/credential/__init__.py:958 -#: awx/main/models/credential/__init__.py:987 -#: awx/main/models/credential/__init__.py:1051 -#: awx/main/models/credential/__init__.py:1092 -#: awx/main/models/credential/__init__.py:1125 -#: awx/main/models/credential/__init__.py:1177 -msgid "Username" -msgstr "Gebruikersnaam" - -#: awx/main/models/credential/__init__.py:118 -msgid "Username for this credential." -msgstr "Gebruikersnaam voor deze referentie." - -#: awx/main/models/credential/__init__.py:124 -#: awx/main/models/credential/__init__.py:690 -#: awx/main/models/credential/__init__.py:745 -#: awx/main/models/credential/__init__.py:810 -#: awx/main/models/credential/__init__.py:934 -#: awx/main/models/credential/__init__.py:962 -#: awx/main/models/credential/__init__.py:991 -#: awx/main/models/credential/__init__.py:1055 -#: awx/main/models/credential/__init__.py:1096 -#: awx/main/models/credential/__init__.py:1129 -#: awx/main/models/credential/__init__.py:1181 -msgid "Password" -msgstr "Wachtwoord" - -#: awx/main/models/credential/__init__.py:125 -msgid "" -"Password for this credential (or \"ASK\" to prompt the user for machine " -"credentials)." -msgstr "" -"Wachtwoord voor deze referentie (of \"ASK\" om de gebruiker om de " -"machinereferenties te vragen)." - -#: awx/main/models/credential/__init__.py:132 -msgid "Security Token" -msgstr "Beveiligingstoken" - -#: awx/main/models/credential/__init__.py:133 -msgid "Security Token for this credential" -msgstr "Beveiligingstoken voor deze referentie" - -#: awx/main/models/credential/__init__.py:139 -msgid "Project" -msgstr "Project" - -#: awx/main/models/credential/__init__.py:140 -msgid "The identifier for the project." -msgstr "De id voor het project." - -#: awx/main/models/credential/__init__.py:146 -msgid "Domain" -msgstr "Domein" - -#: awx/main/models/credential/__init__.py:147 -msgid "The identifier for the domain." -msgstr "De id voor het domein." - -#: awx/main/models/credential/__init__.py:152 -msgid "SSH private key" -msgstr "SSH-privésleutel" - -#: awx/main/models/credential/__init__.py:153 -msgid "RSA or DSA private key to be used instead of password." -msgstr "RSA- of DSA-privésleutel te gebruiken in plaats van wachtwoord." - -#: awx/main/models/credential/__init__.py:159 -msgid "SSH key unlock" -msgstr "SSH-sleutelontgrendeling" - -#: awx/main/models/credential/__init__.py:160 -msgid "" -"Passphrase to unlock SSH private key if encrypted (or \"ASK\" to prompt the " -"user for machine credentials)." -msgstr "" -"Wachtwoordzin om SSH-privésleutel te ontgrendelen indien versleuteld (of " -"\"ASK\" om de gebruiker om de machinereferenties te vragen)." - -#: awx/main/models/credential/__init__.py:168 -msgid "Privilege escalation method." -msgstr "Methode voor verhoging van rechten." - -#: awx/main/models/credential/__init__.py:174 -msgid "Privilege escalation username." -msgstr "Gebruikersnaam voor verhoging van rechten." - -#: awx/main/models/credential/__init__.py:180 -msgid "Password for privilege escalation method." -msgstr "Wachtwoord voor methode voor verhoging van rechten." - -#: awx/main/models/credential/__init__.py:186 -msgid "Vault password (or \"ASK\" to prompt the user)." -msgstr "Wachtwoord voor kluis (of \"ASK\" om de gebruiker te vragen)." - -#: awx/main/models/credential/__init__.py:190 -msgid "Whether to use the authorize mechanism." -msgstr "Of het autorisatiemechanisme mag worden gebruikt." - -#: awx/main/models/credential/__init__.py:196 -msgid "Password used by the authorize mechanism." -msgstr "Wachtwoord gebruikt door het autorisatiemechanisme." - -#: awx/main/models/credential/__init__.py:202 -msgid "Client Id or Application Id for the credential" -msgstr "Klant-id voor toepassings-id voor de referentie" - -#: awx/main/models/credential/__init__.py:208 -msgid "Secret Token for this credential" -msgstr "Geheim token voor deze referentie" +#: awx/main/models/base.py:33 awx/main/models/base.py:39 +#: awx/main/models/base.py:44 awx/main/models/base.py:49 +msgid "Run" +msgstr "Uitvoeren" -#: awx/main/models/credential/__init__.py:214 -msgid "Subscription identifier for this credential" -msgstr "Abonnements-id voor deze referentie" +#: awx/main/models/base.py:34 awx/main/models/base.py:40 +#: awx/main/models/base.py:45 awx/main/models/base.py:50 +msgid "Check" +msgstr "Controleren" -#: awx/main/models/credential/__init__.py:220 -msgid "Tenant identifier for this credential" -msgstr "Huurder-id voor deze referentie" +#: awx/main/models/base.py:35 +msgid "Scan" +msgstr "Scannen" -#: awx/main/models/credential/__init__.py:244 +#: awx/main/models/credential/__init__.py:96 msgid "" "Specify the type of credential you want to create. Refer to the Ansible " "Tower documentation for details on each type." -msgstr "" -"Geef het type referentie op dat u wilt maken. Raadpleeg de documentatie voor" -" Ansible Tower voor details over elk type." +msgstr "Geef het type referentie op dat u wilt maken. Raadpleeg de documentatie voor Ansible Tower voor details over elk type." -#: awx/main/models/credential/__init__.py:258 -#: awx/main/models/credential/__init__.py:476 +#: awx/main/models/credential/__init__.py:110 +#: awx/main/models/credential/__init__.py:353 msgid "" -"Enter inputs using either JSON or YAML syntax. Use the radio button to " -"toggle between the two. Refer to the Ansible Tower documentation for example" -" syntax." -msgstr "" -"Geef inputs op met JSON- of YAML-syntaxis. Gebruik het keuzerondje om te " -"wisselen tussen de twee. Raadpleeg de documentatie voor Ansible Tower voor " -"voorbeeldsyntaxis." +"Enter inputs using either JSON or YAML syntax. Refer to the Ansible Tower " +"documentation for example syntax." +msgstr "Geef inputs op met JSON- of YAML-syntaxis. Raadpleeg de documentatie voor Ansible Tower voor voorbeeldsyntaxis." -#: awx/main/models/credential/__init__.py:457 -#: awx/main/models/credential/__init__.py:681 +#: awx/main/models/credential/__init__.py:325 +#: awx/main/models/credential/__init__.py:594 msgid "Machine" msgstr "Machine" -#: awx/main/models/credential/__init__.py:458 -#: awx/main/models/credential/__init__.py:772 +#: awx/main/models/credential/__init__.py:326 +#: awx/main/models/credential/__init__.py:680 msgid "Vault" msgstr "Kluis" -#: awx/main/models/credential/__init__.py:459 -#: awx/main/models/credential/__init__.py:801 +#: awx/main/models/credential/__init__.py:327 +#: awx/main/models/credential/__init__.py:707 msgid "Network" msgstr "Netwerk" -#: awx/main/models/credential/__init__.py:460 -#: awx/main/models/credential/__init__.py:736 +#: awx/main/models/credential/__init__.py:328 +#: awx/main/models/credential/__init__.py:649 msgid "Source Control" msgstr "Broncontrole" -#: awx/main/models/credential/__init__.py:461 +#: awx/main/models/credential/__init__.py:329 msgid "Cloud" msgstr "Cloud" -#: awx/main/models/credential/__init__.py:462 -#: awx/main/models/credential/__init__.py:1087 +#: awx/main/models/credential/__init__.py:330 +msgid "Personal Access Token" +msgstr "Persoonlijke toegangstoken" + +#: awx/main/models/credential/__init__.py:331 +#: awx/main/models/credential/__init__.py:1015 msgid "Insights" msgstr "Inzichten" -#: awx/main/models/credential/__init__.py:483 +#: awx/main/models/credential/__init__.py:332 +msgid "External" +msgstr "Extern" + +#: awx/main/models/credential/__init__.py:333 +msgid "Kubernetes" +msgstr "Kubernetes" + +#: awx/main/models/credential/__init__.py:359 msgid "" -"Enter injectors using either JSON or YAML syntax. Use the radio button to " -"toggle between the two. Refer to the Ansible Tower documentation for example" -" syntax." -msgstr "" -"Geef injectoren op met JSON- of YAML-syntaxis. Gebruik het keuzerondje om te" -" wisselen tussen de twee. Raadpleeg de documentatie voor Ansible Tower voor " -"voorbeeldsyntaxis." +"Enter injectors using either JSON or YAML syntax. Refer to the Ansible Tower " +"documentation for example syntax." +msgstr "Geef injectoren op met JSON- of YAML-syntaxis. Raadpleeg de documentatie voor Ansible Tower voor voorbeeldsyntaxis." -#: awx/main/models/credential/__init__.py:534 +#: awx/main/models/credential/__init__.py:428 #, python-format msgid "adding %s credential type" msgstr "%s soort toegangsgegevens toevoegen" -#: awx/main/models/credential/__init__.py:696 -#: awx/main/models/credential/__init__.py:815 +#: awx/main/models/credential/__init__.py:602 +#: awx/main/models/credential/__init__.py:658 +#: awx/main/models/credential/__init__.py:716 +#: awx/main/models/credential/__init__.py:838 +#: awx/main/models/credential/__init__.py:864 +#: awx/main/models/credential/__init__.py:891 +#: awx/main/models/credential/__init__.py:951 +#: awx/main/models/credential/__init__.py:1024 +#: awx/main/models/credential/__init__.py:1055 +#: awx/main/models/credential/__init__.py:1105 +msgid "Password" +msgstr "Wachtwoord" + +#: awx/main/models/credential/__init__.py:608 +#: awx/main/models/credential/__init__.py:721 msgid "SSH Private Key" msgstr "SSH-privésleutel" -#: awx/main/models/credential/__init__.py:703 -#: awx/main/models/credential/__init__.py:757 -#: awx/main/models/credential/__init__.py:822 +#: awx/main/models/credential/__init__.py:615 +msgid "Signed SSH Certificate" +msgstr "Ondertekend SSH-certificaat" + +#: awx/main/models/credential/__init__.py:621 +#: awx/main/models/credential/__init__.py:670 +#: awx/main/models/credential/__init__.py:728 msgid "Private Key Passphrase" msgstr "Privésleutel wachtwoordzin" -#: awx/main/models/credential/__init__.py:709 +#: awx/main/models/credential/__init__.py:627 msgid "Privilege Escalation Method" msgstr "Methode voor verhoging van rechten" -#: awx/main/models/credential/__init__.py:711 +#: awx/main/models/credential/__init__.py:629 msgid "" -"Specify a method for \"become\" operations. This is equivalent to specifying" -" the --become-method Ansible parameter." -msgstr "" -"Specificeer een methode voor 'become'-operaties. Dit staat gelijk aan het " -"specificeren van de Ansible-parameter voor de --become-method" +"Specify a method for \"become\" operations. This is equivalent to specifying " +"the --become-method Ansible parameter." +msgstr "Specificeer een methode voor 'become'-operaties. Dit staat gelijk aan het specificeren van de Ansible-parameter voor de --become-method" -#: awx/main/models/credential/__init__.py:716 +#: awx/main/models/credential/__init__.py:634 msgid "Privilege Escalation Username" msgstr "Gebruikersnaam verhoging van rechten" -#: awx/main/models/credential/__init__.py:720 +#: awx/main/models/credential/__init__.py:638 msgid "Privilege Escalation Password" msgstr "Wachtwoord verhoging van rechten" -#: awx/main/models/credential/__init__.py:750 +#: awx/main/models/credential/__init__.py:663 msgid "SCM Private Key" msgstr "SCM-privésleutel" -#: awx/main/models/credential/__init__.py:777 +#: awx/main/models/credential/__init__.py:685 msgid "Vault Password" msgstr "Wachtwoord kluis" -#: awx/main/models/credential/__init__.py:783 +#: awx/main/models/credential/__init__.py:691 msgid "Vault Identifier" msgstr "Id kluis" -#: awx/main/models/credential/__init__.py:786 +#: awx/main/models/credential/__init__.py:694 msgid "" -"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+." -msgstr "" -"Specificeer een (optioneel) kluis-id. Dit staat gelijk aan het specificeren " -"van de Ansible-parameter voor de --vault-id voor het opgeven van meerdere " -"kluiswachtwoorden. Let op: deze functie werkt alleen in Ansible 2.4+." +"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+." +msgstr "Specificeer een (optioneel) kluis-id. Dit staat gelijk aan het specificeren van de Ansible-parameter voor de --vault-id voor het opgeven van meerdere kluiswachtwoorden. Let op: deze functie werkt alleen in Ansible 2.4+." -#: awx/main/models/credential/__init__.py:827 +#: awx/main/models/credential/__init__.py:733 msgid "Authorize" msgstr "Autoriseren" -#: awx/main/models/credential/__init__.py:831 +#: awx/main/models/credential/__init__.py:737 msgid "Authorize Password" msgstr "Wachtwoord autoriseren" -#: awx/main/models/credential/__init__.py:848 +#: awx/main/models/credential/__init__.py:751 msgid "Amazon Web Services" msgstr "Amazon webservices" -#: awx/main/models/credential/__init__.py:853 +#: awx/main/models/credential/__init__.py:756 msgid "Access Key" msgstr "Toegangssleutel" -#: awx/main/models/credential/__init__.py:857 +#: awx/main/models/credential/__init__.py:760 msgid "Secret Key" msgstr "Geheime sleutel" -#: awx/main/models/credential/__init__.py:862 +#: awx/main/models/credential/__init__.py:765 msgid "STS Token" msgstr "STS-token" -#: awx/main/models/credential/__init__.py:865 +#: awx/main/models/credential/__init__.py:768 msgid "" "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." -msgstr "" -"Security Token Service (STS) is een webdienst waarmee u tijdelijke " -"toegangsgegevens met beperkte rechten aan kunt vragen voor gebruikers van " -"AWS Identity en Access Management (IAM)" +msgstr "Security Token Service (STS) is een webdienst waarmee u tijdelijke toegangsgegevens met beperkte rechten aan kunt vragen voor gebruikers van AWS Identity en Access Management (IAM)" -#: awx/main/models/credential/__init__.py:879 awx/main/models/inventory.py:990 +#: awx/main/models/credential/__init__.py:780 awx/main/models/inventory.py:833 msgid "OpenStack" msgstr "OpenStack" -#: awx/main/models/credential/__init__.py:888 +#: awx/main/models/credential/__init__.py:789 msgid "Password (API Key)" msgstr "Wachtwoord (API-sleutel)" -#: awx/main/models/credential/__init__.py:893 -#: awx/main/models/credential/__init__.py:1120 +#: awx/main/models/credential/__init__.py:794 +#: awx/main/models/credential/__init__.py:1046 msgid "Host (Authentication URL)" msgstr "Host (authenticatie-URL)" -#: awx/main/models/credential/__init__.py:895 +#: awx/main/models/credential/__init__.py:796 msgid "" -"The host to authenticate with. For example, " -"https://openstack.business.com/v2.0/" -msgstr "" -"De host waarmee geauthenticeerd moet worden. Bijvoorbeeld " -"https://openstack.business.com/v2.0/" +"The host to authenticate with. For example, https://openstack.business.com/" +"v2.0/" +msgstr "De host waarmee geauthenticeerd moet worden. Bijvoorbeeld https://openstack.business.com/v2.0/" -#: awx/main/models/credential/__init__.py:899 +#: awx/main/models/credential/__init__.py:800 msgid "Project (Tenant Name)" msgstr "Projecten (naam huurder)" -#: awx/main/models/credential/__init__.py:903 +#: awx/main/models/credential/__init__.py:804 msgid "Domain Name" msgstr "Domeinnaam" -#: awx/main/models/credential/__init__.py:905 +#: awx/main/models/credential/__init__.py:806 msgid "" "OpenStack domains define administrative boundaries. It is only needed for " "Keystone v3 authentication URLs. Refer to Ansible Tower documentation for " "common scenarios." -msgstr "" -"Domeinen van OpenStack bepalen administratieve grenzen. Het is alleen nodig " -"voor Keystone v3 authenticatie-URL's. Raadpleeg documentatie van Ansible " -"Tower voor veel voorkomende scenario's." +msgstr "Domeinen van OpenStack bepalen administratieve grenzen. Het is alleen nodig voor Keystone v3 authenticatie-URL's. Raadpleeg documentatie van Ansible Tower voor veel voorkomende scenario's." + +#: awx/main/models/credential/__init__.py:812 +#: awx/main/models/credential/__init__.py:1110 +#: awx/main/models/credential/__init__.py:1144 +msgid "Verify SSL" +msgstr "SSL verifiëren" -#: awx/main/models/credential/__init__.py:919 awx/main/models/inventory.py:987 +#: awx/main/models/credential/__init__.py:823 awx/main/models/inventory.py:830 msgid "VMware vCenter" msgstr "VMware vCenter" -#: awx/main/models/credential/__init__.py:924 +#: awx/main/models/credential/__init__.py:828 msgid "VCenter Host" msgstr "VCenter-host" -#: awx/main/models/credential/__init__.py:926 +#: awx/main/models/credential/__init__.py:830 msgid "" "Enter the hostname or IP address that corresponds to your VMware vCenter." -msgstr "" -"Voer de hostnaam of het IP-adres in dat overeenkomt met uw VMware vCenter." +msgstr "Voer de hostnaam of het IP-adres in dat overeenkomt met uw VMware vCenter." -#: awx/main/models/credential/__init__.py:947 awx/main/models/inventory.py:988 +#: awx/main/models/credential/__init__.py:849 awx/main/models/inventory.py:831 msgid "Red Hat Satellite 6" msgstr "Red Hat Satellite 6" -#: awx/main/models/credential/__init__.py:952 +#: awx/main/models/credential/__init__.py:854 msgid "Satellite 6 URL" msgstr "Satellite 6-URL" -#: awx/main/models/credential/__init__.py:954 +#: awx/main/models/credential/__init__.py:856 msgid "" "Enter the URL that corresponds to your Red Hat Satellite 6 server. For " "example, https://satellite.example.org" -msgstr "" -"Voer de URL in die overeenkomt met uw sRed Hat Satellite 6-server. " -"Bijvoorbeeld https://satellite.example.org" +msgstr "Voer de URL in die overeenkomt met uw sRed Hat Satellite 6-server. Bijvoorbeeld https://satellite.example.org" -#: awx/main/models/credential/__init__.py:975 awx/main/models/inventory.py:989 +#: awx/main/models/credential/__init__.py:875 awx/main/models/inventory.py:832 msgid "Red Hat CloudForms" msgstr "Red Hat CloudForms" -#: awx/main/models/credential/__init__.py:980 +#: awx/main/models/credential/__init__.py:880 msgid "CloudForms URL" msgstr "CloudForms-URL" -#: awx/main/models/credential/__init__.py:982 +#: awx/main/models/credential/__init__.py:882 msgid "" "Enter the URL for the virtual machine that corresponds to your CloudForms " "instance. For example, https://cloudforms.example.org" -msgstr "" -"Voer de URL in voor de virtuele machine die overeenkomt met uw CloudForm-" -"instantie. Bijvoorbeeld https://cloudforms.example.org" +msgstr "Voer de URL in voor de virtuele machine die overeenkomt met uw CloudForms-instantie. Bijvoorbeeld https://cloudforms.example.org" -#: awx/main/models/credential/__init__.py:1004 -#: awx/main/models/inventory.py:985 +#: awx/main/models/credential/__init__.py:902 awx/main/models/inventory.py:828 msgid "Google Compute Engine" msgstr "Google Compute Engine" -#: awx/main/models/credential/__init__.py:1009 +#: awx/main/models/credential/__init__.py:907 msgid "Service Account Email Address" msgstr "E-mailadres service-account" -#: awx/main/models/credential/__init__.py:1011 +#: awx/main/models/credential/__init__.py:909 msgid "" "The email address assigned to the Google Compute Engine service account." -msgstr "" -"Het e-mailadres dat toegewezen is aan het Google Compute Engine-" -"serviceaccount." +msgstr "Het e-mailadres dat toegewezen is aan het Google Compute Engine-serviceaccount." -#: awx/main/models/credential/__init__.py:1017 +#: awx/main/models/credential/__init__.py:915 msgid "" "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" -msgstr "" -"Het project-ID is de toegewezen GCE-identificatie. Dit bestaat vaak uit drie" -" woorden of uit twee woorden, gevolgd door drie getallen. Bijvoorbeeld: " -"project-id-000 of another-project-id" +msgstr "Het project-ID is de toegewezen GCE-identificatie. Dit bestaat vaak uit drie woorden of uit twee woorden, gevolgd door drie getallen. Bijvoorbeeld: project-id-000 of another-project-id" -#: awx/main/models/credential/__init__.py:1023 +#: awx/main/models/credential/__init__.py:921 msgid "RSA Private Key" msgstr "RSA-privésleutel" -#: awx/main/models/credential/__init__.py:1028 +#: awx/main/models/credential/__init__.py:926 msgid "" -"Paste the contents of the PEM file associated with the service account " -"email." -msgstr "" -"Plak hier de inhoud van het PEM-bestand dat bij de e-mail van het " -"serviceaccount hoort." +"Paste the contents of the PEM file associated with the service account email." +msgstr "Plak hier de inhoud van het PEM-bestand dat bij de e-mail van het serviceaccount hoort." -#: awx/main/models/credential/__init__.py:1040 -#: awx/main/models/inventory.py:986 +#: awx/main/models/credential/__init__.py:936 awx/main/models/inventory.py:829 msgid "Microsoft Azure Resource Manager" msgstr "Microsoft Azure Resource Manager" -#: awx/main/models/credential/__init__.py:1045 +#: awx/main/models/credential/__init__.py:941 msgid "Subscription ID" msgstr "Abonnement-ID" -#: awx/main/models/credential/__init__.py:1047 +#: awx/main/models/credential/__init__.py:943 msgid "Subscription ID is an Azure construct, which is mapped to a username." -msgstr "" -"Abonnement-ID is een concept van Azure en is gelinkt aan een gebruikersnaam." - -#: awx/main/models/credential/__init__.py:1060 -msgid "Client ID" -msgstr "Klant-ID" - -#: awx/main/models/credential/__init__.py:1069 -msgid "Tenant ID" -msgstr "Huurder-ID" +msgstr "Abonnement-ID is een concept van Azure en is gelinkt aan een gebruikersnaam." -#: awx/main/models/credential/__init__.py:1073 +#: awx/main/models/credential/__init__.py:969 msgid "Azure Cloud Environment" msgstr "Azure-cloudomgeving" -#: awx/main/models/credential/__init__.py:1075 +#: awx/main/models/credential/__init__.py:971 msgid "" "Environment variable AZURE_CLOUD_ENVIRONMENT when using Azure GovCloud or " "Azure stack." -msgstr "" -"Omgevingsvariabele AZURE_CLOUD_OMGEVING wanneer u Azure GovCloud of Azure " -"stack gebruikt." +msgstr "Omgevingsvariabele AZURE_CLOUD_OMGEVING wanneer u Azure GovCloud of Azure stack gebruikt." + +#: awx/main/models/credential/__init__.py:981 +msgid "GitHub Personal Access Token" +msgstr "GitHub persoonlijke toegangstoken" -#: awx/main/models/credential/__init__.py:1115 -#: awx/main/models/inventory.py:991 +#: awx/main/models/credential/__init__.py:989 +msgid "This token needs to come from your profile settings in GitHub" +msgstr "Deze token moet afkomstig zijn van uw profielinstellingen in GitHub" + +#: awx/main/models/credential/__init__.py:998 +msgid "GitLab Personal Access Token" +msgstr "Persoonlijke toegangstoken van GitLab" + +#: awx/main/models/credential/__init__.py:1006 +msgid "This token needs to come from your profile settings in GitLab" +msgstr "Deze token moet afkomstig zijn van uw profielinstellingen in GitLab" + +#: awx/main/models/credential/__init__.py:1041 awx/main/models/inventory.py:834 msgid "Red Hat Virtualization" msgstr "Red Hat-virtualizering" -#: awx/main/models/credential/__init__.py:1122 +#: awx/main/models/credential/__init__.py:1048 msgid "The host to authenticate with." msgstr "De host waarmee geauthenticeerd moet worden." -#: awx/main/models/credential/__init__.py:1134 +#: awx/main/models/credential/__init__.py:1060 msgid "CA File" msgstr "CA-bestand" -#: awx/main/models/credential/__init__.py:1136 +#: awx/main/models/credential/__init__.py:1062 msgid "Absolute file path to the CA file to use (optional)" msgstr "Absoluut bestandspad naar het CA-bestand om te gebruiken (optioneel)" -#: awx/main/models/credential/__init__.py:1167 -#: awx/main/models/inventory.py:992 +#: awx/main/models/credential/__init__.py:1091 awx/main/models/inventory.py:835 msgid "Ansible Tower" msgstr "Ansible Tower" -#: awx/main/models/credential/__init__.py:1172 +#: awx/main/models/credential/__init__.py:1096 msgid "Ansible Tower Hostname" msgstr "Hostnaam Ansible Tower" -#: awx/main/models/credential/__init__.py:1174 +#: awx/main/models/credential/__init__.py:1098 msgid "The Ansible Tower base URL to authenticate with." msgstr "De basis-URL van Ansible Tower waarmee geautenticeerd moet worden." -#: awx/main/models/credential/__init__.py:1186 -msgid "Verify SSL" -msgstr "SSL verifiëren" +#: awx/main/models/credential/__init__.py:1130 +msgid "OpenShift or Kubernetes API Bearer Token" +msgstr "OpenShift of Kubernetes API-toondertoken" + +#: awx/main/models/credential/__init__.py:1134 +msgid "OpenShift or Kubernetes API Endpoint" +msgstr "OpenShift of Kubernetes API-eindpunt" + +#: awx/main/models/credential/__init__.py:1136 +msgid "The OpenShift or Kubernetes API Endpoint to authenticate with." +msgstr "Het OpenShift of Kubernetes API-eindpunt om mee te authenticeren." + +#: awx/main/models/credential/__init__.py:1139 +msgid "API authentication bearer token" +msgstr "API-authenticatie toondertoken" + +#: awx/main/models/credential/__init__.py:1149 +msgid "Certificate Authority data" +msgstr "Gegevens van de certificeringsinstantie" + +#: awx/main/models/credential/__init__.py:1190 +msgid "Target must be a non-external credential" +msgstr "Doel moet een niet-extern toegangsgegeven zijn" -#: awx/main/models/events.py:105 awx/main/models/events.py:630 +#: awx/main/models/credential/__init__.py:1195 +msgid "Source must be an external credential" +msgstr "Bron moet een extern toegangsgegeven zijn" + +#: awx/main/models/credential/__init__.py:1202 +msgid "Input field must be defined on target credential (options are {})." +msgstr "Inputveld moet gedefinieerd worden op doel-toegangsgegeven (opties zijn {})." + +#: awx/main/models/events.py:152 awx/main/models/events.py:674 msgid "Host Failed" msgstr "Host is mislukt" -#: awx/main/models/events.py:106 awx/main/models/events.py:631 +#: awx/main/models/events.py:153 +msgid "Host Started" +msgstr "Host gestart" + +#: awx/main/models/events.py:154 awx/main/models/events.py:675 msgid "Host OK" msgstr "Host OK" -#: awx/main/models/events.py:107 +#: awx/main/models/events.py:155 msgid "Host Failure" msgstr "Hostmislukking" -#: awx/main/models/events.py:108 awx/main/models/events.py:637 +#: awx/main/models/events.py:156 awx/main/models/events.py:681 msgid "Host Skipped" msgstr "Host overgeslagen" -#: awx/main/models/events.py:109 awx/main/models/events.py:632 +#: awx/main/models/events.py:157 awx/main/models/events.py:676 msgid "Host Unreachable" msgstr "Host onbereikbaar" -#: awx/main/models/events.py:110 awx/main/models/events.py:124 +#: awx/main/models/events.py:158 awx/main/models/events.py:172 msgid "No Hosts Remaining" msgstr "Geen resterende hosts" -#: awx/main/models/events.py:111 +#: awx/main/models/events.py:159 msgid "Host Polling" msgstr "Hostpolling" -#: awx/main/models/events.py:112 +#: awx/main/models/events.py:160 msgid "Host Async OK" msgstr "Host Async OK" -#: awx/main/models/events.py:113 +#: awx/main/models/events.py:161 msgid "Host Async Failure" msgstr "Host Async mislukking" -#: awx/main/models/events.py:114 +#: awx/main/models/events.py:162 msgid "Item OK" msgstr "Item OK" -#: awx/main/models/events.py:115 +#: awx/main/models/events.py:163 msgid "Item Failed" msgstr "Item mislukt" -#: awx/main/models/events.py:116 +#: awx/main/models/events.py:164 msgid "Item Skipped" msgstr "Item overgeslagen" -#: awx/main/models/events.py:117 +#: awx/main/models/events.py:165 msgid "Host Retry" msgstr "Host opnieuw proberen" -#: awx/main/models/events.py:119 +#: awx/main/models/events.py:167 msgid "File Difference" msgstr "Bestandsverschil" -#: awx/main/models/events.py:120 +#: awx/main/models/events.py:168 msgid "Playbook Started" msgstr "Draaiboek gestart" -#: awx/main/models/events.py:121 +#: awx/main/models/events.py:169 msgid "Running Handlers" msgstr "Handlers die worden uitgevoerd" -#: awx/main/models/events.py:122 +#: awx/main/models/events.py:170 msgid "Including File" msgstr "Inclusief bestand" -#: awx/main/models/events.py:123 +#: awx/main/models/events.py:171 msgid "No Hosts Matched" msgstr "Geen overeenkomende hosts" -#: awx/main/models/events.py:125 +#: awx/main/models/events.py:173 msgid "Task Started" msgstr "Taak gestart" -#: awx/main/models/events.py:127 +#: awx/main/models/events.py:175 msgid "Variables Prompted" msgstr "Variabelen gevraagd" -#: awx/main/models/events.py:128 +#: awx/main/models/events.py:176 msgid "Gathering Facts" msgstr "Feiten verzamelen" -#: awx/main/models/events.py:129 +#: awx/main/models/events.py:177 msgid "internal: on Import for Host" msgstr "intern: bij importeren voor host" -#: awx/main/models/events.py:130 +#: awx/main/models/events.py:178 msgid "internal: on Not Import for Host" msgstr "intern: niet bij importeren voor host" -#: awx/main/models/events.py:131 +#: awx/main/models/events.py:179 msgid "Play Started" msgstr "Afspelen gestart" -#: awx/main/models/events.py:132 +#: awx/main/models/events.py:180 msgid "Playbook Complete" msgstr "Draaiboek voltooid" -#: awx/main/models/events.py:136 awx/main/models/events.py:647 +#: awx/main/models/events.py:184 awx/main/models/events.py:691 msgid "Debug" msgstr "Foutopsporing" -#: awx/main/models/events.py:137 awx/main/models/events.py:648 +#: awx/main/models/events.py:185 awx/main/models/events.py:692 msgid "Verbose" msgstr "Uitgebreid" -#: awx/main/models/events.py:138 awx/main/models/events.py:649 +#: awx/main/models/events.py:186 awx/main/models/events.py:693 msgid "Deprecated" msgstr "Afgeschaft" -#: awx/main/models/events.py:139 awx/main/models/events.py:650 +#: awx/main/models/events.py:187 awx/main/models/events.py:694 msgid "Warning" msgstr "Waarschuwing" -#: awx/main/models/events.py:140 awx/main/models/events.py:651 +#: awx/main/models/events.py:188 awx/main/models/events.py:695 msgid "System Warning" msgstr "Systeemwaarschuwing" -#: awx/main/models/events.py:141 awx/main/models/events.py:652 -#: awx/main/models/unified_jobs.py:67 +#: awx/main/models/events.py:189 awx/main/models/events.py:696 +#: awx/main/models/unified_jobs.py:75 msgid "Error" msgstr "Fout" -#: awx/main/models/fact.py:25 -msgid "Host for the facts that the fact scan captured." -msgstr "Host voor de feiten die de feitenscan heeft vastgelegd." - -#: awx/main/models/fact.py:30 -msgid "Date and time of the corresponding fact scan gathering time." -msgstr "Datum en tijd van de verzameltijd van de overeenkomstige feitenscan." - -#: awx/main/models/fact.py:33 -msgid "" -"Arbitrary JSON structure of module facts captured at timestamp for a single " -"host." -msgstr "" -"Willekeurige JSON-structuur van modulefeiten vastgelegd bij de tijdstempel " -"voor één host." - -#: awx/main/models/ha.py:181 +#: awx/main/models/ha.py:175 msgid "Instances that are members of this InstanceGroup" msgstr "Instanties die lid zijn van deze InstanceGroup" -#: awx/main/models/ha.py:186 +#: awx/main/models/ha.py:180 msgid "Instance Group to remotely control this group." msgstr "Instantiegroep om deze groep extern te regelen." -#: awx/main/models/ha.py:193 +#: awx/main/models/ha.py:200 msgid "Percentage of Instances to automatically assign to this group" -msgstr "" -"Percentage van instanties die automatisch aan deze groep toegewezen moeten " -"worden" +msgstr "Percentage van instanties die automatisch aan deze groep toegewezen moeten worden" -#: awx/main/models/ha.py:197 +#: awx/main/models/ha.py:204 msgid "" "Static minimum number of Instances to automatically assign to this group" -msgstr "" -"Statistisch minimumaantal instanties dat automatisch toegewezen moet worden " -"aan deze groep" +msgstr "Statistisch minimumaantal instanties dat automatisch toegewezen moet worden aan deze groep" -#: awx/main/models/ha.py:202 +#: awx/main/models/ha.py:209 msgid "" "List of exact-match Instances that will always be automatically assigned to " "this group" -msgstr "" -"Lijst van exact overeenkomende instanties die altijd automatisch worden " -"toegewezen aan deze groep" +msgstr "Lijst van exact overeenkomende instanties die altijd automatisch worden toegewezen aan deze groep" -#: awx/main/models/inventory.py:61 +#: awx/main/models/inventory.py:80 msgid "Hosts have a direct link to this inventory." msgstr "Hosts hebben een directe koppeling naar deze inventaris." -#: awx/main/models/inventory.py:62 +#: awx/main/models/inventory.py:81 msgid "Hosts for inventory generated using the host_filter property." msgstr "Hosts voor inventaris gegenereerd met de eigenschap host_filter." -#: awx/main/models/inventory.py:67 +#: awx/main/models/inventory.py:86 msgid "inventories" msgstr "inventarissen" -#: awx/main/models/inventory.py:74 +#: awx/main/models/inventory.py:93 msgid "Organization containing this inventory." msgstr "Organisatie die deze inventaris bevat." -#: awx/main/models/inventory.py:81 +#: awx/main/models/inventory.py:100 msgid "Inventory variables in JSON or YAML format." msgstr "Inventarisvariabelen in JSON- of YAML-indeling." -#: awx/main/models/inventory.py:86 -msgid "Flag indicating whether any hosts in this inventory have failed." -msgstr "Vlag die aangeeft of hosts in deze inventaris zijn mislukt." - -#: awx/main/models/inventory.py:91 -msgid "Total number of hosts in this inventory." -msgstr "Totaal aantal hosts in deze inventaris." +#: awx/main/models/inventory.py:105 +msgid "" +"This field is deprecated and will be removed in a future release. Flag " +"indicating whether any hosts in this inventory have failed." +msgstr "Dit veld is verouderd en wordt verwijderd uit toekomstige uitgaven. De vlag geeft aan of hosts in deze inventaris storingen ondervinden." -#: awx/main/models/inventory.py:96 -msgid "Number of hosts in this inventory with active failures." -msgstr "Aantal hosts in deze inventaris met actieve mislukkingen." +#: awx/main/models/inventory.py:111 +msgid "" +"This field is deprecated and will be removed in a future release. Total " +"number of hosts in this inventory." +msgstr "Dit veld is verouderd en wordt verwijderd uit toekomstige uitgaven. Totaalaantal hosts in deze inventaris." -#: awx/main/models/inventory.py:101 -msgid "Total number of groups in this inventory." -msgstr "Totaal aantal groepen in deze inventaris." +#: awx/main/models/inventory.py:117 +msgid "" +"This field is deprecated and will be removed in a future release. Number of " +"hosts in this inventory with active failures." +msgstr "Dit veld is verouderd en wordt verwijderd uit toekomstige uitgaven. Aantal hosts in deze inventaris met actieve storingen." -#: awx/main/models/inventory.py:106 -msgid "Number of groups in this inventory with active failures." -msgstr "Aantal groepen in deze inventaris met actieve mislukkingen." +#: awx/main/models/inventory.py:123 +msgid "" +"This field is deprecated and will be removed in a future release. Total " +"number of groups in this inventory." +msgstr "Dit veld is verouderd en wordt verwijderd uit toekomstige uitgaven. Totaalaantal groepen in deze inventaris." -#: awx/main/models/inventory.py:111 +#: awx/main/models/inventory.py:129 msgid "" -"Flag indicating whether this inventory has any external inventory sources." -msgstr "Vlag die aangeeft of deze inventaris externe inventarisbronnen heeft." +"This field is deprecated and will be removed in a future release. Flag " +"indicating whether this inventory has any external inventory sources." +msgstr "Dit veld is verouderd en wordt verwijderd uit toekomstige uitgaven. Vlag die aangeeft of deze inventaris externe inventarisbronnen bevat." -#: awx/main/models/inventory.py:116 +#: awx/main/models/inventory.py:135 msgid "" "Total number of external inventory sources configured within this inventory." -msgstr "" -"Totaal aantal externe inventarisbronnen dat binnen deze inventaris is " -"geconfigureerd." +msgstr "Totaal aantal externe inventarisbronnen dat binnen deze inventaris is geconfigureerd." -#: awx/main/models/inventory.py:121 +#: awx/main/models/inventory.py:140 msgid "Number of external inventory sources in this inventory with failures." msgstr "Aantal externe inventarisbronnen in deze inventaris met mislukkingen." -#: awx/main/models/inventory.py:128 +#: awx/main/models/inventory.py:147 msgid "Kind of inventory being represented." msgstr "Soort inventaris dat wordt voorgesteld." -#: awx/main/models/inventory.py:134 +#: awx/main/models/inventory.py:153 msgid "Filter that will be applied to the hosts of this inventory." msgstr "Filter dat wordt toegepast op de hosts van deze inventaris." -#: awx/main/models/inventory.py:161 +#: awx/main/models/inventory.py:181 msgid "" "Credentials to be used by hosts belonging to this inventory when accessing " "Red Hat Insights API." -msgstr "" -"Referenties die worden gebruikt door hosts die behoren tot deze inventaris " -"bij toegang tot de Red Hat Insights API." +msgstr "Referenties die worden gebruikt door hosts die behoren tot deze inventaris bij toegang tot de Red Hat Insights API." -#: awx/main/models/inventory.py:170 +#: awx/main/models/inventory.py:190 msgid "Flag indicating the inventory is being deleted." msgstr "Vlag die aangeeft dat de inventaris wordt verwijderd." -#: awx/main/models/inventory.py:459 +#: awx/main/models/inventory.py:245 +msgid "Could not parse subset as slice specification." +msgstr "Kan subset niet als deelspecificatie parseren." + +#: awx/main/models/inventory.py:249 +msgid "Slice number must be less than total number of slices." +msgstr "Deelaantal moet lager zijn dan het totale aantal delen." + +#: awx/main/models/inventory.py:251 +msgid "Slice number must be 1 or higher." +msgstr "Deelaantal moet 1 of hoger zijn." + +#: awx/main/models/inventory.py:388 msgid "Assignment not allowed for Smart Inventory" msgstr "Toewijzing niet toegestaan voor Smart-inventaris" -#: awx/main/models/inventory.py:461 awx/main/models/projects.py:159 +#: awx/main/models/inventory.py:390 awx/main/models/projects.py:166 msgid "Credential kind must be 'insights'." msgstr "Referentiesoort moet 'insights' zijn." -#: awx/main/models/inventory.py:546 +#: awx/main/models/inventory.py:475 msgid "Is this host online and available for running jobs?" msgstr "Is deze host online en beschikbaar om taken uit te voeren?" -#: awx/main/models/inventory.py:552 +#: awx/main/models/inventory.py:481 msgid "" "The value used by the remote inventory source to uniquely identify the host" -msgstr "" -"De waarde die de externe inventarisbron gebruikt om de host uniek te " -"identificeren" +msgstr "De waarde die de externe inventarisbron gebruikt om de host uniek te identificeren" -#: awx/main/models/inventory.py:557 +#: awx/main/models/inventory.py:486 msgid "Host variables in JSON or YAML format." msgstr "Hostvariabelen in JSON- of YAML-indeling." -#: awx/main/models/inventory.py:579 -msgid "Flag indicating whether the last job failed for this host." -msgstr "Vlag die aangeeft of de laatste taak voor deze host is mislukt." - -#: awx/main/models/inventory.py:584 -msgid "" -"Flag indicating whether this host was created/updated from any external " -"inventory sources." -msgstr "" -"Vlag die aangeeft of deze host is gemaakt/bijgewerkt op grond van externe " -"inventarisbronnen." - -#: awx/main/models/inventory.py:590 +#: awx/main/models/inventory.py:509 msgid "Inventory source(s) that created or modified this host." msgstr "Inventarisbronnen die deze host hebben gemaakt of gewijzigd." -#: awx/main/models/inventory.py:595 +#: awx/main/models/inventory.py:514 msgid "Arbitrary JSON structure of most recent ansible_facts, per-host." -msgstr "" -"Willekeurige JSON-structuur van meest recente ansible_facts, per host/" +msgstr "Willekeurige JSON-structuur van meest recente ansible_facts, per host/" -#: awx/main/models/inventory.py:601 +#: awx/main/models/inventory.py:520 msgid "The date and time ansible_facts was last modified." msgstr "De datum en tijd waarop ansible_facts voor het laatst is gewijzigd." -#: awx/main/models/inventory.py:608 +#: awx/main/models/inventory.py:527 msgid "Red Hat Insights host unique identifier." msgstr "Unieke id van Red Hat Insights-host." -#: awx/main/models/inventory.py:743 +#: awx/main/models/inventory.py:641 msgid "Group variables in JSON or YAML format." msgstr "Groepeer variabelen in JSON- of YAML-indeling." -#: awx/main/models/inventory.py:749 +#: awx/main/models/inventory.py:647 msgid "Hosts associated directly with this group." msgstr "Hosts direct gekoppeld aan deze groep." -#: awx/main/models/inventory.py:754 -msgid "Total number of hosts directly or indirectly in this group." -msgstr "Totaal aantal hosts dat direct of indirect in deze groep is." - -#: awx/main/models/inventory.py:759 -msgid "Flag indicating whether this group has any hosts with active failures." -msgstr "Vlag die aangeeft of deze groep hosts met actieve mislukkingen heeft." - -#: awx/main/models/inventory.py:764 -msgid "Number of hosts in this group with active failures." -msgstr "Aantal hosts in deze groep met actieve mislukkingen." - -#: awx/main/models/inventory.py:769 -msgid "Total number of child groups contained within this group." -msgstr "Totaal aantal onderliggende groepen binnen deze groep." - -#: awx/main/models/inventory.py:774 -msgid "Number of child groups within this group that have active failures." -msgstr "Aantal onderliggende groepen in deze groep met actieve mislukkingen." - -#: awx/main/models/inventory.py:779 -msgid "" -"Flag indicating whether this group was created/updated from any external " -"inventory sources." -msgstr "" -"Vlag die aangeeft of deze groep is gemaakt/bijgewerkt op grond van externe " -"inventarisbronnen." - -#: awx/main/models/inventory.py:785 +#: awx/main/models/inventory.py:653 msgid "Inventory source(s) that created or modified this group." msgstr "Inventarisbronnen die deze groep hebben gemaakt of gewijzigd." -#: awx/main/models/inventory.py:981 awx/main/models/projects.py:53 -#: awx/main/models/unified_jobs.py:519 -msgid "Manual" -msgstr "Handmatig" - -#: awx/main/models/inventory.py:982 +#: awx/main/models/inventory.py:825 msgid "File, Directory or Script" msgstr "Bestand, map of script" -#: awx/main/models/inventory.py:983 +#: awx/main/models/inventory.py:826 msgid "Sourced from a Project" msgstr "Afkomstig uit een project" -#: awx/main/models/inventory.py:984 +#: awx/main/models/inventory.py:827 msgid "Amazon EC2" msgstr "Amazon EC2" -#: awx/main/models/inventory.py:993 +#: awx/main/models/inventory.py:836 msgid "Custom Script" msgstr "Aangepast script" -#: awx/main/models/inventory.py:1110 +#: awx/main/models/inventory.py:953 msgid "Inventory source variables in YAML or JSON format." msgstr "Bronvariabelen inventaris in YAML- of JSON-indeling." -#: awx/main/models/inventory.py:1121 +#: awx/main/models/inventory.py:964 msgid "" "Comma-separated list of filter expressions (EC2 only). Hosts are imported " "when ANY of the filters match." -msgstr "" -"Door komma's gescheiden lijst met filterexpressies (alleen EC2). Hosts " -"worden geïmporteerd wanneer WILLEKEURIG WELK van de filters overeenkomt." +msgstr "Door komma's gescheiden lijst met filterexpressies (alleen EC2). Hosts worden geïmporteerd wanneer WILLEKEURIG WELK van de filters overeenkomt." -#: awx/main/models/inventory.py:1127 +#: awx/main/models/inventory.py:970 msgid "Limit groups automatically created from inventory source (EC2 only)." -msgstr "" -"Overschrijf groepen die automatisch gemaakt worden op grond van " -"inventarisbron (alleen EC2)." +msgstr "Overschrijf groepen die automatisch gemaakt worden op grond van inventarisbron (alleen EC2)." -#: awx/main/models/inventory.py:1131 +#: awx/main/models/inventory.py:974 msgid "Overwrite local groups and hosts from remote inventory source." -msgstr "" -"Overschrijf lokale groepen en hosts op grond van externe inventarisbron." +msgstr "Overschrijf lokale groepen en hosts op grond van externe inventarisbron." -#: awx/main/models/inventory.py:1135 +#: awx/main/models/inventory.py:978 msgid "Overwrite local variables from remote inventory source." msgstr "Overschrijf lokale variabelen op grond van externe inventarisbron." -#: awx/main/models/inventory.py:1140 awx/main/models/jobs.py:140 -#: awx/main/models/projects.py:128 +#: awx/main/models/inventory.py:983 awx/main/models/jobs.py:154 +#: awx/main/models/projects.py:135 msgid "The amount of time (in seconds) to run before the task is canceled." -msgstr "" -"De hoeveelheid tijd (in seconden) voor uitvoering voordat de taak wordt " -"geannuleerd." +msgstr "De hoeveelheid tijd (in seconden) voor uitvoering voordat de taak wordt geannuleerd." -#: awx/main/models/inventory.py:1173 +#: awx/main/models/inventory.py:1016 msgid "Image ID" msgstr "Image-id" -#: awx/main/models/inventory.py:1174 +#: awx/main/models/inventory.py:1017 msgid "Availability Zone" msgstr "Beschikbaarheidszone" -#: awx/main/models/inventory.py:1175 -msgid "Account" -msgstr "Account" - -#: awx/main/models/inventory.py:1176 +#: awx/main/models/inventory.py:1019 msgid "Instance ID" msgstr "Instantie-id" -#: awx/main/models/inventory.py:1177 +#: awx/main/models/inventory.py:1020 msgid "Instance State" msgstr "Instantiestaat" -#: awx/main/models/inventory.py:1178 +#: awx/main/models/inventory.py:1021 msgid "Platform" msgstr "Platform" -#: awx/main/models/inventory.py:1179 +#: awx/main/models/inventory.py:1022 msgid "Instance Type" msgstr "Instantietype" -#: awx/main/models/inventory.py:1180 -msgid "Key Name" -msgstr "Sleutelnaam" - -#: awx/main/models/inventory.py:1181 +#: awx/main/models/inventory.py:1024 msgid "Region" msgstr "Regio" -#: awx/main/models/inventory.py:1182 +#: awx/main/models/inventory.py:1025 msgid "Security Group" msgstr "Beveiligingsgroep" -#: awx/main/models/inventory.py:1183 +#: awx/main/models/inventory.py:1026 msgid "Tags" msgstr "Tags" -#: awx/main/models/inventory.py:1184 +#: awx/main/models/inventory.py:1027 msgid "Tag None" msgstr "Tag geen" -#: awx/main/models/inventory.py:1185 +#: awx/main/models/inventory.py:1028 msgid "VPC ID" msgstr "VPC ID" -#: awx/main/models/inventory.py:1253 +#: awx/main/models/inventory.py:1096 #, python-format msgid "" "Cloud-based inventory sources (such as %s) require credentials for the " "matching cloud service." -msgstr "" -"Cloudgebaseerde inventarisbronnen (zoals %s) vereisen referenties voor de " -"overeenkomende cloudservice." +msgstr "Cloudgebaseerde inventarisbronnen (zoals %s) vereisen toegangsgegevens voor de overeenkomende cloudservice." -#: awx/main/models/inventory.py:1259 +#: awx/main/models/inventory.py:1102 msgid "Credential is required for a cloud source." msgstr "Referentie is vereist voor een cloudbron." -#: awx/main/models/inventory.py:1262 +#: awx/main/models/inventory.py:1105 msgid "" "Credentials of type machine, source control, insights and vault are " "disallowed for custom inventory sources." -msgstr "" -"Toegangsgegevens van soort machine, bronbeheer, inzichten en kluis zijn niet" -" toegestaan voor aangepaste inventarisbronnen." +msgstr "Toegangsgegevens van soort machine, bronbeheer, inzichten en kluis zijn niet toegestaan voor aangepaste inventarisbronnen." + +#: awx/main/models/inventory.py:1110 +msgid "" +"Credentials of type insights and vault are disallowed for scm inventory " +"sources." +msgstr "Toegangsgegevens van het soort inzichten en kluis zijn niet toegestaan voor scm-inventarisbronnen." -#: awx/main/models/inventory.py:1314 +#: awx/main/models/inventory.py:1170 #, python-format msgid "Invalid %(source)s region: %(region)s" msgstr "Ongeldige %(source)s regio: %(region)s" -#: awx/main/models/inventory.py:1338 +#: awx/main/models/inventory.py:1194 #, python-format msgid "Invalid filter expression: %(filter)s" msgstr "Ongeldige filterexpressie: %(filter)s" -#: awx/main/models/inventory.py:1359 +#: awx/main/models/inventory.py:1215 #, python-format msgid "Invalid group by choice: %(choice)s" msgstr "Ongeldige groep op keuze: %(choice)s" -#: awx/main/models/inventory.py:1394 +#: awx/main/models/inventory.py:1243 msgid "Project containing inventory file used as source." msgstr "Project met inventarisbestand dat wordt gebruikt als bron." -#: awx/main/models/inventory.py:1555 -#, python-format -msgid "" -"Unable to configure this item for cloud sync. It is already managed by %s." -msgstr "" -"Kan dit item niet configureren voor cloudsynchronisatie. Wordt al beheerd " -"door %s." - -#: awx/main/models/inventory.py:1565 +#: awx/main/models/inventory.py:1416 msgid "" "More than one SCM-based inventory source with update on project update per-" "inventory not allowed." -msgstr "" -"Het is niet toegestaan meer dan één SCM-gebaseerde inventarisbron met een " -"update bovenop een projectupdate per inventaris te hebben." +msgstr "Het is niet toegestaan meer dan één SCM-gebaseerde inventarisbron met een update bovenop een projectupdate per inventaris te hebben." -#: awx/main/models/inventory.py:1572 +#: awx/main/models/inventory.py:1423 msgid "" "Cannot update SCM-based inventory source on launch if set to update on " "project update. Instead, configure the corresponding source project to " "update on launch." -msgstr "" -"Kan SCM-gebaseerde inventarisbron niet bijwerken bij opstarten indien " -"ingesteld op bijwerken bij projectupdate. Configureer in plaats daarvan het " -"overeenkomstige bronproject om bij te werken bij opstarten." - -#: awx/main/models/inventory.py:1579 -msgid "" -"SCM type sources must set `overwrite_vars` to `true` until Ansible 2.5." -msgstr "" -"SCM-typebronnen moeten 'overwrite_vars' instellen op 'true' tot aan Ansible " -"2.5." +msgstr "Kan SCM-gebaseerde inventarisbron niet bijwerken bij opstarten indien ingesteld op bijwerken bij projectupdate. Configureer in plaats daarvan het overeenkomstige bronproject om bij te werken bij opstarten." -#: awx/main/models/inventory.py:1584 +#: awx/main/models/inventory.py:1429 msgid "Cannot set source_path if not SCM type." msgstr "Kan source_path niet instellen als het geen SCM-type is." -#: awx/main/models/inventory.py:1622 +#: awx/main/models/inventory.py:1472 msgid "" "Inventory files from this Project Update were used for the inventory update." -msgstr "" -"Inventarisbestanden uit deze projectupdate zijn gebruikt voor de " -"inventarisupdate." +msgstr "Inventarisbestanden uit deze projectupdate zijn gebruikt voor de inventarisupdate." -#: awx/main/models/inventory.py:1732 +#: awx/main/models/inventory.py:1583 msgid "Inventory script contents" msgstr "Inhoud inventarisscript" -#: awx/main/models/inventory.py:1737 +#: awx/main/models/inventory.py:1588 msgid "Organization owning this inventory script" msgstr "Organisatie die eigenaar is van deze inventarisscript" -#: awx/main/models/jobs.py:66 +#: awx/main/models/jobs.py:74 msgid "" "If enabled, textual changes made to any templated files on the host are " "shown in the standard output" -msgstr "" -"Indien ingeschakeld, worden tekstwijzigingen aangebracht in " -"sjabloonbestanden op de host weergegeven in de standaardoutput" +msgstr "Indien ingeschakeld, worden tekstwijzigingen aangebracht in sjabloonbestanden op de host weergegeven in de standaardoutput" + +#: awx/main/models/jobs.py:106 +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." -#: awx/main/models/jobs.py:145 +#: awx/main/models/jobs.py:159 msgid "" -"If enabled, Tower will act as an Ansible Fact Cache Plugin; persisting facts" -" at the end of a playbook run to the database and caching facts for use by " +"If enabled, Tower will act as an Ansible Fact Cache Plugin; persisting facts " +"at the end of a playbook run to the database and caching facts for use by " "Ansible." -msgstr "" -"Indien ingeschakeld, treedt Tower op als een Ansible Fact Cache Plugin en " -"handhaaft het feiten aan het einde van een draaiboekuitvoering in een " -"database en worden feiten voor gebruik door Ansible in het cachegeheugen " -"opgeslagen." +msgstr "Indien ingeschakeld, treedt Tower op als een Ansible Fact Cache Plugin en handhaaft het feiten aan het einde van een draaiboekuitvoering in een database en worden feiten voor gebruik door Ansible in het cachegeheugen opgeslagen." -#: awx/main/models/jobs.py:163 -msgid "You must provide a Vault credential." -msgstr "U moet een kluisreferentie opgeven." +#: awx/main/models/jobs.py:260 +msgid "" +"The number of jobs to slice into at runtime. Will cause the Job Template to " +"launch a workflow if value is greater than 1." +msgstr "Het aantal taken om in te verdelen bij doorlooptijd. Zorgt ervoor dat de taaksjabloon een workflow opstart als de waarde groter is dan 1." -#: awx/main/models/jobs.py:308 +#: awx/main/models/jobs.py:297 msgid "Job Template must provide 'inventory' or allow prompting for it." msgstr "Taaksjabloon moet 'inventory' verschaffen of toestaan erom te vragen." -#: awx/main/models/jobs.py:398 +#: awx/main/models/jobs.py:308 +#, python-brace-format +msgid "Maximum number of forks ({settings.MAX_FORKS}) exceeded." +msgstr "Maximaal aantal vorken ({settings.MAX_FORKS}) overschreden." + +#: awx/main/models/jobs.py:463 +msgid "Project is missing." +msgstr "Project ontbreekt." + +#: awx/main/models/jobs.py:467 +msgid "Project does not allow override of branch." +msgstr "Project laat geen overschrijving van vertakking toe." + +#: awx/main/models/jobs.py:477 awx/main/models/workflow.py:545 msgid "Field is not configured to prompt on launch." msgstr "Veld is niet ingesteld om een melding te sturen bij opstarten." -#: awx/main/models/jobs.py:404 +#: awx/main/models/jobs.py:483 msgid "Saved launch configurations cannot provide passwords needed to start." -msgstr "" -"Opgeslagen instellingen voor bij opstarten kunnen geen wachtwoorden die " -"nodig zijn voor opstarten opgeven." +msgstr "Opgeslagen instellingen voor bij opstarten kunnen geen wachtwoorden die nodig zijn voor opstarten opgeven." -#: awx/main/models/jobs.py:412 +#: awx/main/models/jobs.py:491 msgid "Job Template {} is missing or undefined." msgstr "Taaksjabloon {} ontbreekt of is niet gedefinieerd." -#: awx/main/models/jobs.py:493 awx/main/models/projects.py:277 +#: awx/main/models/jobs.py:574 awx/main/models/projects.py:278 +#: awx/main/models/projects.py:497 msgid "SCM Revision" msgstr "SCM-revisie" -#: awx/main/models/jobs.py:494 +#: awx/main/models/jobs.py:575 msgid "The SCM Revision from the Project used for this job, if available" -msgstr "" -"De SCM-revisie uit het project gebruikt voor deze taak, indien beschikbaar" +msgstr "De SCM-revisie uit het project gebruikt voor deze taak, indien beschikbaar" -#: awx/main/models/jobs.py:502 +#: awx/main/models/jobs.py:583 msgid "" "The SCM Refresh task used to make sure the playbooks were available for the " "job run" -msgstr "" -"De taak SCM vernieuwen gebruik om te verzekeren dat de draaiboeken " -"beschikbaar waren om de taak uit te voeren" +msgstr "De taak SCM vernieuwen gebruik om te verzekeren dat de draaiboeken beschikbaar waren om de taak uit te voeren" -#: awx/main/models/jobs.py:629 +#: awx/main/models/jobs.py:588 +msgid "" +"If part of a sliced job, the ID of the inventory slice operated on. If not " +"part of sliced job, parameter is not used." +msgstr "Indien onderdeel van een verdeelde taak, is dit het ID van het inventarisdeel waaraan gewerkt wordt. Indien geen onderdeel van een verdeelde taak, wordt deze parameter niet gebruikt." + +#: awx/main/models/jobs.py:594 +msgid "" +"If ran as part of sliced jobs, the total number of slices. If 1, job is not " +"part of a sliced job." +msgstr "Indien uitgevoerd als onderdeel van verdeelde taken, het aantal delen. Indien 1 taak, dan is het niet onderdeel van een verdeelde taak." + +#: awx/main/models/jobs.py:676 #, python-brace-format msgid "{status_value} is not a valid status option." msgstr "{status_value} is geen geldige statusoptie." -#: awx/main/models/jobs.py:1005 +#: awx/main/models/jobs.py:926 +msgid "" +"Inventory applied as a prompt, assuming job template prompts for inventory" +msgstr "Inventarisatie toegepast als een melding, neemt de vorm aan van taaksjabloonmelding voor de inventarisatie" + +#: awx/main/models/jobs.py:1085 msgid "job host summaries" msgstr "taakhostoverzichten" -#: awx/main/models/jobs.py:1077 +#: awx/main/models/jobs.py:1144 msgid "Remove jobs older than a certain number of days" msgstr "Taken ouder dan een bepaald aantal dagen verwijderen" -#: awx/main/models/jobs.py:1078 +#: awx/main/models/jobs.py:1145 msgid "Remove activity stream entries older than a certain number of days" -msgstr "" -"Vermeldingen activiteitenstroom ouder dan een bepaald aantal dagen " -"verwijderen" +msgstr "Vermeldingen activiteitenstroom ouder dan een bepaald aantal dagen verwijderen" -#: awx/main/models/jobs.py:1079 -msgid "Purge and/or reduce the granularity of system tracking data" -msgstr "" -"Granulariteit van systeemtrackinggegevens verwijderen en/of verminderen" +#: awx/main/models/jobs.py:1146 +msgid "Removes expired browser sessions from the database" +msgstr "Verwijdert verlopen browsersessies uit de database" -#: awx/main/models/jobs.py:1149 +#: awx/main/models/jobs.py:1147 +msgid "Removes expired OAuth 2 access tokens and refresh tokens" +msgstr "Verwijdert vervallen OAuth 2-toegangstokens en verversingstokens" + +#: awx/main/models/jobs.py:1217 #, python-brace-format msgid "Variables {list_of_keys} are not allowed for system jobs." msgstr "Variabelen {list_of_keys} zijn niet toegestaan voor systeemtaken." -#: awx/main/models/jobs.py:1164 +#: awx/main/models/jobs.py:1233 msgid "days must be a positive integer." msgstr "dagen moet een positief geheel getal zijn." @@ -3816,127 +4039,169 @@ msgstr "dagen moet een positief geheel getal zijn." msgid "Organization this label belongs to." msgstr "Organisatie waartoe dit label behoort." -#: awx/main/models/mixins.py:309 +#: awx/main/models/mixins.py:321 #, python-brace-format msgid "" "Variables {list_of_keys} are not allowed on launch. Check the Prompt on " -"Launch setting on the Job Template to include Extra Variables." -msgstr "" -"Variabelen {list_of_keys} zijn niet toegestaan bij opstarten. Ga naar de " -"instelling Melding bij opstarten op het taaksjabloon om extra variabelen toe" -" te voegen." +"Launch setting on the {model_name} to include Extra Variables." +msgstr "Variabelen {list_of_keys} zijn niet toegestaan bij opstarten. Ga naar de instelling Melding bij opstarten op {model_name} om extra variabelen toe te voegen." -#: awx/main/models/mixins.py:440 +#: awx/main/models/mixins.py:453 msgid "Local absolute file path containing a custom Python virtualenv to use" -msgstr "" -"Plaatselijk absoluut bestandspad dat een aangepaste Python virtualenv bevat " -"om te gebruiken" +msgstr "Plaatselijk absoluut bestandspad dat een aangepaste Python virtualenv bevat om te gebruiken" -#: awx/main/models/mixins.py:447 +#: awx/main/models/mixins.py:460 msgid "{} is not a valid virtualenv in {}" msgstr "{} is geen geldige virtualenv in {}" +#: awx/main/models/mixins.py:507 awx/main/models/mixins.py:551 +msgid "Service that webhook requests will be accepted from" +msgstr "Service van waar webhookverzoeken worden geaccepteerd" + +#: awx/main/models/mixins.py:512 +msgid "Shared secret that the webhook service will use to sign requests" +msgstr "Gedeeld geheim dat de webhookservice gebruikt om verzoeken te ondertekenen" + +#: awx/main/models/mixins.py:520 awx/main/models/mixins.py:559 +msgid "Personal Access Token for posting back the status to the service API" +msgstr "Persoonlijk Toegangstoken voor het terugplaatsen van de status naar de API-service" + +#: awx/main/models/mixins.py:564 +msgid "Unique identifier of the event that triggered this webhook" +msgstr "Unieke identificatie van de gebeurtenis die deze webhook heeft geactiveerd" + #: awx/main/models/notifications.py:42 +msgid "Email" +msgstr "E-mail" + +#: awx/main/models/notifications.py:43 +msgid "Slack" +msgstr "Slack" + +#: awx/main/models/notifications.py:44 +msgid "Twilio" +msgstr "Twilio" + +#: awx/main/models/notifications.py:45 +msgid "Pagerduty" +msgstr "Pagerduty" + +#: awx/main/models/notifications.py:46 +msgid "Grafana" +msgstr "Grafana" + +#: awx/main/models/notifications.py:47 +msgid "HipChat" +msgstr "HipChat" + +#: awx/main/models/notifications.py:48 awx/main/models/unified_jobs.py:545 +msgid "Webhook" +msgstr "Webhook" + +#: awx/main/models/notifications.py:49 +msgid "Mattermost" +msgstr "Mattermost" + +#: awx/main/models/notifications.py:50 msgid "Rocket.Chat" msgstr "Rocket.Chat" -#: awx/main/models/notifications.py:142 awx/main/models/unified_jobs.py:62 +#: awx/main/models/notifications.py:51 +msgid "IRC" +msgstr "IRC" + +#: awx/main/models/notifications.py:82 +msgid "Optional custom messages for notification template." +msgstr "Optionele aangepaste berichten voor berichtensjabloon." + +#: awx/main/models/notifications.py:212 awx/main/models/unified_jobs.py:70 msgid "Pending" msgstr "In afwachting" -#: awx/main/models/notifications.py:143 awx/main/models/unified_jobs.py:65 +#: awx/main/models/notifications.py:213 awx/main/models/unified_jobs.py:73 msgid "Successful" msgstr "Geslaagd" -#: awx/main/models/notifications.py:144 awx/main/models/unified_jobs.py:66 +#: awx/main/models/notifications.py:214 awx/main/models/unified_jobs.py:74 msgid "Failed" msgstr "Mislukt" -#: awx/main/models/notifications.py:218 -msgid "status_str must be either succeeded or failed" -msgstr "status_str must moet geslaagd of mislukt zijn" +#: awx/main/models/notifications.py:467 +msgid "status must be either running, succeeded or failed" +msgstr "status moet in uitvoering, geslaagd of mislukt zijn" -#: awx/main/models/oauth.py:29 +#: awx/main/models/oauth.py:33 msgid "application" msgstr "toepassing" -#: awx/main/models/oauth.py:35 +#: awx/main/models/oauth.py:40 msgid "Confidential" msgstr "Vertrouwelijk" -#: awx/main/models/oauth.py:36 +#: awx/main/models/oauth.py:41 msgid "Public" msgstr "Openbaar" -#: awx/main/models/oauth.py:43 +#: awx/main/models/oauth.py:47 msgid "Authorization code" msgstr "Machtigingscode" -#: awx/main/models/oauth.py:44 -msgid "Implicit" -msgstr "Impliciet" - -#: awx/main/models/oauth.py:45 +#: awx/main/models/oauth.py:48 msgid "Resource owner password-based" msgstr "Eigenaar hulpbron op basis van wachtwoord" -#: awx/main/models/oauth.py:60 +#: awx/main/models/oauth.py:63 msgid "Organization containing this application." msgstr "Organisatie die deze toepassing bevat." -#: awx/main/models/oauth.py:69 +#: awx/main/models/oauth.py:72 msgid "" "Used for more stringent verification of access to an application when " "creating a token." -msgstr "" -"Gebruikt voor strengere toegangscontrole voor een toepassing bij het " -"aanmaken van een token." +msgstr "Gebruikt voor strengere toegangscontrole voor een toepassing bij het aanmaken van een token." -#: awx/main/models/oauth.py:74 +#: awx/main/models/oauth.py:77 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." +msgstr "Ingesteld op openbaar of vertrouwelijk, afhankelijk van de beveiliging van het toestel van de klant." -#: awx/main/models/oauth.py:78 +#: awx/main/models/oauth.py:81 msgid "" "Set True to skip authorization step for completely trusted applications." -msgstr "" -"Stel in op True om de autorisatie over te slaan voor volledig vertrouwde " -"toepassingen." +msgstr "Stel in op True om de autorisatie over te slaan voor volledig vertrouwde toepassingen." -#: awx/main/models/oauth.py:83 +#: awx/main/models/oauth.py:86 msgid "" "The Grant type the user must use for acquire tokens for this application." -msgstr "" -"Het soort toekenning dat de gebruiker moet gebruiken om tokens te verkrijgen" -" voor deze toepassing." +msgstr "Het soort toekenning dat de gebruiker moet gebruiken om tokens te verkrijgen voor deze toepassing." -#: awx/main/models/oauth.py:91 +#: awx/main/models/oauth.py:94 msgid "access token" msgstr "toegangstoken" -#: awx/main/models/oauth.py:99 +#: awx/main/models/oauth.py:103 msgid "The user representing the token owner" msgstr "De gebruiker die de tokeneigenaar vertegenwoordigt" -#: awx/main/models/oauth.py:114 +#: awx/main/models/oauth.py:117 msgid "" -"Allowed scopes, further restricts user's permissions. Must be a simple " -"space-separated string with allowed scopes ['read', 'write']." -msgstr "" -"Toegestane bereiken, beperkt de machtigingen van de gebruiker verder. Moet " -"een reeks zijn die gescheiden is met enkele spaties en die toegestane " -"bereiken heeft ['lezen', 'schrijven']." +"Allowed scopes, further restricts user's permissions. Must be a simple space-" +"separated string with allowed scopes ['read', 'write']." +msgstr "Toegestane bereiken, beperkt de machtigingen van de gebruiker verder. Moet een reeks zijn die gescheiden is met enkele spaties en die toegestane bereiken heeft ['lezen', 'schrijven']." -#: awx/main/models/oauth.py:133 +#: awx/main/models/oauth.py:140 msgid "" "OAuth2 Tokens cannot be created by users associated with an external " "authentication provider ({})" -msgstr "" -"OAuth2-tokens kunnen niet aangemaakt worden door gebruikers die verbonden " -"zijn met een externe verificatieprovider ({})" +msgstr "OAuth2-tokens kunnen niet aangemaakt worden door gebruikers die verbonden zijn met een externe verificatieprovider ({})" + +#: awx/main/models/organization.py:51 +msgid "Maximum number of hosts allowed to be managed by this organization." +msgstr "Maximumaantal hosts dat door deze organisatie beheerd mag worden." + +#: awx/main/models/projects.py:53 awx/main/models/unified_jobs.py:539 +msgid "Manual" +msgstr "Handmatig" #: awx/main/models/projects.py:54 msgid "Git" @@ -3958,9 +4223,7 @@ msgstr "Red Hat Insights" msgid "" "Local path (relative to PROJECTS_ROOT) containing playbooks and related " "files for this project." -msgstr "" -"Lokaal pad (ten opzichte van PROJECTS_ROOT) met draaiboeken en gerelateerde " -"bestanden voor dit project." +msgstr "Lokaal pad (ten opzichte van PROJECTS_ROOT) met draaiboeken en gerelateerde bestanden voor dit project." #: awx/main/models/projects.py:92 msgid "SCM Type" @@ -3968,8 +4231,7 @@ msgstr "Type SCM" #: awx/main/models/projects.py:93 msgid "Specifies the source control system used to store the project." -msgstr "" -"Specificeert het broncontrolesysteem gebruikt om het project op te slaan." +msgstr "Specificeert het broncontrolesysteem gebruikt om het project op te slaan." #: awx/main/models/projects.py:99 msgid "SCM URL" @@ -3987,147 +4249,172 @@ msgstr "SCM-vertakking" msgid "Specific branch, tag or commit to checkout." msgstr "Specifieke vertakking, tag of toewijzing om uit te checken." -#: awx/main/models/projects.py:111 +#: awx/main/models/projects.py:113 +msgid "SCM refspec" +msgstr "SCM-refspec" + +#: awx/main/models/projects.py:114 +msgid "For git projects, an additional refspec to fetch." +msgstr "Een extra refspec halen voor git-projecten" + +#: awx/main/models/projects.py:118 msgid "Discard any local changes before syncing the project." -msgstr "" -"Verwijder alle lokale wijzigingen voordat u het project synchroniseert." +msgstr "Verwijder alle lokale wijzigingen voordat u het project synchroniseert." -#: awx/main/models/projects.py:115 +#: awx/main/models/projects.py:122 msgid "Delete the project before syncing." msgstr "Verwijder het project alvorens te synchroniseren." -#: awx/main/models/projects.py:144 +#: awx/main/models/projects.py:151 msgid "Invalid SCM URL." msgstr "Ongeldige SCM URL." -#: awx/main/models/projects.py:147 +#: awx/main/models/projects.py:154 msgid "SCM URL is required." msgstr "SCM URL is vereist." -#: awx/main/models/projects.py:155 +#: awx/main/models/projects.py:162 msgid "Insights Credential is required for an Insights Project." msgstr "Insights-referentie is vereist voor een Insights-project." -#: awx/main/models/projects.py:161 +#: awx/main/models/projects.py:168 msgid "Credential kind must be 'scm'." msgstr "Referentie moet 'scm' zijn." -#: awx/main/models/projects.py:178 +#: awx/main/models/projects.py:185 msgid "Invalid credential." msgstr "Ongeldige referentie." -#: awx/main/models/projects.py:263 +#: awx/main/models/projects.py:259 msgid "Update the project when a job is launched that uses the project." -msgstr "" -"Werk het project bij wanneer een taak wordt gestart waarin het project wordt" -" gebruikt." +msgstr "Werk het project bij wanneer een taak wordt gestart waarin het project wordt gebruikt." -#: awx/main/models/projects.py:268 +#: awx/main/models/projects.py:264 msgid "" -"The number of seconds after the last project update ran that a newproject " +"The number of seconds after the last project update ran that a new project " "update will be launched as a job dependency." -msgstr "" -"Het aantal seconden na uitvoering van de laatste projectupdate dat een " -"nieuwe projectupdate wordt gestart als een taakafhankelijkheid." +msgstr "Het aantal seconden na uitvoering van de laatste projectupdate waarna een nieuwe projectupdate wordt gestart als een taakafhankelijkheid." + +#: awx/main/models/projects.py:269 +msgid "" +"Allow changing the SCM branch or revision in a job template that uses this " +"project." +msgstr "Maak het mogelijk om de SCM-tak of de revisie te wijzigen in een taaksjabloon die gebruik maakt van dit project." -#: awx/main/models/projects.py:278 +#: awx/main/models/projects.py:279 msgid "The last revision fetched by a project update" msgstr "De laatste revisie opgehaald door een projectupdate" -#: awx/main/models/projects.py:285 +#: awx/main/models/projects.py:286 msgid "Playbook Files" msgstr "Draaiboekbestanden" -#: awx/main/models/projects.py:286 +#: awx/main/models/projects.py:287 msgid "List of playbooks found in the project" msgstr "Lijst met draaiboekbestanden aangetroffen in het project" -#: awx/main/models/projects.py:293 +#: awx/main/models/projects.py:294 msgid "Inventory Files" msgstr "Inventarisbestanden" -#: awx/main/models/projects.py:294 +#: awx/main/models/projects.py:295 msgid "" "Suggested list of content that could be Ansible inventory in the project" -msgstr "" -"Aanbevolen lijst met inhoud die een Ansible-inventaris in het project kan " -"zijn" +msgstr "Aanbevolen lijst met inhoud die een Ansible-inventaris in het project kan zijn" -#: awx/main/models/rbac.py:36 +#: awx/main/models/projects.py:332 +msgid "Organization cannot be changed when in use by job templates." +msgstr "De organisatie kan niet worden gewijzigd wanneer deze gebruikt wordt door sjablonen." + +#: awx/main/models/projects.py:490 +msgid "Parts of the project update playbook that will be run." +msgstr "Delen van het projectupdatedraaiboek die worden uitgevoerd." + +#: awx/main/models/projects.py:498 +msgid "" +"The SCM Revision discovered by this update for the given project and branch." +msgstr "De SCM-revisie die door deze update voor het betreffende project en de betreffende vertakking ontdekt is." + +#: awx/main/models/rbac.py:35 msgid "System Administrator" msgstr "Systeembeheerder" -#: awx/main/models/rbac.py:37 +#: awx/main/models/rbac.py:36 msgid "System Auditor" msgstr "Systeemcontroleur" -#: awx/main/models/rbac.py:38 +#: awx/main/models/rbac.py:37 msgid "Ad Hoc" msgstr "Ad hoc" -#: awx/main/models/rbac.py:39 +#: awx/main/models/rbac.py:38 msgid "Admin" msgstr "Beheerder" -#: awx/main/models/rbac.py:40 +#: awx/main/models/rbac.py:39 msgid "Project Admin" msgstr "Projectbeheerder" -#: awx/main/models/rbac.py:41 +#: awx/main/models/rbac.py:40 msgid "Inventory Admin" msgstr "Inventarisbeheerder" -#: awx/main/models/rbac.py:42 +#: awx/main/models/rbac.py:41 msgid "Credential Admin" msgstr "Toegangsgegevensbeheerder" -#: awx/main/models/rbac.py:43 +#: awx/main/models/rbac.py:42 msgid "Job Template Admin" msgstr "Beheer taaksjabloon" -#: awx/main/models/rbac.py:44 +#: awx/main/models/rbac.py:43 msgid "Workflow Admin" msgstr "Workflowbeheerder" -#: awx/main/models/rbac.py:45 +#: awx/main/models/rbac.py:44 msgid "Notification Admin" msgstr "Meldingbeheerder" -#: awx/main/models/rbac.py:46 +#: awx/main/models/rbac.py:45 msgid "Auditor" msgstr "Controleur" -#: awx/main/models/rbac.py:47 +#: awx/main/models/rbac.py:46 msgid "Execute" msgstr "Uitvoeren" -#: awx/main/models/rbac.py:48 +#: awx/main/models/rbac.py:47 msgid "Member" msgstr "Lid" -#: awx/main/models/rbac.py:49 +#: awx/main/models/rbac.py:48 msgid "Read" msgstr "Lezen" -#: awx/main/models/rbac.py:50 +#: awx/main/models/rbac.py:49 msgid "Update" msgstr "Bijwerken" -#: awx/main/models/rbac.py:51 +#: awx/main/models/rbac.py:50 msgid "Use" msgstr "Gebruiken" +#: awx/main/models/rbac.py:51 +msgid "Approve" +msgstr "Goedkeuring" + #: awx/main/models/rbac.py:55 msgid "Can manage all aspects of the system" msgstr "Kan alle aspecten van het systeem beheren" #: awx/main/models/rbac.py:56 -msgid "Can view all settings on the system" -msgstr "Kan alle instellingen van het systeem weergeven" +msgid "Can view all aspects of the system" +msgstr "Kan alle aspecten van het systeem bekijken" #: awx/main/models/rbac.py:57 -msgid "May run ad hoc commands on an inventory" -msgstr "Kan ad-hocopdrachten in een inventaris uitvoeren" +#, python-format +msgid "May run ad hoc commands on the %s" +msgstr "Kan mogelijk ad-hoc-opdrachten op de %s uitvoeren" #: awx/main/models/rbac.py:58 #, python-format @@ -4166,8 +4453,8 @@ msgstr "Kan alle meldingen van de %s beheren" #: awx/main/models/rbac.py:65 #, python-format -msgid "Can view all settings for the %s" -msgstr "Kan alle instellingen voor %s weergeven" +msgid "Can view all aspects of the %s" +msgstr "Kan alle aspecten van de %s bekijken" #: awx/main/models/rbac.py:67 msgid "May run any executable resources in the organization" @@ -4176,7 +4463,7 @@ msgstr "Kan alle uitvoerbare hulpbronnen in de organisatie uitvoeren" #: awx/main/models/rbac.py:68 #, python-format msgid "May run the %s" -msgstr "Kan %s uitvoeren" +msgstr "Kan %s mogelijk uitvoeren" #: awx/main/models/rbac.py:70 #, python-format @@ -4186,314 +4473,396 @@ msgstr "Gebruiker is lid van %s" #: awx/main/models/rbac.py:71 #, python-format msgid "May view settings for the %s" -msgstr "Kan instellingen voor %s weergeven" +msgstr "Kan mogelijk instellingen voor %s bekijken" #: awx/main/models/rbac.py:72 -msgid "" -"May update project or inventory or group using the configured source update " -"system" -msgstr "" -"Kan project of inventarisgroep bijwerken met het geconfigureerde " -"bronupdatesysteem" +#, python-format +msgid "May update the %s" +msgstr "Kan de %s mogelijk bijwerken" #: awx/main/models/rbac.py:73 #, python-format msgid "Can use the %s in a job template" msgstr "Kan %s gebruiken in een taaksjabloon" -#: awx/main/models/rbac.py:137 +#: awx/main/models/rbac.py:74 +msgid "Can approve or deny a workflow approval node" +msgstr "Kan een workflow-goedkeuringsknooppunt goedkeuren of weigeren" + +#: awx/main/models/rbac.py:138 msgid "roles" msgstr "rollen" -#: awx/main/models/rbac.py:443 +#: awx/main/models/rbac.py:445 msgid "role_ancestors" msgstr "role_ancestors" -#: awx/main/models/schedules.py:79 +#: awx/main/models/schedules.py:83 msgid "Enables processing of this schedule." msgstr "Maakt de verwerking van dit schema mogelijk." -#: awx/main/models/schedules.py:85 +#: awx/main/models/schedules.py:89 msgid "The first occurrence of the schedule occurs on or after this time." msgstr "Het eerste voorkomen van het schema treedt op of na deze tijd op." -#: awx/main/models/schedules.py:91 +#: awx/main/models/schedules.py:95 msgid "" "The last occurrence of the schedule occurs before this time, aftewards the " "schedule expires." -msgstr "" -"Het laatste voorkomen van het schema treedt voor deze tijd op, nadat het " -"schema is verlopen." +msgstr "Het laatste voorkomen van het schema treedt voor deze tijd op, nadat het schema is verlopen." -#: awx/main/models/schedules.py:95 +#: awx/main/models/schedules.py:99 msgid "A value representing the schedules iCal recurrence rule." msgstr "Een waarde die de iCal-herhalingsregel van het schema voorstelt." -#: awx/main/models/schedules.py:101 +#: awx/main/models/schedules.py:105 msgid "The next time that the scheduled action will run." msgstr "De volgende keer dat de geplande actie wordt uitgevoerd." -#: awx/main/models/unified_jobs.py:61 +#: awx/main/models/unified_jobs.py:69 msgid "New" msgstr "Nieuw" -#: awx/main/models/unified_jobs.py:63 +#: awx/main/models/unified_jobs.py:71 msgid "Waiting" msgstr "Wachten" -#: awx/main/models/unified_jobs.py:64 +#: awx/main/models/unified_jobs.py:72 msgid "Running" -msgstr "Uitvoeren" +msgstr "In uitvoering" -#: awx/main/models/unified_jobs.py:68 +#: awx/main/models/unified_jobs.py:76 msgid "Canceled" msgstr "Geannuleerd" -#: awx/main/models/unified_jobs.py:72 +#: awx/main/models/unified_jobs.py:80 msgid "Never Updated" msgstr "Nooit bijgewerkt" -#: awx/main/models/unified_jobs.py:76 +#: awx/main/models/unified_jobs.py:84 msgid "OK" msgstr "OK" -#: awx/main/models/unified_jobs.py:77 +#: awx/main/models/unified_jobs.py:85 msgid "Missing" msgstr "Ontbrekend" -#: awx/main/models/unified_jobs.py:81 +#: awx/main/models/unified_jobs.py:89 msgid "No External Source" msgstr "Geen externe bron" -#: awx/main/models/unified_jobs.py:88 +#: awx/main/models/unified_jobs.py:96 msgid "Updating" msgstr "Bijwerken" -#: awx/main/models/unified_jobs.py:427 +#: awx/main/models/unified_jobs.py:167 +msgid "The organization used to determine access to this template." +msgstr "De organisatie heeft de toegang tot dit sjabloon bepaald." + +#: awx/main/models/unified_jobs.py:466 msgid "Field is not allowed on launch." msgstr "Veld is niet toegestaan bij opstarten." -#: awx/main/models/unified_jobs.py:455 +#: awx/main/models/unified_jobs.py:494 #, python-brace-format msgid "" -"Variables {list_of_keys} provided, but this template cannot accept " -"variables." -msgstr "" -"Variabelen {list_of_keys} opgegeven, maar deze sjabloon kan geen variabelen " -"accepteren." +"Variables {list_of_keys} provided, but this template cannot accept variables." +msgstr "Variabelen {list_of_keys} opgegeven, maar deze sjabloon kan geen variabelen accepteren." -#: awx/main/models/unified_jobs.py:520 +#: awx/main/models/unified_jobs.py:540 msgid "Relaunch" msgstr "Opnieuw starten" -#: awx/main/models/unified_jobs.py:521 +#: awx/main/models/unified_jobs.py:541 msgid "Callback" msgstr "Terugkoppelen" -#: awx/main/models/unified_jobs.py:522 +#: awx/main/models/unified_jobs.py:542 msgid "Scheduled" msgstr "Gepland" -#: awx/main/models/unified_jobs.py:523 +#: awx/main/models/unified_jobs.py:543 msgid "Dependency" msgstr "Afhankelijkheid" -#: awx/main/models/unified_jobs.py:524 +#: awx/main/models/unified_jobs.py:544 msgid "Workflow" msgstr "Workflow" -#: awx/main/models/unified_jobs.py:525 +#: awx/main/models/unified_jobs.py:546 msgid "Sync" msgstr "Synchroniseren" -#: awx/main/models/unified_jobs.py:573 +#: awx/main/models/unified_jobs.py:601 msgid "The node the job executed on." msgstr "Het knooppunt waarop de taak is uitgevoerd." -#: awx/main/models/unified_jobs.py:579 +#: awx/main/models/unified_jobs.py:607 msgid "The instance that managed the isolated execution environment." msgstr "De instantie die de geïsoleerde uitvoeringsomgeving beheerd heeft." -#: awx/main/models/unified_jobs.py:605 +#: awx/main/models/unified_jobs.py:634 msgid "The date and time the job was queued for starting." -msgstr "" -"De datum en tijd waarop de taak in de wachtrij is gezet om te worden " -"gestart." +msgstr "De datum en tijd waarop de taak in de wachtrij is gezet om te worden gestart." + +#: awx/main/models/unified_jobs.py:639 +msgid "" +"If True, the task manager has already processed potential dependencies for " +"this job." +msgstr "Als dit True is, heeft de taakmanager potentiële afhankelijkheden voor deze functie al verwerkt." -#: awx/main/models/unified_jobs.py:611 +#: awx/main/models/unified_jobs.py:645 msgid "The date and time the job finished execution." msgstr "De datum en tijd waarop de taak de uitvoering heeft beëindigd." -#: awx/main/models/unified_jobs.py:617 +#: awx/main/models/unified_jobs.py:652 +msgid "The date and time when the cancel request was sent." +msgstr "De datum en het tijdstip waarop het annuleringsverzoek is verzonden." + +#: awx/main/models/unified_jobs.py:659 msgid "Elapsed time in seconds that the job ran." msgstr "Verstreken tijd in seconden dat de taak is uitgevoerd." -#: awx/main/models/unified_jobs.py:639 +#: awx/main/models/unified_jobs.py:681 msgid "" -"A status field to indicate the state of the job if it wasn't able to run and" -" capture stdout" -msgstr "" -"Een statusveld om de status van de taak aan te geven als deze niet kon " -"worden uitgevoerd en stdout niet kon worden vastgelegd" +"A status field to indicate the state of the job if it wasn't able to run and " +"capture stdout" +msgstr "Een statusveld om de status van de taak aan te geven als deze niet kon worden uitgevoerd en stdout niet kon worden vastgelegd" + +#: awx/main/models/unified_jobs.py:710 +msgid "The Instance group the job was run under" +msgstr "De instantiegroep waaronder de taak werd uitgevoerd" + +#: awx/main/models/unified_jobs.py:718 +msgid "The organization used to determine access to this unified job." +msgstr "De organisatie heeft de toegang tot deze uniforme taak bepaald." + +#: awx/main/models/workflow.py:85 +msgid "" +"If enabled then the node will only run if all of the parent nodes have met " +"the criteria to reach this node" +msgstr "Indien ingeschakeld zal het knooppunt alleen draaien als alle bovenliggende knooppunten aan de criteria voldoen om dit knooppunt te bereiken" + +#: awx/main/models/workflow.py:154 +msgid "" +"An identifier for this node that is unique within its workflow. It is copied " +"to workflow job nodes corresponding to this node." +msgstr "Een id voor dit knooppunt die uniek is binnen de workflow. De id wordt gekopieerd naar workflow-taakknooppunten die overeenkomen met dit knooppunt." + +#: awx/main/models/workflow.py:229 +msgid "" +"Indicates that a job will not be created when True. Workflow runtime " +"semantics will mark this True if the node is in a path that will decidedly " +"not be ran. A value of False means the node may not run." +msgstr "Geeft aan dat er geen taak wordt aangemaakt indien True. De semantische analyse van de workflowdoorlooptijd zal dit markeren als True als het knooppunt een pad is dat onmiskenbaar niet zal worden uitgevoerd. De waarde False betekent dat het knooppunt mogelijk niet wordt uitgevoerd." -#: awx/main/models/unified_jobs.py:668 -msgid "The Rampart/Instance group the job was run under" -msgstr "De Rampart-/instantiegroep waaronder de taak werd uitgevoerd" +#: awx/main/models/workflow.py:236 +msgid "" +"An identifier coresponding to the workflow job template node that this node " +"was created from." +msgstr "Een id die overeenkomt met het knooppunt voor het workflowtaaksjabloon waaruit dit knooppunt is gemaakt." -#: awx/main/models/workflow.py:203 +#: awx/main/models/workflow.py:282 #, python-brace-format msgid "" -"Bad launch configuration starting template {template_pk} as part of workflow {workflow_pk}. Errors:\n" +"Bad launch configuration starting template {template_pk} as part of workflow " +"{workflow_pk}. Errors:\n" +"{error_text}" +msgstr "Slechte opstartconfiguratie van sjabloon {template_pk} als onderdeel van workflow {workflow_pk}. Fouten:\n" "{error_text}" -msgstr "" -"Slechte opstartconfiguratie met opstartsjabloon {template_pk} als onderdeel " -"van workflow {workflow_pk}. Fouten: {error_text}" -#: awx/main/models/workflow.py:393 -msgid "Field is not allowed for use in workflows." -msgstr "Gebruik van veld in workflows is niet toegestaan." +#: awx/main/models/workflow.py:595 +msgid "" +"If automatically created for a sliced job run, the job template the workflow " +"job was created from." +msgstr "Indien automatisch aangemaakt voor een verdeelde taak, voer dan de taaksjabloon van de workflowtaak waar het van gemaakt is uit." -#: awx/main/notifications/base.py:17 -#: awx/main/notifications/email_backend.py:28 +#: awx/main/models/workflow.py:687 awx/main/models/workflow.py:721 msgid "" -"{} #{} had status {}, view details at {}\n" -"\n" -msgstr "" -"{} #{} had status {}, geef details weer op {}\n" -"\n" +"The amount of time (in seconds) before the approval node expires and fails." +msgstr "De hoeveelheid tijd (in seconden) voordat het goedkeuringsknooppunt verloopt en mislukt." + +#: awx/main/models/workflow.py:725 +msgid "" +"Shows when an approval node (with a timeout assigned to it) has timed out." +msgstr "Geeft aan wanneer een goedkeuringsknooppunt (met een toegewezen time-out) is verlopen." -#: awx/main/notifications/hipchat_backend.py:48 +#: awx/main/notifications/grafana_backend.py:86 +msgid "Error converting time {} or timeEnd {} to int." +msgstr "Fout bij omzetten tijd {} of timeEnd {} tot int." + +#: awx/main/notifications/grafana_backend.py:88 +msgid "Error converting time {} and/or timeEnd {} to int." +msgstr "Fout bij omzetten tijd {} en/of timeEnd {} tot int." + +#: awx/main/notifications/grafana_backend.py:102 +#: awx/main/notifications/grafana_backend.py:104 +msgid "Error sending notification grafana: {}" +msgstr "Fout bij verzending grafana-melding: {}" + +#: awx/main/notifications/hipchat_backend.py:50 msgid "Error sending messages: {}" msgstr "Fout bij verzending van berichten: {}" -#: awx/main/notifications/hipchat_backend.py:50 +#: awx/main/notifications/hipchat_backend.py:52 msgid "Error sending message to hipchat: {}" msgstr "Fout bij verzending van bericht naar hipchat: {}" -#: awx/main/notifications/irc_backend.py:54 +#: awx/main/notifications/irc_backend.py:56 msgid "Exception connecting to irc server: {}" msgstr "Uitzondering bij het maken van de verbinding met de irc-server: {}" -#: awx/main/notifications/mattermost_backend.py:48 #: awx/main/notifications/mattermost_backend.py:50 +#: awx/main/notifications/mattermost_backend.py:52 msgid "Error sending notification mattermost: {}" msgstr "Fout bij verzending bericht mattermost: {}" -#: awx/main/notifications/pagerduty_backend.py:39 +#: awx/main/notifications/pagerduty_backend.py:64 msgid "Exception connecting to PagerDuty: {}" msgstr "Uitzondering bij het maken van de verbinding met PagerDuty: {}" -#: awx/main/notifications/pagerduty_backend.py:48 -#: awx/main/notifications/slack_backend.py:82 -#: awx/main/notifications/slack_backend.py:99 -#: awx/main/notifications/twilio_backend.py:46 +#: awx/main/notifications/pagerduty_backend.py:73 +#: awx/main/notifications/slack_backend.py:58 +#: awx/main/notifications/twilio_backend.py:48 msgid "Exception sending messages: {}" msgstr "Uitzondering bij het verzenden van berichten: {}" -#: awx/main/notifications/rocketchat_backend.py:46 #: awx/main/notifications/rocketchat_backend.py:49 +#: awx/main/notifications/rocketchat_backend.py:52 msgid "Error sending notification rocket.chat: {}" msgstr "Fout bij verzending bericht rocket.chat: {}" -#: awx/main/notifications/twilio_backend.py:36 +#: awx/main/notifications/twilio_backend.py:38 msgid "Exception connecting to Twilio: {}" msgstr "Uitzondering bij het maken van de verbinding met Twilio: {}" -#: awx/main/notifications/webhook_backend.py:38 -#: awx/main/notifications/webhook_backend.py:40 +#: awx/main/notifications/webhook_backend.py:75 +#: awx/main/notifications/webhook_backend.py:77 msgid "Error sending notification webhook: {}" msgstr "Fout bij verzending bericht webhook: {}" -#: awx/main/scheduler/task_manager.py:201 +#: awx/main/scheduler/dag_workflow.py:170 +#, python-brace-format msgid "" -"Job spawned from workflow could not start because it was not in the right " -"state or required manual credentials" -msgstr "" -"Taak voortgebracht vanuit workflow kon niet starten omdat deze niet de " -"juiste status of vereiste handmatige referenties had" +"No error handling path for workflow job node(s) [{node_status}]. Workflow " +"job node(s) missing unified job template and error handling path [{no_ufjt}]." +msgstr "Geen foutverwerkingspad voor workflowtaakknooppunt(en) [{node_status}]. Voor workflowknooppunt(en) ontbreekt een gemeenschappelijk taaksjabloon en foutverwerkingspad [{no_ufjt}]." + +#: awx/main/scheduler/task_manager.py:118 +msgid "" +"Workflow Job spawned from workflow could not start because it would result " +"in recursion (spawn order, most recent first: {})" +msgstr "Workflowtaak voortgebracht vanuit workflow kon niet starten omdat dit resulteerde in een recursie (voortbrengorder, meest recente als eerste: {})" -#: awx/main/scheduler/task_manager.py:205 +#: awx/main/scheduler/task_manager.py:126 msgid "" "Job spawned from workflow could not start because it was missing a related " "resource such as project or inventory" -msgstr "" -"Taak voortgebracht vanuit workflow kon niet starten omdat een gerelateerde " -"resource zoals project of inventaris ontbreekt" +msgstr "Taak voortgebracht vanuit workflow kon niet starten omdat een gerelateerde bron zoals project of inventaris ontbreekt" -#: awx/main/signals.py:632 -msgid "limit_reached" -msgstr "limit_reached" +#: awx/main/scheduler/task_manager.py:135 +msgid "" +"Job spawned from workflow could not start because it was not in the right " +"state or required manual credentials" +msgstr "Taak voortgebracht vanuit workflow kon niet starten omdat deze niet de juiste status of vereiste handmatige referenties had" + +#: awx/main/scheduler/task_manager.py:176 +msgid "No error handling paths found, marking workflow as failed" +msgstr "Geen foutafhandelingspaden gevonden, workflow gemarkeerd als mislukt" -#: awx/main/tasks.py:305 -msgid "Ansible Tower host usage over 90%" -msgstr "Ansible Tower-hostgebruik meer dan 90%" +#: awx/main/scheduler/task_manager.py:508 +#, python-brace-format +msgid "The approval node {name} ({pk}) has expired after {timeout} seconds." +msgstr "Goedkeuringsknooppunt {name} ({pk}) is na {timeout} seconden verlopen." -#: awx/main/tasks.py:310 -msgid "Ansible Tower license will expire soon" -msgstr "De licentie voor Ansible Tower verloopt binnenkort" +#: awx/main/tasks.py:1049 +msgid "Invalid virtual environment selected: {}" +msgstr "Ongeldige virtuele omgeving geselecteerd: {}" -#: awx/main/tasks.py:1358 +#: awx/main/tasks.py:1853 msgid "Job could not start because it does not have a valid inventory." msgstr "Taak kon niet gestart worden omdat deze geen geldig inventaris heeft." -#: awx/main/utils/common.py:97 +#: awx/main/tasks.py:1857 +msgid "Job could not start because it does not have a valid project." +msgstr "Taak kon niet gestart worden omdat deze geen geldig project heeft." + +#: awx/main/tasks.py:1862 +msgid "" +"The project revision for this job template is unknown due to a failed update." +msgstr "De projectrevisie voor deze taaksjabloon is onbekend doordat de update niet kon worden uitgevoerd." + +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:470 +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:498 +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:505 +msgid "" +"No error handling path for workflow job node(s) [({},{})]. Workflow job " +"node(s) missing unified job template and error handling path []." +msgstr "Geen foutverwerkingspad voor workflowtaakknooppunt(en) [({},{})]. Voor workflowknooppunt(en) ontbreekt een gemeenschappelijk taaksjabloon en foutverwerkingspad []." + +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:480 +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:491 +msgid "" +"No error handling path for workflow job node(s) []. Workflow job node(s) " +"missing unified job template and error handling path [{}]." +msgstr "Geen foutverwerkingspad voor workflowtaakknooppunt(en) []. Voor workflowknooppunt(en) ontbreekt een gemeenschappelijk taaksjabloon en foutverwerkingspad [{}]." + +#: awx/main/utils/common.py:87 #, python-format msgid "Unable to convert \"%s\" to boolean" -msgstr "Kan \"%s\" niet converteren in boolean" +msgstr "Kan ‘%s‘ niet omzetten naar boolean" -#: awx/main/utils/common.py:254 +#: awx/main/utils/common.py:261 #, python-format msgid "Unsupported SCM type \"%s\"" -msgstr "Niet-ondersteund SCM-type \"%s\"" +msgstr "Niet-ondersteund SCM-type ‘%s‘" -#: awx/main/utils/common.py:261 awx/main/utils/common.py:273 -#: awx/main/utils/common.py:292 +#: awx/main/utils/common.py:268 awx/main/utils/common.py:280 +#: awx/main/utils/common.py:299 #, python-format msgid "Invalid %s URL" msgstr "Ongeldige %s URL" -#: awx/main/utils/common.py:263 awx/main/utils/common.py:302 +#: awx/main/utils/common.py:270 awx/main/utils/common.py:309 #, python-format msgid "Unsupported %s URL" msgstr "Niet-ondersteunde %s URL" -#: awx/main/utils/common.py:304 +#: awx/main/utils/common.py:311 #, python-format msgid "Unsupported host \"%s\" for file:// URL" -msgstr "Niet-ondersteunde host \"%s\" voor bestand:// URL" +msgstr "Niet-ondersteunde host ‘%s‘ voor bestand:// URL" -#: awx/main/utils/common.py:306 +#: awx/main/utils/common.py:313 #, python-format msgid "Host is required for %s URL" msgstr "Host is vereist voor %s URL" -#: awx/main/utils/common.py:324 +#: awx/main/utils/common.py:331 #, python-format msgid "Username must be \"git\" for SSH access to %s." -msgstr "Gebruikersnaam moet \"git\" zijn voor SSH-toegang tot %s." +msgstr "Gebruikersnaam moet ‘git‘ zijn voor SSH-toegang tot %s." -#: awx/main/utils/common.py:330 +#: awx/main/utils/common.py:337 #, python-format msgid "Username must be \"hg\" for SSH access to %s." -msgstr "Gebruikersnaam moet \"hg\" zijn voor SSH-toegang tot %s." +msgstr "Gebruikersnaam moet ‘hg‘ zijn voor SSH-toegang tot %s." -#: awx/main/utils/common.py:611 +#: awx/main/utils/common.py:668 #, python-brace-format msgid "Input type `{data_type}` is not a dictionary" msgstr "Soort input `{data_type}` is geen woordenlijst" -#: awx/main/utils/common.py:644 +#: awx/main/utils/common.py:701 #, python-brace-format msgid "Variables not compatible with JSON standard (error: {json_error})" msgstr "Variabelen niet compatibel met JSON-norm (fout: {json_error})" -#: awx/main/utils/common.py:650 +#: awx/main/utils/common.py:707 #, python-brace-format msgid "" "Cannot parse as JSON (error: {json_error}) or YAML (error: {yaml_error})." -msgstr "" -"Kan niet parseren als JSON (fout: {json_error}) of YAML (fout: " -"{yaml_error})." +msgstr "Kan niet parseren als JSON (fout: {json_error}) of YAML (fout: {yaml_error})." #: awx/main/validators.py:67 #, python-format @@ -4503,395 +4872,385 @@ msgstr "Ongeldig certificaat of ongeldige sleutel: %s..." #: awx/main/validators.py:83 #, python-format msgid "Invalid private key: unsupported type \"%s\"" -msgstr "Ongeldige privésleutel: niet-ondersteund type \"%s\"" +msgstr "Ongeldige privésleutel: niet-ondersteund type ‘%s‘" #: awx/main/validators.py:87 #, python-format msgid "Unsupported PEM object type: \"%s\"" -msgstr "Niet-ondersteund PEM-objecttype \"%s\"" +msgstr "Niet-ondersteund PEM-objecttype ‘%s‘" #: awx/main/validators.py:112 msgid "Invalid base64-encoded data" msgstr "Ongeldige base64-versleutelde gegevens" -#: awx/main/validators.py:131 +#: awx/main/validators.py:133 msgid "Exactly one private key is required." msgstr "Precies één privésleutel is vereist." -#: awx/main/validators.py:133 +#: awx/main/validators.py:135 msgid "At least one private key is required." msgstr "Ten minste één privésleutel is vereist." -#: awx/main/validators.py:135 +#: awx/main/validators.py:137 #, python-format msgid "" -"At least %(min_keys)d private keys are required, only %(key_count)d " -"provided." -msgstr "" -"Ten minste %(min_keys)d privésleutels zijn vereist, niet meer dan " -"%(key_count)d geleverd." +"At least %(min_keys)d private keys are required, only %(key_count)d provided." +msgstr "Er zijn ten minste %(min_keys)d privésleutels nodig, slechts %(key_count)d geleverd." -#: awx/main/validators.py:138 +#: awx/main/validators.py:140 #, python-format msgid "Only one private key is allowed, %(key_count)d provided." msgstr "Maar één privésleutel is toegestaan, %(key_count)d geleverd." -#: awx/main/validators.py:140 +#: awx/main/validators.py:142 #, python-format msgid "" "No more than %(max_keys)d private keys are allowed, %(key_count)d provided." -msgstr "" -"Niet meer dan %(max_keys)d privésleutels zijn toegestaan, %(key_count)d " -"geleverd." +msgstr "Niet meer dan %(max_keys)d privé-sleutels zijn toegestaan, %(key_count)d geleverd." -#: awx/main/validators.py:145 +#: awx/main/validators.py:147 msgid "Exactly one certificate is required." msgstr "Precies één certificaat is vereist." -#: awx/main/validators.py:147 +#: awx/main/validators.py:149 msgid "At least one certificate is required." msgstr "Ten minste één certificaat is vereist." -#: awx/main/validators.py:149 +#: awx/main/validators.py:151 #, python-format msgid "" "At least %(min_certs)d certificates are required, only %(cert_count)d " "provided." -msgstr "" -"Ten minste %(min_certs)d certificaten zijn vereist, niet meer dan " -"%(cert_count)d geleverd." +msgstr "Er zijn ten minste %(min_certs)d certificaten vereist, maar slechts %(cert_count)d geleverd." -#: awx/main/validators.py:152 +#: awx/main/validators.py:154 #, python-format msgid "Only one certificate is allowed, %(cert_count)d provided." -msgstr "Maar één certificaat is toegestaan, %(cert_count)d geleverd." +msgstr "Slechts één certificaat is toegestaan, %(cert_count)d geleverd." -#: awx/main/validators.py:154 +#: awx/main/validators.py:156 #, python-format msgid "" -"No more than %(max_certs)d certificates are allowed, %(cert_count)d " -"provided." -msgstr "" -"Niet meer dan %(max_certs)d certificaten zijn toegestaan, %(cert_count)d " -"geleverd." +"No more than %(max_certs)d certificates are allowed, %(cert_count)d provided." +msgstr "Niet meer dan %(max_certs)d certificaten zijn toegestaan, %(cert_count)d geleverd." -#: awx/main/views.py:23 +#: awx/main/views.py:30 msgid "API Error" msgstr "API-fout" -#: awx/main/views.py:61 +#: awx/main/views.py:65 msgid "Bad Request" msgstr "Onjuiste aanvraag" -#: awx/main/views.py:62 +#: awx/main/views.py:66 msgid "The request could not be understood by the server." msgstr "De aanvraag kon niet worden begrepen door de server." -#: awx/main/views.py:69 +#: awx/main/views.py:73 msgid "Forbidden" msgstr "Niet-toegestaan" -#: awx/main/views.py:70 +#: awx/main/views.py:74 msgid "You don't have permission to access the requested resource." -msgstr "U bent niet gemachtigd om de aangevraagde resource te openen." +msgstr "U bent niet gemachtigd om de aangevraagde bron te openen." -#: awx/main/views.py:77 +#: awx/main/views.py:81 msgid "Not Found" msgstr "Niet gevonden" -#: awx/main/views.py:78 +#: awx/main/views.py:82 msgid "The requested resource could not be found." -msgstr "De aangevraagde resource is onvindbaar." +msgstr "De aangevraagde bron is onvindbaar." -#: awx/main/views.py:85 +#: awx/main/views.py:89 msgid "Server Error" msgstr "Serverfout" -#: awx/main/views.py:86 +#: awx/main/views.py:90 msgid "A server error has occurred." msgstr "Er is een serverfout opgetreden" -#: awx/settings/defaults.py:725 +#: awx/settings/defaults.py:683 msgid "US East (Northern Virginia)" msgstr "VS Oosten (Northern Virginia)" -#: awx/settings/defaults.py:726 +#: awx/settings/defaults.py:684 msgid "US East (Ohio)" msgstr "VS Oosten (Ohio)" -#: awx/settings/defaults.py:727 +#: awx/settings/defaults.py:685 msgid "US West (Oregon)" msgstr "VS Westen (Oregon)" -#: awx/settings/defaults.py:728 +#: awx/settings/defaults.py:686 msgid "US West (Northern California)" msgstr "VS Westen (Northern California)" -#: awx/settings/defaults.py:729 +#: awx/settings/defaults.py:687 msgid "Canada (Central)" msgstr "Canada (Midden)" -#: awx/settings/defaults.py:730 +#: awx/settings/defaults.py:688 msgid "EU (Frankfurt)" msgstr "EU (Frankfurt)" -#: awx/settings/defaults.py:731 +#: awx/settings/defaults.py:689 msgid "EU (Ireland)" msgstr "EU (Ierland)" -#: awx/settings/defaults.py:732 +#: awx/settings/defaults.py:690 msgid "EU (London)" msgstr "EU (Londen)" -#: awx/settings/defaults.py:733 +#: awx/settings/defaults.py:691 msgid "Asia Pacific (Singapore)" msgstr "Azië-Pacific (Singapore)" -#: awx/settings/defaults.py:734 +#: awx/settings/defaults.py:692 msgid "Asia Pacific (Sydney)" msgstr "Azië-Pacific (Sydney)" -#: awx/settings/defaults.py:735 +#: awx/settings/defaults.py:693 msgid "Asia Pacific (Tokyo)" msgstr "Azië-Pacific (Tokyo)" -#: awx/settings/defaults.py:736 +#: awx/settings/defaults.py:694 msgid "Asia Pacific (Seoul)" msgstr "Azië-Pacific (Seoul)" -#: awx/settings/defaults.py:737 +#: awx/settings/defaults.py:695 msgid "Asia Pacific (Mumbai)" msgstr "Azië-Pacific (Mumbai)" -#: awx/settings/defaults.py:738 +#: awx/settings/defaults.py:696 msgid "South America (Sao Paulo)" msgstr "Zuid-Amerika (Sao Paulo)" -#: awx/settings/defaults.py:739 +#: awx/settings/defaults.py:697 msgid "US West (GovCloud)" msgstr "VS Westen (GovCloud)" -#: awx/settings/defaults.py:740 +#: awx/settings/defaults.py:698 msgid "China (Beijing)" msgstr "China (Beijing)" -#: awx/settings/defaults.py:789 +#: awx/settings/defaults.py:747 msgid "US East 1 (B)" msgstr "VS Oosten 1 (B)" -#: awx/settings/defaults.py:790 +#: awx/settings/defaults.py:748 msgid "US East 1 (C)" msgstr "VS Oosten 1 (C)" -#: awx/settings/defaults.py:791 +#: awx/settings/defaults.py:749 msgid "US East 1 (D)" msgstr "VS Oosten 1 (D)" -#: awx/settings/defaults.py:792 +#: awx/settings/defaults.py:750 msgid "US East 4 (A)" msgstr "VS Oosten 4 (A)" -#: awx/settings/defaults.py:793 +#: awx/settings/defaults.py:751 msgid "US East 4 (B)" msgstr "VS Oosten 4 (B)" -#: awx/settings/defaults.py:794 +#: awx/settings/defaults.py:752 msgid "US East 4 (C)" msgstr "VS Oosten 4 (C)" -#: awx/settings/defaults.py:795 +#: awx/settings/defaults.py:753 msgid "US Central (A)" msgstr "VS Midden (A)" -#: awx/settings/defaults.py:796 +#: awx/settings/defaults.py:754 msgid "US Central (B)" msgstr "VS Midden (B)" -#: awx/settings/defaults.py:797 +#: awx/settings/defaults.py:755 msgid "US Central (C)" msgstr "VS Midden (C)" -#: awx/settings/defaults.py:798 +#: awx/settings/defaults.py:756 msgid "US Central (F)" msgstr "VS Midden (F)" -#: awx/settings/defaults.py:799 +#: awx/settings/defaults.py:757 msgid "US West (A)" msgstr "VS Westen (A)" -#: awx/settings/defaults.py:800 +#: awx/settings/defaults.py:758 msgid "US West (B)" msgstr "VS Westen (B)" -#: awx/settings/defaults.py:801 +#: awx/settings/defaults.py:759 msgid "US West (C)" msgstr "VS Westen (C)" -#: awx/settings/defaults.py:802 +#: awx/settings/defaults.py:760 msgid "Europe West 1 (B)" msgstr "Europa Westen 1 (B)" -#: awx/settings/defaults.py:803 +#: awx/settings/defaults.py:761 msgid "Europe West 1 (C)" msgstr "Europa Westen 1 (C)" -#: awx/settings/defaults.py:804 +#: awx/settings/defaults.py:762 msgid "Europe West 1 (D)" msgstr "Europa Westen 1 (D)" -#: awx/settings/defaults.py:805 +#: awx/settings/defaults.py:763 msgid "Europe West 2 (A)" msgstr "Europa Westen 2 (A)" -#: awx/settings/defaults.py:806 +#: awx/settings/defaults.py:764 msgid "Europe West 2 (B)" msgstr "Europa Westen 2 (B)" -#: awx/settings/defaults.py:807 +#: awx/settings/defaults.py:765 msgid "Europe West 2 (C)" msgstr "Europa Westen 2 (C)" -#: awx/settings/defaults.py:808 +#: awx/settings/defaults.py:766 msgid "Asia East (A)" msgstr "Azië Oosten (A)" -#: awx/settings/defaults.py:809 +#: awx/settings/defaults.py:767 msgid "Asia East (B)" msgstr "Azië Oosten (B)" -#: awx/settings/defaults.py:810 +#: awx/settings/defaults.py:768 msgid "Asia East (C)" msgstr "Azië Oosten (C)" -#: awx/settings/defaults.py:811 +#: awx/settings/defaults.py:769 msgid "Asia Southeast (A)" msgstr "Azië (Zuidoost) (A)" -#: awx/settings/defaults.py:812 +#: awx/settings/defaults.py:770 msgid "Asia Southeast (B)" msgstr "Azië (Zuidoost) (B)" -#: awx/settings/defaults.py:813 +#: awx/settings/defaults.py:771 msgid "Asia Northeast (A)" msgstr "Azië (Noordoost) (A)" -#: awx/settings/defaults.py:814 +#: awx/settings/defaults.py:772 msgid "Asia Northeast (B)" msgstr "Azië (Noordoost) (B)" -#: awx/settings/defaults.py:815 +#: awx/settings/defaults.py:773 msgid "Asia Northeast (C)" msgstr "Azië (Noordoost) (C)" -#: awx/settings/defaults.py:816 +#: awx/settings/defaults.py:774 msgid "Australia Southeast (A)" msgstr "Australië Zuidoost (A)" -#: awx/settings/defaults.py:817 +#: awx/settings/defaults.py:775 msgid "Australia Southeast (B)" msgstr "Australië Zuidoost (B)" -#: awx/settings/defaults.py:818 +#: awx/settings/defaults.py:776 msgid "Australia Southeast (C)" msgstr "Australië Zuidoost (C)" -#: awx/settings/defaults.py:840 +#: awx/settings/defaults.py:798 msgid "US East" msgstr "VS Oosten" -#: awx/settings/defaults.py:841 +#: awx/settings/defaults.py:799 msgid "US East 2" msgstr "VS Oosten 2" -#: awx/settings/defaults.py:842 +#: awx/settings/defaults.py:800 msgid "US Central" msgstr "VS Midden" -#: awx/settings/defaults.py:843 +#: awx/settings/defaults.py:801 msgid "US North Central" msgstr "VS Noord Midden" -#: awx/settings/defaults.py:844 +#: awx/settings/defaults.py:802 msgid "US South Central" msgstr "VS Zuid MIdden" -#: awx/settings/defaults.py:845 +#: awx/settings/defaults.py:803 msgid "US West Central" msgstr "VS West Midden" -#: awx/settings/defaults.py:846 +#: awx/settings/defaults.py:804 msgid "US West" msgstr "VS Westen" -#: awx/settings/defaults.py:847 +#: awx/settings/defaults.py:805 msgid "US West 2" msgstr "VS Westen 2" -#: awx/settings/defaults.py:848 +#: awx/settings/defaults.py:806 msgid "Canada East" msgstr "Canada Oosten" -#: awx/settings/defaults.py:849 +#: awx/settings/defaults.py:807 msgid "Canada Central" msgstr "Canada Midden" -#: awx/settings/defaults.py:850 +#: awx/settings/defaults.py:808 msgid "Brazil South" msgstr "Brazilië Zuiden" -#: awx/settings/defaults.py:851 +#: awx/settings/defaults.py:809 msgid "Europe North" msgstr "Europa Noorden" -#: awx/settings/defaults.py:852 +#: awx/settings/defaults.py:810 msgid "Europe West" msgstr "Europa Westen" -#: awx/settings/defaults.py:853 +#: awx/settings/defaults.py:811 msgid "UK West" msgstr "VK Westen" -#: awx/settings/defaults.py:854 +#: awx/settings/defaults.py:812 msgid "UK South" msgstr "VK Zuiden" -#: awx/settings/defaults.py:855 +#: awx/settings/defaults.py:813 msgid "Asia East" msgstr "Azië Oosten" -#: awx/settings/defaults.py:856 +#: awx/settings/defaults.py:814 msgid "Asia Southeast" msgstr "Azië Zuidoosten" -#: awx/settings/defaults.py:857 +#: awx/settings/defaults.py:815 msgid "Australia East" msgstr "Australië Oosten" -#: awx/settings/defaults.py:858 +#: awx/settings/defaults.py:816 msgid "Australia Southeast" msgstr "Australië Zuidoosten" -#: awx/settings/defaults.py:859 +#: awx/settings/defaults.py:817 msgid "India West" msgstr "India Westen" -#: awx/settings/defaults.py:860 +#: awx/settings/defaults.py:818 msgid "India South" msgstr "India Zuiden" -#: awx/settings/defaults.py:861 +#: awx/settings/defaults.py:819 msgid "Japan East" msgstr "Japan Oosten" -#: awx/settings/defaults.py:862 +#: awx/settings/defaults.py:820 msgid "Japan West" msgstr "Japan Westen" -#: awx/settings/defaults.py:863 +#: awx/settings/defaults.py:821 msgid "Korea Central" msgstr "Korea Midden" -#: awx/settings/defaults.py:864 +#: awx/settings/defaults.py:822 msgid "Korea South" msgstr "Korea Zuiden" @@ -4899,244 +5258,191 @@ msgstr "Korea Zuiden" msgid "Single Sign-On" msgstr "Single Sign-On" -#: awx/sso/conf.py:30 +#: awx/sso/conf.py:41 msgid "" -"Mapping to organization admins/users from social auth accounts. This setting\n" -"controls which users are placed into which Tower organizations based on their\n" -"username and email address. Configuration details are available in the Ansible\n" +"Mapping to organization admins/users from social auth accounts. This " +"setting\n" +"controls which users are placed into which Tower organizations based on " +"their\n" +"username and email address. Configuration details are available in the " +"Ansible\n" "Tower documentation." -msgstr "" -"Toewijzing aan organisatiebeheerders/-gebruikers vanuit sociale " -"verificatieaccounts. Deze instelling bepaalt welke gebruikers in welke " -"Tower-organisaties worden geplaatst op grond van hun gebruikersnaam en " -"e-mailadres. Configuratiedetails zijn beschikbaar in de Ansible Tower-" -"documentatie." +msgstr "Toewijzing aan organisatiebeheerders/-gebruikers vanuit sociale verificatieaccounts. Deze instelling bepaalt welke gebruikers in welke Tower-organisaties worden geplaatst op grond van hun gebruikersnaam en e-mailadres. Configuratiedetails zijn beschikbaar in de Ansible Tower-documentatie." -#: awx/sso/conf.py:55 +#: awx/sso/conf.py:67 msgid "" "Mapping of team members (users) from social auth accounts. Configuration\n" "details are available in Tower documentation." -msgstr "" -"Toewijzing van teamleden (gebruikers) vanuit sociale verificatieaccounts. Configuratie-\n" +msgstr "Toewijzing van teamleden (gebruikers) vanuit sociale verificatieaccounts. Configuratie-\n" "details zijn beschikbaar in de Tower-documentatie." -#: awx/sso/conf.py:80 +#: awx/sso/conf.py:92 msgid "Authentication Backends" msgstr "Verificatiebackends" -#: awx/sso/conf.py:81 +#: awx/sso/conf.py:93 msgid "" "List of authentication backends that are enabled based on license features " "and other authentication settings." -msgstr "" -"Lijst met verificatiebackends die worden ingeschakeld op grond van " -"licentiekenmerken en andere verificatie-instellingen." +msgstr "Lijst met verificatiebackends die worden ingeschakeld op grond van licentiekenmerken en andere verificatie-instellingen." -#: awx/sso/conf.py:94 +#: awx/sso/conf.py:106 msgid "Social Auth Organization Map" msgstr "Organisatiekaart sociale verificatie" -#: awx/sso/conf.py:106 +#: awx/sso/conf.py:118 msgid "Social Auth Team Map" msgstr "Teamkaart sociale verificatie" -#: awx/sso/conf.py:118 +#: awx/sso/conf.py:130 msgid "Social Auth User Fields" msgstr "Gebruikersvelden sociale verificatie" -#: awx/sso/conf.py:119 +#: awx/sso/conf.py:131 msgid "" -"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." -msgstr "" -"Indien ingesteld op een lege lijst `[]`, voorkomt deze instelling dat nieuwe" -" gebruikersaccounts worden gemaakt. Alleen gebruikers die zich eerder hebben" -" aangemeld met sociale verificatie of een gebruikersaccount hebben met een " -"overeenkomend e-mailadres, kunnen zich aanmelden." +"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." +msgstr "Indien ingesteld op een lege lijst `[]`, voorkomt deze instelling dat nieuwe gebruikersaccounts worden gemaakt. Alleen gebruikers die zich eerder hebben aangemeld met sociale verificatie of een gebruikersaccount hebben met een overeenkomend e-mailadres, kunnen zich aanmelden." -#: awx/sso/conf.py:141 +#: awx/sso/conf.py:153 msgid "LDAP Server URI" msgstr "URI LDAP-server" -#: awx/sso/conf.py:142 +#: awx/sso/conf.py:154 msgid "" "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 " +"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." -msgstr "" -"URI om verbinding te maken met een LDAP-server, zoals " -"\"ldap://ldap.example.com:389\" (niet-SSL) of " -"\"ldaps://ldap.example.com:636\" (SSL). Meerdere LDAP-servers kunnen worden " -"opgegeven door ze van elkaar te scheiden met komma's. LDAP-authenticatie is " -"uitgeschakeld als deze parameter leeg is." - -#: awx/sso/conf.py:146 awx/sso/conf.py:162 awx/sso/conf.py:174 -#: awx/sso/conf.py:186 awx/sso/conf.py:202 awx/sso/conf.py:222 -#: awx/sso/conf.py:244 awx/sso/conf.py:259 awx/sso/conf.py:277 -#: awx/sso/conf.py:294 awx/sso/conf.py:306 awx/sso/conf.py:332 -#: awx/sso/conf.py:348 awx/sso/conf.py:362 awx/sso/conf.py:380 -#: awx/sso/conf.py:406 +msgstr "URI om verbinding te maken met een LDAP-server, zoals \"ldap://ldap.example.com:389\" (niet-SSL) of \"ldaps://ldap.example.com:636\" (SSL). Meerdere LDAP-servers kunnen worden opgegeven door ze van elkaar te scheiden met komma's. LDAP-authenticatie is uitgeschakeld als deze parameter leeg is." + +#: awx/sso/conf.py:158 awx/sso/conf.py:173 awx/sso/conf.py:184 +#: awx/sso/conf.py:195 awx/sso/conf.py:210 awx/sso/conf.py:229 +#: awx/sso/conf.py:250 awx/sso/conf.py:264 awx/sso/conf.py:281 +#: awx/sso/conf.py:297 awx/sso/conf.py:308 awx/sso/conf.py:333 +#: awx/sso/conf.py:348 awx/sso/conf.py:361 awx/sso/conf.py:378 +#: awx/sso/conf.py:404 msgid "LDAP" msgstr "LDAP" -#: awx/sso/conf.py:158 +#: awx/sso/conf.py:169 msgid "LDAP Bind DN" msgstr "DN LDAP-binding" -#: awx/sso/conf.py:159 +#: awx/sso/conf.py:170 msgid "" "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 Ansible Tower documentation for example syntax." -msgstr "" -"DN (Distinguished Name) van gebruiker om te binden voor alle zoekquery's. " -"Dit is de systeemgebruikersaccount waarmee we ons aanmelden om LDAP te " -"ondervragen voor andere gebruikersinformatie. Raadpleeg de documentatie van " -"Ansible Tower voor voorbeeldsyntaxis." +msgstr "DN (Distinguished Name) van gebruiker om te binden voor alle zoekquery's. Dit is de systeemgebruikersaccount waarmee we ons aanmelden om LDAP te ondervragen voor andere gebruikersinformatie. Raadpleeg de documentatie van Ansible Tower voor voorbeeldsyntaxis." -#: awx/sso/conf.py:172 +#: awx/sso/conf.py:182 msgid "LDAP Bind Password" msgstr "Wachtwoord voor LDAP-binding" -#: awx/sso/conf.py:173 +#: awx/sso/conf.py:183 msgid "Password used to bind LDAP user account." msgstr "Wachtwoord gebruikt om LDAP-gebruikersaccount te binden." -#: awx/sso/conf.py:184 +#: awx/sso/conf.py:193 msgid "LDAP Start TLS" msgstr "TLS voor starten LDAP" -#: awx/sso/conf.py:185 +#: awx/sso/conf.py:194 msgid "Whether to enable TLS when the LDAP connection is not using SSL." -msgstr "" -"Of TLS moet worden ingeschakeld wanneer de LDAP-verbinding geen SSL " -"gebruikt." +msgstr "Of TLS moet worden ingeschakeld wanneer de LDAP-verbinding geen SSL gebruikt." -#: awx/sso/conf.py:195 +#: awx/sso/conf.py:203 msgid "LDAP Connection Options" msgstr "Opties voor LDAP-verbinding" -#: awx/sso/conf.py:196 +#: awx/sso/conf.py:204 msgid "" "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." -msgstr "" -"Extra opties voor de LDAP-verbinding. LDAP-verwijzingen zijn standaard " -"uitgeschakeld (om te voorkomen dat sommige LDAP-query's vastlopen met AD). " -"Optienamen kunnen tekenreeksen zijn (bijv. \"OPT_REFERRALS\"). Zie " -"https://www.python-ldap.org/doc/html/ldap.html#options voor de opties en " -"waarden die u kunt instellen." +"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." +msgstr "Extra opties voor de LDAP-verbinding. LDAP-verwijzingen zijn standaard uitgeschakeld (om te voorkomen dat sommige LDAP-query's vastlopen met AD). Optienamen kunnen tekenreeksen zijn (bijv. \"OPT_REFERRALS\"). Zie https://www.python-ldap.org/doc/html/ldap.html#options voor de opties en waarden die u kunt instellen." -#: awx/sso/conf.py:215 +#: awx/sso/conf.py:222 msgid "LDAP User Search" msgstr "Gebruikers zoeken met LDAP" -#: awx/sso/conf.py:216 +#: awx/sso/conf.py:223 msgid "" "LDAP search query to find users. Any user that matches the given pattern " -"will be able to login to Tower. The user should also be mapped into a Tower" -" organization (as defined in the AUTH_LDAP_ORGANIZATION_MAP setting). If " +"will be able to login to Tower. The user should also be mapped into a Tower " +"organization (as defined in the AUTH_LDAP_ORGANIZATION_MAP setting). If " "multiple search queries need to be supported use of \"LDAPUnion\" is " "possible. See Tower documentation for details." -msgstr "" -"LDAP-zoekquery om gebruikers te vinden. Iedere gebruiker die past bij het " -"opgegeven patroon kan zich aanmelden bij Tower. De gebruiker moet ook worden" -" toegewezen aan een Tower-organisatie (zoals gedefinieerd in de instelling " -"AUTH_LDAP_ORGANIZATION_MAP setting). Als meerdere zoekquery's moeten worden" -" ondersteund, kan \"LDAPUnion\" worden gebruikt. Zie de Tower-documentatie " -"voor details." - -#: awx/sso/conf.py:238 +msgstr "LDAP-zoekquery om gebruikers te vinden. Iedere gebruiker die past bij het opgegeven patroon kan zich aanmelden bij Tower. De gebruiker moet ook worden toegewezen aan een Tower-organisatie (zoals gedefinieerd in de instelling AUTH_LDAP_ORGANIZATION_MAP setting). Als meerdere zoekquery's moeten worden ondersteund, kan \"LDAPUnion\" worden gebruikt. Zie de Tower-documentatie voor details." + +#: awx/sso/conf.py:244 msgid "LDAP User DN Template" msgstr "Sjabloon voor LDAP-gebruikers-DN" -#: awx/sso/conf.py:239 +#: awx/sso/conf.py:245 msgid "" "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." -msgstr "" -"Alternatief voor het zoeken naar gebruikers als DN's van gebruikers dezelfde" -" indeling hebben. Deze methode is efficiënter voor het opzoeken van " -"gebruikers dan zoeken als de methode bruikbaar is binnen de omgeving van uw " -"organisatie. Als deze instelling een waarde heeft, wordt die gebruikt in " -"plaats van AUTH_LDAP_USER_SEARCH." +msgstr "Alternatief voor het zoeken naar gebruikers als DN's van gebruikers dezelfde indeling hebben. Deze methode is efficiënter voor het opzoeken van gebruikers dan zoeken als de methode bruikbaar is binnen de omgeving van uw organisatie. Als deze instelling een waarde heeft, wordt die gebruikt in plaats van AUTH_LDAP_USER_SEARCH." -#: awx/sso/conf.py:254 +#: awx/sso/conf.py:259 msgid "LDAP User Attribute Map" msgstr "Kenmerkentoewijzing van LDAP-gebruikers" -#: awx/sso/conf.py:255 +#: awx/sso/conf.py:260 msgid "" "Mapping of LDAP user schema to Tower 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 Ansible Tower " "documentation for additional details." -msgstr "" -"Toewijzing van LDAP-gebruikersschema aan gebruikerskenmerken van Tower API. " -"De standaardinstelling is geldig voor ActiveDirectory, maar gebruikers met " -"andere LDAP-configuraties moeten mogelijk de waarden veranderen. Raadpleeg " -"de documentatie van Ansible Tower voor meer informatie." +msgstr "Toewijzing van LDAP-gebruikersschema aan gebruikerskenmerken van Tower API. De standaardinstelling is geldig voor ActiveDirectory, maar gebruikers met andere LDAP-configuraties moeten mogelijk de waarden veranderen. Raadpleeg de documentatie van Ansible Tower voor meer informatie." -#: awx/sso/conf.py:273 +#: awx/sso/conf.py:277 msgid "LDAP Group Search" msgstr "LDAP-groep zoeken" -#: awx/sso/conf.py:274 +#: awx/sso/conf.py:278 msgid "" "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." -msgstr "" -"Gebruikers worden toegewezen aan organisaties op grond van hun lidmaatschap " -"van LDAP-groepen. Deze instelling definieert de LDAP-zoekquery om groepen te" -" vinden. Anders dan het zoeken naar gebruikers biedt het zoeken naar groepen" -" geen ondersteuning voor LDAPSearchUnion." +msgstr "Gebruikers worden toegewezen aan organisaties op grond van hun lidmaatschap van LDAP-groepen. Deze instelling definieert de LDAP-zoekquery om groepen te vinden. Anders dan het zoeken naar gebruikers biedt het zoeken naar groepen geen ondersteuning voor LDAPSearchUnion." -#: awx/sso/conf.py:290 +#: awx/sso/conf.py:293 msgid "LDAP Group Type" msgstr "Type LDAP-groep" -#: awx/sso/conf.py:291 +#: awx/sso/conf.py:294 msgid "" -"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" -msgstr "" -"Mogelijk moet het groepstype worden gewijzigd op grond van het type LDAP-" -"server. Waarden worden vermeld op: https://django-auth-" -"ldap.readthedocs.io/en/stable/groups.html#types-of-groups" +"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" +msgstr "Mogelijk moet het groepstype worden gewijzigd op grond van het type LDAP-server. Waarden worden vermeld op: https://django-auth-ldap.readthedocs.io/en/stable/groups.html#types-of-groups" -#: awx/sso/conf.py:304 +#: awx/sso/conf.py:306 msgid "LDAP Group Type Parameters" msgstr "Parameters LDAP-groepstype" -#: awx/sso/conf.py:305 +#: awx/sso/conf.py:307 msgid "Key value parameters to send the chosen group type init method." -msgstr "" -"Parameters sleutelwaarde om de gekozen init.-methode van de groepssoort te " -"verzenden." +msgstr "Parameters sleutelwaarde om de gekozen init.-methode van de groepssoort te verzenden." -#: awx/sso/conf.py:327 +#: awx/sso/conf.py:328 msgid "LDAP Require Group" msgstr "LDAP-vereist-groep" -#: awx/sso/conf.py:328 +#: awx/sso/conf.py:329 msgid "" "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 via Tower. Only one require group is supported." -msgstr "" -"Groeps-DN vereist voor aanmelding. Indien opgegeven, moet de gebruiker lid " -"zijn van deze groep om zich aan te melden via LDAP. Indien niet ingesteld, " -"kan iedereen in LDAP die overeenkomt met de gebruikerszoekopdracht zich " -"aanmelden via Tower. Maar één vereiste groep wordt ondersteund." +msgstr "Groeps-DN vereist voor aanmelding. Indien opgegeven, moet de gebruiker lid zijn van deze groep om zich aan te melden via LDAP. Indien niet ingesteld, kan iedereen in LDAP die overeenkomt met de gebruikerszoekopdracht zich aanmelden via Tower. Maar één vereiste groep wordt ondersteund." #: awx/sso/conf.py:344 msgid "LDAP Deny Group" @@ -5146,687 +5452,573 @@ msgstr "LDAP-weiger-groep" msgid "" "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." -msgstr "" -"Groeps-DN geweigerd voor aanmelding. Indien opgegeven, kan een gebruikers " -"zich niet aanmelden als deze lid is van deze groep. Maar één weiger-groep " -"wordt ondersteund." +msgstr "Groeps-DN geweigerd voor aanmelding. Indien opgegeven, kan een gebruikers zich niet aanmelden als deze lid is van deze groep. Maar één weiger-groep wordt ondersteund." -#: awx/sso/conf.py:358 +#: awx/sso/conf.py:357 msgid "LDAP User Flags By Group" msgstr "LDAP-gebruikersvlaggen op groep" -#: awx/sso/conf.py:359 +#: awx/sso/conf.py:358 msgid "" "Retrieve users from a given group. At this time, superuser and system " "auditors are the only groups supported. Refer to the Ansible Tower " "documentation for more detail." -msgstr "" -"Gebruikers ophalen uit een opgegeven groep. Op dit moment zijn de enige " -"ondersteunde groepen supergebruikers en systeemauditors. Raadpleeg de " -"documentatie van Ansible Tower voor meer informatie." +msgstr "Gebruikers ophalen uit een opgegeven groep. Op dit moment zijn de enige ondersteunde groepen supergebruikers en systeemauditors. Raadpleeg de documentatie van Ansible Tower voor meer informatie." -#: awx/sso/conf.py:375 +#: awx/sso/conf.py:373 msgid "LDAP Organization Map" msgstr "Toewijzing LDAP-organisaties" -#: awx/sso/conf.py:376 +#: awx/sso/conf.py:374 msgid "" "Mapping between organization admins/users and LDAP groups. This controls " -"which users are placed into which Tower organizations relative to their LDAP" -" group memberships. Configuration details are available in the Ansible Tower" -" documentation." -msgstr "" -"Toewijzing tussen organisatiebeheerders/-gebruikers en LDAP-groepen. Dit " -"bepaalt welke gebruikers worden geplaatst in welke Tower-organisaties ten " -"opzichte van hun LDAP-groepslidmaatschappen. Configuratiedetails zijn " -"beschikbaar in de documentatie van Ansible Tower." +"which users are placed into which Tower organizations relative to their LDAP " +"group memberships. Configuration details are available in the Ansible Tower " +"documentation." +msgstr "Toewijzing tussen organisatiebeheerders/-gebruikers en LDAP-groepen. Dit bepaalt welke gebruikers worden geplaatst in welke Tower-organisaties ten opzichte van hun LDAP-groepslidmaatschappen. Configuratiedetails zijn beschikbaar in de documentatie van Ansible Tower." -#: awx/sso/conf.py:403 +#: awx/sso/conf.py:401 msgid "LDAP Team Map" msgstr "LDAP-teamtoewijzing" -#: awx/sso/conf.py:404 +#: awx/sso/conf.py:402 msgid "" "Mapping between team members (users) and LDAP groups. Configuration details " "are available in the Ansible Tower documentation." -msgstr "" -"Toewijzing tussen teamleden (gebruikers) en LDAP-groepen. " -"Configuratiedetails zijn beschikbaar in de documentatie van Ansible Tower." +msgstr "Toewijzing tussen teamleden (gebruikers) en LDAP-groepen. Configuratiedetails zijn beschikbaar in de documentatie van Ansible Tower." -#: awx/sso/conf.py:440 +#: awx/sso/conf.py:437 msgid "RADIUS Server" msgstr "RADIUS-server" -#: awx/sso/conf.py:441 +#: awx/sso/conf.py:438 msgid "" "Hostname/IP of RADIUS server. RADIUS authentication is disabled if this " "setting is empty." -msgstr "" -"Hostnaam/IP-adres van RADIUS-server. RADIUS-authenticatie wordt " -"uitgeschakeld als deze instelling leeg is." +msgstr "Hostnaam/IP-adres van RADIUS-server. RADIUS-authenticatie wordt uitgeschakeld als deze instelling leeg is." -#: awx/sso/conf.py:443 awx/sso/conf.py:457 awx/sso/conf.py:469 +#: awx/sso/conf.py:440 awx/sso/conf.py:453 awx/sso/conf.py:464 #: awx/sso/models.py:14 msgid "RADIUS" msgstr "RADIUS" -#: awx/sso/conf.py:455 +#: awx/sso/conf.py:451 msgid "RADIUS Port" msgstr "RADIUS-poort" -#: awx/sso/conf.py:456 +#: awx/sso/conf.py:452 msgid "Port of RADIUS server." msgstr "Poort van RADIUS-server." -#: awx/sso/conf.py:467 +#: awx/sso/conf.py:462 msgid "RADIUS Secret" msgstr "RADIUS-geheim" -#: awx/sso/conf.py:468 +#: awx/sso/conf.py:463 msgid "Shared secret for authenticating to RADIUS server." msgstr "Gedeeld geheim voor authenticatie naar RADIUS-server." -#: awx/sso/conf.py:484 +#: awx/sso/conf.py:478 msgid "TACACS+ Server" msgstr "TACACS+ server" -#: awx/sso/conf.py:485 +#: awx/sso/conf.py:479 msgid "Hostname of TACACS+ server." msgstr "Hostnaam van TACACS+ server." -#: awx/sso/conf.py:486 awx/sso/conf.py:499 awx/sso/conf.py:512 -#: awx/sso/conf.py:525 awx/sso/conf.py:537 awx/sso/models.py:15 +#: awx/sso/conf.py:480 awx/sso/conf.py:492 awx/sso/conf.py:504 +#: awx/sso/conf.py:516 awx/sso/conf.py:527 awx/sso/models.py:15 msgid "TACACS+" msgstr "TACACS+" -#: awx/sso/conf.py:497 +#: awx/sso/conf.py:490 msgid "TACACS+ Port" msgstr "TACACS+ poort" -#: awx/sso/conf.py:498 +#: awx/sso/conf.py:491 msgid "Port number of TACACS+ server." msgstr "Poortnummer van TACACS+ server." -#: awx/sso/conf.py:510 +#: awx/sso/conf.py:502 msgid "TACACS+ Secret" msgstr "TACACS+ geheim" -#: awx/sso/conf.py:511 +#: awx/sso/conf.py:503 msgid "Shared secret for authenticating to TACACS+ server." msgstr "Gedeeld geheim voor authenticatie naar TACACS+ server." -#: awx/sso/conf.py:523 +#: awx/sso/conf.py:514 msgid "TACACS+ Auth Session Timeout" msgstr "Time-out TACACS+ authenticatiesessie" -#: awx/sso/conf.py:524 +#: awx/sso/conf.py:515 msgid "TACACS+ session timeout value in seconds, 0 disables timeout." -msgstr "" -"Time-outwaarde TACACS+ sessie in seconden, 0 schakelt de time-out uit." +msgstr "Time-outwaarde TACACS+ sessie in seconden, 0 schakelt de time-out uit." -#: awx/sso/conf.py:535 +#: awx/sso/conf.py:525 msgid "TACACS+ Authentication Protocol" msgstr "TACACS+ authenticatieprotocol" -#: awx/sso/conf.py:536 +#: awx/sso/conf.py:526 msgid "Choose the authentication protocol used by TACACS+ client." -msgstr "" -"Kies het authenticatieprotocol dat wordt gebruikt door de TACACS+ client." +msgstr "Kies het authenticatieprotocol dat wordt gebruikt door de TACACS+ client." -#: awx/sso/conf.py:551 +#: awx/sso/conf.py:540 msgid "Google OAuth2 Callback URL" msgstr "Terugkoppelings-URL voor Google OAuth2" -#: awx/sso/conf.py:552 awx/sso/conf.py:645 awx/sso/conf.py:710 +#: awx/sso/conf.py:541 awx/sso/conf.py:634 awx/sso/conf.py:699 msgid "" "Provide this URL as the callback URL for your application as part of your " "registration process. Refer to the Ansible Tower documentation for more " "detail." -msgstr "" -"Geef deze URL op als de terugkoppelings-URL voor uw toepassing als onderdeel" -" van uw registratieproces. Raadpleeg de documentatie van Ansible Tower voor " -"meer informatie." +msgstr "Geef deze URL op als de terugkoppelings-URL voor uw toepassing als onderdeel van uw registratieproces. Raadpleeg de documentatie van Ansible Tower voor meer informatie." -#: awx/sso/conf.py:555 awx/sso/conf.py:567 awx/sso/conf.py:579 -#: awx/sso/conf.py:592 awx/sso/conf.py:606 awx/sso/conf.py:618 -#: awx/sso/conf.py:630 +#: awx/sso/conf.py:544 awx/sso/conf.py:556 awx/sso/conf.py:568 +#: awx/sso/conf.py:581 awx/sso/conf.py:595 awx/sso/conf.py:607 +#: awx/sso/conf.py:619 msgid "Google OAuth2" msgstr "Google OAuth2" -#: awx/sso/conf.py:565 +#: awx/sso/conf.py:554 msgid "Google OAuth2 Key" msgstr "Google OAuth2-sleutel" -#: awx/sso/conf.py:566 +#: awx/sso/conf.py:555 msgid "The OAuth2 key from your web application." msgstr "De OAuth2-sleutel van uw webtoepassing." -#: awx/sso/conf.py:577 +#: awx/sso/conf.py:566 msgid "Google OAuth2 Secret" msgstr "Google OAuth2-geheim" -#: awx/sso/conf.py:578 +#: awx/sso/conf.py:567 msgid "The OAuth2 secret from your web application." msgstr "Het OAuth2-geheim van uw webtoepassing." -#: awx/sso/conf.py:589 +#: awx/sso/conf.py:578 msgid "Google OAuth2 Whitelisted Domains" msgstr "In whitelist opgenomen Google OAuth2-domeinen" -#: awx/sso/conf.py:590 +#: awx/sso/conf.py:579 msgid "" "Update this setting to restrict the domains who are allowed to login using " "Google OAuth2." -msgstr "" -"Werk deze instelling bij om te beperken welke domeinen zich mogen aanmelden " -"met Google OAuth2." +msgstr "Werk deze instelling bij om te beperken welke domeinen zich mogen aanmelden met Google OAuth2." -#: awx/sso/conf.py:601 +#: awx/sso/conf.py:590 msgid "Google OAuth2 Extra Arguments" msgstr "Extra argumenten Google OAuth2" -#: awx/sso/conf.py:602 +#: awx/sso/conf.py:591 msgid "" -"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 " +"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 Ansible Tower documentation for more detail." -msgstr "" -"Extra argumenten voor Google OAuth2-aanmelding. U kunt een beperking " -"instellen, waardoor de authenticatie toegestaan is voor niet meer dan één " -"domein, zelfs als de gebruiker aangemeld is met meerdere Google-accounts. " -"Raadpleeg de documentatie van Ansible Tower voor meer informatie." +msgstr "Extra argumenten voor Google OAuth2-aanmelding. U kunt een beperking instellen, waardoor de authenticatie toegestaan is voor niet meer dan één domein, zelfs als de gebruiker aangemeld is met meerdere Google-accounts. Raadpleeg de documentatie van Ansible Tower voor meer informatie." -#: awx/sso/conf.py:616 +#: awx/sso/conf.py:605 msgid "Google OAuth2 Organization Map" msgstr "Organisatietoewijzing Google OAuth2" -#: awx/sso/conf.py:628 +#: awx/sso/conf.py:617 msgid "Google OAuth2 Team Map" msgstr "Teamtoewijzing Google OAuth2" -#: awx/sso/conf.py:644 +#: awx/sso/conf.py:633 msgid "GitHub OAuth2 Callback URL" msgstr "Terugkoppelings-URL GitHub OAuth2" -#: awx/sso/conf.py:648 awx/sso/conf.py:660 awx/sso/conf.py:671 -#: awx/sso/conf.py:683 awx/sso/conf.py:695 +#: awx/sso/conf.py:637 awx/sso/conf.py:649 awx/sso/conf.py:660 +#: awx/sso/conf.py:672 awx/sso/conf.py:684 msgid "GitHub OAuth2" msgstr "GitHub OAuth2" -#: awx/sso/conf.py:658 +#: awx/sso/conf.py:647 msgid "GitHub OAuth2 Key" msgstr "GitHub OAuth2-sleutel" -#: awx/sso/conf.py:659 +#: awx/sso/conf.py:648 msgid "The OAuth2 key (Client ID) from your GitHub developer application." msgstr "De OAuth2-sleutel (Client-id) van uw GitHub-ontwikkelaarstoepassing." -#: awx/sso/conf.py:669 +#: awx/sso/conf.py:658 msgid "GitHub OAuth2 Secret" msgstr "GitHub OAuth2-geheim" -#: awx/sso/conf.py:670 +#: awx/sso/conf.py:659 msgid "" "The OAuth2 secret (Client Secret) from your GitHub developer application." -msgstr "" -"Het OAuth2-geheim (Client-geheim) van uw GitHub-ontwikkelaarstoepassing." +msgstr "Het OAuth2-geheim (Client-geheim) van uw GitHub-ontwikkelaarstoepassing." -#: awx/sso/conf.py:681 +#: awx/sso/conf.py:670 msgid "GitHub OAuth2 Organization Map" msgstr "GitHub OAuth2-organisatietoewijzing" -#: awx/sso/conf.py:693 +#: awx/sso/conf.py:682 msgid "GitHub OAuth2 Team Map" msgstr "GitHub OAuth2-teamtoewijzing" -#: awx/sso/conf.py:709 +#: awx/sso/conf.py:698 msgid "GitHub Organization OAuth2 Callback URL" msgstr "OAuth2-terugkoppelings-URL GitHub-organisatie" -#: awx/sso/conf.py:713 awx/sso/conf.py:725 awx/sso/conf.py:736 -#: awx/sso/conf.py:749 awx/sso/conf.py:760 awx/sso/conf.py:772 +#: awx/sso/conf.py:702 awx/sso/conf.py:714 awx/sso/conf.py:725 +#: awx/sso/conf.py:738 awx/sso/conf.py:749 awx/sso/conf.py:761 msgid "GitHub Organization OAuth2" msgstr "Organization OAuth2 van GitHub-organisatie" -#: awx/sso/conf.py:723 +#: awx/sso/conf.py:712 msgid "GitHub Organization OAuth2 Key" -msgstr " OAuth2-sleutel van GitHub-organisatie" +msgstr "OAuth2-sleutel van GitHub-organisatie" -#: awx/sso/conf.py:724 awx/sso/conf.py:802 +#: awx/sso/conf.py:713 awx/sso/conf.py:791 msgid "The OAuth2 key (Client ID) from your GitHub organization application." msgstr "De OAuth2-sleutel (Client-id) van uw GitHub-organisatietoepassing." -#: awx/sso/conf.py:734 +#: awx/sso/conf.py:723 msgid "GitHub Organization OAuth2 Secret" -msgstr " OAuth2-geheim van GitHub-organisatie" +msgstr "OAuth2-geheim van GitHub-organisatie" -#: awx/sso/conf.py:735 awx/sso/conf.py:813 +#: awx/sso/conf.py:724 awx/sso/conf.py:802 msgid "" "The OAuth2 secret (Client Secret) from your GitHub organization application." -msgstr "" -"Het OAuth2-geheim (Client-geheim) van uw GitHub-organisatietoepassing." +msgstr "Het OAuth2-geheim (Client-geheim) van uw GitHub-organisatietoepassing." -#: awx/sso/conf.py:746 +#: awx/sso/conf.py:735 msgid "GitHub Organization Name" msgstr "Naam van GitHub-organisatie" -#: awx/sso/conf.py:747 +#: awx/sso/conf.py:736 msgid "" "The name of your GitHub organization, as used in your organization's URL: " "https://github.com//." -msgstr "" -"De naam van uw GitHub-organisatie zoals gebruikt in de URL van uw " -"organisatie: https://github.com//." +msgstr "De naam van uw GitHub-organisatie zoals gebruikt in de URL van uw organisatie: https://github.com//." -#: awx/sso/conf.py:758 +#: awx/sso/conf.py:747 msgid "GitHub Organization OAuth2 Organization Map" msgstr "OAuth2-organisatietoewijzing van GitHub-organisatie" -#: awx/sso/conf.py:770 +#: awx/sso/conf.py:759 msgid "GitHub Organization OAuth2 Team Map" msgstr "OAuth2-teamtoewijzing van GitHub-organisatie" -#: awx/sso/conf.py:786 +#: awx/sso/conf.py:775 msgid "GitHub Team OAuth2 Callback URL" msgstr "OAuth2-terugkoppelings-URL GitHub-team" -#: awx/sso/conf.py:787 +#: awx/sso/conf.py:776 msgid "" -"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." -msgstr "" -"Maak een toepassing in eigendom van de organisatie op " -"https://github.com/organizations//settings/applications en verkrijg" -" een OAuth2-sleutel (Client-id) en -geheim (Client-geheim). Lever deze URL " -"als de terugkoppelings-URL voor uw toepassing." +"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." +msgstr "Maak een toepassing in eigendom van de organisatie op https://github.com/organizations//settings/applications en verkrijg een OAuth2-sleutel (Client-id) en -geheim (Client-geheim). Lever deze URL als de terugkoppelings-URL voor uw toepassing." -#: awx/sso/conf.py:791 awx/sso/conf.py:803 awx/sso/conf.py:814 -#: awx/sso/conf.py:827 awx/sso/conf.py:838 awx/sso/conf.py:850 +#: awx/sso/conf.py:780 awx/sso/conf.py:792 awx/sso/conf.py:803 +#: awx/sso/conf.py:816 awx/sso/conf.py:827 awx/sso/conf.py:839 msgid "GitHub Team OAuth2" msgstr "OAuth2 van GitHub-team" -#: awx/sso/conf.py:801 +#: awx/sso/conf.py:790 msgid "GitHub Team OAuth2 Key" msgstr "OAuth2-sleutel GitHub-team" -#: awx/sso/conf.py:812 +#: awx/sso/conf.py:801 msgid "GitHub Team OAuth2 Secret" msgstr "OAuth2-geheim GitHub-team" -#: awx/sso/conf.py:824 +#: awx/sso/conf.py:813 msgid "GitHub Team ID" msgstr "Id GitHub-team" -#: awx/sso/conf.py:825 +#: awx/sso/conf.py:814 msgid "" -"Find the numeric team ID using the Github API: http://fabian-" -"kostadinov.github.io/2015/01/16/how-to-find-a-github-team-id/." -msgstr "" -"Zoek de numerieke team-id op met de Github API: http://fabian-" -"kostadinov.github.io/2015/01/16/how-to-find-a-github-team-id/." +"Find the numeric team ID using the Github API: http://fabian-kostadinov." +"github.io/2015/01/16/how-to-find-a-github-team-id/." +msgstr "Zoek de numerieke team-id op met de Github API: http://fabian-kostadinov.github.io/2015/01/16/how-to-find-a-github-team-id/." -#: awx/sso/conf.py:836 +#: awx/sso/conf.py:825 msgid "GitHub Team OAuth2 Organization Map" msgstr "OAuth2-organisatietoewijzing van GitHub-team" -#: awx/sso/conf.py:848 +#: awx/sso/conf.py:837 msgid "GitHub Team OAuth2 Team Map" -msgstr "OAuth2-teamtoewijzing van GitHub-organisatie" +msgstr "OAuth2-teamtoewijzing van GitHub-team" -#: awx/sso/conf.py:864 +#: awx/sso/conf.py:853 msgid "Azure AD OAuth2 Callback URL" msgstr "Terugkoppelings-URL voor Azure AD OAuth2" -#: awx/sso/conf.py:865 +#: awx/sso/conf.py:854 msgid "" "Provide this URL as the callback URL for your application as part of your " "registration process. Refer to the Ansible Tower documentation for more " "detail. " -msgstr "" -"Geef deze URL op als de terugkoppelings-URL voor uw toepassing als onderdeel" -" van uw registratieproces. Raadpleeg de documentatie van Ansible Tower voor " -"meer informatie." +msgstr "Geef deze URL op als de terugkoppelings-URL voor uw toepassing als onderdeel van uw registratieproces. Raadpleeg de documentatie van Ansible Tower voor meer informatie." -#: awx/sso/conf.py:868 awx/sso/conf.py:880 awx/sso/conf.py:891 -#: awx/sso/conf.py:903 awx/sso/conf.py:915 +#: awx/sso/conf.py:857 awx/sso/conf.py:869 awx/sso/conf.py:880 +#: awx/sso/conf.py:892 awx/sso/conf.py:904 msgid "Azure AD OAuth2" msgstr "Azure AD OAuth2" -#: awx/sso/conf.py:878 +#: awx/sso/conf.py:867 msgid "Azure AD OAuth2 Key" msgstr "Azure AD OAuth2-sleutel" -#: awx/sso/conf.py:879 +#: awx/sso/conf.py:868 msgid "The OAuth2 key (Client ID) from your Azure AD application." msgstr "De OAuth2-sleutel (Client-id) van uw Azure AD-toepassing." -#: awx/sso/conf.py:889 +#: awx/sso/conf.py:878 msgid "Azure AD OAuth2 Secret" msgstr "Azure AD OAuth2-geheim" -#: awx/sso/conf.py:890 +#: awx/sso/conf.py:879 msgid "The OAuth2 secret (Client Secret) from your Azure AD application." msgstr "Het OAuth2-geheim (Client-geheim) van uw Azure AD-toepassing." -#: awx/sso/conf.py:901 +#: awx/sso/conf.py:890 msgid "Azure AD OAuth2 Organization Map" msgstr "Azure AD OAuth2-organisatietoewijzing" -#: awx/sso/conf.py:913 +#: awx/sso/conf.py:902 msgid "Azure AD OAuth2 Team Map" msgstr "Azure AD OAuth2-teamtoewijzing" -#: awx/sso/conf.py:938 +#: awx/sso/conf.py:927 msgid "SAML Assertion Consumer Service (ACS) URL" msgstr "URL SAML Assertion Consumer Service (ACS)" -#: awx/sso/conf.py:939 +#: awx/sso/conf.py:928 msgid "" "Register Tower 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." -msgstr "" -"Registreer Tower als serviceprovider (SP) met elke identiteitsprovider (IdP)" -" die u hebt geconfigureerd. Lever uw SP-entiteit-id en deze ACS URL voor uw " -"toepassing." - -#: awx/sso/conf.py:942 awx/sso/conf.py:956 awx/sso/conf.py:970 -#: awx/sso/conf.py:985 awx/sso/conf.py:999 awx/sso/conf.py:1012 -#: awx/sso/conf.py:1033 awx/sso/conf.py:1051 awx/sso/conf.py:1070 -#: awx/sso/conf.py:1106 awx/sso/conf.py:1138 awx/sso/conf.py:1152 -#: awx/sso/conf.py:1169 awx/sso/conf.py:1182 awx/sso/conf.py:1195 -#: awx/sso/conf.py:1213 awx/sso/models.py:16 +msgstr "Registreer Tower als serviceprovider (SP) met elke identiteitsprovider (IdP) die u hebt geconfigureerd. Lever uw SP-entiteit-id en deze ACS URL voor uw toepassing." + +#: awx/sso/conf.py:931 awx/sso/conf.py:944 awx/sso/conf.py:957 +#: awx/sso/conf.py:971 awx/sso/conf.py:984 awx/sso/conf.py:996 +#: awx/sso/conf.py:1016 awx/sso/conf.py:1033 awx/sso/conf.py:1051 +#: awx/sso/conf.py:1086 awx/sso/conf.py:1117 awx/sso/conf.py:1130 +#: awx/sso/conf.py:1146 awx/sso/conf.py:1158 awx/sso/conf.py:1170 +#: awx/sso/conf.py:1189 awx/sso/models.py:16 msgid "SAML" msgstr "SAML" -#: awx/sso/conf.py:953 +#: awx/sso/conf.py:941 msgid "SAML Service Provider Metadata URL" msgstr "URL voor metagegevens van SAML-serviceprovider" -#: awx/sso/conf.py:954 +#: awx/sso/conf.py:942 msgid "" "If your identity provider (IdP) allows uploading an XML metadata file, you " "can download one from this URL." -msgstr "" -"Als uw identiteitsprovider (IdP) toestaat een XML-gegevensbestand te " -"uploaden, kunt u er een uploaden vanaf deze URL." +msgstr "Als uw identiteitsprovider (IdP) toestaat een XML-gegevensbestand te uploaden, kunt u er een uploaden vanaf deze URL." -#: awx/sso/conf.py:966 +#: awx/sso/conf.py:953 msgid "SAML Service Provider Entity ID" msgstr "Entiteit-id van SAML-serviceprovider" -#: awx/sso/conf.py:967 +#: awx/sso/conf.py:954 msgid "" "The application-defined unique identifier used as the audience of the SAML " "service provider (SP) configuration. This is usually the URL for Tower." -msgstr "" -"De toepassingsgedefinieerde unieke id gebruikt als doelgroep van de SAML-" -"serviceprovider (SP)-configuratie. Dit is gewoonlijk de URL voor Tower." +msgstr "De toepassingsgedefinieerde unieke id gebruikt als doelgroep van de SAML-serviceprovider (SP)-configuratie. Dit is gewoonlijk de URL voor Tower." -#: awx/sso/conf.py:982 +#: awx/sso/conf.py:968 msgid "SAML Service Provider Public Certificate" msgstr "Openbaar certificaat SAML-serviceprovider" -#: awx/sso/conf.py:983 +#: awx/sso/conf.py:969 msgid "" -"Create a keypair for Tower to use as a service provider (SP) and include the" -" certificate content here." -msgstr "" -"Maak een sleutelpaar voor Tower om dit te gebruiken als serviceprovider (SP)" -" en neem de certificaatinhoud hier op." +"Create a keypair for Tower to use as a service provider (SP) and include the " +"certificate content here." +msgstr "Maak een sleutelpaar voor Tower om dit te gebruiken als serviceprovider (SP) en neem de certificaatinhoud hier op." -#: awx/sso/conf.py:996 +#: awx/sso/conf.py:981 msgid "SAML Service Provider Private Key" msgstr "Privésleutel SAML-serviceprovider" -#: awx/sso/conf.py:997 +#: awx/sso/conf.py:982 msgid "" -"Create a keypair for Tower to use as a service provider (SP) and include the" -" private key content here." -msgstr "" -"Maak een sleutelpaar voor Tower om dit te gebruiken als serviceprovider (SP)" -" en neem de inhoud van de privésleutel hier op." +"Create a keypair for Tower to use as a service provider (SP) and include the " +"private key content here." +msgstr "Maak een sleutelpaar voor Tower om dit te gebruiken als serviceprovider (SP) en neem de inhoud van de privésleutel hier op." -#: awx/sso/conf.py:1009 +#: awx/sso/conf.py:993 msgid "SAML Service Provider Organization Info" msgstr "Organisatie-informatie SAML-serviceprovider" -#: awx/sso/conf.py:1010 +#: awx/sso/conf.py:994 msgid "" "Provide the URL, display name, and the name of your app. Refer to the " "Ansible Tower documentation for example syntax." -msgstr "" -"Geef de URL, weergavenaam en de naam van uw toepassing op. Raadpleeg de " -"documentatie van Ansible Tower voor voorbeeldsyntaxis." +msgstr "Geef de URL, weergavenaam en de naam van uw toepassing op. Raadpleeg de documentatie van Ansible Tower voor voorbeeldsyntaxis." -#: awx/sso/conf.py:1029 +#: awx/sso/conf.py:1012 msgid "SAML Service Provider Technical Contact" msgstr "Technisch contactpersoon SAML-serviceprovider" -#: awx/sso/conf.py:1030 +#: awx/sso/conf.py:1013 msgid "" -"Provide the name and email address of the technical contact for your service" -" provider. Refer to the Ansible Tower documentation for example syntax." -msgstr "" -"Geef de naam en het e-mailadres van de technische contactpersoon van uw " -"serviceprovider op. Raadpleeg de documentatie van Ansible Tower voor " -"voorbeeldsyntaxis." +"Provide the name and email address of the technical contact for your service " +"provider. Refer to the Ansible Tower documentation for example syntax." +msgstr "Geef de naam en het e-mailadres van de technische contactpersoon van uw serviceprovider op. Raadpleeg de documentatie van Ansible Tower voor voorbeeldsyntaxis." -#: awx/sso/conf.py:1047 +#: awx/sso/conf.py:1029 msgid "SAML Service Provider Support Contact" msgstr "Ondersteuningscontactpersoon SAML-serviceprovider" -#: awx/sso/conf.py:1048 +#: awx/sso/conf.py:1030 msgid "" "Provide the name and email address of the support contact for your service " "provider. Refer to the Ansible Tower documentation for example syntax." -msgstr "" -"Geef de naam en het e-mailadres van de ondersteuningscontactpersoon voor uw " -"serviceprovider op. Raadpleeg de documentatie van Ansible Tower voor " -"voorbeeldsyntaxis." +msgstr "Geef de naam en het e-mailadres van de ondersteuningscontactpersoon voor uw serviceprovider op. Raadpleeg de documentatie van Ansible Tower voor voorbeeldsyntaxis." -#: awx/sso/conf.py:1064 +#: awx/sso/conf.py:1045 msgid "SAML Enabled Identity Providers" msgstr "Id-providers met SAML-mogelijkheden" -#: awx/sso/conf.py:1065 +#: awx/sso/conf.py:1046 msgid "" "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." -msgstr "" -"Configureer de entiteit-id, de SSO URL en het certificaat voor elke id-" -"provider (IdP) die in gebruik is. Meerdere ASAML IdP's worden ondersteund. " -"Sommige IdP's kunnen gebruikersgegevens verschaffen met kenmerknamen die " -"verschillen van de standaard-OID's. Kenmerknamen kunnen worden overschreven " -"voor elke IdP. Raadpleeg de documentatie van Ansible voor meer informatie en" -" syntaxis." - -#: awx/sso/conf.py:1102 +"names may be overridden for each IdP. Refer to the Ansible documentation for " +"additional details and syntax." +msgstr "Configureer de entiteit-id, de SSO URL en het certificaat voor elke id-provider (IdP) die in gebruik is. Meerdere ASAML IdP's worden ondersteund. Sommige IdP's kunnen gebruikersgegevens verschaffen met kenmerknamen die verschillen van de standaard-OID's. Kenmerknamen kunnen worden overschreven voor elke IdP. Raadpleeg de documentatie van Ansible voor meer informatie en syntaxis." + +#: awx/sso/conf.py:1082 msgid "SAML Security Config" msgstr "SAML-beveiligingsconfiguratie" -#: awx/sso/conf.py:1103 +#: awx/sso/conf.py:1083 msgid "" "A dict of key value pairs that are passed to the underlying python-saml " "security setting https://github.com/onelogin/python-saml#settings" -msgstr "" -"Een dict van sleutelwaardeparen die doorgegeven worden aan de onderliggende " -"python-saml-beveiligingsinstelling https://github.com/onelogin/python-" -"saml#settings" +msgstr "Een dict van sleutelwaardeparen die doorgegeven worden aan de onderliggende python-saml-beveiligingsinstelling https://github.com/onelogin/python-saml#settings" -#: awx/sso/conf.py:1135 +#: awx/sso/conf.py:1114 msgid "SAML Service Provider extra configuration data" msgstr "SAML-serviceprovider extra configuratiegegevens" -#: awx/sso/conf.py:1136 +#: awx/sso/conf.py:1115 msgid "" -"A dict of key value pairs to be passed to the underlying python-saml Service" -" Provider configuration setting." -msgstr "" -"Een dict van sleutelwaardeparen die doorgegeven moeten worden aan de " -"onderliggende configuratie-instelling van de python-saml-serviceprovider." +"A dict of key value pairs to be passed to the underlying python-saml Service " +"Provider configuration setting." +msgstr "Een dict van sleutelwaardeparen die doorgegeven moeten worden aan de onderliggende configuratie-instelling van de python-saml-serviceprovider." -#: awx/sso/conf.py:1149 +#: awx/sso/conf.py:1127 msgid "SAML IDP to extra_data attribute mapping" msgstr "SAML IDP voor extra_data kenmerkentoewijzing" -#: awx/sso/conf.py:1150 +#: awx/sso/conf.py:1128 msgid "" "A list of tuples that maps IDP attributes to extra_attributes. Each " "attribute will be a list of values, even if only 1 value." -msgstr "" -"Een lijst van tuples die IDP-kenmerken toewijst aan extra_attributes. Ieder " -"kenmerk is een lijst van variabelen, zelfs als de lijst maar één variabele " -"bevat." +msgstr "Een lijst van tupels die IDP-kenmerken toewijst aan extra_attributes. Ieder kenmerk is een lijst van variabelen, zelfs als de lijst maar één variabele bevat." -#: awx/sso/conf.py:1167 +#: awx/sso/conf.py:1144 msgid "SAML Organization Map" msgstr "SAML-organisatietoewijzing" -#: awx/sso/conf.py:1180 +#: awx/sso/conf.py:1156 msgid "SAML Team Map" msgstr "SAML-teamtoewijzing" -#: awx/sso/conf.py:1193 +#: awx/sso/conf.py:1168 msgid "SAML Organization Attribute Mapping" msgstr "Kenmerktoewijzing SAML-organisatie" -#: awx/sso/conf.py:1194 +#: awx/sso/conf.py:1169 msgid "Used to translate user organization membership into Tower." -msgstr "" -"Gebruikt om organisatielidmaatschap van gebruikers om te zetten in Tower." +msgstr "Gebruikt om organisatielidmaatschap van gebruikers om te zetten in Tower." -#: awx/sso/conf.py:1211 +#: awx/sso/conf.py:1187 msgid "SAML Team Attribute Mapping" msgstr "Kenmerktoewijzing SAML-team" -#: awx/sso/conf.py:1212 +#: awx/sso/conf.py:1188 msgid "Used to translate user team membership into Tower." msgstr "Gebruikt om teamlidmaatschap van gebruikers om te zetten in Tower." -#: awx/sso/fields.py:183 +#: awx/sso/fields.py:81 +msgid "Invalid field." +msgstr "Ongeldig veld." + +#: awx/sso/fields.py:250 #, python-brace-format msgid "Invalid connection option(s): {invalid_options}." msgstr "Ongeldige verbindingsoptie(s): {invalid_options}." -#: awx/sso/fields.py:266 +#: awx/sso/fields.py:334 msgid "Base" msgstr "Basis" -#: awx/sso/fields.py:267 +#: awx/sso/fields.py:335 msgid "One Level" msgstr "Eén niveau" -#: awx/sso/fields.py:268 +#: awx/sso/fields.py:336 msgid "Subtree" msgstr "Substructuur" -#: awx/sso/fields.py:286 +#: awx/sso/fields.py:354 #, python-brace-format msgid "Expected a list of three items but got {length} instead." msgstr "Verwachtte een lijst met drie items, maar kreeg er {length}." -#: awx/sso/fields.py:287 +#: awx/sso/fields.py:355 #, python-brace-format msgid "Expected an instance of LDAPSearch but got {input_type} instead." -msgstr "" -"Verwachtte een instantie van LDAPSearch, maar kreeg in plaats daarvan " -"{input_type}." +msgstr "Verwachtte een instantie van LDAPSearch, maar kreeg in plaats daarvan {input_type}." -#: awx/sso/fields.py:323 +#: awx/sso/fields.py:391 #, python-brace-format msgid "" "Expected an instance of LDAPSearch or LDAPSearchUnion but got {input_type} " "instead." -msgstr "" -"Verwachtte een instantie van LDAPSearch of LDAPSearchUnion, maar kreeg in " -"plaats daarvan {input_type}." +msgstr "Verwachtte een instantie van LDAPSearch of LDAPSearchUnion, maar kreeg in plaats daarvan {input_type}." -#: awx/sso/fields.py:361 +#: awx/sso/fields.py:429 #, python-brace-format msgid "Invalid user attribute(s): {invalid_attrs}." msgstr "Ongeldig(e) gebruikerskenmerk(en): {invalid_attrs}." -#: awx/sso/fields.py:378 +#: awx/sso/fields.py:447 #, python-brace-format msgid "Expected an instance of LDAPGroupType but got {input_type} instead." -msgstr "" -"Verwachtte een instantie van LDAPGroupType, maar kreeg in plaats daarvan " -"{input_type}." +msgstr "Verwachtte een instantie van LDAPGroupType, maar kreeg in plaats daarvan {input_type}." -#: awx/sso/fields.py:418 awx/sso/fields.py:465 +#: awx/sso/fields.py:487 #, python-brace-format msgid "Invalid key(s): {invalid_keys}." msgstr "Ongeldige sleutel(s): {invalid_keys}." -#: awx/sso/fields.py:443 +#: awx/sso/fields.py:513 #, python-brace-format msgid "Invalid user flag: \"{invalid_flag}\"." -msgstr "Ongeldige gebruikersvlag: \"{invalid_flag}\"." - -#: awx/sso/fields.py:464 -#, python-brace-format -msgid "Missing key(s): {missing_keys}." -msgstr "Ontbrekende sleutel(s): {missing_keys}." - -#: awx/sso/fields.py:514 awx/sso/fields.py:631 -#, python-brace-format -msgid "Invalid key(s) for organization map: {invalid_keys}." -msgstr "Ongeldige sleutel(s) voor organisatietoewijzing: {invalid_keys}." - -#: awx/sso/fields.py:532 -#, python-brace-format -msgid "Missing required key for team map: {invalid_keys}." -msgstr "Ontbrekende vereiste sleutel voor teamtoewijzing: {invalid_keys}." - -#: awx/sso/fields.py:533 awx/sso/fields.py:650 -#, python-brace-format -msgid "Invalid key(s) for team map: {invalid_keys}." -msgstr "Ongeldige sleutel(s) voor teamtoewijzing: {invalid_keys}." - -#: awx/sso/fields.py:649 -#, python-brace-format -msgid "Missing required key for team map: {missing_keys}." -msgstr "Ontbrekende vereiste sleutel voor teamtoewijzing: {missing_keys}." +msgstr "Ongeldige gebruikersvlag: ‘{invalid_flag}‘." #: awx/sso/fields.py:667 #, python-brace-format -msgid "Missing required key(s) for org info record: {missing_keys}." -msgstr "Ontbrekende vereiste sleutel(s) voor org info record: {missing_keys}." - -#: awx/sso/fields.py:680 -#, python-brace-format msgid "Invalid language code(s) for org info: {invalid_lang_codes}." msgstr "Ongeldige taalcode(s) voor org info: {invalid_lang_codes}." -#: awx/sso/fields.py:699 -#, python-brace-format -msgid "Missing required key(s) for contact: {missing_keys}." -msgstr "Ontbrekende vereiste sleutel(s) voor contactpersoon: {missing_keys}." - -#: awx/sso/fields.py:711 -#, python-brace-format -msgid "Missing required key(s) for IdP: {missing_keys}." -msgstr "Ontbrekende vereiste sleutel(s) voor IdP: {missing_keys}." - -#: awx/sso/pipeline.py:31 +#: awx/sso/pipeline.py:27 #, python-brace-format msgid "An account cannot be found for {0}" -msgstr "Een account kan niet worden gevonden voor {0}" +msgstr "Er kan geen account worden gevonden voor {0}" -#: awx/sso/pipeline.py:37 +#: awx/sso/pipeline.py:33 msgid "Your account is inactive" msgstr "Uw account is inactief" #: awx/sso/validators.py:20 awx/sso/validators.py:46 #, python-format msgid "DN must include \"%%(user)s\" placeholder for username: %s" -msgstr "DN moet plaatshouder \"%%(user)s\" bevatten voor gebruikersnaam: %s" +msgstr "DN moet plaatshouder ‘%%(user)s‘ bevatten voor gebruikersnaam: %s" #: awx/sso/validators.py:27 #, python-format @@ -5858,36 +6050,8 @@ msgstr "Terug naar Ansible Tower" msgid "Resize" msgstr "Groter/kleiner maken" -#: awx/templates/rest_framework/base.html:37 -msgid "navbar" -msgstr "navbar" - -#: awx/templates/rest_framework/base.html:75 -msgid "content" -msgstr "inhoud" - -#: awx/templates/rest_framework/base.html:78 -msgid "request form" -msgstr "aanvraagformulier" - -#: awx/templates/rest_framework/base.html:134 -msgid "Filters" -msgstr "Filters" - -#: awx/templates/rest_framework/base.html:139 -msgid "main content" -msgstr "hoofdinhoud" - -#: awx/templates/rest_framework/base.html:155 -msgid "request info" -msgstr "aanvraaginformatie" - -#: awx/templates/rest_framework/base.html:159 -msgid "response info" -msgstr "reactie-informatie" - -#: awx/ui/apps.py:9 awx/ui/conf.py:22 awx/ui/conf.py:36 awx/ui/conf.py:51 -#: awx/ui/conf.py:63 awx/ui/conf.py:73 +#: awx/ui/apps.py:9 awx/ui/conf.py:22 awx/ui/conf.py:36 awx/ui/conf.py:50 +#: awx/ui/conf.py:61 awx/ui/conf.py:71 msgid "UI" msgstr "Gebruikersinterface" @@ -5904,12 +6068,12 @@ msgid "Detailed" msgstr "Gedetailleerd" #: awx/ui/conf.py:20 -msgid "Analytics Tracking State" -msgstr "Trackingstatus analyses" +msgid "User Analytics Tracking State" +msgstr "Status voor het volgen van gebruikersanalyse" #: awx/ui/conf.py:21 -msgid "Enable or Disable Analytics Tracking." -msgstr "Tacking analyses in- of uitschakelen" +msgid "Enable or Disable User Analytics Tracking." +msgstr "Volgen van gebruikersanalyse in- of uitschakelen." #: awx/ui/conf.py:31 msgid "Custom Login Info" @@ -5918,62 +6082,48 @@ msgstr "Aangepaste aanmeldgegevens" #: awx/ui/conf.py:32 msgid "" "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, as custom HTML or other markup languages are " +"disclaimer) to a text box in the login modal using this setting. Any content " +"added must be in plain text, as custom HTML or other markup languages are " "not supported." -msgstr "" -"U kunt met deze instelling zo nodig specifieke informatie (zoals juridische " -"informatie of een afwijzing) toevoegen aan een tekstvak in de aanmeldmodus. " -"Alle toegevoegde tekst moet in gewone tekst zijn omdat aangepaste HTML of " -"andere opmaaktalen niet worden ondersteund." +msgstr "U kunt met deze instelling zo nodig specifieke informatie (zoals juridische informatie of een afwijzing) toevoegen aan een tekstvak in de aanmeldmodus. Alle toegevoegde tekst moet in gewone tekst zijn omdat aangepaste HTML of andere opmaaktalen niet worden ondersteund." -#: awx/ui/conf.py:46 +#: awx/ui/conf.py:45 msgid "Custom Logo" msgstr "Aangepast logo" -#: awx/ui/conf.py:47 +#: awx/ui/conf.py:46 msgid "" -"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 " +"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." -msgstr "" -"Om een aangepast logo te maken, levert u een bestand dat u zelf maakt. Het " -"aangepaste logo ziet er op zijn best uit als u een .png-bestand met " -"transparante achtergrond gebruikt. De indelingen GIF, PNG en JPEG worden " -"ondersteund." +msgstr "Om een aangepast logo te maken, levert u een bestand dat u zelf maakt. Het aangepaste logo ziet er op zijn best uit als u een .png-bestand met transparante achtergrond gebruikt. De indelingen GIF, PNG en JPEG worden ondersteund." -#: awx/ui/conf.py:60 +#: awx/ui/conf.py:58 msgid "Max Job Events Retrieved by UI" msgstr "Maximumaantal taakgebeurtenissen dat de UI ophaalt" -#: awx/ui/conf.py:61 +#: awx/ui/conf.py:59 msgid "" "Maximum number of job events for the UI to retrieve within a single request." -msgstr "" -"Maximumaantal taakgebeurtenissen dat de UI op kan halen met een enkele " -"aanvraag." +msgstr "Maximumaantal taakgebeurtenissen dat de UI op kan halen met een enkele aanvraag." -#: awx/ui/conf.py:70 +#: awx/ui/conf.py:68 msgid "Enable Live Updates in the UI" msgstr "Live-updates in de UI inschakelen" -#: awx/ui/conf.py:71 +#: awx/ui/conf.py:69 msgid "" "If disabled, the page will not refresh when events are received. Reloading " "the page will be required to get the latest details." -msgstr "" -"Indien dit uitgeschakeld is, wordt de pagina niet ververst wanneer " -"gebeurtenissen binnenkomen. De pagina moet opnieuw geladen worden om de " -"nieuwste informatie op te halen." +msgstr "Indien dit uitgeschakeld is, wordt de pagina niet ververst wanneer gebeurtenissen binnenkomen. De pagina moet opnieuw geladen worden om de nieuwste informatie op te halen." -#: awx/ui/fields.py:29 +#: awx/ui/fields.py:30 msgid "" "Invalid format for custom logo. Must be a data URL with a base64-encoded " "GIF, PNG or JPEG image." -msgstr "" -"Ongeldige indeling voor aangepast logo. Moet een gegevens-URL zijn met een " -"base64-versleutelde GIF-, PNG- of JPEG-afbeelding." +msgstr "Ongeldige indeling voor aangepast logo. Moet een gegevens-URL zijn met een base64-versleutelde GIF-, PNG- of JPEG-afbeelding." -#: awx/ui/fields.py:30 +#: awx/ui/fields.py:31 msgid "Invalid base64-encoded data in data URL." msgstr "Ongeldige base64-versleutelde gegevens in gegevens-URL." + diff --git a/awx/locale/zh/LC_MESSAGES/django.po b/awx/locale/zh/LC_MESSAGES/django.po new file mode 100644 index 000000000000..7827966e1198 --- /dev/null +++ b/awx/locale/zh/LC_MESSAGES/django.po @@ -0,0 +1,6127 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-05-28 21:45+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: zh \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: awx/api/conf.py:15 +msgid "Idle Time Force Log Out" +msgstr "闲置时间强制退出" + +#: awx/api/conf.py:16 +msgid "" +"Number of seconds that a user is inactive before they will need to login " +"again." +msgstr "用户在需要再次登录前处于不活跃状态的秒数。" + +#: awx/api/conf.py:17 awx/api/conf.py:26 awx/api/conf.py:34 awx/api/conf.py:50 +#: awx/api/conf.py:62 awx/api/conf.py:74 awx/sso/conf.py:97 awx/sso/conf.py:108 +#: awx/sso/conf.py:120 awx/sso/conf.py:135 +msgid "Authentication" +msgstr "身份验证" + +#: awx/api/conf.py:24 +msgid "Maximum number of simultaneous logged in sessions" +msgstr "同步登录会话的最大数量" + +#: awx/api/conf.py:25 +msgid "" +"Maximum number of simultaneous logged in sessions a user may have. To " +"disable enter -1." +msgstr "用户可以具有的同步登录会话的最大数量。要禁用,请输入 -1。" + +#: awx/api/conf.py:32 +msgid "Enable HTTP Basic Auth" +msgstr "启用 HTTP 基本身份验证" + +#: awx/api/conf.py:33 +msgid "Enable HTTP Basic Auth for the API Browser." +msgstr "为 API 浏览器启用 HTTP 基本身份验证。" + +#: awx/api/conf.py:43 +msgid "OAuth 2 Timeout Settings" +msgstr "OAuth 2 超时设置" + +#: awx/api/conf.py:44 +msgid "" +"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." +msgstr "自定义 OAuth 2 超时的字典,可用项为 `ACCESS_TOKEN_EXPIRE_SECONDS`(访问令牌的持续时间,单位为秒数),`AUTHORIZATION_CODE_EXPIRE_SECONDS`(授权代码的持续时间,单位为秒数),以及 `REFRESH_TOKEN_EXPIRE_SECONDS`(访问令牌过期后刷新令牌的持续时间,单位为秒数)。" + +#: awx/api/conf.py:57 +msgid "Allow External Users to Create OAuth2 Tokens" +msgstr "允许外部用户创建 OAuth2 令牌" + +#: awx/api/conf.py:58 +msgid "" +"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." +msgstr "出于安全考虑,不允许来自外部验证提供商(LDAP、SAML、SSO、Radius 等)的用户创建 OAuth2 令牌。要更改此行为,请启用此设置。此设置关闭时不会删除现有令牌。" + +#: awx/api/conf.py:71 +msgid "Login redirect override URL" +msgstr "登录重定向覆写 URL" + +#: awx/api/conf.py:72 +msgid "" +"URL to which unauthorized users will be redirected to log in. If blank, " +"users will be sent to the Tower login page." +msgstr "未授权用户重定向到的登录 URL。如果为空,则会将用户转到 Tower 登录页面。" + +#: awx/api/exceptions.py:20 +msgid "Resource is being used by running jobs." +msgstr "运行的作业正在使用资源。" + +#: awx/api/fields.py:81 +#, python-brace-format +msgid "Invalid key names: {invalid_key_names}" +msgstr "无效的密钥名称:{invalid_key_names}" + +#: awx/api/fields.py:111 +msgid "Credential {} does not exist" +msgstr "凭证 {} 不存在" + +#: awx/api/filters.py:82 +msgid "No related model for field {}." +msgstr "字段 {} 没有相关模型。" + +#: awx/api/filters.py:99 +msgid "Filtering on password fields is not allowed." +msgstr "不允许对密码字段进行过滤。" + +#: awx/api/filters.py:111 awx/api/filters.py:113 +#, python-format +msgid "Filtering on %s is not allowed." +msgstr "不允许对 %s 进行过滤。" + +#: awx/api/filters.py:116 +msgid "Loops not allowed in filters, detected on field {}." +msgstr "过滤器中不允许使用循环,在字段 {} 上检测到。" + +#: awx/api/filters.py:160 +msgid "Query string field name not provided." +msgstr "没有提供查询字符串字段名称。" + +#: awx/api/filters.py:192 +#, python-brace-format +msgid "Invalid {field_name} id: {field_id}" +msgstr "无效的 {field_name} ID:{field_id}" + +#: awx/api/filters.py:333 +msgid "" +"Cannot apply role_level filter to this list because its model does not use " +"roles for access control." +msgstr "无法将 role_level 过滤器应用到此列表,因为其模型不使用角色来进行访问控制。" + +#: awx/api/generics.py:182 +msgid "" +"You did not use correct Content-Type in your HTTP request. If you are using " +"our REST API, the Content-Type must be application/json" +msgstr "您没有在 HTTP 请求中使用正确的 Content-Type。如果您使用的是 REST API,Content-Type 必须是 application/json" + +#: awx/api/generics.py:623 awx/api/generics.py:685 +msgid "\"id\" field must be an integer." +msgstr "“id”字段必须是整数。" + +#: awx/api/generics.py:682 +msgid "\"id\" is required to disassociate" +msgstr "需要“id”才能解除关联" + +#: awx/api/generics.py:733 +msgid "{} 'id' field is missing." +msgstr "{} 'id' 字段缺失。" + +#: awx/api/metadata.py:58 +msgid "Database ID for this {}." +msgstr "此 {} 的数据库 ID。" + +#: awx/api/metadata.py:59 +msgid "Name of this {}." +msgstr "此 {} 的名称。" + +#: awx/api/metadata.py:60 +msgid "Optional description of this {}." +msgstr "此 {} 的可选描述。" + +#: awx/api/metadata.py:61 +msgid "Data type for this {}." +msgstr "此 {} 的数据类型。" + +#: awx/api/metadata.py:62 +msgid "URL for this {}." +msgstr "此 {} 的 URL。" + +#: awx/api/metadata.py:63 +msgid "Data structure with URLs of related resources." +msgstr "包含相关资源的 URL 的数据结构。" + +#: awx/api/metadata.py:64 +msgid "" +"Data structure with name/description for related resources. The output for " +"some objects may be limited for performance reasons." +msgstr "相关资源的名称/描述的数据结构。由于性能的原因,一些对象的输出可能会有所限制。" + +#: awx/api/metadata.py:66 +msgid "Timestamp when this {} was created." +msgstr "创建此 {} 时的时间戳。" + +#: awx/api/metadata.py:67 +msgid "Timestamp when this {} was last modified." +msgstr "最后一次修改 {} 时的时间戳。" + +#: awx/api/parsers.py:33 +msgid "JSON parse error - not a JSON object" +msgstr "JSON 解析错误 - 不是 JSON 对象" + +#: awx/api/parsers.py:36 +#, python-format +msgid "" +"JSON parse error - %s\n" +"Possible cause: trailing comma." +msgstr "JSON 解析错误 - %s 可能的原因:结尾逗号。" + +#: awx/api/serializers.py:169 +msgid "" +"The original object is already named {}, a copy from it cannot have the same " +"name." +msgstr "原始对象已经命名为 {},从中复制的对象不能有相同的名称。" + +#: awx/api/serializers.py:302 +#, python-format +msgid "Cannot use dictionary for %s" +msgstr "无法将字典用于 %s" + +#: awx/api/serializers.py:316 +msgid "Playbook Run" +msgstr "Playbook 运行" + +#: awx/api/serializers.py:317 +msgid "Command" +msgstr "命令" + +#: awx/api/serializers.py:318 awx/main/models/unified_jobs.py:547 +msgid "SCM Update" +msgstr "SCM 更新" + +#: awx/api/serializers.py:319 +msgid "Inventory Sync" +msgstr "清单同步" + +#: awx/api/serializers.py:320 +msgid "Management Job" +msgstr "管理作业" + +#: awx/api/serializers.py:321 +msgid "Workflow Job" +msgstr "工作流作业" + +#: awx/api/serializers.py:322 +msgid "Workflow Template" +msgstr "工作流模板" + +#: awx/api/serializers.py:323 +msgid "Job Template" +msgstr "作业模板" + +#: awx/api/serializers.py:709 +msgid "" +"Indicates whether all of the events generated by this unified job have been " +"saved to the database." +msgstr "表明该统一作业生成的所有事件是否已保存到数据库中。" + +#: awx/api/serializers.py:878 +msgid "Write-only field used to change the password." +msgstr "用于更改密码只写字段。" + +#: awx/api/serializers.py:880 +msgid "Set if the account is managed by an external service" +msgstr "设定帐户是否由外部服务管理" + +#: awx/api/serializers.py:907 +msgid "Password required for new User." +msgstr "新用户需要密码。" + +#: awx/api/serializers.py:992 +#, python-format +msgid "Unable to change %s on user managed by LDAP." +msgstr "无法对 LDAP 管理的用户更改 %s。" + +#: awx/api/serializers.py:1088 +msgid "Must be a simple space-separated string with allowed scopes {}." +msgstr "必须是一个使用允许范围 {} 的以空格分隔的简单字符串。" + +#: awx/api/serializers.py:1186 +msgid "Authorization Grant Type" +msgstr "授权授予类型" + +#: awx/api/serializers.py:1188 awx/main/credential_plugins/azure_kv.py:30 +#: awx/main/models/credential/__init__.py:960 +msgid "Client Secret" +msgstr "客户端机密" + +#: awx/api/serializers.py:1191 +msgid "Client Type" +msgstr "客户端类型" + +#: awx/api/serializers.py:1194 +msgid "Redirect URIs" +msgstr "重定向 URI" + +#: awx/api/serializers.py:1197 +msgid "Skip Authorization" +msgstr "跳过授权" + +#: awx/api/serializers.py:1303 +msgid "Cannot change max_hosts." +msgstr "无法更改 max_hosts。" + +#: awx/api/serializers.py:1336 +msgid "This path is already being used by another manual project." +msgstr "此路径已经被另一个手动项目使用。" + +#: awx/api/serializers.py:1338 +msgid "SCM refspec can only be used with git projects." +msgstr "SCM refspec 只能用于 git 项目。" + +#: awx/api/serializers.py:1415 +msgid "" +"One or more job templates depend on branch override behavior for this " +"project (ids: {})." +msgstr "一个或多个作业模板依赖于此项目的分支覆写行为(ids:{})。" + +#: awx/api/serializers.py:1422 +msgid "Update options must be set to false for manual projects." +msgstr "手动项目必须将更新选项设置为 false。" + +#: awx/api/serializers.py:1428 +msgid "Array of playbooks available within this project." +msgstr "此项目中可用的 playbook 数组。" + +#: awx/api/serializers.py:1447 +msgid "" +"Array of inventory files and directories available within this project, not " +"comprehensive." +msgstr "此项目中可用的清单文件和目录数组,不全面。" + +#: awx/api/serializers.py:1495 awx/api/serializers.py:3048 +#: awx/api/serializers.py:3260 +msgid "A count of hosts uniquely assigned to each status." +msgstr "分配给每个状态的唯一主机数量。" + +#: awx/api/serializers.py:1498 awx/api/serializers.py:3051 +msgid "A count of all plays and tasks for the job run." +msgstr "作业运行中的所有 play 和作业数量。" + +#: awx/api/serializers.py:1625 +msgid "Smart inventories must specify host_filter" +msgstr "智能清单必须指定 host_filter" + +#: awx/api/serializers.py:1713 +#, python-format +msgid "Invalid port specification: %s" +msgstr "无效的端口规格:%s" + +#: awx/api/serializers.py:1724 +msgid "Cannot create Host for Smart Inventory" +msgstr "无法为智能清单创建主机" + +#: awx/api/serializers.py:1808 +msgid "Invalid group name." +msgstr "无效的组名称。" + +#: awx/api/serializers.py:1813 +msgid "Cannot create Group for Smart Inventory" +msgstr "无法为智能清单创建组" + +#: awx/api/serializers.py:1888 +msgid "" +"Script must begin with a hashbang sequence: i.e.... #!/usr/bin/env python" +msgstr "脚本必须以 hashbang 序列开头:即... #!/usr/bin/env python" + +#: awx/api/serializers.py:1917 +msgid "Cloud credential to use for inventory updates." +msgstr "用于库存更新的云凭证。" + +#: awx/api/serializers.py:1938 +msgid "`{}` is a prohibited environment variable" +msgstr "`{}` 是禁止的环境变量" + +#: awx/api/serializers.py:1949 +msgid "If 'source' is 'custom', 'source_script' must be provided." +msgstr "如果 'source' 是 'custom',则必须提供 'source_script'。" + +#: awx/api/serializers.py:1955 +msgid "Must provide an inventory." +msgstr "必须提供清单。" + +#: awx/api/serializers.py:1959 +msgid "" +"The 'source_script' does not belong to the same organization as the " +"inventory." +msgstr "'source_script' 与清单不属于同一机构。" + +#: awx/api/serializers.py:1961 +msgid "'source_script' doesn't exist." +msgstr "'source_script' 不存在。" + +#: awx/api/serializers.py:2063 +msgid "Cannot use manual project for SCM-based inventory." +msgstr "无法在基于 SCM 的清单中使用手动项目。" + +#: awx/api/serializers.py:2068 +msgid "Setting not compatible with existing schedules." +msgstr "设置与现有计划不兼容。" + +#: awx/api/serializers.py:2073 +msgid "Cannot create Inventory Source for Smart Inventory" +msgstr "无法为智能清单创建清单源" + +#: awx/api/serializers.py:2121 +msgid "Project required for scm type sources." +msgstr "scm 类型源所需的项目。" + +#: awx/api/serializers.py:2130 +#, python-format +msgid "Cannot set %s if not SCM type." +msgstr "如果不是 SCM 类型,则无法设置 %s。" + +#: awx/api/serializers.py:2200 +msgid "The project used for this job." +msgstr "用于此作业的项目。" + +#: awx/api/serializers.py:2455 +msgid "Modifications not allowed for managed credential types" +msgstr "不允许对受管凭证类型进行修改" + +#: awx/api/serializers.py:2467 +msgid "" +"Modifications to inputs are not allowed for credential types that are in use" +msgstr "对于正在使用的凭证类型,不允许对输入进行修改" + +#: awx/api/serializers.py:2472 +#, python-format +msgid "Must be 'cloud' or 'net', not %s" +msgstr "必须为 'cloud' 或 'net',不能为 %s" + +#: awx/api/serializers.py:2478 +msgid "'ask_at_runtime' is not supported for custom credentials." +msgstr "自定义凭证不支持 'ask_at_runtime'。" + +#: awx/api/serializers.py:2526 +msgid "Credential Type" +msgstr "凭证类型" + +#: awx/api/serializers.py:2607 +msgid "" +"You cannot change the credential type of the credential, as it may break the " +"functionality of the resources using it." +msgstr "您无法更改凭证的凭证类型,因为它可能会破坏使用该凭证的资源的功能。" + +#: awx/api/serializers.py:2619 +msgid "" +"Write-only field used to add user to owner role. If provided, do not give " +"either team or organization. Only valid for creation." +msgstr "用于将用户添加到所有者角色的只写字段。如果提供,则不给出团队或机构。只在创建时有效。" + +#: awx/api/serializers.py:2624 +msgid "" +"Write-only field used to add team to owner role. If provided, do not give " +"either user or organization. Only valid for creation." +msgstr "用于将团队添加到所有者角色的只写字段。如果提供,则不给出用户或机构。只在创建时有效。" + +#: awx/api/serializers.py:2629 +msgid "" +"Inherit permissions from organization roles. If provided on creation, do not " +"give either user or team." +msgstr "从机构角色继承权限。如果在创建时提供,则不给出用户或团队。" + +#: awx/api/serializers.py:2645 +msgid "Missing 'user', 'team', or 'organization'." +msgstr "缺少 'user' 、'team' 或 'organization'。" + +#: awx/api/serializers.py:2662 +msgid "" +"Credential organization must be set and match before assigning to a team" +msgstr "在分配给团队之前,必须设置凭证机构并匹配" + +#: awx/api/serializers.py:2793 +msgid "This field is required." +msgstr "此字段是必需的。" + +#: awx/api/serializers.py:2802 +msgid "Playbook not found for project." +msgstr "未找到用于项目的 playbook。" + +#: awx/api/serializers.py:2804 +msgid "Must select playbook for project." +msgstr "必须为项目选择 playbook。" + +#: awx/api/serializers.py:2806 awx/api/serializers.py:2808 +msgid "Project does not allow overriding branch." +msgstr "项目不允许覆写分支。" + +#: awx/api/serializers.py:2845 +msgid "Must be a Personal Access Token." +msgstr "必须是一个个人访问令牌。" + +#: awx/api/serializers.py:2848 +msgid "Must match the selected webhook service." +msgstr "必须与所选 Webhook 服务匹配。" + +#: awx/api/serializers.py:2919 +msgid "Cannot enable provisioning callback without an inventory set." +msgstr "无法在没有清单集的情况下启用部署回调。" + +#: awx/api/serializers.py:2922 +msgid "Must either set a default value or ask to prompt on launch." +msgstr "必须设置默认值或要求启动时提示。" + +#: awx/api/serializers.py:2924 awx/main/models/jobs.py:299 +msgid "Job Templates must have a project assigned." +msgstr "作业模板必须分配有一个项目。" + +#: awx/api/serializers.py:3092 +msgid "No change to job limit" +msgstr "作业限制没有发生改变" + +#: awx/api/serializers.py:3093 +msgid "All failed and unreachable hosts" +msgstr "所有失败且无法访问的主机" + +#: awx/api/serializers.py:3108 +msgid "Missing passwords needed to start: {}" +msgstr "缺少启动时所需的密码:{}" + +#: awx/api/serializers.py:3127 +msgid "Relaunch by host status not available until job finishes running." +msgstr "在作业结束运行前,按主机状态重新启动不可用。" + +#: awx/api/serializers.py:3141 +msgid "Job Template Project is missing or undefined." +msgstr "作业模板项目缺失或未定义。" + +#: awx/api/serializers.py:3143 +msgid "Job Template Inventory is missing or undefined." +msgstr "作业模板清单缺失或未定义。" + +#: awx/api/serializers.py:3181 +msgid "Unknown, job may have been ran before launch configurations were saved." +msgstr "未知,在保存启动配置前作业可能已经运行。" + +#: awx/api/serializers.py:3252 awx/main/tasks.py:2800 awx/main/tasks.py:2818 +msgid "{} are prohibited from use in ad hoc commands." +msgstr "{} 被禁止在临时命令中使用。" + +#: awx/api/serializers.py:3340 awx/api/views/__init__.py:4243 +#, python-brace-format +msgid "" +"Standard Output too large to display ({text_size} bytes), only download " +"supported for sizes over {supported_size} bytes." +msgstr "标准输出太大,无法显示({text_size} 字节),超过 {supported_size} 字节的大小只支持下载。" + +#: awx/api/serializers.py:3653 +msgid "Provided variable {} has no database value to replace with." +msgstr "提供的变量 {} 没有要替换的数据库值。" + +#: awx/api/serializers.py:3671 +msgid "\"$encrypted$ is a reserved keyword, may not be used for {}.\"" +msgstr "\"$encrypted$ 是一个保留的关键字,可能不能用于 {}\"" + +#: awx/api/serializers.py:4078 +msgid "A project is required to run a job." +msgstr "运行一个作业时需要一个项目。" + +#: awx/api/serializers.py:4080 +msgid "Missing a revision to run due to failed project update." +msgstr "由于项目更新失败,缺少运行的修订版本。" + +#: awx/api/serializers.py:4084 +msgid "The inventory associated with this Job Template is being deleted." +msgstr "与此作业模板关联的清单将被删除。" + +#: awx/api/serializers.py:4086 awx/api/serializers.py:4202 +msgid "The provided inventory is being deleted." +msgstr "提供的清单将被删除。" + +#: awx/api/serializers.py:4094 +msgid "Cannot assign multiple {} credentials." +msgstr "无法分配多个 {} 凭证。" + +#: awx/api/serializers.py:4098 +msgid "Cannot assign a Credential of kind `{}`" +msgstr "无法分配类型为 `{}` 的凭证" + +#: awx/api/serializers.py:4111 +msgid "" +"Removing {} credential at launch time without replacement is not supported. " +"Provided list lacked credential(s): {}." +msgstr "不支持在不替换的情况下在启动时删除 {} 凭证。提供的列表缺少凭证:{}。" + +#: awx/api/serializers.py:4200 +msgid "The inventory associated with this Workflow is being deleted." +msgstr "与此 Workflow 关联的清单将被删除。" + +#: awx/api/serializers.py:4271 +msgid "Message type '{}' invalid, must be either 'message' or 'body'" +msgstr "消息类型 '{}' 无效,必须是 'message' 或 'body'" + +#: awx/api/serializers.py:4277 +msgid "Expected string for '{}', found {}, " +msgstr "'{}' 的预期字符串,找到 {}," + +#: awx/api/serializers.py:4281 +msgid "Messages cannot contain newlines (found newline in {} event)" +msgstr "消息不能包含新行(在 {} 事件中找到新行)" + +#: awx/api/serializers.py:4287 +msgid "Expected dict for 'messages' field, found {}" +msgstr "'messages' 字段的预期字典,找到 {}" + +#: awx/api/serializers.py:4291 +msgid "" +"Event '{}' invalid, must be one of 'started', 'success', 'error', or " +"'workflow_approval'" +msgstr "事件 '{}' 无效,必须是 'started'、'success'、'error' 或 'workflow_approval' 之一" + +#: awx/api/serializers.py:4297 +msgid "Expected dict for event '{}', found {}" +msgstr "事件 '{}' 的预期字典,找到 {}" + +#: awx/api/serializers.py:4302 +msgid "" +"Workflow Approval event '{}' invalid, must be one of 'running', 'approved', " +"'timed_out', or 'denied'" +msgstr "工作流批准事件 '{}' 无效,必须是 'running'、'approved'、'timed_out' 或 'denied' 之一。" + +#: awx/api/serializers.py:4309 +msgid "Expected dict for workflow approval event '{}', found {}" +msgstr "工作流批准事件 '{}' 的预期字典,找到 {}" + +#: awx/api/serializers.py:4336 +msgid "Unable to render message '{}': {}" +msgstr "无法呈现消息 '{}':{}" + +#: awx/api/serializers.py:4338 +msgid "Field '{}' unavailable" +msgstr "字段 '{}' 不可用" + +#: awx/api/serializers.py:4340 +msgid "Security error due to field '{}'" +msgstr "因为字段 '{}' 导致安全错误" + +#: awx/api/serializers.py:4360 +msgid "Webhook body for '{}' should be a json dictionary. Found type '{}'." +msgstr "'{}' 的 Webhook 正文应该是 json 字典。找到类型 '{}'。" + +#: awx/api/serializers.py:4363 +msgid "Webhook body for '{}' is not a valid json dictionary ({})." +msgstr "'{}' 的 Webhook 正文不是有效的 json 字典 ({})。" + +#: awx/api/serializers.py:4381 +msgid "" +"Missing required fields for Notification Configuration: notification_type" +msgstr "通知配置缺少所需字段:notification_type" + +#: awx/api/serializers.py:4408 +msgid "No values specified for field '{}'" +msgstr "没有为字段 '{}' 指定值" + +#: awx/api/serializers.py:4413 +msgid "HTTP method must be either 'POST' or 'PUT'." +msgstr "HTTP 方法必须是 'POST' 或 'PUT'。" + +#: awx/api/serializers.py:4415 +msgid "Missing required fields for Notification Configuration: {}." +msgstr "通知配置缺少所需字段:{}。" + +#: awx/api/serializers.py:4418 +msgid "Configuration field '{}' incorrect type, expected {}." +msgstr "配置字段 '{}' 类型错误,预期为 {}。" + +#: awx/api/serializers.py:4435 +msgid "Notification body" +msgstr "通知正文" + +#: awx/api/serializers.py:4515 +msgid "" +"Valid DTSTART required in rrule. Value should start with: DTSTART:" +"YYYYMMDDTHHMMSSZ" +msgstr "rrule 中需要有效的 DTSTART。值应该以 DTSTART:YYYMMDDTHHMMSSZ 开头" + +#: awx/api/serializers.py:4517 +msgid "" +"DTSTART cannot be a naive datetime. Specify ;TZINFO= or YYYYMMDDTHHMMSSZZ." +msgstr "DTSTART 不能是一个不带时区的日期时间。指定 ;TZINFO= 或 YYYMMDDTHHMMSSZ。" + +#: awx/api/serializers.py:4519 +msgid "Multiple DTSTART is not supported." +msgstr "不支持多个 DTSTART。" + +#: awx/api/serializers.py:4521 +msgid "RRULE required in rrule." +msgstr "rrule 中需要 RRULE。" + +#: awx/api/serializers.py:4523 +msgid "Multiple RRULE is not supported." +msgstr "不支持多个 RRULE。" + +#: awx/api/serializers.py:4525 +msgid "INTERVAL required in rrule." +msgstr "rrule 需要 INTERVAL。" + +#: awx/api/serializers.py:4527 +msgid "SECONDLY is not supported." +msgstr "不支持 SECONDLY。" + +#: awx/api/serializers.py:4529 +msgid "Multiple BYMONTHDAYs not supported." +msgstr "不支持多个 BYMONTHDAY。" + +#: awx/api/serializers.py:4531 +msgid "Multiple BYMONTHs not supported." +msgstr "不支持多个 BYMONTH。" + +#: awx/api/serializers.py:4533 +msgid "BYDAY with numeric prefix not supported." +msgstr "不支持带有数字前缀的 BYDAY。" + +#: awx/api/serializers.py:4535 +msgid "BYYEARDAY not supported." +msgstr "不支持 BYYEARDAY。" + +#: awx/api/serializers.py:4537 +msgid "BYWEEKNO not supported." +msgstr "不支持 BYWEEKNO。" + +#: awx/api/serializers.py:4539 +msgid "RRULE may not contain both COUNT and UNTIL" +msgstr "RRULE 可能不包含 COUNT 和 UNTIL" + +#: awx/api/serializers.py:4543 +msgid "COUNT > 999 is unsupported." +msgstr "不支持 COUNT > 999。" + +#: awx/api/serializers.py:4549 +msgid "rrule parsing failed validation: {}" +msgstr "rrule 解析失败验证:{}" + +#: awx/api/serializers.py:4611 +msgid "Inventory Source must be a cloud resource." +msgstr "清单源必须是云资源。" + +#: awx/api/serializers.py:4613 +msgid "Manual Project cannot have a schedule set." +msgstr "手动项目不能有计划集。" + +#: awx/api/serializers.py:4616 +msgid "" +"Inventory sources with `update_on_project_update` cannot be scheduled. " +"Schedule its source project `{}` instead." +msgstr "无法调度带有 `update_on_project_update` 的清单源。改为调度其源项目 `{}`。" + +#: awx/api/serializers.py:4626 +msgid "" +"Count of jobs in the running or waiting state that are targeted for this " +"instance" +msgstr "处于运行状态或等待状态的针对此实例的作业计数" + +#: awx/api/serializers.py:4631 +msgid "Count of all jobs that target this instance" +msgstr "所有针对此实例的作业计数" + +#: awx/api/serializers.py:4664 +msgid "" +"Count of jobs in the running or waiting state that are targeted for this " +"instance group" +msgstr "处于运行状态或等待状态的针对此实例组的作业计数" + +#: awx/api/serializers.py:4669 +msgid "Count of all jobs that target this instance group" +msgstr "所有针对此实例组的作业计数" + +#: awx/api/serializers.py:4674 +msgid "Indicates whether instance group controls any other group" +msgstr "指明实例组是否控制任何其他组" + +#: awx/api/serializers.py:4678 +msgid "" +"Indicates whether instances in this group are isolated.Isolated groups have " +"a designated controller group." +msgstr "指明此组中的实例是否被隔离。隔离的组具有指定的控制器组。" + +#: awx/api/serializers.py:4683 +msgid "" +"Indicates whether instances in this group are containerized.Containerized " +"groups have a designated Openshift or Kubernetes cluster." +msgstr "指明此组中的实例是否容器化。容器化的组具有指定的 Openshift 或 Kubernetes 集群。" + +#: awx/api/serializers.py:4691 +msgid "Policy Instance Percentage" +msgstr "策略实例百分比" + +#: awx/api/serializers.py:4692 +msgid "" +"Minimum percentage of all instances that will be automatically assigned to " +"this group when new instances come online." +msgstr "新实例上线时将自动分配给此组的所有实例的最小百分比。" + +#: awx/api/serializers.py:4697 +msgid "Policy Instance Minimum" +msgstr "策略实例最小值" + +#: awx/api/serializers.py:4698 +msgid "" +"Static minimum number of Instances that will be automatically assign to this " +"group when new instances come online." +msgstr "新实例上线时自动分配给此组的静态最小实例数量。" + +#: awx/api/serializers.py:4703 +msgid "Policy Instance List" +msgstr "策略实例列表" + +#: awx/api/serializers.py:4704 +msgid "List of exact-match Instances that will be assigned to this group" +msgstr "将分配给此组的完全匹配实例的列表" + +#: awx/api/serializers.py:4730 +msgid "Duplicate entry {}." +msgstr "重复条目 {}。" + +#: awx/api/serializers.py:4732 +msgid "{} is not a valid hostname of an existing instance." +msgstr "{} 不是现有实例的有效主机名。" + +#: awx/api/serializers.py:4734 awx/api/views/mixin.py:98 +msgid "" +"Isolated instances may not be added or removed from instances groups via the " +"API." +msgstr "可能无法通过 API 为实例组添加或删除隔离的实例。" + +#: awx/api/serializers.py:4736 awx/api/views/mixin.py:102 +msgid "Isolated instance group membership may not be managed via the API." +msgstr "可能无法通过 API 管理隔离的实例组成员资格。" + +#: awx/api/serializers.py:4738 awx/api/serializers.py:4743 +#: awx/api/serializers.py:4748 +msgid "Containerized instances may not be managed via the API" +msgstr "可能无法通过 API 管理容器化实例" + +#: awx/api/serializers.py:4753 +msgid "tower instance group name may not be changed." +msgstr "可能不会更改 tower 实例组名称。" + +#: awx/api/serializers.py:4758 +msgid "Only Kubernetes credentials can be associated with an Instance Group" +msgstr "只有 Kubernetes 凭证可以与实例组关联" + +#: awx/api/serializers.py:4797 +msgid "" +"When present, shows the field name of the role or relationship that changed." +msgstr "存在时,显示更改的角色或关系的字段名称。" + +#: awx/api/serializers.py:4799 +msgid "" +"When present, shows the model on which the role or relationship was defined." +msgstr "存在时,显示定义角色或关系的模型。" + +#: awx/api/serializers.py:4832 +msgid "" +"A summary of the new and changed values when an object is created, updated, " +"or deleted" +msgstr "创建、更新或删除对象时新值和更改值的概述" + +#: awx/api/serializers.py:4834 +msgid "" +"For create, update, and delete events this is the object type that was " +"affected. For associate and disassociate events this is the object type " +"associated or disassociated with object2." +msgstr "对于创建、更新和删除事件,这是受影响的对象类型。对于关联和解除关联事件,这是与对象 2 关联或解除关联的对象类型。" + +#: awx/api/serializers.py:4837 +msgid "" +"Unpopulated for create, update, and delete events. For associate and " +"disassociate events this is the object type that object1 is being associated " +"with." +msgstr "创建、更新和删除事件未填充。对于关联和解除关联事件,这是对象 1 要关联的对象类型。" + +#: awx/api/serializers.py:4840 +msgid "The action taken with respect to the given object(s)." +msgstr "对给定对象执行的操作。" + +#: awx/api/views/__init__.py:181 +msgid "Dashboard" +msgstr "仪表板" + +#: awx/api/views/__init__.py:271 +msgid "Dashboard Jobs Graphs" +msgstr "仪表板作业图形" + +#: awx/api/views/__init__.py:307 +#, python-format +msgid "Unknown period \"%s\"" +msgstr "未知时期 \"%s\"" + +#: awx/api/views/__init__.py:321 +msgid "Instances" +msgstr "实例" + +#: awx/api/views/__init__.py:329 +msgid "Instance Detail" +msgstr "实例详情" + +#: awx/api/views/__init__.py:346 +msgid "Instance Jobs" +msgstr "实例作业" + +#: awx/api/views/__init__.py:360 +msgid "Instance's Instance Groups" +msgstr "实例的实例组" + +#: awx/api/views/__init__.py:369 +msgid "Instance Groups" +msgstr "实例组" + +#: awx/api/views/__init__.py:377 +msgid "Instance Group Detail" +msgstr "实例组详情" + +#: awx/api/views/__init__.py:392 +msgid "Isolated Groups can not be removed from the API" +msgstr "无法从 API 中删除隔离的组" + +#: awx/api/views/__init__.py:394 +msgid "" +"Instance Groups acting as a controller for an Isolated Group can not be " +"removed from the API" +msgstr "无法从 API 中删除作为隔离组控制器的实例组" + +#: awx/api/views/__init__.py:400 +msgid "Instance Group Running Jobs" +msgstr "实例组的运行作业" + +#: awx/api/views/__init__.py:409 +msgid "Instance Group's Instances" +msgstr "实例组的实例" + +#: awx/api/views/__init__.py:419 +msgid "Schedules" +msgstr "计划" + +#: awx/api/views/__init__.py:433 +msgid "Schedule Recurrence Rule Preview" +msgstr "计划重复规则预览" + +#: awx/api/views/__init__.py:480 +msgid "Cannot assign credential when related template is null." +msgstr "当相关模板为 null 时无法分配凭证。" + +#: awx/api/views/__init__.py:485 +msgid "Related template cannot accept {} on launch." +msgstr "相关的模板无法在启动时接受 {}。" + +#: awx/api/views/__init__.py:487 +msgid "" +"Credential that requires user input on launch cannot be used in saved launch " +"configuration." +msgstr "在启动时需要用户输入的凭证不能用于保存的启动配置。" + +#: awx/api/views/__init__.py:493 +msgid "Related template is not configured to accept credentials on launch." +msgstr "相关的模板未配置为在启动时接受凭证。" + +#: awx/api/views/__init__.py:495 +#, python-brace-format +msgid "" +"This launch configuration already provides a {credential_type} credential." +msgstr "此启动配置已经提供了 {credential_type} 凭证。" + +#: awx/api/views/__init__.py:498 +#, python-brace-format +msgid "Related template already uses {credential_type} credential." +msgstr "相关的模板已使用 {credential_type} 凭证。" + +#: awx/api/views/__init__.py:516 +msgid "Schedule Jobs List" +msgstr "调度作业列表" + +#: awx/api/views/__init__.py:600 awx/api/views/__init__.py:4452 +msgid "" +"You cannot assign an Organization participation role as a child role for a " +"Team." +msgstr "您不能分配机构参与角色作为团队的子角色。" + +#: awx/api/views/__init__.py:604 awx/api/views/__init__.py:4466 +msgid "You cannot grant system-level permissions to a team." +msgstr "您不能为团队授予系统级别权限。" + +#: awx/api/views/__init__.py:611 awx/api/views/__init__.py:4458 +msgid "" +"You cannot grant credential access to a team when the Organization field " +"isn't set, or belongs to a different organization" +msgstr "您不能在机构字段未设置或属于不同机构时为团队授予凭证访问权限" + +#: awx/api/views/__init__.py:713 +msgid "Project Schedules" +msgstr "项目计划" + +#: awx/api/views/__init__.py:724 +msgid "Project SCM Inventory Sources" +msgstr "项目 SCM 清单源" + +#: awx/api/views/__init__.py:825 +msgid "Project Update Events List" +msgstr "项目更新事件列表" + +#: awx/api/views/__init__.py:839 +msgid "System Job Events List" +msgstr "系统作业事件列表" + +#: awx/api/views/__init__.py:873 +msgid "Project Update SCM Inventory Updates" +msgstr "项目更新 SCM 清单更新" + +#: awx/api/views/__init__.py:918 +msgid "Me" +msgstr "我" + +#: awx/api/views/__init__.py:927 +msgid "OAuth 2 Applications" +msgstr "OAuth 2 应用" + +#: awx/api/views/__init__.py:936 +msgid "OAuth 2 Application Detail" +msgstr "OAuth 2 应用详情" + +#: awx/api/views/__init__.py:949 +msgid "OAuth 2 Application Tokens" +msgstr "OAuth 2 应用令牌" + +#: awx/api/views/__init__.py:971 +msgid "OAuth2 Tokens" +msgstr "OAuth2 令牌" + +#: awx/api/views/__init__.py:980 +msgid "OAuth2 User Tokens" +msgstr "OAuth2 用户令牌" + +#: awx/api/views/__init__.py:992 +msgid "OAuth2 User Authorized Access Tokens" +msgstr "OAuth2 用户授权访问令牌" + +#: awx/api/views/__init__.py:1007 +msgid "Organization OAuth2 Applications" +msgstr "机构 OAuth2 应用" + +#: awx/api/views/__init__.py:1019 +msgid "OAuth2 Personal Access Tokens" +msgstr "OAuth2 个人访问令牌" + +#: awx/api/views/__init__.py:1034 +msgid "OAuth Token Detail" +msgstr "OAuth 令牌详情" + +#: awx/api/views/__init__.py:1096 awx/api/views/__init__.py:4419 +msgid "" +"You cannot grant credential access to a user not in the credentials' " +"organization" +msgstr "您不能为不在凭证机构中的用户授予凭证访问权限" + +#: awx/api/views/__init__.py:1100 awx/api/views/__init__.py:4423 +msgid "You cannot grant private credential access to another user" +msgstr "您不能为其他用户授予私有凭证访问权限" + +#: awx/api/views/__init__.py:1198 +#, python-format +msgid "Cannot change %s." +msgstr "无法更改 %s。" + +#: awx/api/views/__init__.py:1204 +msgid "Cannot delete user." +msgstr "无法删除用户。" + +#: awx/api/views/__init__.py:1228 +msgid "Deletion not allowed for managed credential types" +msgstr "不允许删除受管凭证类型" + +#: awx/api/views/__init__.py:1230 +msgid "Credential types that are in use cannot be deleted" +msgstr "无法删除正在使用中的凭证类型" + +#: awx/api/views/__init__.py:1381 +msgid "External Credential Test" +msgstr "外部凭证测试" + +#: awx/api/views/__init__.py:1408 +msgid "Credential Input Source Detail" +msgstr "凭证输入源详情" + +#: awx/api/views/__init__.py:1416 awx/api/views/__init__.py:1424 +msgid "Credential Input Sources" +msgstr "凭证输入源" + +#: awx/api/views/__init__.py:1439 +msgid "External Credential Type Test" +msgstr "外部凭证类型测试" + +#: awx/api/views/__init__.py:1497 +msgid "The inventory for this host is already being deleted." +msgstr "此主机的清单已经被删除。" + +#: awx/api/views/__init__.py:1614 +msgid "SSLError while trying to connect to {}" +msgstr "尝试连接到 {} 时出现 SSLError" + +#: awx/api/views/__init__.py:1616 +msgid "Request to {} timed out." +msgstr "请求 {} 超时。" + +#: awx/api/views/__init__.py:1618 +msgid "Unknown exception {} while trying to GET {}" +msgstr "尝试 GET {} 时出现未知异常 {}" + +#: awx/api/views/__init__.py:1622 +msgid "" +"Unauthorized access. Please check your Insights Credential username and " +"password." +msgstr "未授权访问。请检查您的 Insights 用户名和密码。" + +#: awx/api/views/__init__.py:1626 +msgid "" +"Failed to access the Insights API at URL {}. Server responded with {} status " +"code and message {}" +msgstr "无法通过 URL {} 访问 Insights API。服务器以 {} 状态代码和消息 {} 作为响应" + +#: awx/api/views/__init__.py:1635 +msgid "Expected JSON response from Insights at URL {} but instead got {}" +msgstr "访问 URL {} 时来自 Insights 的预期 JSON 响应,但实际为 {}" + +#: awx/api/views/__init__.py:1653 +msgid "Could not translate Insights system ID {} into an Insights platform ID." +msgstr "无法将 Insights 系统 ID {} 转换为 Insights 平台 ID。" + +#: awx/api/views/__init__.py:1695 +msgid "This host is not recognized as an Insights host." +msgstr "此主机不被识别为 Insights 主机。" + +#: awx/api/views/__init__.py:1703 +msgid "The Insights Credential for \"{}\" was not found." +msgstr "未找到 \"{}\" 的 Insights 凭证。" + +#: awx/api/views/__init__.py:1782 +msgid "Cyclical Group association." +msgstr "周期性组关联。" + +#: awx/api/views/__init__.py:1948 +msgid "Inventory subset argument must be a string." +msgstr "清单子集参数必须是字符串。" + +#: awx/api/views/__init__.py:1952 +msgid "Subset does not use any supported syntax." +msgstr "子集未使用任何支持的语法。" + +#: awx/api/views/__init__.py:2002 +msgid "Inventory Source List" +msgstr "清单源列表" + +#: awx/api/views/__init__.py:2014 +msgid "Inventory Sources Update" +msgstr "清单源更新" + +#: awx/api/views/__init__.py:2047 +msgid "Could not start because `can_update` returned False" +msgstr "无法启动,因为 `can_update` 返回 False" + +#: awx/api/views/__init__.py:2055 +msgid "No inventory sources to update." +msgstr "没有需要更新的清单源。" + +#: awx/api/views/__init__.py:2077 +msgid "Inventory Source Schedules" +msgstr "清单源计划" + +#: awx/api/views/__init__.py:2104 +msgid "Notification Templates can only be assigned when source is one of {}." +msgstr "只有源是 {} 之一时才能分配通知模板。" + +#: awx/api/views/__init__.py:2202 +msgid "Source already has credential assigned." +msgstr "源已经分配有凭证。" + +#: awx/api/views/__init__.py:2350 +msgid "'credentials' cannot be used in combination with 'extra_credentials'." +msgstr "'credentials' 无法与 'extra_credentials' 结合使用。" + +#: awx/api/views/__init__.py:2368 +msgid "Incorrect type. Expected a list received {}." +msgstr "类型不正确。预期为列表,收到的是 {}。" + +#: awx/api/views/__init__.py:2466 +msgid "Job Template Schedules" +msgstr "作业模板计划" + +#: awx/api/views/__init__.py:2515 +msgid "Field '{}' is missing from survey spec." +msgstr "问卷调查规格中缺少字段 '{}'。" + +#: awx/api/views/__init__.py:2517 +msgid "Expected {} for field '{}', received {} type." +msgstr "字段 '{}' 预期为 {},收到的是 {} 类型。" + +#: awx/api/views/__init__.py:2521 +msgid "'spec' doesn't contain any items." +msgstr "'spec' 不包含任何项。" + +#: awx/api/views/__init__.py:2535 +#, python-format +msgid "Survey question %s is not a json object." +msgstr "问卷调查问题 %s 不是 json 对象。" + +#: awx/api/views/__init__.py:2538 +#, python-brace-format +msgid "'{field_name}' missing from survey question {idx}" +msgstr "问卷调查问题 {idx} 中缺少 '{field_name}'" + +#: awx/api/views/__init__.py:2548 +#, python-brace-format +msgid "'{field_name}' in survey question {idx} expected to be {type_label}." +msgstr "问卷调查问题 {idx} 中的 '{field_name}' 预期为 {type_label}。" + +#: awx/api/views/__init__.py:2552 +#, python-format +msgid "'variable' '%(item)s' duplicated in survey question %(survey)s." +msgstr "问卷调查问题 %(survey)s 中的 'variable' '%(item)s' 重复。" + +#: awx/api/views/__init__.py:2562 +#, python-brace-format +msgid "" +"'{survey_item[type]}' in survey question {idx} is not one of " +"'{allowed_types}' allowed question types." +msgstr "问卷调查问题 {idx} 中的 '{survey_item[type]}' 不是 '{allowed_types}' 允许的问题类型之一。" + +#: awx/api/views/__init__.py:2572 +#, python-brace-format +msgid "" +"Default value {survey_item[default]} in survey question {idx} expected to be " +"{type_label}." +msgstr "问卷调查问题 {idx} 中的默认值 {survey_item[default]} 预期为 {type_label}。" + +#: awx/api/views/__init__.py:2582 +#, python-brace-format +msgid "The {min_or_max} limit in survey question {idx} expected to be integer." +msgstr "问卷调查问题 {idx} 中的 {min_or_max} 限制预期为整数。" + +#: awx/api/views/__init__.py:2592 +#, python-brace-format +msgid "Survey question {idx} of type {survey_item[type]} must specify choices." +msgstr "类型为 {survey_item[type]} 的问卷调查问题 {idx} 必须指定选择。" + +#: awx/api/views/__init__.py:2606 +msgid "Multiple Choice (Single Select) can only have one default value." +msgstr "多项选择(单选)只能有一个默认值。" + +#: awx/api/views/__init__.py:2610 +msgid "Default choice must be answered from the choices listed." +msgstr "默认的选择必须从列出的选择中回答。" + +#: awx/api/views/__init__.py:2619 +#, python-brace-format +msgid "" +"$encrypted$ is a reserved keyword for password question defaults, survey " +"question {idx} is type {survey_item[type]}." +msgstr "$encrypted$ 是密码问题默认值的保留关键字,问卷调查问题 {idx} 的类型是 {survey_item[type]}。" + +#: awx/api/views/__init__.py:2633 +#, python-brace-format +msgid "" +"$encrypted$ is a reserved keyword, may not be used for new default in " +"position {idx}." +msgstr "$encrypted$ 是一个保留关键字,可能无法用于位置 {idx} 中的新默认值。" + +#: awx/api/views/__init__.py:2705 +#, python-brace-format +msgid "Cannot assign multiple {credential_type} credentials." +msgstr "无法分配多个 {credential_type} 凭证。" + +#: awx/api/views/__init__.py:2709 +msgid "Cannot assign a Credential of kind `{}`." +msgstr "无法分配种类为 `{}` 的凭证。" + +#: awx/api/views/__init__.py:2726 +msgid "Extra credentials must be network or cloud." +msgstr "额外凭证必须是网络或云。" + +#: awx/api/views/__init__.py:2748 +msgid "Maximum number of labels for {} reached." +msgstr "已达到 {} 的最大标签数。" + +#: awx/api/views/__init__.py:2871 +msgid "No matching host could be found!" +msgstr "无法找到匹配的主机!" + +#: awx/api/views/__init__.py:2874 +msgid "Multiple hosts matched the request!" +msgstr "多个主机与请求匹配!" + +#: awx/api/views/__init__.py:2879 +msgid "Cannot start automatically, user input required!" +msgstr "无法自动启动,需要用户输入!" + +#: awx/api/views/__init__.py:2887 +msgid "Host callback job already pending." +msgstr "主机回调作业已经待处理。" + +#: awx/api/views/__init__.py:2903 awx/api/views/__init__.py:3664 +msgid "Error starting job!" +msgstr "启动作业出错!" + +#: awx/api/views/__init__.py:3027 awx/api/views/__init__.py:3047 +msgid "Cycle detected." +msgstr "检测到循环。" + +#: awx/api/views/__init__.py:3039 +msgid "Relationship not allowed." +msgstr "不允许使用关系。" + +#: awx/api/views/__init__.py:3268 +msgid "Cannot relaunch slice workflow job orphaned from job template." +msgstr "无法重新启动从作业模板中孤立的分片工作流作业。" + +#: awx/api/views/__init__.py:3270 +msgid "Cannot relaunch sliced workflow job after slice count has changed." +msgstr "分片计数发生变化后无法重新启动分片工作流作业。" + +#: awx/api/views/__init__.py:3303 +msgid "Workflow Job Template Schedules" +msgstr "工作流作业模板计划" + +#: awx/api/views/__init__.py:3446 awx/api/views/__init__.py:4087 +msgid "Superuser privileges needed." +msgstr "需要超级用户权限。" + +#: awx/api/views/__init__.py:3479 +msgid "System Job Template Schedules" +msgstr "系统作业模板计划" + +#: awx/api/views/__init__.py:3647 +#, python-brace-format +msgid "Wait until job finishes before retrying on {status_value} hosts." +msgstr "在 {status_value} 主机上重试前等待作业完成。" + +#: awx/api/views/__init__.py:3652 +#, python-brace-format +msgid "Cannot retry on {status_value} hosts, playbook stats not available." +msgstr "无法在 {status_value} 主机上重试,playbook 统计数据不可用。" + +#: awx/api/views/__init__.py:3657 +#, python-brace-format +msgid "Cannot relaunch because previous job had 0 {status_value} hosts." +msgstr "无法重新启动,因为以前的作业有 0 个 {status_value} 主机。" + +#: awx/api/views/__init__.py:3686 +msgid "Cannot create schedule because job requires credential passwords." +msgstr "无法创建计划,因为作业需要凭证密码。" + +#: awx/api/views/__init__.py:3691 +msgid "Cannot create schedule because job was launched by legacy method." +msgstr "无法创建计划,因为作业是由旧方法启动的。" + +#: awx/api/views/__init__.py:3693 +msgid "Cannot create schedule because a related resource is missing." +msgstr "无法创建计划,因为缺少相关资源。" + +#: awx/api/views/__init__.py:3748 +msgid "Job Host Summaries List" +msgstr "作业主机摘要列表" + +#: awx/api/views/__init__.py:3802 +msgid "Job Event Children List" +msgstr "作业事件子级列表" + +#: awx/api/views/__init__.py:3818 +msgid "Job Event Hosts List" +msgstr "作业事件主机列表" + +#: awx/api/views/__init__.py:3833 +msgid "Job Events List" +msgstr "作业事件列表" + +#: awx/api/views/__init__.py:4044 +msgid "Ad Hoc Command Events List" +msgstr "临时命令事件列表" + +#: awx/api/views/__init__.py:4289 +msgid "Delete not allowed while there are pending notifications" +msgstr "在有待处理的通知时不允许删除" + +#: awx/api/views/__init__.py:4297 +msgid "Notification Template Test" +msgstr "通知模板测试" + +#: awx/api/views/__init__.py:4557 awx/api/views/__init__.py:4572 +msgid "User does not have permission to approve or deny this workflow." +msgstr "用户没有批准或拒绝此工作流的权限。" + +#: awx/api/views/__init__.py:4559 awx/api/views/__init__.py:4574 +msgid "This workflow step has already been approved or denied." +msgstr "此工作流步骤已经被批准或拒绝。" + +#: awx/api/views/inventory.py:63 +msgid "Inventory Update Events List" +msgstr "清单更新事件列表" + +#: awx/api/views/inventory.py:90 +msgid "Cannot delete inventory script." +msgstr "无法删除清单脚本。" + +#: awx/api/views/inventory.py:149 +#, python-brace-format +msgid "{0}" +msgstr "{0}" + +#: awx/api/views/metrics.py:30 +msgid "Metrics" +msgstr "指标" + +#: awx/api/views/mixin.py:46 +msgid "Cannot delete job resource when associated workflow job is running." +msgstr "关联的工作流作业正在运行时无法删除作业资源。" + +#: awx/api/views/mixin.py:51 +msgid "Cannot delete running job resource." +msgstr "无法删除正在运行的作业资源。" + +#: awx/api/views/mixin.py:56 +msgid "Job has not finished processing events." +msgstr "作业还没有完成处理事件。" + +#: awx/api/views/mixin.py:153 +msgid "Related job {} is still processing events." +msgstr "相关的作业 {} 仍在处理事件。" + +#: awx/api/views/root.py:49 awx/templates/rest_framework/api.html:28 +msgid "REST API" +msgstr "REST API" + +#: awx/api/views/root.py:59 awx/templates/rest_framework/api.html:4 +msgid "AWX REST API" +msgstr "AWX REST API" + +#: awx/api/views/root.py:72 +msgid "API OAuth 2 Authorization Root" +msgstr "API OAuth 2 授权根" + +#: awx/api/views/root.py:139 +msgid "Version 2" +msgstr "版本 2" + +#: awx/api/views/root.py:148 +msgid "Ping" +msgstr "Ping" + +#: awx/api/views/root.py:180 awx/api/views/root.py:225 awx/conf/apps.py:10 +msgid "Configuration" +msgstr "配置" + +#: awx/api/views/root.py:202 awx/api/views/root.py:308 +msgid "Invalid License" +msgstr "无效许可证" + +#: awx/api/views/root.py:207 +msgid "The provided credentials are invalid (HTTP 401)." +msgstr "提供的凭证无效 (HTTP 401)。" + +#: awx/api/views/root.py:209 +msgid "Unable to connect to proxy server." +msgstr "无法连接到代理服务器。" + +#: awx/api/views/root.py:211 +msgid "Could not connect to subscription service." +msgstr "无法连接订阅服务。" + +#: awx/api/views/root.py:284 +msgid "Invalid license data" +msgstr "无效的许可证数据" + +#: awx/api/views/root.py:286 +msgid "Missing 'eula_accepted' property" +msgstr "缺少 'eula_accepted' 属性" + +#: awx/api/views/root.py:290 +msgid "'eula_accepted' value is invalid" +msgstr "'eula_accepted' 值无效" + +#: awx/api/views/root.py:293 +msgid "'eula_accepted' must be True" +msgstr "'eula_accepted' 必须为 True" + +#: awx/api/views/root.py:300 +msgid "Invalid JSON" +msgstr "无效的 JSON" + +#: awx/api/views/root.py:319 +msgid "Invalid license" +msgstr "无效的许可证" + +#: awx/api/views/root.py:327 +msgid "Failed to remove license." +msgstr "删除许可证失败。" + +#: awx/api/views/webhooks.py:143 +msgid "Webhook previously received, aborting." +msgstr "之前已收到 Webhook,正在中止。" + +#: awx/conf/conf.py:20 +msgid "Bud Frogs" +msgstr "Bud Frogs" + +#: awx/conf/conf.py:21 +msgid "Bunny" +msgstr "Bunny" + +#: awx/conf/conf.py:22 +msgid "Cheese" +msgstr "Cheese" + +#: awx/conf/conf.py:23 +msgid "Daemon" +msgstr "Daemon" + +#: awx/conf/conf.py:24 +msgid "Default Cow" +msgstr "Default Cow" + +#: awx/conf/conf.py:25 +msgid "Dragon" +msgstr "Dragon" + +#: awx/conf/conf.py:26 +msgid "Elephant in Snake" +msgstr "Elephant in Snake" + +#: awx/conf/conf.py:27 +msgid "Elephant" +msgstr "Elephant" + +#: awx/conf/conf.py:28 +msgid "Eyes" +msgstr "Eyes" + +#: awx/conf/conf.py:29 +msgid "Hello Kitty" +msgstr "Hello Kitty" + +#: awx/conf/conf.py:30 +msgid "Kitty" +msgstr "Kitty" + +#: awx/conf/conf.py:31 +msgid "Luke Koala" +msgstr "Luke Koala" + +#: awx/conf/conf.py:32 +msgid "Meow" +msgstr "Meow" + +#: awx/conf/conf.py:33 +msgid "Milk" +msgstr "Milk" + +#: awx/conf/conf.py:34 +msgid "Moofasa" +msgstr "Moofasa" + +#: awx/conf/conf.py:35 +msgid "Moose" +msgstr "Moose" + +#: awx/conf/conf.py:36 +msgid "Ren" +msgstr "Ren" + +#: awx/conf/conf.py:37 +msgid "Sheep" +msgstr "Sheep" + +#: awx/conf/conf.py:38 +msgid "Small Cow" +msgstr "Small Cow" + +#: awx/conf/conf.py:39 +msgid "Stegosaurus" +msgstr "Stegosaurus" + +#: awx/conf/conf.py:40 +msgid "Stimpy" +msgstr "Stimpy" + +#: awx/conf/conf.py:41 +msgid "Super Milker" +msgstr "Super Milker" + +#: awx/conf/conf.py:42 +msgid "Three Eyes" +msgstr "Three Eyes" + +#: awx/conf/conf.py:43 +msgid "Turkey" +msgstr "Turkey" + +#: awx/conf/conf.py:44 +msgid "Turtle" +msgstr "Turtle" + +#: awx/conf/conf.py:45 +msgid "Tux" +msgstr "Tux" + +#: awx/conf/conf.py:46 +msgid "Udder" +msgstr "Udder" + +#: awx/conf/conf.py:47 +msgid "Vader Koala" +msgstr "Vader Koala" + +#: awx/conf/conf.py:48 +msgid "Vader" +msgstr "Vader" + +#: awx/conf/conf.py:49 +msgid "WWW" +msgstr "WWW" + +#: awx/conf/conf.py:52 +msgid "Cow Selection" +msgstr "Cow 选择" + +#: awx/conf/conf.py:53 +msgid "Select which cow to use with cowsay when running jobs." +msgstr "选择在运行作业时要用于 cowsay 的 cow。" + +#: awx/conf/conf.py:54 awx/conf/conf.py:75 +msgid "Cows" +msgstr "Cow" + +#: awx/conf/conf.py:73 +msgid "Example Read-Only Setting" +msgstr "只读设置示例" + +#: awx/conf/conf.py:74 +msgid "Example setting that cannot be changed." +msgstr "无法更改的设置示例。" + +#: awx/conf/conf.py:90 +msgid "Example Setting" +msgstr "设置示例" + +#: awx/conf/conf.py:91 +msgid "Example setting which can be different for each user." +msgstr "每个用户之间可以各不相同的设置示例。" + +#: awx/conf/conf.py:92 awx/conf/registry.py:81 awx/conf/views.py:56 +msgid "User" +msgstr "用户" + +#: awx/conf/fields.py:63 awx/sso/fields.py:595 +#, python-brace-format +msgid "" +"Expected None, True, False, a string or list of strings but got {input_type} " +"instead." +msgstr "预期为 None、True、False、字符串或字符串列表,但实际为 {input_type}。" + +#: awx/conf/fields.py:104 +#, python-brace-format +msgid "Expected list of strings but got {input_type} instead." +msgstr "预期为字符串列表,但实际为 {input_type}。" + +#: awx/conf/fields.py:105 +#, python-brace-format +msgid "{path} is not a valid path choice." +msgstr "{path} 不是有效的路径选择。" + +#: awx/conf/fields.py:149 +msgid "Enter a valid URL" +msgstr "输入有效的 URL" + +#: awx/conf/fields.py:187 +#, python-brace-format +msgid "\"{input}\" is not a valid string." +msgstr "\"{input}\" 不是有效字符串。" + +#: awx/conf/fields.py:202 +#, python-brace-format +msgid "Expected a list of tuples of max length 2 but got {input_type} instead." +msgstr "预期为最大长度为 2 的元组列表,但实际为 {input_type}。" + +#: awx/conf/registry.py:73 awx/conf/tests/unit/test_registry.py:155 +msgid "All" +msgstr "所有" + +#: awx/conf/registry.py:74 awx/conf/tests/unit/test_registry.py:156 +msgid "Changed" +msgstr "已更改" + +#: awx/conf/registry.py:82 +msgid "User-Defaults" +msgstr "User-Defaults" + +#: awx/conf/registry.py:143 +msgid "This value has been set manually in a settings file." +msgstr "此值已在设置文件中手动设置。" + +#: awx/conf/tests/unit/test_registry.py:46 +#: awx/conf/tests/unit/test_registry.py:56 +#: awx/conf/tests/unit/test_registry.py:72 +#: awx/conf/tests/unit/test_registry.py:87 +#: awx/conf/tests/unit/test_registry.py:100 +#: awx/conf/tests/unit/test_registry.py:106 +#: awx/conf/tests/unit/test_registry.py:126 +#: awx/conf/tests/unit/test_registry.py:132 +#: awx/conf/tests/unit/test_registry.py:145 +#: awx/conf/tests/unit/test_registry.py:157 +#: awx/conf/tests/unit/test_registry.py:166 +#: awx/conf/tests/unit/test_registry.py:172 +#: awx/conf/tests/unit/test_registry.py:184 +#: awx/conf/tests/unit/test_registry.py:191 +#: awx/conf/tests/unit/test_registry.py:233 +#: awx/conf/tests/unit/test_registry.py:251 +#: awx/conf/tests/unit/test_settings.py:79 +#: awx/conf/tests/unit/test_settings.py:97 +#: awx/conf/tests/unit/test_settings.py:112 +#: awx/conf/tests/unit/test_settings.py:127 +#: awx/conf/tests/unit/test_settings.py:143 +#: awx/conf/tests/unit/test_settings.py:156 +#: awx/conf/tests/unit/test_settings.py:173 +#: awx/conf/tests/unit/test_settings.py:189 +#: awx/conf/tests/unit/test_settings.py:200 +#: awx/conf/tests/unit/test_settings.py:216 +#: awx/conf/tests/unit/test_settings.py:237 +#: awx/conf/tests/unit/test_settings.py:259 +#: awx/conf/tests/unit/test_settings.py:285 +#: awx/conf/tests/unit/test_settings.py:299 +#: awx/conf/tests/unit/test_settings.py:323 +#: awx/conf/tests/unit/test_settings.py:343 +#: awx/conf/tests/unit/test_settings.py:360 +#: awx/conf/tests/unit/test_settings.py:374 +#: awx/conf/tests/unit/test_settings.py:398 +#: awx/conf/tests/unit/test_settings.py:411 +#: awx/conf/tests/unit/test_settings.py:430 +#: awx/conf/tests/unit/test_settings.py:466 awx/main/conf.py:24 +#: awx/main/conf.py:33 awx/main/conf.py:43 awx/main/conf.py:53 +#: awx/main/conf.py:65 awx/main/conf.py:78 awx/main/conf.py:91 +#: awx/main/conf.py:116 awx/main/conf.py:129 awx/main/conf.py:142 +#: awx/main/conf.py:154 awx/main/conf.py:162 awx/main/conf.py:173 +#: awx/main/conf.py:405 awx/main/conf.py:830 awx/main/conf.py:840 +#: awx/main/conf.py:852 +msgid "System" +msgstr "系统" + +#: awx/conf/tests/unit/test_registry.py:151 +#: awx/conf/tests/unit/test_registry.py:158 +msgid "OtherSystem" +msgstr "OtherSystem" + +#: awx/conf/views.py:48 +msgid "Setting Categories" +msgstr "设置类别" + +#: awx/conf/views.py:70 +msgid "Setting Detail" +msgstr "设置详情" + +#: awx/conf/views.py:162 +msgid "Logging Connectivity Test" +msgstr "日志记录连接测试" + +#: awx/main/access.py:66 +#, python-format +msgid "Required related field %s for permission check." +msgstr "权限检查需要相关字段 %s。" + +#: awx/main/access.py:82 +#, python-format +msgid "Bad data found in related field %s." +msgstr "相关字段 %s 中找到错误数据。" + +#: awx/main/access.py:331 +msgid "License is missing." +msgstr "缺少许可证。" + +#: awx/main/access.py:333 +msgid "License has expired." +msgstr "许可证已过期。" + +#: awx/main/access.py:341 +#, python-format +msgid "License count of %s instances has been reached." +msgstr "已达到 %s 实例的许可证计数。" + +#: awx/main/access.py:343 +#, python-format +msgid "License count of %s instances has been exceeded." +msgstr "已超过 %s 实例的许可证计数。" + +#: awx/main/access.py:345 +msgid "Host count exceeds available instances." +msgstr "主机计数超过可用实例。" + +#: awx/main/access.py:363 awx/main/access.py:372 +#, python-format +msgid "" +"You have already reached the maximum number of %s hosts allowed for your " +"organization. Contact your System Administrator for assistance." +msgstr "您已经达到您的机构允许的最大 %s 主机数。请联系系统管理员寻求帮助。" + +#: awx/main/access.py:927 +msgid "Unable to change inventory on a host." +msgstr "无法更改主机上的清单。" + +#: awx/main/access.py:948 awx/main/access.py:990 +msgid "Cannot associate two items from different inventories." +msgstr "无法将两个项与不同的清单关联。" + +#: awx/main/access.py:978 +msgid "Unable to change inventory on a group." +msgstr "无法更改组上的清单。" + +#: awx/main/access.py:1264 +msgid "Unable to change organization on a team." +msgstr "无法更改团队上的机构。" + +#: awx/main/access.py:1280 +msgid "The {} role cannot be assigned to a team" +msgstr "无法为团队分配 {} 角色" + +#: awx/main/access.py:1474 +msgid "Insufficient access to Job Template credentials." +msgstr "对作业模板凭证的访问权限不足。" + +#: awx/main/access.py:1639 awx/main/access.py:2063 +msgid "Job was launched with secret prompts provided by another user." +msgstr "作业是使用其他用户提供的机密提示启动的。" + +#: awx/main/access.py:1648 +msgid "Job has been orphaned from its job template and organization." +msgstr "作业已从其作业模板和机构中孤立。" + +#: awx/main/access.py:1650 +msgid "Job was launched with prompted fields you do not have access to." +msgstr "作业启动时带有您无法访问的提示字段。" + +#: awx/main/access.py:1652 +msgid "" +"Job was launched with unknown prompted fields. Organization admin " +"permissions required." +msgstr "作业以未知的提示字段启动。需要机构管理员权限。" + +#: awx/main/access.py:2053 +msgid "Workflow Job was launched with unknown prompts." +msgstr "工作流作业启动时显示未知提示。" + +#: awx/main/access.py:2065 +msgid "Job was launched with prompts you lack access to." +msgstr "作业启动时显示您无法访问的提示。" + +#: awx/main/access.py:2067 +msgid "Job was launched with prompts no longer accepted." +msgstr "作业启动时显示不再接受的提示。" + +#: awx/main/access.py:2079 +msgid "" +"You do not have permission to the workflow job resources required for " +"relaunch." +msgstr "您没有权限访问重新启动所需的工作流作业资源。" + +#: awx/main/apps.py:8 +msgid "Main" +msgstr "主要" + +#: awx/main/conf.py:22 +msgid "Enable Activity Stream" +msgstr "启用活动流" + +#: awx/main/conf.py:23 +msgid "Enable capturing activity for the activity stream." +msgstr "为活动流启用捕获活动。" + +#: awx/main/conf.py:31 +msgid "Enable Activity Stream for Inventory Sync" +msgstr "为清单同步启用活动流" + +#: awx/main/conf.py:32 +msgid "" +"Enable capturing activity for the activity stream when running inventory " +"sync." +msgstr "在运行清单同步时,为活动流启用捕获活动。" + +#: awx/main/conf.py:40 +msgid "All Users Visible to Organization Admins" +msgstr "机构管理员可见所有用户" + +#: awx/main/conf.py:41 +msgid "" +"Controls whether any Organization Admin can view all users and teams, even " +"those not associated with their Organization." +msgstr "控制任何机构管理员是否可查看所有用户和团队,甚至包括与其机构没有关联的用户和团队。" + +#: awx/main/conf.py:50 +msgid "Organization Admins Can Manage Users and Teams" +msgstr "机构管理员可以管理用户和团队" + +#: awx/main/conf.py:51 +msgid "" +"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." +msgstr "控制机构管理员是否具有创建和管理用户和团队的权限。如果您使用 LDAP 或 SAML 集成,您可能需要禁用此功能。" + +#: awx/main/conf.py:62 +msgid "Base URL of the Tower host" +msgstr "Tower 主机的基本 URL" + +#: awx/main/conf.py:63 +msgid "" +"This setting is used by services like notifications to render a valid url to " +"the Tower host." +msgstr "此设置供通知类的服务用来为 Tower 主机呈现有效的 URL。" + +#: awx/main/conf.py:72 +msgid "Remote Host Headers" +msgstr "远程主机标头" + +#: awx/main/conf.py:73 +msgid "" +"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." +msgstr "为确定远程主机名或 IP 而搜索的 HTTP 标头和元键。如果位于反向代理后端,请将其他项添加到此列表,如 \"HTTP_X_FORWARDED_FOR\"。请参阅管理员指南的“代理支持”部分以了解更多详情。" + +#: awx/main/conf.py:85 +msgid "Proxy IP Whitelist" +msgstr "代理 IP 白名单" + +#: awx/main/conf.py:86 +msgid "" +"If Tower is behind a reverse proxy/load balancer, use this setting to " +"whitelist the proxy IP addresses from which Tower 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')" +msgstr "如果 Tower 位于反向代理/负载均衡器后端,则使用此设置可将 Tower 应该信任自定义 REMOTE_HOST_HEADERS 标头值的代理服务器 IP 地址列入白名单。如果此设置为空列表(默认设置),则将无条件地信任由 REMOTE_HOST_HEADERS 指定的标头。" + +#: awx/main/conf.py:112 +msgid "License" +msgstr "许可证" + +#: awx/main/conf.py:113 +msgid "" +"The license controls which features and functionality are enabled. Use /api/" +"v2/config/ to update or change the license." +msgstr "许可证控制启用哪些特性和功能。使用 /api/v2/config/ 来更新或更改许可证。" + +#: awx/main/conf.py:127 +msgid "Red Hat customer username" +msgstr "红帽客户用户名" + +#: awx/main/conf.py:128 +msgid "" +"This username is used to retrieve license information and to send Automation " +"Analytics" +msgstr "此用户名用于检索许可证信息并发送自动化分析" + +#: awx/main/conf.py:140 +msgid "Red Hat customer password" +msgstr "红帽客户密码" + +#: awx/main/conf.py:141 +msgid "" +"This password is used to retrieve license information and to send Automation " +"Analytics" +msgstr "此密码用于检索许可证信息并发送自动化分析" + +#: awx/main/conf.py:152 +msgid "Automation Analytics upload URL." +msgstr "自动化分析上传 URL。" + +#: awx/main/conf.py:153 +msgid "" +"This setting is used to to configure data collection for the Automation " +"Analytics dashboard" +msgstr "此设置用于为自动化分析仪表板配置数据收集" + +#: awx/main/conf.py:161 +msgid "Unique identifier for an AWX/Tower installation" +msgstr "AWX/Tower 安装的唯一标识符" + +#: awx/main/conf.py:170 +msgid "Custom virtual environment paths" +msgstr "自定义虚拟环境路径" + +#: awx/main/conf.py:171 +msgid "" +"Paths where Tower will look for custom virtual environments (in addition to /" +"var/lib/awx/venv/). Enter one path per line." +msgstr "(除了 /var/lib/awx/venv/ 之外)Tower 将在这些路径查找自定义虚拟环境。每行输入一个路径。" + +#: awx/main/conf.py:181 +msgid "Ansible Modules Allowed for Ad Hoc Jobs" +msgstr "允许用于临时作业的 Ansible 模块" + +#: awx/main/conf.py:182 +msgid "List of modules allowed to be used by ad-hoc jobs." +msgstr "允许供临时作业使用的模块列表。" + +#: awx/main/conf.py:183 awx/main/conf.py:205 awx/main/conf.py:214 +#: awx/main/conf.py:225 awx/main/conf.py:235 awx/main/conf.py:245 +#: awx/main/conf.py:256 awx/main/conf.py:267 awx/main/conf.py:278 +#: awx/main/conf.py:290 awx/main/conf.py:299 awx/main/conf.py:312 +#: awx/main/conf.py:325 awx/main/conf.py:337 awx/main/conf.py:348 +#: awx/main/conf.py:359 awx/main/conf.py:371 awx/main/conf.py:383 +#: awx/main/conf.py:394 awx/main/conf.py:414 awx/main/conf.py:424 +#: awx/main/conf.py:434 awx/main/conf.py:450 awx/main/conf.py:463 +#: awx/main/conf.py:477 awx/main/conf.py:491 awx/main/conf.py:503 +#: awx/main/conf.py:513 awx/main/conf.py:524 awx/main/conf.py:534 +#: awx/main/conf.py:545 awx/main/conf.py:555 awx/main/conf.py:565 +#: awx/main/conf.py:577 awx/main/conf.py:589 awx/main/conf.py:601 +#: awx/main/conf.py:615 awx/main/conf.py:627 +msgid "Jobs" +msgstr "作业" + +#: awx/main/conf.py:192 +msgid "Always" +msgstr "始终" + +#: awx/main/conf.py:193 +msgid "Never" +msgstr "永不" + +#: awx/main/conf.py:194 +msgid "Only On Job Template Definitions" +msgstr "仅在作业模板定义中" + +#: awx/main/conf.py:197 +msgid "When can extra variables contain Jinja templates?" +msgstr "额外变量何时可以包含 Jinja 模板?" + +#: awx/main/conf.py:199 +msgid "" +"Ansible allows variable substitution via the Jinja2 templating language for " +"--extra-vars. This poses a potential security risk where Tower 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\"." +msgstr "Ansible 允许通过 Jinja2 模板语言为 --extra-vars 替换变量。这会带来潜在的安全风险,因为能够在作业启动时指定额外变量的 Tower 用户可使用 Jinja2 模板来运行任意 Python。建议将此值设为 \"template\" 或 \"never\"。" + +#: awx/main/conf.py:212 +msgid "Enable job isolation" +msgstr "启用作业隔离" + +#: awx/main/conf.py:213 +msgid "" +"Isolates an Ansible job from protected parts of the system to prevent " +"exposing sensitive information." +msgstr "将 Ansible 作业与系统受保护的部分隔离,以防止公开敏感信息。" + +#: awx/main/conf.py:221 +msgid "Job execution path" +msgstr "作业执行路径" + +#: awx/main/conf.py:222 +msgid "" +"The directory in which Tower will create new temporary directories for job " +"execution and isolation (such as credential files and custom inventory " +"scripts)." +msgstr "Tower 将在此目录下为作业执行和隔离创建新临时目录(如凭证文件和自定义清单脚本)。" + +#: awx/main/conf.py:233 +msgid "Paths to hide from isolated jobs" +msgstr "向隔离作业隐藏的路径" + +#: awx/main/conf.py:234 +msgid "" +"Additional paths to hide from isolated processes. Enter one path per line." +msgstr "向隔离进程隐藏的其他路径。每行输入一个路径。" + +#: awx/main/conf.py:243 +msgid "Paths to expose to isolated jobs" +msgstr "向隔离作业公开的路径" + +#: awx/main/conf.py:244 +msgid "" +"Whitelist of paths that would otherwise be hidden to expose to isolated " +"jobs. Enter one path per line." +msgstr "要向隔离作业公开的原本隐藏的路径白名单。每行输入一个路径。" + +#: awx/main/conf.py:254 +msgid "Verbosity level for isolated node management tasks" +msgstr "隔离节点管理任务的详细程度" + +#: awx/main/conf.py:255 +msgid "" +"This can be raised to aid in debugging connection issues for isolated task " +"execution" +msgstr "可以提高此设置来帮助对隔离任务执行中的连接问题进行调试。" + +#: awx/main/conf.py:265 +msgid "Isolated status check interval" +msgstr "隔离状态检查间隔" + +#: awx/main/conf.py:266 +msgid "" +"The number of seconds to sleep between status checks for jobs running on " +"isolated instances." +msgstr "在隔离实例中运行的任务的状态检查间休眠的秒数。" + +#: awx/main/conf.py:275 +msgid "Isolated launch timeout" +msgstr "隔离启动超时" + +#: awx/main/conf.py:276 +msgid "" +"The timeout (in seconds) for launching jobs on isolated instances. This " +"includes the time needed to copy source control files (playbooks) to the " +"isolated instance." +msgstr "在隔离的实例中启动作业的超时时间(以秒为单位)。这包括将源控制文件 (playbook) 复制到隔离实例所需的时间。" + +#: awx/main/conf.py:287 +msgid "Isolated connection timeout" +msgstr "隔离连接超时" + +#: awx/main/conf.py:288 +msgid "" +"Ansible SSH connection timeout (in seconds) to use when communicating with " +"isolated instances. Value should be substantially greater than expected " +"network latency." +msgstr "与隔离的实例通信时使用的 Ansible SSH 连接超时(以秒为单位)。值应显著大于预期的网络延迟。" + +#: awx/main/conf.py:297 +msgid "Isolated host key checking" +msgstr "隔离主机密钥检查" + +#: awx/main/conf.py:298 +msgid "" +"When set to True, AWX will enforce strict host key checking for " +"communication with isolated nodes." +msgstr "当设置为 True 时,AWX 将针对与隔离的节点进行通信实行严格的主机密钥检查。" + +#: awx/main/conf.py:308 +msgid "Generate RSA keys for isolated instances" +msgstr "为隔离的实例生成 RSA 密钥" + +#: awx/main/conf.py:309 +msgid "" +"If set, a random RSA key will be generated and distributed to isolated " +"instances. To disable this behavior and manage authentication for isolated " +"instances outside of Tower, disable this setting." +msgstr "如果设置,将生成随机 RSA 密钥并将其分发到隔离的实例。要禁用此行为并管理 Tower 之外的隔离实例的身份验证,请禁用此设置。" + +#: awx/main/conf.py:323 awx/main/conf.py:324 +msgid "The RSA private key for SSH traffic to isolated instances" +msgstr "到隔离实例的 SSH 流量的 RSA 私钥" + +#: awx/main/conf.py:335 awx/main/conf.py:336 +msgid "The RSA public key for SSH traffic to isolated instances" +msgstr "到隔离实例的 SSH 流量的 RSA 公钥" + +#: awx/main/conf.py:345 +msgid "Enable detailed resource profiling on all playbook runs" +msgstr "启用所有 playbook 运行的详细资源分析" + +#: awx/main/conf.py:346 +msgid "" +"If set, detailed resource profiling data will be collected on all jobs. This " +"data can be gathered with `sosreport`." +msgstr "如果设置,将收集所有作业的详细资源分析数据。这些数据可使用 `sosreport` 进行收集。" + +#: awx/main/conf.py:356 +msgid "Interval (in seconds) between polls for cpu usage." +msgstr "cpu 用量轮询间隔(以秒为单位)。" + +#: awx/main/conf.py:357 +msgid "" +"Interval (in seconds) between polls for cpu usage. Setting this lower than " +"the default will affect playbook performance." +msgstr "cpu 用量轮询间隔(以秒为单位)。此设置低于默认值会影响 playbook 性能。" + +#: awx/main/conf.py:368 +msgid "Interval (in seconds) between polls for memory usage." +msgstr "内存用量轮询间隔(以秒为单位)。" + +#: awx/main/conf.py:369 +msgid "" +"Interval (in seconds) between polls for memory usage. Setting this lower " +"than the default will affect playbook performance." +msgstr "内存用量轮询间隔(以秒为单位)。此设置低于默认值会影响 playbook 性能。" + +#: awx/main/conf.py:380 +msgid "Interval (in seconds) between polls for PID count." +msgstr "PID 计数轮询间隔(以秒为单位)。" + +#: awx/main/conf.py:381 +msgid "" +"Interval (in seconds) between polls for PID count. Setting this lower than " +"the default will affect playbook performance." +msgstr "PID 计数轮询间隔(以秒为单位)。此设置低于默认值会影响 playbook 的性能。" + +#: awx/main/conf.py:392 +msgid "Extra Environment Variables" +msgstr "额外环境变量" + +#: awx/main/conf.py:393 +msgid "" +"Additional environment variables set for playbook runs, inventory updates, " +"project updates, and notification sending." +msgstr "为 playbook 运行、库存更新、项目更新和通知发送设置的额外环境变量。" + +#: awx/main/conf.py:403 +msgid "Gather data for Automation Analytics" +msgstr "为自动化分析收集数据" + +#: awx/main/conf.py:404 +msgid "Enables Tower to gather data on automation and send it to Red Hat." +msgstr "允许 Tower 收集自动化数据并将其发送给红帽。" + +#: awx/main/conf.py:412 +msgid "Run Project Updates With Higher Verbosity" +msgstr "以更高的详细程度运行项目更新" + +#: awx/main/conf.py:413 +msgid "" +"Adds the CLI -vvv flag to ansible-playbook runs of project_update.yml used " +"for project updates." +msgstr "将 CLI -vvv 标记添加到用于项目更新的 project_update.yml 的 ansible-playbook 运行。" + +#: awx/main/conf.py:422 +msgid "Enable Role Download" +msgstr "启用角色下载" + +#: awx/main/conf.py:423 +msgid "" +"Allows roles to be dynamically downloaded from a requirements.yml file for " +"SCM projects." +msgstr "允许从 SCM 项目的 requirements.yml 文件中动态下载角色。" + +#: awx/main/conf.py:432 +msgid "Enable Collection(s) Download" +msgstr "启用集合下载" + +#: awx/main/conf.py:433 +msgid "" +"Allows collections to be dynamically downloaded from a requirements.yml file " +"for SCM projects." +msgstr "允许从 SCM 项目的 requirements.yml 文件中动态下载集合。" + +#: awx/main/conf.py:443 +msgid "Primary Galaxy Server URL" +msgstr "主 Galaxy 服务器 URL" + +#: awx/main/conf.py:445 +msgid "" +"For organizations that run their own Galaxy service, this gives the option " +"to specify a host as the primary galaxy server. Requirements will be " +"downloaded from the primary if the specific role or collection is available " +"there. If the content is not avilable in the primary, or if this field is " +"left blank, it will default to galaxy.ansible.com." +msgstr "对于运行其自己的 Galaxy 服务的机构,这提供了将主机指定为主 galaxy 服务器的选项。如果特定角色或集合可用,则需要从主服务器下载这些要求。如果内容在主服务器中不可用,或者此字段留空,则默认为 galaxy.ansible.com。" + +#: awx/main/conf.py:459 +msgid "Primary Galaxy Server Username" +msgstr "主 Galaxy 服务器用户名" + +#: awx/main/conf.py:460 +msgid "" +"For using a galaxy server at higher precedence than the public Ansible " +"Galaxy. The username to use for basic authentication against the Galaxy " +"instance, this is mutually exclusive with PRIMARY_GALAXY_TOKEN." +msgstr "适用于以高于公共 Ansible Galaxy 的优先级使用 galaxy 服务器的情况。用于针对 Galaxy 实例进行基本身份验证的用户名,与 PRIMARY_GALAXY_TOKEN 相互排斥。" + +#: awx/main/conf.py:473 +msgid "Primary Galaxy Server Password" +msgstr "主 Galaxy 服务器密码" + +#: awx/main/conf.py:474 +msgid "" +"For using a galaxy server at higher precedence than the public Ansible " +"Galaxy. The password to use for basic authentication against the Galaxy " +"instance, this is mutually exclusive with PRIMARY_GALAXY_TOKEN." +msgstr "适用于以高于公共 Ansible Galaxy 的优先级使用 galaxy 服务器的情况。用于针对 Galaxy 实例进行基本身份验证的密码,与 PRIMARY_GALAXY_TOKEN 相互排斥。" + +#: awx/main/conf.py:487 +msgid "Primary Galaxy Server Token" +msgstr "主 Galaxy 服务器令牌" + +#: awx/main/conf.py:488 +msgid "" +"For using a galaxy server at higher precedence than the public Ansible " +"Galaxy. The token to use for connecting with the Galaxy instance, this is " +"mutually exclusive with corresponding username and password settings." +msgstr "适用于以高于公共 Ansible Galaxy 的优先级使用 galaxy 服务器的情况。用于连接 Galaxy 实例的令牌,与对应的用户名和密码设置相互排斥。" + +#: awx/main/conf.py:500 +msgid "Primary Galaxy Authentication URL" +msgstr "主 Galaxy 身份验证 URL" + +#: awx/main/conf.py:501 +msgid "" +"For using a galaxy server at higher precedence than the public Ansible " +"Galaxy. The token_endpoint of a Keycloak server." +msgstr "适用于以高于公共 Ansible Galaxy 的优先级使用 galaxy 服务器的情况。Keycloak 服务器的 token_endpoint。" + +#: awx/main/conf.py:511 +msgid "Allow Access to Public Galaxy" +msgstr "允许访问公共 Galaxy" + +#: awx/main/conf.py:512 +msgid "" +"Allow or deny access to the public Ansible Galaxy during project updates." +msgstr "在项目更新期间允许或拒绝访问公共 Ansible Galaxy。" + +#: awx/main/conf.py:521 +msgid "Ignore Ansible Galaxy SSL Certificate Verification" +msgstr "忽略 Ansible Galaxy SSL 证书验证" + +#: awx/main/conf.py:522 +msgid "" +"If set to true, certificate validation will not be done wheninstalling " +"content from any Galaxy server." +msgstr "如果设为 true,则在从任何 Galaxy 服务器安装内容时将不执行证书验证。" + +#: awx/main/conf.py:532 +msgid "Standard Output Maximum Display Size" +msgstr "标准输出最大显示大小" + +#: awx/main/conf.py:533 +msgid "" +"Maximum Size of Standard Output in bytes to display before requiring the " +"output be downloaded." +msgstr "要求下载输出前显示的最大标准输出大小(以字节为单位)。" + +#: awx/main/conf.py:542 +msgid "Job Event Standard Output Maximum Display Size" +msgstr "作业事件标准输出最大显示大小" + +#: awx/main/conf.py:544 +msgid "" +"Maximum Size of Standard Output in bytes to display for a single job or ad " +"hoc command event. `stdout` will end with `…` when truncated." +msgstr "为单个作业或临时命令事件显示的最大标准输出大小(以字节为单位)。`stdout` 在截断时会以 `…` 结束。" + +#: awx/main/conf.py:553 +msgid "Maximum Scheduled Jobs" +msgstr "最多调度作业" + +#: awx/main/conf.py:554 +msgid "" +"Maximum number of the same job template that can be waiting to run when " +"launching from a schedule before no more are created." +msgstr "从计划启动时可以等待运行的同一作业模板的最大数量,此后不再创建更多模板。" + +#: awx/main/conf.py:563 +msgid "Ansible Callback Plugins" +msgstr "Ansible 回调插件" + +#: awx/main/conf.py:564 +msgid "" +"List of paths to search for extra callback plugins to be used when running " +"jobs. Enter one path per line." +msgstr "用于搜索供运行作业时使用的额外回调插件的路径列表。每行输入一个路径。" + +#: awx/main/conf.py:574 +msgid "Default Job Timeout" +msgstr "默认作业超时" + +#: awx/main/conf.py:575 +msgid "" +"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." +msgstr "允许运行作业的最长时间(以秒为单位)。使用 0 值表示不应应用超时。在单个作业模板中设置的超时会覆写此值。" + +#: awx/main/conf.py:586 +msgid "Default Inventory Update Timeout" +msgstr "默认清单更新超时" + +#: awx/main/conf.py:587 +msgid "" +"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." +msgstr "允许运行清单更新的最长时间(以秒为单位)。使用 0 值表示不应应用超时。在单个清单源中设置的超时会覆写此值。" + +#: awx/main/conf.py:598 +msgid "Default Project Update Timeout" +msgstr "默认项目更新超时" + +#: awx/main/conf.py:599 +msgid "" +"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." +msgstr "允许运行项目更新的最长时间(以秒为单位)。使用 0 值表示不应应用超时。在单个项目中设置的超时会覆写此值。" + +#: awx/main/conf.py:610 +msgid "Per-Host Ansible Fact Cache Timeout" +msgstr "每个主机 Ansible 事实缓存超时" + +#: awx/main/conf.py:611 +msgid "" +"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." +msgstr "存储的 Ansible 事实自上次修改后被视为有效的最长时间(以秒为单位)。只有有效且未过时的事实才会被 playbook 访问。注意,这不会影响从数据库中删除 ansible_facts。使用 0 值表示不应应用超时。" + +#: awx/main/conf.py:624 +msgid "Maximum number of forks per job." +msgstr "每个作业的最大 fork 数量。" + +#: awx/main/conf.py:625 +msgid "" +"Saving a Job Template with more than this number of forks will result in an " +"error. When set to 0, no limit is applied." +msgstr "在保存作业模板时带有比这个数量更多的 fork 会出错。如果设置为 0,则代表没有限制。" + +#: awx/main/conf.py:636 +msgid "Logging Aggregator" +msgstr "日志记录聚合器" + +#: awx/main/conf.py:637 +msgid "Hostname/IP where external logs will be sent to." +msgstr "外部日志发送到的主机名/IP。" + +#: awx/main/conf.py:638 awx/main/conf.py:649 awx/main/conf.py:661 +#: awx/main/conf.py:671 awx/main/conf.py:683 awx/main/conf.py:698 +#: awx/main/conf.py:710 awx/main/conf.py:719 awx/main/conf.py:729 +#: awx/main/conf.py:741 awx/main/conf.py:752 awx/main/conf.py:764 +#: awx/main/conf.py:777 awx/main/conf.py:787 awx/main/conf.py:799 +#: awx/main/conf.py:810 awx/main/conf.py:820 +msgid "Logging" +msgstr "日志记录" + +#: awx/main/conf.py:646 +msgid "Logging Aggregator Port" +msgstr "日志记录聚合器端口" + +#: awx/main/conf.py:647 +msgid "" +"Port on Logging Aggregator to send logs to (if required and not provided in " +"Logging Aggregator)." +msgstr "将日志发送到的日志记录聚合器上的端口(如果需要且未在日志聚合器中提供)。" + +#: awx/main/conf.py:659 +msgid "Logging Aggregator Type" +msgstr "日志记录聚合器类型" + +#: awx/main/conf.py:660 +msgid "Format messages for the chosen log aggregator." +msgstr "所选日志聚合器的格式消息。" + +#: awx/main/conf.py:669 +msgid "Logging Aggregator Username" +msgstr "日志记录聚合器用户名" + +#: awx/main/conf.py:670 +msgid "Username for external log aggregator (if required; HTTP/s only)." +msgstr "外部日志聚合器的用户名(如果需要。只支持 HTTP/s)。" + +#: awx/main/conf.py:681 +msgid "Logging Aggregator Password/Token" +msgstr "日志记录聚合器密码/令牌" + +#: awx/main/conf.py:682 +msgid "" +"Password or authentication token for external log aggregator (if required; " +"HTTP/s only)." +msgstr "外部日志聚合器的密码或身份验证令牌(如果需要。HTTP/s)。" + +#: awx/main/conf.py:691 +msgid "Loggers Sending Data to Log Aggregator Form" +msgstr "将数据发送到日志聚合器表单的日志记录器" + +#: awx/main/conf.py:692 +msgid "" +"List of loggers that will send HTTP logs to the collector, these can include " +"any or all of: \n" +"awx - service logs\n" +"activity_stream - activity stream records\n" +"job_events - callback data from Ansible job events\n" +"system_tracking - facts gathered from scan jobs." +msgstr "将 HTTP 日志发送到收集器的日志记录器列表,其中包括以下任意一种或全部:\n" +"awx - 服务日志\n" +"activity_stream - 活动流记录\n" +"job_events - Ansible 作业事件的回调数据\n" +"system_tracking - 从扫描作业收集的事实。" + +#: awx/main/conf.py:705 +msgid "Log System Tracking Facts Individually" +msgstr "单独记录系统跟踪事实" + +#: awx/main/conf.py:706 +msgid "" +"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." +msgstr "如果设置,则会为扫描中找到的每个软件包、服务或其他项发送系统跟踪事实,以便提高搜索查询的粒度。如果未设置,则将以单一字典形式发送事实,从而提高事实处理的效率。" + +#: awx/main/conf.py:717 +msgid "Enable External Logging" +msgstr "启用外部日志记录" + +#: awx/main/conf.py:718 +msgid "Enable sending logs to external log aggregator." +msgstr "启用将日志发送到外部日志聚合器。" + +#: awx/main/conf.py:727 +msgid "Cluster-wide Tower unique identifier." +msgstr "集群范围的 Tower 唯一标识符。" + +#: awx/main/conf.py:728 +msgid "Useful to uniquely identify Tower instances." +msgstr "用于唯一标识 Tower 实例。" + +#: awx/main/conf.py:737 +msgid "Logging Aggregator Protocol" +msgstr "日志记录聚合器协议" + +#: awx/main/conf.py:738 +msgid "" +"Protocol used to communicate with log aggregator. HTTPS/HTTP assumes HTTPS " +"unless http:// is explicitly used in the Logging Aggregator hostname." +msgstr "用于与日志聚合器通信的协议。HTTPS/HTTP 假设 HTTPS,除非在日志记录聚合器主机名中明确使用 http://。" + +#: awx/main/conf.py:748 +msgid "TCP Connection Timeout" +msgstr "TCP 连接超时" + +#: awx/main/conf.py:749 +msgid "" +"Number of seconds for a TCP connection to external log aggregator to " +"timeout. Applies to HTTPS and TCP log aggregator protocols." +msgstr "与外部日志聚合器的 TCP 连接超时的秒数。适用于 HTTPS 和 TCP 日志聚合器协议。" + +#: awx/main/conf.py:759 +msgid "Enable/disable HTTPS certificate verification" +msgstr "启用/禁用 HTTPS 证书验证" + +#: awx/main/conf.py:760 +msgid "" +"Flag to control enable/disable of certificate verification when " +"LOG_AGGREGATOR_PROTOCOL is \"https\". If enabled, Tower's log handler will " +"verify certificate sent by external log aggregator before establishing " +"connection." +msgstr "当 LOG_aggregator_PROTOCOL 为 \"https\" 时,用来控制证书启用/禁用证书验证的标记。如果启用,Tower 的日志处理程序会在建立连接前验证外部日志聚合器发送的证书。" + +#: awx/main/conf.py:772 +msgid "Logging Aggregator Level Threshold" +msgstr "日志记录聚合器级别阈值" + +#: awx/main/conf.py:773 +msgid "" +"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)" +msgstr "日志处理程序使用的级别阈值。从最低到最高的严重性为:DEBUG、INFO、WARNING、ERROR、CRITICAL。日志处理程序会忽略严重性低于阈值的消息。(类别 awx.anlytics 下的消息会忽略此设置)" + +#: awx/main/conf.py:785 +msgid "Enabled external log aggregation auditing" +msgstr "启用外部日志聚合审核" + +#: awx/main/conf.py:786 +msgid "" +"When enabled, all external logs emitted by Tower will also be written to /" +"var/log/tower/external.log. This is an experimental setting intended to be " +"used for debugging external log aggregation issues (and may be subject to " +"change in the future)." +msgstr "启用后,Tower 发出的所有外部日志也会写入 /var/log/tower/external.log。这是一个实验性设置,用于调试外部日志聚合问题(将来可能会有变化)。" + +#: awx/main/conf.py:795 +msgid "Maximum disk persistance for external log aggregation (in GB)" +msgstr "外部日志聚合的最大磁盘持久性存储(以 GB 为单位)" + +#: awx/main/conf.py:796 +msgid "" +"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." +msgstr "外部日志聚合器停机时要保存的数据量(以 GB 为单位)(默认为 1),与 rsyslogd queue.maxdiskspace 设置相同。" + +#: awx/main/conf.py:806 +msgid "File system location for rsyslogd disk persistence" +msgstr "rsyslogd 磁盘持久存储在文件系统中的位置" + +#: awx/main/conf.py:807 +msgid "" +"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." +msgstr "在外部日志聚合器停止工作后,重试的持久性日志的位置(默认为 /var/lib/awx)。与 rsyslogd queue.spoolDirectory 的设置相同。" + +#: awx/main/conf.py:817 +msgid "Enable rsyslogd debugging" +msgstr "启用 rsyslogd 调试" + +#: awx/main/conf.py:818 +msgid "" +"Enabled high verbosity debugging for rsyslogd. Useful for debugging " +"connection issues for external log aggregation." +msgstr "为 rsyslogd 启用高度详细调试。用于调试外部日志聚合的连接问题。" + +#: awx/main/conf.py:828 +msgid "Message Durability" +msgstr "消息持续时间" + +#: awx/main/conf.py:829 +msgid "" +"When set (the default), underlying queues will be persisted to disk. " +"Disable this to enable higher message bus throughput." +msgstr "如果设置(默认值),底层队列将保留到磁盘中。禁用此设置可启用更高消息总线吞吐量。" + +#: awx/main/conf.py:838 +msgid "Last gather date for Automation Analytics." +msgstr "为自动化分析收集的最新数据" + +#: awx/main/conf.py:848 +msgid "Automation Analytics Gather Interval" +msgstr "自动化分析收集间隔" + +#: awx/main/conf.py:849 +msgid "Interval (in seconds) between data gathering." +msgstr "收集数据间的间隔(以秒为单位)。" + +#: awx/main/conf.py:871 awx/sso/conf.py:1239 +msgid "\n" +msgstr "\n" + +#: awx/main/conf.py:892 +msgid "" +"A URL for Primary Galaxy must be defined before disabling public Galaxy." +msgstr "在禁用公共 Galaxy 之前,必须定义主 Galaxy 的 URL。" + +#: awx/main/conf.py:912 +msgid "Cannot provide field if PRIMARY_GALAXY_URL is not set." +msgstr "如果未设置 PRIMARY_Galaxy_URL,则无法提供字段。" + +#: awx/main/conf.py:925 +#, python-brace-format +msgid "" +"Galaxy server settings are not available until Ansible {min_version}, you " +"are running {current_version}." +msgstr "在 Ansible {min_version} 之前,Galaxy 服务器设置不可用,您正在运行 {current_version}。" + +#: awx/main/conf.py:934 +msgid "" +"Setting Galaxy token and authentication URL is mutually exclusive with " +"username and password." +msgstr "设置 Galaxy 令牌和验证 URL 与用户名和密码相互排斥。" + +#: awx/main/conf.py:937 +msgid "If authenticating via username and password, both must be provided." +msgstr "如果通过用户名和密码进行身份验证,则必须提供两者。" + +#: awx/main/conf.py:943 +msgid "" +"If authenticating via token, both token and authentication URL must be " +"provided." +msgstr "如果通过令牌进行身份验证,则必须提供令牌和身份验证 URL。" + +#: awx/main/constants.py:17 +msgid "Sudo" +msgstr "Sudo" + +#: awx/main/constants.py:17 +msgid "Su" +msgstr "Su" + +#: awx/main/constants.py:17 +msgid "Pbrun" +msgstr "Pbrun" + +#: awx/main/constants.py:17 +msgid "Pfexec" +msgstr "Pfexec" + +#: awx/main/constants.py:18 +msgid "DZDO" +msgstr "DZDO" + +#: awx/main/constants.py:18 +msgid "Pmrun" +msgstr "Pmrun" + +#: awx/main/constants.py:18 +msgid "Runas" +msgstr "Runas" + +#: awx/main/constants.py:19 +msgid "Enable" +msgstr "启用" + +#: awx/main/constants.py:19 +msgid "Doas" +msgstr "Doas" + +#: awx/main/constants.py:19 +msgid "Ksu" +msgstr "Ksu" + +#: awx/main/constants.py:20 +msgid "Machinectl" +msgstr "Machinectl" + +#: awx/main/constants.py:20 +msgid "Sesu" +msgstr "Sesu" + +#: awx/main/constants.py:22 +msgid "None" +msgstr "无" + +#: awx/main/credential_plugins/aim.py:16 +msgid "CyberArk AIM URL" +msgstr "CyberArk AIM URL" + +#: awx/main/credential_plugins/aim.py:21 +msgid "Application ID" +msgstr "应用 ID" + +#: awx/main/credential_plugins/aim.py:26 +msgid "Client Key" +msgstr "客户端密钥" + +#: awx/main/credential_plugins/aim.py:32 +msgid "Client Certificate" +msgstr "客户端证书" + +#: awx/main/credential_plugins/aim.py:38 +msgid "Verify SSL Certificates" +msgstr "验证 SSL 证书" + +#: awx/main/credential_plugins/aim.py:44 +msgid "Object Query" +msgstr "对象查询" + +#: awx/main/credential_plugins/aim.py:46 +msgid "" +"Lookup query for the object. Ex: Safe=TestSafe;Object=testAccountName123" +msgstr "对象的查找查询。例如:\"Safe=TestSafe;Object=testAccountName123\"" + +#: awx/main/credential_plugins/aim.py:49 +msgid "Object Query Format" +msgstr "对象查询格式" + +#: awx/main/credential_plugins/aim.py:55 +msgid "Reason" +msgstr "原因" + +#: awx/main/credential_plugins/aim.py:57 +msgid "" +"Object request reason. This is only needed if it is required by the object's " +"policy." +msgstr "对象请求原因。只有对象策略要求时才需要此设置。" + +#: awx/main/credential_plugins/azure_kv.py:21 +msgid "Vault URL (DNS Name)" +msgstr "Vault URL(DNS 名称)" + +#: awx/main/credential_plugins/azure_kv.py:26 +#: awx/main/models/credential/__init__.py:956 +msgid "Client ID" +msgstr "客户端 ID" + +#: awx/main/credential_plugins/azure_kv.py:35 +#: awx/main/models/credential/__init__.py:965 +msgid "Tenant ID" +msgstr "租户 ID" + +#: awx/main/credential_plugins/azure_kv.py:39 +msgid "Cloud Environment" +msgstr "云环境" + +#: awx/main/credential_plugins/azure_kv.py:40 +msgid "Specify which azure cloud environment to use." +msgstr "指定要使用的云环境。" + +#: awx/main/credential_plugins/azure_kv.py:46 +msgid "Secret Name" +msgstr "机密名称" + +#: awx/main/credential_plugins/azure_kv.py:48 +msgid "The name of the secret to look up." +msgstr "要查找的机密的名称。" + +#: awx/main/credential_plugins/azure_kv.py:51 +#: awx/main/credential_plugins/conjur.py:47 +msgid "Secret Version" +msgstr "机密版本" + +#: awx/main/credential_plugins/azure_kv.py:53 +#: awx/main/credential_plugins/conjur.py:49 +#: awx/main/credential_plugins/hashivault.py:67 +msgid "" +"Used to specify a specific secret version (if left empty, the latest version " +"will be used)." +msgstr "用于指定特定机密版本(如果留空,则会使用最新版本)。" + +#: awx/main/credential_plugins/conjur.py:18 +msgid "Conjur URL" +msgstr "Conjur URL" + +#: awx/main/credential_plugins/conjur.py:23 +msgid "API Key" +msgstr "API 密钥" + +#: awx/main/credential_plugins/conjur.py:28 awx/main/models/inventory.py:1018 +msgid "Account" +msgstr "帐户" + +#: awx/main/credential_plugins/conjur.py:32 +#: awx/main/models/credential/__init__.py:598 +#: awx/main/models/credential/__init__.py:654 +#: awx/main/models/credential/__init__.py:712 +#: awx/main/models/credential/__init__.py:785 +#: awx/main/models/credential/__init__.py:834 +#: awx/main/models/credential/__init__.py:860 +#: awx/main/models/credential/__init__.py:887 +#: awx/main/models/credential/__init__.py:947 +#: awx/main/models/credential/__init__.py:1020 +#: awx/main/models/credential/__init__.py:1051 +#: awx/main/models/credential/__init__.py:1101 +msgid "Username" +msgstr "用户名" + +#: awx/main/credential_plugins/conjur.py:36 +msgid "Public Key Certificate" +msgstr "公钥证书" + +#: awx/main/credential_plugins/conjur.py:42 +msgid "Secret Identifier" +msgstr "机密标识符" + +#: awx/main/credential_plugins/conjur.py:44 +msgid "The identifier for the secret e.g., /some/identifier" +msgstr "机密的标识符,如 /some/identifier" + +#: awx/main/credential_plugins/hashivault.py:19 +msgid "Server URL" +msgstr "服务器 URL" + +#: awx/main/credential_plugins/hashivault.py:22 +msgid "The URL to the HashiCorp Vault" +msgstr "HashiCorp Vault 的 URL" + +#: awx/main/credential_plugins/hashivault.py:25 +#: awx/main/models/credential/__init__.py:986 +#: awx/main/models/credential/__init__.py:1003 +msgid "Token" +msgstr "令牌" + +#: awx/main/credential_plugins/hashivault.py:28 +msgid "The access token used to authenticate to the Vault server" +msgstr "用于向 Vault 服务器进行身份验证的访问令牌" + +#: awx/main/credential_plugins/hashivault.py:31 +msgid "CA Certificate" +msgstr "CA 证书" + +#: awx/main/credential_plugins/hashivault.py:34 +msgid "" +"The CA certificate used to verify the SSL certificate of the Vault server" +msgstr "用于验证 Vault 服务器 SSL 证书的 CA 证书" + +#: awx/main/credential_plugins/hashivault.py:38 +msgid "Path to Secret" +msgstr "机密的路径" + +#: awx/main/credential_plugins/hashivault.py:40 +msgid "The path to the secret stored in the secret backend e.g, /some/secret/" +msgstr "存储在机密后端的机密路径,如 /some/secret/" + +#: awx/main/credential_plugins/hashivault.py:48 +msgid "API Version" +msgstr "API 版本" + +#: awx/main/credential_plugins/hashivault.py:50 +msgid "" +"API v1 is for static key/value lookups. API v2 is for versioned key/value " +"lookups." +msgstr "API v1 用于静态键/值查找。API v2 用于版本化的键/值查找。" + +#: awx/main/credential_plugins/hashivault.py:55 +msgid "Name of Secret Backend" +msgstr "机密后端名称" + +#: awx/main/credential_plugins/hashivault.py:57 +msgid "" +"The name of the kv secret backend (if left empty, the first segment of the " +"secret path will be used)." +msgstr "kv 机密后端的名称(如果留空,将使用机密路径的第一个分段)。" + +#: awx/main/credential_plugins/hashivault.py:60 +#: awx/main/models/inventory.py:1023 +msgid "Key Name" +msgstr "密钥名称" + +#: awx/main/credential_plugins/hashivault.py:62 +msgid "The name of the key to look up in the secret." +msgstr "在机密中查找的密钥名称。" + +#: awx/main/credential_plugins/hashivault.py:65 +msgid "Secret Version (v2 only)" +msgstr "机密版本(仅限 v2)" + +#: awx/main/credential_plugins/hashivault.py:74 +msgid "Unsigned Public Key" +msgstr "未签名的公钥" + +#: awx/main/credential_plugins/hashivault.py:79 +msgid "Role Name" +msgstr "角色名称" + +#: awx/main/credential_plugins/hashivault.py:81 +msgid "The name of the role used to sign." +msgstr "用于签名的角色的名称。" + +#: awx/main/credential_plugins/hashivault.py:84 +msgid "Valid Principals" +msgstr "有效主体" + +#: awx/main/credential_plugins/hashivault.py:86 +msgid "" +"Valid principals (either usernames or hostnames) that the certificate should " +"be signed for." +msgstr "应该为之签署证书的有效主体(用户名或主机名)。" + +#: awx/main/fields.py:67 +#, python-brace-format +msgid "'{value}' is not one of ['{allowed_values}']" +msgstr "'{value}' 不是 ['{allowed_values}'] 之一" + +#: awx/main/fields.py:439 +#, python-brace-format +msgid "{type} provided in relative path {path}, expected {expected_type}" +msgstr "{path} 在相对路径中提供了 {type},预期为 {expected_type}" + +#: awx/main/fields.py:444 +#, python-brace-format +msgid "{type} provided, expected {expected_type}" +msgstr "提供了 {type},预期为 {expected_type}" + +#: awx/main/fields.py:449 +#, python-brace-format +msgid "Schema validation error in relative path {path} ({error})" +msgstr "相对路径 {path} ({error}) 中的模式验证错误" + +#: awx/main/fields.py:558 +#, python-format +msgid "required for %s" +msgstr "为 %s 所需" + +#: awx/main/fields.py:632 +msgid "secret values must be of type string, not {}" +msgstr "机密值必须是字符串类型,不能是 {}" + +#: awx/main/fields.py:667 +#, python-format +msgid "cannot be set unless \"%s\" is set" +msgstr "无法设置,除非设置了 \"%s\"" + +#: awx/main/fields.py:702 +msgid "must be set when SSH key is encrypted." +msgstr "必须在 SSH 密钥加密时设置。" + +#: awx/main/fields.py:710 +msgid "should not be set when SSH key is not encrypted." +msgstr "不应在 SSH 密钥没有加密时设置。" + +#: awx/main/fields.py:769 +msgid "'dependencies' is not supported for custom credentials." +msgstr "'dependencies' 不支持用于自定义凭证。" + +#: awx/main/fields.py:783 +msgid "\"tower\" is a reserved field name" +msgstr "\"tower\" 是保留字段名称" + +#: awx/main/fields.py:790 +#, python-format +msgid "field IDs must be unique (%s)" +msgstr "字段 ID 必须是唯一的 (%s)" + +#: awx/main/fields.py:805 +msgid "{} is not a {}" +msgstr "{} 不是 {}" + +#: awx/main/fields.py:811 +#, python-brace-format +msgid "{sub_key} not allowed for {element_type} type ({element_id})" +msgstr "{sub_key} 不允许用于 {element_type} 类型 ({element_id})" + +#: awx/main/fields.py:869 +msgid "" +"Environment variable {} may affect Ansible configuration so its use is not " +"allowed in credentials." +msgstr "环境变量 {} 可能会影响 Ansible 配置,因此在凭证中不允许使用它。" + +#: awx/main/fields.py:875 +msgid "Environment variable {} is blacklisted from use in credentials." +msgstr "环境变量 {} 列入了禁止在凭证中使用的黑名单。" + +#: awx/main/fields.py:903 +msgid "" +"Must define unnamed file injector in order to reference `tower.filename`." +msgstr "必须定义未命名的文件注入程序,以便引用 `tower.filename`。" + +#: awx/main/fields.py:910 +msgid "Cannot directly reference reserved `tower` namespace container." +msgstr "无法直接引用保留的 `tower` 命名空间容器。" + +#: awx/main/fields.py:920 +msgid "Must use multi-file syntax when injecting multiple files" +msgstr "在注入多个文件时必须使用多文件语法" + +#: awx/main/fields.py:940 +#, python-brace-format +msgid "{sub_key} uses an undefined field ({error_msg})" +msgstr "{sub_key} 使用了未定义字段 ({error_msg})" + +#: awx/main/fields.py:947 +#, python-brace-format +msgid "" +"Syntax error rendering template for {sub_key} inside of {type} ({error_msg})" +msgstr "为 {type} 内的 {sub_key} 呈现模板时出现语法错误 ({error_msg})" + +#: awx/main/middleware.py:118 +msgid "Formats of all available named urls" +msgstr "所有可用命名 url 的格式" + +#: awx/main/middleware.py:119 +msgid "" +"Read-only list of key-value pairs that shows the standard format of all " +"available named URLs." +msgstr "键值对的只读列表,显示所有可用命名 URL 的标准格式。" + +#: awx/main/middleware.py:121 awx/main/middleware.py:131 +msgid "Named URL" +msgstr "命名 URL" + +#: awx/main/middleware.py:128 +msgid "List of all named url graph nodes." +msgstr "所有命名 url 图形节点的列表。" + +#: awx/main/middleware.py:129 +msgid "" +"Read-only list of key-value pairs that exposes named URL graph topology. Use " +"this list to programmatically generate named URLs for resources" +msgstr "键值对的只读列表,显示命名 URL 图形拓扑。使用此列表以编程方式为资源生成命名 URL。" + +#: awx/main/models/activity_stream.py:28 +msgid "Entity Created" +msgstr "已创建实体" + +#: awx/main/models/activity_stream.py:29 +msgid "Entity Updated" +msgstr "已更新实体" + +#: awx/main/models/activity_stream.py:30 +msgid "Entity Deleted" +msgstr "已删除实体" + +#: awx/main/models/activity_stream.py:31 +msgid "Entity Associated with another Entity" +msgstr "与另一个实体关联的实体" + +#: awx/main/models/activity_stream.py:32 +msgid "Entity was Disassociated with another Entity" +msgstr "实体已与另一实体解除关联" + +#: awx/main/models/activity_stream.py:45 +msgid "The cluster node the activity took place on." +msgstr "发生活动的集群节点。" + +#: awx/main/models/ad_hoc_commands.py:97 +msgid "No valid inventory." +msgstr "没有有效的清单。" + +#: awx/main/models/ad_hoc_commands.py:104 +msgid "You must provide a machine / SSH credential." +msgstr "您必须提供机器 / SSH 凭证。" + +#: awx/main/models/ad_hoc_commands.py:115 +#: awx/main/models/ad_hoc_commands.py:123 +msgid "Invalid type for ad hoc command" +msgstr "对临时命令无效的类型" + +#: awx/main/models/ad_hoc_commands.py:118 +msgid "Unsupported module for ad hoc commands." +msgstr "不支持用于临时命令的模块。" + +#: awx/main/models/ad_hoc_commands.py:126 +#, python-format +msgid "No argument passed to %s module." +msgstr "没有将参数传递给 %s 模块。" + +#: awx/main/models/base.py:33 awx/main/models/base.py:39 +#: awx/main/models/base.py:44 awx/main/models/base.py:49 +msgid "Run" +msgstr "运行" + +#: awx/main/models/base.py:34 awx/main/models/base.py:40 +#: awx/main/models/base.py:45 awx/main/models/base.py:50 +msgid "Check" +msgstr "检查" + +#: awx/main/models/base.py:35 +msgid "Scan" +msgstr "扫描" + +#: awx/main/models/credential/__init__.py:96 +msgid "" +"Specify the type of credential you want to create. Refer to the Ansible " +"Tower documentation for details on each type." +msgstr "指定您要创建的凭证类型。有关每种类型的详情,请参阅 Ansible Tower 文档。" + +#: awx/main/models/credential/__init__.py:110 +#: awx/main/models/credential/__init__.py:353 +msgid "" +"Enter inputs using either JSON or YAML syntax. Refer to the Ansible Tower " +"documentation for example syntax." +msgstr "使用 JSON 或 YAML 语法进行输入。示例语法请参阅 Ansible Tower 文档。" + +#: awx/main/models/credential/__init__.py:325 +#: awx/main/models/credential/__init__.py:594 +msgid "Machine" +msgstr "机器" + +#: awx/main/models/credential/__init__.py:326 +#: awx/main/models/credential/__init__.py:680 +msgid "Vault" +msgstr "Vault" + +#: awx/main/models/credential/__init__.py:327 +#: awx/main/models/credential/__init__.py:707 +msgid "Network" +msgstr "网络" + +#: awx/main/models/credential/__init__.py:328 +#: awx/main/models/credential/__init__.py:649 +msgid "Source Control" +msgstr "源控制" + +#: awx/main/models/credential/__init__.py:329 +msgid "Cloud" +msgstr "云" + +#: awx/main/models/credential/__init__.py:330 +msgid "Personal Access Token" +msgstr "个人访问令牌" + +#: awx/main/models/credential/__init__.py:331 +#: awx/main/models/credential/__init__.py:1015 +msgid "Insights" +msgstr "Insights" + +#: awx/main/models/credential/__init__.py:332 +msgid "External" +msgstr "外部" + +#: awx/main/models/credential/__init__.py:333 +msgid "Kubernetes" +msgstr "Kubernetes" + +#: awx/main/models/credential/__init__.py:359 +msgid "" +"Enter injectors using either JSON or YAML syntax. Refer to the Ansible Tower " +"documentation for example syntax." +msgstr "使用 JSON 或 YAML 语法输入注入程序。示例语法请参阅 Ansible Tower 文档。" + +#: awx/main/models/credential/__init__.py:428 +#, python-format +msgid "adding %s credential type" +msgstr "添加 %s 凭证类型" + +#: awx/main/models/credential/__init__.py:602 +#: awx/main/models/credential/__init__.py:658 +#: awx/main/models/credential/__init__.py:716 +#: awx/main/models/credential/__init__.py:838 +#: awx/main/models/credential/__init__.py:864 +#: awx/main/models/credential/__init__.py:891 +#: awx/main/models/credential/__init__.py:951 +#: awx/main/models/credential/__init__.py:1024 +#: awx/main/models/credential/__init__.py:1055 +#: awx/main/models/credential/__init__.py:1105 +msgid "Password" +msgstr "密码" + +#: awx/main/models/credential/__init__.py:608 +#: awx/main/models/credential/__init__.py:721 +msgid "SSH Private Key" +msgstr "SSH 私钥" + +#: awx/main/models/credential/__init__.py:615 +msgid "Signed SSH Certificate" +msgstr "签名的 SSH 证书" + +#: awx/main/models/credential/__init__.py:621 +#: awx/main/models/credential/__init__.py:670 +#: awx/main/models/credential/__init__.py:728 +msgid "Private Key Passphrase" +msgstr "私钥密码" + +#: awx/main/models/credential/__init__.py:627 +msgid "Privilege Escalation Method" +msgstr "权限升级方法" + +#: awx/main/models/credential/__init__.py:629 +msgid "" +"Specify a method for \"become\" operations. This is equivalent to specifying " +"the --become-method Ansible parameter." +msgstr "指定 \"become\" 操作的方法。这等同于指定 --become-method Ansible 参数。" + +#: awx/main/models/credential/__init__.py:634 +msgid "Privilege Escalation Username" +msgstr "权限升级用户名" + +#: awx/main/models/credential/__init__.py:638 +msgid "Privilege Escalation Password" +msgstr "权限升级密码" + +#: awx/main/models/credential/__init__.py:663 +msgid "SCM Private Key" +msgstr "SCM 私钥" + +#: awx/main/models/credential/__init__.py:685 +msgid "Vault Password" +msgstr "Vault 密码" + +#: awx/main/models/credential/__init__.py:691 +msgid "Vault Identifier" +msgstr "Vault 标识符" + +#: awx/main/models/credential/__init__.py:694 +msgid "" +"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+." +msgstr "指定(可选)Vault ID。这等同于为提供多个 Vault 密码指定 --vault-id Ansible 参数。请注意:此功能只在 Ansible 2.4+ 中有效。" + +#: awx/main/models/credential/__init__.py:733 +msgid "Authorize" +msgstr "授权" + +#: awx/main/models/credential/__init__.py:737 +msgid "Authorize Password" +msgstr "授权密码" + +#: awx/main/models/credential/__init__.py:751 +msgid "Amazon Web Services" +msgstr "Amazon Web Services" + +#: awx/main/models/credential/__init__.py:756 +msgid "Access Key" +msgstr "访问密钥" + +#: awx/main/models/credential/__init__.py:760 +msgid "Secret Key" +msgstr "机密密钥" + +#: awx/main/models/credential/__init__.py:765 +msgid "STS Token" +msgstr "STS 令牌" + +#: awx/main/models/credential/__init__.py:768 +msgid "" +"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." +msgstr "安全令牌服务 (STS) 是一个 Web 服务,让您可以为 AWS 身份和访问管理 (IAM) 用户请求临时的有限权限凭证。" + +#: awx/main/models/credential/__init__.py:780 awx/main/models/inventory.py:833 +msgid "OpenStack" +msgstr "OpenStack" + +#: awx/main/models/credential/__init__.py:789 +msgid "Password (API Key)" +msgstr "密码(API 密钥)" + +#: awx/main/models/credential/__init__.py:794 +#: awx/main/models/credential/__init__.py:1046 +msgid "Host (Authentication URL)" +msgstr "主机(身份验证 URL)" + +#: awx/main/models/credential/__init__.py:796 +msgid "" +"The host to authenticate with. For example, https://openstack.business.com/" +"v2.0/" +msgstr "要进行身份验证的主机。例如:https://openstack.business.com/v_2.0/" + +#: awx/main/models/credential/__init__.py:800 +msgid "Project (Tenant Name)" +msgstr "项目(租户名称)" + +#: awx/main/models/credential/__init__.py:804 +msgid "Domain Name" +msgstr "域名" + +#: awx/main/models/credential/__init__.py:806 +msgid "" +"OpenStack domains define administrative boundaries. It is only needed for " +"Keystone v3 authentication URLs. Refer to Ansible Tower documentation for " +"common scenarios." +msgstr "OpenStack 域定义了管理边界。只有 Keystone v3 身份验证 URL 需要域。常见的情景请参阅 Ansible Tower 文档。" + +#: awx/main/models/credential/__init__.py:812 +#: awx/main/models/credential/__init__.py:1110 +#: awx/main/models/credential/__init__.py:1144 +msgid "Verify SSL" +msgstr "验证 SSL" + +#: awx/main/models/credential/__init__.py:823 awx/main/models/inventory.py:830 +msgid "VMware vCenter" +msgstr "VMware vCenter" + +#: awx/main/models/credential/__init__.py:828 +msgid "VCenter Host" +msgstr "vCenter 主机" + +#: awx/main/models/credential/__init__.py:830 +msgid "" +"Enter the hostname or IP address that corresponds to your VMware vCenter." +msgstr "输入与 VMware vCenter 对应的主机名或 IP 地址。" + +#: awx/main/models/credential/__init__.py:849 awx/main/models/inventory.py:831 +msgid "Red Hat Satellite 6" +msgstr "红帽卫星 6" + +#: awx/main/models/credential/__init__.py:854 +msgid "Satellite 6 URL" +msgstr "卫星 6 URL" + +#: awx/main/models/credential/__init__.py:856 +msgid "" +"Enter the URL that corresponds to your Red Hat Satellite 6 server. For " +"example, https://satellite.example.org" +msgstr "输入与您的红帽卫星 6 服务器对应的 URL。例如:https://satellite.example.org" + +#: awx/main/models/credential/__init__.py:875 awx/main/models/inventory.py:832 +msgid "Red Hat CloudForms" +msgstr "Red Hat CloudForms" + +#: awx/main/models/credential/__init__.py:880 +msgid "CloudForms URL" +msgstr "CloudForms URL" + +#: awx/main/models/credential/__init__.py:882 +msgid "" +"Enter the URL for the virtual machine that corresponds to your CloudForms " +"instance. For example, https://cloudforms.example.org" +msgstr "输入与您的 CloudForms 实例对应的虚拟机的 URL。例如:https://cloudforms.example.org" + +#: awx/main/models/credential/__init__.py:902 awx/main/models/inventory.py:828 +msgid "Google Compute Engine" +msgstr "Google Compute Engine" + +#: awx/main/models/credential/__init__.py:907 +msgid "Service Account Email Address" +msgstr "服务账户电子邮件地址" + +#: awx/main/models/credential/__init__.py:909 +msgid "" +"The email address assigned to the Google Compute Engine service account." +msgstr "分配给 Google Compute Engine 服务账户的电子邮件地址。" + +#: awx/main/models/credential/__init__.py:915 +msgid "" +"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" +msgstr "项目 ID 是 GCE 分配的标识。它通常由两三个单词构成,后跟三位数字。示例:project-id-000 和 another-project-id" + +#: awx/main/models/credential/__init__.py:921 +msgid "RSA Private Key" +msgstr "RSA 私钥" + +#: awx/main/models/credential/__init__.py:926 +msgid "" +"Paste the contents of the PEM file associated with the service account email." +msgstr "粘贴与服务账户电子邮件关联的 PEM 文件的内容。" + +#: awx/main/models/credential/__init__.py:936 awx/main/models/inventory.py:829 +msgid "Microsoft Azure Resource Manager" +msgstr "Microsoft Azure Resource Manager" + +#: awx/main/models/credential/__init__.py:941 +msgid "Subscription ID" +msgstr "订阅 ID" + +#: awx/main/models/credential/__init__.py:943 +msgid "Subscription ID is an Azure construct, which is mapped to a username." +msgstr "订阅 ID 是一个 Azure 构造函数,它映射到一个用户名。" + +#: awx/main/models/credential/__init__.py:969 +msgid "Azure Cloud Environment" +msgstr "Azure 云环境" + +#: awx/main/models/credential/__init__.py:971 +msgid "" +"Environment variable AZURE_CLOUD_ENVIRONMENT when using Azure GovCloud or " +"Azure stack." +msgstr "使用 Azure GovCloud 或 Azure 堆栈时的环境变量 Azure_CLOUD_ENVIRONMENT。" + +#: awx/main/models/credential/__init__.py:981 +msgid "GitHub Personal Access Token" +msgstr "GitHub 个人访问令牌" + +#: awx/main/models/credential/__init__.py:989 +msgid "This token needs to come from your profile settings in GitHub" +msgstr "此令牌需要来自您在 GitHub 中的配置文件设置" + +#: awx/main/models/credential/__init__.py:998 +msgid "GitLab Personal Access Token" +msgstr "GitLab 个人访问令牌" + +#: awx/main/models/credential/__init__.py:1006 +msgid "This token needs to come from your profile settings in GitLab" +msgstr "此令牌需要来自您在 GitLab 中的配置文件设置" + +#: awx/main/models/credential/__init__.py:1041 awx/main/models/inventory.py:834 +msgid "Red Hat Virtualization" +msgstr "红帽虚拟化" + +#: awx/main/models/credential/__init__.py:1048 +msgid "The host to authenticate with." +msgstr "要进行验证的主机。" + +#: awx/main/models/credential/__init__.py:1060 +msgid "CA File" +msgstr "CA 文件" + +#: awx/main/models/credential/__init__.py:1062 +msgid "Absolute file path to the CA file to use (optional)" +msgstr "要使用的 CA 文件的绝对文件路径(可选)" + +#: awx/main/models/credential/__init__.py:1091 awx/main/models/inventory.py:835 +msgid "Ansible Tower" +msgstr "Ansible Tower" + +#: awx/main/models/credential/__init__.py:1096 +msgid "Ansible Tower Hostname" +msgstr "Ansible Tower 主机名" + +#: awx/main/models/credential/__init__.py:1098 +msgid "The Ansible Tower base URL to authenticate with." +msgstr "要进行身份验证的 Ansible Tower 基本 URL。" + +#: awx/main/models/credential/__init__.py:1130 +msgid "OpenShift or Kubernetes API Bearer Token" +msgstr "OpenShift 或 Kubernetes API 持有者令牌" + +#: awx/main/models/credential/__init__.py:1134 +msgid "OpenShift or Kubernetes API Endpoint" +msgstr "OpenShift 或 Kubernetes API 端点" + +#: awx/main/models/credential/__init__.py:1136 +msgid "The OpenShift or Kubernetes API Endpoint to authenticate with." +msgstr "要进行身份验证的 OpenShift 或 Kubernetes API 端点。" + +#: awx/main/models/credential/__init__.py:1139 +msgid "API authentication bearer token" +msgstr "API 身份验证持有者令牌" + +#: awx/main/models/credential/__init__.py:1149 +msgid "Certificate Authority data" +msgstr "证书颁发机构数据" + +#: awx/main/models/credential/__init__.py:1190 +msgid "Target must be a non-external credential" +msgstr "目标必须是非外部凭证" + +#: awx/main/models/credential/__init__.py:1195 +msgid "Source must be an external credential" +msgstr "源必须是外部凭证" + +#: awx/main/models/credential/__init__.py:1202 +msgid "Input field must be defined on target credential (options are {})." +msgstr "输入字段必须在目标凭证上定义(选项为 {})。" + +#: awx/main/models/events.py:152 awx/main/models/events.py:674 +msgid "Host Failed" +msgstr "主机故障" + +#: awx/main/models/events.py:153 +msgid "Host Started" +msgstr "主机已启动" + +#: awx/main/models/events.py:154 awx/main/models/events.py:675 +msgid "Host OK" +msgstr "主机正常" + +#: awx/main/models/events.py:155 +msgid "Host Failure" +msgstr "主机故障" + +#: awx/main/models/events.py:156 awx/main/models/events.py:681 +msgid "Host Skipped" +msgstr "主机已跳过" + +#: awx/main/models/events.py:157 awx/main/models/events.py:676 +msgid "Host Unreachable" +msgstr "主机无法访问" + +#: awx/main/models/events.py:158 awx/main/models/events.py:172 +msgid "No Hosts Remaining" +msgstr "没有剩余主机" + +#: awx/main/models/events.py:159 +msgid "Host Polling" +msgstr "主机轮询" + +#: awx/main/models/events.py:160 +msgid "Host Async OK" +msgstr "主机异步正常" + +#: awx/main/models/events.py:161 +msgid "Host Async Failure" +msgstr "主机同步故障" + +#: awx/main/models/events.py:162 +msgid "Item OK" +msgstr "项正常" + +#: awx/main/models/events.py:163 +msgid "Item Failed" +msgstr "项故障" + +#: awx/main/models/events.py:164 +msgid "Item Skipped" +msgstr "项已跳过" + +#: awx/main/models/events.py:165 +msgid "Host Retry" +msgstr "主机重试" + +#: awx/main/models/events.py:167 +msgid "File Difference" +msgstr "文件差异" + +#: awx/main/models/events.py:168 +msgid "Playbook Started" +msgstr "Playbook 已启动" + +#: awx/main/models/events.py:169 +msgid "Running Handlers" +msgstr "正在运行的处理程序" + +#: awx/main/models/events.py:170 +msgid "Including File" +msgstr "包含文件" + +#: awx/main/models/events.py:171 +msgid "No Hosts Matched" +msgstr "未匹配主机" + +#: awx/main/models/events.py:173 +msgid "Task Started" +msgstr "任务已启动" + +#: awx/main/models/events.py:175 +msgid "Variables Prompted" +msgstr "提示变量" + +#: awx/main/models/events.py:176 +msgid "Gathering Facts" +msgstr "收集事实" + +#: awx/main/models/events.py:177 +msgid "internal: on Import for Host" +msgstr "内部:导入主机时" + +#: awx/main/models/events.py:178 +msgid "internal: on Not Import for Host" +msgstr "内部:不为主机导入时" + +#: awx/main/models/events.py:179 +msgid "Play Started" +msgstr "Play 已启动" + +#: awx/main/models/events.py:180 +msgid "Playbook Complete" +msgstr "Playbook 完成" + +#: awx/main/models/events.py:184 awx/main/models/events.py:691 +msgid "Debug" +msgstr "调试" + +#: awx/main/models/events.py:185 awx/main/models/events.py:692 +msgid "Verbose" +msgstr "详细" + +#: awx/main/models/events.py:186 awx/main/models/events.py:693 +msgid "Deprecated" +msgstr "已弃用" + +#: awx/main/models/events.py:187 awx/main/models/events.py:694 +msgid "Warning" +msgstr "警告" + +#: awx/main/models/events.py:188 awx/main/models/events.py:695 +msgid "System Warning" +msgstr "系统警告" + +#: awx/main/models/events.py:189 awx/main/models/events.py:696 +#: awx/main/models/unified_jobs.py:75 +msgid "Error" +msgstr "错误" + +#: awx/main/models/ha.py:175 +msgid "Instances that are members of this InstanceGroup" +msgstr "属于此实例组成员的实例" + +#: awx/main/models/ha.py:180 +msgid "Instance Group to remotely control this group." +msgstr "远程控制此组的实例组。" + +#: awx/main/models/ha.py:200 +msgid "Percentage of Instances to automatically assign to this group" +msgstr "自动分配给此组的实例百分比" + +#: awx/main/models/ha.py:204 +msgid "" +"Static minimum number of Instances to automatically assign to this group" +msgstr "自动分配给此组的静态最小实例数量" + +#: awx/main/models/ha.py:209 +msgid "" +"List of exact-match Instances that will always be automatically assigned to " +"this group" +msgstr "将始终自动分配给此组的完全匹配实例的列表" + +#: awx/main/models/inventory.py:80 +msgid "Hosts have a direct link to this inventory." +msgstr "主机具有指向此清单的直接链接。" + +#: awx/main/models/inventory.py:81 +msgid "Hosts for inventory generated using the host_filter property." +msgstr "使用 host_filter 属性生成的清单的主机。" + +#: awx/main/models/inventory.py:86 +msgid "inventories" +msgstr "清单" + +#: awx/main/models/inventory.py:93 +msgid "Organization containing this inventory." +msgstr "包含此清单的机构。" + +#: awx/main/models/inventory.py:100 +msgid "Inventory variables in JSON or YAML format." +msgstr "JSON 或 YAML 格式的清单变量。" + +#: awx/main/models/inventory.py:105 +msgid "" +"This field is deprecated and will be removed in a future release. Flag " +"indicating whether any hosts in this inventory have failed." +msgstr "此字段已弃用,并将在以后的发行版本中删除。指示此清单中是否有任何主机故障的标记。" + +#: awx/main/models/inventory.py:111 +msgid "" +"This field is deprecated and will be removed in a future release. Total " +"number of hosts in this inventory." +msgstr "此字段已弃用,并将在以后的发行版本中删除。此清单中的主机总数。" + +#: awx/main/models/inventory.py:117 +msgid "" +"This field is deprecated and will be removed in a future release. Number of " +"hosts in this inventory with active failures." +msgstr "此字段已弃用,并将在以后的发行版本中删除。此清单中有活跃故障的主机数量。" + +#: awx/main/models/inventory.py:123 +msgid "" +"This field is deprecated and will be removed in a future release. Total " +"number of groups in this inventory." +msgstr "此字段已弃用,并将在以后的发行版本中删除。此清单中的总组数。" + +#: awx/main/models/inventory.py:129 +msgid "" +"This field is deprecated and will be removed in a future release. Flag " +"indicating whether this inventory has any external inventory sources." +msgstr "此字段已弃用,并将在以后的发行版本中删除。表示此清单是否有任何外部清单源的标记。" + +#: awx/main/models/inventory.py:135 +msgid "" +"Total number of external inventory sources configured within this inventory." +msgstr "在此清单中配置的外部清单源总数。" + +#: awx/main/models/inventory.py:140 +msgid "Number of external inventory sources in this inventory with failures." +msgstr "此清单中有故障的外部清单源数量。" + +#: awx/main/models/inventory.py:147 +msgid "Kind of inventory being represented." +msgstr "所代表的清单种类。" + +#: awx/main/models/inventory.py:153 +msgid "Filter that will be applied to the hosts of this inventory." +msgstr "将应用到此清单的主机的过滤器。" + +#: awx/main/models/inventory.py:181 +msgid "" +"Credentials to be used by hosts belonging to this inventory when accessing " +"Red Hat Insights API." +msgstr "访问红帽 Insights API 时供属于此清单的主机使用的凭证。" + +#: awx/main/models/inventory.py:190 +msgid "Flag indicating the inventory is being deleted." +msgstr "指示正在删除清单的标记。" + +#: awx/main/models/inventory.py:245 +msgid "Could not parse subset as slice specification." +msgstr "无法将子集作为分片规格来解析。" + +#: awx/main/models/inventory.py:249 +msgid "Slice number must be less than total number of slices." +msgstr "分片数量必须小于分片总数。" + +#: awx/main/models/inventory.py:251 +msgid "Slice number must be 1 or higher." +msgstr "分片数量必须为 1 或更高。" + +#: awx/main/models/inventory.py:388 +msgid "Assignment not allowed for Smart Inventory" +msgstr "智能清单不允许分配" + +#: awx/main/models/inventory.py:390 awx/main/models/projects.py:166 +msgid "Credential kind must be 'insights'." +msgstr "凭证种类必须是 'inights'。" + +#: awx/main/models/inventory.py:475 +msgid "Is this host online and available for running jobs?" +msgstr "此主机是否在线,并可用于运行作业?" + +#: awx/main/models/inventory.py:481 +msgid "" +"The value used by the remote inventory source to uniquely identify the host" +msgstr "远程清单源用来唯一标识主机的值" + +#: awx/main/models/inventory.py:486 +msgid "Host variables in JSON or YAML format." +msgstr "JSON 或 YAML 格式的主机变量。" + +#: awx/main/models/inventory.py:509 +msgid "Inventory source(s) that created or modified this host." +msgstr "创建或修改此主机的清单源。" + +#: awx/main/models/inventory.py:514 +msgid "Arbitrary JSON structure of most recent ansible_facts, per-host." +msgstr "每个主机最近的 ansible_facts 的任意 JSON 结构。" + +#: awx/main/models/inventory.py:520 +msgid "The date and time ansible_facts was last modified." +msgstr "最后修改 ansible_facts 的日期和时间。" + +#: awx/main/models/inventory.py:527 +msgid "Red Hat Insights host unique identifier." +msgstr "红帽 Insights 主机唯一标识符。" + +#: awx/main/models/inventory.py:641 +msgid "Group variables in JSON or YAML format." +msgstr "JSON 或 YAML 格式的组变量。" + +#: awx/main/models/inventory.py:647 +msgid "Hosts associated directly with this group." +msgstr "与此组直接关联的主机。" + +#: awx/main/models/inventory.py:653 +msgid "Inventory source(s) that created or modified this group." +msgstr "创建或修改此组的清单源。" + +#: awx/main/models/inventory.py:825 +msgid "File, Directory or Script" +msgstr "文件、目录或脚本" + +#: awx/main/models/inventory.py:826 +msgid "Sourced from a Project" +msgstr "源于项目" + +#: awx/main/models/inventory.py:827 +msgid "Amazon EC2" +msgstr "Amazon EC2" + +#: awx/main/models/inventory.py:836 +msgid "Custom Script" +msgstr "自定义脚本" + +#: awx/main/models/inventory.py:953 +msgid "Inventory source variables in YAML or JSON format." +msgstr "YAML 或 JSON 格式的清单源变量。" + +#: awx/main/models/inventory.py:964 +msgid "" +"Comma-separated list of filter expressions (EC2 only). Hosts are imported " +"when ANY of the filters match." +msgstr "以逗号分隔的过滤器表达式列表(仅限 EC2)。当任何过滤器匹配时会导入主机。" + +#: awx/main/models/inventory.py:970 +msgid "Limit groups automatically created from inventory source (EC2 only)." +msgstr "限制从清单源自动创建的组(仅限 EC2)。" + +#: awx/main/models/inventory.py:974 +msgid "Overwrite local groups and hosts from remote inventory source." +msgstr "从远程清单源覆盖本地组和主机。" + +#: awx/main/models/inventory.py:978 +msgid "Overwrite local variables from remote inventory source." +msgstr "从远程清单源覆盖本地变量。" + +#: awx/main/models/inventory.py:983 awx/main/models/jobs.py:154 +#: awx/main/models/projects.py:135 +msgid "The amount of time (in seconds) to run before the task is canceled." +msgstr "取消任务前运行的时间(以秒为单位)。" + +#: awx/main/models/inventory.py:1016 +msgid "Image ID" +msgstr "镜像 ID" + +#: awx/main/models/inventory.py:1017 +msgid "Availability Zone" +msgstr "可用性区域" + +#: awx/main/models/inventory.py:1019 +msgid "Instance ID" +msgstr "实例 ID" + +#: awx/main/models/inventory.py:1020 +msgid "Instance State" +msgstr "实例状态" + +#: awx/main/models/inventory.py:1021 +msgid "Platform" +msgstr "平台" + +#: awx/main/models/inventory.py:1022 +msgid "Instance Type" +msgstr "实例类型" + +#: awx/main/models/inventory.py:1024 +msgid "Region" +msgstr "区域" + +#: awx/main/models/inventory.py:1025 +msgid "Security Group" +msgstr "安全组" + +#: awx/main/models/inventory.py:1026 +msgid "Tags" +msgstr "标签" + +#: awx/main/models/inventory.py:1027 +msgid "Tag None" +msgstr "标签 None" + +#: awx/main/models/inventory.py:1028 +msgid "VPC ID" +msgstr "VPC ID" + +#: awx/main/models/inventory.py:1096 +#, python-format +msgid "" +"Cloud-based inventory sources (such as %s) require credentials for the " +"matching cloud service." +msgstr "基于云的清单源(如 %s)需要匹配的云服务的凭证。" + +#: awx/main/models/inventory.py:1102 +msgid "Credential is required for a cloud source." +msgstr "云源需要凭证。" + +#: awx/main/models/inventory.py:1105 +msgid "" +"Credentials of type machine, source control, insights and vault are " +"disallowed for custom inventory sources." +msgstr "对于自定义清单源,不允许使用机器、源控制、insights 和 vault 类型的凭证。" + +#: awx/main/models/inventory.py:1110 +msgid "" +"Credentials of type insights and vault are disallowed for scm inventory " +"sources." +msgstr "对于 scm 清单源,不允许使用 insights 和 vault 类型的凭证。" + +#: awx/main/models/inventory.py:1170 +#, python-format +msgid "Invalid %(source)s region: %(region)s" +msgstr "无效的 %(source)s 区域:%(region)s" + +#: awx/main/models/inventory.py:1194 +#, python-format +msgid "Invalid filter expression: %(filter)s" +msgstr "无效的过滤器表达式:%(filter)s" + +#: awx/main/models/inventory.py:1215 +#, python-format +msgid "Invalid group by choice: %(choice)s" +msgstr "选择的组无效:%(choice)s" + +#: awx/main/models/inventory.py:1243 +msgid "Project containing inventory file used as source." +msgstr "包含用作源的清单文件的项目。" + +#: awx/main/models/inventory.py:1416 +msgid "" +"More than one SCM-based inventory source with update on project update per-" +"inventory not allowed." +msgstr "不允许多个基于 SCM 的清单源按清单在项目更新时更新。" + +#: awx/main/models/inventory.py:1423 +msgid "" +"Cannot update SCM-based inventory source on launch if set to update on " +"project update. Instead, configure the corresponding source project to " +"update on launch." +msgstr "如果设置为在项目更新时更新,则无法在启动时更新基于 SCM 的清单源。应将对应的源项目配置为在启动时更新。" + +#: awx/main/models/inventory.py:1429 +msgid "Cannot set source_path if not SCM type." +msgstr "如果不是 SCM 类型,则无法设置 source_path。" + +#: awx/main/models/inventory.py:1472 +msgid "" +"Inventory files from this Project Update were used for the inventory update." +msgstr "此项目更新中的清单文件用于清单更新。" + +#: awx/main/models/inventory.py:1583 +msgid "Inventory script contents" +msgstr "清单脚本内容" + +#: awx/main/models/inventory.py:1588 +msgid "Organization owning this inventory script" +msgstr "拥有此清单脚本的机构" + +#: awx/main/models/jobs.py:74 +msgid "" +"If enabled, textual changes made to any templated files on the host are " +"shown in the standard output" +msgstr "如果启用,标准输出中会显示对主机上任何模板文件进行的文本更改。" + +#: awx/main/models/jobs.py:106 +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 时才允许使用。" + +#: awx/main/models/jobs.py:159 +msgid "" +"If enabled, Tower will act as an Ansible Fact Cache Plugin; persisting facts " +"at the end of a playbook run to the database and caching facts for use by " +"Ansible." +msgstr "如果启用,Tower 将充当 Ansible 事实缓存插件;将 playbook 末尾的事实保留到数据库,并缓存事实以供 Ansible 使用。" + +#: awx/main/models/jobs.py:260 +msgid "" +"The number of jobs to slice into at runtime. Will cause the Job Template to " +"launch a workflow if value is greater than 1." +msgstr "要在运行时分片的作业数量。如果值大于 1,将导致作业模板启动工作流。" + +#: awx/main/models/jobs.py:297 +msgid "Job Template must provide 'inventory' or allow prompting for it." +msgstr "作业模板必须提供“清单”或允许相关提示。" + +#: awx/main/models/jobs.py:308 +#, python-brace-format +msgid "Maximum number of forks ({settings.MAX_FORKS}) exceeded." +msgstr "超过了最大 fork 数量 ({settings.MAX_FORKS}) 。" + +#: awx/main/models/jobs.py:463 +msgid "Project is missing." +msgstr "项目缺失。" + +#: awx/main/models/jobs.py:467 +msgid "Project does not allow override of branch." +msgstr "项目不允许覆写分支。" + +#: awx/main/models/jobs.py:477 awx/main/models/workflow.py:545 +msgid "Field is not configured to prompt on launch." +msgstr "字段没有配置为启动时提示。" + +#: awx/main/models/jobs.py:483 +msgid "Saved launch configurations cannot provide passwords needed to start." +msgstr "保存的启动配置无法提供启动所需的密码。" + +#: awx/main/models/jobs.py:491 +msgid "Job Template {} is missing or undefined." +msgstr "作业模板 {} 缺失或未定义。" + +#: awx/main/models/jobs.py:574 awx/main/models/projects.py:278 +#: awx/main/models/projects.py:497 +msgid "SCM Revision" +msgstr "SCM 修订" + +#: awx/main/models/jobs.py:575 +msgid "The SCM Revision from the Project used for this job, if available" +msgstr "用于此作业的项目中的 SCM 修订(如果可用)" + +#: awx/main/models/jobs.py:583 +msgid "" +"The SCM Refresh task used to make sure the playbooks were available for the " +"job run" +msgstr "用于确保 playbook 可用于作业运行的 SCM 刷新任务" + +#: awx/main/models/jobs.py:588 +msgid "" +"If part of a sliced job, the ID of the inventory slice operated on. If not " +"part of sliced job, parameter is not used." +msgstr "如果是分片作业的一部分,则为所操作的清单分片的 ID。如果不是分片作业的一部分,则不使用参数。" + +#: awx/main/models/jobs.py:594 +msgid "" +"If ran as part of sliced jobs, the total number of slices. If 1, job is not " +"part of a sliced job." +msgstr "如果作为分片作业的一部分运行,则为分片总数。如果为 1,则作业不是分片作业的一部分。" + +#: awx/main/models/jobs.py:676 +#, python-brace-format +msgid "{status_value} is not a valid status option." +msgstr "{status_value} 不是有效的状态选项。" + +#: awx/main/models/jobs.py:926 +msgid "" +"Inventory applied as a prompt, assuming job template prompts for inventory" +msgstr "作为提示而应用的清单,假定作业模板提示提供清单" + +#: awx/main/models/jobs.py:1085 +msgid "job host summaries" +msgstr "作业主机摘要" + +#: awx/main/models/jobs.py:1144 +msgid "Remove jobs older than a certain number of days" +msgstr "删除超过特定天数的作业" + +#: awx/main/models/jobs.py:1145 +msgid "Remove activity stream entries older than a certain number of days" +msgstr "删除比特定天数旧的活动流条目" + +#: awx/main/models/jobs.py:1146 +msgid "Removes expired browser sessions from the database" +msgstr "从数据库中删除已过期的浏览器会话" + +#: awx/main/models/jobs.py:1147 +msgid "Removes expired OAuth 2 access tokens and refresh tokens" +msgstr "删除已过期的 OAuth 2 访问令牌并刷新令牌" + +#: awx/main/models/jobs.py:1217 +#, python-brace-format +msgid "Variables {list_of_keys} are not allowed for system jobs." +msgstr "系统作业不允许使用变量 {list_of_keys}。" + +#: awx/main/models/jobs.py:1233 +msgid "days must be a positive integer." +msgstr "天必须为正整数。" + +#: awx/main/models/label.py:29 +msgid "Organization this label belongs to." +msgstr "此标签属于的机构。" + +#: awx/main/models/mixins.py:321 +#, python-brace-format +msgid "" +"Variables {list_of_keys} are not allowed on launch. Check the Prompt on " +"Launch setting on the {model_name} to include Extra Variables." +msgstr "启动时不允许使用变量 {list_of_keys}。在 {model_name} 上选中“启动时提示”设置,以包含额外变量。" + +#: awx/main/models/mixins.py:453 +msgid "Local absolute file path containing a custom Python virtualenv to use" +msgstr "本地绝对文件路径,包含要使用的自定义 Python virtualenv" + +#: awx/main/models/mixins.py:460 +msgid "{} is not a valid virtualenv in {}" +msgstr "{} 在 {} 中不是有效的 virtualenv" + +#: awx/main/models/mixins.py:507 awx/main/models/mixins.py:551 +msgid "Service that webhook requests will be accepted from" +msgstr "Webhook 请求的服务将被接受" + +#: awx/main/models/mixins.py:512 +msgid "Shared secret that the webhook service will use to sign requests" +msgstr "Webhook 服务将用于签署请求的共享机密" + +#: awx/main/models/mixins.py:520 awx/main/models/mixins.py:559 +msgid "Personal Access Token for posting back the status to the service API" +msgstr "将状态发回服务 API 的个人访问令牌" + +#: awx/main/models/mixins.py:564 +msgid "Unique identifier of the event that triggered this webhook" +msgstr "触发此 Webhook 的事件的唯一标识符" + +#: awx/main/models/notifications.py:42 +msgid "Email" +msgstr "电子邮件" + +#: awx/main/models/notifications.py:43 +msgid "Slack" +msgstr "Slack" + +#: awx/main/models/notifications.py:44 +msgid "Twilio" +msgstr "Twilio" + +#: awx/main/models/notifications.py:45 +msgid "Pagerduty" +msgstr "Pagerduty" + +#: awx/main/models/notifications.py:46 +msgid "Grafana" +msgstr "Grafana" + +#: awx/main/models/notifications.py:47 +msgid "HipChat" +msgstr "HipChat" + +#: awx/main/models/notifications.py:48 awx/main/models/unified_jobs.py:545 +msgid "Webhook" +msgstr "Webhook" + +#: awx/main/models/notifications.py:49 +msgid "Mattermost" +msgstr "Mattermost" + +#: awx/main/models/notifications.py:50 +msgid "Rocket.Chat" +msgstr "Rocket.Chat" + +#: awx/main/models/notifications.py:51 +msgid "IRC" +msgstr "IRC" + +#: awx/main/models/notifications.py:82 +msgid "Optional custom messages for notification template." +msgstr "通知模板的可选自定义消息。" + +#: awx/main/models/notifications.py:212 awx/main/models/unified_jobs.py:70 +msgid "Pending" +msgstr "待处理" + +#: awx/main/models/notifications.py:213 awx/main/models/unified_jobs.py:73 +msgid "Successful" +msgstr "成功" + +#: awx/main/models/notifications.py:214 awx/main/models/unified_jobs.py:74 +msgid "Failed" +msgstr "失败" + +#: awx/main/models/notifications.py:467 +msgid "status must be either running, succeeded or failed" +msgstr "状态必须是正在运行、成功或失败" + +#: awx/main/models/oauth.py:33 +msgid "application" +msgstr "应用" + +#: awx/main/models/oauth.py:40 +msgid "Confidential" +msgstr "机密" + +#: awx/main/models/oauth.py:41 +msgid "Public" +msgstr "公开" + +#: awx/main/models/oauth.py:47 +msgid "Authorization code" +msgstr "授权代码" + +#: awx/main/models/oauth.py:48 +msgid "Resource owner password-based" +msgstr "基于资源所有者密码" + +#: awx/main/models/oauth.py:63 +msgid "Organization containing this application." +msgstr "包含此应用的机构。" + +#: awx/main/models/oauth.py:72 +msgid "" +"Used for more stringent verification of access to an application when " +"creating a token." +msgstr "用于在创建令牌时更严格地验证对应用的访问。" + +#: awx/main/models/oauth.py:77 +msgid "" +"Set to Public or Confidential depending on how secure the client device is." +msgstr "根据客户端设备的安全情况,设置为公共或机密。" + +#: awx/main/models/oauth.py:81 +msgid "" +"Set True to skip authorization step for completely trusted applications." +msgstr "设为 True 可为完全可信的应用跳过授权步骤。" + +#: awx/main/models/oauth.py:86 +msgid "" +"The Grant type the user must use for acquire tokens for this application." +msgstr "用户必须用来获取此应用令牌的授予类型。" + +#: awx/main/models/oauth.py:94 +msgid "access token" +msgstr "访问令牌" + +#: awx/main/models/oauth.py:103 +msgid "The user representing the token owner" +msgstr "代表令牌所有者的用户" + +#: awx/main/models/oauth.py:117 +msgid "" +"Allowed scopes, further restricts user's permissions. Must be a simple space-" +"separated string with allowed scopes ['read', 'write']." +msgstr "允许的范围,进一步限制用户的权限。必须是带有允许范围 ['read', 'write'] 的简单空格分隔字符串。" + +#: awx/main/models/oauth.py:140 +msgid "" +"OAuth2 Tokens cannot be created by users associated with an external " +"authentication provider ({})" +msgstr "OAuth2 令牌不能由与外部身份验证提供商 ({}) 关联的用户创建" + +#: awx/main/models/organization.py:51 +msgid "Maximum number of hosts allowed to be managed by this organization." +msgstr "允许由此机构管理的最大主机数。" + +#: awx/main/models/projects.py:53 awx/main/models/unified_jobs.py:539 +msgid "Manual" +msgstr "手动" + +#: awx/main/models/projects.py:54 +msgid "Git" +msgstr "Git" + +#: awx/main/models/projects.py:55 +msgid "Mercurial" +msgstr "Mercurial" + +#: awx/main/models/projects.py:56 +msgid "Subversion" +msgstr "Subversion" + +#: awx/main/models/projects.py:57 +msgid "Red Hat Insights" +msgstr "红帽 Insights" + +#: awx/main/models/projects.py:83 +msgid "" +"Local path (relative to PROJECTS_ROOT) containing playbooks and related " +"files for this project." +msgstr "本地路径(与 PROJECTS_ROOT 相对),包含此项目的 playbook 及相关文件。" + +#: awx/main/models/projects.py:92 +msgid "SCM Type" +msgstr "SCM 类型" + +#: awx/main/models/projects.py:93 +msgid "Specifies the source control system used to store the project." +msgstr "指定用来存储项目的源控制系统。" + +#: awx/main/models/projects.py:99 +msgid "SCM URL" +msgstr "SCM URL" + +#: awx/main/models/projects.py:100 +msgid "The location where the project is stored." +msgstr "项目的存储位置。" + +#: awx/main/models/projects.py:106 +msgid "SCM Branch" +msgstr "SCM 分支" + +#: awx/main/models/projects.py:107 +msgid "Specific branch, tag or commit to checkout." +msgstr "特定分支、标签或提交签出。" + +#: awx/main/models/projects.py:113 +msgid "SCM refspec" +msgstr "SCM refspec" + +#: awx/main/models/projects.py:114 +msgid "For git projects, an additional refspec to fetch." +msgstr "对于 git 项目,要获取的额外 refspec。" + +#: awx/main/models/projects.py:118 +msgid "Discard any local changes before syncing the project." +msgstr "在同步项目前丢弃任何本地更改。" + +#: awx/main/models/projects.py:122 +msgid "Delete the project before syncing." +msgstr "在同步前删除项目。" + +#: awx/main/models/projects.py:151 +msgid "Invalid SCM URL." +msgstr "无效的 SCM URL。" + +#: awx/main/models/projects.py:154 +msgid "SCM URL is required." +msgstr "需要 SCM URL。" + +#: awx/main/models/projects.py:162 +msgid "Insights Credential is required for an Insights Project." +msgstr "Insights 项目需要 Insights 凭证。" + +#: awx/main/models/projects.py:168 +msgid "Credential kind must be 'scm'." +msgstr "凭证种类必须是 'scm'。" + +#: awx/main/models/projects.py:185 +msgid "Invalid credential." +msgstr "无效凭证。" + +#: awx/main/models/projects.py:259 +msgid "Update the project when a job is launched that uses the project." +msgstr "当使用项目的作业启动时更新项目。" + +#: awx/main/models/projects.py:264 +msgid "" +"The number of seconds after the last project update ran that a new project " +"update will be launched as a job dependency." +msgstr "最后一次项目更新运行后等待的秒数,此后将启动一个新项目更新作为作业依赖项。" + +#: awx/main/models/projects.py:269 +msgid "" +"Allow changing the SCM branch or revision in a job template that uses this " +"project." +msgstr "允许在使用此项目的作业模板中更改 SCM 分支或修订版本。" + +#: awx/main/models/projects.py:279 +msgid "The last revision fetched by a project update" +msgstr "项目更新获取的最后修订版本" + +#: awx/main/models/projects.py:286 +msgid "Playbook Files" +msgstr "Playbook 文件" + +#: awx/main/models/projects.py:287 +msgid "List of playbooks found in the project" +msgstr "项目中找到的 playbook 列表" + +#: awx/main/models/projects.py:294 +msgid "Inventory Files" +msgstr "清单文件" + +#: awx/main/models/projects.py:295 +msgid "" +"Suggested list of content that could be Ansible inventory in the project" +msgstr "可作为项目中的 Ansible 清单的建议内容列表" + +#: awx/main/models/projects.py:332 +msgid "Organization cannot be changed when in use by job templates." +msgstr "当作业使用时不能更改机构。" + +#: awx/main/models/projects.py:490 +msgid "Parts of the project update playbook that will be run." +msgstr "将要运行的项目更新 playbook 的部分。" + +#: awx/main/models/projects.py:498 +msgid "" +"The SCM Revision discovered by this update for the given project and branch." +msgstr "此更新针对给定项目和分支发现的 SCM 修订。" + +#: awx/main/models/rbac.py:35 +msgid "System Administrator" +msgstr "系统管理员" + +#: awx/main/models/rbac.py:36 +msgid "System Auditor" +msgstr "系统审核员" + +#: awx/main/models/rbac.py:37 +msgid "Ad Hoc" +msgstr "临时" + +#: awx/main/models/rbac.py:38 +msgid "Admin" +msgstr "管理员" + +#: awx/main/models/rbac.py:39 +msgid "Project Admin" +msgstr "项目管理员" + +#: awx/main/models/rbac.py:40 +msgid "Inventory Admin" +msgstr "清单管理员" + +#: awx/main/models/rbac.py:41 +msgid "Credential Admin" +msgstr "凭证管理员" + +#: awx/main/models/rbac.py:42 +msgid "Job Template Admin" +msgstr "作业模板管理员" + +#: awx/main/models/rbac.py:43 +msgid "Workflow Admin" +msgstr "工作流管理员" + +#: awx/main/models/rbac.py:44 +msgid "Notification Admin" +msgstr "通知管理员" + +#: awx/main/models/rbac.py:45 +msgid "Auditor" +msgstr "审核员" + +#: awx/main/models/rbac.py:46 +msgid "Execute" +msgstr "执行" + +#: awx/main/models/rbac.py:47 +msgid "Member" +msgstr "成员" + +#: awx/main/models/rbac.py:48 +msgid "Read" +msgstr "读取" + +#: awx/main/models/rbac.py:49 +msgid "Update" +msgstr "更新" + +#: awx/main/models/rbac.py:50 +msgid "Use" +msgstr "使用" + +#: awx/main/models/rbac.py:51 +msgid "Approve" +msgstr "批准" + +#: awx/main/models/rbac.py:55 +msgid "Can manage all aspects of the system" +msgstr "可以管理系统的所有方面" + +#: awx/main/models/rbac.py:56 +msgid "Can view all aspects of the system" +msgstr "可以查看系统的所有方面" + +#: awx/main/models/rbac.py:57 +#, python-format +msgid "May run ad hoc commands on the %s" +msgstr "您可以在 %s 上运行临时命令" + +#: awx/main/models/rbac.py:58 +#, python-format +msgid "Can manage all aspects of the %s" +msgstr "可以管理 %s 的所有方面" + +#: awx/main/models/rbac.py:59 +#, python-format +msgid "Can manage all projects of the %s" +msgstr "可以管理 %s 的所有方面" + +#: awx/main/models/rbac.py:60 +#, python-format +msgid "Can manage all inventories of the %s" +msgstr "可以管理 %s 的所有清单" + +#: awx/main/models/rbac.py:61 +#, python-format +msgid "Can manage all credentials of the %s" +msgstr "可以管理 %s 的所有凭证" + +#: awx/main/models/rbac.py:62 +#, python-format +msgid "Can manage all job templates of the %s" +msgstr "可以管理 %s 的所有作业模板" + +#: awx/main/models/rbac.py:63 +#, python-format +msgid "Can manage all workflows of the %s" +msgstr "可以管理 %s 的所有工作流" + +#: awx/main/models/rbac.py:64 +#, python-format +msgid "Can manage all notifications of the %s" +msgstr "可以管理 %s 的所有通知" + +#: awx/main/models/rbac.py:65 +#, python-format +msgid "Can view all aspects of the %s" +msgstr "可以查看 %s 的所有方面" + +#: awx/main/models/rbac.py:67 +msgid "May run any executable resources in the organization" +msgstr "可在机构中运行任何可执行资源" + +#: awx/main/models/rbac.py:68 +#, python-format +msgid "May run the %s" +msgstr "可运行 %s" + +#: awx/main/models/rbac.py:70 +#, python-format +msgid "User is a member of the %s" +msgstr "用户是 %s 的成员" + +#: awx/main/models/rbac.py:71 +#, python-format +msgid "May view settings for the %s" +msgstr "可查看 %s 的设置" + +#: awx/main/models/rbac.py:72 +#, python-format +msgid "May update the %s" +msgstr "可更新 %s" + +#: awx/main/models/rbac.py:73 +#, python-format +msgid "Can use the %s in a job template" +msgstr "可以使用作业模板中的 %s" + +#: awx/main/models/rbac.py:74 +msgid "Can approve or deny a workflow approval node" +msgstr "可以批准或拒绝工作流批准节点" + +#: awx/main/models/rbac.py:138 +msgid "roles" +msgstr "角色" + +#: awx/main/models/rbac.py:445 +msgid "role_ancestors" +msgstr "role_ancestors" + +#: awx/main/models/schedules.py:83 +msgid "Enables processing of this schedule." +msgstr "启用此计划的处理。" + +#: awx/main/models/schedules.py:89 +msgid "The first occurrence of the schedule occurs on or after this time." +msgstr "计划第一次发生在此时间或此时间之后。" + +#: awx/main/models/schedules.py:95 +msgid "" +"The last occurrence of the schedule occurs before this time, aftewards the " +"schedule expires." +msgstr "计划最后一次发生在此时间之前,计划过期之后。" + +#: awx/main/models/schedules.py:99 +msgid "A value representing the schedules iCal recurrence rule." +msgstr "代表计划 iCal 重复规则的值。" + +#: awx/main/models/schedules.py:105 +msgid "The next time that the scheduled action will run." +msgstr "调度的操作下次运行的时间。" + +#: awx/main/models/unified_jobs.py:69 +msgid "New" +msgstr "新" + +#: awx/main/models/unified_jobs.py:71 +msgid "Waiting" +msgstr "等待" + +#: awx/main/models/unified_jobs.py:72 +msgid "Running" +msgstr "运行中" + +#: awx/main/models/unified_jobs.py:76 +msgid "Canceled" +msgstr "已取消" + +#: awx/main/models/unified_jobs.py:80 +msgid "Never Updated" +msgstr "永不更新" + +#: awx/main/models/unified_jobs.py:84 +msgid "OK" +msgstr "确定" + +#: awx/main/models/unified_jobs.py:85 +msgid "Missing" +msgstr "缺少" + +#: awx/main/models/unified_jobs.py:89 +msgid "No External Source" +msgstr "没有外部源" + +#: awx/main/models/unified_jobs.py:96 +msgid "Updating" +msgstr "更新" + +#: awx/main/models/unified_jobs.py:167 +msgid "The organization used to determine access to this template." +msgstr "用于决定访问此模板的机构。" + +#: awx/main/models/unified_jobs.py:466 +msgid "Field is not allowed on launch." +msgstr "启动时不允许使用字段。" + +#: awx/main/models/unified_jobs.py:494 +#, python-brace-format +msgid "" +"Variables {list_of_keys} provided, but this template cannot accept variables." +msgstr "提供了 {list_of_keys} 变量,但此模板无法接受变量。" + +#: awx/main/models/unified_jobs.py:540 +msgid "Relaunch" +msgstr "重新启动" + +#: awx/main/models/unified_jobs.py:541 +msgid "Callback" +msgstr "回调" + +#: awx/main/models/unified_jobs.py:542 +msgid "Scheduled" +msgstr "已调度" + +#: awx/main/models/unified_jobs.py:543 +msgid "Dependency" +msgstr "依赖项" + +#: awx/main/models/unified_jobs.py:544 +msgid "Workflow" +msgstr "工作流" + +#: awx/main/models/unified_jobs.py:546 +msgid "Sync" +msgstr "同步" + +#: awx/main/models/unified_jobs.py:601 +msgid "The node the job executed on." +msgstr "执行作业的节点。" + +#: awx/main/models/unified_jobs.py:607 +msgid "The instance that managed the isolated execution environment." +msgstr "管理隔离执行环境的实例。" + +#: awx/main/models/unified_jobs.py:634 +msgid "The date and time the job was queued for starting." +msgstr "作业加入启动队列的日期和时间。" + +#: awx/main/models/unified_jobs.py:639 +msgid "" +"If True, the task manager has already processed potential dependencies for " +"this job." +msgstr "如果为 True,则任务管理器已处理了此作业的潜在依赖关系。" + +#: awx/main/models/unified_jobs.py:645 +msgid "The date and time the job finished execution." +msgstr "作业完成执行的日期和时间。" + +#: awx/main/models/unified_jobs.py:652 +msgid "The date and time when the cancel request was sent." +msgstr "发送取消请求的日期和时间。" + +#: awx/main/models/unified_jobs.py:659 +msgid "Elapsed time in seconds that the job ran." +msgstr "作业运行所经过的时间(以秒为单位)。" + +#: awx/main/models/unified_jobs.py:681 +msgid "" +"A status field to indicate the state of the job if it wasn't able to run and " +"capture stdout" +msgstr "当无法运行和捕获 stdout 时指示作业状态的状态字段" + +#: awx/main/models/unified_jobs.py:710 +msgid "The Instance group the job was run under" +msgstr "作业在其下运行的实例组" + +#: awx/main/models/unified_jobs.py:718 +msgid "The organization used to determine access to this unified job." +msgstr "用于决定访问这个统一作业的机构。" + +#: awx/main/models/workflow.py:85 +msgid "" +"If enabled then the node will only run if all of the parent nodes have met " +"the criteria to reach this node" +msgstr "如果启用,则节点仅在所有父节点都满足了访问该节点的条件时才运行" + +#: awx/main/models/workflow.py:154 +msgid "" +"An identifier for this node that is unique within its workflow. It is copied " +"to workflow job nodes corresponding to this node." +msgstr "此节点的标识符在其工作流中是唯一的。它被复制到与该节点对应的工作流作业节点上。" + +#: awx/main/models/workflow.py:229 +msgid "" +"Indicates that a job will not be created when True. Workflow runtime " +"semantics will mark this True if the node is in a path that will decidedly " +"not be ran. A value of False means the node may not run." +msgstr "True 表示不会创建作业。如果节点位于肯定不会运行的路径中,则 Workflow 运行时语义会将此值标记为 True。False 值表示节点可能无法运行。" + +#: awx/main/models/workflow.py:236 +msgid "" +"An identifier coresponding to the workflow job template node that this node " +"was created from." +msgstr "一个标识符,针对此节点从中创建的工作流作业模板节点。" + +#: awx/main/models/workflow.py:282 +#, python-brace-format +msgid "" +"Bad launch configuration starting template {template_pk} as part of workflow " +"{workflow_pk}. Errors:\n" +"{error_text}" +msgstr "工作流 {workflow_pk} 中的错误启动配置启动模板 {template_pk}。错误:\n" +"{error_text}" + +#: awx/main/models/workflow.py:595 +msgid "" +"If automatically created for a sliced job run, the job template the workflow " +"job was created from." +msgstr "如果为分片作业运行自动创建,则为用于创建工作流作业的作业模板。" + +#: awx/main/models/workflow.py:687 awx/main/models/workflow.py:721 +msgid "" +"The amount of time (in seconds) before the approval node expires and fails." +msgstr "批准节点过期并失败前的时间(以秒为单位)。" + +#: awx/main/models/workflow.py:725 +msgid "" +"Shows when an approval node (with a timeout assigned to it) has timed out." +msgstr "显示批准节点(为其分配了超时)超时的时间。" + +#: awx/main/notifications/grafana_backend.py:86 +msgid "Error converting time {} or timeEnd {} to int." +msgstr "将时间 {} 或 timeEnd {} 转换为 int 时出错。" + +#: awx/main/notifications/grafana_backend.py:88 +msgid "Error converting time {} and/or timeEnd {} to int." +msgstr "将时间 {} 和/或 timeEnd {} 转换为 int 时出错。" + +#: awx/main/notifications/grafana_backend.py:102 +#: awx/main/notifications/grafana_backend.py:104 +msgid "Error sending notification grafana: {}" +msgstr "发送通知 grafana 时出错:{}" + +#: awx/main/notifications/hipchat_backend.py:50 +msgid "Error sending messages: {}" +msgstr "发送消息时出错:{}" + +#: awx/main/notifications/hipchat_backend.py:52 +msgid "Error sending message to hipchat: {}" +msgstr "向 hipchat 发送消息时出错:{}" + +#: awx/main/notifications/irc_backend.py:56 +msgid "Exception connecting to irc server: {}" +msgstr "连接到 irc 服务器时出现异常:{}" + +#: awx/main/notifications/mattermost_backend.py:50 +#: awx/main/notifications/mattermost_backend.py:52 +msgid "Error sending notification mattermost: {}" +msgstr "发送通知 mattermost 时出错:{}" + +#: awx/main/notifications/pagerduty_backend.py:64 +msgid "Exception connecting to PagerDuty: {}" +msgstr "连接到 PagerDuty 时出现异常:{}" + +#: awx/main/notifications/pagerduty_backend.py:73 +#: awx/main/notifications/slack_backend.py:58 +#: awx/main/notifications/twilio_backend.py:48 +msgid "Exception sending messages: {}" +msgstr "发送消息时出现异常:{}" + +#: awx/main/notifications/rocketchat_backend.py:49 +#: awx/main/notifications/rocketchat_backend.py:52 +msgid "Error sending notification rocket.chat: {}" +msgstr "发送通知 rocket.chat 时出错:{}" + +#: awx/main/notifications/twilio_backend.py:38 +msgid "Exception connecting to Twilio: {}" +msgstr "连接到 Twilio 时出现异常:{}" + +#: awx/main/notifications/webhook_backend.py:75 +#: awx/main/notifications/webhook_backend.py:77 +msgid "Error sending notification webhook: {}" +msgstr "发送通知 Webhook 时出错:{}" + +#: awx/main/scheduler/dag_workflow.py:170 +#, python-brace-format +msgid "" +"No error handling path for workflow job node(s) [{node_status}]. Workflow " +"job node(s) missing unified job template and error handling path [{no_ufjt}]." +msgstr "工作流作业节点没有错误处理路径 [{node_status}]。工作流作业节点缺少统一作业模板和错误处理路径 [{no_ufjt}]。" + +#: awx/main/scheduler/task_manager.py:118 +msgid "" +"Workflow Job spawned from workflow could not start because it would result " +"in recursion (spawn order, most recent first: {})" +msgstr "从工作流生成的工作流作业可能无法启动,因为它会导致递归(生成顺序,最近最先:{})" + +#: awx/main/scheduler/task_manager.py:126 +msgid "" +"Job spawned from workflow could not start because it was missing a related " +"resource such as project or inventory" +msgstr "从工作流生成的作业可能无法启动,因为它缺少了相关资源,如项目或清单" + +#: awx/main/scheduler/task_manager.py:135 +msgid "" +"Job spawned from workflow could not start because it was not in the right " +"state or required manual credentials" +msgstr "从工作流生成的作业可能无法启动,因为它不处于正确的状态或需要手动凭证。" + +#: awx/main/scheduler/task_manager.py:176 +msgid "No error handling paths found, marking workflow as failed" +msgstr "未找到错误处理路径,将工作流标记为失败" + +#: awx/main/scheduler/task_manager.py:508 +#, python-brace-format +msgid "The approval node {name} ({pk}) has expired after {timeout} seconds." +msgstr "批准节点 {name} ({pk}) 已在 {timeout} 秒后过期。" + +#: awx/main/tasks.py:1049 +msgid "Invalid virtual environment selected: {}" +msgstr "选择了无效的虚拟环境:{}" + +#: awx/main/tasks.py:1853 +msgid "Job could not start because it does not have a valid inventory." +msgstr "作业无法启动,因为它没有有效的清单。" + +#: awx/main/tasks.py:1857 +msgid "Job could not start because it does not have a valid project." +msgstr "作业无法启动,因为它没有有效的项目。" + +#: awx/main/tasks.py:1862 +msgid "" +"The project revision for this job template is unknown due to a failed update." +msgstr "由于更新失败,此作业模板的项目修订版本未知。" + +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:470 +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:498 +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:505 +msgid "" +"No error handling path for workflow job node(s) [({},{})]. Workflow job " +"node(s) missing unified job template and error handling path []." +msgstr "工作流作业节点没有错误处理路径 [({},{})]。工作流作业节点缺少统一作业模板和错误处理路径 []。" + +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:480 +#: awx/main/tests/unit/scheduler/test_dag_workflow.py:491 +msgid "" +"No error handling path for workflow job node(s) []. Workflow job node(s) " +"missing unified job template and error handling path [{}]." +msgstr "工作流作业节点没有错误处理路径 []。工作流作业节点缺少统一作业模板和错误处理路径 [{}]。" + +#: awx/main/utils/common.py:87 +#, python-format +msgid "Unable to convert \"%s\" to boolean" +msgstr "无法将 \"%s\" 转换为布尔值" + +#: awx/main/utils/common.py:261 +#, python-format +msgid "Unsupported SCM type \"%s\"" +msgstr "不受支持的 SCM 类型 \"%s\"" + +#: awx/main/utils/common.py:268 awx/main/utils/common.py:280 +#: awx/main/utils/common.py:299 +#, python-format +msgid "Invalid %s URL" +msgstr "无效的 %s URL" + +#: awx/main/utils/common.py:270 awx/main/utils/common.py:309 +#, python-format +msgid "Unsupported %s URL" +msgstr "不受支持的 %s URL" + +#: awx/main/utils/common.py:311 +#, python-format +msgid "Unsupported host \"%s\" for file:// URL" +msgstr "用于 file:// URL的主机 \"%s\" 不受支持" + +#: awx/main/utils/common.py:313 +#, python-format +msgid "Host is required for %s URL" +msgstr "%s URL 需要主机" + +#: awx/main/utils/common.py:331 +#, python-format +msgid "Username must be \"git\" for SSH access to %s." +msgstr "用户名必须是 \"git\" 以供 SSH 访问 %s。" + +#: awx/main/utils/common.py:337 +#, python-format +msgid "Username must be \"hg\" for SSH access to %s." +msgstr "用户名必须是 \"hg\" 以供 SSH 访问 %s。" + +#: awx/main/utils/common.py:668 +#, python-brace-format +msgid "Input type `{data_type}` is not a dictionary" +msgstr "输入类型 `{data_type}` 不是字典" + +#: awx/main/utils/common.py:701 +#, python-brace-format +msgid "Variables not compatible with JSON standard (error: {json_error})" +msgstr "与 JSON 标准不兼容的变量(错误:{json_error})" + +#: awx/main/utils/common.py:707 +#, python-brace-format +msgid "" +"Cannot parse as JSON (error: {json_error}) or YAML (error: {yaml_error})." +msgstr "无法解析为 JSON(错误:{json_error})或 YAML(错误:{yaml_error})。" + +#: awx/main/validators.py:67 +#, python-format +msgid "Invalid certificate or key: %s..." +msgstr "无效的证书或密钥:%s..." + +#: awx/main/validators.py:83 +#, python-format +msgid "Invalid private key: unsupported type \"%s\"" +msgstr "无效的私钥:不受支持的类型 \"%s\"" + +#: awx/main/validators.py:87 +#, python-format +msgid "Unsupported PEM object type: \"%s\"" +msgstr "不受支持的 PEM 对象类型:\"%s\"" + +#: awx/main/validators.py:112 +msgid "Invalid base64-encoded data" +msgstr "无效的 base64 编码数据" + +#: awx/main/validators.py:133 +msgid "Exactly one private key is required." +msgstr "只需要一个私钥。" + +#: awx/main/validators.py:135 +msgid "At least one private key is required." +msgstr "至少需要一个私钥。" + +#: awx/main/validators.py:137 +#, python-format +msgid "" +"At least %(min_keys)d private keys are required, only %(key_count)d provided." +msgstr "至少需要 %(min_keys)d 个私钥,只提供了 %(key_count)d 个。" + +#: awx/main/validators.py:140 +#, python-format +msgid "Only one private key is allowed, %(key_count)d provided." +msgstr "只允许一个私钥,提供了 %(key_count)d 个。" + +#: awx/main/validators.py:142 +#, python-format +msgid "" +"No more than %(max_keys)d private keys are allowed, %(key_count)d provided." +msgstr "不允许超过 %(max_keys)d 个私钥,提供 %(key_count)d 个。" + +#: awx/main/validators.py:147 +msgid "Exactly one certificate is required." +msgstr "只需要一个证书。" + +#: awx/main/validators.py:149 +msgid "At least one certificate is required." +msgstr "至少需要一个证书。" + +#: awx/main/validators.py:151 +#, python-format +msgid "" +"At least %(min_certs)d certificates are required, only %(cert_count)d " +"provided." +msgstr "至少需要 %(min_certs)d 个证书,只提供了 %(cert_count)d 个。" + +#: awx/main/validators.py:154 +#, python-format +msgid "Only one certificate is allowed, %(cert_count)d provided." +msgstr "只允许一个证书,提供了 %(cert_count)d 个。" + +#: awx/main/validators.py:156 +#, python-format +msgid "" +"No more than %(max_certs)d certificates are allowed, %(cert_count)d provided." +msgstr "不允许超过 %(max_certs)d 个证书,提供了 %(cert_count)d 个。" + +#: awx/main/views.py:30 +msgid "API Error" +msgstr "API 错误" + +#: awx/main/views.py:65 +msgid "Bad Request" +msgstr "错误请求" + +#: awx/main/views.py:66 +msgid "The request could not be understood by the server." +msgstr "服务器无法理解此请求。" + +#: awx/main/views.py:73 +msgid "Forbidden" +msgstr "禁止" + +#: awx/main/views.py:74 +msgid "You don't have permission to access the requested resource." +msgstr "您没有权限访问请求的资源。" + +#: awx/main/views.py:81 +msgid "Not Found" +msgstr "未找到" + +#: awx/main/views.py:82 +msgid "The requested resource could not be found." +msgstr "无法找到请求的资源。" + +#: awx/main/views.py:89 +msgid "Server Error" +msgstr "服务器错误" + +#: awx/main/views.py:90 +msgid "A server error has occurred." +msgstr "发生服务器错误。" + +#: awx/settings/defaults.py:683 +msgid "US East (Northern Virginia)" +msgstr "美国东部(北弗吉尼亚)" + +#: awx/settings/defaults.py:684 +msgid "US East (Ohio)" +msgstr "美国东部(俄亥俄)" + +#: awx/settings/defaults.py:685 +msgid "US West (Oregon)" +msgstr "美国西部(俄勒冈)" + +#: awx/settings/defaults.py:686 +msgid "US West (Northern California)" +msgstr "美国西部(北加利福尼亚)" + +#: awx/settings/defaults.py:687 +msgid "Canada (Central)" +msgstr "加拿大(中部)" + +#: awx/settings/defaults.py:688 +msgid "EU (Frankfurt)" +msgstr "欧盟(法兰克福)" + +#: awx/settings/defaults.py:689 +msgid "EU (Ireland)" +msgstr "欧盟(爱尔兰)" + +#: awx/settings/defaults.py:690 +msgid "EU (London)" +msgstr "欧盟(伦敦)" + +#: awx/settings/defaults.py:691 +msgid "Asia Pacific (Singapore)" +msgstr "亚太(新加坡)" + +#: awx/settings/defaults.py:692 +msgid "Asia Pacific (Sydney)" +msgstr "亚太(悉尼)" + +#: awx/settings/defaults.py:693 +msgid "Asia Pacific (Tokyo)" +msgstr "亚太(东京)" + +#: awx/settings/defaults.py:694 +msgid "Asia Pacific (Seoul)" +msgstr "亚太(首尔)" + +#: awx/settings/defaults.py:695 +msgid "Asia Pacific (Mumbai)" +msgstr "亚太(孟买)" + +#: awx/settings/defaults.py:696 +msgid "South America (Sao Paulo)" +msgstr "南美(圣保罗)" + +#: awx/settings/defaults.py:697 +msgid "US West (GovCloud)" +msgstr "美国西部 (GovCloud)" + +#: awx/settings/defaults.py:698 +msgid "China (Beijing)" +msgstr "中国(北京)" + +#: awx/settings/defaults.py:747 +msgid "US East 1 (B)" +msgstr "美国东部 1 (B)" + +#: awx/settings/defaults.py:748 +msgid "US East 1 (C)" +msgstr "美国东部 1 (C)" + +#: awx/settings/defaults.py:749 +msgid "US East 1 (D)" +msgstr "美国东部 1 (D)" + +#: awx/settings/defaults.py:750 +msgid "US East 4 (A)" +msgstr "美国东部 4 (A)" + +#: awx/settings/defaults.py:751 +msgid "US East 4 (B)" +msgstr "美国东部 4 (B)" + +#: awx/settings/defaults.py:752 +msgid "US East 4 (C)" +msgstr "美国东部 4 (C)" + +#: awx/settings/defaults.py:753 +msgid "US Central (A)" +msgstr "美国中部 (A)" + +#: awx/settings/defaults.py:754 +msgid "US Central (B)" +msgstr "美国中部 (B)" + +#: awx/settings/defaults.py:755 +msgid "US Central (C)" +msgstr "美国中部 (C)" + +#: awx/settings/defaults.py:756 +msgid "US Central (F)" +msgstr "美国中部 (F)" + +#: awx/settings/defaults.py:757 +msgid "US West (A)" +msgstr "美国西部 (A)" + +#: awx/settings/defaults.py:758 +msgid "US West (B)" +msgstr "美国西部 (B)" + +#: awx/settings/defaults.py:759 +msgid "US West (C)" +msgstr "美国西部 (C)" + +#: awx/settings/defaults.py:760 +msgid "Europe West 1 (B)" +msgstr "西欧 1 (B)" + +#: awx/settings/defaults.py:761 +msgid "Europe West 1 (C)" +msgstr "西欧 1 (C)" + +#: awx/settings/defaults.py:762 +msgid "Europe West 1 (D)" +msgstr "西欧 1 (D)" + +#: awx/settings/defaults.py:763 +msgid "Europe West 2 (A)" +msgstr "西欧 2 (A)" + +#: awx/settings/defaults.py:764 +msgid "Europe West 2 (B)" +msgstr "西欧 2 (B)" + +#: awx/settings/defaults.py:765 +msgid "Europe West 2 (C)" +msgstr "西欧 2 (C)" + +#: awx/settings/defaults.py:766 +msgid "Asia East (A)" +msgstr "东亚 (A)" + +#: awx/settings/defaults.py:767 +msgid "Asia East (B)" +msgstr "东亚 (B)" + +#: awx/settings/defaults.py:768 +msgid "Asia East (C)" +msgstr "东亚 (C)" + +#: awx/settings/defaults.py:769 +msgid "Asia Southeast (A)" +msgstr "东南亚 (A)" + +#: awx/settings/defaults.py:770 +msgid "Asia Southeast (B)" +msgstr "东南亚 (B)" + +#: awx/settings/defaults.py:771 +msgid "Asia Northeast (A)" +msgstr "亚州东北部 (A)" + +#: awx/settings/defaults.py:772 +msgid "Asia Northeast (B)" +msgstr "亚州东北部 (B)" + +#: awx/settings/defaults.py:773 +msgid "Asia Northeast (C)" +msgstr "亚州东北部 (C)" + +#: awx/settings/defaults.py:774 +msgid "Australia Southeast (A)" +msgstr "澳洲东南部 (A)" + +#: awx/settings/defaults.py:775 +msgid "Australia Southeast (B)" +msgstr "澳洲东南部 (B)" + +#: awx/settings/defaults.py:776 +msgid "Australia Southeast (C)" +msgstr "澳洲东南部 (C)" + +#: awx/settings/defaults.py:798 +msgid "US East" +msgstr "美国东部" + +#: awx/settings/defaults.py:799 +msgid "US East 2" +msgstr "美国东部 2" + +#: awx/settings/defaults.py:800 +msgid "US Central" +msgstr "美国中部" + +#: awx/settings/defaults.py:801 +msgid "US North Central" +msgstr "美国中北部" + +#: awx/settings/defaults.py:802 +msgid "US South Central" +msgstr "美国中南部" + +#: awx/settings/defaults.py:803 +msgid "US West Central" +msgstr "美国中西部" + +#: awx/settings/defaults.py:804 +msgid "US West" +msgstr "美国西部" + +#: awx/settings/defaults.py:805 +msgid "US West 2" +msgstr "美国西部 2" + +#: awx/settings/defaults.py:806 +msgid "Canada East" +msgstr "加拿大东部" + +#: awx/settings/defaults.py:807 +msgid "Canada Central" +msgstr "加拿大中部" + +#: awx/settings/defaults.py:808 +msgid "Brazil South" +msgstr "巴西南部" + +#: awx/settings/defaults.py:809 +msgid "Europe North" +msgstr "北欧" + +#: awx/settings/defaults.py:810 +msgid "Europe West" +msgstr "西欧" + +#: awx/settings/defaults.py:811 +msgid "UK West" +msgstr "英国西部" + +#: awx/settings/defaults.py:812 +msgid "UK South" +msgstr "英国南部" + +#: awx/settings/defaults.py:813 +msgid "Asia East" +msgstr "东亚" + +#: awx/settings/defaults.py:814 +msgid "Asia Southeast" +msgstr "东南亚" + +#: awx/settings/defaults.py:815 +msgid "Australia East" +msgstr "澳洲东部" + +#: awx/settings/defaults.py:816 +msgid "Australia Southeast" +msgstr "澳洲东南部" + +#: awx/settings/defaults.py:817 +msgid "India West" +msgstr "印度西部" + +#: awx/settings/defaults.py:818 +msgid "India South" +msgstr "印度南部" + +#: awx/settings/defaults.py:819 +msgid "Japan East" +msgstr "日本东部" + +#: awx/settings/defaults.py:820 +msgid "Japan West" +msgstr "日本西部" + +#: awx/settings/defaults.py:821 +msgid "Korea Central" +msgstr "韩国中部" + +#: awx/settings/defaults.py:822 +msgid "Korea South" +msgstr "韩国南部" + +#: awx/sso/apps.py:9 +msgid "Single Sign-On" +msgstr "单点登录" + +#: awx/sso/conf.py:41 +msgid "" +"Mapping to organization admins/users from social auth accounts. This " +"setting\n" +"controls which users are placed into which Tower organizations based on " +"their\n" +"username and email address. Configuration details are available in the " +"Ansible\n" +"Tower documentation." +msgstr "从社交身份验证帐户映射到机构管理员/用户。此设置可根据用户的用户名和电子邮件地址控制哪些用户被放置到哪些 Tower 机构。配置详情可在 Ansible Tower 文档中找到。" + +#: awx/sso/conf.py:67 +msgid "" +"Mapping of team members (users) from social auth accounts. Configuration\n" +"details are available in Tower documentation." +msgstr "从社交身份验证帐户映射团队成员(用户)。配置详情可在 Tower 文档中找到。" + +#: awx/sso/conf.py:92 +msgid "Authentication Backends" +msgstr "身份验证后端" + +#: awx/sso/conf.py:93 +msgid "" +"List of authentication backends that are enabled based on license features " +"and other authentication settings." +msgstr "根据许可证功能和其他身份验证设置启用的身份验证后端列表。" + +#: awx/sso/conf.py:106 +msgid "Social Auth Organization Map" +msgstr "社交身份验证机构映射" + +#: awx/sso/conf.py:118 +msgid "Social Auth Team Map" +msgstr "社交身份验证团队映射" + +#: awx/sso/conf.py:130 +msgid "Social Auth User Fields" +msgstr "社交身份验证用户字段" + +#: awx/sso/conf.py:131 +msgid "" +"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." +msgstr "当设置为空列表 `[]` 时,此设置可防止创建新用户帐户。只有之前已经使用社交身份验证登录或用户帐户有匹配电子邮件地址的用户才能登录。" + +#: awx/sso/conf.py:153 +msgid "LDAP Server URI" +msgstr "LDAP 服务器 URI" + +#: awx/sso/conf.py:154 +msgid "" +"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." +msgstr "要连接到 LDAP 服务器的 URI,如 \"ldap://ldap.example.com:389\"(非 SSL)或 \"ldaps://ldap.example.com:636\" (SSL)。可通过使用空格或逗号分隔来指定多个 LDAP 服务器。如果此参数为空,则禁用 LDAP 身份验证。" + +#: awx/sso/conf.py:158 awx/sso/conf.py:173 awx/sso/conf.py:184 +#: awx/sso/conf.py:195 awx/sso/conf.py:210 awx/sso/conf.py:229 +#: awx/sso/conf.py:250 awx/sso/conf.py:264 awx/sso/conf.py:281 +#: awx/sso/conf.py:297 awx/sso/conf.py:308 awx/sso/conf.py:333 +#: awx/sso/conf.py:348 awx/sso/conf.py:361 awx/sso/conf.py:378 +#: awx/sso/conf.py:404 +msgid "LDAP" +msgstr "LDAP" + +#: awx/sso/conf.py:169 +msgid "LDAP Bind DN" +msgstr "LDAP 绑定 DN" + +#: awx/sso/conf.py:170 +msgid "" +"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 Ansible Tower documentation for example syntax." +msgstr "要为所有搜索查询绑定的用户的 DN(识别名)。这是我们用来登录以查询 LDAP 系统中其他用户信息的用户帐户。示例语法请参阅 Ansible Tower 文档。" + +#: awx/sso/conf.py:182 +msgid "LDAP Bind Password" +msgstr "LDAP 绑定密码" + +#: awx/sso/conf.py:183 +msgid "Password used to bind LDAP user account." +msgstr "用于绑定 LDAP 用户帐户的密码。" + +#: awx/sso/conf.py:193 +msgid "LDAP Start TLS" +msgstr "LDAP 启动 TLS" + +#: awx/sso/conf.py:194 +msgid "Whether to enable TLS when the LDAP connection is not using SSL." +msgstr "是否在 LDAP 连接没有使用 SSL 时启用 TLS。" + +#: awx/sso/conf.py:203 +msgid "LDAP Connection Options" +msgstr "LDAP 连接选项" + +#: awx/sso/conf.py:204 +msgid "" +"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." +msgstr "为 LDAP 连接设置的附加选项。LDAP 引用默认为禁用(以防止某些 LDAP 查询与 AD 一起挂起)。选项名称应该是字符串(例如:\"OPT_referrals\")。请参阅 https://www.python-ldap.org/doc/html/ldap.html#options 了解您可以设置的可能选项和值。" + +#: awx/sso/conf.py:222 +msgid "LDAP User Search" +msgstr "LDAP 用户搜索" + +#: awx/sso/conf.py:223 +msgid "" +"LDAP search query to find users. Any user that matches the given pattern " +"will be able to login to Tower. The user should also be mapped into a Tower " +"organization (as defined in the AUTH_LDAP_ORGANIZATION_MAP setting). If " +"multiple search queries need to be supported use of \"LDAPUnion\" is " +"possible. See Tower documentation for details." +msgstr "用于查找用户的 LDAP 搜索查询。任何匹配给定模式的用户都可以登录 Tower。用户也应该映射到 Tower 机构(如 AUTH_LDAP_ORGANIZATION_MAP 设置中定义的)。如果需要支持多个搜索查询,可以使用 \"LDAPUnion\"。详情请参阅 Tower 文档。" + +#: awx/sso/conf.py:244 +msgid "LDAP User DN Template" +msgstr "LDAP 用户 DN 模板" + +#: awx/sso/conf.py:245 +msgid "" +"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." +msgstr "当用户 DN 都是相同格式时用户搜索的替代方式。如果在您的机构环境中可用,这种用户查找方法比搜索更为高效。如果此设置具有值,将使用它来代替 AUTH_LDAP_USER_SEARCH。" + +#: awx/sso/conf.py:259 +msgid "LDAP User Attribute Map" +msgstr "LDAP 用户属性映射" + +#: awx/sso/conf.py:260 +msgid "" +"Mapping of LDAP user schema to Tower 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 Ansible Tower " +"documentation for additional details." +msgstr "将 LDAP 用户模式映射到 Tower API 用户属性。默认设置对 ActiveDirectory 有效,但具有其他 LDAP 配置的用户可能需要更改值。如需了解更多详情,请参阅 Ansible Tower 文档。" + +#: awx/sso/conf.py:277 +msgid "LDAP Group Search" +msgstr "LDAP 组搜索" + +#: awx/sso/conf.py:278 +msgid "" +"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." +msgstr "用户根据在其 LDAP 组中的成员资格映射到机构。此设置定义了 LDAP 搜索查询来查找组。与用户搜索不同,组搜索不支持 LDAPSearchUnion。" + +#: awx/sso/conf.py:293 +msgid "LDAP Group Type" +msgstr "LDAP 组类型" + +#: awx/sso/conf.py:294 +msgid "" +"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" +msgstr "可能需要根据 LDAP 服务器的类型更改组类型。值列于:https://django-auth-ldap.readthedocs.io/en/stable/groups.html#types-of-groups" + +#: awx/sso/conf.py:306 +msgid "LDAP Group Type Parameters" +msgstr "LDAP 组类型参数" + +#: awx/sso/conf.py:307 +msgid "Key value parameters to send the chosen group type init method." +msgstr "发送所选组类型 init 方法的键值参数。" + +#: awx/sso/conf.py:328 +msgid "LDAP Require Group" +msgstr "LDAP 需要组" + +#: awx/sso/conf.py:329 +msgid "" +"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 via Tower. Only one require group is supported." +msgstr "登录时所需的组 DN。如果指定,用户必须是此组的成员才能通过 LDAP 登录。如果未设置,与用户搜索匹配的 LDAP 中的任何人都可以通过 Tower 进行登录。只支持一个需要组。" + +#: awx/sso/conf.py:344 +msgid "LDAP Deny Group" +msgstr "LDAP 拒绝组" + +#: awx/sso/conf.py:345 +msgid "" +"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." +msgstr "被拒绝登录的组 DN。如果指定,则不允许属于此组成员的用户登录。只支持一个拒绝组。" + +#: awx/sso/conf.py:357 +msgid "LDAP User Flags By Group" +msgstr "LDAP 用户标记(按组)" + +#: awx/sso/conf.py:358 +msgid "" +"Retrieve users from a given group. At this time, superuser and system " +"auditors are the only groups supported. Refer to the Ansible Tower " +"documentation for more detail." +msgstr "从给定的组中检索用户。此时,超级用户和系统审核员是唯一支持的组。请参阅 Ansible Tower 文档了解更多详情。" + +#: awx/sso/conf.py:373 +msgid "LDAP Organization Map" +msgstr "LDAP 机构映射" + +#: awx/sso/conf.py:374 +msgid "" +"Mapping between organization admins/users and LDAP groups. This controls " +"which users are placed into which Tower organizations relative to their LDAP " +"group memberships. Configuration details are available in the Ansible Tower " +"documentation." +msgstr "机构管理员/用户和 LDAP 组之间的映射。此设置根据用户的 LDAP 组成员资格控制哪些用户被放置到哪些 Tower 机构中。配置详情可在 Ansible Tower 文档中找到。" + +#: awx/sso/conf.py:401 +msgid "LDAP Team Map" +msgstr "LDAP 团队映射" + +#: awx/sso/conf.py:402 +msgid "" +"Mapping between team members (users) and LDAP groups. Configuration details " +"are available in the Ansible Tower documentation." +msgstr "团队成员(用户)和 LDAP 组之间的映射。配置详情可在 Ansible Tower 文档中找到。" + +#: awx/sso/conf.py:437 +msgid "RADIUS Server" +msgstr "RADIUS 服务器" + +#: awx/sso/conf.py:438 +msgid "" +"Hostname/IP of RADIUS server. RADIUS authentication is disabled if this " +"setting is empty." +msgstr "RADIUS 服务器的主机名/IP。如果此设置为空,则禁用 RADIUS 身份验证。" + +#: awx/sso/conf.py:440 awx/sso/conf.py:453 awx/sso/conf.py:464 +#: awx/sso/models.py:14 +msgid "RADIUS" +msgstr "RADIUS" + +#: awx/sso/conf.py:451 +msgid "RADIUS Port" +msgstr "RADIUS 端口" + +#: awx/sso/conf.py:452 +msgid "Port of RADIUS server." +msgstr "RADIUS 服务器的端口。" + +#: awx/sso/conf.py:462 +msgid "RADIUS Secret" +msgstr "RADIUS 机密" + +#: awx/sso/conf.py:463 +msgid "Shared secret for authenticating to RADIUS server." +msgstr "用于向 RADIUS 服务器进行身份验证的共享机密。" + +#: awx/sso/conf.py:478 +msgid "TACACS+ Server" +msgstr "TACACS+ 服务器" + +#: awx/sso/conf.py:479 +msgid "Hostname of TACACS+ server." +msgstr "TACACS+ 服务器的主机名。" + +#: awx/sso/conf.py:480 awx/sso/conf.py:492 awx/sso/conf.py:504 +#: awx/sso/conf.py:516 awx/sso/conf.py:527 awx/sso/models.py:15 +msgid "TACACS+" +msgstr "TACACS+" + +#: awx/sso/conf.py:490 +msgid "TACACS+ Port" +msgstr "TACACS+ 端口" + +#: awx/sso/conf.py:491 +msgid "Port number of TACACS+ server." +msgstr "TACACS+ 服务器的端口号。" + +#: awx/sso/conf.py:502 +msgid "TACACS+ Secret" +msgstr "TACACS+ 机密" + +#: awx/sso/conf.py:503 +msgid "Shared secret for authenticating to TACACS+ server." +msgstr "用于向 TACACS+ 服务器进行身份验证的共享机密。" + +#: awx/sso/conf.py:514 +msgid "TACACS+ Auth Session Timeout" +msgstr "TACACS+ 身份验证会话超时" + +#: awx/sso/conf.py:515 +msgid "TACACS+ session timeout value in seconds, 0 disables timeout." +msgstr "TACACS+ 会话超时值(以秒为单位),0 表示禁用超时。" + +#: awx/sso/conf.py:525 +msgid "TACACS+ Authentication Protocol" +msgstr "TACACS+ 身份验证协议" + +#: awx/sso/conf.py:526 +msgid "Choose the authentication protocol used by TACACS+ client." +msgstr "选择 TACACS+ 客户端使用的身份验证协议。" + +#: awx/sso/conf.py:540 +msgid "Google OAuth2 Callback URL" +msgstr "Google OAuth2 回调 URL" + +#: awx/sso/conf.py:541 awx/sso/conf.py:634 awx/sso/conf.py:699 +msgid "" +"Provide this URL as the callback URL for your application as part of your " +"registration process. Refer to the Ansible Tower documentation for more " +"detail." +msgstr "在您的注册过程中,提供此 URL 作为应用的回调 URL。请参阅 Ansible Tower 文档了解更多详情。" + +#: awx/sso/conf.py:544 awx/sso/conf.py:556 awx/sso/conf.py:568 +#: awx/sso/conf.py:581 awx/sso/conf.py:595 awx/sso/conf.py:607 +#: awx/sso/conf.py:619 +msgid "Google OAuth2" +msgstr "Google OAuth2" + +#: awx/sso/conf.py:554 +msgid "Google OAuth2 Key" +msgstr "Google OAuth2 密钥" + +#: awx/sso/conf.py:555 +msgid "The OAuth2 key from your web application." +msgstr "您的 Web 应用中的 OAuth2 密钥。" + +#: awx/sso/conf.py:566 +msgid "Google OAuth2 Secret" +msgstr "Google OAuth2 机密" + +#: awx/sso/conf.py:567 +msgid "The OAuth2 secret from your web application." +msgstr "您的 Web 应用中的 OAuth2 机密。" + +#: awx/sso/conf.py:578 +msgid "Google OAuth2 Whitelisted Domains" +msgstr "Google OAuth2 白名单域" + +#: awx/sso/conf.py:579 +msgid "" +"Update this setting to restrict the domains who are allowed to login using " +"Google OAuth2." +msgstr "更新此设置,以限制允许使用 Google OAuth2 登录的域。" + +#: awx/sso/conf.py:590 +msgid "Google OAuth2 Extra Arguments" +msgstr "Google OAuth2 额外参数" + +#: awx/sso/conf.py:591 +msgid "" +"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 Ansible Tower documentation for more detail." +msgstr "用于 Google OAuth2 登录的额外参数。您可以将其限制为只允许单个域进行身份验证,即使用户使用多个 Google 帐户登录。请参阅 Ansible Tower 文档了解更多详情。" + +#: awx/sso/conf.py:605 +msgid "Google OAuth2 Organization Map" +msgstr "Google OAuth2 机构映射" + +#: awx/sso/conf.py:617 +msgid "Google OAuth2 Team Map" +msgstr "Google OAuth2 团队映射" + +#: awx/sso/conf.py:633 +msgid "GitHub OAuth2 Callback URL" +msgstr "GitHub OAuth2 回调 URL" + +#: awx/sso/conf.py:637 awx/sso/conf.py:649 awx/sso/conf.py:660 +#: awx/sso/conf.py:672 awx/sso/conf.py:684 +msgid "GitHub OAuth2" +msgstr "GitHub OAuth2" + +#: awx/sso/conf.py:647 +msgid "GitHub OAuth2 Key" +msgstr "GitHub OAuth2 密钥" + +#: awx/sso/conf.py:648 +msgid "The OAuth2 key (Client ID) from your GitHub developer application." +msgstr "您的 GitHub 开发应用中的 OAuth2 密钥(客户端 ID)。" + +#: awx/sso/conf.py:658 +msgid "GitHub OAuth2 Secret" +msgstr "GitHub OAuth2 机密" + +#: awx/sso/conf.py:659 +msgid "" +"The OAuth2 secret (Client Secret) from your GitHub developer application." +msgstr "您的 GitHub 开发应用中的 OAuth2 机密(客户端机密)。" + +#: awx/sso/conf.py:670 +msgid "GitHub OAuth2 Organization Map" +msgstr "GitHub OAuth2 机构映射" + +#: awx/sso/conf.py:682 +msgid "GitHub OAuth2 Team Map" +msgstr "GitHub OAuth2 团队映射" + +#: awx/sso/conf.py:698 +msgid "GitHub Organization OAuth2 Callback URL" +msgstr "GitHub 机构 OAuth2 回调 URL" + +#: awx/sso/conf.py:702 awx/sso/conf.py:714 awx/sso/conf.py:725 +#: awx/sso/conf.py:738 awx/sso/conf.py:749 awx/sso/conf.py:761 +msgid "GitHub Organization OAuth2" +msgstr "GitHub 机构 OAuth2" + +#: awx/sso/conf.py:712 +msgid "GitHub Organization OAuth2 Key" +msgstr "GitHub 机构 OAuth2 密钥" + +#: awx/sso/conf.py:713 awx/sso/conf.py:791 +msgid "The OAuth2 key (Client ID) from your GitHub organization application." +msgstr "您的 GitHub 机构应用中的 OAuth2 密钥(客户端 ID)。" + +#: awx/sso/conf.py:723 +msgid "GitHub Organization OAuth2 Secret" +msgstr "GitHub 机构 OAuth2 机密" + +#: awx/sso/conf.py:724 awx/sso/conf.py:802 +msgid "" +"The OAuth2 secret (Client Secret) from your GitHub organization application." +msgstr "您的 GitHub 机构应用中的 OAuth2 机密(客户端机密)。" + +#: awx/sso/conf.py:735 +msgid "GitHub Organization Name" +msgstr "GitHub 机构名称" + +#: awx/sso/conf.py:736 +msgid "" +"The name of your GitHub organization, as used in your organization's URL: " +"https://github.com//." +msgstr "GitHub 机构的名称,用于您的机构 URL:https://github.com//。" + +#: awx/sso/conf.py:747 +msgid "GitHub Organization OAuth2 Organization Map" +msgstr "GitHub 机构 OAuth2 机构映射" + +#: awx/sso/conf.py:759 +msgid "GitHub Organization OAuth2 Team Map" +msgstr "GitHub 机构 OAuth2 团队映射" + +#: awx/sso/conf.py:775 +msgid "GitHub Team OAuth2 Callback URL" +msgstr "GitHub 团队 OAuth2 回调 URL" + +#: awx/sso/conf.py:776 +msgid "" +"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." +msgstr "在 https://github.com/organizations//settings/applications 创建一个机构拥有的应用,并获取 OAuth2 密钥(客户端 ID)和机密(客户端机密)。为您的应用提供此 URL 作为回调 URL。" + +#: awx/sso/conf.py:780 awx/sso/conf.py:792 awx/sso/conf.py:803 +#: awx/sso/conf.py:816 awx/sso/conf.py:827 awx/sso/conf.py:839 +msgid "GitHub Team OAuth2" +msgstr "GitHub 团队 OAuth2" + +#: awx/sso/conf.py:790 +msgid "GitHub Team OAuth2 Key" +msgstr "GitHub 团队 OAuth2 机密" + +#: awx/sso/conf.py:801 +msgid "GitHub Team OAuth2 Secret" +msgstr "GitHub 团队 OAuth2 机密" + +#: awx/sso/conf.py:813 +msgid "GitHub Team ID" +msgstr "GitHub 团队 ID" + +#: awx/sso/conf.py:814 +msgid "" +"Find the numeric team ID using the Github API: http://fabian-kostadinov." +"github.io/2015/01/16/how-to-find-a-github-team-id/." +msgstr "使用 Github API 查找数字团队 ID:http://fabian-kostadinov.github.io/2015/01/16/how-to-find-a-github-team-id/。" + +#: awx/sso/conf.py:825 +msgid "GitHub Team OAuth2 Organization Map" +msgstr "GitHub 团队 OAuth2 机构映射" + +#: awx/sso/conf.py:837 +msgid "GitHub Team OAuth2 Team Map" +msgstr "GitHub 团队 OAuth2 团队映射" + +#: awx/sso/conf.py:853 +msgid "Azure AD OAuth2 Callback URL" +msgstr "Azure AD OAuth2 回调 URL" + +#: awx/sso/conf.py:854 +msgid "" +"Provide this URL as the callback URL for your application as part of your " +"registration process. Refer to the Ansible Tower documentation for more " +"detail. " +msgstr "在您的注册过程中,提供此 URL 作为应用的回调 URL。请参阅 Ansible Tower 文档了解更多详情。" + +#: awx/sso/conf.py:857 awx/sso/conf.py:869 awx/sso/conf.py:880 +#: awx/sso/conf.py:892 awx/sso/conf.py:904 +msgid "Azure AD OAuth2" +msgstr "Azure AD OAuth2" + +#: awx/sso/conf.py:867 +msgid "Azure AD OAuth2 Key" +msgstr "Azure AD OAuth2 密钥" + +#: awx/sso/conf.py:868 +msgid "The OAuth2 key (Client ID) from your Azure AD application." +msgstr "您的 Azure AD 应用的 OAuth2 密钥(客户端 ID)。" + +#: awx/sso/conf.py:878 +msgid "Azure AD OAuth2 Secret" +msgstr "Azure AD OAuth2 机密" + +#: awx/sso/conf.py:879 +msgid "The OAuth2 secret (Client Secret) from your Azure AD application." +msgstr "您的 Azure AD 应用的 OAuth2 机密(客户端机密)。" + +#: awx/sso/conf.py:890 +msgid "Azure AD OAuth2 Organization Map" +msgstr "Azure AD OAuth2 机构映射" + +#: awx/sso/conf.py:902 +msgid "Azure AD OAuth2 Team Map" +msgstr "Azure AD OAuth2 团队映射" + +#: awx/sso/conf.py:927 +msgid "SAML Assertion Consumer Service (ACS) URL" +msgstr "SAML 断言使用者服务 (ACS) URL" + +#: awx/sso/conf.py:928 +msgid "" +"Register Tower 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." +msgstr "针对您配置的每个身份提供商 (IdP) 将 Tower 注册为服务供应商 (SP)。为您的应用提供您的 SP 实体 ID 和此 ACS URL。" + +#: awx/sso/conf.py:931 awx/sso/conf.py:944 awx/sso/conf.py:957 +#: awx/sso/conf.py:971 awx/sso/conf.py:984 awx/sso/conf.py:996 +#: awx/sso/conf.py:1016 awx/sso/conf.py:1033 awx/sso/conf.py:1051 +#: awx/sso/conf.py:1086 awx/sso/conf.py:1117 awx/sso/conf.py:1130 +#: awx/sso/conf.py:1146 awx/sso/conf.py:1158 awx/sso/conf.py:1170 +#: awx/sso/conf.py:1189 awx/sso/models.py:16 +msgid "SAML" +msgstr "SAML" + +#: awx/sso/conf.py:941 +msgid "SAML Service Provider Metadata URL" +msgstr "SAML 服务提供商元数据 URL" + +#: awx/sso/conf.py:942 +msgid "" +"If your identity provider (IdP) allows uploading an XML metadata file, you " +"can download one from this URL." +msgstr "如果身份提供商 (IdP) 允许上传 XML 元数据文件,您可以从此 URL 下载一个。" + +#: awx/sso/conf.py:953 +msgid "SAML Service Provider Entity ID" +msgstr "SAML 服务提供商实体 ID" + +#: awx/sso/conf.py:954 +msgid "" +"The application-defined unique identifier used as the audience of the SAML " +"service provider (SP) configuration. This is usually the URL for Tower." +msgstr "应用定义的唯一标识符,用作 SAML 服务提供商 (SP) 配置的读者。这通常是 Tower 的 URL。" + +#: awx/sso/conf.py:968 +msgid "SAML Service Provider Public Certificate" +msgstr "SAML 服务提供商公共证书" + +#: awx/sso/conf.py:969 +msgid "" +"Create a keypair for Tower to use as a service provider (SP) and include the " +"certificate content here." +msgstr "为 Tower 创建一个密钥对,以用作服务提供商 (SP),并在此包含证书内容。" + +#: awx/sso/conf.py:981 +msgid "SAML Service Provider Private Key" +msgstr "SAML 服务提供商私钥" + +#: awx/sso/conf.py:982 +msgid "" +"Create a keypair for Tower to use as a service provider (SP) and include the " +"private key content here." +msgstr "为 Tower 创建一个密钥对,以用作服务提供商 (SP),并在此包含私钥内容。" + +#: awx/sso/conf.py:993 +msgid "SAML Service Provider Organization Info" +msgstr "SAML 服务提供商机构信息" + +#: awx/sso/conf.py:994 +msgid "" +"Provide the URL, display name, and the name of your app. Refer to the " +"Ansible Tower documentation for example syntax." +msgstr "提供 URL、显示名称和应用名称。示例语法请参阅 Ansible Tower 文档。" + +#: awx/sso/conf.py:1012 +msgid "SAML Service Provider Technical Contact" +msgstr "SAML 服务提供商技术联系人" + +#: awx/sso/conf.py:1013 +msgid "" +"Provide the name and email address of the technical contact for your service " +"provider. Refer to the Ansible Tower documentation for example syntax." +msgstr "为您的服务提供商提供技术联系人的姓名和电子邮件地址。示例语法请参阅 Ansible Tower 文档。" + +#: awx/sso/conf.py:1029 +msgid "SAML Service Provider Support Contact" +msgstr "SAML 服务提供商支持联系人" + +#: awx/sso/conf.py:1030 +msgid "" +"Provide the name and email address of the support contact for your service " +"provider. Refer to the Ansible Tower documentation for example syntax." +msgstr "为您的服务提供商提供支持联系人的姓名和电子邮件地址。示例语法请参阅 Ansible Tower 文档。" + +#: awx/sso/conf.py:1045 +msgid "SAML Enabled Identity Providers" +msgstr "SAML 启用的身份提供商" + +#: awx/sso/conf.py:1046 +msgid "" +"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." +msgstr "为使用中的每个身份提供商 (IdP) 配置实体 ID 、SSO URL 和证书。支持多个 SAML IdP。某些 IdP 可使用与默认 OID 不同的属性名称提供用户数据。每个 IdP 的属性名称可能会被覆写。如需了解更多详情和语法,请参阅 Ansible 文档。" + +#: awx/sso/conf.py:1082 +msgid "SAML Security Config" +msgstr "SAML 安全配置" + +#: awx/sso/conf.py:1083 +msgid "" +"A dict of key value pairs that are passed to the underlying python-saml " +"security setting https://github.com/onelogin/python-saml#settings" +msgstr "传递给底层 python-saml 安全设置的键值对字典 https://github.com/onelogin/python-saml#settings" + +#: awx/sso/conf.py:1114 +msgid "SAML Service Provider extra configuration data" +msgstr "SAML 服务提供商额外配置数据" + +#: awx/sso/conf.py:1115 +msgid "" +"A dict of key value pairs to be passed to the underlying python-saml Service " +"Provider configuration setting." +msgstr "传递给底层 python-saml 服务提供商配置设置的键值对字典。" + +#: awx/sso/conf.py:1127 +msgid "SAML IDP to extra_data attribute mapping" +msgstr "SAML IDP 到 extra_data 属性映射" + +#: awx/sso/conf.py:1128 +msgid "" +"A list of tuples that maps IDP attributes to extra_attributes. Each " +"attribute will be a list of values, even if only 1 value." +msgstr "将 IDP 属性映射到 extra_attributes 的元祖列表。每个属性将是一个值列表,即使只有 1 个值。" + +#: awx/sso/conf.py:1144 +msgid "SAML Organization Map" +msgstr "SAML 机构映射" + +#: awx/sso/conf.py:1156 +msgid "SAML Team Map" +msgstr "SAML 团队映射" + +#: awx/sso/conf.py:1168 +msgid "SAML Organization Attribute Mapping" +msgstr "SAML 机构属性映射" + +#: awx/sso/conf.py:1169 +msgid "Used to translate user organization membership into Tower." +msgstr "用于将用户机构成员资格转换为 Tower。" + +#: awx/sso/conf.py:1187 +msgid "SAML Team Attribute Mapping" +msgstr "SAML 团队属性映射" + +#: awx/sso/conf.py:1188 +msgid "Used to translate user team membership into Tower." +msgstr "用于将用户团队成员资格转换为 Tower。" + +#: awx/sso/fields.py:81 +msgid "Invalid field." +msgstr "无效字段。" + +#: awx/sso/fields.py:250 +#, python-brace-format +msgid "Invalid connection option(s): {invalid_options}." +msgstr "无效的连接选项:{invalid_options}。" + +#: awx/sso/fields.py:334 +msgid "Base" +msgstr "基本" + +#: awx/sso/fields.py:335 +msgid "One Level" +msgstr "一个级别" + +#: awx/sso/fields.py:336 +msgid "Subtree" +msgstr "子树" + +#: awx/sso/fields.py:354 +#, python-brace-format +msgid "Expected a list of three items but got {length} instead." +msgstr "预期为三个项的列表,但实际为 {length}。" + +#: awx/sso/fields.py:355 +#, python-brace-format +msgid "Expected an instance of LDAPSearch but got {input_type} instead." +msgstr "预期为 LDAPSearch 实例,但实际为 {input_type}。" + +#: awx/sso/fields.py:391 +#, python-brace-format +msgid "" +"Expected an instance of LDAPSearch or LDAPSearchUnion but got {input_type} " +"instead." +msgstr "预期为 LDAPSearch 或 LDAPSearchUnion 实例,但实际为 {input_type}。" + +#: awx/sso/fields.py:429 +#, python-brace-format +msgid "Invalid user attribute(s): {invalid_attrs}." +msgstr "无效的用户属性:{invalid_attrs}。" + +#: awx/sso/fields.py:447 +#, python-brace-format +msgid "Expected an instance of LDAPGroupType but got {input_type} instead." +msgstr "预期为 LDAPGroupType 实例,但实际为 {input_type}。" + +#: awx/sso/fields.py:487 +#, python-brace-format +msgid "Invalid key(s): {invalid_keys}." +msgstr "无效的密钥:{invalid_keys}。" + +#: awx/sso/fields.py:513 +#, python-brace-format +msgid "Invalid user flag: \"{invalid_flag}\"." +msgstr "无效的用户标记:\"{invalid_flag}\"。" + +#: awx/sso/fields.py:667 +#, python-brace-format +msgid "Invalid language code(s) for org info: {invalid_lang_codes}." +msgstr "用于机构信息的语言代码无效:{invalid_lang_codes}。" + +#: awx/sso/pipeline.py:27 +#, python-brace-format +msgid "An account cannot be found for {0}" +msgstr "无法为 {0} 找到帐户" + +#: awx/sso/pipeline.py:33 +msgid "Your account is inactive" +msgstr "您的帐户不活跃" + +#: awx/sso/validators.py:20 awx/sso/validators.py:46 +#, python-format +msgid "DN must include \"%%(user)s\" placeholder for username: %s" +msgstr "DN 必须包含 \"%%\" 占位符用于用户名:%s" + +#: awx/sso/validators.py:27 +#, python-format +msgid "Invalid DN: %s" +msgstr "无效的 DN:%s" + +#: awx/sso/validators.py:58 +#, python-format +msgid "Invalid filter: %s" +msgstr "无效的过滤器:%s" + +#: awx/sso/validators.py:69 +msgid "TACACS+ secret does not allow non-ascii characters" +msgstr "TACACS+ 机密不允许使用非 ascii 字符" + +#: awx/templates/error.html:4 +msgid "AWX" +msgstr "AWX" + +#: awx/templates/rest_framework/api.html:42 +msgid "Ansible Tower API Guide" +msgstr "Ansible Tower API 指南" + +#: awx/templates/rest_framework/api.html:43 +msgid "Back to Ansible Tower" +msgstr "返回 Ansible Tower" + +#: awx/templates/rest_framework/api.html:44 +msgid "Resize" +msgstr "调整大小" + +#: awx/ui/apps.py:9 awx/ui/conf.py:22 awx/ui/conf.py:36 awx/ui/conf.py:50 +#: awx/ui/conf.py:61 awx/ui/conf.py:71 +msgid "UI" +msgstr "UI" + +#: awx/ui/conf.py:16 +msgid "Off" +msgstr "关" + +#: awx/ui/conf.py:17 +msgid "Anonymous" +msgstr "匿名" + +#: awx/ui/conf.py:18 +msgid "Detailed" +msgstr "详细" + +#: awx/ui/conf.py:20 +msgid "User Analytics Tracking State" +msgstr "用户分析跟踪状态" + +#: awx/ui/conf.py:21 +msgid "Enable or Disable User Analytics Tracking." +msgstr "启用或禁用用户分析跟踪。" + +#: awx/ui/conf.py:31 +msgid "Custom Login Info" +msgstr "自定义登录信息" + +#: awx/ui/conf.py:32 +msgid "" +"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, as custom HTML or other markup languages are " +"not supported." +msgstr "如果需要,您可以使用此设置在登录模态的文本框中添加特定信息(如法律声明或免责声明)。添加的任何内容都必须使用明文,因为不支持自定义 HTML 或其他标记语言。" + +#: awx/ui/conf.py:45 +msgid "Custom Logo" +msgstr "自定义徽标" + +#: awx/ui/conf.py:46 +msgid "" +"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." +msgstr "要设置自定义徽标,请提供一个您创建的文件。要使自定义徽标达到最佳效果,请使用带透明背景的 .png 文件。支持 GIF 、PNG 和 JPEG 格式。" + +#: awx/ui/conf.py:58 +msgid "Max Job Events Retrieved by UI" +msgstr "UI 检索的最大作业事件数" + +#: awx/ui/conf.py:59 +msgid "" +"Maximum number of job events for the UI to retrieve within a single request." +msgstr "UI 在单个请求中检索的最大作业事件数。" + +#: awx/ui/conf.py:68 +msgid "Enable Live Updates in the UI" +msgstr "在 UI 中启用实时更新" + +#: awx/ui/conf.py:69 +msgid "" +"If disabled, the page will not refresh when events are received. Reloading " +"the page will be required to get the latest details." +msgstr "如果禁用,则在收到事件时不会刷新页面。需要重新载入页面才能获取最新详情。" + +#: awx/ui/fields.py:30 +msgid "" +"Invalid format for custom logo. Must be a data URL with a base64-encoded " +"GIF, PNG or JPEG image." +msgstr "无效的自定义徽标格式。必须是包含 base64 编码 GIF 、PNG 或 JPEG 图像的数据 URL。" + +#: awx/ui/fields.py:31 +msgid "Invalid base64-encoded data in data URL." +msgstr "数据 URL 中的 base64 编码数据无效。" + diff --git a/awx/main/access.py b/awx/main/access.py index 7f9be6533352..89a6c0607ded 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -11,7 +11,6 @@ from django.conf import settings from django.db.models import Q, Prefetch from django.contrib.auth.models import User -from django.contrib.contenttypes.models import ContentType from django.utils.translation import ugettext_lazy as _ from django.core.exceptions import ObjectDoesNotExist @@ -307,7 +306,7 @@ def user_has_resource_access(resource): return True # User has access to both, permission check passed - def check_license(self, add_host_name=None, feature=None, check_expiration=True): + def check_license(self, add_host_name=None, feature=None, check_expiration=True, quiet=False): validation_info = get_licenser().validate() if validation_info.get('license_type', 'UNLICENSED') == 'open': return @@ -317,8 +316,10 @@ def check_license(self, add_host_name=None, feature=None, check_expiration=True) validation_info['time_remaining'] = 99999999 validation_info['grace_period_remaining'] = 99999999 - report_violation = lambda message: logger.error(message) - + if quiet: + report_violation = lambda message: None + else: + report_violation = lambda message: logger.warning(message) if ( validation_info.get('trial', False) is True or validation_info['instance_count'] == 10 # basic 10 license @@ -332,14 +333,14 @@ def report_violation(message): report_violation(_("License has expired.")) free_instances = validation_info.get('free_instances', 0) - available_instances = validation_info.get('available_instances', 0) + instance_count = validation_info.get('instance_count', 0) if add_host_name: host_exists = Host.objects.filter(name=add_host_name).exists() if not host_exists and free_instances == 0: - report_violation(_("License count of %s instances has been reached.") % available_instances) + report_violation(_("License count of %s instances has been reached.") % instance_count) elif not host_exists and free_instances < 0: - report_violation(_("License count of %s instances has been exceeded.") % available_instances) + report_violation(_("License count of %s instances has been exceeded.") % instance_count) elif not add_host_name and free_instances < 0: report_violation(_("Host count exceeds available instances.")) @@ -403,14 +404,6 @@ def get_user_capabilities(self, obj, method_list=[], parent_obj=None, capabiliti # Cannot copy manual project without errors user_capabilities[display_method] = False continue - elif display_method in ['start', 'schedule'] and isinstance(obj, Group): # TODO: remove in 3.3 - try: - if obj.deprecated_inventory_source and not obj.deprecated_inventory_source._can_update(): - user_capabilities[display_method] = False - continue - except Group.deprecated_inventory_source.RelatedObjectDoesNotExist: - user_capabilities[display_method] = False - continue elif display_method in ['start', 'schedule'] and isinstance(obj, (Project)): if obj.scm_type == '': user_capabilities[display_method] = False @@ -502,7 +495,7 @@ def can_unattach(self, obj, sub_obj, relationship, data=None): # due to this special case, we use symmetrical logic with attach permission return self._can_attach(notification_template=sub_obj, resource_obj=obj) return super(NotificationAttachMixin, self).can_unattach( - obj, sub_obj, relationship, relationship, data=data + obj, sub_obj, relationship, data=data ) @@ -648,8 +641,8 @@ def can_admin(self, obj, data, allow_orphans=False, check_setting=True): # in these cases only superusers can modify orphan users return False return not obj.roles.all().exclude( - content_type=ContentType.objects.get_for_model(User) - ).filter(ancestors__in=self.user.roles.all()).exists() + ancestors__in=self.user.roles.all() + ).exists() else: return self.is_all_org_admin(obj) @@ -787,7 +780,6 @@ def can_change(self, obj, data): return self.user in obj.admin_role def can_delete(self, obj): - self.check_license(check_expiration=False) is_change_possible = self.can_change(obj, None) if not is_change_possible: return False @@ -907,7 +899,7 @@ class HostAccess(BaseAccess): model = Host select_related = ('created_by', 'modified_by', 'inventory', 'last_job__job_template', 'last_job_host_summary__job',) - prefetch_related = ('groups',) + prefetch_related = ('groups', 'inventory_sources') def filtered_queryset(self): return self.model.objects.filter(inventory__in=Inventory.accessible_pk_qs(self.user, 'read_role')) @@ -1111,11 +1103,6 @@ class CredentialTypeAccess(BaseAccess): def can_use(self, obj): return True - def get_method_capability(self, method, obj, parent_obj): - if obj.managed_by_tower: - return False - return super(CredentialTypeAccess, self).get_method_capability(method, obj, parent_obj) - def filtered_queryset(self): return self.model.objects.all() @@ -1190,6 +1177,8 @@ def can_delete(self, obj): def get_user_capabilities(self, obj, **kwargs): user_capabilities = super(CredentialAccess, self).get_user_capabilities(obj, **kwargs) user_capabilities['use'] = self.can_use(obj) + if getattr(obj, 'managed_by_tower', False) is True: + user_capabilities['edit'] = user_capabilities['delete'] = False return user_capabilities @@ -1409,7 +1398,7 @@ class JobTemplateAccess(NotificationAttachMixin, BaseAccess): ''' model = JobTemplate - select_related = ('created_by', 'modified_by', 'inventory', 'project', + select_related = ('created_by', 'modified_by', 'inventory', 'project', 'organization', 'next_schedule',) prefetch_related = ( 'instance_groups', @@ -1433,16 +1422,11 @@ def can_add(self, data): Users who are able to create deploy jobs can also run normal and check (dry run) jobs. ''' if not data: # So the browseable API will work - return ( - Project.accessible_objects(self.user, 'use_role').exists() or - Inventory.accessible_objects(self.user, 'use_role').exists()) + return Project.accessible_objects(self.user, 'use_role').exists() # if reference_obj is provided, determine if it can be copied reference_obj = data.get('reference_obj', None) - if 'survey_enabled' in data and data['survey_enabled']: - self.check_license(feature='surveys') - if self.user.is_superuser: return True @@ -1502,22 +1486,23 @@ def can_start(self, obj, validate_license=True): return self.user in obj.execute_role def can_change(self, obj, data): - data_for_change = data if self.user not in obj.admin_role and not self.user.is_superuser: return False - if data is not None: - data = dict(data) + if data is None: + return True - if self.changes_are_non_sensitive(obj, data): - if 'survey_enabled' in data and obj.survey_enabled != data['survey_enabled'] and data['survey_enabled']: - self.check_license(feature='surveys') - return True + data = dict(data) - for required_field in ('inventory', 'project'): - required_obj = getattr(obj, required_field, None) - if required_field not in data_for_change and required_obj is not None: - data_for_change[required_field] = required_obj.pk - return self.can_read(obj) and (self.can_add(data_for_change) if data is not None else True) + if self.changes_are_non_sensitive(obj, data): + return True + + for required_field, cls in (('inventory', Inventory), ('project', Project)): + is_mandatory = True + if not getattr(obj, '{}_id'.format(required_field)): + is_mandatory = False + if not self.check_related(required_field, cls, data, obj=obj, role_field='use_role', mandatory=is_mandatory): + return False + return True def changes_are_non_sensitive(self, obj, data): ''' @@ -1525,8 +1510,7 @@ def changes_are_non_sensitive(self, obj, data): thus can be made by a job template administrator which may not have access to the any inventory, project, or credentials associated with the template. ''' - # We are white listing fields that can - field_whitelist = [ + allowed_fields = [ 'name', 'description', 'forks', 'limit', 'verbosity', 'extra_vars', 'job_tags', 'force_handlers', 'skip_tags', 'ask_variables_on_launch', 'ask_tags_on_launch', 'ask_job_type_on_launch', 'ask_skip_tags_on_launch', @@ -1541,7 +1525,7 @@ def changes_are_non_sensitive(self, obj, data): if k not in [x.name for x in obj._meta.concrete_fields]: continue if hasattr(obj, k) and getattr(obj, k) != v: - if k not in field_whitelist and v != getattr(obj, '%s_id' % k, None) \ + if k not in allowed_fields and v != getattr(obj, '%s_id' % k, None) \ and not (hasattr(obj, '%s_id' % k) and getattr(obj, '%s_id' % k) is None and v == ''): # Equate '' to None in the case of foreign keys return False return True @@ -1552,9 +1536,9 @@ def can_delete(self, obj): @check_superuser def can_attach(self, obj, sub_obj, relationship, data, skip_sub_obj_read_check=False): if relationship == "instance_groups": - if not obj.project.organization: + if not obj.organization: return False - return self.user.can_access(type(sub_obj), "read", sub_obj) and self.user in obj.project.organization.admin_role + return self.user.can_access(type(sub_obj), "read", sub_obj) and self.user in obj.organization.admin_role if relationship == 'credentials' and isinstance(sub_obj, Credential): return self.user in obj.admin_role and self.user in sub_obj.use_role return super(JobTemplateAccess, self).can_attach( @@ -1585,6 +1569,7 @@ class JobAccess(BaseAccess): select_related = ('created_by', 'modified_by', 'job_template', 'inventory', 'project', 'project_update',) prefetch_related = ( + 'organization', 'unified_job_template', 'instance_group', 'credentials__credential_type', @@ -1605,42 +1590,19 @@ def filtered_queryset(self): return qs.filter( Q(job_template__in=JobTemplate.accessible_objects(self.user, 'read_role')) | - Q(inventory__organization__in=org_access_qs) | - Q(project__organization__in=org_access_qs)).distinct() - - def related_orgs(self, obj): - orgs = [] - if obj.inventory and obj.inventory.organization: - orgs.append(obj.inventory.organization) - if obj.project and obj.project.organization and obj.project.organization not in orgs: - orgs.append(obj.project.organization) - return orgs - - def org_access(self, obj, role_types=['admin_role']): - orgs = self.related_orgs(obj) - for org in orgs: - for role_type in role_types: - role = getattr(org, role_type) - if self.user in role: - return True - return False + Q(organization__in=org_access_qs)).distinct() def can_add(self, data, validate_license=True): - if validate_license: - self.check_license() - - if not data: # So the browseable API will work - return True - return self.user.is_superuser + raise NotImplementedError('Direct job creation not possible in v2 API') def can_change(self, obj, data): - return (obj.status == 'new' and - self.can_read(obj) and - self.can_add(data, validate_license=False)) + raise NotImplementedError('Direct job editing not supported in v2 API') @check_superuser def can_delete(self, obj): - return self.org_access(obj) + if not obj.organization: + return False + return self.user in obj.organization.admin_role def can_start(self, obj, validate_license=True): if validate_license: @@ -1660,6 +1622,7 @@ def can_start(self, obj, validate_license=True): except JobLaunchConfig.DoesNotExist: config = None + # Standard permissions model if obj.job_template and (self.user not in obj.job_template.execute_role): return False @@ -1674,24 +1637,17 @@ def can_start(self, obj, validate_license=True): if JobLaunchConfigAccess(self.user).can_add({'reference_obj': config}): return True - org_access = bool(obj.inventory) and self.user in obj.inventory.organization.inventory_admin_role - project_access = obj.project is None or self.user in obj.project.admin_role - credential_access = all([self.user in cred.use_role for cred in obj.credentials.all()]) + # Standard permissions model without job template involved + if obj.organization and self.user in obj.organization.execute_role: + return True + elif not (obj.job_template or obj.organization): + raise PermissionDenied(_('Job has been orphaned from its job template and organization.')) + elif obj.job_template and config is not None: + raise PermissionDenied(_('Job was launched with prompted fields you do not have access to.')) + elif obj.job_template and config is None: + raise PermissionDenied(_('Job was launched with unknown prompted fields. Organization admin permissions required.')) - # job can be relaunched if user could make an equivalent JT - ret = org_access and credential_access and project_access - if not ret and self.save_messages and not self.messages: - if not obj.job_template: - pretext = _('Job has been orphaned from its job template.') - elif config is None: - pretext = _('Job was launched with unknown prompted fields.') - else: - pretext = _('Job was launched with prompted fields.') - if credential_access: - self.messages['detail'] = '{} {}'.format(pretext, _(' Organization level permissions required.')) - else: - self.messages['detail'] = '{} {}'.format(pretext, _(' You do not have permission to related resources.')) - return ret + return False def get_method_capability(self, method, obj, parent_obj): if method == 'start': @@ -1704,10 +1660,16 @@ def get_method_capability(self, method, obj, parent_obj): def can_cancel(self, obj): if not obj.can_cancel: return False - # Delete access allows org admins to stop running jobs - if self.user == obj.created_by or self.can_delete(obj): + # Users may always cancel their own jobs + if self.user == obj.created_by: + return True + # Users with direct admin to JT may cancel jobs started by anyone + if obj.job_template and self.user in obj.job_template.admin_role: + return True + # If orphaned, allow org JT admins to stop running jobs + if not obj.job_template and obj.organization and self.user in obj.organization.job_template_admin_role: return True - return obj.job_template is not None and self.user in obj.job_template.admin_role + return False class SystemJobTemplateAccess(BaseAccess): @@ -1942,11 +1904,11 @@ def can_delete(self, obj): # TODO: notification attachments? class WorkflowJobTemplateAccess(NotificationAttachMixin, BaseAccess): ''' - I can only see/manage Workflow Job Templates if I'm a super user + I can see/manage Workflow Job Templates based on object roles ''' model = WorkflowJobTemplate - select_related = ('created_by', 'modified_by', 'next_schedule', + select_related = ('created_by', 'modified_by', 'organization', 'next_schedule', 'admin_role', 'execute_role', 'read_role',) def filtered_queryset(self): @@ -1964,10 +1926,6 @@ def can_add(self, data): if not data: # So the browseable API will work return Organization.accessible_objects(self.user, 'workflow_admin_role').exists() - # will check this if surveys are added to WFJT - if 'survey_enabled' in data and data['survey_enabled']: - self.check_license(feature='surveys') - return ( self.check_related('organization', Organization, data, role_field='workflow_admin_role', mandatory=True) and self.check_related('inventory', Inventory, data, role_field='use_role') @@ -2036,7 +1994,7 @@ class WorkflowJobAccess(BaseAccess): I can also cancel it if I started it ''' model = WorkflowJob - select_related = ('created_by', 'modified_by',) + select_related = ('created_by', 'modified_by', 'organization',) def filtered_queryset(self): return WorkflowJob.objects.filter( @@ -2238,7 +2196,7 @@ class JobEventAccess(BaseAccess): ''' model = JobEvent - prefetch_related = ('hosts', 'job__job_template', 'host',) + prefetch_related = ('job__job_template', 'host',) def filtered_queryset(self): return self.model.objects.filter( @@ -2330,6 +2288,7 @@ class UnifiedJobTemplateAccess(BaseAccess): prefetch_related = ( 'last_job', 'current_job', + 'organization', 'credentials__credential_type', Prefetch('labels', queryset=Label.objects.all().order_by('name')), ) @@ -2369,6 +2328,7 @@ class UnifiedJobAccess(BaseAccess): prefetch_related = ( 'created_by', 'modified_by', + 'organization', 'unified_job_node__workflow_job', 'unified_job_template', 'instance_group', @@ -2399,8 +2359,7 @@ def filtered_queryset(self): Q(unified_job_template_id__in=UnifiedJobTemplate.accessible_pk_qs(self.user, 'read_role')) | Q(inventoryupdate__inventory_source__inventory__id__in=inv_pk_qs) | Q(adhoccommand__inventory__id__in=inv_pk_qs) | - Q(job__inventory__organization__in=org_auditor_qs) | - Q(job__project__organization__in=org_auditor_qs) + Q(organization__in=org_auditor_qs) ) return qs @@ -2427,6 +2386,9 @@ def filtered_queryset(self): def can_add(self, data): if not JobLaunchConfigAccess(self.user).can_add(data): return False + if not data: + return Role.objects.filter(role_field__in=['update_role', 'execute_role'], ancestors__in=self.user.roles.all()).exists() + return self.check_related('unified_job_template', UnifiedJobTemplate, data, role_field='execute_role', mandatory=True) @check_superuser @@ -2514,13 +2476,16 @@ def can_delete(self, obj): class LabelAccess(BaseAccess): ''' - I can see/use a Label if I have permission to associated organization + I can see/use a Label if I have permission to associated organization, or to a JT that the label is on ''' model = Label prefetch_related = ('modified_by', 'created_by', 'organization',) def filtered_queryset(self): - return self.model.objects.all() + return self.model.objects.filter( + Q(organization__in=Organization.accessible_pk_qs(self.user, 'read_role')) | + Q(unifiedjobtemplate_labels__in=UnifiedJobTemplate.accessible_pk_qs(self.user, 'read_role')) + ) @check_superuser def can_add(self, data): @@ -2785,6 +2750,9 @@ def can_add(self, data): else: return (self.check_related('workflow_approval_template', UnifiedJobTemplate, role_field='admin_role')) + def can_change(self, obj, data): + return self.user.can_access(WorkflowJobTemplate, 'change', obj.workflow_job_template, data={}) + def can_start(self, obj, validate_license=False): # for copying WFJTs that contain approval nodes if self.user.is_superuser: diff --git a/awx/main/analytics/__init__.py b/awx/main/analytics/__init__.py index 9ab526f29be2..6aee1cef911f 100644 --- a/awx/main/analytics/__init__.py +++ b/awx/main/analytics/__init__.py @@ -1 +1 @@ -from .core import register, gather, ship, table_version # noqa +from .core import all_collectors, expensive_collectors, register, gather, ship # noqa diff --git a/awx/main/analytics/broadcast_websocket.py b/awx/main/analytics/broadcast_websocket.py new file mode 100644 index 000000000000..d8abcb4745d2 --- /dev/null +++ b/awx/main/analytics/broadcast_websocket.py @@ -0,0 +1,169 @@ +import datetime +import asyncio +import logging +import aioredis +import redis +import re + +from prometheus_client import ( + generate_latest, + Gauge, + Counter, + Enum, + CollectorRegistry, + parser, +) + +from django.conf import settings + + +BROADCAST_WEBSOCKET_REDIS_KEY_NAME = 'broadcast_websocket_stats' + + +logger = logging.getLogger('awx.analytics.broadcast_websocket') + + +def dt_to_seconds(dt): + return int((dt - datetime.datetime(1970,1,1)).total_seconds()) + + +def now_seconds(): + return dt_to_seconds(datetime.datetime.now()) + + +def safe_name(s): + # Replace all non alpha-numeric characters with _ + return re.sub('[^0-9a-zA-Z]+', '_', s) + + +# Second granularity; Per-minute +class FixedSlidingWindow(): + def __init__(self, start_time=None): + self.buckets = dict() + self.start_time = start_time or now_seconds() + + def cleanup(self, now_bucket=None): + now_bucket = now_bucket or now_seconds() + if self.start_time + 60 < now_bucket: + self.start_time = now_bucket - 60 + + # Delete old entries + for k in list(self.buckets.keys()): + if k < self.start_time: + del self.buckets[k] + + def record(self, ts=None): + now_bucket = ts or dt_to_seconds(datetime.datetime.now()) + + val = self.buckets.get(now_bucket, 0) + self.buckets[now_bucket] = val + 1 + + self.cleanup(now_bucket) + + def render(self, ts=None): + self.cleanup(now_bucket=ts) + return sum(self.buckets.values()) or 0 + + +class BroadcastWebsocketStatsManager(): + def __init__(self, event_loop, local_hostname): + self._local_hostname = local_hostname + + self._event_loop = event_loop + self._stats = dict() + self._redis_key = BROADCAST_WEBSOCKET_REDIS_KEY_NAME + + def new_remote_host_stats(self, remote_hostname): + self._stats[remote_hostname] = BroadcastWebsocketStats(self._local_hostname, + remote_hostname) + return self._stats[remote_hostname] + + def delete_remote_host_stats(self, remote_hostname): + del self._stats[remote_hostname] + + async def run_loop(self): + try: + redis_conn = await aioredis.create_redis_pool(settings.BROKER_URL) + while True: + stats_data_str = ''.join(stat.serialize() for stat in self._stats.values()) + await redis_conn.set(self._redis_key, stats_data_str) + + await asyncio.sleep(settings.BROADCAST_WEBSOCKET_STATS_POLL_RATE_SECONDS) + except Exception as e: + logger.warn(e) + await asyncio.sleep(settings.BROADCAST_WEBSOCKET_STATS_POLL_RATE_SECONDS) + self.start() + + def start(self): + self.async_task = self._event_loop.create_task(self.run_loop()) + return self.async_task + + @classmethod + def get_stats_sync(cls): + ''' + Stringified verion of all the stats + ''' + redis_conn = redis.Redis.from_url(settings.BROKER_URL) + stats_str = redis_conn.get(BROADCAST_WEBSOCKET_REDIS_KEY_NAME) or b'' + return parser.text_string_to_metric_families(stats_str.decode('UTF-8')) + + +class BroadcastWebsocketStats(): + def __init__(self, local_hostname, remote_hostname): + self._local_hostname = local_hostname + self._remote_hostname = remote_hostname + self._registry = CollectorRegistry() + + # TODO: More robust replacement + self.name = safe_name(self._local_hostname) + self.remote_name = safe_name(self._remote_hostname) + + self._messages_received_total = Counter(f'awx_{self.remote_name}_messages_received_total', + 'Number of messages received, to be forwarded, by the broadcast websocket system', + registry=self._registry) + self._messages_received = Gauge(f'awx_{self.remote_name}_messages_received', + 'Number forwarded messages received by the broadcast websocket system, for the duration of the current connection', + registry=self._registry) + self._connection = Enum(f'awx_{self.remote_name}_connection', + 'Websocket broadcast connection', + states=['disconnected', 'connected'], + registry=self._registry) + self._connection.state('disconnected') + self._connection_start = Gauge(f'awx_{self.remote_name}_connection_start', + 'Time the connection was established', + registry=self._registry) + + self._messages_received_per_minute = Gauge(f'awx_{self.remote_name}_messages_received_per_minute', + 'Messages received per minute', + registry=self._registry) + self._internal_messages_received_per_minute = FixedSlidingWindow() + + def unregister(self): + self._registry.unregister(f'awx_{self.remote_name}_messages_received') + self._registry.unregister(f'awx_{self.remote_name}_connection') + + def record_message_received(self): + self._internal_messages_received_per_minute.record() + self._messages_received.inc() + self._messages_received_total.inc() + + def record_connection_established(self): + self._connection.state('connected') + self._connection_start.set_to_current_time() + self._messages_received.set(0) + + def record_connection_lost(self): + self._connection.state('disconnected') + + def get_connection_duration(self): + return (datetime.datetime.now() - self._connection_established_ts).total_seconds() + + def render(self): + msgs_per_min = self._internal_messages_received_per_minute.render() + self._messages_received_per_minute.set(msgs_per_min) + + def serialize(self): + self.render() + + registry_data = generate_latest(self._registry).decode('UTF-8') + return registry_data diff --git a/awx/main/analytics/collectors.py b/awx/main/analytics/collectors.py index 80a773f1923d..b0ac43cc6505 100644 --- a/awx/main/analytics/collectors.py +++ b/awx/main/analytics/collectors.py @@ -1,3 +1,4 @@ +import io import os import os.path import platform @@ -6,13 +7,14 @@ from django.db.models import Count from django.conf import settings from django.utils.timezone import now +from django.utils.translation import ugettext_lazy as _ from awx.conf.license import get_license from awx.main.utils import (get_awx_version, get_ansible_version, get_custom_venv_choices, camelcase_to_underscore) from awx.main import models from django.contrib.sessions.models import Session -from awx.main.analytics import register, table_version +from awx.main.analytics import register ''' This module is used to define metrics collected by awx.main.analytics.gather() @@ -31,9 +33,9 @@ def something(since): ''' -@register('config', '1.0') -def config(since): - license_info = get_license(show_key=False) +@register('config', '1.2', description=_('General platform configuration.')) +def config(since, **kwargs): + license_info = get_license() install_type = 'traditional' if os.environ.get('container') == 'oci': install_type = 'openshift' @@ -53,6 +55,7 @@ def config(since): 'ansible_version': get_ansible_version(), 'license_type': license_info.get('license_type', 'UNLICENSED'), 'free_instances': license_info.get('free_instances', 0), + 'total_licensed_instances': license_info.get('instance_count', 0), 'license_expiry': license_info.get('time_remaining', 0), 'pendo_tracking': settings.PENDO_TRACKING_STATE, 'authentication_backends': settings.AUTHENTICATION_BACKENDS, @@ -62,8 +65,8 @@ def config(since): } -@register('counts', '1.0') -def counts(since): +@register('counts', '1.0', description=_('Counts of objects such as organizations, inventories, and projects')) +def counts(since, **kwargs): counts = {} for cls in (models.Organization, models.Team, models.User, models.Inventory, models.Credential, models.Project, @@ -97,8 +100,8 @@ def counts(since): return counts -@register('org_counts', '1.0') -def org_counts(since): +@register('org_counts', '1.0', description=_('Counts of users and teams by organization')) +def org_counts(since, **kwargs): counts = {} for org in models.Organization.objects.annotate(num_users=Count('member_role__members', distinct=True), num_teams=Count('teams', distinct=True)).values('name', 'id', 'num_users', 'num_teams'): @@ -109,8 +112,8 @@ def org_counts(since): return counts -@register('cred_type_counts', '1.0') -def cred_type_counts(since): +@register('cred_type_counts', '1.0', description=_('Counts of credentials by credential type')) +def cred_type_counts(since, **kwargs): counts = {} for cred_type in models.CredentialType.objects.annotate(num_credentials=Count( 'credentials', distinct=True)).values('name', 'id', 'managed_by_tower', 'num_credentials'): @@ -121,28 +124,33 @@ def cred_type_counts(since): return counts -@register('inventory_counts', '1.0') -def inventory_counts(since): +@register('inventory_counts', '1.2', description=_('Inventories, their inventory sources, and host counts')) +def inventory_counts(since, **kwargs): counts = {} for inv in models.Inventory.objects.filter(kind='').annotate(num_sources=Count('inventory_sources', distinct=True), num_hosts=Count('hosts', distinct=True)).only('id', 'name', 'kind'): + source_list = [] + for source in inv.inventory_sources.filter().annotate(num_hosts=Count('hosts', distinct=True)).values('name','source', 'num_hosts'): + source_list.append(source) counts[inv.id] = {'name': inv.name, 'kind': inv.kind, 'hosts': inv.num_hosts, - 'sources': inv.num_sources + 'sources': inv.num_sources, + 'source_list': source_list } for smart_inv in models.Inventory.objects.filter(kind='smart'): counts[smart_inv.id] = {'name': smart_inv.name, 'kind': smart_inv.kind, - 'num_hosts': smart_inv.hosts.count(), - 'num_sources': smart_inv.inventory_sources.count() + 'hosts': smart_inv.hosts.count(), + 'sources': 0, + 'source_list': [] } return counts -@register('projects_by_scm_type', '1.0') -def projects_by_scm_type(since): +@register('projects_by_scm_type', '1.0', description=_('Counts of projects by source control type')) +def projects_by_scm_type(since, **kwargs): counts = dict( (t[0] or 'manual', 0) for t in models.Project.SCM_TYPE_CHOICES @@ -160,8 +168,8 @@ def _get_isolated_datetime(last_check): return last_check -@register('instance_info', '1.0') -def instance_info(since, include_hostnames=False): +@register('instance_info', '1.0', description=_('Cluster topology and capacity')) +def instance_info(since, include_hostnames=False, **kwargs): info = {} instances = models.Instance.objects.values_list('hostname').values( 'uuid', 'version', 'capacity', 'cpu', 'memory', 'managed_by_policy', 'hostname', 'last_isolated_check', 'enabled') @@ -186,8 +194,7 @@ def instance_info(since, include_hostnames=False): return info -@register('job_counts', '1.0') -def job_counts(since): +def job_counts(since, **kwargs): counts = {} counts['total_jobs'] = models.UnifiedJob.objects.exclude(launch_type='sync').count() counts['status'] = dict(models.UnifiedJob.objects.exclude(launch_type='sync').values_list('status').annotate(Count('status')).order_by()) @@ -196,8 +203,7 @@ def job_counts(since): return counts -@register('job_instance_counts', '1.0') -def job_instance_counts(since): +def job_instance_counts(since, **kwargs): counts = {} job_types = models.UnifiedJob.objects.exclude(launch_type='sync').values_list( 'execution_node', 'launch_type').annotate(job_launch_type=Count('launch_type')).order_by() @@ -211,34 +217,79 @@ def job_instance_counts(since): return counts -@register('query_info', '1.0') -def query_info(since, collection_type): +@register('query_info', '1.0', description=_('Metadata about the analytics collected')) +def query_info(since, collection_type, until, **kwargs): query_info = {} query_info['last_run'] = str(since) - query_info['current_time'] = str(now()) + query_info['current_time'] = str(until) query_info['collection_type'] = collection_type return query_info -# Copies Job Events from db to a .csv to be shipped -@table_version('events_table.csv', '1.0') -@table_version('unified_jobs_table.csv', '1.0') -@table_version('unified_job_template_table.csv', '1.0') -def copy_tables(since, full_path): - def _copy_table(table, query, path): - file_path = os.path.join(path, table + '_table.csv') - file = open(file_path, 'w', encoding='utf-8') - with connection.cursor() as cursor: - cursor.copy_expert(query, file) - file.close() - return file_path +''' +The event table can be *very* large, and we have a 100MB upload limit. + +Split large table dumps at dump time into a series of files. +''' +MAX_TABLE_SIZE = 200 * 1048576 + + +class FileSplitter(io.StringIO): + def __init__(self, filespec=None, *args, **kwargs): + self.filespec = filespec + self.files = [] + self.currentfile = None + self.header = None + self.counter = 0 + self.cycle_file() + + def cycle_file(self): + if self.currentfile: + self.currentfile.close() + self.counter = 0 + fname = '{}_split{}'.format(self.filespec, len(self.files)) + self.currentfile = open(fname, 'w', encoding='utf-8') + self.files.append(fname) + if self.header: + self.currentfile.write('{}\n'.format(self.header)) + + def file_list(self): + self.currentfile.close() + # Check for an empty dump + if len(self.header) + 1 == self.counter: + os.remove(self.files[-1]) + self.files = self.files[:-1] + # If we only have one file, remove the suffix + if len(self.files) == 1: + os.rename(self.files[0],self.files[0].replace('_split0','')) + return self.files + + def write(self, s): + if not self.header: + self.header = s[0:s.index('\n')] + self.counter += self.currentfile.write(s) + if self.counter >= MAX_TABLE_SIZE: + self.cycle_file() + +def _copy_table(table, query, path): + file_path = os.path.join(path, table + '_table.csv') + file = FileSplitter(filespec=file_path) + with connection.cursor() as cursor: + cursor.copy_expert(query, file) + return file.file_list() + + +@register('events_table', '1.2', format='csv', description=_('Automation task records'), expensive=True) +def events_table(since, full_path, until, **kwargs): events_query = '''COPY (SELECT main_jobevent.id, main_jobevent.created, + main_jobevent.modified, main_jobevent.uuid, main_jobevent.parent_uuid, main_jobevent.event, main_jobevent.event_data::json->'task_action' AS task_action, + (CASE WHEN event = 'playbook_on_stats' THEN event_data END) as playbook_on_stats, main_jobevent.failed, main_jobevent.changed, main_jobevent.playbook, @@ -248,16 +299,27 @@ def _copy_table(table, query, path): main_jobevent.job_id, main_jobevent.host_id, main_jobevent.host_name + , CAST(main_jobevent.event_data::json->>'start' AS TIMESTAMP WITH TIME ZONE) AS start, + CAST(main_jobevent.event_data::json->>'end' AS TIMESTAMP WITH TIME ZONE) AS end, + main_jobevent.event_data::json->'duration' AS duration, + main_jobevent.event_data::json->'res'->'warnings' AS warnings, + main_jobevent.event_data::json->'res'->'deprecations' AS deprecations FROM main_jobevent - WHERE main_jobevent.created > {} - ORDER BY main_jobevent.id ASC) TO STDOUT WITH CSV HEADER'''.format(since.strftime("'%Y-%m-%d %H:%M:%S'")) - _copy_table(table='events', query=events_query, path=full_path) + WHERE (main_jobevent.created > '{}' AND main_jobevent.created <= '{}') + ORDER BY main_jobevent.id ASC) TO STDOUT WITH CSV HEADER + '''.format(since.isoformat(),until.isoformat()) + return _copy_table(table='events', query=events_query, path=full_path) + +@register('unified_jobs_table', '1.1', format='csv', description=_('Data on jobs run'), expensive=True) +def unified_jobs_table(since, full_path, until, **kwargs): unified_job_query = '''COPY (SELECT main_unifiedjob.id, main_unifiedjob.polymorphic_ctype_id, django_content_type.model, - main_project.organization_id, + main_unifiedjob.organization_id, main_organization.name as organization_name, + main_job.inventory_id, + main_inventory.name as inventory_name, main_unifiedjob.created, main_unifiedjob.name, main_unifiedjob.unified_job_template_id, @@ -274,15 +336,20 @@ def _copy_table(table, query, path): main_unifiedjob.job_explanation, main_unifiedjob.instance_group_id FROM main_unifiedjob - JOIN main_job ON main_unifiedjob.id = main_job.unifiedjob_ptr_id JOIN django_content_type ON main_unifiedjob.polymorphic_ctype_id = django_content_type.id - JOIN main_project ON main_project.unifiedjobtemplate_ptr_id = main_job.project_id - JOIN main_organization ON main_organization.id = main_project.organization_id - WHERE main_unifiedjob.created > {} - AND main_unifiedjob.launch_type != 'sync' - ORDER BY main_unifiedjob.id ASC) TO STDOUT WITH CSV HEADER'''.format(since.strftime("'%Y-%m-%d %H:%M:%S'")) - _copy_table(table='unified_jobs', query=unified_job_query, path=full_path) + LEFT JOIN main_job ON main_unifiedjob.id = main_job.unifiedjob_ptr_id + LEFT JOIN main_inventory ON main_job.inventory_id = main_inventory.id + LEFT JOIN main_organization ON main_organization.id = main_unifiedjob.organization_id + WHERE ((main_unifiedjob.created > '{0}' AND main_unifiedjob.created <= '{1}') + OR (main_unifiedjob.finished > '{0}' AND main_unifiedjob.finished <= '{1}')) + AND main_unifiedjob.launch_type != 'sync' + ORDER BY main_unifiedjob.id ASC) TO STDOUT WITH CSV HEADER + '''.format(since.isoformat(),until.isoformat()) + return _copy_table(table='unified_jobs', query=unified_job_query, path=full_path) + +@register('unified_job_template_table', '1.0', format='csv', description=_('Data on job templates')) +def unified_job_template_table(since, full_path, **kwargs): unified_job_template_query = '''COPY (SELECT main_unifiedjobtemplate.id, main_unifiedjobtemplate.polymorphic_ctype_id, django_content_type.model, @@ -300,6 +367,73 @@ def _copy_table(table, query, path): main_unifiedjobtemplate.status FROM main_unifiedjobtemplate, django_content_type WHERE main_unifiedjobtemplate.polymorphic_ctype_id = django_content_type.id - ORDER BY main_unifiedjobtemplate.id ASC) TO STDOUT WITH CSV HEADER'''.format(since.strftime("'%Y-%m-%d %H:%M:%S'")) - _copy_table(table='unified_job_template', query=unified_job_template_query, path=full_path) - return + ORDER BY main_unifiedjobtemplate.id ASC) TO STDOUT WITH CSV HEADER''' + return _copy_table(table='unified_job_template', query=unified_job_template_query, path=full_path) + + +@register('workflow_job_node_table', '1.0', format='csv', description=_('Data on workflow runs'), expensive=True) +def workflow_job_node_table(since, full_path, until, **kwargs): + workflow_job_node_query = '''COPY (SELECT main_workflowjobnode.id, + main_workflowjobnode.created, + main_workflowjobnode.modified, + main_workflowjobnode.job_id, + main_workflowjobnode.unified_job_template_id, + main_workflowjobnode.workflow_job_id, + main_workflowjobnode.inventory_id, + success_nodes.nodes AS success_nodes, + failure_nodes.nodes AS failure_nodes, + always_nodes.nodes AS always_nodes, + main_workflowjobnode.do_not_run, + main_workflowjobnode.all_parents_must_converge + FROM main_workflowjobnode + LEFT JOIN ( + SELECT from_workflowjobnode_id, ARRAY_AGG(to_workflowjobnode_id) AS nodes + FROM main_workflowjobnode_success_nodes + GROUP BY from_workflowjobnode_id + ) success_nodes ON main_workflowjobnode.id = success_nodes.from_workflowjobnode_id + LEFT JOIN ( + SELECT from_workflowjobnode_id, ARRAY_AGG(to_workflowjobnode_id) AS nodes + FROM main_workflowjobnode_failure_nodes + GROUP BY from_workflowjobnode_id + ) failure_nodes ON main_workflowjobnode.id = failure_nodes.from_workflowjobnode_id + LEFT JOIN ( + SELECT from_workflowjobnode_id, ARRAY_AGG(to_workflowjobnode_id) AS nodes + FROM main_workflowjobnode_always_nodes + GROUP BY from_workflowjobnode_id + ) always_nodes ON main_workflowjobnode.id = always_nodes.from_workflowjobnode_id + WHERE (main_workflowjobnode.modified > '{}' AND main_workflowjobnode.modified <= '{}') + ORDER BY main_workflowjobnode.id ASC) TO STDOUT WITH CSV HEADER + '''.format(since.isoformat(),until.isoformat()) + return _copy_table(table='workflow_job_node', query=workflow_job_node_query, path=full_path) + + +@register('workflow_job_template_node_table', '1.0', format='csv', description=_('Data on workflows')) +def workflow_job_template_node_table(since, full_path, **kwargs): + workflow_job_template_node_query = '''COPY (SELECT main_workflowjobtemplatenode.id, + main_workflowjobtemplatenode.created, + main_workflowjobtemplatenode.modified, + main_workflowjobtemplatenode.unified_job_template_id, + main_workflowjobtemplatenode.workflow_job_template_id, + main_workflowjobtemplatenode.inventory_id, + success_nodes.nodes AS success_nodes, + failure_nodes.nodes AS failure_nodes, + always_nodes.nodes AS always_nodes, + main_workflowjobtemplatenode.all_parents_must_converge + FROM main_workflowjobtemplatenode + LEFT JOIN ( + SELECT from_workflowjobtemplatenode_id, ARRAY_AGG(to_workflowjobtemplatenode_id) AS nodes + FROM main_workflowjobtemplatenode_success_nodes + GROUP BY from_workflowjobtemplatenode_id + ) success_nodes ON main_workflowjobtemplatenode.id = success_nodes.from_workflowjobtemplatenode_id + LEFT JOIN ( + SELECT from_workflowjobtemplatenode_id, ARRAY_AGG(to_workflowjobtemplatenode_id) AS nodes + FROM main_workflowjobtemplatenode_failure_nodes + GROUP BY from_workflowjobtemplatenode_id + ) failure_nodes ON main_workflowjobtemplatenode.id = failure_nodes.from_workflowjobtemplatenode_id + LEFT JOIN ( + SELECT from_workflowjobtemplatenode_id, ARRAY_AGG(to_workflowjobtemplatenode_id) AS nodes + FROM main_workflowjobtemplatenode_always_nodes + GROUP BY from_workflowjobtemplatenode_id + ) always_nodes ON main_workflowjobtemplatenode.id = always_nodes.from_workflowjobtemplatenode_id + ORDER BY main_workflowjobtemplatenode.id ASC) TO STDOUT WITH CSV HEADER''' + return _copy_table(table='workflow_job_template_node', query=workflow_job_template_node_query, path=full_path) diff --git a/awx/main/analytics/core.py b/awx/main/analytics/core.py index a08cc84b8b63..e9f7f99bc056 100644 --- a/awx/main/analytics/core.py +++ b/awx/main/analytics/core.py @@ -14,21 +14,17 @@ from awx.conf.license import get_license from awx.main.models import Job from awx.main.access import access_registry -from awx.main.models.ha import TowerAnalyticsState -from awx.main.utils import get_awx_http_client_headers +from awx.main.utils import get_awx_http_client_headers, set_environ - -__all__ = ['register', 'gather', 'ship', 'table_version'] +__all__ = ['register', 'gather', 'ship'] logger = logging.getLogger('awx.main.analytics') -manifest = dict() - def _valid_license(): try: - if get_license(show_key=False).get('license_type', 'UNLICENSED') == 'open': + if get_license().get('license_type', 'UNLICENSED') == 'open': return False access_registry[Job](None).check_license() except PermissionDenied: @@ -37,110 +33,194 @@ def _valid_license(): return True -def register(key, version): +def all_collectors(): + from awx.main.analytics import collectors + + collector_dict = {} + module = collectors + for name, func in inspect.getmembers(module): + if inspect.isfunction(func) and hasattr(func, '__awx_analytics_key__'): + key = func.__awx_analytics_key__ + desc = func.__awx_analytics_description__ or '' + version = func.__awx_analytics_version__ + collector_dict[key] = { 'name': key, 'version': version, 'description': desc} + return collector_dict + + +def expensive_collectors(): + from awx.main.analytics import collectors + + ret = [] + module = collectors + for name, func in inspect.getmembers(module): + if inspect.isfunction(func) and hasattr(func, '__awx_analytics_key__') and func.__awx_expensive__: + ret.append(func.__awx_analytics_key__) + return ret + + +def register(key, version, description=None, format='json', expensive=False): """ A decorator used to register a function as a metric collector. - Decorated functions should return JSON-serializable objects. + Decorated functions should do the following based on format: + - json: return JSON-serializable objects. + - csv: write CSV data to a filename named 'key' @register('projects_by_scm_type', 1) def projects_by_scm_type(): - return {'git': 5, 'svn': 1, 'hg': 0} + return {'git': 5, 'svn': 1} """ def decorate(f): f.__awx_analytics_key__ = key f.__awx_analytics_version__ = version + f.__awx_analytics_description__ = description + f.__awx_analytics_type__ = format + f.__awx_expensive__ = expensive return f return decorate -def table_version(file_name, version): - - global manifest - manifest[file_name] = version - - def decorate(f): - return f - - return decorate - - -def gather(dest=None, module=None, collection_type='scheduled'): +def gather(dest=None, module=None, subset = None, since = None, until = now(), collection_type='scheduled'): """ Gather all defined metrics and write them as JSON files in a .tgz :param dest: the (optional) absolute path to write a compressed tarball - :pararm module: the module to search for registered analytic collector + :param module: the module to search for registered analytic collector functions; defaults to awx.main.analytics.collectors """ - - run_now = now() - state = TowerAnalyticsState.get_solo() - last_run = state.last_run - logger.debug("Last analytics run was: {}".format(last_run)) - - max_interval = now() - timedelta(weeks=4) - if last_run < max_interval or not last_run: - last_run = max_interval + def _write_manifest(destdir, manifest): + path = os.path.join(destdir, 'manifest.json') + with open(path, 'w', encoding='utf-8') as f: + try: + json.dump(manifest, f) + except Exception: + f.close() + os.remove(f.name) + logger.exception("Could not generate manifest.json") + + last_run = since or settings.AUTOMATION_ANALYTICS_LAST_GATHER or (now() - timedelta(weeks=4)) + logger.debug("Last analytics run was: {}".format(settings.AUTOMATION_ANALYTICS_LAST_GATHER)) if _valid_license() is False: logger.exception("Invalid License provided, or No License Provided") - return "Error: Invalid License provided, or No License Provided" + return None if collection_type != 'dry-run' and not settings.INSIGHTS_TRACKING_STATE: logger.error("Automation Analytics not enabled. Use --dry-run to gather locally without sending.") - return + return None - if module is None: + collector_list = [] + if module: + collector_module = module + else: from awx.main.analytics import collectors - module = collectors - - + collector_module = collectors + for name, func in inspect.getmembers(collector_module): + if ( + inspect.isfunction(func) and + hasattr(func, '__awx_analytics_key__') and + (not subset or name in subset) + ): + collector_list.append((name, func)) + + manifest = dict() dest = dest or tempfile.mkdtemp(prefix='awx_analytics') - for name, func in inspect.getmembers(module): - if inspect.isfunction(func) and hasattr(func, '__awx_analytics_key__'): + gather_dir = os.path.join(dest, 'stage') + os.mkdir(gather_dir, 0o700) + num_splits = 1 + for name, func in collector_list: + if func.__awx_analytics_type__ == 'json': key = func.__awx_analytics_key__ - manifest['{}.json'.format(key)] = func.__awx_analytics_version__ - path = '{}.json'.format(os.path.join(dest, key)) + path = '{}.json'.format(os.path.join(gather_dir, key)) with open(path, 'w', encoding='utf-8') as f: try: - if func.__name__ == 'query_info': - json.dump(func(last_run, collection_type=collection_type), f) - else: - json.dump(func(last_run), f) + json.dump(func(last_run, collection_type=collection_type, until=until), f) + manifest['{}.json'.format(key)] = func.__awx_analytics_version__ except Exception: logger.exception("Could not generate metric {}.json".format(key)) f.close() os.remove(f.name) - - path = os.path.join(dest, 'manifest.json') - with open(path, 'w', encoding='utf-8') as f: - try: - json.dump(manifest, f) - except Exception: - logger.exception("Could not generate manifest.json") - f.close() - os.remove(f.name) - + elif func.__awx_analytics_type__ == 'csv': + key = func.__awx_analytics_key__ + try: + files = func(last_run, full_path=gather_dir, until=until) + if files: + manifest['{}.csv'.format(key)] = func.__awx_analytics_version__ + if len(files) > num_splits: + num_splits = len(files) + except Exception: + logger.exception("Could not generate metric {}.csv".format(key)) + + if not manifest: + # No data was collected + logger.warning("No data from {} to {}".format(last_run, until)) + shutil.rmtree(dest) + return None + + # Always include config.json if we're using our collectors + if 'config.json' not in manifest.keys() and not module: + from awx.main.analytics import collectors + config = collectors.config + path = '{}.json'.format(os.path.join(gather_dir, config.__awx_analytics_key__)) + with open(path, 'w', encoding='utf-8') as f: + try: + json.dump(collectors.config(last_run), f) + manifest['config.json'] = config.__awx_analytics_version__ + except Exception: + logger.exception("Could not generate metric {}.json".format(key)) + f.close() + os.remove(f.name) + shutil.rmtree(dest) + return None + + stage_dirs = [gather_dir] + if num_splits > 1: + for i in range(0, num_splits): + split_path = os.path.join(dest, 'split{}'.format(i)) + os.mkdir(split_path, 0o700) + filtered_manifest = {} + shutil.copy(os.path.join(gather_dir, 'config.json'), split_path) + filtered_manifest['config.json'] = manifest['config.json'] + suffix = '_split{}'.format(i) + for file in os.listdir(gather_dir): + if file.endswith(suffix): + old_file = os.path.join(gather_dir, file) + new_filename = file.replace(suffix, '') + new_file = os.path.join(split_path, new_filename) + shutil.move(old_file, new_file) + filtered_manifest[new_filename] = manifest[new_filename] + _write_manifest(split_path, filtered_manifest) + stage_dirs.append(split_path) + + for item in list(manifest.keys()): + if not os.path.exists(os.path.join(gather_dir, item)): + manifest.pop(item) + _write_manifest(gather_dir, manifest) + + tarfiles = [] try: - collectors.copy_tables(since=last_run, full_path=dest) + for i in range(0, len(stage_dirs)): + stage_dir = stage_dirs[i] + # can't use isoformat() since it has colons, which GNU tar doesn't like + tarname = '_'.join([ + settings.SYSTEM_UUID, + until.strftime('%Y-%m-%d-%H%M%S%z'), + str(i) + ]) + tgz = shutil.make_archive( + os.path.join(os.path.dirname(dest), tarname), + 'gztar', + stage_dir + ) + tarfiles.append(tgz) except Exception: - logger.exception("Could not copy tables") - - # can't use isoformat() since it has colons, which GNU tar doesn't like - tarname = '_'.join([ - settings.SYSTEM_UUID, - run_now.strftime('%Y-%m-%d-%H%M%S%z') - ]) - tgz = shutil.make_archive( - os.path.join(os.path.dirname(dest), tarname), - 'gztar', - dest - ) - shutil.rmtree(dest) - return tgz + shutil.rmtree(stage_dir, ignore_errors = True) + logger.exception("Failed to write analytics archive file") + finally: + shutil.rmtree(dest, ignore_errors = True) + return tarfiles def ship(path): @@ -150,6 +230,9 @@ def ship(path): if not path: logger.error('Automation Analytics TAR not found') return + if not os.path.exists(path): + logger.error('Automation Analytics TAR {} not found'.format(path)) + return if "Error:" in str(path): return try: @@ -169,19 +252,18 @@ def ship(path): s = requests.Session() s.headers = get_awx_http_client_headers() s.headers.pop('Content-Type') - response = s.post(url, - files=files, - verify="/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", - auth=(rh_user, rh_password), - headers=s.headers, - timeout=(31, 31)) - if response.status_code != 202: + with set_environ(**settings.AWX_TASK_ENV): + response = s.post(url, + files=files, + verify="/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", + auth=(rh_user, rh_password), + headers=s.headers, + timeout=(31, 31)) + # Accept 2XX status_codes + if response.status_code >= 300: return logger.exception('Upload failed with status {}, {}'.format(response.status_code, response.text)) - run_now = now() - state = TowerAnalyticsState.get_solo() - state.last_run = run_now - state.save() finally: # cleanup tar.gz - os.remove(path) + if os.path.exists(path): + os.remove(path) diff --git a/awx/main/analytics/metrics.py b/awx/main/analytics/metrics.py index 1dd85eb6a7c2..20bf8ae83053 100644 --- a/awx/main/analytics/metrics.py +++ b/awx/main/analytics/metrics.py @@ -12,7 +12,7 @@ from awx.conf.license import get_license from awx.main.utils import (get_awx_version, get_ansible_version) from awx.main.analytics.collectors import ( - counts, + counts, instance_info, job_instance_counts, job_counts, @@ -54,7 +54,7 @@ def metrics(): - license_info = get_license(show_key=False) + license_info = get_license() SYSTEM_INFO.info({ 'install_uuid': settings.INSTALL_UUID, 'insights_analytics': str(settings.INSIGHTS_TRACKING_STATE), @@ -68,7 +68,7 @@ def metrics(): 'external_logger_type': getattr(settings, 'LOG_AGGREGATOR_TYPE', 'None') }) - LICENSE_INSTANCE_TOTAL.set(str(license_info.get('available_instances', 0))) + LICENSE_INSTANCE_TOTAL.set(str(license_info.get('instance_count', 0))) LICENSE_INSTANCE_FREE.set(str(license_info.get('free_instances', 0))) current_counts = counts(None) diff --git a/awx/main/conf.py b/awx/main/conf.py index 33ed4d9e13e2..6bf86db21472 100644 --- a/awx/main/conf.py +++ b/awx/main/conf.py @@ -1,8 +1,5 @@ # Python -import json import logging -import os -from distutils.version import LooseVersion as Version # Django from django.utils.translation import ugettext_lazy as _ @@ -14,6 +11,7 @@ # Tower from awx.conf import fields, register, register_validate + logger = logging.getLogger('awx.main.conf') register( @@ -80,11 +78,11 @@ ) register( - 'PROXY_IP_WHITELIST', + 'PROXY_IP_ALLOWED_LIST', field_class=fields.StringListField, - label=_('Proxy IP Whitelist'), + label=_('Proxy IP Allowed List'), help_text=_("If Tower is behind a reverse proxy/load balancer, use this setting " - "to whitelist the proxy IP addresses from which Tower should trust " + "to configure the proxy IP addresses from which Tower 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')"), @@ -93,22 +91,10 @@ ) -def _load_default_license_from_file(): - try: - license_file = os.environ.get('AWX_LICENSE_FILE', '/etc/tower/license') - if os.path.exists(license_file): - license_data = json.load(open(license_file)) - logger.debug('Read license data from "%s".', license_file) - return license_data - except Exception: - logger.warning('Could not read license from "%s".', license_file, exc_info=True) - return {} - - register( 'LICENSE', field_class=fields.DictField, - default=_load_default_license_from_file, + default=lambda: {}, label=_('License'), help_text=_('The license controls which features and functionality are ' 'enabled. Use /api/v2/config/ to update or change ' @@ -125,7 +111,7 @@ def _load_default_license_from_file(): encrypted=False, read_only=False, label=_('Red Hat customer username'), - help_text=_('This username is used to retrieve license information and to send Automation Analytics'), # noqa + help_text=_('This username is used to send data to Automation Analytics'), category=_('System'), category_slug='system', ) @@ -138,7 +124,33 @@ def _load_default_license_from_file(): encrypted=True, read_only=False, label=_('Red Hat customer password'), - help_text=_('This password is used to retrieve license information and to send Automation Analytics'), # noqa + help_text=_('This password is used to send data to Automation Analytics'), + category=_('System'), + category_slug='system', +) + +register( + 'SUBSCRIPTIONS_USERNAME', + field_class=fields.CharField, + default='', + allow_blank=True, + encrypted=False, + read_only=False, + label=_('Red Hat or Satellite username'), + help_text=_('This username is used to retrieve subscription and content information'), # noqa + category=_('System'), + category_slug='system', +) + +register( + 'SUBSCRIPTIONS_PASSWORD', + field_class=fields.CharField, + default='', + allow_blank=True, + encrypted=True, + read_only=False, + label=_('Red Hat or Satellite password'), + help_text=_('This password is used to retrieve subscription and content information'), # noqa category=_('System'), category_slug='system', ) @@ -149,7 +161,7 @@ def _load_default_license_from_file(): default='https://example.com', schemes=('http', 'https'), allow_plain_hostname=True, # Allow hostname only without TLD. - label=_('Automation Analytics upload URL.'), + label=_('Automation Analytics upload URL'), help_text=_('This setting is used to to configure data collection for the Automation Analytics dashboard'), category=_('System'), category_slug='system', @@ -241,23 +253,11 @@ def _load_default_license_from_file(): field_class=fields.StringListField, required=False, label=_('Paths to expose to isolated jobs'), - help_text=_('Whitelist of paths that would otherwise be hidden to expose to isolated jobs. Enter one path per line.'), + 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', ) -register( - 'AWX_ISOLATED_VERBOSITY', - field_class=fields.IntegerField, - min_value=0, - max_value=5, - label=_('Verbosity level for isolated node management tasks'), - help_text=_('This can be raised to aid in debugging connection issues for isolated task execution'), - category=_('Jobs'), - category_slug='jobs', - default=0 -) - register( 'AWX_ISOLATED_CHECK_INTERVAL', field_class=fields.IntegerField, @@ -266,6 +266,7 @@ def _load_default_license_from_file(): help_text=_('The number of seconds to sleep between status checks for jobs running on isolated instances.'), category=_('Jobs'), category_slug='jobs', + unit=_('seconds'), ) register( @@ -277,6 +278,7 @@ def _load_default_license_from_file(): 'This includes the time needed to copy source control files (playbooks) to the isolated instance.'), category=_('Jobs'), category_slug='jobs', + unit=_('seconds'), ) register( @@ -289,6 +291,7 @@ def _load_default_license_from_file(): 'Value should be substantially greater than expected network latency.'), category=_('Jobs'), category_slug='jobs', + unit=_('seconds'), ) register( @@ -436,82 +439,16 @@ def _load_default_license_from_file(): ) register( - 'PRIMARY_GALAXY_URL', - field_class=fields.URLField, - required=False, - allow_blank=True, - label=_('Primary Galaxy Server URL'), + 'AWX_SHOW_PLAYBOOK_LINKS', + field_class=fields.BooleanField, + default=False, + label=_('Follow symlinks'), help_text=_( - 'For organizations that run their own Galaxy service, this gives the option to specify a ' - 'host as the primary galaxy server. Requirements will be downloaded from the primary if the ' - 'specific role or collection is available there. If the content is not avilable in the primary, ' - 'or if this field is left blank, it will default to galaxy.ansible.com.' + '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' -) - -register( - 'PRIMARY_GALAXY_USERNAME', - field_class=fields.CharField, - required=False, - allow_blank=True, - label=_('Primary Galaxy Server Username'), - help_text=_('For using a galaxy server at higher precedence than the public Ansible Galaxy. ' - 'The username to use for basic authentication against the Galaxy instance, ' - 'this is mutually exclusive with PRIMARY_GALAXY_TOKEN.'), - category=_('Jobs'), - category_slug='jobs' -) - -register( - 'PRIMARY_GALAXY_PASSWORD', - field_class=fields.CharField, - encrypted=True, - required=False, - allow_blank=True, - label=_('Primary Galaxy Server Password'), - help_text=_('For using a galaxy server at higher precedence than the public Ansible Galaxy. ' - 'The password to use for basic authentication against the Galaxy instance, ' - 'this is mutually exclusive with PRIMARY_GALAXY_TOKEN.'), - category=_('Jobs'), - category_slug='jobs' -) - -register( - 'PRIMARY_GALAXY_TOKEN', - field_class=fields.CharField, - encrypted=True, - required=False, - allow_blank=True, - label=_('Primary Galaxy Server Token'), - help_text=_('For using a galaxy server at higher precedence than the public Ansible Galaxy. ' - 'The token to use for connecting with the Galaxy instance, ' - 'this is mutually exclusive with corresponding username and password settings.'), - category=_('Jobs'), - category_slug='jobs' -) - -register( - 'PRIMARY_GALAXY_AUTH_URL', - field_class=fields.CharField, - required=False, - allow_blank=True, - label=_('Primary Galaxy Authentication URL'), - help_text=_('For using a galaxy server at higher precedence than the public Ansible Galaxy. ' - 'The token_endpoint of a Keycloak server.'), - category=_('Jobs'), - category_slug='jobs' -) - -register( - 'PUBLIC_GALAXY_ENABLED', - field_class=fields.BooleanField, - default=True, - label=_('Allow Access to Public Galaxy'), - help_text=_('Allow or deny access to the public Ansible Galaxy during project updates.'), - category=_('Jobs'), - category_slug='jobs' + category_slug='jobs', ) register( @@ -519,7 +456,7 @@ def _load_default_license_from_file(): field_class=fields.BooleanField, default=False, label=_('Ignore Ansible Galaxy SSL Certificate Verification'), - help_text=_('If set to true, certificate validation will not be done when' + help_text=_('If set to true, certificate validation will not be done when ' 'installing content from any Galaxy server.'), category=_('Jobs'), category_slug='jobs' @@ -576,6 +513,7 @@ def _load_default_license_from_file(): 'timeout should be imposed. A timeout set on an individual job template will override this.'), category=_('Jobs'), category_slug='jobs', + unit=_('seconds'), ) register( @@ -588,6 +526,7 @@ def _load_default_license_from_file(): 'timeout should be imposed. A timeout set on an individual inventory source will override this.'), category=_('Jobs'), category_slug='jobs', + unit=_('seconds'), ) register( @@ -600,6 +539,7 @@ def _load_default_license_from_file(): 'timeout should be imposed. A timeout set on an individual project will override this.'), category=_('Jobs'), category_slug='jobs', + unit=_('seconds'), ) register( @@ -614,6 +554,19 @@ def _load_default_license_from_file(): 'Use a value of 0 to indicate that no timeout should be imposed.'), category=_('Jobs'), category_slug='jobs', + unit=_('seconds'), +) + +register( + 'MAX_FORKS', + field_class=fields.IntegerField, + allow_null=False, + default=200, + 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', ) register( @@ -655,7 +608,7 @@ def _load_default_license_from_file(): allow_blank=True, default='', label=_('Logging Aggregator Username'), - help_text=_('Username for external log aggregator (if required).'), + help_text=_('Username for external log aggregator (if required; HTTP/s only).'), category=_('Logging'), category_slug='logging', required=False, @@ -667,7 +620,7 @@ def _load_default_license_from_file(): default='', encrypted=True, label=_('Logging Aggregator Password/Token'), - help_text=_('Password or authentication token for external log aggregator (if required).'), + help_text=_('Password or authentication token for external log aggregator (if required; HTTP/s only).'), category=_('Logging'), category_slug='logging', required=False, @@ -739,6 +692,7 @@ def _load_default_license_from_file(): 'aggregator protocols.'), category=_('Logging'), category_slug='logging', + unit=_('seconds'), ) register( 'LOG_AGGREGATOR_VERIFY_CERT', @@ -766,24 +720,61 @@ def _load_default_license_from_file(): category_slug='logging', ) register( - 'LOG_AGGREGATOR_AUDIT', + 'LOG_AGGREGATOR_MAX_DISK_USAGE_GB', + field_class=fields.IntegerField, + default=1, + min_value=1, + 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.'), + category=_('Logging'), + category_slug='logging', +) +register( + 'LOG_AGGREGATOR_MAX_DISK_USAGE_PATH', + field_class=fields.CharField, + default='/var/lib/awx', + 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', +) +register( + 'LOG_AGGREGATOR_RSYSLOGD_DEBUG', field_class=fields.BooleanField, - allow_null=True, default=False, - label=_('Enabled external log aggregation auditing'), - help_text=_('When enabled, all external logs emitted by Tower will also be written to /var/log/tower/external.log. This is an experimental setting intended to be used for debugging external log aggregation issues (and may be subject to change in the future).'), # noqa + 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', ) + register( - 'BROKER_DURABILITY', - field_class=fields.BooleanField, - label=_('Message Durability'), - help_text=_('When set (the default), underlying queues will be persisted to disk. Disable this to enable higher message bus throughput.'), + 'AUTOMATION_ANALYTICS_LAST_GATHER', + field_class=fields.DateTimeField, + label=_('Last gather date for Automation Analytics.'), + allow_null=True, + category=_('System'), + category_slug='system' +) + + +register( + 'AUTOMATION_ANALYTICS_GATHER_INTERVAL', + field_class=fields.IntegerField, + label=_('Automation Analytics Gather Interval'), + help_text=_('Interval (in seconds) between data gathering.'), + default=14400, # every 4 hours + min_value=1800, # every 30 minutes category=_('System'), category_slug='system', + unit=_('seconds'), ) @@ -805,75 +796,4 @@ def logging_validate(serializer, attrs): return attrs -def galaxy_validate(serializer, attrs): - """Ansible Galaxy config options have mutual exclusivity rules, these rules - are enforced here on serializer validation so that users will not be able - to save settings which obviously break all project updates. - """ - prefix = 'PRIMARY_GALAXY_' - - from awx.main.constants import GALAXY_SERVER_FIELDS - if not any('{}{}'.format(prefix, subfield.upper()) in attrs for subfield in GALAXY_SERVER_FIELDS): - return attrs - - def _new_value(setting_name): - if setting_name in attrs: - return attrs[setting_name] - elif not serializer.instance: - return '' - return getattr(serializer.instance, setting_name, '') - - galaxy_data = {} - for subfield in GALAXY_SERVER_FIELDS: - galaxy_data[subfield] = _new_value('{}{}'.format(prefix, subfield.upper())) - errors = {} - if not galaxy_data['url']: - for k, v in galaxy_data.items(): - if v: - setting_name = '{}{}'.format(prefix, k.upper()) - errors.setdefault(setting_name, []) - errors[setting_name].append(_( - 'Cannot provide field if PRIMARY_GALAXY_URL is not set.' - )) - for k in GALAXY_SERVER_FIELDS: - if galaxy_data[k]: - setting_name = '{}{}'.format(prefix, k.upper()) - if (not serializer.instance) or (not getattr(serializer.instance, setting_name, '')): - # new auth is applied, so check if compatible with version - from awx.main.utils import get_ansible_version - current_version = get_ansible_version() - min_version = '2.9' - if Version(current_version) < Version(min_version): - errors.setdefault(setting_name, []) - errors[setting_name].append(_( - 'Galaxy server settings are not available until Ansible {min_version}, ' - 'you are running {current_version}.' - ).format(min_version=min_version, current_version=current_version)) - if (galaxy_data['password'] or galaxy_data['username']) and (galaxy_data['token'] or galaxy_data['auth_url']): - for k in ('password', 'username', 'token', 'auth_url'): - setting_name = '{}{}'.format(prefix, k.upper()) - if setting_name in attrs: - errors.setdefault(setting_name, []) - errors[setting_name].append(_( - 'Setting Galaxy token and authentication URL is mutually exclusive with username and password.' - )) - if bool(galaxy_data['username']) != bool(galaxy_data['password']): - msg = _('If authenticating via username and password, both must be provided.') - for k in ('username', 'password'): - setting_name = '{}{}'.format(prefix, k.upper()) - errors.setdefault(setting_name, []) - errors[setting_name].append(msg) - if bool(galaxy_data['token']) != bool(galaxy_data['auth_url']): - msg = _('If authenticating via token, both token and authentication URL must be provided.') - for k in ('token', 'auth_url'): - setting_name = '{}{}'.format(prefix, k.upper()) - errors.setdefault(setting_name, []) - errors[setting_name].append(msg) - - if errors: - raise serializers.ValidationError(errors) - return attrs - - register_validate('logging', logging_validate) -register_validate('jobs', galaxy_validate) diff --git a/awx/main/constants.py b/awx/main/constants.py index 4c98d264dd52..323f61f31101 100644 --- a/awx/main/constants.py +++ b/awx/main/constants.py @@ -10,8 +10,7 @@ 'ANSI_SGR_PATTERN', 'CAN_CANCEL', 'ACTIVE_STATES', 'STANDARD_INVENTORY_UPDATE_ENV' ] - -CLOUD_PROVIDERS = ('azure_rm', 'ec2', 'gce', 'vmware', 'openstack', 'rhv', 'satellite6', 'cloudforms', 'tower') +CLOUD_PROVIDERS = ('azure_rm', 'ec2', 'gce', 'vmware', 'openstack', 'rhv', 'satellite6', 'tower') SCHEDULEABLE_PROVIDERS = CLOUD_PROVIDERS + ('custom', 'scm',) PRIVILEGE_ESCALATION_METHODS = [ ('sudo', _('Sudo')), ('su', _('Su')), ('pbrun', _('Pbrun')), ('pfexec', _('Pfexec')), @@ -32,17 +31,17 @@ CAN_CANCEL = ('new', 'pending', 'waiting', 'running') ACTIVE_STATES = CAN_CANCEL CENSOR_VALUE = '************' -ENV_BLACKLIST = frozenset(( +ENV_BLOCKLIST = frozenset(( 'VIRTUAL_ENV', 'PATH', 'PYTHONPATH', 'PROOT_TMP_DIR', 'JOB_ID', 'INVENTORY_ID', 'INVENTORY_SOURCE_ID', 'INVENTORY_UPDATE_ID', 'AD_HOC_COMMAND_ID', 'REST_API_URL', 'REST_API_TOKEN', 'MAX_EVENT_RES', 'CALLBACK_QUEUE', 'CALLBACK_CONNECTION', 'CACHE', 'JOB_CALLBACK_DEBUG', 'INVENTORY_HOSTVARS', - 'AWX_HOST', 'PROJECT_REVISION' + 'AWX_HOST', 'PROJECT_REVISION', 'SUPERVISOR_WEB_CONFIG_PATH' )) # loggers that may be called in process of emitting a log -LOGGER_BLACKLIST = ( +LOGGER_BLOCKLIST = ( 'awx.main.utils.handlers', 'awx.main.utils.formatters', 'awx.main.utils.filters', @@ -51,7 +50,3 @@ # loggers that may be called getting logging settings 'awx.conf' ) - -# these correspond to both AWX and Ansible settings to keep naming consistent -# for instance, settings.PRIMARY_GALAXY_AUTH_URL vs env var ANSIBLE_GALAXY_SERVER_FOO_AUTH_URL -GALAXY_SERVER_FIELDS = ('url', 'username', 'password', 'token', 'auth_url') diff --git a/awx/main/consumers.py b/awx/main/consumers.py index a4fcdc96a64c..4fc1196dbeb0 100644 --- a/awx/main/consumers.py +++ b/awx/main/consumers.py @@ -1,97 +1,246 @@ import json import logging +import time +import hmac +import asyncio -from channels import Group -from channels.auth import channel_session_user_from_http, channel_session_user - -from django.utils.encoding import smart_str -from django.http.cookie import parse_cookie from django.core.serializers.json import DjangoJSONEncoder +from django.conf import settings +from django.utils.encoding import force_bytes +from django.contrib.auth.models import User + +from channels.generic.websocket import AsyncJsonWebsocketConsumer +from channels.layers import get_channel_layer +from channels.db import database_sync_to_async logger = logging.getLogger('awx.main.consumers') XRF_KEY = '_auth_user_xrf' -def discard_groups(message): - if 'groups' in message.channel_session: - for group in message.channel_session['groups']: - Group(group).discard(message.reply_channel) - - -@channel_session_user_from_http -def ws_connect(message): - headers = dict(message.content.get('headers', '')) - message.reply_channel.send({"accept": True}) - message.content['method'] = 'FAKE' - if message.user.is_authenticated: - message.reply_channel.send( - {"text": json.dumps({"accept": True, "user": message.user.id})} - ) - # store the valid CSRF token from the cookie so we can compare it later - # on ws_receive - cookie_token = parse_cookie( - smart_str(headers.get(b'cookie')) - ).get('csrftoken') - if cookie_token: - message.channel_session[XRF_KEY] = cookie_token - else: - logger.error("Request user is not authenticated to use websocket.") - message.reply_channel.send({"close": True}) - return None - - -@channel_session_user -def ws_disconnect(message): - discard_groups(message) - - -@channel_session_user -def ws_receive(message): - from awx.main.access import consumer_access - user = message.user - raw_data = message.content['text'] - data = json.loads(raw_data) - - xrftoken = data.get('xrftoken') - if ( - not xrftoken or - XRF_KEY not in message.channel_session or - xrftoken != message.channel_session[XRF_KEY] - ): - logger.error( - "access denied to channel, XRF mismatch for {}".format(user.username) - ) - message.reply_channel.send({ - "text": json.dumps({"error": "access denied to channel"}) - }) - return - - if 'groups' in data: - discard_groups(message) - groups = data['groups'] - current_groups = set(message.channel_session.pop('groups') if 'groups' in message.channel_session else []) - for group_name,v in groups.items(): - if type(v) is list: - for oid in v: - name = '{}-{}'.format(group_name, oid) - access_cls = consumer_access(group_name) - if access_cls is not None: - user_access = access_cls(user) - if not user_access.get_queryset().filter(pk=oid).exists(): - message.reply_channel.send({"text": json.dumps( - {"error": "access denied to channel {0} for resource id {1}".format(group_name, oid)})}) - continue - current_groups.add(name) - Group(name).add(message.reply_channel) - else: - current_groups.add(group_name) - Group(group_name).add(message.reply_channel) - message.channel_session['groups'] = list(current_groups) +class WebsocketSecretAuthHelper: + """ + Middlewareish for websockets to verify node websocket broadcast interconnect. + + Note: The "ish" is due to the channels routing interface. Routing occurs + _after_ authentication; making it hard to apply this auth to _only_ a subset of + websocket endpoints. + """ + + @classmethod + def construct_secret(cls): + nonce_serialized = f"{int(time.time())}" + payload_dict = { + 'secret': settings.BROADCAST_WEBSOCKET_SECRET, + 'nonce': nonce_serialized + } + payload_serialized = json.dumps(payload_dict) + + secret_serialized = hmac.new(force_bytes(settings.BROADCAST_WEBSOCKET_SECRET), + msg=force_bytes(payload_serialized), + digestmod='sha256').hexdigest() + + return 'HMAC-SHA256 {}:{}'.format(nonce_serialized, secret_serialized) + + + @classmethod + def verify_secret(cls, s, nonce_tolerance=300): + try: + (prefix, payload) = s.split(' ') + if prefix != 'HMAC-SHA256': + raise ValueError('Unsupported encryption algorithm') + (nonce_parsed, secret_parsed) = payload.split(':') + except Exception: + raise ValueError("Failed to parse secret") + + try: + payload_expected = { + 'secret': settings.BROADCAST_WEBSOCKET_SECRET, + 'nonce': nonce_parsed, + } + payload_serialized = json.dumps(payload_expected) + except Exception: + raise ValueError("Failed to create hash to compare to secret.") + + secret_serialized = hmac.new(force_bytes(settings.BROADCAST_WEBSOCKET_SECRET), + msg=force_bytes(payload_serialized), + digestmod='sha256').hexdigest() + + if secret_serialized != secret_parsed: + raise ValueError("Invalid secret") + + # Avoid timing attack and check the nonce after all the heavy lifting + now = int(time.time()) + nonce_parsed = int(nonce_parsed) + nonce_diff = now - nonce_parsed + if abs(nonce_diff) > nonce_tolerance: + logger.warn(f"Potential replay attack or machine(s) time out of sync by {nonce_diff} seconds.") + raise ValueError(f"Potential replay attack or machine(s) time out of sync by {nonce_diff} seconds.") + + return True + + @classmethod + def is_authorized(cls, scope): + secret = '' + for k, v in scope['headers']: + if k.decode("utf-8") == 'secret': + secret = v.decode("utf-8") + break + WebsocketSecretAuthHelper.verify_secret(secret) + + +class BroadcastConsumer(AsyncJsonWebsocketConsumer): + + async def connect(self): + try: + WebsocketSecretAuthHelper.is_authorized(self.scope) + except Exception: + logger.warn(f"client '{self.channel_name}' failed to authorize against the broadcast endpoint.") + await self.close() + return + + await self.accept() + await self.channel_layer.group_add(settings.BROADCAST_WEBSOCKET_GROUP_NAME, self.channel_name) + logger.info(f"client '{self.channel_name}' joined the broadcast group.") + + async def disconnect(self, code): + logger.info(f"client '{self.channel_name}' disconnected from the broadcast group.") + await self.channel_layer.group_discard(settings.BROADCAST_WEBSOCKET_GROUP_NAME, self.channel_name) + + async def internal_message(self, event): + await self.send(event['text']) + + +class EventConsumer(AsyncJsonWebsocketConsumer): + async def connect(self): + user = self.scope['user'] + if user and not user.is_anonymous: + await self.accept() + await self.send_json({"accept": True, "user": user.id}) + # store the valid CSRF token from the cookie so we can compare it later + # on ws_receive + cookie_token = self.scope['cookies'].get('csrftoken') + if cookie_token: + self.scope['session'][XRF_KEY] = cookie_token + else: + logger.error("Request user is not authenticated to use websocket.") + # TODO: Carry over from channels 1 implementation + # We should never .accept() the client and close without sending a close message + await self.accept() + await self.send_json({"close": True}) + await self.close() + + async def disconnect(self, code): + current_groups = set(self.scope['session'].pop('groups') if 'groups' in self.scope['session'] else []) + for group_name in current_groups: + await self.channel_layer.group_discard( + group_name, + self.channel_name, + ) + + @database_sync_to_async + def user_can_see_object_id(self, user_access, oid): + # At this point user is a channels.auth.UserLazyObject object + # This causes problems with our generic role permissions checking. + # Specifically, type(user) != User + # Therefore, get the "real" User objects from the database before + # calling the access permission methods + user_access.user = User.objects.get(id=user_access.user.id) + res = user_access.get_queryset().filter(pk=oid).exists() + return res + + async def receive_json(self, data): + from awx.main.access import consumer_access + user = self.scope['user'] + xrftoken = data.get('xrftoken') + if ( + not xrftoken or + XRF_KEY not in self.scope["session"] or + xrftoken != self.scope["session"][XRF_KEY] + ): + logger.error(f"access denied to channel, XRF mismatch for {user.username}") + await self.send_json({"error": "access denied to channel"}) + return + + if 'groups' in data: + groups = data['groups'] + new_groups = set() + current_groups = set(self.scope['session'].pop('groups') if 'groups' in self.scope['session'] else []) + for group_name,v in groups.items(): + if type(v) is list: + for oid in v: + name = '{}-{}'.format(group_name, oid) + access_cls = consumer_access(group_name) + if access_cls is not None: + user_access = access_cls(user) + if not await self.user_can_see_object_id(user_access, oid): + await self.send_json({"error": "access denied to channel {0} for resource id {1}".format(group_name, oid)}) + continue + new_groups.add(name) + else: + await self.send_json({"error": "access denied to channel"}) + logger.error(f"groups must be a list, not {groups}") + return + + old_groups = current_groups - new_groups + for group_name in old_groups: + await self.channel_layer.group_discard( + group_name, + self.channel_name, + ) + + new_groups_exclusive = new_groups - current_groups + for group_name in new_groups_exclusive: + await self.channel_layer.group_add( + group_name, + self.channel_name + ) + self.scope['session']['groups'] = new_groups + await self.send_json({ + "groups_current": list(new_groups), + "groups_left": list(old_groups), + "groups_joined": list(new_groups_exclusive) + }) + + async def internal_message(self, event): + await self.send(event['text']) + + +def run_sync(func): + event_loop = asyncio.new_event_loop() + event_loop.run_until_complete(func) + event_loop.close() + + +def _dump_payload(payload): + try: + return json.dumps(payload, cls=DjangoJSONEncoder) + except ValueError: + logger.error("Invalid payload to emit") + return None def emit_channel_notification(group, payload): - try: - Group(group).send({"text": json.dumps(payload, cls=DjangoJSONEncoder)}) - except ValueError: - logger.error("Invalid payload emitting channel {} on topic: {}".format(group, payload)) + from awx.main.wsbroadcast import wrap_broadcast_msg # noqa + + payload_dumped = _dump_payload(payload) + if payload_dumped is None: + return + + channel_layer = get_channel_layer() + + run_sync(channel_layer.group_send( + group, + { + "type": "internal.message", + "text": payload_dumped + }, + )) + + run_sync(channel_layer.group_send( + settings.BROADCAST_WEBSOCKET_GROUP_NAME, + { + "type": "internal.message", + "text": wrap_broadcast_msg(group, payload_dumped), + }, + )) diff --git a/awx/main/credential_plugins/aim.py b/awx/main/credential_plugins/aim.py index f9e0076b40aa..7c99665bf09b 100644 --- a/awx/main/credential_plugins/aim.py +++ b/awx/main/credential_plugins/aim.py @@ -1,15 +1,10 @@ -from .plugin import CredentialPlugin +from .plugin import CredentialPlugin, CertFiles, raise_for_status from urllib.parse import quote, urlencode, urljoin from django.utils.translation import ugettext_lazy as _ import requests -# AWX -from awx.main.utils import ( - create_temporary_fifo, -) - aim_inputs = { 'fields': [{ 'id': 'url', @@ -43,7 +38,7 @@ 'id': 'object_query', 'label': _('Object Query'), 'type': 'string', - 'help_text': _('Lookup query for the object. Ex: "Safe=TestSafe;Object=testAccountName123"'), + 'help_text': _('Lookup query for the object. Ex: Safe=TestSafe;Object=testAccountName123'), }, { 'id': 'object_query_format', 'label': _('Object Query Format'), @@ -81,22 +76,15 @@ def aim_backend(**kwargs): request_qs = '?' + urlencode(query_params, quote_via=quote) request_url = urljoin(url, '/'.join(['AIMWebService', 'api', 'Accounts'])) - cert = None - if client_cert and client_key: - cert = ( - create_temporary_fifo(client_cert.encode()), - create_temporary_fifo(client_key.encode()) + with CertFiles(client_cert, client_key) as cert: + res = requests.get( + request_url + request_qs, + timeout=30, + cert=cert, + verify=verify, + allow_redirects=False, ) - elif client_cert: - cert = create_temporary_fifo(client_cert.encode()) - - res = requests.get( - request_url + request_qs, - timeout=30, - cert=cert, - verify=verify, - ) - res.raise_for_status() + raise_for_status(res) return res.json()['Content'] diff --git a/awx/main/credential_plugins/azure_kv.py b/awx/main/credential_plugins/azure_kv.py index 77b7394b1fe4..645e6f6b1aa3 100644 --- a/awx/main/credential_plugins/azure_kv.py +++ b/awx/main/credential_plugins/azure_kv.py @@ -3,6 +3,16 @@ from django.utils.translation import ugettext_lazy as _ from azure.keyvault import KeyVaultClient, KeyVaultAuthentication from azure.common.credentials import ServicePrincipalCredentials +from msrestazure import azure_cloud + + +# https://github.com/Azure/msrestazure-for-python/blob/master/msrestazure/azure_cloud.py +clouds = [ + vars(azure_cloud)[n] + for n in dir(azure_cloud) + if n.startswith("AZURE_") and n.endswith("_CLOUD") +] +default_cloud = vars(azure_cloud)["AZURE_PUBLIC_CLOUD"] azure_keyvault_inputs = { @@ -24,6 +34,12 @@ 'id': 'tenant', 'label': _('Tenant ID'), 'type': 'string' + }, { + 'id': 'cloud_name', + 'label': _('Cloud Environment'), + 'help_text': _('Specify which azure cloud environment to use.'), + 'choices': list(set([default_cloud.name] + [c.name for c in clouds])), + 'default': default_cloud.name }], 'metadata': [{ 'id': 'secret_field', @@ -42,6 +58,7 @@ def azure_keyvault_backend(**kwargs): url = kwargs['url'] + [cloud] = [c for c in clouds if c.name == kwargs.get('cloud_name', default_cloud.name)] def auth_callback(server, resource, scope): credentials = ServicePrincipalCredentials( @@ -49,7 +66,7 @@ def auth_callback(server, resource, scope): client_id = kwargs['client'], secret = kwargs['secret'], tenant = kwargs['tenant'], - resource = "https://vault.azure.net", + resource = f"https://{cloud.suffixes.keyvault_dns.split('.', 1).pop()}", ) token = credentials.token return token['token_type'], token['access_token'] diff --git a/awx/main/credential_plugins/conjur.py b/awx/main/credential_plugins/conjur.py index 55fd2e60f2ce..5cd87007fc06 100644 --- a/awx/main/credential_plugins/conjur.py +++ b/awx/main/credential_plugins/conjur.py @@ -1,16 +1,11 @@ -from .plugin import CredentialPlugin +from .plugin import CredentialPlugin, CertFiles, raise_for_status import base64 -from urllib.parse import urljoin, quote_plus +from urllib.parse import urljoin, quote from django.utils.translation import ugettext_lazy as _ import requests -# AWX -from awx.main.utils import ( - create_temporary_fifo, -) - conjur_inputs = { 'fields': [{ @@ -55,32 +50,32 @@ def conjur_backend(**kwargs): url = kwargs['url'] api_key = kwargs['api_key'] - account = quote_plus(kwargs['account']) - username = quote_plus(kwargs['username']) - secret_path = quote_plus(kwargs['secret_path']) + account = quote(kwargs['account'], safe='') + username = quote(kwargs['username'], safe='') + secret_path = quote(kwargs['secret_path'], safe='') version = kwargs.get('secret_version') cacert = kwargs.get('cacert', None) auth_kwargs = { 'headers': {'Content-Type': 'text/plain'}, - 'data': api_key + 'data': api_key, + 'allow_redirects': False, } - if cacert: - auth_kwargs['verify'] = create_temporary_fifo(cacert.encode()) - # https://www.conjur.org/api.html#authentication-authenticate-post - resp = requests.post( - urljoin(url, '/'.join(['authn', account, username, 'authenticate'])), - **auth_kwargs - ) - resp.raise_for_status() + with CertFiles(cacert) as cert: + # https://www.conjur.org/api.html#authentication-authenticate-post + auth_kwargs['verify'] = cert + resp = requests.post( + urljoin(url, '/'.join(['authn', account, username, 'authenticate'])), + **auth_kwargs + ) + raise_for_status(resp) token = base64.b64encode(resp.content).decode('utf-8') lookup_kwargs = { 'headers': {'Authorization': 'Token token="{}"'.format(token)}, + 'allow_redirects': False, } - if cacert: - lookup_kwargs['verify'] = create_temporary_fifo(cacert.encode()) # https://www.conjur.org/api.html#secrets-retrieve-a-secret-get path = urljoin(url, '/'.join([ @@ -92,8 +87,10 @@ def conjur_backend(**kwargs): if version: path = '?'.join([path, version]) - resp = requests.get(path, timeout=30, **lookup_kwargs) - resp.raise_for_status() + with CertFiles(cacert) as cert: + lookup_kwargs['verify'] = cert + resp = requests.get(path, timeout=30, **lookup_kwargs) + raise_for_status(resp) return resp.text diff --git a/awx/main/credential_plugins/hashivault.py b/awx/main/credential_plugins/hashivault.py index c9caafba6b5b..7e262912a461 100644 --- a/awx/main/credential_plugins/hashivault.py +++ b/awx/main/credential_plugins/hashivault.py @@ -3,16 +3,11 @@ import pathlib from urllib.parse import urljoin -from .plugin import CredentialPlugin +from .plugin import CredentialPlugin, CertFiles, raise_for_status import requests from django.utils.translation import ugettext_lazy as _ -# AWX -from awx.main.utils import ( - create_temporary_fifo, -) - base_inputs = { 'fields': [{ 'id': 'url', @@ -32,14 +27,41 @@ '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': 'default_auth_path', + 'label': _('Path to Approle Auth'), + 'type': 'string', + 'multiline': False, + 'default': 'approle', + 'help_text': _('The AppRole Authentication path to use if one isn\'t provided in the metadata when linking to an input field. Defaults to \'approle\'') + } + ], 'metadata': [{ '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', + 'multiline': False, + 'help_text': _('The path where the Authentication method is mounted e.g, approle') }], - 'required': ['url', 'token', 'secret_path'], + 'required': ['url', 'secret_path'], } hashi_kv_inputs = copy.deepcopy(base_inputs) @@ -88,8 +110,44 @@ hashi_ssh_inputs['required'].extend(['public_key', 'role']) +def handle_auth(**kwargs): + token = None + + if kwargs.get('token'): + token = kwargs['token'] + elif kwargs.get('role_id') and kwargs.get('secret_id'): + token = approle_auth(**kwargs) + else: + raise Exception('Either token or AppRole parameters must be set') + + return token + + +def approle_auth(**kwargs): + role_id = kwargs['role_id'] + secret_id = kwargs['secret_id'] + # we first try to use the 'auth_path' from the metadata + # if not found we try to fetch the 'default_auth_path' from inputs + auth_path = kwargs.get('auth_path') or kwargs['default_auth_path'] + + url = urljoin(kwargs['url'], 'v1') + cacert = kwargs.get('cacert', None) + + request_kwargs = {'timeout': 30} + # AppRole Login + request_kwargs['json'] = {'role_id': role_id, 'secret_id': secret_id} + sess = requests.Session() + request_url = '/'.join([url, 'auth', auth_path, 'login']).rstrip('/') + with CertFiles(cacert) as cert: + request_kwargs['verify'] = cert + resp = sess.post(request_url, **request_kwargs) + resp.raise_for_status() + token = resp.json()['auth']['client_token'] + return token + + def kv_backend(**kwargs): - token = kwargs['token'] + token = handle_auth(**kwargs) url = kwargs['url'] secret_path = kwargs['secret_path'] secret_backend = kwargs.get('secret_backend', None) @@ -97,13 +155,14 @@ def kv_backend(**kwargs): cacert = kwargs.get('cacert', None) api_version = kwargs['api_version'] - request_kwargs = {'timeout': 30} - if cacert: - request_kwargs['verify'] = create_temporary_fifo(cacert.encode()) + request_kwargs = { + 'timeout': 30, + 'allow_redirects': False, + } sess = requests.Session() sess.headers['Authorization'] = 'Bearer {}'.format(token) - # Compatability header for older installs of Hashicorp Vault + # Compatibility header for older installs of Hashicorp Vault sess.headers['X-Vault-Token'] = token if api_version == 'v2': @@ -126,8 +185,10 @@ def kv_backend(**kwargs): path_segments = [secret_path] request_url = urljoin(url, '/'.join(['v1'] + path_segments)).rstrip('/') - response = sess.get(request_url, **request_kwargs) - response.raise_for_status() + with CertFiles(cacert) as cert: + request_kwargs['verify'] = cert + response = sess.get(request_url, **request_kwargs) + raise_for_status(response) json = response.json() if api_version == 'v2': @@ -144,15 +205,16 @@ def kv_backend(**kwargs): def ssh_backend(**kwargs): - token = kwargs['token'] + token = handle_auth(**kwargs) url = urljoin(kwargs['url'], 'v1') secret_path = kwargs['secret_path'] role = kwargs['role'] cacert = kwargs.get('cacert', None) - request_kwargs = {'timeout': 30} - if cacert: - request_kwargs['verify'] = create_temporary_fifo(cacert.encode()) + request_kwargs = { + 'timeout': 30, + 'allow_redirects': False, + } request_kwargs['json'] = {'public_key': kwargs['public_key']} if kwargs.get('valid_principals'): @@ -164,9 +226,12 @@ def ssh_backend(**kwargs): sess.headers['X-Vault-Token'] = token # https://www.vaultproject.io/api/secret/ssh/index.html#sign-ssh-key request_url = '/'.join([url, secret_path, 'sign', role]).rstrip('/') - resp = sess.post(request_url, **request_kwargs) - resp.raise_for_status() + with CertFiles(cacert) as cert: + request_kwargs['verify'] = cert + resp = sess.post(request_url, **request_kwargs) + + raise_for_status(resp) return resp.json()['data']['signed_key'] diff --git a/awx/main/credential_plugins/plugin.py b/awx/main/credential_plugins/plugin.py index c5edde7bc1c3..fa5c770fd110 100644 --- a/awx/main/credential_plugins/plugin.py +++ b/awx/main/credential_plugins/plugin.py @@ -1,3 +1,55 @@ +import os +import tempfile + from collections import namedtuple +from requests.exceptions import HTTPError + CredentialPlugin = namedtuple('CredentialPlugin', ['name', 'inputs', 'backend']) + + +def raise_for_status(resp): + resp.raise_for_status() + if resp.status_code >= 300: + exc = HTTPError() + setattr(exc, 'response', resp) + raise exc + + +class CertFiles(): + """ + A context manager used for writing a certificate and (optional) key + to $TMPDIR, and cleaning up afterwards. + + This is particularly useful as a shared resource for credential plugins + that want to pull cert/key data out of the database and persist it + temporarily to the file system so that it can loaded into the openssl + certificate chain (generally, for HTTPS requests plugins make via the + Python requests library) + + with CertFiles(cert_data, key_data) as cert: + # cert is string representing a path to the cert or pemfile + # temporarily written to disk + requests.post(..., cert=cert) + """ + + certfile = None + + def __init__(self, cert, key=None): + self.cert = cert + self.key = key + + def __enter__(self): + if not self.cert: + return None + self.certfile = tempfile.NamedTemporaryFile('wb', delete=False) + self.certfile.write(self.cert.encode()) + if self.key: + self.certfile.write(b'\n') + self.certfile.write(self.key.encode()) + self.certfile.flush() + return str(self.certfile.name) + + def __exit__(self, *args): + if self.certfile and os.path.exists(self.certfile.name): + os.remove(self.certfile.name) diff --git a/awx/main/db/profiled_pg/base.py b/awx/main/db/profiled_pg/base.py index 820e867508ba..2a449437ce15 100644 --- a/awx/main/db/profiled_pg/base.py +++ b/awx/main/db/profiled_pg/base.py @@ -24,7 +24,7 @@ def __init__(self, log, db, dest='/var/log/tower/profile'): try: self.threshold = cache.get('awx-profile-sql-threshold') except Exception: - # if we can't reach memcached, just assume profiling's off + # if we can't reach the cache, just assume profiling's off self.threshold = None def append(self, query): @@ -64,7 +64,7 @@ def write(self, query): if not os.path.isdir(self.dest): os.makedirs(self.dest) progname = ' '.join(sys.argv) - for match in ('uwsgi', 'dispatcher', 'callback_receiver', 'runworker'): + for match in ('uwsgi', 'dispatcher', 'callback_receiver', 'wsbroadcast'): if match in progname: progname = match break @@ -110,7 +110,7 @@ def __getattr__(self, attr): class DatabaseWrapper(BaseDatabaseWrapper): """ This is a special subclass of Django's postgres DB backend which - based on - the value of a special flag in memcached - captures slow queries and + the value of a special flag in cache - captures slow queries and writes profile and Python stack metadata to the disk. """ @@ -133,19 +133,19 @@ def force_debug_cursor(self): # is the same mechanism used by libraries like the django-debug-toolbar) # # in _this_ implementation, we represent it as a property which will - # check memcache for a special flag to be set (when the flag is set, it + # check the cache for a special flag to be set (when the flag is set, it # means we should start recording queries because somebody called # `awx-manage profile_sql`) # # it's worth noting that this property is wrapped w/ @memoize because # Django references this attribute _constantly_ (in particular, once - # per executed query); doing a memcached.get() _at most_ once per + # per executed query); doing a cache.get() _at most_ once per # second is a good enough window to detect when profiling is turned # on/off by a system administrator try: threshold = cache.get('awx-profile-sql-threshold') except Exception: - # if we can't reach memcached, just assume profiling's off + # if we can't reach the cache, just assume profiling's off threshold = None self.queries_log.threshold = threshold return threshold is not None diff --git a/awx/main/dispatch/__init__.py b/awx/main/dispatch/__init__.py index 50f912427e2d..587a8219aa04 100644 --- a/awx/main/dispatch/__init__.py +++ b/awx/main/dispatch/__init__.py @@ -1,5 +1,62 @@ +import psycopg2 +import select + +from contextlib import contextmanager + from django.conf import settings +NOT_READY = ([], [], []) + + def get_local_queuename(): return settings.CLUSTER_HOST_ID + + +class PubSub(object): + def __init__(self, conn): + assert conn.autocommit, "Connection must be in autocommit mode." + self.conn = conn + + def listen(self, channel): + with self.conn.cursor() as cur: + cur.execute('LISTEN "%s";' % channel) + + def unlisten(self, channel): + with self.conn.cursor() as cur: + cur.execute('UNLISTEN "%s";' % channel) + + def notify(self, channel, payload): + with self.conn.cursor() as cur: + cur.execute('SELECT pg_notify(%s, %s);', (channel, payload)) + + def events(self, select_timeout=5, yield_timeouts=False): + while True: + if select.select([self.conn], [], [], select_timeout) == NOT_READY: + if yield_timeouts: + yield None + else: + self.conn.poll() + while self.conn.notifies: + yield self.conn.notifies.pop(0) + + def close(self): + self.conn.close() + + +@contextmanager +def pg_bus_conn(): + conf = settings.DATABASES['default'] + conn = psycopg2.connect(dbname=conf['NAME'], + host=conf['HOST'], + user=conf['USER'], + password=conf['PASSWORD'], + port=conf['PORT'], + **conf.get("OPTIONS", {})) + # Django connection.cursor().connection doesn't have autocommit=True on + conn.set_session(autocommit=True) + pubsub = PubSub(conn) + yield pubsub + conn.close() + + diff --git a/awx/main/dispatch/control.py b/awx/main/dispatch/control.py index 5f081e84f2b3..47cc60b40d3b 100644 --- a/awx/main/dispatch/control.py +++ b/awx/main/dispatch/control.py @@ -1,11 +1,13 @@ import logging -import socket +import uuid +import json from django.conf import settings +import redis from awx.main.dispatch import get_local_queuename -from awx.main.dispatch.kombu import Connection -from kombu import Queue, Exchange, Producer, Consumer + +from . import pg_bus_conn logger = logging.getLogger('awx.main.dispatch') @@ -20,40 +22,43 @@ def __init__(self, service, host=None): raise RuntimeError('{} must be in {}'.format(service, self.services)) self.service = service self.queuename = host or get_local_queuename() - self.queue = Queue(self.queuename, Exchange(self.queuename), routing_key=self.queuename) - - def publish(self, msg, conn, **kwargs): - producer = Producer( - exchange=self.queue.exchange, - channel=conn, - routing_key=self.queuename - ) - producer.publish(msg, expiration=5, **kwargs) def status(self, *args, **kwargs): - return self.control_with_reply('status', *args, **kwargs) + r = redis.Redis.from_url(settings.BROKER_URL) + if self.service == 'dispatcher': + stats = r.get(f'awx_{self.service}_statistics') or b'' + return stats.decode('utf-8') + else: + workers = [] + for key in r.keys('awx_callback_receiver_statistics_*'): + workers.append(r.get(key).decode('utf-8')) + return '\n'.join(workers) def running(self, *args, **kwargs): return self.control_with_reply('running', *args, **kwargs) + @classmethod + def generate_reply_queue_name(cls): + return f"reply_to_{str(uuid.uuid4()).replace('-','_')}" + def control_with_reply(self, command, timeout=5): logger.warn('checking {} {} for {}'.format(self.service, command, self.queuename)) - reply_queue = Queue(name="amq.rabbitmq.reply-to") + reply_queue = Control.generate_reply_queue_name() self.result = None - with Connection(settings.BROKER_URL) as conn: - with Consumer(conn, reply_queue, callbacks=[self.process_message], no_ack=True): - self.publish({'control': command}, conn, reply_to='amq.rabbitmq.reply-to') - try: - conn.drain_events(timeout=timeout) - except socket.timeout: - logger.error('{} did not reply within {}s'.format(self.service, timeout)) - raise - return self.result - def control(self, msg, **kwargs): - with Connection(settings.BROKER_URL) as conn: - self.publish(msg, conn) + with pg_bus_conn() as conn: + conn.listen(reply_queue) + conn.notify(self.queuename, + json.dumps({'control': command, 'reply_to': reply_queue})) - def process_message(self, body, message): - self.result = body - message.ack() + for reply in conn.events(select_timeout=timeout, yield_timeouts=True): + if reply is None: + logger.error(f'{self.service} did not reply within {timeout}s') + raise RuntimeError(f"{self.service} did not reply within {timeout}s") + break + + return json.loads(reply.payload) + + def control(self, msg, **kwargs): + with pg_bus_conn() as conn: + conn.notify(self.queuename, json.dumps(msg)) diff --git a/awx/main/dispatch/kombu.py b/awx/main/dispatch/kombu.py deleted file mode 100644 index 94fc7a035ec3..000000000000 --- a/awx/main/dispatch/kombu.py +++ /dev/null @@ -1,42 +0,0 @@ -from amqp.exceptions import PreconditionFailed -from django.conf import settings -from kombu.connection import Connection as KombuConnection -from kombu.transport import pyamqp - -import logging - -logger = logging.getLogger('awx.main.dispatch') - - -__all__ = ['Connection'] - - -class Connection(KombuConnection): - - def __init__(self, *args, **kwargs): - super(Connection, self).__init__(*args, **kwargs) - class _Channel(pyamqp.Channel): - - def queue_declare(self, queue, *args, **kwargs): - kwargs['durable'] = settings.BROKER_DURABILITY - try: - return super(_Channel, self).queue_declare(queue, *args, **kwargs) - except PreconditionFailed as e: - if "inequivalent arg 'durable'" in getattr(e, 'reply_text', None): - logger.error( - 'queue {} durability is not {}, deleting and recreating'.format( - - queue, - kwargs['durable'] - ) - ) - self.queue_delete(queue) - return super(_Channel, self).queue_declare(queue, *args, **kwargs) - - class _Connection(pyamqp.Connection): - Channel = _Channel - - class _Transport(pyamqp.Transport): - Connection = _Connection - - self.transport_cls = _Transport diff --git a/awx/main/dispatch/periodic.py b/awx/main/dispatch/periodic.py new file mode 100644 index 000000000000..c76366a8d47b --- /dev/null +++ b/awx/main/dispatch/periodic.py @@ -0,0 +1,56 @@ +import logging +import os +import time +from multiprocessing import Process + +from django.conf import settings +from django.db import connections +from schedule import Scheduler + +from awx.main.dispatch.worker import TaskWorker + +logger = logging.getLogger('awx.main.dispatch.periodic') + + +class Scheduler(Scheduler): + + def run_continuously(self): + idle_seconds = max( + 1, + min(self.jobs).period.total_seconds() / 2 + ) + + def run(): + ppid = os.getppid() + logger.warn('periodic beat started') + while True: + if os.getppid() != ppid: + # if the parent PID changes, this process has been orphaned + # via e.g., segfault or sigkill, we should exit too + pid = os.getpid() + logger.warn(f'periodic beat exiting gracefully pid:{pid}') + raise SystemExit() + try: + for conn in connections.all(): + # If the database connection has a hiccup, re-establish a new + # connection + conn.close_if_unusable_or_obsolete() + self.run_pending() + except Exception: + logger.exception( + 'encountered an error while scheduling periodic tasks' + ) + time.sleep(idle_seconds) + + process = Process(target=run) + process.daemon = True + process.start() + + +def run_continuously(): + scheduler = Scheduler() + for task in settings.CELERYBEAT_SCHEDULE.values(): + apply_async = TaskWorker.resolve_callable(task['task']).apply_async + total_seconds = task['schedule'].total_seconds() + scheduler.every(total_seconds).seconds.do(apply_async) + scheduler.run_continuously() diff --git a/awx/main/dispatch/pool.py b/awx/main/dispatch/pool.py index fce8ff7281bc..dc97402788dc 100644 --- a/awx/main/dispatch/pool.py +++ b/awx/main/dispatch/pool.py @@ -1,8 +1,11 @@ import logging import os import random +import signal import sys +import time import traceback +from datetime import datetime from uuid import uuid4 import collections @@ -25,6 +28,12 @@ logger = logging.getLogger('awx.main.dispatch') +class NoOpResultQueue(object): + + def put(self, item): + pass + + class PoolWorker(object): ''' Used to track a worker child process and its pending and finished messages. @@ -54,11 +63,13 @@ class PoolWorker(object): It is "idle" when self.managed_tasks is empty. ''' - def __init__(self, queue_size, target, args): + track_managed_tasks = False + + def __init__(self, queue_size, target, args, **kwargs): self.messages_sent = 0 self.messages_finished = 0 self.managed_tasks = collections.OrderedDict() - self.finished = MPQueue(queue_size) + self.finished = MPQueue(queue_size) if self.track_managed_tasks else NoOpResultQueue() self.queue = MPQueue(queue_size) self.process = Process(target=target, args=(self.queue, self.finished) + args) self.process.daemon = True @@ -72,10 +83,8 @@ def put(self, body): if not body.get('uuid'): body['uuid'] = str(uuid4()) uuid = body['uuid'] - logger.debug('delivered {} to worker[{}] qsize {}'.format( - uuid, self.pid, self.qsize - )) - self.managed_tasks[uuid] = body + if self.track_managed_tasks: + self.managed_tasks[uuid] = body self.queue.put(body, block=True, timeout=5) self.messages_sent += 1 self.calculate_managed_tasks() @@ -112,6 +121,8 @@ def exitcode(self): return str(self.process.exitcode) def calculate_managed_tasks(self): + if not self.track_managed_tasks: + return # look to see if any tasks were finished finished = [] for _ in range(self.finished.qsize()): @@ -136,6 +147,8 @@ def calculate_managed_tasks(self): @property def current_task(self): + if not self.track_managed_tasks: + return None self.calculate_managed_tasks() # the task at [0] is the one that's running right now (or is about to # be running) @@ -146,6 +159,8 @@ def current_task(self): @property def orphaned_tasks(self): + if not self.track_managed_tasks: + return [] orphaned = [] if not self.alive: # if this process had a running task that never finished, @@ -180,6 +195,11 @@ def idle(self): return not self.busy +class StatefulPoolWorker(PoolWorker): + + track_managed_tasks = True + + class WorkerPool(object): ''' Creates a pool of forked PoolWorkers. @@ -201,6 +221,7 @@ def perform_work(self, body): ) ''' + pool_cls = PoolWorker debug_meta = '' def __init__(self, min_workers=None, queue_size=None): @@ -223,10 +244,10 @@ def up(self): idx = len(self.workers) # It's important to close these because we're _about_ to fork, and we # don't want the forked processes to inherit the open sockets - # for the DB and memcached connections (that way lies race conditions) + # for the DB and cache connections (that way lies race conditions) django_connection.close() django_cache.close() - worker = PoolWorker(self.queue_size, self.target, (idx,) + self.target_args) + worker = self.pool_cls(self.queue_size, self.target, (idx,) + self.target_args) self.workers.append(worker) try: worker.start() @@ -237,17 +258,17 @@ def up(self): return idx, worker def debug(self, *args, **kwargs): - self.cleanup() tmpl = Template( + 'Recorded at: {{ dt }} \n' '{{ pool.name }}[pid:{{ pool.pid }}] workers total={{ workers|length }} {{ meta }} \n' '{% for w in workers %}' '. worker[pid:{{ w.pid }}]{% if not w.alive %} GONE exit={{ w.exitcode }}{% endif %}' ' sent={{ w.messages_sent }}' - ' finished={{ w.messages_finished }}' + '{% if w.messages_finished %} finished={{ w.messages_finished }}{% endif %}' ' qsize={{ w.managed_tasks|length }}' ' rss={{ w.mb }}MB' '{% for task in w.managed_tasks.values() %}' - '\n - {% if loop.index0 == 0 %}running {% else %}queued {% endif %}' + '\n - {% if loop.index0 == 0 %}running {% if "age" in task %}for: {{ "%.1f" % task["age"] }}s {% endif %}{% else %}queued {% endif %}' '{{ task["uuid"] }} ' '{% if "task" in task %}' '{{ task["task"].rsplit(".", 1)[-1] }}' @@ -261,7 +282,11 @@ def debug(self, *args, **kwargs): '\n' '{% endfor %}' ) - return tmpl.render(pool=self, workers=self.workers, meta=self.debug_meta) + now = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S UTC') + return tmpl.render( + pool=self, workers=self.workers, meta=self.debug_meta, + dt=now + ) def write(self, preferred_queue, body): queue_order = sorted(range(len(self.workers)), key=lambda x: -1 if x==preferred_queue else x) @@ -294,6 +319,8 @@ class AutoscalePool(WorkerPool): down based on demand ''' + pool_cls = StatefulPoolWorker + def __init__(self, *args, **kwargs): self.max_workers = kwargs.pop('max_workers', None) super(AutoscalePool, self).__init__(*args, **kwargs) @@ -310,6 +337,10 @@ def __init__(self, *args, **kwargs): # max workers can't be less than min_workers self.max_workers = max(self.min_workers, self.max_workers) + def debug(self, *args, **kwargs): + self.cleanup() + return super(AutoscalePool, self).debug(*args, **kwargs) + @property def should_grow(self): if len(self.workers) < self.min_workers: @@ -368,6 +399,26 @@ def cleanup(self): logger.warn('scaling down worker pid:{}'.format(w.pid)) w.quit() self.workers.remove(w) + if w.alive: + # if we discover a task manager invocation that's been running + # too long, reap it (because otherwise it'll just hold the postgres + # advisory lock forever); the goal of this code is to discover + # deadlocks or other serious issues in the task manager that cause + # the task manager to never do more work + current_task = w.current_task + if current_task and isinstance(current_task, dict): + if current_task.get('task', '').endswith('tasks.run_task_manager'): + if 'started' not in current_task: + w.managed_tasks[ + current_task['uuid'] + ]['started'] = time.time() + age = time.time() - current_task['started'] + w.managed_tasks[current_task['uuid']]['age'] = age + if age > (60 * 5): + logger.error( + f'run_task_manager has held the advisory lock for >5m, sending SIGTERM to {w.pid}' + ) # noqa + os.kill(w.pid, signal.SIGTERM) for m in orphaned: # if all the workers are dead, spawn at least one diff --git a/awx/main/dispatch/publish.py b/awx/main/dispatch/publish.py index 9bbd7ae45fd0..f7fd7cf4fb23 100644 --- a/awx/main/dispatch/publish.py +++ b/awx/main/dispatch/publish.py @@ -1,12 +1,12 @@ import inspect import logging import sys +import json from uuid import uuid4 from django.conf import settings -from kombu import Exchange, Producer -from awx.main.dispatch.kombu import Connection +from . import pg_bus_conn logger = logging.getLogger('awx.main.dispatch') @@ -39,24 +39,22 @@ def run(self, a, b): add.apply_async([1, 1]) Adder.apply_async([1, 1]) - # Tasks can also define a specific target queue or exchange type: + # Tasks can also define a specific target queue or use the special fan-out queue tower_broadcast: @task(queue='slow-tasks') def snooze(): time.sleep(10) - @task(queue='tower_broadcast', exchange_type='fanout') + @task(queue='tower_broadcast') def announce(): print("Run this everywhere!") """ - def __init__(self, queue=None, exchange_type=None): + def __init__(self, queue=None): self.queue = queue - self.exchange_type = exchange_type def __call__(self, fn=None): queue = self.queue - exchange_type = self.exchange_type class PublisherMixin(object): @@ -73,9 +71,12 @@ def apply_async(cls, args=None, kwargs=None, queue=None, uuid=None, **kw): kwargs = kwargs or {} queue = ( queue or - getattr(cls.queue, 'im_func', cls.queue) or - settings.CELERY_DEFAULT_QUEUE + getattr(cls.queue, 'im_func', cls.queue) ) + if not queue: + msg = f'{cls.name}: Queue value required and may not be None' + logger.error(msg) + raise ValueError(msg) obj = { 'uuid': task_id, 'args': args, @@ -86,21 +87,8 @@ def apply_async(cls, args=None, kwargs=None, queue=None, uuid=None, **kw): if callable(queue): queue = queue() if not settings.IS_TESTING(sys.argv): - with Connection(settings.BROKER_URL) as conn: - exchange = Exchange(queue, type=exchange_type or 'direct') - producer = Producer(conn) - logger.debug('publish {}({}, queue={})'.format( - cls.name, - task_id, - queue - )) - producer.publish(obj, - serializer='json', - compression='bzip2', - exchange=exchange, - declare=[exchange], - delivery_mode="persistent", - routing_key=queue) + with pg_bus_conn() as conn: + conn.notify(queue, json.dumps(obj)) return (obj, queue) # If the object we're wrapping *is* a class (e.g., RunJob), return diff --git a/awx/main/dispatch/worker/__init__.py b/awx/main/dispatch/worker/__init__.py index 009386914ffc..6fe8f64608b5 100644 --- a/awx/main/dispatch/worker/__init__.py +++ b/awx/main/dispatch/worker/__init__.py @@ -1,3 +1,3 @@ -from .base import AWXConsumer, BaseWorker # noqa +from .base import AWXConsumerRedis, AWXConsumerPG, BaseWorker # noqa from .callback import CallbackBrokerWorker # noqa from .task import TaskWorker # noqa diff --git a/awx/main/dispatch/worker/base.py b/awx/main/dispatch/worker/base.py index 9a0b4735df03..8b44c71e43b6 100644 --- a/awx/main/dispatch/worker/base.py +++ b/awx/main/dispatch/worker/base.py @@ -5,14 +5,18 @@ import logging import signal import sys +import redis +import json +import psycopg2 +import time from uuid import UUID from queue import Empty as QueueEmpty from django import db -from kombu import Producer -from kombu.mixins import ConsumerMixin +from django.conf import settings from awx.main.dispatch.pool import WorkerPool +from awx.main.dispatch import pg_bus_conn if 'run_callback_receiver' in sys.argv: logger = logging.getLogger('awx.main.commands.run_callback_receiver') @@ -31,16 +35,21 @@ class WorkerSignalHandler: def __init__(self): self.kill_now = False + signal.signal(signal.SIGTERM, signal.SIG_DFL) signal.signal(signal.SIGINT, self.exit_gracefully) def exit_gracefully(self, *args, **kwargs): self.kill_now = True -class AWXConsumer(ConsumerMixin): +class AWXConsumerBase(object): - def __init__(self, name, connection, worker, queues=[], pool=None): - self.connection = connection + last_stats = time.time() + + def __init__(self, name, worker, queues=[], pool=None): + self.should_stop = False + + self.name = name self.total_messages = 0 self.queues = queues self.worker = worker @@ -48,26 +57,17 @@ def __init__(self, name, connection, worker, queues=[], pool=None): if pool is None: self.pool = WorkerPool() self.pool.init_workers(self.worker.work_loop) - - def get_consumers(self, Consumer, channel): - logger.debug(self.listening_on) - return [Consumer(queues=self.queues, accept=['json'], - callbacks=[self.process_task])] + self.redis = redis.Redis.from_url(settings.BROKER_URL) @property def listening_on(self): - return 'listening on {}'.format([ - '{} [{}]'.format(q.name, q.exchange.type) for q in self.queues - ]) + return f'listening on {self.queues}' - def control(self, body, message): + def control(self, body): logger.warn(body) control = body.get('control') if control in ('status', 'running'): - producer = Producer( - channel=self.connection, - routing_key=message.properties['reply_to'] - ) + reply_queue = body['reply_to'] if control == 'status': msg = '\n'.join([self.listening_on, self.pool.debug()]) elif control == 'running': @@ -75,20 +75,21 @@ def control(self, body, message): for worker in self.pool.workers: worker.calculate_managed_tasks() msg.extend(worker.managed_tasks.keys()) - producer.publish(msg) + + with pg_bus_conn() as conn: + conn.notify(reply_queue, json.dumps(msg)) elif control == 'reload': for worker in self.pool.workers: worker.quit() else: logger.error('unrecognized control message: {}'.format(control)) - message.ack() - def process_task(self, body, message): + def process_task(self, body): if 'control' in body: try: - return self.control(body, message) + return self.control(body) except Exception: - logger.exception("Exception handling control message:") + logger.exception(f"Exception handling control message: {body}") return if len(self.pool): if "uuid" in body and body['uuid']: @@ -102,21 +103,64 @@ def process_task(self, body, message): queue = 0 self.pool.write(queue, body) self.total_messages += 1 - message.ack() + self.record_statistics() + + def record_statistics(self): + if time.time() - self.last_stats > 1: # buffer stat recording to once per second + try: + self.redis.set(f'awx_{self.name}_statistics', self.pool.debug()) + self.last_stats = time.time() + except Exception: + logger.exception(f"encountered an error communicating with redis to store {self.name} statistics") + self.last_stats = time.time() def run(self, *args, **kwargs): signal.signal(signal.SIGINT, self.stop) signal.signal(signal.SIGTERM, self.stop) - self.worker.on_start() - super(AWXConsumer, self).run(*args, **kwargs) + + # Child should implement other things here def stop(self, signum, frame): - self.should_stop = True # this makes the kombu mixin stop consuming + self.should_stop = True logger.warn('received {}, stopping'.format(signame(signum))) self.worker.on_stop() raise SystemExit() +class AWXConsumerRedis(AWXConsumerBase): + def run(self, *args, **kwargs): + super(AWXConsumerRedis, self).run(*args, **kwargs) + self.worker.on_start() + + while True: + logger.debug(f'{os.getpid()} is alive') + time.sleep(60) + + +class AWXConsumerPG(AWXConsumerBase): + def run(self, *args, **kwargs): + super(AWXConsumerPG, self).run(*args, **kwargs) + + logger.warn(f"Running worker {self.name} listening to queues {self.queues}") + init = False + + while True: + try: + with pg_bus_conn() as conn: + for queue in self.queues: + conn.listen(queue) + if init is False: + self.worker.on_start() + init = True + for e in conn.events(): + self.process_task(json.loads(e.payload)) + if self.should_stop: + return + except psycopg2.InterfaceError: + logger.warn("Stale Postgres message bus connection, reconnecting") + continue + + class BaseWorker(object): def read(self, queue): @@ -148,7 +192,6 @@ def work_loop(self, queue, finished, idx, *args): finally: if 'uuid' in body: uuid = body['uuid'] - logger.debug('task {} is finished'.format(uuid)) finished.put(uuid) logger.warn('worker exiting gracefully pid:{}'.format(os.getpid())) diff --git a/awx/main/dispatch/worker/callback.py b/awx/main/dispatch/worker/callback.py index 3e348a07ffe4..e61a51609454 100644 --- a/awx/main/dispatch/worker/callback.py +++ b/awx/main/dispatch/worker/callback.py @@ -1,26 +1,31 @@ +import json import logging +import os +import signal import time import traceback -from queue import Empty as QueueEmpty -from django.utils.timezone import now as tz_now from django.conf import settings +from django.utils.timezone import now as tz_now from django.db import DatabaseError, OperationalError, connection as django_connection -from django.db.utils import InterfaceError, InternalError, IntegrityError +from django.db.utils import InterfaceError, InternalError + +import psutil + +import redis from awx.main.consumers import emit_channel_notification from awx.main.models import (JobEvent, AdHocCommandEvent, ProjectUpdateEvent, - InventoryUpdateEvent, SystemJobEvent, UnifiedJob) + InventoryUpdateEvent, SystemJobEvent, UnifiedJob, + Job) +from awx.main.tasks import handle_success_and_failure_notifications from awx.main.models.events import emit_event_detail +from awx.main.utils.profiling import AWXProfiler from .base import BaseWorker logger = logging.getLogger('awx.main.commands.run_callback_receiver') -# the number of seconds to buffer events in memory before flushing -# using JobEvent.objects.bulk_create() -BUFFER_SECONDS = .1 - class CallbackBrokerWorker(BaseWorker): ''' @@ -32,20 +37,73 @@ class CallbackBrokerWorker(BaseWorker): ''' MAX_RETRIES = 2 + last_stats = time.time() + last_flush = time.time() + total = 0 + last_event = '' + prof = None def __init__(self): self.buff = {} + self.pid = os.getpid() + self.redis = redis.Redis.from_url(settings.BROKER_URL) + self.prof = AWXProfiler("CallbackBrokerWorker") + for key in self.redis.keys('awx_callback_receiver_statistics_*'): + self.redis.delete(key) def read(self, queue): try: - return queue.get(block=True, timeout=BUFFER_SECONDS) - except QueueEmpty: - return {'event': 'FLUSH'} + res = self.redis.blpop(settings.CALLBACK_QUEUE, timeout=1) + if res is None: + return {'event': 'FLUSH'} + self.total += 1 + return json.loads(res[1]) + except redis.exceptions.RedisError: + logger.exception("encountered an error communicating with redis") + time.sleep(1) + except (json.JSONDecodeError, KeyError): + logger.exception("failed to decode JSON message from redis") + finally: + self.record_statistics() + return {'event': 'FLUSH'} + + def record_statistics(self): + # buffer stat recording to once per (by default) 5s + if time.time() - self.last_stats > settings.JOB_EVENT_STATISTICS_INTERVAL: + try: + self.redis.set(f'awx_callback_receiver_statistics_{self.pid}', self.debug()) + self.last_stats = time.time() + except Exception: + logger.exception("encountered an error communicating with redis") + self.last_stats = time.time() + + def debug(self): + return f'. worker[pid:{self.pid}] sent={self.total} rss={self.mb}MB {self.last_event}' + + @property + def mb(self): + return '{:0.3f}'.format( + psutil.Process(self.pid).memory_info().rss / 1024.0 / 1024.0 + ) + + def toggle_profiling(self, *args): + if not self.prof.is_started(): + self.prof.start() + logger.error('profiling is enabled') + else: + filepath = self.prof.stop() + logger.error(f'profiling is disabled, wrote {filepath}') + + def work_loop(self, *args, **kw): + if settings.AWX_CALLBACK_PROFILE: + signal.signal(signal.SIGUSR1, self.toggle_profiling) + return super(CallbackBrokerWorker, self).work_loop(*args, **kw) def flush(self, force=False): now = tz_now() if ( force or + (time.time() - self.last_flush) > settings.JOB_EVENT_BUFFER_SECONDS or any([len(events) >= 1000 for events in self.buff.values()]) ): for cls, events in self.buff.items(): @@ -56,30 +114,25 @@ def flush(self, force=False): e.modified = now try: cls.objects.bulk_create(events) - except Exception as exc: + except Exception: # if an exception occurs, we should re-attempt to save the # events one-by-one, because something in the list is - # broken/stale (e.g., an IntegrityError on a specific event) + # broken/stale for e in events: try: - if ( - isinstance(exc, IntegrityError), - getattr(e, 'host_id', '') - ): - # this is one potential IntegrityError we can - # work around - if the host disappears before - # the event can be processed - e.host_id = None e.save() except Exception: logger.exception('Database Error Saving Job Event') for e in events: emit_event_detail(e) self.buff = {} + self.last_flush = time.time() def perform_work(self, body): try: flush = body.get('event') == 'FLUSH' + if flush: + self.last_event = '' if not flush: event_map = { 'job_id': JobEvent, @@ -95,6 +148,8 @@ def perform_work(self, body): job_identifier = body[key] break + self.last_event = f'\n\t- {cls.__name__} for #{job_identifier} ({body.get("event", "")} {body.get("uuid", "")})' # noqa + if body.get('event') == 'EOF': try: final_counter = body.get('final_counter', 0) @@ -111,19 +166,14 @@ def perform_work(self, body): # have all the data we need to send out success/failure # notification templates uj = UnifiedJob.objects.get(pk=job_identifier) - if hasattr(uj, 'send_notification_templates'): - retries = 0 - while retries < 5: - if uj.finished: - uj.send_notification_templates('succeeded' if uj.status == 'successful' else 'failed') - break - else: - # wait a few seconds to avoid a race where the - # events are persisted _before_ the UJ.status - # changes from running -> successful - retries += 1 - time.sleep(1) - uj = UnifiedJob.objects.get(pk=job_identifier) + + if isinstance(uj, Job): + # *actual playbooks* send their success/failure + # notifications in response to the playbook_on_stats + # event handling code in main.models.events + pass + elif hasattr(uj, 'send_notification_templates'): + handle_success_and_failure_notifications.apply_async([uj.id]) except Exception: logger.exception('Worker failed to emit notifications: Job {}'.format(job_identifier)) return diff --git a/awx/main/exceptions.py b/awx/main/exceptions.py index 8aadfd80b0fe..64cbc94783ad 100644 --- a/awx/main/exceptions.py +++ b/awx/main/exceptions.py @@ -30,3 +30,10 @@ def TaskError(self, task, rc): AwxTaskError = _AwxTaskError() + + +class PostRunError(Exception): + def __init__(self, msg, status='failed', tb=''): + self.status = status + self.tb = tb + super(PostRunError, self).__init__(msg) diff --git a/awx/main/fields.py b/awx/main/fields.py index d395803c7cbb..0122b0ab80d9 100644 --- a/awx/main/fields.py +++ b/awx/main/fields.py @@ -7,8 +7,8 @@ import re import urllib.parse -from jinja2 import Environment, StrictUndefined -from jinja2.exceptions import UndefinedError, TemplateSyntaxError +from jinja2 import sandbox, StrictUndefined +from jinja2.exceptions import UndefinedError, TemplateSyntaxError, SecurityError # Django from django.contrib.postgres.fields import JSONField as upstream_JSONBField @@ -50,13 +50,14 @@ batch_role_ancestor_rebuilding, Role, ROLE_SINGLETON_SYSTEM_ADMINISTRATOR, ROLE_SINGLETON_SYSTEM_AUDITOR ) -from awx.main.constants import ENV_BLACKLIST +from awx.main.constants import ENV_BLOCKLIST from awx.main import utils __all__ = ['AutoOneToOneField', 'ImplicitRoleField', 'JSONField', 'SmartFilterField', 'OrderedManyToManyField', - 'update_role_parentage_for_instance', 'is_implicit_parent'] + 'update_role_parentage_for_instance', + 'is_implicit_parent'] # Provide a (better) custom error message for enum jsonschema validation @@ -140,8 +141,9 @@ def resolve_role_field(obj, field): return [] if len(field_components) == 1: - role_cls = str(utils.get_current_apps().get_model('main', 'Role')) - if not str(type(obj)) == role_cls: + # use extremely generous duck typing to accomidate all possible forms + # of the model that may be used during various migrations + if obj._meta.model_name != 'role' or obj._meta.app_label != 'main': raise Exception(smart_text('{} refers to a {}, not a Role'.format(field, type(obj)))) ret.append(obj.id) else: @@ -197,18 +199,27 @@ def update_role_parentage_for_instance(instance): updates the parents listing for all the roles of a given instance if they have changed ''' + parents_removed = set() + parents_added = set() for implicit_role_field in getattr(instance.__class__, '__implicit_role_fields'): cur_role = getattr(instance, implicit_role_field.name) original_parents = set(json.loads(cur_role.implicit_parents)) new_parents = implicit_role_field._resolve_parent_roles(instance) - cur_role.parents.remove(*list(original_parents - new_parents)) - cur_role.parents.add(*list(new_parents - original_parents)) + removals = original_parents - new_parents + if removals: + cur_role.parents.remove(*list(removals)) + parents_removed.add(cur_role.pk) + additions = new_parents - original_parents + if additions: + cur_role.parents.add(*list(additions)) + parents_added.add(cur_role.pk) new_parents_list = list(new_parents) new_parents_list.sort() new_parents_json = json.dumps(new_parents_list) if cur_role.implicit_parents != new_parents_json: cur_role.implicit_parents = new_parents_json - cur_role.save() + cur_role.save(update_fields=['implicit_parents']) + return (parents_added, parents_removed) class ImplicitRoleDescriptor(ForwardManyToOneDescriptor): @@ -256,20 +267,18 @@ def bind_m2m_changed(self, _self, _role_class, cls): field_names = [field_names] for field_name in field_names: - # Handle the OR syntax for role parents - if type(field_name) == tuple: - continue - - if type(field_name) == bytes: - field_name = field_name.decode('utf-8') if field_name.startswith('singleton:'): continue field_name, sep, field_attr = field_name.partition('.') - field = getattr(cls, field_name) + # Non existent fields will occur if ever a parent model is + # moved inside a migration, needed for job_template_organization_field + # migration in particular + # consistency is assured by unit test awx.main.tests.functional + field = getattr(cls, field_name, None) - if type(field) is ReverseManyToOneDescriptor or \ + if field and type(field) is ReverseManyToOneDescriptor or \ type(field) is ManyToManyDescriptor: if '.' in field_attr: @@ -628,6 +637,14 @@ def validate(self, value, model_instance): else: decrypted_values[k] = v + # don't allow secrets with $encrypted$ on new object creation + if not model_instance.pk: + for field in model_instance.credential_type.secret_fields: + if value.get(field) == '$encrypted$': + raise serializers.ValidationError({ + self.name: [f'$encrypted$ is a reserved keyword, and cannot be used for {field}.'] + }) + super(JSONSchemaField, self).validate(decrypted_values, model_instance) errors = {} for error in Draft4Validator( @@ -861,9 +878,9 @@ def validate_env_var_allowed(self, env_var): 'use is not allowed in credentials.').format(env_var), code='invalid', params={'value': env_var}, ) - if env_var in ENV_BLACKLIST: + if env_var in ENV_BLOCKLIST: raise django_exceptions.ValidationError( - _('Environment variable {} is blacklisted from use in credentials.').format(env_var), + _('Environment variable {} is not allowed to be used in credentials.').format(env_var), code='invalid', params={'value': env_var}, ) @@ -923,7 +940,7 @@ def __str__(self): self.validate_env_var_allowed(key) for key, tmpl in injector.items(): try: - Environment( + sandbox.ImmutableSandboxedEnvironment( undefined=StrictUndefined ).from_string(tmpl).render(valid_namespace) except UndefinedError as e: @@ -933,6 +950,10 @@ def __str__(self): code='invalid', params={'value': value}, ) + except SecurityError as e: + raise django_exceptions.ValidationError( + _('Encountered unsafe code execution: {}').format(e) + ) except TemplateSyntaxError as e: raise django_exceptions.ValidationError( _('Syntax error rendering template for {sub_key} inside of {type} ({error_msg})').format( diff --git a/awx/main/isolated/manager.py b/awx/main/isolated/manager.py index bc4a5201d0fc..de4783e27726 100644 --- a/awx/main/isolated/manager.py +++ b/awx/main/isolated/manager.py @@ -7,6 +7,7 @@ import time import logging import yaml +import datetime from django.conf import settings import ansible_runner @@ -15,7 +16,6 @@ from awx.main.utils import ( get_system_task_capacity ) -from awx.main.queue import CallbackQueueDispatcher logger = logging.getLogger('awx.isolated.manager') playbook_logger = logging.getLogger('awx.isolated.manager.playbooks') @@ -32,12 +32,14 @@ def set_pythonpath(venv_libdir, env): class IsolatedManager(object): - def __init__(self, canceled_callback=None, check_callback=None, pod_manager=None): + def __init__(self, event_handler, canceled_callback=None, check_callback=None, pod_manager=None): """ + :param event_handler: a callable used to persist event data from isolated nodes :param canceled_callback: a callable - which returns `True` or `False` - signifying if the job has been prematurely canceled """ + self.event_handler = event_handler self.canceled_callback = canceled_callback self.check_callback = check_callback self.started_at = None @@ -73,6 +75,7 @@ def build_runner_params(self, hosts, verbosity=1): env['ANSIBLE_RETRY_FILES_ENABLED'] = 'False' env['ANSIBLE_HOST_KEY_CHECKING'] = str(settings.AWX_ISOLATED_HOST_KEY_CHECKING) env['ANSIBLE_LIBRARY'] = os.path.join(os.path.dirname(awx.__file__), 'plugins', 'isolated') + env['ANSIBLE_COLLECTIONS_PATHS'] = settings.AWX_ANSIBLE_COLLECTIONS_PATHS set_pythonpath(os.path.join(settings.ANSIBLE_VENV_PATH, 'lib'), env) def finished_callback(runner_obj): @@ -108,7 +111,6 @@ def finished_callback(runner_obj): 'cancel_callback': self.canceled_callback, 'settings': { 'job_timeout': settings.AWX_ISOLATED_LAUNCH_TIMEOUT, - 'pexpect_timeout': getattr(settings, 'PEXPECT_TIMEOUT', 5), 'suppress_ansible_output': True, }, } @@ -122,6 +124,7 @@ def run_management_playbook(self, playbook, private_data_dir, idle_timeout=None, dir=private_data_dir ) params = self.runner_params.copy() + params.get('envvars', dict())['ANSIBLE_CALLBACK_WHITELIST'] = 'profile_tasks' params['playbook'] = playbook params['private_data_dir'] = iso_dir if idle_timeout: @@ -148,7 +151,6 @@ def dispatch(self, playbook=None, module=None, module_args=None): # don't rsync source control metadata (it can be huge!) '- /project/.git', '- /project/.svn', - '- /project/.hg', # don't rsync job events that are in the process of being written '- /artifacts/job_events/*-partial.json.tmp', # don't rsync the ssh_key FIFO @@ -168,7 +170,8 @@ def dispatch(self, playbook=None, module=None, module_args=None): extravars = { 'src': self.private_data_dir, 'dest': settings.AWX_PROOT_BASE_PATH, - 'ident': self.ident + 'ident': self.ident, + 'job_id': self.instance.id, } if playbook: extravars['playbook'] = playbook @@ -204,11 +207,13 @@ def check(self, interval=None): :param interval: an interval (in seconds) to wait between status polls """ interval = interval if interval is not None else settings.AWX_ISOLATED_CHECK_INTERVAL - extravars = {'src': self.private_data_dir} + extravars = { + 'src': self.private_data_dir, + 'job_id': self.instance.id + } status = 'failed' rc = None last_check = time.time() - dispatcher = CallbackQueueDispatcher() while status == 'failed': canceled = self.canceled_callback() if self.canceled_callback else False @@ -221,9 +226,13 @@ def check(self, interval=None): logger.warning('Isolated job {} was manually canceled.'.format(self.instance.id)) logger.debug('Checking on isolated job {} with `check_isolated.yml`.'.format(self.instance.id)) + time_start = datetime.datetime.now() runner_obj = self.run_management_playbook('check_isolated.yml', self.private_data_dir, extravars=extravars) + time_end = datetime.datetime.now() + time_diff = time_end - time_start + logger.debug('Finished checking on isolated job {} with `check_isolated.yml` took {} seconds.'.format(self.instance.id, time_diff.total_seconds())) status, rc = runner_obj.status, runner_obj.rc if self.check_callback is not None and not self.captured_command_artifact: @@ -238,7 +247,7 @@ def check(self, interval=None): except json.decoder.JSONDecodeError: # Just in case it's not fully here yet. pass - self.consume_events(dispatcher) + self.consume_events() last_check = time.time() @@ -266,19 +275,11 @@ def check(self, interval=None): # consume events one last time just to be sure we didn't miss anything # in the final sync - self.consume_events(dispatcher) - - # emit an EOF event - event_data = { - 'event': 'EOF', - 'final_counter': len(self.handled_events) - } - event_data.setdefault(self.event_data_key, self.instance.id) - dispatcher.dispatch(event_data) + self.consume_events() return status, rc - def consume_events(self, dispatcher): + def consume_events(self): # discover new events and ingest them events_path = self.path_to('artifacts', self.ident, 'job_events') @@ -288,7 +289,7 @@ def consume_events(self, dispatcher): if os.path.exists(events_path): for event in set(os.listdir(events_path)) - self.handled_events: path = os.path.join(events_path, event) - if os.path.exists(path): + if os.path.exists(path) and os.path.isfile(path): try: event_data = json.load( open(os.path.join(events_path, event), 'r') @@ -302,16 +303,10 @@ def consume_events(self, dispatcher): # practice # in this scenario, just ignore this event and try it # again on the next sync - pass - event_data.setdefault(self.event_data_key, self.instance.id) - dispatcher.dispatch(event_data) + continue + self.event_handler(event_data) self.handled_events.add(event) - # handle artifacts - if event_data.get('event_data', {}).get('artifact_data', {}): - self.instance.artifacts = event_data['event_data']['artifact_data'] - self.instance.save(update_fields=['artifacts']) - def cleanup(self): extravars = { @@ -370,39 +365,37 @@ def health_check(self, instance_qs): private_data_dir ) - if runner_obj.status == 'successful': - for instance in instance_qs: - task_result = {} - try: - task_result = runner_obj.get_fact_cache(instance.hostname) - except Exception: - logger.exception('Failed to read status from isolated instances') - if 'awx_capacity_cpu' in task_result and 'awx_capacity_mem' in task_result: - task_result = { - 'cpu': task_result['awx_cpu'], - 'mem': task_result['awx_mem'], - 'capacity_cpu': task_result['awx_capacity_cpu'], - 'capacity_mem': task_result['awx_capacity_mem'], - 'version': task_result['awx_capacity_version'] - } - IsolatedManager.update_capacity(instance, task_result) - logger.debug('Isolated instance {} successful heartbeat'.format(instance.hostname)) - elif instance.capacity == 0: - logger.debug('Isolated instance {} previously marked as lost, could not re-join.'.format( - instance.hostname)) - else: - logger.warning('Could not update status of isolated instance {}'.format(instance.hostname)) - if instance.is_lost(isolated=True): - instance.capacity = 0 - instance.save(update_fields=['capacity']) - logger.error('Isolated instance {} last checked in at {}, marked as lost.'.format( - instance.hostname, instance.modified)) + for instance in instance_qs: + task_result = {} + try: + task_result = runner_obj.get_fact_cache(instance.hostname) + except Exception: + logger.exception('Failed to read status from isolated instances') + if 'awx_capacity_cpu' in task_result and 'awx_capacity_mem' in task_result: + task_result = { + 'cpu': task_result['awx_cpu'], + 'mem': task_result['awx_mem'], + 'capacity_cpu': task_result['awx_capacity_cpu'], + 'capacity_mem': task_result['awx_capacity_mem'], + 'version': task_result['awx_capacity_version'] + } + IsolatedManager.update_capacity(instance, task_result) + logger.debug('Isolated instance {} successful heartbeat'.format(instance.hostname)) + elif instance.capacity == 0: + logger.debug('Isolated instance {} previously marked as lost, could not re-join.'.format( + instance.hostname)) + else: + logger.warning('Could not update status of isolated instance {}'.format(instance.hostname)) + if instance.is_lost(isolated=True): + instance.capacity = 0 + instance.save(update_fields=['capacity']) + logger.error('Isolated instance {} last checked in at {}, marked as lost.'.format( + instance.hostname, instance.modified)) finally: if os.path.exists(private_data_dir): shutil.rmtree(private_data_dir) - def run(self, instance, private_data_dir, playbook, module, module_args, - event_data_key, ident=None): + def run(self, instance, private_data_dir, playbook, module, module_args, ident=None): """ Run a job on an isolated host. @@ -413,14 +406,12 @@ def run(self, instance, private_data_dir, playbook, module, module_args, :param playbook: the playbook to run :param module: the module to run :param module_args: the module args to use - :param event_data_key: e.g., job_id, inventory_id, ... For a completed job run, this function returns (status, rc), representing the status and return code of the isolated `ansible-playbook` run. """ self.ident = ident - self.event_data_key = event_data_key self.instance = instance self.private_data_dir = private_data_dir self.runner_params = self.build_runner_params( @@ -431,9 +422,4 @@ def run(self, instance, private_data_dir, playbook, module, module_args, status, rc = self.dispatch(playbook, module, module_args) if status == 'successful': status, rc = self.check() - else: - # emit an EOF event - event_data = {'event': 'EOF', 'final_counter': 0} - event_data.setdefault(self.event_data_key, self.instance.id) - CallbackQueueDispatcher().dispatch(event_data) return status, rc diff --git a/awx/main/management/commands/bottleneck.py b/awx/main/management/commands/bottleneck.py new file mode 100644 index 000000000000..beac1d0745dd --- /dev/null +++ b/awx/main/management/commands/bottleneck.py @@ -0,0 +1,96 @@ +from django.core.management.base import BaseCommand +from django.db import connection + +from awx.main.models import JobTemplate + + +class Command(BaseCommand): + help = "Find the slowest tasks and hosts for a Job Template's most recent runs." + + def add_arguments(self, parser): + parser.add_argument('--template', dest='jt', type=int, + help='ID of the Job Template to profile') + parser.add_argument('--threshold', dest='threshold', type=float, default=30, + help='Only show tasks that took at least this many seconds (defaults to 30)') + parser.add_argument('--history', dest='history', type=float, default=25, + help='The number of historic jobs to look at') + parser.add_argument('--ignore', action='append', help='ignore a specific action (e.g., --ignore git)') + + def handle(self, *args, **options): + jt = options['jt'] + threshold = options['threshold'] + history = options['history'] + ignore = options['ignore'] + + print('## ' + JobTemplate.objects.get(pk=jt).name + f' (last {history} runs)\n') + with connection.cursor() as cursor: + cursor.execute( + f''' + SELECT + b.id, b.job_id, b.host_name, b.created - a.created delta, + b.task task, + b.event_data::json->'task_action' task_action, + b.event_data::json->'task_path' task_path + FROM main_jobevent a JOIN main_jobevent b + ON b.parent_uuid = a.parent_uuid AND a.host_name = b.host_name + WHERE + a.event = 'runner_on_start' AND + b.event != 'runner_on_start' AND + b.event != 'runner_on_skipped' AND + b.failed = false AND + a.job_id IN ( + SELECT unifiedjob_ptr_id FROM main_job + WHERE job_template_id={jt} + ORDER BY unifiedjob_ptr_id DESC + LIMIT {history} + ) + ORDER BY delta DESC; + ''' + ) + slowest_events = cursor.fetchall() + + def format_td(x): + return str(x).split('.')[0] + + fastest = dict() + for event in slowest_events: + _id, job_id, host, duration, task, action, playbook = event + playbook = playbook.rsplit('/')[-1] + if ignore and action in ignore: + continue + if host: + fastest[(action, playbook)] = (_id, host, format_td(duration)) + + host_counts = dict() + warned = set() + print(f'slowest tasks (--threshold={threshold})\n---') + + for event in slowest_events: + _id, job_id, host, duration, task, action, playbook = event + if ignore and action in ignore: + continue + if duration.total_seconds() < threshold: + break + playbook = playbook.rsplit('/')[-1] + human_duration = format_td(duration) + + fastest_summary = '' + fastest_match = fastest.get((action, playbook)) + if fastest_match[2] != human_duration and (host, action, playbook) not in warned: + warned.add((host, action, playbook)) + fastest_summary = ' ' + self.style.WARNING(f'{fastest_match[1]} ran this in {fastest_match[2]}s at /api/v2/job_events/{fastest_match[0]}/') + + url = f'/api/v2/jobs/{job_id}/' + print(' -- '.join([url, host, human_duration, action, task, playbook]) + fastest_summary) + host_counts.setdefault(host, []) + host_counts[host].append(duration) + + host_counts = sorted(host_counts.items(), key=lambda item: [e.total_seconds() for e in item[1]], reverse=True) + + print('\nslowest hosts\n---') + for h, matches in host_counts: + total = len(matches) + total_seconds = sum([e.total_seconds() for e in matches]) + print(f'{h} had {total} tasks that ran longer than {threshold} second(s) for a total of {total_seconds}') + + print('') diff --git a/awx/main/management/commands/callback_stats.py b/awx/main/management/commands/callback_stats.py index 8667a15a1871..0a6108960711 100644 --- a/awx/main/management/commands/callback_stats.py +++ b/awx/main/management/commands/callback_stats.py @@ -9,6 +9,13 @@ class Command(BaseCommand): def handle(self, *args, **options): with connection.cursor() as cursor: + start = {} + for relation in ( + 'main_jobevent', 'main_inventoryupdateevent', + 'main_projectupdateevent', 'main_adhoccommandevent' + ): + cursor.execute(f"SELECT MAX(id) FROM {relation};") + start[relation] = cursor.fetchone()[0] or 0 clear = False while True: lines = [] @@ -17,21 +24,17 @@ def handle(self, *args, **options): 'main_projectupdateevent', 'main_adhoccommandevent' ): lines.append(relation) - for label, interval in ( - ('last minute: ', '1 minute'), - ('last 5 minutes:', '5 minutes'), - ('last hour: ', '1 hour'), - ): - cursor.execute( - f"SELECT MAX(id) - MIN(id) FROM {relation} WHERE modified > now() - '{interval}'::interval;" - ) - events = cursor.fetchone()[0] or 0 - lines.append(f'↳ {label} {events}') + minimum = start[relation] + cursor.execute( + f"SELECT MAX(id) - MIN(id) FROM {relation} WHERE id > {minimum} AND modified > now() - '1 minute'::interval;" + ) + events = cursor.fetchone()[0] or 0 + lines.append(f'↳ last minute {events}') lines.append('') if clear: - for i in range(20): + for i in range(12): sys.stdout.write('\x1b[1A\x1b[2K') - for l in lines: - print(l) + for line in lines: + print(line) clear = True time.sleep(.25) diff --git a/awx/main/management/commands/check_license.py b/awx/main/management/commands/check_license.py index 8c0798cc5323..356ab422492a 100644 --- a/awx/main/management/commands/check_license.py +++ b/awx/main/management/commands/check_license.py @@ -18,7 +18,5 @@ def handle(self, *args, **options): super(Command, self).__init__() license = get_licenser().validate() if options.get('data'): - if license.get('license_key', '') != 'UNLICENSED': - license['license_key'] = '********' return json.dumps(license) return license.get('license_type', 'none') diff --git a/awx/main/management/commands/check_migrations.py b/awx/main/management/commands/check_migrations.py index 50ea35496094..6f9cfc77276d 100644 --- a/awx/main/management/commands/check_migrations.py +++ b/awx/main/management/commands/check_migrations.py @@ -8,5 +8,7 @@ class Command(MakeMigrations): def execute(self, *args, **options): settings = connections['default'].settings_dict.copy() settings['ENGINE'] = 'sqlite3' + if 'application_name' in settings['OPTIONS']: + del settings['OPTIONS']['application_name'] connections['default'] = DatabaseWrapper(settings) return MakeMigrations().execute(*args, **options) diff --git a/awx/main/management/commands/cleanup_jobs.py b/awx/main/management/commands/cleanup_jobs.py index 81f405da4c15..66953acde93e 100644 --- a/awx/main/management/commands/cleanup_jobs.py +++ b/awx/main/management/commands/cleanup_jobs.py @@ -16,13 +16,12 @@ Job, AdHocCommand, ProjectUpdate, InventoryUpdate, SystemJob, WorkflowJob, Notification ) -from awx.main.signals import ( # noqa - emit_update_inventory_on_created_or_deleted, - emit_update_inventory_computed_fields, +from awx.main.signals import ( disable_activity_stream, disable_computed_fields ) -from django.db.models.signals import post_save, post_delete, m2m_changed # noqa + +from awx.main.utils.deletion import AWXCollector, pre_delete class Command(BaseCommand): @@ -60,27 +59,37 @@ def add_arguments(self, parser): action='store_true', dest='only_workflow_jobs', help='Remove workflow jobs') + def cleanup_jobs(self): - #jobs_qs = Job.objects.exclude(status__in=('pending', 'running')) - #jobs_qs = jobs_qs.filter(created__lte=self.cutoff) skipped, deleted = 0, 0 - jobs = Job.objects.filter(created__lt=self.cutoff) - for job in jobs.iterator(): - job_display = '"%s" (%d host summaries, %d events)' % \ - (str(job), - job.job_host_summaries.count(), job.job_events.count()) - if job.status in ('pending', 'waiting', 'running'): - action_text = 'would skip' if self.dry_run else 'skipping' - self.logger.debug('%s %s job %s', action_text, job.status, job_display) - skipped += 1 + + batch_size = 1000000 + + while True: + # get queryset for available jobs to remove + qs = Job.objects.filter(created__lt=self.cutoff).exclude(status__in=['pending', 'waiting', 'running']) + # get pk list for the first N (batch_size) objects + pk_list = qs[0:batch_size].values_list('pk') + # You cannot delete queries with sql LIMIT set, so we must + # create a new query from this pk_list + qs_batch = Job.objects.filter(pk__in=pk_list) + just_deleted = 0 + if not self.dry_run: + del_query = pre_delete(qs_batch) + collector = AWXCollector(del_query.db) + collector.collect(del_query) + _, models_deleted = collector.delete() + if models_deleted: + just_deleted = models_deleted['main.Job'] + deleted += just_deleted else: - action_text = 'would delete' if self.dry_run else 'deleting' - self.logger.info('%s %s', action_text, job_display) - if not self.dry_run: - job.delete() - deleted += 1 + just_deleted = 0 # break from loop, this is dry run + deleted = qs.count() + + if just_deleted == 0: + break - skipped += Job.objects.filter(created__gte=self.cutoff).count() + skipped += (Job.objects.filter(created__gte=self.cutoff) | Job.objects.filter(status__in=['pending', 'waiting', 'running'])).count() return skipped, deleted def cleanup_ad_hoc_commands(self): diff --git a/awx/main/management/commands/create_preload_data.py b/awx/main/management/commands/create_preload_data.py index 297622af4677..9b1d1317359a 100644 --- a/awx/main/management/commands/create_preload_data.py +++ b/awx/main/management/commands/create_preload_data.py @@ -42,6 +42,16 @@ def handle(self, *args, **kwargs): }, created_by=superuser) c.admin_role.members.add(superuser) + public_galaxy_credential = Credential( + name='Ansible Galaxy', + managed_by_tower=True, + credential_type=CredentialType.objects.get(kind='galaxy'), + inputs = { + 'url': 'https://galaxy.ansible.com/' + } + ) + public_galaxy_credential.save() + o.galaxy_credentials.add(public_galaxy_credential) i = Inventory.objects.create(name='Demo Inventory', organization=o, created_by=superuser) diff --git a/awx/main/management/commands/deprovision_instance.py b/awx/main/management/commands/deprovision_instance.py index ed9ceefa1e1f..248e1e7684ae 100644 --- a/awx/main/management/commands/deprovision_instance.py +++ b/awx/main/management/commands/deprovision_instance.py @@ -1,8 +1,6 @@ # Copyright (c) 2016 Ansible, Inc. # All Rights Reserved -import subprocess - from django.db import transaction from django.core.management.base import BaseCommand, CommandError @@ -33,18 +31,9 @@ def handle(self, *args, **options): with advisory_lock('instance_registration_%s' % hostname): instance = Instance.objects.filter(hostname=hostname) if instance.exists(): - isolated = instance.first().is_isolated() instance.delete() print("Instance Removed") - if isolated: - print('Successfully deprovisioned {}'.format(hostname)) - else: - result = subprocess.Popen("rabbitmqctl forget_cluster_node rabbitmq@{}".format(hostname), shell=True).wait() - if result != 0: - print("Node deprovisioning may have failed when attempting to " - "remove the RabbitMQ instance {} from the cluster".format(hostname)) - else: - print('Successfully deprovisioned {}'.format(hostname)) + print('Successfully deprovisioned {}'.format(hostname)) print('(changed: True)') else: print('No instance found matching name {}'.format(hostname)) diff --git a/awx/main/management/commands/gather_analytics.py b/awx/main/management/commands/gather_analytics.py index aa096d6f28ae..b5e8427955ce 100644 --- a/awx/main/management/commands/gather_analytics.py +++ b/awx/main/management/commands/gather_analytics.py @@ -1,6 +1,9 @@ import logging + from awx.main.analytics import gather, ship +from dateutil import parser from django.core.management.base import BaseCommand +from django.utils.timezone import now class Command(BaseCommand): @@ -15,6 +18,10 @@ def add_arguments(self, parser): help='Gather analytics without shipping. Works even if analytics are disabled in settings.') parser.add_argument('--ship', dest='ship', action='store_true', help='Enable to ship metrics to the Red Hat Cloud') + parser.add_argument('--since', dest='since', action='store', + help='Start date for collection') + parser.add_argument('--until', dest='until', action='store', + help='End date for collection') def init_logging(self): self.logger = logging.getLogger('awx.main.analytics') @@ -28,11 +35,28 @@ def handle(self, *args, **options): self.init_logging() opt_ship = options.get('ship') opt_dry_run = options.get('dry-run') + opt_since = options.get('since') or None + opt_until = options.get('until') or None + + if opt_since: + since = parser.parse(opt_since) + else: + since = None + if opt_until: + until = parser.parse(opt_until) + else: + until = now() + if opt_ship and opt_dry_run: self.logger.error('Both --ship and --dry-run cannot be processed at the same time.') return - tgz = gather(collection_type='manual' if not opt_dry_run else 'dry-run') - if tgz: - self.logger.debug(tgz) + tgzfiles = gather(collection_type='manual' if not opt_dry_run else 'dry-run', since = since, until = until) + if tgzfiles: + for tgz in tgzfiles: + self.logger.info(tgz) + else: + self.logger.error('No analytics collected') if opt_ship: - ship(tgz) + if tgzfiles: + for tgz in tgzfiles: + ship(tgz) diff --git a/awx/main/management/commands/graph_jobs.py b/awx/main/management/commands/graph_jobs.py new file mode 100644 index 000000000000..f1c8ad75e174 --- /dev/null +++ b/awx/main/management/commands/graph_jobs.py @@ -0,0 +1,117 @@ +# Python +import asciichartpy as chart +import collections +import time +import sys + +# Django +from django.db.models import Count +from django.core.management.base import BaseCommand + +# AWX +from awx.main.models import ( + Job, + Instance +) + + +DEFAULT_WIDTH = 100 +DEFAULT_HEIGHT = 30 + + +def chart_color_lookup(color_str): + return getattr(chart, color_str) + + +def clear_screen(): + print(chr(27) + "[2J") + + +class JobStatus(): + def __init__(self, status, color, width): + self.status = status + self.color = color + self.color_code = chart_color_lookup(color) + self.x = collections.deque(maxlen=width) + self.y = collections.deque(maxlen=width) + + def tick(self, x, y): + self.x.append(x) + self.y.append(y) + + +class JobStatusController: + RESET = chart_color_lookup('reset') + + def __init__(self, width): + self.plots = [ + JobStatus('pending', 'red', width), + JobStatus('waiting', 'blue', width), + JobStatus('running', 'green', width) + ] + self.ts_start = int(time.time()) + + def tick(self): + ts = int(time.time()) - self.ts_start + q = Job.objects.filter(status__in=['pending','waiting','running']).values_list('status').order_by().annotate(Count('status')) + status_count = dict(pending=0, waiting=0, running=0) + for status, count in q: + status_count[status] = count + + for p in self.plots: + p.tick(ts, status_count[p.status]) + + def series(self): + return [list(p.y) for p in self.plots] + + def generate_status(self): + line = "" + lines = [] + for p in self.plots: + lines.append(f'{p.color_code}{p.status} {p.y[-1]}{self.RESET}') + + line += ", ".join(lines) + '\n' + + width = 5 + time_running = int(time.time()) - self.ts_start + instances = Instance.objects.all().order_by('hostname') + line += "Capacity: " + ", ".join([f"{instance.capacity:{width}}" for instance in instances]) + '\n' + line += "Remaining: " + ", ".join([f"{instance.remaining_capacity:{width}}" for instance in instances]) + '\n' + line += f"Seconds running: {time_running}" + '\n' + + return line + + +class Command(BaseCommand): + help = "Plot pending, waiting, running jobs over time on the terminal" + + def add_arguments(self, parser): + parser.add_argument('--refresh', dest='refresh', type=float, default=1.0, + help='Time between refreshes of the graph and data in seconds (defaults to 1.0)') + parser.add_argument('--width', dest='width', type=int, default=DEFAULT_WIDTH, + help=f'Width of the graph (defaults to {DEFAULT_WIDTH})') + parser.add_argument('--height', dest='height', type=int, default=DEFAULT_HEIGHT, + help=f'Height of the graph (defaults to {DEFAULT_HEIGHT})') + + def handle(self, *args, **options): + refresh_seconds = options['refresh'] + width = options['width'] + height = options['height'] + + jctl = JobStatusController(width) + + conf = { + 'colors': [chart_color_lookup(p.color) for p in jctl.plots], + 'height': height, + } + + while True: + jctl.tick() + + draw = chart.plot(jctl.series(), conf) + status_line = jctl.generate_status() + clear_screen() + print(draw) + sys.stdout.write(status_line) + time.sleep(refresh_seconds) + diff --git a/awx/main/management/commands/inventory_import.py b/awx/main/management/commands/inventory_import.py index 7bd60e3402ac..a86cc3db4848 100644 --- a/awx/main/management/commands/inventory_import.py +++ b/awx/main/management/commands/inventory_import.py @@ -12,7 +12,6 @@ import time import traceback import shutil -from distutils.version import LooseVersion as Version # Django from django.conf import settings @@ -20,6 +19,9 @@ from django.db import connection, transaction from django.utils.encoding import smart_text +# DRF error class to distinguish license exceptions +from rest_framework.exceptions import PermissionDenied + # AWX inventory imports from awx.main.models.inventory import ( Inventory, @@ -32,14 +34,14 @@ # other AWX imports from awx.main.models.rbac import batch_role_ancestor_rebuilding +# TODO: remove proot utils once we move to running inv. updates in containers from awx.main.utils import ( - ignore_inventory_computed_fields, check_proot_installed, wrap_args_with_proot, build_proot_temp_dir, + ignore_inventory_computed_fields, get_licenser ) -from awx.main.utils.common import _get_ansible_version from awx.main.signals import disable_activity_stream from awx.main.constants import STANDARD_INVENTORY_UPDATE_ENV from awx.main.utils.pglock import advisory_lock @@ -55,11 +57,11 @@ See http://www.ansible.com/renew for license information.''' LICENSE_MESSAGE = '''\ -Number of licensed instances exceeded, would bring available instances to %(new_count)d, system is licensed for %(available_instances)d. +Number of licensed instances exceeded, would bring available instances to %(new_count)d, system is licensed for %(instance_count)d. See http://www.ansible.com/renew for license extension information.''' DEMO_LICENSE_MESSAGE = '''\ -Demo mode free license count exceeded, would bring available instances to %(new_count)d, demo mode allows %(available_instances)d. +Demo mode free license count exceeded, would bring available instances to %(new_count)d, demo mode allows %(instance_count)d. See http://www.ansible.com/renew for licensing information.''' @@ -77,13 +79,11 @@ class AnsibleInventoryLoader(object): /usr/bin/ansible/ansible-inventory -i hosts --list ''' - def __init__(self, source, is_custom=False, venv_path=None, verbosity=0): + def __init__(self, source, venv_path=None, verbosity=0): self.source = source - self.source_dir = functioning_dir(self.source) - self.is_custom = is_custom - self.tmp_private_dir = None - self.method = 'ansible-inventory' self.verbosity = verbosity + # TODO: remove once proot has been removed + self.tmp_private_dir = None if venv_path: self.venv_path = venv_path else: @@ -133,43 +133,34 @@ def get_base_args(self): # NOTE: why do we add "python" to the start of these args? # the script that runs ansible-inventory specifies a python interpreter # that makes no sense in light of the fact that we put all the dependencies - # inside of /venv/ansible, so we override the specified interpreter + # inside of /var/lib/awx/venv/ansible, so we override the specified interpreter # https://github.com/ansible/ansible/issues/50714 bargs = ['python', ansible_inventory_path, '-i', self.source] - ansible_version = _get_ansible_version(ansible_inventory_path[:-len('-inventory')]) - if ansible_version != 'unknown': - this_version = Version(ansible_version) - if this_version >= Version('2.5'): - bargs.extend(['--playbook-dir', self.source_dir]) - if this_version >= Version('2.8'): - if self.verbosity: - # INFO: -vvv, DEBUG: -vvvvv, for inventory, any more than 3 makes little difference - bargs.append('-{}'.format('v' * min(5, self.verbosity * 2 + 1))) + bargs.extend(['--playbook-dir', functioning_dir(self.source)]) + if self.verbosity: + # INFO: -vvv, DEBUG: -vvvvv, for inventory, any more than 3 makes little difference + bargs.append('-{}'.format('v' * min(5, self.verbosity * 2 + 1))) logger.debug('Using base command: {}'.format(' '.join(bargs))) return bargs + # TODO: Remove this once we move to running ansible-inventory in containers + # and don't need proot for process isolation anymore def get_proot_args(self, cmd, env): cwd = os.getcwd() if not check_proot_installed(): raise RuntimeError("proot is not installed but is configured for use") kwargs = {} - if self.is_custom: - # use source's tmp dir for proot, task manager will delete folder - logger.debug("Using provided directory '{}' for isolation.".format(self.source_dir)) - kwargs['proot_temp_dir'] = self.source_dir - cwd = self.source_dir - else: - # we cannot safely store tmp data in source dir or trust script contents - if env['AWX_PRIVATE_DATA_DIR']: - # If this is non-blank, file credentials are being used and we need access - private_data_dir = functioning_dir(env['AWX_PRIVATE_DATA_DIR']) - logger.debug("Using private credential data in '{}'.".format(private_data_dir)) - kwargs['private_data_dir'] = private_data_dir - self.tmp_private_dir = build_proot_temp_dir() - logger.debug("Using fresh temporary directory '{}' for isolation.".format(self.tmp_private_dir)) - kwargs['proot_temp_dir'] = self.tmp_private_dir - kwargs['proot_show_paths'] = [functioning_dir(self.source)] + # we cannot safely store tmp data in source dir or trust script contents + if env['AWX_PRIVATE_DATA_DIR']: + # If this is non-blank, file credentials are being used and we need access + private_data_dir = functioning_dir(env['AWX_PRIVATE_DATA_DIR']) + logger.debug("Using private credential data in '{}'.".format(private_data_dir)) + kwargs['private_data_dir'] = private_data_dir + self.tmp_private_dir = build_proot_temp_dir() + logger.debug("Using fresh temporary directory '{}' for isolation.".format(self.tmp_private_dir)) + kwargs['proot_temp_dir'] = self.tmp_private_dir + kwargs['proot_show_paths'] = [functioning_dir(self.source), settings.AWX_ANSIBLE_COLLECTIONS_PATHS] logger.debug("Running from `{}` working directory.".format(cwd)) if self.venv_path != settings.ANSIBLE_VENV_PATH: @@ -177,12 +168,14 @@ def get_proot_args(self, cmd, env): return wrap_args_with_proot(cmd, cwd, **kwargs) + def command_to_json(self, cmd): data = {} stdout, stderr = '', '' env = self.build_env() - if ((self.is_custom or 'AWX_PRIVATE_DATA_DIR' in env) and + # TODO: remove proot args once inv. updates run in containers + if (('AWX_PRIVATE_DATA_DIR' in env) and getattr(settings, 'AWX_PROOT_ENABLED', False)): cmd = self.get_proot_args(cmd, env) @@ -191,11 +184,13 @@ def command_to_json(self, cmd): stdout = smart_text(stdout) stderr = smart_text(stderr) + # TODO: can be removed when proot is removed if self.tmp_private_dir: shutil.rmtree(self.tmp_private_dir, True) + if proc.returncode != 0: raise RuntimeError('%s failed (rc=%d) with stdout:\n%s\nstderr:\n%s' % ( - self.method, proc.returncode, stdout, stderr)) + 'ansible-inventory', proc.returncode, stdout, stderr)) for line in stderr.splitlines(): logger.error(line) @@ -238,9 +233,9 @@ def add_arguments(self, parser): action='store_true', default=False, help='overwrite (rather than merge) variables') parser.add_argument('--keep-vars', dest='keep_vars', action='store_true', default=False, - help='use database variables if set') + help='DEPRECATED legacy option, has no effect') parser.add_argument('--custom', dest='custom', action='store_true', default=False, - help='this is a custom inventory script') + help='DEPRECATED indicates a custom inventory script, no longer used') parser.add_argument('--source', dest='source', type=str, default=None, metavar='s', help='inventory directory, file, or script to load') parser.add_argument('--enabled-var', dest='enabled_var', type=str, @@ -266,12 +261,12 @@ def add_arguments(self, parser): 'specifies the unique, immutable instance ID, may be ' 'specified as "foo.bar" to traverse nested dicts.') - def set_logging_level(self): + def set_logging_level(self, verbosity): log_levels = dict(enumerate([logging.WARNING, logging.INFO, logging.DEBUG, 0])) - logger.setLevel(log_levels.get(self.verbosity, 0)) + logger.setLevel(log_levels.get(verbosity, 0)) - def _get_instance_id(self, from_dict, default=''): + def _get_instance_id(self, variables, default=''): ''' Retrieve the instance ID from the given dict of host variables. @@ -279,15 +274,23 @@ def _get_instance_id(self, from_dict, default=''): the lookup will traverse into nested dicts, equivalent to: from_dict.get('foo', {}).get('bar', default) + + Multiple ID variables may be specified as 'foo.bar,foobar', so that + it will first try to find 'bar' inside of 'foo', and if unable, + will try to find 'foobar' as a fallback ''' instance_id = default if getattr(self, 'instance_id_var', None): - for key in self.instance_id_var.split('.'): - if not hasattr(from_dict, 'get'): - instance_id = default + for single_instance_id in self.instance_id_var.split(','): + from_dict = variables + for key in single_instance_id.split('.'): + if not hasattr(from_dict, 'get'): + instance_id = default + break + instance_id = from_dict.get(key, default) + from_dict = instance_id + if instance_id: break - instance_id = from_dict.get(key, default) - from_dict = instance_id return smart_text(instance_id) def _get_enabled(self, from_dict, default=None): @@ -321,7 +324,8 @@ def _get_enabled(self, from_dict, default=None): else: raise NotImplementedError('Value of enabled {} not understood.'.format(enabled)) - def get_source_absolute_path(self, source): + @staticmethod + def get_source_absolute_path(source): if not os.path.exists(source): raise IOError('Source does not exist: %s' % source) source = os.path.join(os.getcwd(), os.path.dirname(source), @@ -329,61 +333,6 @@ def get_source_absolute_path(self, source): source = os.path.normpath(os.path.abspath(source)) return source - def load_inventory_from_database(self): - ''' - Load inventory and related objects from the database. - ''' - # Load inventory object based on name or ID. - if self.inventory_id: - q = dict(id=self.inventory_id) - else: - q = dict(name=self.inventory_name) - try: - self.inventory = Inventory.objects.get(**q) - except Inventory.DoesNotExist: - raise CommandError('Inventory with %s = %s cannot be found' % list(q.items())[0]) - except Inventory.MultipleObjectsReturned: - raise CommandError('Inventory with %s = %s returned multiple results' % list(q.items())[0]) - logger.info('Updating inventory %d: %s' % (self.inventory.pk, - self.inventory.name)) - - # Load inventory source if specified via environment variable (when - # inventory_import is called from an InventoryUpdate task). - inventory_source_id = os.getenv('INVENTORY_SOURCE_ID', None) - inventory_update_id = os.getenv('INVENTORY_UPDATE_ID', None) - if inventory_source_id: - try: - self.inventory_source = InventorySource.objects.get(pk=inventory_source_id, - inventory=self.inventory) - except InventorySource.DoesNotExist: - raise CommandError('Inventory source with id=%s not found' % - inventory_source_id) - try: - self.inventory_update = InventoryUpdate.objects.get(pk=inventory_update_id) - except InventoryUpdate.DoesNotExist: - raise CommandError('Inventory update with id=%s not found' % - inventory_update_id) - # Otherwise, create a new inventory source to capture this invocation - # via command line. - else: - with ignore_inventory_computed_fields(): - self.inventory_source, created = InventorySource.objects.get_or_create( - inventory=self.inventory, - source='file', - source_path=os.path.abspath(self.source), - overwrite=self.overwrite, - overwrite_vars=self.overwrite_vars, - ) - self.inventory_update = self.inventory_source.create_inventory_update( - _eager_fields=dict( - job_args=json.dumps(sys.argv), - job_env=dict(os.environ.items()), - job_cwd=os.getcwd()) - ) - - # FIXME: Wait or raise error if inventory is being updated by another - # source. - def _batch_add_m2m(self, related_manager, *objs, **kwargs): key = (related_manager.instance.pk, related_manager.through._meta.db_table) flush = bool(kwargs.get('flush', False)) @@ -422,7 +371,7 @@ def _build_mem_instance_id_map(self): for mem_host in self.all_group.all_hosts.values(): instance_id = self._get_instance_id(mem_host.variables) if not instance_id: - logger.warning('Host "%s" has no "%s" variable', + logger.warning('Host "%s" has no "%s" variable(s)', mem_host.name, self.instance_id_var) continue mem_host.instance_id = instance_id @@ -496,12 +445,6 @@ def _delete_groups(self): group_names = all_group_names[offset:(offset + self._batch_size)] for group_pk in groups_qs.filter(name__in=group_names).values_list('pk', flat=True): del_group_pks.discard(group_pk) - if self.inventory_source.deprecated_group_id in del_group_pks: # TODO: remove in 3.3 - logger.warning( - 'Group "%s" from v1 API is not deleted by overwrite', - self.inventory_source.deprecated_group.name - ) - del_group_pks.discard(self.inventory_source.deprecated_group_id) # Now delete all remaining groups in batches. all_del_pks = sorted(list(del_group_pks)) for offset in range(0, len(all_del_pks), self._batch_size): @@ -534,12 +477,6 @@ def _delete_group_children_and_hosts(self): # Set of all host pks managed by this inventory source all_source_host_pks = self._existing_host_pks() for db_group in db_groups.all(): - if self.inventory_source.deprecated_group_id == db_group.id: # TODO: remove in 3.3 - logger.debug( - 'Group "%s" from v1 API child group/host connections preserved', - db_group.name - ) - continue # Delete child group relationships not present in imported data. db_children = db_group.children db_children_name_pk_map = dict(db_children.values_list('name', 'pk')) @@ -661,11 +598,12 @@ def _create_update_groups(self): if group_name in existing_group_names: continue mem_group = self.all_group.all_groups[group_name] + group_desc = mem_group.variables.pop('_awx_description', 'imported') group = self.inventory.groups.update_or_create( name=group_name, defaults={ 'variables':json.dumps(mem_group.variables), - 'description':'imported' + 'description':group_desc } )[0] logger.debug('Group "%s" added', group.name) @@ -788,8 +726,9 @@ def _create_update_hosts(self): # Create any new hosts. for mem_host_name in sorted(mem_host_names_to_update): mem_host = self.all_group.all_hosts[mem_host_name] - host_attrs = dict(variables=json.dumps(mem_host.variables), - description='imported') + import_vars = mem_host.variables + host_desc = import_vars.pop('_awx_description', 'imported') + host_attrs = dict(variables=json.dumps(import_vars), description=host_desc) enabled = self._get_enabled(mem_host.variables) if enabled is not None: host_attrs['enabled'] = enabled @@ -883,70 +822,68 @@ def load_into_database(self): Load inventory from in-memory groups to the database, overwriting or merging as appropriate. ''' - with advisory_lock('inventory_{}_update'.format(self.inventory.id)): - # FIXME: Attribute changes to superuser? - # Perform __in queries in batches (mainly for unit tests using SQLite). - self._batch_size = 500 - self._build_db_instance_id_map() - self._build_mem_instance_id_map() - if self.overwrite: - self._delete_hosts() - self._delete_groups() - self._delete_group_children_and_hosts() - self._update_inventory() - self._create_update_groups() - self._create_update_hosts() - self._create_update_group_children() - self._create_update_group_hosts() + # FIXME: Attribute changes to superuser? + # Perform __in queries in batches (mainly for unit tests using SQLite). + self._batch_size = 500 + self._build_db_instance_id_map() + self._build_mem_instance_id_map() + if self.overwrite: + self._delete_hosts() + self._delete_groups() + self._delete_group_children_and_hosts() + self._update_inventory() + self._create_update_groups() + self._create_update_hosts() + self._create_update_group_children() + self._create_update_group_hosts() def remote_tower_license_compare(self, local_license_type): # this requires https://github.com/ansible/ansible/pull/52747 source_vars = self.all_group.variables remote_license_type = source_vars.get('tower_metadata', {}).get('license_type', None) if remote_license_type is None: - raise CommandError('Unexpected Error: Tower inventory plugin missing needed metadata!') + raise PermissionDenied('Unexpected Error: Tower inventory plugin missing needed metadata!') if local_license_type != remote_license_type: - raise CommandError('Tower server licenses must match: source: {} local: {}'.format( + raise PermissionDenied('Tower server licenses must match: source: {} local: {}'.format( remote_license_type, local_license_type )) def check_license(self): license_info = get_licenser().validate() local_license_type = license_info.get('license_type', 'UNLICENSED') - if license_info.get('license_key', 'UNLICENSED') == 'UNLICENSED': + if local_license_type == 'UNLICENSED': logger.error(LICENSE_NON_EXISTANT_MESSAGE) - raise CommandError('No license found!') + raise PermissionDenied('No license found!') elif local_license_type == 'open': return - available_instances = license_info.get('available_instances', 0) + instance_count = license_info.get('instance_count', 0) free_instances = license_info.get('free_instances', 0) time_remaining = license_info.get('time_remaining', 0) + hard_error = license_info.get('trial', False) is True or license_info['instance_count'] == 10 new_count = Host.objects.active_count() - if time_remaining <= 0 and not license_info.get('demo', False): - logger.error(LICENSE_EXPIRED_MESSAGE) - if license_info.get('trial', False) is True: - raise CommandError("License has expired!") + if time_remaining <= 0: + if hard_error: + logger.error(LICENSE_EXPIRED_MESSAGE) + raise PermissionDenied("License has expired!") + else: + logger.warning(LICENSE_EXPIRED_MESSAGE) # special check for tower-type inventory sources # but only if running the plugin TOWER_SOURCE_FILES = ['tower.yml', 'tower.yaml'] - if self.inventory_source.source == 'tower' and any(f in self.source for f in TOWER_SOURCE_FILES): + if self.inventory_source.source == 'tower' and any(f in self.inventory_source.source_path for f in TOWER_SOURCE_FILES): # only if this is the 2nd call to license check, we cannot compare before running plugin if hasattr(self, 'all_group'): self.remote_tower_license_compare(local_license_type) if free_instances < 0: d = { 'new_count': new_count, - 'available_instances': available_instances, + 'instance_count': instance_count, } - if license_info.get('demo', False): - logger.error(DEMO_LICENSE_MESSAGE % d) - else: + if hard_error: logger.error(LICENSE_MESSAGE % d) - if ( - license_info.get('trial', False) is True or - license_info['instance_count'] == 10 # basic 10 license - ): - raise CommandError('License count exceeded!') + raise PermissionDenied('License count exceeded!') + else: + logger.warning(LICENSE_MESSAGE % d) def check_org_host_limit(self): license_info = get_licenser().validate() @@ -959,7 +896,7 @@ def check_org_host_limit(self): active_count = Host.objects.org_active_count(org.id) if active_count > org.max_hosts: - raise CommandError('Host limit for organization exceeded!') + raise PermissionDenied('Host limit for organization exceeded!') def mark_license_failure(self, save=True): self.inventory_update.license_error = True @@ -970,16 +907,103 @@ def mark_org_limits_failure(self, save=True): self.inventory_update.save(update_fields=['org_host_limit_error']) def handle(self, *args, **options): - self.verbosity = int(options.get('verbosity', 1)) - self.set_logging_level() - self.inventory_name = options.get('inventory_name', None) - self.inventory_id = options.get('inventory_id', None) - venv_path = options.get('venv', None) + # Load inventory and related objects from database. + inventory_name = options.get('inventory_name', None) + inventory_id = options.get('inventory_id', None) + if inventory_name and inventory_id: + raise CommandError('--inventory-name and --inventory-id are mutually exclusive') + elif not inventory_name and not inventory_id: + raise CommandError('--inventory-name or --inventory-id is required') + + with advisory_lock('inventory_{}_import'.format(inventory_id)): + # Obtain rest of the options needed to run update + raw_source = options.get('source', None) + if not raw_source: + raise CommandError('--source is required') + verbosity = int(options.get('verbosity', 1)) + self.set_logging_level(verbosity) + venv_path = options.get('venv', None) + + # Load inventory object based on name or ID. + if inventory_id: + q = dict(id=inventory_id) + else: + q = dict(name=inventory_name) + try: + inventory = Inventory.objects.get(**q) + except Inventory.DoesNotExist: + raise CommandError('Inventory with %s = %s cannot be found' % list(q.items())[0]) + except Inventory.MultipleObjectsReturned: + raise CommandError('Inventory with %s = %s returned multiple results' % list(q.items())[0]) + logger.info('Updating inventory %d: %s' % (inventory.pk, inventory.name)) + + + # Create ad-hoc inventory source and inventory update objects + with ignore_inventory_computed_fields(): + source = Command.get_source_absolute_path(raw_source) + + inventory_source, created = InventorySource.objects.get_or_create( + inventory=inventory, + source='file', + source_path=os.path.abspath(source), + overwrite=bool(options.get('overwrite', False)), + overwrite_vars=bool(options.get('overwrite_vars', False)), + ) + inventory_update = inventory_source.create_inventory_update( + _eager_fields=dict( + job_args=json.dumps(sys.argv), + job_env=dict(os.environ.items()), + job_cwd=os.getcwd()) + ) + + data = AnsibleInventoryLoader( + source=source, venv_path=venv_path, verbosity=verbosity + ).load() + + logger.debug('Finished loading from source: %s', source) + + status, tb, exc = 'error', '', None + try: + self.perform_update(options, data, inventory_update) + status = 'successful' + except Exception as e: + exc = e + if isinstance(e, KeyboardInterrupt): + status = 'canceled' + else: + tb = traceback.format_exc() + + with ignore_inventory_computed_fields(): + inventory_update = InventoryUpdate.objects.get(pk=inventory_update.pk) + inventory_update.result_traceback = tb + inventory_update.status = status + inventory_update.save(update_fields=['status', 'result_traceback']) + inventory_source.status = status + inventory_source.save(update_fields=['status']) + + if exc: + logger.error(str(exc)) + + if exc: + if isinstance(exc, CommandError): + sys.exit(1) + raise exc + + def perform_update(self, options, data, inventory_update): + """Shared method for both awx-manage CLI updates and inventory updates + from the tasks system. + + This saves the inventory data to the database, calling load_into_database + but also wraps that method in a host of options processing + """ + # outside of normal options, these are needed as part of programatic interface + self.inventory = inventory_update.inventory + self.inventory_source = inventory_update.inventory_source + self.inventory_update = inventory_update + + # the update options, could be parser object or dict self.overwrite = bool(options.get('overwrite', False)) self.overwrite_vars = bool(options.get('overwrite_vars', False)) - self.keep_vars = bool(options.get('keep_vars', False)) - self.is_custom = bool(options.get('custom', False)) - self.source = options.get('source', None) self.enabled_var = options.get('enabled_var', None) self.enabled_value = options.get('enabled_value', None) self.group_filter = options.get('group_filter', None) or r'^.+$' @@ -987,17 +1011,6 @@ def handle(self, *args, **options): self.exclude_empty_groups = bool(options.get('exclude_empty_groups', False)) self.instance_id_var = options.get('instance_id_var', None) - self.invoked_from_dispatcher = False if os.getenv('INVENTORY_SOURCE_ID', None) is None else True - - # Load inventory and related objects from database. - if self.inventory_name and self.inventory_id: - raise CommandError('--inventory-name and --inventory-id are mutually exclusive') - elif not self.inventory_name and not self.inventory_id: - raise CommandError('--inventory-name or --inventory-id is required') - if (self.overwrite or self.overwrite_vars) and self.keep_vars: - raise CommandError('--overwrite/--overwrite-vars and --keep-vars are mutually exclusive') - if not self.source: - raise CommandError('--source is required') try: self.group_filter_re = re.compile(self.group_filter) except re.error: @@ -1007,54 +1020,44 @@ def handle(self, *args, **options): except re.error: raise CommandError('invalid regular expression for --host-filter') - ''' - TODO: Remove this deprecation when we remove support for rax.py - ''' - if self.source == "rax.py": - logger.info("Rackspace inventory sync is Deprecated in Tower 3.1.0 and support for Rackspace will be removed in a future release.") - begin = time.time() - self.load_inventory_from_database() - try: - self.check_license() - except CommandError as e: - self.mark_license_failure(save=True) - raise e + # Since perform_update can be invoked either through the awx-manage CLI + # or from the task system, we need to create a new lock at this level + # (even though inventory_import.Command.handle -- which calls + # perform_update -- has its own lock, inventory_ID_import) + with advisory_lock('inventory_{}_perform_update'.format(self.inventory.id)): - try: - # Check the per-org host limits - self.check_org_host_limit() - except CommandError as e: - self.mark_org_limits_failure(save=True) - raise e + try: + self.check_license() + except PermissionDenied as e: + self.mark_license_failure(save=True) + raise e + + try: + # Check the per-org host limits + self.check_org_host_limit() + except PermissionDenied as e: + self.mark_org_limits_failure(save=True) + raise e - status, tb, exc = 'error', '', None - try: if settings.SQL_DEBUG: queries_before = len(connection.queries) # Update inventory update for this command line invocation. with ignore_inventory_computed_fields(): + # TODO: move this to before perform_update iu = self.inventory_update if iu.status != 'running': with transaction.atomic(): self.inventory_update.status = 'running' self.inventory_update.save() - source = self.get_source_absolute_path(self.source) - - data = AnsibleInventoryLoader(source=source, is_custom=self.is_custom, - venv_path=venv_path, verbosity=self.verbosity).load() - - logger.debug('Finished loading from source: %s', source) logger.info('Processing JSON output...') inventory = MemInventory( group_filter_re=self.group_filter_re, host_filter_re=self.host_filter_re) inventory = dict_to_mem_data(data, inventory=inventory) - del data # forget dict from import, could be large - logger.info('Loaded %d groups, %d hosts', len(inventory.all_group.all_groups), len(inventory.all_group.all_hosts)) @@ -1094,10 +1097,11 @@ def handle(self, *args, **options): if settings.SQL_DEBUG: queries_before2 = len(connection.queries) self.inventory.update_computed_fields() - if settings.SQL_DEBUG: - logger.warning('update computed fields took %d queries', - len(connection.queries) - queries_before2) - # Check if the license is valid. + if settings.SQL_DEBUG: + logger.warning('update computed fields took %d queries', + len(connection.queries) - queries_before2) + + # Check if the license is valid. # If the license is not valid, a CommandError will be thrown, # and inventory update will be marked as invalid. # with transaction.atomic() will roll back the changes. @@ -1107,11 +1111,11 @@ def handle(self, *args, **options): # Check the per-org host limits license_fail = False self.check_org_host_limit() - except CommandError as e: + except PermissionDenied as e: if license_fail: - self.mark_license_failure() + self.mark_license_failure(save=True) else: - self.mark_org_limits_failure() + self.mark_org_limits_failure(save=True) raise e if settings.SQL_DEBUG: @@ -1120,7 +1124,6 @@ def handle(self, *args, **options): else: logger.info('Inventory import completed for %s in %0.1fs', self.inventory_source.name, time.time() - begin) - status = 'successful' # If we're in debug mode, then log the queries and time # used to do the operation. @@ -1130,29 +1133,3 @@ def handle(self, *args, **options): logger.warning('Inventory import required %d queries ' 'taking %0.3fs', len(queries_this_import), sqltime) - except Exception as e: - if isinstance(e, KeyboardInterrupt): - status = 'canceled' - exc = e - elif isinstance(e, CommandError): - exc = e - else: - tb = traceback.format_exc() - exc = e - - if not self.invoked_from_dispatcher: - with ignore_inventory_computed_fields(): - self.inventory_update = InventoryUpdate.objects.get(pk=self.inventory_update.pk) - self.inventory_update.result_traceback = tb - self.inventory_update.status = status - self.inventory_update.save(update_fields=['status', 'result_traceback']) - self.inventory_source.status = status - self.inventory_source.save(update_fields=['status']) - - if exc: - logger.error(str(exc)) - - if exc: - if isinstance(exc, CommandError): - sys.exit(1) - raise exc diff --git a/awx/main/management/commands/profile_sql.py b/awx/main/management/commands/profile_sql.py index bbcf10dd27b0..585fb3d7061d 100644 --- a/awx/main/management/commands/profile_sql.py +++ b/awx/main/management/commands/profile_sql.py @@ -19,3 +19,9 @@ def handle(self, **options): profile_sql.delay( threshold=options['threshold'], minutes=options['minutes'] ) + if options['threshold'] > 0: + print(f"SQL profiling initiated with a threshold of {options['threshold']} second(s) and a" + f" duration of {options['minutes']} minute(s), any queries that meet criteria can" + f" be found in /var/log/tower/profile/.") + else: + print("SQL profiling disabled.") diff --git a/awx/main/management/commands/provision_instance.py b/awx/main/management/commands/provision_instance.py index b0b4474622f3..4d7655821ac0 100644 --- a/awx/main/management/commands/provision_instance.py +++ b/awx/main/management/commands/provision_instance.py @@ -13,7 +13,7 @@ class Command(BaseCommand): """ Internal tower command. - Regsiter this instance with the database for HA tracking. + Register this instance with the database for HA tracking. """ help = ( diff --git a/awx/main/management/commands/regenerate_secret_key.py b/awx/main/management/commands/regenerate_secret_key.py index 2e3d1a127d54..61a2c46b4cc8 100644 --- a/awx/main/management/commands/regenerate_secret_key.py +++ b/awx/main/management/commands/regenerate_secret_key.py @@ -82,7 +82,7 @@ def _oauth2_app_secrets(self): OAuth2Application.objects.filter(pk=app.pk).update(client_secret=encrypted) def _settings(self): - # don't update memcached, the *actual* value isn't changing + # don't update the cache, the *actual* value isn't changing post_save.disconnect(on_post_save_setting, sender=Setting) for setting in Setting.objects.filter().order_by('pk'): if settings_registry.is_setting_encrypted(setting.key): diff --git a/awx/main/management/commands/register_queue.py b/awx/main/management/commands/register_queue.py index af462dde364b..edd8068b892c 100644 --- a/awx/main/management/commands/register_queue.py +++ b/awx/main/management/commands/register_queue.py @@ -6,6 +6,7 @@ from awx.main.models import Instance, InstanceGroup from django.core.management.base import BaseCommand, CommandError +from django.db import transaction class InstanceNotFound(Exception): @@ -15,32 +16,24 @@ def __init__(self, message, changed, *args, **kwargs): super(InstanceNotFound, self).__init__(*args, **kwargs) -class Command(BaseCommand): - - def add_arguments(self, parser): - parser.add_argument('--queuename', dest='queuename', type=str, - help='Queue to create/update') - parser.add_argument('--hostnames', dest='hostnames', type=str, - help='Comma-Delimited Hosts to add to the Queue (will not remove already assigned instances)') - parser.add_argument('--controller', dest='controller', type=str, - default='', help='The controlling group (makes this an isolated group)') - parser.add_argument('--instance_percent', dest='instance_percent', type=int, default=0, - help='The percentage of active instances that will be assigned to this group'), - parser.add_argument('--instance_minimum', dest='instance_minimum', type=int, default=0, - help='The minimum number of instance that will be retained for this group from available instances') +class RegisterQueue: + def __init__(self, queuename, controller, instance_percent, inst_min, hostname_list): + self.instance_not_found_err = None + self.queuename = queuename + self.controller = controller + self.instance_percent = instance_percent + self.instance_min = inst_min + self.hostname_list = hostname_list - - def get_create_update_instance_group(self, queuename, instance_percent, instance_min): - ig = InstanceGroup.objects.filter(name=queuename) + def get_create_update_instance_group(self): created = False changed = False - - (ig, created) = InstanceGroup.objects.get_or_create(name=queuename) - if ig.policy_instance_percentage != instance_percent: - ig.policy_instance_percentage = instance_percent + (ig, created) = InstanceGroup.objects.get_or_create(name=self.queuename) + if ig.policy_instance_percentage != self.instance_percent: + ig.policy_instance_percentage = self.instance_percent changed = True - if ig.policy_instance_minimum != instance_min: - ig.policy_instance_minimum = instance_min + if ig.policy_instance_minimum != self.instance_min: + ig.policy_instance_minimum = self.instance_min changed = True if changed: @@ -48,12 +41,12 @@ def get_create_update_instance_group(self, queuename, instance_percent, instance return (ig, created, changed) - def update_instance_group_controller(self, ig, controller): + def update_instance_group_controller(self, ig): changed = False control_ig = None - if controller: - control_ig = InstanceGroup.objects.filter(name=controller).first() + if self.controller: + control_ig = InstanceGroup.objects.filter(name=self.controller).first() if control_ig and ig.controller_id != control_ig.pk: ig.controller = control_ig @@ -62,10 +55,10 @@ def update_instance_group_controller(self, ig, controller): return (control_ig, changed) - def add_instances_to_group(self, ig, hostname_list): + def add_instances_to_group(self, ig): changed = False - instance_list_unique = set([x.strip() for x in hostname_list if x]) + instance_list_unique = set([x.strip() for x in self.hostname_list if x]) instances = [] for inst_name in instance_list_unique: instance = Instance.objects.filter(hostname=inst_name) @@ -86,42 +79,61 @@ def add_instances_to_group(self, ig, hostname_list): return (instances, changed) + def register(self): + with advisory_lock('cluster_policy_lock'): + with transaction.atomic(): + changed2 = False + changed3 = False + (ig, created, changed1) = self.get_create_update_instance_group() + if created: + print("Creating instance group {}".format(ig.name)) + elif not created: + print("Instance Group already registered {}".format(ig.name)) + + if self.controller: + (ig_ctrl, changed2) = self.update_instance_group_controller(ig) + if changed2: + print("Set controller group {} on {}.".format(self.controller, self.queuename)) + + try: + (instances, changed3) = self.add_instances_to_group(ig) + for i in instances: + print("Added instance {} to {}".format(i.hostname, ig.name)) + except InstanceNotFound as e: + self.instance_not_found_err = e + + if any([changed1, changed2, changed3]): + print('(changed: True)') + + +class Command(BaseCommand): + + def add_arguments(self, parser): + parser.add_argument('--queuename', dest='queuename', type=str, + help='Queue to create/update') + parser.add_argument('--hostnames', dest='hostnames', type=str, + help='Comma-Delimited Hosts to add to the Queue (will not remove already assigned instances)') + parser.add_argument('--controller', dest='controller', type=str, + default='', help='The controlling group (makes this an isolated group)') + parser.add_argument('--instance_percent', dest='instance_percent', type=int, default=0, + help='The percentage of active instances that will be assigned to this group'), + parser.add_argument('--instance_minimum', dest='instance_minimum', type=int, default=0, + help='The minimum number of instance that will be retained for this group from available instances') + + def handle(self, **options): - instance_not_found_err = None queuename = options.get('queuename') if not queuename: raise CommandError("Specify `--queuename` to use this command.") ctrl = options.get('controller') inst_per = options.get('instance_percent') - inst_min = options.get('instance_minimum') + instance_min = options.get('instance_minimum') hostname_list = [] if options.get('hostnames'): hostname_list = options.get('hostnames').split(",") - with advisory_lock('instance_group_registration_{}'.format(queuename)): - changed2 = False - changed3 = False - (ig, created, changed1) = self.get_create_update_instance_group(queuename, inst_per, inst_min) - if created: - print("Creating instance group {}".format(ig.name)) - elif not created: - print("Instance Group already registered {}".format(ig.name)) - - if ctrl: - (ig_ctrl, changed2) = self.update_instance_group_controller(ig, ctrl) - if changed2: - print("Set controller group {} on {}.".format(ctrl, queuename)) - - try: - (instances, changed3) = self.add_instances_to_group(ig, hostname_list) - for i in instances: - print("Added instance {} to {}".format(i.hostname, ig.name)) - except InstanceNotFound as e: - instance_not_found_err = e - - if any([changed1, changed2, changed3]): - print('(changed: True)') - - if instance_not_found_err: - print(instance_not_found_err.message) + rq = RegisterQueue(queuename, ctrl, inst_per, instance_min, hostname_list) + rq.register() + if rq.instance_not_found_err: + print(rq.instance_not_found_err.message) sys.exit(1) diff --git a/awx/main/management/commands/remove_from_queue.py b/awx/main/management/commands/remove_from_queue.py index df7530992c89..b24974921976 100644 --- a/awx/main/management/commands/remove_from_queue.py +++ b/awx/main/management/commands/remove_from_queue.py @@ -32,4 +32,7 @@ def handle(self, *arg, **options): sys.exit(1) i = i.first() ig.instances.remove(i) + if i.hostname in ig.policy_instance_list: + ig.policy_instance_list.remove(i.hostname) + ig.save() print("Instance removed from instance group") diff --git a/awx/main/management/commands/run_callback_receiver.py b/awx/main/management/commands/run_callback_receiver.py index 51608a8b7a8d..23922a753763 100644 --- a/awx/main/management/commands/run_callback_receiver.py +++ b/awx/main/management/commands/run_callback_receiver.py @@ -3,10 +3,9 @@ from django.conf import settings from django.core.management.base import BaseCommand -from kombu import Exchange, Queue -from awx.main.dispatch.kombu import Connection -from awx.main.dispatch.worker import AWXConsumer, CallbackBrokerWorker +from awx.main.dispatch.control import Control +from awx.main.dispatch.worker import AWXConsumerRedis, CallbackBrokerWorker class Command(BaseCommand): @@ -17,24 +16,23 @@ class Command(BaseCommand): ''' help = 'Launch the job callback receiver' + def add_arguments(self, parser): + parser.add_argument('--status', dest='status', action='store_true', + help='print the internal state of any running dispatchers') + def handle(self, *arg, **options): - with Connection(settings.BROKER_URL) as conn: - consumer = None - try: - consumer = AWXConsumer( - 'callback_receiver', - conn, - CallbackBrokerWorker(), - [ - Queue( - settings.CALLBACK_QUEUE, - Exchange(settings.CALLBACK_QUEUE, type='direct'), - routing_key=settings.CALLBACK_QUEUE - ) - ] - ) - consumer.run() - except KeyboardInterrupt: - print('Terminating Callback Receiver') - if consumer: - consumer.stop() + if options.get('status'): + print(Control('callback_receiver').status()) + return + consumer = None + try: + consumer = AWXConsumerRedis( + 'callback_receiver', + CallbackBrokerWorker(), + queues=[getattr(settings, 'CALLBACK_QUEUE', '')], + ) + consumer.run() + except KeyboardInterrupt: + print('Terminating Callback Receiver') + if consumer: + consumer.stop() diff --git a/awx/main/management/commands/run_dispatcher.py b/awx/main/management/commands/run_dispatcher.py index b57034b9f8ce..fb8c1b4a6b1f 100644 --- a/awx/main/management/commands/run_dispatcher.py +++ b/awx/main/management/commands/run_dispatcher.py @@ -1,21 +1,17 @@ # Copyright (c) 2015 Ansible, Inc. # All Rights Reserved. -import os import logging -from multiprocessing import Process from django.conf import settings from django.core.cache import cache as django_cache from django.core.management.base import BaseCommand -from django.db import connection as django_connection, connections -from kombu import Exchange, Queue +from django.db import connection as django_connection -from awx.main.utils.handlers import AWXProxyHandler from awx.main.dispatch import get_local_queuename, reaper from awx.main.dispatch.control import Control -from awx.main.dispatch.kombu import Connection from awx.main.dispatch.pool import AutoscalePool -from awx.main.dispatch.worker import AWXConsumer, TaskWorker +from awx.main.dispatch.worker import AWXConsumerPG, TaskWorker +from awx.main.dispatch import periodic logger = logging.getLogger('awx.main.dispatch') @@ -36,71 +32,6 @@ def add_arguments(self, parser): help=('cause the dispatcher to recycle all of its worker processes;' 'running jobs will run to completion first')) - def beat(self): - from celery import Celery - from celery.beat import PersistentScheduler - from celery.apps import beat - - class AWXScheduler(PersistentScheduler): - - def __init__(self, *args, **kwargs): - self.ppid = os.getppid() - super(AWXScheduler, self).__init__(*args, **kwargs) - - def setup_schedule(self): - super(AWXScheduler, self).setup_schedule() - self.update_from_dict(settings.CELERYBEAT_SCHEDULE) - - def tick(self, *args, **kwargs): - if os.getppid() != self.ppid: - # if the parent PID changes, this process has been orphaned - # via e.g., segfault or sigkill, we should exit too - raise SystemExit() - return super(AWXScheduler, self).tick(*args, **kwargs) - - def apply_async(self, entry, producer=None, advance=True, **kwargs): - for conn in connections.all(): - # If the database connection has a hiccup, re-establish a new - # connection - conn.close_if_unusable_or_obsolete() - task = TaskWorker.resolve_callable(entry.task) - result, queue = task.apply_async() - - class TaskResult(object): - id = result['uuid'] - - return TaskResult() - - sched_file = '/var/lib/awx/beat.db' - app = Celery() - app.conf.BROKER_URL = settings.BROKER_URL - app.conf.CELERY_TASK_RESULT_EXPIRES = False - - # celery in py3 seems to have a bug where the celerybeat schedule - # shelve can become corrupted; we've _only_ seen this in Ubuntu and py36 - # it can be avoided by detecting and removing the corrupted file - # at some point, we'll just stop using celerybeat, because it's clearly - # buggy, too -_- - # - # https://github.com/celery/celery/issues/4777 - sched = AWXScheduler(schedule_filename=sched_file, app=app) - try: - sched.setup_schedule() - except Exception: - logger.exception('{} is corrupted, removing.'.format(sched_file)) - sched._remove_db() - finally: - try: - sched.close() - except Exception: - logger.exception('{} failed to sync/close'.format(sched_file)) - - beat.Beat( - 30, - app, - schedule=sched_file, scheduler_cls=AWXScheduler - ).run() - def handle(self, *arg, **options): if options.get('status'): print(Control('dispatcher').status()) @@ -113,45 +44,27 @@ def handle(self, *arg, **options): # It's important to close these because we're _about_ to fork, and we # don't want the forked processes to inherit the open sockets - # for the DB and memcached connections (that way lies race conditions) + # for the DB and cache connections (that way lies race conditions) django_connection.close() django_cache.close() - beat = Process(target=self.beat) - beat.daemon = True - beat.start() + + # spawn a daemon thread to periodically enqueues scheduled tasks + # (like the node heartbeat) + periodic.run_continuously() reaper.reap() consumer = None - # don't ship external logs inside the dispatcher's parent process - # this exists to work around a race condition + deadlock bug on fork - # in cpython itself: - # https://bugs.python.org/issue37429 - AWXProxyHandler.disable() - with Connection(settings.BROKER_URL) as conn: - try: - bcast = 'tower_broadcast_all' - queues = [ - Queue(q, Exchange(q), routing_key=q) - for q in (settings.AWX_CELERY_QUEUES_STATIC + [get_local_queuename()]) - ] - queues.append( - Queue( - construct_bcast_queue_name(bcast), - exchange=Exchange(bcast, type='fanout'), - routing_key=bcast, - reply=True - ) - ) - consumer = AWXConsumer( - 'dispatcher', - conn, - TaskWorker(), - queues, - AutoscalePool(min_workers=4) - ) - consumer.run() - except KeyboardInterrupt: - logger.debug('Terminating Task Dispatcher') - if consumer: - consumer.stop() + try: + queues = ['tower_broadcast_all', get_local_queuename()] + consumer = AWXConsumerPG( + 'dispatcher', + TaskWorker(), + queues, + AutoscalePool(min_workers=4) + ) + consumer.run() + except KeyboardInterrupt: + logger.debug('Terminating Task Dispatcher') + if consumer: + consumer.stop() diff --git a/awx/main/management/commands/run_wsbroadcast.py b/awx/main/management/commands/run_wsbroadcast.py new file mode 100644 index 000000000000..5801f4e5d035 --- /dev/null +++ b/awx/main/management/commands/run_wsbroadcast.py @@ -0,0 +1,167 @@ +# Copyright (c) 2015 Ansible, Inc. +# All Rights Reserved. +import logging +import asyncio +import datetime +import re +import redis +import time +from datetime import datetime as dt + +from django.core.management.base import BaseCommand +from django.db import connection +from django.db.models import Q +from django.db.migrations.executor import MigrationExecutor + +from awx.main.analytics.broadcast_websocket import ( + BroadcastWebsocketStatsManager, + safe_name, +) +from awx.main.wsbroadcast import BroadcastWebsocketManager + + +logger = logging.getLogger('awx.main.wsbroadcast') + + +class Command(BaseCommand): + help = 'Launch the websocket broadcaster' + + def add_arguments(self, parser): + parser.add_argument('--status', dest='status', action='store_true', + help='print the internal state of any running broadcast websocket') + + @classmethod + def display_len(cls, s): + return len(re.sub('\x1b.*?m', '', s)) + + @classmethod + def _format_lines(cls, host_stats, padding=5): + widths = [0 for i in host_stats[0]] + for entry in host_stats: + for i, e in enumerate(entry): + if Command.display_len(e) > widths[i]: + widths[i] = Command.display_len(e) + paddings = [padding for i in widths] + + lines = [] + for entry in host_stats: + line = "" + for pad, width, value in zip(paddings, widths, entry): + if len(value) > Command.display_len(value): + width += len(value) - Command.display_len(value) + total_width = width + pad + line += f'{value:{total_width}}' + lines.append(line) + return lines + + @classmethod + def get_connection_status(cls, me, hostnames, data): + host_stats = [('hostname', 'state', 'start time', 'duration (sec)')] + for h in hostnames: + connection_color = '91' # red + h_safe = safe_name(h) + prefix = f'awx_{h_safe}' + connection_state = data.get(f'{prefix}_connection', 'N/A') + connection_started = 'N/A' + connection_duration = 'N/A' + if connection_state is None: + connection_state = 'unknown' + if connection_state == 'connected': + connection_color = '92' # green + connection_started = data.get(f'{prefix}_connection_start', 'Error') + if connection_started != 'Error': + connection_started = datetime.datetime.fromtimestamp(connection_started) + connection_duration = int((dt.now() - connection_started).total_seconds()) + + connection_state = f'\033[{connection_color}m{connection_state}\033[0m' + + host_stats.append((h, connection_state, str(connection_started), str(connection_duration))) + + return host_stats + + @classmethod + def get_connection_stats(cls, me, hostnames, data): + host_stats = [('hostname', 'total', 'per minute')] + for h in hostnames: + h_safe = safe_name(h) + prefix = f'awx_{h_safe}' + messages_total = data.get(f'{prefix}_messages_received', '0') + messages_per_minute = data.get(f'{prefix}_messages_received_per_minute', '0') + + host_stats.append((h, str(int(messages_total)), str(int(messages_per_minute)))) + + return host_stats + + def handle(self, *arg, **options): + # it's necessary to delay this import in case + # database migrations are still running + from awx.main.models.ha import Instance + + executor = MigrationExecutor(connection) + migrating = bool(executor.migration_plan(executor.loader.graph.leaf_nodes())) + registered = False + + if not migrating: + try: + Instance.objects.me() + registered = True + except RuntimeError: + pass + + if migrating or not registered: + # In containerized deployments, migrations happen in the task container, + # and the services running there don't start until migrations are + # finished. + # *This* service runs in the web container, and it's possible that it can + # start _before_ migrations are finished, thus causing issues with the ORM + # queries it makes (specifically, conf.settings queries). + # This block is meant to serve as a sort of bail-out for the situation + # where migrations aren't yet finished (similar to the migration + # detection middleware that the uwsgi processes have) or when instance + # registration isn't done yet + logger.error('AWX is currently installing/upgrading. Trying again in 5s...') + time.sleep(5) + return + + if options.get('status'): + try: + stats_all = BroadcastWebsocketStatsManager.get_stats_sync() + except redis.exceptions.ConnectionError as e: + print(f"Unable to get Broadcast Websocket Status. Failed to connect to redis {e}") + return + + data = {} + for family in stats_all: + if family.type == 'gauge' and len(family.samples) > 1: + for sample in family.samples: + if sample.value >= 1: + data[family.name] = sample.labels[family.name] + break + else: + data[family.name] = family.samples[0].value + + me = Instance.objects.me() + hostnames = [i.hostname for i in Instance.objects.exclude(Q(hostname=me.hostname) | Q(rampart_groups__controller__isnull=False))] + + host_stats = Command.get_connection_status(me, hostnames, data) + lines = Command._format_lines(host_stats) + + print(f'Broadcast websocket connection status from "{me.hostname}" to:') + print('\n'.join(lines)) + + host_stats = Command.get_connection_stats(me, hostnames, data) + lines = Command._format_lines(host_stats) + + print(f'\nBroadcast websocket connection stats from "{me.hostname}" to:') + print('\n'.join(lines)) + + return + + try: + broadcast_websocket_mgr = BroadcastWebsocketManager() + task = broadcast_websocket_mgr.start() + + loop = asyncio.get_event_loop() + loop.run_until_complete(task) + except KeyboardInterrupt: + logger.debug('Terminating Websocket Broadcaster') diff --git a/awx/main/managers.py b/awx/main/managers.py index a2af79af8c58..ae93a552a021 100644 --- a/awx/main/managers.py +++ b/awx/main/managers.py @@ -3,6 +3,7 @@ import sys import logging +import os from django.db import models from django.conf import settings @@ -43,25 +44,17 @@ def org_active_count(self, org_id): inventory_sources__source='tower' ).filter(inventory__organization=org_id).values('name').distinct().count() - def active_counts_by_org(self): - """Return the counts of active, unique hosts for each organization. - Construction of query involves: - - remove any ordering specified in model's Meta - - Exclude hosts sourced from another Tower - - Consider only hosts where the canonical inventory is owned by each organization - - Restrict the query to only count distinct names - - Return the counts - """ - return self.order_by().exclude( - inventory_sources__source='tower' - ).values('inventory__organization').annotate( - inventory__organization__count=models.Count('name', distinct=True)) - def get_queryset(self): """When the parent instance of the host query set has a `kind=smart` and a `host_filter` set. Use the `host_filter` to generate the queryset for the hosts. """ - qs = super(HostManager, self).get_queryset() + qs = super(HostManager, self).get_queryset().defer( + 'last_job__extra_vars', + 'last_job_host_summary__job__extra_vars', + 'last_job__artifacts', + 'last_job_host_summary__job__artifacts', + ) + if (hasattr(self, 'instance') and hasattr(self.instance, 'host_filter') and hasattr(self.instance, 'kind')): @@ -78,8 +71,7 @@ def get_queryset(self): self.core_filters = {} qs = qs & q - unique_by_name = qs.order_by('name', 'pk').distinct('name') - return qs.filter(pk__in=unique_by_name) + return qs.order_by('name', 'pk').distinct('name') return qs @@ -115,21 +107,45 @@ def me(self): return node[0] raise RuntimeError("No instance found with the current cluster host id") - def register(self, uuid=None, hostname=None): + def register(self, uuid=None, hostname=None, ip_address=None): if not uuid: uuid = settings.SYSTEM_UUID if not hostname: hostname = settings.CLUSTER_HOST_ID with advisory_lock('instance_registration_%s' % hostname): + if settings.AWX_AUTO_DEPROVISION_INSTANCES: + # detect any instances with the same IP address. + # if one exists, set it to None + inst_conflicting_ip = self.filter(ip_address=ip_address).exclude(hostname=hostname) + if inst_conflicting_ip.exists(): + for other_inst in inst_conflicting_ip: + other_hostname = other_inst.hostname + other_inst.ip_address = None + other_inst.save(update_fields=['ip_address']) + logger.warning("IP address {0} conflict detected, ip address unset for host {1}.".format(ip_address, other_hostname)) + instance = self.filter(hostname=hostname) if instance.exists(): - return (False, instance[0]) - instance = self.create(uuid=uuid, hostname=hostname, capacity=0) + instance = instance.get() + if instance.ip_address != ip_address: + instance.ip_address = ip_address + instance.save(update_fields=['ip_address']) + return (True, instance) + else: + return (False, instance) + instance = self.create(uuid=uuid, + hostname=hostname, + ip_address=ip_address, + capacity=0) return (True, instance) def get_or_register(self): if settings.AWX_AUTO_DEPROVISION_INSTANCES: - return self.register() + from awx.main.management.commands.register_queue import RegisterQueue + pod_ip = os.environ.get('MY_POD_IP') + registered = self.register(ip_address=pod_ip) + RegisterQueue('tower', None, 100, 0, []).register() + return registered else: return (False, self.me()) diff --git a/awx/main/middleware.py b/awx/main/middleware.py index d79eb06af963..8bfd27381102 100644 --- a/awx/main/middleware.py +++ b/awx/main/middleware.py @@ -1,34 +1,27 @@ # Copyright (c) 2015 Ansible, Inc. # All Rights Reserved. -import uuid import logging import threading import time -import cProfile -import pstats -import os import urllib.parse from django.conf import settings from django.contrib.auth.models import User -from django.db.models.signals import post_save from django.db.migrations.executor import MigrationExecutor -from django.db import IntegrityError, connection -from django.utils.functional import curry -from django.shortcuts import get_object_or_404, redirect +from django.db import connection +from django.shortcuts import redirect from django.apps import apps from django.utils.deprecation import MiddlewareMixin from django.utils.translation import ugettext_lazy as _ from django.urls import reverse, resolve -from awx.main.models import ActivityStream from awx.main.utils.named_url_graph import generate_graph, GraphNode from awx.conf import fields, register +from awx.main.utils.profiling import AWXProfiler logger = logging.getLogger('awx.main.middleware') -analytics_logger = logging.getLogger('awx.analytics.activity_stream') perf_logger = logging.getLogger('awx.analytics.performance') @@ -36,11 +29,14 @@ class TimingMiddleware(threading.local, MiddlewareMixin): dest = '/var/log/tower/profile' + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.prof = AWXProfiler("TimingMiddleware") + def process_request(self, request): self.start_time = time.time() if settings.AWX_REQUEST_PROFILE: - self.prof = cProfile.Profile() - self.prof.enable() + self.prof.start() def process_response(self, request, response): if not hasattr(self, 'start_time'): # some tools may not invoke process_request @@ -48,88 +44,10 @@ def process_response(self, request, response): total_time = time.time() - self.start_time response['X-API-Total-Time'] = '%0.3fs' % total_time if settings.AWX_REQUEST_PROFILE: - self.prof.disable() - cprofile_file = self.save_profile_file(request) - response['cprofile_file'] = cprofile_file + response['X-API-Profile-File'] = self.prof.stop() perf_logger.info('api response times', extra=dict(python_objects=dict(request=request, response=response))) return response - def save_profile_file(self, request): - if not os.path.isdir(self.dest): - os.makedirs(self.dest) - filename = '%.3fs-%s.pstats' % (pstats.Stats(self.prof).total_tt, uuid.uuid4()) - filepath = os.path.join(self.dest, filename) - with open(filepath, 'w') as f: - f.write('%s %s\n' % (request.method, request.get_full_path())) - pstats.Stats(self.prof, stream=f).sort_stats('cumulative').print_stats() - - if settings.AWX_REQUEST_PROFILE_WITH_DOT: - from gprof2dot import main as generate_dot - raw = os.path.join(self.dest, filename) + '.raw' - pstats.Stats(self.prof).dump_stats(raw) - generate_dot([ - '-n', '2.5', '-f', 'pstats', '-o', - os.path.join( self.dest, filename).replace('.pstats', '.dot'), - raw - ]) - os.remove(raw) - return filepath - - -class ActivityStreamMiddleware(threading.local, MiddlewareMixin): - - def __init__(self, get_response=None): - self.disp_uid = None - self.instance_ids = [] - super().__init__(get_response) - - def process_request(self, request): - if hasattr(request, 'user') and request.user.is_authenticated: - user = request.user - else: - user = None - - set_actor = curry(self.set_actor, user) - self.disp_uid = str(uuid.uuid1()) - self.instance_ids = [] - post_save.connect(set_actor, sender=ActivityStream, dispatch_uid=self.disp_uid, weak=False) - - def process_response(self, request, response): - drf_request = getattr(request, 'drf_request', None) - drf_user = getattr(drf_request, 'user', None) - if self.disp_uid is not None: - post_save.disconnect(dispatch_uid=self.disp_uid) - - for instance in ActivityStream.objects.filter(id__in=self.instance_ids): - if drf_user and drf_user.id: - instance.actor = drf_user - try: - instance.save(update_fields=['actor']) - analytics_logger.info('Activity Stream update entry for %s' % str(instance.object1), - extra=dict(changes=instance.changes, relationship=instance.object_relationship_type, - actor=drf_user.username, operation=instance.operation, - object1=instance.object1, object2=instance.object2)) - except IntegrityError: - logger.debug("Integrity Error saving Activity Stream instance for id : " + str(instance.id)) - # else: - # obj1_type_actual = instance.object1_type.split(".")[-1] - # if obj1_type_actual in ("InventoryUpdate", "ProjectUpdate", "Job") and instance.id is not None: - # instance.delete() - - self.instance_ids = [] - return response - - def set_actor(self, user, sender, instance, **kwargs): - if sender == ActivityStream: - if isinstance(user, User) and instance.actor is None: - user = User.objects.filter(id=user.id) - if user.exists(): - user = user[0] - instance.actor = user - else: - if instance.id not in self.instance_ids: - self.instance_ids.append(instance.id) - class SessionTimeoutMiddleware(MiddlewareMixin): """ @@ -192,21 +110,55 @@ def __init__(self, get_response=None): ) super().__init__(get_response) - def _named_url_to_pk(self, node, named_url): + @staticmethod + def _hijack_for_old_jt_name(node, kwargs, named_url): + try: + int(named_url) + return False + except ValueError: + pass + JobTemplate = node.model + name = urllib.parse.unquote(named_url) + return JobTemplate.objects.filter(name=name).order_by('organization__created').first() + + @classmethod + def _named_url_to_pk(cls, node, resource, named_url): kwargs = {} - if not node.populate_named_url_query_kwargs(kwargs, named_url): - return named_url - return str(get_object_or_404(node.model, **kwargs).pk) - - def _convert_named_url(self, url_path): + if node.populate_named_url_query_kwargs(kwargs, named_url): + match = node.model.objects.filter(**kwargs).first() + if match: + return str(match.pk) + else: + # if the name does *not* resolve to any actual resource, + # we should still attempt to route it through so that 401s are + # respected + # using "zero" here will cause the URL regex to match e.g., + # /api/v2/users//, but it also means that anonymous + # users will go down the path of having their credentials + # verified; in this way, *anonymous* users will that visit + # /api/v2/users/invalid-username/ *won't* see a 404, they'll + # see a 401 as if they'd gone to /api/v2/users/0/ + # + return '0' + if resource == 'job_templates' and '++' not in named_url: + # special case for deprecated job template case + # will not raise a 404 on its own + jt = cls._hijack_for_old_jt_name(node, kwargs, named_url) + if jt: + return str(jt.pk) + return named_url + + @classmethod + def _convert_named_url(cls, url_path): url_units = url_path.split('/') # If the identifier is an empty string, it is always invalid. if len(url_units) < 6 or url_units[1] != 'api' or url_units[2] not in ['v2'] or not url_units[4]: return url_path resource = url_units[3] if resource in settings.NAMED_URL_MAPPINGS: - url_units[4] = self._named_url_to_pk(settings.NAMED_URL_GRAPH[settings.NAMED_URL_MAPPINGS[resource]], - url_units[4]) + url_units[4] = cls._named_url_to_pk( + settings.NAMED_URL_GRAPH[settings.NAMED_URL_MAPPINGS[resource]], + resource, url_units[4]) return '/'.join(url_units) def process_request(self, request): @@ -217,6 +169,7 @@ def process_request(self, request): old_path = request.path_info new_path = self._convert_named_url(old_path) if request.path_info != new_path: + request.environ['awx.named_url_rewritten'] = request.path request.path = request.path.replace(request.path_info, new_path) request.path_info = new_path @@ -228,4 +181,4 @@ def process_request(self, request): plan = executor.migration_plan(executor.loader.graph.leaf_nodes()) if bool(plan) and \ getattr(resolve(request.path), 'url_name', '') != 'migrations_notran': - return redirect(reverse("ui:migrations_notran")) + return redirect(reverse("ui_next:migrations_notran")) diff --git a/awx/main/migrations/0006_v320_release.py b/awx/main/migrations/0006_v320_release.py index d5712f8e0ae5..69b95f03e7d6 100644 --- a/awx/main/migrations/0006_v320_release.py +++ b/awx/main/migrations/0006_v320_release.py @@ -464,7 +464,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='unifiedjob', name='instance_group', - field=models.ForeignKey(on_delete=models.SET_NULL, default=None, blank=True, to='main.InstanceGroup', help_text='The Rampart/Instance group the job was run under', null=True), + field=models.ForeignKey(on_delete=models.SET_NULL, default=None, blank=True, to='main.InstanceGroup', help_text='The Instance group the job was run under', null=True), ), migrations.AddField( model_name='unifiedjobtemplate', diff --git a/awx/main/migrations/0032_v330_polymorphic_delete.py b/awx/main/migrations/0032_v330_polymorphic_delete.py index dd3d6a769f4d..da351240d811 100644 --- a/awx/main/migrations/0032_v330_polymorphic_delete.py +++ b/awx/main/migrations/0032_v330_polymorphic_delete.py @@ -16,6 +16,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='unifiedjob', name='instance_group', - field=models.ForeignKey(blank=True, default=None, help_text='The Rampart/Instance group the job was run under', null=True, on_delete=awx.main.utils.polymorphic.SET_NULL, to='main.InstanceGroup'), + field=models.ForeignKey(blank=True, default=None, help_text='The Instance group the job was run under', null=True, on_delete=awx.main.utils.polymorphic.SET_NULL, to='main.InstanceGroup'), ), ] diff --git a/awx/main/migrations/0101_v370_generate_new_uuids_for_iso_nodes.py b/awx/main/migrations/0101_v370_generate_new_uuids_for_iso_nodes.py index 8f4961d62d4e..6db567341950 100644 --- a/awx/main/migrations/0101_v370_generate_new_uuids_for_iso_nodes.py +++ b/awx/main/migrations/0101_v370_generate_new_uuids_for_iso_nodes.py @@ -3,15 +3,17 @@ from django.db import migrations -from awx.main.models import Instance - def _generate_new_uuid_for_iso_nodes(apps, schema_editor): + Instance = apps.get_model('main', 'Instance') for instance in Instance.objects.all(): - if instance.is_isolated(): + # The below code is a copy paste of instance.is_isolated() + # We can't call is_isolated because we are using the "old" version + # of the Instance definition. + if instance.rampart_groups.filter(controller__isnull=False).exists(): instance.uuid = str(uuid4()) instance.save() - + class Migration(migrations.Migration): diff --git a/awx/main/migrations/0102_v370_unifiedjob_canceled.py b/awx/main/migrations/0102_v370_unifiedjob_canceled.py new file mode 100644 index 000000000000..b5909dd9bcbc --- /dev/null +++ b/awx/main/migrations/0102_v370_unifiedjob_canceled.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-11-25 20:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0101_v370_generate_new_uuids_for_iso_nodes'), + ] + + operations = [ + migrations.AddField( + model_name='unifiedjob', + name='canceled_on', + field=models.DateTimeField(db_index=True, default=None, editable=False, help_text='The date and time when the cancel request was sent.', null=True), + ), + ] diff --git a/awx/main/migrations/0103_v370_remove_computed_fields.py b/awx/main/migrations/0103_v370_remove_computed_fields.py new file mode 100644 index 000000000000..ca81402df9ab --- /dev/null +++ b/awx/main/migrations/0103_v370_remove_computed_fields.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.16 on 2019-02-21 17:35 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0102_v370_unifiedjob_canceled'), + ] + + operations = [ + migrations.RemoveField( + model_name='group', + name='groups_with_active_failures', + ), + migrations.RemoveField( + model_name='group', + name='has_active_failures', + ), + migrations.RemoveField( + model_name='group', + name='has_inventory_sources', + ), + migrations.RemoveField( + model_name='group', + name='hosts_with_active_failures', + ), + migrations.RemoveField( + model_name='group', + name='total_groups', + ), + migrations.RemoveField( + model_name='group', + name='total_hosts', + ), + migrations.RemoveField( + model_name='host', + name='has_active_failures', + ), + migrations.RemoveField( + model_name='host', + name='has_inventory_sources', + ), + migrations.AlterField( + model_name='jobhostsummary', + name='failed', + field=models.BooleanField(db_index=True, default=False, editable=False), + ), + ] diff --git a/awx/main/migrations/0104_v370_cleanup_old_scan_jts.py b/awx/main/migrations/0104_v370_cleanup_old_scan_jts.py new file mode 100644 index 000000000000..f698498ac3f6 --- /dev/null +++ b/awx/main/migrations/0104_v370_cleanup_old_scan_jts.py @@ -0,0 +1,24 @@ +# Generated by Django 2.2.8 on 2020-01-15 20:01 + +from django.db import migrations, models + + +def cleanup_scan_jts(apps, schema_editor): + JobTemplate = apps.get_model('main', 'JobTemplate') + JobTemplate.objects.filter(job_type='scan').update(job_type='run') + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0103_v370_remove_computed_fields'), + ] + + operations = [ + migrations.RunPython(cleanup_scan_jts), + migrations.AlterField( + model_name='jobtemplate', + name='job_type', + field=models.CharField(choices=[('run', 'Run'), ('check', 'Check')], default='run', max_length=64), + ), + ] diff --git a/awx/main/migrations/0105_v370_remove_jobevent_parent_and_hosts.py b/awx/main/migrations/0105_v370_remove_jobevent_parent_and_hosts.py new file mode 100644 index 000000000000..a327667ba738 --- /dev/null +++ b/awx/main/migrations/0105_v370_remove_jobevent_parent_and_hosts.py @@ -0,0 +1,21 @@ +# Generated by Django 2.2.8 on 2020-01-15 18:01 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0104_v370_cleanup_old_scan_jts'), + ] + + operations = [ + migrations.RemoveField( + model_name='jobevent', + name='parent', + ), + migrations.RemoveField( + model_name='jobevent', + name='hosts', + ), + ] diff --git a/awx/main/migrations/0106_v370_remove_inventory_groups_with_active_failures.py b/awx/main/migrations/0106_v370_remove_inventory_groups_with_active_failures.py new file mode 100644 index 000000000000..bc210e9fd3ab --- /dev/null +++ b/awx/main/migrations/0106_v370_remove_inventory_groups_with_active_failures.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.8 on 2020-01-27 12:39 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0105_v370_remove_jobevent_parent_and_hosts'), + ] + + operations = [ + migrations.RemoveField( + model_name='inventory', + name='groups_with_active_failures', + ), + ] diff --git a/awx/main/migrations/0107_v370_workflow_convergence_api_toggle.py b/awx/main/migrations/0107_v370_workflow_convergence_api_toggle.py new file mode 100644 index 000000000000..ec22305f03a3 --- /dev/null +++ b/awx/main/migrations/0107_v370_workflow_convergence_api_toggle.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.4 on 2020-01-08 22:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0106_v370_remove_inventory_groups_with_active_failures'), + ] + + operations = [ + migrations.AddField( + model_name='workflowjobnode', + name='all_parents_must_converge', + field=models.BooleanField(default=False, help_text='If enabled then the node will only run if all of the parent nodes have met the criteria to reach this node'), + ), + migrations.AddField( + model_name='workflowjobtemplatenode', + name='all_parents_must_converge', + field=models.BooleanField(default=False, help_text='If enabled then the node will only run if all of the parent nodes have met the criteria to reach this node'), + ), + ] diff --git a/awx/main/migrations/0108_v370_unifiedjob_dependencies_processed.py b/awx/main/migrations/0108_v370_unifiedjob_dependencies_processed.py new file mode 100644 index 000000000000..6c10b11083a8 --- /dev/null +++ b/awx/main/migrations/0108_v370_unifiedjob_dependencies_processed.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.8 on 2020-02-06 16:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0107_v370_workflow_convergence_api_toggle'), + ] + + operations = [ + migrations.AddField( + model_name='unifiedjob', + name='dependencies_processed', + field=models.BooleanField(default=False, editable=False, help_text='If True, the task manager has already processed potential dependencies for this job.'), + ), + ] diff --git a/awx/main/migrations/0109_v370_job_template_organization_field.py b/awx/main/migrations/0109_v370_job_template_organization_field.py new file mode 100644 index 000000000000..505538594a26 --- /dev/null +++ b/awx/main/migrations/0109_v370_job_template_organization_field.py @@ -0,0 +1,81 @@ +# Generated by Django 2.2.4 on 2019-08-07 19:56 + +import awx.main.utils.polymorphic +import awx.main.fields +from django.db import migrations, models +import django.db.models.deletion + +from awx.main.migrations._rbac import ( + rebuild_role_parentage, rebuild_role_hierarchy, + migrate_ujt_organization, migrate_ujt_organization_backward, + restore_inventory_admins, restore_inventory_admins_backward +) + + +def rebuild_jt_parents(apps, schema_editor): + rebuild_role_parentage(apps, schema_editor, models=('jobtemplate',)) + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0108_v370_unifiedjob_dependencies_processed'), + ] + + operations = [ + # backwards parents and ancestors caching + migrations.RunPython(migrations.RunPython.noop, rebuild_jt_parents), + # add new organization field for JT and all other unified jobs + migrations.AddField( + model_name='unifiedjob', + name='tmp_organization', + field=models.ForeignKey(blank=True, help_text='The organization used to determine access to this unified job.', null=True, on_delete=awx.main.utils.polymorphic.SET_NULL, related_name='unifiedjobs', to='main.Organization'), + ), + migrations.AddField( + model_name='unifiedjobtemplate', + name='tmp_organization', + field=models.ForeignKey(blank=True, help_text='The organization used to determine access to this template.', null=True, on_delete=awx.main.utils.polymorphic.SET_NULL, related_name='unifiedjobtemplates', to='main.Organization'), + ), + # while new and old fields exist, copy the organization fields + migrations.RunPython(migrate_ujt_organization, migrate_ujt_organization_backward), + # with data saved, remove old fields + migrations.RemoveField( + model_name='project', + name='organization', + ), + migrations.RemoveField( + model_name='workflowjobtemplate', + name='organization', + ), + # now, without safely rename the new field without conflicts from old field + migrations.RenameField( + model_name='unifiedjobtemplate', + old_name='tmp_organization', + new_name='organization', + ), + migrations.RenameField( + model_name='unifiedjob', + old_name='tmp_organization', + new_name='organization', + ), + # parentage of job template roles has genuinely changed at this point + migrations.AlterField( + model_name='jobtemplate', + name='admin_role', + field=awx.main.fields.ImplicitRoleField(editable=False, null='True', on_delete=django.db.models.deletion.CASCADE, parent_role=['organization.job_template_admin_role'], related_name='+', to='main.Role'), + ), + migrations.AlterField( + model_name='jobtemplate', + name='execute_role', + field=awx.main.fields.ImplicitRoleField(editable=False, null='True', on_delete=django.db.models.deletion.CASCADE, parent_role=['admin_role', 'organization.execute_role'], related_name='+', to='main.Role'), + ), + migrations.AlterField( + model_name='jobtemplate', + name='read_role', + field=awx.main.fields.ImplicitRoleField(editable=False, null='True', on_delete=django.db.models.deletion.CASCADE, parent_role=['organization.auditor_role', 'inventory.organization.auditor_role', 'execute_role', 'admin_role'], related_name='+', to='main.Role'), + ), + # Re-compute the role parents and ancestors caching + migrations.RunPython(rebuild_jt_parents, migrations.RunPython.noop), + # for all permissions that will be removed, make them explicit + migrations.RunPython(restore_inventory_admins, restore_inventory_admins_backward), + ] diff --git a/awx/main/migrations/0110_v370_instance_ip_address.py b/awx/main/migrations/0110_v370_instance_ip_address.py new file mode 100644 index 000000000000..914be02c5219 --- /dev/null +++ b/awx/main/migrations/0110_v370_instance_ip_address.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.8 on 2020-02-12 17:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0109_v370_job_template_organization_field'), + ] + + operations = [ + migrations.AddField( + model_name='instance', + name='ip_address', + field=models.CharField(blank=True, default=None, max_length=50, null=True, unique=True), + ), + ] diff --git a/awx/main/migrations/0111_v370_delete_channelgroup.py b/awx/main/migrations/0111_v370_delete_channelgroup.py new file mode 100644 index 000000000000..d17270fb9056 --- /dev/null +++ b/awx/main/migrations/0111_v370_delete_channelgroup.py @@ -0,0 +1,16 @@ +# Generated by Django 2.2.8 on 2020-02-17 14:50 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0110_v370_instance_ip_address'), + ] + + operations = [ + migrations.DeleteModel( + name='ChannelGroup', + ), + ] diff --git a/awx/main/migrations/0112_v370_workflow_node_identifier.py b/awx/main/migrations/0112_v370_workflow_node_identifier.py new file mode 100644 index 000000000000..dff2a348b325 --- /dev/null +++ b/awx/main/migrations/0112_v370_workflow_node_identifier.py @@ -0,0 +1,61 @@ +# Generated by Django 2.2.8 on 2020-03-14 02:29 + +from django.db import migrations, models +import uuid +import logging + + +logger = logging.getLogger('awx.main.migrations') + + +def create_uuid(apps, schema_editor): + WorkflowJobTemplateNode = apps.get_model('main', 'WorkflowJobTemplateNode') + ct = 0 + for node in WorkflowJobTemplateNode.objects.iterator(): + node.identifier = uuid.uuid4() + node.save(update_fields=['identifier']) + ct += 1 + if ct: + logger.info(f'Automatically created uuid4 identifier for {ct} workflow nodes') + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0111_v370_delete_channelgroup'), + ] + + operations = [ + migrations.AddField( + model_name='workflowjobnode', + name='identifier', + field=models.CharField(blank=True, help_text='An identifier coresponding to the workflow job template node that this node was created from.', max_length=512), + ), + migrations.AddField( + model_name='workflowjobtemplatenode', + name='identifier', + field=models.CharField(blank=True, null=True, help_text='An identifier for this node that is unique within its workflow. It is copied to workflow job nodes corresponding to this node.', max_length=512), + ), + migrations.RunPython(create_uuid, migrations.RunPython.noop), # this fixes the uuid4 issue + migrations.AlterField( + model_name='workflowjobtemplatenode', + name='identifier', + field=models.CharField(default=uuid.uuid4, help_text='An identifier for this node that is unique within its workflow. It is copied to workflow job nodes corresponding to this node.', max_length=512), + ), + migrations.AlterUniqueTogether( + name='workflowjobtemplatenode', + unique_together={('identifier', 'workflow_job_template')}, + ), + migrations.AddIndex( + model_name='workflowjobnode', + index=models.Index(fields=['identifier', 'workflow_job'], name='main_workfl_identif_87b752_idx'), + ), + migrations.AddIndex( + model_name='workflowjobnode', + index=models.Index(fields=['identifier'], name='main_workfl_identif_efdfe8_idx'), + ), + migrations.AddIndex( + model_name='workflowjobtemplatenode', + index=models.Index(fields=['identifier'], name='main_workfl_identif_0cc025_idx'), + ), + ] diff --git a/awx/main/migrations/0113_v370_event_bigint.py b/awx/main/migrations/0113_v370_event_bigint.py new file mode 100644 index 000000000000..e8b5af664fa5 --- /dev/null +++ b/awx/main/migrations/0113_v370_event_bigint.py @@ -0,0 +1,118 @@ +# Generated by Django 2.2.8 on 2020-02-21 16:31 + +from django.db import migrations, models, connection + + +def migrate_event_data(apps, schema_editor): + # see: https://github.com/ansible/awx/issues/6010 + # + # the goal of this function is to end with event tables (e.g., main_jobevent) + # that have a bigint primary key (because the old usage of an integer + # numeric isn't enough, as its range is about 2.1B, see: + # https://www.postgresql.org/docs/9.1/datatype-numeric.html) + + # unfortunately, we can't do this with a simple ALTER TABLE, because + # for tables with hundreds of millions or billions of rows, the ALTER TABLE + # can take *hours* on modest hardware. + # + # the approach in this migration means that post-migration, event data will + # *not* immediately show up, but will be repopulated over time progressively + # the trade-off here is not having to wait hours for the full data migration + # before you can start and run AWX again (including new playbook runs) + for tblname in ( + 'main_jobevent', 'main_inventoryupdateevent', + 'main_projectupdateevent', 'main_adhoccommandevent', + 'main_systemjobevent' + ): + with connection.cursor() as cursor: + # rename the current event table + cursor.execute( + f'ALTER TABLE {tblname} RENAME TO _old_{tblname};' + ) + # create a *new* table with the same schema + cursor.execute( + f'CREATE TABLE {tblname} (LIKE _old_{tblname} INCLUDING ALL);' + ) + # alter the *new* table so that the primary key is a big int + cursor.execute( + f'ALTER TABLE {tblname} ALTER COLUMN id TYPE bigint USING id::bigint;' + ) + + # recreate counter for the new table's primary key to + # start where the *old* table left off (we have to do this because the + # counter changed from an int to a bigint) + cursor.execute(f'DROP SEQUENCE IF EXISTS "{tblname}_id_seq" CASCADE;') + cursor.execute(f'CREATE SEQUENCE "{tblname}_id_seq";') + cursor.execute( + f'ALTER TABLE "{tblname}" ALTER COLUMN "id" ' + f"SET DEFAULT nextval('{tblname}_id_seq');" + ) + cursor.execute( + f"SELECT setval('{tblname}_id_seq', (SELECT MAX(id) FROM _old_{tblname}), true);" + ) + + # replace the BTREE index on main_jobevent.job_id with + # a BRIN index to drastically improve per-UJ lookup performance + # see: https://info.crunchydata.com/blog/postgresql-brin-indexes-big-data-performance-with-minimal-storage + if tblname == 'main_jobevent': + cursor.execute("SELECT indexname FROM pg_indexes WHERE tablename='main_jobevent' AND indexdef LIKE '%USING btree (job_id)';") + old_index = cursor.fetchone()[0] + cursor.execute(f'DROP INDEX {old_index}') + cursor.execute('CREATE INDEX main_jobevent_job_id_brin_idx ON main_jobevent USING brin (job_id);') + + # remove all of the indexes and constraints from the old table + # (they just slow down the data migration) + cursor.execute(f"SELECT indexname, indexdef FROM pg_indexes WHERE tablename='_old_{tblname}' AND indexname != '{tblname}_pkey';") + indexes = cursor.fetchall() + + cursor.execute(f"SELECT conname, contype, pg_catalog.pg_get_constraintdef(r.oid, true) as condef FROM pg_catalog.pg_constraint r WHERE r.conrelid = '_old_{tblname}'::regclass AND conname != '{tblname}_pkey';") + constraints = cursor.fetchall() + + for indexname, indexdef in indexes: + cursor.execute(f'DROP INDEX IF EXISTS {indexname}') + for conname, contype, condef in constraints: + cursor.execute(f'ALTER TABLE _old_{tblname} DROP CONSTRAINT IF EXISTS {conname}') + + +class FakeAlterField(migrations.AlterField): + + def database_forwards(self, *args): + # this is intentionally left blank, because we're + # going to accomplish the migration with some custom raw SQL + pass + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0112_v370_workflow_node_identifier'), + ] + + operations = [ + migrations.RunPython(migrate_event_data), + FakeAlterField( + model_name='adhoccommandevent', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + FakeAlterField( + model_name='inventoryupdateevent', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + FakeAlterField( + model_name='jobevent', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + FakeAlterField( + model_name='projectupdateevent', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + FakeAlterField( + model_name='systemjobevent', + name='id', + field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + ] diff --git a/awx/main/migrations/0114_v370_remove_deprecated_manual_inventory_sources.py b/awx/main/migrations/0114_v370_remove_deprecated_manual_inventory_sources.py new file mode 100644 index 000000000000..f3b796e0aef1 --- /dev/null +++ b/awx/main/migrations/0114_v370_remove_deprecated_manual_inventory_sources.py @@ -0,0 +1,39 @@ +# Generated by Django 2.2.11 on 2020-04-03 00:11 + +from django.db import migrations, models + + +def remove_manual_inventory_sources(apps, schema_editor): + '''Previously we would automatically create inventory sources after + Group creation and we would use the parent Group as our interface for the user. + During that process we would create InventorySource that had a source of "manual". + ''' + InventoryUpdate = apps.get_model('main', 'InventoryUpdate') + InventoryUpdate.objects.filter(source='').delete() + InventorySource = apps.get_model('main', 'InventorySource') + InventorySource.objects.filter(source='').delete() + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0113_v370_event_bigint'), + ] + + operations = [ + migrations.RemoveField( + model_name='inventorysource', + name='deprecated_group', + ), + migrations.RunPython(remove_manual_inventory_sources), + migrations.AlterField( + model_name='inventorysource', + name='source', + field=models.CharField(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'), ('cloudforms', 'Red Hat CloudForms'), ('openstack', 'OpenStack'), ('rhv', 'Red Hat Virtualization'), ('tower', 'Ansible Tower'), ('custom', 'Custom Script')], default=None, max_length=32), + ), + migrations.AlterField( + model_name='inventoryupdate', + name='source', + field=models.CharField(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'), ('cloudforms', 'Red Hat CloudForms'), ('openstack', 'OpenStack'), ('rhv', 'Red Hat Virtualization'), ('tower', 'Ansible Tower'), ('custom', 'Custom Script')], default=None, max_length=32), + ), + ] diff --git a/awx/main/migrations/0115_v370_schedule_set_null.py b/awx/main/migrations/0115_v370_schedule_set_null.py new file mode 100644 index 000000000000..10e5798d1715 --- /dev/null +++ b/awx/main/migrations/0115_v370_schedule_set_null.py @@ -0,0 +1,24 @@ +# Generated by Django 2.2.11 on 2020-05-04 02:26 + +import awx.main.utils.polymorphic +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0114_v370_remove_deprecated_manual_inventory_sources'), + ] + + operations = [ + migrations.AlterField( + model_name='unifiedjob', + name='schedule', + field=models.ForeignKey(default=None, editable=False, null=True, on_delete=awx.main.utils.polymorphic.SET_NULL, to='main.Schedule'), + ), + migrations.AlterField( + model_name='unifiedjobtemplate', + name='next_schedule', + field=models.ForeignKey(default=None, editable=False, null=True, on_delete=awx.main.utils.polymorphic.SET_NULL, related_name='unifiedjobtemplate_as_next_schedule+', to='main.Schedule'), + ), + ] diff --git a/awx/main/migrations/0116_v400_remove_hipchat_notifications.py b/awx/main/migrations/0116_v400_remove_hipchat_notifications.py new file mode 100644 index 000000000000..e366436bdc3a --- /dev/null +++ b/awx/main/migrations/0116_v400_remove_hipchat_notifications.py @@ -0,0 +1,34 @@ +# Generated by Django 2.2.11 on 2020-05-19 02:27 + +from django.db import migrations, models + + +def remove_hipchat_notifications(apps, schema_editor): + ''' + HipChat notifications are no longer in service, remove any that are found. + ''' + Notification = apps.get_model('main', 'Notification') + Notification.objects.filter(notification_type='hipchat').delete() + NotificationTemplate = apps.get_model('main', 'NotificationTemplate') + NotificationTemplate.objects.filter(notification_type='hipchat').delete() + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0115_v370_schedule_set_null'), + ] + + operations = [ + migrations.RunPython(remove_hipchat_notifications), + migrations.AlterField( + model_name='notification', + name='notification_type', + field=models.CharField(choices=[('email', 'Email'), ('grafana', 'Grafana'), ('irc', 'IRC'), ('mattermost', 'Mattermost'), ('pagerduty', 'Pagerduty'), ('rocketchat', 'Rocket.Chat'), ('slack', 'Slack'), ('twilio', 'Twilio'), ('webhook', 'Webhook')], max_length=32), + ), + migrations.AlterField( + model_name='notificationtemplate', + name='notification_type', + field=models.CharField(choices=[('email', 'Email'), ('grafana', 'Grafana'), ('irc', 'IRC'), ('mattermost', 'Mattermost'), ('pagerduty', 'Pagerduty'), ('rocketchat', 'Rocket.Chat'), ('slack', 'Slack'), ('twilio', 'Twilio'), ('webhook', 'Webhook')], max_length=32), + ), + ] diff --git a/awx/main/migrations/0117_v400_remove_cloudforms_inventory.py b/awx/main/migrations/0117_v400_remove_cloudforms_inventory.py new file mode 100644 index 000000000000..9a94c6b02b4c --- /dev/null +++ b/awx/main/migrations/0117_v400_remove_cloudforms_inventory.py @@ -0,0 +1,25 @@ +# Generated by Django 2.2.11 on 2020-05-01 13:25 + +from django.db import migrations, models +from awx.main.migrations._inventory_source import delete_cloudforms_inv_source + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0116_v400_remove_hipchat_notifications'), + ] + + operations = [ + migrations.RunPython(delete_cloudforms_inv_source), + migrations.AlterField( + model_name='inventorysource', + name='source', + field=models.CharField(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'), ('tower', 'Ansible Tower'), ('custom', 'Custom Script')], default=None, max_length=32), + ), + migrations.AlterField( + model_name='inventoryupdate', + name='source', + field=models.CharField(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'), ('tower', 'Ansible Tower'), ('custom', 'Custom Script')], default=None, max_length=32), + ), + ] diff --git a/awx/main/migrations/0118_add_remote_archive_scm_type.py b/awx/main/migrations/0118_add_remote_archive_scm_type.py new file mode 100644 index 000000000000..246ca4c8234b --- /dev/null +++ b/awx/main/migrations/0118_add_remote_archive_scm_type.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.11 on 2020-08-18 22:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0117_v400_remove_cloudforms_inventory'), + ] + + operations = [ + migrations.AlterField( + model_name='project', + name='scm_type', + field=models.CharField(blank=True, choices=[('', 'Manual'), ('git', 'Git'), ('hg', 'Mercurial'), ('svn', 'Subversion'), ('insights', 'Red Hat Insights'), ('archive', 'Remote Archive')], default='', help_text='Specifies the source control system used to store the project.', max_length=8, verbose_name='SCM Type'), + ), + migrations.AlterField( + model_name='projectupdate', + name='scm_type', + field=models.CharField(blank=True, choices=[('', 'Manual'), ('git', 'Git'), ('hg', 'Mercurial'), ('svn', 'Subversion'), ('insights', 'Red Hat Insights'), ('archive', 'Remote Archive')], default='', help_text='Specifies the source control system used to store the project.', max_length=8, verbose_name='SCM Type'), + ), + ] diff --git a/awx/main/migrations/0119_inventory_plugins.py b/awx/main/migrations/0119_inventory_plugins.py new file mode 100644 index 000000000000..670fb7887bc7 --- /dev/null +++ b/awx/main/migrations/0119_inventory_plugins.py @@ -0,0 +1,104 @@ +# Generated by Django 2.2.11 on 2020-07-20 19:56 + +import logging +import yaml + +from django.db import migrations, models + +from awx.main.models.base import VarsDictProperty + +from ._inventory_source_vars import FrozenInjectors + + +logger = logging.getLogger('awx.main.migrations') + + +def _get_inventory_sources(InventorySource): + return InventorySource.objects.filter(source__in=['ec2', 'gce', 'azure_rm', 'vmware', 'satellite6', 'openstack', 'rhv', 'tower']) + + +def inventory_source_vars_forward(apps, schema_editor): + InventorySource = apps.get_model("main", "InventorySource") + ''' + The Django app registry does not keep track of model inheritance. The + source_vars_dict property comes from InventorySourceOptions via inheritance. + This adds that property. Luckily, other properteries and functionality from + InventorySourceOptions is not needed by the injector logic. + ''' + setattr(InventorySource, 'source_vars_dict', VarsDictProperty('source_vars')) + source_vars_backup = dict() + + for inv_source_obj in _get_inventory_sources(InventorySource): + + if inv_source_obj.source in FrozenInjectors: + source_vars_backup[inv_source_obj.id] = dict(inv_source_obj.source_vars_dict) + + injector = FrozenInjectors[inv_source_obj.source]() + new_inv_source_vars = injector.inventory_as_dict(inv_source_obj, None) + inv_source_obj.source_vars = yaml.dump(new_inv_source_vars) + inv_source_obj.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0118_add_remote_archive_scm_type'), + ] + + operations = [ + migrations.RunPython(inventory_source_vars_forward), + migrations.RemoveField( + model_name='inventorysource', + name='group_by', + ), + migrations.RemoveField( + model_name='inventoryupdate', + name='group_by', + ), + migrations.RemoveField( + model_name='inventorysource', + name='instance_filters', + ), + migrations.RemoveField( + model_name='inventoryupdate', + name='instance_filters', + ), + migrations.RemoveField( + model_name='inventorysource', + name='source_regions', + ), + migrations.RemoveField( + model_name='inventoryupdate', + name='source_regions', + ), + migrations.AddField( + model_name='inventorysource', + name='enabled_value', + field=models.TextField(blank=True, default='', help_text='Only used when enabled_var is set. Value when the host is considered enabled. For example if enabled_var="status.power_state"and enabled_value="powered_on" with host variables:{ "status": { "power_state": "powered_on", "created": "2020-08-04T18:13:04+00:00", "healthy": true }, "name": "foobar", "ip_address": "192.168.2.1"}The host would be marked enabled. If power_state where any value other than powered_on then the host would be disabled when imported into Tower. If the key is not found then the host will be enabled'), + ), + migrations.AddField( + model_name='inventorysource', + name='enabled_var', + field=models.TextField(blank=True, default='', help_text='Retrieve the enabled state from the given dict of host variables. The enabled variable may be specified as "foo.bar", in which case the lookup will traverse into nested dicts, equivalent to: from_dict.get("foo", {}).get("bar", default)'), + ), + migrations.AddField( + model_name='inventorysource', + name='host_filter', + field=models.TextField(blank=True, default='', help_text='Regex where only matching hosts will be imported into Tower.'), + ), + migrations.AddField( + model_name='inventoryupdate', + name='enabled_value', + field=models.TextField(blank=True, default='', help_text='Only used when enabled_var is set. Value when the host is considered enabled. For example if enabled_var="status.power_state"and enabled_value="powered_on" with host variables:{ "status": { "power_state": "powered_on", "created": "2020-08-04T18:13:04+00:00", "healthy": true }, "name": "foobar", "ip_address": "192.168.2.1"}The host would be marked enabled. If power_state where any value other than powered_on then the host would be disabled when imported into Tower. If the key is not found then the host will be enabled'), + ), + migrations.AddField( + model_name='inventoryupdate', + name='enabled_var', + field=models.TextField(blank=True, default='', help_text='Retrieve the enabled state from the given dict of host variables. The enabled variable may be specified as "foo.bar", in which case the lookup will traverse into nested dicts, equivalent to: from_dict.get("foo", {}).get("bar", default)'), + ), + migrations.AddField( + model_name='inventoryupdate', + name='host_filter', + field=models.TextField(blank=True, default='', help_text='Regex where only matching hosts will be imported into Tower.'), + ), + ] diff --git a/awx/main/migrations/0120_galaxy_credentials.py b/awx/main/migrations/0120_galaxy_credentials.py new file mode 100644 index 000000000000..a94c22e30b12 --- /dev/null +++ b/awx/main/migrations/0120_galaxy_credentials.py @@ -0,0 +1,51 @@ +# Generated by Django 2.2.11 on 2020-08-04 15:19 + +import logging + +import awx.main.fields +from awx.main.utils.encryption import encrypt_field, decrypt_field + +from django.db import migrations, models +from django.utils.timezone import now +import django.db.models.deletion + +from awx.main.migrations import _galaxy as galaxy +from awx.main.models import CredentialType as ModernCredentialType +from awx.main.utils.common import set_current_apps + +logger = logging.getLogger('awx.main.migrations') + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0119_inventory_plugins'), + ] + + operations = [ + migrations.AlterField( + model_name='credentialtype', + name='kind', + field=models.CharField(choices=[('ssh', 'Machine'), ('vault', 'Vault'), ('net', 'Network'), ('scm', 'Source Control'), ('cloud', 'Cloud'), ('token', 'Personal Access Token'), ('insights', 'Insights'), ('external', 'External'), ('kubernetes', 'Kubernetes'), ('galaxy', 'Galaxy/Automation Hub')], max_length=32), + ), + migrations.CreateModel( + name='OrganizationGalaxyCredentialMembership', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('position', models.PositiveIntegerField(db_index=True, default=None, null=True)), + ('credential', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.Credential')), + ('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.Organization')), + ], + ), + migrations.AddField( + model_name='organization', + name='galaxy_credentials', + field=awx.main.fields.OrderedManyToManyField(blank=True, related_name='organization_galaxy_credentials', through='main.OrganizationGalaxyCredentialMembership', to='main.Credential'), + ), + migrations.AddField( + model_name='credential', + name='managed_by_tower', + field=models.BooleanField(default=False, editable=False), + ), + migrations.RunPython(galaxy.migrate_galaxy_settings) + ] diff --git a/awx/main/migrations/0121_delete_toweranalyticsstate.py b/awx/main/migrations/0121_delete_toweranalyticsstate.py new file mode 100644 index 000000000000..d1e1ceb37cfe --- /dev/null +++ b/awx/main/migrations/0121_delete_toweranalyticsstate.py @@ -0,0 +1,16 @@ +# Generated by Django 2.2.11 on 2020-07-24 17:41 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0120_galaxy_credentials'), + ] + + operations = [ + migrations.DeleteModel( + name='TowerAnalyticsState', + ), + ] diff --git a/awx/main/migrations/0122_really_remove_cloudforms_inventory.py b/awx/main/migrations/0122_really_remove_cloudforms_inventory.py new file mode 100644 index 000000000000..ee44573304dc --- /dev/null +++ b/awx/main/migrations/0122_really_remove_cloudforms_inventory.py @@ -0,0 +1,13 @@ +from django.db import migrations +from awx.main.migrations._inventory_source import delete_cloudforms_inv_source + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0121_delete_toweranalyticsstate'), + ] + + operations = [ + migrations.RunPython(delete_cloudforms_inv_source), + ] diff --git a/awx/main/migrations/0123_drop_hg_support.py b/awx/main/migrations/0123_drop_hg_support.py new file mode 100644 index 000000000000..089c6bba6f15 --- /dev/null +++ b/awx/main/migrations/0123_drop_hg_support.py @@ -0,0 +1,23 @@ +from django.db import migrations, models +from awx.main.migrations._hg_removal import delete_hg_scm + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0122_really_remove_cloudforms_inventory'), + ] + + operations = [ + migrations.RunPython(delete_hg_scm), + migrations.AlterField( + model_name='project', + name='scm_type', + field=models.CharField(blank=True, choices=[('', 'Manual'), ('git', 'Git'), ('svn', 'Subversion'), ('insights', 'Red Hat Insights'), ('archive', 'Remote Archive')], default='', help_text='Specifies the source control system used to store the project.', max_length=8, verbose_name='SCM Type'), + ), + migrations.AlterField( + model_name='projectupdate', + name='scm_type', + field=models.CharField(blank=True, choices=[('', 'Manual'), ('git', 'Git'), ('svn', 'Subversion'), ('insights', 'Red Hat Insights'), ('archive', 'Remote Archive')], default='', help_text='Specifies the source control system used to store the project.', max_length=8, verbose_name='SCM Type'), + ), + ] diff --git a/awx/main/migrations/_galaxy.py b/awx/main/migrations/_galaxy.py new file mode 100644 index 000000000000..b85b7b3aafc6 --- /dev/null +++ b/awx/main/migrations/_galaxy.py @@ -0,0 +1,125 @@ +# Generated by Django 2.2.11 on 2020-08-04 15:19 + +import logging + +from awx.main.utils.encryption import encrypt_field, decrypt_field + +from django.conf import settings +from django.utils.timezone import now + +from awx.main.models import CredentialType as ModernCredentialType +from awx.main.utils.common import set_current_apps + +logger = logging.getLogger('awx.main.migrations') + + +def migrate_galaxy_settings(apps, schema_editor): + Organization = apps.get_model('main', 'Organization') + if Organization.objects.count() == 0: + # nothing to migrate + return + set_current_apps(apps) + ModernCredentialType.setup_tower_managed_defaults() + CredentialType = apps.get_model('main', 'CredentialType') + Credential = apps.get_model('main', 'Credential') + Setting = apps.get_model('conf', 'Setting') + + galaxy_type = CredentialType.objects.get(kind='galaxy') + private_galaxy_url = Setting.objects.filter(key='PRIMARY_GALAXY_URL').first() + + # by default, prior versions of AWX/Tower automatically pulled content + # from galaxy.ansible.com + public_galaxy_enabled = True + public_galaxy_setting = Setting.objects.filter(key='PUBLIC_GALAXY_ENABLED').first() + if public_galaxy_setting and public_galaxy_setting.value is False: + # ...UNLESS this behavior was explicitly disabled via this setting + public_galaxy_enabled = False + + public_galaxy_credential = Credential( + created=now(), + modified=now(), + name='Ansible Galaxy', + managed_by_tower=True, + credential_type=galaxy_type, + inputs = { + 'url': 'https://galaxy.ansible.com/' + } + ) + public_galaxy_credential.save() + + for org in Organization.objects.all(): + if private_galaxy_url and private_galaxy_url.value: + # If a setting exists for a private Galaxy URL, make a credential for it + username = Setting.objects.filter(key='PRIMARY_GALAXY_USERNAME').first() + password = Setting.objects.filter(key='PRIMARY_GALAXY_PASSWORD').first() + if (username and username.value) or (password and password.value): + logger.error( + f'Specifying HTTP basic auth for the Ansible Galaxy API ' + f'({private_galaxy_url.value}) is no longer supported. ' + 'Please provide an API token instead after your upgrade ' + 'has completed', + ) + inputs = { + 'url': private_galaxy_url.value + } + token = Setting.objects.filter(key='PRIMARY_GALAXY_TOKEN').first() + if token and token.value: + inputs['token'] = decrypt_field(token, 'value') + auth_url = Setting.objects.filter(key='PRIMARY_GALAXY_AUTH_URL').first() + if auth_url and auth_url.value: + inputs['auth_url'] = auth_url.value + name = f'Private Galaxy ({private_galaxy_url.value})' + if 'cloud.redhat.com' in inputs['url']: + name = f'Ansible Automation Hub ({private_galaxy_url.value})' + cred = Credential( + created=now(), + modified=now(), + name=name, + organization=org, + credential_type=galaxy_type, + inputs=inputs + ) + cred.save() + if token and token.value: + # encrypt based on the primary key from the prior save + cred.inputs['token'] = encrypt_field(cred, 'token') + cred.save() + org.galaxy_credentials.add(cred) + + fallback_servers = getattr(settings, 'FALLBACK_GALAXY_SERVERS', []) + for fallback in fallback_servers: + url = fallback.get('url', None) + auth_url = fallback.get('auth_url', None) + username = fallback.get('username', None) + password = fallback.get('password', None) + token = fallback.get('token', None) + if username or password: + logger.error( + f'Specifying HTTP basic auth for the Ansible Galaxy API ' + f'({url}) is no longer supported. ' + 'Please provide an API token instead after your upgrade ' + 'has completed', + ) + inputs = {'url': url} + if token: + inputs['token'] = token + if auth_url: + inputs['auth_url'] = auth_url + cred = Credential( + created=now(), + modified=now(), + name=f'Ansible Galaxy ({url})', + organization=org, + credential_type=galaxy_type, + inputs=inputs + ) + cred.save() + if token: + # encrypt based on the primary key from the prior save + cred.inputs['token'] = encrypt_field(cred, 'token') + cred.save() + org.galaxy_credentials.add(cred) + + if public_galaxy_enabled: + # If public Galaxy was enabled, associate it to the org + org.galaxy_credentials.add(public_galaxy_credential) diff --git a/awx/main/migrations/_hg_removal.py b/awx/main/migrations/_hg_removal.py new file mode 100644 index 000000000000..70ca0b5a2981 --- /dev/null +++ b/awx/main/migrations/_hg_removal.py @@ -0,0 +1,19 @@ +import logging + +from awx.main.utils.common import set_current_apps + +logger = logging.getLogger('awx.main.migrations') + + +def delete_hg_scm(apps, schema_editor): + set_current_apps(apps) + Project = apps.get_model('main', 'Project') + ProjectUpdate = apps.get_model('main', 'ProjectUpdate') + + ProjectUpdate.objects.filter(project__scm_type='hg').update(scm_type='') + update_ct = Project.objects.filter(scm_type='hg').update(scm_type='') + + if update_ct: + logger.warn('Changed {} mercurial projects to manual, deprecation period ended'.format( + update_ct + )) diff --git a/awx/main/migrations/_inventory_source.py b/awx/main/migrations/_inventory_source.py index c532ab33f545..c53a18f035b6 100644 --- a/awx/main/migrations/_inventory_source.py +++ b/awx/main/migrations/_inventory_source.py @@ -1,7 +1,11 @@ import logging +from uuid import uuid4 + from django.utils.encoding import smart_text +from django.utils.timezone import now +from awx.main.utils.common import set_current_apps from awx.main.utils.common import parse_yaml_or_json logger = logging.getLogger('awx.main.migrations') @@ -87,3 +91,15 @@ def back_out_new_instance_id(apps, source, new_id): modified_ct, source )) + +def delete_cloudforms_inv_source(apps, schema_editor): + set_current_apps(apps) + InventorySource = apps.get_model('main', 'InventorySource') + InventoryUpdate = apps.get_model('main', 'InventoryUpdate') + CredentialType = apps.get_model('main', 'CredentialType') + InventoryUpdate.objects.filter(inventory_source__source='cloudforms').delete() + InventorySource.objects.filter(source='cloudforms').delete() + ct = CredentialType.objects.filter(namespace='cloudforms').first() + if ct: + ct.credentials.all().delete() + ct.delete() diff --git a/awx/main/migrations/_inventory_source_vars.py b/awx/main/migrations/_inventory_source_vars.py new file mode 100644 index 000000000000..263b5666a2dc --- /dev/null +++ b/awx/main/migrations/_inventory_source_vars.py @@ -0,0 +1,757 @@ +import json +import re +import logging + +from django.utils.translation import ugettext_lazy as _ +from django.utils.encoding import iri_to_uri + + +FrozenInjectors = dict() +logger = logging.getLogger('awx.main.migrations') + + +class PluginFileInjector(object): + plugin_name = None # Ansible core name used to reference plugin + # every source should have collection, these are for the collection name + namespace = None + collection = None + + def inventory_as_dict(self, inventory_source, private_data_dir): + """Default implementation of inventory plugin file contents. + There are some valid cases when all parameters can be obtained from + the environment variables, example "plugin: linode" is valid + ideally, however, some options should be filled from the inventory source data + """ + if self.plugin_name is None: + raise NotImplementedError('At minimum the plugin name is needed for inventory plugin use.') + proper_name = f'{self.namespace}.{self.collection}.{self.plugin_name}' + return {'plugin': proper_name} + + +class azure_rm(PluginFileInjector): + plugin_name = 'azure_rm' + namespace = 'azure' + collection = 'azcollection' + + def inventory_as_dict(self, inventory_source, private_data_dir): + ret = super(azure_rm, self).inventory_as_dict(inventory_source, private_data_dir) + + source_vars = inventory_source.source_vars_dict + + ret['fail_on_template_errors'] = False + + group_by_hostvar = { + 'location': {'prefix': '', 'separator': '', 'key': 'location'}, + 'tag': {'prefix': '', 'separator': '', 'key': 'tags.keys() | list if tags else []'}, + # Introduced with https://github.com/ansible/ansible/pull/53046 + 'security_group': {'prefix': '', 'separator': '', 'key': 'security_group'}, + 'resource_group': {'prefix': '', 'separator': '', 'key': 'resource_group'}, + # Note, os_family was not documented correctly in script, but defaulted to grouping by it + 'os_family': {'prefix': '', 'separator': '', 'key': 'os_disk.operating_system_type'} + } + # by default group by everything + # always respect user setting, if they gave it + group_by = [ + grouping_name for grouping_name in group_by_hostvar + if source_vars.get('group_by_{}'.format(grouping_name), True) + ] + ret['keyed_groups'] = [group_by_hostvar[grouping_name] for grouping_name in group_by] + if 'tag' in group_by: + # Nasty syntax to reproduce "key_value" group names in addition to "key" + ret['keyed_groups'].append({ + 'prefix': '', 'separator': '', + 'key': r'dict(tags.keys() | map("regex_replace", "^(.*)$", "\1_") | list | zip(tags.values() | list)) if tags else []' + }) + + # Compatibility content + # TODO: add proper support for instance_filters non-specific to compatibility + # TODO: add proper support for group_by non-specific to compatibility + # Dashes were not configurable in azure_rm.py script, we do not want unicode, so always use this + ret['use_contrib_script_compatible_sanitization'] = True + # use same host names as script + ret['plain_host_names'] = True + # By default the script did not filter hosts + ret['default_host_filters'] = [] + # User-given host filters + user_filters = [] + old_filterables = [ + ('resource_groups', 'resource_group'), + ('tags', 'tags') + # locations / location would be an entry + # but this would conflict with source_regions + ] + for key, loc in old_filterables: + value = source_vars.get(key, None) + if value and isinstance(value, str): + # tags can be list of key:value pairs + # e.g. 'Creator:jmarshall, peanutbutter:jelly' + # or tags can be a list of keys + # e.g. 'Creator, peanutbutter' + if key == "tags": + # grab each key value pair + for kvpair in value.split(','): + # split into key and value + kv = kvpair.split(':') + # filter out any host that does not have key + # in their tags.keys() variable + user_filters.append('"{}" not in tags.keys()'.format(kv[0].strip())) + # if a value is provided, check that the key:value pair matches + if len(kv) > 1: + user_filters.append('tags["{}"] != "{}"'.format(kv[0].strip(), kv[1].strip())) + else: + user_filters.append('{} not in {}'.format( + loc, value.split(',') + )) + if user_filters: + ret.setdefault('exclude_host_filters', []) + ret['exclude_host_filters'].extend(user_filters) + + ret['conditional_groups'] = {'azure': True} + ret['hostvar_expressions'] = { + 'provisioning_state': 'provisioning_state | title', + 'computer_name': 'name', + 'type': 'resource_type', + 'private_ip': 'private_ipv4_addresses[0] if private_ipv4_addresses else None', + 'public_ip': 'public_ipv4_addresses[0] if public_ipv4_addresses else None', + 'public_ip_name': 'public_ip_name if public_ip_name is defined else None', + 'public_ip_id': 'public_ip_id if public_ip_id is defined else None', + 'tags': 'tags if tags else None' + } + # Special functionality from script + if source_vars.get('use_private_ip', False): + ret['hostvar_expressions']['ansible_host'] = 'private_ipv4_addresses[0]' + # end compatibility content + + if inventory_source.source_regions and 'all' not in inventory_source.source_regions: + # initialize a list for this section in inventory file + ret.setdefault('exclude_host_filters', []) + # make a python list of the regions we will use + python_regions = [x.strip() for x in inventory_source.source_regions.split(',')] + # convert that list in memory to python syntax in a string + # now put that in jinja2 syntax operating on hostvar key "location" + # and put that as an entry in the exclusions list + ret['exclude_host_filters'].append("location not in {}".format(repr(python_regions))) + return ret + + +class ec2(PluginFileInjector): + plugin_name = 'aws_ec2' + namespace = 'amazon' + collection = 'aws' + + + def _get_ec2_group_by_choices(self): + return [ + ('ami_id', _('Image ID')), + ('availability_zone', _('Availability Zone')), + ('aws_account', _('Account')), + ('instance_id', _('Instance ID')), + ('instance_state', _('Instance State')), + ('platform', _('Platform')), + ('instance_type', _('Instance Type')), + ('key_pair', _('Key Name')), + ('region', _('Region')), + ('security_group', _('Security Group')), + ('tag_keys', _('Tags')), + ('tag_none', _('Tag None')), + ('vpc_id', _('VPC ID')), + ] + + def _compat_compose_vars(self): + return { + # vars that change + 'ec2_block_devices': ( + "dict(block_device_mappings | map(attribute='device_name') | list | zip(block_device_mappings " + "| map(attribute='ebs.volume_id') | list))" + ), + 'ec2_dns_name': 'public_dns_name', + 'ec2_group_name': 'placement.group_name', + 'ec2_instance_profile': 'iam_instance_profile | default("")', + 'ec2_ip_address': 'public_ip_address', + 'ec2_kernel': 'kernel_id | default("")', + 'ec2_monitored': "monitoring.state in ['enabled', 'pending']", + 'ec2_monitoring_state': 'monitoring.state', + 'ec2_placement': 'placement.availability_zone', + 'ec2_ramdisk': 'ramdisk_id | default("")', + 'ec2_reason': 'state_transition_reason', + 'ec2_security_group_ids': "security_groups | map(attribute='group_id') | list | join(',')", + 'ec2_security_group_names': "security_groups | map(attribute='group_name') | list | join(',')", + 'ec2_tag_Name': 'tags.Name', + 'ec2_state': 'state.name', + 'ec2_state_code': 'state.code', + 'ec2_state_reason': 'state_reason.message if state_reason is defined else ""', + 'ec2_sourceDestCheck': 'source_dest_check | default(false) | lower | string', # snake_case syntax intended + 'ec2_account_id': 'owner_id', + # vars that just need ec2_ prefix + 'ec2_ami_launch_index': 'ami_launch_index | string', + 'ec2_architecture': 'architecture', + 'ec2_client_token': 'client_token', + 'ec2_ebs_optimized': 'ebs_optimized', + 'ec2_hypervisor': 'hypervisor', + 'ec2_image_id': 'image_id', + 'ec2_instance_type': 'instance_type', + 'ec2_key_name': 'key_name', + 'ec2_launch_time': r'launch_time | regex_replace(" ", "T") | regex_replace("(\+)(\d\d):(\d)(\d)$", ".\g<2>\g<3>Z")', + 'ec2_platform': 'platform | default("")', + 'ec2_private_dns_name': 'private_dns_name', + 'ec2_private_ip_address': 'private_ip_address', + 'ec2_public_dns_name': 'public_dns_name', + 'ec2_region': 'placement.region', + 'ec2_root_device_name': 'root_device_name', + 'ec2_root_device_type': 'root_device_type', + # many items need blank defaults because the script tended to keep a common schema + 'ec2_spot_instance_request_id': 'spot_instance_request_id | default("")', + 'ec2_subnet_id': 'subnet_id | default("")', + 'ec2_virtualization_type': 'virtualization_type', + 'ec2_vpc_id': 'vpc_id | default("")', + # same as ec2_ip_address, the script provided this + 'ansible_host': 'public_ip_address', + # new with https://github.com/ansible/ansible/pull/53645 + 'ec2_eventsSet': 'events | default("")', + 'ec2_persistent': 'persistent | default(false)', + 'ec2_requester_id': 'requester_id | default("")' + } + + def inventory_as_dict(self, inventory_source, private_data_dir): + ret = super(ec2, self).inventory_as_dict(inventory_source, private_data_dir) + + keyed_groups = [] + group_by_hostvar = { + 'ami_id': {'prefix': '', 'separator': '', 'key': 'image_id', 'parent_group': 'images'}, + # 2 entries for zones for same groups to establish 2 parentage trees + 'availability_zone': {'prefix': '', 'separator': '', 'key': 'placement.availability_zone', 'parent_group': 'zones'}, + 'aws_account': {'prefix': '', 'separator': '', 'key': 'ec2_account_id', 'parent_group': 'accounts'}, # composed var + 'instance_id': {'prefix': '', 'separator': '', 'key': 'instance_id', 'parent_group': 'instances'}, # normally turned off + 'instance_state': {'prefix': 'instance_state', 'key': 'ec2_state', 'parent_group': 'instance_states'}, # composed var + # ec2_platform is a composed var, but group names do not match up to hostvar exactly + 'platform': {'prefix': 'platform', 'key': 'platform | default("undefined")', 'parent_group': 'platforms'}, + 'instance_type': {'prefix': 'type', 'key': 'instance_type', 'parent_group': 'types'}, + 'key_pair': {'prefix': 'key', 'key': 'key_name', 'parent_group': 'keys'}, + 'region': {'prefix': '', 'separator': '', 'key': 'placement.region', 'parent_group': 'regions'}, + # Security requires some ninja jinja2 syntax, credit to s-hertel + 'security_group': {'prefix': 'security_group', 'key': 'security_groups | map(attribute="group_name")', 'parent_group': 'security_groups'}, + # tags cannot be parented in exactly the same way as the script due to + # https://github.com/ansible/ansible/pull/53812 + 'tag_keys': [ + {'prefix': 'tag', 'key': 'tags', 'parent_group': 'tags'}, + {'prefix': 'tag', 'key': 'tags.keys()', 'parent_group': 'tags'} + ], + # 'tag_none': None, # grouping by no tags isn't a different thing with plugin + # naming is redundant, like vpc_id_vpc_8c412cea, but intended + 'vpc_id': {'prefix': 'vpc_id', 'key': 'vpc_id', 'parent_group': 'vpcs'}, + } + # -- same-ish as script here -- + group_by = [x.strip().lower() for x in inventory_source.group_by.split(',') if x.strip()] + for choice in self._get_ec2_group_by_choices(): + value = bool((group_by and choice[0] in group_by) or (not group_by and choice[0] != 'instance_id')) + # -- end sameness to script -- + if value: + this_keyed_group = group_by_hostvar.get(choice[0], None) + # If a keyed group syntax does not exist, there is nothing we can do to get this group + if this_keyed_group is not None: + if isinstance(this_keyed_group, list): + keyed_groups.extend(this_keyed_group) + else: + keyed_groups.append(this_keyed_group) + # special case, this parentage is only added if both zones and regions are present + if not group_by or ('region' in group_by and 'availability_zone' in group_by): + keyed_groups.append({'prefix': '', 'separator': '', 'key': 'placement.availability_zone', 'parent_group': '{{ placement.region }}'}) + + source_vars = inventory_source.source_vars_dict + # This is a setting from the script, hopefully no one used it + # if true, it replaces dashes, but not in region / loc names + replace_dash = bool(source_vars.get('replace_dash_in_groups', True)) + # Compatibility content + legacy_regex = { + True: r"[^A-Za-z0-9\_]", + False: r"[^A-Za-z0-9\_\-]" # do not replace dash, dash is allowed + }[replace_dash] + list_replacer = 'map("regex_replace", "{rx}", "_") | list'.format(rx=legacy_regex) + # this option, a plugin option, will allow dashes, but not unicode + # when set to False, unicode will be allowed, but it was not allowed by script + # thus, we always have to use this option, and always use our custom regex + ret['use_contrib_script_compatible_sanitization'] = True + for grouping_data in keyed_groups: + if grouping_data['key'] in ('placement.region', 'placement.availability_zone'): + # us-east-2 is always us-east-2 according to ec2.py + # no sanitization in region-ish groups for the script standards, ever ever + continue + if grouping_data['key'] == 'tags': + # dict jinja2 transformation + grouping_data['key'] = 'dict(tags.keys() | {replacer} | zip(tags.values() | {replacer}))'.format( + replacer=list_replacer + ) + elif grouping_data['key'] == 'tags.keys()' or grouping_data['prefix'] == 'security_group': + # list jinja2 transformation + grouping_data['key'] += ' | {replacer}'.format(replacer=list_replacer) + else: + # string transformation + grouping_data['key'] += ' | regex_replace("{rx}", "_")'.format(rx=legacy_regex) + # end compatibility content + + if source_vars.get('iam_role_arn', None): + ret['iam_role_arn'] = source_vars['iam_role_arn'] + + # This was an allowed ec2.ini option, also plugin option, so pass through + if source_vars.get('boto_profile', None): + ret['boto_profile'] = source_vars['boto_profile'] + + elif not replace_dash: + # Using the plugin, but still want dashes allowed + ret['use_contrib_script_compatible_sanitization'] = True + + if source_vars.get('nested_groups') is False: + for this_keyed_group in keyed_groups: + this_keyed_group.pop('parent_group', None) + + if keyed_groups: + ret['keyed_groups'] = keyed_groups + + # Instance ID not part of compat vars, because of settings.EC2_INSTANCE_ID_VAR + compose_dict = {'ec2_id': 'instance_id'} + inst_filters = {} + + # Compatibility content + compose_dict.update(self._compat_compose_vars()) + # plugin provides "aws_ec2", but not this which the script gave + ret['groups'] = {'ec2': True} + if source_vars.get('hostname_variable') is not None: + hnames = [] + for expr in source_vars.get('hostname_variable').split(','): + if expr == 'public_dns_name': + hnames.append('dns-name') + elif not expr.startswith('tag:') and '_' in expr: + hnames.append(expr.replace('_', '-')) + else: + hnames.append(expr) + ret['hostnames'] = hnames + else: + # public_ip as hostname is non-default plugin behavior, script behavior + ret['hostnames'] = [ + 'network-interface.addresses.association.public-ip', + 'dns-name', + 'private-dns-name' + ] + # The script returned only running state by default, the plugin does not + # https://docs.aws.amazon.com/cli/latest/reference/ec2/describe-instances.html#options + # options: pending | running | shutting-down | terminated | stopping | stopped + inst_filters['instance-state-name'] = ['running'] + # end compatibility content + + if source_vars.get('destination_variable') or source_vars.get('vpc_destination_variable'): + for fd in ('destination_variable', 'vpc_destination_variable'): + if source_vars.get(fd): + compose_dict['ansible_host'] = source_vars.get(fd) + break + + if compose_dict: + ret['compose'] = compose_dict + + if inventory_source.instance_filters: + # logic used to live in ec2.py, now it belongs to us. Yay more code? + filter_sets = [f for f in inventory_source.instance_filters.split(',') if f] + + for instance_filter in filter_sets: + # AND logic not supported, unclear how to... + instance_filter = instance_filter.strip() + if not instance_filter or '=' not in instance_filter: + continue + filter_key, filter_value = [x.strip() for x in instance_filter.split('=', 1)] + if not filter_key: + continue + inst_filters[filter_key] = filter_value + + if inst_filters: + ret['filters'] = inst_filters + + if inventory_source.source_regions and 'all' not in inventory_source.source_regions: + ret['regions'] = inventory_source.source_regions.split(',') + + return ret + + +class gce(PluginFileInjector): + plugin_name = 'gcp_compute' + namespace = 'google' + collection = 'cloud' + + def _compat_compose_vars(self): + # missing: gce_image, gce_uuid + # https://github.com/ansible/ansible/issues/51884 + return { + 'gce_description': 'description if description else None', + 'gce_machine_type': 'machineType', + 'gce_name': 'name', + 'gce_network': 'networkInterfaces[0].network.name', + 'gce_private_ip': 'networkInterfaces[0].networkIP', + 'gce_public_ip': 'networkInterfaces[0].accessConfigs[0].natIP | default(None)', + 'gce_status': 'status', + 'gce_subnetwork': 'networkInterfaces[0].subnetwork.name', + 'gce_tags': 'tags.get("items", [])', + 'gce_zone': 'zone', + 'gce_metadata': 'metadata.get("items", []) | items2dict(key_name="key", value_name="value")', + # NOTE: image hostvar is enabled via retrieve_image_info option + 'gce_image': 'image', + # We need this as long as hostnames is non-default, otherwise hosts + # will not be addressed correctly, was returned in script + 'ansible_ssh_host': 'networkInterfaces[0].accessConfigs[0].natIP | default(networkInterfaces[0].networkIP)' + } + + def inventory_as_dict(self, inventory_source, private_data_dir): + ret = super(gce, self).inventory_as_dict(inventory_source, private_data_dir) + + # auth related items + ret['auth_kind'] = "serviceaccount" + + filters = [] + # TODO: implement gce group_by options + # gce never processed the group_by field, if it had, we would selectively + # apply those options here, but it did not, so all groups are added here + keyed_groups = [ + # the jinja2 syntax is duplicated with compose + # https://github.com/ansible/ansible/issues/51883 + {'prefix': 'network', 'key': 'gce_subnetwork'}, # composed var + {'prefix': '', 'separator': '', 'key': 'gce_private_ip'}, # composed var + {'prefix': '', 'separator': '', 'key': 'gce_public_ip'}, # composed var + {'prefix': '', 'separator': '', 'key': 'machineType'}, + {'prefix': '', 'separator': '', 'key': 'zone'}, + {'prefix': 'tag', 'key': 'gce_tags'}, # composed var + {'prefix': 'status', 'key': 'status | lower'}, + # NOTE: image hostvar is enabled via retrieve_image_info option + {'prefix': '', 'separator': '', 'key': 'image'}, + ] + # This will be used as the gce instance_id, must be universal, non-compat + compose_dict = {'gce_id': 'id'} + + # Compatibility content + # TODO: proper group_by and instance_filters support, irrelevant of compat mode + # The gce.py script never sanitized any names in any way + ret['use_contrib_script_compatible_sanitization'] = True + # Perform extra API query to get the image hostvar + ret['retrieve_image_info'] = True + # Add in old hostvars aliases + compose_dict.update(self._compat_compose_vars()) + # Non-default names to match script + ret['hostnames'] = ['name', 'public_ip', 'private_ip'] + # end compatibility content + + if keyed_groups: + ret['keyed_groups'] = keyed_groups + if filters: + ret['filters'] = filters + if compose_dict: + ret['compose'] = compose_dict + if inventory_source.source_regions and 'all' not in inventory_source.source_regions: + ret['zones'] = inventory_source.source_regions.split(',') + return ret + + +class vmware(PluginFileInjector): + plugin_name = 'vmware_vm_inventory' + namespace = 'community' + collection = 'vmware' + + def inventory_as_dict(self, inventory_source, private_data_dir): + ret = super(vmware, self).inventory_as_dict(inventory_source, private_data_dir) + ret['strict'] = False + # Documentation of props, see + # https://github.com/ansible/ansible/blob/devel/docs/docsite/rst/scenario_guides/vmware_scenarios/vmware_inventory_vm_attributes.rst + UPPERCASE_PROPS = [ + "availableField", + "configIssue", + "configStatus", + "customValue", # optional + "datastore", + "effectiveRole", + "guestHeartbeatStatus", # optional + "layout", # optional + "layoutEx", # optional + "name", + "network", + "overallStatus", + "parentVApp", # optional + "permission", + "recentTask", + "resourcePool", + "rootSnapshot", + "snapshot", # optional + "triggeredAlarmState", + "value" + ] + NESTED_PROPS = [ + "capability", + "config", + "guest", + "runtime", + "storage", + "summary", # repeat of other properties + ] + ret['properties'] = UPPERCASE_PROPS + NESTED_PROPS + ret['compose'] = {'ansible_host': 'guest.ipAddress'} # default value + ret['compose']['ansible_ssh_host'] = ret['compose']['ansible_host'] + # the ansible_uuid was unique every host, every import, from the script + ret['compose']['ansible_uuid'] = '99999999 | random | to_uuid' + for prop in UPPERCASE_PROPS: + if prop == prop.lower(): + continue + ret['compose'][prop.lower()] = prop + ret['with_nested_properties'] = True + # ret['property_name_format'] = 'lower_case' # only dacrystal/topic/vmware-inventory-plugin-property-format + + # process custom options + vmware_opts = dict(inventory_source.source_vars_dict.items()) + if inventory_source.instance_filters: + vmware_opts.setdefault('host_filters', inventory_source.instance_filters) + if inventory_source.group_by: + vmware_opts.setdefault('groupby_patterns', inventory_source.group_by) + + alias_pattern = vmware_opts.get('alias_pattern') + if alias_pattern: + ret.setdefault('hostnames', []) + for alias in alias_pattern.split(','): # make best effort + striped_alias = alias.replace('{', '').replace('}', '').strip() # make best effort + if not striped_alias: + continue + ret['hostnames'].append(striped_alias) + + host_pattern = vmware_opts.get('host_pattern') # not working in script + if host_pattern: + stripped_hp = host_pattern.replace('{', '').replace('}', '').strip() # make best effort + ret['compose']['ansible_host'] = stripped_hp + ret['compose']['ansible_ssh_host'] = stripped_hp + + host_filters = vmware_opts.get('host_filters') + if host_filters: + ret.setdefault('filters', []) + for hf in host_filters.split(','): + striped_hf = hf.replace('{', '').replace('}', '').strip() # make best effort + if not striped_hf: + continue + ret['filters'].append(striped_hf) + else: + # default behavior filters by power state + ret['filters'] = ['runtime.powerState == "poweredOn"'] + + groupby_patterns = vmware_opts.get('groupby_patterns') + ret.setdefault('keyed_groups', []) + if groupby_patterns: + for pattern in groupby_patterns.split(','): + stripped_pattern = pattern.replace('{', '').replace('}', '').strip() # make best effort + ret['keyed_groups'].append({ + 'prefix': '', 'separator': '', + 'key': stripped_pattern + }) + else: + # default groups from script + for entry in ('config.guestId', '"templates" if config.template else "guests"'): + ret['keyed_groups'].append({ + 'prefix': '', 'separator': '', + 'key': entry + }) + + return ret + + +class openstack(PluginFileInjector): + plugin_name = 'openstack' + namespace = 'openstack' + collection = 'cloud' + + def inventory_as_dict(self, inventory_source, private_data_dir): + def use_host_name_for_name(a_bool_maybe): + if not isinstance(a_bool_maybe, bool): + # Could be specified by user via "host" or "uuid" + return a_bool_maybe + elif a_bool_maybe: + return 'name' # plugin default + else: + return 'uuid' + + ret = super(openstack, self).inventory_as_dict(inventory_source, private_data_dir) + ret['fail_on_errors'] = True + ret['expand_hostvars'] = True + ret['inventory_hostname'] = use_host_name_for_name(False) + # Note: mucking with defaults will break import integrity + # For the plugin, we need to use the same defaults as the old script + # or else imports will conflict. To find script defaults you have + # to read source code of the script. + # + # Script Defaults Plugin Defaults + # 'use_hostnames': False, 'name' (True) + # 'expand_hostvars': True, 'no' (False) + # 'fail_on_errors': True, 'no' (False) + # + # These are, yet again, different from ansible_variables in script logic + # but those are applied inconsistently + source_vars = inventory_source.source_vars_dict + for var_name in ['expand_hostvars', 'fail_on_errors']: + if var_name in source_vars: + ret[var_name] = source_vars[var_name] + if 'use_hostnames' in source_vars: + ret['inventory_hostname'] = use_host_name_for_name(source_vars['use_hostnames']) + return ret + + +class rhv(PluginFileInjector): + """ovirt uses the custom credential templating, and that is all + """ + plugin_name = 'ovirt' + initial_version = '2.9' + namespace = 'ovirt' + collection = 'ovirt' + + def inventory_as_dict(self, inventory_source, private_data_dir): + ret = super(rhv, self).inventory_as_dict(inventory_source, private_data_dir) + ret['ovirt_insecure'] = False # Default changed from script + # TODO: process strict option upstream + ret['compose'] = { + 'ansible_host': '(devices.values() | list)[0][0] if devices else None' + } + ret['keyed_groups'] = [] + for key in ('cluster', 'status'): + ret['keyed_groups'].append({'prefix': key, 'separator': '_', 'key': key}) + ret['keyed_groups'].append({'prefix': 'tag', 'separator': '_', 'key': 'tags'}) + ret['ovirt_hostname_preference'] = ['name', 'fqdn'] + source_vars = inventory_source.source_vars_dict + for key, value in source_vars.items(): + if key == 'plugin': + continue + ret[key] = value + return ret + + +class satellite6(PluginFileInjector): + plugin_name = 'foreman' + namespace = 'theforeman' + collection = 'foreman' + + def inventory_as_dict(self, inventory_source, private_data_dir): + ret = super(satellite6, self).inventory_as_dict(inventory_source, private_data_dir) + ret['validate_certs'] = False + + group_patterns = '[]' + group_prefix = 'foreman_' + want_hostcollections = False + want_ansible_ssh_host = False + want_facts = True + + foreman_opts = inventory_source.source_vars_dict.copy() + for k, v in foreman_opts.items(): + if k == 'satellite6_group_patterns' and isinstance(v, str): + group_patterns = v + elif k == 'satellite6_group_prefix' and isinstance(v, str): + group_prefix = v + elif k == 'satellite6_want_hostcollections' and isinstance(v, bool): + want_hostcollections = v + elif k == 'satellite6_want_ansible_ssh_host' and isinstance(v, bool): + want_ansible_ssh_host = v + elif k == 'satellite6_want_facts' and isinstance(v, bool): + want_facts = v + # add backwards support for ssl_verify + # plugin uses new option, validate_certs, instead + elif k == 'ssl_verify' and isinstance(v, bool): + ret['validate_certs'] = v + else: + ret[k] = str(v) + + # Compatibility content + group_by_hostvar = { + "environment": {"prefix": "{}environment_".format(group_prefix), + "separator": "", + "key": "foreman['environment_name'] | lower | regex_replace(' ', '') | " + "regex_replace('[^A-Za-z0-9_]', '_') | regex_replace('none', '')"}, + "location": {"prefix": "{}location_".format(group_prefix), + "separator": "", + "key": "foreman['location_name'] | lower | regex_replace(' ', '') | regex_replace('[^A-Za-z0-9_]', '_')"}, + "organization": {"prefix": "{}organization_".format(group_prefix), + "separator": "", + "key": "foreman['organization_name'] | lower | regex_replace(' ', '') | regex_replace('[^A-Za-z0-9_]', '_')"}, + "lifecycle_environment": {"prefix": "{}lifecycle_environment_".format(group_prefix), + "separator": "", + "key": "foreman['content_facet_attributes']['lifecycle_environment_name'] | " + "lower | regex_replace(' ', '') | regex_replace('[^A-Za-z0-9_]', '_')"}, + "content_view": {"prefix": "{}content_view_".format(group_prefix), + "separator": "", + "key": "foreman['content_facet_attributes']['content_view_name'] | " + "lower | regex_replace(' ', '') | regex_replace('[^A-Za-z0-9_]', '_')"} + } + + ret['legacy_hostvars'] = True # convert hostvar structure to the form used by the script + ret['want_params'] = True + ret['group_prefix'] = group_prefix + ret['want_hostcollections'] = want_hostcollections + ret['want_facts'] = want_facts + + if want_ansible_ssh_host: + ret['compose'] = {'ansible_ssh_host': "foreman['ip6'] | default(foreman['ip'], true)"} + ret['keyed_groups'] = [group_by_hostvar[grouping_name] for grouping_name in group_by_hostvar] + + def form_keyed_group(group_pattern): + """ + Converts foreman group_pattern to + inventory plugin keyed_group + + e.g. {app_param}-{tier_param}-{dc_param} + becomes + "%s-%s-%s" | format(app_param, tier_param, dc_param) + """ + if type(group_pattern) is not str: + return None + params = re.findall('{[^}]*}', group_pattern) + if len(params) == 0: + return None + + param_names = [] + for p in params: + param_names.append(p[1:-1].strip()) # strip braces and space + + # form keyed_group key by + # replacing curly braces with '%s' + # (for use with jinja's format filter) + key = group_pattern + for p in params: + key = key.replace(p, '%s', 1) + + # apply jinja filter to key + key = '"{}" | format({})'.format(key, ', '.join(param_names)) + + keyed_group = {'key': key, + 'separator': ''} + return keyed_group + + try: + group_patterns = json.loads(group_patterns) + + if type(group_patterns) is list: + for group_pattern in group_patterns: + keyed_group = form_keyed_group(group_pattern) + if keyed_group: + ret['keyed_groups'].append(keyed_group) + except json.JSONDecodeError: + logger.warning('Could not parse group_patterns. Expected JSON-formatted string, found: {}' + .format(group_patterns)) + + return ret + + +class tower(PluginFileInjector): + plugin_name = 'tower' + namespace = 'awx' + collection = 'awx' + + def inventory_as_dict(self, inventory_source, private_data_dir): + ret = super(tower, self).inventory_as_dict(inventory_source, private_data_dir) + # Credentials injected as env vars, same as script + try: + # plugin can take an actual int type + identifier = int(inventory_source.instance_filters) + except ValueError: + # inventory_id could be a named URL + identifier = iri_to_uri(inventory_source.instance_filters) + ret['inventory_id'] = identifier + ret['include_metadata'] = True # used for license check + return ret + + +for cls in PluginFileInjector.__subclasses__(): + FrozenInjectors[cls.__name__] = cls diff --git a/awx/main/migrations/_rbac.py b/awx/main/migrations/_rbac.py index 9b85c710868a..0b1f81953cd6 100644 --- a/awx/main/migrations/_rbac.py +++ b/awx/main/migrations/_rbac.py @@ -1,6 +1,9 @@ import logging from time import time +from django.db.models import Subquery, OuterRef, F + +from awx.main.fields import update_role_parentage_for_instance from awx.main.models.rbac import Role, batch_role_ancestor_rebuilding logger = logging.getLogger('rbac_migrations') @@ -10,11 +13,11 @@ def create_roles(apps, schema_editor): ''' Implicit role creation happens in our post_save hook for all of our resources. Here we iterate through all of our resource types and call - .save() to ensure all that happens for every object in the system before we - get busy with the actual migration work. + .save() to ensure all that happens for every object in the system. - This gets run after migrate_users, which does role creation for users a - little differently. + This can be used whenever new roles are introduced in a migration to + create those roles for pre-existing objects that did not previously + have them created via signals. ''' models = [ @@ -35,7 +38,189 @@ def create_roles(apps, schema_editor): obj.save() +def delete_all_user_roles(apps, schema_editor): + ContentType = apps.get_model('contenttypes', "ContentType") + Role = apps.get_model('main', "Role") + User = apps.get_model('auth', "User") + user_content_type = ContentType.objects.get_for_model(User) + for role in Role.objects.filter(content_type=user_content_type).iterator(): + role.delete() + + +UNIFIED_ORG_LOOKUPS = { + # Job Templates had an implicit organization via their project + 'jobtemplate': 'project', + # Inventory Sources had an implicit organization via their inventory + 'inventorysource': 'inventory', + # Projects had an explicit organization in their subclass table + 'project': None, + # Workflow JTs also had an explicit organization in their subclass table + 'workflowjobtemplate': None, + # Jobs inherited project from job templates as a convenience field + 'job': 'project', + # Inventory Sources had an convenience field of inventory + 'inventoryupdate': 'inventory', + # Project Updates did not have a direct organization field, obtained it from project + 'projectupdate': 'project', + # Workflow Jobs are handled same as project updates + # Sliced jobs are a special case, but old data is not given special treatment for simplicity + 'workflowjob': 'workflow_job_template', + # AdHocCommands do not have a template, but still migrate them + 'adhoccommand': 'inventory' +} + + +def implicit_org_subquery(UnifiedClass, cls, backward=False): + """Returns a subquery that returns the so-called organization for objects + in the class in question, before migration to the explicit unified org field. + In some cases, this can still be applied post-migration. + """ + if cls._meta.model_name not in UNIFIED_ORG_LOOKUPS: + return None + cls_name = cls._meta.model_name + source_field = UNIFIED_ORG_LOOKUPS[cls_name] + + unified_field = UnifiedClass._meta.get_field(cls_name) + unified_ptr = unified_field.remote_field.name + if backward: + qs = UnifiedClass.objects.filter(**{cls_name: OuterRef('id')}).order_by().values_list('tmp_organization')[:1] + elif source_field is None: + qs = cls.objects.filter(**{unified_ptr: OuterRef('id')}).order_by().values_list('organization')[:1] + else: + intermediary_field = cls._meta.get_field(source_field) + intermediary_model = intermediary_field.related_model + intermediary_reverse_rel = intermediary_field.remote_field.name + qs = intermediary_model.objects.filter(**{ + # this filter leverages the fact that the Unified models have same pk as subclasses. + # For instance... filters projects used in job template, where that job template + # has same id same as UJT from the outer reference (which it does) + intermediary_reverse_rel: OuterRef('id')} + ).order_by().values_list('organization')[:1] + return Subquery(qs) + + +def _migrate_unified_organization(apps, unified_cls_name, backward=False): + """Given a unified base model (either UJT or UJ) + and a dict org_field_mapping which gives related model to get org from + saves organization for those objects to the temporary migration + variable tmp_organization on the unified model + (optimized method) + """ + start = time() + UnifiedClass = apps.get_model('main', unified_cls_name) + ContentType = apps.get_model('contenttypes', 'ContentType') + + for cls in UnifiedClass.__subclasses__(): + cls_name = cls._meta.model_name + if backward and UNIFIED_ORG_LOOKUPS.get(cls_name, 'not-found') is not None: + logger.debug('Not reverse migrating {}, existing data should remain valid'.format(cls_name)) + continue + logger.debug('{}Migrating {} to new organization field'.format('Reverse ' if backward else '', cls_name)) + + sub_qs = implicit_org_subquery(UnifiedClass, cls, backward=backward) + if sub_qs is None: + logger.debug('Class {} has no organization migration'.format(cls_name)) + continue + + this_ct = ContentType.objects.get_for_model(cls) + if backward: + r = cls.objects.order_by().update(organization=sub_qs) + else: + r = UnifiedClass.objects.order_by().filter(polymorphic_ctype=this_ct).update(tmp_organization=sub_qs) + if r: + logger.info('Organization migration on {} affected {} rows.'.format(cls_name, r)) + logger.info('Unified organization migration completed in {:.4f} seconds'.format(time() - start)) + + +def migrate_ujt_organization(apps, schema_editor): + '''Move organization field to UJT and UJ models''' + _migrate_unified_organization(apps, 'UnifiedJobTemplate') + _migrate_unified_organization(apps, 'UnifiedJob') + + +def migrate_ujt_organization_backward(apps, schema_editor): + '''Move organization field from UJT and UJ models back to their original places''' + _migrate_unified_organization(apps, 'UnifiedJobTemplate', backward=True) + _migrate_unified_organization(apps, 'UnifiedJob', backward=True) + + +def _restore_inventory_admins(apps, schema_editor, backward=False): + """With the JT.organization changes, admins of organizations connected to + job templates via inventory will have their permissions demoted. + This maintains current permissions over the migration by granting the + permissions they used to have explicitly on the JT itself. + """ + start = time() + JobTemplate = apps.get_model('main', 'JobTemplate') + User = apps.get_model('auth', 'User') + changed_ct = 0 + jt_qs = JobTemplate.objects.filter(inventory__isnull=False) + jt_qs = jt_qs.exclude(inventory__organization=F('project__organization')) + jt_qs = jt_qs.only('id', 'admin_role_id', 'execute_role_id', 'inventory_id') + for jt in jt_qs.iterator(): + org = jt.inventory.organization + for jt_role, org_roles in ( + ('admin_role', ('admin_role', 'job_template_admin_role',)), + ('execute_role', ('execute_role',)) + ): + role_id = getattr(jt, '{}_id'.format(jt_role)) + + user_qs = User.objects + if not backward: + # In this specific case, the name for the org role and JT roles were the same + org_role_ids = [getattr(org, '{}_id'.format(role_name)) for role_name in org_roles] + user_qs = user_qs.filter(roles__in=org_role_ids) + # bizarre migration behavior - ancestors / descendents of + # migration version of Role model is reversed, using current model briefly + ancestor_ids = list( + Role.objects.filter(descendents=role_id).values_list('id', flat=True) + ) + # same as Role.__contains__, filter for "user in jt.admin_role" + user_qs = user_qs.exclude(roles__in=ancestor_ids) + else: + # use the database to filter intersection of users without access + # to the JT role and either organization role + user_qs = user_qs.filter(roles__in=[org.admin_role_id, org.execute_role_id]) + # in reverse, intersection of users who have both + user_qs = user_qs.filter(roles=role_id) + + user_ids = list(user_qs.values_list('id', flat=True)) + if not user_ids: + continue + + role = getattr(jt, jt_role) + logger.debug('{} {} on jt {} for users {} via inventory.organization {}'.format( + 'Removing' if backward else 'Setting', + jt_role, jt.pk, user_ids, org.pk + )) + if not backward: + # in reverse, explit role becomes redundant + role.members.add(*user_ids) + else: + role.members.remove(*user_ids) + changed_ct += len(user_ids) + + if changed_ct: + logger.info('{} explicit JT permission for {} users in {:.4f} seconds'.format( + 'Removed' if backward else 'Added', + changed_ct, time() - start + )) + + +def restore_inventory_admins(apps, schema_editor): + _restore_inventory_admins(apps, schema_editor) + + +def restore_inventory_admins_backward(apps, schema_editor): + _restore_inventory_admins(apps, schema_editor, backward=True) + + def rebuild_role_hierarchy(apps, schema_editor): + ''' + This should be called in any migration when ownerships are changed. + Ex. I remove a user from the admin_role of a credential. + Ancestors are cached from parents for performance, this re-computes ancestors. + ''' logger.info('Computing role roots..') start = time() roots = Role.objects \ @@ -46,14 +231,74 @@ def rebuild_role_hierarchy(apps, schema_editor): start = time() Role.rebuild_role_ancestor_list(roots, []) stop = time() - logger.info('Rebuild completed in %f seconds' % (stop - start)) + logger.info('Rebuild ancestors completed in %f seconds' % (stop - start)) logger.info('Done.') -def delete_all_user_roles(apps, schema_editor): +def rebuild_role_parentage(apps, schema_editor, models=None): + ''' + This should be called in any migration when any parent_role entry + is modified so that the cached parent fields will be updated. Ex: + foo_role = ImplicitRoleField( + parent_role=['bar_role'] # change to parent_role=['admin_role'] + ) + + This is like rebuild_role_hierarchy, but that method updates ancestors, + whereas this method updates parents. + ''' + start = time() + seen_models = set() + model_ct = 0 + noop_ct = 0 ContentType = apps.get_model('contenttypes', "ContentType") - Role = apps.get_model('main', "Role") - User = apps.get_model('auth', "User") - user_content_type = ContentType.objects.get_for_model(User) - for role in Role.objects.filter(content_type=user_content_type).iterator(): - role.delete() + additions = set() + removals = set() + + role_qs = Role.objects + if models: + # update_role_parentage_for_instance is expensive + # if the models have been downselected, ignore those which are not in the list + ct_ids = list(ContentType.objects.filter( + model__in=[name.lower() for name in models] + ).values_list('id', flat=True)) + role_qs = role_qs.filter(content_type__in=ct_ids) + + for role in role_qs.iterator(): + if not role.object_id: + continue + model_tuple = (role.content_type_id, role.object_id) + if model_tuple in seen_models: + continue + seen_models.add(model_tuple) + + # The GenericForeignKey does not work right in migrations + # with the usage as role.content_object + # so we do the lookup ourselves with current migration models + ct = role.content_type + app = ct.app_label + ct_model = apps.get_model(app, ct.model) + content_object = ct_model.objects.get(pk=role.object_id) + + parents_added, parents_removed = update_role_parentage_for_instance(content_object) + additions.update(parents_added) + removals.update(parents_removed) + if parents_added: + model_ct += 1 + logger.debug('Added to parents of roles {} of {}'.format(parents_added, content_object)) + if parents_removed: + model_ct += 1 + logger.debug('Removed from parents of roles {} of {}'.format(parents_removed, content_object)) + else: + noop_ct += 1 + + logger.debug('No changes to role parents for {} resources'.format(noop_ct)) + logger.debug('Added parents to {} roles'.format(len(additions))) + logger.debug('Removed parents from {} roles'.format(len(removals))) + if model_ct: + logger.info('Updated implicit parents of {} resources'.format(model_ct)) + + logger.info('Rebuild parentage completed in %f seconds' % (time() - start)) + + # this is ran because the ordinary signals for + # Role.parents.add and Role.parents.remove not called in migration + Role.rebuild_role_ancestor_list(list(additions), list(removals)) diff --git a/awx/main/models/__init__.py b/awx/main/models/__init__.py index 2dbe95951117..87fa5d791f33 100644 --- a/awx/main/models/__init__.py +++ b/awx/main/models/__init__.py @@ -3,6 +3,7 @@ # Django from django.conf import settings # noqa +from django.db import connection from django.db.models.signals import pre_delete # noqa # AWX @@ -58,7 +59,6 @@ WorkflowJob, WorkflowJobNode, WorkflowJobOptions, WorkflowJobTemplate, WorkflowJobTemplateNode, WorkflowApproval, WorkflowApprovalTemplate, ) -from awx.main.models.channels import ChannelGroup # noqa from awx.api.versioning import reverse from awx.main.models.oauth import ( # noqa OAuth2AccessToken, OAuth2Application @@ -80,6 +80,33 @@ User.add_to_class('accessible_objects', user_accessible_objects) +def enforce_bigint_pk_migration(): + # + # NOTE: this function is not actually in use anymore, + # but has been intentionally kept for historical purposes, + # and to serve as an illustration if we ever need to perform + # bulk modification/migration of event data in the future. + # + # see: https://github.com/ansible/awx/issues/6010 + # look at all the event tables and verify that they have been fully migrated + # from the *old* int primary key table to the replacement bigint table + # if not, attempt to migrate them in the background + # + for tblname in ( + 'main_jobevent', 'main_inventoryupdateevent', + 'main_projectupdateevent', 'main_adhoccommandevent', + 'main_systemjobevent' + ): + with connection.cursor() as cursor: + cursor.execute( + 'SELECT 1 FROM information_schema.tables WHERE table_name=%s', + (f'_old_{tblname}',) + ) + if bool(cursor.rowcount): + from awx.main.tasks import migrate_legacy_event_data + migrate_legacy_event_data.apply_async([tblname]) + + def cleanup_created_modified_by(sender, **kwargs): # work around a bug in django-polymorphic that doesn't properly # handle cascades for reverse foreign keys on the polymorphic base model @@ -107,9 +134,15 @@ def user_get_auditor_of_organizations(user): return Organization.objects.filter(auditor_role__members=user) +@property +def created(user): + return user.date_joined + + User.add_to_class('organizations', user_get_organizations) User.add_to_class('admin_of_organizations', user_get_admin_of_organizations) User.add_to_class('auditor_of_organizations', user_get_auditor_of_organizations) +User.add_to_class('created', created) @property diff --git a/awx/main/models/base.py b/awx/main/models/base.py index 915b18977f35..8fb8e2d78218 100644 --- a/awx/main/models/base.py +++ b/awx/main/models/base.py @@ -15,6 +15,7 @@ # AWX from awx.main.utils import encrypt_field, parse_yaml_or_json +from awx.main.constants import CLOUD_PROVIDERS __all__ = ['prevent_search', 'VarsDictProperty', 'BaseModel', 'CreatedModifiedModel', 'PasswordFieldsModel', 'PrimordialModel', 'CommonModel', @@ -50,7 +51,7 @@ (PERM_INVENTORY_CHECK, _('Check')), ] -CLOUD_INVENTORY_SOURCES = ['ec2', 'vmware', 'gce', 'azure_rm', 'openstack', 'rhv', 'custom', 'satellite6', 'cloudforms', 'scm', 'tower',] +CLOUD_INVENTORY_SOURCES = list(CLOUD_PROVIDERS) + ['scm', 'custom'] VERBOSITY_CHOICES = [ (0, '0 (Normal)'), @@ -406,7 +407,7 @@ class AuthToken(BaseModel): sensitive_data = prevent_search(models.CharField(...)) The flag set by this function is used by - `awx.api.filters.FieldLookupBackend` to blacklist fields and relations that + `awx.api.filters.FieldLookupBackend` to block fields and relations that should not be searchable/filterable via search query params """ setattr(relation, '__prevent_search__', True) diff --git a/awx/main/models/channels.py b/awx/main/models/channels.py deleted file mode 100644 index bd4f9514ba38..000000000000 --- a/awx/main/models/channels.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.db import models - - -class ChannelGroup(models.Model): - group = models.CharField(max_length=200, unique=True) - channels = models.TextField() diff --git a/awx/main/models/credential/__init__.py b/awx/main/models/credential/__init__.py index 0e0f50e5b3c4..e8a288408345 100644 --- a/awx/main/models/credential/__init__.py +++ b/awx/main/models/credential/__init__.py @@ -11,7 +11,7 @@ from types import SimpleNamespace # Jinja2 -from jinja2 import Template +from jinja2 import sandbox # Django from django.db import models @@ -96,6 +96,10 @@ class Meta: help_text=_('Specify the type of credential you want to create. Refer ' 'to the Ansible Tower documentation for details on each type.') ) + managed_by_tower = models.BooleanField( + default=False, + editable=False + ) organization = models.ForeignKey( 'Organization', null=True, @@ -331,6 +335,7 @@ class Meta: ('insights', _('Insights')), ('external', _('External')), ('kubernetes', _('Kubernetes')), + ('galaxy', _('Galaxy/Automation Hub')), ) kind = models.CharField( @@ -514,8 +519,11 @@ class TowerNamespace: # If any file templates are provided, render the files and update the # special `tower` template namespace so the filename can be # referenced in other injectors + + sandbox_env = sandbox.ImmutableSandboxedEnvironment() + for file_label, file_tmpl in file_tmpls.items(): - data = Template(file_tmpl).render(**namespace) + data = sandbox_env.from_string(file_tmpl).render(**namespace) _, path = tempfile.mkstemp(dir=private_data_dir) with open(path, 'w') as f: f.write(data) @@ -537,14 +545,14 @@ class TowerNamespace: except ValidationError as e: logger.error('Ignoring prohibited env var {}, reason: {}'.format(env_var, e)) continue - env[env_var] = Template(tmpl).render(**namespace) - safe_env[env_var] = Template(tmpl).render(**safe_namespace) + env[env_var] = sandbox_env.from_string(tmpl).render(**namespace) + safe_env[env_var] = sandbox_env.from_string(tmpl).render(**safe_namespace) if 'INVENTORY_UPDATE_ID' not in env: # awx-manage inventory_update does not support extra_vars via -e extra_vars = {} for var_name, tmpl in self.injectors.get('extra_vars', {}).items(): - extra_vars[var_name] = Template(tmpl).render(**namespace) + extra_vars[var_name] = sandbox_env.from_string(tmpl).render(**namespace) def build_extra_vars_file(vars, private_dir): handle, path = tempfile.mkstemp(dir = private_dir) @@ -799,6 +807,10 @@ def create(self): 'id': 'project', 'label': ugettext_noop('Project (Tenant Name)'), 'type': 'string', + }, { + 'id': 'project_domain_name', + 'label': ugettext_noop('Project (Domain Name)'), + 'type': 'string', }, { 'id': 'domain', 'label': ugettext_noop('Domain Name'), @@ -807,6 +819,11 @@ def create(self): 'It is only needed for Keystone v3 authentication ' 'URLs. Refer to Ansible Tower documentation for ' 'common scenarios.') + }, { + 'id': 'region', + 'label': ugettext_noop('Region Name'), + 'type': 'string', + 'help_text': ugettext_noop('For some cloud providers, like OVH, region must be specified'), }, { 'id': 'verify_ssl', 'label': ugettext_noop('Verify SSL'), @@ -869,33 +886,6 @@ def create(self): } ) -ManagedCredentialType( - namespace='cloudforms', - kind='cloud', - name=ugettext_noop('Red Hat CloudForms'), - managed_by_tower=True, - inputs={ - 'fields': [{ - 'id': 'host', - 'label': ugettext_noop('CloudForms URL'), - 'type': 'string', - 'help_text': ugettext_noop('Enter the URL for the virtual machine that ' - 'corresponds to your CloudForms instance. ' - 'For example, https://cloudforms.example.org') - }, { - 'id': 'username', - 'label': ugettext_noop('Username'), - 'type': 'string' - }, { - 'id': 'password', - 'label': ugettext_noop('Password'), - 'type': 'string', - 'secret': True, - }], - 'required': ['host', 'username', 'password'], - } -) - ManagedCredentialType( namespace='gce', kind='cloud', @@ -1099,26 +1089,36 @@ def create(self): }, { 'id': 'username', 'label': ugettext_noop('Username'), - 'type': 'string' + 'type': 'string', + 'help_text': ugettext_noop('The Ansible Tower user to authenticate as.' + 'This should not be set if an OAuth token is being used.') }, { 'id': 'password', 'label': ugettext_noop('Password'), 'type': 'string', 'secret': True, + }, { + 'id': 'oauth_token', + 'label': ugettext_noop('OAuth Token'), + 'type': 'string', + 'secret': True, + 'help_text': ugettext_noop('An OAuth token to use to authenticate to Tower with.' + 'This should not be set if username/password are being used.') }, { 'id': 'verify_ssl', 'label': ugettext_noop('Verify SSL'), 'type': 'boolean', 'secret': False }], - 'required': ['host', 'username', 'password'], + 'required': ['host'], }, injectors={ 'env': { 'TOWER_HOST': '{{host}}', 'TOWER_USERNAME': '{{username}}', 'TOWER_PASSWORD': '{{password}}', - 'TOWER_VERIFY_SSL': '{{verify_ssl}}' + 'TOWER_VERIFY_SSL': '{{verify_ssl}}', + 'TOWER_OAUTH_TOKEN': '{{oauth_token}}' } }, ) @@ -1136,7 +1136,7 @@ def create(self): 'help_text': ugettext_noop('The OpenShift or Kubernetes API Endpoint to authenticate with.') },{ 'id': 'bearer_token', - 'label': ugettext_noop('API authentication bearer token.'), + 'label': ugettext_noop('API authentication bearer token'), 'type': 'string', 'secret': True, },{ @@ -1156,6 +1156,38 @@ def create(self): ) +ManagedCredentialType( + namespace='galaxy_api_token', + kind='galaxy', + name=ugettext_noop('Ansible Galaxy/Automation Hub API Token'), + inputs={ + 'fields': [{ + 'id': 'url', + 'label': ugettext_noop('Galaxy Server URL'), + 'type': 'string', + 'help_text': ugettext_noop('The URL of the Galaxy instance to connect to.') + },{ + 'id': 'auth_url', + 'label': ugettext_noop('Auth Server URL'), + 'type': 'string', + 'help_text': ugettext_noop( + 'The URL of a Keycloak server token_endpoint, if using ' + 'SSO auth.' + ) + },{ + 'id': 'token', + 'label': ugettext_noop('API Token'), + 'type': 'string', + 'secret': True, + 'help_text': ugettext_noop( + 'A token to use for authentication against the Galaxy instance.' + ) + }], + 'required': ['url'], + } +) + + class CredentialInputSource(PrimordialModel): class Meta: diff --git a/awx/main/models/credential/injectors.py b/awx/main/models/credential/injectors.py index cdf193f8e966..ef30b9194540 100644 --- a/awx/main/models/credential/injectors.py +++ b/awx/main/models/credential/injectors.py @@ -77,9 +77,12 @@ def _openstack_data(cred): username=cred.get_input('username', default=''), password=cred.get_input('password', default=''), project_name=cred.get_input('project', default='')) + if cred.has_input('project_domain_name'): + openstack_auth['project_domain_name'] = cred.get_input('project_domain_name', default='') if cred.has_input('domain'): openstack_auth['domain_name'] = cred.get_input('domain', default='') verify_state = cred.get_input('verify_ssl', default=True) + openstack_data = { 'clouds': { 'devstack': { @@ -88,6 +91,10 @@ def _openstack_data(cred): }, }, } + + if cred.has_input('project_region_name'): + openstack_data['clouds']['devstack']['region_name'] = cred.get_input('project_region_name', default='') + return openstack_data @@ -99,3 +106,17 @@ def openstack(cred, env, private_data_dir): f.close() os.chmod(path, stat.S_IRUSR | stat.S_IWUSR) env['OS_CLIENT_CONFIG_FILE'] = path + + +def kubernetes_bearer_token(cred, env, private_data_dir): + env['K8S_AUTH_HOST'] = cred.get_input('host', default='') + env['K8S_AUTH_API_KEY'] = cred.get_input('bearer_token', default='') + if cred.get_input('verify_ssl') and 'ssl_ca_cert' in cred.inputs: + env['K8S_AUTH_VERIFY_SSL'] = 'True' + handle, path = tempfile.mkstemp(dir=private_data_dir) + with os.fdopen(handle, 'w') as f: + os.chmod(path, stat.S_IRUSR | stat.S_IWUSR) + f.write(cred.get_input('ssl_ca_cert')) + env['K8S_AUTH_SSL_CA_CERT'] = path + else: + env['K8S_AUTH_VERIFY_SSL'] = 'False' diff --git a/awx/main/models/events.py b/awx/main/models/events.py index 6b5fc10405ae..90cc6f60946f 100644 --- a/awx/main/models/events.py +++ b/awx/main/models/events.py @@ -4,10 +4,12 @@ import logging from collections import defaultdict -from django.db import models, DatabaseError +from django.conf import settings +from django.core.exceptions import ObjectDoesNotExist +from django.db import models, DatabaseError, connection from django.utils.dateparse import parse_datetime from django.utils.text import Truncator -from django.utils.timezone import utc +from django.utils.timezone import utc, now from django.utils.translation import ugettext_lazy as _ from django.utils.encoding import force_text @@ -57,7 +59,18 @@ def create_host_status_counts(event_data): return dict(host_status_counts) +MINIMAL_EVENTS = set([ + 'playbook_on_play_start', 'playbook_on_task_start', + 'playbook_on_stats', 'EOF' +]) + + def emit_event_detail(event): + if ( + settings.UI_LIVE_UPDATES_ENABLED is False and + event.event not in MINIMAL_EVENTS + ): + return cls = event.__class__ relation = { JobEvent: 'job_id', @@ -337,30 +350,43 @@ def _update_from_event_data(self): pass if isinstance(self, JobEvent): - hostnames = self._hostnames() - self._update_host_summary_from_stats(hostnames) - if self.job.inventory: - try: - self.job.inventory.update_computed_fields() - except DatabaseError: - logger.exception('Computed fields database error saving event {}'.format(self.pk)) + try: + job = self.job + except ObjectDoesNotExist: + job = None + if job: + hostnames = self._hostnames() + self._update_host_summary_from_stats(set(hostnames)) + if job.inventory: + try: + job.inventory.update_computed_fields() + except DatabaseError: + logger.exception('Computed fields database error saving event {}'.format(self.pk)) + + # find parent links and progagate changed=T and failed=T + changed = job.job_events.filter(changed=True).exclude(parent_uuid=None).only('parent_uuid').values_list('parent_uuid', flat=True).distinct() # noqa + failed = job.job_events.filter(failed=True).exclude(parent_uuid=None).only('parent_uuid').values_list('parent_uuid', flat=True).distinct() # noqa + + JobEvent.objects.filter( + job_id=self.job_id, uuid__in=changed + ).update(changed=True) + JobEvent.objects.filter( + job_id=self.job_id, uuid__in=failed + ).update(failed=True) + + # send success/failure notifications when we've finished handling the playbook_on_stats event + from awx.main.tasks import handle_success_and_failure_notifications # circular import + + def _send_notifications(): + handle_success_and_failure_notifications.apply_async([job.id]) + connection.on_commit(_send_notifications) - # find parent links and progagate changed=T and failed=T - changed = self.job.job_events.filter(changed=True).exclude(parent_uuid=None).only('parent_uuid').values_list('parent_uuid', flat=True).distinct() # noqa - failed = self.job.job_events.filter(failed=True).exclude(parent_uuid=None).only('parent_uuid').values_list('parent_uuid', flat=True).distinct() # noqa - - JobEvent.objects.filter( - job_id=self.job_id, uuid__in=changed - ).update(changed=True) - JobEvent.objects.filter( - job_id=self.job_id, uuid__in=failed - ).update(failed=True) for field in ('playbook', 'play', 'task', 'role'): value = force_text(event_data.get(field, '')).strip() if value != getattr(self, field): setattr(self, field, value) - if isinstance(self, JobEvent): + if settings.LOG_AGGREGATOR_ENABLED: analytics_logger.info( 'Event data saved.', extra=dict(python_objects=dict(job_event=self)) @@ -400,11 +426,14 @@ def create_from_data(cls, **kwargs): except (KeyError, ValueError): kwargs.pop('created', None) + host_map = kwargs.pop('host_map', {}) + sanitize_event_keys(kwargs, cls.VALID_KEYS) workflow_job_id = kwargs.pop('workflow_job_id', None) event = cls(**kwargs) if workflow_job_id: setattr(event, 'workflow_job_id', workflow_job_id) + setattr(event, 'host_map', host_map) event._update_from_event_data() return event @@ -431,6 +460,7 @@ class Meta: ('job', 'parent_uuid'), ] + id = models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID') job = models.ForeignKey( 'Job', related_name='job_events', @@ -450,19 +480,6 @@ class Meta: default='', editable=False, ) - hosts = models.ManyToManyField( - 'Host', - related_name='job_events', - editable=False, - ) - parent = models.ForeignKey( - 'self', - related_name='children', - null=True, - default=None, - on_delete=models.SET_NULL, - editable=False, - ) parent_uuid = models.CharField( max_length=1024, default='', @@ -486,32 +503,62 @@ def _hostnames(self): def _update_host_summary_from_stats(self, hostnames): with ignore_inventory_computed_fields(): - if not self.job or not self.job.inventory: + try: + if not self.job or not self.job.inventory: + logger.info('Event {} missing job or inventory, host summaries not updated'.format(self.pk)) + return + except ObjectDoesNotExist: logger.info('Event {} missing job or inventory, host summaries not updated'.format(self.pk)) return - qs = self.job.inventory.hosts.filter(name__in=hostnames) job = self.job + + from awx.main.models import Host, JobHostSummary # circular import + all_hosts = Host.objects.filter( + pk__in=self.host_map.values() + ).only('id') + existing_host_ids = set(h.id for h in all_hosts) + + summaries = dict() for host in hostnames: + host_id = self.host_map.get(host, None) + if host_id not in existing_host_ids: + host_id = None host_stats = {} for stat in ('changed', 'dark', 'failures', 'ignored', 'ok', 'processed', 'rescued', 'skipped'): try: host_stats[stat] = self.event_data.get(stat, {}).get(host, 0) except AttributeError: # in case event_data[stat] isn't a dict. pass - if qs.filter(name=host).exists(): - host_actual = qs.get(name=host) - host_summary, created = job.job_host_summaries.get_or_create(host=host_actual, host_name=host_actual.name, defaults=host_stats) - else: - host_summary, created = job.job_host_summaries.get_or_create(host_name=host, defaults=host_stats) + summary = JobHostSummary( + created=now(), modified=now(), job_id=job.id, host_id=host_id, host_name=host, **host_stats + ) + summary.failed = bool(summary.dark or summary.failures) + summaries[(host_id, host)] = summary + + JobHostSummary.objects.bulk_create(summaries.values()) + + # update the last_job_id and last_job_host_summary_id + # in single queries + host_mapping = dict( + (summary['host_id'], summary['id']) + for summary in JobHostSummary.objects.filter(job_id=job.id).values('id', 'host_id') + ) + updated_hosts = set() + for h in all_hosts: + # if the hostname *shows up* in the playbook_on_stats event + if h.name in hostnames: + h.last_job_id = job.id + updated_hosts.add(h) + if h.id in host_mapping: + h.last_job_host_summary_id = host_mapping[h.id] + updated_hosts.add(h) + + Host.objects.bulk_update( + list(updated_hosts), + ['last_job_id', 'last_job_host_summary_id'], + batch_size=100 + ) - if not created: - update_fields = [] - for stat, value in host_stats.items(): - if getattr(host_summary, stat) != value: - setattr(host_summary, stat, value) - update_fields.append(stat) - if update_fields: - host_summary.save(update_fields=update_fields) @property def job_verbosity(self): @@ -532,6 +579,7 @@ class Meta: ('project_update', 'end_line'), ] + id = models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID') project_update = models.ForeignKey( 'ProjectUpdate', related_name='project_update_events', @@ -617,6 +665,7 @@ def create_from_data(cls, **kwargs): kwargs.pop('created', None) sanitize_event_keys(kwargs, cls.VALID_KEYS) + kwargs.pop('workflow_job_id', None) event = cls(**kwargs) event._update_from_event_data() return event @@ -682,6 +731,7 @@ class Meta: FAILED_EVENTS = [x[0] for x in EVENT_TYPES if x[2]] EVENT_CHOICES = [(x[0], x[1]) for x in EVENT_TYPES] + id = models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID') event = models.CharField( max_length=100, choices=EVENT_CHOICES, @@ -744,6 +794,7 @@ class Meta: ('inventory_update', 'end_line'), ] + id = models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID') inventory_update = models.ForeignKey( 'InventoryUpdate', related_name='inventory_update_events', @@ -777,6 +828,7 @@ class Meta: ('system_job', 'end_line'), ] + id = models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID') system_job = models.ForeignKey( 'SystemJob', related_name='system_job_events', diff --git a/awx/main/models/ha.py b/awx/main/models/ha.py index acc529a29a71..50717866535b 100644 --- a/awx/main/models/ha.py +++ b/awx/main/models/ha.py @@ -12,6 +12,7 @@ from django.conf import settings from django.utils.timezone import now, timedelta +import redis from solo.models import SingletonModel from awx import __version__ as awx_application_version @@ -23,7 +24,7 @@ from awx.main.utils import get_cpu_capacity, get_mem_capacity, get_system_task_capacity from awx.main.models.mixins import RelatedJobsMixin -__all__ = ('Instance', 'InstanceGroup', 'TowerScheduleState', 'TowerAnalyticsState') +__all__ = ('Instance', 'InstanceGroup', 'TowerScheduleState') class HasPolicyEditsMixin(HasEditsMixin): @@ -53,6 +54,13 @@ class Instance(HasPolicyEditsMixin, BaseModel): uuid = models.CharField(max_length=40) hostname = models.CharField(max_length=250, unique=True) + ip_address = models.CharField( + blank=True, + null=True, + default=None, + max_length=50, + unique=True, + ) created = models.DateTimeField(auto_now_add=True) modified = models.DateTimeField(auto_now=True) last_isolated_check = models.DateTimeField( @@ -145,6 +153,14 @@ def refresh_capacity(self): self.capacity = get_system_task_capacity(self.capacity_adjustment) else: self.capacity = 0 + + try: + # if redis is down for some reason, that means we can't persist + # playbook event data; we should consider this a zero capacity event + redis.Redis.from_url(settings.BROKER_URL).ping() + except redis.ConnectionError: + self.capacity = 0 + self.cpu = cpu[0] self.memory = mem[0] self.cpu_capacity = cpu[1] @@ -245,18 +261,20 @@ class Meta: app_label = 'main' - def fit_task_to_most_remaining_capacity_instance(self, task): + @staticmethod + def fit_task_to_most_remaining_capacity_instance(task, instances): instance_most_capacity = None - for i in self.instances.filter(capacity__gt=0, enabled=True).order_by('hostname'): + for i in instances: if i.remaining_capacity >= task.task_impact and \ (instance_most_capacity is None or i.remaining_capacity > instance_most_capacity.remaining_capacity): instance_most_capacity = i return instance_most_capacity - def find_largest_idle_instance(self): + @staticmethod + def find_largest_idle_instance(instances): largest_instance = None - for i in self.instances.filter(capacity__gt=0, enabled=True).order_by('hostname'): + for i in instances: if i.jobs_running == 0: if largest_instance is None: largest_instance = i @@ -280,10 +298,6 @@ class TowerScheduleState(SingletonModel): schedule_last_run = models.DateTimeField(auto_now_add=True) -class TowerAnalyticsState(SingletonModel): - last_run = models.DateTimeField(auto_now_add=True) - - def schedule_policy_task(): from awx.main.tasks import apply_cluster_membership_policies connection.on_commit(lambda: apply_cluster_membership_policies.apply_async()) diff --git a/awx/main/models/inventory.py b/awx/main/models/inventory.py index c8bc88eb0b3e..5305e6e532c8 100644 --- a/awx/main/models/inventory.py +++ b/awx/main/models/inventory.py @@ -4,17 +4,12 @@ # Python import datetime import time -import itertools import logging import re import copy import os.path from urllib.parse import urljoin import yaml -import configparser -import tempfile -from io import StringIO -from distutils.version import LooseVersion as Version # Django from django.conf import settings @@ -23,7 +18,6 @@ from django.db import transaction from django.core.exceptions import ValidationError from django.utils.timezone import now -from django.utils.encoding import iri_to_uri from django.db.models import Q # REST Framework @@ -60,7 +54,7 @@ JobNotificationMixin, ) from awx.main.models.credential.injectors import _openstack_data -from awx.main.utils import _inventory_updates, region_sorting, get_licenser +from awx.main.utils import _inventory_updates from awx.main.utils.safe_yaml import sanitize_jinja @@ -123,12 +117,6 @@ class Meta: help_text=_('This field is deprecated and will be removed in a future release. ' 'Total number of groups in this inventory.'), ) - groups_with_active_failures = models.PositiveIntegerField( - default=0, - editable=False, - help_text=_('This field is deprecated and will be removed in a future release. ' - 'Number of groups in this inventory with active failures.'), - ) has_inventory_sources = models.BooleanField( default=False, editable=False, @@ -339,139 +327,17 @@ def get_script_data(self, hostvars=False, towervars=False, show_all=False, slice return data - def update_host_computed_fields(self): - ''' - Update computed fields for all hosts in this inventory. - ''' - hosts_to_update = {} - hosts_qs = self.hosts - # Define queryset of all hosts with active failures. - hosts_with_active_failures = hosts_qs.filter(last_job_host_summary__isnull=False, last_job_host_summary__failed=True).values_list('pk', flat=True) - # Find all hosts that need the has_active_failures flag set. - hosts_to_set = hosts_qs.filter(has_active_failures=False, pk__in=hosts_with_active_failures) - for host_pk in hosts_to_set.values_list('pk', flat=True): - host_updates = hosts_to_update.setdefault(host_pk, {}) - host_updates['has_active_failures'] = True - # Find all hosts that need the has_active_failures flag cleared. - hosts_to_clear = hosts_qs.filter(has_active_failures=True).exclude(pk__in=hosts_with_active_failures) - for host_pk in hosts_to_clear.values_list('pk', flat=True): - host_updates = hosts_to_update.setdefault(host_pk, {}) - host_updates['has_active_failures'] = False - # Define queryset of all hosts with cloud inventory sources. - hosts_with_cloud_inventory = hosts_qs.filter(inventory_sources__source__in=CLOUD_INVENTORY_SOURCES).values_list('pk', flat=True) - # Find all hosts that need the has_inventory_sources flag set. - hosts_to_set = hosts_qs.filter(has_inventory_sources=False, pk__in=hosts_with_cloud_inventory) - for host_pk in hosts_to_set.values_list('pk', flat=True): - host_updates = hosts_to_update.setdefault(host_pk, {}) - host_updates['has_inventory_sources'] = True - # Find all hosts that need the has_inventory_sources flag cleared. - hosts_to_clear = hosts_qs.filter(has_inventory_sources=True).exclude(pk__in=hosts_with_cloud_inventory) - for host_pk in hosts_to_clear.values_list('pk', flat=True): - host_updates = hosts_to_update.setdefault(host_pk, {}) - host_updates['has_inventory_sources'] = False - # Now apply updates to hosts where needed (in batches). - all_update_pks = list(hosts_to_update.keys()) - - def _chunk(items, chunk_size): - for i, group in itertools.groupby(enumerate(items), lambda x: x[0] // chunk_size): - yield (g[1] for g in group) - - for update_pks in _chunk(all_update_pks, 500): - for host in hosts_qs.filter(pk__in=update_pks): - host_updates = hosts_to_update[host.pk] - for field, value in host_updates.items(): - setattr(host, field, value) - host.save(update_fields=host_updates.keys()) - - def update_group_computed_fields(self): - ''' - Update computed fields for all active groups in this inventory. - ''' - group_children_map = self.get_group_children_map() - group_hosts_map = self.get_group_hosts_map() - active_host_pks = set(self.hosts.values_list('pk', flat=True)) - failed_host_pks = set(self.hosts.filter(last_job_host_summary__failed=True).values_list('pk', flat=True)) - # active_group_pks = set(self.groups.values_list('pk', flat=True)) - failed_group_pks = set() # Update below as we check each group. - groups_with_cloud_pks = set(self.groups.filter(inventory_sources__source__in=CLOUD_INVENTORY_SOURCES).values_list('pk', flat=True)) - groups_to_update = {} - - # Build list of group pks to check, starting with the groups at the - # deepest level within the tree. - root_group_pks = set(self.root_groups.values_list('pk', flat=True)) - group_depths = {} # pk: max_depth - - def update_group_depths(group_pk, current_depth=0): - max_depth = group_depths.get(group_pk, -1) - # Arbitrarily limit depth to avoid hitting Python recursion limit (which defaults to 1000). - if current_depth > 100: - return - if current_depth > max_depth: - group_depths[group_pk] = current_depth - for child_pk in group_children_map.get(group_pk, set()): - update_group_depths(child_pk, current_depth + 1) - for group_pk in root_group_pks: - update_group_depths(group_pk) - group_pks_to_check = [x[1] for x in sorted([(v,k) for k,v in group_depths.items()], reverse=True)] - - for group_pk in group_pks_to_check: - # Get all children and host pks for this group. - parent_pks_to_check = set([group_pk]) - parent_pks_checked = set() - child_pks = set() - host_pks = set() - while parent_pks_to_check: - for parent_pk in list(parent_pks_to_check): - c_ids = group_children_map.get(parent_pk, set()) - child_pks.update(c_ids) - parent_pks_to_check.remove(parent_pk) - parent_pks_checked.add(parent_pk) - parent_pks_to_check.update(c_ids - parent_pks_checked) - h_ids = group_hosts_map.get(parent_pk, set()) - host_pks.update(h_ids) - # Define updates needed for this group. - group_updates = groups_to_update.setdefault(group_pk, {}) - group_updates.update({ - 'total_hosts': len(active_host_pks & host_pks), - 'has_active_failures': bool(failed_host_pks & host_pks), - 'hosts_with_active_failures': len(failed_host_pks & host_pks), - 'total_groups': len(child_pks), - 'groups_with_active_failures': len(failed_group_pks & child_pks), - 'has_inventory_sources': bool(group_pk in groups_with_cloud_pks), - }) - if group_updates['has_active_failures']: - failed_group_pks.add(group_pk) - - # Now apply updates to each group as needed (in batches). - all_update_pks = list(groups_to_update.keys()) - for offset in range(0, len(all_update_pks), 500): - update_pks = all_update_pks[offset:(offset + 500)] - for group in self.groups.filter(pk__in=update_pks): - group_updates = groups_to_update[group.pk] - for field, value in list(group_updates.items()): - if getattr(group, field) != value: - setattr(group, field, value) - else: - group_updates.pop(field) - if group_updates: - group.save(update_fields=group_updates.keys()) - - def update_computed_fields(self, update_groups=True, update_hosts=True): + def update_computed_fields(self): ''' Update model fields that are computed from database relationships. ''' logger.debug("Going to update inventory computed fields, pk={0}".format(self.pk)) start_time = time.time() - if update_hosts: - self.update_host_computed_fields() - if update_groups: - self.update_group_computed_fields() active_hosts = self.hosts - failed_hosts = active_hosts.filter(has_active_failures=True) + failed_hosts = active_hosts.filter(last_job_host_summary__failed=True) active_groups = self.groups if self.kind == 'smart': active_groups = active_groups.none() - failed_groups = active_groups.filter(has_active_failures=True) if self.kind == 'smart': active_inventory_sources = self.inventory_sources.none() else: @@ -482,7 +348,6 @@ def update_computed_fields(self, update_groups=True, update_hosts=True): 'total_hosts': active_hosts.count(), 'hosts_with_active_failures': failed_hosts.count(), 'total_groups': active_groups.count(), - 'groups_with_active_failures': failed_groups.count(), 'has_inventory_sources': bool(active_inventory_sources.count()), 'total_inventory_sources': active_inventory_sources.count(), 'inventory_sources_with_failures': failed_inventory_sources.count(), @@ -545,7 +410,7 @@ def save(self, *args, **kwargs): if (self.kind == 'smart' and 'host_filter' in kwargs.get('update_fields', ['host_filter']) and connection.vendor != 'sqlite'): # Minimal update of host_count for smart inventory host filter changes - self.update_computed_fields(update_groups=False, update_hosts=False) + self.update_computed_fields() def delete(self, *args, **kwargs): self._update_host_smart_inventory_memeberships() @@ -556,9 +421,9 @@ def delete(self, *args, **kwargs): ''' def _get_related_jobs(self): return UnifiedJob.objects.non_polymorphic().filter( - Q(Job___inventory=self) | - Q(InventoryUpdate___inventory_source__inventory=self) | - Q(AdHocCommand___inventory=self) + Q(job__inventory=self) | + Q(inventoryupdate__inventory=self) | + Q(adhoccommand__inventory=self) ) @@ -631,18 +496,6 @@ class Meta: editable=False, on_delete=models.SET_NULL, ) - has_active_failures = models.BooleanField( - default=False, - editable=False, - help_text=_('This field is deprecated and will be removed in a future release. ' - 'Flag indicating whether the last job failed for this host.'), - ) - has_inventory_sources = models.BooleanField( - default=False, - editable=False, - help_text=_('This field is deprecated and will be removed in a future release. ' - 'Flag indicating whether this host was created/updated from any external inventory sources.'), - ) inventory_sources = models.ManyToManyField( 'InventorySource', related_name='hosts', @@ -673,34 +526,6 @@ class Meta: def get_absolute_url(self, request=None): return reverse('api:host_detail', kwargs={'pk': self.pk}, request=request) - def update_computed_fields(self, update_inventory=True, update_groups=True): - ''' - Update model fields that are computed from database relationships. - ''' - has_active_failures = bool(self.last_job_host_summary and - self.last_job_host_summary.failed) - active_inventory_sources = self.inventory_sources.filter(source__in=CLOUD_INVENTORY_SOURCES) - computed_fields = { - 'has_active_failures': has_active_failures, - 'has_inventory_sources': bool(active_inventory_sources.count()), - } - for field, value in computed_fields.items(): - if getattr(self, field) != value: - setattr(self, field, value) - else: - computed_fields.pop(field) - if computed_fields: - self.save(update_fields=computed_fields.keys()) - # Groups and inventory may also need to be updated when host fields - # change. - # NOTE: I think this is no longer needed - # if update_groups: - # for group in self.all_groups: - # group.update_computed_fields() - # if update_inventory: - # self.inventory.update_computed_fields(update_groups=False, - # update_hosts=False) - # Rebuild summary fields cache variables_dict = VarsDictProperty('variables') @property @@ -815,42 +640,6 @@ class Meta: blank=True, help_text=_('Hosts associated directly with this group.'), ) - total_hosts = models.PositiveIntegerField( - default=0, - editable=False, - help_text=_('This field is deprecated and will be removed in a future release. ' - 'Total number of hosts directly or indirectly in this group.'), - ) - has_active_failures = models.BooleanField( - default=False, - editable=False, - help_text=_('This field is deprecated and will be removed in a future release. ' - 'Flag indicating whether this group has any hosts with active failures.'), - ) - hosts_with_active_failures = models.PositiveIntegerField( - default=0, - editable=False, - help_text=_('This field is deprecated and will be removed in a future release. ' - 'Number of hosts in this group with active failures.'), - ) - total_groups = models.PositiveIntegerField( - default=0, - editable=False, - help_text=_('This field is deprecated and will be removed in a future release. ' - 'Total number of child groups contained within this group.'), - ) - groups_with_active_failures = models.PositiveIntegerField( - default=0, - editable=False, - help_text=_('This field is deprecated and will be removed in a future release. ' - 'Number of child groups within this group that have active failures.'), - ) - has_inventory_sources = models.BooleanField( - default=False, - editable=False, - help_text=_('This field is deprecated and will be removed in a future release. ' - 'Flag indicating whether this group was created/updated from any external inventory sources.'), - ) inventory_sources = models.ManyToManyField( 'InventorySource', related_name='groups', @@ -925,32 +714,6 @@ def mark_actual(): mark_actual() activity_stream_delete(None, self) - def update_computed_fields(self): - ''' - Update model fields that are computed from database relationships. - ''' - active_hosts = self.all_hosts - failed_hosts = active_hosts.filter(last_job_host_summary__failed=True) - active_groups = self.all_children - # FIXME: May not be accurate unless we always update groups depth-first. - failed_groups = active_groups.filter(has_active_failures=True) - active_inventory_sources = self.inventory_sources.filter(source__in=CLOUD_INVENTORY_SOURCES) - computed_fields = { - 'total_hosts': active_hosts.count(), - 'has_active_failures': bool(failed_hosts.count()), - 'hosts_with_active_failures': failed_hosts.count(), - 'total_groups': active_groups.count(), - 'groups_with_active_failures': failed_groups.count(), - 'has_inventory_sources': bool(active_inventory_sources.count()), - } - for field, value in computed_fields.items(): - if getattr(self, field) != value: - setattr(self, field, value) - else: - computed_fields.pop(field) - if computed_fields: - self.save(update_fields=computed_fields.keys()) - variables_dict = VarsDictProperty('variables') def get_all_parents(self, except_pks=None): @@ -1040,8 +803,8 @@ def ad_hoc_commands(self): ''' def _get_related_jobs(self): return UnifiedJob.objects.non_polymorphic().filter( - Q(Job___inventory=self.inventory) | - Q(InventoryUpdate___inventory_source__groups=self) + Q(job__inventory=self.inventory) | + Q(inventoryupdate__inventory_source__groups=self) ) @@ -1053,7 +816,6 @@ class InventorySourceOptions(BaseModel): injectors = dict() SOURCE_CHOICES = [ - ('', _('Manual')), ('file', _('File, Directory or Script')), ('scm', _('Sourced from a Project')), ('ec2', _('Amazon EC2')), @@ -1061,7 +823,6 @@ class InventorySourceOptions(BaseModel): ('azure_rm', _('Microsoft Azure Resource Manager')), ('vmware', _('VMware vCenter')), ('satellite6', _('Red Hat Satellite 6')), - ('cloudforms', _('Red Hat CloudForms')), ('openstack', _('OpenStack')), ('rhv', _('Red Hat Virtualization')), ('tower', _('Ansible Tower')), @@ -1075,97 +836,14 @@ class InventorySourceOptions(BaseModel): (2, '2 (DEBUG)'), ] - # Use tools/scripts/get_ec2_filter_names.py to build this list. - INSTANCE_FILTER_NAMES = [ - "architecture", - "association.allocation-id", - "association.association-id", - "association.ip-owner-id", - "association.public-ip", - "availability-zone", - "block-device-mapping.attach-time", - "block-device-mapping.delete-on-termination", - "block-device-mapping.device-name", - "block-device-mapping.status", - "block-device-mapping.volume-id", - "client-token", - "dns-name", - "group-id", - "group-name", - "hypervisor", - "iam-instance-profile.arn", - "image-id", - "instance-id", - "instance-lifecycle", - "instance-state-code", - "instance-state-name", - "instance-type", - "instance.group-id", - "instance.group-name", - "ip-address", - "kernel-id", - "key-name", - "launch-index", - "launch-time", - "monitoring-state", - "network-interface-private-dns-name", - "network-interface.addresses.association.ip-owner-id", - "network-interface.addresses.association.public-ip", - "network-interface.addresses.primary", - "network-interface.addresses.private-ip-address", - "network-interface.attachment.attach-time", - "network-interface.attachment.attachment-id", - "network-interface.attachment.delete-on-termination", - "network-interface.attachment.device-index", - "network-interface.attachment.instance-id", - "network-interface.attachment.instance-owner-id", - "network-interface.attachment.status", - "network-interface.availability-zone", - "network-interface.description", - "network-interface.group-id", - "network-interface.group-name", - "network-interface.mac-address", - "network-interface.network-interface.id", - "network-interface.owner-id", - "network-interface.requester-id", - "network-interface.requester-managed", - "network-interface.source-destination-check", - "network-interface.status", - "network-interface.subnet-id", - "network-interface.vpc-id", - "owner-id", - "placement-group-name", - "platform", - "private-dns-name", - "private-ip-address", - "product-code", - "product-code.type", - "ramdisk-id", - "reason", - "requester-id", - "reservation-id", - "root-device-name", - "root-device-type", - "source-dest-check", - "spot-instance-request-id", - "state-reason-code", - "state-reason-message", - "subnet-id", - "tag-key", - "tag-value", - "tenancy", - "virtualization-type", - "vpc-id" - ] - class Meta: abstract = True source = models.CharField( max_length=32, choices=SOURCE_CHOICES, - blank=True, - default='', + blank=False, + default=None, ) source_path = models.CharField( max_length=1024, @@ -1184,22 +862,38 @@ class Meta: default='', help_text=_('Inventory source variables in YAML or JSON format.'), ) - source_regions = models.CharField( - max_length=1024, + enabled_var = models.TextField( blank=True, default='', + help_text=_('Retrieve the enabled state from the given dict of host ' + 'variables. The enabled variable may be specified as "foo.bar", ' + 'in which case the lookup will traverse into nested dicts, ' + 'equivalent to: from_dict.get("foo", {}).get("bar", default)'), ) - instance_filters = models.CharField( - max_length=1024, + enabled_value = models.TextField( blank=True, default='', - help_text=_('Comma-separated list of filter expressions (EC2 only). Hosts are imported when ANY of the filters match.'), - ) - group_by = models.CharField( - max_length=1024, + help_text=_('Only used when enabled_var is set. Value when the host is ' + 'considered enabled. For example if enabled_var="status.power_state"' + 'and enabled_value="powered_on" with host variables:' + '{' + ' "status": {' + ' "power_state": "powered_on",' + ' "created": "2020-08-04T18:13:04+00:00",' + ' "healthy": true' + ' },' + ' "name": "foobar",' + ' "ip_address": "192.168.2.1"' + '}' + 'The host would be marked enabled. If power_state where any ' + 'value other than powered_on then the host would be disabled ' + 'when imported into Tower. If the key is not found then the ' + 'host will be enabled'), + ) + host_filter = models.TextField( blank=True, default='', - help_text=_('Limit groups automatically created from inventory source (EC2 only).'), + help_text=_('Regex where only matching hosts will be imported into Tower.'), ) overwrite = models.BooleanField( default=False, @@ -1220,102 +914,6 @@ class Meta: default=1, ) - @classmethod - def get_ec2_region_choices(cls): - ec2_region_names = getattr(settings, 'EC2_REGION_NAMES', {}) - ec2_name_replacements = { - 'us': 'US', - 'ap': 'Asia Pacific', - 'eu': 'Europe', - 'sa': 'South America', - } - import boto.ec2 - regions = [('all', 'All')] - for region in boto.ec2.regions(): - label = ec2_region_names.get(region.name, '') - if not label: - label_parts = [] - for part in region.name.split('-'): - part = ec2_name_replacements.get(part.lower(), part.title()) - label_parts.append(part) - label = ' '.join(label_parts) - regions.append((region.name, label)) - return sorted(regions, key=region_sorting) - - @classmethod - def get_ec2_group_by_choices(cls): - return [ - ('ami_id', _('Image ID')), - ('availability_zone', _('Availability Zone')), - ('aws_account', _('Account')), - ('instance_id', _('Instance ID')), - ('instance_state', _('Instance State')), - ('platform', _('Platform')), - ('instance_type', _('Instance Type')), - ('key_pair', _('Key Name')), - ('region', _('Region')), - ('security_group', _('Security Group')), - ('tag_keys', _('Tags')), - ('tag_none', _('Tag None')), - ('vpc_id', _('VPC ID')), - ] - - @classmethod - def get_gce_region_choices(self): - """Return a complete list of regions in GCE, as a list of - two-tuples. - """ - # It's not possible to get a list of regions from GCE without - # authenticating first. Therefore, use a list from settings. - regions = list(getattr(settings, 'GCE_REGION_CHOICES', [])) - regions.insert(0, ('all', 'All')) - return sorted(regions, key=region_sorting) - - @classmethod - def get_azure_rm_region_choices(self): - """Return a complete list of regions in Microsoft Azure, as a list of - two-tuples. - """ - # It's not possible to get a list of regions from Azure without - # authenticating first (someone reading these might think there's - # a pattern here!). Therefore, you guessed it, use a list from - # settings. - regions = list(getattr(settings, 'AZURE_RM_REGION_CHOICES', [])) - regions.insert(0, ('all', 'All')) - return sorted(regions, key=region_sorting) - - @classmethod - def get_vmware_region_choices(self): - """Return a complete list of regions in VMware, as a list of two-tuples - (but note that VMware doesn't actually have regions!). - """ - return [('all', 'All')] - - @classmethod - def get_openstack_region_choices(self): - """I don't think openstack has regions""" - return [('all', 'All')] - - @classmethod - def get_satellite6_region_choices(self): - """Red Hat Satellite 6 region choices (not implemented)""" - return [('all', 'All')] - - @classmethod - def get_cloudforms_region_choices(self): - """Red Hat CloudForms region choices (not implemented)""" - return [('all', 'All')] - - @classmethod - def get_rhv_region_choices(self): - """No region supprt""" - return [('all', 'All')] - - @classmethod - def get_tower_region_choices(self): - """No region supprt""" - return [('all', 'All')] - @staticmethod def cloud_credential_validation(source, cred): if not source: @@ -1380,78 +978,9 @@ def credential(self): if cred is not None: return cred.pk - def clean_source_regions(self): - regions = self.source_regions - - if self.source in CLOUD_PROVIDERS: - get_regions = getattr(self, 'get_%s_region_choices' % self.source) - valid_regions = [x[0] for x in get_regions()] - region_transform = lambda x: x.strip().lower() - else: - return '' - all_region = region_transform('all') - valid_regions = [region_transform(x) for x in valid_regions] - regions = [region_transform(x) for x in regions.split(',') if x.strip()] - if all_region in regions: - return all_region - invalid_regions = [] - for r in regions: - if r not in valid_regions and r not in invalid_regions: - invalid_regions.append(r) - if invalid_regions: - raise ValidationError(_('Invalid %(source)s region: %(region)s') % { - 'source': self.source, 'region': ', '.join(invalid_regions)}) - return ','.join(regions) source_vars_dict = VarsDictProperty('source_vars') - def clean_instance_filters(self): - instance_filters = str(self.instance_filters or '') - if self.source == 'ec2': - invalid_filters = [] - instance_filter_re = re.compile(r'^((tag:.+)|([a-z][a-z\.-]*[a-z]))=.*$') - for instance_filter in instance_filters.split(','): - instance_filter = instance_filter.strip() - if not instance_filter: - continue - if not instance_filter_re.match(instance_filter): - invalid_filters.append(instance_filter) - continue - instance_filter_name = instance_filter.split('=', 1)[0] - if instance_filter_name.startswith('tag:'): - continue - if instance_filter_name not in self.INSTANCE_FILTER_NAMES: - invalid_filters.append(instance_filter) - if invalid_filters: - raise ValidationError(_('Invalid filter expression: %(filter)s') % - {'filter': ', '.join(invalid_filters)}) - return instance_filters - elif self.source in ('vmware', 'tower'): - return instance_filters - else: - return '' - - def clean_group_by(self): - group_by = str(self.group_by or '') - if self.source == 'ec2': - get_choices = getattr(self, 'get_%s_group_by_choices' % self.source) - valid_choices = [x[0] for x in get_choices()] - choice_transform = lambda x: x.strip().lower() - valid_choices = [choice_transform(x) for x in valid_choices] - choices = [choice_transform(x) for x in group_by.split(',') if x.strip()] - invalid_choices = [] - for c in choices: - if c not in valid_choices and c not in invalid_choices: - invalid_choices.append(c) - if invalid_choices: - raise ValidationError(_('Invalid group by choice: %(choice)s') % - {'choice': ', '.join(invalid_choices)}) - return ','.join(choices) - elif self.source == 'vmware': - return group_by - else: - return '' - class InventorySource(UnifiedJobTemplate, InventorySourceOptions, CustomVirtualEnvMixin, RelatedJobsMixin): @@ -1469,14 +998,6 @@ class Meta: on_delete=models.CASCADE, ) - deprecated_group = models.OneToOneField( - 'Group', - related_name='deprecated_inventory_source', - null=True, - default=None, - on_delete=models.CASCADE, - ) - source_project = models.ForeignKey( 'Project', related_name='scm_inventory_sources', @@ -1509,10 +1030,14 @@ def _get_unified_job_class(cls): @classmethod def _get_unified_job_field_names(cls): return set(f.name for f in InventorySourceOptions._meta.fields) | set( - ['name', 'description', 'credentials', 'inventory'] + ['name', 'description', 'organization', 'credentials', 'inventory'] ) def save(self, *args, **kwargs): + # if this is a new object, inherit organization from its inventory + if not self.pk and self.inventory and self.inventory.organization_id and not self.organization_id: + self.organization_id = self.inventory.organization_id + # If update_fields has been specified, add our field names to it, # if it hasn't been specified, then we're just doing a normal save. update_fields = kwargs.get('update_fields', []) @@ -1556,7 +1081,7 @@ def save(self, *args, **kwargs): self.update() if not getattr(_inventory_updates, 'is_updating', False): if self.inventory is not None: - self.inventory.update_computed_fields(update_groups=False, update_hosts=False) + self.inventory.update_computed_fields() def _get_current_status(self): if self.source: @@ -1642,16 +1167,6 @@ def notification_templates(self): started=list(started_notification_templates), success=list(success_notification_templates)) - def clean_source(self): # TODO: remove in 3.3 - source = self.source - if source and self.deprecated_group: - qs = self.deprecated_group.inventory_sources.filter(source__in=CLOUD_INVENTORY_SOURCES) - existing_sources = qs.exclude(pk=self.pk) - if existing_sources.count(): - s = u', '.join([x.deprecated_group.name for x in existing_sources]) - raise ValidationError(_('Unable to configure this item for cloud sync. It is already managed by %s.') % s) - return source - def clean_update_on_project_update(self): if self.update_on_project_update is True and \ self.source == 'scm' and \ @@ -1740,8 +1255,6 @@ def websocket_emit_data(self): if self.inventory_source.inventory is not None: websocket_data.update(dict(inventory_id=self.inventory_source.inventory.pk)) - if self.inventory_source.deprecated_group is not None: # TODO: remove in 3.3 - websocket_data.update(dict(group_id=self.inventory_source.deprecated_group.id)) return websocket_data def get_absolute_url(self, request=None): @@ -1850,21 +1363,21 @@ def get_absolute_url(self, request=None): return reverse('api:inventory_script_detail', kwargs={'pk': self.pk}, request=request) -# TODO: move to awx/main/models/inventory/injectors.py class PluginFileInjector(object): - # if plugin_name is not given, no inventory plugin functionality exists plugin_name = None # Ansible core name used to reference plugin - # if initial_version is None, but we have plugin name, injection logic exists, - # but it is vaporware, meaning we do not use it for some reason in Ansible core - initial_version = None # at what version do we switch to the plugin - ini_env_reference = None # env var name that points to old ini config file # base injector should be one of None, "managed", or "template" # this dictates which logic to borrow from playbook injectors base_injector = None + # every source should have collection, these are for the collection name + namespace = None + collection = None + collection_migration = '2.9' # Starting with this version, we use collections - def __init__(self, ansible_version): - # This is InventoryOptions instance, could be source or inventory update - self.ansible_version = ansible_version + @classmethod + def get_proper_name(cls): + if cls.plugin_name is None: + return None + return f'{cls.namespace}.{cls.collection}.{cls.plugin_name}' @property def filename(self): @@ -1873,22 +1386,6 @@ def filename(self): """ return '{0}.yml'.format(self.plugin_name) - @property - def script_name(self): - """Name of the script located in awx/plugins/inventory - """ - return '{0}.py'.format(self.__class__.__name__) - - def inventory_as_dict(self, inventory_update, private_data_dir): - """Default implementation of inventory plugin file contents. - There are some valid cases when all parameters can be obtained from - the environment variables, example "plugin: linode" is valid - ideally, however, some options should be filled from the inventory source data - """ - if self.plugin_name is None: - raise NotImplementedError('At minimum the plugin name is needed for inventory plugin use.') - return {'plugin': self.plugin_name} - def inventory_contents(self, inventory_update, private_data_dir): """Returns a string that is the content for the inventory file for the inventory plugin """ @@ -1898,17 +1395,19 @@ def inventory_contents(self, inventory_update, private_data_dir): width=1000 ) - def should_use_plugin(self): - return bool( - self.plugin_name and self.initial_version and - Version(self.ansible_version) >= Version(self.initial_version) - ) + def inventory_as_dict(self, inventory_update, private_data_dir): + source_vars = dict(inventory_update.source_vars_dict) # make a copy + proper_name = self.get_proper_name() + ''' + None conveys that we should use the user-provided plugin. + Note that a plugin value of '' should still be overridden. + ''' + if proper_name is not None: + source_vars['plugin'] = proper_name + return source_vars def build_env(self, inventory_update, env, private_data_dir, private_data_files): - if self.should_use_plugin(): - injector_env = self.get_plugin_env(inventory_update, private_data_dir, private_data_files) - else: - injector_env = self.get_script_env(inventory_update, private_data_dir, private_data_files) + injector_env = self.get_plugin_env(inventory_update, private_data_dir, private_data_files) env.update(injector_env) # Preserves current behavior for Ansible change in default planned for 2.10 env['ANSIBLE_TRANSFORM_INVALID_GROUP_CHARS'] = 'never' @@ -1916,7 +1415,6 @@ def build_env(self, inventory_update, env, private_data_dir, private_data_files) def _get_shared_env(self, inventory_update, private_data_dir, private_data_files): """By default, we will apply the standard managed_by_tower injectors - for the script injection """ injected_env = {} credential = inventory_update.get_cloud_credential() @@ -1942,51 +1440,22 @@ def _get_shared_env(self, inventory_update, private_data_dir, private_data_files return injected_env def get_plugin_env(self, inventory_update, private_data_dir, private_data_files): - return self._get_shared_env(inventory_update, private_data_dir, private_data_files) - - def get_script_env(self, inventory_update, private_data_dir, private_data_files): - injected_env = self._get_shared_env(inventory_update, private_data_dir, private_data_files) - - # Put in env var reference to private ini data files, if relevant - if self.ini_env_reference: - credential = inventory_update.get_cloud_credential() - cred_data = private_data_files['credentials'] - injected_env[self.ini_env_reference] = cred_data[credential] - - return injected_env + env = self._get_shared_env(inventory_update, private_data_dir, private_data_files) + env['ANSIBLE_COLLECTIONS_PATHS'] = settings.AWX_ANSIBLE_COLLECTIONS_PATHS + return env def build_private_data(self, inventory_update, private_data_dir): - if self.should_use_plugin(): - return self.build_plugin_private_data(inventory_update, private_data_dir) - else: - return self.build_script_private_data(inventory_update, private_data_dir) - - def build_script_private_data(self, inventory_update, private_data_dir): - return None + return self.build_plugin_private_data(inventory_update, private_data_dir) def build_plugin_private_data(self, inventory_update, private_data_dir): return None - @staticmethod - def dump_cp(cp, credential): - """Dump config parser data and return it as a string. - Helper method intended for use by build_script_private_data - """ - if cp.sections(): - f = StringIO() - cp.write(f) - private_data = {'credentials': {}} - private_data['credentials'][credential] = f.getvalue() - return private_data - else: - return None - class azure_rm(PluginFileInjector): plugin_name = 'azure_rm' - initial_version = '2.8' # Driven by unsafe group names issue, hostvars, host names - ini_env_reference = 'AZURE_INI_PATH' base_injector = 'managed' + namespace = 'azure' + collection = 'azcollection' def get_plugin_env(self, *args, **kwargs): ret = super(azure_rm, self).get_plugin_env(*args, **kwargs) @@ -1994,133 +1463,12 @@ def get_plugin_env(self, *args, **kwargs): ret['ANSIBLE_JINJA2_NATIVE'] = str(True) return ret - def inventory_as_dict(self, inventory_update, private_data_dir): - ret = super(azure_rm, self).inventory_as_dict(inventory_update, private_data_dir) - - source_vars = inventory_update.source_vars_dict - - ret['fail_on_template_errors'] = False - - group_by_hostvar = { - 'location': {'prefix': '', 'separator': '', 'key': 'location'}, - 'tag': {'prefix': '', 'separator': '', 'key': 'tags.keys() | list if tags else []'}, - # Introduced with https://github.com/ansible/ansible/pull/53046 - 'security_group': {'prefix': '', 'separator': '', 'key': 'security_group'}, - 'resource_group': {'prefix': '', 'separator': '', 'key': 'resource_group'}, - # Note, os_family was not documented correctly in script, but defaulted to grouping by it - 'os_family': {'prefix': '', 'separator': '', 'key': 'os_disk.operating_system_type'} - } - # by default group by everything - # always respect user setting, if they gave it - group_by = [ - grouping_name for grouping_name in group_by_hostvar - if source_vars.get('group_by_{}'.format(grouping_name), True) - ] - ret['keyed_groups'] = [group_by_hostvar[grouping_name] for grouping_name in group_by] - if 'tag' in group_by: - # Nasty syntax to reproduce "key_value" group names in addition to "key" - ret['keyed_groups'].append({ - 'prefix': '', 'separator': '', - 'key': r'dict(tags.keys() | map("regex_replace", "^(.*)$", "\1_") | list | zip(tags.values() | list)) if tags else []' - }) - - # Compatibility content - # TODO: add proper support for instance_filters non-specific to compatibility - # TODO: add proper support for group_by non-specific to compatibility - # Dashes were not configurable in azure_rm.py script, we do not want unicode, so always use this - ret['use_contrib_script_compatible_sanitization'] = True - # use same host names as script - ret['plain_host_names'] = True - # By default the script did not filter hosts - ret['default_host_filters'] = [] - # User-given host filters - user_filters = [] - old_filterables = [ - ('resource_groups', 'resource_group'), - ('tags', 'tags') - # locations / location would be an entry - # but this would conflict with source_regions - ] - for key, loc in old_filterables: - value = source_vars.get(key, None) - if value and isinstance(value, str): - # tags can be list of key:value pairs - # e.g. 'Creator:jmarshall, peanutbutter:jelly' - # or tags can be a list of keys - # e.g. 'Creator, peanutbutter' - if key == "tags": - # grab each key value pair - for kvpair in value.split(','): - # split into key and value - kv = kvpair.split(':') - # filter out any host that does not have key - # in their tags.keys() variable - user_filters.append('"{}" not in tags.keys()'.format(kv[0].strip())) - # if a value is provided, check that the key:value pair matches - if len(kv) > 1: - user_filters.append('tags["{}"] != "{}"'.format(kv[0].strip(), kv[1].strip())) - else: - user_filters.append('{} not in {}'.format( - loc, value.split(',') - )) - if user_filters: - ret.setdefault('exclude_host_filters', []) - ret['exclude_host_filters'].extend(user_filters) - - ret['conditional_groups'] = {'azure': True} - ret['hostvar_expressions'] = { - 'provisioning_state': 'provisioning_state | title', - 'computer_name': 'name', - 'type': 'resource_type', - 'private_ip': 'private_ipv4_addresses[0] if private_ipv4_addresses else None', - 'public_ip': 'public_ipv4_addresses[0] if public_ipv4_addresses else None', - 'public_ip_name': 'public_ip_name if public_ip_name is defined else None', - 'public_ip_id': 'public_ip_id if public_ip_id is defined else None', - 'tags': 'tags if tags else None' - } - # Special functionality from script - if source_vars.get('use_private_ip', False): - ret['hostvar_expressions']['ansible_host'] = 'private_ipv4_addresses[0]' - # end compatibility content - - if inventory_update.source_regions and 'all' not in inventory_update.source_regions: - # initialize a list for this section in inventory file - ret.setdefault('exclude_host_filters', []) - # make a python list of the regions we will use - python_regions = [x.strip() for x in inventory_update.source_regions.split(',')] - # convert that list in memory to python syntax in a string - # now put that in jinja2 syntax operating on hostvar key "location" - # and put that as an entry in the exclusions list - ret['exclude_host_filters'].append("location not in {}".format(repr(python_regions))) - return ret - - def build_script_private_data(self, inventory_update, private_data_dir): - cp = configparser.RawConfigParser() - section = 'azure' - cp.add_section(section) - cp.set(section, 'include_powerstate', 'yes') - cp.set(section, 'group_by_resource_group', 'yes') - cp.set(section, 'group_by_location', 'yes') - cp.set(section, 'group_by_tag', 'yes') - - if inventory_update.source_regions and 'all' not in inventory_update.source_regions: - cp.set( - section, 'locations', - ','.join([x.strip() for x in inventory_update.source_regions.split(',')]) - ) - - azure_rm_opts = dict(inventory_update.source_vars_dict.items()) - for k, v in azure_rm_opts.items(): - cp.set(section, k, str(v)) - return self.dump_cp(cp, inventory_update.get_cloud_credential()) - class ec2(PluginFileInjector): plugin_name = 'aws_ec2' - # blocked by https://github.com/ansible/ansible/issues/54059 - # initial_version = '2.8' # Driven by unsafe group names issue, parent_group templating, hostvars - ini_env_reference = 'EC2_INI_PATH' base_injector = 'managed' + namespace = 'amazon' + collection = 'aws' def get_plugin_env(self, *args, **kwargs): ret = super(ec2, self).get_plugin_env(*args, **kwargs) @@ -2128,235 +1476,12 @@ def get_plugin_env(self, *args, **kwargs): ret['ANSIBLE_JINJA2_NATIVE'] = str(True) return ret - def _compat_compose_vars(self): - return { - # vars that change - 'ec2_block_devices': ( - "dict(block_device_mappings | map(attribute='device_name') | list | zip(block_device_mappings " - "| map(attribute='ebs.volume_id') | list))" - ), - 'ec2_dns_name': 'public_dns_name', - 'ec2_group_name': 'placement.group_name', - 'ec2_instance_profile': 'iam_instance_profile | default("")', - 'ec2_ip_address': 'public_ip_address', - 'ec2_kernel': 'kernel_id | default("")', - 'ec2_monitored': "monitoring.state in ['enabled', 'pending']", - 'ec2_monitoring_state': 'monitoring.state', - 'ec2_placement': 'placement.availability_zone', - 'ec2_ramdisk': 'ramdisk_id | default("")', - 'ec2_reason': 'state_transition_reason', - 'ec2_security_group_ids': "security_groups | map(attribute='group_id') | list | join(',')", - 'ec2_security_group_names': "security_groups | map(attribute='group_name') | list | join(',')", - 'ec2_tag_Name': 'tags.Name', - 'ec2_state': 'state.name', - 'ec2_state_code': 'state.code', - 'ec2_state_reason': 'state_reason.message if state_reason is defined else ""', - 'ec2_sourceDestCheck': 'source_dest_check | default(false) | lower | string', # snake_case syntax intended - 'ec2_account_id': 'owner_id', - # vars that just need ec2_ prefix - 'ec2_ami_launch_index': 'ami_launch_index | string', - 'ec2_architecture': 'architecture', - 'ec2_client_token': 'client_token', - 'ec2_ebs_optimized': 'ebs_optimized', - 'ec2_hypervisor': 'hypervisor', - 'ec2_image_id': 'image_id', - 'ec2_instance_type': 'instance_type', - 'ec2_key_name': 'key_name', - 'ec2_launch_time': r'launch_time | regex_replace(" ", "T") | regex_replace("(\+)(\d\d):(\d)(\d)$", ".\g<2>\g<3>Z")', - 'ec2_platform': 'platform | default("")', - 'ec2_private_dns_name': 'private_dns_name', - 'ec2_private_ip_address': 'private_ip_address', - 'ec2_public_dns_name': 'public_dns_name', - 'ec2_region': 'placement.region', - 'ec2_root_device_name': 'root_device_name', - 'ec2_root_device_type': 'root_device_type', - # many items need blank defaults because the script tended to keep a common schema - 'ec2_spot_instance_request_id': 'spot_instance_request_id | default("")', - 'ec2_subnet_id': 'subnet_id | default("")', - 'ec2_virtualization_type': 'virtualization_type', - 'ec2_vpc_id': 'vpc_id | default("")', - # same as ec2_ip_address, the script provided this - 'ansible_host': 'public_ip_address', - # new with https://github.com/ansible/ansible/pull/53645 - 'ec2_eventsSet': 'events | default("")', - 'ec2_persistent': 'persistent | default(false)', - 'ec2_requester_id': 'requester_id | default("")' - } - - def inventory_as_dict(self, inventory_update, private_data_dir): - ret = super(ec2, self).inventory_as_dict(inventory_update, private_data_dir) - - keyed_groups = [] - group_by_hostvar = { - 'ami_id': {'prefix': '', 'separator': '', 'key': 'image_id', 'parent_group': 'images'}, - # 2 entries for zones for same groups to establish 2 parentage trees - 'availability_zone': {'prefix': '', 'separator': '', 'key': 'placement.availability_zone', 'parent_group': 'zones'}, - 'aws_account': {'prefix': '', 'separator': '', 'key': 'ec2_account_id', 'parent_group': 'accounts'}, # composed var - 'instance_id': {'prefix': '', 'separator': '', 'key': 'instance_id', 'parent_group': 'instances'}, # normally turned off - 'instance_state': {'prefix': 'instance_state', 'key': 'ec2_state', 'parent_group': 'instance_states'}, # composed var - # ec2_platform is a composed var, but group names do not match up to hostvar exactly - 'platform': {'prefix': 'platform', 'key': 'platform | default("undefined")', 'parent_group': 'platforms'}, - 'instance_type': {'prefix': 'type', 'key': 'instance_type', 'parent_group': 'types'}, - 'key_pair': {'prefix': 'key', 'key': 'key_name', 'parent_group': 'keys'}, - 'region': {'prefix': '', 'separator': '', 'key': 'placement.region', 'parent_group': 'regions'}, - # Security requires some ninja jinja2 syntax, credit to s-hertel - 'security_group': {'prefix': 'security_group', 'key': 'security_groups | map(attribute="group_name")', 'parent_group': 'security_groups'}, - # tags cannot be parented in exactly the same way as the script due to - # https://github.com/ansible/ansible/pull/53812 - 'tag_keys': [ - {'prefix': 'tag', 'key': 'tags', 'parent_group': 'tags'}, - {'prefix': 'tag', 'key': 'tags.keys()', 'parent_group': 'tags'} - ], - # 'tag_none': None, # grouping by no tags isn't a different thing with plugin - # naming is redundant, like vpc_id_vpc_8c412cea, but intended - 'vpc_id': {'prefix': 'vpc_id', 'key': 'vpc_id', 'parent_group': 'vpcs'}, - } - # -- same-ish as script here -- - group_by = [x.strip().lower() for x in inventory_update.group_by.split(',') if x.strip()] - for choice in inventory_update.get_ec2_group_by_choices(): - value = bool((group_by and choice[0] in group_by) or (not group_by and choice[0] != 'instance_id')) - # -- end sameness to script -- - if value: - this_keyed_group = group_by_hostvar.get(choice[0], None) - # If a keyed group syntax does not exist, there is nothing we can do to get this group - if this_keyed_group is not None: - if isinstance(this_keyed_group, list): - keyed_groups.extend(this_keyed_group) - else: - keyed_groups.append(this_keyed_group) - # special case, this parentage is only added if both zones and regions are present - if not group_by or ('region' in group_by and 'availability_zone' in group_by): - keyed_groups.append({'prefix': '', 'separator': '', 'key': 'placement.availability_zone', 'parent_group': '{{ placement.region }}'}) - - source_vars = inventory_update.source_vars_dict - # This is a setting from the script, hopefully no one used it - # if true, it replaces dashes, but not in region / loc names - replace_dash = bool(source_vars.get('replace_dash_in_groups', True)) - # Compatibility content - legacy_regex = { - True: r"[^A-Za-z0-9\_]", - False: r"[^A-Za-z0-9\_\-]" # do not replace dash, dash is whitelisted - }[replace_dash] - list_replacer = 'map("regex_replace", "{rx}", "_") | list'.format(rx=legacy_regex) - # this option, a plugin option, will allow dashes, but not unicode - # when set to False, unicode will be allowed, but it was not allowed by script - # thus, we always have to use this option, and always use our custom regex - ret['use_contrib_script_compatible_sanitization'] = True - for grouping_data in keyed_groups: - if grouping_data['key'] in ('placement.region', 'placement.availability_zone'): - # us-east-2 is always us-east-2 according to ec2.py - # no sanitization in region-ish groups for the script standards, ever ever - continue - if grouping_data['key'] == 'tags': - # dict jinja2 transformation - grouping_data['key'] = 'dict(tags.keys() | {replacer} | zip(tags.values() | {replacer}))'.format( - replacer=list_replacer - ) - elif grouping_data['key'] == 'tags.keys()' or grouping_data['prefix'] == 'security_group': - # list jinja2 transformation - grouping_data['key'] += ' | {replacer}'.format(replacer=list_replacer) - else: - # string transformation - grouping_data['key'] += ' | regex_replace("{rx}", "_")'.format(rx=legacy_regex) - # end compatibility content - - # This was an allowed ec2.ini option, also plugin option, so pass through - if source_vars.get('boto_profile', None): - ret['boto_profile'] = source_vars['boto_profile'] - - elif not replace_dash: - # Using the plugin, but still want dashes whitelisted - ret['use_contrib_script_compatible_sanitization'] = True - - if keyed_groups: - ret['keyed_groups'] = keyed_groups - - # Instance ID not part of compat vars, because of settings.EC2_INSTANCE_ID_VAR - compose_dict = {'ec2_id': 'instance_id'} - inst_filters = {} - - # Compatibility content - compose_dict.update(self._compat_compose_vars()) - # plugin provides "aws_ec2", but not this which the script gave - ret['groups'] = {'ec2': True} - # public_ip as hostname is non-default plugin behavior, script behavior - ret['hostnames'] = [ - 'network-interface.addresses.association.public-ip', - 'dns-name', - 'private-dns-name' - ] - # The script returned only running state by default, the plugin does not - # https://docs.aws.amazon.com/cli/latest/reference/ec2/describe-instances.html#options - # options: pending | running | shutting-down | terminated | stopping | stopped - inst_filters['instance-state-name'] = ['running'] - # end compatibility content - - if compose_dict: - ret['compose'] = compose_dict - - if inventory_update.instance_filters: - # logic used to live in ec2.py, now it belongs to us. Yay more code? - filter_sets = [f for f in inventory_update.instance_filters.split(',') if f] - - for instance_filter in filter_sets: - # AND logic not supported, unclear how to... - instance_filter = instance_filter.strip() - if not instance_filter or '=' not in instance_filter: - continue - filter_key, filter_value = [x.strip() for x in instance_filter.split('=', 1)] - if not filter_key: - continue - inst_filters[filter_key] = filter_value - - if inst_filters: - ret['filters'] = inst_filters - - if inventory_update.source_regions and 'all' not in inventory_update.source_regions: - ret['regions'] = inventory_update.source_regions.split(',') - - return ret - - def build_script_private_data(self, inventory_update, private_data_dir): - cp = configparser.RawConfigParser() - # Build custom ec2.ini for ec2 inventory script to use. - section = 'ec2' - cp.add_section(section) - ec2_opts = dict(inventory_update.source_vars_dict.items()) - regions = inventory_update.source_regions or 'all' - regions = ','.join([x.strip() for x in regions.split(',')]) - regions_blacklist = ','.join(settings.EC2_REGIONS_BLACKLIST) - ec2_opts['regions'] = regions - ec2_opts.setdefault('regions_exclude', regions_blacklist) - ec2_opts.setdefault('destination_variable', 'public_dns_name') - ec2_opts.setdefault('vpc_destination_variable', 'ip_address') - ec2_opts.setdefault('route53', 'False') - ec2_opts.setdefault('all_instances', 'True') - ec2_opts.setdefault('all_rds_instances', 'False') - ec2_opts.setdefault('include_rds_clusters', 'False') - ec2_opts.setdefault('rds', 'False') - ec2_opts.setdefault('nested_groups', 'True') - ec2_opts.setdefault('elasticache', 'False') - ec2_opts.setdefault('stack_filters', 'False') - if inventory_update.instance_filters: - ec2_opts.setdefault('instance_filters', inventory_update.instance_filters) - group_by = [x.strip().lower() for x in inventory_update.group_by.split(',') if x.strip()] - for choice in inventory_update.get_ec2_group_by_choices(): - value = bool((group_by and choice[0] in group_by) or (not group_by and choice[0] != 'instance_id')) - ec2_opts.setdefault('group_by_%s' % choice[0], str(value)) - if 'cache_path' not in ec2_opts: - cache_path = tempfile.mkdtemp(prefix='ec2_cache', dir=private_data_dir) - ec2_opts['cache_path'] = cache_path - ec2_opts.setdefault('cache_max_age', '300') - for k, v in ec2_opts.items(): - cp.set(section, k, str(v)) - return self.dump_cp(cp, inventory_update.get_cloud_credential()) - class gce(PluginFileInjector): plugin_name = 'gcp_compute' - initial_version = '2.8' # Driven by unsafe group names issue, hostvars - ini_env_reference = 'GCE_INI_PATH' base_injector = 'managed' + namespace = 'google' + collection = 'cloud' def get_plugin_env(self, *args, **kwargs): ret = super(gce, self).get_plugin_env(*args, **kwargs) @@ -2364,156 +1489,31 @@ def get_plugin_env(self, *args, **kwargs): ret['ANSIBLE_JINJA2_NATIVE'] = str(True) return ret - def get_script_env(self, inventory_update, private_data_dir, private_data_files): - env = super(gce, self).get_script_env(inventory_update, private_data_dir, private_data_files) - cred = inventory_update.get_cloud_credential() - # these environment keys are unique to the script operation, and are not - # concepts in the modern inventory plugin or gce Ansible module - # email and project are redundant with the creds file - env['GCE_EMAIL'] = cred.get_input('username', default='') - env['GCE_PROJECT'] = cred.get_input('project', default='') - env['GCE_ZONE'] = inventory_update.source_regions if inventory_update.source_regions != 'all' else '' # noqa - return env - - def _compat_compose_vars(self): - # missing: gce_image, gce_uuid - # https://github.com/ansible/ansible/issues/51884 - return { - 'gce_description': 'description if description else None', - 'gce_machine_type': 'machineType', - 'gce_name': 'name', - 'gce_network': 'networkInterfaces[0].network.name', - 'gce_private_ip': 'networkInterfaces[0].networkIP', - 'gce_public_ip': 'networkInterfaces[0].accessConfigs[0].natIP | default(None)', - 'gce_status': 'status', - 'gce_subnetwork': 'networkInterfaces[0].subnetwork.name', - 'gce_tags': 'tags.get("items", [])', - 'gce_zone': 'zone', - 'gce_metadata': 'metadata.get("items", []) | items2dict(key_name="key", value_name="value")', - # NOTE: image hostvar is enabled via retrieve_image_info option - 'gce_image': 'image', - # We need this as long as hostnames is non-default, otherwise hosts - # will not be addressed correctly, was returned in script - 'ansible_ssh_host': 'networkInterfaces[0].accessConfigs[0].natIP | default(networkInterfaces[0].networkIP)' - } - def inventory_as_dict(self, inventory_update, private_data_dir): - ret = super(gce, self).inventory_as_dict(inventory_update, private_data_dir) + ret = super().inventory_as_dict(inventory_update, private_data_dir) credential = inventory_update.get_cloud_credential() - - # auth related items - ret['projects'] = [credential.get_input('project', default='')] - ret['auth_kind'] = "serviceaccount" - - filters = [] - # TODO: implement gce group_by options - # gce never processed the group_by field, if it had, we would selectively - # apply those options here, but it did not, so all groups are added here - keyed_groups = [ - # the jinja2 syntax is duplicated with compose - # https://github.com/ansible/ansible/issues/51883 - {'prefix': 'network', 'key': 'gce_subnetwork'}, # composed var - {'prefix': '', 'separator': '', 'key': 'gce_private_ip'}, # composed var - {'prefix': '', 'separator': '', 'key': 'gce_public_ip'}, # composed var - {'prefix': '', 'separator': '', 'key': 'machineType'}, - {'prefix': '', 'separator': '', 'key': 'zone'}, - {'prefix': 'tag', 'key': 'gce_tags'}, # composed var - {'prefix': 'status', 'key': 'status | lower'}, - # NOTE: image hostvar is enabled via retrieve_image_info option - {'prefix': '', 'separator': '', 'key': 'image'}, - ] - # This will be used as the gce instance_id, must be universal, non-compat - compose_dict = {'gce_id': 'id'} - - # Compatibility content - # TODO: proper group_by and instance_filters support, irrelevant of compat mode - # The gce.py script never sanitized any names in any way - ret['use_contrib_script_compatible_sanitization'] = True - # Perform extra API query to get the image hostvar - ret['retrieve_image_info'] = True - # Add in old hostvars aliases - compose_dict.update(self._compat_compose_vars()) - # Non-default names to match script - ret['hostnames'] = ['name', 'public_ip', 'private_ip'] - # end compatibility content - - if keyed_groups: - ret['keyed_groups'] = keyed_groups - if filters: - ret['filters'] = filters - if compose_dict: - ret['compose'] = compose_dict - if inventory_update.source_regions and 'all' not in inventory_update.source_regions: - ret['zones'] = inventory_update.source_regions.split(',') + # InventorySource.source_vars take precedence over ENV vars + if 'projects' not in ret: + ret['projects'] = [credential.get_input('project', default='')] return ret - def build_script_private_data(self, inventory_update, private_data_dir): - cp = configparser.RawConfigParser() - # by default, the GCE inventory source caches results on disk for - # 5 minutes; disable this behavior - cp.add_section('cache') - cp.set('cache', 'cache_max_age', '0') - return self.dump_cp(cp, inventory_update.get_cloud_credential()) - class vmware(PluginFileInjector): - # plugin_name = 'vmware_vm_inventory' # FIXME: implement me - ini_env_reference = 'VMWARE_INI_PATH' + plugin_name = 'vmware_vm_inventory' base_injector = 'managed' - - @property - def script_name(self): - return 'vmware_inventory.py' # exception - - def build_script_private_data(self, inventory_update, private_data_dir): - cp = configparser.RawConfigParser() - credential = inventory_update.get_cloud_credential() - - # Allow custom options to vmware inventory script. - section = 'vmware' - cp.add_section(section) - cp.set('vmware', 'cache_max_age', '0') - cp.set('vmware', 'validate_certs', str(settings.VMWARE_VALIDATE_CERTS)) - cp.set('vmware', 'username', credential.get_input('username', default='')) - cp.set('vmware', 'password', credential.get_input('password', default='')) - cp.set('vmware', 'server', credential.get_input('host', default='')) - - vmware_opts = dict(inventory_update.source_vars_dict.items()) - if inventory_update.instance_filters: - vmware_opts.setdefault('host_filters', inventory_update.instance_filters) - if inventory_update.group_by: - vmware_opts.setdefault('groupby_patterns', inventory_update.group_by) - - for k, v in vmware_opts.items(): - cp.set(section, k, str(v)) - - return self.dump_cp(cp, credential) + namespace = 'community' + collection = 'vmware' class openstack(PluginFileInjector): - ini_env_reference = 'OS_CLIENT_CONFIG_FILE' plugin_name = 'openstack' - # minimum version of 2.7.8 may be theoretically possible - initial_version = '2.8' # Driven by consistency with other sources - - @property - def script_name(self): - return 'openstack_inventory.py' # exception + namespace = 'openstack' + collection = 'cloud' - def _get_clouds_dict(self, inventory_update, cred, private_data_dir, mk_cache=True): + def _get_clouds_dict(self, inventory_update, cred, private_data_dir): openstack_data = _openstack_data(cred) openstack_data['clouds']['devstack']['private'] = inventory_update.source_vars_dict.get('private', True) - if mk_cache: - # Retrieve cache path from inventory update vars if available, - # otherwise create a temporary cache path only for this update. - cache = inventory_update.source_vars_dict.get('cache', {}) - if not isinstance(cache, dict): - cache = {} - if not cache.get('path', ''): - cache_path = tempfile.mkdtemp(prefix='openstack_cache', dir=private_data_dir) - cache['path'] = cache_path - openstack_data['cache'] = cache ansible_variables = { 'use_hostnames': True, 'expand_hostvars': False, @@ -2530,128 +1530,44 @@ def _get_clouds_dict(self, inventory_update, cred, private_data_dir, mk_cache=Tr openstack_data['ansible'] = ansible_variables return openstack_data - def build_script_private_data(self, inventory_update, private_data_dir, mk_cache=True): + def build_plugin_private_data(self, inventory_update, private_data_dir): credential = inventory_update.get_cloud_credential() private_data = {'credentials': {}} - openstack_data = self._get_clouds_dict(inventory_update, credential, private_data_dir, mk_cache=mk_cache) + openstack_data = self._get_clouds_dict(inventory_update, credential, private_data_dir) private_data['credentials'][credential] = yaml.safe_dump( openstack_data, default_flow_style=False, allow_unicode=True ) return private_data - def build_plugin_private_data(self, inventory_update, private_data_dir): - # Credentials can be passed in the same way as the script did - # but do not create the tmp cache file - return self.build_script_private_data(inventory_update, private_data_dir, mk_cache=False) - def get_plugin_env(self, inventory_update, private_data_dir, private_data_files): - return self.get_script_env(inventory_update, private_data_dir, private_data_files) - - def inventory_as_dict(self, inventory_update, private_data_dir): - def use_host_name_for_name(a_bool_maybe): - if not isinstance(a_bool_maybe, bool): - # Could be specified by user via "host" or "uuid" - return a_bool_maybe - elif a_bool_maybe: - return 'name' # plugin default - else: - return 'uuid' - - ret = dict( - plugin=self.plugin_name, - fail_on_errors=True, - expand_hostvars=True, - inventory_hostname=use_host_name_for_name(False), - ) - # Note: mucking with defaults will break import integrity - # For the plugin, we need to use the same defaults as the old script - # or else imports will conflict. To find script defaults you have - # to read source code of the script. - # - # Script Defaults Plugin Defaults - # 'use_hostnames': False, 'name' (True) - # 'expand_hostvars': True, 'no' (False) - # 'fail_on_errors': True, 'no' (False) - # - # These are, yet again, different from ansible_variables in script logic - # but those are applied inconsistently - source_vars = inventory_update.source_vars_dict - for var_name in ['expand_hostvars', 'fail_on_errors']: - if var_name in source_vars: - ret[var_name] = source_vars[var_name] - if 'use_hostnames' in source_vars: - ret['inventory_hostname'] = use_host_name_for_name(source_vars['use_hostnames']) - return ret + env = super(openstack, self).get_plugin_env(inventory_update, private_data_dir, private_data_files) + credential = inventory_update.get_cloud_credential() + cred_data = private_data_files['credentials'] + env['OS_CLIENT_CONFIG_FILE'] = cred_data[credential] + return env class rhv(PluginFileInjector): """ovirt uses the custom credential templating, and that is all """ - # plugin_name = 'FIXME' # contribute inventory plugin to Ansible + plugin_name = 'ovirt' base_injector = 'template' - - @property - def script_name(self): - return 'ovirt4.py' # exception + initial_version = '2.9' + namespace = 'ovirt' + collection = 'ovirt' class satellite6(PluginFileInjector): plugin_name = 'foreman' - ini_env_reference = 'FOREMAN_INI_PATH' - # initial_version = '2.8' # FIXME: turn on after plugin is validated - # No base injector, because this does not work in playbooks. Bug?? - - @property - def script_name(self): - return 'foreman.py' # exception - - def build_script_private_data(self, inventory_update, private_data_dir): - cp = configparser.RawConfigParser() - credential = inventory_update.get_cloud_credential() - - section = 'foreman' - cp.add_section(section) - - group_patterns = '[]' - group_prefix = 'foreman_' - want_hostcollections = 'False' - foreman_opts = dict(inventory_update.source_vars_dict.items()) - foreman_opts.setdefault('ssl_verify', 'False') - for k, v in foreman_opts.items(): - if k == 'satellite6_group_patterns' and isinstance(v, str): - group_patterns = v - elif k == 'satellite6_group_prefix' and isinstance(v, str): - group_prefix = v - elif k == 'satellite6_want_hostcollections' and isinstance(v, bool): - want_hostcollections = v - else: - cp.set(section, k, str(v)) - - if credential: - cp.set(section, 'url', credential.get_input('host', default='')) - cp.set(section, 'user', credential.get_input('username', default='')) - cp.set(section, 'password', credential.get_input('password', default='')) - - section = 'ansible' - cp.add_section(section) - cp.set(section, 'group_patterns', group_patterns) - cp.set(section, 'want_facts', 'True') - cp.set(section, 'want_hostcollections', str(want_hostcollections)) - cp.set(section, 'group_prefix', group_prefix) - - section = 'cache' - cp.add_section(section) - cp.set(section, 'path', '/tmp') - cp.set(section, 'max_age', '0') - - return self.dump_cp(cp, credential) + namespace = 'theforeman' + collection = 'foreman' def get_plugin_env(self, inventory_update, private_data_dir, private_data_files): # this assumes that this is merged # https://github.com/ansible/ansible/pull/52693 credential = inventory_update.get_cloud_credential() - ret = {} + ret = super(satellite6, self).get_plugin_env(inventory_update, private_data_dir, private_data_files) if credential: ret['FOREMAN_SERVER'] = credential.get_input('host', default='') ret['FOREMAN_USER'] = credential.get_input('username', default='') @@ -2659,65 +1575,11 @@ def get_plugin_env(self, inventory_update, private_data_dir, private_data_files) return ret -class cloudforms(PluginFileInjector): - # plugin_name = 'FIXME' # contribute inventory plugin to Ansible - ini_env_reference = 'CLOUDFORMS_INI_PATH' - # Also no base_injector because this does not work in playbooks - - def build_script_private_data(self, inventory_update, private_data_dir): - cp = configparser.RawConfigParser() - credential = inventory_update.get_cloud_credential() - - section = 'cloudforms' - cp.add_section(section) - - if credential: - cp.set(section, 'url', credential.get_input('host', default='')) - cp.set(section, 'username', credential.get_input('username', default='')) - cp.set(section, 'password', credential.get_input('password', default='')) - cp.set(section, 'ssl_verify', "false") - - cloudforms_opts = dict(inventory_update.source_vars_dict.items()) - for opt in ['version', 'purge_actions', 'clean_group_keys', 'nest_tags', 'suffix', 'prefer_ipv4']: - if opt in cloudforms_opts: - cp.set(section, opt, str(cloudforms_opts[opt])) - - section = 'cache' - cp.add_section(section) - cp.set(section, 'max_age', "0") - cache_path = tempfile.mkdtemp( - prefix='cloudforms_cache', - dir=private_data_dir - ) - cp.set(section, 'path', cache_path) - - return self.dump_cp(cp, credential) - - class tower(PluginFileInjector): plugin_name = 'tower' base_injector = 'template' - initial_version = '2.8' # Driven by "include_metadata" hostvars - - def get_script_env(self, inventory_update, private_data_dir, private_data_files): - env = super(tower, self).get_script_env(inventory_update, private_data_dir, private_data_files) - env['TOWER_INVENTORY'] = inventory_update.instance_filters - env['TOWER_LICENSE_TYPE'] = get_licenser().validate().get('license_type', 'unlicensed') - return env - - def inventory_as_dict(self, inventory_update, private_data_dir): - # Credentials injected as env vars, same as script - try: - # plugin can take an actual int type - identifier = int(inventory_update.instance_filters) - except ValueError: - # inventory_id could be a named URL - identifier = iri_to_uri(inventory_update.instance_filters) - return { - 'plugin': self.plugin_name, - 'inventory_id': identifier, - 'include_metadata': True # used for license check - } + namespace = 'awx' + collection = 'awx' for cls in PluginFileInjector.__subclasses__(): diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py index 97cc226814ca..53fde7be8cdd 100644 --- a/awx/main/models/jobs.py +++ b/awx/main/models/jobs.py @@ -13,6 +13,7 @@ # Django from django.conf import settings +from django.core.exceptions import ValidationError from django.db import models #from django.core.cache import cache from django.utils.encoding import smart_str @@ -28,7 +29,7 @@ from awx.main.models.base import ( BaseModel, CreatedModifiedModel, prevent_search, accepts_json, - JOB_TYPE_CHOICES, VERBOSITY_CHOICES, + JOB_TYPE_CHOICES, NEW_JOB_TYPE_CHOICES, VERBOSITY_CHOICES, VarsDictProperty ) from awx.main.models.events import JobEvent, SystemJobEvent @@ -198,12 +199,17 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, SurveyJobTemplateMixin, Resour 'labels', 'instance_groups', 'credentials', 'survey_spec' ] FIELDS_TO_DISCARD_AT_COPY = ['vault_credential', 'credential'] - SOFT_UNIQUE_TOGETHER = [('polymorphic_ctype', 'name')] + SOFT_UNIQUE_TOGETHER = [('polymorphic_ctype', 'name', 'organization')] class Meta: app_label = 'main' ordering = ('name',) + job_type = models.CharField( + max_length=64, + choices=NEW_JOB_TYPE_CHOICES, + default='run', + ) host_config_key = prevent_search(models.CharField( max_length=1024, blank=True, @@ -256,13 +262,17 @@ class Meta: ) admin_role = ImplicitRoleField( - parent_role=['project.organization.job_template_admin_role', 'inventory.organization.job_template_admin_role'] + parent_role=['organization.job_template_admin_role'] ) execute_role = ImplicitRoleField( - parent_role=['admin_role', 'project.organization.execute_role', 'inventory.organization.execute_role'], + parent_role=['admin_role', 'organization.execute_role'], ) read_role = ImplicitRoleField( - parent_role=['project.organization.auditor_role', 'inventory.organization.auditor_role', 'execute_role', 'admin_role'], + parent_role=[ + 'organization.auditor_role', + 'inventory.organization.auditor_role', # partial support for old inheritance via inventory + 'execute_role', 'admin_role' + ], ) @@ -273,7 +283,7 @@ def _get_unified_job_class(cls): @classmethod def _get_unified_job_field_names(cls): return set(f.name for f in JobOptions._meta.fields) | set( - ['name', 'description', 'survey_passwords', 'labels', 'credentials', + ['name', 'description', 'organization', 'survey_passwords', 'labels', 'credentials', 'job_slice_number', 'job_slice_count'] ) @@ -293,6 +303,11 @@ def validation_errors(self): def resources_needed_to_start(self): return [fd for fd in ['project', 'inventory'] if not getattr(self, '{}_id'.format(fd))] + def clean_forks(self): + if settings.MAX_FORKS > 0 and self.forks > settings.MAX_FORKS: + raise ValidationError(_(f'Maximum number of forks ({settings.MAX_FORKS}) exceeded.')) + return self.forks + def create_job(self, **kwargs): ''' Create a new job based on this template. @@ -308,6 +323,41 @@ def get_effective_slice_ct(self, kwargs): else: return self.job_slice_count + def save(self, *args, **kwargs): + update_fields = kwargs.get('update_fields', []) + # if project is deleted for some reason, then keep the old organization + # to retain ownership for organization admins + if self.project and self.project.organization_id != self.organization_id: + self.organization_id = self.project.organization_id + if 'organization' not in update_fields and 'organization_id' not in update_fields: + update_fields.append('organization_id') + return super(JobTemplate, self).save(*args, **kwargs) + + def validate_unique(self, exclude=None): + """Custom over-ride for JT specifically + because organization is inferred from project after full_clean is finished + thus the organization field is not yet set when validation happens + """ + errors = [] + for ut in JobTemplate.SOFT_UNIQUE_TOGETHER: + kwargs = {'name': self.name} + if self.project: + kwargs['organization'] = self.project.organization_id + else: + kwargs['organization'] = None + qs = JobTemplate.objects.filter(**kwargs) + if self.pk: + qs = qs.exclude(pk=self.pk) + if qs.exists(): + errors.append( + '%s with this (%s) combination already exists.' % ( + JobTemplate.__name__, + ', '.join(set(ut) - {'polymorphic_ctype'}) + ) + ) + if errors: + raise ValidationError(errors) + def create_unified_job(self, **kwargs): prevent_slicing = kwargs.pop('_prevent_slicing', False) slice_ct = self.get_effective_slice_ct(kwargs) @@ -389,13 +439,9 @@ def _accept_or_ignore_job_kwargs(self, **kwargs): field = self._meta.get_field(field_name) if isinstance(field, models.ManyToManyField): old_value = set(old_value.all()) - if getattr(self, '_deprecated_credential_launch', False): - # TODO: remove this code branch when support for `extra_credentials` goes away - new_value = set(kwargs[field_name]) - else: - new_value = set(kwargs[field_name]) - old_value - if not new_value: - continue + new_value = set(kwargs[field_name]) - old_value + if not new_value: + continue if new_value == old_value: # no-op case: Fields the same as template's value @@ -468,13 +514,13 @@ def notification_templates(self): success_notification_templates = list(base_notification_templates.filter( unifiedjobtemplate_notification_templates_for_success__in=[self, self.project])) # Get Organization NotificationTemplates - if self.project is not None and self.project.organization is not None: + if self.organization is not None: error_notification_templates = set(error_notification_templates + list(base_notification_templates.filter( - organization_notification_templates_for_errors=self.project.organization))) + organization_notification_templates_for_errors=self.organization))) started_notification_templates = set(started_notification_templates + list(base_notification_templates.filter( - organization_notification_templates_for_started=self.project.organization))) + organization_notification_templates_for_started=self.organization))) success_notification_templates = set(success_notification_templates + list(base_notification_templates.filter( - organization_notification_templates_for_success=self.project.organization))) + organization_notification_templates_for_success=self.organization))) return dict(error=list(error_notification_templates), started=list(started_notification_templates), success=list(success_notification_templates)) @@ -577,7 +623,7 @@ def ansible_virtualenv_path(self): for virtualenv in ( self.job_template.custom_virtualenv if self.job_template else None, self.project.custom_virtualenv, - self.project.organization.custom_virtualenv if self.project.organization else None + self.organization.custom_virtualenv if self.organization else None ): if virtualenv: return virtualenv @@ -731,8 +777,8 @@ def is_containerized(self): @property def preferred_instance_groups(self): - if self.project is not None and self.project.organization is not None: - organization_groups = [x for x in self.project.organization.instance_groups.all()] + if self.organization is not None: + organization_groups = [x for x in self.organization.instance_groups.all()] else: organization_groups = [] if self.inventory is not None: @@ -753,6 +799,10 @@ def awx_meta_vars(self): if self.project: for name in ('awx', 'tower'): r['{}_project_revision'.format(name)] = self.project.scm_revision + r['{}_project_scm_branch'.format(name)] = self.project.scm_branch + if self.scm_branch: + for name in ('awx', 'tower'): + r['{}_job_scm_branch'.format(name)] = self.scm_branch if self.job_template: for name in ('awx', 'tower'): r['{}_job_template_id'.format(name)] = self.job_template.pk @@ -819,8 +869,10 @@ def finish_job_fact_cache(self, destination, modification_times): continue host.ansible_facts = ansible_facts host.ansible_facts_modified = now() - ansible_local_system_id = ansible_facts.get('ansible_local', {}).get('insights', {}).get('system_id', None) - ansible_facts_system_id = ansible_facts.get('insights', {}).get('system_id', None) + ansible_local = ansible_facts.get('ansible_local', {}).get('insights', {}) + ansible_facts = ansible_facts.get('insights', {}) + ansible_local_system_id = ansible_local.get('system_id', None) if isinstance(ansible_local, dict) else None + ansible_facts_system_id = ansible_facts.get('system_id', None) if isinstance(ansible_facts, dict) else None if ansible_local_system_id: print("Setting local {}".format(ansible_local_system_id)) logger.debug("Insights system_id {} found for host <{}, {}> in" @@ -1061,7 +1113,7 @@ class Meta: processed = models.PositiveIntegerField(default=0, editable=False) rescued = models.PositiveIntegerField(default=0, editable=False) skipped = models.PositiveIntegerField(default=0, editable=False) - failed = models.BooleanField(default=False, editable=False) + failed = models.BooleanField(default=False, editable=False, db_index=True) def __str__(self): host = getattr_dne(self, 'host') @@ -1082,21 +1134,6 @@ def save(self, *args, **kwargs): self.failed = bool(self.dark or self.failures) update_fields.append('failed') super(JobHostSummary, self).save(*args, **kwargs) - self.update_host_last_job_summary() - - def update_host_last_job_summary(self): - update_fields = [] - if self.host is None: - return - if self.host.last_job_id != self.job_id: - self.host.last_job_id = self.job_id - update_fields.append('last_job_id') - if self.host.last_job_host_summary_id != self.id: - self.host.last_job_host_summary_id = self.id - update_fields.append('last_job_host_summary_id') - if update_fields: - self.host.save(update_fields=update_fields) - #self.host.update_computed_fields() class SystemJobOptions(BaseModel): @@ -1133,7 +1170,7 @@ def _get_unified_job_class(cls): @classmethod def _get_unified_job_field_names(cls): - return ['name', 'description', 'job_type', 'extra_vars'] + return ['name', 'description', 'organization', 'job_type', 'extra_vars'] def get_absolute_url(self, request=None): return reverse('api:system_job_template_detail', kwargs={'pk': self.pk}, request=request) diff --git a/awx/main/models/mixins.py b/awx/main/models/mixins.py index e51807f47b94..ce6d3717a7b6 100644 --- a/awx/main/models/mixins.py +++ b/awx/main/models/mixins.py @@ -566,7 +566,6 @@ class Meta: def update_webhook_status(self, status): if not self.webhook_credential: - logger.debug("No credential configured to post back webhook status, skipping.") return status_api = self.extra_vars_dict.get('tower_webhook_status_api') diff --git a/awx/main/models/notifications.py b/awx/main/models/notifications.py index 8b321c231e8b..1f01de88952f 100644 --- a/awx/main/models/notifications.py +++ b/awx/main/models/notifications.py @@ -12,7 +12,7 @@ from django.db import connection from django.utils.translation import ugettext_lazy as _ from django.utils.encoding import smart_str, force_text -from jinja2 import sandbox +from jinja2 import sandbox, ChainableUndefined from jinja2.exceptions import TemplateSyntaxError, UndefinedError, SecurityError # AWX @@ -23,7 +23,6 @@ from awx.main.notifications.slack_backend import SlackBackend from awx.main.notifications.twilio_backend import TwilioBackend from awx.main.notifications.pagerduty_backend import PagerDutyBackend -from awx.main.notifications.hipchat_backend import HipChatBackend from awx.main.notifications.webhook_backend import WebhookBackend from awx.main.notifications.mattermost_backend import MattermostBackend from awx.main.notifications.grafana_backend import GrafanaBackend @@ -44,7 +43,6 @@ class NotificationTemplate(CommonModelNameNotUnique): ('twilio', _('Twilio'), TwilioBackend), ('pagerduty', _('Pagerduty'), PagerDutyBackend), ('grafana', _('Grafana'), GrafanaBackend), - ('hipchat', _('HipChat'), HipChatBackend), ('webhook', _('Webhook'), WebhookBackend), ('mattermost', _('Mattermost'), MattermostBackend), ('rocketchat', _('Rocket.Chat'), RocketChatBackend), @@ -264,27 +262,25 @@ class JobNotificationMixin(object): 'running': 'started', 'failed': 'error'} # Tree of fields that can be safely referenced in a notification message - JOB_FIELDS_WHITELIST = ['id', 'type', 'url', 'created', 'modified', 'name', 'description', 'job_type', 'playbook', - 'forks', 'limit', 'verbosity', 'job_tags', 'force_handlers', 'skip_tags', 'start_at_task', - 'timeout', 'use_fact_cache', 'launch_type', 'status', 'failed', 'started', 'finished', - 'elapsed', 'job_explanation', 'execution_node', 'controller_node', 'allow_simultaneous', - 'scm_revision', 'diff_mode', 'job_slice_number', 'job_slice_count', 'custom_virtualenv', - 'approval_status', 'approval_node_name', 'workflow_url', - {'host_status_counts': ['skipped', 'ok', 'changed', 'failures', 'dark']}, - {'playbook_counts': ['play_count', 'task_count']}, - {'summary_fields': [{'inventory': ['id', 'name', 'description', 'has_active_failures', - 'total_hosts', 'hosts_with_active_failures', 'total_groups', - 'groups_with_active_failures', 'has_inventory_sources', - 'total_inventory_sources', 'inventory_sources_with_failures', - 'organization_id', 'kind']}, - {'project': ['id', 'name', 'description', 'status', 'scm_type']}, - {'project_update': ['id', 'name', 'description', 'status', 'failed']}, - {'job_template': ['id', 'name', 'description']}, - {'unified_job_template': ['id', 'name', 'description', 'unified_job_type']}, - {'instance_group': ['name', 'id']}, - {'created_by': ['id', 'username', 'first_name', 'last_name']}, - {'labels': ['count', 'results']}, - {'source_workflow_job': ['description', 'elapsed', 'failed', 'id', 'name', 'status']}]}] + JOB_FIELDS_ALLOWED_LIST = ['id', 'type', 'url', 'created', 'modified', 'name', 'description', 'job_type', 'playbook', + 'forks', 'limit', 'verbosity', 'job_tags', 'force_handlers', 'skip_tags', 'start_at_task', + 'timeout', 'use_fact_cache', 'launch_type', 'status', 'failed', 'started', 'finished', + 'elapsed', 'job_explanation', 'execution_node', 'controller_node', 'allow_simultaneous', + 'scm_revision', 'diff_mode', 'job_slice_number', 'job_slice_count', 'custom_virtualenv', + 'approval_status', 'approval_node_name', 'workflow_url', 'scm_branch', 'artifacts', + {'host_status_counts': ['skipped', 'ok', 'changed', 'failed', 'failures', 'dark' + 'processed', 'rescued', 'ignored']}, + {'summary_fields': [{'inventory': ['id', 'name', 'description', 'has_active_failures', + 'total_hosts', 'hosts_with_active_failures', 'total_groups', + 'has_inventory_sources', + 'total_inventory_sources', 'inventory_sources_with_failures', + 'organization_id', 'kind']}, + {'project': ['id', 'name', 'description', 'status', 'scm_type']}, + {'job_template': ['id', 'name', 'description']}, + {'unified_job_template': ['id', 'name', 'description', 'unified_job_type']}, + {'instance_group': ['name', 'id']}, + {'created_by': ['id', 'username', 'first_name', 'last_name']}, + {'labels': ['count', 'results']}]}] @classmethod def context_stub(cls): @@ -292,6 +288,7 @@ def context_stub(cls): Context has the same structure as the context that will actually be used to render a notification message.""" context = {'job': {'allow_simultaneous': False, + 'artifacts': {}, 'controller_node': 'foo_controller', 'created': datetime.datetime(2018, 11, 13, 6, 4, 0, 0, tzinfo=datetime.timezone.utc), 'custom_virtualenv': 'my_venv', @@ -303,7 +300,7 @@ def context_stub(cls): 'finished': False, 'force_handlers': False, 'forks': 0, - 'host_status_counts': {'skipped': 1, 'ok': 5, 'changed': 3, 'failures': 0, 'dark': 0}, + 'host_status_counts': {'skipped': 1, 'ok': 5, 'changed': 3, 'failures': 0, 'dark': 0, 'failed': False, 'processed': 0, 'rescued': 0}, 'id': 42, 'job_explanation': 'Sample job explanation', 'job_slice_count': 1, @@ -314,8 +311,8 @@ def context_stub(cls): 'limit': 'bar_limit', 'modified': datetime.datetime(2018, 12, 13, 6, 4, 0, 0, tzinfo=datetime.timezone.utc), 'name': 'Stub JobTemplate', - 'playbook_counts': {'play_count': 5, 'task_count': 10}, 'playbook': 'ping.yml', + 'scm_branch': '', 'scm_revision': '', 'skip_tags': '', 'start_at_task': '', @@ -327,7 +324,6 @@ def context_stub(cls): 'username': 'admin'}, 'instance_group': {'id': 1, 'name': 'tower'}, 'inventory': {'description': 'Sample inventory description', - 'groups_with_active_failures': 0, 'has_active_failures': False, 'has_inventory_sources': False, 'hosts_with_active_failures': 0, @@ -348,18 +344,10 @@ def context_stub(cls): 'name': 'Stub project', 'scm_type': 'git', 'status': 'successful'}, - 'project_update': {'id': 5, 'name': 'Stub Project Update', 'description': 'Project Update', - 'status': 'running', 'failed': False}, 'unified_job_template': {'description': 'Sample unified job template description', 'id': 39, 'name': 'Stub Job Template', - 'unified_job_type': 'job'}, - 'source_workflow_job': {'description': 'Sample workflow job description', - 'elapsed': 0.000, - 'failed': False, - 'id': 88, - 'name': 'Stub WorkflowJobTemplate', - 'status': 'running'}}, + 'unified_job_type': 'job'}}, 'timeout': 0, 'type': 'job', 'url': '/api/v2/jobs/13/', @@ -391,30 +379,40 @@ def context_stub(cls): def context(self, serialized_job): """Returns a dictionary that can be used for rendering notification messages. - The context will contain whitelisted content retrieved from a serialized job object - (see JobNotificationMixin.JOB_FIELDS_WHITELIST), the job's friendly name, + The context will contain allowed content retrieved from a serialized job object + (see JobNotificationMixin.JOB_FIELDS_ALLOWED_LIST the job's friendly name, and a url to the job run.""" - context = {'job': {}, - 'job_friendly_name': self.get_notification_friendly_name(), - 'url': self.get_ui_url(), - 'job_metadata': self.notification_data()} - - def build_context(node, fields, whitelisted_fields): - for safe_field in whitelisted_fields: + job_context = {'host_status_counts': {}} + summary = None + if hasattr(self, 'job_host_summaries'): + summary = self.job_host_summaries.first() + if summary: + from awx.api.serializers import JobHostSummarySerializer + summary_data = JobHostSummarySerializer(summary).to_representation(summary) + job_context['host_status_counts'] = summary_data + context = { + 'job': job_context, + 'job_friendly_name': self.get_notification_friendly_name(), + 'url': self.get_ui_url(), + 'job_metadata': self.notification_data() + } + + def build_context(node, fields, allowed_fields): + for safe_field in allowed_fields: if type(safe_field) is dict: - field, whitelist_subnode = safe_field.copy().popitem() + field, allowed_subnode = safe_field.copy().popitem() # ensure content present in job serialization if field not in fields: continue subnode = fields[field] node[field] = {} - build_context(node[field], subnode, whitelist_subnode) + build_context(node[field], subnode, allowed_subnode) else: # ensure content present in job serialization if safe_field not in fields: continue node[safe_field] = fields[safe_field] - build_context(context['job'], serialized_job, self.JOB_FIELDS_WHITELIST) + build_context(context['job'], serialized_job, self.JOB_FIELDS_ALLOWED_LIST) return context @@ -428,7 +426,7 @@ def notification_data(self): raise RuntimeError("Define me") def build_notification_message(self, nt, status): - env = sandbox.ImmutableSandboxedEnvironment() + env = sandbox.ImmutableSandboxedEnvironment(undefined=ChainableUndefined) from awx.api.serializers import UnifiedJobSerializer job_serialization = UnifiedJobSerializer(self).to_representation(self) diff --git a/awx/main/models/organization.py b/awx/main/models/organization.py index df5d491d2034..bf2e07d255c7 100644 --- a/awx/main/models/organization.py +++ b/awx/main/models/organization.py @@ -6,7 +6,6 @@ # Django from django.conf import settings from django.db import models -from django.db.models import Q from django.contrib.auth.models import User from django.contrib.sessions.models import Session from django.utils.timezone import now as tz_now @@ -46,6 +45,12 @@ class Meta: blank=True, through='OrganizationInstanceGroupMembership' ) + galaxy_credentials = OrderedManyToManyField( + 'Credential', + blank=True, + through='OrganizationGalaxyCredentialMembership', + related_name='%(class)s_galaxy_credentials' + ) max_hosts = models.PositiveIntegerField( blank=True, default=0, @@ -106,12 +111,24 @@ def get_absolute_url(self, request=None): RelatedJobsMixin ''' def _get_related_jobs(self): - project_ids = self.projects.all().values_list('id') - return UnifiedJob.objects.non_polymorphic().filter( - Q(Job___project__in=project_ids) | - Q(ProjectUpdate___project__in=project_ids) | - Q(InventoryUpdate___inventory_source__inventory__organization=self) - ) + return UnifiedJob.objects.non_polymorphic().filter(organization=self) + + +class OrganizationGalaxyCredentialMembership(models.Model): + + organization = models.ForeignKey( + 'Organization', + on_delete=models.CASCADE + ) + credential = models.ForeignKey( + 'Credential', + on_delete=models.CASCADE + ) + position = models.PositiveIntegerField( + null=True, + default=None, + db_index=True, + ) class Team(CommonModelNameNotUnique, ResourceMixin): diff --git a/awx/main/models/projects.py b/awx/main/models/projects.py index cc31842d4d26..65fb8304cef6 100644 --- a/awx/main/models/projects.py +++ b/awx/main/models/projects.py @@ -52,9 +52,9 @@ class ProjectOptions(models.Model): SCM_TYPE_CHOICES = [ ('', _('Manual')), ('git', _('Git')), - ('hg', _('Mercurial')), ('svn', _('Subversion')), ('insights', _('Red Hat Insights')), + ('archive', _('Remote Archive')), ] class Meta: @@ -194,12 +194,17 @@ def get_project_path(self, check_if_exists=True): if not check_if_exists or os.path.exists(smart_str(proj_path)): return proj_path + def get_cache_path(self): + local_path = os.path.basename(self.local_path) + if local_path: + return os.path.join(settings.PROJECTS_ROOT, '.__awx_cache', local_path) + @property def playbooks(self): results = [] project_path = self.get_project_path() if project_path: - for dirpath, dirnames, filenames in os.walk(smart_str(project_path)): + for dirpath, dirnames, filenames in os.walk(smart_str(project_path), followlinks=settings.AWX_SHOW_PLAYBOOK_LINKS): if skip_directory(dirpath): continue for filename in filenames: @@ -254,13 +259,6 @@ class Meta: app_label = 'main' ordering = ('id',) - organization = models.ForeignKey( - 'Organization', - blank=True, - null=True, - on_delete=models.CASCADE, - related_name='projects', - ) scm_update_on_launch = models.BooleanField( default=False, help_text=_('Update the project when a job is launched that uses the project.'), @@ -329,9 +327,16 @@ def _get_unified_job_class(cls): @classmethod def _get_unified_job_field_names(cls): return set(f.name for f in ProjectOptions._meta.fields) | set( - ['name', 'description'] + ['name', 'description', 'organization'] ) + def clean_organization(self): + if self.pk: + old_org_id = getattr(self, '_prior_values_store', {}).get('organization_id', None) + if self.organization_id != old_org_id and self.jobtemplates.exists(): + raise ValidationError({'organization': _('Organization cannot be changed when in use by job templates.')}) + return self.organization + def save(self, *args, **kwargs): new_instance = not bool(self.pk) pre_save_vals = getattr(self, '_prior_values_store', {}) @@ -418,6 +423,10 @@ def needs_update_on_launch(self): return True return False + @property + def cache_id(self): + return str(self.last_job_id) + @property def notification_templates(self): base_notification_templates = NotificationTemplate.objects @@ -450,16 +459,17 @@ def get_absolute_url(self, request=None): ''' def _get_related_jobs(self): return UnifiedJob.objects.non_polymorphic().filter( - models.Q(Job___project=self) | - models.Q(ProjectUpdate___project=self) + models.Q(job__project=self) | + models.Q(projectupdate__project=self) ) def delete(self, *args, **kwargs): - path_to_delete = self.get_project_path(check_if_exists=False) + paths_to_delete = (self.get_project_path(check_if_exists=False), self.get_cache_path()) r = super(Project, self).delete(*args, **kwargs) - if self.scm_type and path_to_delete: # non-manual, concrete path - from awx.main.tasks import delete_project_files - delete_project_files.delay(path_to_delete) + for path_to_delete in paths_to_delete: + if self.scm_type and path_to_delete: # non-manual, concrete path + from awx.main.tasks import delete_project_files + delete_project_files.delay(path_to_delete) return r @@ -554,6 +564,19 @@ def result_stdout(self): def result_stdout_raw(self): return self._result_stdout_raw(redact_sensitive=True) + @property + def branch_override(self): + """Whether a branch other than the project default is used.""" + if not self.project: + return True + return bool(self.scm_branch and self.scm_branch != self.project.scm_branch) + + @property + def cache_id(self): + if self.branch_override or self.job_type == 'check' or (not self.project): + return str(self.id) + return self.project.cache_id + def result_stdout_raw_limited(self, start_line=0, end_line=None, redact_sensitive=True): return self._result_stdout_raw_limited(start_line, end_line, redact_sensitive=redact_sensitive) @@ -584,8 +607,8 @@ def get_notification_friendly_name(self): @property def preferred_instance_groups(self): - if self.project is not None and self.project.organization is not None: - organization_groups = [x for x in self.project.organization.instance_groups.all()] + if self.organization is not None: + organization_groups = [x for x in self.organization.instance_groups.all()] else: organization_groups = [] template_groups = [x for x in super(ProjectUpdate, self).preferred_instance_groups] @@ -597,10 +620,7 @@ def preferred_instance_groups(self): def save(self, *args, **kwargs): added_update_fields = [] if not self.job_tags: - job_tags = ['update_{}'.format(self.scm_type)] - if self.job_type == 'run': - job_tags.append('install_roles') - job_tags.append('install_collections') + job_tags = ['update_{}'.format(self.scm_type), 'install_roles', 'install_collections'] self.job_tags = ','.join(job_tags) added_update_fields.append('job_tags') if self.scm_delete_on_update and 'delete' not in self.job_tags and self.job_type == 'check': diff --git a/awx/main/models/schedules.py b/awx/main/models/schedules.py index 58aee91d9661..5b907a43334a 100644 --- a/awx/main/models/schedules.py +++ b/awx/main/models/schedules.py @@ -191,7 +191,7 @@ def coerce_naive_until(cls, rrule): return rrule @classmethod - def rrulestr(cls, rrule, **kwargs): + def rrulestr(cls, rrule, fast_forward=True, **kwargs): """ Apply our own custom rrule parsing requirements """ @@ -205,11 +205,22 @@ def rrulestr(cls, rrule, **kwargs): 'A valid TZID must be provided (e.g., America/New_York)' ) - if 'MINUTELY' in rrule or 'HOURLY' in rrule: + if ( + fast_forward and + ('MINUTELY' in rrule or 'HOURLY' in rrule) and + 'COUNT=' not in rrule + ): try: first_event = x[0] - if first_event < now() - datetime.timedelta(days=365 * 5): - raise ValueError('RRULE values with more than 1000 events are not allowed.') + # If the first event was over a week ago... + if (now() - first_event).days > 7: + # hourly/minutely rrules with far-past DTSTART values + # are *really* slow to precompute + # start *from* one week ago to speed things up drastically + dtstart = x._rrule[0]._dtstart.strftime(':%Y%m%dT') + new_start = (now() - datetime.timedelta(days=7)).strftime(':%Y%m%dT') + new_rrule = rrule.replace(dtstart, new_start) + return Schedule.rrulestr(new_rrule, fast_forward=False) except IndexError: pass return x diff --git a/awx/main/models/unified_jobs.py b/awx/main/models/unified_jobs.py index 3613ac4d34df..c50c8668d5b3 100644 --- a/awx/main/models/unified_jobs.py +++ b/awx/main/models/unified_jobs.py @@ -3,6 +3,7 @@ # Python from io import StringIO +import datetime import codecs import json import logging @@ -35,6 +36,7 @@ NotificationFieldsModel, prevent_search ) +from awx.main.dispatch import get_local_queuename from awx.main.dispatch.control import Control as ControlDispatcher from awx.main.registrar import activity_stream_registrar from awx.main.models.mixins import ResourceMixin, TaskManagerUnifiedJobMixin @@ -101,7 +103,7 @@ class Meta: ordering = ('name',) # unique_together here is intentionally commented out. Please make sure sub-classes of this model # contain at least this uniqueness restriction: SOFT_UNIQUE_TOGETHER = [('polymorphic_ctype', 'name')] - #unique_together = [('polymorphic_ctype', 'name')] + #unique_together = [('polymorphic_ctype', 'name', 'organization')] old_pk = models.PositiveIntegerField( null=True, @@ -148,7 +150,7 @@ class Meta: default=None, editable=False, related_name='%(class)s_as_next_schedule+', - on_delete=models.SET_NULL, + on_delete=polymorphic.SET_NULL, ) status = models.CharField( max_length=32, @@ -156,6 +158,14 @@ class Meta: default='ok', editable=False, ) + organization = models.ForeignKey( + 'Organization', + blank=True, + null=True, + on_delete=polymorphic.SET_NULL, + related_name='%(class)ss', + help_text=_('The organization used to determine access to this template.'), + ) credentials = models.ManyToManyField( 'Credential', related_name='%(class)ss', @@ -403,9 +413,8 @@ def create_unified_job(self, **kwargs): if 'extra_vars' in validated_kwargs: unified_job.handle_extra_data(validated_kwargs['extra_vars']) - if not getattr(self, '_deprecated_credential_launch', False): - # Create record of provided prompts for relaunch and rescheduling - unified_job.create_config_from_prompts(kwargs, parent=self) + # Create record of provided prompts for relaunch and rescheduling + unified_job.create_config_from_prompts(kwargs, parent=self) # manually issue the create activity stream entry _after_ M2M relations # have been associated to the UJ @@ -577,7 +586,7 @@ class Meta: null=True, default=None, editable=False, - on_delete=models.SET_NULL, + on_delete=polymorphic.SET_NULL, ) dependent_jobs = models.ManyToManyField( 'self', @@ -623,6 +632,11 @@ class Meta: editable=False, help_text=_("The date and time the job was queued for starting."), ) + dependencies_processed = models.BooleanField( + default=False, + editable=False, + help_text=_("If True, the task manager has already processed potential dependencies for this job.") + ) finished = models.DateTimeField( null=True, default=None, @@ -630,6 +644,13 @@ class Meta: help_text=_("The date and time the job finished execution."), db_index=True, ) + canceled_on = models.DateTimeField( + null=True, + default=None, + editable=False, + help_text=_("The date and time when the cancel request was sent."), + db_index=True, + ) elapsed = models.DecimalField( max_digits=12, decimal_places=3, @@ -685,7 +706,15 @@ class Meta: null=True, default=None, on_delete=polymorphic.SET_NULL, - help_text=_('The Rampart/Instance group the job was run under'), + help_text=_('The Instance group the job was run under'), + ) + organization = models.ForeignKey( + 'Organization', + blank=True, + null=True, + on_delete=polymorphic.SET_NULL, + related_name='%(class)ss', + help_text=_('The organization used to determine access to this unified job.'), ) credentials = models.ManyToManyField( 'Credential', @@ -833,13 +862,24 @@ def save(self, *args, **kwargs): self.unified_job_template = self._get_parent_instance() if 'unified_job_template' not in update_fields: update_fields.append('unified_job_template') - + + if self.cancel_flag and not self.canceled_on: + # Record the 'canceled' time. + self.canceled_on = now() + if 'canceled_on' not in update_fields: + update_fields.append('canceled_on') # Okay; we're done. Perform the actual save. result = super(UnifiedJob, self).save(*args, **kwargs) # If status changed, update the parent instance. if self.status != status_before: - self._update_parent_instance() + # Update parent outside of the transaction for Job w/ allow_simultaneous=True + # This dodges lock contention at the expense of the foreign key not being + # completely correct. + if getattr(self, 'allow_simultaneous', False): + connection.on_commit(self._update_parent_instance) + else: + self._update_parent_instance() # Done. return result @@ -928,6 +968,10 @@ def create_config_from_prompts(self, kwargs, parent=None): def event_class(self): raise NotImplementedError() + @property + def job_type_name(self): + return self.get_real_instance_class()._meta.verbose_name.replace(' ', '_') + @property def result_stdout_text(self): related = UnifiedJobDeprecatedStdout.objects.get(pk=self.pk) @@ -997,6 +1041,8 @@ def result_stdout_raw_handle(self, enforce_max_bytes=True): dir=settings.JOBOUTPUT_ROOT, encoding='utf-8' ) + from awx.main.tasks import purge_old_stdout_files # circular import + purge_old_stdout_files.apply_async() # Before the addition of event-based stdout, older versions of # awx stored stdout as raw text blobs in a certain database column @@ -1185,7 +1231,7 @@ def task_impact(self): def websocket_emit_data(self): ''' Return extra data that should be included when submitting data to the browser over the websocket connection ''' - websocket_data = dict(type=self.get_real_instance_class()._meta.verbose_name.replace(' ', '_')) + websocket_data = dict(type=self.job_type_name) if self.spawned_by_workflow: websocket_data.update(dict(workflow_job_id=self.workflow_job_id, workflow_node_id=self.workflow_node_id)) @@ -1199,12 +1245,17 @@ def _websocket_emit_status(self, status): status_data['instance_group_name'] = self.instance_group.name else: status_data['instance_group_name'] = None + elif status in ['successful', 'failed', 'canceled'] and self.finished: + status_data['finished'] = datetime.datetime.strftime(self.finished, "%Y-%m-%dT%H:%M:%S.%fZ") status_data.update(self.websocket_emit_data()) status_data['group_name'] = 'jobs' + if getattr(self, 'unified_job_template_id', None): + status_data['unified_job_template_id'] = self.unified_job_template_id emit_channel_notification('jobs-status_changed', status_data) if self.spawned_by_workflow: status_data['group_name'] = "workflow_events" + status_data['workflow_job_template_id'] = self.unified_job_template.id emit_channel_notification('workflow_events-' + str(self.workflow_job_id), status_data) except IOError: # includes socket errors logger.exception('%s failed to emit channel msg about status change', self.log_format) @@ -1319,9 +1370,9 @@ def actually_running(self): timeout = 5 try: running = self.celery_task_id in ControlDispatcher( - 'dispatcher', self.execution_node + 'dispatcher', self.controller_node or self.execution_node ).running(timeout=timeout) - except socket.timeout: + except (socket.timeout, RuntimeError): logger.error('could not reach dispatcher on {} within {}s'.format( self.execution_node, timeout )) @@ -1425,7 +1476,7 @@ def awx_meta_vars(self): return r def get_queue_name(self): - return self.controller_node or self.execution_node or settings.CELERY_DEFAULT_QUEUE + return self.controller_node or self.execution_node or get_local_queuename() def is_isolated(self): return bool(self.controller_node) diff --git a/awx/main/models/workflow.py b/awx/main/models/workflow.py index 100ba1c323f1..d9ac8afcf9df 100644 --- a/awx/main/models/workflow.py +++ b/awx/main/models/workflow.py @@ -4,6 +4,7 @@ # Python import json import logging +from uuid import uuid4 from copy import copy from urllib.parse import urljoin @@ -79,6 +80,11 @@ class Meta: symmetrical=False, related_name='%(class)ss_always', ) + all_parents_must_converge = models.BooleanField( + default=False, + help_text=_("If enabled then the node will only run if all of the parent nodes " + "have met the criteria to reach this node") + ) unified_job_template = models.ForeignKey( 'UnifiedJobTemplate', related_name='%(class)ss', @@ -102,7 +108,7 @@ def _get_workflow_job_field_names(cls): ''' return ['workflow_job', 'unified_job_template', 'extra_data', 'survey_passwords', - 'inventory', 'credentials', 'char_prompts'] + 'inventory', 'credentials', 'char_prompts', 'all_parents_must_converge'] def create_workflow_job_node(self, **kwargs): ''' @@ -116,6 +122,7 @@ def create_workflow_job_node(self, **kwargs): create_kwargs[field_name] = kwargs[field_name] elif hasattr(self, field_name): create_kwargs[field_name] = getattr(self, field_name) + create_kwargs['identifier'] = self.identifier new_node = WorkflowJobNode.objects.create(**create_kwargs) if self.pk: allowed_creds = self.credentials.all() @@ -130,15 +137,30 @@ class WorkflowJobTemplateNode(WorkflowNodeBase): FIELDS_TO_PRESERVE_AT_COPY = [ 'unified_job_template', 'workflow_job_template', 'success_nodes', 'failure_nodes', 'always_nodes', 'credentials', 'inventory', 'extra_data', 'survey_passwords', - 'char_prompts' + 'char_prompts', 'all_parents_must_converge', 'identifier' ] - REENCRYPTION_BLACKLIST_AT_COPY = ['extra_data', 'survey_passwords'] + REENCRYPTION_BLOCKLIST_AT_COPY = ['extra_data', 'survey_passwords'] workflow_job_template = models.ForeignKey( 'WorkflowJobTemplate', related_name='workflow_job_template_nodes', on_delete=models.CASCADE, ) + identifier = models.CharField( + max_length=512, + default=uuid4, + blank=False, + help_text=_( + 'An identifier for this node that is unique within its workflow. ' + 'It is copied to workflow job nodes corresponding to this node.'), + ) + + class Meta: + app_label = 'main' + unique_together = (("identifier", "workflow_job_template"),) + indexes = [ + models.Index(fields=['identifier']), + ] def get_absolute_url(self, request=None): return reverse('api:workflow_job_template_node_detail', kwargs={'pk': self.pk}, request=request) @@ -208,6 +230,18 @@ class WorkflowJobNode(WorkflowNodeBase): "semantics will mark this True if the node is in a path that will " "decidedly not be ran. A value of False means the node may not run."), ) + identifier = models.CharField( + max_length=512, + blank=True, # blank denotes pre-migration job nodes + help_text=_('An identifier coresponding to the workflow job template node that this node was created from.'), + ) + + class Meta: + app_label = 'main' + indexes = [ + models.Index(fields=["identifier", "workflow_job"]), + models.Index(fields=['identifier']), + ] def get_absolute_url(self, request=None): return reverse('api:workflow_job_node_detail', kwargs={'pk': self.pk}, request=request) @@ -330,7 +364,7 @@ def workflow_nodes(self): @classmethod def _get_unified_job_field_names(cls): r = set(f.name for f in WorkflowJobOptions._meta.fields) | set( - ['name', 'description', 'survey_passwords', 'labels', 'limit', 'scm_branch'] + ['name', 'description', 'organization', 'survey_passwords', 'labels', 'limit', 'scm_branch'] ) r.remove('char_prompts') # needed due to copying launch config to launch config return r @@ -371,19 +405,12 @@ class WorkflowJobTemplate(UnifiedJobTemplate, WorkflowJobOptions, SurveyJobTempl SOFT_UNIQUE_TOGETHER = [('polymorphic_ctype', 'name', 'organization')] FIELDS_TO_PRESERVE_AT_COPY = [ - 'labels', 'instance_groups', 'workflow_job_template_nodes', 'credentials', 'survey_spec' + 'labels', 'organization', 'instance_groups', 'workflow_job_template_nodes', 'credentials', 'survey_spec' ] class Meta: app_label = 'main' - organization = models.ForeignKey( - 'Organization', - blank=True, - null=True, - on_delete=models.SET_NULL, - related_name='workflows', - ) ask_inventory_on_launch = AskForField( blank=True, default=False, @@ -593,7 +620,7 @@ def get_absolute_url(self, request=None): return reverse('api:workflow_job_detail', kwargs={'pk': self.pk}, request=request) def get_ui_url(self): - return urljoin(settings.TOWER_URL_BASE, '/#/workflows/{}'.format(self.pk)) + return urljoin(settings.TOWER_URL_BASE, '/#/jobs/workflow/{}'.format(self.pk)) def notification_data(self): result = super(WorkflowJob, self).notification_data() @@ -647,7 +674,7 @@ def actually_running(self): return self.status == 'running' -class WorkflowApprovalTemplate(UnifiedJobTemplate): +class WorkflowApprovalTemplate(UnifiedJobTemplate, RelatedJobsMixin): FIELDS_TO_PRESERVE_AT_COPY = ['description', 'timeout',] @@ -675,6 +702,12 @@ def get_absolute_url(self, request=None): def workflow_job_template(self): return self.workflowjobtemplatenodes.first().workflow_job_template + ''' + RelatedJobsMixin + ''' + def _get_related_jobs(self): + return UnifiedJob.objects.filter(unified_job_template=self) + class WorkflowApproval(UnifiedJob, JobNotificationMixin): class Meta: @@ -719,7 +752,7 @@ def event_class(self): return None def get_ui_url(self): - return urljoin(settings.TOWER_URL_BASE, '/#/workflows/{}'.format(self.workflow_job.id)) + return urljoin(settings.TOWER_URL_BASE, '/#/jobs/workflow/{}'.format(self.workflow_job.id)) def _get_parent_field_name(self): return 'workflow_approval_template' @@ -744,9 +777,15 @@ def deny(self, request=None): def signal_start(self, **kwargs): can_start = super(WorkflowApproval, self).signal_start(**kwargs) + self.started = self.created + self.save(update_fields=['started']) self.send_approval_notification('running') return can_start + @property + def event_processing_finished(self): + return True + def send_approval_notification(self, approval_status): from awx.main.tasks import send_notifications # avoid circular import if self.workflow_job_template is None: @@ -801,7 +840,7 @@ def build_approval_notification_message(self, nt, approval_status): return (msg, body) def context(self, approval_status): - workflow_url = urljoin(settings.TOWER_URL_BASE, '/#/workflows/{}'.format(self.workflow_job.id)) + workflow_url = urljoin(settings.TOWER_URL_BASE, '/#/jobs/workflow/{}'.format(self.workflow_job.id)) return {'approval_status': approval_status, 'approval_node_name': self.workflow_approval_template.name, 'workflow_url': workflow_url, diff --git a/awx/main/notifications/grafana_backend.py b/awx/main/notifications/grafana_backend.py index 58137f27aa1b..8e8b6489520b 100644 --- a/awx/main/notifications/grafana_backend.py +++ b/awx/main/notifications/grafana_backend.py @@ -2,6 +2,7 @@ # All Rights Reserved. import datetime +import json import logging import requests import dateutil.parser as dp @@ -12,6 +13,19 @@ from awx.main.notifications.base import AWXBaseEmailBackend from awx.main.notifications.custom_notification_base import CustomNotificationBase +DEFAULT_MSG = CustomNotificationBase.DEFAULT_MSG + +DEFAULT_APPROVAL_RUNNING_MSG = CustomNotificationBase.DEFAULT_APPROVAL_RUNNING_MSG +DEFAULT_APPROVAL_RUNNING_BODY = CustomNotificationBase.DEFAULT_APPROVAL_RUNNING_BODY + +DEFAULT_APPROVAL_APPROVED_MSG = CustomNotificationBase.DEFAULT_APPROVAL_APPROVED_MSG +DEFAULT_APPROVAL_APPROVED_BODY = CustomNotificationBase.DEFAULT_APPROVAL_APPROVED_BODY + +DEFAULT_APPROVAL_TIMEOUT_MSG = CustomNotificationBase.DEFAULT_APPROVAL_TIMEOUT_MSG +DEFAULT_APPROVAL_TIMEOUT_BODY = CustomNotificationBase.DEFAULT_APPROVAL_TIMEOUT_BODY + +DEFAULT_APPROVAL_DENIED_MSG = CustomNotificationBase.DEFAULT_APPROVAL_DENIED_MSG +DEFAULT_APPROVAL_DENIED_BODY = CustomNotificationBase.DEFAULT_APPROVAL_DENIED_BODY logger = logging.getLogger('awx.main.notifications.grafana_backend') @@ -23,6 +37,15 @@ class GrafanaBackend(AWXBaseEmailBackend, CustomNotificationBase): recipient_parameter = "grafana_url" sender_parameter = None + DEFAULT_BODY = "{{ job_metadata }}" + default_messages = {"started": {"body": DEFAULT_BODY, "message": DEFAULT_MSG}, + "success": {"body": DEFAULT_BODY, "message": DEFAULT_MSG}, + "error": {"body": DEFAULT_BODY, "message": DEFAULT_MSG}, + "workflow_approval": {"running": {"message": DEFAULT_APPROVAL_RUNNING_MSG, "body": DEFAULT_APPROVAL_RUNNING_BODY}, + "approved": {"message": DEFAULT_APPROVAL_APPROVED_MSG,"body": DEFAULT_APPROVAL_APPROVED_BODY}, + "timed_out": {"message": DEFAULT_APPROVAL_TIMEOUT_MSG, "body": DEFAULT_APPROVAL_TIMEOUT_BODY}, + "denied": {"message": DEFAULT_APPROVAL_DENIED_MSG, "body": DEFAULT_APPROVAL_DENIED_BODY}}} + def __init__(self, grafana_key,dashboardId=None, panelId=None, annotation_tags=None, grafana_no_verify_ssl=False, isRegion=True, fail_silently=False, **kwargs): super(GrafanaBackend, self).__init__(fail_silently=fail_silently) @@ -34,6 +57,13 @@ def __init__(self, grafana_key,dashboardId=None, panelId=None, annotation_tags=N self.isRegion = isRegion def format_body(self, body): + # expect body to be a string representing a dict + try: + potential_body = json.loads(body) + if isinstance(potential_body, dict): + body = potential_body + except json.JSONDecodeError: + body = {} return body def send_messages(self, messages): @@ -41,18 +71,21 @@ def send_messages(self, messages): for m in messages: grafana_data = {} grafana_headers = {} - try: - epoch=datetime.datetime.utcfromtimestamp(0) - grafana_data['time'] = int((dp.parse(m.body['started']).replace(tzinfo=None) - epoch).total_seconds() * 1000) - grafana_data['timeEnd'] = int((dp.parse(m.body['finished']).replace(tzinfo=None) - epoch).total_seconds() * 1000) - except ValueError: - logger.error(smart_text(_("Error converting time {} or timeEnd {} to int.").format(m.body['started'],m.body['finished']))) - if not self.fail_silently: - raise Exception(smart_text(_("Error converting time {} and/or timeEnd {} to int.").format(m.body['started'],m.body['finished']))) + if 'started' in m.body: + try: + epoch=datetime.datetime.utcfromtimestamp(0) + grafana_data['time'] = grafana_data['timeEnd'] = int((dp.parse(m.body['started']).replace(tzinfo=None) - epoch).total_seconds() * 1000) + if m.body.get('finished'): + grafana_data['timeEnd'] = int((dp.parse(m.body['finished']).replace(tzinfo=None) - epoch).total_seconds() * 1000) + except ValueError: + logger.error(smart_text(_("Error converting time {} or timeEnd {} to int.").format(m.body['started'],m.body['finished']))) + if not self.fail_silently: + raise Exception(smart_text(_("Error converting time {} and/or timeEnd {} to int.").format(m.body['started'],m.body['finished']))) grafana_data['isRegion'] = self.isRegion grafana_data['dashboardId'] = self.dashboardId grafana_data['panelId'] = self.panelId - grafana_data['tags'] = self.annotation_tags + if self.annotation_tags: + grafana_data['tags'] = self.annotation_tags grafana_data['text'] = m.subject grafana_headers['Authorization'] = "Bearer {}".format(self.grafana_key) grafana_headers['Content-Type'] = "application/json" @@ -61,8 +94,8 @@ def send_messages(self, messages): headers=grafana_headers, verify=(not self.grafana_no_verify_ssl)) if r.status_code >= 400: - logger.error(smart_text(_("Error sending notification grafana: {}").format(r.text))) + logger.error(smart_text(_("Error sending notification grafana: {}").format(r.status_code))) if not self.fail_silently: - raise Exception(smart_text(_("Error sending notification grafana: {}").format(r.text))) + raise Exception(smart_text(_("Error sending notification grafana: {}").format(r.status_code))) sent_messages += 1 return sent_messages diff --git a/awx/main/notifications/hipchat_backend.py b/awx/main/notifications/hipchat_backend.py deleted file mode 100644 index 16790644a35c..000000000000 --- a/awx/main/notifications/hipchat_backend.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright (c) 2016 Ansible, Inc. -# All Rights Reserved. - -import logging - -import requests - -from django.utils.encoding import smart_text -from django.utils.translation import ugettext_lazy as _ - -from awx.main.notifications.base import AWXBaseEmailBackend -from awx.main.notifications.custom_notification_base import CustomNotificationBase - -logger = logging.getLogger('awx.main.notifications.hipchat_backend') - - -class HipChatBackend(AWXBaseEmailBackend, CustomNotificationBase): - - init_parameters = {"token": {"label": "Token", "type": "password"}, - "rooms": {"label": "Destination Rooms", "type": "list"}, - "color": {"label": "Notification Color", "type": "string"}, - "api_url": {"label": "API Url (e.g: https://mycompany.hipchat.com)", "type": "string"}, - "notify": {"label": "Notify room", "type": "bool"}, - "message_from": {"label": "Label to be shown with notification", "type": "string"}} - recipient_parameter = "rooms" - sender_parameter = "message_from" - - def __init__(self, token, color, api_url, notify, fail_silently=False, **kwargs): - super(HipChatBackend, self).__init__(fail_silently=fail_silently) - self.token = token - if color is not None: - self.color = color.lower() - self.api_url = api_url - self.notify = notify - - def send_messages(self, messages): - sent_messages = 0 - - for m in messages: - for rcp in m.recipients(): - r = requests.post("{}/v2/room/{}/notification".format(self.api_url, rcp), - params={"auth_token": self.token}, - verify=False, - json={"color": self.color, - "message": m.subject, - "notify": self.notify, - "from": m.from_email, - "message_format": "text"}) - if r.status_code != 204: - logger.error(smart_text(_("Error sending messages: {}").format(r.text))) - if not self.fail_silently: - raise Exception(smart_text(_("Error sending message to hipchat: {}").format(r.text))) - sent_messages += 1 - return sent_messages diff --git a/awx/main/notifications/mattermost_backend.py b/awx/main/notifications/mattermost_backend.py index 7a759d41a363..59a1c6f5e10c 100644 --- a/awx/main/notifications/mattermost_backend.py +++ b/awx/main/notifications/mattermost_backend.py @@ -3,7 +3,6 @@ import logging import requests -import json from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -45,10 +44,10 @@ def send_messages(self, messages): payload['text'] = m.subject r = requests.post("{}".format(m.recipients()[0]), - data=json.dumps(payload), verify=(not self.mattermost_no_verify_ssl)) + json=payload, verify=(not self.mattermost_no_verify_ssl)) if r.status_code >= 400: - logger.error(smart_text(_("Error sending notification mattermost: {}").format(r.text))) + logger.error(smart_text(_("Error sending notification mattermost: {}").format(r.status_code))) if not self.fail_silently: - raise Exception(smart_text(_("Error sending notification mattermost: {}").format(r.text))) + raise Exception(smart_text(_("Error sending notification mattermost: {}").format(r.status_code))) sent_messages += 1 return sent_messages diff --git a/awx/main/notifications/pagerduty_backend.py b/awx/main/notifications/pagerduty_backend.py index 45869a34dbcb..18d2290caff7 100644 --- a/awx/main/notifications/pagerduty_backend.py +++ b/awx/main/notifications/pagerduty_backend.py @@ -11,9 +11,20 @@ from awx.main.notifications.base import AWXBaseEmailBackend from awx.main.notifications.custom_notification_base import CustomNotificationBase -DEFAULT_BODY = CustomNotificationBase.DEFAULT_BODY DEFAULT_MSG = CustomNotificationBase.DEFAULT_MSG +DEFAULT_APPROVAL_RUNNING_MSG = CustomNotificationBase.DEFAULT_APPROVAL_RUNNING_MSG +DEFAULT_APPROVAL_RUNNING_BODY = CustomNotificationBase.DEFAULT_APPROVAL_RUNNING_BODY + +DEFAULT_APPROVAL_APPROVED_MSG = CustomNotificationBase.DEFAULT_APPROVAL_APPROVED_MSG +DEFAULT_APPROVAL_APPROVED_BODY = CustomNotificationBase.DEFAULT_APPROVAL_APPROVED_BODY + +DEFAULT_APPROVAL_TIMEOUT_MSG = CustomNotificationBase.DEFAULT_APPROVAL_TIMEOUT_MSG +DEFAULT_APPROVAL_TIMEOUT_BODY = CustomNotificationBase.DEFAULT_APPROVAL_TIMEOUT_BODY + +DEFAULT_APPROVAL_DENIED_MSG = CustomNotificationBase.DEFAULT_APPROVAL_DENIED_MSG +DEFAULT_APPROVAL_DENIED_BODY = CustomNotificationBase.DEFAULT_APPROVAL_DENIED_BODY + logger = logging.getLogger('awx.main.notifications.pagerduty_backend') @@ -30,10 +41,10 @@ class PagerDutyBackend(AWXBaseEmailBackend, CustomNotificationBase): default_messages = {"started": {"message": DEFAULT_MSG, "body": DEFAULT_BODY}, "success": {"message": DEFAULT_MSG, "body": DEFAULT_BODY}, "error": {"message": DEFAULT_MSG, "body": DEFAULT_BODY}, - "workflow_approval": {"running": {"message": DEFAULT_MSG, "body": DEFAULT_BODY}, - "approved": {"message": DEFAULT_MSG,"body": DEFAULT_BODY}, - "timed_out": {"message": DEFAULT_MSG, "body": DEFAULT_BODY}, - "denied": {"message": DEFAULT_MSG, "body": DEFAULT_BODY}}} + "workflow_approval": {"running": {"message": DEFAULT_APPROVAL_RUNNING_MSG, "body": DEFAULT_APPROVAL_RUNNING_BODY}, + "approved": {"message": DEFAULT_APPROVAL_APPROVED_MSG,"body": DEFAULT_APPROVAL_APPROVED_BODY}, + "timed_out": {"message": DEFAULT_APPROVAL_TIMEOUT_MSG, "body": DEFAULT_APPROVAL_TIMEOUT_BODY}, + "denied": {"message": DEFAULT_APPROVAL_DENIED_MSG, "body": DEFAULT_APPROVAL_DENIED_BODY}}} def __init__(self, subdomain, token, fail_silently=False, **kwargs): super(PagerDutyBackend, self).__init__(fail_silently=fail_silently) diff --git a/awx/main/notifications/rocketchat_backend.py b/awx/main/notifications/rocketchat_backend.py index 1ad367fb57e6..df271bf80d89 100644 --- a/awx/main/notifications/rocketchat_backend.py +++ b/awx/main/notifications/rocketchat_backend.py @@ -46,9 +46,9 @@ def send_messages(self, messages): if r.status_code >= 400: logger.error(smart_text( - _("Error sending notification rocket.chat: {}").format(r.text))) + _("Error sending notification rocket.chat: {}").format(r.status_code))) if not self.fail_silently: raise Exception(smart_text( - _("Error sending notification rocket.chat: {}").format(r.text))) + _("Error sending notification rocket.chat: {}").format(r.status_code))) sent_messages += 1 return sent_messages diff --git a/awx/main/notifications/webhook_backend.py b/awx/main/notifications/webhook_backend.py index b9c2c35d22e2..d67fc11a355d 100644 --- a/awx/main/notifications/webhook_backend.py +++ b/awx/main/notifications/webhook_backend.py @@ -57,6 +57,7 @@ def format_body(self, body): def send_messages(self, messages): sent_messages = 0 + self.headers['Content-Type'] = 'application/json' if 'User-Agent' not in self.headers: self.headers['User-Agent'] = "Tower {}".format(get_awx_version()) if self.http_method.lower() not in ['put','post']: @@ -68,12 +69,12 @@ def send_messages(self, messages): auth = (self.username, self.password) r = chosen_method("{}".format(m.recipients()[0]), auth=auth, - json=m.body, + data=json.dumps(m.body, ensure_ascii=False).encode('utf-8'), headers=self.headers, verify=(not self.disable_ssl_verification)) if r.status_code >= 400: - logger.error(smart_text(_("Error sending notification webhook: {}").format(r.text))) + logger.error(smart_text(_("Error sending notification webhook: {}").format(r.status_code))) if not self.fail_silently: - raise Exception(smart_text(_("Error sending notification webhook: {}").format(r.text))) + raise Exception(smart_text(_("Error sending notification webhook: {}").format(r.status_code))) sent_messages += 1 return sent_messages diff --git a/awx/main/queue.py b/awx/main/queue.py index 0da0e22e48e0..762879fd2c5f 100644 --- a/awx/main/queue.py +++ b/awx/main/queue.py @@ -4,15 +4,11 @@ # Python import json import logging -import os +import redis # Django from django.conf import settings -# Kombu -from awx.main.dispatch.kombu import Connection -from kombu import Exchange, Producer -from kombu.serialization import registry __all__ = ['CallbackQueueDispatcher'] @@ -28,47 +24,12 @@ def default(self, o): return super(AnsibleJSONEncoder, self).default(o) -registry.register( - 'json-ansible', - lambda obj: json.dumps(obj, cls=AnsibleJSONEncoder), - lambda obj: json.loads(obj), - content_type='application/json', - content_encoding='utf-8' -) - - class CallbackQueueDispatcher(object): def __init__(self): - self.callback_connection = getattr(settings, 'BROKER_URL', None) - self.connection_queue = getattr(settings, 'CALLBACK_QUEUE', '') - self.connection = None - self.exchange = None + self.queue = getattr(settings, 'CALLBACK_QUEUE', '') self.logger = logging.getLogger('awx.main.queue.CallbackQueueDispatcher') + self.connection = redis.Redis.from_url(settings.BROKER_URL) def dispatch(self, obj): - if not self.callback_connection or not self.connection_queue: - return - active_pid = os.getpid() - for retry_count in range(4): - try: - if not hasattr(self, 'connection_pid'): - self.connection_pid = active_pid - if self.connection_pid != active_pid: - self.connection = None - if self.connection is None: - self.connection = Connection(self.callback_connection) - self.exchange = Exchange(self.connection_queue, type='direct') - - producer = Producer(self.connection) - producer.publish(obj, - serializer='json-ansible', - compression='bzip2', - exchange=self.exchange, - declare=[self.exchange], - delivery_mode="persistent" if settings.PERSISTENT_CALLBACK_MESSAGES else "transient", - routing_key=self.connection_queue) - return - except Exception as e: - self.logger.info('Publish Job Event Exception: %r, retry=%d', e, - retry_count, exc_info=True) + self.connection.rpush(self.queue, json.dumps(obj, cls=AnsibleJSONEncoder)) diff --git a/awx/main/redact.py b/awx/main/redact.py index 77fc062135c8..32899d935e94 100644 --- a/awx/main/redact.py +++ b/awx/main/redact.py @@ -1,23 +1,15 @@ import re import urllib.parse as urlparse -from django.conf import settings - REPLACE_STR = '$encrypted$' class UriCleaner(object): REPLACE_STR = REPLACE_STR - SENSITIVE_URI_PATTERN = re.compile(r'(\w+:(\/?\/?)[^\s]+)', re.MULTILINE) # NOQA + SENSITIVE_URI_PATTERN = re.compile(r'(\w{1,20}:(\/?\/?)[^\s]+)', re.MULTILINE) # NOQA @staticmethod def remove_sensitive(cleartext): - # exclude_list contains the items that will _not_ be redacted - exclude_list = [settings.PUBLIC_GALAXY_SERVER['url']] - if settings.PRIMARY_GALAXY_URL: - exclude_list += [settings.PRIMARY_GALAXY_URL] - if settings.FALLBACK_GALAXY_SERVERS: - exclude_list += [server['url'] for server in settings.FALLBACK_GALAXY_SERVERS] redactedtext = cleartext text_index = 0 while True: @@ -25,10 +17,6 @@ def remove_sensitive(cleartext): if not match: break uri_str = match.group(1) - # Do not redact items from the exclude list - if any(uri_str.startswith(exclude_uri) for exclude_uri in exclude_list): - text_index = match.start() + len(uri_str) - continue try: # May raise a ValueError if invalid URI for one reason or another o = urlparse.urlsplit(uri_str) diff --git a/awx/main/routing.py b/awx/main/routing.py index 0a49f25c6cb6..2866d46ed05b 100644 --- a/awx/main/routing.py +++ b/awx/main/routing.py @@ -1,8 +1,38 @@ -from channels.routing import route +import redis +import logging +from django.conf.urls import url +from django.conf import settings -channel_routing = [ - route("websocket.connect", "awx.main.consumers.ws_connect", path=r'^/websocket/$'), - route("websocket.disconnect", "awx.main.consumers.ws_disconnect", path=r'^/websocket/$'), - route("websocket.receive", "awx.main.consumers.ws_receive", path=r'^/websocket/$'), +from channels.auth import AuthMiddlewareStack +from channels.routing import ProtocolTypeRouter, URLRouter + +from . import consumers + + +logger = logging.getLogger('awx.main.routing') + + +class AWXProtocolTypeRouter(ProtocolTypeRouter): + def __init__(self, *args, **kwargs): + try: + r = redis.Redis.from_url(settings.BROKER_URL) + for k in r.scan_iter('asgi:*', 500): + logger.debug(f"cleaning up Redis key {k}") + r.delete(k) + except redis.exceptions.RedisError as e: + logger.warn("encountered an error communicating with redis.") + raise e + super().__init__(*args, **kwargs) + + +websocket_urlpatterns = [ + url(r'websocket/$', consumers.EventConsumer), + url(r'websocket/broadcast/$', consumers.BroadcastConsumer), ] + +application = AWXProtocolTypeRouter({ + 'websocket': AuthMiddlewareStack( + URLRouter(websocket_urlpatterns) + ), +}) diff --git a/awx/main/scheduler/dag_simple.py b/awx/main/scheduler/dag_simple.py index 71f1ff73c2e9..5a354edbba19 100644 --- a/awx/main/scheduler/dag_simple.py +++ b/awx/main/scheduler/dag_simple.py @@ -89,8 +89,8 @@ def run_status(obj): run_status(n['node_object']), color ) - for label, edges in self.node_from_edges_by_label.iteritems(): - for from_node, to_nodes in edges.iteritems(): + for label, edges in self.node_from_edges_by_label.items(): + for from_node, to_nodes in edges.items(): for to_node in to_nodes: doc += "%s -> %s [ label=\"%s\" ];\n" % ( run_status(self.nodes[from_node]['node_object']), @@ -123,7 +123,7 @@ def add_edge(self, from_obj, to_obj, label): self.root_nodes.discard(to_obj_ord) if from_obj_ord is None and to_obj_ord is None: - raise LookupError("From object {} and to object not found".format(from_obj, to_obj)) + raise LookupError("From object {} and to object {} not found".format(from_obj, to_obj)) elif from_obj_ord is None: raise LookupError("From object not found {}".format(from_obj)) elif to_obj_ord is None: @@ -140,36 +140,36 @@ def add_edge(self, from_obj, to_obj, label): def find_ord(self, obj): return self.node_obj_to_node_index.get(obj, None) - def _get_dependencies_by_label(self, node_index, label): + def _get_children_by_label(self, node_index, label): return [self.nodes[index] for index in self.node_from_edges_by_label.get(label, {}) .get(node_index, [])] - def get_dependencies(self, obj, label=None): + def get_children(self, obj, label=None): this_ord = self.find_ord(obj) nodes = [] if label: - return self._get_dependencies_by_label(this_ord, label) + return self._get_children_by_label(this_ord, label) else: nodes = [] - for l in self.node_from_edges_by_label.keys(): - nodes.extend(self._get_dependencies_by_label(this_ord, l)) + for label_obj in self.node_from_edges_by_label.keys(): + nodes.extend(self._get_children_by_label(this_ord, label_obj)) return nodes - def _get_dependents_by_label(self, node_index, label): + def _get_parents_by_label(self, node_index, label): return [self.nodes[index] for index in self.node_to_edges_by_label.get(label, {}) .get(node_index, [])] - def get_dependents(self, obj, label=None): + def get_parents(self, obj, label=None): this_ord = self.find_ord(obj) nodes = [] if label: - return self._get_dependents_by_label(this_ord, label) + return self._get_parents_by_label(this_ord, label) else: nodes = [] - for l in self.node_to_edges_by_label.keys(): - nodes.extend(self._get_dependents_by_label(this_ord, l)) + for label_obj in self.node_to_edges_by_label.keys(): + nodes.extend(self._get_parents_by_label(this_ord, label_obj)) return nodes def get_root_nodes(self): @@ -188,7 +188,7 @@ def has_cycle(self): while stack: node_obj = stack.pop() - children = [node['node_object'] for node in self.get_dependencies(node_obj)] + children = [node['node_object'] for node in self.get_children(node_obj)] children_to_add = list(filter(lambda node_obj: node_obj not in node_objs_visited, children)) if children_to_add: @@ -212,7 +212,7 @@ def visit(node): if obj.id in obj_ids_processed: return - for child in self.get_dependencies(obj): + for child in self.get_children(obj): visit(child) obj_ids_processed.add(obj.id) nodes_sorted.appendleft(node) diff --git a/awx/main/scheduler/dag_workflow.py b/awx/main/scheduler/dag_workflow.py index 8676630247f1..3d26a4da7f00 100644 --- a/awx/main/scheduler/dag_workflow.py +++ b/awx/main/scheduler/dag_workflow.py @@ -55,7 +55,7 @@ def _init_graph(self, workflow_job_or_jt): def _are_relevant_parents_finished(self, node): obj = node['node_object'] - parent_nodes = [p['node_object'] for p in self.get_dependents(obj)] + parent_nodes = [p['node_object'] for p in self.get_parents(obj)] for p in parent_nodes: if p.do_not_run is True: continue @@ -69,33 +69,55 @@ def _are_relevant_parents_finished(self, node): return False return True + def _all_parents_met_convergence_criteria(self, node): + # This function takes any node and checks that all it's parents have met their criteria to run the child. + # This returns a boolean and is really only useful if the node is an ALL convergence node and is + # intended to be used in conjuction with the node property `all_parents_must_converge` + obj = node['node_object'] + parent_nodes = [p['node_object'] for p in self.get_parents(obj)] + for p in parent_nodes: + #node has a status + if p.job and p.job.status in ["successful", "failed"]: + if p.job and p.job.status == "successful": + status = "success_nodes" + elif p.job and p.job.status == "failed": + status = "failure_nodes" + #check that the nodes status matches either a pathway of the same status or is an always path. + if (p not in [node['node_object'] for node in self.get_parents(obj, status)] and + p not in [node['node_object'] for node in self.get_parents(obj, "always_nodes")]): + return False + return True + def bfs_nodes_to_run(self): nodes = self.get_root_nodes() nodes_found = [] node_ids_visited = set() - for index, n in enumerate(nodes): obj = n['node_object'] if obj.id in node_ids_visited: continue node_ids_visited.add(obj.id) - if obj.do_not_run is True: continue - - if obj.job: + elif obj.job: if obj.job.status in ['failed', 'error', 'canceled']: - nodes.extend(self.get_dependencies(obj, 'failure_nodes') + - self.get_dependencies(obj, 'always_nodes')) + nodes.extend(self.get_children(obj, 'failure_nodes') + + self.get_children(obj, 'always_nodes')) elif obj.job.status == 'successful': - nodes.extend(self.get_dependencies(obj, 'success_nodes') + - self.get_dependencies(obj, 'always_nodes')) + nodes.extend(self.get_children(obj, 'success_nodes') + + self.get_children(obj, 'always_nodes')) elif obj.unified_job_template is None: - nodes.extend(self.get_dependencies(obj, 'failure_nodes') + - self.get_dependencies(obj, 'always_nodes')) + nodes.extend(self.get_children(obj, 'failure_nodes') + + self.get_children(obj, 'always_nodes')) else: - if self._are_relevant_parents_finished(n): + # This catches root nodes or ANY convergence nodes + if not obj.all_parents_must_converge and self._are_relevant_parents_finished(n): nodes_found.append(n) + # This catches ALL convergence nodes + elif obj.all_parents_must_converge and self._are_relevant_parents_finished(n): + if self._all_parents_met_convergence_criteria(n): + nodes_found.append(n) + return [n['node_object'] for n in nodes_found] def cancel_node_jobs(self): @@ -135,8 +157,8 @@ def has_workflow_failed(self): for node in failed_nodes: obj = node['node_object'] - if (len(self.get_dependencies(obj, 'failure_nodes')) + - len(self.get_dependencies(obj, 'always_nodes'))) == 0: + if (len(self.get_children(obj, 'failure_nodes')) + + len(self.get_children(obj, 'always_nodes'))) == 0: if obj.unified_job_template is None: res = True failed_unified_job_template_node_ids.append(str(obj.id)) @@ -145,8 +167,8 @@ def has_workflow_failed(self): failed_path_nodes_id_status.append((str(obj.id), obj.job.status)) if res is True: - s = _("No error handle path for workflow job node(s) [{node_status}] workflow job " - "node(s) missing unified job template and error handle path [{no_ufjt}].") + s = _("No error handling path for workflow job node(s) [{node_status}]. Workflow job " + "node(s) missing unified job template and error handling path [{no_ufjt}].") parms = { 'node_status': '', 'no_ufjt': '', @@ -190,35 +212,48 @@ def _should_mark_node_dnr(self, node, parent_nodes): pass elif p.job: if p.job.status == 'successful': - if node in (self.get_dependencies(p, 'success_nodes') + - self.get_dependencies(p, 'always_nodes')): + if node in (self.get_children(p, 'success_nodes') + + self.get_children(p, 'always_nodes')): return False elif p.job.status in ['failed', 'error', 'canceled']: - if node in (self.get_dependencies(p, 'failure_nodes') + - self.get_dependencies(p, 'always_nodes')): + if node in (self.get_children(p, 'failure_nodes') + + self.get_children(p, 'always_nodes')): return False else: return False - elif p.do_not_run is False and p.unified_job_template is None: - if node in (self.get_dependencies(p, 'failure_nodes') + - self.get_dependencies(p, 'always_nodes')): + elif not p.do_not_run and p.unified_job_template is None: + if node in (self.get_children(p, 'failure_nodes') + + self.get_children(p, 'always_nodes')): return False else: return False return True + + r''' + determine if the current node is a convergence node by checking if all the + parents are finished then checking to see if all parents meet the needed + path criteria to run the convergence child. + (i.e. parent must fail, parent must succeed, etc. to proceed) + + Return a list object + ''' def mark_dnr_nodes(self): root_nodes = self.get_root_nodes() nodes_marked_do_not_run = [] for node in self.sort_nodes_topological(): obj = node['node_object'] - - if obj.do_not_run is False and not obj.job and node not in root_nodes: - parent_nodes = [p['node_object'] for p in self.get_dependents(obj)] - if self._are_all_nodes_dnr_decided(parent_nodes): - if self._should_mark_node_dnr(node, parent_nodes): + parent_nodes = [p['node_object'] for p in self.get_parents(obj)] + if not obj.do_not_run and not obj.job and node not in root_nodes: + if obj.all_parents_must_converge: + if any(p.do_not_run for p in parent_nodes) or not self._all_parents_met_convergence_criteria(node): obj.do_not_run = True nodes_marked_do_not_run.append(node) + else: + if self._are_all_nodes_dnr_decided(parent_nodes): + if self._should_mark_node_dnr(node, parent_nodes): + obj.do_not_run = True + nodes_marked_do_not_run.append(node) return [n['node_object'] for n in nodes_marked_do_not_run] diff --git a/awx/main/scheduler/kubernetes.py b/awx/main/scheduler/kubernetes.py index 862cfd3f04e1..529a5e5442ea 100644 --- a/awx/main/scheduler/kubernetes.py +++ b/awx/main/scheduler/kubernetes.py @@ -12,6 +12,24 @@ logger = logging.getLogger('awx.main.scheduler') +def deepmerge(a, b): + """ + Merge dict structures and return the result. + + >>> a = {'first': {'all_rows': {'pass': 'dog', 'number': '1'}}} + >>> b = {'first': {'all_rows': {'fail': 'cat', 'number': '5'}}} + >>> import pprint; pprint.pprint(deepmerge(a, b)) + {'first': {'all_rows': {'fail': 'cat', 'number': '5', 'pass': 'dog'}}} + """ + if isinstance(a, dict) and isinstance(b, dict): + return dict([(k, deepmerge(a.get(k), b.get(k))) + for k in set(a.keys()).union(b.keys())]) + elif b is None: + return a + else: + return b + + class PodManager(object): def __init__(self, task=None): @@ -128,11 +146,13 @@ def pod_definition(self): pod_spec = {**default_pod_spec, **pod_spec_override} if self.task: - pod_spec['metadata']['name'] = self.pod_name - pod_spec['metadata']['labels'] = { - 'ansible-awx': settings.INSTALL_UUID, - 'ansible-awx-job-id': str(self.task.id) - } + pod_spec['metadata'] = deepmerge( + pod_spec.get('metadata', {}), + dict(name=self.pod_name, + labels={ + 'ansible-awx': settings.INSTALL_UUID, + 'ansible-awx-job-id': str(self.task.id) + })) pod_spec['spec']['containers'][0]['name'] = self.pod_name return pod_spec diff --git a/awx/main/scheduler/task_manager.py b/awx/main/scheduler/task_manager.py index 32e8a7defbc7..43d43fe64dce 100644 --- a/awx/main/scheduler/task_manager.py +++ b/awx/main/scheduler/task_manager.py @@ -7,15 +7,20 @@ import uuid import json import random +from types import SimpleNamespace # Django from django.db import transaction, connection -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ugettext_lazy as _, gettext_noop from django.utils.timezone import now as tz_now +from django.conf import settings +from django.db.models import Q # AWX +from awx.main.dispatch.reaper import reap_job from awx.main.models import ( AdHocCommand, + Instance, InstanceGroup, InventorySource, InventoryUpdate, @@ -23,6 +28,7 @@ Project, ProjectUpdate, SystemJob, + UnifiedJob, WorkflowApproval, WorkflowJob, WorkflowJobTemplate @@ -41,11 +47,46 @@ class TaskManager(): def __init__(self): + ''' + Do NOT put database queries or other potentially expensive operations + in the task manager init. The task manager object is created every time a + job is created, transitions state, and every 30 seconds on each tower node. + More often then not, the object is destroyed quickly because the NOOP case is hit. + + The NOOP case is short-circuit logic. If the task manager realizes that another instance + of the task manager is already running, then it short-circuits and decides not to run. + ''' self.graph = dict() + # start task limit indicates how many pending jobs can be started on this + # .schedule() run. Starting jobs is expensive, and there is code in place to reap + # the task manager after 5 minutes. At scale, the task manager can easily take more than + # 5 minutes to start pending jobs. If this limit is reached, pending jobs + # will no longer be started and will be started on the next task manager cycle. + self.start_task_limit = settings.START_TASK_LIMIT + + def after_lock_init(self): + ''' + Init AFTER we know this instance of the task manager will run because the lock is acquired. + ''' + instances = Instance.objects.filter(~Q(hostname=None), capacity__gt=0, enabled=True) + self.real_instances = {i.hostname: i for i in instances} + + instances_partial = [SimpleNamespace(obj=instance, + remaining_capacity=instance.remaining_capacity, + capacity=instance.capacity, + jobs_running=instance.jobs_running, + hostname=instance.hostname) for instance in instances] + + instances_by_hostname = {i.hostname: i for i in instances_partial} + for rampart_group in InstanceGroup.objects.prefetch_related('instances'): self.graph[rampart_group.name] = dict(graph=DependencyGraph(rampart_group.name), capacity_total=rampart_group.capacity, - consumed_capacity=0) + consumed_capacity=0, + instances=[]) + for instance in rampart_group.instances.filter(capacity__gt=0, enabled=True).order_by('hostname'): + if instance.hostname in instances_by_hostname: + self.graph[rampart_group.name]['instances'].append(instances_by_hostname[instance.hostname]) def is_job_blocked(self, task): # TODO: I'm not happy with this, I think blocking behavior should be decided outside of the dependency graph @@ -74,21 +115,6 @@ def get_tasks(self, status_list=('pending', 'waiting', 'running')): key=lambda task: task.created) return all_tasks - - def get_latest_project_update_tasks(self, all_sorted_tasks): - project_ids = set() - for task in all_sorted_tasks: - if isinstance(task, Job): - project_ids.add(task.project_id) - return ProjectUpdate.objects.filter(id__in=project_ids) - - def get_latest_inventory_update_tasks(self, all_sorted_tasks): - inventory_ids = set() - for task in all_sorted_tasks: - if isinstance(task, Job): - inventory_ids.add(task.inventory_id) - return InventoryUpdate.objects.filter(id__in=inventory_ids) - def get_running_workflow_jobs(self): graph_workflow_jobs = [wf for wf in WorkflowJob.objects.filter(status='running')] @@ -128,7 +154,7 @@ def spawn_workflow_graph_jobs(self, workflow_jobs): logger.info('Refusing to start recursive workflow-in-workflow id={}, wfjt={}, ancestors={}'.format( job.id, spawn_node.unified_job_template.pk, [wa.pk for wa in workflow_ancestors])) display_list = [spawn_node.unified_job_template] + workflow_ancestors - job.job_explanation = _( + job.job_explanation = gettext_noop( "Workflow Job spawned from workflow could not start because it " "would result in recursion (spawn order, most recent first: {})" ).format(', '.join(['<{}>'.format(tmp) for tmp in display_list])) @@ -137,8 +163,8 @@ def spawn_workflow_graph_jobs(self, workflow_jobs): job.id, spawn_node.unified_job_template.pk, [wa.pk for wa in workflow_ancestors])) if not job._resources_sufficient_for_launch(): can_start = False - job.job_explanation = _("Job spawned from workflow could not start because it " - "was missing a related resource such as project or inventory") + job.job_explanation = gettext_noop("Job spawned from workflow could not start because it " + "was missing a related resource such as project or inventory") if can_start: if workflow_job.start_args: start_args = json.loads(decrypt_field(workflow_job, 'start_args')) @@ -146,8 +172,8 @@ def spawn_workflow_graph_jobs(self, workflow_jobs): start_args = {} can_start = job.signal_start(**start_args) if not can_start: - job.job_explanation = _("Job spawned from workflow could not start because it " - "was not in the right state or required manual credentials") + job.job_explanation = gettext_noop("Job spawned from workflow could not start because it " + "was not in the right state or required manual credentials") if not can_start: job.status = 'failed' job.save(update_fields=['status', 'job_explanation']) @@ -187,7 +213,7 @@ def process_finished_workflow_jobs(self, workflow_jobs): workflow_job.status = new_status if reason: logger.info(reason) - workflow_job.job_explanation = _("No error handling paths found, marking workflow as failed") + workflow_job.job_explanation = gettext_noop("No error handling paths found, marking workflow as failed") update_fields.append('job_explanation') workflow_job.start_args = '' # blank field to remove encrypted passwords workflow_job.save(update_fields=update_fields) @@ -200,10 +226,11 @@ def process_finished_workflow_jobs(self, workflow_jobs): schedule_task_manager() return result - def get_dependent_jobs_for_inv_and_proj_update(self, job_obj): - return [{'type': j.model_to_str(), 'id': j.id} for j in job_obj.dependent_jobs.all()] - def start_task(self, task, rampart_group, dependent_tasks=None, instance=None): + self.start_task_limit -= 1 + if self.start_task_limit == 0: + # schedule another run immediately after this task manager + schedule_task_manager() from awx.main.tasks import handle_work_error, handle_work_success dependent_tasks = dependent_tasks or [] @@ -243,7 +270,7 @@ def start_task(self, task, rampart_group, dependent_tasks=None, instance=None): # non-Ansible jobs on isolated instances run on controller task.instance_group = rampart_group.controller task.execution_node = random.choice(list(rampart_group.controller.instances.all().values_list('hostname', flat=True))) - logger.debug('Submitting isolated {} to queue {}.'.format( + logger.debug('Submitting isolated {} to queue {} on node {}.'.format( task.log_format, task.instance_group.name, task.execution_node)) elif controller_node: task.instance_group = rampart_group @@ -258,7 +285,7 @@ def start_task(self, task, rampart_group, dependent_tasks=None, instance=None): for group in InstanceGroup.objects.all(): if group.is_containerized or group.controller_id: continue - match = group.fit_task_to_most_remaining_capacity_instance(task) + match = group.fit_task_to_most_remaining_capacity_instance(task, group.instances.all()) if match: break task.instance_group = rampart_group @@ -364,10 +391,6 @@ def get_latest_inventory_update(self, inventory_source): def should_update_inventory_source(self, job, latest_inventory_update): now = tz_now() - # Already processed dependencies for this job - if job.dependent_jobs.all(): - return False - if latest_inventory_update is None: return True ''' @@ -393,8 +416,6 @@ def get_latest_project_update(self, job): def should_update_related_project(self, job, latest_project_update): now = tz_now() - if job.dependent_jobs.all(): - return False if latest_project_update is None: return True @@ -426,18 +447,21 @@ def should_update_related_project(self, job, latest_project_update): return True return False - def generate_dependencies(self, task): - dependencies = [] - if type(task) is Job: + def generate_dependencies(self, undeped_tasks): + created_dependencies = [] + for task in undeped_tasks: + dependencies = [] + if not type(task) is Job: + continue # TODO: Can remove task.project None check after scan-job-default-playbook is removed if task.project is not None and task.project.scm_update_on_launch is True: latest_project_update = self.get_latest_project_update(task) if self.should_update_related_project(task, latest_project_update): project_task = self.create_project_update(task) + created_dependencies.append(project_task) dependencies.append(project_task) else: - if latest_project_update.status in ['waiting', 'pending', 'running']: - dependencies.append(latest_project_update) + dependencies.append(latest_project_update) # Inventory created 2 seconds behind job try: @@ -452,62 +476,27 @@ def generate_dependencies(self, task): latest_inventory_update = self.get_latest_inventory_update(inventory_source) if self.should_update_inventory_source(task, latest_inventory_update): inventory_task = self.create_inventory_update(task, inventory_source) + created_dependencies.append(inventory_task) dependencies.append(inventory_task) else: - if latest_inventory_update.status in ['waiting', 'pending', 'running']: - dependencies.append(latest_inventory_update) + dependencies.append(latest_inventory_update) if len(dependencies) > 0: self.capture_chain_failure_dependencies(task, dependencies) - return dependencies - - def process_dependencies(self, dependent_task, dependency_tasks): - for task in dependency_tasks: - if self.is_job_blocked(task): - logger.debug("Dependent {} is blocked from running".format(task.log_format)) - continue - preferred_instance_groups = task.preferred_instance_groups - found_acceptable_queue = False - idle_instance_that_fits = None - for rampart_group in preferred_instance_groups: - if idle_instance_that_fits is None: - idle_instance_that_fits = rampart_group.find_largest_idle_instance() - if not rampart_group.is_containerized and self.get_remaining_capacity(rampart_group.name) <= 0: - logger.debug("Skipping group {} capacity <= 0".format(rampart_group.name)) - continue - execution_instance = rampart_group.fit_task_to_most_remaining_capacity_instance(task) - if execution_instance: - logger.debug("Starting dependent {} in group {} instance {}".format( - task.log_format, rampart_group.name, execution_instance.hostname)) - elif not execution_instance and idle_instance_that_fits: - if not rampart_group.is_containerized: - execution_instance = idle_instance_that_fits - logger.debug("Starting dependent {} in group {} on idle instance {}".format( - task.log_format, rampart_group.name, execution_instance.hostname)) - if execution_instance or rampart_group.is_containerized: - self.graph[rampart_group.name]['graph'].add_job(task) - tasks_to_fail = [t for t in dependency_tasks if t != task] - tasks_to_fail += [dependent_task] - self.start_task(task, rampart_group, tasks_to_fail, execution_instance) - found_acceptable_queue = True - break - else: - logger.debug("No instance available in group {} to run job {} w/ capacity requirement {}".format( - rampart_group.name, task.log_format, task.task_impact)) - if not found_acceptable_queue: - logger.debug("Dependent {} couldn't be scheduled on graph, waiting for next cycle".format(task.log_format)) + UnifiedJob.objects.filter(pk__in = [task.pk for task in undeped_tasks]).update(dependencies_processed=True) + return created_dependencies def process_pending_tasks(self, pending_tasks): running_workflow_templates = set([wf.unified_job_template_id for wf in self.get_running_workflow_jobs()]) for task in pending_tasks: - self.process_dependencies(task, self.generate_dependencies(task)) + if self.start_task_limit <= 0: + break if self.is_job_blocked(task): logger.debug("{} is blocked from running".format(task.log_format)) continue preferred_instance_groups = task.preferred_instance_groups found_acceptable_queue = False - idle_instance_that_fits = None if isinstance(task, WorkflowJob): if task.unified_job_template_id in running_workflow_templates: if not task.allow_simultaneous: @@ -524,24 +513,24 @@ def process_pending_tasks(self, pending_tasks): found_acceptable_queue = True break - if idle_instance_that_fits is None: - idle_instance_that_fits = rampart_group.find_largest_idle_instance() remaining_capacity = self.get_remaining_capacity(rampart_group.name) if not rampart_group.is_containerized and self.get_remaining_capacity(rampart_group.name) <= 0: logger.debug("Skipping group {}, remaining_capacity {} <= 0".format( rampart_group.name, remaining_capacity)) continue - execution_instance = rampart_group.fit_task_to_most_remaining_capacity_instance(task) - if execution_instance: - logger.debug("Starting {} in group {} instance {} (remaining_capacity={})".format( - task.log_format, rampart_group.name, execution_instance.hostname, remaining_capacity)) - elif not execution_instance and idle_instance_that_fits: + execution_instance = InstanceGroup.fit_task_to_most_remaining_capacity_instance(task, self.graph[rampart_group.name]['instances']) or \ + InstanceGroup.find_largest_idle_instance(self.graph[rampart_group.name]['instances']) + + if execution_instance or rampart_group.is_containerized: if not rampart_group.is_containerized: - execution_instance = idle_instance_that_fits + execution_instance.remaining_capacity = max(0, execution_instance.remaining_capacity - task.task_impact) + execution_instance.jobs_running += 1 logger.debug("Starting {} in group {} instance {} (remaining_capacity={})".format( task.log_format, rampart_group.name, execution_instance.hostname, remaining_capacity)) - if execution_instance or rampart_group.is_containerized: + + if execution_instance: + execution_instance = self.real_instances[execution_instance.hostname] self.graph[rampart_group.name]['graph'].add_job(task) self.start_task(task, rampart_group, task.get_jobs_fail_chain(), execution_instance) found_acceptable_queue = True @@ -571,16 +560,23 @@ def timeout_approval_node(self): task.job_explanation = timeout_message task.save(update_fields=['status', 'job_explanation', 'timed_out']) + def reap_jobs_from_orphaned_instances(self): + # discover jobs that are in running state but aren't on an execution node + # that we know about; this is a fairly rare event, but it can occur if you, + # for example, SQL backup an awx install with running jobs and restore it + # elsewhere + for j in UnifiedJob.objects.filter( + status__in=['pending', 'waiting', 'running'], + ).exclude( + execution_node__in=Instance.objects.values_list('hostname', flat=True) + ): + if j.execution_node and not j.is_containerized: + logger.error(f'{j.execution_node} is not a registered instance; reaping {j.log_format}') + reap_job(j, 'failed') + def calculate_capacity_consumed(self, tasks): self.graph = InstanceGroup.objects.capacity_values(tasks=tasks, graph=self.graph) - def would_exceed_capacity(self, task, instance_group): - current_capacity = self.graph[instance_group]['consumed_capacity'] - capacity_total = self.graph[instance_group]['capacity_total'] - if current_capacity == 0: - return False - return (task.task_impact + current_capacity > capacity_total) - def consume_capacity(self, task, instance_group): logger.debug('{} consumed {} capacity units from {} with prior total of {}'.format( task.log_format, task.task_impact, instance_group, @@ -598,11 +594,17 @@ def process_tasks(self, all_sorted_tasks): self.process_running_tasks(running_tasks) pending_tasks = [t for t in all_sorted_tasks if t.status == 'pending'] + undeped_tasks = [t for t in pending_tasks if not t.dependencies_processed] + dependencies = self.generate_dependencies(undeped_tasks) + self.process_pending_tasks(dependencies) self.process_pending_tasks(pending_tasks) def _schedule(self): finished_wfjs = [] all_sorted_tasks = self.get_tasks() + + self.after_lock_init() + if len(all_sorted_tasks) > 0: # TODO: Deal with # latest_project_updates = self.get_latest_project_update_tasks(all_sorted_tasks) @@ -627,6 +629,7 @@ def _schedule(self): self.spawn_workflow_graph_jobs(running_workflow_tasks) self.timeout_approval_node() + self.reap_jobs_from_orphaned_instances() self.process_tasks(all_sorted_tasks) return finished_wfjs @@ -641,3 +644,4 @@ def schedule(self): logger.debug("Starting Scheduler") with task_manager_bulk_reschedule(): self._schedule() + logger.debug("Finishing Scheduler") diff --git a/awx/main/scheduler/tasks.py b/awx/main/scheduler/tasks.py index c0d3dd842e2f..7da6a305a937 100644 --- a/awx/main/scheduler/tasks.py +++ b/awx/main/scheduler/tasks.py @@ -5,11 +5,12 @@ # AWX from awx.main.scheduler import TaskManager from awx.main.dispatch.publish import task +from awx.main.dispatch import get_local_queuename logger = logging.getLogger('awx.main.scheduler') -@task() +@task(queue=get_local_queuename) def run_task_manager(): logger.debug("Running Tower task manager.") TaskManager().schedule() diff --git a/awx/main/signals.py b/awx/main/signals.py index 8e2c6fe030e0..0a29fa9d6c32 100644 --- a/awx/main/signals.py +++ b/awx/main/signals.py @@ -6,10 +6,10 @@ import logging import threading import json -import pkg_resources import sys # Django +from django.db import connection from django.conf import settings from django.db.models.signals import ( pre_save, @@ -52,6 +52,7 @@ __all__ = [] logger = logging.getLogger('awx.main.signals') +analytics_logger = logging.getLogger('awx.analytics.activity_stream') # Update has_active_failures for inventory/groups when a Host/Group is deleted, # when a Host-Group or Group-Group relationship is updated, or when a Job is deleted @@ -71,41 +72,6 @@ def get_current_user_or_none(): return u -def emit_update_inventory_computed_fields(sender, **kwargs): - logger.debug("In update inventory computed fields") - if getattr(_inventory_updates, 'is_updating', False): - return - instance = kwargs['instance'] - if sender == Group.hosts.through: - sender_name = 'group.hosts' - elif sender == Group.parents.through: - sender_name = 'group.parents' - elif sender == Host.inventory_sources.through: - sender_name = 'host.inventory_sources' - elif sender == Group.inventory_sources.through: - sender_name = 'group.inventory_sources' - else: - sender_name = str(sender._meta.verbose_name) - if kwargs['signal'] == post_save: - if sender == Job: - return - sender_action = 'saved' - elif kwargs['signal'] == post_delete: - sender_action = 'deleted' - elif kwargs['signal'] == m2m_changed and kwargs['action'] in ('post_add', 'post_remove', 'post_clear'): - sender_action = 'changed' - else: - return - logger.debug('%s %s, updating inventory computed fields: %r %r', - sender_name, sender_action, sender, kwargs) - try: - inventory = instance.inventory - except Inventory.DoesNotExist: - pass - else: - update_inventory_computed_fields.delay(inventory.id, True) - - def emit_update_inventory_on_created_or_deleted(sender, **kwargs): if getattr(_inventory_updates, 'is_updating', False): return @@ -124,7 +90,9 @@ def emit_update_inventory_on_created_or_deleted(sender, **kwargs): pass else: if inventory is not None: - update_inventory_computed_fields.delay(inventory.id, True) + connection.on_commit( + lambda: update_inventory_computed_fields.delay(inventory.id) + ) def rebuild_role_ancestor_list(reverse, model, instance, pk_set, action, **kwargs): @@ -153,6 +121,27 @@ def sync_superuser_status_to_rbac(instance, **kwargs): Role.singleton(ROLE_SINGLETON_SYSTEM_ADMINISTRATOR).members.remove(instance) +def sync_rbac_to_superuser_status(instance, sender, **kwargs): + 'When the is_superuser flag is false but a user has the System Admin role, update the database to reflect that' + if kwargs['action'] in ['post_add', 'post_remove', 'post_clear']: + new_status_value = bool(kwargs['action'] == 'post_add') + if hasattr(instance, 'singleton_name'): # duck typing, role.members.add() vs user.roles.add() + role = instance + if role.singleton_name == ROLE_SINGLETON_SYSTEM_ADMINISTRATOR: + if kwargs['pk_set']: + kwargs['model'].objects.filter(pk__in=kwargs['pk_set']).update(is_superuser=new_status_value) + elif kwargs['action'] == 'post_clear': + kwargs['model'].objects.all().update(is_superuser=False) + else: + user = instance + if kwargs['action'] == 'post_clear': + user.is_superuser = False + user.save(update_fields=['is_superuser']) + elif kwargs['model'].objects.filter(pk__in=kwargs['pk_set'], singleton_name=ROLE_SINGLETON_SYSTEM_ADMINISTRATOR).exists(): + user.is_superuser = new_status_value + user.save(update_fields=['is_superuser']) + + def rbac_activity_stream(instance, sender, **kwargs): # Only if we are associating/disassociating if kwargs['action'] in ['pre_add', 'pre_remove']: @@ -182,24 +171,33 @@ def rbac_activity_stream(instance, sender, **kwargs): def cleanup_detached_labels_on_deleted_parent(sender, instance, **kwargs): - for l in instance.labels.all(): - if l.is_candidate_for_detach(): - l.delete() + for label in instance.labels.all(): + if label.is_candidate_for_detach(): + label.delete() def save_related_job_templates(sender, instance, **kwargs): '''save_related_job_templates loops through all of the - job templates that use an Inventory or Project that have had their + job templates that use an Inventory that have had their Organization updated. This triggers the rebuilding of the RBAC hierarchy and ensures the proper access restrictions. ''' - if sender not in (Project, Inventory): + if sender is not Inventory: raise ValueError('This signal callback is only intended for use with Project or Inventory') + update_fields = kwargs.get('update_fields', None) + if ((update_fields and not ('organization' in update_fields or 'organization_id' in update_fields)) or + kwargs.get('created', False)): + return + if instance._prior_values_store.get('organization_id') != instance.organization_id: jtq = JobTemplate.objects.filter(**{sender.__name__.lower(): instance}) for jt in jtq: - update_role_parentage_for_instance(jt) + parents_added, parents_removed = update_role_parentage_for_instance(jt) + if parents_added or parents_removed: + logger.info('Permissions on JT {} changed due to inventory {} organization change from {} to {}.'.format( + jt.pk, instance.pk, instance._prior_values_store.get('organization_id'), instance.organization_id + )) def connect_computed_field_signals(): @@ -207,10 +205,6 @@ def connect_computed_field_signals(): post_delete.connect(emit_update_inventory_on_created_or_deleted, sender=Host) post_save.connect(emit_update_inventory_on_created_or_deleted, sender=Group) post_delete.connect(emit_update_inventory_on_created_or_deleted, sender=Group) - m2m_changed.connect(emit_update_inventory_computed_fields, sender=Group.hosts.through) - m2m_changed.connect(emit_update_inventory_computed_fields, sender=Group.parents.through) - m2m_changed.connect(emit_update_inventory_computed_fields, sender=Host.inventory_sources.through) - m2m_changed.connect(emit_update_inventory_computed_fields, sender=Group.inventory_sources.through) post_save.connect(emit_update_inventory_on_created_or_deleted, sender=InventorySource) post_delete.connect(emit_update_inventory_on_created_or_deleted, sender=InventorySource) post_save.connect(emit_update_inventory_on_created_or_deleted, sender=Job) @@ -219,12 +213,12 @@ def connect_computed_field_signals(): connect_computed_field_signals() -post_save.connect(save_related_job_templates, sender=Project) post_save.connect(save_related_job_templates, sender=Inventory) m2m_changed.connect(rebuild_role_ancestor_list, Role.parents.through) m2m_changed.connect(rbac_activity_stream, Role.members.through) m2m_changed.connect(rbac_activity_stream, Role.parents.through) post_save.connect(sync_superuser_status_to_rbac, sender=User) +m2m_changed.connect(sync_rbac_to_superuser_status, Role.members.through) pre_delete.connect(cleanup_detached_labels_on_deleted_parent, sender=UnifiedJob) pre_delete.connect(cleanup_detached_labels_on_deleted_parent, sender=UnifiedJobTemplate) @@ -347,10 +341,6 @@ def disable_computed_fields(): post_delete.disconnect(emit_update_inventory_on_created_or_deleted, sender=Host) post_save.disconnect(emit_update_inventory_on_created_or_deleted, sender=Group) post_delete.disconnect(emit_update_inventory_on_created_or_deleted, sender=Group) - m2m_changed.disconnect(emit_update_inventory_computed_fields, sender=Group.hosts.through) - m2m_changed.disconnect(emit_update_inventory_computed_fields, sender=Group.parents.through) - m2m_changed.disconnect(emit_update_inventory_computed_fields, sender=Host.inventory_sources.through) - m2m_changed.disconnect(emit_update_inventory_computed_fields, sender=Group.inventory_sources.through) post_save.disconnect(emit_update_inventory_on_created_or_deleted, sender=InventorySource) post_delete.disconnect(emit_update_inventory_on_created_or_deleted, sender=InventorySource) post_save.disconnect(emit_update_inventory_on_created_or_deleted, sender=Job) @@ -396,12 +386,24 @@ def model_serializer_mapping(): } +def emit_activity_stream_change(instance): + if 'migrate' in sys.argv: + # don't emit activity stream external logs during migrations, it + # could be really noisy + return + from awx.api.serializers import ActivityStreamSerializer + actor = None + if instance.actor: + actor = instance.actor.username + summary_fields = ActivityStreamSerializer(instance).get_summary_fields(instance) + analytics_logger.info('Activity Stream update entry for %s' % str(instance.object1), + extra=dict(changes=instance.changes, relationship=instance.object_relationship_type, + actor=actor, operation=instance.operation, + object1=instance.object1, object2=instance.object2, summary_fields=summary_fields)) + + def activity_stream_create(sender, instance, created, **kwargs): if created and activity_stream_enabled: - # TODO: remove deprecated_group conditional in 3.3 - # Skip recording any inventory source directly associated with a group. - if isinstance(instance, InventorySource) and instance.deprecated_group: - return _type = type(instance) if getattr(_type, '_deferred', False): return @@ -413,7 +415,7 @@ def activity_stream_create(sender, instance, created, **kwargs): '{} ({})'.format(c.name, c.id) for c in instance.credentials.iterator() ] - changes['labels'] = [l.name for l in instance.labels.iterator()] + changes['labels'] = [label.name for label in instance.labels.iterator()] if 'extra_vars' in changes: changes['extra_vars'] = instance.display_extra_vars() if type(instance) == OAuth2AccessToken: @@ -432,6 +434,9 @@ def activity_stream_create(sender, instance, created, **kwargs): else: activity_entry.setting = conf_to_dict(instance) activity_entry.save() + connection.on_commit( + lambda: emit_activity_stream_change(activity_entry) + ) def activity_stream_update(sender, instance, **kwargs): @@ -463,15 +468,14 @@ def activity_stream_update(sender, instance, **kwargs): else: activity_entry.setting = conf_to_dict(instance) activity_entry.save() + connection.on_commit( + lambda: emit_activity_stream_change(activity_entry) + ) def activity_stream_delete(sender, instance, **kwargs): if not activity_stream_enabled: return - # TODO: remove deprecated_group conditional in 3.3 - # Skip recording any inventory source directly associated with a group. - if isinstance(instance, InventorySource) and instance.deprecated_group: - return # Inventory delete happens in the task system rather than request-response-cycle. # If we trigger this handler there we may fall into db-integrity-related race conditions. # So we add flag verification to prevent normal signal handling. This funciton will be @@ -500,6 +504,9 @@ def activity_stream_delete(sender, instance, **kwargs): object1=object1, actor=get_current_user_or_none()) activity_entry.save() + connection.on_commit( + lambda: emit_activity_stream_change(activity_entry) + ) def activity_stream_associate(sender, instance, **kwargs): @@ -573,6 +580,9 @@ def activity_stream_associate(sender, instance, **kwargs): activity_entry.role.add(role) activity_entry.object_relationship_type = obj_rel activity_entry.save() + connection.on_commit( + lambda: emit_activity_stream_change(activity_entry) + ) @receiver(current_user_getter) @@ -625,16 +635,6 @@ def deny_orphaned_approvals(sender, instance, **kwargs): @receiver(post_save, sender=Session) def save_user_session_membership(sender, **kwargs): session = kwargs.get('instance', None) - if pkg_resources.get_distribution('channels').version >= '2': - # If you get into this code block, it means we upgraded channels, but - # didn't make the settings.SESSIONS_PER_USER feature work - raise RuntimeError( - 'save_user_session_membership must be updated for channels>=2: ' - 'http://channels.readthedocs.io/en/latest/one-to-two.html#requirements' - ) - if 'runworker' in sys.argv: - # don't track user session membership for websocket per-channel sessions - return if not session: return user_id = session.get_decoded().get(SESSION_KEY, None) diff --git a/awx/main/tasks.py b/awx/main/tasks.py index 6741e0d52274..a700d8df6146 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -26,12 +26,12 @@ # Django from django.conf import settings -from django.db import transaction, DatabaseError, IntegrityError +from django.db import transaction, DatabaseError, IntegrityError, ProgrammingError, connection from django.db.models.fields.related import ForeignKey from django.utils.timezone import now, timedelta from django.utils.encoding import smart_str from django.contrib.auth.models import User -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ugettext_lazy as _, gettext_noop from django.core.cache import cache from django.core.exceptions import ObjectDoesNotExist @@ -50,8 +50,10 @@ # AWX from awx import __version__ as awx_application_version -from awx.main.constants import CLOUD_PROVIDERS, PRIVILEGE_ESCALATION_METHODS, STANDARD_INVENTORY_UPDATE_ENV, GALAXY_SERVER_FIELDS +from awx.main.constants import PRIVILEGE_ESCALATION_METHODS, STANDARD_INVENTORY_UPDATE_ENV from awx.main.access import access_registry +from awx.main.analytics import all_collectors, expensive_collectors +from awx.main.redact import UriCleaner from awx.main.models import ( Schedule, TowerScheduleState, Instance, InstanceGroup, UnifiedJob, Notification, @@ -61,20 +63,22 @@ build_safe_env ) from awx.main.constants import ACTIVE_STATES -from awx.main.exceptions import AwxTaskError +from awx.main.exceptions import AwxTaskError, PostRunError from awx.main.queue import CallbackQueueDispatcher from awx.main.isolated import manager as isolated_manager from awx.main.dispatch.publish import task from awx.main.dispatch import get_local_queuename, reaper -from awx.main.utils import (get_ssh_version, update_scm_url, +from awx.main.utils import (update_scm_url, ignore_inventory_computed_fields, ignore_inventory_group_removal, extract_ansible_vars, schedule_task_manager, get_awx_version) from awx.main.utils.ansible import read_ansible_config -from awx.main.utils.common import _get_ansible_version, get_custom_venv_choices +from awx.main.utils.common import get_custom_venv_choices +from awx.main.utils.external_logging import reconfigure_rsyslog from awx.main.utils.safe_yaml import safe_dump, sanitize_jinja from awx.main.utils.reload import stop_local_services from awx.main.utils.pglock import advisory_lock +from awx.main.utils.handlers import SpecialInventoryHandler from awx.main.consumers import emit_channel_notification from awx.main import analytics from awx.conf import settings_registry @@ -134,6 +138,9 @@ def dispatch_startup(): if Instance.objects.me().is_controller(): awx_isolated_heartbeat() + # Update Tower's rsyslog.conf file based on loggins settings in the db + reconfigure_rsyslog() + def inform_cluster_of_shutdown(): try: @@ -150,7 +157,7 @@ def inform_cluster_of_shutdown(): logger.exception('Encountered problem with normal shutdown signal.') -@task() +@task(queue=get_local_queuename) def apply_cluster_membership_policies(): started_waiting = time.time() with advisory_lock('cluster_policy_lock', wait=True): @@ -263,7 +270,7 @@ def apply_cluster_membership_policies(): logger.debug('Cluster policy computation finished in {} seconds'.format(time.time() - started_compute)) -@task(queue='tower_broadcast_all', exchange_type='fanout') +@task(queue='tower_broadcast_all') def handle_setting_changes(setting_keys): orig_len = len(setting_keys) for i in range(orig_len): @@ -273,8 +280,14 @@ 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() -@task(queue='tower_broadcast_all', exchange_type='fanout') + +@task(queue='tower_broadcast_all') def delete_project_files(project_path): # TODO: possibly implement some retry logic lock_file = project_path + '.lock' @@ -292,9 +305,9 @@ def delete_project_files(project_path): logger.exception('Could not remove lock file {}'.format(lock_file)) -@task(queue='tower_broadcast_all', exchange_type='fanout') +@task(queue='tower_broadcast_all') def profile_sql(threshold=1, minutes=1): - if threshold == 0: + if threshold <= 0: cache.delete('awx-profile-sql-threshold') logger.error('SQL PROFILING DISABLED') else: @@ -306,7 +319,7 @@ def profile_sql(threshold=1, minutes=1): logger.error('SQL QUERIES >={}s ENABLED FOR {} MINUTE(S)'.format(threshold, minutes)) -@task() +@task(queue=get_local_queuename) def send_notifications(notification_list, job_id=None): if not isinstance(notification_list, list): raise TypeError("notification_list should be of type list") @@ -335,19 +348,71 @@ def send_notifications(notification_list, job_id=None): logger.exception('Error saving notification {} result.'.format(notification.id)) -@task() +@task(queue=get_local_queuename) def gather_analytics(): + def _gather_and_ship(subset, since, until): + tgzfiles = [] + try: + tgzfiles = analytics.gather(subset=subset, since=since, until=until) + # empty analytics without raising an exception is not an error + if not tgzfiles: + return True + logger.info('Gathered analytics from {} to {}: {}'.format(since, until, tgzfiles)) + for tgz in tgzfiles: + analytics.ship(tgz) + except Exception: + logger.exception('Error gathering and sending analytics for {} to {}.'.format(since,until)) + return False + finally: + if tgzfiles: + for tgz in tgzfiles: + if os.path.exists(tgz): + os.remove(tgz) + return True + + from awx.conf.models import Setting + from rest_framework.fields import DateTimeField + from awx.main.signals import disable_activity_stream if not settings.INSIGHTS_TRACKING_STATE: return - try: - tgz = analytics.gather() - if not tgz: - return - logger.debug('gathered analytics: {}'.format(tgz)) - analytics.ship(tgz) - finally: - if os.path.exists(tgz): - os.remove(tgz) + if not (settings.AUTOMATION_ANALYTICS_URL and settings.REDHAT_USERNAME and settings.REDHAT_PASSWORD): + logger.debug('Not gathering analytics, configuration is invalid') + return + last_gather = Setting.objects.filter(key='AUTOMATION_ANALYTICS_LAST_GATHER').first() + if last_gather: + last_time = DateTimeField().to_internal_value(last_gather.value) + else: + last_time = None + gather_time = now() + if not last_time or ((gather_time - last_time).total_seconds() > settings.AUTOMATION_ANALYTICS_GATHER_INTERVAL): + with advisory_lock('gather_analytics_lock', wait=False) as acquired: + if acquired is False: + logger.debug('Not gathering analytics, another task holds lock') + return + subset = list(all_collectors().keys()) + incremental_collectors = [] + for collector in expensive_collectors(): + if collector in subset: + subset.remove(collector) + incremental_collectors.append(collector) + + # Cap gathering at 4 weeks of data if there has been no data gathering + since = last_time or (gather_time - timedelta(weeks=4)) + + if incremental_collectors: + start = since + until = None + while start < gather_time: + until = start + timedelta(hours = 4) + if (until > gather_time): + until = gather_time + if not _gather_and_ship(incremental_collectors, since=start, until=until): + break + start = until + with disable_activity_stream(): + settings.AUTOMATION_ANALYTICS_LAST_GATHER = until + if subset: + _gather_and_ship(subset, since=since, until=gather_time) @task(queue=get_local_queuename) @@ -474,10 +539,10 @@ def awx_isolated_heartbeat(): # Slow pass looping over isolated IGs and their isolated instances if len(isolated_instance_qs) > 0: logger.debug("Managing isolated instances {}.".format(','.join([inst.hostname for inst in isolated_instance_qs]))) - isolated_manager.IsolatedManager().health_check(isolated_instance_qs) + isolated_manager.IsolatedManager(CallbackQueueDispatcher.dispatch).health_check(isolated_instance_qs) -@task() +@task(queue=get_local_queuename) def awx_periodic_scheduler(): with advisory_lock('awx_periodic_scheduler_lock', wait=False) as acquired: if acquired is False: @@ -499,7 +564,7 @@ def awx_periodic_scheduler(): invalid_license = False try: - access_registry[Job](None).check_license() + access_registry[Job](None).check_license(quiet=True) except PermissionDenied as e: invalid_license = e @@ -527,14 +592,15 @@ def awx_periodic_scheduler(): continue if not can_start: new_unified_job.status = 'failed' - new_unified_job.job_explanation = "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() -@task() +@task(queue=get_local_queuename) def handle_work_success(task_actual): try: instance = UnifiedJob.get_instance_by_type(task_actual['type'], task_actual['id']) @@ -547,7 +613,7 @@ def handle_work_success(task_actual): schedule_task_manager() -@task() +@task(queue=get_local_queuename) def handle_work_error(task_id, *args, **kwargs): subtasks = kwargs.get('subtasks', None) logger.debug('Executing error task id %s, subtasks: %s' % (task_id, str(subtasks))) @@ -569,7 +635,7 @@ def handle_work_error(task_id, *args, **kwargs): first_instance = instance first_instance_type = each_task['type'] - if instance.celery_task_id != task_id and not instance.cancel_flag: + if instance.celery_task_id != task_id and not instance.cancel_flag and not instance.status == 'successful': instance.status = 'failed' instance.failed = True if not instance.job_explanation: @@ -587,8 +653,27 @@ def handle_work_error(task_id, *args, **kwargs): pass -@task() -def update_inventory_computed_fields(inventory_id, should_update_hosts=True): +@task(queue=get_local_queuename) +def handle_success_and_failure_notifications(job_id): + uj = UnifiedJob.objects.get(pk=job_id) + retries = 0 + while retries < 5: + if uj.finished: + uj.send_notification_templates('succeeded' if uj.status == 'successful' else 'failed') + return + else: + # wait a few seconds to avoid a race where the + # events are persisted _before_ the UJ.status + # changes from running -> successful + retries += 1 + time.sleep(1) + uj = UnifiedJob.objects.get(pk=job_id) + + logger.warn(f"Failed to even try to send notifications for job '{uj}' due to job not being in finished state.") + + +@task(queue=get_local_queuename) +def update_inventory_computed_fields(inventory_id): ''' Signal handler and wrapper around inventory.update_computed_fields to prevent unnecessary recursive calls. @@ -599,7 +684,7 @@ def update_inventory_computed_fields(inventory_id, should_update_hosts=True): return i = i[0] try: - i.update_computed_fields(update_hosts=should_update_hosts) + 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.') @@ -629,7 +714,7 @@ def update_smart_memberships_for_inventory(smart_inventory): return False -@task() +@task(queue=get_local_queuename) def update_host_smart_inventory_memberships(): smart_inventories = Inventory.objects.filter(kind='smart', host_filter__isnull=False, pending_deletion=False) changed_inventories = set([]) @@ -642,10 +727,58 @@ def update_host_smart_inventory_memberships(): logger.exception('Failed to update smart inventory memberships for {}'.format(smart_inventory.pk)) # Update computed fields for changed inventories outside atomic action for smart_inventory in changed_inventories: - smart_inventory.update_computed_fields(update_groups=False, update_hosts=False) + smart_inventory.update_computed_fields() + + +@task(queue=get_local_queuename) +def migrate_legacy_event_data(tblname): + # + # NOTE: this function is not actually in use anymore, + # but has been intentionally kept for historical purposes, + # and to serve as an illustration if we ever need to perform + # bulk modification/migration of event data in the future. + # + if 'event' not in tblname: + return + with advisory_lock(f'bigint_migration_{tblname}', wait=False) as acquired: + if acquired is False: + return + chunk = settings.JOB_EVENT_MIGRATION_CHUNK_SIZE + + def _remaining(): + try: + cursor.execute(f'SELECT MAX(id) FROM _old_{tblname};') + return cursor.fetchone()[0] + except ProgrammingError: + # the table is gone (migration is unnecessary) + return None + + with connection.cursor() as cursor: + total_rows = _remaining() + while total_rows: + with transaction.atomic(): + cursor.execute( + f'INSERT INTO {tblname} SELECT * FROM _old_{tblname} ORDER BY id DESC LIMIT {chunk} RETURNING id;' + ) + last_insert_pk = cursor.fetchone() + if last_insert_pk is None: + # this means that the SELECT from the old table was + # empty, and there was nothing to insert (so we're done) + break + last_insert_pk = last_insert_pk[0] + cursor.execute( + f'DELETE FROM _old_{tblname} WHERE id IN (SELECT id FROM _old_{tblname} ORDER BY id DESC LIMIT {chunk});' + ) + logger.warn( + f'migrated int -> bigint rows to {tblname} from _old_{tblname}; # ({last_insert_pk} rows remaining)' + ) + + if _remaining() is None: + cursor.execute(f'DROP TABLE IF EXISTS _old_{tblname}') + logger.warn(f'{tblname} primary key migration to bigint has finished') -@task() +@task(queue=get_local_queuename) def delete_inventory(inventory_id, user_id, retries=5): # Delete inventory as user if user_id is None: @@ -744,25 +877,12 @@ def update_model(self, pk, _attempt=0, **updates): logger.error('Failed to update %s after %d retries.', self.model._meta.object_name, _attempt) - def get_ansible_version(self, instance): - if not hasattr(self, '_ansible_version'): - self._ansible_version = _get_ansible_version( - ansible_path=self.get_path_to_ansible(instance, executable='ansible')) - return self._ansible_version - def get_path_to(self, *args): ''' Return absolute path relative to this file. ''' return os.path.abspath(os.path.join(os.path.dirname(__file__), *args)) - def get_path_to_ansible(self, instance, executable='ansible-playbook', **kwargs): - venv_path = getattr(instance, 'ansible_virtualenv_path', settings.ANSIBLE_VENV_PATH) - venv_exe = os.path.join(venv_path, 'bin', executable) - if os.path.exists(venv_exe): - return venv_exe - return shutil.which(executable) - def build_private_data(self, instance, private_data_dir): ''' Return SSH private key data (only if stored in DB as ssh_key_data). @@ -805,21 +925,14 @@ def build_private_data_files(self, instance, private_data_dir): private_data = self.build_private_data(instance, private_data_dir) private_data_files = {'credentials': {}} if private_data is not None: - ssh_ver = get_ssh_version() - ssh_too_old = True if ssh_ver == "unknown" else Version(ssh_ver) < Version("6.0") - openssh_keys_supported = ssh_ver != "unknown" and Version(ssh_ver) >= Version("6.5") for credential, data in private_data.get('credentials', {}).items(): - # Bail out now if a private key was provided in OpenSSH format - # and we're running an earlier version (<6.5). - if 'OPENSSH PRIVATE KEY' in data and not openssh_keys_supported: - raise RuntimeError(OPENSSH_KEY_ERROR) # OpenSSH formatted keys must have a trailing newline to be # accepted by ssh-add. if 'OPENSSH PRIVATE KEY' in data and not data.endswith('\n'): data += '\n' # For credentials used with ssh-add, write to a named pipe which # will be read then closed, instead of leaving the SSH key on disk. - if credential and credential.credential_type.namespace in ('ssh', 'scm') and not ssh_too_old: + if credential and credential.credential_type.namespace in ('ssh', 'scm'): try: os.mkdir(os.path.join(private_data_dir, 'env')) except OSError as e: @@ -924,8 +1037,6 @@ def build_params_resource_profiling(self, instance, private_data_dir): 'resource_profiling_memory_poll_interval': mem_poll_interval, 'resource_profiling_pid_poll_interval': pid_poll_interval, 'resource_profiling_results_dir': results_dir}) - else: - logger.debug('Resource profiling not enabled for task') return resource_profiling_params @@ -1116,6 +1227,13 @@ def event_handler(self, event_data): Ansible runner puts a parent_uuid on each event, no matter what the type. AWX only saves the parent_uuid if the event is for a Job. ''' + # cache end_line locally for RunInventoryUpdate tasks + # which generate job events from two 'streams': + # ansible-inventory and the awx.main.commands.inventory_import + # logger + if isinstance(self, RunInventoryUpdate): + self.end_line = event_data['end_line'] + if event_data.get(self.event_data_key, None): if self.event_data_key != 'job_id': event_data.pop('parent_uuid', None) @@ -1130,7 +1248,27 @@ def event_handler(self, event_data): else: event_data['host_name'] = '' event_data['host_id'] = '' - should_write_event = False + if event_data.get('event') == 'playbook_on_stats': + event_data['host_map'] = self.host_map + + if isinstance(self, RunProjectUpdate): + # it's common for Ansible's SCM modules to print + # error messages on failure that contain the plaintext + # basic auth credentials (username + password) + # it's also common for the nested event data itself (['res']['...']) + # to contain unredacted text on failure + # this is a _little_ expensive to filter + # with regex, but project updates don't have many events, + # so it *should* have a negligible performance impact + task = event_data.get('event_data', {}).get('task_action') + try: + if task in ('git', 'svn'): + event_data_json = json.dumps(event_data) + event_data_json = UriCleaner.remove_sensitive(event_data_json) + event_data = json.loads(event_data_json) + except json.JSONDecodeError: + pass + event_data.setdefault(self.event_data_key, self.instance.id) self.dispatcher.dispatch(event_data) self.event_ct += 1 @@ -1142,13 +1280,17 @@ def event_handler(self, event_data): self.instance.artifacts = event_data['event_data']['artifact_data'] self.instance.save(update_fields=['artifacts']) - return should_write_event + return False def cancel_callback(self): ''' Ansible runner callback to tell the job when/if it is canceled ''' - self.instance = self.update_model(self.instance.pk) + unified_job_id = self.instance.pk + self.instance = self.update_model(unified_job_id) + if not self.instance: + logger.error('unified job {} was deleted while running, canceling'.format(unified_job_id)) + return True if self.instance.cancel_flag or self.instance.status == 'canceled': cancel_wait = (now() - self.instance.modified).seconds if self.instance.modified else 0 if cancel_wait > 5: @@ -1309,7 +1451,6 @@ def run(self, pk, **kwargs): 'status_handler': self.status_handler, 'settings': { 'job_timeout': self.get_instance_timeout(self.instance), - 'pexpect_timeout': getattr(settings, 'PEXPECT_TIMEOUT', 5), 'suppress_ansible_output': True, **process_isolation_params, **resource_profiling_params, @@ -1338,6 +1479,7 @@ def run(self, pk, **kwargs): if not params[v]: del params[v] + self.dispatcher = CallbackQueueDispatcher() if self.instance.is_isolated() or containerized: module_args = None if 'module_args' in params: @@ -1352,6 +1494,7 @@ def run(self, pk, **kwargs): ansible_runner.utils.dump_artifacts(params) isolated_manager_instance = isolated_manager.IsolatedManager( + self.event_handler, canceled_callback=lambda: self.update_model(self.instance.pk).cancel_flag, check_callback=self.check_handler, pod_manager=pod_manager @@ -1361,11 +1504,9 @@ def run(self, pk, **kwargs): params.get('playbook'), params.get('module'), module_args, - event_data_key=self.event_data_key, ident=str(self.instance.pk)) - self.event_ct = len(isolated_manager_instance.handled_events) + self.finished_callback(None) else: - self.dispatcher = CallbackQueueDispatcher() res = ansible_runner.interface.run(**params) status = res.status rc = res.rc @@ -1374,6 +1515,8 @@ def run(self, pk, **kwargs): self.instance.job_explanation = "Job terminated due to timeout" status = 'failed' extra_update_fields['job_explanation'] = self.instance.job_explanation + # ensure failure notification sends even if playbook_on_stats event is not triggered + handle_success_and_failure_notifications.apply_async([self.instance.job.id]) except InvalidVirtualenvError as e: extra_update_fields['job_explanation'] = e.message @@ -1387,6 +1530,12 @@ def run(self, pk, **kwargs): try: self.post_run_hook(self.instance, status) + except PostRunError as exc: + if status == 'successful': + status = exc.status + extra_update_fields['job_explanation'] = exc.args[0] + if exc.tb: + extra_update_fields['result_traceback'] = exc.tb except Exception: logger.exception('{} Post run hook errored.'.format(self.instance.log_format)) @@ -1443,7 +1592,7 @@ def deploy_container_group_pod(self, task): -@task() +@task(queue=get_local_queuename) class RunJob(BaseTask): ''' Run a job using ansible-playbook. @@ -1520,21 +1669,10 @@ def build_passwords(self, job, runtime_passwords): return passwords - def add_ansible_venv(self, venv_path, env, isolated=False): - super(RunJob, self).add_ansible_venv(venv_path, env, isolated=isolated) - # Add awx/lib to PYTHONPATH. - env['PYTHONPATH'] = env.get('PYTHONPATH', '') + self.get_path_to('..', 'lib') + ':' - def build_env(self, job, private_data_dir, isolated=False, private_data_files=None): ''' Build environment dictionary for ansible-playbook. ''' - plugin_dir = self.get_path_to('..', 'plugins', 'callback') - plugin_dirs = [plugin_dir] - if hasattr(settings, 'AWX_ANSIBLE_CALLBACK_PLUGINS') and \ - settings.AWX_ANSIBLE_CALLBACK_PLUGINS: - plugin_dirs.extend(settings.AWX_ANSIBLE_CALLBACK_PLUGINS) - plugin_path = ':'.join(plugin_dirs) env = super(RunJob, self).build_env(job, private_data_dir, isolated=isolated, private_data_files=private_data_files) @@ -1545,22 +1683,14 @@ def build_env(self, job, private_data_dir, isolated=False, private_data_files=No # callbacks to work. env['JOB_ID'] = str(job.pk) env['INVENTORY_ID'] = str(job.inventory.pk) - if job.use_fact_cache: - library_path = env.get('ANSIBLE_LIBRARY') - env['ANSIBLE_LIBRARY'] = ':'.join( - filter(None, [ - library_path, - self.get_path_to('..', 'plugins', 'library') - ]) - ) if job.project: env['PROJECT_REVISION'] = job.project.scm_revision env['ANSIBLE_RETRY_FILES_ENABLED'] = "False" env['MAX_EVENT_RES'] = str(settings.MAX_EVENT_RES_DATA) if not isolated: - env['ANSIBLE_CALLBACK_PLUGINS'] = plugin_path env['ANSIBLE_STDOUT_CALLBACK'] = 'yaml' - env['TOWER_HOST'] = settings.TOWER_URL_BASE + if hasattr(settings, 'AWX_ANSIBLE_CALLBACK_PLUGINS') and settings.AWX_ANSIBLE_CALLBACK_PLUGINS: + env['ANSIBLE_CALLBACK_PLUGINS'] = ':'.join(settings.AWX_ANSIBLE_CALLBACK_PLUGINS) env['AWX_HOST'] = settings.TOWER_URL_BASE # Create a directory for ControlPath sockets that is unique to each @@ -1658,8 +1788,12 @@ def build_args(self, job, private_data_dir, passwords): args.append('--vault-id') args.append('{}@prompt'.format(vault_id)) - if job.forks: # FIXME: Max limit? - args.append('--forks=%d' % job.forks) + if job.forks: + if settings.MAX_FORKS > 0 and job.forks > settings.MAX_FORKS: + logger.warning(f'Maximum number of forks ({settings.MAX_FORKS}) exceeded.') + args.append('--forks=%d' % settings.MAX_FORKS) + else: + args.append('--forks=%d' % job.forks) if job.force_handlers: args.append('--force-handlers') if job.limit: @@ -1690,7 +1824,7 @@ def build_extra_vars_file(self, job, private_data_dir): # By default, all extra vars disallow Jinja2 template usage for # security reasons; top level key-values defined in JT.extra_vars, however, - # are whitelisted as "safe" (because they can only be set by users with + # are allowed as "safe" (because they can only be set by users with # higher levels of privilege - those that have the ability create and # edit Job Templates) safe_dict = {} @@ -1753,44 +1887,31 @@ def pre_run_hook(self, job, private_data_dir): project_path = job.project.get_project_path(check_if_exists=False) job_revision = job.project.scm_revision sync_needs = [] - all_sync_needs = ['update_{}'.format(job.project.scm_type), 'install_roles', 'install_collections'] + source_update_tag = 'update_{}'.format(job.project.scm_type) + branch_override = bool(job.scm_branch and job.scm_branch != job.project.scm_branch) if not job.project.scm_type: pass # manual projects are not synced, user has responsibility for that elif not os.path.exists(project_path): logger.debug('Performing fresh clone of {} on this instance.'.format(job.project)) - sync_needs = all_sync_needs - elif not job.project.scm_revision: - logger.debug('Revision not known for {}, will sync with remote'.format(job.project)) - sync_needs = all_sync_needs - elif job.project.scm_type == 'git': + sync_needs.append(source_update_tag) + elif job.project.scm_type == 'git' and job.project.scm_revision and (not branch_override): git_repo = git.Repo(project_path) try: - desired_revision = job.project.scm_revision - if job.scm_branch and job.scm_branch != job.project.scm_branch: - desired_revision = job.scm_branch # could be commit or not, but will try as commit - current_revision = git_repo.head.commit.hexsha - if desired_revision == current_revision: - job_revision = desired_revision - logger.info('Skipping project sync for {} because commit is locally available'.format(job.log_format)) + if job_revision == git_repo.head.commit.hexsha: + logger.debug('Skipping project sync for {} because commit is locally available'.format(job.log_format)) else: - sync_needs = all_sync_needs + sync_needs.append(source_update_tag) except (ValueError, BadGitName): logger.debug('Needed commit for {} not in local source tree, will sync with remote'.format(job.log_format)) - sync_needs = all_sync_needs + sync_needs.append(source_update_tag) else: - sync_needs = all_sync_needs + logger.debug('Project not available locally, {} will sync with remote'.format(job.log_format)) + sync_needs.append(source_update_tag) + + has_cache = os.path.exists(os.path.join(job.project.get_cache_path(), job.project.cache_id)) # Galaxy requirements are not supported for manual projects - if not sync_needs and job.project.scm_type: - # see if we need a sync because of presence of roles - galaxy_req_path = os.path.join(project_path, 'roles', 'requirements.yml') - if os.path.exists(galaxy_req_path): - logger.debug('Running project sync for {} because of galaxy role requirements.'.format(job.log_format)) - sync_needs.append('install_roles') - - galaxy_collections_req_path = os.path.join(project_path, 'collections', 'requirements.yml') - if os.path.exists(galaxy_collections_req_path): - logger.debug('Running project sync for {} because of galaxy collections requirements.'.format(job.log_format)) - sync_needs.append('install_collections') + if job.project.scm_type and ((not has_cache) or branch_override): + sync_needs.extend(['install_roles', 'install_collections']) if sync_needs: pu_ig = job.instance_group @@ -1808,7 +1929,7 @@ def pre_run_hook(self, job, private_data_dir): execution_node=pu_en, celery_task_id=job.celery_task_id ) - if job.scm_branch and job.scm_branch != job.project.scm_branch: + if branch_override: sync_metafields['scm_branch'] = job.scm_branch if 'update_' not in sync_metafields['job_tags']: sync_metafields['scm_revision'] = job_revision @@ -1840,10 +1961,7 @@ def pre_run_hook(self, job, private_data_dir): if job_revision: job = self.update_model(job.pk, scm_revision=job_revision) # Project update does not copy the folder, so copy here - RunProjectUpdate.make_local_copy( - project_path, os.path.join(private_data_dir, 'project'), - job.project.scm_type, job_revision - ) + RunProjectUpdate.make_local_copy(job.project, private_data_dir, scm_revision=job_revision) if job.inventory.kind == 'smart': # cache smart inventory memberships so that the host_filter query is not @@ -1870,10 +1988,11 @@ def final_run_hook(self, job, status, private_data_dir, fact_modification_times, except Inventory.DoesNotExist: pass else: - update_inventory_computed_fields.delay(inventory.id, True) + if inventory is not None: + update_inventory_computed_fields.delay(inventory.id) -@task() +@task(queue=get_local_queuename) class RunProjectUpdate(BaseTask): model = ProjectUpdate @@ -1882,10 +2001,7 @@ class RunProjectUpdate(BaseTask): @property def proot_show_paths(self): - show_paths = [settings.PROJECTS_ROOT] - if self.job_private_data_dir: - show_paths.append(self.job_private_data_dir) - return show_paths + return [settings.PROJECTS_ROOT] def __init__(self, *args, job_private_data_dir=None, **kwargs): super(RunProjectUpdate, self).__init__(*args, **kwargs) @@ -1919,12 +2035,6 @@ def build_private_data(self, project_update, private_data_dir): credential = project_update.credential if credential.has_input('ssh_key_data'): private_data['credentials'][credential] = credential.get_input('ssh_key_data', default='') - - # Create dir where collections will live for the job run - if project_update.job_type != 'check' and getattr(self, 'job_private_data_dir'): - for folder_name in ('requirements_collections', 'requirements_roles'): - folder_path = os.path.join(self.job_private_data_dir, folder_name) - os.mkdir(folder_path, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC) return private_data def build_passwords(self, project_update, runtime_passwords): @@ -1955,33 +2065,27 @@ def build_env(self, project_update, private_data_dir, isolated=False, private_da # like https://github.com/ansible/ansible/issues/30064 env['TMP'] = settings.AWX_PROOT_BASE_PATH env['PROJECT_UPDATE_ID'] = str(project_update.pk) - env['ANSIBLE_CALLBACK_PLUGINS'] = self.get_path_to('..', 'plugins', 'callback') - env['ANSIBLE_STDOUT_CALLBACK'] = 'yaml' if settings.GALAXY_IGNORE_CERTS: env['ANSIBLE_GALAXY_IGNORE'] = True - # Set up the public Galaxy server, if enabled - if settings.PUBLIC_GALAXY_ENABLED: - galaxy_servers = [settings.PUBLIC_GALAXY_SERVER] - else: - galaxy_servers = [] - # Set up fallback Galaxy servers, if configured - if settings.FALLBACK_GALAXY_SERVERS: - galaxy_servers = settings.FALLBACK_GALAXY_SERVERS + galaxy_servers - # Set up the primary Galaxy server, if configured - if settings.PRIMARY_GALAXY_URL: - galaxy_servers = [{'id': 'primary_galaxy'}] + galaxy_servers - for key in GALAXY_SERVER_FIELDS: - value = getattr(settings, 'PRIMARY_GALAXY_{}'.format(key.upper())) - if value: - galaxy_servers[0][key] = value - for server in galaxy_servers: - for key in GALAXY_SERVER_FIELDS: - if not server.get(key): - continue - env_key = ('ANSIBLE_GALAXY_SERVER_{}_{}'.format(server.get('id', 'unnamed'), key)).upper() - env[env_key] = server[key] - # now set the precedence of galaxy servers - env['ANSIBLE_GALAXY_SERVER_LIST'] = ','.join([server.get('id', 'unnamed') for server in galaxy_servers]) + + # build out env vars for Galaxy credentials (in order) + galaxy_server_list = [] + if project_update.project.organization: + for i, cred in enumerate( + project_update.project.organization.galaxy_credentials.all() + ): + env[f'ANSIBLE_GALAXY_SERVER_SERVER{i}_URL'] = cred.get_input('url') + auth_url = cred.get_input('auth_url', default=None) + token = cred.get_input('token', default=None) + if token: + env[f'ANSIBLE_GALAXY_SERVER_SERVER{i}_TOKEN'] = token + if auth_url: + env[f'ANSIBLE_GALAXY_SERVER_SERVER{i}_AUTH_URL'] = auth_url + galaxy_server_list.append(f'server{i}') + + if galaxy_server_list: + env['ANSIBLE_GALAXY_SERVER_LIST'] = ','.join(galaxy_server_list) + return env def _build_scm_url_extra_vars(self, project_update): @@ -2012,7 +2116,7 @@ def _build_scm_url_extra_vars(self, project_update): scm_username = False elif scm_url_parts.scheme.endswith('ssh'): scm_password = False - elif scm_type == 'insights': + elif scm_type in ('insights', 'archive'): extra_vars['scm_username'] = scm_username extra_vars['scm_password'] = scm_password scm_url = update_scm_url(scm_type, scm_url, scm_username, @@ -2047,25 +2151,39 @@ def build_extra_vars_file(self, project_update, private_data_dir): extra_vars.update(extra_vars_new) scm_branch = project_update.scm_branch - branch_override = bool(scm_branch and project_update.scm_branch != project_update.project.scm_branch) - if project_update.job_type == 'run' and (not branch_override): - scm_branch = project_update.project.scm_revision + if project_update.job_type == 'run' and (not project_update.branch_override): + if project_update.project.scm_revision: + scm_branch = project_update.project.scm_revision + elif not scm_branch: + raise RuntimeError('Could not determine a revision to run from project.') elif not scm_branch: - scm_branch = {'hg': 'tip'}.get(project_update.scm_type, 'HEAD') + scm_branch = 'HEAD' + + 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}.' + ) + extra_vars.update({ - 'project_path': project_update.get_project_path(check_if_exists=False), + 'projects_root': settings.PROJECTS_ROOT.rstrip('/'), + 'local_path': os.path.basename(project_update.project.local_path), + 'project_path': project_update.get_project_path(check_if_exists=False), # deprecated 'insights_url': settings.INSIGHTS_URL_BASE, - 'awx_license_type': get_license(show_key=False).get('license_type', 'UNLICENSED'), + 'awx_license_type': get_license().get('license_type', 'UNLICENSED'), 'awx_version': get_awx_version(), 'scm_url': scm_url, 'scm_branch': scm_branch, 'scm_clean': project_update.scm_clean, - 'roles_enabled': settings.AWX_ROLES_ENABLED, - 'collections_enabled': settings.AWX_COLLECTIONS_ENABLED, + 'roles_enabled': galaxy_creds_are_defined and settings.AWX_ROLES_ENABLED, + 'collections_enabled': galaxy_creds_are_defined and settings.AWX_COLLECTIONS_ENABLED, }) - if project_update.job_type != 'check' and self.job_private_data_dir: - extra_vars['collections_destination'] = os.path.join(self.job_private_data_dir, 'requirements_collections') - extra_vars['roles_destination'] = os.path.join(self.job_private_data_dir, 'requirements_roles') # apply custom refspec from user for PR refs and the like if project_update.scm_refspec: extra_vars['scm_refspec'] = project_update.scm_refspec @@ -2075,7 +2193,7 @@ def build_extra_vars_file(self, project_update, private_data_dir): self._write_extra_vars_file(private_data_dir, extra_vars) def build_cwd(self, project_update, private_data_dir): - return self.get_path_to('..', 'playbooks') + return os.path.join(private_data_dir, 'project') def build_playbook_path_relative_to_cwd(self, project_update, private_data_dir): return os.path.join('project_update.yml') @@ -2160,7 +2278,11 @@ def release_lock(self, instance): def acquire_lock(self, instance, blocking=True): lock_path = instance.get_lock_file() if lock_path is None: - raise RuntimeError(u'Invalid lock file path') + # If from migration or someone blanked local_path for any other reason, recoverable by save + instance.save() + lock_path = instance.get_lock_file() + if lock_path is None: + raise RuntimeError(u'Invalid lock file path') try: self.lock_fd = os.open(lock_path, os.O_RDWR | os.O_CREAT) @@ -2197,8 +2319,7 @@ def pre_run_hook(self, instance, private_data_dir): os.mkdir(settings.PROJECTS_ROOT) self.acquire_lock(instance) self.original_branch = None - if (instance.scm_type == 'git' and instance.job_type == 'run' and instance.project and - instance.scm_branch != instance.project.scm_branch): + if instance.scm_type == 'git' and instance.branch_override: project_path = instance.project.get_project_path(check_if_exists=False) if os.path.exists(project_path): git_repo = git.Repo(project_path) @@ -2207,17 +2328,54 @@ def pre_run_hook(self, instance, private_data_dir): else: self.original_branch = git_repo.active_branch + stage_path = os.path.join(instance.get_cache_path(), 'stage') + if os.path.exists(stage_path): + logger.warning('{0} unexpectedly existed before update'.format(stage_path)) + shutil.rmtree(stage_path) + os.makedirs(stage_path) # presence of empty cache indicates lack of roles or collections + + # the project update playbook is not in a git repo, but uses a vendoring directory + # to be consistent with the ansible-runner model, + # that is moved into the runner projecct folder here + awx_playbooks = self.get_path_to('..', 'playbooks') + copy_tree(awx_playbooks, os.path.join(private_data_dir, 'project')) + + @staticmethod + def clear_project_cache(cache_dir, keep_value): + if os.path.isdir(cache_dir): + for entry in os.listdir(cache_dir): + old_path = os.path.join(cache_dir, entry) + if entry not in (keep_value, 'stage'): + # invalidate, then delete + new_path = os.path.join(cache_dir,'.~~delete~~' + entry) + try: + os.rename(old_path, new_path) + shutil.rmtree(new_path) + except OSError: + logger.warning(f"Could not remove cache directory {old_path}") + @staticmethod - def make_local_copy(project_path, destination_folder, scm_type, scm_revision): - if scm_type == 'git': + def make_local_copy(p, job_private_data_dir, scm_revision=None): + """Copy project content (roles and collections) to a job private_data_dir + + :param object p: Either a project or a project update + :param str job_private_data_dir: The root of the target ansible-runner folder + :param str scm_revision: For branch_override cases, the git revision to copy + """ + project_path = p.get_project_path(check_if_exists=False) + destination_folder = os.path.join(job_private_data_dir, 'project') + if not scm_revision: + scm_revision = p.scm_revision + + if p.scm_type == 'git': git_repo = git.Repo(project_path) if not os.path.exists(destination_folder): os.mkdir(destination_folder, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC) tmp_branch_name = 'awx_internal/{}'.format(uuid4()) # always clone based on specific job revision - if not scm_revision: + if not p.scm_revision: raise RuntimeError('Unexpectedly could not determine a revision to run from project.') - source_branch = git_repo.create_head(tmp_branch_name, scm_revision) + source_branch = git_repo.create_head(tmp_branch_name, p.scm_revision) # git clone must take file:// syntax for source repo or else options like depth will be ignored source_as_uri = Path(project_path).as_uri() git.Repo.clone_from( @@ -2234,7 +2392,21 @@ def make_local_copy(project_path, destination_folder, scm_type, scm_revision): # force option is necessary because remote refs are not counted, although no information is lost git_repo.delete_head(tmp_branch_name, force=True) else: - copy_tree(project_path, destination_folder) + copy_tree(project_path, destination_folder, preserve_symlinks=1) + + # copy over the roles and collection cache to job folder + cache_path = os.path.join(p.get_cache_path(), p.cache_id) + subfolders = [] + if settings.AWX_COLLECTIONS_ENABLED: + subfolders.append('requirements_collections') + if settings.AWX_ROLES_ENABLED: + subfolders.append('requirements_roles') + for subfolder in subfolders: + cache_subpath = os.path.join(cache_path, subfolder) + if os.path.exists(cache_subpath): + dest_subpath = os.path.join(job_private_data_dir, subfolder) + copy_tree(cache_subpath, dest_subpath, preserve_symlinks=1) + logger.debug('{0} {1} prepared {2} from cache'.format(type(p).__name__, p.pk, dest_subpath)) def post_run_hook(self, instance, status): # To avoid hangs, very important to release lock even if errors happen here @@ -2242,13 +2414,29 @@ def post_run_hook(self, instance, status): if self.playbook_new_revision: instance.scm_revision = self.playbook_new_revision instance.save(update_fields=['scm_revision']) + + # Roles and collection folders copy to durable cache + base_path = instance.get_cache_path() + stage_path = os.path.join(base_path, 'stage') + if status == 'successful' and 'install_' in instance.job_tags: + # Clear other caches before saving this one, and if branch is overridden + # do not clear cache for main branch, but do clear it for other branches + self.clear_project_cache(base_path, keep_value=instance.project.cache_id) + cache_path = os.path.join(base_path, instance.cache_id) + if os.path.exists(stage_path): + if os.path.exists(cache_path): + logger.warning('Rewriting cache at {0}, performance may suffer'.format(cache_path)) + shutil.rmtree(cache_path) + os.rename(stage_path, cache_path) + logger.debug('{0} wrote to cache at {1}'.format(instance.log_format, cache_path)) + elif os.path.exists(stage_path): + shutil.rmtree(stage_path) # cannot trust content update produced + if self.job_private_data_dir: - # copy project folder before resetting to default branch - # because some git-tree-specific resources (like submodules) might matter - self.make_local_copy( - instance.get_project_path(check_if_exists=False), os.path.join(self.job_private_data_dir, 'project'), - instance.scm_type, instance.scm_revision - ) + if status == 'successful': + # copy project folder before resetting to default branch + # because some git-tree-specific resources (like submodules) might matter + self.make_local_copy(instance, self.job_private_data_dir) if self.original_branch: # for git project syncs, non-default branches can be problems # restore to branch the repo was on before this run @@ -2283,16 +2471,24 @@ def should_use_proot(self, project_update): return getattr(settings, 'AWX_PROOT_ENABLED', False) -@task() +@task(queue=get_local_queuename) class RunInventoryUpdate(BaseTask): model = InventoryUpdate event_model = InventoryUpdateEvent event_data_key = 'inventory_update_id' + # TODO: remove once inv updates run in containers + def should_use_proot(self, inventory_update): + ''' + Return whether this task should use proot. + ''' + return getattr(settings, 'AWX_PROOT_ENABLED', False) + + # TODO: remove once inv updates run in containers @property def proot_show_paths(self): - return [self.get_path_to('..', 'plugins', 'inventory')] + return [settings.AWX_ANSIBLE_COLLECTIONS_PATHS] def build_private_data(self, inventory_update, private_data_dir): """ @@ -2310,19 +2506,15 @@ def build_private_data(self, inventory_update, private_data_dir): If no private data is needed, return None. """ if inventory_update.source in InventorySource.injectors: - injector = InventorySource.injectors[inventory_update.source](self.get_ansible_version(inventory_update)) + injector = InventorySource.injectors[inventory_update.source]() return injector.build_private_data(inventory_update, private_data_dir) def build_env(self, inventory_update, private_data_dir, isolated, private_data_files=None): - """Build environment dictionary for inventory import. - - This used to be the mechanism by which any data that needs to be passed - to the inventory update script is set up. In particular, this is how - inventory update is aware of its proper credentials. + """Build environment dictionary for ansible-inventory. - Most environment injection is now accomplished by the credential - injectors. The primary purpose this still serves is to - still point to the inventory update INI or config file. + Most environment variables related to credentials or configuration + are accomplished by the inventory source injectors (in this method) + or custom credential type injectors (in main run method). """ env = super(RunInventoryUpdate, self).build_env(inventory_update, private_data_dir, @@ -2330,27 +2522,27 @@ def build_env(self, inventory_update, private_data_dir, isolated, private_data_f private_data_files=private_data_files) if private_data_files is None: private_data_files = {} - self.add_awx_venv(env) - # Pass inventory source ID to inventory script. + # TODO: remove once containers replace custom venvs + self.add_ansible_venv(inventory_update.ansible_virtualenv_path, env, isolated=isolated) + + # Legacy environment variables, were used as signal to awx-manage command + # now they are provided in case some scripts may be relying on them env['INVENTORY_SOURCE_ID'] = str(inventory_update.inventory_source_id) env['INVENTORY_UPDATE_ID'] = str(inventory_update.pk) env.update(STANDARD_INVENTORY_UPDATE_ENV) injector = None if inventory_update.source in InventorySource.injectors: - injector = InventorySource.injectors[inventory_update.source](self.get_ansible_version(inventory_update)) + injector = InventorySource.injectors[inventory_update.source]() if injector is not None: env = injector.build_env(inventory_update, env, private_data_dir, private_data_files) - # All CLOUD_PROVIDERS sources implement as either script or auto plugin - if injector.should_use_plugin(): - env['ANSIBLE_INVENTORY_ENABLED'] = 'auto' - else: - env['ANSIBLE_INVENTORY_ENABLED'] = 'script' + # All CLOUD_PROVIDERS sources implement as inventory plugin from collection + env['ANSIBLE_INVENTORY_ENABLED'] = 'auto' if inventory_update.source in ['scm', 'custom']: for env_k in inventory_update.source_vars_dict: - if str(env_k) not in env and str(env_k) not in settings.INV_ENV_VARIABLE_BLACKLIST: + if str(env_k) not in env and str(env_k) not in settings.INV_ENV_VARIABLE_BLOCKED: env[str(env_k)] = str(inventory_update.source_vars_dict[env_k]) elif inventory_update.source == 'file': raise NotImplementedError('Cannot update file sources through the task system.') @@ -2397,52 +2589,25 @@ def build_args(self, inventory_update, private_data_dir, passwords): if inventory is None: raise RuntimeError('Inventory Source is not associated with an Inventory.') - # Piece together the initial command to run via. the shell. - args = ['awx-manage', 'inventory_import'] - args.extend(['--inventory-id', str(inventory.pk)]) + args = ['ansible-inventory', '--list', '--export'] - # Add appropriate arguments for overwrite if the inventory_update - # object calls for it. - if inventory_update.overwrite: - args.append('--overwrite') - if inventory_update.overwrite_vars: - args.append('--overwrite-vars') + # Add arguments for the source inventory file/script/thing + source_location = self.pseudo_build_inventory(inventory_update, private_data_dir) + args.append('-i') + args.append(source_location) - # Declare the virtualenv the management command should activate - # as it calls ansible-inventory - args.extend(['--venv', inventory_update.ansible_virtualenv_path]) + args.append('--output') + args.append(os.path.join(private_data_dir, 'artifacts', 'output.json')) + + if os.path.isdir(source_location): + playbook_dir = source_location + else: + playbook_dir = os.path.dirname(source_location) + args.extend(['--playbook-dir', playbook_dir]) + + if inventory_update.verbosity: + args.append('-' + 'v' * min(5, inventory_update.verbosity * 2 + 1)) - src = inventory_update.source - # Add several options to the shell arguments based on the - # inventory-source-specific setting in the AWX configuration. - # These settings are "per-source"; it's entirely possible that - # they will be different between cloud providers if an AWX user - # actively uses more than one. - if getattr(settings, '%s_ENABLED_VAR' % src.upper(), False): - args.extend(['--enabled-var', - getattr(settings, '%s_ENABLED_VAR' % src.upper())]) - if getattr(settings, '%s_ENABLED_VALUE' % src.upper(), False): - args.extend(['--enabled-value', - getattr(settings, '%s_ENABLED_VALUE' % src.upper())]) - if getattr(settings, '%s_GROUP_FILTER' % src.upper(), False): - args.extend(['--group-filter', - getattr(settings, '%s_GROUP_FILTER' % src.upper())]) - if getattr(settings, '%s_HOST_FILTER' % src.upper(), False): - args.extend(['--host-filter', - getattr(settings, '%s_HOST_FILTER' % src.upper())]) - if getattr(settings, '%s_EXCLUDE_EMPTY_GROUPS' % src.upper()): - args.append('--exclude-empty-groups') - if getattr(settings, '%s_INSTANCE_ID_VAR' % src.upper(), False): - args.extend(['--instance-id-var', - getattr(settings, '%s_INSTANCE_ID_VAR' % src.upper()),]) - # Add arguments for the source inventory script - args.append('--source') - args.append(self.pseudo_build_inventory(inventory_update, private_data_dir)) - if src == 'custom': - args.append("--custom") - args.append('-v%d' % inventory_update.verbosity) - if settings.DEBUG: - args.append('--traceback') return args def build_inventory(self, inventory_update, private_data_dir): @@ -2459,19 +2624,15 @@ def pseudo_build_inventory(self, inventory_update, private_data_dir): injector = None if inventory_update.source in InventorySource.injectors: - injector = InventorySource.injectors[src](self.get_ansible_version(inventory_update)) + injector = InventorySource.injectors[src]() if injector is not None: - if injector.should_use_plugin(): - content = injector.inventory_contents(inventory_update, private_data_dir) - # must be a statically named file - inventory_path = os.path.join(private_data_dir, injector.filename) - with open(inventory_path, 'w') as f: - f.write(content) - os.chmod(inventory_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) - else: - # Use the vendored script path - inventory_path = self.get_path_to('..', 'plugins', 'inventory', injector.script_name) + content = injector.inventory_contents(inventory_update, private_data_dir) + # must be a statically named file + inventory_path = os.path.join(private_data_dir, injector.filename) + with open(inventory_path, 'w') as f: + f.write(content) + os.chmod(inventory_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) elif src == 'scm': inventory_path = os.path.join(private_data_dir, 'project', inventory_update.source_path) elif src == 'custom': @@ -2486,21 +2647,13 @@ def pseudo_build_inventory(self, inventory_update, private_data_dir): def build_cwd(self, inventory_update, private_data_dir): ''' - There are two cases where the inventory "source" is in a different + There is one case where the inventory "source" is in a different location from the private data: - - deprecated vendored inventory scripts in awx/plugins/inventory - SCM, where source needs to live in the project folder - in these cases, the inventory does not exist in the standard tempdir ''' src = inventory_update.source if src == 'scm' and inventory_update.source_project_update: return os.path.join(private_data_dir, 'project') - if src in CLOUD_PROVIDERS: - injector = None - if src in InventorySource.injectors: - injector = InventorySource.injectors[src](self.get_ansible_version(inventory_update)) - if (not injector) or (not injector.should_use_plugin()): - return self.get_path_to('..', 'plugins', 'inventory') return private_data_dir def build_playbook_path_relative_to_cwd(self, inventory_update, private_data_dir): @@ -2514,13 +2667,21 @@ def pre_run_hook(self, inventory_update, private_data_dir): source_project = None if inventory_update.inventory_source: source_project = inventory_update.inventory_source.source_project - if (inventory_update.source=='scm' and inventory_update.launch_type!='scm' and source_project): - # In project sync, pulling galaxy roles is not needed + if (inventory_update.source=='scm' and inventory_update.launch_type!='scm' and + source_project and source_project.scm_type): # never ever update manual projects + + # Check if the content cache exists, so that we do not unnecessarily re-download roles + sync_needs = ['update_{}'.format(source_project.scm_type)] + has_cache = os.path.exists(os.path.join(source_project.get_cache_path(), source_project.cache_id)) + # Galaxy requirements are not supported for manual projects + if not has_cache: + sync_needs.extend(['install_roles', 'install_collections']) + local_project_sync = source_project.create_project_update( _eager_fields=dict( launch_type="sync", job_type='run', - job_tags='update_{},install_collections'.format(source_project.scm_type), # roles are never valid for inventory + job_tags=','.join(sync_needs), status='running', execution_node=inventory_update.execution_node, instance_group = inventory_update.instance_group, @@ -2544,14 +2705,79 @@ def pre_run_hook(self, inventory_update, private_data_dir): raise elif inventory_update.source == 'scm' and inventory_update.launch_type == 'scm' and source_project: # This follows update, not sync, so make copy here - project_path = source_project.get_project_path(check_if_exists=False) - RunProjectUpdate.make_local_copy( - project_path, os.path.join(private_data_dir, 'project'), - source_project.scm_type, source_project.scm_revision - ) + RunProjectUpdate.make_local_copy(source_project, private_data_dir) + + def post_run_hook(self, inventory_update, status): + if status != 'successful': + return # nothing to save, step out of the way to allow error reporting + private_data_dir = inventory_update.job_env['AWX_PRIVATE_DATA_DIR'] + expected_output = os.path.join(private_data_dir, 'artifacts', 'output.json') + with open(expected_output) as f: + data = json.load(f) -@task() + # build inventory save options + options = dict( + overwrite=inventory_update.overwrite, + overwrite_vars=inventory_update.overwrite_vars, + ) + src = inventory_update.source + + if inventory_update.enabled_var: + options['enabled_var'] = inventory_update.enabled_var + options['enabled_value'] = inventory_update.enabled_value + else: + if getattr(settings, '%s_ENABLED_VAR' % src.upper(), False): + options['enabled_var'] = getattr(settings, '%s_ENABLED_VAR' % src.upper()) + if getattr(settings, '%s_ENABLED_VALUE' % src.upper(), False): + options['enabled_value'] = getattr(settings, '%s_ENABLED_VALUE' % src.upper()) + + if inventory_update.host_filter: + options['host_filter'] = inventory_update.host_filter + + if getattr(settings, '%s_EXCLUDE_EMPTY_GROUPS' % src.upper()): + options['exclude_empty_groups'] = True + if getattr(settings, '%s_INSTANCE_ID_VAR' % src.upper(), False): + options['instance_id_var'] = getattr(settings, '%s_INSTANCE_ID_VAR' % src.upper()) + + # Verbosity is applied to saving process, as well as ansible-inventory CLI option + if inventory_update.verbosity: + options['verbosity'] = inventory_update.verbosity + + handler = SpecialInventoryHandler( + self.event_handler, self.cancel_callback, + verbosity=inventory_update.verbosity, + job_timeout=self.get_instance_timeout(self.instance), + start_time=inventory_update.started, + counter=self.event_ct, initial_line=self.end_line + ) + inv_logger = logging.getLogger('awx.main.commands.inventory_import') + formatter = inv_logger.handlers[0].formatter + formatter.job_start = inventory_update.started + handler.formatter = formatter + inv_logger.handlers[0] = handler + + from awx.main.management.commands.inventory_import import Command as InventoryImportCommand + cmd = InventoryImportCommand() + try: + # save the inventory data to database. + # canceling exceptions will be handled in the global post_run_hook + cmd.perform_update(options, data, inventory_update) + except PermissionDenied as exc: + logger.exception('License error saving {} content'.format(inventory_update.log_format)) + raise PostRunError(str(exc), status='error') + except PostRunError: + logger.exception('Error saving {} content, rolling back changes'.format(inventory_update.log_format)) + raise + except Exception: + logger.exception('Exception saving {} content, rolling back changes.'.format( + inventory_update.log_format)) + raise PostRunError( + 'Error occured while saving inventory data, see traceback or server logs', + status='error', tb=traceback.format_exc()) + + +@task(queue=get_local_queuename) class RunAdHocCommand(BaseTask): ''' Run an ad hoc command using ansible. @@ -2608,7 +2834,6 @@ def build_env(self, ad_hoc_command, private_data_dir, isolated=False, private_da ''' Build environment dictionary for ansible. ''' - plugin_dir = self.get_path_to('..', 'plugins', 'callback') env = super(RunAdHocCommand, self).build_env(ad_hoc_command, private_data_dir, isolated=isolated, private_data_files=private_data_files) @@ -2618,13 +2843,15 @@ def build_env(self, ad_hoc_command, private_data_dir, isolated=False, private_da env['AD_HOC_COMMAND_ID'] = str(ad_hoc_command.pk) env['INVENTORY_ID'] = str(ad_hoc_command.inventory.pk) env['INVENTORY_HOSTVARS'] = str(True) - env['ANSIBLE_CALLBACK_PLUGINS'] = plugin_dir env['ANSIBLE_LOAD_CALLBACK_PLUGINS'] = '1' env['ANSIBLE_SFTP_BATCH_MODE'] = 'False' - # Specify empty SSH args (should disable ControlPersist entirely for - # ad hoc commands). - env.setdefault('ANSIBLE_SSH_ARGS', '') + # Create a directory for ControlPath sockets that is unique to each + # ad hoc command and visible inside the proot environment (when enabled). + cp_dir = os.path.join(private_data_dir, 'cp') + if not os.path.exists(cp_dir): + os.mkdir(cp_dir, 0o700) + env['ANSIBLE_SSH_CONTROL_PATH'] = cp_dir return env @@ -2741,7 +2968,7 @@ def final_run_hook(self, adhoc_job, status, private_data_dir, fact_modification_ isolated_manager_instance.cleanup() -@task() +@task(queue=get_local_queuename) class RunSystemJob(BaseTask): model = SystemJob @@ -2815,11 +3042,16 @@ def _reconstruct_relationships(copy_mapping): new_obj.save() -@task() +@task(queue=get_local_queuename) def deep_copy_model_obj( model_module, model_name, obj_pk, new_obj_pk, - user_pk, sub_obj_list, permission_check_func=None + 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 + 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 @@ -2854,4 +3086,4 @@ def deep_copy_model_obj( ), permission_check_func[2]) permission_check_func(creater, copy_mapping.values()) if isinstance(new_obj, Inventory): - update_inventory_computed_fields.delay(new_obj.id, True) + update_inventory_computed_fields.delay(new_obj.id) diff --git a/awx/main/tests/conftest.py b/awx/main/tests/conftest.py index 5b660b29cab3..409a4504ba5d 100644 --- a/awx/main/tests/conftest.py +++ b/awx/main/tests/conftest.py @@ -107,11 +107,6 @@ def workflow_job_template_factory(): return create_workflow_job_template -@pytest.fixture -def get_ssh_version(mocker): - return mocker.patch('awx.main.tasks.get_ssh_version', return_value='OpenSSH_6.9p1, LibreSSL 2.1.8') - - @pytest.fixture def job_template_with_survey_passwords_unit(job_template_with_survey_passwords_factory): return job_template_with_survey_passwords_factory(persisted=False) @@ -136,8 +131,8 @@ def delete(self, key): def pytest_runtest_teardown(item, nextitem): # clear Django cache at the end of every test ran - # NOTE: this should not be memcache, see test_cache in test_env.py - # this is a local test cache, so we want every test to start with empty cache + # NOTE: this should not be memcache (as it is deprecated), nor should it be redis. + # This is a local test cache, so we want every test to start with an empty cache cache.clear() diff --git a/awx/main/tests/data/inventory/plugins/azure_rm/files/azure_rm.yml b/awx/main/tests/data/inventory/plugins/azure_rm/files/azure_rm.yml deleted file mode 100644 index 4479e7905872..000000000000 --- a/awx/main/tests/data/inventory/plugins/azure_rm/files/azure_rm.yml +++ /dev/null @@ -1,43 +0,0 @@ -conditional_groups: - azure: true -default_host_filters: [] -exclude_host_filters: -- resource_group not in ['foo_resources', 'bar_resources'] -- '"Creator" not in tags.keys()' -- tags["Creator"] != "jmarshall" -- '"peanutbutter" not in tags.keys()' -- tags["peanutbutter"] != "jelly" -- location not in ['southcentralus', 'westus'] -fail_on_template_errors: false -hostvar_expressions: - ansible_host: private_ipv4_addresses[0] - computer_name: name - private_ip: private_ipv4_addresses[0] if private_ipv4_addresses else None - provisioning_state: provisioning_state | title - public_ip: public_ipv4_addresses[0] if public_ipv4_addresses else None - public_ip_id: public_ip_id if public_ip_id is defined else None - public_ip_name: public_ip_name if public_ip_name is defined else None - tags: tags if tags else None - type: resource_type -keyed_groups: -- key: location - prefix: '' - separator: '' -- key: tags.keys() | list if tags else [] - prefix: '' - separator: '' -- key: security_group - prefix: '' - separator: '' -- key: resource_group - prefix: '' - separator: '' -- key: os_disk.operating_system_type - prefix: '' - separator: '' -- key: dict(tags.keys() | map("regex_replace", "^(.*)$", "\1_") | list | zip(tags.values() | list)) if tags else [] - prefix: '' - separator: '' -plain_host_names: true -plugin: azure_rm -use_contrib_script_compatible_sanitization: true diff --git a/awx/main/tests/data/inventory/plugins/ec2/files/aws_ec2.yml b/awx/main/tests/data/inventory/plugins/ec2/files/aws_ec2.yml deleted file mode 100644 index b228770d361a..000000000000 --- a/awx/main/tests/data/inventory/plugins/ec2/files/aws_ec2.yml +++ /dev/null @@ -1,82 +0,0 @@ -boto_profile: /tmp/my_boto_stuff -compose: - ansible_host: public_ip_address - ec2_account_id: owner_id - ec2_ami_launch_index: ami_launch_index | string - ec2_architecture: architecture - ec2_block_devices: dict(block_device_mappings | map(attribute='device_name') | list | zip(block_device_mappings | map(attribute='ebs.volume_id') | list)) - ec2_client_token: client_token - ec2_dns_name: public_dns_name - ec2_ebs_optimized: ebs_optimized - ec2_eventsSet: events | default("") - ec2_group_name: placement.group_name - ec2_hypervisor: hypervisor - ec2_id: instance_id - ec2_image_id: image_id - ec2_instance_profile: iam_instance_profile | default("") - ec2_instance_type: instance_type - ec2_ip_address: public_ip_address - ec2_kernel: kernel_id | default("") - ec2_key_name: key_name - ec2_launch_time: launch_time | regex_replace(" ", "T") | regex_replace("(\+)(\d\d):(\d)(\d)$", ".\g<2>\g<3>Z") - ec2_monitored: monitoring.state in ['enabled', 'pending'] - ec2_monitoring_state: monitoring.state - ec2_persistent: persistent | default(false) - ec2_placement: placement.availability_zone - ec2_platform: platform | default("") - ec2_private_dns_name: private_dns_name - ec2_private_ip_address: private_ip_address - ec2_public_dns_name: public_dns_name - ec2_ramdisk: ramdisk_id | default("") - ec2_reason: state_transition_reason - ec2_region: placement.region - ec2_requester_id: requester_id | default("") - ec2_root_device_name: root_device_name - ec2_root_device_type: root_device_type - ec2_security_group_ids: security_groups | map(attribute='group_id') | list | join(',') - ec2_security_group_names: security_groups | map(attribute='group_name') | list | join(',') - ec2_sourceDestCheck: source_dest_check | default(false) | lower | string - ec2_spot_instance_request_id: spot_instance_request_id | default("") - ec2_state: state.name - ec2_state_code: state.code - ec2_state_reason: state_reason.message if state_reason is defined else "" - ec2_subnet_id: subnet_id | default("") - ec2_tag_Name: tags.Name - ec2_virtualization_type: virtualization_type - ec2_vpc_id: vpc_id | default("") -filters: - instance-state-name: - - running -groups: - ec2: true -hostnames: -- network-interface.addresses.association.public-ip -- dns-name -- private-dns-name -keyed_groups: -- key: placement.availability_zone - parent_group: zones - prefix: '' - separator: '' -- key: instance_type | regex_replace("[^A-Za-z0-9\_]", "_") - parent_group: types - prefix: type -- key: placement.region - parent_group: regions - prefix: '' - separator: '' -- key: dict(tags.keys() | map("regex_replace", "[^A-Za-z0-9\_]", "_") | list | zip(tags.values() | map("regex_replace", "[^A-Za-z0-9\_]", "_") | list)) - parent_group: tags - prefix: tag -- key: tags.keys() | map("regex_replace", "[^A-Za-z0-9\_]", "_") | list - parent_group: tags - prefix: tag -- key: placement.availability_zone - parent_group: '{{ placement.region }}' - prefix: '' - separator: '' -plugin: aws_ec2 -regions: -- us-east-2 -- ap-south-1 -use_contrib_script_compatible_sanitization: true diff --git a/awx/main/tests/data/inventory/plugins/gce/files/gcp_compute.yml b/awx/main/tests/data/inventory/plugins/gce/files/gcp_compute.yml deleted file mode 100644 index 9f1ea11c36f3..000000000000 --- a/awx/main/tests/data/inventory/plugins/gce/files/gcp_compute.yml +++ /dev/null @@ -1,50 +0,0 @@ -auth_kind: serviceaccount -compose: - ansible_ssh_host: networkInterfaces[0].accessConfigs[0].natIP | default(networkInterfaces[0].networkIP) - gce_description: description if description else None - gce_id: id - gce_image: image - gce_machine_type: machineType - gce_metadata: metadata.get("items", []) | items2dict(key_name="key", value_name="value") - gce_name: name - gce_network: networkInterfaces[0].network.name - gce_private_ip: networkInterfaces[0].networkIP - gce_public_ip: networkInterfaces[0].accessConfigs[0].natIP | default(None) - gce_status: status - gce_subnetwork: networkInterfaces[0].subnetwork.name - gce_tags: tags.get("items", []) - gce_zone: zone -hostnames: -- name -- public_ip -- private_ip -keyed_groups: -- key: gce_subnetwork - prefix: network -- key: gce_private_ip - prefix: '' - separator: '' -- key: gce_public_ip - prefix: '' - separator: '' -- key: machineType - prefix: '' - separator: '' -- key: zone - prefix: '' - separator: '' -- key: gce_tags - prefix: tag -- key: status | lower - prefix: status -- key: image - prefix: '' - separator: '' -plugin: gcp_compute -projects: -- fooo -retrieve_image_info: true -use_contrib_script_compatible_sanitization: true -zones: -- us-east4-a -- us-west1-b diff --git a/awx/main/tests/data/inventory/plugins/openstack/files/file_reference b/awx/main/tests/data/inventory/plugins/openstack/files/file_reference index daf13976f47c..c578942ca1a7 100644 --- a/awx/main/tests/data/inventory/plugins/openstack/files/file_reference +++ b/awx/main/tests/data/inventory/plugins/openstack/files/file_reference @@ -1,14 +1,11 @@ -ansible: - expand_hostvars: true - fail_on_errors: true - use_hostnames: false clouds: devstack: auth: auth_url: https://foo.invalid domain_name: fooo password: fooo + project_domain_name: fooo project_name: fooo username: fooo - private: false + private: true verify: false diff --git a/awx/main/tests/data/inventory/plugins/openstack/files/openstack.yml b/awx/main/tests/data/inventory/plugins/openstack/files/openstack.yml deleted file mode 100644 index c2b6ee58f315..000000000000 --- a/awx/main/tests/data/inventory/plugins/openstack/files/openstack.yml +++ /dev/null @@ -1,4 +0,0 @@ -expand_hostvars: true -fail_on_errors: true -inventory_hostname: uuid -plugin: openstack diff --git a/awx/main/tests/data/inventory/scripts/rhv/env.json b/awx/main/tests/data/inventory/plugins/rhv/env.json similarity index 100% rename from awx/main/tests/data/inventory/scripts/rhv/env.json rename to awx/main/tests/data/inventory/plugins/rhv/env.json diff --git a/awx/main/tests/data/inventory/scripts/rhv/files/file_reference b/awx/main/tests/data/inventory/plugins/rhv/files/file_reference similarity index 100% rename from awx/main/tests/data/inventory/scripts/rhv/files/file_reference rename to awx/main/tests/data/inventory/plugins/rhv/files/file_reference diff --git a/awx/main/tests/data/inventory/plugins/satellite6/files/foreman.yml b/awx/main/tests/data/inventory/plugins/satellite6/files/foreman.yml deleted file mode 100644 index 7528dbc9e9cd..000000000000 --- a/awx/main/tests/data/inventory/plugins/satellite6/files/foreman.yml +++ /dev/null @@ -1 +0,0 @@ -plugin: foreman diff --git a/awx/main/tests/data/inventory/plugins/tower/env.json b/awx/main/tests/data/inventory/plugins/tower/env.json index abdfef7e8d58..9ce3d90f9582 100644 --- a/awx/main/tests/data/inventory/plugins/tower/env.json +++ b/awx/main/tests/data/inventory/plugins/tower/env.json @@ -3,5 +3,6 @@ "TOWER_HOST": "https://foo.invalid", "TOWER_PASSWORD": "fooo", "TOWER_USERNAME": "fooo", + "TOWER_OAUTH_TOKEN": "", "TOWER_VERIFY_SSL": "False" } \ No newline at end of file diff --git a/awx/main/tests/data/inventory/plugins/tower/files/tower.yml b/awx/main/tests/data/inventory/plugins/tower/files/tower.yml deleted file mode 100644 index d8d0efdc9acf..000000000000 --- a/awx/main/tests/data/inventory/plugins/tower/files/tower.yml +++ /dev/null @@ -1,3 +0,0 @@ -include_metadata: true -inventory_id: 42 -plugin: tower diff --git a/awx/main/tests/data/inventory/plugins/vmware/env.json b/awx/main/tests/data/inventory/plugins/vmware/env.json new file mode 100644 index 000000000000..97563377c050 --- /dev/null +++ b/awx/main/tests/data/inventory/plugins/vmware/env.json @@ -0,0 +1,7 @@ +{ + "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/inventory/scripts/azure_rm/env.json b/awx/main/tests/data/inventory/scripts/azure_rm/env.json deleted file mode 100644 index 4a47eab9e6f0..000000000000 --- a/awx/main/tests/data/inventory/scripts/azure_rm/env.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "ANSIBLE_TRANSFORM_INVALID_GROUP_CHARS": "never", - "AZURE_CLIENT_ID": "fooo", - "AZURE_CLOUD_ENVIRONMENT": "fooo", - "AZURE_INI_PATH": "{{ file_reference }}", - "AZURE_SECRET": "fooo", - "AZURE_SUBSCRIPTION_ID": "fooo", - "AZURE_TENANT": "fooo" -} \ No newline at end of file diff --git a/awx/main/tests/data/inventory/scripts/azure_rm/files/file_reference b/awx/main/tests/data/inventory/scripts/azure_rm/files/file_reference deleted file mode 100644 index e3410bff066b..000000000000 --- a/awx/main/tests/data/inventory/scripts/azure_rm/files/file_reference +++ /dev/null @@ -1,11 +0,0 @@ -[azure] -include_powerstate = yes -group_by_resource_group = yes -group_by_location = yes -group_by_tag = yes -locations = southcentralus,westus -base_source_var = value_of_var -use_private_ip = True -resource_groups = foo_resources,bar_resources -tags = Creator:jmarshall, peanutbutter:jelly - diff --git a/awx/main/tests/data/inventory/scripts/cloudforms/env.json b/awx/main/tests/data/inventory/scripts/cloudforms/env.json deleted file mode 100644 index 68a4fd727168..000000000000 --- a/awx/main/tests/data/inventory/scripts/cloudforms/env.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "ANSIBLE_TRANSFORM_INVALID_GROUP_CHARS": "never", - "CLOUDFORMS_INI_PATH": "{{ file_reference }}" -} \ No newline at end of file diff --git a/awx/main/tests/data/inventory/scripts/cloudforms/files/cache_dir b/awx/main/tests/data/inventory/scripts/cloudforms/files/cache_dir deleted file mode 100644 index 973b0fcf001b..000000000000 --- a/awx/main/tests/data/inventory/scripts/cloudforms/files/cache_dir +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/awx/main/tests/data/inventory/scripts/cloudforms/files/file_reference b/awx/main/tests/data/inventory/scripts/cloudforms/files/file_reference deleted file mode 100644 index de6b1f15bf80..000000000000 --- a/awx/main/tests/data/inventory/scripts/cloudforms/files/file_reference +++ /dev/null @@ -1,16 +0,0 @@ -[cloudforms] -url = https://foo.invalid -username = fooo -password = fooo -ssl_verify = false -version = 2.4 -purge_actions = maybe -clean_group_keys = this_key -nest_tags = yes -suffix = .ppt -prefer_ipv4 = yes - -[cache] -max_age = 0 -path = {{ cache_dir }} - diff --git a/awx/main/tests/data/inventory/scripts/ec2/env.json b/awx/main/tests/data/inventory/scripts/ec2/env.json deleted file mode 100644 index 255fe066c478..000000000000 --- a/awx/main/tests/data/inventory/scripts/ec2/env.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "ANSIBLE_TRANSFORM_INVALID_GROUP_CHARS": "never", - "AWS_ACCESS_KEY_ID": "fooo", - "AWS_SECRET_ACCESS_KEY": "fooo", - "AWS_SECURITY_TOKEN": "fooo", - "EC2_INI_PATH": "{{ file_reference }}" -} \ No newline at end of file diff --git a/awx/main/tests/data/inventory/scripts/ec2/files/cache_dir b/awx/main/tests/data/inventory/scripts/ec2/files/cache_dir deleted file mode 100644 index 973b0fcf001b..000000000000 --- a/awx/main/tests/data/inventory/scripts/ec2/files/cache_dir +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/awx/main/tests/data/inventory/scripts/ec2/files/file_reference b/awx/main/tests/data/inventory/scripts/ec2/files/file_reference deleted file mode 100644 index aef5c1441cd7..000000000000 --- a/awx/main/tests/data/inventory/scripts/ec2/files/file_reference +++ /dev/null @@ -1,32 +0,0 @@ -[ec2] -base_source_var = value_of_var -boto_profile = /tmp/my_boto_stuff -regions = us-east-2,ap-south-1 -regions_exclude = us-gov-west-1,cn-north-1 -destination_variable = public_dns_name -vpc_destination_variable = ip_address -route53 = False -all_instances = True -all_rds_instances = False -include_rds_clusters = False -rds = False -nested_groups = True -elasticache = False -stack_filters = False -instance_filters = foobaa -group_by_ami_id = False -group_by_availability_zone = True -group_by_aws_account = False -group_by_instance_id = False -group_by_instance_state = False -group_by_platform = False -group_by_instance_type = True -group_by_key_pair = False -group_by_region = True -group_by_security_group = False -group_by_tag_keys = True -group_by_tag_none = False -group_by_vpc_id = False -cache_path = {{ cache_dir }} -cache_max_age = 300 - diff --git a/awx/main/tests/data/inventory/scripts/gce/env.json b/awx/main/tests/data/inventory/scripts/gce/env.json deleted file mode 100644 index caf92752c0a3..000000000000 --- a/awx/main/tests/data/inventory/scripts/gce/env.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "ANSIBLE_TRANSFORM_INVALID_GROUP_CHARS": "never", - "GCE_CREDENTIALS_FILE_PATH": "{{ file_reference }}", - "GCE_EMAIL": "fooo", - "GCE_INI_PATH": "{{ file_reference_0 }}", - "GCE_PROJECT": "fooo", - "GCE_ZONE": "us-east4-a,us-west1-b", - "GCP_AUTH_KIND": "serviceaccount", - "GCP_ENV_TYPE": "tower", - "GCP_PROJECT": "fooo", - "GCP_SERVICE_ACCOUNT_FILE": "{{ file_reference }}" -} \ No newline at end of file diff --git a/awx/main/tests/data/inventory/scripts/gce/files/file_reference b/awx/main/tests/data/inventory/scripts/gce/files/file_reference deleted file mode 100644 index ed4c40def122..000000000000 --- a/awx/main/tests/data/inventory/scripts/gce/files/file_reference +++ /dev/null @@ -1,7 +0,0 @@ -{ - "type": "service_account", - "private_key": "{{private_key}}", - "client_email": "fooo", - "project_id": "fooo", - "token_uri": "https://oauth2.googleapis.com/token" -} \ No newline at end of file diff --git a/awx/main/tests/data/inventory/scripts/gce/files/file_reference_0 b/awx/main/tests/data/inventory/scripts/gce/files/file_reference_0 deleted file mode 100644 index 0362854f7267..000000000000 --- a/awx/main/tests/data/inventory/scripts/gce/files/file_reference_0 +++ /dev/null @@ -1,3 +0,0 @@ -[cache] -cache_max_age = 0 - diff --git a/awx/main/tests/data/inventory/scripts/openstack/env.json b/awx/main/tests/data/inventory/scripts/openstack/env.json deleted file mode 100644 index 88dfb239c373..000000000000 --- a/awx/main/tests/data/inventory/scripts/openstack/env.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "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/scripts/openstack/files/cache_dir b/awx/main/tests/data/inventory/scripts/openstack/files/cache_dir deleted file mode 100644 index 973b0fcf001b..000000000000 --- a/awx/main/tests/data/inventory/scripts/openstack/files/cache_dir +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/awx/main/tests/data/inventory/scripts/openstack/files/file_reference b/awx/main/tests/data/inventory/scripts/openstack/files/file_reference deleted file mode 100644 index 92a624965e33..000000000000 --- a/awx/main/tests/data/inventory/scripts/openstack/files/file_reference +++ /dev/null @@ -1,16 +0,0 @@ -ansible: - expand_hostvars: true - fail_on_errors: true - use_hostnames: false -cache: - path: {{ cache_dir }} -clouds: - devstack: - auth: - auth_url: https://foo.invalid - domain_name: fooo - password: fooo - project_name: fooo - username: fooo - private: false - verify: false diff --git a/awx/main/tests/data/inventory/scripts/satellite6/env.json b/awx/main/tests/data/inventory/scripts/satellite6/env.json deleted file mode 100644 index f1ce94fd5643..000000000000 --- a/awx/main/tests/data/inventory/scripts/satellite6/env.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "ANSIBLE_TRANSFORM_INVALID_GROUP_CHARS": "never", - "FOREMAN_INI_PATH": "{{ file_reference }}" -} \ No newline at end of file diff --git a/awx/main/tests/data/inventory/scripts/satellite6/files/file_reference b/awx/main/tests/data/inventory/scripts/satellite6/files/file_reference deleted file mode 100644 index 9fb7639c441e..000000000000 --- a/awx/main/tests/data/inventory/scripts/satellite6/files/file_reference +++ /dev/null @@ -1,17 +0,0 @@ -[foreman] -base_source_var = value_of_var -ssl_verify = False -url = https://foo.invalid -user = fooo -password = fooo - -[ansible] -group_patterns = foo_group_patterns -want_facts = True -want_hostcollections = True -group_prefix = foo_group_prefix - -[cache] -path = /tmp -max_age = 0 - diff --git a/awx/main/tests/data/inventory/scripts/tower/env.json b/awx/main/tests/data/inventory/scripts/tower/env.json deleted file mode 100644 index c52ddba27acc..000000000000 --- a/awx/main/tests/data/inventory/scripts/tower/env.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "ANSIBLE_TRANSFORM_INVALID_GROUP_CHARS": "never", - "TOWER_HOST": "https://foo.invalid", - "TOWER_INVENTORY": "42", - "TOWER_LICENSE_TYPE": "open", - "TOWER_PASSWORD": "fooo", - "TOWER_USERNAME": "fooo", - "TOWER_VERIFY_SSL": "False" -} \ No newline at end of file diff --git a/awx/main/tests/data/inventory/scripts/vmware/env.json b/awx/main/tests/data/inventory/scripts/vmware/env.json deleted file mode 100644 index 41433e8c8303..000000000000 --- a/awx/main/tests/data/inventory/scripts/vmware/env.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "ANSIBLE_TRANSFORM_INVALID_GROUP_CHARS": "never", - "VMWARE_HOST": "https://foo.invalid", - "VMWARE_INI_PATH": "{{ file_reference }}", - "VMWARE_PASSWORD": "fooo", - "VMWARE_USER": "fooo", - "VMWARE_VALIDATE_CERTS": "False" -} \ No newline at end of file diff --git a/awx/main/tests/data/inventory/scripts/vmware/files/file_reference b/awx/main/tests/data/inventory/scripts/vmware/files/file_reference deleted file mode 100644 index 8a4d8a770096..000000000000 --- a/awx/main/tests/data/inventory/scripts/vmware/files/file_reference +++ /dev/null @@ -1,10 +0,0 @@ -[vmware] -cache_max_age = 0 -validate_certs = False -username = fooo -password = fooo -server = https://foo.invalid -base_source_var = value_of_var -host_filters = foobaa -groupby_patterns = fouo - diff --git a/awx/main/tests/docs/test_swagger_generation.py b/awx/main/tests/docs/test_swagger_generation.py index 5ee30e0b1e75..5def85b3d3d6 100644 --- a/awx/main/tests/docs/test_swagger_generation.py +++ b/awx/main/tests/docs/test_swagger_generation.py @@ -50,8 +50,6 @@ def _prepare(self, get, admin): data.update(response.accepted_renderer.get_customizations() or {}) data['host'] = None - if not pytest.config.getoption("--genschema"): - data['modified'] = datetime.datetime.utcnow().isoformat() data['schemes'] = ['https'] data['consumes'] = ['application/json'] @@ -79,10 +77,14 @@ def _prepare(self, get, admin): data['paths'] = revised_paths self.__class__.JSON = data - def test_sanity(self, release): + def test_sanity(self, release, request): JSON = self.__class__.JSON JSON['info']['version'] = release + + if not request.config.getoption('--genschema'): + JSON['modified'] = datetime.datetime.utcnow().isoformat() + # Make some basic assertions about the rendered JSON so we can # be sure it doesn't break across DRF upgrades and view/serializer # changes. @@ -105,9 +107,6 @@ def test_sanity(self, release): 'get', 'put', 'patch', 'delete' ] - # Test deprecated paths - assert paths['/api/v2/jobs/{id}/extra_credentials/']['get']['deprecated'] is True - @pytest.mark.parametrize('path', [ '/api/', '/api/v2/', @@ -118,7 +117,7 @@ def test_basic_paths(self, path, get, admin): # hit a couple important endpoints so we always have example data get(path, user=admin, expect=200) - def test_autogen_response_examples(self, swagger_autogen): + def test_autogen_response_examples(self, swagger_autogen, request): for pattern, node in TestSwaggerGeneration.JSON['paths'].items(): pattern = pattern.replace('{id}', '[0-9]+') pattern = pattern.replace(r'{category_slug}', r'[a-zA-Z0-9\-]+') @@ -141,7 +140,7 @@ def test_autogen_response_examples(self, swagger_autogen): for param in node[method].get('parameters'): if param['in'] == 'body': node[method]['parameters'].remove(param) - if pytest.config.getoption("--genschema"): + if request.config.getoption("--genschema"): pytest.skip("In schema generator skipping swagger generator", allow_module_level=True) else: node[method].setdefault('parameters', []).append({ @@ -177,7 +176,7 @@ def teardown_class(cls): data ) data = re.sub( - r'"action_node": "awx-[^"]+"', + r'"action_node": "[^"]+"', '"action_node": "awx"', data ) diff --git a/awx/main/tests/factories/README.md b/awx/main/tests/factories/README.md index c451c02598ad..916c996cfa80 100644 --- a/awx/main/tests/factories/README.md +++ b/awx/main/tests/factories/README.md @@ -52,11 +52,11 @@ patterns -------- `mk` functions are single object fixtures. They should create only a single object with the minimum deps. -They should also accept a `persited` flag, if they must be persisted to work, they raise an error if persisted=False +They should also accept a `persisted` flag, if they must be persisted to work, they raise an error if persisted=False `generate` and `apply` functions are helpers that build up the various parts of a `create` functions objects. These should be useful for more than one create function to use and should explicitly accept all of the values needed -to execute. These functions should also be robust and have very speciifc error reporting about constraints and/or +to execute. These functions should also be robust and have very specific error reporting about constraints and/or bad values. `create` functions compose many of the `mk` and `generate` functions to make different object diff --git a/awx/main/tests/factories/tower.py b/awx/main/tests/factories/tower.py index bfa7f9fc1b9d..87a7b436eb57 100644 --- a/awx/main/tests/factories/tower.py +++ b/awx/main/tests/factories/tower.py @@ -220,7 +220,7 @@ def create_job_template(name, roles=None, persisted=True, webhook_service='', ** if 'organization' in kwargs: org = kwargs['organization'] if type(org) is not Organization: - org = mk_organization(org, '%s-desc'.format(org), persisted=persisted) + org = mk_organization(org, org, persisted=persisted) if 'credential' in kwargs: cred = kwargs['credential'] @@ -298,7 +298,7 @@ def create_organization(name, roles=None, persisted=True, **kwargs): labels = {} notification_templates = {} - org = mk_organization(name, '%s-desc'.format(name), persisted=persisted) + org = mk_organization(name, name, persisted=persisted) if 'inventories' in kwargs: for i in kwargs['inventories']: @@ -319,11 +319,11 @@ def create_organization(name, roles=None, persisted=True, **kwargs): users = generate_users(org, teams, False, persisted, users=kwargs.get('users')) if 'labels' in kwargs: - for l in kwargs['labels']: - if type(l) is Label: - labels[l.name] = l + for label_obj in kwargs['labels']: + if type(label_obj) is Label: + labels[label_obj.name] = label_obj else: - labels[l] = mk_label(l, organization=org, persisted=persisted) + labels[label_obj] = mk_label(label_obj, organization=org, persisted=persisted) if 'notification_templates' in kwargs: for nt in kwargs['notification_templates']: diff --git a/awx/main/tests/functional/__init__.py b/awx/main/tests/functional/__init__.py index 262a142790ac..15c299128125 100644 --- a/awx/main/tests/functional/__init__.py +++ b/awx/main/tests/functional/__init__.py @@ -2,6 +2,9 @@ from django.db.models.signals import post_migrate from django.apps import apps from django.conf import settings +from unittest import mock + +import contextlib def app_post_migration(sender, app_config, **kwargs): @@ -23,3 +26,13 @@ def app_post_migration(sender, app_config, **kwargs): +@contextlib.contextmanager +def immediate_on_commit(): + """ + Context manager executing transaction.on_commit() hooks immediately as + if the connection was in auto-commit mode. + """ + def on_commit(func): + func() + with mock.patch('django.db.connection.on_commit', side_effect=on_commit) as patch: + yield patch diff --git a/awx/main/tests/functional/analytics/test_collectors.py b/awx/main/tests/functional/analytics/test_collectors.py new file mode 100644 index 000000000000..1d643588d1a9 --- /dev/null +++ b/awx/main/tests/functional/analytics/test_collectors.py @@ -0,0 +1,167 @@ +import pytest +import tempfile +import os +import re +import shutil +import csv + +from django.utils.timezone import now +from datetime import timedelta +from django.db.backends.sqlite3.base import SQLiteCursorWrapper + +from awx.main.analytics import collectors + +from awx.main.models import ( + ProjectUpdate, + InventorySource, + WorkflowJob, + WorkflowJobNode, + JobTemplate, +) + + +@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") + + def write_stdout(self, sql, fd): + # 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. + sql = sql.strip() + assert sql.startswith("COPY (") + assert sql.endswith(") TO STDOUT WITH CSV HEADER") + + sql = sql.replace("COPY (", "") + sql = sql.replace(") TO STDOUT WITH CSV HEADER", "") + # sqlite equivalent + sql = sql.replace("ARRAY_AGG", "GROUP_CONCAT") + # SQLite doesn't support isoformatted dates, because that would be useful + sql = sql.replace("+00:00", "") + i = re.compile(r'(?P\d\d\d\d-\d\d-\d\d)T') + sql = i.sub(r'\g ', sql) + + # Remove JSON style queries + # TODO: could replace JSON style queries with sqlite kind of equivalents + sql_new = [] + for line in sql.split("\n"): + if line.find("main_jobevent.event_data::") == -1: + sql_new.append(line) + elif not line.endswith(","): + sql_new[-1] = sql_new[-1].rstrip(",") + sql = "\n".join(sql_new) + + self.execute(sql) + results = self.fetchall() + headers = [i[0] for i in self.description] + + csv_handle = csv.writer( + fd, + delimiter=",", + quoting=csv.QUOTE_ALL, + escapechar="\\", + lineterminator="\n", + ) + csv_handle.writerow(headers) + csv_handle.writerows(results) + + setattr(SQLiteCursorWrapper, "copy_expert", write_stdout) + request.addfinalizer(lambda: shutil.rmtree(path)) + request.addfinalizer(lambda: delattr(SQLiteCursorWrapper, "copy_expert")) + return path + + +@pytest.mark.django_db +def test_copy_tables_unified_job_query( + sqlite_copy_expert, project, inventory, job_template +): + """ + Ensure that various unified job types are in the output of the query. + """ + + time_start = now() - timedelta(hours=9) + inv_src = InventorySource.objects.create( + name="inventory_update1", inventory=inventory, source="gce" + ) + + project_update_name = ProjectUpdate.objects.create( + project=project, name="project_update1" + ).name + inventory_update_name = inv_src.create_unified_job().name + job_name = job_template.create_unified_job().name + + with tempfile.TemporaryDirectory() as tmpdir: + collectors.unified_jobs_table(time_start, tmpdir, until = now() + timedelta(seconds=1)) + with open(os.path.join(tmpdir, "unified_jobs_table.csv")) as f: + lines = "".join([line for line in f]) + + assert project_update_name in lines + assert inventory_update_name in lines + assert job_name in lines + + +@pytest.fixture +def workflow_job(states=["new", "new", "new", "new", "new"]): + """ + Workflow topology: + node[0] + /\ + s/ \f + / \ + node[1,5] node[3] + / \ + s/ \f + / \ + node[2] node[4] + """ + wfj = WorkflowJob.objects.create() + jt = JobTemplate.objects.create(name="test-jt") + nodes = [ + WorkflowJobNode.objects.create(workflow_job=wfj, unified_job_template=jt) + for i in range(0, 6) + ] + for node, state in zip(nodes, states): + if state: + node.job = jt.create_job() + node.job.status = state + node.job.save() + node.save() + nodes[0].success_nodes.add(nodes[1]) + nodes[0].success_nodes.add(nodes[5]) + nodes[1].success_nodes.add(nodes[2]) + nodes[0].failure_nodes.add(nodes[3]) + nodes[3].failure_nodes.add(nodes[4]) + return wfj + + +@pytest.mark.django_db +def test_copy_tables_workflow_job_node_query(sqlite_copy_expert, workflow_job): + time_start = now() - timedelta(hours=9) + + with tempfile.TemporaryDirectory() as tmpdir: + collectors.workflow_job_node_table(time_start, tmpdir, until = now() + timedelta(seconds=1)) + with open(os.path.join(tmpdir, "workflow_job_node_table.csv")) as f: + reader = csv.reader(f) + # Pop the headers + next(reader) + lines = [line for line in reader] + + ids = [int(line[0]) for line in lines] + + assert ids == list( + workflow_job.workflow_nodes.all().values_list("id", flat=True) + ) + + for index, relationship in zip( + [7, 8, 9], ["success_nodes", "failure_nodes", "always_nodes"] + ): + for i, l in enumerate(lines): + related_nodes = ( + [int(e) for e in l[index].split(",")] if l[index] else [] + ) + assert related_nodes == list( + getattr(workflow_job.workflow_nodes.all()[i], relationship) + .all() + .values_list("id", flat=True) + ), f"(right side) workflow_nodes.all()[{i}].{relationship}.all()" diff --git a/awx/main/tests/functional/analytics/test_core.py b/awx/main/tests/functional/analytics/test_core.py index 01f3858661dc..f3cc1fcd4bfe 100644 --- a/awx/main/tests/functional/analytics/test_core.py +++ b/awx/main/tests/functional/analytics/test_core.py @@ -10,17 +10,17 @@ @register('example', '1.0') -def example(since): +def example(since, **kwargs): return {'awx': 123} @register('bad_json', '1.0') -def bad_json(since): +def bad_json(since, **kwargs): return set() @register('throws_error', '1.0') -def throws_error(since): +def throws_error(since, **kwargs): raise ValueError() @@ -39,9 +39,9 @@ def mock_valid_license(): def test_gather(mock_valid_license): settings.INSIGHTS_TRACKING_STATE = True - tgz = gather(module=importlib.import_module(__name__)) + tgzfiles = gather(module=importlib.import_module(__name__)) files = {} - with tarfile.open(tgz, "r:gz") as archive: + with tarfile.open(tgzfiles[0], "r:gz") as archive: for member in archive.getmembers(): files[member.name] = archive.extractfile(member) @@ -53,7 +53,8 @@ def test_gather(mock_valid_license): assert './bad_json.json' not in files.keys() assert './throws_error.json' not in files.keys() try: - os.remove(tgz) + for tgz in tgzfiles: + os.remove(tgz) except Exception: pass diff --git a/awx/main/tests/functional/analytics/test_counts.py b/awx/main/tests/functional/analytics/test_counts.py index 1c68b6fa34d9..877f21badadf 100644 --- a/awx/main/tests/functional/analytics/test_counts.py +++ b/awx/main/tests/functional/analytics/test_counts.py @@ -13,10 +13,10 @@ def test_empty(): "active_host_count": 0, "credential": 0, "custom_inventory_script": 0, - "custom_virtualenvs": 0, # dev env ansible3 + "custom_virtualenvs": 0, # dev env ansible3 "host": 0, "inventory": 0, - "inventories": {'normal': 0, 'smart': 0}, + "inventories": {"normal": 0, "smart": 0}, "job_template": 0, "notification_template": 0, "organization": 0, @@ -27,28 +27,97 @@ def test_empty(): "user": 0, "workflow_job_template": 0, "unified_job": 0, - "pending_jobs": 0 + "pending_jobs": 0, } @pytest.mark.django_db -def test_database_counts(organization_factory, job_template_factory, - workflow_job_template_factory): - objs = organization_factory('org', superusers=['admin']) - jt = job_template_factory('test', organization=objs.organization, - inventory='test_inv', project='test_project', - credential='test_cred') - workflow_job_template_factory('test') +def test_database_counts( + organization_factory, job_template_factory, workflow_job_template_factory +): + objs = organization_factory("org", superusers=["admin"]) + jt = job_template_factory( + "test", + organization=objs.organization, + inventory="test_inv", + project="test_project", + credential="test_cred", + ) + workflow_job_template_factory("test") models.Team(organization=objs.organization).save() models.Host(inventory=jt.inventory).save() models.Schedule( - rrule='DTSTART;TZID=America/New_York:20300504T150000', - unified_job_template=jt.job_template + rrule="DTSTART;TZID=America/New_York:20300504T150000", + unified_job_template=jt.job_template, ).save() models.CustomInventoryScript(organization=objs.organization).save() counts = collectors.counts(None) - for key in ('organization', 'team', 'user', 'inventory', 'credential', - 'project', 'job_template', 'workflow_job_template', 'host', - 'schedule', 'custom_inventory_script'): + for key in ( + "organization", + "team", + "user", + "inventory", + "credential", + "project", + "job_template", + "workflow_job_template", + "host", + "schedule", + "custom_inventory_script", + ): assert counts[key] == 1 + + +@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)] + + s1 = inv1.inventory_sources.create(name="src1", source="ec2") + s2 = inv1.inventory_sources.create(name="src2", source="file") + s3 = inv1.inventory_sources.create(name="src3", source="gce") + + s1.hosts.create(name="host1", inventory=inv1) + s1.hosts.create(name="host2", inventory=inv1) + s1.hosts.create(name="host3", inventory=inv1) + + s2.hosts.create(name="host4", inventory=inv1) + s2.hosts.create(name="host5", inventory=inv1) + + s3.hosts.create(name="host6", inventory=inv1) + + s1 = inv2.inventory_sources.create(name="src1", source="ec2") + + s1.hosts.create(name="host1", inventory=inv2) + s1.hosts.create(name="host2", inventory=inv2) + s1.hosts.create(name="host3", inventory=inv2) + + inv_counts = collectors.inventory_counts(None) + + assert { + inv1.id: { + "name": "inv-0", + "kind": "", + "hosts": 6, + "sources": 3, + "source_list": [ + {"name": "src1", "source": "ec2", "num_hosts": 3}, + {"name": "src2", "source": "file", "num_hosts": 2}, + {"name": "src3", "source": "gce", "num_hosts": 1}, + ], + }, + inv2.id: { + "name": "inv-1", + "kind": "", + "hosts": 3, + "sources": 1, + "source_list": [{"name": "src1", "source": "ec2", "num_hosts": 3}], + }, + inv3.id: { + "name": "inv-2", + "kind": "", + "hosts": 0, + "sources": 0, + "source_list": [], + }, + } == inv_counts diff --git a/awx/main/tests/functional/analytics/test_projects_by_scm_type.py b/awx/main/tests/functional/analytics/test_projects_by_scm_type.py deleted file mode 100644 index 29ffbd6283ef..000000000000 --- a/awx/main/tests/functional/analytics/test_projects_by_scm_type.py +++ /dev/null @@ -1,32 +0,0 @@ -import pytest -import random - -from awx.main.models import Project -from awx.main.analytics import collectors - - -@pytest.mark.django_db -def test_empty(): - assert collectors.projects_by_scm_type(None) == { - 'manual': 0, - 'git': 0, - 'svn': 0, - 'hg': 0, - 'insights': 0 - } - - -@pytest.mark.django_db -@pytest.mark.parametrize('scm_type', [t[0] for t in Project.SCM_TYPE_CHOICES]) -def test_multiple(scm_type): - expected = { - 'manual': 0, - 'git': 0, - 'svn': 0, - 'hg': 0, - 'insights': 0 - } - for i in range(random.randint(0, 10)): - Project(scm_type=scm_type).save() - expected[scm_type or 'manual'] += 1 - assert collectors.projects_by_scm_type(None) == expected diff --git a/awx/main/tests/functional/api/test_activity_streams.py b/awx/main/tests/functional/api/test_activity_streams.py index 8e49da6e0c9e..c002932a07a7 100644 --- a/awx/main/tests/functional/api/test_activity_streams.py +++ b/awx/main/tests/functional/api/test_activity_streams.py @@ -1,7 +1,6 @@ import pytest from awx.api.versioning import reverse -from awx.main.middleware import ActivityStreamMiddleware from awx.main.models.activity_stream import ActivityStream from awx.main.access import ActivityStreamAccess from awx.conf.models import Setting @@ -61,28 +60,6 @@ def test_ctint_activity_stream(monkeypatch, get, user, settings): assert response.data['summary_fields']['setting'][0]['name'] == 'FOO' -@pytest.mark.django_db -def test_middleware_actor_added(monkeypatch, post, get, user, settings): - settings.ACTIVITY_STREAM_ENABLED = True - u = user('admin-poster', True) - - url = reverse('api:organization_list') - response = post(url, - dict(name='test-org', description='test-desc'), - u, - middleware=ActivityStreamMiddleware()) - assert response.status_code == 201 - - org_id = response.data['id'] - activity_stream = ActivityStream.objects.filter(organization__pk=org_id).first() - - url = reverse('api:activity_stream_detail', kwargs={'pk': activity_stream.pk}) - response = get(url, u) - - assert response.status_code == 200 - assert response.data['summary_fields']['actor']['username'] == 'admin-poster' - - @pytest.mark.django_db def test_rbac_stream_resource_roles(activity_stream_entry, organization, org_admin, settings): settings.ACTIVITY_STREAM_ENABLED = True diff --git a/awx/main/tests/functional/api/test_credential.py b/awx/main/tests/functional/api/test_credential.py index adf61d0e45b6..e8e7b4b271d1 100644 --- a/awx/main/tests/functional/api/test_credential.py +++ b/awx/main/tests/functional/api/test_credential.py @@ -60,6 +60,36 @@ def test_credential_validation_error_with_bad_user(post, admin, credentialtype_s assert response.data['user'][0] == 'Incorrect type. Expected pk value, received str.' +@pytest.mark.django_db +def test_credential_validation_error_with_no_owner_field(post, admin, credentialtype_ssh): + params = { + 'credential_type': credentialtype_ssh.id, + 'inputs': {'username': 'someusername'}, + 'name': 'Some name', + } + response = post(reverse('api:credential_list'), params, admin) + assert response.status_code == 400 + assert response.data['detail'][0] == "Missing 'user', 'team', or 'organization'." + + +@pytest.mark.django_db +def test_credential_validation_error_with_multiple_owner_fields(post, admin, alice, team, organization, credentialtype_ssh): + params = { + 'credential_type': credentialtype_ssh.id, + 'inputs': {'username': 'someusername'}, + 'team': team.id, + 'user': alice.id, + 'organization': organization.id, + 'name': 'Some name', + } + 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." + ) + + @pytest.mark.django_db def test_create_user_credential_via_user_credentials_list(post, get, alice, credentialtype_ssh): params = { @@ -645,33 +675,6 @@ def test_net_create_ok(post, organization, admin): assert cred.inputs['authorize'] is True -# -# Cloudforms Credentials -# -@pytest.mark.django_db -def test_cloudforms_create_ok(post, organization, admin): - params = { - 'credential_type': 1, - 'name': 'Best credential ever', - 'inputs': { - 'host': 'some_host', - 'username': 'some_username', - 'password': 'some_password', - } - } - cloudforms = CredentialType.defaults['cloudforms']() - cloudforms.save() - params['organization'] = organization.id - 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.inputs['host'] == 'some_host' - assert cred.inputs['username'] == 'some_username' - assert decrypt_field(cred, 'password') == 'some_password' - - # # GCE Credentials # @@ -972,7 +975,7 @@ def test_field_removal(put, organization, admin, credentialtype_ssh): ['insights_inventories', Inventory()], ['unifiedjobs', Job()], ['unifiedjobtemplates', JobTemplate()], - ['unifiedjobtemplates', InventorySource()], + ['unifiedjobtemplates', InventorySource(source='ec2')], ['projects', Project()], ['workflowjobnodes', WorkflowJobNode()], ]) @@ -1123,6 +1126,22 @@ def _change_credential_type(): assert response.status_code == 200 +@pytest.mark.django_db +@pytest.mark.parametrize('field', ['password', 'ssh_key_data']) +def test_secret_fields_cannot_be_special_encrypted_variable(post, organization, admin, credentialtype_ssh, field): + params = { + 'name': 'Best credential ever', + 'credential_type': credentialtype_ssh.id, + 'inputs': { + 'username': 'joe', + field: '$encrypted$', + }, + 'organization': organization.id, + } + response = post(reverse('api:credential_list'), params, admin, status=400) + assert str(response.data['inputs'][0]) == f'$encrypted$ is a reserved keyword, and cannot be used for {field}.' + + @pytest.mark.django_db def test_ssh_unlock_needed(put, organization, admin, credentialtype_ssh): params = { diff --git a/awx/main/tests/functional/api/test_credential_type.py b/awx/main/tests/functional/api/test_credential_type.py index 45b5e79994bd..bf7aa4ceffae 100644 --- a/awx/main/tests/functional/api/test_credential_type.py +++ b/awx/main/tests/functional/api/test_credential_type.py @@ -220,7 +220,7 @@ def test_create_valid_kind(kind, get, post, admin): @pytest.mark.django_db -@pytest.mark.parametrize('kind', ['ssh', 'vault', 'scm', 'insights']) +@pytest.mark.parametrize('kind', ['ssh', 'vault', 'scm', 'insights', 'kubernetes', 'galaxy']) def test_create_invalid_kind(kind, get, post, admin): response = post(reverse('api:credential_type_list'), { 'kind': kind, 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 880b7ff8925c..38fea20c4d46 100644 --- a/awx/main/tests/functional/api/test_deprecated_credential_assignment.py +++ b/awx/main/tests/functional/api/test_deprecated_credential_assignment.py @@ -24,41 +24,6 @@ def job_template(job_template, project, inventory): return job_template -@pytest.mark.django_db -def test_extra_credentials_filtering(get, job_template, admin, - machine_credential, vault_credential, credential): - job_template.credentials.add(machine_credential) - job_template.credentials.add(vault_credential) - job_template.credentials.add(credential) - url = reverse( - 'api:job_template_extra_credentials_list', - kwargs={'pk': job_template.pk} - ) - resp = get(url, admin, expect=200) - assert resp.data['count'] == 1 - assert resp.data['results'][0]['id'] == credential.pk - - -@pytest.mark.django_db -def test_extra_credentials_requires_cloud_or_net(get, post, job_template, admin, - machine_credential, vault_credential, credential, - net_credential): - url = reverse( - 'api:job_template_extra_credentials_list', - kwargs={'pk': job_template.pk} - ) - - for cred in (machine_credential, vault_credential): - resp = post(url, {'associate': True, 'id': cred.pk}, admin, expect=400) - assert 'Extra credentials must be network or cloud.' in smart_str(resp.content) - - post(url, {'associate': True, 'id': credential.pk}, admin, expect=204) - assert get(url, admin).data['count'] == 1 - - post(url, {'associate': True, 'id': net_credential.pk}, admin, expect=204) - assert get(url, admin).data['count'] == 2 - - @pytest.mark.django_db def test_prevent_multiple_machine_creds(get, post, job_template, admin, machine_credential): url = reverse( @@ -115,52 +80,6 @@ def test_prevent_multiple_machine_creds_at_launch(get, post, job_template, admin assert 'Cannot assign multiple Machine credentials.' in smart_str(resp.content) -@pytest.mark.django_db -def test_extra_credentials_unique_by_kind(get, post, job_template, admin, - credentialtype_aws): - url = reverse( - 'api:job_template_extra_credentials_list', - kwargs={'pk': job_template.pk} - ) - - def _new_cred(name): - return { - 'name': name, - 'credential_type': credentialtype_aws.pk, - 'inputs': { - 'username': 'bob', - 'password': 'secret', - } - } - - post(url, _new_cred('First Cred'), admin, expect=201) - assert get(url, admin).data['count'] == 1 - - resp = post(url, _new_cred('Second Cred'), admin, expect=400) - assert 'Cannot assign multiple Amazon Web Services credentials.' in smart_str(resp.content) - - -@pytest.mark.django_db -def test_extra_credentials_at_launch(get, post, job_template, admin, credential): - url = reverse('api:job_template_launch', kwargs={'pk': job_template.pk}) - pk = post(url, {'extra_credentials': [credential.pk]}, admin, expect=201).data['job'] - summary_fields = get(reverse('api:job_detail', kwargs={'pk': pk}), admin).data['summary_fields'] - - assert len(summary_fields['credentials']) == 1 - - -@pytest.mark.django_db -def test_modify_extra_credentials_at_launch(get, post, job_template, admin, - machine_credential, vault_credential, credential): - job_template.credentials.add(machine_credential) - job_template.credentials.add(vault_credential) - url = reverse('api:job_template_launch', kwargs={'pk': job_template.pk}) - pk = post(url, {'extra_credentials': [credential.pk]}, admin, expect=201).data['job'] - - summary_fields = get(reverse('api:job_detail', kwargs={'pk': pk}), admin).data['summary_fields'] - assert len(summary_fields['credentials']) == 3 - - @pytest.mark.django_db def test_ssh_password_prompted_at_launch(get, post, job_template, admin, machine_credential): job_template.credentials.add(machine_credential) @@ -229,25 +148,6 @@ def test_vault_credential_with_password_at_launch(get, post, job_template, admin signal_start.assert_called_with(vault_password='testing123') -@pytest.mark.django_db -def test_extra_creds_prompted_at_launch(get, post, job_template, admin, net_credential): - url = reverse('api:job_template_launch', kwargs={'pk': job_template.pk}) - resp = post(url, {'extra_credentials': [net_credential.pk]}, admin, expect=201) - - summary_fields = get( - reverse('api:job_detail', kwargs={'pk': resp.data['job']}), - admin - ).data['summary_fields'] - assert len(summary_fields['credentials']) == 1 - - -@pytest.mark.django_db -def test_invalid_mixed_credentials_specification(get, post, job_template, admin, net_credential): - url = reverse('api:job_template_launch', kwargs={'pk': job_template.pk}) - post(url=url, data={'credentials': [net_credential.pk], 'extra_credentials': [net_credential.pk]}, - user=admin, expect=400) - - @pytest.mark.django_db def test_deprecated_credential_activity_stream(patch, admin_user, machine_credential, job_template): job_template.credentials.add(machine_credential) diff --git a/awx/main/tests/functional/api/test_generic.py b/awx/main/tests/functional/api/test_generic.py index c6fe7ca18800..2933cb09e640 100644 --- a/awx/main/tests/functional/api/test_generic.py +++ b/awx/main/tests/functional/api/test_generic.py @@ -4,7 +4,7 @@ @pytest.mark.django_db -def test_proxy_ip_whitelist(get, patch, admin): +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': [ @@ -23,37 +23,37 @@ def process_request(self, request): def process_response(self, request, response): self.environ = request.environ - # By default, `PROXY_IP_WHITELIST` is disabled, so custom `REMOTE_HOST_HEADERS` + # By default, `PROXY_IP_ALLOWED_LIST` is disabled, so custom `REMOTE_HOST_HEADERS` # should just pass through middleware = HeaderTrackingMiddleware() get(url, user=admin, middleware=middleware, HTTP_X_FROM_THE_LOAD_BALANCER='some-actual-ip') assert middleware.environ['HTTP_X_FROM_THE_LOAD_BALANCER'] == 'some-actual-ip' - # If `PROXY_IP_WHITELIST` is restricted to 10.0.1.100 and we make a request + # If `PROXY_IP_ALLOWED_LIST` is restricted to 10.0.1.100 and we make a request # from 8.9.10.11, the custom `HTTP_X_FROM_THE_LOAD_BALANCER` header should # be stripped patch(url, user=admin, data={ - 'PROXY_IP_WHITELIST': ['10.0.1.100'] + 'PROXY_IP_ALLOWED_LIST': ['10.0.1.100'] }) middleware = HeaderTrackingMiddleware() get(url, user=admin, middleware=middleware, REMOTE_ADDR='8.9.10.11', HTTP_X_FROM_THE_LOAD_BALANCER='some-actual-ip') assert 'HTTP_X_FROM_THE_LOAD_BALANCER' not in middleware.environ - # If 8.9.10.11 is added to `PROXY_IP_WHITELIST` the + # If 8.9.10.11 is added to `PROXY_IP_ALLOWED_LIST` the # `HTTP_X_FROM_THE_LOAD_BALANCER` header should be passed through again patch(url, user=admin, data={ - 'PROXY_IP_WHITELIST': ['10.0.1.100', '8.9.10.11'] + 'PROXY_IP_ALLOWED_LIST': ['10.0.1.100', '8.9.10.11'] }) middleware = HeaderTrackingMiddleware() get(url, user=admin, middleware=middleware, REMOTE_ADDR='8.9.10.11', HTTP_X_FROM_THE_LOAD_BALANCER='some-actual-ip') assert middleware.environ['HTTP_X_FROM_THE_LOAD_BALANCER'] == 'some-actual-ip' - # Allow whitelisting of proxy hostnames in addition to IP addresses + # Allow allowed list of proxy hostnames in addition to IP addresses patch(url, user=admin, data={ - 'PROXY_IP_WHITELIST': ['my.proxy.example.org'] + 'PROXY_IP_ALLOWED_LIST': ['my.proxy.example.org'] }) middleware = HeaderTrackingMiddleware() get(url, user=admin, middleware=middleware, REMOTE_ADDR='8.9.10.11', diff --git a/awx/main/tests/functional/api/test_inventory.py b/awx/main/tests/functional/api/test_inventory.py index a86846e5c80c..5bad1b6f305e 100644 --- a/awx/main/tests/functional/api/test_inventory.py +++ b/awx/main/tests/functional/api/test_inventory.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import pytest +import json from unittest import mock from django.core.exceptions import ValidationError @@ -8,8 +9,6 @@ from awx.main.models import InventorySource, Inventory, ActivityStream -import json - @pytest.fixture def scm_inventory(inventory, project): @@ -60,6 +59,42 @@ def test_inventory_source_unique_together_with_inv(inventory_factory): is2.validate_unique() +@pytest.mark.django_db +def test_inventory_host_name_unique(scm_inventory, post, admin_user): + inv_src = scm_inventory.inventory_sources.first() + inv_src.groups.create(name='barfoo', inventory=scm_inventory) + resp = post( + reverse('api:inventory_hosts_list', kwargs={'pk': scm_inventory.id}), + { + 'name': 'barfoo', + 'inventory_id': scm_inventory.id, + }, + admin_user, + expect=400 + ) + + assert resp.status_code == 400 + assert "A Group with that name already exists." in json.dumps(resp.data) + + +@pytest.mark.django_db +def test_inventory_group_name_unique(scm_inventory, post, admin_user): + inv_src = scm_inventory.inventory_sources.first() + inv_src.hosts.create(name='barfoo', inventory=scm_inventory) + resp = post( + reverse('api:inventory_groups_list', kwargs={'pk': scm_inventory.id}), + { + 'name': 'barfoo', + 'inventory_id': scm_inventory.id, + }, + admin_user, + expect=400 + ) + + assert resp.status_code == 400 + assert "A Host with that name already exists." in json.dumps(resp.data) + + @pytest.mark.parametrize("role_field,expected_status_code", [ (None, 403), ('admin_role', 200), @@ -413,7 +448,7 @@ def test_inventory_update_access_called(post, inventory_source, alice, mock_acce @pytest.mark.django_db def test_inventory_source_vars_prohibition(post, inventory, admin_user): with mock.patch('awx.api.serializers.settings') as mock_settings: - mock_settings.INV_ENV_VARIABLE_BLACKLIST = ('FOOBAR',) + mock_settings.INV_ENV_VARIABLE_BLOCKED = ('FOOBAR',) r = post(reverse('api:inventory_source_list'), {'name': 'new inv src', 'source_vars': '{\"FOOBAR\": \"val\"}', 'inventory': inventory.pk}, admin_user, expect=400) @@ -486,7 +521,8 @@ def test_vault_credential_not_allowed(self, project, inventory, vault_credential data={ 'inventory': inventory.pk, 'name': 'fobar', 'source': 'scm', 'source_project': project.pk, 'source_path': '', - 'credential': vault_credential.pk + 'credential': vault_credential.pk, + 'source_vars': 'plugin: a.b.c', }, expect=400, user=admin_user @@ -525,7 +561,7 @@ def test_credentials_relationship_mapping(self, project, inventory, organization data={ 'inventory': inventory.pk, 'name': 'fobar', 'source': 'scm', 'source_project': project.pk, 'source_path': '', - 'credential': os_cred.pk + 'credential': os_cred.pk, 'source_vars': 'plugin: a.b.c', }, expect=201, user=admin_user @@ -599,9 +635,15 @@ def test_remove_scm_inv_src(self, delete, scm_inventory, admin_user): delete(inv_src.get_absolute_url(), admin_user, expect=204) assert scm_inventory.inventory_sources.count() == 0 - def test_adding_inv_src_ok(self, post, scm_inventory, admin_user): - post(reverse('api:inventory_inventory_sources_list', kwargs={'pk': scm_inventory.id}), - {'name': 'new inv src', 'update_on_project_update': False, 'source': 'scm', 'overwrite_vars': True}, + def test_adding_inv_src_ok(self, post, scm_inventory, project, admin_user): + post(reverse('api:inventory_inventory_sources_list', + kwargs={'pk': scm_inventory.id}), + {'name': 'new inv src', + 'source_project': project.pk, + 'update_on_project_update': False, + 'source': 'scm', + 'overwrite_vars': True, + 'source_vars': 'plugin: a.b.c'}, admin_user, expect=201) def test_adding_inv_src_prohibited(self, post, scm_inventory, project, admin_user): @@ -621,7 +663,7 @@ def test_two_update_on_project_update_inv_src_prohibited(self, patch, scm_invent def test_adding_inv_src_without_proj_access_prohibited(self, post, project, inventory, rando): inventory.admin_role.members.add(rando) post(reverse('api:inventory_inventory_sources_list', kwargs={'pk': inventory.id}), - {'name': 'new inv src', 'source_project': project.pk, 'source': 'scm', 'overwrite_vars': True}, + {'name': 'new inv src', 'source_project': project.pk, 'source': 'scm', 'overwrite_vars': True, 'source_vars': 'plugin: a.b.c'}, rando, expect=403) diff --git a/awx/main/tests/functional/api/test_job.py b/awx/main/tests/functional/api/test_job.py index 39c5d179a6cb..a0467bd6c09c 100644 --- a/awx/main/tests/functional/api/test_job.py +++ b/awx/main/tests/functional/api/test_job.py @@ -23,21 +23,27 @@ @pytest.mark.django_db -def test_extra_credentials(get, organization_factory, job_template_factory, credential): - objs = organization_factory("org", superusers=['admin']) - jt = job_template_factory("jt", organization=objs.organization, - inventory='test_inv', project='test_proj').job_template - jt.credentials.add(credential) - jt.save() - job = jt.create_unified_job() +def test_job_relaunch_permission_denied_response( + post, get, inventory, project, credential, net_credential, machine_credential): + jt = JobTemplate.objects.create(name='testjt', inventory=inventory, project=project, ask_credential_on_launch=True) + jt.credentials.add(machine_credential) + jt_user = User.objects.create(username='jobtemplateuser') + jt.execute_role.members.add(jt_user) + with impersonate(jt_user): + job = jt.create_unified_job() - url = reverse('api:job_extra_credentials_list', kwargs={'pk': job.pk}) - response = get(url, user=objs.superusers.admin) - assert response.data.get('count') == 1 + # User capability is shown for this + r = get(job.get_absolute_url(), jt_user, expect=200) + assert r.data['summary_fields']['user_capabilities']['start'] + + # Job has prompted credential, launch denied w/ message + job.launch_config.credentials.add(net_credential) + r = post(reverse('api:job_relaunch', kwargs={'pk':job.pk}), {}, jt_user, expect=403) + assert 'launched with prompted fields you do not have access to' in r.data['detail'] @pytest.mark.django_db -def test_job_relaunch_permission_denied_response( +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) jt.credentials.add(machine_credential) @@ -50,11 +56,9 @@ def test_job_relaunch_permission_denied_response( r = get(job.get_absolute_url(), jt_user, expect=200) assert r.data['summary_fields']['user_capabilities']['start'] - # Job has prompted extra_credential, launch denied w/ message + # Job has prompted credential, launch denied w/ message job.launch_config.credentials.add(net_credential) r = post(reverse('api:job_relaunch', kwargs={'pk':job.pk}), {}, jt_user, expect=403) - assert 'launched with prompted fields' in r.data['detail'] - assert 'do not have permission' in r.data['detail'] @pytest.mark.django_db @@ -153,7 +157,8 @@ def test_summary_fields_recent_jobs(job_template, admin_user, get): 'id': job.id, 'status': 'failed', 'finished': job.finished, - 'type': 'job' + 'canceled_on': None, + 'type': 'job' } for job in jobs[-10:][::-1]] @@ -208,7 +213,8 @@ def test_block_related_unprocessed_events(mocker, organization, project, delete, status='finished', finished=time_of_finish, job_template=job_template, - project=project + project=project, + organization=project.organization ) view = RelatedJobsPreventDeleteMixin() time_of_request = time_of_finish + relativedelta(seconds=2) 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 c2cd279bab61..80b2fcadfb8d 100644 --- a/awx/main/tests/functional/api/test_job_runtime_params.py +++ b/awx/main/tests/functional/api/test_job_runtime_params.py @@ -264,18 +264,6 @@ def test_job_launch_fails_without_credential_access(job_template_prompts, runtim dict(credentials=runtime_data['credentials']), rando, expect=403) -@pytest.mark.django_db -@pytest.mark.job_runtime_vars -def test_job_block_scan_job_type_change(job_template_prompts, post, admin_user): - job_template = job_template_prompts(True) - - # Assure that changing the type of a scan job blocks the launch - response = post(reverse('api:job_template_launch', kwargs={'pk':job_template.pk}), - dict(job_type='scan'), admin_user, expect=400) - - assert 'job_type' in response.data - - @pytest.mark.django_db def test_job_launch_JT_with_validation(machine_credential, credential, deploy_jobtemplate): deploy_jobtemplate.extra_vars = '{"job_template_var": 3}' @@ -316,7 +304,7 @@ def test_job_launch_with_default_creds(machine_credential, vault_credential, dep @pytest.mark.django_db def test_job_launch_JT_enforces_unique_credentials_kinds(machine_credential, credentialtype_aws, deploy_jobtemplate): """ - JT launching should require that extra_credentials have distinct CredentialTypes + JT launching should require that credentials have distinct CredentialTypes """ creds = [] for i in range(2): @@ -371,6 +359,71 @@ def test_job_launch_fails_with_missing_vault_password(machine_credential, vault_ assert response.data['passwords_needed_to_start'] == ['vault_password'] +@pytest.mark.django_db +def test_job_launch_with_added_cred_and_vault_password(credential, machine_credential, vault_credential, + deploy_jobtemplate, post, admin): + # see: https://github.com/ansible/awx/issues/8202 + vault_credential.inputs['vault_password'] = 'ASK' + vault_credential.save() + payload = { + 'credentials': [vault_credential.id, machine_credential.id], + 'credential_passwords': {'vault_password': 'vault-me'}, + } + + deploy_jobtemplate.ask_credential_on_launch = True + deploy_jobtemplate.credentials.remove(credential) + deploy_jobtemplate.credentials.add(vault_credential) + deploy_jobtemplate.save() + + with mock.patch.object(Job, 'signal_start') as signal_start: + post( + reverse('api:job_template_launch', kwargs={'pk': deploy_jobtemplate.pk}), + payload, + admin, + expect=201, + ) + signal_start.assert_called_with(**{ + 'vault_password': 'vault-me' + }) + + +@pytest.mark.django_db +def test_job_launch_with_multiple_launch_time_passwords(credential, machine_credential, vault_credential, + deploy_jobtemplate, post, admin): + # see: https://github.com/ansible/awx/issues/8202 + deploy_jobtemplate.ask_credential_on_launch = True + deploy_jobtemplate.credentials.remove(credential) + deploy_jobtemplate.credentials.add(machine_credential) + deploy_jobtemplate.credentials.add(vault_credential) + deploy_jobtemplate.save() + + second_machine_credential = Credential( + name='SSH #2', + credential_type=machine_credential.credential_type, + inputs={'password': 'ASK'} + ) + second_machine_credential.save() + + vault_credential.inputs['vault_password'] = 'ASK' + vault_credential.save() + payload = { + 'credentials': [vault_credential.id, second_machine_credential.id], + 'credential_passwords': {'ssh_password': 'ssh-me', 'vault_password': 'vault-me'}, + } + + with mock.patch.object(Job, 'signal_start') as signal_start: + post( + reverse('api:job_template_launch', kwargs={'pk': deploy_jobtemplate.pk}), + payload, + admin, + expect=201, + ) + signal_start.assert_called_with(**{ + 'ssh_password': 'ssh-me', + 'vault_password': 'vault-me', + }) + + @pytest.mark.django_db @pytest.mark.parametrize('launch_kwargs', [ {'vault_password.abc': 'vault-me-1', 'vault_password.xyz': 'vault-me-2'}, @@ -495,25 +548,26 @@ def test_job_launch_pass_with_prompted_vault_password(machine_credential, vault_ @pytest.mark.django_db -def test_job_launch_JT_with_credentials(machine_credential, credential, net_credential, deploy_jobtemplate): +def test_job_launch_JT_with_credentials(machine_credential, credential, net_credential, kube_credential, deploy_jobtemplate): deploy_jobtemplate.ask_credential_on_launch = True deploy_jobtemplate.save() - kv = dict(credentials=[credential.pk, net_credential.pk, machine_credential.pk]) + kv = dict(credentials=[credential.pk, net_credential.pk, machine_credential.pk, kube_credential.pk]) serializer = JobLaunchSerializer(data=kv, context={'template': deploy_jobtemplate}) validated = serializer.is_valid() assert validated, serializer.errors - kv['credentials'] = [credential, net_credential, machine_credential] # convert to internal value + kv['credentials'] = [credential, net_credential, machine_credential, kube_credential] # convert to internal value prompted_fields, ignored_fields, errors = deploy_jobtemplate._accept_or_ignore_job_kwargs( _exclude_errors=['required', 'prompts'], **kv) job_obj = deploy_jobtemplate.create_unified_job(**prompted_fields) creds = job_obj.credentials.all() - assert len(creds) == 3 + assert len(creds) == 4 assert credential in creds assert net_credential in creds assert machine_credential in creds + assert kube_credential in creds @pytest.mark.django_db diff --git a/awx/main/tests/functional/api/test_job_template.py b/awx/main/tests/functional/api/test_job_template.py index 6ae9e87d7e16..1883f6e6ecd9 100644 --- a/awx/main/tests/functional/api/test_job_template.py +++ b/awx/main/tests/functional/api/test_job_template.py @@ -6,7 +6,7 @@ # AWX from awx.api.serializers import JobTemplateSerializer from awx.api.versioning import reverse -from awx.main.models import Job, JobTemplate, CredentialType, WorkflowJobTemplate +from awx.main.models import Job, JobTemplate, CredentialType, WorkflowJobTemplate, Organization, Project from awx.main.migrations import _save_password_keys as save_password_keys # Django @@ -30,35 +30,19 @@ def test_create(post, project, machine_credential, inventory, alice, grant_proje project.use_role.members.add(alice) if grant_inventory: inventory.use_role.members.add(alice) + project.organization.job_template_admin_role.members.add(alice) - r = post(reverse('api:job_template_list'), { - 'name': 'Some name', - 'project': project.id, - 'inventory': inventory.id, - 'playbook': 'helloworld.yml', - }, alice) - assert r.status_code == expect - - -@pytest.mark.django_db -def test_extra_credential_creation(get, post, organization_factory, job_template_factory, credentialtype_aws): - objs = organization_factory("org", superusers=['admin']) - jt = job_template_factory("jt", organization=objs.organization, - inventory='test_inv', project='test_proj').job_template - - url = reverse('api:job_template_extra_credentials_list', kwargs={'pk': jt.pk}) - response = post(url, { - 'name': 'My Cred', - 'credential_type': credentialtype_aws.pk, - 'inputs': { - 'username': 'bob', - 'password': 'secret', - } - }, objs.superusers.admin) - assert response.status_code == 201 - - response = get(url, user=objs.superusers.admin) - assert response.data.get('count') == 1 + post( + url=reverse('api:job_template_list'), + data={ + 'name': 'Some name', + 'project': project.id, + 'inventory': inventory.id, + 'playbook': 'helloworld.yml' + }, + user=alice, + expect=expect + ) @pytest.mark.django_db @@ -83,93 +67,23 @@ def test_invalid_credential_kind_xfail(get, post, organization_factory, job_temp @pytest.mark.django_db -def test_extra_credential_unique_type_xfail(get, post, organization_factory, job_template_factory, credentialtype_aws): - objs = organization_factory("org", superusers=['admin']) - jt = job_template_factory("jt", organization=objs.organization, - inventory='test_inv', project='test_proj').job_template - - url = reverse('api:job_template_extra_credentials_list', kwargs={'pk': jt.pk}) - response = post(url, { - 'name': 'My Cred', - 'credential_type': credentialtype_aws.pk, - 'inputs': { - 'username': 'bob', - 'password': 'secret', - } - }, objs.superusers.admin) - assert response.status_code == 201 - - response = get(url, user=objs.superusers.admin) - assert response.data.get('count') == 1 - - # this request should fail because you can't assign the same type (aws) - # twice - response = post(url, { - 'name': 'My Cred', - 'credential_type': credentialtype_aws.pk, - 'inputs': { - 'username': 'joe', - 'password': 'another-secret', - } - }, objs.superusers.admin) - assert response.status_code == 400 - - response = get(url, user=objs.superusers.admin) - assert response.data.get('count') == 1 - - -@pytest.mark.django_db -def test_attach_extra_credential(get, post, organization_factory, job_template_factory, credential): - objs = organization_factory("org", superusers=['admin']) - jt = job_template_factory("jt", organization=objs.organization, - inventory='test_inv', project='test_proj').job_template - - url = reverse('api:job_template_extra_credentials_list', kwargs={'pk': jt.pk}) - response = post(url, { - 'associate': True, - 'id': credential.id, - }, objs.superusers.admin) - assert response.status_code == 204 - - response = get(url, user=objs.superusers.admin) - assert response.data.get('count') == 1 - - -@pytest.mark.django_db -def test_detach_extra_credential(get, post, organization_factory, job_template_factory, credential): - objs = organization_factory("org", superusers=['admin']) - jt = job_template_factory("jt", organization=objs.organization, - inventory='test_inv', project='test_proj').job_template - jt.credentials.add(credential) - jt.save() - - url = reverse('api:job_template_extra_credentials_list', kwargs={'pk': jt.pk}) - response = post(url, { - 'disassociate': True, - 'id': credential.id, - }, objs.superusers.admin) - assert response.status_code == 204 - - response = get(url, user=objs.superusers.admin) - assert response.data.get('count') == 0 - - -@pytest.mark.django_db -def test_attach_extra_credential_wrong_kind_xfail(get, post, organization_factory, job_template_factory, machine_credential): - """Extra credentials only allow net + cloud credentials""" - objs = organization_factory("org", superusers=['admin']) - jt = job_template_factory("jt", organization=objs.organization, - inventory='test_inv', project='test_proj').job_template - - url = reverse('api:job_template_extra_credentials_list', kwargs={'pk': jt.pk}) - response = post(url, { - 'associate': True, - 'id': machine_credential.id, - }, objs.superusers.admin) - assert response.status_code == 400 - - response = get(url, user=objs.superusers.admin) - assert response.data.get('count') == 0 +def test_create_with_forks_exceeding_maximum_xfail(alice, post, project, inventory, settings): + project.use_role.members.add(alice) + inventory.use_role.members.add(alice) + settings.MAX_FORKS = 10 + response = post( + url=reverse('api:job_template_list'), + data={ + 'name': 'Some name', + 'project': project.id, + 'inventory': inventory.id, + 'playbook': 'helloworld.yml', + 'forks': 11, + }, + user=alice, + expect=400 + ) + assert 'Maximum number of forks (10) exceeded' in str(response.data) @pytest.mark.django_db @@ -343,57 +257,6 @@ def test_launch_with_pending_deletion_inventory_workflow(get, post, organization assert resp.data['inventory'] == ['The inventory associated with this Workflow is being deleted.'] -@pytest.mark.django_db -def test_launch_with_extra_credentials(get, post, organization_factory, - job_template_factory, machine_credential, - credential, net_credential): - objs = organization_factory("org", superusers=['admin']) - jt = job_template_factory("jt", organization=objs.organization, - inventory='test_inv', project='test_proj').job_template - jt.ask_credential_on_launch = True - jt.save() - - resp = post( - reverse('api:job_template_launch', kwargs={'pk': jt.pk}), - dict( - credentials=[machine_credential.pk, credential.pk, net_credential.pk] - ), - objs.superusers.admin, expect=201 - ) - job_pk = resp.data.get('id') - - resp = get(reverse('api:job_extra_credentials_list', kwargs={'pk': job_pk}), objs.superusers.admin) - assert resp.data.get('count') == 2 - - resp = get(reverse('api:job_template_extra_credentials_list', kwargs={'pk': jt.pk}), objs.superusers.admin) - assert resp.data.get('count') == 0 - - -@pytest.mark.django_db -def test_launch_with_extra_credentials_not_allowed(get, post, organization_factory, - job_template_factory, machine_credential, - credential, net_credential): - objs = organization_factory("org", superusers=['admin']) - jt = job_template_factory("jt", organization=objs.organization, - inventory='test_inv', project='test_proj').job_template - jt.credentials.add(machine_credential) - jt.ask_credential_on_launch = False - jt.save() - - resp = post( - reverse('api:job_template_launch', kwargs={'pk': jt.pk}), - dict( - credentials=[machine_credential.pk, credential.pk, net_credential.pk] - ), - objs.superusers.admin - ) - assert 'credentials' in resp.data['ignored_fields'].keys() - job_pk = resp.data.get('id') - - resp = get(reverse('api:job_extra_credentials_list', kwargs={'pk': job_pk}), objs.superusers.admin) - assert resp.data.get('count') == 0 - - @pytest.mark.django_db def test_jt_without_project(inventory): data = dict(name="Test", job_type="run", @@ -494,6 +357,72 @@ def test_job_template_unset_custom_virtualenv(get, patch, organization_factory, assert resp.data['custom_virtualenv'] is None +@pytest.mark.django_db +def test_jt_organization_follows_project(post, patch, admin_user): + org1 = Organization.objects.create(name='foo1') + org2 = Organization.objects.create(name='foo2') + project_common = dict(scm_type='git', playbook_files=['helloworld.yml']) + project1 = Project.objects.create(name='proj1', organization=org1, **project_common) + project2 = Project.objects.create(name='proj2', organization=org2, **project_common) + r = post( + url=reverse('api:job_template_list'), + data={ + "name": "fooo", + "ask_inventory_on_launch": True, + "project": project1.pk, + "playbook": "helloworld.yml" + }, + user=admin_user, + expect=201 + ) + data = r.data + assert data['organization'] == project1.organization_id + data['project'] = project2.id + jt = JobTemplate.objects.get(pk=data['id']) + r = patch( + url=jt.get_absolute_url(), + data=data, + user=admin_user, + expect=200 + ) + assert r.data['organization'] == project2.organization_id + + +@pytest.mark.django_db +def test_jt_organization_field_is_read_only(patch, post, project, admin_user): + org = project.organization + jt = JobTemplate.objects.create( + name='foo_jt', + ask_inventory_on_launch=True, + project=project, playbook='helloworld.yml' + ) + org2 = Organization.objects.create(name='foo2') + r = patch( + url=jt.get_absolute_url(), + data={'organization': org2.id}, + user=admin_user, + expect=200 + ) + assert r.data['organization'] == org.id + assert JobTemplate.objects.get(pk=jt.pk).organization == org + + # similar test, but on creation + r = post( + url=reverse('api:job_template_list'), + data={ + 'name': 'foobar', + 'project': project.id, + 'organization': org2.id, + 'ask_inventory_on_launch': True, + 'playbook': 'helloworld.yml' + }, + user=admin_user, + expect=201 + ) + assert r.data['organization'] == org.id + assert JobTemplate.objects.get(pk=r.data['id']).organization == org + + @pytest.mark.django_db def test_callback_disallowed_null_inventory(project): jt = JobTemplate.objects.create( diff --git a/awx/main/tests/functional/api/test_oauth.py b/awx/main/tests/functional/api/test_oauth.py index 3973ebeac373..0c185bd38637 100644 --- a/awx/main/tests/functional/api/test_oauth.py +++ b/awx/main/tests/functional/api/test_oauth.py @@ -1,8 +1,8 @@ -import pytest import base64 -import contextlib import json -from unittest import mock +import time + +import pytest from django.db import connection from django.test.utils import override_settings @@ -12,22 +12,11 @@ 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 -@contextlib.contextmanager -def immediate_on_commit(): - """ - Context manager executing transaction.on_commit() hooks immediately as - if the connection was in auto-commit mode. - """ - def on_commit(func): - func() - with mock.patch('django.db.connection.on_commit', side_effect=on_commit) as patch: - yield patch - - @pytest.mark.django_db def test_personal_access_token_creation(oauth_application, post, alice): url = drf_reverse('api:oauth_authorization_root_view') + 'token/' @@ -339,6 +328,38 @@ def test_refresh_accesstoken(oauth_application, post, get, delete, admin): 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): diff --git a/awx/main/tests/functional/api/test_organization_counts.py b/awx/main/tests/functional/api/test_organization_counts.py index 9c4f536b09eb..d45b1fa08343 100644 --- a/awx/main/tests/functional/api/test_organization_counts.py +++ b/awx/main/tests/functional/api/test_organization_counts.py @@ -2,6 +2,8 @@ from awx.api.versioning import reverse +from awx.main.models import Project, Host + @pytest.fixture def organization_resource_creator(organization, user): @@ -19,21 +21,26 @@ def rf(users, admins, job_templates, projects, inventories, teams): for i in range(inventories): inventory = organization.inventories.create(name="associated-inv %s" % i) for i in range(projects): - organization.projects.create(name="test-proj %s" % i, - description="test-proj-desc") + Project.objects.create( + name="test-proj %s" % i, + description="test-proj-desc", + organization=organization + ) # Mix up the inventories and projects used by the job templates i_proj = 0 i_inv = 0 for i in range(job_templates): - project = organization.projects.all()[i_proj] + project = Project.objects.filter(organization=organization)[i_proj] + # project = organization.projects.all()[i_proj] inventory = organization.inventories.all()[i_inv] project.jobtemplates.create(name="test-jt %s" % i, description="test-job-template-desc", inventory=inventory, - playbook="test_playbook.yml") + playbook="test_playbook.yml", + organization=organization) i_proj += 1 i_inv += 1 - if i_proj >= organization.projects.count(): + if i_proj >= Project.objects.filter(organization=organization).count(): i_proj = 0 if i_inv >= organization.inventories.count(): i_inv = 0 @@ -74,6 +81,8 @@ def test_org_counts_detail_admin(resourced_organization, user, get): assert response.status_code == 200 counts = response.data['summary_fields']['related_field_counts'] + assert counts['hosts'] == 0 + counts.pop('hosts') assert counts == COUNTS_PRIMES @@ -86,6 +95,8 @@ def test_org_counts_detail_member(resourced_organization, user, get): assert response.status_code == 200 counts = response.data['summary_fields']['related_field_counts'] + assert counts['hosts'] == 0 + counts.pop('hosts') assert counts == { 'users': COUNTS_PRIMES['users'], # Policy is that members can see other users and admins 'admins': COUNTS_PRIMES['admins'], @@ -104,6 +115,7 @@ def test_org_counts_list_admin(resourced_organization, user, get): assert response.status_code == 200 counts = response.data['results'][0]['summary_fields']['related_field_counts'] + assert 'hosts' not in counts # doesn't show in list view assert counts == COUNTS_PRIMES @@ -116,6 +128,7 @@ def test_org_counts_list_member(resourced_organization, user, get): assert response.status_code == 200 counts = response.data['results'][0]['summary_fields']['related_field_counts'] + assert 'hosts' not in counts # doesn't show in list view assert counts == { 'users': COUNTS_PRIMES['users'], # Policy is that members can see other users and admins @@ -138,6 +151,7 @@ def test_new_org_zero_counts(user, post): new_org_list = post_response.render().data counts_dict = new_org_list['summary_fields']['related_field_counts'] + assert 'hosts' not in counts_dict # doesn't show in list view assert counts_dict == COUNTS_ZEROS @@ -160,6 +174,19 @@ def test_two_organizations(resourced_organization, organizations, user, get): assert counts[org_id_zero] == COUNTS_ZEROS +@pytest.mark.django_db +def test_hosts_counted(resourced_organization, user, get): + admin_user = user('admin', True) + assert Host.objects.org_active_count(resourced_organization.id) == 0 + resourced_organization.inventories.first().hosts.create(name='Some Host') + assert Host.objects.org_active_count(resourced_organization.id) == 1 + response = get(reverse('api:organization_detail', kwargs={'pk': resourced_organization.pk}), admin_user) + assert response.status_code == 200 + + counts = response.data['summary_fields']['related_field_counts'] + assert counts['hosts'] == Host.objects.org_active_count(resourced_organization.id) == 1 + + @pytest.mark.django_db def test_scan_JT_counted(resourced_organization, user, get): admin_user = user('admin', True) @@ -173,18 +200,23 @@ def test_scan_JT_counted(resourced_organization, user, get): # Test detail view detail_response = get(reverse('api:organization_detail', kwargs={'pk': resourced_organization.pk}), admin_user) assert detail_response.status_code == 200 - assert detail_response.data['summary_fields']['related_field_counts'] == counts_dict + counts = detail_response.data['summary_fields']['related_field_counts'] + assert 'hosts' in counts + counts.pop('hosts') + assert counts == counts_dict @pytest.mark.django_db def test_JT_not_double_counted(resourced_organization, user, get): admin_user = user('admin', True) + proj = Project.objects.filter(organization=resourced_organization).all()[0] # Add a run job template to the org - resourced_organization.projects.all()[0].jobtemplates.create( + proj.jobtemplates.create( job_type='run', inventory=resourced_organization.inventories.all()[0], - project=resourced_organization.projects.all()[0], - name='double-linked-job-template') + project=proj, + name='double-linked-job-template', + organization=resourced_organization) counts_dict = COUNTS_PRIMES counts_dict['job_templates'] += 1 @@ -196,39 +228,7 @@ def test_JT_not_double_counted(resourced_organization, user, get): # Test detail view detail_response = get(reverse('api:organization_detail', kwargs={'pk': resourced_organization.pk}), admin_user) assert detail_response.status_code == 200 - assert detail_response.data['summary_fields']['related_field_counts'] == counts_dict - - -@pytest.mark.django_db -def test_JT_associated_with_project(organizations, project, user, get): - # Check that adding a project to an organization gets the project's JT - # included in the organization's JT count - external_admin = user('admin', True) - two_orgs = organizations(2) - organization = two_orgs[0] - other_org = two_orgs[1] - - unrelated_inv = other_org.inventories.create(name='not-in-organization') - organization.projects.add(project) - project.jobtemplates.create(name="test-jt", - description="test-job-template-desc", - inventory=unrelated_inv, - playbook="test_playbook.yml") - - response = get(reverse('api:organization_list'), external_admin) - assert response.status_code == 200 - - org_id = organization.id - counts = {} - for org_json in response.data['results']: - working_id = org_json['id'] - counts[working_id] = org_json['summary_fields']['related_field_counts'] - - assert counts[org_id] == { - 'users': 0, - 'admins': 0, - 'job_templates': 1, - 'projects': 1, - 'inventories': 0, - 'teams': 0 - } + counts = detail_response.data['summary_fields']['related_field_counts'] + assert 'hosts' in counts + counts.pop('hosts') + assert counts == counts_dict diff --git a/awx/main/tests/functional/api/test_organizations.py b/awx/main/tests/functional/api/test_organizations.py index 082719967631..6c45c0c68197 100644 --- a/awx/main/tests/functional/api/test_organizations.py +++ b/awx/main/tests/functional/api/test_organizations.py @@ -9,7 +9,7 @@ import pytest # AWX -from awx.main.models import ProjectUpdate +from awx.main.models import ProjectUpdate, CredentialType, Credential from awx.api.versioning import reverse @@ -288,3 +288,90 @@ def sort_keys(x): assert resp.data['error'] == u"Resource is being used by running jobs." assert resp_sorted == expect_sorted + + +@pytest.mark.django_db +def test_galaxy_credential_association_forbidden(alice, organization, post): + galaxy = CredentialType.defaults['galaxy_api_token']() + galaxy.save() + + cred = Credential.objects.create( + credential_type=galaxy, + name='Public Galaxy', + organization=organization, + inputs={ + 'url': 'https://galaxy.ansible.com/' + } + ) + url = reverse('api:organization_galaxy_credentials_list', kwargs={'pk': organization.id}) + post( + url, + {'associate': True, 'id': cred.pk}, + user=alice, + expect=403 + ) + + +@pytest.mark.django_db +def test_galaxy_credential_type_enforcement(admin, organization, post): + ssh = CredentialType.defaults['ssh']() + ssh.save() + + cred = Credential.objects.create( + credential_type=ssh, + name='SSH Credential', + organization=organization, + ) + url = reverse('api:organization_galaxy_credentials_list', kwargs={'pk': organization.id}) + resp = post( + url, + {'associate': True, 'id': cred.pk}, + user=admin, + expect=400 + ) + assert resp.data['msg'] == 'Credential must be a Galaxy credential, not Machine.' + + +@pytest.mark.django_db +def test_galaxy_credential_association(alice, admin, organization, post, get): + galaxy = CredentialType.defaults['galaxy_api_token']() + galaxy.save() + + for i in range(5): + cred = Credential.objects.create( + credential_type=galaxy, + name=f'Public Galaxy {i + 1}', + organization=organization, + inputs={ + 'url': 'https://galaxy.ansible.com/' + } + ) + url = reverse('api:organization_galaxy_credentials_list', kwargs={'pk': organization.id}) + post( + url, + {'associate': True, 'id': cred.pk}, + user=admin, + expect=204 + ) + resp = get(url, user=admin) + assert [cred['name'] for cred in resp.data['results']] == [ + 'Public Galaxy 1', + 'Public Galaxy 2', + 'Public Galaxy 3', + 'Public Galaxy 4', + 'Public Galaxy 5', + ] + + post( + url, + {'disassociate': True, 'id': Credential.objects.get(name='Public Galaxy 3').pk}, + user=admin, + expect=204 + ) + resp = get(url, user=admin) + assert [cred['name'] for cred in resp.data['results']] == [ + 'Public Galaxy 1', + 'Public Galaxy 2', + 'Public Galaxy 4', + 'Public Galaxy 5', + ] diff --git a/awx/main/tests/functional/api/test_project.py b/awx/main/tests/functional/api/test_project.py index af463635572a..a31eb0804a41 100644 --- a/awx/main/tests/functional/api/test_project.py +++ b/awx/main/tests/functional/api/test_project.py @@ -54,7 +54,9 @@ def test_no_changing_overwrite_behavior_if_used(post, patch, organization, admin data={ 'name': 'fooo', 'organization': organization.id, - 'allow_override': True + 'allow_override': True, + 'scm_type': 'git', + 'scm_url': 'https://github.com/ansible/test-playbooks.git' }, user=admin_user, expect=201 @@ -83,7 +85,9 @@ def test_changing_overwrite_behavior_okay_if_not_used(post, patch, organization, data={ 'name': 'fooo', 'organization': organization.id, - 'allow_override': True + 'allow_override': True, + 'scm_type': 'git', + 'scm_url': 'https://github.com/ansible/test-playbooks.git' }, user=admin_user, expect=201 @@ -95,3 +99,12 @@ def test_changing_overwrite_behavior_okay_if_not_used(post, patch, organization, expect=200 ) assert Project.objects.get(pk=r1.data['id']).allow_override is False + + +@pytest.mark.django_db +def test_scm_project_local_path_invalid(get, patch, project, admin): + url = reverse('api:project_detail', kwargs={'pk': project.id}) + resp = patch(url, {'local_path': '/foo/bar'}, user=admin, expect=400) + assert resp.data['local_path'] == [ + 'Cannot change local_path for git-based projects' + ] diff --git a/awx/main/tests/functional/api/test_rbac_displays.py b/awx/main/tests/functional/api/test_rbac_displays.py index 4180647d4472..d0a0cb4f9812 100644 --- a/awx/main/tests/functional/api/test_rbac_displays.py +++ b/awx/main/tests/functional/api/test_rbac_displays.py @@ -282,10 +282,6 @@ class MockObj: list_serializer.child.to_representation(project) assert 'capability_map' not in list_serializer.child.context - # Models for which the prefetch is valid for do - list_serializer.child.to_representation(job_template) - assert set(list_serializer.child.context['capability_map'][job_template.id].keys()) == set(('copy', 'edit', 'start')) - @pytest.mark.django_db def test_prefetch_group_capabilities(group, rando): diff --git a/awx/main/tests/functional/api/test_schedules.py b/awx/main/tests/functional/api/test_schedules.py index bdb3534fb309..bdaa6aa4a648 100644 --- a/awx/main/tests/functional/api/test_schedules.py +++ b/awx/main/tests/functional/api/test_schedules.py @@ -1,6 +1,8 @@ +import datetime import pytest from django.utils.encoding import smart_str +from django.utils.timezone import now from awx.api.versioning import reverse from awx.main.models import JobTemplate, Schedule @@ -140,7 +142,6 @@ def test_encrypted_survey_answer(post, patch, admin_user, project, inventory, su ("DTSTART:20030925T104941Z RRULE:FREQ=DAILY;INTERVAL=10;COUNT=500;UNTIL=20040925T104941Z", "RRULE may not contain both COUNT and UNTIL"), # noqa ("DTSTART;TZID=America/New_York:20300308T050000Z RRULE:FREQ=DAILY;INTERVAL=1", "rrule parsing failed validation"), ("DTSTART:20300308T050000 RRULE:FREQ=DAILY;INTERVAL=1", "DTSTART cannot be a naive datetime"), - ("DTSTART:19700101T000000Z RRULE:FREQ=MINUTELY;INTERVAL=1", "more than 1000 events are not allowed"), # noqa ]) def test_invalid_rrules(post, admin_user, project, inventory, rrule, error): job_template = JobTemplate.objects.create( @@ -342,6 +343,40 @@ def test_months_with_31_days(post, admin_user): ] +@pytest.mark.django_db +@pytest.mark.timeout(3) +@pytest.mark.parametrize('freq, delta, total_seconds', ( + ('MINUTELY', 1, 60), + ('MINUTELY', 15, 15 * 60), + ('HOURLY', 1, 3600), + ('HOURLY', 2, 3600 * 2), +)) +def test_really_old_dtstart(post, admin_user, freq, delta, total_seconds): + url = reverse('api:schedule_rrule') + # every , at the :30 second mark + rrule = f'DTSTART;TZID=America/New_York:20051231T000030 RRULE:FREQ={freq};INTERVAL={delta}' + start = now() + next_ten = post(url, {'rrule': rrule}, admin_user, expect=200).data['utc'] + + assert len(next_ten) == 10 + + # the first date is *in the future* + assert next_ten[0] >= start + + # ...but *no more than* into the future + assert now() + datetime.timedelta(**{ + 'minutes' if freq == 'MINUTELY' else 'hours': delta + }) + + # every date in the list is greater than the last + for i, x in enumerate(next_ten): + if i == 0: + continue + assert x.second == 30 + delta = (x - next_ten[i - 1]) + assert delta.total_seconds() == total_seconds + + def test_dst_rollback_duplicates(post, admin_user): # From Nov 2 -> Nov 3, 2030, daylight savings ends and we "roll back" an hour. # Make sure we don't "double count" duplicate times in the "rolled back" @@ -365,3 +400,77 @@ def test_zoneinfo(get, admin_user): url = reverse('api:schedule_zoneinfo') r = get(url, admin_user, expect=200) assert {'name': 'America/New_York'} in r.data + + +@pytest.mark.django_db +def test_normal_user_can_create_jt_schedule(options, post, project, inventory, alice): + jt = JobTemplate.objects.create( + name='test-jt', + project=project, + playbook='helloworld.yml', + inventory=inventory + ) + jt.save() + url = reverse('api:schedule_list') + + # can't create a schedule on the JT because we don't have execute rights + params = { + 'name': 'My Example Schedule', + 'rrule': RRULE_EXAMPLE, + 'unified_job_template': jt.id, + } + assert 'POST' not in options(url, user=alice).data['actions'].keys() + post(url, params, alice, expect=403) + + # now we can, because we're allowed to execute the JT + jt.execute_role.members.add(alice) + assert 'POST' in options(url, user=alice).data['actions'].keys() + post(url, params, alice, expect=201) + + +@pytest.mark.django_db +def test_normal_user_can_create_project_schedule(options, post, project, alice): + url = reverse('api:schedule_list') + + # can't create a schedule on the project because we don't have update rights + params = { + 'name': 'My Example Schedule', + 'rrule': RRULE_EXAMPLE, + 'unified_job_template': project.id, + } + assert 'POST' not in options(url, user=alice).data['actions'].keys() + post(url, params, alice, expect=403) + + # use role does *not* grant the ability to schedule + project.use_role.members.add(alice) + assert 'POST' not in options(url, user=alice).data['actions'].keys() + post(url, params, alice, expect=403) + + # now we can, because we're allowed to update project + project.update_role.members.add(alice) + assert 'POST' in options(url, user=alice).data['actions'].keys() + post(url, params, alice, expect=201) + + +@pytest.mark.django_db +def test_normal_user_can_create_inventory_update_schedule(options, post, inventory_source, alice): + url = reverse('api:schedule_list') + + # can't create a schedule on the project because we don't have update rights + params = { + 'name': 'My Example Schedule', + 'rrule': RRULE_EXAMPLE, + 'unified_job_template': inventory_source.id, + } + assert 'POST' not in options(url, user=alice).data['actions'].keys() + post(url, params, alice, expect=403) + + # use role does *not* grant the ability to schedule + inventory_source.inventory.use_role.members.add(alice) + assert 'POST' not in options(url, user=alice).data['actions'].keys() + post(url, params, alice, expect=403) + + # now we can, because we're allowed to update project + inventory_source.inventory.update_role.members.add(alice) + assert 'POST' in options(url, user=alice).data['actions'].keys() + post(url, params, alice, expect=201) diff --git a/awx/main/tests/functional/api/test_settings.py b/awx/main/tests/functional/api/test_settings.py index a88aa8c20b9e..8a1f50035fab 100644 --- a/awx/main/tests/functional/api/test_settings.py +++ b/awx/main/tests/functional/api/test_settings.py @@ -4,19 +4,13 @@ # Python import pytest -import os -import time from django.conf import settings -from kombu.utils.url import parse_url - -# Mock -from unittest import mock # AWX from awx.api.versioning import reverse from awx.conf.models import Setting -from awx.main.utils.handlers import AWXProxyHandler, LoggingConnectivityException +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 @@ -24,15 +18,6 @@ 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 -@pytest.fixture -def mock_no_license_file(mocker): - ''' - Ensures that tests don't pick up dev container license file - ''' - os.environ['AWX_LICENSE_FILE'] = '/does_not_exist' - return None - - @pytest.mark.django_db def test_url_base_defaults_to_request(options, admin): # If TOWER_URL_BASE is not set, default to the Tower request hostname @@ -238,73 +223,95 @@ def test_ui_settings(get, put, patch, delete, admin): @pytest.mark.django_db -def test_logging_aggregrator_connection_test_requires_superuser(get, post, alice): +def test_logging_aggregator_connection_test_requires_superuser(post, alice): url = reverse('api:setting_logging_test') post(url, {}, user=alice, expect=403) -@pytest.mark.parametrize('key', [ - 'LOG_AGGREGATOR_TYPE', - 'LOG_AGGREGATOR_HOST', -]) @pytest.mark.django_db -def test_logging_aggregrator_connection_test_bad_request(get, post, admin, key): +def test_logging_aggregator_connection_test_not_enabled(post, admin): url = reverse('api:setting_logging_test') - resp = post(url, {}, user=admin, expect=400) - assert 'This field is required.' in resp.data.get(key, []) + resp = post(url, {}, user=admin, expect=409) + assert 'Logging not enabled' in resp.data.get('error') + +def _mock_logging_defaults(): + # Pre-populate settings obj with defaults + class MockSettings: + pass + mock_settings_obj = MockSettings() + mock_settings_json = dict() + for key in settings_registry.get_registered_settings(category_slug='logging'): + value = settings_registry.get_setting_field(key).get_default() + setattr(mock_settings_obj, key, value) + mock_settings_json[key] = value + setattr(mock_settings_obj, 'MAX_EVENT_RES_DATA', 700000) + return mock_settings_obj, mock_settings_json + + +@pytest.mark.parametrize('key, value, error', [ + ['LOG_AGGREGATOR_TYPE', 'logstash', 'Cannot enable log aggregator without providing host.'], + ['LOG_AGGREGATOR_HOST', 'https://logstash', 'Cannot enable log aggregator without providing type.'] +]) @pytest.mark.django_db -def test_logging_aggregrator_connection_test_valid(mocker, get, post, admin): - with mock.patch.object(AWXProxyHandler, 'perform_test') as perform_test: - url = reverse('api:setting_logging_test') - user_data = { - 'LOG_AGGREGATOR_TYPE': 'logstash', - 'LOG_AGGREGATOR_HOST': 'localhost', - 'LOG_AGGREGATOR_PORT': 8080, - 'LOG_AGGREGATOR_USERNAME': 'logger', - 'LOG_AGGREGATOR_PASSWORD': 'mcstash' - } - post(url, user_data, user=admin, expect=200) - args, kwargs = perform_test.call_args_list[0] - create_settings = kwargs['custom_settings'] - for k, v in user_data.items(): - assert hasattr(create_settings, k) - assert getattr(create_settings, k) == v +def test_logging_aggregator_missing_settings(put, post, admin, key, value, error): + _, mock_settings = _mock_logging_defaults() + mock_settings['LOG_AGGREGATOR_ENABLED'] = True + mock_settings[key] = value + url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'logging'}) + response = put(url, data=mock_settings, user=admin, expect=400) + assert error in str(response.data) +@pytest.mark.parametrize('type, host, port, username, password', [ + ['logstash', 'localhost', 8080, 'logger', 'mcstash'], + ['loggly', 'http://logs-01.loggly.com/inputs/1fd38090-hash-h4a$h-8d80-t0k3n71/tag/http/', None, None, None], + ['splunk', 'https://yoursplunk:8088/services/collector/event', None, None, None], + ['other', '97.221.40.41', 9000, 'logger', 'mcstash'], + ['sumologic', 'https://endpoint5.collection.us2.sumologic.com/receiver/v1/http/Zagnw_f9XGr_zZgd-_EPM0hb8_rUU7_RU8Q==', + None, None, None] +]) @pytest.mark.django_db -def test_logging_aggregrator_connection_test_with_masked_password(mocker, patch, post, admin): +def test_logging_aggregator_valid_settings(put, post, admin, type, host, port, username, password): + _, mock_settings = _mock_logging_defaults() + # type = 'splunk' + # host = 'https://yoursplunk:8088/services/collector/event' + mock_settings['LOG_AGGREGATOR_ENABLED'] = True + mock_settings['LOG_AGGREGATOR_TYPE'] = type + mock_settings['LOG_AGGREGATOR_HOST'] = host + if port: + mock_settings['LOG_AGGREGATOR_PORT'] = port + if username: + mock_settings['LOG_AGGREGATOR_USERNAME'] = username + if password: + mock_settings['LOG_AGGREGATOR_PASSWORD'] = password url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'logging'}) - patch(url, user=admin, data={'LOG_AGGREGATOR_PASSWORD': 'password123'}, expect=200) - time.sleep(1) # log settings are cached slightly - - with mock.patch.object(AWXProxyHandler, 'perform_test') as perform_test: - url = reverse('api:setting_logging_test') - user_data = { - 'LOG_AGGREGATOR_TYPE': 'logstash', - 'LOG_AGGREGATOR_HOST': 'localhost', - 'LOG_AGGREGATOR_PORT': 8080, - 'LOG_AGGREGATOR_USERNAME': 'logger', - 'LOG_AGGREGATOR_PASSWORD': '$encrypted$' - } - post(url, user_data, user=admin, expect=200) - args, kwargs = perform_test.call_args_list[0] - create_settings = kwargs['custom_settings'] - assert getattr(create_settings, 'LOG_AGGREGATOR_PASSWORD') == 'password123' + response = put(url, data=mock_settings, user=admin, expect=200) + assert type in response.data.get('LOG_AGGREGATOR_TYPE') + assert host in response.data.get('LOG_AGGREGATOR_HOST') + if port: + assert port == response.data.get('LOG_AGGREGATOR_PORT') + if username: + assert username in response.data.get('LOG_AGGREGATOR_USERNAME') + if password: # Note: password should be encrypted + assert '$encrypted$' in response.data.get('LOG_AGGREGATOR_PASSWORD') @pytest.mark.django_db -def test_logging_aggregrator_connection_test_invalid(mocker, get, post, admin): - with mock.patch.object(AWXProxyHandler, 'perform_test') as perform_test: - perform_test.side_effect = LoggingConnectivityException('404: Not Found') - url = reverse('api:setting_logging_test') - resp = post(url, { - 'LOG_AGGREGATOR_TYPE': 'logstash', - 'LOG_AGGREGATOR_HOST': 'localhost', - 'LOG_AGGREGATOR_PORT': 8080 - }, user=admin, expect=500) - assert resp.data == {'error': '404: Not Found'} +def test_logging_aggregator_connection_test_valid(put, post, admin): + _, mock_settings = _mock_logging_defaults() + type = 'other' + host = 'https://localhost' + mock_settings['LOG_AGGREGATOR_ENABLED'] = True + mock_settings['LOG_AGGREGATOR_TYPE'] = type + mock_settings['LOG_AGGREGATOR_HOST'] = host + # POST to save these mock settings + url = reverse('api:setting_singleton_detail', kwargs={'category_slug': 'logging'}) + put(url, data=mock_settings, user=admin, expect=200) + # "Test" the logger + url = reverse('api:setting_logging_test') + post(url, {}, user=admin, expect=202) @pytest.mark.django_db @@ -386,15 +393,3 @@ def test_saml_x509cert_validation(patch, get, admin, headers): } }) assert resp.status_code == 200 - - -@pytest.mark.django_db -def test_broker_url_with_special_characters(): - settings.BROKER_URL = 'amqp://guest:a@ns:ibl3#@rabbitmq:5672//' - url = parse_url(settings.BROKER_URL) - assert url['transport'] == 'amqp' - assert url['hostname'] == 'rabbitmq' - assert url['port'] == 5672 - assert url['userid'] == 'guest' - assert url['password'] == 'a@ns:ibl3#' - assert url['virtual_host'] == '/' 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 faae3cce3c5b..3aa6c4024eda 100644 --- a/awx/main/tests/functional/api/test_unified_job_template.py +++ b/awx/main/tests/functional/api/test_unified_job_template.py @@ -1,6 +1,7 @@ import pytest from awx.api.versioning import reverse +from awx.main import models @pytest.mark.django_db @@ -9,3 +10,76 @@ def test_aliased_forward_reverse_field_searches(instance, options, get, admin): response = options(url, None, admin) assert 'job_template__search' in response.data['related_search_fields'] get(reverse("api:unified_job_template_list") + "?job_template__search=anything", user=admin, expect=200) + + +@pytest.mark.django_db +@pytest.mark.parametrize('model', ( + 'Project', + 'JobTemplate', + 'WorkflowJobTemplate' +)) +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'] + ) + if orm_style: + data['project_id'] = proj.id + else: + data['project'] = proj.id + data['playbook'] = 'helloworld.yml' + data['ask_inventory_on_launch'] = True + return data + + def test_organization_blank_on_edit_of_orphan(self, model, admin_user, patch): + cls = getattr(models, model) + data = self.data_for_model(model, orm_style=True) + obj = cls.objects.create(**data) + patch( + url=obj.get_absolute_url(), + data={'name': 'foooooo'}, + user=admin_user, + expect=200 + ) + obj.refresh_from_db() + assert obj.name == 'foooooo' + + def test_organization_blank_on_edit_of_orphan_as_nonsuperuser(self, model, rando, patch): + """Test case reflects historical bug where ordinary users got weird error + message when editing an orphaned project + """ + cls = getattr(models, model) + data = self.data_for_model(model, orm_style=True) + obj = cls.objects.create(**data) + if model == 'JobTemplate': + obj.project.admin_role.members.add(rando) + obj.admin_role.members.add(rando) + patch( + url=obj.get_absolute_url(), + data={'name': 'foooooo'}, + user=rando, + expect=200 + ) + obj.refresh_from_db() + assert obj.name == 'foooooo' + + def test_organization_blank_on_edit_of_normal(self, model, admin_user, patch, organization): + cls = getattr(models, model) + data = self.data_for_model(model, orm_style=True) + data['organization'] = organization + obj = cls.objects.create(**data) + patch( + url=obj.get_absolute_url(), + data={'name': 'foooooo'}, + user=admin_user, + expect=200 + ) + obj.refresh_from_db() + assert obj.name == 'foooooo' 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 64a71c91d3cf..e228f502a663 100644 --- a/awx/main/tests/functional/api/test_unified_jobs_stdout.py +++ b/awx/main/tests/functional/api/test_unified_jobs_stdout.py @@ -23,9 +23,9 @@ def _mk_project_update(): def _mk_inventory_update(): - source = InventorySource() + source = InventorySource(source='ec2') source.save() - iu = InventoryUpdate(inventory_source=source) + iu = InventoryUpdate(inventory_source=source, source='e2') return iu 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 711d8fedf4cb..554a0cfc63dd 100644 --- a/awx/main/tests/functional/api/test_unified_jobs_view.py +++ b/awx/main/tests/functional/api/test_unified_jobs_view.py @@ -123,7 +123,11 @@ def test_delete_project_update_in_active_state(project, delete, admin, status): @pytest.mark.parametrize("status", list(TEST_STATES)) @pytest.mark.django_db def test_delete_inventory_update_in_active_state(inventory_source, delete, admin, status): - i = InventoryUpdate.objects.create(inventory_source=inventory_source, status=status) + i = InventoryUpdate.objects.create( + inventory_source=inventory_source, + status=status, + source=inventory_source.source + ) url = reverse('api:inventory_update_detail', kwargs={'pk': i.pk}) delete(url, None, admin, expect=403) diff --git a/awx/main/tests/functional/api/test_user.py b/awx/main/tests/functional/api/test_user.py index f0a4ffea8466..821b37d6aef5 100644 --- a/awx/main/tests/functional/api/test_user.py +++ b/awx/main/tests/functional/api/test_user.py @@ -1,7 +1,10 @@ +from datetime import date + import pytest from django.contrib.sessions.middleware import SessionMiddleware +from awx.main.models import User from awx.api.versioning import reverse @@ -48,3 +51,33 @@ def test_create_delete_create_user(post, delete, admin): response = post(reverse('api:user_list'), EXAMPLE_USER_DATA, admin, middleware=SessionMiddleware()) print(response.data) assert response.status_code == 201 + + +@pytest.mark.django_db +def test_user_cannot_update_last_login(patch, admin): + assert admin.last_login is None + patch( + reverse('api:user_detail', kwargs={'pk': admin.pk}), + {'last_login': '2020-03-13T16:39:47.303016Z'}, + admin, + middleware=SessionMiddleware() + ) + assert User.objects.get(pk=admin.pk).last_login is None + + +@pytest.mark.django_db +def test_user_verify_attribute_created(admin, get): + assert admin.created == admin.date_joined + resp = get( + reverse('api:user_detail', kwargs={'pk': admin.pk}), + admin + ) + assert resp.data['created'] == admin.date_joined + + past = date(2020, 1, 1).isoformat() + for op, count in (('gt', 1), ('lt', 0)): + resp = get( + reverse('api:user_list') + f'?created__{op}={past}', + admin + ) + assert resp.data['count'] == count diff --git a/awx/main/tests/functional/api/test_workflow_node.py b/awx/main/tests/functional/api/test_workflow_node.py index 64c22898df9f..6253548d601e 100644 --- a/awx/main/tests/functional/api/test_workflow_node.py +++ b/awx/main/tests/functional/api/test_workflow_node.py @@ -71,13 +71,25 @@ def test_node_accepts_prompted_fields(inventory, project, workflow_job_template, user=admin_user, expect=201) +@pytest.mark.django_db +@pytest.mark.parametrize("field_name, field_value", [ + ('all_parents_must_converge', True), + ('all_parents_must_converge', False), +]) +def test_create_node_with_field(field_name, field_value, workflow_job_template, post, admin_user): + url = reverse('api:workflow_job_template_workflow_nodes_list', + kwargs={'pk': workflow_job_template.pk}) + res = post(url, {field_name: field_value}, user=admin_user, expect=201) + assert res.data[field_name] == field_value + + @pytest.mark.django_db class TestApprovalNodes(): def test_approval_node_creation(self, post, approval_node, admin_user): url = reverse('api:workflow_job_template_node_create_approval', kwargs={'pk': approval_node.pk, 'version': 'v2'}) post(url, {'name': 'Test', 'description': 'Approval Node', 'timeout': 0}, - user=admin_user, expect=200) + user=admin_user, expect=201) approval_node = WorkflowJobTemplateNode.objects.get(pk=approval_node.pk) assert isinstance(approval_node.unified_job_template, WorkflowApprovalTemplate) @@ -96,9 +108,9 @@ def test_approval_node_creation_failure(self, post, approval_node, admin_user): assert {'name': ['This field may not be blank.']} == json.loads(r.content) @pytest.mark.parametrize("is_admin, is_org_admin, status", [ - [True, False, 200], # if they're a WFJT admin, they get a 200 + [True, False, 201], # if they're a WFJT admin, they get a 201 [False, False, 403], # if they're not a WFJT *nor* org admin, they get a 403 - [False, True, 200], # if they're an organization admin, they get a 200 + [False, True, 201], # if they're an organization admin, they get a 201 ]) def test_approval_node_creation_rbac(self, post, approval_node, alice, is_admin, is_org_admin, status): url = reverse('api:workflow_job_template_node_create_approval', @@ -153,7 +165,7 @@ def test_approval_node_approve(self, post, admin_user, job_template): url = reverse('api:workflow_job_template_node_create_approval', kwargs={'pk': node.pk, 'version': 'v2'}) post(url, {'name': 'Approve Test', 'description': '', 'timeout': 0}, - user=admin_user, expect=200) + user=admin_user, expect=201) post(reverse('api:workflow_job_template_launch', kwargs={'pk': wfjt.pk}), user=admin_user, expect=201) wf_job = WorkflowJob.objects.first() @@ -183,7 +195,7 @@ def test_approval_node_deny(self, post, admin_user, job_template): url = reverse('api:workflow_job_template_node_create_approval', kwargs={'pk': node.pk, 'version': 'v2'}) post(url, {'name': 'Deny Test', 'description': '', 'timeout': 0}, - user=admin_user, expect=200) + user=admin_user, expect=201) post(reverse('api:workflow_job_template_launch', kwargs={'pk': wfjt.pk}), user=admin_user, expect=201) wf_job = WorkflowJob.objects.first() diff --git a/awx/main/tests/functional/commands/test_cleanup_jobs.py b/awx/main/tests/functional/commands/test_cleanup_jobs.py new file mode 100644 index 000000000000..98be403d1fc3 --- /dev/null +++ b/awx/main/tests/functional/commands/test_cleanup_jobs.py @@ -0,0 +1,179 @@ +import pytest +from datetime import datetime, timedelta +from pytz import timezone +from collections import OrderedDict + +from django.db.models.deletion import Collector, SET_NULL, CASCADE +from django.core.management import call_command + +from awx.main.utils.deletion import AWXCollector +from awx.main.models import ( + JobTemplate, User, Job, JobEvent, Notification, + WorkflowJobNode, JobHostSummary +) + + +@pytest.fixture +def setup_environment(inventory, project, machine_credential, host, notification_template, label): + ''' + Create old jobs and new jobs, with various other objects to hit the + related fields of Jobs. This makes sure on_delete() effects are tested + properly. + ''' + old_jobs = [] + new_jobs = [] + days = 10 + days_str = str(days) + + jt = JobTemplate.objects.create(name='testjt', inventory=inventory, project=project) + jt.credentials.add(machine_credential) + jt_user = User.objects.create(username='jobtemplateuser') + jt.execute_role.members.add(jt_user) + + notification = Notification() + notification.notification_template = notification_template + notification.save() + + for i in range(3): + job1 = jt.create_job() + job1.created =datetime.now(tz=timezone('UTC')) + job1.save() + # create jobs with current time + JobEvent.create_from_data(job_id=job1.pk, uuid='abc123', event='runner_on_start', + stdout='a' * 1025).save() + new_jobs.append(job1) + + job2 = jt.create_job() + # create jobs 10 days ago + job2.created = datetime.now(tz=timezone('UTC')) - timedelta(days=days) + job2.save() + job2.dependent_jobs.add(job1) + JobEvent.create_from_data(job_id=job2.pk, uuid='abc123', event='runner_on_start', + stdout='a' * 1025).save() + old_jobs.append(job2) + + jt.last_job = job2 + jt.current_job = job2 + jt.save() + host.last_job = job2 + host.save() + notification.unifiedjob_notifications.add(job2) + label.unifiedjob_labels.add(job2) + jn = WorkflowJobNode.objects.create(job=job2) + jn.save() + jh = JobHostSummary.objects.create(job=job2) + jh.save() + + return (old_jobs, new_jobs, days_str) + + +@pytest.mark.django_db +def test_cleanup_jobs(setup_environment): + (old_jobs, new_jobs, days_str) = setup_environment + + # related_fields + related = [f for f in Job._meta.get_fields(include_hidden=True) + if f.auto_created and not + f.concrete and + (f.one_to_one or f.one_to_many)] + + job = old_jobs[-1] # last job + + # gather related objects for job + related_should_be_removed = {} + related_should_be_null = {} + for r in related: + qs = r.related_model._base_manager.using('default').filter( + **{"%s__in" % r.field.name: [job.pk]} + ) + if qs.exists(): + if r.field.remote_field.on_delete == CASCADE: + related_should_be_removed[qs.model] = set(qs.values_list('pk', flat=True)) + if r.field.remote_field.on_delete == SET_NULL: + related_should_be_null[(qs.model,r.field.name)] = set(qs.values_list('pk', flat=True)) + + assert related_should_be_removed + assert related_should_be_null + + call_command('cleanup_jobs', '--days', days_str) + # make sure old jobs are removed + assert not Job.objects.filter(pk__in=[obj.pk for obj in old_jobs]).exists() + + # make sure new jobs are untouched + assert len(new_jobs) == Job.objects.filter(pk__in=[obj.pk for obj in new_jobs]).count() + + # make sure related objects are destroyed or set to NULL (none) + for model, values in related_should_be_removed.items(): + assert not model.objects.filter(pk__in=values).exists() + + for (model,fieldname), values in related_should_be_null.items(): + for v in values: + assert not getattr(model.objects.get(pk=v), fieldname) + + +@pytest.mark.django_db +def test_awxcollector(setup_environment): + ''' + Efforts to improve the performance of cleanup_jobs involved + sub-classing the django Collector class. This unit test will + check for parity between the django Collector and the modified + AWXCollector class. AWXCollector is used in cleanup_jobs to + bulk-delete old jobs from the database. + + Specifically, Collector has four dictionaries to check: + .dependencies, .data, .fast_deletes, and .field_updates + + These tests will convert each dictionary from AWXCollector + (after running .collect on jobs), from querysets to sets of + objects. The final result should be a dictionary that is + equivalent to django's Collector. + ''' + + (old_jobs, new_jobs, days_str) = setup_environment + collector = Collector('default') + collector.collect(old_jobs) + + awx_col = AWXCollector('default') + # awx_col accepts a queryset as input + awx_col.collect(Job.objects.filter(pk__in=[obj.pk for obj in old_jobs])) + + # check that dependencies are the same + assert awx_col.dependencies == collector.dependencies + + # check that objects to delete are the same + awx_del_dict = OrderedDict() + for model, instances in awx_col.data.items(): + awx_del_dict.setdefault(model, set()) + for inst in instances: + # .update() will put each object in a queryset into the set + awx_del_dict[model].update(inst) + assert awx_del_dict == collector.data + + # check that field updates are the same + awx_del_dict = OrderedDict() + for model, instances_for_fieldvalues in awx_col.field_updates.items(): + awx_del_dict.setdefault(model, {}) + for (field, value), instances in instances_for_fieldvalues.items(): + awx_del_dict[model].setdefault((field,value), set()) + for inst in instances: + awx_del_dict[model][(field,value)].update(inst) + + # collector field updates don't use the base (polymorphic parent) model, e.g. + # it will use JobTemplate instead of UnifiedJobTemplate. Therefore, + # we need to rebuild the dictionary and grab the model from the field + collector_del_dict = OrderedDict() + for model, instances_for_fieldvalues in collector.field_updates.items(): + for (field,value), instances in instances_for_fieldvalues.items(): + collector_del_dict.setdefault(field.model, {}) + collector_del_dict[field.model][(field, value)] = collector.field_updates[model][(field,value)] + assert awx_del_dict == collector_del_dict + + # check that fast deletes are the same + collector_fast_deletes = set() + for q in collector.fast_deletes: + collector_fast_deletes.update(q) + + awx_col_fast_deletes = set() + for q in awx_col.fast_deletes: + awx_col_fast_deletes.update(q) + assert collector_fast_deletes == awx_col_fast_deletes diff --git a/awx/main/tests/functional/commands/test_inventory_import.py b/awx/main/tests/functional/commands/test_inventory_import.py index 630eca7b05a2..0500ef197c6e 100644 --- a/awx/main/tests/functional/commands/test_inventory_import.py +++ b/awx/main/tests/functional/commands/test_inventory_import.py @@ -9,6 +9,9 @@ # Django from django.core.management.base import CommandError +# for license errors +from rest_framework.exceptions import PermissionDenied + # AWX from awx.main.management.commands import inventory_import from awx.main.models import Inventory, Host, Group, InventorySource @@ -83,7 +86,7 @@ def load(self): return self._data -def mock_logging(self): +def mock_logging(self, level): pass @@ -228,7 +231,7 @@ def test_memberships_are_respected(self, inventory): assert inventory.hosts.count() == 1 # baseline worked inv_src2 = inventory.inventory_sources.create( - name='bar', overwrite=True + name='bar', overwrite=True, source='ec2' ) os.environ['INVENTORY_SOURCE_ID'] = str(inv_src2.pk) os.environ['INVENTORY_UPDATE_ID'] = str(inv_src2.create_unified_job().pk) @@ -322,6 +325,6 @@ def test_tower_version_compare(): "version": "2.0.1-1068-g09684e2c41" } } - with pytest.raises(CommandError): + with pytest.raises(PermissionDenied): cmd.remote_tower_license_compare('very_supported') cmd.remote_tower_license_compare('open') 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 dffaacb866f8..d27b4329cdf8 100644 --- a/awx/main/tests/functional/commands/test_secret_key_regeneration.py +++ b/awx/main/tests/functional/commands/test_secret_key_regeneration.py @@ -65,6 +65,7 @@ def test_encrypted_notification_secrets(self, notification_template_with_encrypt assert nc['token'].startswith(PREFIX) Slack = nt.CLASS_FOR_NOTIFICATION_TYPE[nt.notification_type] + class TestBackend(Slack): def __init__(self, *args, **kw): diff --git a/awx/main/tests/functional/conftest.py b/awx/main/tests/functional/conftest.py index 5b545d8e912b..71119500036e 100644 --- a/awx/main/tests/functional/conftest.py +++ b/awx/main/tests/functional/conftest.py @@ -125,9 +125,9 @@ def __get__(self, obj, obj_type): @pytest.fixture def run_computed_fields_right_away(request): - def run_me(inventory_id, should_update_hosts=True): + def run_me(inventory_id): i = Inventory.objects.get(id=inventory_id) - i.update_computed_fields(update_hosts=should_update_hosts) + i.update_computed_fields() mocked = mock.patch( 'awx.main.signals.update_inventory_computed_fields.delay', @@ -145,7 +145,6 @@ def project(instance, organization): description="test-proj-desc", organization=organization, playbook_files=['helloworld.yml', 'alt-helloworld.yml'], - local_path='_92__test_proj', scm_revision='1234567890123456789012345678901234567890', scm_url='localhost', scm_type='git' @@ -180,8 +179,8 @@ def factory(name): @pytest.fixture -def job_factory(job_template, admin): - def factory(job_template=job_template, initial_state='new', created_by=admin): +def job_factory(jt_linked, admin): + def factory(job_template=jt_linked, initial_state='new', created_by=admin): return job_template.create_unified_job(_eager_fields={ 'status': initial_state, 'created_by': created_by}) return factory @@ -568,7 +567,10 @@ def invsrc(name, source=None, inventory=None): @pytest.fixture def inventory_update(inventory_source): - return InventoryUpdate.objects.create(inventory_source=inventory_source) + return InventoryUpdate.objects.create( + inventory_source=inventory_source, + source=inventory_source.source + ) @pytest.fixture @@ -701,11 +703,8 @@ def factory(inventory=inventory, credential=machine_credential, initial_state='n @pytest.fixture -def job_template(organization): - jt = JobTemplate(name='test-job_template') - jt.save() - - return jt +def job_template(): + return JobTemplate.objects.create(name='test-job_template') @pytest.fixture @@ -717,20 +716,16 @@ def job_template_labels(organization, job_template): @pytest.fixture -def jt_linked(job_template_factory, credential, net_credential, vault_credential): +def jt_linked(organization, project, inventory, machine_credential, credential, net_credential, vault_credential): ''' A job template with a reasonably complete set of related objects to test RBAC and other functionality affected by related objects ''' - objects = job_template_factory( - 'testJT', organization='org1', project='proj1', inventory='inventory1', - credential='cred1') - jt = objects.job_template - jt.credentials.add(vault_credential) - jt.save() - # Add AWS cloud credential and network credential - jt.credentials.add(credential) - jt.credentials.add(net_credential) + jt = JobTemplate.objects.create( + project=project, inventory=inventory, playbook='helloworld.yml', + organization=organization + ) + jt.credentials.add(machine_credential, vault_credential, credential, net_credential) return jt diff --git a/awx/main/tests/functional/core/test_licenses.py b/awx/main/tests/functional/core/test_licenses.py deleted file mode 100644 index f59318502ccd..000000000000 --- a/awx/main/tests/functional/core/test_licenses.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2015 Ansible, Inc. -# All Rights Reserved. - -from awx.main.utils.common import StubLicense - - -def test_stub_license(): - license_actual = StubLicense().validate() - assert license_actual['license_key'] == 'OPEN' - assert license_actual['valid_key'] - assert license_actual['compliant'] - assert license_actual['license_type'] == 'open' - diff --git a/awx/main/tests/functional/models/test_activity_stream.py b/awx/main/tests/functional/models/test_activity_stream.py index 0529b5377b40..f2206417598f 100644 --- a/awx/main/tests/functional/models/test_activity_stream.py +++ b/awx/main/tests/functional/models/test_activity_stream.py @@ -12,6 +12,7 @@ CredentialType, Inventory, InventorySource, + Project, User ) @@ -99,8 +100,8 @@ def test_non_implicit_associations_are_recorded(self, project): ).count() == 1, 'In loop %s' % i def test_model_associations_are_recorded(self, organization): - proj1 = organization.projects.create(name='proj1') - proj2 = organization.projects.create(name='proj2') + proj1 = Project.objects.create(name='proj1', organization=organization) + proj2 = Project.objects.create(name='proj2', organization=organization) proj2.use_role.parents.add(proj1.admin_role) assert ActivityStream.objects.filter(role=proj1.admin_role, project=proj2).count() == 1 diff --git a/awx/main/tests/functional/models/test_context_managers.py b/awx/main/tests/functional/models/test_context_managers.py index 61aad54ad4e1..0e1fe024f21c 100644 --- a/awx/main/tests/functional/models/test_context_managers.py +++ b/awx/main/tests/functional/models/test_context_managers.py @@ -11,6 +11,7 @@ # AWX models from awx.main.models.organization import Organization from awx.main.models import ActivityStream, Job +from awx.main.tests.functional import immediate_on_commit @pytest.mark.django_db @@ -34,9 +35,10 @@ class TestComputedFields: def test_computed_fields_normal_use(self, mocker, inventory): job = Job.objects.create(name='fake-job', inventory=inventory) - with mocker.patch.object(update_inventory_computed_fields, 'delay'): - job.delete() - update_inventory_computed_fields.delay.assert_called_once_with(inventory.id, True) + 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) def test_disable_computed_fields(self, mocker, inventory): job = Job.objects.create(name='fake-job', inventory=inventory) diff --git a/awx/main/tests/functional/models/test_events.py b/awx/main/tests/functional/models/test_events.py index a61c3fda2550..943bd34654d9 100644 --- a/awx/main/tests/functional/models/test_events.py +++ b/awx/main/tests/functional/models/test_events.py @@ -1,7 +1,9 @@ from unittest import mock import pytest -from awx.main.models import Job, JobEvent +from django.utils.timezone import now + +from awx.main.models import Job, JobEvent, Inventory, Host, JobHostSummary @pytest.mark.django_db @@ -61,3 +63,148 @@ def test_parent_failed(emit, 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'] + + +@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, + 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 diff --git a/awx/main/tests/functional/models/test_inventory.py b/awx/main/tests/functional/models/test_inventory.py index 7c272ed6253b..04b92d5a1dba 100644 --- a/awx/main/tests/functional/models/test_inventory.py +++ b/awx/main/tests/functional/models/test_inventory.py @@ -2,7 +2,6 @@ import pytest from unittest import mock -import json from django.core.exceptions import ValidationError @@ -17,7 +16,6 @@ Job ) from awx.main.constants import CLOUD_PROVIDERS -from awx.main.models.inventory import PluginFileInjector from awx.main.utils.filters import SmartFilter @@ -170,7 +168,8 @@ def test_source_location(self, scm_inventory_source): inventory_update = InventoryUpdate( inventory_source=scm_inventory_source, source_path=scm_inventory_source.source_path) - assert inventory_update.get_actual_source_path().endswith('_92__test_proj/inventory_file') + p = scm_inventory_source.source_project + assert inventory_update.get_actual_source_path().endswith(f'_{p.id}__test_proj/inventory_file') def test_no_unwanted_updates(self, scm_inventory_source): # Changing the non-sensitive fields should not trigger update @@ -197,9 +196,10 @@ def test_related_group_jobs(self, group): assert job.id in [jerb.id for jerb in group._get_related_jobs()] def test_related_group_update(self, group): - src = group.inventory_sources.create(name='foo') + src = group.inventory_sources.create(name='foo', source='ec2') job = InventoryUpdate.objects.create( - inventory_source=src + inventory_source=src, + source=src.source ) assert job.id in [jerb.id for jerb in group._get_related_jobs()] @@ -226,13 +226,6 @@ def test_clean_update_on_project_update_multiple(self, inventory): @pytest.mark.django_db class TestInventorySourceInjectors: - def test_should_use_plugin(self): - class foo(PluginFileInjector): - plugin_name = 'foo_compute' - initial_version = '2.7.8' - assert not foo('2.7.7').should_use_plugin() - assert foo('2.8').should_use_plugin() - def test_extra_credentials(self, project, credential): inventory_source = InventorySource.objects.create( name='foo', source='custom', source_project=project @@ -262,45 +255,22 @@ def test_plugin_filenames(self, source, filename): are named correctly, because Ansible will reject files that do not have these exact names """ - injector = InventorySource.injectors[source]('2.7.7') + injector = InventorySource.injectors[source]() assert injector.filename == filename - @pytest.mark.parametrize('source,script_name', [ - ('ec2', 'ec2.py'), - ('rhv', 'ovirt4.py'), - ('satellite6', 'foreman.py'), - ('openstack', 'openstack_inventory.py') - ], ids=['ec2', 'rhv', 'satellite6', 'openstack']) - def test_script_filenames(self, source, script_name): - """Ansible has several exceptions in naming of scripts - """ - injector = InventorySource.injectors[source]('2.7.7') - assert injector.script_name == script_name - - def test_group_by_azure(self): - injector = InventorySource.injectors['azure_rm']('2.9') - inv_src = InventorySource( - name='azure source', source='azure_rm', - source_vars={'group_by_os_family': True} - ) - group_by_on = injector.inventory_as_dict(inv_src, '/tmp/foo') - # suspicious, yes, that is just what the script did - expected_groups = 6 - assert len(group_by_on['keyed_groups']) == expected_groups - inv_src.source_vars = json.dumps({'group_by_os_family': False}) - group_by_off = injector.inventory_as_dict(inv_src, '/tmp/foo') - # much better, everyone should turn off the flag and live in the future - assert len(group_by_off['keyed_groups']) == expected_groups - 1 - - def test_tower_plugin_named_url(self): - injector = InventorySource.injectors['tower']('2.9') - inv_src = InventorySource( - name='my tower source', source='tower', - # named URL pattern "inventory++organization" - instance_filters='Designer hair 읰++Cosmetic_products䵆' - ) - result = injector.inventory_as_dict(inv_src, '/tmp/foo') - assert result['inventory_id'] == 'Designer%20hair%20%EC%9D%B0++Cosmetic_products%E4%B5%86' + @pytest.mark.parametrize('source,proper_name', [ + ('ec2', 'amazon.aws.aws_ec2'), + ('openstack', 'openstack.cloud.openstack'), + ('gce', 'google.cloud.gcp_compute'), + ('azure_rm', 'azure.azcollection.azure_rm'), + ('vmware', 'community.vmware.vmware_vm_inventory'), + ('rhv', 'ovirt.ovirt.ovirt'), + ('satellite6', 'theforeman.foreman.foreman'), + ('tower', 'awx.awx.tower'), + ]) + def test_plugin_proper_names(self, source, proper_name): + injector = InventorySource.injectors[source]() + assert injector.get_proper_name() == proper_name @pytest.mark.django_db diff --git a/awx/main/tests/functional/models/test_job.py b/awx/main/tests/functional/models/test_job.py index 31b430d268e9..c6c4d2d6e6c3 100644 --- a/awx/main/tests/functional/models/test_job.py +++ b/awx/main/tests/functional/models/test_job.py @@ -1,6 +1,9 @@ import pytest -from awx.main.models import JobTemplate, Job, JobHostSummary, WorkflowJob, Inventory +from awx.main.models import ( + JobTemplate, Job, JobHostSummary, + WorkflowJob, Inventory, Project, Organization +) @pytest.mark.django_db @@ -13,7 +16,7 @@ def test_awx_virtualenv_from_settings(inventory, project, machine_credential): ) jt.credentials.add(machine_credential) job = jt.create_unified_job() - assert job.ansible_virtualenv_path == '/venv/ansible' + assert job.ansible_virtualenv_path == '/var/lib/awx/venv/ansible' @pytest.mark.django_db @@ -29,38 +32,39 @@ def test_prevent_slicing(): @pytest.mark.django_db -def test_awx_custom_virtualenv(inventory, project, machine_credential): +def test_awx_custom_virtualenv(inventory, project, machine_credential, organization): jt = JobTemplate.objects.create( name='my-jt', inventory=inventory, project=project, - playbook='helloworld.yml' + playbook='helloworld.yml', + organization=organization ) jt.credentials.add(machine_credential) job = jt.create_unified_job() - job.project.organization.custom_virtualenv = '/venv/fancy-org' - job.project.organization.save() - assert job.ansible_virtualenv_path == '/venv/fancy-org' + job.organization.custom_virtualenv = '/var/lib/awx/venv/fancy-org' + job.organization.save() + assert job.ansible_virtualenv_path == '/var/lib/awx/venv/fancy-org' - job.project.custom_virtualenv = '/venv/fancy-proj' + job.project.custom_virtualenv = '/var/lib/awx/venv/fancy-proj' job.project.save() - assert job.ansible_virtualenv_path == '/venv/fancy-proj' + assert job.ansible_virtualenv_path == '/var/lib/awx/venv/fancy-proj' - job.job_template.custom_virtualenv = '/venv/fancy-jt' + job.job_template.custom_virtualenv = '/var/lib/awx/venv/fancy-jt' job.job_template.save() - assert job.ansible_virtualenv_path == '/venv/fancy-jt' + assert job.ansible_virtualenv_path == '/var/lib/awx/venv/fancy-jt' @pytest.mark.django_db def test_awx_custom_virtualenv_without_jt(project): - project.custom_virtualenv = '/venv/fancy-proj' + project.custom_virtualenv = '/var/lib/awx/venv/fancy-proj' project.save() job = Job(project=project) job.save() job = Job.objects.get(pk=job.id) - assert job.ansible_virtualenv_path == '/venv/fancy-proj' + assert job.ansible_virtualenv_path == '/var/lib/awx/venv/fancy-proj' @pytest.mark.django_db @@ -78,6 +82,22 @@ def test_job_host_summary_representation(host): assert 'N/A changed=1 dark=2 failures=3 ignored=4 ok=5 processed=6 rescued=7 skipped=8' == str(jhs) +@pytest.mark.django_db +def test_jt_organization_follows_project(): + org1 = Organization.objects.create(name='foo1') + org2 = Organization.objects.create(name='foo2') + project1 = Project.objects.create(name='proj1', organization=org1) + project2 = Project.objects.create(name='proj2', organization=org2) + jt = JobTemplate.objects.create( + name='foo', playbook='helloworld.yml', + project=project1 + ) + assert jt.organization == org1 + jt.project = project2 + jt.save() + assert JobTemplate.objects.get(pk=jt.id).organization == org2 + + @pytest.mark.django_db class TestSlicingModels: diff --git a/awx/main/tests/functional/models/test_notifications.py b/awx/main/tests/functional/models/test_notifications.py index 00cc217a82c0..5e5f19f0fd8c 100644 --- a/awx/main/tests/functional/models/test_notifications.py +++ b/awx/main/tests/functional/models/test_notifications.py @@ -12,6 +12,7 @@ class TestJobNotificationMixin(object): CONTEXT_STRUCTURE = {'job': {'allow_simultaneous': bool, + 'artifacts': {}, 'custom_virtualenv': str, 'controller_node': str, 'created': datetime.datetime, @@ -23,8 +24,11 @@ class TestJobNotificationMixin(object): 'finished': bool, 'force_handlers': bool, 'forks': int, - 'host_status_counts': {'skipped': int, 'ok': int, 'changed': int, - 'failures': int, 'dark': int}, + 'host_status_counts': { + 'skipped': int, 'ok': int, 'changed': int, + 'failures': int, 'dark': int, 'processed': int, + 'rescued': int, 'failed': bool + }, 'id': int, 'job_explanation': str, 'job_slice_count': int, @@ -36,7 +40,7 @@ class TestJobNotificationMixin(object): 'modified': datetime.datetime, 'name': str, 'playbook': str, - 'playbook_counts': {'play_count': int, 'task_count': int}, + 'scm_branch': str, 'scm_revision': str, 'skip_tags': str, 'start_at_task': str, @@ -48,7 +52,6 @@ class TestJobNotificationMixin(object): 'username': str}, 'instance_group': {'id': int, 'name': str}, 'inventory': {'description': str, - 'groups_with_active_failures': int, 'has_active_failures': bool, 'has_inventory_sources': bool, 'hosts_with_active_failures': int, @@ -69,17 +72,10 @@ class TestJobNotificationMixin(object): 'name': str, 'scm_type': str, 'status': str}, - 'project_update': {'id': int, 'name': str, 'description': str, 'status': str, 'failed': bool}, 'unified_job_template': {'description': str, 'id': int, 'name': str, - 'unified_job_type': str}, - 'source_workflow_job': {'description': str, - 'elapsed': float, - 'failed': bool, - 'id': int, - 'name': str, - 'status': str}}, + 'unified_job_type': str}}, 'timeout': int, 'type': str, @@ -110,10 +106,14 @@ def check_structure(expected_structure, obj): assert isinstance(obj[key], dict) check_structure(expected_structure[key], obj[key]) else: - assert isinstance(obj[key], expected_structure[key]) + if key == 'job_explanation': + assert isinstance(str(obj[key]), expected_structure[key]) + else: + assert isinstance(obj[key], expected_structure[key]) kwargs = {} if JobClass is InventoryUpdate: kwargs['inventory_source'] = inventory_source + kwargs['source'] = inventory_source.source elif JobClass is ProjectUpdate: kwargs['project'] = project @@ -123,6 +123,15 @@ def check_structure(expected_structure, obj): context = job.context(job_serialization) check_structure(TestJobNotificationMixin.CONTEXT_STRUCTURE, context) + + @pytest.mark.django_db + def test_context_job_metadata_with_unicode(self): + job = Job.objects.create(name='批量安装项目') + job_serialization = UnifiedJobSerializer(job).to_representation(job) + context = job.context(job_serialization) + assert '批量安装项目' in context['job_metadata'] + + def test_context_stub(self): """The context stub is a fake context used to validate custom notification messages. Ensure that this also has the expected structure. Furthermore, ensure that the stub context contains diff --git a/awx/main/tests/functional/models/test_project.py b/awx/main/tests/functional/models/test_project.py index 719c37436e2d..d3c34498b021 100644 --- a/awx/main/tests/functional/models/test_project.py +++ b/awx/main/tests/functional/models/test_project.py @@ -1,7 +1,7 @@ import pytest from unittest import mock -from awx.main.models import Project +from awx.main.models import Project, Credential, CredentialType from awx.main.models.organization import Organization @@ -34,8 +34,54 @@ def test_sensitive_change_triggers_update(project): mock_update.assert_called_once_with() +@pytest.mark.django_db +def test_local_path_autoset(organization): + with mock.patch.object(Project, "update"): + p = Project.objects.create( + name="test-proj", + organization=organization, + scm_url='localhost', + scm_type='git' + ) + assert p.local_path == f'_{p.id}__test_proj' + + @pytest.mark.django_db def test_foreign_key_change_changes_modified_by(project, organization): assert project._get_fields_snapshot()['organization_id'] == organization.id project.organization = Organization(name='foo', pk=41) assert project._get_fields_snapshot()['organization_id'] == 41 + + +@pytest.mark.django_db +def test_project_related_jobs(project): + update = project.create_unified_job() + assert update.id in [u.id for u in project._get_related_jobs()] + + +@pytest.mark.django_db +def test_galaxy_credentials(project): + org = project.organization + galaxy = CredentialType.defaults['galaxy_api_token']() + galaxy.save() + for i in range(5): + cred = Credential.objects.create( + name=f'Ansible Galaxy {i + 1}', + organization=org, + credential_type=galaxy, + inputs={ + 'url': 'https://galaxy.ansible.com/' + } + ) + cred.save() + org.galaxy_credentials.add(cred) + + assert [ + cred.name for cred in org.galaxy_credentials.all() + ] == [ + 'Ansible Galaxy 1', + 'Ansible Galaxy 2', + 'Ansible Galaxy 3', + 'Ansible Galaxy 4', + 'Ansible Galaxy 5', + ] diff --git a/awx/main/tests/functional/models/test_schedule.py b/awx/main/tests/functional/models/test_schedule.py index 525fdd3022f4..fb5bfbf2715f 100644 --- a/awx/main/tests/functional/models/test_schedule.py +++ b/awx/main/tests/functional/models/test_schedule.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import datetime, timedelta from contextlib import contextmanager from django.utils.timezone import now @@ -103,7 +103,8 @@ def test_computed_fields_time_change(self, job_template): Schedule.objects.filter(pk=s.pk).update(next_run=old_next_run) s.next_run = old_next_run prior_modified = s.modified - s.update_computed_fields() + with mock.patch('awx.main.models.schedules.emit_channel_notification'): + s.update_computed_fields() assert s.next_run != old_next_run assert s.modified == prior_modified @@ -133,7 +134,8 @@ def test_computed_fields_turning_on_via_rrule(self, job_template): assert s.next_run is None assert job_template.next_schedule is None s.rrule = self.distant_rrule - s.update_computed_fields() + with mock.patch('awx.main.models.schedules.emit_channel_notification'): + s.update_computed_fields() assert s.next_run is not None assert job_template.next_schedule == s @@ -159,6 +161,58 @@ 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" + 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() + + # the next few scheduled events should be the next minute/hour incremented + next_five_events = list(sched.rrulestr(sched.rrule).xafter(now(), count=5)) + + assert next_five_events[0] > now() + last = None + for event in next_five_events: + if last: + assert event == last + ( + timedelta(minutes=1) if freq == 'MINUTELY' else timedelta(hours=1) + ) + last = event + + @pytest.mark.django_db def test_repeats_forever(job_template): s = Schedule( @@ -323,16 +377,19 @@ def test_dst_phantom_hour(job_template): @pytest.mark.django_db +@pytest.mark.timeout(3) def test_beginning_of_time(job_template): # ensure that really large generators don't have performance issues + start = now() rrule = 'DTSTART:19700101T000000Z RRULE:FREQ=MINUTELY;INTERVAL=1' s = Schedule( name='Some Schedule', rrule=rrule, unified_job_template=job_template ) - with pytest.raises(ValueError): - s.save() + s.save() + assert s.next_run > start + assert (s.next_run - start).total_seconds() < 60 @pytest.mark.django_db diff --git a/awx/main/tests/functional/models/test_unified_job.py b/awx/main/tests/functional/models/test_unified_job.py index c1d0967583eb..7b5f6d432ba6 100644 --- a/awx/main/tests/functional/models/test_unified_job.py +++ b/awx/main/tests/functional/models/test_unified_job.py @@ -13,6 +13,7 @@ WorkflowApprovalTemplate, Project, WorkflowJob, Schedule, Credential ) +from awx.api.versioning import reverse @pytest.mark.django_db @@ -26,6 +27,29 @@ def test_subclass_types(rando): ]) +@pytest.mark.django_db +def test_soft_unique_together(post, project, admin_user): + """This tests that SOFT_UNIQUE_TOGETHER restrictions are applied correctly. + """ + jt1 = JobTemplate.objects.create( + name='foo_jt', + project=project + ) + assert jt1.organization == project.organization + r = post( + url=reverse('api:job_template_list'), + data=dict( + name='foo_jt', # same as first + project=project.id, + ask_inventory_on_launch=True, + playbook='helloworld.yml' + ), + user=admin_user, + expect=400 + ) + assert 'combination already exists' in str(r.data) + + @pytest.mark.django_db class TestCreateUnifiedJob: ''' @@ -283,13 +307,13 @@ def r(hosts, forks): def test_limit_task_impact(self, job_host_limit, run_computed_fields_right_away): job = job_host_limit(5, 2) - job.inventory.refresh_from_db() # FIXME: computed fields operates on reloaded inventory + job.inventory.update_computed_fields() assert job.inventory.total_hosts == 5 assert job.task_impact == 2 + 1 # forks becomes constraint def test_host_task_impact(self, job_host_limit, run_computed_fields_right_away): job = job_host_limit(3, 5) - job.inventory.refresh_from_db() # FIXME: computed fields operates on reloaded inventory + job.inventory.update_computed_fields() assert job.task_impact == 3 + 1 # hosts becomes constraint def test_shard_task_impact(self, slice_job_factory, run_computed_fields_right_away): @@ -304,6 +328,7 @@ def test_shard_task_impact(self, slice_job_factory, run_computed_fields_right_aw len(jobs[0].inventory.get_script_data(slice_number=i + 1, slice_count=3)['all']['hosts']) for i in range(3) ] == [1, 1, 1] + jobs[0].inventory.update_computed_fields() assert [job.task_impact for job in jobs] == [2, 2, 2] # plus one base task impact # Uneven distribution - first job takes the extra host jobs[0].inventory.hosts.create(name='remainder_foo') @@ -311,5 +336,5 @@ def test_shard_task_impact(self, slice_job_factory, run_computed_fields_right_aw len(jobs[0].inventory.get_script_data(slice_number=i + 1, slice_count=3)['all']['hosts']) for i in range(3) ] == [2, 1, 1] - jobs[0].inventory.refresh_from_db() # FIXME: computed fields operates on reloaded inventory + jobs[0].inventory.update_computed_fields() assert [job.task_impact for job in jobs] == [3, 2, 2] 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 b459241f55c1..b763ef5ca36b 100644 --- a/awx/main/tests/functional/task_management/test_rampart_groups.py +++ b/awx/main/tests/functional/task_management/test_rampart_groups.py @@ -67,7 +67,7 @@ def test_multi_group_with_shared_dependency(instance_factory, default_instance_g pu = p.project_updates.first() TaskManager.start_task.assert_called_once_with(pu, default_instance_group, - [j1], + [j1,j2], default_instance_group.instances.all()[0]) pu.finished = pu.created + timedelta(seconds=1) pu.status = "successful" @@ -193,7 +193,7 @@ def test_instance_group_basic_policies(instance_factory, instance_group_factory) ig2 = InstanceGroup.objects.get(id=ig2.id) ig3 = InstanceGroup.objects.get(id=ig3.id) assert len(ig0.instances.all()) == 1 - assert i0 in ig0.instances.all() + assert i0 in ig0.instances.all() assert len(InstanceGroup.objects.get(id=ig1.id).instances.all()) == 2 assert i1 in ig1.instances.all() assert i2 in ig1.instances.all() diff --git a/awx/main/tests/functional/task_management/test_scheduler.py b/awx/main/tests/functional/task_management/test_scheduler.py index 1ad3d7e0356a..9ba1e068c977 100644 --- a/awx/main/tests/functional/task_management/test_scheduler.py +++ b/awx/main/tests/functional/task_management/test_scheduler.py @@ -6,7 +6,7 @@ from awx.main.scheduler import TaskManager from awx.main.scheduler.dependency_graph import DependencyGraph from awx.main.utils import encrypt_field -from awx.main.models import WorkflowJobTemplate, JobTemplate +from awx.main.models import WorkflowJobTemplate, JobTemplate, Job @pytest.mark.django_db @@ -307,8 +307,8 @@ def test_shared_dependencies_launch(default_instance_group, job_template_factory TaskManager().schedule() pu = p.project_updates.first() iu = ii.inventory_updates.first() - TaskManager.start_task.assert_has_calls([mock.call(pu, default_instance_group, [iu, j1], instance), - mock.call(iu, default_instance_group, [pu, j1], instance)]) + TaskManager.start_task.assert_has_calls([mock.call(iu, default_instance_group, [j1, j2, pu], instance), + mock.call(pu, default_instance_group, [j1, j2, iu], instance)]) pu.status = "successful" pu.finished = pu.created + timedelta(seconds=1) pu.save() @@ -383,3 +383,35 @@ def test_job_not_blocking_inventory_update(default_instance_group, job_template_ dependency_graph = DependencyGraph(None) dependency_graph.add_job(job) assert not dependency_graph.is_job_blocked(inventory_update) + + +@pytest.mark.django_db +def test_generate_dependencies_only_once(job_template_factory): + objects = job_template_factory('jt', organization='org1') + + job = objects.job_template.create_job() + job.status = "pending" + job.name = "job_gen_dep" + job.save() + + + with mock.patch("awx.main.scheduler.TaskManager.start_task"): + # job starts with dependencies_processed as False + assert not job.dependencies_processed + # run one cycle of ._schedule() to generate dependencies + TaskManager()._schedule() + + # make sure dependencies_processed is now True + job = Job.objects.filter(name="job_gen_dep")[0] + assert job.dependencies_processed + + # Run ._schedule() again, but make sure .generate_dependencies() is not + # called with job in the argument list + tm = TaskManager() + tm.generate_dependencies = mock.MagicMock() + tm._schedule() + + # .call_args is tuple, (positional_args, kwargs), [0][0] then is + # the first positional arg, i.e. the first argument of + # .generate_dependencies() + assert tm.generate_dependencies.call_args[0][0] == [] diff --git a/awx/main/tests/functional/test_credential.py b/awx/main/tests/functional/test_credential.py index 721bf5c043ac..27f67b96f4b8 100644 --- a/awx/main/tests/functional/test_credential.py +++ b/awx/main/tests/functional/test_credential.py @@ -79,8 +79,8 @@ def test_default_cred_types(): 'aws', 'azure_kv', 'azure_rm', - 'cloudforms', 'conjur', + 'galaxy_api_token', 'gce', 'github_token', 'gitlab_token', diff --git a/awx/main/tests/functional/test_credential_plugins.py b/awx/main/tests/functional/test_credential_plugins.py new file mode 100644 index 000000000000..b8daf6b41ef3 --- /dev/null +++ b/awx/main/tests/functional/test_credential_plugins.py @@ -0,0 +1,6 @@ +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]) diff --git a/awx/main/tests/functional/test_dispatch.py b/awx/main/tests/functional/test_dispatch.py index 97f1ccf1d6e1..e92867d6a557 100644 --- a/awx/main/tests/functional/test_dispatch.py +++ b/awx/main/tests/functional/test_dispatch.py @@ -10,11 +10,22 @@ from awx.main.models import Job, WorkflowJob, Instance from awx.main.dispatch import reaper -from awx.main.dispatch.pool import PoolWorker, WorkerPool, AutoscalePool +from awx.main.dispatch.pool import StatefulPoolWorker, WorkerPool, AutoscalePool from awx.main.dispatch.publish import task from awx.main.dispatch.worker import BaseWorker, TaskWorker +''' +Prevent logger. calls from triggering database operations +''' + + +@pytest.fixture(autouse=True) +def _disable_database_settings(mocker): + m = mocker.patch('awx.conf.settings.SettingsWrapper.all_supported_settings', new_callable=mock.PropertyMock) + m.return_value = [] + + def restricted(a, b): raise AssertionError("This code should not run because it isn't decorated with @task") @@ -69,7 +80,7 @@ def perform_work(self, body, result_queue): class TestPoolWorker: def setup_method(self, test_method): - self.worker = PoolWorker(1000, self.tick, tuple()) + self.worker = StatefulPoolWorker(1000, self.tick, tuple()) def tick(self): self.worker.finished.put(self.worker.queue.get()['uuid']) @@ -324,22 +335,23 @@ def test_method_callable(self): assert Adder().run(2, 2) == 4 def test_function_apply_async(self): - message, queue = add.apply_async([2, 2]) + 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 == 'awx_private_queue' + assert queue == 'foobar' def test_method_apply_async(self): - message, queue = Adder.apply_async([2, 2]) + 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 == 'awx_private_queue' + assert queue == 'foobar' - def test_apply_with_queue(self): - message, queue = add.apply_async([2, 2], queue='abc123') - assert queue == 'abc123' + 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]) diff --git a/awx/main/tests/functional/test_galaxy_credential_migration.py b/awx/main/tests/functional/test_galaxy_credential_migration.py new file mode 100644 index 000000000000..110628e19c02 --- /dev/null +++ b/awx/main/tests/functional/test_galaxy_credential_migration.py @@ -0,0 +1,115 @@ +import importlib + +from django.conf import settings +from django.contrib.contenttypes.models import ContentType +import pytest + +from awx.main.models import Credential, Organization +from awx.conf.models import Setting +from awx.main.migrations import _galaxy as galaxy + + +class FakeApps(object): + def get_model(self, app, model): + if app == 'contenttypes': + return ContentType + return getattr(importlib.import_module(f'awx.{app}.models'), model) + + +apps = FakeApps() + + +@pytest.mark.django_db +def test_default_public_galaxy(): + org = Organization.objects.create() + assert org.galaxy_credentials.count() == 0 + galaxy.migrate_galaxy_settings(apps, None) + assert org.galaxy_credentials.count() == 1 + creds = org.galaxy_credentials.all() + assert creds[0].name == 'Ansible Galaxy' + assert creds[0].inputs['url'] == 'https://galaxy.ansible.com/' + + +@pytest.mark.django_db +def test_public_galaxy_disabled(): + Setting.objects.create(key='PUBLIC_GALAXY_ENABLED', value=False) + org = Organization.objects.create() + assert org.galaxy_credentials.count() == 0 + galaxy.migrate_galaxy_settings(apps, None) + assert org.galaxy_credentials.count() == 0 + + +@pytest.mark.django_db +def test_rh_automation_hub(): + Setting.objects.create(key='PRIMARY_GALAXY_URL', value='https://cloud.redhat.com/api/automation-hub/') + Setting.objects.create(key='PRIMARY_GALAXY_TOKEN', value='secret123') + org = Organization.objects.create() + assert org.galaxy_credentials.count() == 0 + galaxy.migrate_galaxy_settings(apps, None) + assert org.galaxy_credentials.count() == 2 + assert org.galaxy_credentials.first().name == 'Ansible Automation Hub (https://cloud.redhat.com/api/automation-hub/)' # noqa + + +@pytest.mark.django_db +def test_multiple_galaxies(): + for i in range(5): + Organization.objects.create(name=f'Org {i}') + + Setting.objects.create(key='PRIMARY_GALAXY_URL', value='https://example.org/') + Setting.objects.create(key='PRIMARY_GALAXY_AUTH_URL', value='https://auth.example.org/') + Setting.objects.create(key='PRIMARY_GALAXY_USERNAME', value='user') + Setting.objects.create(key='PRIMARY_GALAXY_PASSWORD', value='pass') + Setting.objects.create(key='PRIMARY_GALAXY_TOKEN', value='secret123') + + for org in Organization.objects.all(): + assert org.galaxy_credentials.count() == 0 + + galaxy.migrate_galaxy_settings(apps, None) + + for org in Organization.objects.all(): + assert org.galaxy_credentials.count() == 2 + creds = org.galaxy_credentials.all() + assert creds[0].name == 'Private Galaxy (https://example.org/)' + assert creds[0].inputs['url'] == 'https://example.org/' + assert creds[0].inputs['auth_url'] == 'https://auth.example.org/' + assert creds[0].inputs['token'].startswith('$encrypted$') + assert creds[0].get_input('token') == 'secret123' + + assert creds[1].name == 'Ansible Galaxy' + assert creds[1].inputs['url'] == 'https://galaxy.ansible.com/' + + public_galaxy_creds = Credential.objects.filter(name='Ansible Galaxy') + assert public_galaxy_creds.count() == 1 + assert public_galaxy_creds.first().managed_by_tower is True + + +@pytest.mark.django_db +def test_fallback_galaxies(): + org = Organization.objects.create() + assert org.galaxy_credentials.count() == 0 + Setting.objects.create(key='PRIMARY_GALAXY_URL', value='https://example.org/') + Setting.objects.create(key='PRIMARY_GALAXY_AUTH_URL', value='https://auth.example.org/') + Setting.objects.create(key='PRIMARY_GALAXY_TOKEN', value='secret123') + try: + settings.FALLBACK_GALAXY_SERVERS = [{ + 'id': 'abc123', + 'url': 'https://some-other-galaxy.example.org/', + 'auth_url': 'https://some-other-galaxy.sso.example.org/', + 'username': 'user', + 'password': 'pass', + 'token': 'fallback123', + }] + galaxy.migrate_galaxy_settings(apps, None) + finally: + settings.FALLBACK_GALAXY_SERVERS = [] + assert org.galaxy_credentials.count() == 3 + creds = org.galaxy_credentials.all() + assert creds[0].name == 'Private Galaxy (https://example.org/)' + assert creds[0].inputs['url'] == 'https://example.org/' + assert creds[1].name == 'Ansible Galaxy (https://some-other-galaxy.example.org/)' + assert creds[1].inputs['url'] == 'https://some-other-galaxy.example.org/' + assert creds[1].inputs['auth_url'] == 'https://some-other-galaxy.sso.example.org/' + assert creds[1].inputs['token'].startswith('$encrypted$') + assert creds[1].get_input('token') == 'fallback123' + assert creds[2].name == 'Ansible Galaxy' + assert creds[2].inputs['url'] == 'https://galaxy.ansible.com/' diff --git a/awx/main/tests/functional/test_instances.py b/awx/main/tests/functional/test_instances.py index 404b55722771..649c4c646afd 100644 --- a/awx/main/tests/functional/test_instances.py +++ b/awx/main/tests/functional/test_instances.py @@ -1,7 +1,7 @@ import pytest from unittest import mock -from awx.main.models import AdHocCommand, InventoryUpdate, Job, JobTemplate, ProjectUpdate +from awx.main.models import AdHocCommand, InventoryUpdate, JobTemplate, ProjectUpdate from awx.main.models.ha import Instance, InstanceGroup from awx.main.tasks import apply_cluster_membership_policies from awx.api.versioning import reverse @@ -297,7 +297,10 @@ def test_ad_hoc_instance_groups(self, instance_group_factory, inventory, default assert ad_hoc.preferred_instance_groups == [ig_inv, ig_org] def test_inventory_update_instance_groups(self, instance_group_factory, inventory_source, default_instance_group): - iu = InventoryUpdate.objects.create(inventory_source=inventory_source) + iu = InventoryUpdate.objects.create( + inventory_source=inventory_source, + source=inventory_source.source + ) assert iu.preferred_instance_groups == [default_instance_group] ig_org = instance_group_factory("OrgIstGrp", [default_instance_group.instances.first()]) ig_inv = instance_group_factory("InvIstGrp", [default_instance_group.instances.first()]) @@ -310,7 +313,7 @@ def test_inventory_update_instance_groups(self, instance_group_factory, inventor assert iu.preferred_instance_groups == [ig_inv, ig_org] def test_project_update_instance_groups(self, instance_group_factory, project, default_instance_group): - pu = ProjectUpdate.objects.create(project=project) + pu = ProjectUpdate.objects.create(project=project, organization=project.organization) assert pu.preferred_instance_groups == [default_instance_group] ig_org = instance_group_factory("OrgIstGrp", [default_instance_group.instances.first()]) ig_tmp = instance_group_factory("TmpIstGrp", [default_instance_group.instances.first()]) @@ -321,7 +324,7 @@ def test_project_update_instance_groups(self, instance_group_factory, project, d def test_job_instance_groups(self, instance_group_factory, inventory, project, default_instance_group): jt = JobTemplate.objects.create(inventory=inventory, project=project) - job = Job.objects.create(inventory=inventory, job_template=jt, project=project) + job = jt.create_unified_job() assert job.preferred_instance_groups == [default_instance_group] ig_org = instance_group_factory("OrgIstGrp", [default_instance_group.instances.first()]) ig_inv = instance_group_factory("InvIstGrp", [default_instance_group.instances.first()]) diff --git a/awx/main/tests/functional/test_inventory_source_injectors.py b/awx/main/tests/functional/test_inventory_source_injectors.py index c41e596edca1..fc28c92294f8 100644 --- a/awx/main/tests/functional/test_inventory_source_injectors.py +++ b/awx/main/tests/functional/test_inventory_source_injectors.py @@ -14,64 +14,6 @@ DATA = os.path.join(os.path.dirname(data.__file__), 'inventory') -TEST_SOURCE_FIELDS = { - 'vmware': { - 'instance_filters': 'foobaa', - 'group_by': 'fouo' - }, - 'ec2': { - 'instance_filters': 'foobaa', - # group_by selected to capture some non-trivial cross-interactions - 'group_by': 'availability_zone,instance_type,tag_keys,region', - 'source_regions': 'us-east-2,ap-south-1' - }, - 'gce': { - 'source_regions': 'us-east4-a,us-west1-b' # surfaced as env var - }, - 'azure_rm': { - 'source_regions': 'southcentralus,westus' - }, - 'tower': { - 'instance_filters': '42' - } -} - -INI_TEST_VARS = { - 'ec2': { - 'boto_profile': '/tmp/my_boto_stuff' - }, - 'gce': {}, - 'openstack': { - 'private': False, - 'use_hostnames': False, - 'expand_hostvars': True, - 'fail_on_errors': True - }, - 'rhv': {}, # there are none - 'tower': {}, # there are none - 'vmware': { - # setting VMWARE_VALIDATE_CERTS is duplicated with env var - }, - 'azure_rm': { - 'use_private_ip': True, - 'resource_groups': 'foo_resources,bar_resources', - 'tags': 'Creator:jmarshall, peanutbutter:jelly' - }, - 'satellite6': { - 'satellite6_group_patterns': 'foo_group_patterns', - 'satellite6_group_prefix': 'foo_group_prefix', - 'satellite6_want_hostcollections': True - }, - 'cloudforms': { - 'version': '2.4', - 'purge_actions': 'maybe', - 'clean_group_keys': 'this_key', - 'nest_tags': 'yes', - 'suffix': '.ppt', - 'prefer_ipv4': 'yes' - } -} - def generate_fake_var(element): """Given a credential type field element, makes up something acceptable. @@ -107,21 +49,27 @@ def credential_kind(source): @pytest.fixture -def fake_credential_factory(source): - ct = CredentialType.defaults[credential_kind(source)]() - ct.save() +def fake_credential_factory(): + def wrap(source): + ct = CredentialType.defaults[credential_kind(source)]() + ct.save() - inputs = {} - var_specs = {} # pivoted version of inputs - for element in ct.inputs.get('fields'): - var_specs[element['id']] = element - for var in var_specs.keys(): - inputs[var] = generate_fake_var(var_specs[var]) + inputs = {} + var_specs = {} # pivoted version of inputs + for element in ct.inputs.get('fields'): + var_specs[element['id']] = element + for var in var_specs.keys(): + inputs[var] = generate_fake_var(var_specs[var]) + + if source == 'tower': + inputs.pop('oauth_token') # mutually exclusive with user/pass + + return Credential.objects.create( + credential_type=ct, + inputs=inputs + ) + return wrap - return Credential.objects.create( - credential_type=ct, - inputs=inputs - ) def read_content(private_data_dir, raw_env, inventory_update): @@ -233,28 +181,22 @@ def create_reference_data(source_dir, env, content): @pytest.mark.django_db @pytest.mark.parametrize('this_kind', CLOUD_PROVIDERS) -@pytest.mark.parametrize('script_or_plugin', ['scripts', 'plugins']) -def test_inventory_update_injected_content(this_kind, script_or_plugin, inventory): +def test_inventory_update_injected_content(this_kind, inventory, fake_credential_factory): + 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') - if this_kind in INI_TEST_VARS: - src_vars.update(INI_TEST_VARS[this_kind]) - extra_kwargs = {} - if this_kind in TEST_SOURCE_FIELDS: - extra_kwargs.update(TEST_SOURCE_FIELDS[this_kind]) + src_vars['plugin'] = injector.get_proper_name() inventory_source = InventorySource.objects.create( inventory=inventory, source=this_kind, source_vars=src_vars, - **extra_kwargs ) inventory_source.credentials.add(fake_credential_factory(this_kind)) inventory_update = inventory_source.create_unified_job() task = RunInventoryUpdate() - use_plugin = bool(script_or_plugin == 'plugins') - if use_plugin and InventorySource.injectors[this_kind].plugin_name is None: - pytest.skip('Use of inventory plugin is not enabled for this source') - def substitute_run(envvars=None, **_kw): """This method will replace run_pexpect instead of running, it will read the private data directory contents @@ -262,11 +204,20 @@ def substitute_run(envvars=None, **_kw): If MAKE_INVENTORY_REFERENCE_FILES is set, it will produce reference files """ private_data_dir = envvars.pop('AWX_PRIVATE_DATA_DIR') - assert envvars.pop('ANSIBLE_INVENTORY_ENABLED') == ('auto' if use_plugin else 'script') + assert envvars.pop('ANSIBLE_INVENTORY_ENABLED') == 'auto' set_files = bool(os.getenv("MAKE_INVENTORY_REFERENCE_FILES", 'false').lower()[0] not in ['f', '0']) env, content = read_content(private_data_dir, envvars, inventory_update) + + # Assert inventory plugin inventory file is in private_data_dir + inventory_filename = InventorySource.injectors[inventory_update.source]().filename + assert 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 - base_dir = os.path.join(DATA, script_or_plugin) + env.pop('PYTHONPATH') + env.pop('VIRTUAL_ENV') + env.pop('PROOT_TMP_DIR') + base_dir = os.path.join(DATA, 'plugins') if not os.path.exists(base_dir): os.mkdir(base_dir) source_dir = os.path.join(base_dir, this_kind) # this_kind is a global @@ -274,6 +225,8 @@ def substitute_run(envvars=None, **_kw): create_reference_data(source_dir, env, content) pytest.skip('You set MAKE_INVENTORY_REFERENCE_FILES, so this created files, unset to run actual test.') else: + 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? ' @@ -283,9 +236,6 @@ def substitute_run(envvars=None, **_kw): expected_file_list = os.listdir(files_dir) except FileNotFoundError: expected_file_list = [] - assert set(expected_file_list) == set(content.keys()), ( - 'Inventory update runtime environment does not have expected files' - ) for f_name in expected_file_list: with open(os.path.join(files_dir, f_name), 'r') as f: ref_content = f.read() @@ -300,20 +250,12 @@ def substitute_run(envvars=None, **_kw): Res = namedtuple('Result', ['status', 'rc']) return Res('successful', 0) - mock_licenser = mock.Mock(return_value=mock.Mock( - validate=mock.Mock(return_value={'license_type': 'open'}) - )) - # Mock this so that it will not send events to the callback receiver # because doing so in pytest land creates large explosions with mock.patch('awx.main.queue.CallbackQueueDispatcher.dispatch', lambda self, obj: None): - # Force the update to use the script injector - with mock.patch('awx.main.models.inventory.PluginFileInjector.should_use_plugin', return_value=use_plugin): - # Also do not send websocket status updates - with mock.patch.object(UnifiedJob, 'websocket_emit_status', mock.Mock()): - # The point of this test is that we replace run with assertions - with mock.patch('awx.main.tasks.ansible_runner.interface.run', substitute_run): - # mocking the licenser is necessary for the tower source - with mock.patch('awx.main.models.inventory.get_licenser', mock_licenser): - # so this sets up everything for a run and then yields control over to substitute_run - task.run(inventory_update.pk) + # Also do not send websocket status updates + with mock.patch.object(UnifiedJob, 'websocket_emit_status', mock.Mock()): + # The point of this test is that we replace run with assertions + with mock.patch('awx.main.tasks.ansible_runner.interface.run', substitute_run): + # so this sets up everything for a run and then yields control over to substitute_run + task.run(inventory_update.pk) diff --git a/awx/main/tests/functional/test_inventory_source_migration.py b/awx/main/tests/functional/test_inventory_source_migration.py index 739b92232216..2b1e08939228 100644 --- a/awx/main/tests/functional/test_inventory_source_migration.py +++ b/awx/main/tests/functional/test_inventory_source_migration.py @@ -5,6 +5,8 @@ from django.apps import apps +from awx.main.models import InventorySource, InventoryUpdate, ManagedCredentialType, CredentialType, Credential + @pytest.mark.parametrize('vars,id_var,result', [ ({'foo': {'bar': '1234'}}, 'foo.bar', '1234'), @@ -37,3 +39,43 @@ def test_apply_new_instance_id(inventory_source): host2.refresh_from_db() assert host1.instance_id == '' assert host2.instance_id == 'bad_user' + + +@pytest.mark.django_db +def test_cloudforms_inventory_removal(inventory): + ManagedCredentialType( + name='Red Hat CloudForms', + namespace='cloudforms', + kind='cloud', + managed_by_tower=True, + inputs={}, + ) + CredentialType.defaults['cloudforms']().save() + cloudforms = CredentialType.objects.get(namespace='cloudforms') + Credential.objects.create( + name='test', + credential_type=cloudforms, + ) + + for source in ('ec2', 'cloudforms'): + i = InventorySource.objects.create( + name='test', + inventory=inventory, + organization=inventory.organization, + source=source, + ) + InventoryUpdate.objects.create( + name='test update', + inventory_source=i, + source=source, + ) + assert Credential.objects.count() == 1 + assert InventorySource.objects.count() == 2 # ec2 + cf + assert InventoryUpdate.objects.count() == 2 # ec2 + cf + invsrc.delete_cloudforms_inv_source(apps, None) + assert InventorySource.objects.count() == 1 # ec2 + assert InventoryUpdate.objects.count() == 1 # ec2 + assert InventorySource.objects.first().source == 'ec2' + assert InventoryUpdate.objects.first().source == 'ec2' + assert Credential.objects.count() == 0 + assert CredentialType.objects.filter(namespace='cloudforms').exists() is False diff --git a/awx/main/tests/functional/test_jobs.py b/awx/main/tests/functional/test_jobs.py index 9dab9c5d57cf..b4754a6803a9 100644 --- a/awx/main/tests/functional/test_jobs.py +++ b/awx/main/tests/functional/test_jobs.py @@ -1,8 +1,11 @@ +import redis import pytest from unittest import mock import json -from awx.main.models import Job, Instance, JobHostSummary +from awx.main.models import (Job, Instance, JobHostSummary, InventoryUpdate, + InventorySource, Project, ProjectUpdate, + SystemJob, AdHocCommand) from awx.main.tasks import cluster_node_heartbeat from django.test.utils import override_settings @@ -23,7 +26,8 @@ def test_orphan_unified_job_creation(instance, inventory): @mock.patch('awx.main.utils.common.get_mem_capacity', lambda: (8000,62)) def test_job_capacity_and_with_inactive_node(): i = Instance.objects.create(hostname='test-1') - i.refresh_capacity() + with mock.patch.object(redis.client.Redis, 'ping', lambda self: True): + i.refresh_capacity() assert i.capacity == 62 i.enabled = False i.save() @@ -33,6 +37,44 @@ def test_job_capacity_and_with_inactive_node(): assert i.capacity == 0 +@pytest.mark.django_db +@mock.patch('awx.main.utils.common.get_cpu_capacity', lambda: (2,8)) +@mock.patch('awx.main.utils.common.get_mem_capacity', lambda: (8000,62)) +def test_job_capacity_with_redis_disabled(): + i = Instance.objects.create(hostname='test-1') + + def _raise(self): + raise redis.ConnectionError() + with mock.patch.object(redis.client.Redis, 'ping', _raise): + i.refresh_capacity() + assert i.capacity == 0 + + +@pytest.mark.django_db +def test_job_type_name(): + job = Job.objects.create() + assert job.job_type_name == 'job' + + ahc = AdHocCommand.objects.create() + assert ahc.job_type_name == 'ad_hoc_command' + + source = InventorySource.objects.create(source='ec2') + source.save() + iu = InventoryUpdate.objects.create( + inventory_source=source, + source='ec2' + ) + assert iu.job_type_name == 'inventory_update' + + proj = Project.objects.create() + proj.save() + pu = ProjectUpdate.objects.create(project=proj) + assert pu.job_type_name == 'project_update' + + sjob = SystemJob.objects.create() + assert sjob.job_type_name == 'system_job' + + @pytest.mark.django_db def test_job_notification_data(inventory, machine_credential, project): encrypted_str = "$encrypted$" diff --git a/awx/main/tests/functional/test_labels.py b/awx/main/tests/functional/test_labels.py new file mode 100644 index 000000000000..fad1869d0e89 --- /dev/null +++ b/awx/main/tests/functional/test_labels.py @@ -0,0 +1,37 @@ +import pytest + +# awx +from awx.main.models import WorkflowJobTemplate +from awx.api.versioning import reverse + + +@pytest.mark.django_db +def test_workflow_can_add_label(org_admin,organization, post): + # create workflow + wfjt = WorkflowJobTemplate.objects.create(name='test-wfjt') + wfjt.organization = organization + # create label + wfjt.admin_role.members.add(org_admin) + url = reverse('api:workflow_job_template_label_list', kwargs={'pk': wfjt.pk}) + data = {'name': 'dev-label', 'organization': organization.id} + label = post(url, user=org_admin, data=data, expect=201) + assert label.data['name'] == 'dev-label' + + +@pytest.mark.django_db +def test_workflow_can_remove_label(org_admin, organization, post, get): + # create workflow + wfjt = WorkflowJobTemplate.objects.create(name='test-wfjt') + wfjt.organization = organization + # create label + wfjt.admin_role.members.add(org_admin) + label = wfjt.labels.create(name='dev-label', organization=organization) + # delete label + url = reverse('api:workflow_job_template_label_list', kwargs={'pk': wfjt.pk}) + data = { + "id": label.pk, + "disassociate": True + } + post(url, data, org_admin, expect=204) + results = get(url, org_admin, expect=200) + assert results.data['count'] == 0 diff --git a/awx/main/tests/functional/test_licenses.py b/awx/main/tests/functional/test_licenses.py index 6c34321f8d22..757349ee1360 100644 --- a/awx/main/tests/functional/test_licenses.py +++ b/awx/main/tests/functional/test_licenses.py @@ -1,6 +1,5 @@ import glob -import json import os from django.conf import settings @@ -30,8 +29,7 @@ def find_embedded_source_version(path, name): # Check variations of '-' and '_' in filenames due to python for fname in [name, name.replace('-','_')]: if entry.startswith(fname) and entry.endswith('.tar.gz'): - entry = entry[:-7] - (n, v) = entry.rsplit('-',1) + v = entry.split(name + '-')[1].split('.tar.gz')[0] return v return None @@ -66,28 +64,6 @@ def read_api_requirements(path): ret[name] = { 'name': name, 'version': version} 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('/','-') - 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()) @@ -114,12 +90,9 @@ def remediate_licenses_and_requirements(licenses, requirements): base_dir = settings.BASE_DIR api_licenses = index_licenses('%s/../docs/licenses' % base_dir) - ui_licenses = index_licenses('%s/../docs/licenses/ui' % base_dir) api_requirements = read_api_requirements('%s/../requirements' % base_dir) - ui_requirements = read_ui_requirements('%s/ui' % base_dir) errors = [] - errors += remediate_licenses_and_requirements(ui_licenses, ui_requirements) errors += remediate_licenses_and_requirements(api_licenses, api_requirements) if errors: raise Exception('Included licenses not consistent with requirements:\n%s' % diff --git a/awx/main/tests/functional/test_named_url.py b/awx/main/tests/functional/test_named_url.py index 6ad6512d482d..6482dac3a82f 100644 --- a/awx/main/tests/functional/test_named_url.py +++ b/awx/main/tests/functional/test_named_url.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import pytest from django.core.exceptions import ImproperlyConfigured @@ -26,7 +27,7 @@ def setup_module(module): def teardown_module(module): - # settings_registry will be persistent states unless we explicitly clean them up. + # 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') @@ -58,10 +59,25 @@ def test_organization(get, admin_user): @pytest.mark.django_db def test_job_template(get, admin_user): - test_jt = JobTemplate.objects.create(name='test_jt') + test_org = Organization.objects.create(name='test_org') + test_jt = JobTemplate.objects.create(name='test_jt', organization=test_org) url = reverse('api:job_template_detail', kwargs={'pk': test_jt.pk}) response = get(url, user=admin_user, expect=200) - assert response.data['related']['named_url'].endswith('/test_jt/') + assert response.data['related']['named_url'].endswith('/test_jt++test_org/') + + +@pytest.mark.django_db +def test_job_template_old_way(get, admin_user, mocker): + test_org = Organization.objects.create(name='test_org') + test_jt = JobTemplate.objects.create(name='test_jt ♥', organization=test_org) + url = reverse('api:job_template_detail', kwargs={'pk': test_jt.pk}) + + response = get(url, user=admin_user, expect=200) + new_url = response.data['related']['named_url'] + old_url = '/'.join([url.rsplit('/', 2)[0], test_jt.name, '']) + + assert URLModificationMiddleware._convert_named_url(new_url) == url + assert URLModificationMiddleware._convert_named_url(old_url) == url @pytest.mark.django_db @@ -170,7 +186,11 @@ def test_group(get, admin_user): def test_inventory_source(get, admin_user): test_org = Organization.objects.create(name='test_org') test_inv = Inventory.objects.create(name='test_inv', organization=test_org) - test_source = InventorySource.objects.create(name='test_source', inventory=test_inv) + test_source = InventorySource.objects.create( + name='test_source', + inventory=test_inv, + source='ec2' + ) url = reverse('api:inventory_source_detail', kwargs={'pk': test_source.pk}) response = get(url, user=admin_user, expect=200) assert response.data['related']['named_url'].endswith('/test_source++test_inv++test_org/') @@ -199,3 +219,27 @@ def test_credential(get, admin_user, credentialtype_ssh): url = reverse('api:credential_detail', kwargs={'pk': test_cred.pk}) response = get(url, user=admin_user, expect=200) assert response.data['related']['named_url'].endswith('/test_cred++Machine+ssh++/') + + +@pytest.mark.django_db +def test_403_vs_404(get): + cindy = User.objects.create( + username='cindy', + password='test_user', + is_superuser=False + ) + bob = User.objects.create( + username='bob', + password='test_user', + is_superuser=False + ) + + # bob cannot see cindy, pk lookup should be a 403 + url = reverse('api:user_detail', kwargs={'pk': cindy.pk}) + get(url, user=bob, expect=403) + + # bob cannot see cindy, username lookup should be a 404 + get('/api/v2/users/cindy/', user=bob, expect=404) + + get(f'/api/v2/users/{cindy.pk}/', expect=401) + get('/api/v2/users/cindy/', expect=404) diff --git a/awx/main/tests/functional/test_notifications.py b/awx/main/tests/functional/test_notifications.py index e147445f1822..1c5e46fcda1d 100644 --- a/awx/main/tests/functional/test_notifications.py +++ b/awx/main/tests/functional/test_notifications.py @@ -90,7 +90,7 @@ def test_inherited_notification_templates(get, post, user, organization, project notification_templates.append(response.data['id']) i = Inventory.objects.create(name='test', organization=organization) i.save() - isrc = InventorySource.objects.create(name='test', inventory=i) + isrc = InventorySource.objects.create(name='test', inventory=i, source='ec2') isrc.save() jt = JobTemplate.objects.create(name='test', inventory=i, project=project, playbook='debug.yml') jt.save() diff --git a/awx/main/tests/functional/test_projects.py b/awx/main/tests/functional/test_projects.py index 2106c7d3f78b..ccfbd06627c9 100644 --- a/awx/main/tests/functional/test_projects.py +++ b/awx/main/tests/functional/test_projects.py @@ -29,8 +29,8 @@ def team_project_list(organization_factory): @pytest.mark.django_db def test_get_project_path(project): # Test combining projects root with project local path - with mock.patch('awx.main.models.projects.settings.PROJECTS_ROOT', '/var/lib/awx'): - assert project.get_project_path(check_if_exists=False) == '/var/lib/awx/_92__test_proj' + with mock.patch('awx.main.models.projects.settings.PROJECTS_ROOT', '/var/lib/foo'): + assert project.get_project_path(check_if_exists=False) == f'/var/lib/foo/_{project.id}__test_proj' @pytest.mark.django_db @@ -213,34 +213,14 @@ def test_project_credential_protection(post, put, project, organization, scm_cre }, org_admin, expect=403 ) post( - reverse('api:project_list'), { - 'name': 'should not create', - 'organization':organization.id, + reverse('api:project_list'), { + 'name': 'should not create', + 'organization':organization.id, 'credential': scm_credential.id }, org_admin, expect=403 ) -@pytest.mark.django_db() -def test_create_project_null_organization(post, organization, admin): - post(reverse('api:project_list'), { 'name': 't', 'organization': None}, admin, expect=201) - - -@pytest.mark.django_db() -def test_create_project_null_organization_xfail(post, organization, org_admin): - post(reverse('api:project_list'), { 'name': 't', 'organization': None}, org_admin, expect=403) - - -@pytest.mark.django_db() -def test_patch_project_null_organization(patch, organization, project, admin): - patch(reverse('api:project_detail', kwargs={'pk':project.id,}), { 'name': 't', 'organization': organization.id}, admin, expect=200) - - -@pytest.mark.django_db() -def test_patch_project_null_organization_xfail(patch, project, org_admin): - patch(reverse('api:project_detail', kwargs={'pk':project.id,}), { 'name': 't', 'organization': None}, org_admin, expect=400) - - @pytest.mark.django_db def test_cannot_schedule_manual_project(manual_project, admin_user, post): response = post( diff --git a/awx/main/tests/functional/test_rbac_job.py b/awx/main/tests/functional/test_rbac_job.py index 5dbe797f6ce6..ed3aee47cbd7 100644 --- a/awx/main/tests/functional/test_rbac_job.py +++ b/awx/main/tests/functional/test_rbac_job.py @@ -1,5 +1,7 @@ import pytest +from rest_framework.exceptions import PermissionDenied + from awx.main.access import ( JobAccess, JobLaunchConfigAccess, @@ -19,8 +21,6 @@ Credential ) -from rest_framework.exceptions import PermissionDenied - from crum import impersonate @@ -29,7 +29,8 @@ def normal_job(deploy_jobtemplate): return Job.objects.create( job_template=deploy_jobtemplate, project=deploy_jobtemplate.project, - inventory=deploy_jobtemplate.inventory + inventory=deploy_jobtemplate.inventory, + organization=deploy_jobtemplate.organization ) @@ -170,9 +171,11 @@ def test_job_relaunch_resource_access(self, user, inventory, machine_credential, machine_credential.use_role.members.add(u) access = JobAccess(u) - assert access.can_start(job_with_links, validate_license=False) == can_start, ( - "Inventory access: {}\nCredential access: {}\n Expected access: {}".format(inv_access, cred_access, can_start) - ) + if can_start: + assert access.can_start(job_with_links, validate_license=False) + else: + with pytest.raises(PermissionDenied): + access.can_start(job_with_links, validate_license=False) def test_job_relaunch_credential_access( self, inventory, project, credential, net_credential): @@ -187,7 +190,8 @@ def test_job_relaunch_credential_access( # Job has prompted net credential, launch denied w/ message job = jt.create_unified_job(credentials=[net_credential]) - assert not jt_user.can_access(Job, 'start', job, validate_license=False) + with pytest.raises(PermissionDenied): + jt_user.can_access(Job, 'start', job, validate_license=False) def test_prompted_credential_relaunch_denied( self, inventory, project, net_credential, rando): @@ -200,7 +204,8 @@ def test_prompted_credential_relaunch_denied( # Job has prompted net credential, rando lacks permission to use it job = jt.create_unified_job(credentials=[net_credential]) - assert not rando.can_access(Job, 'start', job, validate_license=False) + with pytest.raises(PermissionDenied): + rando.can_access(Job, 'start', job, validate_license=False) def test_prompted_credential_relaunch_allowed( self, inventory, project, net_credential, rando): diff --git a/awx/main/tests/functional/test_rbac_job_start.py b/awx/main/tests/functional/test_rbac_job_start.py index d205d992d322..6fa34cc87422 100644 --- a/awx/main/tests/functional/test_rbac_job_start.py +++ b/awx/main/tests/functional/test_rbac_job_start.py @@ -1,5 +1,7 @@ import pytest +from rest_framework.exceptions import PermissionDenied + from awx.main.models.inventory import Inventory from awx.main.models.credential import Credential from awx.main.models.jobs import JobTemplate, Job @@ -89,8 +91,8 @@ def test_slice_job(slice_job_factory, rando): @pytest.mark.django_db class TestJobRelaunchAccess: @pytest.fixture - def job_no_prompts(self, machine_credential, inventory): - jt = JobTemplate.objects.create(name='test-job_template', inventory=inventory) + def job_no_prompts(self, machine_credential, inventory, organization): + jt = JobTemplate.objects.create(name='test-job_template', inventory=inventory, organization=organization) jt.credentials.add(machine_credential) return jt.create_unified_job() @@ -119,10 +121,20 @@ def test_normal_relaunch_via_job_template(self, job_no_prompts, rando): job_no_prompts.job_template.execute_role.members.add(rando) assert rando.can_access(Job, 'start', job_no_prompts) + def test_orphan_relaunch_via_organization(self, job_no_prompts, rando, organization): + "JT for job has been deleted, relevant organization roles will allow management" + assert job_no_prompts.organization == organization + organization.execute_role.members.add(rando) + job_no_prompts.job_template.delete() + job_no_prompts.job_template = None # Django should do this for us, but it does not + assert rando.can_access(Job, 'start', job_no_prompts) + def test_no_relaunch_without_prompted_fields_access(self, job_with_prompts, rando): "Has JT execute_role but no use_role on inventory & credential - deny relaunch" job_with_prompts.job_template.execute_role.members.add(rando) - assert not rando.can_access(Job, 'start', job_with_prompts) + with pytest.raises(PermissionDenied) as exc: + rando.can_access(Job, 'start', job_with_prompts) + assert 'Job was launched with prompted fields you do not have access to' in str(exc) def test_can_relaunch_with_prompted_fields_access(self, job_with_prompts, rando): "Has use_role on the prompted inventory & credential - allow relaunch" @@ -141,11 +153,15 @@ def test_no_relaunch_after_limit_change(self, inventory, machine_credential, ran jt.ask_limit_on_launch = False jt.save() jt.execute_role.members.add(rando) - assert not rando.can_access(Job, 'start', job_with_prompts) + with pytest.raises(PermissionDenied): + rando.can_access(Job, 'start', job_with_prompts) def test_can_relaunch_if_limit_was_prompt(self, job_with_prompts, rando): "Job state differs from JT, but only on prompted fields - allow relaunch" job_with_prompts.job_template.execute_role.members.add(rando) job_with_prompts.limit = 'webservers' job_with_prompts.save() - assert not rando.can_access(Job, 'start', job_with_prompts) + job_with_prompts.inventory.use_role.members.add(rando) + for cred in job_with_prompts.credentials.all(): + cred.use_role.members.add(rando) + assert rando.can_access(Job, 'start', job_with_prompts) diff --git a/awx/main/tests/functional/test_rbac_job_templates.py b/awx/main/tests/functional/test_rbac_job_templates.py index 26d3628f9eed..99dc1e22cea1 100644 --- a/awx/main/tests/functional/test_rbac_job_templates.py +++ b/awx/main/tests/functional/test_rbac_job_templates.py @@ -8,8 +8,7 @@ ScheduleAccess ) from awx.main.models.jobs import JobTemplate -from awx.main.models.organization import Organization -from awx.main.models.schedules import Schedule +from awx.main.models import Project, Organization, Inventory, Schedule, User @mock.patch.object(BaseAccess, 'check_license', return_value=None) @@ -24,6 +23,29 @@ def test_job_template_access_superuser(check_license, user, deploy_jobtemplate): assert access.can_add({}) +@pytest.mark.django_db +class TestImplicitAccess: + def test_org_execute(self, jt_linked, rando): + assert rando not in jt_linked.execute_role + jt_linked.organization.execute_role.members.add(rando) + assert rando in jt_linked.execute_role + + def test_org_admin(self, jt_linked, rando): + assert rando not in jt_linked.execute_role + jt_linked.organization.job_template_admin_role.members.add(rando) + assert rando in jt_linked.execute_role + + def test_org_auditor(self, jt_linked, rando): + assert rando not in jt_linked.read_role + jt_linked.organization.auditor_role.members.add(rando) + assert rando in jt_linked.read_role + + def test_deprecated_inventory_read(self, jt_linked, rando): + assert rando not in jt_linked.read_role + jt_linked.inventory.organization.execute_role.members.add(rando) + assert rando in jt_linked.read_role + + @pytest.mark.django_db def test_job_template_access_read_level(jt_linked, rando): ssh_cred = jt_linked.machine_credential @@ -44,23 +66,37 @@ def test_job_template_access_read_level(jt_linked, rando): @pytest.mark.django_db -def test_job_template_access_use_level(jt_linked, rando): - ssh_cred = jt_linked.machine_credential - vault_cred = jt_linked.vault_credentials[0] +def test_project_use_access(project, rando): + project.use_role.members.add(rando) + access = JobTemplateAccess(rando) + assert access.can_add(None) + assert access.can_add({'project': project.id, 'ask_inventory_on_launch': True}) + project2 = Project.objects.create( + name='second-project', scm_type=project.scm_type, playbook_files=project.playbook_files, + organization=project.organization, + ) + project2.use_role.members.add(rando) + jt = JobTemplate.objects.create(project=project, ask_inventory_on_launch=True) + jt.admin_role.members.add(rando) + assert access.can_change(jt, {'project': project2.pk}) + +@pytest.mark.django_db +def test_job_template_access_use_level(jt_linked, rando): access = JobTemplateAccess(rando) jt_linked.project.use_role.members.add(rando) jt_linked.inventory.use_role.members.add(rando) - ssh_cred.use_role.members.add(rando) - vault_cred.use_role.members.add(rando) - + jt_linked.admin_role.members.add(rando) proj_pk = jt_linked.project.pk - assert access.can_add(dict(inventory=jt_linked.inventory.pk, project=proj_pk)) - assert access.can_add(dict(credential=ssh_cred.pk, project=proj_pk)) - assert access.can_add(dict(vault_credential=vault_cred.pk, project=proj_pk)) + + assert access.can_change(jt_linked, {'job_type': 'check', 'project': proj_pk}) + assert access.can_change(jt_linked, {'job_type': 'check', 'inventory': None}) for cred in jt_linked.credentials.all(): - assert not access.can_unattach(jt_linked, cred, 'credentials', {}) + assert access.can_unattach(jt_linked, cred, 'credentials', {}) + + assert access.can_add(dict(inventory=jt_linked.inventory.pk, project=proj_pk)) + assert access.can_add(dict(project=proj_pk)) @pytest.mark.django_db @@ -69,22 +105,20 @@ def test_job_template_access_admin(role_names, jt_linked, rando): ssh_cred = jt_linked.machine_credential access = JobTemplateAccess(rando) - # Appoint this user as admin of the organization - #jt_linked.inventory.organization.admin_role.members.add(rando) + assert not access.can_read(jt_linked) assert not access.can_delete(jt_linked) + # Appoint this user to the org role + organization = jt_linked.organization for role_name in role_names: - role = getattr(jt_linked.inventory.organization, role_name) - role.members.add(rando) + getattr(organization, role_name).members.add(rando) # Assign organization permission in the same way the create view does - organization = jt_linked.inventory.organization ssh_cred.admin_role.parents.add(organization.admin_role) proj_pk = jt_linked.project.pk assert access.can_add(dict(inventory=jt_linked.inventory.pk, project=proj_pk)) - assert access.can_add(dict(credential=ssh_cred.pk, project=proj_pk)) for cred in jt_linked.credentials.all(): assert access.can_unattach(jt_linked, cred, 'credentials', {}) @@ -94,7 +128,7 @@ def test_job_template_access_admin(role_names, jt_linked, rando): @pytest.mark.django_db -def test_job_template_extra_credentials_prompts_access( +def test_job_template_credentials_prompts_access( rando, post, inventory, project, machine_credential, vault_credential): jt = JobTemplate.objects.create( name = 'test-jt', @@ -105,24 +139,24 @@ def test_job_template_extra_credentials_prompts_access( ) jt.credentials.add(machine_credential) jt.execute_role.members.add(rando) - r = post( + post( reverse('api:job_template_launch', kwargs={'pk': jt.id}), - {'credentials': [machine_credential.pk, vault_credential.pk]}, rando + {'credentials': [machine_credential.pk, vault_credential.pk]}, rando, + expect=403 ) - assert r.status_code == 403 @pytest.mark.django_db class TestJobTemplateCredentials: - def test_job_template_cannot_add_extra_credentials(self, job_template, credential, rando): + def test_job_template_cannot_add_credentials(self, job_template, credential, rando): job_template.admin_role.members.add(rando) credential.read_role.members.add(rando) # without permission to credential, user can not attach it assert not JobTemplateAccess(rando).can_attach( job_template, credential, 'credentials', {}) - def test_job_template_can_add_extra_credentials(self, job_template, credential, rando): + def test_job_template_can_add_credentials(self, job_template, credential, rando): job_template.admin_role.members.add(rando) credential.use_role.members.add(rando) # user has permission to apply credential @@ -148,26 +182,39 @@ 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, rando, post): - - project.admin_role.members.add(rando) - with mock.patch( - 'awx.main.models.projects.ProjectOptions.playbooks', - new_callable=mock.PropertyMock(return_value=['helloworld.yml'])): - response = post(reverse('api:job_template_list'), dict( - name='newly-created-jt', - job_type='run', - ask_inventory_on_launch=True, - ask_credential_on_launch=True, - project=project.pk, - playbook='helloworld.yml' - ), rando) +def test_job_template_creator_access(project, organization, rando, post): + project.use_role.members.add(rando) + response = post(url=reverse('api:job_template_list'), data=dict( + name='newly-created-jt', + ask_inventory_on_launch=True, + project=project.pk, + playbook='helloworld.yml' + ), user=rando, expect=201) - assert response.status_code == 201 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 + assert rando in jt_obj.admin_role.members.all() + + +@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): + if lacking != 'project': + project.use_role.members.add(rando) + else: + project.read_role.members.add(rando) + if lacking != 'inventory': + inventory.use_role.members.add(rando) + else: + inventory.read_role.members.add(rando) + 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) @pytest.mark.django_db @@ -237,27 +284,104 @@ def test_prompts_access_checked(self, job_template, inventory, credential, rando @pytest.mark.django_db -def test_jt_org_ownership_change(user, jt_linked): - admin1 = user('admin1') - org1 = jt_linked.project.organization - org1.admin_role.members.add(admin1) - a1_access = JobTemplateAccess(admin1) - - assert a1_access.can_read(jt_linked) - - - admin2 = user('admin2') - org2 = Organization.objects.create(name='mrroboto', description='domo') - org2.admin_role.members.add(admin2) - a2_access = JobTemplateAccess(admin2) - - assert not a2_access.can_read(jt_linked) +class TestProjectOrganization: + """Tests stories related to management of JT organization via its project + which have some bearing on RBAC integrity + """ + + def test_new_project_org_change(self, project, patch, admin_user): + org2 = Organization.objects.create(name='bar') + patch( + url=project.get_absolute_url(), + data={'organization': org2.id}, + user=admin_user, + expect=200 + ) + assert Project.objects.get(pk=project.id).organization_id == org2.id + + def test_jt_org_cannot_change(self, project, post, patch, admin_user): + post( + url=reverse('api:job_template_list'), + data={ + 'name': 'foo_template', + 'project': project.id, + 'playbook': 'helloworld.yml', + 'ask_inventory_on_launch': True + }, + user=admin_user, + expect=201 + ) + org2 = Organization.objects.create(name='bar') + r = patch( + url=project.get_absolute_url(), + data={'organization': org2.id}, + user=admin_user, + expect=400 + ) + assert 'Organization cannot be changed' in str(r.data) + def test_orphan_JT_adoption(self, project, patch, admin_user, org_admin): + jt = JobTemplate.objects.create( + name='bar', + ask_inventory_on_launch=True, + playbook='helloworld.yml' + ) + 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_linked.project.organization = org2 - jt_linked.project.save() - jt_linked.inventory.organization = org2 - jt_linked.inventory.save() + jt = JobTemplate.objects.create(name='foo', inventory=inv) + assert admins[0] in jt.read_role + assert admins[1] not in jt.read_role - assert a2_access.can_read(jt_linked) - assert not a1_access.can_read(jt_linked) + 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/test_rbac_label.py index 955894c06f21..ed819df9f049 100644 --- a/awx/main/tests/functional/test_rbac_label.py +++ b/awx/main/tests/functional/test_rbac_label.py @@ -20,8 +20,19 @@ def test_label_get_queryset_su(label, user): @pytest.mark.django_db -def test_label_access(label, user): +def test_label_read_access(label, user): access = LabelAccess(user('user', False)) + assert not access.can_read(label) + label.organization.member_role.members.add(user('user', False)) + assert access.can_read(label) + + +@pytest.mark.django_db +def test_label_jt_read_access(label, user, job_template): + access = LabelAccess(user('user', False)) + assert not access.can_read(label) + job_template.read_role.members.add(user('user', False)) + job_template.labels.add(label) assert access.can_read(label) diff --git a/awx/main/tests/functional/test_rbac_migration.py b/awx/main/tests/functional/test_rbac_migration.py new file mode 100644 index 000000000000..2f8e72b73bb2 --- /dev/null +++ b/awx/main/tests/functional/test_rbac_migration.py @@ -0,0 +1,105 @@ +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_role.py b/awx/main/tests/functional/test_rbac_role.py index 838a410d5836..e308d1a6ea8f 100644 --- a/awx/main/tests/functional/test_rbac_role.py +++ b/awx/main/tests/functional/test_rbac_role.py @@ -60,6 +60,8 @@ def test_org_user_role_attach(user, organization, inventory): ''' admin = user('admin') nonmember = user('nonmember') + other_org = Organization.objects.create(name="other_org") + other_org.member_role.members.add(nonmember) inventory.admin_role.members.add(nonmember) organization.admin_role.members.add(admin) @@ -186,13 +188,17 @@ def test_need_all_orgs_to_admin_user(user): # Orphaned user can be added to member role, only in special cases @pytest.mark.django_db -def test_orphaned_user_allowed(org_admin, rando, organization): +def test_orphaned_user_allowed(org_admin, rando, organization, org_credential): ''' We still allow adoption of orphaned* users by assigning them to organization member role, but only in the situation where the org admin already posesses indirect access to all of the user's roles *orphaned means user is not a member of any organization ''' + # give a descendent role to rando, to trigger the conditional + # where all ancestor roles of rando should be in the set of + # org_admin roles. + org_credential.admin_role.members.add(rando) role_access = RoleAccess(org_admin) org_access = OrganizationAccess(org_admin) assert role_access.can_attach(organization.member_role, rando, 'members', None) diff --git a/awx/main/tests/functional/test_rbac_user.py b/awx/main/tests/functional/test_rbac_user.py index ca3d268b1872..b62a0db25f83 100644 --- a/awx/main/tests/functional/test_rbac_user.py +++ b/awx/main/tests/functional/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 +from awx.main.models import User, Organization, Inventory, Role class TestSysAuditorTransactional(TransactionTestCase): @@ -170,4 +170,34 @@ 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) - \ No newline at end of file + + +@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/test_rbac_workflow.py index a53d7693246d..6e9208235858 100644 --- a/awx/main/tests/functional/test_rbac_workflow.py +++ b/awx/main/tests/functional/test_rbac_workflow.py @@ -62,10 +62,11 @@ def test_org_workflow_admin_role_inheritance(self, wfjt, org_member): @pytest.mark.django_db class TestWorkflowJobTemplateNodeAccess: - def test_no_jt_access_to_edit(self, wfjt_node, org_admin): + def test_no_jt_access_to_edit(self, wfjt_node, rando): # without access to the related job template, admin to the WFJT can # not change the prompted parameters - access = WorkflowJobTemplateNodeAccess(org_admin) + wfjt_node.workflow_job_template.admin_role.members.add(rando) + access = WorkflowJobTemplateNodeAccess(rando) assert not access.can_change(wfjt_node, {'job_type': 'check'}) def test_node_edit_allowed(self, wfjt_node, org_admin): diff --git a/awx/main/tests/functional/test_tasks.py b/awx/main/tests/functional/test_tasks.py index f1cf382a7c0f..c7bc50c8d28d 100644 --- a/awx/main/tests/functional/test_tasks.py +++ b/awx/main/tests/functional/test_tasks.py @@ -30,7 +30,7 @@ class TestDependentInventoryUpdate: def test_dependent_inventory_updates_is_called(self, scm_inventory_source, scm_revision_file): task = RunProjectUpdate() task.revision_path = scm_revision_file - proj_update = ProjectUpdate.objects.create(project=scm_inventory_source.source_project) + proj_update = scm_inventory_source.source_project.create_project_update() with mock.patch.object(RunProjectUpdate, '_update_dependent_inventories') as inv_update_mck: with mock.patch.object(RunProjectUpdate, 'release_lock'): task.post_run_hook(proj_update, 'successful') @@ -39,7 +39,7 @@ def test_dependent_inventory_updates_is_called(self, scm_inventory_source, scm_r def test_no_unwanted_dependent_inventory_updates(self, project, scm_revision_file): task = RunProjectUpdate() task.revision_path = scm_revision_file - proj_update = ProjectUpdate.objects.create(project=project) + proj_update = project.create_project_update() with mock.patch.object(RunProjectUpdate, '_update_dependent_inventories') as inv_update_mck: with mock.patch.object(RunProjectUpdate, 'release_lock'): task.post_run_hook(proj_update, 'successful') diff --git a/awx/main/tests/unit/analytics/test_broadcast_websocket.py b/awx/main/tests/unit/analytics/test_broadcast_websocket.py new file mode 100644 index 000000000000..6edfe51b9240 --- /dev/null +++ b/awx/main/tests/unit/analytics/test_broadcast_websocket.py @@ -0,0 +1,69 @@ +import datetime + +from awx.main.analytics.broadcast_websocket import FixedSlidingWindow +from awx.main.analytics.broadcast_websocket import dt_to_seconds + + +class TestFixedSlidingWindow(): + + def ts(self, **kwargs): + e = { + 'year': 1985, + 'month': 1, + 'day': 1, + 'hour': 1, + } + return dt_to_seconds(datetime.datetime(**kwargs, **e)) + + def test_record_same_minute(self): + """ + Legend: + - = record() + ^ = render() + |---| = 1 minute, 60 seconds + + .................... + |------------------------------------------------------------| + ^^^^^^^^^^^^^^^^^^^^ + """ + + fsw = FixedSlidingWindow(self.ts(minute=0, second=0, microsecond=0)) + for i in range(20): + fsw.record(self.ts(minute=0, second=i, microsecond=0)) + assert (i + 1) == fsw.render(self.ts(minute=0, second=i, microsecond=0)) + + + def test_record_same_minute_render_diff_minute(self): + """ + Legend: + - = record() + ^ = render() + |---| = 1 minute, 60 seconds + + .................... + |------------------------------------------------------------| + ^^ ^ + AB C + |------------------------------------------------------------| + ^^^^^^^^^^^^^^^^^^^^^ + DEEEEEEEEEEEEEEEEEEEF + """ + + fsw = FixedSlidingWindow(self.ts(minute=0, second=0, microsecond=0)) + for i in range(20): + fsw.record(self.ts(minute=0, second=i, microsecond=0)) + + assert 20 == fsw.render(self.ts(minute=0, second=19, microsecond=0)), \ + "A. The second of the last record() call" + assert 20 == fsw.render(self.ts(minute=0, second=20, microsecond=0)), \ + "B. The second after the last record() call" + assert 20 == fsw.render(self.ts(minute=0, second=59, microsecond=0)), \ + "C. Last second in the same minute that all record() called in" + assert 20 == fsw.render(self.ts(minute=1, second=0, microsecond=0)), \ + "D. First second of the minute following the minute that all record() calls in" + for i in range(20): + 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" 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 4c0751ffbeb3..00ba8639878e 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 @@ -30,6 +30,8 @@ def job_template(mocker): mock_jt.host_config_key = '9283920492' mock_jt.validation_errors = mock_JT_resource_data mock_jt.webhook_service = '' + mock_jt.organization_id = None + mock_jt.webhook_credential_id = None return mock_jt diff --git a/awx/main/tests/unit/api/test_filters.py b/awx/main/tests/unit/api/test_filters.py index 913413a35f55..4a951890e750 100644 --- a/awx/main/tests/unit/api/test_filters.py +++ b/awx/main/tests/unit/api/test_filters.py @@ -57,7 +57,7 @@ def test_empty_in(empty_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) + value, new_lookup, _ = field_lookup.value_to_python(JobTemplate, 'project__name__in', valid_value) assert 'foo' in value diff --git a/awx/main/tests/unit/api/test_logger.py b/awx/main/tests/unit/api/test_logger.py new file mode 100644 index 000000000000..95ee8f9a98a1 --- /dev/null +++ b/awx/main/tests/unit/api/test_logger.py @@ -0,0 +1,185 @@ +import pytest + +from django.conf import settings + +from awx.main.utils.external_logging import construct_rsyslog_conf_template +from awx.main.tests.functional.api.test_settings import _mock_logging_defaults + +''' +# Example User Data +data_logstash = { + "LOG_AGGREGATOR_TYPE": "logstash", + "LOG_AGGREGATOR_HOST": "localhost", + "LOG_AGGREGATOR_PORT": 8080, + "LOG_AGGREGATOR_PROTOCOL": "tcp", + "LOG_AGGREGATOR_USERNAME": "logger", + "LOG_AGGREGATOR_PASSWORD": "mcstash" +} + +data_netcat = { + "LOG_AGGREGATOR_TYPE": "other", + "LOG_AGGREGATOR_HOST": "localhost", + "LOG_AGGREGATOR_PORT": 9000, + "LOG_AGGREGATOR_PROTOCOL": "udp", +} + +data_loggly = { + "LOG_AGGREGATOR_TYPE": "loggly", + "LOG_AGGREGATOR_HOST": "http://logs-01.loggly.com/inputs/1fd38090-2af1-4e1e-8d80-492899da0f71/tag/http/", + "LOG_AGGREGATOR_PORT": 8080, + "LOG_AGGREGATOR_PROTOCOL": "https" +} +''' + + +# Test reconfigure logging settings function +# name this whatever you want +@pytest.mark.parametrize( + 'enabled, log_type, host, port, protocol, expected_config', [ + ( + True, + 'loggly', + 'http://logs-01.loggly.com/inputs/1fd38090-2af1-4e1e-8d80-492899da0f71/tag/http/', + None, + 'https', + '\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" errorfile="/var/log/tower/rsyslog.err" action.resumeInterval="5" restpath="inputs/1fd38090-2af1-4e1e-8d80-492899da0f71/tag/http/")', # noqa + ]) + ), + ( + True, # localhost w/ custom UDP port + 'other', + 'localhost', + 9000, + 'udp', + '\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 + ]) + ), + ( + True, # localhost w/ custom TCP port + 'other', + 'localhost', + 9000, + 'tcp', + '\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 + ]) + ), + ( + True, # https, default port 443 + 'splunk', + 'https://yoursplunk/services/collector/event', + None, + None, + '\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" errorfile="/var/log/tower/rsyslog.err" action.resumeInterval="5" restpath="services/collector/event")', # noqa + ]) + ), + ( + True, # http, default port 80 + 'splunk', + 'http://yoursplunk/services/collector/event', + None, + None, + '\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" errorfile="/var/log/tower/rsyslog.err" action.resumeInterval="5" restpath="services/collector/event")', # noqa + ]) + ), + ( + True, # https, custom port in URL string + 'splunk', + 'https://yoursplunk:8088/services/collector/event', + None, + None, + '\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" errorfile="/var/log/tower/rsyslog.err" action.resumeInterval="5" restpath="services/collector/event")', # noqa + ]) + ), + ( + True, # https, custom port explicitly specified + 'splunk', + 'https://yoursplunk/services/collector/event', + 8088, + None, + '\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" errorfile="/var/log/tower/rsyslog.err" action.resumeInterval="5" restpath="services/collector/event")', # noqa + ]) + ), + ( + True, # no scheme specified in URL, default to https, respect custom port + 'splunk', + 'yoursplunk.org/services/collector/event', + 8088, + 'https', + '\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" errorfile="/var/log/tower/rsyslog.err" action.resumeInterval="5" restpath="services/collector/event")', # noqa + ]) + ), + ( + True, # respect custom http-only port + 'splunk', + 'http://yoursplunk.org/services/collector/event', + 8088, + None, + '\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" errorfile="/var/log/tower/rsyslog.err" action.resumeInterval="5" restpath="services/collector/event")', # noqa + ]) + ), + ( + True, # valid sumologic config + 'sumologic', + 'https://endpoint5.collection.us2.sumologic.com/receiver/v1/http/ZaVnC4dhaV0qoiETY0MrM3wwLoDgO1jFgjOxE6-39qokkj3LGtOroZ8wNaN2M6DtgYrJZsmSi4-36_Up5TbbN_8hosYonLKHSSOSKY845LuLZBCBwStrHQ==', # noqa + None, + 'https', + '\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" errorfile="/var/log/tower/rsyslog.err" action.resumeInterval="5" restpath="receiver/v1/http/ZaVnC4dhaV0qoiETY0MrM3wwLoDgO1jFgjOxE6-39qokkj3LGtOroZ8wNaN2M6DtgYrJZsmSi4-36_Up5TbbN_8hosYonLKHSSOSKY845LuLZBCBwStrHQ==")', # noqa + ]) + ), + ] +) +def test_rsyslog_conf_template(enabled, log_type, host, port, protocol, expected_config): + + mock_settings, _ = _mock_logging_defaults() + + # Set test settings + logging_defaults = getattr(settings, 'LOGGING') + setattr(mock_settings, 'LOGGING', logging_defaults) + setattr(mock_settings, 'LOGGING["handlers"]["external_logger"]["address"]', '/var/run/awx-rsyslog/rsyslog.sock') + setattr(mock_settings, 'LOG_AGGREGATOR_ENABLED', enabled) + setattr(mock_settings, 'LOG_AGGREGATOR_TYPE', log_type) + setattr(mock_settings, 'LOG_AGGREGATOR_HOST', host) + if port: + setattr(mock_settings, 'LOG_AGGREGATOR_PORT', port) + if protocol: + setattr(mock_settings, 'LOG_AGGREGATOR_PROTOCOL', protocol) + + # create rsyslog conf template + tmpl = construct_rsyslog_conf_template(mock_settings) + + # check validity of created template + assert expected_config in tmpl + + +def test_splunk_auth(): + mock_settings, _ = _mock_logging_defaults() + # Set test settings + logging_defaults = getattr(settings, 'LOGGING') + setattr(mock_settings, 'LOGGING', logging_defaults) + setattr(mock_settings, 'LOG_AGGREGATOR_ENABLED', True) + setattr(mock_settings, 'LOG_AGGREGATOR_TYPE', 'splunk') + setattr(mock_settings, 'LOG_AGGREGATOR_HOST', 'example.org') + setattr(mock_settings, 'LOG_AGGREGATOR_PASSWORD', 'SECRET-TOKEN') + + tmpl = construct_rsyslog_conf_template(mock_settings) + assert 'httpheaderkey="Authorization" httpheadervalue="Splunk SECRET-TOKEN"' in tmpl diff --git a/awx/main/tests/unit/commands/test_inventory_import.py b/awx/main/tests/unit/commands/test_inventory_import.py index 105086bcb813..db3e01408b2d 100644 --- a/awx/main/tests/unit/commands/test_inventory_import.py +++ b/awx/main/tests/unit/commands/test_inventory_import.py @@ -33,32 +33,6 @@ def test_invalid_options_name_and_id(self): assert 'inventory-id' in str(err.value) assert 'exclusive' in str(err.value) - def test_invalid_options_id_and_keep_vars(self): - # You can't overwrite and keep_vars at the same time, that wouldn't make sense - cmd = Command() - with pytest.raises(CommandError) as err: - cmd.handle( - inventory_id=42, overwrite=True, keep_vars=True - ) - assert 'overwrite-vars' in str(err.value) - assert 'exclusive' in str(err.value) - - def test_invalid_options_id_but_no_source(self): - # Need a source to import - cmd = Command() - with pytest.raises(CommandError) as err: - cmd.handle( - inventory_id=42, overwrite=True, keep_vars=True - ) - assert 'overwrite-vars' in str(err.value) - assert 'exclusive' in str(err.value) - with pytest.raises(CommandError) as err: - cmd.handle( - inventory_id=42, overwrite_vars=True, keep_vars=True - ) - assert 'overwrite-vars' in str(err.value) - assert 'exclusive' in str(err.value) - def test_invalid_options_missing_source(self): cmd = Command() with pytest.raises(CommandError) as err: diff --git a/awx/main/tests/unit/models/test_ha.py b/awx/main/tests/unit/models/test_ha.py index 0e29caf8aa15..2534acfd15f1 100644 --- a/awx/main/tests/unit/models/test_ha.py +++ b/awx/main/tests/unit/models/test_ha.py @@ -45,19 +45,14 @@ class TestInstanceGroup(object): (T(100), Is([50, 0, 20, 99, 11, 1, 5, 99]), None, "The task don't a fit, you must a quit!"), ]) def test_fit_task_to_most_remaining_capacity_instance(self, task, instances, instance_fit_index, reason): - with mock.patch.object(InstanceGroup, - 'instances', - Mock(spec_set=['filter'], - filter=lambda *args, **kargs: Mock(spec_set=['order_by'], - order_by=lambda x: instances))): - ig = InstanceGroup(id=10) + ig = InstanceGroup(id=10) - if instance_fit_index is None: - assert ig.fit_task_to_most_remaining_capacity_instance(task) is None, reason - else: - assert ig.fit_task_to_most_remaining_capacity_instance(task) == \ - instances[instance_fit_index], reason + instance_picked = ig.fit_task_to_most_remaining_capacity_instance(task, instances) + if instance_fit_index is None: + assert instance_picked is None, reason + else: + assert instance_picked == instances[instance_fit_index], reason @pytest.mark.parametrize('instances,instance_fit_index,reason', [ (Is([(0, 100)]), 0, "One idle instance, pick it"), @@ -70,16 +65,12 @@ def test_find_largest_idle_instance(self, instances, instance_fit_index, reason) def filter_offline_instances(*args): return filter(lambda i: i.capacity > 0, instances) - with mock.patch.object(InstanceGroup, - 'instances', - Mock(spec_set=['filter'], - filter=lambda *args, **kargs: Mock(spec_set=['order_by'], - order_by=filter_offline_instances))): - ig = InstanceGroup(id=10) + ig = InstanceGroup(id=10) + instances_online_only = filter_offline_instances(instances) - if instance_fit_index is None: - assert ig.find_largest_idle_instance() is None, reason - else: - assert ig.find_largest_idle_instance() == \ - instances[instance_fit_index], reason + if instance_fit_index is None: + assert ig.find_largest_idle_instance(instances_online_only) is None, reason + else: + assert ig.find_largest_idle_instance(instances_online_only) == \ + instances[instance_fit_index], reason diff --git a/awx/main/tests/unit/models/test_jobs.py b/awx/main/tests/unit/models/test_jobs.py index b8964a94f8a2..f28691f50010 100644 --- a/awx/main/tests/unit/models/test_jobs.py +++ b/awx/main/tests/unit/models/test_jobs.py @@ -89,6 +89,27 @@ def test_finish_job_fact_cache_with_existing_data(job, hosts, inventory, mocker, hosts[1].save.assert_called_once_with() +def test_finish_job_fact_cache_with_malformed_fact(job, hosts, inventory, mocker, tmpdir): + fact_cache = os.path.join(tmpdir, 'facts') + modified_times = {} + job.start_job_fact_cache(fact_cache, modified_times, 0) + + for h in hosts: + h.save = mocker.Mock() + + for h in hosts: + filepath = os.path.join(fact_cache, h.name) + with open(filepath, 'w') as f: + json.dump({'ansible_local': {'insights': 'this is an unexpected error from ansible'}}, f) + new_modification_time = time.time() + 3600 + os.utime(filepath, (new_modification_time, new_modification_time)) + + job.finish_job_fact_cache(fact_cache, modified_times) + + for h in hosts: + assert h.insights_system_id is None + + def test_finish_job_fact_cache_with_bad_data(job, hosts, inventory, mocker, tmpdir): fact_cache = os.path.join(tmpdir, 'facts') modified_times = {} 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 328b695371e6..a3f9123f379f 100644 --- a/awx/main/tests/unit/models/test_unified_job_unit.py +++ b/awx/main/tests/unit/models/test_unified_job_unit.py @@ -6,6 +6,7 @@ UnifiedJobTemplate, WorkflowJob, WorkflowJobNode, + WorkflowApprovalTemplate, Job, User, Project, @@ -65,6 +66,16 @@ def test_cancel_job_explanation(unified_job): unified_job.save.assert_called_with(update_fields=['cancel_flag', 'start_args', 'status', 'job_explanation']) +def test_organization_copy_to_jobs(): + ''' + All unified job types should infer their organization from their template organization + ''' + for cls in UnifiedJobTemplate.__subclasses__(): + if cls is WorkflowApprovalTemplate: + continue # these do not track organization + assert 'organization' in cls._get_unified_job_field_names(), cls + + def test_log_representation(): ''' Common representation used inside of log messages diff --git a/awx/main/tests/unit/models/test_workflow_unit.py b/awx/main/tests/unit/models/test_workflow_unit.py index 5f53d97a0baf..83ce1a58b131 100644 --- a/awx/main/tests/unit/models/test_workflow_unit.py +++ b/awx/main/tests/unit/models/test_workflow_unit.py @@ -171,12 +171,14 @@ def test_create_no_prompts(self, wfjt_node_no_prompts, workflow_job_unit, mocker 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) + workflow_job=workflow_job_unit, + identifier=mocker.ANY) def test_create_with_prompts(self, wfjt_node_with_prompts, workflow_job_unit, credential, mocker): mock_create = mocker.MagicMock() @@ -185,12 +187,14 @@ def test_create_with_prompts(self, wfjt_node_with_prompts, workflow_job_unit, cr 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) + workflow_job=workflow_job_unit, + identifier=mocker.ANY) @mock.patch('awx.main.models.workflow.WorkflowNodeBase.get_parent_nodes', lambda self: []) diff --git a/awx/main/tests/unit/notifications/test_grafana.py b/awx/main/tests/unit/notifications/test_grafana.py index f78d2b5c65c3..e243238b8445 100644 --- a/awx/main/tests/unit/notifications/test_grafana.py +++ b/awx/main/tests/unit/notifications/test_grafana.py @@ -18,7 +18,7 @@ def test_send_messages(): requests_mock.post.assert_called_once_with( 'https://example.com/api/annotations', headers={'Content-Type': 'application/json', 'Authorization': 'Bearer testapikey'}, - json={'tags': [], 'text': 'test subject', 'isRegion': True, 'timeEnd': 120000, 'panelId': None, 'time': 60000, 'dashboardId': None}, + json={'text': 'test subject', 'isRegion': True, 'timeEnd': 120000, 'panelId': None, 'time': 60000, 'dashboardId': None}, verify=True) assert sent_messages == 1 @@ -36,7 +36,7 @@ def test_send_messages_with_no_verify_ssl(): requests_mock.post.assert_called_once_with( 'https://example.com/api/annotations', headers={'Content-Type': 'application/json', 'Authorization': 'Bearer testapikey'}, - json={'tags': [], 'text': 'test subject', 'isRegion': True, 'timeEnd': 120000, 'panelId': None,'time': 60000, 'dashboardId': None}, + json={'text': 'test subject', 'isRegion': True, 'timeEnd': 120000, 'panelId': None,'time': 60000, 'dashboardId': None}, verify=False) assert sent_messages == 1 @@ -54,7 +54,7 @@ def test_send_messages_with_dashboardid(): requests_mock.post.assert_called_once_with( 'https://example.com/api/annotations', headers={'Content-Type': 'application/json', 'Authorization': 'Bearer testapikey'}, - json={'tags': [], 'text': 'test subject', 'isRegion': True, 'timeEnd': 120000, 'panelId': None, 'time': 60000, 'dashboardId': 42}, + json={'text': 'test subject', 'isRegion': True, 'timeEnd': 120000, 'panelId': None, 'time': 60000, 'dashboardId': 42}, verify=True) assert sent_messages == 1 @@ -72,7 +72,7 @@ def test_send_messages_with_panelid(): requests_mock.post.assert_called_once_with( 'https://example.com/api/annotations', headers={'Content-Type': 'application/json', 'Authorization': 'Bearer testapikey'}, - json={'tags': [], 'text': 'test subject', 'isRegion': True, 'timeEnd': 120000, 'panelId': 42, 'time': 60000, 'dashboardId': None}, + json={'text': 'test subject', 'isRegion': True, 'timeEnd': 120000, 'panelId': 42, 'time': 60000, 'dashboardId': None}, verify=True) assert sent_messages == 1 @@ -90,7 +90,7 @@ def test_send_messages_with_bothids(): requests_mock.post.assert_called_once_with( 'https://example.com/api/annotations', headers={'Content-Type': 'application/json', 'Authorization': 'Bearer testapikey'}, - json={'tags': [], 'text': 'test subject', 'isRegion': True, 'timeEnd': 120000, 'panelId': 42, 'time': 60000, 'dashboardId': 42}, + json={'text': 'test subject', 'isRegion': True, 'timeEnd': 120000, 'panelId': 42, 'time': 60000, 'dashboardId': 42}, verify=True) assert sent_messages == 1 diff --git a/awx/main/tests/unit/scheduler/test_dag_workflow.py b/awx/main/tests/unit/scheduler/test_dag_workflow.py index 7f85ba2f85ae..aaec50191a36 100644 --- a/awx/main/tests/unit/scheduler/test_dag_workflow.py +++ b/awx/main/tests/unit/scheduler/test_dag_workflow.py @@ -19,6 +19,7 @@ def __init__(self, id=None, job=None, do_not_run=False, unified_job_template=Non self.job = job self.do_not_run = do_not_run self.unified_job_template = unified_job_template + self.all_parents_must_converge = False @pytest.fixture @@ -94,7 +95,7 @@ def test_mark_dnr_nodes(self, workflow_dag_1): (g, nodes) = workflow_dag_1 r''' - S0 + 0 /\ S / \ / \ @@ -113,7 +114,7 @@ def test_mark_dnr_nodes(self, workflow_dag_1): assert 0 == len(do_not_run_nodes) r''' - S0 + 0 /\ S / \ / \ @@ -133,6 +134,260 @@ def test_mark_dnr_nodes(self, workflow_dag_1): assert nodes[3] == do_not_run_nodes[0] +class TestAllWorkflowNodes(): + # test workflow convergence is functioning as expected + @pytest.fixture + def simple_all_convergence(self, wf_node_generator): + g = WorkflowDAG() + nodes = [wf_node_generator() for i in range(4)] + for n in nodes: + g.add_node(n) + + r''' + 0 + /\ + S / \ S + / \ + 1 2 + \ / + F \ / S + \/ + 3 + + ''' + g.add_edge(nodes[0], nodes[1], "success_nodes") + g.add_edge(nodes[0], nodes[2], "success_nodes") + g.add_edge(nodes[1], nodes[3], "failure_nodes") + g.add_edge(nodes[2], nodes[3], "success_nodes") + nodes[3].all_parents_must_converge = True + nodes[0].job = Job(status='successful') + nodes[1].job = Job(status='failed') + nodes[2].job = Job(status='successful') + return (g, nodes) + + def test_simple_all_convergence(self, 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" + + nodes_to_run = g.bfs_nodes_to_run() + assert 1 == len(nodes_to_run), "Node 3, and only node 3, should be chosen to run" + assert nodes[3] == nodes_to_run[0], "Only node 3 should be chosen to run" + + @pytest.fixture + def workflow_all_converge_1(self, wf_node_generator): + g = WorkflowDAG() + nodes = [wf_node_generator() for i in range(3)] + for n in nodes: + g.add_node(n) + r''' + 0 + |\ F + | \ + S| 1 + | / + |/ A + 2 + ''' + g.add_edge(nodes[0], nodes[1], "failure_nodes") + g.add_edge(nodes[0], nodes[2], "success_nodes") + g.add_edge(nodes[1], nodes[2], "always_nodes") + nodes[2].all_parents_must_converge = True + nodes[0].job = Job(status='successful') + return (g, nodes) + + def test_all_converge_edge_case_1(self, 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" + assert nodes[2] == dnr_nodes[1], "Node 2 should be marked DNR" + + nodes_to_run = g.bfs_nodes_to_run() + assert 0 == len(nodes_to_run), "No nodes should be chosen to run" + + @pytest.fixture + def workflow_all_converge_2(self, wf_node_generator): + """The ordering of _1 and this test, _2, is _slightly_ different. + The hope is that topological sorting results in 2 being processed before 3 + and/or 3 before 2. + """ + g = WorkflowDAG() + nodes = [wf_node_generator() for i in range(3)] + for n in nodes: + g.add_node(n) + r''' + 0 + |\ S + | \ + F| 1 + | / + |/ A + 2 + ''' + g.add_edge(nodes[0], nodes[1], "success_nodes") + g.add_edge(nodes[0], nodes[2], "failure_nodes") + g.add_edge(nodes[1], nodes[2], "always_nodes") + nodes[2].all_parents_must_converge = True + nodes[0].job = Job(status='successful') + return (g, nodes) + + def test_all_converge_edge_case_2(self, 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" + + nodes_to_run = g.bfs_nodes_to_run() + assert 1 == len(nodes_to_run), "Node 2, and only node 2, should be chosen to run" + assert nodes[1] == nodes_to_run[0], "Only node 2 should be chosen to run" + + @pytest.fixture + def workflow_all_converge_will_run(self, wf_node_generator): + g = WorkflowDAG() + nodes = [wf_node_generator() for i in range(4)] + for n in nodes: + g.add_node(n) + r''' + 0 1 2 + S \ F | / S + \ | / + \ | / + \|/ + | + 3 + ''' + g.add_edge(nodes[0], nodes[3], "success_nodes") + g.add_edge(nodes[1], nodes[3], "failure_nodes") + g.add_edge(nodes[2], nodes[3], "success_nodes") + nodes[3].all_parents_must_converge = True + + nodes[0].job = Job(status='successful') + nodes[1].job = Job(status='failed') + nodes[2].job = Job(status='running') + return (g, nodes) + + def test_workflow_all_converge_will_run(self, 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" + + nodes_to_run = g.bfs_nodes_to_run() + assert 0 == len(nodes_to_run), "No nodes should run yet" + + nodes[2].job.status = 'successful' + nodes_to_run = g.bfs_nodes_to_run() + assert 1 == len(nodes_to_run), "1 and only 1 node should want to run" + assert nodes[3] == nodes_to_run[0], "Convergence node should be chosen to run" + + @pytest.fixture + def workflow_all_converge_dnr(self, wf_node_generator): + g = WorkflowDAG() + nodes = [wf_node_generator() for i in range(4)] + for n in nodes: + g.add_node(n) + r''' + 0 1 2 + S \ F | / F + \ | / + \ | / + \|/ + | + 3 + ''' + g.add_edge(nodes[0], nodes[3], "success_nodes") + g.add_edge(nodes[1], nodes[3], "failure_nodes") + g.add_edge(nodes[2], nodes[3], "failure_nodes") + nodes[3].all_parents_must_converge = True + + nodes[0].job = Job(status='successful') + nodes[1].job = Job(status='running') + nodes[2].job = Job(status='failed') + return (g, nodes) + + def test_workflow_all_converge_while_parent_runs(self, 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" + + nodes_to_run = g.bfs_nodes_to_run() + assert 0 == len(nodes_to_run), "No nodes should run yet" + + def test_workflow_all_converge_with_incorrect_parent(self, workflow_all_converge_dnr): + # Another tick of the scheduler + (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" + assert nodes[3] == dnr_nodes[0], "Convergence node should be marked DNR" + + nodes_to_run = g.bfs_nodes_to_run() + assert 0 == len(nodes_to_run), "Convergence node should NOT be chosen to run because it is DNR" + + 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 + nodes[1].job.status = 'failed' + dnr_nodes = g.mark_dnr_nodes() + assert 0 == len(dnr_nodes), "No nodes should be marked DNR" + + nodes_to_run = g.bfs_nodes_to_run() + assert 1 == len(nodes_to_run), "Convergence node should be chosen to run" + + @pytest.fixture + def workflow_all_converge_deep_dnr_tree(self, wf_node_generator): + g = WorkflowDAG() + nodes = [wf_node_generator() for i in range(7)] + for n in nodes: + g.add_node(n) + r''' + 0 1 2 + \ | / + S \ S| / F + \ | / + \|/ + | + 3 + /\ + S / \ S + / \ + 4| | 5 + \ / + S \ / S + \/ + 6 + ''' + g.add_edge(nodes[0], nodes[3], "success_nodes") + g.add_edge(nodes[1], nodes[3], "success_nodes") + g.add_edge(nodes[2], nodes[3], "failure_nodes") + g.add_edge(nodes[3], nodes[4], "success_nodes") + g.add_edge(nodes[3], nodes[5], "success_nodes") + g.add_edge(nodes[4], nodes[6], "success_nodes") + g.add_edge(nodes[5], nodes[6], "success_nodes") + nodes[3].all_parents_must_converge = True + nodes[4].all_parents_must_converge = True + nodes[5].all_parents_must_converge = True + nodes[6].all_parents_must_converge = True + + nodes[0].job = Job(status='successful') + nodes[1].job = Job(status='successful') + nodes[2].job = Job(status='successful') + 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 + dnr_nodes = g.mark_dnr_nodes() + + assert 4 == len(dnr_nodes), "All nodes w/ no jobs should be marked DNR" + assert nodes[3] in dnr_nodes + assert nodes[4] in dnr_nodes + assert nodes[5] in dnr_nodes + assert nodes[6] in dnr_nodes + + nodes_to_run = g.bfs_nodes_to_run() + assert 0 == len(nodes_to_run), "All non-run nodes should be DNR and NOT candidates to run" + + class TestIsWorkflowDone(): @pytest.fixture def workflow_dag_2(self, workflow_dag_1): @@ -212,8 +467,8 @@ def test_workflow_done_and_failed(self, workflow_dag_failed): assert g.is_workflow_done() is True assert g.has_workflow_failed() == \ - (True, smart_text(_("No error handle path for workflow job node(s) [({},{})] workflow job node(s)" - " missing unified job template and error handle path [].").format(nodes[2].id, nodes[2].job.status))) + (True, smart_text(_("No error handling path for workflow job node(s) [({},{})]. Workflow job node(s)" + " missing unified job template and error handling path [].").format(nodes[2].id, nodes[2].job.status))) def test_is_workflow_done_no_unified_job_tempalte_end(self, workflow_dag_failed): (g, nodes) = workflow_dag_failed @@ -222,8 +477,8 @@ def test_is_workflow_done_no_unified_job_tempalte_end(self, workflow_dag_failed) assert g.is_workflow_done() is True assert g.has_workflow_failed() == \ - (True, smart_text(_("No error handle path for workflow job node(s) [] workflow job node(s) missing" - " unified job template and error handle path [{}].").format(nodes[2].id))) + (True, smart_text(_("No error handling path for workflow job node(s) []. Workflow job node(s) missing" + " unified job template and error handling path [{}].").format(nodes[2].id))) def test_is_workflow_done_no_unified_job_tempalte_begin(self, workflow_dag_1): (g, nodes) = workflow_dag_1 @@ -233,22 +488,22 @@ def test_is_workflow_done_no_unified_job_tempalte_begin(self, workflow_dag_1): assert g.is_workflow_done() is True assert g.has_workflow_failed() == \ - (True, smart_text(_("No error handle path for workflow job node(s) [] workflow job node(s) missing" - " unified job template and error handle path [{}].").format(nodes[0].id))) + (True, smart_text(_("No error handling path for workflow job node(s) []. Workflow job node(s) missing" + " unified job template and error handling path [{}].").format(nodes[0].id))) def test_canceled_should_fail(self, workflow_dag_canceled): (g, nodes) = workflow_dag_canceled assert g.has_workflow_failed() == \ - (True, smart_text(_("No error handle path for workflow job node(s) [({},{})] workflow job node(s)" - " missing unified job template and error handle path [].").format(nodes[0].id, nodes[0].job.status))) + (True, smart_text(_("No error handling path for workflow job node(s) [({},{})]. Workflow job node(s)" + " missing unified job template and error handling path [].").format(nodes[0].id, nodes[0].job.status))) def test_failure_should_fail(self, workflow_dag_failure): (g, nodes) = workflow_dag_failure assert g.has_workflow_failed() == \ - (True, smart_text(_("No error handle path for workflow job node(s) [({},{})] workflow job node(s)" - " missing unified job template and error handle path [].").format(nodes[0].id, nodes[0].job.status))) + (True, smart_text(_("No error handling path for workflow job node(s) [({},{})]. Workflow job node(s)" + " missing unified job template and error handling path [].").format(nodes[0].id, nodes[0].job.status))) class TestBFSNodesToRun(): diff --git a/awx/main/tests/unit/test_access.py b/awx/main/tests/unit/test_access.py index adb2b2dcae7a..49c2a54467b5 100644 --- a/awx/main/tests/unit/test_access.py +++ b/awx/main/tests/unit/test_access.py @@ -148,7 +148,9 @@ def job_template_with_ids(job_template_factory): 'testJT', project=proj, inventory=inv, credential=credential, cloud_credential=cloud_cred, network_credential=net_cred, persisted=False) - return jt_objects.job_template + jt = jt_objects.job_template + jt.organization = Organization(id=1, pk=1, name='fooOrg') + return jt def test_superuser(mocker): @@ -180,51 +182,27 @@ def test_jt_existing_values_are_nonsensitive(job_template_with_ids, user_unit): def test_change_jt_sensitive_data(job_template_with_ids, mocker, user_unit): """Assure that can_add is called with all ForeignKeys.""" - job_template_with_ids.admin_role = Role() + class RoleReturnsTrue(Role): + class Meta: + proxy = True + + def __contains__(self, accessor): + return True - data = {'inventory': job_template_with_ids.inventory.id + 1} - access = JobTemplateAccess(user_unit) - - mock_add = mock.MagicMock(return_value=False) - with mock.patch('awx.main.models.rbac.Role.__contains__', return_value=True): - with mocker.patch('awx.main.access.JobTemplateAccess.can_add', mock_add): - with mocker.patch('awx.main.access.JobTemplateAccess.can_read', return_value=True): - assert not access.can_change(job_template_with_ids, data) + job_template_with_ids.admin_role = RoleReturnsTrue() + job_template_with_ids.organization.job_template_admin_role = RoleReturnsTrue() - mock_add.assert_called_once_with({ - 'inventory': data['inventory'], - 'project': job_template_with_ids.project.id - }) + inv2 = Inventory() + inv2.use_role = RoleReturnsTrue() + data = {'inventory': inv2} + access = JobTemplateAccess(user_unit) -def test_jt_add_scan_job_check(job_template_with_ids, user_unit): - "Assure that permissions to add scan jobs work correctly" + assert not access.changes_are_non_sensitive(job_template_with_ids, data) - access = JobTemplateAccess(user_unit) - project = job_template_with_ids.project - inventory = job_template_with_ids.inventory - project.use_role = Role() - inventory.use_role = Role() - organization = Organization(name='test-org') - inventory.organization = organization - organization.admin_role = Role() - - def mock_get_object(Class, **kwargs): - if Class == Project: - return project - elif Class == Inventory: - return inventory - else: - raise Exception('Item requested has not been mocked') - - - with mock.patch('awx.main.models.rbac.Role.__contains__', return_value=True): - with mock.patch('awx.main.access.get_object_or_400', mock_get_object): - assert access.can_add({ - 'project': project.pk, - 'inventory': inventory.pk, - 'job_type': 'scan' - }) + job_template_with_ids.inventory.use_role = RoleReturnsTrue() + job_template_with_ids.project.use_role = RoleReturnsTrue() + assert access.can_change(job_template_with_ids, data) def mock_raise_none(self, add_host=False, feature=None, check_expiration=True): diff --git a/awx/main/tests/unit/test_fields.py b/awx/main/tests/unit/test_fields.py index 479d4728b34e..94d3eaab9220 100644 --- a/awx/main/tests/unit/test_fields.py +++ b/awx/main/tests/unit/test_fields.py @@ -2,10 +2,17 @@ import pytest from django.core.exceptions import ValidationError +from django.apps import apps +from django.db.models.fields.related import ForeignKey +from django.db.models.fields.related_descriptors import ( + ReverseManyToOneDescriptor, + ForwardManyToOneDescriptor +) + from rest_framework.serializers import ValidationError as DRFValidationError from awx.main.models import Credential, CredentialType, BaseModel -from awx.main.fields import JSONSchemaField +from awx.main.fields import JSONSchemaField, ImplicitRoleField, ImplicitRoleDescriptor @pytest.mark.parametrize('schema, given, message', [ @@ -151,7 +158,7 @@ def test_cred_type_injectors_schema(injectors, valid): ) field = CredentialType._meta.get_field('injectors') if valid is False: - with pytest.raises(ValidationError, message="Injector was supposed to throw a validation error, data: {}".format(injectors)): + with pytest.raises(ValidationError): field.clean(injectors, type_) else: field.clean(injectors, type_) @@ -194,3 +201,57 @@ def test_credential_creation_validation_failure(inputs): with pytest.raises(Exception) as e: field.validate(inputs, cred) assert e.type in (ValidationError, DRFValidationError) + + +def test_implicit_role_field_parents(): + """This assures that every ImplicitRoleField only references parents + which are relationships that actually exist + """ + app_models = apps.get_app_config('main').get_models() + for cls in app_models: + for field in cls._meta.get_fields(): + if not isinstance(field, ImplicitRoleField): + continue + + if not field.parent_role: + continue + + field_names = field.parent_role + if type(field_names) is not list: + field_names = [field_names] + + for field_name in field_names: + # this type of specification appears to have been considered + # at some point, but does not exist in the app and would + # need support and tests built out for it + assert not isinstance(field_name, tuple) + # also used to be a thing before py3 upgrade + assert not isinstance(field_name, bytes) + # this is always coherent + if field_name.startswith('singleton:'): + continue + # separate out parent role syntax + field_name, sep, field_attr = field_name.partition('.') + # now make primary assertion, that specified paths exist + assert hasattr(cls, field_name) + + # inspect in greater depth + second_field = cls._meta.get_field(field_name) + second_field_descriptor = getattr(cls, field_name) + # all supported linkage types + assert isinstance(second_field_descriptor, ( + ReverseManyToOneDescriptor, # not currently used + ImplicitRoleDescriptor, + ForwardManyToOneDescriptor + )) + # only these links are supported + if field_attr: + if isinstance(second_field_descriptor, ReverseManyToOneDescriptor): + assert type(second_field) is ForeignKey + rel_model = cls._meta.get_field(field_name).related_model + third_field = getattr(rel_model, field_attr) + # expecting for related_model.foo_role, test role field type + assert isinstance(third_field, ImplicitRoleDescriptor) + else: + # expecting simple format of foo_role + assert type(second_field) is ImplicitRoleField diff --git a/awx/main/tests/unit/test_redact.py b/awx/main/tests/unit/test_redact.py index fa59ca43d772..d240ccb9ce7b 100644 --- a/awx/main/tests/unit/test_redact.py +++ b/awx/main/tests/unit/test_redact.py @@ -152,3 +152,10 @@ def test_uri_scm_cleartext_redact_and_replace(test_data): # Ensure the host didn't get redacted assert redacted_str.count(uri.host) == test_data['host_occurrences'] + +@pytest.mark.timeout(1) +def test_large_string_performance(): + length = 100000 + redacted = UriCleaner.remove_sensitive('x' * length) + assert len(redacted) == length + diff --git a/awx/main/tests/unit/test_tasks.py b/awx/main/tests/unit/test_tasks.py index 2bae5aef9ae9..166ea95f197b 100644 --- a/awx/main/tests/unit/test_tasks.py +++ b/awx/main/tests/unit/test_tasks.py @@ -25,6 +25,7 @@ Job, JobTemplate, Notification, + Organization, Project, ProjectUpdate, UnifiedJob, @@ -38,6 +39,8 @@ from awx.main.utils import encrypt_field, encrypt_value from awx.main.utils.safe_yaml import SafeLoader +from awx.main.utils.licensing import Licenser + class TestJobExecution(object): EXAMPLE_PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----\nxyz==\n-----END PRIVATE KEY-----' @@ -59,9 +62,25 @@ def patch_Job(): yield +@pytest.fixture +def patch_Organization(): + _credentials = [] + credentials_mock = mock.Mock(**{ + 'all': lambda: _credentials, + 'add': _credentials.append, + 'exists': lambda: len(_credentials) > 0, + 'spec_set': ['all', 'add', 'exists'], + }) + with mock.patch.object(Organization, 'galaxy_credentials', credentials_mock): + yield + + @pytest.fixture def job(): - return Job(pk=1, id=1, project=Project(), inventory=Inventory(), job_template=JobTemplate(id=1, name='foo')) + return Job( + pk=1, id=1, + project=Project(local_path='/projects/_23_foo'), + inventory=Inventory(), job_template=JobTemplate(id=1, name='foo')) @pytest.fixture @@ -126,11 +145,8 @@ def test_send_notifications_list(mock_notifications_filter, mock_job_get, mocker @pytest.mark.parametrize("key,value", [ ('REST_API_TOKEN', 'SECRET'), ('SECRET_KEY', 'SECRET'), - ('RABBITMQ_PASS', 'SECRET'), ('VMWARE_PASSWORD', 'SECRET'), ('API_SECRET', 'SECRET'), - ('CALLBACK_CONNECTION', 'amqp://tower:password@localhost:5672/tower'), - ('ANSIBLE_GALAXY_SERVER_PRIMARY_GALAXY_PASSWORD', 'SECRET'), ('ANSIBLE_GALAXY_SERVER_PRIMARY_GALAXY_TOKEN', 'SECRET'), ]) def test_safe_env_filtering(key, value): @@ -164,6 +180,96 @@ def test_openstack_client_config_generation(mocker, source, expected, private_da 'source_vars_dict': {}, 'get_cloud_credential': mocker.Mock(return_value=credential), 'get_extra_credentials': lambda x: [], + 'ansible_virtualenv_path': '/var/lib/awx/venv/foo' + }) + cloud_config = update.build_private_data(inventory_update, private_data_dir) + cloud_credential = yaml.safe_load( + cloud_config.get('credentials')[credential] + ) + assert cloud_credential['clouds'] == { + 'devstack': { + 'auth': { + 'auth_url': 'https://keystone.openstack.example.org', + 'password': 'secrete', + 'project_name': 'demo-project', + 'username': 'demo', + 'domain_name': 'my-demo-domain', + }, + 'verify': expected, + 'private': True, + } + } + + +@pytest.mark.parametrize("source,expected", [ + (None, True), (False, False), (True, True) +]) +def test_openstack_client_config_generation_with_project_domain_name(mocker, source, expected, private_data_dir): + update = tasks.RunInventoryUpdate() + credential_type = CredentialType.defaults['openstack']() + inputs = { + 'host': 'https://keystone.openstack.example.org', + 'username': 'demo', + 'password': 'secrete', + 'project': 'demo-project', + 'domain': 'my-demo-domain', + 'project_domain_name': 'project-domain', + } + if source is not None: + inputs['verify_ssl'] = source + credential = Credential(pk=1, credential_type=credential_type, inputs=inputs) + + inventory_update = mocker.Mock(**{ + 'source': 'openstack', + 'source_vars_dict': {}, + 'get_cloud_credential': mocker.Mock(return_value=credential), + 'get_extra_credentials': lambda x: [], + 'ansible_virtualenv_path': '/var/lib/awx/venv/foo' + }) + cloud_config = update.build_private_data(inventory_update, private_data_dir) + cloud_credential = yaml.safe_load( + cloud_config.get('credentials')[credential] + ) + assert cloud_credential['clouds'] == { + 'devstack': { + 'auth': { + 'auth_url': 'https://keystone.openstack.example.org', + 'password': 'secrete', + 'project_name': 'demo-project', + 'username': 'demo', + 'domain_name': 'my-demo-domain', + 'project_domain_name': 'project-domain', + }, + 'verify': expected, + 'private': True, + } + } + + +@pytest.mark.parametrize("source,expected", [ + (None, True), (False, False), (True, True) +]) +def test_openstack_client_config_generation_with_project_region_name(mocker, source, expected, private_data_dir): + update = tasks.RunInventoryUpdate() + credential_type = CredentialType.defaults['openstack']() + inputs = { + 'host': 'https://keystone.openstack.example.org', + 'username': 'demo', + 'password': 'secrete', + 'project': 'demo-project', + 'domain': 'my-demo-domain', + 'project_domain_name': 'project-domain', + 'project_region_name': 'region-name', + } + if source is not None: + inputs['verify_ssl'] = source + credential = Credential(pk=1, credential_type=credential_type, inputs=inputs) + + inventory_update = mocker.Mock(**{ + 'source': 'openstack', + 'source_vars_dict': {}, + 'get_cloud_credential': mocker.Mock(return_value=credential), + 'get_extra_credentials': lambda x: [], 'ansible_virtualenv_path': '/venv/foo' }) cloud_config = update.build_private_data(inventory_update, private_data_dir) @@ -178,9 +284,11 @@ def test_openstack_client_config_generation(mocker, source, expected, private_da 'project_name': 'demo-project', 'username': 'demo', 'domain_name': 'my-demo-domain', + 'project_domain_name': 'project-domain', }, 'verify': expected, 'private': True, + 'region_name': 'region-name', } } @@ -206,7 +314,7 @@ def test_openstack_client_config_generation_with_private_source_vars(mocker, sou 'source_vars_dict': {'private': source}, 'get_cloud_credential': mocker.Mock(return_value=credential), 'get_extra_credentials': lambda x: [], - 'ansible_virtualenv_path': '/venv/foo' + 'ansible_virtualenv_path': '/var/lib/awx/venv/foo' }) cloud_config = update.build_private_data(inventory_update, private_data_dir) cloud_credential = yaml.load( @@ -304,7 +412,7 @@ def test_nested_launchtime_vars_unsafe(self, job, private_data_dir): assert extra_vars['msg'] == {'a': [self.UNSAFE]} assert hasattr(extra_vars['msg']['a'][0], '__UNSAFE__') - def test_whitelisted_jt_extra_vars(self, job, private_data_dir): + def test_allowed_jt_extra_vars(self, job, private_data_dir): job.job_template.extra_vars = job.extra_vars = json.dumps({'msg': self.UNSAFE}) task = tasks.RunJob() @@ -315,7 +423,7 @@ def test_whitelisted_jt_extra_vars(self, job, private_data_dir): assert extra_vars['msg'] == self.UNSAFE assert not hasattr(extra_vars['msg'], '__UNSAFE__') - def test_nested_whitelisted_vars(self, job, private_data_dir): + def test_nested_allowed_vars(self, job, private_data_dir): job.extra_vars = json.dumps({'msg': {'a': {'b': [self.UNSAFE]}}}) job.job_template.extra_vars = job.extra_vars task = tasks.RunJob() @@ -363,7 +471,9 @@ def test_overwritten_jt_extra_vars(self, job, private_data_dir): class TestGenericRun(): def test_generic_failure(self, patch_Job): - job = Job(status='running', inventory=Inventory(), project=Project()) + job = Job( + status='running', inventory=Inventory(), + project=Project(local_path='/projects/_23_foo')) job.websocket_emit_status = mock.Mock() task = tasks.RunJob() @@ -562,13 +672,13 @@ def test_valid_custom_virtualenv(self, patch_Job, private_data_dir): def test_invalid_custom_virtualenv(self, patch_Job, private_data_dir): job = Job(project=Project(), inventory=Inventory()) - job.project.custom_virtualenv = '/venv/missing' + job.project.custom_virtualenv = '/var/lib/awx/venv/missing' task = tasks.RunJob() with pytest.raises(tasks.InvalidVirtualenvError) as e: task.build_env(job, private_data_dir) - assert 'Invalid virtual environment selected: /venv/missing' == str(e.value) + assert 'Invalid virtual environment selected: /var/lib/awx/venv/missing' == str(e.value) class TestAdhocRun(TestJobExecution): @@ -994,6 +1104,43 @@ def test_multi_vault_password_ask(self, private_data_dir, job): 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): + 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' + cert = open(env['K8S_AUTH_SSL_CA_CERT'], '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'] == tasks.HIDDEN_PASSWORD + def test_aws_cloud_credential(self, job, private_data_dir): aws = CredentialType.defaults['aws']() credential = Credential( @@ -1695,28 +1842,132 @@ def test_awx_task_env(self, settings, private_data_dir, job): assert env['FOO'] == 'BAR' +@pytest.mark.usefixtures("patch_Organization") +class TestProjectUpdateGalaxyCredentials(TestJobExecution): + + @pytest.fixture + def project_update(self): + org = Organization(pk=1) + proj = Project(pk=1, organization=org) + project_update = ProjectUpdate(pk=1, project=proj, scm_type='git') + project_update.websocket_emit_status = mock.Mock() + return project_update + + parametrize = { + 'test_galaxy_credentials_ignore_certs': [ + dict(ignore=True), + dict(ignore=False), + ], + } + + def test_galaxy_credentials_ignore_certs(self, private_data_dir, project_update, ignore): + settings.GALAXY_IGNORE_CERTS = ignore + task = tasks.RunProjectUpdate() + env = task.build_env(project_update, private_data_dir) + if ignore: + assert env['ANSIBLE_GALAXY_IGNORE'] is True + else: + assert 'ANSIBLE_GALAXY_IGNORE' not in env + + def test_galaxy_credentials_empty(self, private_data_dir, project_update): + + class RunProjectUpdate(tasks.RunProjectUpdate): + __vars__ = {} + + def _write_extra_vars_file(self, private_data_dir, extra_vars, *kw): + self.__vars__ = extra_vars + + task = RunProjectUpdate() + env = task.build_env(project_update, private_data_dir) + + with mock.patch.object(Licenser, 'validate', lambda *args, **kw: {}): + task.build_extra_vars_file(project_update, private_data_dir) + + assert task.__vars__['roles_enabled'] is False + assert task.__vars__['collections_enabled'] is False + for k in env: + assert not k.startswith('ANSIBLE_GALAXY_SERVER') + + def test_single_public_galaxy(self, private_data_dir, project_update): + class RunProjectUpdate(tasks.RunProjectUpdate): + __vars__ = {} + + def _write_extra_vars_file(self, private_data_dir, extra_vars, *kw): + self.__vars__ = extra_vars + + credential_type = CredentialType.defaults['galaxy_api_token']() + public_galaxy = Credential(pk=1, credential_type=credential_type, inputs={ + 'url': 'https://galaxy.ansible.com/', + }) + project_update.project.organization.galaxy_credentials.add(public_galaxy) + task = RunProjectUpdate() + env = task.build_env(project_update, private_data_dir) + + with mock.patch.object(Licenser, 'validate', lambda *args, **kw: {}): + task.build_extra_vars_file(project_update, private_data_dir) + + assert task.__vars__['roles_enabled'] is True + assert task.__vars__['collections_enabled'] is True + assert sorted([ + (k, v) for k, v in env.items() + if k.startswith('ANSIBLE_GALAXY') + ]) == [ + ('ANSIBLE_GALAXY_SERVER_LIST', 'server0'), + ('ANSIBLE_GALAXY_SERVER_SERVER0_URL', 'https://galaxy.ansible.com/'), + ] + + def test_multiple_galaxy_endpoints(self, private_data_dir, project_update): + credential_type = CredentialType.defaults['galaxy_api_token']() + public_galaxy = Credential(pk=1, credential_type=credential_type, inputs={ + 'url': 'https://galaxy.ansible.com/', + }) + rh = Credential(pk=2, credential_type=credential_type, inputs={ + 'url': 'https://cloud.redhat.com/api/automation-hub/', + 'auth_url': 'https://sso.redhat.com/example/openid-connect/token/', + 'token': 'secret123' + }) + project_update.project.organization.galaxy_credentials.add(public_galaxy) + project_update.project.organization.galaxy_credentials.add(rh) + task = tasks.RunProjectUpdate() + env = task.build_env(project_update, private_data_dir) + assert sorted([ + (k, v) for k, v in env.items() + if k.startswith('ANSIBLE_GALAXY') + ]) == [ + ('ANSIBLE_GALAXY_SERVER_LIST', 'server0,server1'), + ('ANSIBLE_GALAXY_SERVER_SERVER0_URL', 'https://galaxy.ansible.com/'), + ('ANSIBLE_GALAXY_SERVER_SERVER1_AUTH_URL', 'https://sso.redhat.com/example/openid-connect/token/'), # noqa + ('ANSIBLE_GALAXY_SERVER_SERVER1_TOKEN', 'secret123'), + ('ANSIBLE_GALAXY_SERVER_SERVER1_URL', 'https://cloud.redhat.com/api/automation-hub/'), + ] + + +@pytest.mark.usefixtures("patch_Organization") class TestProjectUpdateCredentials(TestJobExecution): @pytest.fixture def project_update(self): - project_update = ProjectUpdate(pk=1, project=Project(pk=1)) + project_update = ProjectUpdate( + pk=1, + project=Project(pk=1, organization=Organization(pk=1)), + ) project_update.websocket_emit_status = mock.Mock() return project_update parametrize = { 'test_username_and_password_auth': [ dict(scm_type='git'), - dict(scm_type='hg'), dict(scm_type='svn'), + dict(scm_type='archive'), ], 'test_ssh_key_auth': [ dict(scm_type='git'), - dict(scm_type='hg'), dict(scm_type='svn'), + dict(scm_type='archive'), ], 'test_awx_task_env': [ dict(scm_type='git'), - dict(scm_type='hg'), dict(scm_type='svn'), + dict(scm_type='archive'), ] } @@ -1736,7 +1987,9 @@ def test_process_isolation_exposes_projects_root(self, private_data_dir, project assert settings.PROJECTS_ROOT in process_isolation['process_isolation_show_paths'] task._write_extra_vars_file = mock.Mock() - task.build_extra_vars_file(project_update, private_data_dir) + + with mock.patch.object(Licenser, 'validate', lambda *args, **kw: {}): + task.build_extra_vars_file(project_update, private_data_dir) call_args, _ = task._write_extra_vars_file.call_args_list[0] _, extra_vars = call_args @@ -1814,11 +2067,6 @@ def test_source_without_credential(self, mocker, inventory_update, private_data_ assert 'AWS_ACCESS_KEY_ID' not in env assert 'AWS_SECRET_ACCESS_KEY' not in env - assert 'EC2_INI_PATH' in env - - config = configparser.ConfigParser() - config.read(env['EC2_INI_PATH']) - assert 'ec2' in config.sections() @pytest.mark.parametrize('with_credential', [True, False]) def test_custom_source(self, with_credential, mocker, inventory_update, private_data_dir): @@ -1857,8 +2105,8 @@ def get_creds(): credential, env, {}, [], private_data_dir ) - assert '--custom' in ' '.join(args) - script = args[args.index('--source') + 1] + assert '-i' in ' '.join(args) + script = args[args.index('-i') + 1] with open(script, 'r') as f: assert f.read() == inventory_update.source_script.script assert env['FOO'] == 'BAR' @@ -1884,20 +2132,13 @@ def get_cred(): inventory_update.get_cloud_credential = get_cred inventory_update.get_extra_credentials = mocker.Mock(return_value=[]) - # force test to use the ec2 script injection logic, as opposed to plugin - with mocker.patch('awx.main.tasks._get_ansible_version', mocker.MagicMock(return_value='2.7')): - private_data_files = task.build_private_data_files(inventory_update, private_data_dir) - env = task.build_env(inventory_update, private_data_dir, False, private_data_files) + private_data_files = task.build_private_data_files(inventory_update, private_data_dir) + env = task.build_env(inventory_update, private_data_dir, False, private_data_files) safe_env = build_safe_env(env) assert env['AWS_ACCESS_KEY_ID'] == 'bob' assert env['AWS_SECRET_ACCESS_KEY'] == 'secret' - assert 'EC2_INI_PATH' in env - - config = configparser.ConfigParser() - config.read(env['EC2_INI_PATH']) - assert 'ec2' in config.sections() assert safe_env['AWS_SECRET_ACCESS_KEY'] == tasks.HIDDEN_PASSWORD @@ -1928,17 +2169,15 @@ def get_cred(): credential, env, safe_env, [], private_data_dir ) - config = configparser.ConfigParser() - config.read(env['VMWARE_INI_PATH']) - assert config.get('vmware', 'username') == 'bob' - assert config.get('vmware', 'password') == 'secret' - assert config.get('vmware', 'server') == 'https://example.org' + env["VMWARE_USER"] == "bob", + env["VMWARE_PASSWORD"] == "secret", + env["VMWARE_HOST"] == "https://example.org", + env["VMWARE_VALIDATE_CERTS"] == "False", def test_azure_rm_source_with_tenant(self, private_data_dir, inventory_update, mocker): task = tasks.RunInventoryUpdate() azure_rm = CredentialType.defaults['azure_rm']() inventory_update.source = 'azure_rm' - inventory_update.source_regions = 'north, south, east, west' def get_cred(): cred = Credential( @@ -1955,15 +2194,9 @@ def get_cred(): return cred inventory_update.get_cloud_credential = get_cred inventory_update.get_extra_credentials = mocker.Mock(return_value=[]) - inventory_update.source_vars = { - 'include_powerstate': 'yes', - 'group_by_resource_group': 'no' - } - # force azure_rm inventory to use script injection logic, as opposed to plugin - with mocker.patch('awx.main.tasks._get_ansible_version', mocker.MagicMock(return_value='2.7')): - private_data_files = task.build_private_data_files(inventory_update, private_data_dir) - env = task.build_env(inventory_update, private_data_dir, False, private_data_files) + private_data_files = task.build_private_data_files(inventory_update, private_data_dir) + env = task.build_env(inventory_update, private_data_dir, False, private_data_files) safe_env = build_safe_env(env) @@ -1973,22 +2206,12 @@ def get_cred(): assert env['AZURE_SUBSCRIPTION_ID'] == 'some-subscription' assert env['AZURE_CLOUD_ENVIRONMENT'] == 'foobar' - config = configparser.ConfigParser() - config.read(env['AZURE_INI_PATH']) - assert config.get('azure', 'include_powerstate') == 'yes' - assert config.get('azure', 'group_by_resource_group') == 'no' - assert config.get('azure', 'group_by_location') == 'yes' - assert 'group_by_security_group' not in config.items('azure') - assert config.get('azure', 'group_by_tag') == 'yes' - assert config.get('azure', 'locations') == 'north,south,east,west' - assert safe_env['AZURE_SECRET'] == tasks.HIDDEN_PASSWORD def test_azure_rm_source_with_password(self, private_data_dir, inventory_update, mocker): task = tasks.RunInventoryUpdate() azure_rm = CredentialType.defaults['azure_rm']() inventory_update.source = 'azure_rm' - inventory_update.source_regions = 'all' def get_cred(): cred = Credential( @@ -2004,16 +2227,9 @@ def get_cred(): return cred inventory_update.get_cloud_credential = get_cred inventory_update.get_extra_credentials = mocker.Mock(return_value=[]) - inventory_update.source_vars = { - 'include_powerstate': 'yes', - 'group_by_resource_group': 'no', - 'group_by_security_group': 'no' - } - # force azure_rm inventory to use script injection logic, as opposed to plugin - with mocker.patch('awx.main.tasks._get_ansible_version', mocker.MagicMock(return_value='2.7')): - private_data_files = task.build_private_data_files(inventory_update, private_data_dir) - env = task.build_env(inventory_update, private_data_dir, False, private_data_files) + private_data_files = task.build_private_data_files(inventory_update, private_data_dir) + env = task.build_env(inventory_update, private_data_dir, False, private_data_files) safe_env = build_safe_env(env) @@ -2022,21 +2238,12 @@ def get_cred(): assert env['AZURE_PASSWORD'] == 'secret' assert env['AZURE_CLOUD_ENVIRONMENT'] == 'foobar' - config = configparser.ConfigParser() - config.read(env['AZURE_INI_PATH']) - assert config.get('azure', 'include_powerstate') == 'yes' - assert config.get('azure', 'group_by_resource_group') == 'no' - assert config.get('azure', 'group_by_location') == 'yes' - assert config.get('azure', 'group_by_security_group') == 'no' - assert config.get('azure', 'group_by_tag') == 'yes' - assert 'locations' not in config.items('azure') assert safe_env['AZURE_PASSWORD'] == tasks.HIDDEN_PASSWORD def test_gce_source(self, inventory_update, private_data_dir, mocker): task = tasks.RunInventoryUpdate() gce = CredentialType.defaults['gce']() inventory_update.source = 'gce' - inventory_update.source_regions = 'all' def get_cred(): cred = Credential( @@ -2073,18 +2280,6 @@ def run(expected_gce_zone): assert json_data['client_email'] == 'bob' assert json_data['project_id'] == 'some-project' - config = configparser.ConfigParser() - config.read(env['GCE_INI_PATH']) - assert 'cache' in config.sections() - assert config.getint('cache', 'cache_max_age') == 0 - - # Change the initial version of the inventory plugin to force use of script - with mock.patch('awx.main.models.inventory.gce.initial_version', None): - run('') - - inventory_update.source_regions = 'us-east-4' - run('us-east-4') - def test_openstack_source(self, inventory_update, private_data_dir, mocker): task = tasks.RunInventoryUpdate() openstack = CredentialType.defaults['openstack']() @@ -2146,65 +2341,20 @@ def get_cred(): inventory_update.get_cloud_credential = get_cred inventory_update.get_extra_credentials = mocker.Mock(return_value=[]) - inventory_update.source_vars = '{"satellite6_group_patterns": "[a,b,c]", "satellite6_group_prefix": "hey_", "satellite6_want_hostcollections": True}' - - private_data_files = task.build_private_data_files(inventory_update, private_data_dir) - env = task.build_env(inventory_update, private_data_dir, False, private_data_files) - - config = configparser.ConfigParser() - config.read(env['FOREMAN_INI_PATH']) - assert config.get('foreman', 'url') == 'https://example.org' - assert config.get('foreman', 'user') == 'bob' - assert config.get('foreman', 'password') == 'secret' - assert config.get('ansible', 'group_patterns') == '[a,b,c]' - assert config.get('ansible', 'group_prefix') == 'hey_' - assert config.get('ansible', 'want_hostcollections') == 'True' - - def test_cloudforms_source(self, inventory_update, private_data_dir, mocker): - task = tasks.RunInventoryUpdate() - cloudforms = CredentialType.defaults['cloudforms']() - inventory_update.source = 'cloudforms' - - def get_cred(): - cred = Credential( - pk=1, - credential_type=cloudforms, - inputs = { - 'username': 'bob', - 'password': 'secret', - 'host': 'https://example.org' - } - ) - cred.inputs['password'] = encrypt_field( - cred, 'password' - ) - return cred - inventory_update.get_cloud_credential = get_cred - inventory_update.get_extra_credentials = mocker.Mock(return_value=[]) - - inventory_update.source_vars = '{"prefer_ipv4": True}' - private_data_files = task.build_private_data_files(inventory_update, private_data_dir) env = task.build_env(inventory_update, private_data_dir, False, private_data_files) + safe_env = build_safe_env(env) - config = configparser.ConfigParser() - config.read(env['CLOUDFORMS_INI_PATH']) - assert config.get('cloudforms', 'url') == 'https://example.org' - assert config.get('cloudforms', 'username') == 'bob' - assert config.get('cloudforms', 'password') == 'secret' - assert config.get('cloudforms', 'ssl_verify') == 'false' - assert config.get('cloudforms', 'prefer_ipv4') == 'True' - - cache_path = config.get('cache', 'path') - assert cache_path.startswith(env['AWX_PRIVATE_DATA_DIR']) - assert os.path.isdir(cache_path) + assert env["FOREMAN_SERVER"] == "https://example.org" + assert env["FOREMAN_USER"] == "bob" + assert env["FOREMAN_PASSWORD"] == "secret" + assert safe_env["FOREMAN_PASSWORD"] == tasks.HIDDEN_PASSWORD @pytest.mark.parametrize('verify', [True, False]) def test_tower_source(self, verify, inventory_update, private_data_dir, mocker): task = tasks.RunInventoryUpdate() tower = CredentialType.defaults['tower']() inventory_update.source = 'tower' - inventory_update.instance_filters = '12345' inputs = { 'host': 'https://tower.example.org', 'username': 'bob', @@ -2219,16 +2369,13 @@ def get_cred(): inventory_update.get_cloud_credential = get_cred inventory_update.get_extra_credentials = mocker.Mock(return_value=[]) - # force tower inventory source to use script injection logic, as opposed to plugin - with mocker.patch('awx.main.tasks._get_ansible_version', mocker.MagicMock(return_value='2.7')): - env = task.build_env(inventory_update, private_data_dir, False) + env = task.build_env(inventory_update, private_data_dir, False) safe_env = build_safe_env(env) assert env['TOWER_HOST'] == 'https://tower.example.org' assert env['TOWER_USERNAME'] == 'bob' assert env['TOWER_PASSWORD'] == 'secret' - assert env['TOWER_INVENTORY'] == '12345' if verify: assert env['TOWER_VERIFY_SSL'] == 'True' else: @@ -2239,7 +2386,6 @@ def test_tower_source_ssl_verify_empty(self, inventory_update, private_data_dir, task = tasks.RunInventoryUpdate() tower = CredentialType.defaults['tower']() inventory_update.source = 'tower' - inventory_update.instance_filters = '12345' inputs = { 'host': 'https://tower.example.org', 'username': 'bob', @@ -2283,9 +2429,8 @@ def get_cred(): inventory_update.get_extra_credentials = mocker.Mock(return_value=[]) settings.AWX_TASK_ENV = {'FOO': 'BAR'} - with mocker.patch('awx.main.tasks._get_ansible_version', mocker.MagicMock(return_value='2.7')): - private_data_files = task.build_private_data_files(inventory_update, private_data_dir) - env = task.build_env(inventory_update, private_data_dir, False, private_data_files) + private_data_files = task.build_private_data_files(inventory_update, private_data_dir) + env = task.build_env(inventory_update, private_data_dir, False, private_data_files) assert env['FOO'] == 'BAR' @@ -2317,7 +2462,7 @@ def test_aquire_lock_open_fail_logged(logging_getLogger, os_open): ProjectUpdate = tasks.RunProjectUpdate() - with pytest.raises(OSError, message='dummy message'): + 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')) @@ -2343,7 +2488,7 @@ def test_aquire_lock_acquisition_fail_logged(fcntl_lockf, logging_getLogger, os_ fcntl_lockf.side_effect = err ProjectUpdate = tasks.RunProjectUpdate() - with pytest.raises(IOError, message='dummy message'): + 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 aquire lock on file [{1}]: {2}".format(3, 'this_file_does_not_exist', 'dummy message')) @@ -2371,3 +2516,23 @@ def test_managed_injector_redaction(injector_cls): if secret_field_name in template: env[env_name] = 'very_secret_value' assert 'very_secret_value' not in str(build_safe_env(env)) + + +@mock.patch('logging.getLogger') +def test_notification_job_not_finished(logging_getLogger, mocker): + uj = mocker.MagicMock() + uj.finished = False + logger = mocker.Mock() + logging_getLogger.return_value = logger + + with mocker.patch('awx.main.models.UnifiedJob.objects.get', uj): + tasks.handle_success_and_failure_notifications(1) + assert logger.warn.called_with(f"Failed to even try to send notifications for job '{uj}' due to job not being in finished state.") + + +def test_notification_job_finished(mocker): + uj = mocker.MagicMock(send_notification_templates=mocker.MagicMock(), finished=True) + + with mocker.patch('awx.main.models.UnifiedJob.objects.get', mocker.MagicMock(return_value=uj)): + tasks.handle_success_and_failure_notifications(1) + uj.send_notification_templates.assert_called() diff --git a/awx/main/tests/unit/utils/test_common.py b/awx/main/tests/unit/utils/test_common.py index edacfc1423f8..8c07020c5375 100644 --- a/awx/main/tests/unit/utils/test_common.py +++ b/awx/main/tests/unit/utils/test_common.py @@ -215,11 +215,3 @@ def test_get_custom_venv_choices(): os.path.join(temp_dir, ''), os.path.join(custom_venv_1, '') ] - - -def test_region_sorting(): - s = [('Huey', 'China1'), - ('Dewey', 'UK1'), - ('Lewie', 'US1'), - ('All', 'All')] - assert [x[1] for x in sorted(s, key=common.region_sorting)] == ['All', 'US1', 'China1', 'UK1'] diff --git a/awx/main/tests/unit/utils/test_filters.py b/awx/main/tests/unit/utils/test_filters.py index 54ae9c969169..76effe828435 100644 --- a/awx/main/tests/unit/utils/test_filters.py +++ b/awx/main/tests/unit/utils/test_filters.py @@ -79,8 +79,8 @@ def __init__(self): @mock.patch('awx.main.utils.filters.get_model', return_value=mockHost()) class TestSmartFilterQueryFromString(): @mock.patch( - 'awx.api.filters.get_field_from_path', - lambda model, path: (model, path) # disable field filtering, because a__b isn't a real Host field + 'awx.api.filters.get_fields_from_path', + lambda model, path: ([model], path) # disable field filtering, because a__b isn't a real Host field ) @pytest.mark.parametrize("filter_string,q_expected", [ ('facts__facts__blank=""', Q(**{u"facts__facts__blank": u""})), diff --git a/awx/main/tests/unit/utils/test_handlers.py b/awx/main/tests/unit/utils/test_handlers.py deleted file mode 100644 index 6fa9b1f992a4..000000000000 --- a/awx/main/tests/unit/utils/test_handlers.py +++ /dev/null @@ -1,393 +0,0 @@ -# -*- coding: utf-8 -*- -import base64 -import logging -import socket -import datetime -from dateutil.tz import tzutc -from io import StringIO -from uuid import uuid4 - -from unittest import mock - -from django.conf import LazySettings -from django.utils.encoding import smart_str -import pytest -import requests -from requests_futures.sessions import FuturesSession - -from awx.main.utils.handlers import (BaseHandler, BaseHTTPSHandler as HTTPSHandler, - TCPHandler, UDPHandler, _encode_payload_for_socket, - PARAM_NAMES, LoggingConnectivityException, - AWXProxyHandler) -from awx.main.utils.formatters import LogstashFormatter - - -@pytest.fixture() -def https_adapter(): - class FakeHTTPSAdapter(requests.adapters.HTTPAdapter): - requests = [] - status = 200 - reason = None - - def send(self, request, **kwargs): - self.requests.append(request) - resp = requests.models.Response() - resp.status_code = self.status - resp.reason = self.reason - resp.request = request - return resp - - return FakeHTTPSAdapter() - - -@pytest.fixture() -def connection_error_adapter(): - class ConnectionErrorAdapter(requests.adapters.HTTPAdapter): - - def send(self, request, **kwargs): - err = requests.packages.urllib3.exceptions.SSLError() - raise requests.exceptions.ConnectionError(err, request=request) - - return ConnectionErrorAdapter() - - -@pytest.fixture -def fake_socket(tmpdir_factory, request): - sok = socket.socket - sok.send = mock.MagicMock() - sok.connect = mock.MagicMock() - sok.setblocking = mock.MagicMock() - sok.close = mock.MagicMock() - return sok - - -def test_https_logging_handler_requests_async_implementation(): - handler = HTTPSHandler() - assert isinstance(handler.session, FuturesSession) - - -def test_https_logging_handler_has_default_http_timeout(): - handler = TCPHandler() - assert handler.tcp_timeout == 5 - - -@pytest.mark.parametrize('param', ['host', 'port', 'indv_facts']) -def test_base_logging_handler_defaults(param): - handler = BaseHandler() - assert hasattr(handler, param) and getattr(handler, param) is None - - -@pytest.mark.parametrize('param', ['host', 'port', 'indv_facts']) -def test_base_logging_handler_kwargs(param): - handler = BaseHandler(**{param: 'EXAMPLE'}) - assert hasattr(handler, param) and getattr(handler, param) == 'EXAMPLE' - - -@pytest.mark.parametrize('params', [ - { - 'LOG_AGGREGATOR_HOST': 'https://server.invalid', - 'LOG_AGGREGATOR_PORT': 22222, - 'LOG_AGGREGATOR_TYPE': 'loggly', - 'LOG_AGGREGATOR_USERNAME': 'foo', - 'LOG_AGGREGATOR_PASSWORD': 'bar', - 'LOG_AGGREGATOR_INDIVIDUAL_FACTS': True, - 'LOG_AGGREGATOR_TCP_TIMEOUT': 96, - 'LOG_AGGREGATOR_VERIFY_CERT': False, - 'LOG_AGGREGATOR_PROTOCOL': 'https' - }, - { - 'LOG_AGGREGATOR_HOST': 'https://server.invalid', - 'LOG_AGGREGATOR_PORT': 22222, - 'LOG_AGGREGATOR_PROTOCOL': 'udp' - } -]) -def test_real_handler_from_django_settings(params): - settings = LazySettings() - settings.configure(**params) - handler = AWXProxyHandler().get_handler(custom_settings=settings) - # need the _reverse_ dictionary from PARAM_NAMES - attr_lookup = {} - for attr_name, setting_name in PARAM_NAMES.items(): - attr_lookup[setting_name] = attr_name - for setting_name, val in params.items(): - attr_name = attr_lookup[setting_name] - if attr_name == 'protocol': - continue - assert hasattr(handler, attr_name) - - -def test_invalid_kwarg_to_real_handler(): - settings = LazySettings() - settings.configure(**{ - 'LOG_AGGREGATOR_HOST': 'https://server.invalid', - 'LOG_AGGREGATOR_PORT': 22222, - 'LOG_AGGREGATOR_PROTOCOL': 'udp', - 'LOG_AGGREGATOR_VERIFY_CERT': False # setting not valid for UDP handler - }) - handler = AWXProxyHandler().get_handler(custom_settings=settings) - assert not hasattr(handler, 'verify_cert') - - -def test_protocol_not_specified(): - settings = LazySettings() - settings.configure(**{ - 'LOG_AGGREGATOR_HOST': 'https://server.invalid', - 'LOG_AGGREGATOR_PORT': 22222, - 'LOG_AGGREGATOR_PROTOCOL': None # awx/settings/defaults.py - }) - handler = AWXProxyHandler().get_handler(custom_settings=settings) - assert isinstance(handler, logging.NullHandler) - - -def test_base_logging_handler_emit_system_tracking(dummy_log_record): - handler = BaseHandler(host='127.0.0.1', indv_facts=True) - handler.setFormatter(LogstashFormatter()) - dummy_log_record.name = 'awx.analytics.system_tracking' - dummy_log_record.msg = None - dummy_log_record.inventory_id = 11 - dummy_log_record.host_name = 'my_lucky_host' - dummy_log_record.job_id = 777 - dummy_log_record.ansible_facts = { - "ansible_kernel": "4.4.66-boot2docker", - "ansible_machine": "x86_64", - "ansible_swapfree_mb": 4663, - } - dummy_log_record.ansible_facts_modified = datetime.datetime.now(tzutc()).isoformat() - sent_payloads = handler.emit(dummy_log_record) - - assert len(sent_payloads) == 1 - assert sent_payloads[0]['ansible_facts'] == dummy_log_record.ansible_facts - assert sent_payloads[0]['ansible_facts_modified'] == dummy_log_record.ansible_facts_modified - assert sent_payloads[0]['level'] == 'INFO' - assert sent_payloads[0]['logger_name'] == 'awx.analytics.system_tracking' - assert sent_payloads[0]['job_id'] == dummy_log_record.job_id - assert sent_payloads[0]['inventory_id'] == dummy_log_record.inventory_id - assert sent_payloads[0]['host_name'] == dummy_log_record.host_name - - -@pytest.mark.parametrize('host, port, normalized, hostname_only', [ - ('http://localhost', None, 'http://localhost', False), - ('http://localhost', 8080, 'http://localhost:8080', False), - ('https://localhost', 443, 'https://localhost:443', False), - ('ftp://localhost', 443, 'ftp://localhost:443', False), - ('https://localhost:550', 443, 'https://localhost:550', False), - ('https://localhost:yoho/foobar', 443, 'https://localhost:443/foobar', False), - ('https://localhost:yoho/foobar', None, 'https://localhost:yoho/foobar', False), - ('http://splunk.server:8088/services/collector/event', 80, - 'http://splunk.server:8088/services/collector/event', False), - ('http://splunk.server/services/collector/event', 8088, - 'http://splunk.server:8088/services/collector/event', False), - ('splunk.server:8088/services/collector/event', 80, - 'http://splunk.server:8088/services/collector/event', False), - ('splunk.server/services/collector/event', 8088, - 'http://splunk.server:8088/services/collector/event', False), - ('localhost', None, 'http://localhost', False), - ('localhost', 8080, 'http://localhost:8080', False), - ('localhost', 4399, 'localhost', True), - ('tcp://localhost:4399/foo/bar', 4399, 'localhost', True), -]) -def test_base_logging_handler_host_format(host, port, normalized, hostname_only): - handler = BaseHandler(host=host, port=port) - assert handler._get_host(scheme='http', hostname_only=hostname_only) == normalized - - -@pytest.mark.parametrize( - 'status, reason, exc', - [(200, '200 OK', None), (404, 'Not Found', LoggingConnectivityException)] -) -@pytest.mark.parametrize('protocol', ['http', 'https', None]) -def test_https_logging_handler_connectivity_test(https_adapter, status, reason, exc, protocol): - host = 'example.org' - if protocol: - host = '://'.join([protocol, host]) - https_adapter.status = status - https_adapter.reason = reason - settings = LazySettings() - settings.configure(**{ - 'LOG_AGGREGATOR_HOST': host, - 'LOG_AGGREGATOR_PORT': 8080, - 'LOG_AGGREGATOR_TYPE': 'logstash', - 'LOG_AGGREGATOR_USERNAME': 'user', - 'LOG_AGGREGATOR_PASSWORD': 'password', - 'LOG_AGGREGATOR_LOGGERS': ['awx', 'activity_stream', 'job_events', 'system_tracking'], - 'LOG_AGGREGATOR_PROTOCOL': 'https', - 'CLUSTER_HOST_ID': '', - 'LOG_AGGREGATOR_TOWER_UUID': str(uuid4()), - 'LOG_AGGREGATOR_LEVEL': 'DEBUG', - }) - - class FakeHTTPSHandler(HTTPSHandler): - - def __init__(self, *args, **kwargs): - super(FakeHTTPSHandler, self).__init__(*args, **kwargs) - self.session.mount('{}://'.format(protocol or 'https'), https_adapter) - - def emit(self, record): - return super(FakeHTTPSHandler, self).emit(record) - - with mock.patch.object(AWXProxyHandler, 'get_handler_class') as mock_get_class: - mock_get_class.return_value = FakeHTTPSHandler - if exc: - with pytest.raises(exc) as e: - AWXProxyHandler().perform_test(settings) - assert str(e).endswith('%s: %s' % (status, reason)) - else: - assert AWXProxyHandler().perform_test(settings) is None - - -def test_https_logging_handler_logstash_auth_info(): - handler = HTTPSHandler(message_type='logstash', username='bob', password='ansible') - handler._add_auth_information() - assert isinstance(handler.session.auth, requests.auth.HTTPBasicAuth) - assert handler.session.auth.username == 'bob' - assert handler.session.auth.password == 'ansible' - - -def test_https_logging_handler_splunk_auth_info(): - handler = HTTPSHandler(message_type='splunk', password='ansible') - handler._add_auth_information() - assert handler.session.headers['Authorization'] == 'Splunk ansible' - assert handler.session.headers['Content-Type'] == 'application/json' - - -def test_https_logging_handler_connection_error(connection_error_adapter, - dummy_log_record): - handler = HTTPSHandler(host='127.0.0.1', message_type='logstash') - handler.setFormatter(LogstashFormatter()) - handler.session.mount('http://', connection_error_adapter) - - buff = StringIO() - logging.getLogger('awx.main.utils.handlers').addHandler( - logging.StreamHandler(buff) - ) - - async_futures = handler.emit(dummy_log_record) - with pytest.raises(requests.exceptions.ConnectionError): - [future.result() for future in async_futures] - assert 'failed to emit log to external aggregator\nTraceback' in buff.getvalue() - - # we should only log failures *periodically*, so causing *another* - # immediate failure shouldn't report a second ConnectionError - buff.truncate(0) - async_futures = handler.emit(dummy_log_record) - with pytest.raises(requests.exceptions.ConnectionError): - [future.result() for future in async_futures] - assert buff.getvalue() == '' - - -@pytest.mark.parametrize('message_type', ['logstash', 'splunk']) -def test_https_logging_handler_emit_without_cred(https_adapter, dummy_log_record, - message_type): - handler = HTTPSHandler(host='127.0.0.1', message_type=message_type) - handler.setFormatter(LogstashFormatter()) - handler.session.mount('https://', https_adapter) - async_futures = handler.emit(dummy_log_record) - [future.result() for future in async_futures] - - assert len(https_adapter.requests) == 1 - request = https_adapter.requests[0] - assert request.url == 'https://127.0.0.1/' - assert request.method == 'POST' - - if message_type == 'logstash': - # A username + password weren't used, so this header should be missing - assert 'Authorization' not in request.headers - - if message_type == 'splunk': - assert request.headers['Authorization'] == 'Splunk None' - - -def test_https_logging_handler_emit_logstash_with_creds(https_adapter, - dummy_log_record): - handler = HTTPSHandler(host='127.0.0.1', - username='user', password='pass', - message_type='logstash') - handler.setFormatter(LogstashFormatter()) - handler.session.mount('https://', https_adapter) - async_futures = handler.emit(dummy_log_record) - [future.result() for future in async_futures] - - assert len(https_adapter.requests) == 1 - request = https_adapter.requests[0] - assert request.headers['Authorization'] == 'Basic %s' % smart_str(base64.b64encode(b"user:pass")) - - -def test_https_logging_handler_emit_splunk_with_creds(https_adapter, - dummy_log_record): - handler = HTTPSHandler(host='127.0.0.1', - password='pass', message_type='splunk') - handler.setFormatter(LogstashFormatter()) - handler.session.mount('https://', https_adapter) - async_futures = handler.emit(dummy_log_record) - [future.result() for future in async_futures] - - assert len(https_adapter.requests) == 1 - request = https_adapter.requests[0] - assert request.headers['Authorization'] == 'Splunk pass' - - -@pytest.mark.parametrize('payload, encoded_payload', [ - ('foobar', 'foobar'), - ({'foo': 'bar'}, '{"foo": "bar"}'), - ({u'测试键': u'测试值'}, '{"测试键": "测试值"}'), -]) -def test_encode_payload_for_socket(payload, encoded_payload): - assert _encode_payload_for_socket(payload).decode('utf-8') == encoded_payload - - -def test_udp_handler_create_socket_at_init(): - handler = UDPHandler(host='127.0.0.1', port=4399) - assert hasattr(handler, 'socket') - assert isinstance(handler.socket, socket.socket) - assert handler.socket.family == socket.AF_INET - assert handler.socket.type == socket.SOCK_DGRAM - - -def test_udp_handler_send(dummy_log_record): - handler = UDPHandler(host='127.0.0.1', port=4399) - handler.setFormatter(LogstashFormatter()) - with mock.patch('awx.main.utils.handlers._encode_payload_for_socket', return_value="des") as encode_mock,\ - mock.patch.object(handler, 'socket') as socket_mock: - handler.emit(dummy_log_record) - encode_mock.assert_called_once_with(handler.format(dummy_log_record)) - socket_mock.sendto.assert_called_once_with("des", ('127.0.0.1', 4399)) - - -def test_tcp_handler_send(fake_socket, dummy_log_record): - handler = TCPHandler(host='127.0.0.1', port=4399, tcp_timeout=5) - handler.setFormatter(LogstashFormatter()) - with mock.patch('socket.socket', return_value=fake_socket) as sok_init_mock,\ - mock.patch('select.select', return_value=([], [fake_socket], [])): - handler.emit(dummy_log_record) - sok_init_mock.assert_called_once_with(socket.AF_INET, socket.SOCK_STREAM) - fake_socket.connect.assert_called_once_with(('127.0.0.1', 4399)) - fake_socket.setblocking.assert_called_once_with(0) - fake_socket.send.assert_called_once_with(handler.format(dummy_log_record)) - fake_socket.close.assert_called_once() - - -def test_tcp_handler_return_if_socket_unavailable(fake_socket, dummy_log_record): - handler = TCPHandler(host='127.0.0.1', port=4399, tcp_timeout=5) - handler.setFormatter(LogstashFormatter()) - with mock.patch('socket.socket', return_value=fake_socket) as sok_init_mock,\ - mock.patch('select.select', return_value=([], [], [])): - handler.emit(dummy_log_record) - sok_init_mock.assert_called_once_with(socket.AF_INET, socket.SOCK_STREAM) - fake_socket.connect.assert_called_once_with(('127.0.0.1', 4399)) - fake_socket.setblocking.assert_called_once_with(0) - assert not fake_socket.send.called - fake_socket.close.assert_called_once() - - -def test_tcp_handler_log_exception(fake_socket, dummy_log_record): - handler = TCPHandler(host='127.0.0.1', port=4399, tcp_timeout=5) - handler.setFormatter(LogstashFormatter()) - with mock.patch('socket.socket', return_value=fake_socket) as sok_init_mock,\ - mock.patch('select.select', return_value=([], [], [])),\ - mock.patch('awx.main.utils.handlers.logger') as logger_mock: - fake_socket.connect.side_effect = Exception("foo") - handler.emit(dummy_log_record) - sok_init_mock.assert_called_once_with(socket.AF_INET, socket.SOCK_STREAM) - logger_mock.exception.assert_called_once() - fake_socket.close.assert_called_once() - assert not fake_socket.send.called diff --git a/awx/main/tests/unit/utils/test_reload.py b/awx/main/tests/unit/utils/test_reload.py index 87c2689da8b8..525a90e6aad2 100644 --- a/awx/main/tests/unit/utils/test_reload.py +++ b/awx/main/tests/unit/utils/test_reload.py @@ -8,7 +8,7 @@ def test_produce_supervisor_command(mocker): 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.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/utils/ansible.py b/awx/main/utils/ansible.py index fa15c47ab186..18011504b965 100644 --- a/awx/main/utils/ansible.py +++ b/awx/main/utils/ansible.py @@ -106,7 +106,7 @@ def could_be_inventory(project_path, dir_path, filename): def read_ansible_config(project_path, variables_of_interest): fnames = ['/etc/ansible/ansible.cfg'] if project_path: - fnames.insert(0, os.path.join(project_path, 'ansible.cfg')) + fnames.append(os.path.join(project_path, 'ansible.cfg')) values = {} try: parser = ConfigParser() diff --git a/awx/main/utils/common.py b/awx/main/utils/common.py index cc3272794f70..283a028f3f8e 100644 --- a/awx/main/utils/common.py +++ b/awx/main/utils/common.py @@ -43,9 +43,9 @@ __all__ = [ 'get_object_or_400', 'camelcase_to_underscore', 'underscore_to_camelcase', 'memoize', - 'memoize_delete', 'get_ansible_version', 'get_ssh_version', 'get_licenser', 'get_awx_http_client_headers', + 'memoize_delete', 'get_ansible_version', 'get_licenser', 'get_awx_http_client_headers', 'get_awx_version', 'update_scm_url', 'get_type_for_model', 'get_model_for_type', - 'copy_model_by_class', 'region_sorting', 'copy_m2m_relationships', + 'copy_model_by_class', 'copy_m2m_relationships', 'prefetch_page_capabilities', 'to_python_boolean', 'ignore_inventory_computed_fields', 'ignore_inventory_group_removal', '_inventory_updates', 'get_pk_from_dict', 'getattrd', 'getattr_dne', 'NoDefaultProvided', 'get_current_apps', 'set_current_apps', @@ -55,7 +55,7 @@ 'model_instance_diff', 'parse_yaml_or_json', 'RequireDebugTrueOrTest', 'has_model_field_prefetched', 'set_environ', 'IllegalArgumentError', 'get_custom_venv_choices', 'get_external_account', 'task_manager_bulk_reschedule', - 'schedule_task_manager', 'classproperty', 'create_temporary_fifo', 'truncate_stdout', + 'schedule_task_manager', 'classproperty', 'create_temporary_fifo', 'truncate_stdout' ] @@ -86,15 +86,6 @@ def to_python_boolean(value, allow_none=False): raise ValueError(_(u'Unable to convert "%s" to boolean') % value) -def region_sorting(region): - # python3's removal of sorted(cmp=...) is _stupid_ - if region[1].lower() == 'all': - return '' - elif region[1].lower().startswith('us'): - return region[1] - return 'ZZZ' + str(region[1]) - - def camelcase_to_underscore(s): ''' Convert CamelCase names to lowercase_with_underscore. @@ -170,13 +161,14 @@ def memoize_delete(function_name): return cache.delete(function_name) -def _get_ansible_version(ansible_path): +@memoize() +def get_ansible_version(): ''' Return Ansible version installed. Ansible path needs to be provided to account for custom virtual environments ''' try: - proc = subprocess.Popen([ansible_path, '--version'], + proc = subprocess.Popen(['ansible', '--version'], stdout=subprocess.PIPE) result = smart_str(proc.communicate()[0]) return result.split('\n')[0].replace('ansible', '').strip() @@ -184,25 +176,6 @@ def _get_ansible_version(ansible_path): return 'unknown' -@memoize() -def get_ansible_version(): - return _get_ansible_version('ansible') - - -@memoize() -def get_ssh_version(): - ''' - Return SSH version installed. - ''' - try: - proc = subprocess.Popen(['ssh', '-V'], - stderr=subprocess.PIPE) - result = smart_str(proc.communicate()[1]) - return result.split(" ")[0].split("_")[1] - except Exception: - return 'unknown' - - def get_awx_version(): ''' Return AWX version as reported by setuptools. @@ -216,7 +189,7 @@ def get_awx_version(): def get_awx_http_client_headers(): - license = get_license(show_key=False).get('license_type', 'UNLICENSED') + license = get_license().get('license_type', 'UNLICENSED') headers = { 'Content-Type': 'application/json', 'User-Agent': '{} {} ({})'.format( @@ -228,34 +201,15 @@ def get_awx_http_client_headers(): return headers -class StubLicense(object): - - features = { - 'activity_streams': True, - 'ha': True, - 'ldap': True, - 'multiple_organizations': True, - 'surveys': True, - 'system_tracking': True, - 'rebranding': True, - 'enterprise_auth': True, - 'workflows': True, - } - - def validate(self): - return dict(license_key='OPEN', - valid_key=True, - compliant=True, - features=self.features, - license_type='open') - - def get_licenser(*args, **kwargs): + from awx.main.utils.licensing import Licenser, OpenLicense try: - from tower_license import TowerLicense - return TowerLicense(*args, **kwargs) - except ImportError: - return StubLicense(*args, **kwargs) + if os.path.exists('/var/lib/awx/.tower_version'): + return Licenser(*args, **kwargs) + else: + return OpenLicense() + except Exception as e: + raise ValueError(_('Error importing Tower License: %s') % e) def update_scm_url(scm_type, url, username=True, password=True, @@ -268,9 +222,8 @@ def update_scm_url(scm_type, url, username=True, password=True, ''' # Handle all of the URL formats supported by the SCM systems: # git: https://www.kernel.org/pub/software/scm/git/docs/git-clone.html#URLS - # hg: http://www.selenic.com/mercurial/hg.1.html#url-paths # svn: http://svnbook.red-bean.com/en/1.7/svn-book.html#svn.advanced.reposurls - if scm_type not in ('git', 'hg', 'svn', 'insights'): + if scm_type not in ('git', 'svn', 'insights', 'archive'): raise ValueError(_('Unsupported SCM type "%s"') % str(scm_type)) if not url.strip(): return '' @@ -302,8 +255,8 @@ def update_scm_url(scm_type, url, username=True, password=True, # SCP style before passed to git module. parts = urllib.parse.urlsplit('git+ssh://%s' % modified_url) # Handle local paths specified without file scheme (e.g. /path/to/foo). - # Only supported by git and hg. - elif scm_type in ('git', 'hg'): + # Only supported by git. + elif scm_type == 'git': if not url.startswith('/'): parts = urllib.parse.urlsplit('file:///%s' % url) else: @@ -314,9 +267,9 @@ def update_scm_url(scm_type, url, username=True, password=True, # Validate that scheme is valid for given scm_type. scm_type_schemes = { 'git': ('ssh', 'git', 'git+ssh', 'http', 'https', 'ftp', 'ftps', 'file'), - 'hg': ('http', 'https', 'ssh', 'file'), 'svn': ('http', 'https', 'svn', 'svn+ssh', 'file'), - 'insights': ('http', 'https') + 'insights': ('http', 'https'), + 'archive': ('http', 'https'), } if parts.scheme not in scm_type_schemes.get(scm_type, ()): raise ValueError(_('Unsupported %s URL') % scm_type) @@ -345,14 +298,8 @@ def update_scm_url(scm_type, url, username=True, password=True, if scm_type == 'git' and parts.scheme.endswith('ssh') and parts.hostname in special_git_hosts and netloc_password: #raise ValueError('Password not allowed for SSH access to %s.' % parts.hostname) netloc_password = '' - special_hg_hosts = ('bitbucket.org', 'altssh.bitbucket.org') - if scm_type == 'hg' and parts.scheme == 'ssh' and parts.hostname in special_hg_hosts and netloc_username != 'hg': - raise ValueError(_('Username must be "hg" for SSH access to %s.') % parts.hostname) - if scm_type == 'hg' and parts.scheme == 'ssh' and netloc_password: - #raise ValueError('Password not supported for SSH with Mercurial.') - netloc_password = '' - if netloc_username and parts.scheme != 'file' and scm_type != "insights": + if netloc_username and parts.scheme != 'file' and scm_type not in ("insights", "archive"): netloc = u':'.join([urllib.parse.quote(x,safe='') for x in (netloc_username, netloc_password) if x]) else: netloc = u'' @@ -380,13 +327,13 @@ def get_allowed_fields(obj, serializer_mapping): 'oauth2application': ['client_secret'] } model_name = obj._meta.model_name - field_blacklist = ACTIVITY_STREAM_FIELD_EXCLUSIONS.get(model_name, []) + fields_excluded = ACTIVITY_STREAM_FIELD_EXCLUSIONS.get(model_name, []) # see definition of from_db for CredentialType # injection logic of any managed types are incompatible with activity stream if model_name == 'credentialtype' and obj.managed_by_tower and obj.namespace: - field_blacklist.extend(['inputs', 'injectors']) - if field_blacklist: - allowed_fields = [f for f in allowed_fields if f not in field_blacklist] + fields_excluded.extend(['inputs', 'injectors']) + if fields_excluded: + allowed_fields = [f for f in allowed_fields if f not in fields_excluded] return allowed_fields @@ -1023,14 +970,17 @@ def get_custom_venv_choices(custom_paths=None): custom_venv_choices = [] for custom_venv_path in all_venv_paths: - if os.path.exists(custom_venv_path): - custom_venv_choices.extend([ - os.path.join(custom_venv_path, x, '') - for x in os.listdir(custom_venv_path) - if x != 'awx' and - os.path.isdir(os.path.join(custom_venv_path, x)) and - os.path.exists(os.path.join(custom_venv_path, x, 'bin', 'activate')) - ]) + try: + if os.path.exists(custom_venv_path): + custom_venv_choices.extend([ + os.path.join(custom_venv_path, x, '') + for x in os.listdir(custom_venv_path) + if x != 'awx' and + os.path.isdir(os.path.join(custom_venv_path, x)) and + os.path.exists(os.path.join(custom_venv_path, x, 'bin', 'activate')) + ]) + except Exception: + logger.exception("Encountered an error while discovering custom virtual environments.") return custom_venv_choices diff --git a/awx/main/utils/deletion.py b/awx/main/utils/deletion.py new file mode 100644 index 000000000000..8fc78d540f23 --- /dev/null +++ b/awx/main/utils/deletion.py @@ -0,0 +1,177 @@ +from django.contrib.contenttypes.models import ContentType +from django.db.models.deletion import ( + DO_NOTHING, Collector, get_candidate_relations_to_delete, +) +from collections import Counter, OrderedDict +from django.db import transaction +from django.db.models import sql + + +def bulk_related_objects(field, objs, using): + # This overrides the method in django.contrib.contenttypes.fields.py + """ + Return all objects related to ``objs`` via this ``GenericRelation``. + """ + return field.remote_field.model._base_manager.db_manager(using).filter(**{ + "%s__pk" % field.content_type_field_name: ContentType.objects.db_manager(using).get_for_model( + field.model, for_concrete_model=field.for_concrete_model).pk, + "%s__in" % field.object_id_field_name: list(objs.values_list('pk', flat=True)) + }) + + +def pre_delete(qs): + # taken from .delete method in django.db.models.query.py + assert qs.query.can_filter(), \ + "Cannot use 'limit' or 'offset' with delete." + + if qs._fields is not None: + raise TypeError("Cannot call delete() after .values() or .values_list()") + + del_query = qs._chain() + + # The delete is actually 2 queries - one to find related objects, + # and one to delete. Make sure that the discovery of related + # objects is performed on the same database as the deletion. + del_query._for_write = True + + # Disable non-supported fields. + del_query.query.select_for_update = False + del_query.query.select_related = False + del_query.query.clear_ordering(force_empty=True) + return del_query + + +class AWXCollector(Collector): + + def add(self, objs, source=None, nullable=False, reverse_dependency=False): + """ + Add 'objs' to the collection of objects to be deleted. If the call is + the result of a cascade, 'source' should be the model that caused it, + and 'nullable' should be set to True if the relation can be null. + + Return a list of all objects that were not already collected. + """ + if not objs.exists(): + return objs + model = objs.model + self.data.setdefault(model, []) + self.data[model].append(objs) + # Nullable relationships can be ignored -- they are nulled out before + # deleting, and therefore do not affect the order in which objects have + # to be deleted. + if source is not None and not nullable: + if reverse_dependency: + source, model = model, source + self.dependencies.setdefault( + source._meta.concrete_model, set()).add(model._meta.concrete_model) + return objs + + def add_field_update(self, field, value, objs): + """ + Schedule a field update. 'objs' must be a homogeneous iterable + collection of model instances (e.g. a QuerySet). + """ + if not objs.exists(): + return + model = objs.model + self.field_updates.setdefault(model, {}) + self.field_updates[model].setdefault((field, value), []) + self.field_updates[model][(field, value)].append(objs) + + def collect(self, objs, source=None, nullable=False, collect_related=True, + source_attr=None, reverse_dependency=False, keep_parents=False): + """ + Add 'objs' to the collection of objects to be deleted as well as all + parent instances. 'objs' must be a homogeneous iterable collection of + model instances (e.g. a QuerySet). If 'collect_related' is True, + related objects will be handled by their respective on_delete handler. + + If the call is the result of a cascade, 'source' should be the model + that caused it and 'nullable' should be set to True, if the relation + can be null. + + If 'reverse_dependency' is True, 'source' will be deleted before the + current model, rather than after. (Needed for cascading to parent + models, the one case in which the cascade follows the forwards + direction of an FK rather than the reverse direction.) + + If 'keep_parents' is True, data of parent model's will be not deleted. + """ + + if hasattr(objs, 'polymorphic_disabled'): + objs.polymorphic_disabled = True + + if self.can_fast_delete(objs): + self.fast_deletes.append(objs) + return + new_objs = self.add(objs, source, nullable, + reverse_dependency=reverse_dependency) + if not new_objs.exists(): + return + + model = new_objs.model + + if not keep_parents: + # Recursively collect concrete model's parent models, but not their + # related objects. These will be found by meta.get_fields() + concrete_model = model._meta.concrete_model + for ptr in concrete_model._meta.parents.keys(): + if ptr: + parent_objs = ptr.objects.filter(pk__in = new_objs.values_list('pk', flat=True)) + self.collect(parent_objs, source=model, + collect_related=False, + reverse_dependency=True) + if collect_related: + parents = model._meta.parents + for related in get_candidate_relations_to_delete(model._meta): + # Preserve parent reverse relationships if keep_parents=True. + if keep_parents and related.model in parents: + continue + field = related.field + if field.remote_field.on_delete == DO_NOTHING: + continue + related_qs = self.related_objects(related, new_objs) + if self.can_fast_delete(related_qs, from_field=field): + self.fast_deletes.append(related_qs) + elif related_qs: + field.remote_field.on_delete(self, field, related_qs, self.using) + for field in model._meta.private_fields: + if hasattr(field, 'bulk_related_objects'): + # It's something like generic foreign key. + sub_objs = bulk_related_objects(field, new_objs, self.using) + self.collect(sub_objs, source=model, nullable=True) + + def delete(self): + self.sort() + + # collect pk_list before deletion (once things start to delete + # queries might not be able to retreive pk list) + del_dict = OrderedDict() + for model, instances in self.data.items(): + del_dict.setdefault(model, []) + for inst in instances: + del_dict[model] += list(inst.values_list('pk', flat=True)) + + deleted_counter = Counter() + + with transaction.atomic(using=self.using, savepoint=False): + + # update fields + for model, instances_for_fieldvalues in self.field_updates.items(): + for (field, value), instances in instances_for_fieldvalues.items(): + for inst in instances: + query = sql.UpdateQuery(model) + query.update_batch(inst.values_list('pk', flat=True), + {field.name: value}, self.using) + # fast deletes + for qs in self.fast_deletes: + count = qs._raw_delete(using=self.using) + deleted_counter[qs.model._meta.label] += count + + # delete instances + for model, pk_list in del_dict.items(): + query = sql.DeleteQuery(model) + count = query.delete_batch(pk_list, self.using) + deleted_counter[model._meta.label] += count + + return sum(deleted_counter.values()), dict(deleted_counter) diff --git a/awx/main/utils/external_logging.py b/awx/main/utils/external_logging.py new file mode 100644 index 000000000000..f551dad2c2a8 --- /dev/null +++ b/awx/main/utils/external_logging.py @@ -0,0 +1,123 @@ +import os +import shutil +import tempfile +import urllib.parse as urlparse + +from django.conf import settings + +from awx.main.utils.reload import supervisor_service_command + + +def construct_rsyslog_conf_template(settings=settings): + tmpl = '' + parts = [] + enabled = getattr(settings, 'LOG_AGGREGATOR_ENABLED') + host = getattr(settings, 'LOG_AGGREGATOR_HOST', '') + 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) + spool_directory = getattr(settings, 'LOG_AGGREGATOR_MAX_DISK_USAGE_PATH', '/var/lib/awx').rstrip('/') + + if not os.access(spool_directory, os.W_OK): + spool_directory = '/var/lib/awx' + + max_bytes = settings.MAX_EVENT_RES_DATA + if settings.LOG_AGGREGATOR_RSYSLOGD_DEBUG: + parts.append('$DebugLevel 2') + parts.extend([ + '$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")', + 'template(name="awx" type="string" string="%rawmsg-after-pri%")', + ]) + + def escape_quotes(x): + return x.replace('"', '\\"') + + if not enabled: + parts.append('action(type="omfile" file="/dev/null")') # rsyslog needs *at least* one valid action to start + tmpl = '\n'.join(parts) + return tmpl + + if protocol.startswith('http'): + scheme = 'https' + # urlparse requires '//' to be provided if scheme is not specified + original_parsed = urlparse.urlsplit(host) + if (not original_parsed.scheme and not host.startswith('//')) or original_parsed.hostname is None: + host = '%s://%s' % (scheme, host) if scheme else '//%s' % host + parsed = urlparse.urlsplit(host) + + host = escape_quotes(parsed.hostname) + try: + if parsed.port: + port = parsed.port + except ValueError: + port = settings.LOG_AGGREGATOR_PORT + + # https://github.com/rsyslog/rsyslog-doc/blob/master/source/configuration/modules/omhttp.rst + ssl = 'on' if parsed.scheme == 'https' else 'off' + skip_verify = 'off' if settings.LOG_AGGREGATOR_VERIFY_CERT else 'on' + allow_unsigned = 'off' if settings.LOG_AGGREGATOR_VERIFY_CERT else 'on' + if not port: + port = 443 if parsed.scheme == 'https' else 80 + + params = [ + 'type="omhttp"', + f'server="{host}"', + f'serverport="{port}"', + f'usehttps="{ssl}"', + f'allowunsignedcerts="{allow_unsigned}"', + f'skipverifyhost="{skip_verify}"', + 'action.resumeRetryCount="-1"', + 'template="awx"', + 'errorfile="/var/log/tower/rsyslog.err"', + f'action.resumeInterval="{timeout}"' + ] + if parsed.path: + path = urlparse.quote(parsed.path[1:], safe='/=') + if parsed.query: + path = f'{path}?{urlparse.quote(parsed.query)}' + params.append(f'restpath="{path}"') + username = escape_quotes(getattr(settings, 'LOG_AGGREGATOR_USERNAME', '')) + password = escape_quotes(getattr(settings, 'LOG_AGGREGATOR_PASSWORD', '')) + if getattr(settings, 'LOG_AGGREGATOR_TYPE', None) == 'splunk': + # splunk has a weird authorization header + if password: + # from omhttp docs: + # https://www.rsyslog.com/doc/v8-stable/configuration/modules/omhttp.html + # > Currently only a single additional header/key pair is + # > configurable, further development is needed to support + # > arbitrary header key/value lists. + params.append('httpheaderkey="Authorization"') + params.append(f'httpheadervalue="Splunk {password}"') + elif username: + params.append(f'uid="{username}"') + if password: + # you can only have a basic auth password if there's a username + params.append(f'pwd="{password}"') + 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 + ) + else: + parts.append('action(type="omfile" file="/dev/null")') # rsyslog needs *at least* one valid action to start + tmpl = '\n'.join(parts) + return tmpl + + +def reconfigure_rsyslog(): + tmpl = construct_rsyslog_conf_template() + # Write config to a temp file then move it to preserve atomicity + with tempfile.TemporaryDirectory(prefix='rsyslog-conf-') as temp_dir: + path = temp_dir + '/rsyslog.conf.temp' + with open(path, 'w') as f: + os.chmod(path, 0o640) + f.write(tmpl + '\n') + shutil.move(path, '/var/lib/awx/rsyslog/rsyslog.conf') + supervisor_service_command(command='restart', service='awx-rsyslogd') diff --git a/awx/main/utils/filters.py b/awx/main/utils/filters.py index 2edee2b66a4b..5a01599e862c 100644 --- a/awx/main/utils/filters.py +++ b/awx/main/utils/filters.py @@ -15,7 +15,7 @@ from django.db import models from django.conf import settings -from awx.main.constants import LOGGER_BLACKLIST +from awx.main.constants import LOGGER_BLOCKLIST from awx.main.utils.common import get_search_fields __all__ = ['SmartFilter', 'ExternalLoggerEnabled', 'DynamicLevelFilter'] @@ -48,11 +48,11 @@ def __set__(self, instance, value): instance.settings_override[self.setting_name] = value -def record_is_blacklisted(record): - """Given a log record, return True if it is considered to be in - the logging blacklist, return False if not +def record_is_blocked(record): + """Given a log record, return True if it is considered to be + blocked, return False if not """ - for logger_name in LOGGER_BLACKLIST: + for logger_name in LOGGER_BLOCKLIST: if record.name.startswith(logger_name): return True return False @@ -81,7 +81,7 @@ def filter(self, record): True - should be logged """ # Do not send exceptions to external logger - if record_is_blacklisted(record): + if record_is_blocked(record): return False # General enablement if not self.enabled_flag: @@ -108,8 +108,8 @@ def filter(self, record): """Filters out logs that have a level below the threshold defined by the databse setting LOG_AGGREGATOR_LEVEL """ - if record_is_blacklisted(record): - # Fine to write blacklisted loggers to file, apply default filtering level + if record_is_blocked(record): + # Fine to write denied loggers to file, apply default filtering level cutoff_level = logging.WARNING else: try: @@ -179,7 +179,7 @@ def strip_quotes_json_logic(self, v): pyparsing do the heavy lifting. TODO: separate django filter requests from our custom json filter request so we don't process the key any. This could be - accomplished using a whitelist or introspecting the + accomplished using an allowed list or introspecting the relationship refered to to see if it's a jsonb type. ''' def _json_path_to_contains(self, k, v): diff --git a/awx/main/utils/formatters.py b/awx/main/utils/formatters.py index 19850c359719..8afd121d5cbd 100644 --- a/awx/main/utils/formatters.py +++ b/awx/main/utils/formatters.py @@ -3,13 +3,14 @@ from copy import copy import json -import time import logging import traceback import socket from datetime import datetime - +from dateutil.tz import tzutc +from django.utils.timezone import now +from django.core.serializers.json import DjangoJSONEncoder from django.conf import settings @@ -17,8 +18,15 @@ class TimeFormatter(logging.Formatter): ''' Custom log formatter used for inventory imports ''' + def __init__(self, start_time=None, **kwargs): + if start_time is None: + self.job_start = now() + else: + self.job_start = start_time + super(TimeFormatter, self).__init__(**kwargs) + def format(self, record): - record.relativeSeconds = record.relativeCreated / 1000.0 + record.relativeSeconds = (now() - self.job_start).total_seconds() return logging.Formatter.format(self, record) @@ -91,22 +99,28 @@ def get_debug_fields(self, record): 'processName': record.processName, } - @classmethod - def format_timestamp(cls, time): - tstamp = datetime.utcfromtimestamp(time) - return tstamp.strftime("%Y-%m-%dT%H:%M:%S") + ".%03d" % (tstamp.microsecond / 1000) + "Z" - @classmethod def format_exception(cls, exc_info): return ''.join(traceback.format_exception(*exc_info)) if exc_info else '' @classmethod def serialize(cls, message): - return bytes(json.dumps(message), 'utf-8') + return json.dumps(message, cls=DjangoJSONEncoder) + '\n' class LogstashFormatter(LogstashFormatterBase): + def __init__(self, *args, **kwargs): + self.cluster_host_id = settings.CLUSTER_HOST_ID + self.tower_uuid = None + uuid = ( + getattr(settings, 'LOG_AGGREGATOR_TOWER_UUID', None) or + getattr(settings, 'INSTALL_UUID', None) + ) + if uuid: + self.tower_uuid = uuid + super(LogstashFormatter, self).__init__(*args, **kwargs) + def reformat_data_for_log(self, raw_data, kind=None): ''' Process dictionaries from various contexts (job events, activity stream @@ -121,45 +135,14 @@ def reformat_data_for_log(self, raw_data, kind=None): pass # best effort here, if it's not valid JSON, then meh return raw_data elif kind == 'system_tracking': - data = copy(raw_data['ansible_facts']) + data = copy(raw_data.get('ansible_facts', {})) else: data = copy(raw_data) if isinstance(data, str): data = json.loads(data) data_for_log = {} - def index_by_name(alist): - """Takes a list of dictionaries with `name` as a key in each dict - and returns a dictionary indexed by those names""" - adict = {} - for item in alist: - subdict = copy(item) - if 'name' in subdict: - name = subdict.get('name', None) - elif 'path' in subdict: - name = subdict.get('path', None) - if name: - # Logstash v2 can not accept '.' in a name - name = name.replace('.', '_') - adict[name] = subdict - return adict - - def convert_to_type(t, val): - if t is float: - val = val[:-1] if val.endswith('s') else val - try: - return float(val) - except ValueError: - return val - elif t is int: - try: - return int(val) - except ValueError: - return val - elif t is str: - return val - - if kind == 'job_events': + if kind == 'job_events' and raw_data.get('python_objects', {}).get('job_event'): job_event = raw_data['python_objects']['job_event'] for field_object in job_event._meta.fields: @@ -177,9 +160,6 @@ def convert_to_type(t, val): try: data_for_log[key] = getattr(job_event, fd) - if fd in ['created', 'modified'] and data_for_log[key] is not None: - time_float = time.mktime(data_for_log[key].timetuple()) - data_for_log[key] = self.format_timestamp(time_float) except Exception as e: data_for_log[key] = 'Exception `{}` producing field'.format(e) @@ -193,11 +173,26 @@ def convert_to_type(t, val): data['ansible_python'].pop('version_info', None) data_for_log['ansible_facts'] = data - data_for_log['ansible_facts_modified'] = raw_data['ansible_facts_modified'] - data_for_log['inventory_id'] = raw_data['inventory_id'] - data_for_log['host_name'] = raw_data['host_name'] - data_for_log['job_id'] = raw_data['job_id'] + data_for_log['ansible_facts_modified'] = raw_data.get('ansible_facts_modified') + data_for_log['inventory_id'] = raw_data.get('inventory_id') + data_for_log['host_name'] = raw_data.get('host_name') + data_for_log['job_id'] = raw_data.get('job_id') elif kind == 'performance': + def convert_to_type(t, val): + if t is float: + val = val[:-1] if val.endswith('s') else val + try: + return float(val) + except ValueError: + return val + elif t is int: + try: + return int(val) + except ValueError: + return val + elif t is str: + return val + request = raw_data['python_objects']['request'] response = raw_data['python_objects']['response'] @@ -231,28 +226,17 @@ def get_extra_fields(self, record): log_kind = record.name[len('awx.analytics.'):] fields = self.reformat_data_for_log(fields, kind=log_kind) # General AWX metadata - for log_name, setting_name in [ - ('type', 'LOG_AGGREGATOR_TYPE'), - ('cluster_host_id', 'CLUSTER_HOST_ID'), - ('tower_uuid', 'LOG_AGGREGATOR_TOWER_UUID')]: - if hasattr(settings, setting_name): - fields[log_name] = getattr(settings, setting_name, None) - elif log_name == 'type': - fields[log_name] = 'other' - - uuid = ( - getattr(settings, 'LOG_AGGREGATOR_TOWER_UUID', None) or - getattr(settings, 'INSTALL_UUID', None) - ) - if uuid: - fields['tower_uuid'] = uuid + fields['cluster_host_id'] = self.cluster_host_id + fields['tower_uuid'] = self.tower_uuid return fields def format(self, record): + stamp = datetime.utcfromtimestamp(record.created) + stamp = stamp.replace(tzinfo=tzutc()) message = { # Field not included, but exist in related logs # 'path': record.pathname - '@timestamp': self.format_timestamp(record.created), + '@timestamp': stamp, 'message': record.getMessage(), 'host': self.host, @@ -268,4 +252,7 @@ def format(self, record): if record.exc_info: 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} return self.serialize(message) diff --git a/awx/main/utils/handlers.py b/awx/main/utils/handlers.py index 289f64d06466..b6eefd9c5952 100644 --- a/awx/main/utils/handlers.py +++ b/awx/main/utils/handlers.py @@ -3,387 +3,97 @@ # Python import logging -import json -import requests -import time -import threading -import socket -import select -from urllib import parse as urlparse -from concurrent.futures import ThreadPoolExecutor -from requests.exceptions import RequestException +import os.path # Django from django.conf import settings - -# requests futures, a dependency used by these handlers -from requests_futures.sessions import FuturesSession +from django.utils.timezone import now # AWX -from awx.main.utils.formatters import LogstashFormatter - - -__all__ = ['BaseHTTPSHandler', 'TCPHandler', 'UDPHandler', - 'AWXProxyHandler'] - - -logger = logging.getLogger('awx.main.utils.handlers') - - -# Translation of parameter names to names in Django settings -# logging settings category, only those related to handler / log emission -PARAM_NAMES = { - 'host': 'LOG_AGGREGATOR_HOST', - 'port': 'LOG_AGGREGATOR_PORT', - 'message_type': 'LOG_AGGREGATOR_TYPE', - 'username': 'LOG_AGGREGATOR_USERNAME', - 'password': 'LOG_AGGREGATOR_PASSWORD', - 'indv_facts': 'LOG_AGGREGATOR_INDIVIDUAL_FACTS', - 'tcp_timeout': 'LOG_AGGREGATOR_TCP_TIMEOUT', - 'verify_cert': 'LOG_AGGREGATOR_VERIFY_CERT', - 'protocol': 'LOG_AGGREGATOR_PROTOCOL' -} - - -def unused_callback(sess, resp): - pass - - -class LoggingConnectivityException(Exception): - pass - - -class VerboseThreadPoolExecutor(ThreadPoolExecutor): - - last_log_emit = 0 - - def submit(self, func, *args, **kwargs): - def _wrapped(*args, **kwargs): - try: - return func(*args, **kwargs) - except Exception: - # If an exception occurs in a concurrent thread worker (like - # a ConnectionError or a read timeout), periodically log - # that failure. - # - # This approach isn't really thread-safe, so we could - # potentially log once per thread every 10 seconds, but it - # beats logging *every* failed HTTP request in a scenario where - # you've typo'd your log aggregator hostname. - now = time.time() - if now - self.last_log_emit > 10: - logger.exception('failed to emit log to external aggregator') - self.last_log_emit = now - raise - return super(VerboseThreadPoolExecutor, self).submit(_wrapped, *args, - **kwargs) +from awx.main.exceptions import PostRunError -class SocketResult: - ''' - A class to be the return type of methods that send data over a socket - allows object to be used in the same way as a request futures object - ''' - def __init__(self, ok, reason=None): - self.ok = ok - self.reason = reason +class RSysLogHandler(logging.handlers.SysLogHandler): - def result(self): - return self + append_nul = False + def _connect_unixsocket(self, address): + super(RSysLogHandler, self)._connect_unixsocket(address) + self.socket.setblocking(False) -class BaseHandler(logging.Handler): - def __init__(self, host=None, port=None, indv_facts=None, **kwargs): - super(BaseHandler, self).__init__() - self.host = host - self.port = port - self.indv_facts = indv_facts - - def _send(self, payload): - """Actually send message to log aggregator. - """ - return payload - - def _format_and_send_record(self, record): - if self.indv_facts: - return [self._send(json.loads(self.format(record)))] - return [self._send(self.format(record))] - - def emit(self, record): - """ - Emit a log record. Returns a list of zero or more - implementation-specific objects for tests. - """ - try: - return self._format_and_send_record(record) - except (KeyboardInterrupt, SystemExit): - raise - except Exception: - self.handleError(record) - - def _get_host(self, scheme='', hostname_only=False): - """Return the host name of log aggregator. - """ - host = self.host or '' - # urlparse requires '//' to be provided if scheme is not specified - original_parsed = urlparse.urlsplit(host) - if (not original_parsed.scheme and not host.startswith('//')) or original_parsed.hostname is None: - host = '%s://%s' % (scheme, host) if scheme else '//%s' % host - parsed = urlparse.urlsplit(host) - - if hostname_only: - return parsed.hostname - + def emit(self, msg): + if not settings.LOG_AGGREGATOR_ENABLED: + return + if not os.path.exists(settings.LOGGING['handlers']['external_logger']['address']): + return try: - port = parsed.port or self.port - except ValueError: - port = self.port - netloc = parsed.netloc if port is None else '%s:%s' % (parsed.hostname, port) - - url_components = list(parsed) - url_components[1] = netloc - ret = urlparse.urlunsplit(url_components) - return ret.lstrip('/') - - -class BaseHTTPSHandler(BaseHandler): - ''' - Originally derived from python-logstash library - Non-blocking request accomplished by FuturesSession, similar - to the loggly-python-handler library - ''' - def _add_auth_information(self): - if self.message_type == 'logstash': - if not self.username: - # Logstash authentication not enabled - return - logstash_auth = requests.auth.HTTPBasicAuth(self.username, self.password) - self.session.auth = logstash_auth - elif self.message_type == 'splunk': - auth_header = "Splunk %s" % self.password - headers = { - "Authorization": auth_header, - "Content-Type": "application/json" - } - self.session.headers.update(headers) - - def __init__(self, fqdn=False, message_type=None, username=None, password=None, - tcp_timeout=5, verify_cert=True, **kwargs): - self.fqdn = fqdn - self.message_type = message_type - self.username = username - self.password = password - self.tcp_timeout = tcp_timeout - self.verify_cert = verify_cert - super(BaseHTTPSHandler, self).__init__(**kwargs) - self.session = FuturesSession(executor=VerboseThreadPoolExecutor( - max_workers=2 # this is the default used by requests_futures - )) - self._add_auth_information() - - def _get_post_kwargs(self, payload_input): - if self.message_type == 'splunk': - # Splunk needs data nested under key "event" - if not isinstance(payload_input, dict): - payload_input = json.loads(payload_input) - payload_input = {'event': payload_input} - if isinstance(payload_input, dict): - payload_str = json.dumps(payload_input) + return super(RSysLogHandler, self).emit(msg) + except ConnectionRefusedError: + # rsyslogd has gone to lunch; this generally means that it's just + # been restarted (due to a configuration change) + # unfortunately, we can't log that because...rsyslogd is down (and + # would just us back ddown this code path) + pass + except BlockingIOError: + # for , rsyslogd is no longer reading from the domain socket, and + # we're unable to write any more to it without blocking (we've seen this behavior + # from time to time when logging is totally misconfigured; + # in this scenario, it also makes more sense to just drop the messages, + # because the alternative is blocking the socket.send() in the + # Python process, which we definitely don't want to do) + pass + + +class SpecialInventoryHandler(logging.Handler): + """Logging handler used for the saving-to-database part of inventory updates + ran by the task system + this dispatches events directly to be processed by the callback receiver, + as opposed to ansible-runner + """ + + def __init__(self, event_handler, cancel_callback, job_timeout, verbosity, + start_time=None, counter=0, initial_line=0, **kwargs): + self.event_handler = event_handler + self.cancel_callback = cancel_callback + self.job_timeout = job_timeout + if start_time is None: + self.job_start = now() else: - payload_str = payload_input - kwargs = dict(data=payload_str, background_callback=unused_callback, - timeout=self.tcp_timeout) - if self.verify_cert is False: - kwargs['verify'] = False - return kwargs - - - def _send(self, payload): - """See: - https://docs.python.org/3/library/concurrent.futures.html#future-objects - http://pythonhosted.org/futures/ - """ - return self.session.post(self._get_host(scheme='https'), - **self._get_post_kwargs(payload)) - - -def _encode_payload_for_socket(payload): - encoded_payload = payload - if isinstance(encoded_payload, dict): - encoded_payload = json.dumps(encoded_payload, ensure_ascii=False) - if isinstance(encoded_payload, str): - encoded_payload = encoded_payload.encode('utf-8') - return encoded_payload - - -class TCPHandler(BaseHandler): - def __init__(self, tcp_timeout=5, **kwargs): - self.tcp_timeout = tcp_timeout - super(TCPHandler, self).__init__(**kwargs) - - def _send(self, payload): - payload = _encode_payload_for_socket(payload) - sok = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - try: - sok.connect((self._get_host(hostname_only=True), self.port or 0)) - sok.setblocking(0) - _, ready_to_send, _ = select.select([], [sok], [], float(self.tcp_timeout)) - if len(ready_to_send) == 0: - ret = SocketResult(False, "Socket currently busy, failed to send message") - logger.warning(ret.reason) - else: - sok.send(payload) - ret = SocketResult(True) # success! - except Exception as e: - ret = SocketResult(False, "Error sending message from %s: %s" % - (TCPHandler.__name__, - ' '.join(str(arg) for arg in e.args))) - logger.exception(ret.reason) - finally: - sok.close() - return ret - - -class UDPHandler(BaseHandler): - message = "Cannot determine if UDP messages are received." - - def __init__(self, **kwargs): - super(UDPHandler, self).__init__(**kwargs) - self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - - def _send(self, payload): - payload = _encode_payload_for_socket(payload) - self.socket.sendto(payload, (self._get_host(hostname_only=True), self.port or 0)) - return SocketResult(True, reason=self.message) - - -class AWXNullHandler(logging.NullHandler): - ''' - Only additional this does is accept arbitrary __init__ params because - the proxy handler does not (yet) work with arbitrary handler classes - ''' - def __init__(self, *args, **kwargs): - super(AWXNullHandler, self).__init__() - - -HANDLER_MAPPING = { - 'https': BaseHTTPSHandler, - 'tcp': TCPHandler, - 'udp': UDPHandler, -} - - -class AWXProxyHandler(logging.Handler): - ''' - Handler specific to the AWX external logging feature - - Will dynamically create a handler specific to the configured - protocol, and will create a new one automatically on setting change - - Managing parameters: - All parameters will get their value from settings as a default - if the parameter was either provided on init, or set manually, - this value will take precedence. - Parameters match same parameters in the actualized handler classes. - ''' - - thread_local = threading.local() - _auditor = None - - def __init__(self, **kwargs): - # TODO: process 'level' kwarg - super(AWXProxyHandler, self).__init__(**kwargs) - self._handler = None - self._old_kwargs = {} - - @property - def auditor(self): - if not self._auditor: - self._auditor = logging.handlers.RotatingFileHandler( - filename='/var/log/tower/external.log', - maxBytes=1024 * 1024 * 50, # 50 MB - backupCount=5, - ) - - class WritableLogstashFormatter(LogstashFormatter): - @classmethod - def serialize(cls, message): - return json.dumps(message) - - self._auditor.setFormatter(WritableLogstashFormatter()) - return self._auditor - - def get_handler_class(self, protocol): - return HANDLER_MAPPING.get(protocol, AWXNullHandler) - - def get_handler(self, custom_settings=None, force_create=False): - new_kwargs = {} - use_settings = custom_settings or settings - for field_name, setting_name in PARAM_NAMES.items(): - val = getattr(use_settings, setting_name, None) - if val is None: - continue - new_kwargs[field_name] = val - if new_kwargs == self._old_kwargs and self._handler and (not force_create): - # avoids re-creating session objects, and other such things - return self._handler - self._old_kwargs = new_kwargs.copy() - # TODO: remove any kwargs no applicable to that particular handler - protocol = new_kwargs.pop('protocol', None) - HandlerClass = self.get_handler_class(protocol) - # cleanup old handler and make new one - if self._handler: - self._handler.close() - logger.debug('Creating external log handler due to startup or settings change.') - self._handler = HandlerClass(**new_kwargs) - if self.formatter: - # self.format(record) is called inside of emit method - # so not safe to assume this can be handled within self - self._handler.setFormatter(self.formatter) - return self._handler + self.job_start = start_time + self.last_check = self.job_start + self.counter = counter + self.skip_level = [logging.WARNING, logging.INFO, logging.DEBUG, 0][verbosity] + self._current_line = initial_line + super(SpecialInventoryHandler, self).__init__(**kwargs) def emit(self, record): - if AWXProxyHandler.thread_local.enabled: - actual_handler = self.get_handler() - if settings.LOG_AGGREGATOR_AUDIT: - self.auditor.setLevel(settings.LOG_AGGREGATOR_LEVEL) - self.auditor.emit(record) - return actual_handler.emit(record) - - def perform_test(self, custom_settings): - """ - Tests logging connectivity for given settings module. - @raises LoggingConnectivityException - """ - handler = self.get_handler(custom_settings=custom_settings, force_create=True) - handler.setFormatter(LogstashFormatter()) - logger = logging.getLogger(__file__) - fn, lno, func, _ = logger.findCaller() - record = logger.makeRecord('awx', 10, fn, lno, - 'AWX Connection Test', tuple(), - None, func) - futures = handler.emit(record) - for future in futures: - try: - resp = future.result() - if not resp.ok: - if isinstance(resp, SocketResult): - raise LoggingConnectivityException( - 'Socket error: {}'.format(resp.reason or '') - ) - else: - raise LoggingConnectivityException( - ': '.join([str(resp.status_code), resp.reason or '']) - ) - except RequestException as e: - raise LoggingConnectivityException(str(e)) - - @classmethod - def disable(cls): - cls.thread_local.enabled = False - - -AWXProxyHandler.thread_local.enabled = True + # check cancel and timeout status regardless of log level + this_time = now() + if (this_time - self.last_check).total_seconds() > 0.5: # cancel callback is expensive + self.last_check = this_time + if self.cancel_callback(): + raise PostRunError('Inventory update has been canceled', status='canceled') + if self.job_timeout and ((this_time - self.job_start).total_seconds() > self.job_timeout): + raise PostRunError('Inventory update has timed out', status='canceled') + + # skip logging for low severity logs + if record.levelno < self.skip_level: + return + + self.counter += 1 + msg = self.format(record) + n_lines = len(msg.strip().split('\n')) # don't count line breaks at boundry of text + dispatch_data = dict( + created=now().isoformat(), + event='verbose', + counter=self.counter, + stdout=msg, + start_line=self._current_line, + end_line=self._current_line + n_lines + ) + self._current_line += n_lines + + self.event_handler(dispatch_data) ColorHandler = logging.StreamHandler diff --git a/awx/main/utils/licensing.py b/awx/main/utils/licensing.py new file mode 100644 index 000000000000..9b248536b500 --- /dev/null +++ b/awx/main/utils/licensing.py @@ -0,0 +1,439 @@ +# Copyright (c) 2015 Ansible, Inc. +# All Rights Reserved. + +''' +This is intended to be a lightweight license class for verifying subscriptions, and parsing subscription data +from entitlement certificates. + +The Licenser class can do the following: + - Parse an Entitlement cert to generate license +''' + +import base64 +import configparser +from datetime import datetime, timezone +import collections +import copy +import io +import json +import logging +import re +import requests +import time +import zipfile + +from dateutil.parser import parse as parse_date + +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography import x509 + +# Django +from django.conf import settings +from django.utils.translation import ugettext_lazy as _ + +# AWX +from awx.main.models import Host + +MAX_INSTANCES = 9999999 + +logger = logging.getLogger(__name__) + + +def rhsm_config(): + path = '/etc/rhsm/rhsm.conf' + config = configparser.ConfigParser() + config.read(path) + return config + + +def validate_entitlement_manifest(data): + buff = io.BytesIO() + buff.write(base64.b64decode(data)) + try: + z = zipfile.ZipFile(buff) + except zipfile.BadZipFile as e: + raise ValueError(_("Invalid manifest: a subscription manifest zip file is required.")) from e + buff = io.BytesIO() + + files = z.namelist() + if 'consumer_export.zip' not in files or 'signature' not in files: + raise ValueError(_("Invalid manifest: missing required files.")) + export = z.open('consumer_export.zip').read() + sig = z.open('signature').read() + with open('/etc/tower/candlepin-redhat-ca.crt', 'rb') as f: + cert = x509.load_pem_x509_certificate(f.read(), backend=default_backend()) + key = cert.public_key() + try: + key.verify(sig, export, padding=padding.PKCS1v15(), algorithm=hashes.SHA256()) + except InvalidSignature as e: + raise ValueError(_("Invalid manifest: signature verification failed.")) from e + + buff.write(export) + z = zipfile.ZipFile(buff) + subs = [] + for f in z.filelist: + if f.filename.startswith('export/entitlements') and f.filename.endswith('.json'): + subs.append(json.loads(z.open(f).read())) + if subs: + return subs + raise ValueError(_("Invalid manifest: manifest contains no subscriptions.")) + + +class OpenLicense(object): + def validate(self): + return dict( + license_type='open', + valid_key=True, + subscription_name='OPEN', + product_name="AWX", + ) + + +class Licenser(object): + # warn when there is a month (30 days) left on the subscription + SUBSCRIPTION_TIMEOUT = 60 * 60 * 24 * 30 + + UNLICENSED_DATA = dict( + subscription_name=None, + sku=None, + support_level=None, + instance_count=0, + license_date=0, + license_type="UNLICENSED", + product_name="Red Hat Ansible Automation Platform", + valid_key=False + ) + + def __init__(self, **kwargs): + self._attrs = dict( + instance_count=0, + license_date=0, + license_type='UNLICENSED', + ) + self.config = rhsm_config() + if not kwargs: + license_setting = getattr(settings, 'LICENSE', None) + if license_setting is not None: + kwargs = license_setting + + if 'company_name' in kwargs: + kwargs.pop('company_name') + self._attrs.update(kwargs) + if 'valid_key' in self._attrs: + if not self._attrs['valid_key']: + self._unset_attrs() + else: + self._unset_attrs() + + + def _unset_attrs(self): + self._attrs = self.UNLICENSED_DATA.copy() + + + def license_from_manifest(self, manifest): + def is_appropriate_manifest_sub(sub): + if sub['pool']['activeSubscription'] is False: + return False + now = datetime.now(timezone.utc) + if parse_date(sub['startDate']) > now: + return False + if parse_date(sub['endDate']) < now: + return False + products = sub['pool']['providedProducts'] + if any(product.get('productId') == '480' for product in products): + return True + return False + + def _can_aggregate(sub, license): + # We aggregate multiple subs into a larger meta-sub, if they match + # + # No current sub in aggregate + if not license: + return True + # Same SKU type (SER vs MCT vs others)? + if license['sku'][0:3] != sub['pool']['productId'][0:3]: + return False + return True + + # Parse output for subscription metadata to build config + license = dict() + for sub in manifest: + if not is_appropriate_manifest_sub(sub): + logger.warning("Subscription %s (%s) in manifest is not active or for another product" % + (sub['pool']['productName'], sub['pool']['productId'])) + continue + if not _can_aggregate(sub, license): + logger.warning("Subscription %s (%s) in manifest does not match other manifest subscriptions" % + (sub['pool']['productName'], sub['pool']['productId'])) + continue + + license.setdefault('sku', sub['pool']['productId']) + license.setdefault('subscription_name', sub['pool']['productName']) + license.setdefault('pool_id', sub['pool']['id']) + license.setdefault('product_name', sub['pool']['productName']) + license.setdefault('valid_key', True) + license.setdefault('license_type', 'enterprise') + license.setdefault('satellite', False) + # Use the nearest end date + endDate = parse_date(sub['endDate']) + currentEndDateStr = license.get('license_date', '4102462800') # 2100-01-01 + currentEndDate = datetime.fromtimestamp(int(currentEndDateStr), timezone.utc) + if endDate < currentEndDate: + license['license_date'] = endDate.strftime('%s') + instances = sub['quantity'] + 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']) + + if not license: + logger.error("No valid subscriptions found in manifest") + self._attrs.update(license) + settings.LICENSE = self._attrs + return self._attrs + + + def update(self, **kwargs): + # Update attributes of the current license. + if 'instance_count' in kwargs: + kwargs['instance_count'] = int(kwargs['instance_count']) + if 'license_date' in kwargs: + kwargs['license_date'] = int(kwargs['license_date']) + self._attrs.update(kwargs) + + + def validate_rh(self, user, pw): + 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 + if not host: + host = getattr(settings, 'REDHAT_CANDLEPIN_HOST', None) + + if not user: + raise ValueError('subscriptions_username is required') + + if not pw: + raise ValueError('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) + else: + json = self.get_satellite_subs(host, user, pw) + return self.generate_license_options_from_entitlements(json) + return [] + + + def get_rhsm_subs(self, host, user, pw): + verify = getattr(settings, 'REDHAT_CANDLEPIN_VERIFY', True) + json = [] + try: + subs = requests.get( + '/'.join([host, 'subscription/users/{}/owners'.format(user)]), + verify=verify, + auth=(user, pw) + ) + except requests.exceptions.ConnectionError as error: + raise error + except OSError as error: + raise OSError('Unable to open certificate bundle {}. Check that Ansible Tower is running on Red Hat Enterprise Linux.'.format(verify)) from error # noqa + subs.raise_for_status() + + for sub in subs.json(): + resp = requests.get( + '/'.join([ + host, + 'subscription/owners/{}/pools/?match=*tower*'.format(sub['key']) + ]), + verify=verify, + auth=(user, pw) + ) + resp.raise_for_status() + json.extend(resp.json()) + return json + + + def get_satellite_subs(self, host, user, pw): + port = None + try: + verify = str(self.config.get("rhsm", "repo_ca_cert")) + 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) + if port: + host = ':'.join([host, port]) + json = [] + try: + orgs = requests.get( + '/'.join([host, 'katello/api/organizations']), + verify=verify, + auth=(user, pw) + ) + except requests.exceptions.ConnectionError as error: + raise error + except OSError as error: + raise OSError('Unable to open certificate bundle {}. Check that Ansible Tower is running on Red Hat Enterprise Linux.'.format(verify)) from error # noqa + orgs.raise_for_status() + + for org in orgs.json()['results']: + resp = requests.get( + '/'.join([ + host, + '/katello/api/organizations/{}/subscriptions/?search=Red Hat Ansible Automation'.format(org['id']) + ]), + verify=verify, + auth=(user, pw) + ) + resp.raise_for_status() + results = resp.json()['results'] + if results != []: + for sub in results: + # Parse output for subscription metadata to build config + license = dict() + license['productId'] = sub['product_id'] + license['quantity'] = int(sub['quantity']) + license['support_level'] = sub['support_level'] + license['subscription_name'] = sub['name'] + license['id'] = sub['upstream_pool_id'] + license['endDate'] = sub['end_date'] + license['productName'] = "Red Hat Ansible Automation" + license['valid_key'] = True + license['license_type'] = 'enterprise' + license['satellite'] = True + 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 + # Products that contain Ansible Tower + products = sub.get('providedProducts', []) + if any(product.get('productId') == '480' for product in products): + return True + return False + + + def generate_license_options_from_entitlements(self, json): + from dateutil.parser import parse + ValidSub = collections.namedtuple('ValidSub', 'sku name support_level end_date trial quantity pool_id satellite') + valid_subs = [] + for sub in json: + satellite = sub.get('satellite') + if satellite: + is_valid = self.is_appropriate_sat_sub(sub) + else: + is_valid = self.is_appropriate_sub(sub) + if is_valid: + try: + end_date = parse(sub.get('endDate')) + except Exception: + continue + now = datetime.utcnow() + 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'] + else: + for attr in sub.get('productAttributes', []): + if attr.get('name') == 'support_level': + support_level = attr.get('value') + + valid_subs.append(ValidSub( + sku, sub['productName'], support_level, end_date, trial, quantity, pool_id, satellite + )) + + if valid_subs: + licenses = [] + for sub in valid_subs: + license = self.__class__(subscription_name='Red Hat Ansible Automation Platform') + license._attrs['instance_count'] = int(sub.quantity) + license._attrs['sku'] = sub.sku + license._attrs['support_level'] = sub.support_level + license._attrs['license_type'] = 'enterprise' + if sub.trial: + license._attrs['trial'] = True + license._attrs['license_type'] = 'trial' + license._attrs['instance_count'] = min( + MAX_INSTANCES, license._attrs['instance_count'] + ) + human_instances = license._attrs['instance_count'] + if human_instances == MAX_INSTANCES: + human_instances = 'Unlimited' + subscription_name = re.sub( + r' \([\d]+ Managed Nodes', + ' ({} Managed Nodes'.format(human_instances), + sub.name + ) + license._attrs['subscription_name'] = subscription_name + 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 + ) + licenses.append(license._attrs.copy()) + return licenses + + raise ValueError( + 'No valid Red Hat Ansible Automation subscription could be found for this account.' # noqa + ) + + + def validate(self): + # Return license attributes with additional validation info. + attrs = copy.deepcopy(self._attrs) + type = attrs.get('license_type', 'none') + + if (type == 'UNLICENSED' or False): + attrs.update(dict(valid_key=False, compliant=False)) + return attrs + attrs['valid_key'] = True + + if Host: + current_instances = Host.objects.active_count() + else: + current_instances = 0 + instance_count = int(attrs.get('instance_count', 0)) + attrs['current_instances'] = current_instances + free_instances = (instance_count - current_instances) + attrs['free_instances'] = max(0, free_instances) + + license_date = int(attrs.get('license_date', 0) or 0) + current_date = int(time.time()) + time_remaining = license_date - current_date + attrs['time_remaining'] = time_remaining + if attrs.setdefault('trial', False): + attrs['grace_period_remaining'] = time_remaining + else: + attrs['grace_period_remaining'] = (license_date + 2592000) - current_date + attrs['compliant'] = bool(time_remaining > 0 and free_instances >= 0) + attrs['date_warning'] = bool(time_remaining < self.SUBSCRIPTION_TIMEOUT) + attrs['date_expired'] = bool(time_remaining <= 0) + return attrs diff --git a/awx/main/utils/named_url_graph.py b/awx/main/utils/named_url_graph.py index f17c503d4c1d..f1b72e0aef2d 100644 --- a/awx/main/utils/named_url_graph.py +++ b/awx/main/utils/named_url_graph.py @@ -77,6 +77,8 @@ def _encode_uri(self, text): Performance assured: http://stackoverflow.com/a/27086669 ''' for c in URL_PATH_RESERVED_CHARSET: + if not isinstance(text, str): + text = str(text) # needed for WFJT node creation, identifier temporarily UUID4 type if c in text: text = text.replace(c, URL_PATH_RESERVED_CHARSET[c]) text = text.replace(NAMED_URL_RES_INNER_DILIMITER, @@ -200,14 +202,14 @@ def _get_all_unique_togethers(model): def _check_unique_together_fields(model, ut): - has_name = False + name_field = None fk_names = [] fields = [] is_valid = True for field_name in ut: field = model._meta.get_field(field_name) - if field_name == 'name': - has_name = True + if field_name in ('name', 'identifier'): + name_field = field_name elif type(field) == models.ForeignKey and field.related_model != model: fk_names.append(field_name) elif issubclass(type(field), models.CharField) and field.choices: @@ -219,8 +221,8 @@ def _check_unique_together_fields(model, ut): return (), (), is_valid fk_names.sort() fields.sort(reverse=True) - if has_name: - fields.append('name') + if name_field: + fields.append(name_field) fields.reverse() return tuple(fk_names), tuple(fields), is_valid @@ -315,3 +317,8 @@ def generate_graph(models): settings.NAMED_URL_GRAPH = largest_graph for node in settings.NAMED_URL_GRAPH.values(): node.add_bindings() + + +def reset_counters(): + for node in settings.NAMED_URL_GRAPH.values(): + node.counter = 0 diff --git a/awx/main/utils/profiling.py b/awx/main/utils/profiling.py new file mode 100644 index 000000000000..c550175d7b5e --- /dev/null +++ b/awx/main/utils/profiling.py @@ -0,0 +1,151 @@ +import cProfile +import functools +import pstats +import os +import uuid +import datetime +import json +import sys + + +class AWXProfileBase: + def __init__(self, name, dest): + self.name = name + self.dest = dest + self.results = {} + + def generate_results(self): + raise RuntimeError("define me") + + def output_results(self, fname=None): + if not os.path.isdir(self.dest): + os.makedirs(self.dest) + + if fname: + fpath = os.path.join(self.dest, fname) + with open(fpath, 'w') as f: + f.write(json.dumps(self.results, indent=2)) + + +class AWXTiming(AWXProfileBase): + def __init__(self, name, dest='/var/log/tower/timing'): + super().__init__(name, dest) + + self.time_start = None + self.time_end = None + + def start(self): + self.time_start = datetime.datetime.now() + + def stop(self): + self.time_end = datetime.datetime.now() + + self.generate_results() + self.output_results() + + def generate_results(self): + diff = (self.time_end - self.time_start).total_seconds() + self.results = { + 'name': self.name, + 'diff': f'{diff}-seconds', + } + + def output_results(self): + fname = f"{self.results['diff']}-{self.name}-{uuid.uuid4()}.time" + super().output_results(fname) + + +def timing(name, *init_args, **init_kwargs): + def decorator_profile(func): + @functools.wraps(func) + def wrapper_profile(*args, **kwargs): + timing = AWXTiming(name, *init_args, **init_kwargs) + timing.start() + res = func(*args, **kwargs) + timing.stop() + return res + return wrapper_profile + return decorator_profile + + +class AWXProfiler(AWXProfileBase): + def __init__(self, name, dest='/var/log/tower/profile', dot_enabled=True): + ''' + Try to do as little as possible in init. Instead, do the init + only when the profiling is started. + ''' + super().__init__(name, dest) + self.started = False + self.dot_enabled = dot_enabled + self.results = { + 'total_time_seconds': 0, + } + + def generate_results(self): + self.results['total_time_seconds'] = pstats.Stats(self.prof).total_tt + + def output_results(self): + super().output_results() + + filename_base = '%.3fs-%s-%s-%s' % (self.results['total_time_seconds'], self.name, self.pid, uuid.uuid4()) + pstats_filepath = os.path.join(self.dest, f"{filename_base}.pstats") + extra_data = "" + + if self.dot_enabled: + try: + from gprof2dot import main as generate_dot + except ImportError: + extra_data = 'Dot graph generation failed due to package "gprof2dot" being unavailable.' + else: + raw_filepath = os.path.join(self.dest, f"{filename_base}.raw") + dot_filepath = os.path.join(self.dest, f"{filename_base}.dot") + + pstats.Stats(self.prof).dump_stats(raw_filepath) + generate_dot([ + '-n', '2.5', '-f', 'pstats', '-o', + dot_filepath, + raw_filepath + ]) + os.remove(raw_filepath) + + with open(pstats_filepath, 'w') as f: + print(f"{self.name}, {extra_data}", file=f) + pstats.Stats(self.prof, stream=f).sort_stats('cumulative').print_stats() + return pstats_filepath + + + def start(self): + self.prof = cProfile.Profile() + self.pid = os.getpid() + + self.prof.enable() + self.started = True + + def is_started(self): + return self.started + + def stop(self): + if self.started: + self.prof.disable() + + self.generate_results() + res = self.output_results() + self.started = False + return res + else: + print("AWXProfiler::stop() called without calling start() first", file=sys.stderr) + return None + + +def profile(name, *init_args, **init_kwargs): + def decorator_profile(func): + @functools.wraps(func) + def wrapper_profile(*args, **kwargs): + prof = AWXProfiler(name, *init_args, **init_kwargs) + prof.start() + res = func(*args, **kwargs) + prof.stop() + return res + return wrapper_profile + return decorator_profile + diff --git a/awx/main/utils/reload.py b/awx/main/utils/reload.py index bdfcc0dcc9e4..37c7c48cfe5c 100644 --- a/awx/main/utils/reload.py +++ b/awx/main/utils/reload.py @@ -4,25 +4,24 @@ # Python import subprocess import logging +import os -# Django -from django.conf import settings logger = logging.getLogger('awx.main.utils.reload') -def _supervisor_service_command(command, communicate=True): +def supervisor_service_command(command, service='*', communicate=True): ''' example use pattern of supervisorctl: # supervisorctl restart tower-processes:receiver tower-processes:factcacher ''' - group_name = 'tower-processes' - if settings.DEBUG: - group_name = 'awx-processes' args = ['supervisorctl'] - if settings.DEBUG: - args.extend(['-c', '/supervisor.conf']) - args.extend([command, '{}:*'.format(group_name)]) + + supervisor_config_path = os.getenv('SUPERVISOR_WEB_CONFIG_PATH', None) + if supervisor_config_path: + args.extend(['-c', supervisor_config_path]) + + args.extend([command, ':'.join(['tower-processes', service])]) logger.debug('Issuing command to {} services, args={}'.format(command, args)) supervisor_process = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) @@ -30,15 +29,16 @@ def _supervisor_service_command(command, communicate=True): restart_stdout, restart_err = supervisor_process.communicate() restart_code = supervisor_process.returncode if restart_code or restart_err: - logger.error('supervisorctl {} errored with exit code `{}`, stdout:\n{}stderr:\n{}'.format( - command, restart_code, restart_stdout.strip(), restart_err.strip())) + logger.error('supervisorctl {} {} errored with exit code `{}`, stdout:\n{}stderr:\n{}'.format( + command, service, restart_code, restart_stdout.strip(), restart_err.strip())) else: - logger.info('supervisorctl {} finished, stdout:\n{}'.format( - command, restart_stdout.strip())) + logger.debug( + 'supervisorctl {} {} succeeded'.format(command, service) + ) else: logger.info('Submitted supervisorctl {} command, not waiting for result'.format(command)) def stop_local_services(communicate=True): logger.warn('Stopping services on this node in response to user action') - _supervisor_service_command(command='stop', communicate=communicate) + supervisor_service_command(command='stop', communicate=communicate) diff --git a/awx/main/utils/safe_yaml.py b/awx/main/utils/safe_yaml.py index 4147092ba882..7e4a5b14968d 100644 --- a/awx/main/utils/safe_yaml.py +++ b/awx/main/utils/safe_yaml.py @@ -36,7 +36,7 @@ def safe_dump(x, safe_dict=None): _unless_ they've been deemed "trusted" (meaning, they likely were set/added by a user with a high level of privilege). - This function allows you to pass in a trusted `safe_dict` to whitelist + This function allows you to pass in a trusted `safe_dict` to allow certain extra vars so that they are _not_ marked as `!unsafe` in the resulting YAML. Anything _not_ in this dict will automatically be `!unsafe`. diff --git a/awx/main/wsbroadcast.py b/awx/main/wsbroadcast.py new file mode 100644 index 000000000000..a97baf45f42a --- /dev/null +++ b/awx/main/wsbroadcast.py @@ -0,0 +1,204 @@ +import json +import logging +import asyncio + +import aiohttp +from aiohttp import client_exceptions + +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, +) + + +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']) + + +def get_broadcast_hosts(): + Instance = apps.get_model('main', 'Instance') + instances = Instance.objects.filter(rampart_groups__controller__isnull=True) \ + .exclude(hostname=Instance.objects.me().hostname) \ + .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.me().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 + + 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.warn(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.warn(f"Connection from {self.name} to {self.remote_host} cancelled.") + self.stats.record_connection_lost() + raise + except client_exceptions.ClientConnectorError as e: + logger.warn(f"Connection from {self.name} to {self.remote_host} failed: '{e}'.") + except asyncio.TimeoutError: + logger.warn(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.warn(f"Connection from {self.name} to {self.remote_host} failed for unknown reason: '{e}'.") + else: + logger.warn(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.warn(logmsg) + continue + + (group, message) = unwrap_broadcast_msg(payload) + + 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 = 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.warn(f"Removing {deleted_remote_hosts} from websocket broadcast list") + if new_remote_hosts: + logger.warn(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/playbooks/action_plugins/project_archive.py b/awx/playbooks/action_plugins/project_archive.py new file mode 100644 index 000000000000..d5dff804a64e --- /dev/null +++ b/awx/playbooks/action_plugins/project_archive.py @@ -0,0 +1,98 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import errno +import os +import tarfile +import zipfile + +from ansible.plugins.action import ActionBase +from ansible.utils.display import Display + +display = Display() + +try: + from zipfile import BadZipFile +except ImportError: + from zipfile import BadZipfile as BadZipFile # py2 compat + + +class ActionModule(ActionBase): + def run(self, tmp=None, task_vars=None): + self._supports_check_mode = False + + result = super(ActionModule, self).run(tmp, task_vars) + + src = self._task.args.get("src") + proj_path = self._task.args.get("project_path") + force = self._task.args.get("force", False) + + try: + archive = zipfile.ZipFile(src) + get_filenames = archive.namelist + get_members = archive.infolist + except BadZipFile: + try: + archive = tarfile.open(src) + except tarfile.ReadError: + result["failed"] = True + result["msg"] = "{0} is not a valid archive".format(src) + return result + get_filenames = archive.getnames + get_members = archive.getmembers + + # Most well formed archives contain a single root directory, typically named + # project-name-1.0.0. The project contents should be inside that directory. + start_index = 0 + root_contents = set( + [filename.split(os.path.sep)[0] for filename in get_filenames()] + ) + if len(root_contents) == 1: + start_index = len(list(root_contents)[0]) + 1 + + for member in get_members(): + try: + filename = member.filename + except AttributeError: + filename = member.name + + # Skip the archive base directory + if not filename[start_index:]: + continue + + dest = os.path.join(proj_path, filename[start_index:]) + + if not force and os.path.exists(dest): + continue + + try: + is_dir = member.is_dir() + except AttributeError: + try: + is_dir = member.isdir() + except AttributeError: + is_dir = member.filename[-1] == '/' # py2 compat for ZipInfo + + if is_dir: + try: + os.makedirs(dest) + except OSError as exc: # Python >= 2.5 + if exc.errno == errno.EEXIST and os.path.isdir(dest): + pass + else: + raise + else: + try: + member_f = archive.open(member) + except TypeError: + member_f = tarfile.ExFileObject(archive, member) + + with open(dest, "wb") as f: + f.write(member_f.read()) + member_f.close() + + archive.close() + + result["changed"] = True + return result diff --git a/awx/playbooks/check_isolated.yml b/awx/playbooks/check_isolated.yml index 53a2183ec5a5..472b772fbb9c 100644 --- a/awx/playbooks/check_isolated.yml +++ b/awx/playbooks/check_isolated.yml @@ -5,8 +5,13 @@ - name: Poll for status of active job. hosts: all gather_facts: false + collections: + - ansible.posix tasks: + - name: "Output job the playbook is running for" + debug: + msg: "Checking on job {{ job_id }}" - name: Determine if daemon process is alive. shell: "ansible-runner is-alive {{src}}" @@ -38,6 +43,7 @@ recursive: true set_remote_user: false rsync_opts: + - "--blocking-io" - "--rsh=$RSH" environment: RSH: "oc rsh --config={{ ansible_kubectl_config }}" @@ -51,6 +57,7 @@ mode: pull set_remote_user: false rsync_opts: + - "--blocking-io" - "--rsh=$RSH" environment: RSH: "oc rsh --config={{ ansible_kubectl_config }}" diff --git a/awx/playbooks/library/project_archive.py b/awx/playbooks/library/project_archive.py new file mode 100644 index 000000000000..4a046e354dd8 --- /dev/null +++ b/awx/playbooks/library/project_archive.py @@ -0,0 +1,40 @@ +ANSIBLE_METADATA = { + "metadata_version": "1.0", + "status": ["stableinterface"], + "supported_by": "community", +} + + +DOCUMENTATION = """ +--- +module: project_archive +short_description: unpack a project archive +description: + - Unpacks an archive that contains a project, in order to support handling versioned + artifacts from (for example) GitHub Releases or Artifactory builds. + - Handles projects in the archive root, or in a single base directory of the archive. +version_added: "2.9" +options: + src: + description: + - The source archive of the project artifact + required: true + project_path: + description: + - Directory to write the project archive contents + required: true + force: + description: + - Files in the project_path will be overwritten by matching files in the archive + default: False + +author: + - "Philip Douglass" @philipsd6 +""" + +EXAMPLES = """ +- project_archive: + src: "{{ project_path }}/.archive/project.tar.gz" + project_path: "{{ project_path }}" + force: "{{ scm_clean }}" +""" diff --git a/awx/playbooks/project_update.yml b/awx/playbooks/project_update.yml index 6dc90bb365b9..a7b7007d566c 100644 --- a/awx/playbooks/project_update.yml +++ b/awx/playbooks/project_update.yml @@ -1,6 +1,9 @@ --- # The following variables will be set by the runner of this playbook: -# project_path: PROJECTS_DIR/_local_path_ +# projects_root: Global location for caching project checkouts and roles and collections +# should not have trailing slash on end +# local_path: Path within projects_root to use for this project +# project_path: A simple join of projects_root/local_path folders # scm_url: https://server/repo # insights_url: Insights service URL (from configuration) # scm_branch: branch/tag/revision (HEAD if unset) @@ -11,8 +14,6 @@ # scm_refspec: a refspec to fetch in addition to obtaining version # roles_enabled: Value of the global setting to enable roles downloading # collections_enabled: Value of the global setting to enable collections downloading -# roles_destination: Path to save roles from galaxy to -# collections_destination: Path to save collections from galaxy to # awx_version: Current running version of the awx or tower as a string # awx_license_type: "open" for AWX; else presume Tower @@ -47,26 +48,6 @@ tags: - update_git - - block: - - name: update project using hg - hg: - dest: "{{project_path|quote}}" - repo: "{{scm_url|quote}}" - revision: "{{scm_branch|quote}}" - force: "{{scm_clean}}" - register: hg_result - - - name: Set the hg repository version - set_fact: - scm_version: "{{ hg_result['after'] }}" - when: "'after' in hg_result" - - - name: parse hg version string properly - set_fact: - scm_version: "{{scm_version|regex_replace('^([A-Za-z0-9]+).*$', '\\1')}}" - tags: - - update_hg - - block: - name: update project using svn subversion: @@ -114,60 +95,120 @@ tags: - update_insights + - block: + - name: Ensure the project archive directory is present + file: + dest: "{{ project_path|quote }}/.archive" + state: directory + + - 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) }}" + force_basic_auth: true + register: get_archive + + - name: Unpack archive + project_archive: + src: "{{ get_archive.dest }}" + 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/" + excludes: + - "{{ get_archive.dest|basename }}" + when: unarchived.changed + register: previous_archive + + - name: Remove previous archives + file: + path: "{{ item.path }}" + state: absent + loop: "{{ previous_archive.files }}" + when: previous_archive.files|default([]) + + - name: Set scm_version to archive sha1 checksum + set_fact: + scm_version: "{{ get_archive.checksum_src }}" + tags: + - update_archive + - name: Repository Version debug: msg: "Repository Version {{ scm_version }}" tags: - update_git - - update_hg - update_svn - update_insights + - update_archive - hosts: localhost gather_facts: false connection: local name: Install content with ansible-galaxy command if necessary + vars: + yaml_exts: + - {ext: .yml} + - {ext: .yaml} tasks: - block: - - name: detect requirements.yml + - name: detect roles/requirements.(yml/yaml) stat: - path: '{{project_path|quote}}/roles/requirements.yml' + path: "{{project_path|quote}}/roles/requirements{{ item.ext }}" + with_items: "{{ yaml_exts }}" register: doesRequirementsExist - - name: fetch galaxy roles from requirements.yml - command: ansible-galaxy install -r requirements.yml -p {{roles_destination|quote}}{{ ' -' + 'v' * ansible_verbosity if ansible_verbosity else '' }} + - name: fetch galaxy roles from requirements.(yml/yaml) + command: > + ansible-galaxy role install -r {{ item.stat.path }} + --roles-path {{projects_root}}/.__awx_cache/{{local_path}}/stage/requirements_roles + {{ ' -' + 'v' * ansible_verbosity if ansible_verbosity else '' }} args: - chdir: "{{project_path|quote}}/roles" + chdir: "{{project_path|quote}}" register: galaxy_result - when: doesRequirementsExist.stat.exists + with_items: "{{ doesRequirementsExist.results }}" + when: item.stat.exists changed_when: "'was installed successfully' in galaxy_result.stdout" environment: ANSIBLE_FORCE_COLOR: false + GIT_SSH_COMMAND: "ssh -o StrictHostKeyChecking=no" when: roles_enabled|bool tags: - install_roles - block: - - name: detect collections/requirements.yml + - name: detect collections/requirements.(yml/yaml) stat: - path: '{{project_path|quote}}/collections/requirements.yml' + path: "{{project_path|quote}}/collections/requirements{{ item.ext }}" + with_items: "{{ yaml_exts }}" register: doesCollectionRequirementsExist - - name: fetch galaxy collections from collections/requirements.yml - command: ansible-galaxy collection install -r requirements.yml -p {{collections_destination|quote}}{{ ' -' + 'v' * ansible_verbosity if ansible_verbosity else '' }} + - name: fetch galaxy collections from collections/requirements.(yml/yaml) + command: > + ansible-galaxy collection install -r {{ item.stat.path }} + --collections-path {{projects_root}}/.__awx_cache/{{local_path}}/stage/requirements_collections + {{ ' -' + 'v' * ansible_verbosity if ansible_verbosity else '' }} args: - chdir: "{{project_path|quote}}/collections" + chdir: "{{project_path|quote}}" register: galaxy_collection_result - when: doesCollectionRequirementsExist.stat.exists + with_items: "{{ doesCollectionRequirementsExist.results }}" + when: item.stat.exists changed_when: "'Installing ' in galaxy_collection_result.stdout" environment: ANSIBLE_FORCE_COLOR: false - ANSIBLE_COLLECTIONS_PATHS: "{{ collections_destination }}" + ANSIBLE_COLLECTIONS_PATHS: "{{projects_root}}/.__awx_cache/{{local_path}}/stage/requirements_collections" + GIT_SSH_COMMAND: "ssh -o StrictHostKeyChecking=no" when: - - "ansible_version.full is version_compare('2.8', '>=')" + - "ansible_version.full is version_compare('2.9', '>=')" - collections_enabled|bool tags: - install_collections diff --git a/awx/playbooks/run_isolated.yml b/awx/playbooks/run_isolated.yml index c57aa4b33d82..76ea42d17cd5 100644 --- a/awx/playbooks/run_isolated.yml +++ b/awx/playbooks/run_isolated.yml @@ -9,8 +9,14 @@ gather_facts: false vars: secret: "{{ lookup('pipe', 'cat ' + src + '/env/ssh_key') }}" + collections: + - ansible.posix tasks: + - name: "Output job the playbook is running for" + debug: + msg: "Checking on job {{ job_id }}" + - name: synchronize job environment with isolated host synchronize: copy_links: true @@ -25,6 +31,7 @@ dest: "{{ dest }}" set_remote_user: false rsync_opts: + - "--blocking-io" - "--rsh=$RSH" environment: RSH: "oc rsh --config={{ ansible_kubectl_config }}" diff --git a/awx/playbooks/scan_facts.yml b/awx/playbooks/scan_facts.yml deleted file mode 100644 index 884760f7178f..000000000000 --- a/awx/playbooks/scan_facts.yml +++ /dev/null @@ -1,36 +0,0 @@ ---- -- hosts: all - vars: - scan_use_checksum: false - scan_use_recursive: false - tasks: - - - name: "Scan packages (Unix/Linux)" - scan_packages: - os_family: '{{ ansible_os_family }}' - when: ansible_os_family != "Windows" - - name: "Scan services (Unix/Linux)" - scan_services: - when: ansible_os_family != "Windows" - - name: "Scan files (Unix/Linux)" - scan_files: - paths: '{{ scan_file_paths }}' - get_checksum: '{{ scan_use_checksum }}' - recursive: '{{ scan_use_recursive }}' - when: scan_file_paths is defined and ansible_os_family != "Windows" - - name: "Scan Insights for Machine ID (Unix/Linux)" - scan_insights: - when: ansible_os_family != "Windows" - - - name: "Scan packages (Windows)" - win_scan_packages: - when: ansible_os_family == "Windows" - - name: "Scan services (Windows)" - win_scan_services: - when: ansible_os_family == "Windows" - - name: "Scan files (Windows)" - win_scan_files: - paths: '{{ scan_file_paths }}' - get_checksum: '{{ scan_use_checksum }}' - recursive: '{{ scan_use_recursive }}' - when: scan_file_paths is defined and ansible_os_family == "Windows" diff --git a/awx/plugins/fact_caching/awx.py b/awx/plugins/fact_caching/awx.py deleted file mode 100755 index 207292c37eba..000000000000 --- a/awx/plugins/fact_caching/awx.py +++ /dev/null @@ -1,136 +0,0 @@ -# Copyright (c) 2015 Ansible, Inc. -# This file is a utility Ansible plugin that is not part of the AWX or Ansible -# packages. It does not import any code from either package, nor does its -# license apply to Ansible or AWX. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# Neither the name of the nor the names of its contributors -# may be used to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. - -import os -import memcache -import json -import datetime -import base64 -from dateutil import parser -from dateutil.tz import tzutc - -from ansible import constants as C - -try: - from ansible.cache.base import BaseCacheModule -except Exception: - from ansible.plugins.cache.base import BaseCacheModule - - -class CacheModule(BaseCacheModule): - - def __init__(self, *args, **kwargs): - self.mc = memcache.Client([C.CACHE_PLUGIN_CONNECTION], debug=0) - self._timeout = int(C.CACHE_PLUGIN_TIMEOUT) - self._inventory_id = os.environ['INVENTORY_ID'] - - @property - def host_names_key(self): - return '{}'.format(self._inventory_id) - - def translate_host_key(self, host_name): - return '{}-{}'.format(self._inventory_id, base64.b64encode(host_name.encode('utf-8'))) - - def translate_modified_key(self, host_name): - return '{}-{}-modified'.format(self._inventory_id, base64.b64encode(host_name.encode('utf-8'))) - - def get(self, key): - host_key = self.translate_host_key(key) - modified_key = self.translate_modified_key(key) - - ''' - Cache entry expired - ''' - modified = self.mc.get(modified_key) - if modified is None: - raise KeyError - modified = parser.parse(modified).replace(tzinfo=tzutc()) - now_utc = datetime.datetime.now(tzutc()) - if self._timeout != 0 and (modified + datetime.timedelta(seconds=self._timeout)) < now_utc: - raise KeyError - - value_json = self.mc.get(host_key) - if value_json is None: - raise KeyError - try: - return json.loads(value_json) - # If cache entry is corrupt or bad, fail gracefully. - except (TypeError, ValueError): - self.delete(key) - raise KeyError - - def set(self, key, value): - host_key = self.translate_host_key(key) - modified_key = self.translate_modified_key(key) - - self.mc.set(host_key, json.dumps(value)) - value = json.dumps(value) - rc = self.mc.set(host_key, value) - if rc == 0 and len(value) > self.mc.server_max_value_length: - self._display.error( - "memcache.set('{}', '?') failed, value > server_max_value_length ({} bytes)".format( - key, len(value) - ) - ) - self.mc.set(modified_key, datetime.datetime.now(tzutc()).isoformat()) - - def keys(self): - return self.mc.get(self.host_names_key) - - def contains(self, key): - try: - self.get(key) - return True - except KeyError: - return False - - def delete(self, key): - self.set(key, {}) - - def flush(self): - host_names = self.mc.get(self.host_names_key) - if not host_names: - return - - for k in host_names: - self.mc.delete(self.translate_host_key(k)) - self.mc.delete(self.translate_modified_key(k)) - - def copy(self): - ret = dict() - host_names = self.mc.get(self.host_names_key) - if not host_names: - return - - for k in host_names: - ret[k] = self.mc.get(self.translate_host_key(k)) - return ret - diff --git a/awx/plugins/inventory/azure_rm.ini.example b/awx/plugins/inventory/azure_rm.ini.example deleted file mode 100644 index 9df6b8834dc0..000000000000 --- a/awx/plugins/inventory/azure_rm.ini.example +++ /dev/null @@ -1,22 +0,0 @@ -# -# Configuration file for azure_rm.py -# -[azure] -# Control which resource groups are included. By default all resources groups are included. -# Set resource_groups to a comma separated list of resource groups names. -#resource_groups= - -# Control which tags are included. Set tags to a comma separated list of keys or key:value pairs -#tags= - -# Control which locations are included. Set locations to a comma separated list (e.g. eastus,eastus2,westus) -#locations= - -# Include powerstate. If you don't need powerstate information, turning it off improves runtime performance. -include_powerstate=yes - -# Control grouping with the following boolean flags. Valid values: yes, no, true, false, True, False, 0, 1. -group_by_resource_group=yes -group_by_location=yes -group_by_security_group=yes -group_by_tag=yes diff --git a/awx/plugins/inventory/azure_rm.py b/awx/plugins/inventory/azure_rm.py deleted file mode 100755 index 691c196f11e5..000000000000 --- a/awx/plugins/inventory/azure_rm.py +++ /dev/null @@ -1,973 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (c) 2016 Matt Davis, -# Chris Houseknecht, -# -# This file is part of Ansible -# -# Ansible is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Ansible. If not, see . -# - -''' -Azure External Inventory Script -=============================== -Generates dynamic inventory by making API requests to the Azure Resource -Manager using the Azure Python SDK. For instruction on installing the -Azure Python SDK see https://azure-sdk-for-python.readthedocs.io/ - -Authentication --------------- -The order of precedence is command line arguments, environment variables, -and finally the [default] profile found in ~/.azure/credentials. - -If using a credentials file, it should be an ini formatted file with one or -more sections, which we refer to as profiles. The script looks for a -[default] section, if a profile is not specified either on the command line -or with an environment variable. The keys in a profile will match the -list of command line arguments below. - -For command line arguments and environment variables specify a profile found -in your ~/.azure/credentials file, or a service principal or Active Directory -user. - -Command line arguments: - - profile - - client_id - - secret - - subscription_id - - tenant - - ad_user - - password - - cloud_environment - - adfs_authority_url - -Environment variables: - - AZURE_PROFILE - - AZURE_CLIENT_ID - - AZURE_SECRET - - AZURE_SUBSCRIPTION_ID - - AZURE_TENANT - - AZURE_AD_USER - - AZURE_PASSWORD - - AZURE_CLOUD_ENVIRONMENT - - AZURE_ADFS_AUTHORITY_URL - -Run for Specific Host ------------------------ -When run for a specific host using the --host option, a resource group is -required. For a specific host, this script returns the following variables: - -{ - "ansible_host": "XXX.XXX.XXX.XXX", - "computer_name": "computer_name2", - "fqdn": null, - "id": "/subscriptions/subscription-id/resourceGroups/galaxy-production/providers/Microsoft.Compute/virtualMachines/object-name", - "image": { - "offer": "CentOS", - "publisher": "OpenLogic", - "sku": "7.1", - "version": "latest" - }, - "location": "westus", - "mac_address": "00-00-5E-00-53-FE", - "name": "object-name", - "network_interface": "interface-name", - "network_interface_id": "/subscriptions/subscription-id/resourceGroups/galaxy-production/providers/Microsoft.Network/networkInterfaces/object-name1", - "network_security_group": null, - "network_security_group_id": null, - "os_disk": { - "name": "object-name", - "operating_system_type": "Linux" - }, - "plan": null, - "powerstate": "running", - "private_ip": "172.26.3.6", - "private_ip_alloc_method": "Static", - "provisioning_state": "Succeeded", - "public_ip": "XXX.XXX.XXX.XXX", - "public_ip_alloc_method": "Static", - "public_ip_id": "/subscriptions/subscription-id/resourceGroups/galaxy-production/providers/Microsoft.Network/publicIPAddresses/object-name", - "public_ip_name": "object-name", - "resource_group": "galaxy-production", - "security_group": "object-name", - "security_group_id": "/subscriptions/subscription-id/resourceGroups/galaxy-production/providers/Microsoft.Network/networkSecurityGroups/object-name", - "tags": { - "db": "database" - }, - "type": "Microsoft.Compute/virtualMachines", - "virtual_machine_size": "Standard_DS4" -} - -Groups ------- -When run in --list mode, instances are grouped by the following categories: - - azure - - location - - resource_group - - security_group - - tag key - - tag key_value - -Control groups using azure_rm.ini or set environment variables: - -AZURE_GROUP_BY_RESOURCE_GROUP=yes -AZURE_GROUP_BY_LOCATION=yes -AZURE_GROUP_BY_SECURITY_GROUP=yes -AZURE_GROUP_BY_TAG=yes - -Select hosts within specific resource groups by assigning a comma separated list to: - -AZURE_RESOURCE_GROUPS=resource_group_a,resource_group_b - -Select hosts for specific tag key by assigning a comma separated list of tag keys to: - -AZURE_TAGS=key1,key2,key3 - -Select hosts for specific locations: - -AZURE_LOCATIONS=eastus,westus,eastus2 - -Or, select hosts for specific tag key:value pairs by assigning a comma separated list key:value pairs to: - -AZURE_TAGS=key1:value1,key2:value2 - -If you don't need the powerstate, you can improve performance by turning off powerstate fetching: -AZURE_INCLUDE_POWERSTATE=no - -azure_rm.ini ------------- -As mentioned above, you can control execution using environment variables or a .ini file. A sample -azure_rm.ini is included. The name of the .ini file is the basename of the inventory script (in this case -'azure_rm') with a .ini extension. It also assumes the .ini file is alongside the script. To specify -a different path for the .ini file, define the AZURE_INI_PATH environment variable: - - export AZURE_INI_PATH=/path/to/custom.ini - -Powerstate: ------------ -The powerstate attribute indicates whether or not a host is running. If the value is 'running', the machine is -up. If the value is anything other than 'running', the machine is down, and will be unreachable. - -Examples: ---------- - Execute /bin/uname on all instances in the galaxy-qa resource group - $ ansible -i azure_rm.py galaxy-qa -m shell -a "/bin/uname -a" - - Use the inventory script to print instance specific information - $ contrib/inventory/azure_rm.py --host my_instance_host_name --pretty - - Use with a playbook - $ ansible-playbook -i contrib/inventory/azure_rm.py my_playbook.yml --limit galaxy-qa - - -Insecure Platform Warning -------------------------- -If you receive InsecurePlatformWarning from urllib3, install the -requests security packages: - - pip install requests[security] - - -author: - - Chris Houseknecht (@chouseknecht) - - Matt Davis (@nitzmahone) - -Company: Ansible by Red Hat - -Version: 1.0.0 -''' - -import argparse -import json -import os -import re -import sys -import inspect - -try: - # python2 - import ConfigParser as cp -except ImportError: - # python3 - import configparser as cp - -from os.path import expanduser -import ansible.module_utils.six.moves.urllib.parse as urlparse - -HAS_AZURE = True -HAS_AZURE_EXC = None -HAS_AZURE_CLI_CORE = True -CLIError = None - -try: - from msrestazure.azure_active_directory import AADTokenCredentials - from msrestazure.azure_exceptions import CloudError - from msrestazure.azure_active_directory import MSIAuthentication - from msrestazure import azure_cloud - from azure.mgmt.compute import __version__ as azure_compute_version - from azure.common import AzureMissingResourceHttpError, AzureHttpError - from azure.common.credentials import ServicePrincipalCredentials, UserPassCredentials - from azure.mgmt.network import NetworkManagementClient - from azure.mgmt.resource.resources import ResourceManagementClient - from azure.mgmt.resource.subscriptions import SubscriptionClient - from azure.mgmt.compute import ComputeManagementClient - from adal.authentication_context import AuthenticationContext -except ImportError as exc: - HAS_AZURE_EXC = exc - HAS_AZURE = False - -try: - from azure.cli.core.util import CLIError - from azure.common.credentials import get_azure_cli_credentials, get_cli_profile - from azure.common.cloud import get_cli_active_cloud -except ImportError: - HAS_AZURE_CLI_CORE = False - CLIError = Exception - -try: - from ansible.release import __version__ as ansible_version -except ImportError: - ansible_version = 'unknown' - -AZURE_CREDENTIAL_ENV_MAPPING = dict( - profile='AZURE_PROFILE', - subscription_id='AZURE_SUBSCRIPTION_ID', - client_id='AZURE_CLIENT_ID', - secret='AZURE_SECRET', - tenant='AZURE_TENANT', - ad_user='AZURE_AD_USER', - password='AZURE_PASSWORD', - cloud_environment='AZURE_CLOUD_ENVIRONMENT', - adfs_authority_url='AZURE_ADFS_AUTHORITY_URL' -) - -AZURE_CONFIG_SETTINGS = dict( - resource_groups='AZURE_RESOURCE_GROUPS', - tags='AZURE_TAGS', - locations='AZURE_LOCATIONS', - include_powerstate='AZURE_INCLUDE_POWERSTATE', - group_by_resource_group='AZURE_GROUP_BY_RESOURCE_GROUP', - group_by_location='AZURE_GROUP_BY_LOCATION', - group_by_security_group='AZURE_GROUP_BY_SECURITY_GROUP', - group_by_tag='AZURE_GROUP_BY_TAG', - group_by_os_family='AZURE_GROUP_BY_OS_FAMILY', - use_private_ip='AZURE_USE_PRIVATE_IP' -) - -AZURE_MIN_VERSION = "2.0.0" -ANSIBLE_USER_AGENT = 'Ansible/{0}'.format(ansible_version) - - -def azure_id_to_dict(id): - pieces = re.sub(r'^\/', '', id).split('/') - result = {} - index = 0 - while index < len(pieces) - 1: - result[pieces[index]] = pieces[index + 1] - index += 1 - return result - - -class AzureRM(object): - - def __init__(self, args): - self._args = args - self._cloud_environment = None - self._compute_client = None - self._resource_client = None - self._network_client = None - self._adfs_authority_url = None - self._resource = None - - self.debug = False - if args.debug: - self.debug = True - - self.credentials = self._get_credentials(args) - if not self.credentials: - self.fail("Failed to get credentials. Either pass as parameters, set environment variables, " - "or define a profile in ~/.azure/credentials.") - - # if cloud_environment specified, look up/build Cloud object - raw_cloud_env = self.credentials.get('cloud_environment') - if not raw_cloud_env: - self._cloud_environment = azure_cloud.AZURE_PUBLIC_CLOUD # SDK default - else: - # try to look up "well-known" values via the name attribute on azure_cloud members - all_clouds = [x[1] for x in inspect.getmembers(azure_cloud) if isinstance(x[1], azure_cloud.Cloud)] - matched_clouds = [x for x in all_clouds if x.name == raw_cloud_env] - if len(matched_clouds) == 1: - self._cloud_environment = matched_clouds[0] - elif len(matched_clouds) > 1: - self.fail("Azure SDK failure: more than one cloud matched for cloud_environment name '{0}'".format(raw_cloud_env)) - else: - if not urlparse.urlparse(raw_cloud_env).scheme: - self.fail("cloud_environment must be an endpoint discovery URL or one of {0}".format([x.name for x in all_clouds])) - try: - self._cloud_environment = azure_cloud.get_cloud_from_metadata_endpoint(raw_cloud_env) - except Exception as e: - self.fail("cloud_environment {0} could not be resolved: {1}".format(raw_cloud_env, e.message)) - - if self.credentials.get('subscription_id', None) is None: - self.fail("Credentials did not include a subscription_id value.") - self.log("setting subscription_id") - self.subscription_id = self.credentials['subscription_id'] - - # get authentication authority - # for adfs, user could pass in authority or not. - # for others, use default authority from cloud environment - if self.credentials.get('adfs_authority_url'): - self._adfs_authority_url = self.credentials.get('adfs_authority_url') - else: - self._adfs_authority_url = self._cloud_environment.endpoints.active_directory - - # get resource from cloud environment - self._resource = self._cloud_environment.endpoints.active_directory_resource_id - - if self.credentials.get('credentials'): - self.azure_credentials = self.credentials.get('credentials') - elif self.credentials.get('client_id') and self.credentials.get('secret') and self.credentials.get('tenant'): - self.azure_credentials = ServicePrincipalCredentials(client_id=self.credentials['client_id'], - secret=self.credentials['secret'], - tenant=self.credentials['tenant'], - cloud_environment=self._cloud_environment) - - elif self.credentials.get('ad_user') is not None and \ - self.credentials.get('password') is not None and \ - self.credentials.get('client_id') is not None and \ - self.credentials.get('tenant') is not None: - - self.azure_credentials = self.acquire_token_with_username_password( - self._adfs_authority_url, - self._resource, - self.credentials['ad_user'], - self.credentials['password'], - self.credentials['client_id'], - self.credentials['tenant']) - - elif self.credentials.get('ad_user') is not None and self.credentials.get('password') is not None: - tenant = self.credentials.get('tenant') - if not tenant: - tenant = 'common' - self.azure_credentials = UserPassCredentials(self.credentials['ad_user'], - self.credentials['password'], - tenant=tenant, - cloud_environment=self._cloud_environment) - - else: - self.fail("Failed to authenticate with provided credentials. Some attributes were missing. " - "Credentials must include client_id, secret and tenant or ad_user and password, or " - "ad_user, password, client_id, tenant and adfs_authority_url(optional) for ADFS authentication, or " - "be logged in using AzureCLI.") - - def log(self, msg): - if self.debug: - print(msg + u'\n') - - def fail(self, msg): - raise Exception(msg) - - def _get_profile(self, profile="default"): - path = expanduser("~") - path += "/.azure/credentials" - try: - config = cp.ConfigParser() - config.read(path) - except Exception as exc: - self.fail("Failed to access {0}. Check that the file exists and you have read " - "access. {1}".format(path, str(exc))) - credentials = dict() - for key in AZURE_CREDENTIAL_ENV_MAPPING: - try: - credentials[key] = config.get(profile, key, raw=True) - except: - pass - - if credentials.get('client_id') is not None or credentials.get('ad_user') is not None: - return credentials - - return None - - def _get_env_credentials(self): - env_credentials = dict() - for attribute, env_variable in AZURE_CREDENTIAL_ENV_MAPPING.items(): - env_credentials[attribute] = os.environ.get(env_variable, None) - - if env_credentials['profile'] is not None: - credentials = self._get_profile(env_credentials['profile']) - return credentials - - if env_credentials['client_id'] is not None or env_credentials['ad_user'] is not None: - return env_credentials - - return None - - def _get_azure_cli_credentials(self): - credentials, subscription_id = get_azure_cli_credentials() - cloud_environment = get_cli_active_cloud() - - cli_credentials = { - 'credentials': credentials, - 'subscription_id': subscription_id, - 'cloud_environment': cloud_environment - } - return cli_credentials - - def _get_msi_credentials(self, subscription_id_param=None): - credentials = MSIAuthentication() - subscription_id_param = subscription_id_param or os.environ.get(AZURE_CREDENTIAL_ENV_MAPPING['subscription_id'], None) - try: - # try to get the subscription in MSI to test whether MSI is enabled - subscription_client = SubscriptionClient(credentials) - subscription = next(subscription_client.subscriptions.list()) - subscription_id = str(subscription.subscription_id) - return { - 'credentials': credentials, - 'subscription_id': subscription_id_param or subscription_id - } - except Exception as exc: - return None - - def _get_credentials(self, params): - # Get authentication credentials. - # Precedence: cmd line parameters-> environment variables-> default profile in ~/.azure/credentials. - - self.log('Getting credentials') - - arg_credentials = dict() - for attribute, env_variable in AZURE_CREDENTIAL_ENV_MAPPING.items(): - arg_credentials[attribute] = getattr(params, attribute) - - # try module params - if arg_credentials['profile'] is not None: - self.log('Retrieving credentials with profile parameter.') - credentials = self._get_profile(arg_credentials['profile']) - return credentials - - if arg_credentials['client_id'] is not None: - self.log('Received credentials from parameters.') - return arg_credentials - - if arg_credentials['ad_user'] is not None: - self.log('Received credentials from parameters.') - return arg_credentials - - # try environment - env_credentials = self._get_env_credentials() - if env_credentials: - self.log('Received credentials from env.') - return env_credentials - - # try default profile from ~./azure/credentials - default_credentials = self._get_profile() - if default_credentials: - self.log('Retrieved default profile credentials from ~/.azure/credentials.') - return default_credentials - - msi_credentials = self._get_msi_credentials(arg_credentials.get('subscription_id')) - if msi_credentials: - self.log('Retrieved credentials from MSI.') - return msi_credentials - - try: - if HAS_AZURE_CLI_CORE: - self.log('Retrieving credentials from AzureCLI profile') - cli_credentials = self._get_azure_cli_credentials() - return cli_credentials - except CLIError as ce: - self.log('Error getting AzureCLI profile credentials - {0}'.format(ce)) - - return None - - def acquire_token_with_username_password(self, authority, resource, username, password, client_id, tenant): - authority_uri = authority - - if tenant is not None: - authority_uri = authority + '/' + tenant - - context = AuthenticationContext(authority_uri) - token_response = context.acquire_token_with_username_password(resource, username, password, client_id) - return AADTokenCredentials(token_response) - - def _register(self, key): - try: - # We have to perform the one-time registration here. Otherwise, we receive an error the first - # time we attempt to use the requested client. - resource_client = self.rm_client - resource_client.providers.register(key) - except Exception as exc: - self.log("One-time registration of {0} failed - {1}".format(key, str(exc))) - self.log("You might need to register {0} using an admin account".format(key)) - self.log(("To register a provider using the Python CLI: " - "https://docs.microsoft.com/azure/azure-resource-manager/" - "resource-manager-common-deployment-errors#noregisteredproviderfound")) - - def get_mgmt_svc_client(self, client_type, base_url, api_version): - client = client_type(self.azure_credentials, - self.subscription_id, - base_url=base_url, - api_version=api_version) - client.config.add_user_agent(ANSIBLE_USER_AGENT) - return client - - @property - def network_client(self): - self.log('Getting network client') - if not self._network_client: - self._network_client = self.get_mgmt_svc_client(NetworkManagementClient, - self._cloud_environment.endpoints.resource_manager, - '2017-06-01') - self._register('Microsoft.Network') - return self._network_client - - @property - def rm_client(self): - self.log('Getting resource manager client') - if not self._resource_client: - self._resource_client = self.get_mgmt_svc_client(ResourceManagementClient, - self._cloud_environment.endpoints.resource_manager, - '2017-05-10') - return self._resource_client - - @property - def compute_client(self): - self.log('Getting compute client') - if not self._compute_client: - self._compute_client = self.get_mgmt_svc_client(ComputeManagementClient, - self._cloud_environment.endpoints.resource_manager, - '2017-03-30') - self._register('Microsoft.Compute') - return self._compute_client - - -class AzureInventory(object): - - def __init__(self): - - self._args = self._parse_cli_args() - - try: - rm = AzureRM(self._args) - except Exception as e: - sys.exit("{0}".format(str(e))) - - self._compute_client = rm.compute_client - self._network_client = rm.network_client - self._resource_client = rm.rm_client - self._security_groups = None - - self.resource_groups = [] - self.tags = None - self.locations = None - self.replace_dash_in_groups = False - self.group_by_resource_group = True - self.group_by_location = True - self.group_by_os_family = True - self.group_by_security_group = True - self.group_by_tag = True - self.include_powerstate = True - self.use_private_ip = False - - self._inventory = dict( - _meta=dict( - hostvars=dict() - ), - azure=[] - ) - - self._get_settings() - - if self._args.resource_groups: - self.resource_groups = self._args.resource_groups.split(',') - - if self._args.tags: - self.tags = self._args.tags.split(',') - - if self._args.locations: - self.locations = self._args.locations.split(',') - - if self._args.no_powerstate: - self.include_powerstate = False - - self.get_inventory() - print(self._json_format_dict(pretty=self._args.pretty)) - sys.exit(0) - - def _parse_cli_args(self): - # Parse command line arguments - parser = argparse.ArgumentParser( - description='Produce an Ansible Inventory file for an Azure subscription') - parser.add_argument('--list', action='store_true', default=True, - help='List instances (default: True)') - parser.add_argument('--debug', action='store_true', default=False, - help='Send debug messages to STDOUT') - parser.add_argument('--host', action='store', - help='Get all information about an instance') - parser.add_argument('--pretty', action='store_true', default=False, - help='Pretty print JSON output(default: False)') - parser.add_argument('--profile', action='store', - help='Azure profile contained in ~/.azure/credentials') - parser.add_argument('--subscription_id', action='store', - help='Azure Subscription Id') - parser.add_argument('--client_id', action='store', - help='Azure Client Id ') - parser.add_argument('--secret', action='store', - help='Azure Client Secret') - parser.add_argument('--tenant', action='store', - help='Azure Tenant Id') - parser.add_argument('--ad_user', action='store', - help='Active Directory User') - parser.add_argument('--password', action='store', - help='password') - parser.add_argument('--adfs_authority_url', action='store', - help='Azure ADFS authority url') - parser.add_argument('--cloud_environment', action='store', - help='Azure Cloud Environment name or metadata discovery URL') - parser.add_argument('--resource-groups', action='store', - help='Return inventory for comma separated list of resource group names') - parser.add_argument('--tags', action='store', - help='Return inventory for comma separated list of tag key:value pairs') - parser.add_argument('--locations', action='store', - help='Return inventory for comma separated list of locations') - parser.add_argument('--no-powerstate', action='store_true', default=False, - help='Do not include the power state of each virtual host') - return parser.parse_args() - - def get_inventory(self): - if len(self.resource_groups) > 0: - # get VMs for requested resource groups - for resource_group in self.resource_groups: - try: - virtual_machines = self._compute_client.virtual_machines.list(resource_group.lower()) - except Exception as exc: - sys.exit("Error: fetching virtual machines for resource group {0} - {1}".format(resource_group, str(exc))) - if self._args.host or self.tags: - selected_machines = self._selected_machines(virtual_machines) - self._load_machines(selected_machines) - else: - self._load_machines(virtual_machines) - else: - # get all VMs within the subscription - try: - virtual_machines = self._compute_client.virtual_machines.list_all() - except Exception as exc: - sys.exit("Error: fetching virtual machines - {0}".format(str(exc))) - - if self._args.host or self.tags or self.locations: - selected_machines = self._selected_machines(virtual_machines) - self._load_machines(selected_machines) - else: - self._load_machines(virtual_machines) - - def _load_machines(self, machines): - for machine in machines: - id_dict = azure_id_to_dict(machine.id) - - # TODO - The API is returning an ID value containing resource group name in ALL CAPS. If/when it gets - # fixed, we should remove the .lower(). Opened Issue - # #574: https://github.com/Azure/azure-sdk-for-python/issues/574 - resource_group = id_dict['resourceGroups'].lower() - - if self.group_by_security_group: - self._get_security_groups(resource_group) - - host_vars = dict( - ansible_host=None, - private_ip=None, - private_ip_alloc_method=None, - public_ip=None, - public_ip_name=None, - public_ip_id=None, - public_ip_alloc_method=None, - fqdn=None, - location=machine.location, - name=machine.name, - type=machine.type, - id=machine.id, - tags=machine.tags, - network_interface_id=None, - network_interface=None, - resource_group=resource_group, - mac_address=None, - plan=(machine.plan.name if machine.plan else None), - virtual_machine_size=machine.hardware_profile.vm_size, - computer_name=(machine.os_profile.computer_name if machine.os_profile else None), - provisioning_state=machine.provisioning_state, - ) - - host_vars['os_disk'] = dict( - name=machine.storage_profile.os_disk.name, - operating_system_type=machine.storage_profile.os_disk.os_type.value.lower() - ) - - if self.include_powerstate: - host_vars['powerstate'] = self._get_powerstate(resource_group, machine.name) - - if machine.storage_profile.image_reference: - host_vars['image'] = dict( - offer=machine.storage_profile.image_reference.offer, - publisher=machine.storage_profile.image_reference.publisher, - sku=machine.storage_profile.image_reference.sku, - version=machine.storage_profile.image_reference.version - ) - - # Add windows details - if machine.os_profile is not None and machine.os_profile.windows_configuration is not None: - host_vars['ansible_connection'] = 'winrm' - host_vars['windows_auto_updates_enabled'] = \ - machine.os_profile.windows_configuration.enable_automatic_updates - host_vars['windows_timezone'] = machine.os_profile.windows_configuration.time_zone - host_vars['windows_rm'] = None - if machine.os_profile.windows_configuration.win_rm is not None: - host_vars['windows_rm'] = dict(listeners=None) - if machine.os_profile.windows_configuration.win_rm.listeners is not None: - host_vars['windows_rm']['listeners'] = [] - for listener in machine.os_profile.windows_configuration.win_rm.listeners: - host_vars['windows_rm']['listeners'].append(dict(protocol=listener.protocol.name, - certificate_url=listener.certificate_url)) - - for interface in machine.network_profile.network_interfaces: - interface_reference = self._parse_ref_id(interface.id) - network_interface = self._network_client.network_interfaces.get( - interface_reference['resourceGroups'], - interface_reference['networkInterfaces']) - if network_interface.primary: - if self.group_by_security_group and \ - self._security_groups[resource_group].get(network_interface.id, None): - host_vars['security_group'] = \ - self._security_groups[resource_group][network_interface.id]['name'] - host_vars['security_group_id'] = \ - self._security_groups[resource_group][network_interface.id]['id'] - host_vars['network_interface'] = network_interface.name - host_vars['network_interface_id'] = network_interface.id - host_vars['mac_address'] = network_interface.mac_address - for ip_config in network_interface.ip_configurations: - host_vars['private_ip'] = ip_config.private_ip_address - host_vars['private_ip_alloc_method'] = ip_config.private_ip_allocation_method - if self.use_private_ip: - host_vars['ansible_host'] = ip_config.private_ip_address - if ip_config.public_ip_address: - public_ip_reference = self._parse_ref_id(ip_config.public_ip_address.id) - public_ip_address = self._network_client.public_ip_addresses.get( - public_ip_reference['resourceGroups'], - public_ip_reference['publicIPAddresses']) - if not self.use_private_ip: - host_vars['ansible_host'] = public_ip_address.ip_address - host_vars['public_ip'] = public_ip_address.ip_address - host_vars['public_ip_name'] = public_ip_address.name - host_vars['public_ip_alloc_method'] = public_ip_address.public_ip_allocation_method - host_vars['public_ip_id'] = public_ip_address.id - if public_ip_address.dns_settings: - host_vars['fqdn'] = public_ip_address.dns_settings.fqdn - - self._add_host(host_vars) - - def _selected_machines(self, virtual_machines): - selected_machines = [] - for machine in virtual_machines: - if self._args.host and self._args.host == machine.name: - selected_machines.append(machine) - if self.tags and self._tags_match(machine.tags, self.tags): - selected_machines.append(machine) - if self.locations and machine.location in self.locations: - selected_machines.append(machine) - return selected_machines - - def _get_security_groups(self, resource_group): - ''' For a given resource_group build a mapping of network_interface.id to security_group name ''' - if not self._security_groups: - self._security_groups = dict() - if not self._security_groups.get(resource_group): - self._security_groups[resource_group] = dict() - for group in self._network_client.network_security_groups.list(resource_group): - if group.network_interfaces: - for interface in group.network_interfaces: - self._security_groups[resource_group][interface.id] = dict( - name=group.name, - id=group.id - ) - - def _get_powerstate(self, resource_group, name): - try: - vm = self._compute_client.virtual_machines.get(resource_group, - name, - expand='instanceview') - except Exception as exc: - sys.exit("Error: fetching instanceview for host {0} - {1}".format(name, str(exc))) - - return next((s.code.replace('PowerState/', '') - for s in vm.instance_view.statuses if s.code.startswith('PowerState')), None) - - def _add_host(self, vars): - - host_name = self._to_safe(vars['name']) - resource_group = self._to_safe(vars['resource_group']) - operating_system_type = self._to_safe(vars['os_disk']['operating_system_type'].lower()) - security_group = None - if vars.get('security_group'): - security_group = self._to_safe(vars['security_group']) - - if self.group_by_os_family: - if not self._inventory.get(operating_system_type): - self._inventory[operating_system_type] = [] - self._inventory[operating_system_type].append(host_name) - - if self.group_by_resource_group: - if not self._inventory.get(resource_group): - self._inventory[resource_group] = [] - self._inventory[resource_group].append(host_name) - - if self.group_by_location: - if not self._inventory.get(vars['location']): - self._inventory[vars['location']] = [] - self._inventory[vars['location']].append(host_name) - - if self.group_by_security_group and security_group: - if not self._inventory.get(security_group): - self._inventory[security_group] = [] - self._inventory[security_group].append(host_name) - - self._inventory['_meta']['hostvars'][host_name] = vars - self._inventory['azure'].append(host_name) - - if self.group_by_tag and vars.get('tags'): - for key, value in vars['tags'].items(): - safe_key = self._to_safe(key) - safe_value = safe_key + '_' + self._to_safe(value) - if not self._inventory.get(safe_key): - self._inventory[safe_key] = [] - if not self._inventory.get(safe_value): - self._inventory[safe_value] = [] - self._inventory[safe_key].append(host_name) - self._inventory[safe_value].append(host_name) - - def _json_format_dict(self, pretty=False): - # convert inventory to json - if pretty: - return json.dumps(self._inventory, sort_keys=True, indent=2) - else: - return json.dumps(self._inventory) - - def _get_settings(self): - # Load settings from the .ini, if it exists. Otherwise, - # look for environment values. - file_settings = self._load_settings() - if file_settings: - for key in AZURE_CONFIG_SETTINGS: - if key in ('resource_groups', 'tags', 'locations') and file_settings.get(key): - values = file_settings.get(key).split(',') - if len(values) > 0: - setattr(self, key, values) - elif file_settings.get(key): - val = self._to_boolean(file_settings[key]) - setattr(self, key, val) - else: - env_settings = self._get_env_settings() - for key in AZURE_CONFIG_SETTINGS: - if key in('resource_groups', 'tags', 'locations') and env_settings.get(key): - values = env_settings.get(key).split(',') - if len(values) > 0: - setattr(self, key, values) - elif env_settings.get(key, None) is not None: - val = self._to_boolean(env_settings[key]) - setattr(self, key, val) - - def _parse_ref_id(self, reference): - response = {} - keys = reference.strip('/').split('/') - for index in range(len(keys)): - if index < len(keys) - 1 and index % 2 == 0: - response[keys[index]] = keys[index + 1] - return response - - def _to_boolean(self, value): - if value in ['Yes', 'yes', 1, 'True', 'true', True]: - result = True - elif value in ['No', 'no', 0, 'False', 'false', False]: - result = False - else: - result = True - return result - - def _get_env_settings(self): - env_settings = dict() - for attribute, env_variable in AZURE_CONFIG_SETTINGS.items(): - env_settings[attribute] = os.environ.get(env_variable, None) - return env_settings - - def _load_settings(self): - basename = os.path.splitext(os.path.basename(__file__))[0] - default_path = os.path.join(os.path.dirname(__file__), (basename + '.ini')) - path = os.path.expanduser(os.path.expandvars(os.environ.get('AZURE_INI_PATH', default_path))) - config = None - settings = None - try: - config = cp.ConfigParser() - config.read(path) - except: - pass - - if config is not None: - settings = dict() - for key in AZURE_CONFIG_SETTINGS: - try: - settings[key] = config.get('azure', key, raw=True) - except: - pass - - return settings - - def _tags_match(self, tag_obj, tag_args): - ''' - Return True if the tags object from a VM contains the requested tag values. - - :param tag_obj: Dictionary of string:string pairs - :param tag_args: List of strings in the form key=value - :return: boolean - ''' - - if not tag_obj: - return False - - matches = 0 - for arg in tag_args: - arg_key = arg - arg_value = None - if re.search(r':', arg): - arg_key, arg_value = arg.split(':') - if arg_value and tag_obj.get(arg_key, None) == arg_value: - matches += 1 - elif not arg_value and tag_obj.get(arg_key, None) is not None: - matches += 1 - if matches == len(tag_args): - return True - return False - - def _to_safe(self, word): - ''' Converts 'bad' characters in a string to underscores so they can be used as Ansible groups ''' - regex = r"[^A-Za-z0-9\_" - if not self.replace_dash_in_groups: - regex += r"\-" - return re.sub(regex + "]", "_", word) - - -def main(): - if not HAS_AZURE: - sys.exit("The Azure python sdk is not installed (try `pip install 'azure>={0}' --upgrade`) - {1}".format(AZURE_MIN_VERSION, HAS_AZURE_EXC)) - - AzureInventory() - - -if __name__ == '__main__': - main() diff --git a/awx/plugins/inventory/cloudforms.ini.example b/awx/plugins/inventory/cloudforms.ini.example deleted file mode 100644 index 30b9aa609e0c..000000000000 --- a/awx/plugins/inventory/cloudforms.ini.example +++ /dev/null @@ -1,40 +0,0 @@ -[cloudforms] - -# the version of CloudForms ; currently not used, but tested with -version = 4.1 - -# This should be the hostname of the CloudForms server -url = https://cfme.example.com - -# This will more than likely need to be a local CloudForms username -username = - -# The password for said username -password = - -# True = verify SSL certificate / False = trust anything -ssl_verify = True - -# limit the number of vms returned per request -limit = 100 - -# purge the CloudForms actions from hosts -purge_actions = True - -# Clean up group names (from tags and other groupings so Ansible doesn't complain) -clean_group_keys = True - -# Explode tags into nested groups / subgroups -nest_tags = False - -# If set, ensure host name are suffixed with this value -# Note: This suffix *must* include the leading '.' as it is appended to the hostname as is -# suffix = .example.org - -# If true, will try and use an IPv4 address for the ansible_ssh_host rather than just the first IP address in the list -prefer_ipv4 = False - -[cache] - -# Maximum time to trust the cache in seconds -max_age = 600 diff --git a/awx/plugins/inventory/cloudforms.py b/awx/plugins/inventory/cloudforms.py deleted file mode 100755 index c934c8348905..000000000000 --- a/awx/plugins/inventory/cloudforms.py +++ /dev/null @@ -1,485 +0,0 @@ -#!/usr/bin/env python -# vim: set fileencoding=utf-8 : -# -# Copyright (C) 2016 Guido Günther -# -# This script is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with it. If not, see . -# -# This is loosely based on the foreman inventory script -# -- Josh Preston -# - -from __future__ import print_function -import argparse -from ansible.module_utils.six.moves import configparser as ConfigParser -import os -import re -from time import time -import requests -from requests.auth import HTTPBasicAuth -import warnings -from ansible.errors import AnsibleError - -try: - import json -except ImportError: - import simplejson as json - - -class CloudFormsInventory(object): - def __init__(self): - """ - Main execution path - """ - self.inventory = dict() # A list of groups and the hosts in that group - self.hosts = dict() # Details about hosts in the inventory - - # Parse CLI arguments - self.parse_cli_args() - - # Read settings - self.read_settings() - - # Cache - if self.args.refresh_cache or not self.is_cache_valid(): - self.update_cache() - else: - self.load_inventory_from_cache() - self.load_hosts_from_cache() - - data_to_print = "" - - # Data to print - if self.args.host: - if self.args.debug: - print("Fetching host [%s]" % self.args.host) - data_to_print += self.get_host_info(self.args.host) - else: - self.inventory['_meta'] = {'hostvars': {}} - for hostname in self.hosts: - self.inventory['_meta']['hostvars'][hostname] = { - 'cloudforms': self.hosts[hostname], - } - # include the ansible_ssh_host in the top level - if 'ansible_ssh_host' in self.hosts[hostname]: - self.inventory['_meta']['hostvars'][hostname]['ansible_ssh_host'] = self.hosts[hostname]['ansible_ssh_host'] - - data_to_print += self.json_format_dict(self.inventory, self.args.pretty) - - print(data_to_print) - - def is_cache_valid(self): - """ - Determines if the cache files have expired, or if it is still valid - """ - if self.args.debug: - print("Determining if cache [%s] is still valid (< %s seconds old)" % (self.cache_path_hosts, self.cache_max_age)) - - if os.path.isfile(self.cache_path_hosts): - mod_time = os.path.getmtime(self.cache_path_hosts) - current_time = time() - if (mod_time + self.cache_max_age) > current_time: - if os.path.isfile(self.cache_path_inventory): - if self.args.debug: - print("Cache is still valid!") - return True - - if self.args.debug: - print("Cache is stale or does not exist.") - - return False - - def read_settings(self): - """ - Reads the settings from the cloudforms.ini file - """ - config = ConfigParser.SafeConfigParser() - config_paths = [ - os.path.dirname(os.path.realpath(__file__)) + '/cloudforms.ini', - "/etc/ansible/cloudforms.ini", - ] - - env_value = os.environ.get('CLOUDFORMS_INI_PATH') - if env_value is not None: - config_paths.append(os.path.expanduser(os.path.expandvars(env_value))) - - if self.args.debug: - for config_path in config_paths: - print("Reading from configuration file [%s]" % config_path) - - config.read(config_paths) - - # CloudForms API related - if config.has_option('cloudforms', 'url'): - self.cloudforms_url = config.get('cloudforms', 'url') - else: - self.cloudforms_url = None - - if not self.cloudforms_url: - warnings.warn("No url specified, expected something like 'https://cfme.example.com'") - - if config.has_option('cloudforms', 'username'): - self.cloudforms_username = config.get('cloudforms', 'username') - else: - self.cloudforms_username = None - - if not self.cloudforms_username: - warnings.warn("No username specified, you need to specify a CloudForms username.") - - if config.has_option('cloudforms', 'password'): - self.cloudforms_pw = config.get('cloudforms', 'password', raw=True) - else: - self.cloudforms_pw = None - - if not self.cloudforms_pw: - warnings.warn("No password specified, you need to specify a password for the CloudForms user.") - - if config.has_option('cloudforms', 'ssl_verify'): - self.cloudforms_ssl_verify = config.getboolean('cloudforms', 'ssl_verify') - else: - self.cloudforms_ssl_verify = True - - if config.has_option('cloudforms', 'version'): - self.cloudforms_version = config.get('cloudforms', 'version') - else: - self.cloudforms_version = None - - if config.has_option('cloudforms', 'limit'): - self.cloudforms_limit = config.getint('cloudforms', 'limit') - else: - self.cloudforms_limit = 100 - - if config.has_option('cloudforms', 'purge_actions'): - self.cloudforms_purge_actions = config.getboolean('cloudforms', 'purge_actions') - else: - self.cloudforms_purge_actions = True - - if config.has_option('cloudforms', 'clean_group_keys'): - self.cloudforms_clean_group_keys = config.getboolean('cloudforms', 'clean_group_keys') - else: - self.cloudforms_clean_group_keys = True - - if config.has_option('cloudforms', 'nest_tags'): - self.cloudforms_nest_tags = config.getboolean('cloudforms', 'nest_tags') - else: - self.cloudforms_nest_tags = False - - if config.has_option('cloudforms', 'suffix'): - self.cloudforms_suffix = config.get('cloudforms', 'suffix') - if self.cloudforms_suffix[0] != '.': - raise AnsibleError('Leading fullstop is required for Cloudforms suffix') - else: - self.cloudforms_suffix = None - - if config.has_option('cloudforms', 'prefer_ipv4'): - self.cloudforms_prefer_ipv4 = config.getboolean('cloudforms', 'prefer_ipv4') - else: - self.cloudforms_prefer_ipv4 = False - - # Ansible related - try: - group_patterns = config.get('ansible', 'group_patterns') - except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): - group_patterns = "[]" - - self.group_patterns = eval(group_patterns) - - # Cache related - try: - cache_path = os.path.expanduser(config.get('cache', 'path')) - except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): - cache_path = '.' - (script, ext) = os.path.splitext(os.path.basename(__file__)) - self.cache_path_hosts = cache_path + "/%s.hosts" % script - self.cache_path_inventory = cache_path + "/%s.inventory" % script - self.cache_max_age = config.getint('cache', 'max_age') - - if self.args.debug: - print("CloudForms settings:") - print("cloudforms_url = %s" % self.cloudforms_url) - print("cloudforms_username = %s" % self.cloudforms_username) - print("cloudforms_pw = %s" % self.cloudforms_pw) - print("cloudforms_ssl_verify = %s" % self.cloudforms_ssl_verify) - print("cloudforms_version = %s" % self.cloudforms_version) - print("cloudforms_limit = %s" % self.cloudforms_limit) - print("cloudforms_purge_actions = %s" % self.cloudforms_purge_actions) - print("Cache settings:") - print("cache_max_age = %s" % self.cache_max_age) - print("cache_path_hosts = %s" % self.cache_path_hosts) - print("cache_path_inventory = %s" % self.cache_path_inventory) - - def parse_cli_args(self): - """ - Command line argument processing - """ - parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on CloudForms managed VMs') - parser.add_argument('--list', action='store_true', default=True, help='List instances (default: True)') - parser.add_argument('--host', action='store', help='Get all the variables about a specific instance') - parser.add_argument('--pretty', action='store_true', default=False, help='Pretty print JSON output (default: False)') - parser.add_argument('--refresh-cache', action='store_true', default=False, - help='Force refresh of cache by making API requests to CloudForms (default: False - use cache files)') - parser.add_argument('--debug', action='store_true', default=False, help='Show debug output while running (default: False)') - self.args = parser.parse_args() - - def _get_json(self, url): - """ - Make a request and return the JSON - """ - results = [] - - ret = requests.get(url, - auth=HTTPBasicAuth(self.cloudforms_username, self.cloudforms_pw), - verify=self.cloudforms_ssl_verify) - - ret.raise_for_status() - - try: - results = json.loads(ret.text) - except ValueError: - warnings.warn("Unexpected response from {0} ({1}): {2}".format(self.cloudforms_url, ret.status_code, ret.reason)) - results = {} - - if self.args.debug: - print("=======================================================================") - print("=======================================================================") - print("=======================================================================") - print(ret.text) - print("=======================================================================") - print("=======================================================================") - print("=======================================================================") - - return results - - def _get_hosts(self): - """ - Get all hosts by paging through the results - """ - limit = self.cloudforms_limit - - page = 0 - last_page = False - - results = [] - - while not last_page: - offset = page * limit - ret = self._get_json("%s/api/vms?offset=%s&limit=%s&expand=resources,tags,hosts,&attributes=ipaddresses" % (self.cloudforms_url, offset, limit)) - results += ret['resources'] - if ret['subcount'] < limit: - last_page = True - page += 1 - - return results - - def update_cache(self): - """ - Make calls to cloudforms and save the output in a cache - """ - self.groups = dict() - self.hosts = dict() - - if self.args.debug: - print("Updating cache...") - - for host in self._get_hosts(): - if self.cloudforms_suffix is not None and not host['name'].endswith(self.cloudforms_suffix): - host['name'] = host['name'] + self.cloudforms_suffix - - # Ignore VMs that are not powered on - if host['power_state'] != 'on': - if self.args.debug: - print("Skipping %s because power_state = %s" % (host['name'], host['power_state'])) - continue - - # purge actions - if self.cloudforms_purge_actions and 'actions' in host: - del host['actions'] - - # Create ansible groups for tags - if 'tags' in host: - - # Create top-level group - if 'tags' not in self.inventory: - self.inventory['tags'] = dict(children=[], vars={}, hosts=[]) - - if not self.cloudforms_nest_tags: - # don't expand tags, just use them in a safe way - for group in host['tags']: - # Add sub-group, as a child of top-level - safe_key = self.to_safe(group['name']) - if safe_key: - if self.args.debug: - print("Adding sub-group '%s' to parent 'tags'" % safe_key) - - if safe_key not in self.inventory['tags']['children']: - self.push(self.inventory['tags'], 'children', safe_key) - - self.push(self.inventory, safe_key, host['name']) - - if self.args.debug: - print("Found tag [%s] for host which will be mapped to [%s]" % (group['name'], safe_key)) - else: - # expand the tags into nested groups / sub-groups - # Create nested groups for tags - safe_parent_tag_name = 'tags' - for tag in host['tags']: - tag_hierarchy = tag['name'][1:].split('/') - - if self.args.debug: - print("Working on list %s" % tag_hierarchy) - - for tag_name in tag_hierarchy: - if self.args.debug: - print("Working on tag_name = %s" % tag_name) - - safe_tag_name = self.to_safe(tag_name) - if self.args.debug: - print("Using sanitized name %s" % safe_tag_name) - - # Create sub-group - if safe_tag_name not in self.inventory: - self.inventory[safe_tag_name] = dict(children=[], vars={}, hosts=[]) - - # Add sub-group, as a child of top-level - if safe_parent_tag_name: - if self.args.debug: - print("Adding sub-group '%s' to parent '%s'" % (safe_tag_name, safe_parent_tag_name)) - - if safe_tag_name not in self.inventory[safe_parent_tag_name]['children']: - self.push(self.inventory[safe_parent_tag_name], 'children', safe_tag_name) - - # Make sure the next one uses this one as it's parent - safe_parent_tag_name = safe_tag_name - - # Add the host to the last tag - self.push(self.inventory[safe_parent_tag_name], 'hosts', host['name']) - - # Set ansible_ssh_host to the first available ip address - if 'ipaddresses' in host and host['ipaddresses'] and isinstance(host['ipaddresses'], list): - # If no preference for IPv4, just use the first entry - if not self.cloudforms_prefer_ipv4: - host['ansible_ssh_host'] = host['ipaddresses'][0] - else: - # Before we search for an IPv4 address, set using the first entry in case we don't find any - host['ansible_ssh_host'] = host['ipaddresses'][0] - for currenthost in host['ipaddresses']: - if '.' in currenthost: - host['ansible_ssh_host'] = currenthost - - # Create additional groups - for key in ('location', 'type', 'vendor'): - safe_key = self.to_safe(host[key]) - - # Create top-level group - if key not in self.inventory: - self.inventory[key] = dict(children=[], vars={}, hosts=[]) - - # Create sub-group - if safe_key not in self.inventory: - self.inventory[safe_key] = dict(children=[], vars={}, hosts=[]) - - # Add sub-group, as a child of top-level - if safe_key not in self.inventory[key]['children']: - self.push(self.inventory[key], 'children', safe_key) - - if key in host: - # Add host to sub-group - self.push(self.inventory[safe_key], 'hosts', host['name']) - - self.hosts[host['name']] = host - self.push(self.inventory, 'all', host['name']) - - if self.args.debug: - print("Saving cached data") - - self.write_to_cache(self.hosts, self.cache_path_hosts) - self.write_to_cache(self.inventory, self.cache_path_inventory) - - def get_host_info(self, host): - """ - Get variables about a specific host - """ - if not self.hosts or len(self.hosts) == 0: - # Need to load cache from cache - self.load_hosts_from_cache() - - if host not in self.hosts: - if self.args.debug: - print("[%s] not found in cache." % host) - - # try updating the cache - self.update_cache() - - if host not in self.hosts: - if self.args.debug: - print("[%s] does not exist after cache update." % host) - # host might not exist anymore - return self.json_format_dict({}, self.args.pretty) - - return self.json_format_dict(self.hosts[host], self.args.pretty) - - def push(self, d, k, v): - """ - Safely puts a new entry onto an array. - """ - if k in d: - d[k].append(v) - else: - d[k] = [v] - - def load_inventory_from_cache(self): - """ - Reads the inventory from the cache file sets self.inventory - """ - cache = open(self.cache_path_inventory, 'r') - json_inventory = cache.read() - self.inventory = json.loads(json_inventory) - - def load_hosts_from_cache(self): - """ - Reads the cache from the cache file sets self.hosts - """ - cache = open(self.cache_path_hosts, 'r') - json_cache = cache.read() - self.hosts = json.loads(json_cache) - - def write_to_cache(self, data, filename): - """ - Writes data in JSON format to a file - """ - json_data = self.json_format_dict(data, True) - cache = open(filename, 'w') - cache.write(json_data) - cache.close() - - def to_safe(self, word): - """ - Converts 'bad' characters in a string to underscores so they can be used as Ansible groups - """ - if self.cloudforms_clean_group_keys: - regex = r"[^A-Za-z0-9\_]" - return re.sub(regex, "_", word.replace(" ", "")) - else: - return word - - def json_format_dict(self, data, pretty=False): - """ - Converts a dict to a JSON object and dumps it as a formatted string - """ - if pretty: - return json.dumps(data, sort_keys=True, indent=2) - else: - return json.dumps(data) - -CloudFormsInventory() diff --git a/awx/plugins/inventory/ec2.ini.example b/awx/plugins/inventory/ec2.ini.example deleted file mode 100644 index 8637d0ff59f9..000000000000 --- a/awx/plugins/inventory/ec2.ini.example +++ /dev/null @@ -1,219 +0,0 @@ -# Ansible EC2 external inventory script settings -# - -[ec2] - -# to talk to a private eucalyptus instance uncomment these lines -# and edit edit eucalyptus_host to be the host name of your cloud controller -#eucalyptus = True -#eucalyptus_host = clc.cloud.domain.org - -# AWS regions to make calls to. Set this to 'all' to make request to all regions -# in AWS and merge the results together. Alternatively, set this to a comma -# separated list of regions. E.g. 'us-east-1,us-west-1,us-west-2' and do not -# provide the 'regions_exclude' option. If this is set to 'auto', AWS_REGION or -# AWS_DEFAULT_REGION environment variable will be read to determine the region. -regions = all -regions_exclude = us-gov-west-1, cn-north-1 - -# When generating inventory, Ansible needs to know how to address a server. -# Each EC2 instance has a lot of variables associated with it. Here is the list: -# http://docs.pythonboto.org/en/latest/ref/ec2.html#module-boto.ec2.instance -# Below are 2 variables that are used as the address of a server: -# - destination_variable -# - vpc_destination_variable - -# This is the normal destination variable to use. If you are running Ansible -# from outside EC2, then 'public_dns_name' makes the most sense. If you are -# running Ansible from within EC2, then perhaps you want to use the internal -# address, and should set this to 'private_dns_name'. The key of an EC2 tag -# may optionally be used; however the boto instance variables hold precedence -# in the event of a collision. -destination_variable = public_dns_name - -# This allows you to override the inventory_name with an ec2 variable, instead -# of using the destination_variable above. Addressing (aka ansible_ssh_host) -# will still use destination_variable. Tags should be written as 'tag_TAGNAME'. -#hostname_variable = tag_Name - -# For server inside a VPC, using DNS names may not make sense. When an instance -# has 'subnet_id' set, this variable is used. If the subnet is public, setting -# this to 'ip_address' will return the public IP address. For instances in a -# private subnet, this should be set to 'private_ip_address', and Ansible must -# be run from within EC2. The key of an EC2 tag may optionally be used; however -# the boto instance variables hold precedence in the event of a collision. -# WARNING: - instances that are in the private vpc, _without_ public ip address -# will not be listed in the inventory until You set: -# vpc_destination_variable = private_ip_address -vpc_destination_variable = ip_address - -# The following two settings allow flexible ansible host naming based on a -# python format string and a comma-separated list of ec2 tags. Note that: -# -# 1) If the tags referenced are not present for some instances, empty strings -# will be substituted in the format string. -# 2) This overrides both destination_variable and vpc_destination_variable. -# -#destination_format = {0}.{1}.example.com -#destination_format_tags = Name,environment - -# To tag instances on EC2 with the resource records that point to them from -# Route53, set 'route53' to True. -route53 = False - -# To use Route53 records as the inventory hostnames, uncomment and set -# to equal the domain name you wish to use. You must also have 'route53' (above) -# set to True. -# route53_hostnames = .example.com - -# To exclude RDS instances from the inventory, uncomment and set to False. -#rds = False - -# To exclude ElastiCache instances from the inventory, uncomment and set to False. -#elasticache = False - -# Additionally, you can specify the list of zones to exclude looking up in -# 'route53_excluded_zones' as a comma-separated list. -# route53_excluded_zones = samplezone1.com, samplezone2.com - -# By default, only EC2 instances in the 'running' state are returned. Set -# 'all_instances' to True to return all instances regardless of state. -all_instances = False - -# By default, only EC2 instances in the 'running' state are returned. Specify -# EC2 instance states to return as a comma-separated list. This -# option is overridden when 'all_instances' is True. -# instance_states = pending, running, shutting-down, terminated, stopping, stopped - -# By default, only RDS instances in the 'available' state are returned. Set -# 'all_rds_instances' to True return all RDS instances regardless of state. -all_rds_instances = False - -# Include RDS cluster information (Aurora etc.) -include_rds_clusters = False - -# By default, only ElastiCache clusters and nodes in the 'available' state -# are returned. Set 'all_elasticache_clusters' and/or 'all_elastic_nodes' -# to True return all ElastiCache clusters and nodes, regardless of state. -# -# Note that all_elasticache_nodes only applies to listed clusters. That means -# if you set all_elastic_clusters to false, no node will be return from -# unavailable clusters, regardless of the state and to what you set for -# all_elasticache_nodes. -all_elasticache_replication_groups = False -all_elasticache_clusters = False -all_elasticache_nodes = False - -# API calls to EC2 are slow. For this reason, we cache the results of an API -# call. Set this to the path you want cache files to be written to. Two files -# will be written to this directory: -# - ansible-ec2.cache -# - ansible-ec2.index -cache_path = ~/.ansible/tmp - -# The number of seconds a cache file is considered valid. After this many -# seconds, a new API call will be made, and the cache file will be updated. -# To disable the cache, set this value to 0 -cache_max_age = 300 - -# Organize groups into a nested/hierarchy instead of a flat namespace. -nested_groups = False - -# Replace - tags when creating groups to avoid issues with ansible -replace_dash_in_groups = True - -# If set to true, any tag of the form "a,b,c" is expanded into a list -# and the results are used to create additional tag_* inventory groups. -expand_csv_tags = False - -# The EC2 inventory output can become very large. To manage its size, -# configure which groups should be created. -group_by_instance_id = True -group_by_region = True -group_by_availability_zone = True -group_by_aws_account = False -group_by_ami_id = True -group_by_instance_type = True -group_by_instance_state = False -group_by_platform = True -group_by_key_pair = True -group_by_vpc_id = True -group_by_security_group = True -group_by_tag_keys = True -group_by_tag_none = True -group_by_route53_names = True -group_by_rds_engine = True -group_by_rds_parameter_group = True -group_by_elasticache_engine = True -group_by_elasticache_cluster = True -group_by_elasticache_parameter_group = True -group_by_elasticache_replication_group = True - -# If you only want to include hosts that match a certain regular expression -# pattern_include = staging-* - -# If you want to exclude any hosts that match a certain regular expression -# pattern_exclude = staging-* - -# Instance filters can be used to control which instances are retrieved for -# inventory. For the full list of possible filters, please read the EC2 API -# docs: http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DescribeInstances.html#query-DescribeInstances-filters -# Filters are key/value pairs separated by '=', to list multiple filters use -# a list separated by commas. To "AND" criteria together, use "&". Note that -# the "AND" is not useful along with stack_filters and so such usage is not allowed. -# See examples below. - -# If you want to apply multiple filters simultaneously, set stack_filters to -# True. Default behaviour is to combine the results of all filters. Stacking -# allows the use of multiple conditions to filter down, for example by -# environment and type of host. -stack_filters = False - -# Retrieve only instances with (key=value) env=staging tag -# instance_filters = tag:env=staging - -# Retrieve only instances with role=webservers OR role=dbservers tag -# instance_filters = tag:role=webservers,tag:role=dbservers - -# Retrieve only t1.micro instances OR instances with tag env=staging -# instance_filters = instance-type=t1.micro,tag:env=staging - -# You can use wildcards in filter values also. Below will list instances which -# tag Name value matches webservers1* -# (ex. webservers15, webservers1a, webservers123 etc) -# instance_filters = tag:Name=webservers1* - -# Retrieve only instances of type t1.micro that also have tag env=stage -# instance_filters = instance-type=t1.micro&tag:env=stage - -# Retrieve instances of type t1.micro AND tag env=stage, as well as any instance -# that are of type m3.large, regardless of env tag -# instance_filters = instance-type=t1.micro&tag:env=stage,instance-type=m3.large - -# An IAM role can be assumed, so all requests are run as that role. -# This can be useful for connecting across different accounts, or to limit user -# access -# iam_role = role-arn - -# A boto configuration profile may be used to separate out credentials -# see http://boto.readthedocs.org/en/latest/boto_config_tut.html -# boto_profile = some-boto-profile-name - - -[credentials] - -# The AWS credentials can optionally be specified here. Credentials specified -# here are ignored if the environment variable AWS_ACCESS_KEY_ID or -# AWS_PROFILE is set, or if the boto_profile property above is set. -# -# Supplying AWS credentials here is not recommended, as it introduces -# non-trivial security concerns. When going down this route, please make sure -# to set access permissions for this file correctly, e.g. handle it the same -# way as you would a private SSH key. -# -# Unlike the boto and AWS configure files, this section does not support -# profiles. -# -# aws_access_key_id = AXXXXXXXXXXXXXX -# aws_secret_access_key = XXXXXXXXXXXXXXXXXXX -# aws_security_token = XXXXXXXXXXXXXXXXXXXXXXXXXXXX diff --git a/awx/plugins/inventory/ec2.py b/awx/plugins/inventory/ec2.py deleted file mode 100755 index 39102ce616b3..000000000000 --- a/awx/plugins/inventory/ec2.py +++ /dev/null @@ -1,1701 +0,0 @@ -#!/usr/bin/env python - -''' -EC2 external inventory script -================================= - -Generates inventory that Ansible can understand by making API request to -AWS EC2 using the Boto library. - -NOTE: This script assumes Ansible is being executed where the environment -variables needed for Boto have already been set: - export AWS_ACCESS_KEY_ID='AK123' - export AWS_SECRET_ACCESS_KEY='abc123' - -Optional region environment variable if region is 'auto' - -This script also assumes that there is an ec2.ini file alongside it. To specify a -different path to ec2.ini, define the EC2_INI_PATH environment variable: - - export EC2_INI_PATH=/path/to/my_ec2.ini - -If you're using eucalyptus you need to set the above variables and -you need to define: - - export EC2_URL=http://hostname_of_your_cc:port/services/Eucalyptus - -If you're using boto profiles (requires boto>=2.24.0) you can choose a profile -using the --boto-profile command line argument (e.g. ec2.py --boto-profile prod) or using -the AWS_PROFILE variable: - - AWS_PROFILE=prod ansible-playbook -i ec2.py myplaybook.yml - -For more details, see: http://docs.pythonboto.org/en/latest/boto_config_tut.html - -When run against a specific host, this script returns the following variables: - - ec2_ami_launch_index - - ec2_architecture - - ec2_association - - ec2_attachTime - - ec2_attachment - - ec2_attachmentId - - ec2_block_devices - - ec2_client_token - - ec2_deleteOnTermination - - ec2_description - - ec2_deviceIndex - - ec2_dns_name - - ec2_eventsSet - - ec2_group_name - - ec2_hypervisor - - ec2_id - - ec2_image_id - - ec2_instanceState - - ec2_instance_type - - ec2_ipOwnerId - - ec2_ip_address - - ec2_item - - ec2_kernel - - ec2_key_name - - ec2_launch_time - - ec2_monitored - - ec2_monitoring - - ec2_networkInterfaceId - - ec2_ownerId - - ec2_persistent - - ec2_placement - - ec2_platform - - ec2_previous_state - - ec2_private_dns_name - - ec2_private_ip_address - - ec2_publicIp - - ec2_public_dns_name - - ec2_ramdisk - - ec2_reason - - ec2_region - - ec2_requester_id - - ec2_root_device_name - - ec2_root_device_type - - ec2_security_group_ids - - ec2_security_group_names - - ec2_shutdown_state - - ec2_sourceDestCheck - - ec2_spot_instance_request_id - - ec2_state - - ec2_state_code - - ec2_state_reason - - ec2_status - - ec2_subnet_id - - ec2_tenancy - - ec2_virtualization_type - - ec2_vpc_id - -These variables are pulled out of a boto.ec2.instance object. There is a lack of -consistency with variable spellings (camelCase and underscores) since this -just loops through all variables the object exposes. It is preferred to use the -ones with underscores when multiple exist. - -In addition, if an instance has AWS tags associated with it, each tag is a new -variable named: - - ec2_tag_[Key] = [Value] - -Security groups are comma-separated in 'ec2_security_group_ids' and -'ec2_security_group_names'. - -When destination_format and destination_format_tags are specified -the destination_format can be built from the instance tags and attributes. -The behavior will first check the user defined tags, then proceed to -check instance attributes, and finally if neither are found 'nil' will -be used instead. - -'my_instance': { - 'region': 'us-east-1', # attribute - 'availability_zone': 'us-east-1a', # attribute - 'private_dns_name': '172.31.0.1', # attribute - 'ec2_tag_deployment': 'blue', # tag - 'ec2_tag_clusterid': 'ansible', # tag - 'ec2_tag_Name': 'webserver', # tag - ... -} - -Inside of the ec2.ini file the following settings are specified: -... -destination_format: {0}-{1}-{2}-{3} -destination_format_tags: Name,clusterid,deployment,private_dns_name -... - -These settings would produce a destination_format as the following: -'webserver-ansible-blue-172.31.0.1' -''' - -# (c) 2012, Peter Sankauskas -# -# This file is part of Ansible, -# -# Ansible is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Ansible. If not, see . - -###################################################################### - -import sys -import os -import argparse -import re -from time import time -import boto -from boto import ec2 -from boto import rds -from boto import elasticache -from boto import route53 -from boto import sts -import six - -from ansible.module_utils import ec2 as ec2_utils - -HAS_BOTO3 = False -try: - import boto3 # noqa - HAS_BOTO3 = True -except ImportError: - pass - -from six.moves import configparser -from collections import defaultdict - -try: - import json -except ImportError: - import simplejson as json - -DEFAULTS = { - 'all_elasticache_clusters': 'False', - 'all_elasticache_nodes': 'False', - 'all_elasticache_replication_groups': 'False', - 'all_instances': 'False', - 'all_rds_instances': 'False', - 'aws_access_key_id': None, - 'aws_secret_access_key': None, - 'aws_security_token': None, - 'boto_profile': None, - 'cache_max_age': '300', - 'cache_path': '~/.ansible/tmp', - 'destination_variable': 'public_dns_name', - 'elasticache': 'True', - 'eucalyptus': 'False', - 'eucalyptus_host': None, - 'expand_csv_tags': 'False', - 'group_by_ami_id': 'True', - 'group_by_availability_zone': 'True', - 'group_by_aws_account': 'False', - 'group_by_elasticache_cluster': 'True', - 'group_by_elasticache_engine': 'True', - 'group_by_elasticache_parameter_group': 'True', - 'group_by_elasticache_replication_group': 'True', - 'group_by_instance_id': 'True', - 'group_by_instance_state': 'False', - 'group_by_instance_type': 'True', - 'group_by_key_pair': 'True', - 'group_by_platform': 'True', - 'group_by_rds_engine': 'True', - 'group_by_rds_parameter_group': 'True', - 'group_by_region': 'True', - 'group_by_route53_names': 'True', - 'group_by_security_group': 'True', - 'group_by_tag_keys': 'True', - 'group_by_tag_none': 'True', - 'group_by_vpc_id': 'True', - 'hostname_variable': None, - 'iam_role': None, - 'include_rds_clusters': 'False', - 'nested_groups': 'False', - 'pattern_exclude': None, - 'pattern_include': None, - 'rds': 'False', - 'regions': 'all', - 'regions_exclude': 'us-gov-west-1, cn-north-1', - 'replace_dash_in_groups': 'True', - 'route53': 'False', - 'route53_excluded_zones': '', - 'route53_hostnames': None, - 'stack_filters': 'False', - 'vpc_destination_variable': 'ip_address' -} - - -class Ec2Inventory(object): - - def _empty_inventory(self): - return {"_meta": {"hostvars": {}}} - - def __init__(self): - ''' Main execution path ''' - - # Inventory grouped by instance IDs, tags, security groups, regions, - # and availability zones - self.inventory = self._empty_inventory() - - self.aws_account_id = None - - # Index of hostname (address) to instance ID - self.index = {} - - # Boto profile to use (if any) - self.boto_profile = None - - # AWS credentials. - self.credentials = {} - - # Read settings and parse CLI arguments - self.parse_cli_args() - self.read_settings() - - # Make sure that profile_name is not passed at all if not set - # as pre 2.24 boto will fall over otherwise - if self.boto_profile: - if not hasattr(boto.ec2.EC2Connection, 'profile_name'): - self.fail_with_error("boto version must be >= 2.24 to use profile") - - # Cache - if self.args.refresh_cache: - self.do_api_calls_update_cache() - elif not self.is_cache_valid(): - self.do_api_calls_update_cache() - - # Data to print - if self.args.host: - data_to_print = self.get_host_info() - - elif self.args.list: - # Display list of instances for inventory - if self.inventory == self._empty_inventory(): - data_to_print = self.get_inventory_from_cache() - else: - data_to_print = self.json_format_dict(self.inventory, True) - - print(data_to_print) - - def is_cache_valid(self): - ''' Determines if the cache files have expired, or if it is still valid ''' - - if os.path.isfile(self.cache_path_cache): - mod_time = os.path.getmtime(self.cache_path_cache) - current_time = time() - if (mod_time + self.cache_max_age) > current_time: - if os.path.isfile(self.cache_path_index): - return True - - return False - - def read_settings(self): - ''' Reads the settings from the ec2.ini file ''' - - scriptbasename = __file__ - scriptbasename = os.path.basename(scriptbasename) - scriptbasename = scriptbasename.replace('.py', '') - - defaults = { - 'ec2': { - 'ini_fallback': os.path.join(os.path.dirname(__file__), 'ec2.ini'), - 'ini_path': os.path.join(os.path.dirname(__file__), '%s.ini' % scriptbasename) - } - } - - if six.PY3: - config = configparser.ConfigParser(DEFAULTS) - else: - config = configparser.SafeConfigParser(DEFAULTS) - ec2_ini_path = os.environ.get('EC2_INI_PATH', defaults['ec2']['ini_path']) - ec2_ini_path = os.path.expanduser(os.path.expandvars(ec2_ini_path)) - - if not os.path.isfile(ec2_ini_path): - ec2_ini_path = os.path.expanduser(defaults['ec2']['ini_fallback']) - - if os.path.isfile(ec2_ini_path): - config.read(ec2_ini_path) - - # Add empty sections if they don't exist - try: - config.add_section('ec2') - except configparser.DuplicateSectionError: - pass - - try: - config.add_section('credentials') - except configparser.DuplicateSectionError: - pass - - # is eucalyptus? - self.eucalyptus = config.getboolean('ec2', 'eucalyptus') - self.eucalyptus_host = config.get('ec2', 'eucalyptus_host') - - # Regions - self.regions = [] - configRegions = config.get('ec2', 'regions') - if (configRegions == 'all'): - if self.eucalyptus_host: - self.regions.append(boto.connect_euca(host=self.eucalyptus_host).region.name, **self.credentials) - else: - configRegions_exclude = config.get('ec2', 'regions_exclude') - - for regionInfo in ec2.regions(): - if regionInfo.name not in configRegions_exclude: - self.regions.append(regionInfo.name) - else: - self.regions = configRegions.split(",") - if 'auto' in self.regions: - env_region = os.environ.get('AWS_REGION') - if env_region is None: - env_region = os.environ.get('AWS_DEFAULT_REGION') - self.regions = [env_region] - - # Destination addresses - self.destination_variable = config.get('ec2', 'destination_variable') - self.vpc_destination_variable = config.get('ec2', 'vpc_destination_variable') - self.hostname_variable = config.get('ec2', 'hostname_variable') - - if config.has_option('ec2', 'destination_format') and \ - config.has_option('ec2', 'destination_format_tags'): - self.destination_format = config.get('ec2', 'destination_format') - self.destination_format_tags = config.get('ec2', 'destination_format_tags').split(',') - else: - self.destination_format = None - self.destination_format_tags = None - - # Route53 - self.route53_enabled = config.getboolean('ec2', 'route53') - self.route53_hostnames = config.get('ec2', 'route53_hostnames') - - self.route53_excluded_zones = [] - self.route53_excluded_zones = [a for a in config.get('ec2', 'route53_excluded_zones').split(',') if a] - - # Include RDS instances? - self.rds_enabled = config.getboolean('ec2', 'rds') - - # Include RDS cluster instances? - self.include_rds_clusters = config.getboolean('ec2', 'include_rds_clusters') - - # Include ElastiCache instances? - self.elasticache_enabled = config.getboolean('ec2', 'elasticache') - - # Return all EC2 instances? - self.all_instances = config.getboolean('ec2', 'all_instances') - - # Instance states to be gathered in inventory. Default is 'running'. - # Setting 'all_instances' to 'yes' overrides this option. - ec2_valid_instance_states = [ - 'pending', - 'running', - 'shutting-down', - 'terminated', - 'stopping', - 'stopped' - ] - self.ec2_instance_states = [] - if self.all_instances: - self.ec2_instance_states = ec2_valid_instance_states - elif config.has_option('ec2', 'instance_states'): - for instance_state in config.get('ec2', 'instance_states').split(','): - instance_state = instance_state.strip() - if instance_state not in ec2_valid_instance_states: - continue - self.ec2_instance_states.append(instance_state) - else: - self.ec2_instance_states = ['running'] - - # Return all RDS instances? (if RDS is enabled) - self.all_rds_instances = config.getboolean('ec2', 'all_rds_instances') - - # Return all ElastiCache replication groups? (if ElastiCache is enabled) - self.all_elasticache_replication_groups = config.getboolean('ec2', 'all_elasticache_replication_groups') - - # Return all ElastiCache clusters? (if ElastiCache is enabled) - self.all_elasticache_clusters = config.getboolean('ec2', 'all_elasticache_clusters') - - # Return all ElastiCache nodes? (if ElastiCache is enabled) - self.all_elasticache_nodes = config.getboolean('ec2', 'all_elasticache_nodes') - - # boto configuration profile (prefer CLI argument then environment variables then config file) - self.boto_profile = self.args.boto_profile or \ - os.environ.get('AWS_PROFILE') or \ - config.get('ec2', 'boto_profile') - - # AWS credentials (prefer environment variables) - if not (self.boto_profile or os.environ.get('AWS_ACCESS_KEY_ID') or - os.environ.get('AWS_PROFILE')): - - aws_access_key_id = config.get('credentials', 'aws_access_key_id') - aws_secret_access_key = config.get('credentials', 'aws_secret_access_key') - aws_security_token = config.get('credentials', 'aws_security_token') - - if aws_access_key_id: - self.credentials = { - 'aws_access_key_id': aws_access_key_id, - 'aws_secret_access_key': aws_secret_access_key - } - if aws_security_token: - self.credentials['security_token'] = aws_security_token - - # Cache related - cache_dir = os.path.expanduser(config.get('ec2', 'cache_path')) - if self.boto_profile: - cache_dir = os.path.join(cache_dir, 'profile_' + self.boto_profile) - if not os.path.exists(cache_dir): - os.makedirs(cache_dir) - - cache_name = 'ansible-ec2' - cache_id = self.boto_profile or os.environ.get('AWS_ACCESS_KEY_ID', self.credentials.get('aws_access_key_id')) - if cache_id: - cache_name = '%s-%s' % (cache_name, cache_id) - cache_name += '-' + str(abs(hash(__file__)))[1:7] - self.cache_path_cache = os.path.join(cache_dir, "%s.cache" % cache_name) - self.cache_path_index = os.path.join(cache_dir, "%s.index" % cache_name) - self.cache_max_age = config.getint('ec2', 'cache_max_age') - - self.expand_csv_tags = config.getboolean('ec2', 'expand_csv_tags') - - # Configure nested groups instead of flat namespace. - self.nested_groups = config.getboolean('ec2', 'nested_groups') - - # Replace dash or not in group names - self.replace_dash_in_groups = config.getboolean('ec2', 'replace_dash_in_groups') - - # IAM role to assume for connection - self.iam_role = config.get('ec2', 'iam_role') - - # Configure which groups should be created. - - group_by_options = [a for a in DEFAULTS if a.startswith('group_by')] - for option in group_by_options: - setattr(self, option, config.getboolean('ec2', option)) - - # Do we need to just include hosts that match a pattern? - self.pattern_include = config.get('ec2', 'pattern_include') - if self.pattern_include: - self.pattern_include = re.compile(self.pattern_include) - - # Do we need to exclude hosts that match a pattern? - self.pattern_exclude = config.get('ec2', 'pattern_exclude') - if self.pattern_exclude: - self.pattern_exclude = re.compile(self.pattern_exclude) - - # Do we want to stack multiple filters? - self.stack_filters = config.getboolean('ec2', 'stack_filters') - - # Instance filters (see boto and EC2 API docs). Ignore invalid filters. - self.ec2_instance_filters = [] - - if config.has_option('ec2', 'instance_filters'): - filters = config.get('ec2', 'instance_filters') - - if self.stack_filters and '&' in filters: - self.fail_with_error("AND filters along with stack_filter enabled is not supported.\n") - - filter_sets = [f for f in filters.split(',') if f] - - for filter_set in filter_sets: - filters = {} - filter_set = filter_set.strip() - for instance_filter in filter_set.split("&"): - instance_filter = instance_filter.strip() - if not instance_filter or '=' not in instance_filter: - continue - filter_key, filter_value = [x.strip() for x in instance_filter.split('=', 1)] - if not filter_key: - continue - filters[filter_key] = filter_value - self.ec2_instance_filters.append(filters.copy()) - - def parse_cli_args(self): - ''' Command line argument processing ''' - - parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on EC2') - parser.add_argument('--list', action='store_true', default=True, - help='List instances (default: True)') - parser.add_argument('--host', action='store', - help='Get all the variables about a specific instance') - parser.add_argument('--refresh-cache', action='store_true', default=False, - help='Force refresh of cache by making API requests to EC2 (default: False - use cache files)') - parser.add_argument('--profile', '--boto-profile', action='store', dest='boto_profile', - help='Use boto profile for connections to EC2') - self.args = parser.parse_args() - - def do_api_calls_update_cache(self): - ''' Do API calls to each region, and save data in cache files ''' - - if self.route53_enabled: - self.get_route53_records() - - for region in self.regions: - self.get_instances_by_region(region) - if self.rds_enabled: - self.get_rds_instances_by_region(region) - if self.elasticache_enabled: - self.get_elasticache_clusters_by_region(region) - self.get_elasticache_replication_groups_by_region(region) - if self.include_rds_clusters: - self.include_rds_clusters_by_region(region) - - self.write_to_cache(self.inventory, self.cache_path_cache) - self.write_to_cache(self.index, self.cache_path_index) - - def connect(self, region): - ''' create connection to api server''' - if self.eucalyptus: - conn = boto.connect_euca(host=self.eucalyptus_host, **self.credentials) - conn.APIVersion = '2010-08-31' - else: - conn = self.connect_to_aws(ec2, region) - return conn - - def boto_fix_security_token_in_profile(self, connect_args): - ''' monkey patch for boto issue boto/boto#2100 ''' - profile = 'profile ' + self.boto_profile - if boto.config.has_option(profile, 'aws_security_token'): - connect_args['security_token'] = boto.config.get(profile, 'aws_security_token') - return connect_args - - def connect_to_aws(self, module, region): - connect_args = self.credentials - - # only pass the profile name if it's set (as it is not supported by older boto versions) - if self.boto_profile: - connect_args['profile_name'] = self.boto_profile - self.boto_fix_security_token_in_profile(connect_args) - - if self.iam_role: - sts_conn = sts.connect_to_region(region, **connect_args) - role = sts_conn.assume_role(self.iam_role, 'ansible_dynamic_inventory') - connect_args['aws_access_key_id'] = role.credentials.access_key - connect_args['aws_secret_access_key'] = role.credentials.secret_key - connect_args['security_token'] = role.credentials.session_token - - conn = module.connect_to_region(region, **connect_args) - # connect_to_region will fail "silently" by returning None if the region name is wrong or not supported - if conn is None: - self.fail_with_error("region name: %s likely not supported, or AWS is down. connection to region failed." % region) - return conn - - def get_instances_by_region(self, region): - ''' Makes an AWS EC2 API call to the list of instances in a particular - region ''' - - try: - conn = self.connect(region) - reservations = [] - if self.ec2_instance_filters: - if self.stack_filters: - filters_dict = {} - for filters in self.ec2_instance_filters: - filters_dict.update(filters) - reservations.extend(conn.get_all_instances(filters=filters_dict)) - else: - for filters in self.ec2_instance_filters: - reservations.extend(conn.get_all_instances(filters=filters)) - else: - reservations = conn.get_all_instances() - - # Pull the tags back in a second step - # AWS are on record as saying that the tags fetched in the first `get_all_instances` request are not - # reliable and may be missing, and the only way to guarantee they are there is by calling `get_all_tags` - instance_ids = [] - for reservation in reservations: - instance_ids.extend([instance.id for instance in reservation.instances]) - - max_filter_value = 199 - tags = [] - for i in range(0, len(instance_ids), max_filter_value): - tags.extend(conn.get_all_tags(filters={'resource-type': 'instance', 'resource-id': instance_ids[i:i + max_filter_value]})) - - tags_by_instance_id = defaultdict(dict) - for tag in tags: - tags_by_instance_id[tag.res_id][tag.name] = tag.value - - if (not self.aws_account_id) and reservations: - self.aws_account_id = reservations[0].owner_id - - for reservation in reservations: - for instance in reservation.instances: - instance.tags = tags_by_instance_id[instance.id] - self.add_instance(instance, region) - - except boto.exception.BotoServerError as e: - if e.error_code == 'AuthFailure': - error = self.get_auth_error_message() - else: - backend = 'Eucalyptus' if self.eucalyptus else 'AWS' - error = "Error connecting to %s backend.\n%s" % (backend, e.message) - self.fail_with_error(error, 'getting EC2 instances') - - def tags_match_filters(self, tags): - ''' return True if given tags match configured filters ''' - if not self.ec2_instance_filters: - return True - - for filters in self.ec2_instance_filters: - for filter_name, filter_value in filters.items(): - if filter_name[:4] != 'tag:': - continue - filter_name = filter_name[4:] - if filter_name not in tags: - if self.stack_filters: - return False - continue - if isinstance(filter_value, list): - if self.stack_filters and tags[filter_name] not in filter_value: - return False - if not self.stack_filters and tags[filter_name] in filter_value: - return True - if isinstance(filter_value, six.string_types): - if self.stack_filters and tags[filter_name] != filter_value: - return False - if not self.stack_filters and tags[filter_name] == filter_value: - return True - - return self.stack_filters - - def get_rds_instances_by_region(self, region): - ''' Makes an AWS API call to the list of RDS instances in a particular - region ''' - - if not HAS_BOTO3: - self.fail_with_error("Working with RDS instances requires boto3 - please install boto3 and try again", - "getting RDS instances") - - client = ec2_utils.boto3_inventory_conn('client', 'rds', region, **self.credentials) - db_instances = client.describe_db_instances() - - try: - conn = self.connect_to_aws(rds, region) - if conn: - marker = None - while True: - instances = conn.get_all_dbinstances(marker=marker) - marker = instances.marker - for index, instance in enumerate(instances): - # Add tags to instances. - instance.arn = db_instances['DBInstances'][index]['DBInstanceArn'] - tags = client.list_tags_for_resource(ResourceName=instance.arn)['TagList'] - instance.tags = {} - for tag in tags: - instance.tags[tag['Key']] = tag['Value'] - if self.tags_match_filters(instance.tags): - self.add_rds_instance(instance, region) - if not marker: - break - except boto.exception.BotoServerError as e: - error = e.reason - - if e.error_code == 'AuthFailure': - error = self.get_auth_error_message() - elif e.error_code == "OptInRequired": - error = "RDS hasn't been enabled for this account yet. " \ - "You must either log in to the RDS service through the AWS console to enable it, " \ - "or set 'rds = False' in ec2.ini" - elif not e.reason == "Forbidden": - error = "Looks like AWS RDS is down:\n%s" % e.message - self.fail_with_error(error, 'getting RDS instances') - - def include_rds_clusters_by_region(self, region): - if not HAS_BOTO3: - self.fail_with_error("Working with RDS clusters requires boto3 - please install boto3 and try again", - "getting RDS clusters") - - client = ec2_utils.boto3_inventory_conn('client', 'rds', region, **self.credentials) - - marker, clusters = '', [] - while marker is not None: - resp = client.describe_db_clusters(Marker=marker) - clusters.extend(resp["DBClusters"]) - marker = resp.get('Marker', None) - - account_id = boto.connect_iam().get_user().arn.split(':')[4] - c_dict = {} - for c in clusters: - # remove these datetime objects as there is no serialisation to json - # currently in place and we don't need the data yet - if 'EarliestRestorableTime' in c: - del c['EarliestRestorableTime'] - if 'LatestRestorableTime' in c: - del c['LatestRestorableTime'] - - if not self.ec2_instance_filters: - matches_filter = True - else: - matches_filter = False - - try: - # arn:aws:rds:::: - tags = client.list_tags_for_resource( - ResourceName='arn:aws:rds:' + region + ':' + account_id + ':cluster:' + c['DBClusterIdentifier']) - c['Tags'] = tags['TagList'] - - if self.ec2_instance_filters: - for filters in self.ec2_instance_filters: - for filter_key, filter_values in filters.items(): - # get AWS tag key e.g. tag:env will be 'env' - tag_name = filter_key.split(":", 1)[1] - # Filter values is a list (if you put multiple values for the same tag name) - matches_filter = any(d['Key'] == tag_name and d['Value'] in filter_values for d in c['Tags']) - - if matches_filter: - # it matches a filter, so stop looking for further matches - break - - if matches_filter: - break - - except Exception as e: - if e.message.find('DBInstanceNotFound') >= 0: - # AWS RDS bug (2016-01-06) means deletion does not fully complete and leave an 'empty' cluster. - # Ignore errors when trying to find tags for these - pass - - # ignore empty clusters caused by AWS bug - if len(c['DBClusterMembers']) == 0: - continue - elif matches_filter: - c_dict[c['DBClusterIdentifier']] = c - - self.inventory['db_clusters'] = c_dict - - def get_elasticache_clusters_by_region(self, region): - ''' Makes an AWS API call to the list of ElastiCache clusters (with - nodes' info) in a particular region.''' - - # ElastiCache boto module doesn't provide a get_all_instances method, - # that's why we need to call describe directly (it would be called by - # the shorthand method anyway...) - try: - conn = self.connect_to_aws(elasticache, region) - if conn: - # show_cache_node_info = True - # because we also want nodes' information - response = conn.describe_cache_clusters(None, None, None, True) - - except boto.exception.BotoServerError as e: - error = e.reason - - if e.error_code == 'AuthFailure': - error = self.get_auth_error_message() - elif e.error_code == "OptInRequired": - error = "ElastiCache hasn't been enabled for this account yet. " \ - "You must either log in to the ElastiCache service through the AWS console to enable it, " \ - "or set 'elasticache = False' in ec2.ini" - elif not e.reason == "Forbidden": - error = "Looks like AWS ElastiCache is down:\n%s" % e.message - self.fail_with_error(error, 'getting ElastiCache clusters') - - try: - # Boto also doesn't provide wrapper classes to CacheClusters or - # CacheNodes. Because of that we can't make use of the get_list - # method in the AWSQueryConnection. Let's do the work manually - clusters = response['DescribeCacheClustersResponse']['DescribeCacheClustersResult']['CacheClusters'] - - except KeyError as e: - error = "ElastiCache query to AWS failed (unexpected format)." - self.fail_with_error(error, 'getting ElastiCache clusters') - - for cluster in clusters: - self.add_elasticache_cluster(cluster, region) - - def get_elasticache_replication_groups_by_region(self, region): - ''' Makes an AWS API call to the list of ElastiCache replication groups - in a particular region.''' - - # ElastiCache boto module doesn't provide a get_all_instances method, - # that's why we need to call describe directly (it would be called by - # the shorthand method anyway...) - try: - conn = self.connect_to_aws(elasticache, region) - if conn: - response = conn.describe_replication_groups() - - except boto.exception.BotoServerError as e: - error = e.reason - - if e.error_code == 'AuthFailure': - error = self.get_auth_error_message() - if not e.reason == "Forbidden": - error = "Looks like AWS ElastiCache [Replication Groups] is down:\n%s" % e.message - self.fail_with_error(error, 'getting ElastiCache clusters') - - try: - # Boto also doesn't provide wrapper classes to ReplicationGroups - # Because of that we can't make use of the get_list method in the - # AWSQueryConnection. Let's do the work manually - replication_groups = response['DescribeReplicationGroupsResponse']['DescribeReplicationGroupsResult']['ReplicationGroups'] - - except KeyError as e: - error = "ElastiCache [Replication Groups] query to AWS failed (unexpected format)." - self.fail_with_error(error, 'getting ElastiCache clusters') - - for replication_group in replication_groups: - self.add_elasticache_replication_group(replication_group, region) - - def get_auth_error_message(self): - ''' create an informative error message if there is an issue authenticating''' - errors = ["Authentication error retrieving ec2 inventory."] - if None in [os.environ.get('AWS_ACCESS_KEY_ID'), os.environ.get('AWS_SECRET_ACCESS_KEY')]: - errors.append(' - No AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY environment vars found') - else: - errors.append(' - AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment vars found but may not be correct') - - boto_paths = ['/etc/boto.cfg', '~/.boto', '~/.aws/credentials'] - boto_config_found = [p for p in boto_paths if os.path.isfile(os.path.expanduser(p))] - if len(boto_config_found) > 0: - errors.append(" - Boto configs found at '%s', but the credentials contained may not be correct" % ', '.join(boto_config_found)) - else: - errors.append(" - No Boto config found at any expected location '%s'" % ', '.join(boto_paths)) - - return '\n'.join(errors) - - def fail_with_error(self, err_msg, err_operation=None): - '''log an error to std err for ansible-playbook to consume and exit''' - if err_operation: - err_msg = 'ERROR: "{err_msg}", while: {err_operation}'.format( - err_msg=err_msg, err_operation=err_operation) - sys.stderr.write(err_msg) - sys.exit(1) - - def get_instance(self, region, instance_id): - conn = self.connect(region) - - reservations = conn.get_all_instances([instance_id]) - for reservation in reservations: - for instance in reservation.instances: - return instance - - def add_instance(self, instance, region): - ''' Adds an instance to the inventory and index, as long as it is - addressable ''' - - # Only return instances with desired instance states - if instance.state not in self.ec2_instance_states: - return - - # Select the best destination address - # When destination_format and destination_format_tags are specified - # the following code will attempt to find the instance tags first, - # then the instance attributes next, and finally if neither are found - # assign nil for the desired destination format attribute. - if self.destination_format and self.destination_format_tags: - dest_vars = [] - inst_tags = getattr(instance, 'tags') - for tag in self.destination_format_tags: - if tag in inst_tags: - dest_vars.append(inst_tags[tag]) - elif hasattr(instance, tag): - dest_vars.append(getattr(instance, tag)) - else: - dest_vars.append('nil') - - dest = self.destination_format.format(*dest_vars) - elif instance.subnet_id: - dest = getattr(instance, self.vpc_destination_variable, None) - if dest is None: - dest = getattr(instance, 'tags').get(self.vpc_destination_variable, None) - else: - dest = getattr(instance, self.destination_variable, None) - if dest is None: - dest = getattr(instance, 'tags').get(self.destination_variable, None) - - if not dest: - # Skip instances we cannot address (e.g. private VPC subnet) - return - - # Set the inventory name - hostname = None - if self.hostname_variable: - if self.hostname_variable.startswith('tag_'): - hostname = instance.tags.get(self.hostname_variable[4:], None) - else: - hostname = getattr(instance, self.hostname_variable) - - # set the hostname from route53 - if self.route53_enabled and self.route53_hostnames: - route53_names = self.get_instance_route53_names(instance) - for name in route53_names: - if name.endswith(self.route53_hostnames): - hostname = name - - # If we can't get a nice hostname, use the destination address - if not hostname: - hostname = dest - # to_safe strips hostname characters like dots, so don't strip route53 hostnames - elif self.route53_enabled and self.route53_hostnames and hostname.endswith(self.route53_hostnames): - hostname = hostname.lower() - else: - hostname = self.to_safe(hostname).lower() - - # if we only want to include hosts that match a pattern, skip those that don't - if self.pattern_include and not self.pattern_include.match(hostname): - return - - # if we need to exclude hosts that match a pattern, skip those - if self.pattern_exclude and self.pattern_exclude.match(hostname): - return - - # Add to index - self.index[hostname] = [region, instance.id] - - # Inventory: Group by instance ID (always a group of 1) - if self.group_by_instance_id: - self.inventory[instance.id] = [hostname] - if self.nested_groups: - self.push_group(self.inventory, 'instances', instance.id) - - # Inventory: Group by region - if self.group_by_region: - self.push(self.inventory, region, hostname) - if self.nested_groups: - self.push_group(self.inventory, 'regions', region) - - # Inventory: Group by availability zone - if self.group_by_availability_zone: - self.push(self.inventory, instance.placement, hostname) - if self.nested_groups: - if self.group_by_region: - self.push_group(self.inventory, region, instance.placement) - self.push_group(self.inventory, 'zones', instance.placement) - - # Inventory: Group by Amazon Machine Image (AMI) ID - if self.group_by_ami_id: - ami_id = self.to_safe(instance.image_id) - self.push(self.inventory, ami_id, hostname) - if self.nested_groups: - self.push_group(self.inventory, 'images', ami_id) - - # Inventory: Group by instance type - if self.group_by_instance_type: - type_name = self.to_safe('type_' + instance.instance_type) - self.push(self.inventory, type_name, hostname) - if self.nested_groups: - self.push_group(self.inventory, 'types', type_name) - - # Inventory: Group by instance state - if self.group_by_instance_state: - state_name = self.to_safe('instance_state_' + instance.state) - self.push(self.inventory, state_name, hostname) - if self.nested_groups: - self.push_group(self.inventory, 'instance_states', state_name) - - # Inventory: Group by platform - if self.group_by_platform: - if instance.platform: - platform = self.to_safe('platform_' + instance.platform) - else: - platform = self.to_safe('platform_undefined') - self.push(self.inventory, platform, hostname) - if self.nested_groups: - self.push_group(self.inventory, 'platforms', platform) - - # Inventory: Group by key pair - if self.group_by_key_pair and instance.key_name: - key_name = self.to_safe('key_' + instance.key_name) - self.push(self.inventory, key_name, hostname) - if self.nested_groups: - self.push_group(self.inventory, 'keys', key_name) - - # Inventory: Group by VPC - if self.group_by_vpc_id and instance.vpc_id: - vpc_id_name = self.to_safe('vpc_id_' + instance.vpc_id) - self.push(self.inventory, vpc_id_name, hostname) - if self.nested_groups: - self.push_group(self.inventory, 'vpcs', vpc_id_name) - - # Inventory: Group by security group - if self.group_by_security_group: - try: - for group in instance.groups: - key = self.to_safe("security_group_" + group.name) - self.push(self.inventory, key, hostname) - if self.nested_groups: - self.push_group(self.inventory, 'security_groups', key) - except AttributeError: - self.fail_with_error('\n'.join(['Package boto seems a bit older.', - 'Please upgrade boto >= 2.3.0.'])) - - # Inventory: Group by AWS account ID - if self.group_by_aws_account: - self.push(self.inventory, self.aws_account_id, hostname) - if self.nested_groups: - self.push_group(self.inventory, 'accounts', self.aws_account_id) - - # Inventory: Group by tag keys - if self.group_by_tag_keys: - for k, v in instance.tags.items(): - if self.expand_csv_tags and v and ',' in v: - values = map(lambda x: x.strip(), v.split(',')) - else: - values = [v] - - for v in values: - if v: - key = self.to_safe("tag_" + k + "=" + v) - else: - key = self.to_safe("tag_" + k) - self.push(self.inventory, key, hostname) - if self.nested_groups: - self.push_group(self.inventory, 'tags', self.to_safe("tag_" + k)) - if v: - self.push_group(self.inventory, self.to_safe("tag_" + k), key) - - # Inventory: Group by Route53 domain names if enabled - if self.route53_enabled and self.group_by_route53_names: - route53_names = self.get_instance_route53_names(instance) - for name in route53_names: - self.push(self.inventory, name, hostname) - if self.nested_groups: - self.push_group(self.inventory, 'route53', name) - - # Global Tag: instances without tags - if self.group_by_tag_none and len(instance.tags) == 0: - self.push(self.inventory, 'tag_none', hostname) - if self.nested_groups: - self.push_group(self.inventory, 'tags', 'tag_none') - - # Global Tag: tag all EC2 instances - self.push(self.inventory, 'ec2', hostname) - - self.inventory["_meta"]["hostvars"][hostname] = self.get_host_info_dict_from_instance(instance) - self.inventory["_meta"]["hostvars"][hostname]['ansible_host'] = dest - - def add_rds_instance(self, instance, region): - ''' Adds an RDS instance to the inventory and index, as long as it is - addressable ''' - - # Only want available instances unless all_rds_instances is True - if not self.all_rds_instances and instance.status != 'available': - return - - # Select the best destination address - dest = instance.endpoint[0] - - if not dest: - # Skip instances we cannot address (e.g. private VPC subnet) - return - - # Set the inventory name - hostname = None - if self.hostname_variable: - if self.hostname_variable.startswith('tag_'): - hostname = instance.tags.get(self.hostname_variable[4:], None) - else: - hostname = getattr(instance, self.hostname_variable) - - # If we can't get a nice hostname, use the destination address - if not hostname: - hostname = dest - - hostname = self.to_safe(hostname).lower() - - # Add to index - self.index[hostname] = [region, instance.id] - - # Inventory: Group by instance ID (always a group of 1) - if self.group_by_instance_id: - self.inventory[instance.id] = [hostname] - if self.nested_groups: - self.push_group(self.inventory, 'instances', instance.id) - - # Inventory: Group by region - if self.group_by_region: - self.push(self.inventory, region, hostname) - if self.nested_groups: - self.push_group(self.inventory, 'regions', region) - - # Inventory: Group by availability zone - if self.group_by_availability_zone: - self.push(self.inventory, instance.availability_zone, hostname) - if self.nested_groups: - if self.group_by_region: - self.push_group(self.inventory, region, instance.availability_zone) - self.push_group(self.inventory, 'zones', instance.availability_zone) - - # Inventory: Group by instance type - if self.group_by_instance_type: - type_name = self.to_safe('type_' + instance.instance_class) - self.push(self.inventory, type_name, hostname) - if self.nested_groups: - self.push_group(self.inventory, 'types', type_name) - - # Inventory: Group by VPC - if self.group_by_vpc_id and instance.subnet_group and instance.subnet_group.vpc_id: - vpc_id_name = self.to_safe('vpc_id_' + instance.subnet_group.vpc_id) - self.push(self.inventory, vpc_id_name, hostname) - if self.nested_groups: - self.push_group(self.inventory, 'vpcs', vpc_id_name) - - # Inventory: Group by security group - if self.group_by_security_group: - try: - if instance.security_group: - key = self.to_safe("security_group_" + instance.security_group.name) - self.push(self.inventory, key, hostname) - if self.nested_groups: - self.push_group(self.inventory, 'security_groups', key) - - except AttributeError: - self.fail_with_error('\n'.join(['Package boto seems a bit older.', - 'Please upgrade boto >= 2.3.0.'])) - # Inventory: Group by tag keys - if self.group_by_tag_keys: - for k, v in instance.tags.items(): - if self.expand_csv_tags and v and ',' in v: - values = map(lambda x: x.strip(), v.split(',')) - else: - values = [v] - - for v in values: - if v: - key = self.to_safe("tag_" + k + "=" + v) - else: - key = self.to_safe("tag_" + k) - self.push(self.inventory, key, hostname) - if self.nested_groups: - self.push_group(self.inventory, 'tags', self.to_safe("tag_" + k)) - if v: - self.push_group(self.inventory, self.to_safe("tag_" + k), key) - - # Inventory: Group by engine - if self.group_by_rds_engine: - self.push(self.inventory, self.to_safe("rds_" + instance.engine), hostname) - if self.nested_groups: - self.push_group(self.inventory, 'rds_engines', self.to_safe("rds_" + instance.engine)) - - # Inventory: Group by parameter group - if self.group_by_rds_parameter_group: - self.push(self.inventory, self.to_safe("rds_parameter_group_" + instance.parameter_group.name), hostname) - if self.nested_groups: - self.push_group(self.inventory, 'rds_parameter_groups', self.to_safe("rds_parameter_group_" + instance.parameter_group.name)) - - # Global Tag: instances without tags - if self.group_by_tag_none and len(instance.tags) == 0: - self.push(self.inventory, 'tag_none', hostname) - if self.nested_groups: - self.push_group(self.inventory, 'tags', 'tag_none') - - # Global Tag: all RDS instances - self.push(self.inventory, 'rds', hostname) - - self.inventory["_meta"]["hostvars"][hostname] = self.get_host_info_dict_from_instance(instance) - self.inventory["_meta"]["hostvars"][hostname]['ansible_host'] = dest - - def add_elasticache_cluster(self, cluster, region): - ''' Adds an ElastiCache cluster to the inventory and index, as long as - it's nodes are addressable ''' - - # Only want available clusters unless all_elasticache_clusters is True - if not self.all_elasticache_clusters and cluster['CacheClusterStatus'] != 'available': - return - - # Select the best destination address - if 'ConfigurationEndpoint' in cluster and cluster['ConfigurationEndpoint']: - # Memcached cluster - dest = cluster['ConfigurationEndpoint']['Address'] - is_redis = False - else: - # Redis sigle node cluster - # Because all Redis clusters are single nodes, we'll merge the - # info from the cluster with info about the node - dest = cluster['CacheNodes'][0]['Endpoint']['Address'] - is_redis = True - - if not dest: - # Skip clusters we cannot address (e.g. private VPC subnet) - return - - # Add to index - self.index[dest] = [region, cluster['CacheClusterId']] - - # Inventory: Group by instance ID (always a group of 1) - if self.group_by_instance_id: - self.inventory[cluster['CacheClusterId']] = [dest] - if self.nested_groups: - self.push_group(self.inventory, 'instances', cluster['CacheClusterId']) - - # Inventory: Group by region - if self.group_by_region and not is_redis: - self.push(self.inventory, region, dest) - if self.nested_groups: - self.push_group(self.inventory, 'regions', region) - - # Inventory: Group by availability zone - if self.group_by_availability_zone and not is_redis: - self.push(self.inventory, cluster['PreferredAvailabilityZone'], dest) - if self.nested_groups: - if self.group_by_region: - self.push_group(self.inventory, region, cluster['PreferredAvailabilityZone']) - self.push_group(self.inventory, 'zones', cluster['PreferredAvailabilityZone']) - - # Inventory: Group by node type - if self.group_by_instance_type and not is_redis: - type_name = self.to_safe('type_' + cluster['CacheNodeType']) - self.push(self.inventory, type_name, dest) - if self.nested_groups: - self.push_group(self.inventory, 'types', type_name) - - # Inventory: Group by VPC (information not available in the current - # AWS API version for ElastiCache) - - # Inventory: Group by security group - if self.group_by_security_group and not is_redis: - - # Check for the existence of the 'SecurityGroups' key and also if - # this key has some value. When the cluster is not placed in a SG - # the query can return None here and cause an error. - if 'SecurityGroups' in cluster and cluster['SecurityGroups'] is not None: - for security_group in cluster['SecurityGroups']: - key = self.to_safe("security_group_" + security_group['SecurityGroupId']) - self.push(self.inventory, key, dest) - if self.nested_groups: - self.push_group(self.inventory, 'security_groups', key) - - # Inventory: Group by engine - if self.group_by_elasticache_engine and not is_redis: - self.push(self.inventory, self.to_safe("elasticache_" + cluster['Engine']), dest) - if self.nested_groups: - self.push_group(self.inventory, 'elasticache_engines', self.to_safe(cluster['Engine'])) - - # Inventory: Group by parameter group - if self.group_by_elasticache_parameter_group: - self.push(self.inventory, self.to_safe("elasticache_parameter_group_" + cluster['CacheParameterGroup']['CacheParameterGroupName']), dest) - if self.nested_groups: - self.push_group(self.inventory, 'elasticache_parameter_groups', self.to_safe(cluster['CacheParameterGroup']['CacheParameterGroupName'])) - - # Inventory: Group by replication group - if self.group_by_elasticache_replication_group and 'ReplicationGroupId' in cluster and cluster['ReplicationGroupId']: - self.push(self.inventory, self.to_safe("elasticache_replication_group_" + cluster['ReplicationGroupId']), dest) - if self.nested_groups: - self.push_group(self.inventory, 'elasticache_replication_groups', self.to_safe(cluster['ReplicationGroupId'])) - - # Global Tag: all ElastiCache clusters - self.push(self.inventory, 'elasticache_clusters', cluster['CacheClusterId']) - - host_info = self.get_host_info_dict_from_describe_dict(cluster) - - self.inventory["_meta"]["hostvars"][dest] = host_info - - # Add the nodes - for node in cluster['CacheNodes']: - self.add_elasticache_node(node, cluster, region) - - def add_elasticache_node(self, node, cluster, region): - ''' Adds an ElastiCache node to the inventory and index, as long as - it is addressable ''' - - # Only want available nodes unless all_elasticache_nodes is True - if not self.all_elasticache_nodes and node['CacheNodeStatus'] != 'available': - return - - # Select the best destination address - dest = node['Endpoint']['Address'] - - if not dest: - # Skip nodes we cannot address (e.g. private VPC subnet) - return - - node_id = self.to_safe(cluster['CacheClusterId'] + '_' + node['CacheNodeId']) - - # Add to index - self.index[dest] = [region, node_id] - - # Inventory: Group by node ID (always a group of 1) - if self.group_by_instance_id: - self.inventory[node_id] = [dest] - if self.nested_groups: - self.push_group(self.inventory, 'instances', node_id) - - # Inventory: Group by region - if self.group_by_region: - self.push(self.inventory, region, dest) - if self.nested_groups: - self.push_group(self.inventory, 'regions', region) - - # Inventory: Group by availability zone - if self.group_by_availability_zone: - self.push(self.inventory, cluster['PreferredAvailabilityZone'], dest) - if self.nested_groups: - if self.group_by_region: - self.push_group(self.inventory, region, cluster['PreferredAvailabilityZone']) - self.push_group(self.inventory, 'zones', cluster['PreferredAvailabilityZone']) - - # Inventory: Group by node type - if self.group_by_instance_type: - type_name = self.to_safe('type_' + cluster['CacheNodeType']) - self.push(self.inventory, type_name, dest) - if self.nested_groups: - self.push_group(self.inventory, 'types', type_name) - - # Inventory: Group by VPC (information not available in the current - # AWS API version for ElastiCache) - - # Inventory: Group by security group - if self.group_by_security_group: - - # Check for the existence of the 'SecurityGroups' key and also if - # this key has some value. When the cluster is not placed in a SG - # the query can return None here and cause an error. - if 'SecurityGroups' in cluster and cluster['SecurityGroups'] is not None: - for security_group in cluster['SecurityGroups']: - key = self.to_safe("security_group_" + security_group['SecurityGroupId']) - self.push(self.inventory, key, dest) - if self.nested_groups: - self.push_group(self.inventory, 'security_groups', key) - - # Inventory: Group by engine - if self.group_by_elasticache_engine: - self.push(self.inventory, self.to_safe("elasticache_" + cluster['Engine']), dest) - if self.nested_groups: - self.push_group(self.inventory, 'elasticache_engines', self.to_safe("elasticache_" + cluster['Engine'])) - - # Inventory: Group by parameter group (done at cluster level) - - # Inventory: Group by replication group (done at cluster level) - - # Inventory: Group by ElastiCache Cluster - if self.group_by_elasticache_cluster: - self.push(self.inventory, self.to_safe("elasticache_cluster_" + cluster['CacheClusterId']), dest) - - # Global Tag: all ElastiCache nodes - self.push(self.inventory, 'elasticache_nodes', dest) - - host_info = self.get_host_info_dict_from_describe_dict(node) - - if dest in self.inventory["_meta"]["hostvars"]: - self.inventory["_meta"]["hostvars"][dest].update(host_info) - else: - self.inventory["_meta"]["hostvars"][dest] = host_info - - def add_elasticache_replication_group(self, replication_group, region): - ''' Adds an ElastiCache replication group to the inventory and index ''' - - # Only want available clusters unless all_elasticache_replication_groups is True - if not self.all_elasticache_replication_groups and replication_group['Status'] != 'available': - return - - # Skip clusters we cannot address (e.g. private VPC subnet or clustered redis) - if replication_group['NodeGroups'][0]['PrimaryEndpoint'] is None or \ - replication_group['NodeGroups'][0]['PrimaryEndpoint']['Address'] is None: - return - - # Select the best destination address (PrimaryEndpoint) - dest = replication_group['NodeGroups'][0]['PrimaryEndpoint']['Address'] - - # Add to index - self.index[dest] = [region, replication_group['ReplicationGroupId']] - - # Inventory: Group by ID (always a group of 1) - if self.group_by_instance_id: - self.inventory[replication_group['ReplicationGroupId']] = [dest] - if self.nested_groups: - self.push_group(self.inventory, 'instances', replication_group['ReplicationGroupId']) - - # Inventory: Group by region - if self.group_by_region: - self.push(self.inventory, region, dest) - if self.nested_groups: - self.push_group(self.inventory, 'regions', region) - - # Inventory: Group by availability zone (doesn't apply to replication groups) - - # Inventory: Group by node type (doesn't apply to replication groups) - - # Inventory: Group by VPC (information not available in the current - # AWS API version for replication groups - - # Inventory: Group by security group (doesn't apply to replication groups) - # Check this value in cluster level - - # Inventory: Group by engine (replication groups are always Redis) - if self.group_by_elasticache_engine: - self.push(self.inventory, 'elasticache_redis', dest) - if self.nested_groups: - self.push_group(self.inventory, 'elasticache_engines', 'redis') - - # Global Tag: all ElastiCache clusters - self.push(self.inventory, 'elasticache_replication_groups', replication_group['ReplicationGroupId']) - - host_info = self.get_host_info_dict_from_describe_dict(replication_group) - - self.inventory["_meta"]["hostvars"][dest] = host_info - - def get_route53_records(self): - ''' Get and store the map of resource records to domain names that - point to them. ''' - - if self.boto_profile: - r53_conn = route53.Route53Connection(profile_name=self.boto_profile) - else: - r53_conn = route53.Route53Connection() - all_zones = r53_conn.get_zones() - - route53_zones = [zone for zone in all_zones if zone.name[:-1] not in self.route53_excluded_zones] - - self.route53_records = {} - - for zone in route53_zones: - rrsets = r53_conn.get_all_rrsets(zone.id) - - for record_set in rrsets: - record_name = record_set.name - - if record_name.endswith('.'): - record_name = record_name[:-1] - - for resource in record_set.resource_records: - self.route53_records.setdefault(resource, set()) - self.route53_records[resource].add(record_name) - - def get_instance_route53_names(self, instance): - ''' Check if an instance is referenced in the records we have from - Route53. If it is, return the list of domain names pointing to said - instance. If nothing points to it, return an empty list. ''' - - instance_attributes = ['public_dns_name', 'private_dns_name', - 'ip_address', 'private_ip_address'] - - name_list = set() - - for attrib in instance_attributes: - try: - value = getattr(instance, attrib) - except AttributeError: - continue - - if value in self.route53_records: - name_list.update(self.route53_records[value]) - - return list(name_list) - - def get_host_info_dict_from_instance(self, instance): - instance_vars = {} - for key in vars(instance): - value = getattr(instance, key) - key = self.to_safe('ec2_' + key) - - # Handle complex types - # state/previous_state changed to properties in boto in https://github.com/boto/boto/commit/a23c379837f698212252720d2af8dec0325c9518 - if key == 'ec2__state': - instance_vars['ec2_state'] = instance.state or '' - instance_vars['ec2_state_code'] = instance.state_code - elif key == 'ec2__previous_state': - instance_vars['ec2_previous_state'] = instance.previous_state or '' - instance_vars['ec2_previous_state_code'] = instance.previous_state_code - elif isinstance(value, (int, bool)): - instance_vars[key] = value - elif isinstance(value, six.string_types): - instance_vars[key] = value.strip() - elif value is None: - instance_vars[key] = '' - elif key == 'ec2_region': - instance_vars[key] = value.name - elif key == 'ec2__placement': - instance_vars['ec2_placement'] = value.zone - elif key == 'ec2_tags': - for k, v in value.items(): - if self.expand_csv_tags and ',' in v: - v = list(map(lambda x: x.strip(), v.split(','))) - key = self.to_safe('ec2_tag_' + k) - instance_vars[key] = v - elif key == 'ec2_groups': - group_ids = [] - group_names = [] - for group in value: - group_ids.append(group.id) - group_names.append(group.name) - instance_vars["ec2_security_group_ids"] = ','.join([str(i) for i in group_ids]) - instance_vars["ec2_security_group_names"] = ','.join([str(i) for i in group_names]) - elif key == 'ec2_block_device_mapping': - instance_vars["ec2_block_devices"] = {} - for k, v in value.items(): - instance_vars["ec2_block_devices"][os.path.basename(k)] = v.volume_id - else: - pass - # TODO Product codes if someone finds them useful - # print key - # print type(value) - # print value - - instance_vars[self.to_safe('ec2_account_id')] = self.aws_account_id - - return instance_vars - - def get_host_info_dict_from_describe_dict(self, describe_dict): - ''' Parses the dictionary returned by the API call into a flat list - of parameters. This method should be used only when 'describe' is - used directly because Boto doesn't provide specific classes. ''' - - # I really don't agree with prefixing everything with 'ec2' - # because EC2, RDS and ElastiCache are different services. - # I'm just following the pattern used until now to not break any - # compatibility. - - host_info = {} - for key in describe_dict: - value = describe_dict[key] - key = self.to_safe('ec2_' + self.uncammelize(key)) - - # Handle complex types - - # Target: Memcached Cache Clusters - if key == 'ec2_configuration_endpoint' and value: - host_info['ec2_configuration_endpoint_address'] = value['Address'] - host_info['ec2_configuration_endpoint_port'] = value['Port'] - - # Target: Cache Nodes and Redis Cache Clusters (single node) - if key == 'ec2_endpoint' and value: - host_info['ec2_endpoint_address'] = value['Address'] - host_info['ec2_endpoint_port'] = value['Port'] - - # Target: Redis Replication Groups - if key == 'ec2_node_groups' and value: - host_info['ec2_endpoint_address'] = value[0]['PrimaryEndpoint']['Address'] - host_info['ec2_endpoint_port'] = value[0]['PrimaryEndpoint']['Port'] - replica_count = 0 - for node in value[0]['NodeGroupMembers']: - if node['CurrentRole'] == 'primary': - host_info['ec2_primary_cluster_address'] = node['ReadEndpoint']['Address'] - host_info['ec2_primary_cluster_port'] = node['ReadEndpoint']['Port'] - host_info['ec2_primary_cluster_id'] = node['CacheClusterId'] - elif node['CurrentRole'] == 'replica': - host_info['ec2_replica_cluster_address_' + str(replica_count)] = node['ReadEndpoint']['Address'] - host_info['ec2_replica_cluster_port_' + str(replica_count)] = node['ReadEndpoint']['Port'] - host_info['ec2_replica_cluster_id_' + str(replica_count)] = node['CacheClusterId'] - replica_count += 1 - - # Target: Redis Replication Groups - if key == 'ec2_member_clusters' and value: - host_info['ec2_member_clusters'] = ','.join([str(i) for i in value]) - - # Target: All Cache Clusters - elif key == 'ec2_cache_parameter_group': - host_info["ec2_cache_node_ids_to_reboot"] = ','.join([str(i) for i in value['CacheNodeIdsToReboot']]) - host_info['ec2_cache_parameter_group_name'] = value['CacheParameterGroupName'] - host_info['ec2_cache_parameter_apply_status'] = value['ParameterApplyStatus'] - - # Target: Almost everything - elif key == 'ec2_security_groups': - - # Skip if SecurityGroups is None - # (it is possible to have the key defined but no value in it). - if value is not None: - sg_ids = [] - for sg in value: - sg_ids.append(sg['SecurityGroupId']) - host_info["ec2_security_group_ids"] = ','.join([str(i) for i in sg_ids]) - - # Target: Everything - # Preserve booleans and integers - elif isinstance(value, (int, bool)): - host_info[key] = value - - # Target: Everything - # Sanitize string values - elif isinstance(value, six.string_types): - host_info[key] = value.strip() - - # Target: Everything - # Replace None by an empty string - elif value is None: - host_info[key] = '' - - else: - # Remove non-processed complex types - pass - - return host_info - - def get_host_info(self): - ''' Get variables about a specific host ''' - - if len(self.index) == 0: - # Need to load index from cache - self.load_index_from_cache() - - if self.args.host not in self.index: - # try updating the cache - self.do_api_calls_update_cache() - if self.args.host not in self.index: - # host might not exist anymore - return self.json_format_dict({}, True) - - (region, instance_id) = self.index[self.args.host] - - instance = self.get_instance(region, instance_id) - return self.json_format_dict(self.get_host_info_dict_from_instance(instance), True) - - def push(self, my_dict, key, element): - ''' Push an element onto an array that may not have been defined in - the dict ''' - group_info = my_dict.setdefault(key, []) - if isinstance(group_info, dict): - host_list = group_info.setdefault('hosts', []) - host_list.append(element) - else: - group_info.append(element) - - def push_group(self, my_dict, key, element): - ''' Push a group as a child of another group. ''' - parent_group = my_dict.setdefault(key, {}) - if not isinstance(parent_group, dict): - parent_group = my_dict[key] = {'hosts': parent_group} - child_groups = parent_group.setdefault('children', []) - if element not in child_groups: - child_groups.append(element) - - def get_inventory_from_cache(self): - ''' Reads the inventory from the cache file and returns it as a JSON - object ''' - - with open(self.cache_path_cache, 'r') as f: - json_inventory = f.read() - return json_inventory - - def load_index_from_cache(self): - ''' Reads the index from the cache file sets self.index ''' - - with open(self.cache_path_index, 'rb') as f: - self.index = json.load(f) - - def write_to_cache(self, data, filename): - ''' Writes data in JSON format to a file ''' - - json_data = self.json_format_dict(data, True) - with open(filename, 'w') as f: - f.write(json_data) - - def uncammelize(self, key): - temp = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', key) - return re.sub('([a-z0-9])([A-Z])', r'\1_\2', temp).lower() - - def to_safe(self, word): - ''' Converts 'bad' characters in a string to underscores so they can be used as Ansible groups ''' - regex = r"[^A-Za-z0-9\_" - if not self.replace_dash_in_groups: - regex += r"\-" - return re.sub(regex + "]", "_", word) - - def json_format_dict(self, data, pretty=False): - ''' Converts a dict to a JSON object and dumps it as a formatted - string ''' - - if pretty: - return json.dumps(data, sort_keys=True, indent=2) - else: - return json.dumps(data) - - -if __name__ == '__main__': - # Run the script - Ec2Inventory() diff --git a/awx/plugins/inventory/foreman.ini.example b/awx/plugins/inventory/foreman.ini.example deleted file mode 100644 index c42382a24aa3..000000000000 --- a/awx/plugins/inventory/foreman.ini.example +++ /dev/null @@ -1,199 +0,0 @@ -# Foreman inventory (https://github.com/theforeman/foreman_ansible_inventory) -# -# This script can be used as an Ansible dynamic inventory. -# The connection parameters are set up via *foreman.ini* -# This is how the script founds the configuration file in -# order of discovery. -# -# * `/etc/ansible/foreman.ini` -# * Current directory of your inventory script. -# * `FOREMAN_INI_PATH` environment variable. -# -# ## Variables and Parameters -# -# The data returned from Foreman for each host is stored in a foreman -# hash so they're available as *host_vars* along with the parameters -# of the host and it's hostgroups: -# -# "foo.example.com": { -# "foreman": { -# "architecture_id": 1, -# "architecture_name": "x86_64", -# "build": false, -# "build_status": 0, -# "build_status_label": "Installed", -# "capabilities": [ -# "build", -# "image" -# ], -# "compute_profile_id": 4, -# "hostgroup_name": "webtier/myapp", -# "id": 70, -# "image_name": "debian8.1", -# ... -# "uuid": "50197c10-5ebb-b5cf-b384-a1e203e19e77" -# }, -# "foreman_params": { -# "testparam1": "foobar", -# "testparam2": "small", -# ... -# } -# -# and could therefore be used in Ansible like: -# -# - debug: msg="From Foreman host {{ foreman['uuid'] }}" -# -# Which yields -# -# TASK [test_foreman : debug] **************************************************** -# ok: [foo.example.com] => { -# "msg": "From Foreman host 50190bd1-052a-a34a-3c9c-df37a39550bf" -# } -# -# ## Automatic Ansible groups -# -# The inventory will provide a set of groups, by default prefixed by -# 'foreman_'. If you want to customize this prefix, change the -# group_prefix option in /etc/ansible/foreman.ini. The rest of this -# guide will assume the default prefix of 'foreman' -# -# The hostgroup, location, organization, content view, and lifecycle -# environment of each host are created as Ansible groups with a -# foreman_ prefix, all lowercase and problematic parameters -# removed. So e.g. the foreman hostgroup -# -# myapp / webtier / datacenter1 -# -# would turn into the Ansible group: -# -# foreman_hostgroup_myapp_webtier_datacenter1 -# -# If the parameter want_hostcollections is set to true, the -# collections each host is in are created as Ansible groups with a -# foreman_hostcollection prefix, all lowercase and problematic -# parameters removed. So e.g. the Foreman host collection -# -# Patch Window Thursday -# -# would turn into the Ansible group: -# -# foreman_hostcollection_patchwindowthursday -# -# If the parameter host_filters is set, it will be used as the -# "search" parameter for the /api/v2/hosts call. This can be used to -# restrict the list of returned host, as shown below. -# -# Furthermore Ansible groups can be created on the fly using the -# *group_patterns* variable in *foreman.ini* so that you can build up -# hierarchies using parameters on the hostgroup and host variables. -# -# Lets assume you have a host that is built using this nested hostgroup: -# -# myapp / webtier / datacenter1 -# -# and each of the hostgroups defines a parameters respectively: -# -# myapp: app_param = myapp -# webtier: tier_param = webtier -# datacenter1: dc_param = datacenter1 -# -# The host is also in a subnet called "mysubnet" and provisioned via an image -# then *group_patterns* like: -# -# [ansible] -# group_patterns = ["{app_param}-{tier_param}-{dc_param}", -# "{app_param}-{tier_param}", -# "{app_param}", -# "{subnet_name}-{provision_method}"] -# -# would put the host into the additional Ansible groups: -# -# - myapp-webtier-datacenter1 -# - myapp-webtier -# - myapp -# - mysubnet-image -# -# by recursively resolving the hostgroups, getting the parameter keys -# and values and doing a Python *string.format()* like replacement on -# it. -# -[foreman] -url = http://localhost:3000/ -user = foreman -password = secret -ssl_verify = True - -# Foreman 1.24 introduces a new reports API to improve performance of the inventory script. -# Note: This requires foreman_ansible plugin installed. -# Set to False if you want to use the old API. Defaults to True. - -use_reports_api = True - -# Retrieve only hosts from the organization "Web Engineering". -# host_filters = organization="Web Engineering" - -# Retrieve only hosts from the organization "Web Engineering" that are -# also in the host collection "Apache Servers". -# host_filters = organization="Web Engineering" and host_collection="Apache Servers" - - -# Foreman Inventory report related configuration options. -# Configs that default to True : -# want_organization , want_location, want_ipv4, want_host_group, want_subnet, want_smart_proxies, want_facts -# Configs that default to False : -# want_ipv6, want_subnet_v6, want_content_facet_attributes, want_host_params - -[report] -want_organization = True -want_location = True -want_ipv4 = True -want_ipv6 = False -want_host_group = True -want_subnet = True -want_subnet_v6 = False -want_smart_proxies = True -want_content_facet_attributes = False -want_host_params = False - -# use this config to determine if facts are to be fetched in the report and stored on the hosts. -# want_facts = False - -# Upon receiving a request to return inventory report, Foreman schedules a report generation job. -# The script then polls the report_data endpoint repeatedly to check if the job is complete and retrieves data -# poll_interval allows to define the polling interval between 2 calls to the report_data endpoint while polling. -# Defaults to 10 seconds - -poll_interval = 10 - -[ansible] -group_patterns = ["{app}-{tier}-{color}", - "{app}-{color}", - "{app}", - "{tier}"] - -group_prefix = foreman_ - -# Whether to create Ansible groups for host collections. Only tested -# with Katello (Red Hat Satellite). Disabled by default to not break -# the script for stand-alone Foreman. -want_hostcollections = False - -# Whether to interpret global parameters value as JSON (if possible, else -# take as is). Only tested with Katello (Red Hat Satellite). -# This allows to define lists and dictionaries (and more complicated structures) -# variables by entering them as JSON string in Foreman parameters. -# Disabled by default as the change would else not be backward compatible. -rich_params = False - -# Whether to populate the ansible_ssh_host variable to explicitly specify the -# connection target. Only tested with Katello (Red Hat Satellite). -# If the foreman 'ip' fact exists then the ansible_ssh_host varibale is populated -# to permit connections where DNS resolution fails. -want_ansible_ssh_host = False - -[cache] -path = . -max_age = 60 - -# Whether to scan foreman to add recently created hosts in inventory cache -scan_new_hosts = True diff --git a/awx/plugins/inventory/foreman.py b/awx/plugins/inventory/foreman.py deleted file mode 100755 index c3f97710d27c..000000000000 --- a/awx/plugins/inventory/foreman.py +++ /dev/null @@ -1,667 +0,0 @@ -#!/usr/bin/env python -# vim: set fileencoding=utf-8 : -# -# Copyright (C) 2016 Guido Günther , -# Daniel Lobato Garcia -# -# This script is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with it. If not, see . -# -# This is somewhat based on cobbler inventory - -# Stdlib imports -# __future__ imports must occur at the beginning of file -from __future__ import print_function -try: - # Python 2 version - import ConfigParser -except ImportError: - # Python 3 version - import configparser as ConfigParser -import json -import argparse -import copy -import os -import re -import sys -from time import time, sleep -from collections import defaultdict -from distutils.version import LooseVersion, StrictVersion - -# 3rd party imports -import requests -if LooseVersion(requests.__version__) < LooseVersion('1.1.0'): - print('This script requires python-requests 1.1 as a minimum version') - sys.exit(1) - -from requests.auth import HTTPBasicAuth - -from ansible.module_utils._text import to_text - - -def json_format_dict(data, pretty=False): - """Converts a dict to a JSON object and dumps it as a formatted string""" - - if pretty: - return json.dumps(data, sort_keys=True, indent=2) - else: - return json.dumps(data) - - -class ForemanInventory(object): - - def __init__(self): - self.inventory = defaultdict(list) # A list of groups and the hosts in that group - self.cache = dict() # Details about hosts in the inventory - self.params = dict() # Params of each host - self.facts = dict() # Facts of each host - self.hostgroups = dict() # host groups - self.hostcollections = dict() # host collections - self.session = None # Requests session - self.config_paths = [ - "/etc/ansible/foreman.ini", - os.path.dirname(os.path.realpath(__file__)) + '/foreman.ini', - ] - env_value = os.environ.get('FOREMAN_INI_PATH') - if env_value is not None: - self.config_paths.append(os.path.expanduser(os.path.expandvars(env_value))) - - def read_settings(self): - """Reads the settings from the foreman.ini file""" - - config = ConfigParser.SafeConfigParser() - config.read(self.config_paths) - - # Foreman API related - try: - self.foreman_url = config.get('foreman', 'url') - self.foreman_user = config.get('foreman', 'user') - self.foreman_pw = config.get('foreman', 'password', raw=True) - self.foreman_ssl_verify = config.getboolean('foreman', 'ssl_verify') - except (ConfigParser.NoOptionError, ConfigParser.NoSectionError) as e: - print("Error parsing configuration: %s" % e, file=sys.stderr) - return False - - # Inventory Report Related - try: - self.foreman_use_reports_api = config.getboolean('foreman', 'use_reports_api') - except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): - self.foreman_use_reports_api = True - - try: - self.want_organization = config.getboolean('report', 'want_organization') - except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): - self.want_organization = True - - try: - self.want_location = config.getboolean('report', 'want_location') - except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): - self.want_location = True - - try: - self.want_IPv4 = config.getboolean('report', 'want_ipv4') - except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): - self.want_IPv4 = True - - try: - self.want_IPv6 = config.getboolean('report', 'want_ipv6') - except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): - self.want_IPv6 = False - - try: - self.want_host_group = config.getboolean('report', 'want_host_group') - except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): - self.want_host_group = True - - try: - self.want_host_params = config.getboolean('report', 'want_host_params') - except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): - self.want_host_params = False - - try: - self.want_subnet = config.getboolean('report', 'want_subnet') - except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): - self.want_subnet = True - - try: - self.want_subnet_v6 = config.getboolean('report', 'want_subnet_v6') - except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): - self.want_subnet_v6 = False - - try: - self.want_smart_proxies = config.getboolean('report', 'want_smart_proxies') - except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): - self.want_smart_proxies = True - - try: - self.want_content_facet_attributes = config.getboolean('report', 'want_content_facet_attributes') - except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): - self.want_content_facet_attributes = False - - try: - self.report_want_facts = config.getboolean('report', 'want_facts') - except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): - self.report_want_facts = True - - try: - self.poll_interval = config.getint('report', 'poll_interval') - except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): - self.poll_interval = 10 - - # Ansible related - try: - group_patterns = config.get('ansible', 'group_patterns') - except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): - group_patterns = "[]" - - self.group_patterns = json.loads(group_patterns) - - try: - self.group_prefix = config.get('ansible', 'group_prefix') - except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): - self.group_prefix = "foreman_" - - try: - self.want_facts = config.getboolean('ansible', 'want_facts') - except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): - self.want_facts = True - - self.want_facts = self.want_facts and self.report_want_facts - - try: - self.want_hostcollections = config.getboolean('ansible', 'want_hostcollections') - except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): - self.want_hostcollections = False - - try: - self.want_ansible_ssh_host = config.getboolean('ansible', 'want_ansible_ssh_host') - except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): - self.want_ansible_ssh_host = False - - # Do we want parameters to be interpreted if possible as JSON? (no by default) - try: - self.rich_params = config.getboolean('ansible', 'rich_params') - except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): - self.rich_params = False - - try: - self.host_filters = config.get('foreman', 'host_filters') - except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): - self.host_filters = None - - # Cache related - try: - cache_path = os.path.expanduser(config.get('cache', 'path')) - except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): - cache_path = '.' - (script, ext) = os.path.splitext(os.path.basename(__file__)) - self.cache_path_cache = cache_path + "/%s.cache" % script - self.cache_path_inventory = cache_path + "/%s.index" % script - self.cache_path_params = cache_path + "/%s.params" % script - self.cache_path_facts = cache_path + "/%s.facts" % script - self.cache_path_hostcollections = cache_path + "/%s.hostcollections" % script - try: - self.cache_max_age = config.getint('cache', 'max_age') - except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): - self.cache_max_age = 60 - try: - self.scan_new_hosts = config.getboolean('cache', 'scan_new_hosts') - except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): - self.scan_new_hosts = False - - return True - - def parse_cli_args(self): - """Command line argument processing""" - - parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on foreman') - parser.add_argument('--list', action='store_true', default=True, help='List instances (default: True)') - parser.add_argument('--host', action='store', help='Get all the variables about a specific instance') - parser.add_argument('--refresh-cache', action='store_true', default=False, - help='Force refresh of cache by making API requests to foreman (default: False - use cache files)') - self.args = parser.parse_args() - - def _get_session(self): - if not self.session: - self.session = requests.session() - self.session.auth = HTTPBasicAuth(self.foreman_user, self.foreman_pw) - self.session.verify = self.foreman_ssl_verify - return self.session - - def _get_json(self, url, ignore_errors=None, params=None): - if params is None: - params = {} - params['per_page'] = 250 - - page = 1 - results = [] - s = self._get_session() - while True: - params['page'] = page - ret = s.get(url, params=params) - if ignore_errors and ret.status_code in ignore_errors: - break - ret.raise_for_status() - json = ret.json() - # /hosts/:id has not results key - if 'results' not in json: - return json - # Facts are returned as dict in results not list - if isinstance(json['results'], dict): - return json['results'] - # List of all hosts is returned paginaged - results = results + json['results'] - if len(results) >= json['subtotal']: - break - page += 1 - if len(json['results']) == 0: - print("Did not make any progress during loop. " - "expected %d got %d" % (json['total'], len(results)), - file=sys.stderr) - break - return results - - def _use_inventory_report(self): - if not self.foreman_use_reports_api: - return False - status_url = "%s/api/v2/status" % self.foreman_url - result = self._get_json(status_url) - foreman_version = (LooseVersion(result.get('version')) >= LooseVersion('1.24.0')) - return foreman_version - - def _fetch_params(self): - options, params = ("no", "yes"), dict() - params["Organization"] = options[self.want_organization] - params["Location"] = options[self.want_location] - params["IPv4"] = options[self.want_IPv4] - params["IPv6"] = options[self.want_IPv6] - params["Facts"] = options[self.want_facts] - params["Host Group"] = options[self.want_host_group] - params["Host Collections"] = options[self.want_hostcollections] - params["Subnet"] = options[self.want_subnet] - params["Subnet v6"] = options[self.want_subnet_v6] - params["Smart Proxies"] = options[self.want_smart_proxies] - params["Content Attributes"] = options[self.want_content_facet_attributes] - params["Host Parameters"] = options[self.want_host_params] - if self.host_filters: - params["Hosts"] = self.host_filters - return params - - def _post_request(self): - url = "%s/ansible/api/v2/ansible_inventories/schedule" % self.foreman_url - session = self._get_session() - params = {'input_values': self._fetch_params()} - ret = session.post(url, json=params) - if not ret: - raise Exception("Error scheduling inventory report on foreman. Please check foreman logs!") - url = "{0}/{1}".format(self.foreman_url, ret.json().get('data_url')) - response = session.get(url) - while response: - if response.status_code != 204: - break - else: - sleep(self.poll_interval) - response = session.get(url) - if not response: - raise Exception("Error receiving inventory report from foreman. Please check foreman logs!") - else: - return response.json() - - def _get_hosts(self): - url = "%s/api/v2/hosts" % self.foreman_url - - params = {} - if self.host_filters: - params['search'] = self.host_filters - - return self._get_json(url, params=params) - - def _get_host_data_by_id(self, hid): - url = "%s/api/v2/hosts/%s" % (self.foreman_url, hid) - return self._get_json(url) - - def _get_facts_by_id(self, hid): - url = "%s/api/v2/hosts/%s/facts" % (self.foreman_url, hid) - return self._get_json(url) - - def _resolve_params(self, host_params): - """Convert host params to dict""" - params = {} - - for param in host_params: - name = param['name'] - if self.rich_params: - try: - params[name] = json.loads(param['value']) - except ValueError: - params[name] = param['value'] - else: - params[name] = param['value'] - - return params - - def _get_facts(self, host): - """Fetch all host facts of the host""" - if not self.want_facts: - return {} - - ret = self._get_facts_by_id(host['id']) - if len(ret.values()) == 0: - facts = {} - elif len(ret.values()) == 1: - facts = list(ret.values())[0] - else: - raise ValueError("More than one set of facts returned for '%s'" % host) - return facts - - def write_to_cache(self, data, filename): - """Write data in JSON format to a file""" - json_data = json_format_dict(data, True) - cache = open(filename, 'w') - cache.write(json_data) - cache.close() - - def _write_cache(self): - self.write_to_cache(self.cache, self.cache_path_cache) - self.write_to_cache(self.inventory, self.cache_path_inventory) - self.write_to_cache(self.params, self.cache_path_params) - self.write_to_cache(self.facts, self.cache_path_facts) - self.write_to_cache(self.hostcollections, self.cache_path_hostcollections) - - def to_safe(self, word): - '''Converts 'bad' characters in a string to underscores - so they can be used as Ansible groups - - >>> ForemanInventory.to_safe("foo-bar baz") - 'foo_barbaz' - ''' - regex = r"[^A-Za-z0-9\_]" - return re.sub(regex, "_", word.replace(" ", "")) - - def update_cache(self, scan_only_new_hosts=False): - """Make calls to foreman and save the output in a cache""" - use_inventory_report = self._use_inventory_report() - if use_inventory_report: - self._update_cache_inventory(scan_only_new_hosts) - else: - self._update_cache_host_api(scan_only_new_hosts) - - def _update_cache_inventory(self, scan_only_new_hosts): - self.groups = dict() - self.hosts = dict() - try: - inventory_report_response = self._post_request() - except Exception: - self._update_cache_host_api(scan_only_new_hosts) - return - host_data = json.loads(inventory_report_response) - for host in host_data: - if not(host) or (host["name"] in self.cache.keys() and scan_only_new_hosts): - continue - dns_name = host['name'] - - host_params = host.pop('host_parameters', {}) - fact_list = host.pop('facts', {}) - content_facet_attributes = host.get('content_attributes', {}) or {} - - # Create ansible groups for hostgroup - group = 'host_group' - val = host.get(group) - if val: - safe_key = self.to_safe('%s%s_%s' % ( - to_text(self.group_prefix), - group, - to_text(val).lower() - )) - self.inventory[safe_key].append(dns_name) - - # Create ansible groups for environment, location and organization - for group in ['environment', 'location', 'organization']: - val = host.get('%s' % group) - if val: - safe_key = self.to_safe('%s%s_%s' % ( - to_text(self.group_prefix), - group, - to_text(val).lower() - )) - self.inventory[safe_key].append(dns_name) - - for group in ['lifecycle_environment', 'content_view']: - val = content_facet_attributes.get('%s_name' % group) - if val: - safe_key = self.to_safe('%s%s_%s' % ( - to_text(self.group_prefix), - group, - to_text(val).lower() - )) - self.inventory[safe_key].append(dns_name) - - params = host_params - - # Ansible groups by parameters in host groups and Foreman host - # attributes. - groupby = dict() - for k, v in params.items(): - groupby[k] = self.to_safe(to_text(v)) - - # The name of the ansible groups is given by group_patterns: - for pattern in self.group_patterns: - try: - key = pattern.format(**groupby) - self.inventory[key].append(dns_name) - except KeyError: - pass # Host not part of this group - - if self.want_hostcollections: - hostcollections = host.get('host_collections') - - if hostcollections: - # Create Ansible groups for host collections - for hostcollection in hostcollections: - safe_key = self.to_safe('%shostcollection_%s' % (self.group_prefix, hostcollection.lower())) - self.inventory[safe_key].append(dns_name) - - self.hostcollections[dns_name] = hostcollections - - self.cache[dns_name] = host - self.params[dns_name] = params - self.facts[dns_name] = fact_list - self.inventory['all'].append(dns_name) - self._write_cache() - - def _update_cache_host_api(self, scan_only_new_hosts): - """Make calls to foreman and save the output in a cache""" - - self.groups = dict() - self.hosts = dict() - - for host in self._get_hosts(): - if host['name'] in self.cache.keys() and scan_only_new_hosts: - continue - dns_name = host['name'] - - host_data = self._get_host_data_by_id(host['id']) - host_params = host_data.get('all_parameters', {}) - - # Create ansible groups for hostgroup - group = 'hostgroup' - val = host.get('%s_title' % group) or host.get('%s_name' % group) - if val: - safe_key = self.to_safe('%s%s_%s' % ( - to_text(self.group_prefix), - group, - to_text(val).lower() - )) - self.inventory[safe_key].append(dns_name) - - # Create ansible groups for environment, location and organization - for group in ['environment', 'location', 'organization']: - val = host.get('%s_name' % group) - if val: - safe_key = self.to_safe('%s%s_%s' % ( - to_text(self.group_prefix), - group, - to_text(val).lower() - )) - self.inventory[safe_key].append(dns_name) - - for group in ['lifecycle_environment', 'content_view']: - val = host.get('content_facet_attributes', {}).get('%s_name' % group) - if val: - safe_key = self.to_safe('%s%s_%s' % ( - to_text(self.group_prefix), - group, - to_text(val).lower() - )) - self.inventory[safe_key].append(dns_name) - - params = self._resolve_params(host_params) - - # Ansible groups by parameters in host groups and Foreman host - # attributes. - groupby = dict() - for k, v in params.items(): - groupby[k] = self.to_safe(to_text(v)) - - # The name of the ansible groups is given by group_patterns: - for pattern in self.group_patterns: - try: - key = pattern.format(**groupby) - self.inventory[key].append(dns_name) - except KeyError: - pass # Host not part of this group - - if self.want_hostcollections: - hostcollections = host_data.get('host_collections') - - if hostcollections: - # Create Ansible groups for host collections - for hostcollection in hostcollections: - safe_key = self.to_safe('%shostcollection_%s' % (self.group_prefix, hostcollection['name'].lower())) - self.inventory[safe_key].append(dns_name) - - self.hostcollections[dns_name] = hostcollections - - self.cache[dns_name] = host - self.params[dns_name] = params - self.facts[dns_name] = self._get_facts(host) - self.inventory['all'].append(dns_name) - self._write_cache() - - def is_cache_valid(self): - """Determines if the cache is still valid""" - if os.path.isfile(self.cache_path_cache): - mod_time = os.path.getmtime(self.cache_path_cache) - current_time = time() - if (mod_time + self.cache_max_age) > current_time: - if (os.path.isfile(self.cache_path_inventory) and - os.path.isfile(self.cache_path_params) and - os.path.isfile(self.cache_path_facts)): - return True - return False - - def load_inventory_from_cache(self): - """Read the index from the cache file sets self.index""" - - with open(self.cache_path_inventory, 'r') as fp: - self.inventory = json.load(fp) - - def load_params_from_cache(self): - """Read the index from the cache file sets self.index""" - - with open(self.cache_path_params, 'r') as fp: - self.params = json.load(fp) - - def load_facts_from_cache(self): - """Read the index from the cache file sets self.facts""" - - if not self.want_facts: - return - with open(self.cache_path_facts, 'r') as fp: - self.facts = json.load(fp) - - def load_hostcollections_from_cache(self): - """Read the index from the cache file sets self.hostcollections""" - - if not self.want_hostcollections: - return - with open(self.cache_path_hostcollections, 'r') as fp: - self.hostcollections = json.load(fp) - - def load_cache_from_cache(self): - """Read the cache from the cache file sets self.cache""" - - with open(self.cache_path_cache, 'r') as fp: - self.cache = json.load(fp) - - def get_inventory(self): - if self.args.refresh_cache or not self.is_cache_valid(): - self.update_cache() - else: - self.load_inventory_from_cache() - self.load_params_from_cache() - self.load_facts_from_cache() - self.load_hostcollections_from_cache() - self.load_cache_from_cache() - if self.scan_new_hosts: - self.update_cache(True) - - def get_host_info(self): - """Get variables about a specific host""" - - if not self.cache or len(self.cache) == 0: - # Need to load index from cache - self.load_cache_from_cache() - - if self.args.host not in self.cache: - # try updating the cache - self.update_cache() - - if self.args.host not in self.cache: - # host might not exist anymore - return json_format_dict({}, True) - - return json_format_dict(self.cache[self.args.host], True) - - def _print_data(self): - data_to_print = "" - if self.args.host: - data_to_print += self.get_host_info() - else: - self.inventory['_meta'] = {'hostvars': {}} - for hostname in self.cache: - self.inventory['_meta']['hostvars'][hostname] = { - 'foreman': self.cache[hostname], - 'foreman_params': self.params[hostname], - } - if self.want_ansible_ssh_host and 'ip' in self.cache[hostname]: - self.inventory['_meta']['hostvars'][hostname]['ansible_ssh_host'] = self.cache[hostname]['ip'] - if self.want_facts: - self.inventory['_meta']['hostvars'][hostname]['foreman_facts'] = self.facts[hostname] - - data_to_print += json_format_dict(self.inventory, True) - - print(data_to_print) - - def run(self): - # Read settings and parse CLI arguments - if not self.read_settings(): - return False - self.parse_cli_args() - self.get_inventory() - self._print_data() - return True - - -if __name__ == '__main__': - sys.exit(not ForemanInventory().run()) diff --git a/awx/plugins/inventory/gce.py b/awx/plugins/inventory/gce.py deleted file mode 100755 index 9a0cef0b5980..000000000000 --- a/awx/plugins/inventory/gce.py +++ /dev/null @@ -1,508 +0,0 @@ -#!/usr/bin/env python -# Copyright 2013 Google Inc. -# -# This file is part of Ansible -# -# Ansible is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Ansible. If not, see . - -''' -GCE external inventory script -================================= - -Generates inventory that Ansible can understand by making API requests -Google Compute Engine via the libcloud library. Full install/configuration -instructions for the gce* modules can be found in the comments of -ansible/test/gce_tests.py. - -When run against a specific host, this script returns the following variables -based on the data obtained from the libcloud Node object: - - gce_uuid - - gce_id - - gce_image - - gce_machine_type - - gce_private_ip - - gce_public_ip - - gce_name - - gce_description - - gce_status - - gce_zone - - gce_tags - - gce_metadata - - gce_network - - gce_subnetwork - -When run in --list mode, instances are grouped by the following categories: - - zone: - zone group name examples are us-central1-b, europe-west1-a, etc. - - instance tags: - An entry is created for each tag. For example, if you have two instances - with a common tag called 'foo', they will both be grouped together under - the 'tag_foo' name. - - network name: - the name of the network is appended to 'network_' (e.g. the 'default' - network will result in a group named 'network_default') - - machine type - types follow a pattern like n1-standard-4, g1-small, etc. - - running status: - group name prefixed with 'status_' (e.g. status_running, status_stopped,..) - - image: - when using an ephemeral/scratch disk, this will be set to the image name - used when creating the instance (e.g. debian-7-wheezy-v20130816). when - your instance was created with a root persistent disk it will be set to - 'persistent_disk' since there is no current way to determine the image. - -Examples: - Execute uname on all instances in the us-central1-a zone - $ ansible -i gce.py us-central1-a -m shell -a "/bin/uname -a" - - Use the GCE inventory script to print out instance specific information - $ contrib/inventory/gce.py --host my_instance - -Author: Eric Johnson -Contributors: Matt Hite , Tom Melendez -Version: 0.0.3 -''' - -try: - import pkg_resources -except ImportError: - # Use pkg_resources to find the correct versions of libraries and set - # sys.path appropriately when there are multiversion installs. We don't - # fail here as there is code that better expresses the errors where the - # library is used. - pass - -USER_AGENT_PRODUCT = "Ansible-gce_inventory_plugin" -USER_AGENT_VERSION = "v2" - -import sys -import os -import argparse - -from time import time - -if sys.version_info >= (3, 0): - import configparser -else: - import ConfigParser as configparser - -import logging -logging.getLogger('libcloud.common.google').addHandler(logging.NullHandler()) - -try: - import json -except ImportError: - import simplejson as json - -try: - from libcloud.compute.types import Provider - from libcloud.compute.providers import get_driver - _ = Provider.GCE -except: - sys.exit("GCE inventory script requires libcloud >= 0.13") - - -class CloudInventoryCache(object): - def __init__(self, cache_name='ansible-cloud-cache', cache_path='/tmp', - cache_max_age=300): - cache_dir = os.path.expanduser(cache_path) - if not os.path.exists(cache_dir): - os.makedirs(cache_dir) - self.cache_path_cache = os.path.join(cache_dir, cache_name) - - self.cache_max_age = cache_max_age - - def is_valid(self, max_age=None): - ''' Determines if the cache files have expired, or if it is still valid ''' - - if max_age is None: - max_age = self.cache_max_age - - if os.path.isfile(self.cache_path_cache): - mod_time = os.path.getmtime(self.cache_path_cache) - current_time = time() - if (mod_time + max_age) > current_time: - return True - - return False - - def get_all_data_from_cache(self, filename=''): - ''' Reads the JSON inventory from the cache file. Returns Python dictionary. ''' - - data = '' - if not filename: - filename = self.cache_path_cache - with open(filename, 'r') as cache: - data = cache.read() - return json.loads(data) - - def write_to_cache(self, data, filename=''): - ''' Writes data to file as JSON. Returns True. ''' - if not filename: - filename = self.cache_path_cache - json_data = json.dumps(data) - with open(filename, 'w') as cache: - cache.write(json_data) - return True - - -class GceInventory(object): - def __init__(self): - # Cache object - self.cache = None - # dictionary containing inventory read from disk - self.inventory = {} - - # Read settings and parse CLI arguments - self.parse_cli_args() - self.config = self.get_config() - self.driver = self.get_gce_driver() - self.ip_type = self.get_inventory_options() - if self.ip_type: - self.ip_type = self.ip_type.lower() - - # Cache management - start_inventory_time = time() - cache_used = False - if self.args.refresh_cache or not self.cache.is_valid(): - self.do_api_calls_update_cache() - else: - self.load_inventory_from_cache() - cache_used = True - self.inventory['_meta']['stats'] = {'use_cache': True} - self.inventory['_meta']['stats'] = { - 'inventory_load_time': time() - start_inventory_time, - 'cache_used': cache_used - } - - # Just display data for specific host - if self.args.host: - print(self.json_format_dict( - self.inventory['_meta']['hostvars'][self.args.host], - pretty=self.args.pretty)) - else: - # Otherwise, assume user wants all instances grouped - zones = self.parse_env_zones() - print(self.json_format_dict(self.inventory, - pretty=self.args.pretty)) - sys.exit(0) - - def get_config(self): - """ - Reads the settings from the gce.ini file. - - Populates a SafeConfigParser object with defaults and - attempts to read an .ini-style configuration from the filename - specified in GCE_INI_PATH. If the environment variable is - not present, the filename defaults to gce.ini in the current - working directory. - """ - gce_ini_default_path = os.path.join( - os.path.dirname(os.path.realpath(__file__)), "gce.ini") - gce_ini_path = os.environ.get('GCE_INI_PATH', gce_ini_default_path) - - # Create a ConfigParser. - # This provides empty defaults to each key, so that environment - # variable configuration (as opposed to INI configuration) is able - # to work. - config = configparser.SafeConfigParser(defaults={ - 'gce_service_account_email_address': '', - 'gce_service_account_pem_file_path': '', - 'gce_project_id': '', - 'gce_zone': '', - 'libcloud_secrets': '', - 'inventory_ip_type': '', - 'cache_path': '~/.ansible/tmp', - 'cache_max_age': '300' - }) - if 'gce' not in config.sections(): - config.add_section('gce') - if 'inventory' not in config.sections(): - config.add_section('inventory') - if 'cache' not in config.sections(): - config.add_section('cache') - - config.read(gce_ini_path) - - ######### - # Section added for processing ini settings - ######### - - # Set the instance_states filter based on config file options - self.instance_states = [] - if config.has_option('gce', 'instance_states'): - states = config.get('gce', 'instance_states') - # Ignore if instance_states is an empty string. - if states: - self.instance_states = states.split(',') - - # Caching - cache_path = config.get('cache', 'cache_path') - cache_max_age = config.getint('cache', 'cache_max_age') - # TOOD(supertom): support project-specific caches - cache_name = 'ansible-gce.cache' - self.cache = CloudInventoryCache(cache_path=cache_path, - cache_max_age=cache_max_age, - cache_name=cache_name) - return config - - def get_inventory_options(self): - """Determine inventory options. Environment variables always - take precedence over configuration files.""" - ip_type = self.config.get('inventory', 'inventory_ip_type') - # If the appropriate environment variables are set, they override - # other configuration - ip_type = os.environ.get('INVENTORY_IP_TYPE', ip_type) - return ip_type - - def get_gce_driver(self): - """Determine the GCE authorization settings and return a - libcloud driver. - """ - # Attempt to get GCE params from a configuration file, if one - # exists. - secrets_path = self.config.get('gce', 'libcloud_secrets') - secrets_found = False - - try: - import secrets - args = list(secrets.GCE_PARAMS) - kwargs = secrets.GCE_KEYWORD_PARAMS - secrets_found = True - except: - pass - - if not secrets_found and secrets_path: - if not secrets_path.endswith('secrets.py'): - err = "Must specify libcloud secrets file as " - err += "/absolute/path/to/secrets.py" - sys.exit(err) - sys.path.append(os.path.dirname(secrets_path)) - try: - import secrets - args = list(getattr(secrets, 'GCE_PARAMS', [])) - kwargs = getattr(secrets, 'GCE_KEYWORD_PARAMS', {}) - secrets_found = True - except: - pass - - if not secrets_found: - args = [ - self.config.get('gce', 'gce_service_account_email_address'), - self.config.get('gce', 'gce_service_account_pem_file_path') - ] - kwargs = {'project': self.config.get('gce', 'gce_project_id'), - 'datacenter': self.config.get('gce', 'gce_zone')} - - # If the appropriate environment variables are set, they override - # other configuration; process those into our args and kwargs. - args[0] = os.environ.get('GCE_EMAIL', args[0]) - args[1] = os.environ.get('GCE_PEM_FILE_PATH', args[1]) - args[1] = os.environ.get('GCE_CREDENTIALS_FILE_PATH', args[1]) - - kwargs['project'] = os.environ.get('GCE_PROJECT', kwargs['project']) - kwargs['datacenter'] = os.environ.get('GCE_ZONE', kwargs['datacenter']) - - # Retrieve and return the GCE driver. - gce = get_driver(Provider.GCE)(*args, **kwargs) - gce.connection.user_agent_append( - '%s/%s' % (USER_AGENT_PRODUCT, USER_AGENT_VERSION), - ) - return gce - - def parse_env_zones(self): - '''returns a list of comma separated zones parsed from the GCE_ZONE environment variable. - If provided, this will be used to filter the results of the grouped_instances call''' - import csv - reader = csv.reader([os.environ.get('GCE_ZONE', "")], skipinitialspace=True) - zones = [r for r in reader] - return [z for z in zones[0]] - - def parse_cli_args(self): - ''' Command line argument processing ''' - - parser = argparse.ArgumentParser( - description='Produce an Ansible Inventory file based on GCE') - parser.add_argument('--list', action='store_true', default=True, - help='List instances (default: True)') - parser.add_argument('--host', action='store', - help='Get all information about an instance') - parser.add_argument('--pretty', action='store_true', default=False, - help='Pretty format (default: False)') - parser.add_argument( - '--refresh-cache', action='store_true', default=False, - help='Force refresh of cache by making API requests (default: False - use cache files)') - self.args = parser.parse_args() - - def node_to_dict(self, inst): - md = {} - - if inst is None: - return {} - - if 'items' in inst.extra['metadata']: - for entry in inst.extra['metadata']['items']: - md[entry['key']] = entry['value'] - - net = inst.extra['networkInterfaces'][0]['network'].split('/')[-1] - subnet = None - if 'subnetwork' in inst.extra['networkInterfaces'][0]: - subnet = inst.extra['networkInterfaces'][0]['subnetwork'].split('/')[-1] - # default to exernal IP unless user has specified they prefer internal - if self.ip_type == 'internal': - ssh_host = inst.private_ips[0] - else: - ssh_host = inst.public_ips[0] if len(inst.public_ips) >= 1 else inst.private_ips[0] - - return { - 'gce_uuid': inst.uuid, - 'gce_id': inst.id, - 'gce_image': inst.image, - 'gce_machine_type': inst.size, - 'gce_private_ip': inst.private_ips[0], - 'gce_public_ip': inst.public_ips[0] if len(inst.public_ips) >= 1 else None, - 'gce_name': inst.name, - 'gce_description': inst.extra['description'], - 'gce_status': inst.extra['status'], - 'gce_zone': inst.extra['zone'].name, - 'gce_tags': inst.extra['tags'], - 'gce_metadata': md, - 'gce_network': net, - 'gce_subnetwork': subnet, - # Hosts don't have a public name, so we add an IP - 'ansible_ssh_host': ssh_host - } - - def load_inventory_from_cache(self): - ''' Loads inventory from JSON on disk. ''' - - try: - self.inventory = self.cache.get_all_data_from_cache() - hosts = self.inventory['_meta']['hostvars'] - except Exception as e: - print( - "Invalid inventory file %s. Please rebuild with -refresh-cache option." - % (self.cache.cache_path_cache)) - raise - - def do_api_calls_update_cache(self): - ''' Do API calls and save data in cache. ''' - zones = self.parse_env_zones() - data = self.group_instances(zones) - self.cache.write_to_cache(data) - self.inventory = data - - def list_nodes(self): - all_nodes = [] - params, more_results = {'maxResults': 500}, True - while more_results: - self.driver.connection.gce_params = params - all_nodes.extend(self.driver.list_nodes()) - more_results = 'pageToken' in params - return all_nodes - - def group_instances(self, zones=None): - '''Group all instances''' - groups = {} - meta = {} - meta["hostvars"] = {} - - for node in self.list_nodes(): - - # This check filters on the desired instance states defined in the - # config file with the instance_states config option. - # - # If the instance_states list is _empty_ then _ALL_ states are returned. - # - # If the instance_states list is _populated_ then check the current - # state against the instance_states list - if self.instance_states and not node.extra['status'] in self.instance_states: - continue - - name = node.name - - meta["hostvars"][name] = self.node_to_dict(node) - - zone = node.extra['zone'].name - - # To avoid making multiple requests per zone - # we list all nodes and then filter the results - if zones and zone not in zones: - continue - - if zone in groups: - groups[zone].append(name) - else: - groups[zone] = [name] - - tags = node.extra['tags'] - for t in tags: - if t.startswith('group-'): - tag = t[6:] - else: - tag = 'tag_%s' % t - if tag in groups: - groups[tag].append(name) - else: - groups[tag] = [name] - - net = node.extra['networkInterfaces'][0]['network'].split('/')[-1] - net = 'network_%s' % net - if net in groups: - groups[net].append(name) - else: - groups[net] = [name] - - machine_type = node.size - if machine_type in groups: - groups[machine_type].append(name) - else: - groups[machine_type] = [name] - - image = node.image and node.image or 'persistent_disk' - if image in groups: - groups[image].append(name) - else: - groups[image] = [name] - - status = node.extra['status'] - stat = 'status_%s' % status.lower() - if stat in groups: - groups[stat].append(name) - else: - groups[stat] = [name] - - for private_ip in node.private_ips: - groups[private_ip] = [name] - - if len(node.public_ips) >= 1: - for public_ip in node.public_ips: - groups[public_ip] = [name] - - groups["_meta"] = meta - - return groups - - def json_format_dict(self, data, pretty=False): - ''' Converts a dict to a JSON object and dumps it as a formatted - string ''' - - if pretty: - return json.dumps(data, sort_keys=True, indent=2) - else: - return json.dumps(data) - -# Run the script -if __name__ == '__main__': - GceInventory() diff --git a/awx/plugins/inventory/openstack.yml b/awx/plugins/inventory/openstack.yml deleted file mode 100644 index 4ffe6fe28163..000000000000 --- a/awx/plugins/inventory/openstack.yml +++ /dev/null @@ -1,25 +0,0 @@ ---- -clouds: - vexxhost: - profile: vexxhost - auth: - project_name: 39e296b2-fc96-42bf-8091-cb742fa13da9 - username: fb886a9b-c37b-442a-9be3-964bed961e04 - password: fantastic-password1 - rax: - cloud: rackspace - auth: - username: example - password: spectacular-password - project_id: 2352426 - region_name: DFW,ORD,IAD - devstack: - auth: - auth_url: https://devstack.example.com - username: stack - password: stack - project_name: stack -ansible: - use_hostnames: true - expand_hostvars: false - fail_on_errors: true diff --git a/awx/plugins/inventory/openstack_inventory.py b/awx/plugins/inventory/openstack_inventory.py deleted file mode 100755 index ab2d96cb8bf4..000000000000 --- a/awx/plugins/inventory/openstack_inventory.py +++ /dev/null @@ -1,272 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) 2012, Marco Vito Moscaritolo -# Copyright (c) 2013, Jesse Keating -# Copyright (c) 2015, Hewlett-Packard Development Company, L.P. -# Copyright (c) 2016, Rackspace Australia -# -# This module is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This software is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this software. If not, see . - -# The OpenStack Inventory module uses os-client-config for configuration. -# https://github.com/openstack/os-client-config -# This means it will either: -# - Respect normal OS_* environment variables like other OpenStack tools -# - Read values from a clouds.yaml file. -# If you want to configure via clouds.yaml, you can put the file in: -# - Current directory -# - ~/.config/openstack/clouds.yaml -# - /etc/openstack/clouds.yaml -# - /etc/ansible/openstack.yml -# The clouds.yaml file can contain entries for multiple clouds and multiple -# regions of those clouds. If it does, this inventory module will by default -# connect to all of them and present them as one contiguous inventory. You -# can limit to one cloud by passing the `--cloud` parameter, or use the -# OS_CLOUD environment variable. If caching is enabled, and a cloud is -# selected, then per-cloud cache folders will be used. -# -# See the adjacent openstack.yml file for an example config file -# There are two ansible inventory specific options that can be set in -# the inventory section. -# expand_hostvars controls whether or not the inventory will make extra API -# calls to fill out additional information about each server -# use_hostnames changes the behavior from registering every host with its UUID -# and making a group of its hostname to only doing this if the -# hostname in question has more than one server -# fail_on_errors causes the inventory to fail and return no hosts if one cloud -# has failed (for example, bad credentials or being offline). -# When set to False, the inventory will return hosts from -# whichever other clouds it can contact. (Default: True) -# -# Also it is possible to pass the correct user by setting an ansible_user: $myuser -# metadata attribute. - -import argparse -import collections -import os -import sys -import time -from distutils.version import StrictVersion -from io import StringIO - -import json - -import openstack as sdk -from openstack.cloud import inventory as sdk_inventory -from openstack.config import loader as cloud_config - -CONFIG_FILES = ['/etc/ansible/openstack.yaml', '/etc/ansible/openstack.yml'] - - -def get_groups_from_server(server_vars, namegroup=True): - groups = [] - - region = server_vars['region'] - cloud = server_vars['cloud'] - metadata = server_vars.get('metadata', {}) - - # Create a group for the cloud - groups.append(cloud) - - # Create a group on region - if region: - groups.append(region) - - # And one by cloud_region - groups.append("%s_%s" % (cloud, region)) - - # Check if group metadata key in servers' metadata - if 'group' in metadata: - groups.append(metadata['group']) - - for extra_group in metadata.get('groups', '').split(','): - if extra_group: - groups.append(extra_group.strip()) - - groups.append('instance-%s' % server_vars['id']) - if namegroup: - groups.append(server_vars['name']) - - for key in ('flavor', 'image'): - if 'name' in server_vars[key]: - groups.append('%s-%s' % (key, server_vars[key]['name'])) - - for key, value in iter(metadata.items()): - groups.append('meta-%s_%s' % (key, value)) - - az = server_vars.get('az', None) - if az: - # Make groups for az, region_az and cloud_region_az - groups.append(az) - groups.append('%s_%s' % (region, az)) - groups.append('%s_%s_%s' % (cloud, region, az)) - return groups - - -def get_host_groups(inventory, refresh=False, cloud=None): - (cache_file, cache_expiration_time) = get_cache_settings(cloud) - if is_cache_stale(cache_file, cache_expiration_time, refresh=refresh): - groups = to_json(get_host_groups_from_cloud(inventory)) - with open(cache_file, 'w') as f: - f.write(groups) - else: - with open(cache_file, 'r') as f: - groups = f.read() - return groups - - -def append_hostvars(hostvars, groups, key, server, namegroup=False): - hostvars[key] = dict( - ansible_ssh_host=server['interface_ip'], - ansible_host=server['interface_ip'], - openstack=server) - - metadata = server.get('metadata', {}) - if 'ansible_user' in metadata: - hostvars[key]['ansible_user'] = metadata['ansible_user'] - - for group in get_groups_from_server(server, namegroup=namegroup): - groups[group].append(key) - - -def get_host_groups_from_cloud(inventory): - groups = collections.defaultdict(list) - firstpass = collections.defaultdict(list) - hostvars = {} - list_args = {} - if hasattr(inventory, 'extra_config'): - use_hostnames = inventory.extra_config['use_hostnames'] - list_args['expand'] = inventory.extra_config['expand_hostvars'] - if StrictVersion(sdk.version.__version__) >= StrictVersion("0.13.0"): - list_args['fail_on_cloud_config'] = \ - inventory.extra_config['fail_on_errors'] - else: - use_hostnames = False - - for server in inventory.list_hosts(**list_args): - - if 'interface_ip' not in server: - continue - firstpass[server['name']].append(server) - for name, servers in firstpass.items(): - if len(servers) == 1 and use_hostnames: - append_hostvars(hostvars, groups, name, servers[0]) - else: - server_ids = set() - # Trap for duplicate results - for server in servers: - server_ids.add(server['id']) - if len(server_ids) == 1 and use_hostnames: - append_hostvars(hostvars, groups, name, servers[0]) - else: - for server in servers: - append_hostvars( - hostvars, groups, server['id'], server, - namegroup=True) - groups['_meta'] = {'hostvars': hostvars} - return groups - - -def is_cache_stale(cache_file, cache_expiration_time, refresh=False): - ''' Determines if cache file has expired, or if it is still valid ''' - if refresh: - return True - if os.path.isfile(cache_file) and os.path.getsize(cache_file) > 0: - mod_time = os.path.getmtime(cache_file) - current_time = time.time() - if (mod_time + cache_expiration_time) > current_time: - return False - return True - - -def get_cache_settings(cloud=None): - config_files = cloud_config.CONFIG_FILES + CONFIG_FILES - if cloud: - config = cloud_config.OpenStackConfig( - config_files=config_files).get_one(cloud=cloud) - else: - config = cloud_config.OpenStackConfig( - config_files=config_files).get_all()[0] - # For inventory-wide caching - cache_expiration_time = config.get_cache_expiration_time() - cache_path = config.get_cache_path() - if cloud: - cache_path = '{0}_{1}'.format(cache_path, cloud) - if not os.path.exists(cache_path): - os.makedirs(cache_path) - cache_file = os.path.join(cache_path, 'ansible-inventory.cache') - return (cache_file, cache_expiration_time) - - -def to_json(in_dict): - return json.dumps(in_dict, sort_keys=True, indent=2) - - -def parse_args(): - parser = argparse.ArgumentParser(description='OpenStack Inventory Module') - parser.add_argument('--cloud', default=os.environ.get('OS_CLOUD'), - help='Cloud name (default: None') - parser.add_argument('--private', - action='store_true', - help='Use private address for ansible host') - parser.add_argument('--refresh', action='store_true', - help='Refresh cached information') - parser.add_argument('--debug', action='store_true', default=False, - help='Enable debug output') - group = parser.add_mutually_exclusive_group(required=True) - group.add_argument('--list', action='store_true', - help='List active servers') - group.add_argument('--host', help='List details about the specific host') - - return parser.parse_args() - - -def main(): - args = parse_args() - try: - # openstacksdk library may write to stdout, so redirect this - sys.stdout = StringIO() - config_files = cloud_config.CONFIG_FILES + CONFIG_FILES - sdk.enable_logging(debug=args.debug) - inventory_args = dict( - refresh=args.refresh, - config_files=config_files, - private=args.private, - cloud=args.cloud, - ) - if hasattr(sdk_inventory.OpenStackInventory, 'extra_config'): - inventory_args.update(dict( - config_key='ansible', - config_defaults={ - 'use_hostnames': False, - 'expand_hostvars': True, - 'fail_on_errors': True, - } - )) - - inventory = sdk_inventory.OpenStackInventory(**inventory_args) - - sys.stdout = sys.__stdout__ - if args.list: - output = get_host_groups(inventory, refresh=args.refresh, cloud=args.cloud) - elif args.host: - output = to_json(inventory.get_host(args.host)) - print(output) - except sdk.exceptions.OpenStackCloudException as e: - sys.stderr.write('%s\n' % e.message) - sys.exit(1) - sys.exit(0) - - -if __name__ == '__main__': - main() diff --git a/awx/plugins/inventory/ovirt4.py b/awx/plugins/inventory/ovirt4.py deleted file mode 100755 index 74205ae449c5..000000000000 --- a/awx/plugins/inventory/ovirt4.py +++ /dev/null @@ -1,257 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright (c) 2016 Red Hat, Inc. -# -# This file is part of Ansible -# -# Ansible is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Ansible. If not, see . -# - -""" -oVirt dynamic inventory script -================================= - -Generates dynamic inventory file for oVirt. - -Script will return following attributes for each virtual machine: - - id - - name - - host - - cluster - - status - - description - - fqdn - - os_type - - template - - tags - - statistics - - devices - -When run in --list mode, virtual machines are grouped by the following categories: - - cluster - - tag - - status - - Note: If there is some virtual machine which has has more tags it will be in both tag - records. - -Examples: - # Execute update of system on webserver virtual machine: - - $ ansible -i contrib/inventory/ovirt4.py webserver -m yum -a "name=* state=latest" - - # Get webserver virtual machine information: - - $ contrib/inventory/ovirt4.py --host webserver - -Author: Ondra Machacek (@machacekondra) -""" - -import argparse -import os -import sys - -from collections import defaultdict - -from ansible.module_utils.six.moves import configparser - -import json - -try: - import ovirtsdk4 as sdk - import ovirtsdk4.types as otypes -except ImportError: - print('oVirt inventory script requires ovirt-engine-sdk-python >= 4.0.0') - sys.exit(1) - - -def parse_args(): - """ - Create command line parser for oVirt dynamic inventory script. - """ - parser = argparse.ArgumentParser( - description='Ansible dynamic inventory script for oVirt.', - ) - parser.add_argument( - '--list', - action='store_true', - default=True, - help='Get data of all virtual machines (default: True).', - ) - parser.add_argument( - '--host', - help='Get data of virtual machines running on specified host.', - ) - parser.add_argument( - '--pretty', - action='store_true', - default=False, - help='Pretty format (default: False).', - ) - return parser.parse_args() - - -def create_connection(): - """ - Create a connection to oVirt engine API. - """ - # Get the path of the configuration file, by default use - # 'ovirt.ini' file in script directory: - default_path = os.path.join( - os.path.dirname(os.path.realpath(__file__)), - 'ovirt.ini', - ) - config_path = os.environ.get('OVIRT_INI_PATH', default_path) - - # Create parser and add ovirt section if it doesn't exist: - config = configparser.SafeConfigParser( - defaults={ - 'ovirt_url': os.environ.get('OVIRT_URL'), - 'ovirt_username': os.environ.get('OVIRT_USERNAME'), - 'ovirt_password': os.environ.get('OVIRT_PASSWORD'), - 'ovirt_ca_file': os.environ.get('OVIRT_CAFILE', ''), - } - ) - if not config.has_section('ovirt'): - config.add_section('ovirt') - config.read(config_path) - - # Create a connection with options defined in ini file: - return sdk.Connection( - url=config.get('ovirt', 'ovirt_url'), - username=config.get('ovirt', 'ovirt_username'), - password=config.get('ovirt', 'ovirt_password', raw=True), - ca_file=config.get('ovirt', 'ovirt_ca_file') or None, - insecure=not config.get('ovirt', 'ovirt_ca_file'), - ) - - -def get_dict_of_struct(connection, vm): - """ - Transform SDK Vm Struct type to Python dictionary. - """ - if vm is None: - return dict() - - vms_service = connection.system_service().vms_service() - clusters_service = connection.system_service().clusters_service() - vm_service = vms_service.vm_service(vm.id) - devices = vm_service.reported_devices_service().list() - tags = vm_service.tags_service().list() - stats = vm_service.statistics_service().list() - labels = vm_service.affinity_labels_service().list() - groups = clusters_service.cluster_service( - vm.cluster.id - ).affinity_groups_service().list() - - return { - 'id': vm.id, - 'name': vm.name, - 'host': connection.follow_link(vm.host).name if vm.host else None, - 'cluster': connection.follow_link(vm.cluster).name, - 'status': str(vm.status), - 'description': vm.description, - 'fqdn': vm.fqdn, - 'os_type': vm.os.type, - 'template': connection.follow_link(vm.template).name, - 'tags': [tag.name for tag in tags], - 'affinity_labels': [label.name for label in labels], - 'affinity_groups': [ - group.name for group in groups - if vm.name in [vm.name for vm in connection.follow_link(group.vms)] - ], - 'statistics': dict( - (stat.name, stat.values[0].datum) for stat in stats if stat.values - ), - 'devices': dict( - (device.name, [ip.address for ip in device.ips]) for device in devices if device.ips - ), - 'ansible_host': next((device.ips[0].address for device in devices if device.ips), None) - } - - -def get_data(connection, vm_name=None): - """ - Obtain data of `vm_name` if specified, otherwise obtain data of all vms. - """ - vms_service = connection.system_service().vms_service() - clusters_service = connection.system_service().clusters_service() - - if vm_name: - vm = vms_service.list(search='name=%s' % vm_name) or [None] - data = get_dict_of_struct( - connection=connection, - vm=vm[0], - ) - else: - vms = dict() - data = defaultdict(list) - for vm in vms_service.list(): - name = vm.name - vm_service = vms_service.vm_service(vm.id) - cluster_service = clusters_service.cluster_service(vm.cluster.id) - - # Add vm to vms dict: - vms[name] = get_dict_of_struct(connection, vm) - - # Add vm to cluster group: - cluster_name = connection.follow_link(vm.cluster).name - data['cluster_%s' % cluster_name].append(name) - - # Add vm to tag group: - tags_service = vm_service.tags_service() - for tag in tags_service.list(): - data['tag_%s' % tag.name].append(name) - - # Add vm to status group: - data['status_%s' % vm.status].append(name) - - # Add vm to affinity group: - for group in cluster_service.affinity_groups_service().list(): - if vm.name in [ - v.name for v in connection.follow_link(group.vms) - ]: - data['affinity_group_%s' % group.name].append(vm.name) - - # Add vm to affinity label group: - affinity_labels_service = vm_service.affinity_labels_service() - for label in affinity_labels_service.list(): - data['affinity_label_%s' % label.name].append(name) - - data["_meta"] = { - 'hostvars': vms, - } - - return data - - -def main(): - args = parse_args() - connection = create_connection() - - print( - json.dumps( - obj=get_data( - connection=connection, - vm_name=args.host, - ), - sort_keys=args.pretty, - indent=args.pretty * 2, - ) - ) - - -if __name__ == '__main__': - main() diff --git a/awx/plugins/inventory/tower.py b/awx/plugins/inventory/tower.py deleted file mode 100755 index 353efe7256ee..000000000000 --- a/awx/plugins/inventory/tower.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright (c) 2016 Red Hat, Inc. -# -# This file is part of Ansible -# -# Ansible is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Ansible. If not, see . -# -""" -Ansible Tower/AWX dynamic inventory script -========================================== - -Generates dynamic inventory for Tower - -Author: Matthew Jones (@matburt) -""" - -import argparse -import re -import os -import sys -import json -import requests -from requests.auth import HTTPBasicAuth - -try: - from urlparse import urljoin -except ImportError: - from urllib.parse import urljoin - - -def parse_configuration(): - """ - Create command line parser for oVirt dynamic inventory script. - """ - parser = argparse.ArgumentParser( - description='Ansible dynamic inventory script for Ansible Tower.', - ) - parser.add_argument( - '--list', - action='store_true', - default=True, - help='Return all hosts known to Tower given a particular inventory', - ) - parser.parse_args() - host_name = os.environ.get("TOWER_HOST", None) - username = os.environ.get("TOWER_USERNAME", None) - password = os.environ.get("TOWER_PASSWORD", None) - ignore_ssl = False - ssl_negative_var = os.environ.get("TOWER_IGNORE_SSL", None) - if ssl_negative_var: - ignore_ssl = ssl_negative_var.lower() in ("1", "yes", "true") - else: - ssl_positive_var = os.environ.get("TOWER_VERIFY_SSL", None) - if ssl_positive_var: - ignore_ssl = ssl_positive_var.lower() not in ('true', '1', 't', 'y', 'yes') - inventory = os.environ.get("TOWER_INVENTORY", None) - license_type = os.environ.get("TOWER_LICENSE_TYPE", "enterprise") - - errors = [] - if not host_name: - errors.append("Missing TOWER_HOST in environment") - if not username: - errors.append("Missing TOWER_USERNAME in environment") - if not password: - errors.append("Missing TOWER_PASSWORD in environment") - if not inventory: - errors.append("Missing TOWER_INVENTORY in environment") - if errors: - raise RuntimeError("\n".join(errors)) - - return dict(tower_host=host_name, - tower_user=username, - tower_pass=password, - tower_inventory=inventory, - tower_license_type=license_type, - ignore_ssl=ignore_ssl) - - -def read_tower_inventory(tower_host, tower_user, tower_pass, inventory, license_type, ignore_ssl=False): - if not re.match('(?:http|https)://', tower_host): - tower_host = "https://{}".format(tower_host) - inventory_url = urljoin(tower_host, "/api/v2/inventories/{}/script/?hostvars=1&towervars=1&all=1".format(inventory.replace('/', ''))) - config_url = urljoin(tower_host, "/api/v2/config/") - try: - if license_type != "open": - config_response = requests.get(config_url, - auth=HTTPBasicAuth(tower_user, tower_pass), - verify=not ignore_ssl) - if config_response.ok: - source_type = config_response.json()['license_info']['license_type'] - if not source_type == license_type: - raise RuntimeError("Tower server licenses must match: source: {} local: {}".format(source_type, - license_type)) - else: - raise RuntimeError("Failed to validate the license of the remote Tower: {}".format(config_response)) - - response = requests.get(inventory_url, - auth=HTTPBasicAuth(tower_user, tower_pass), - verify=not ignore_ssl) - if not response.ok: - # If the GET /api/v2/inventories/N/script is not HTTP 200, print the error code - msg = "Connection to remote host failed: {}".format(response) - if response.text: - msg += " with message: {}".format(response.text) - raise RuntimeError(msg) - try: - # Attempt to parse JSON - return response.json() - except (ValueError, TypeError) as e: - # If the JSON parse fails, print the ValueError - raise RuntimeError("Failed to parse json from host: {}".format(e)) - except requests.ConnectionError as e: - raise RuntimeError("Connection to remote host failed: {}".format(e)) - - -def main(): - config = parse_configuration() - inventory_hosts = read_tower_inventory(config['tower_host'], - config['tower_user'], - config['tower_pass'], - config['tower_inventory'], - config['tower_license_type'], - ignore_ssl=config['ignore_ssl']) - print( - json.dumps( - inventory_hosts - ) - ) - - -if __name__ == '__main__': - main() diff --git a/awx/plugins/inventory/vmware_inventory.py b/awx/plugins/inventory/vmware_inventory.py deleted file mode 100755 index 183b9a19b0e5..000000000000 --- a/awx/plugins/inventory/vmware_inventory.py +++ /dev/null @@ -1,792 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright (C): 2017, Ansible Project -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -# Requirements -# - pyvmomi >= 6.0.0.2016.4 - -# TODO: -# * more jq examples -# * optional folder hierarchy - -""" -$ jq '._meta.hostvars[].config' data.json | head -{ - "alternateguestname": "", - "instanceuuid": "5035a5cd-b8e8-d717-e133-2d383eb0d675", - "memoryhotaddenabled": false, - "guestfullname": "Red Hat Enterprise Linux 7 (64-bit)", - "changeversion": "2016-05-16T18:43:14.977925Z", - "uuid": "4235fc97-5ddb-7a17-193b-9a3ac97dc7b4", - "cpuhotremoveenabled": false, - "vpmcenabled": false, - "firmware": "bios", -""" - -from __future__ import print_function - -import atexit -import datetime -import itertools -import json -import os -import re -import ssl -import sys -import uuid -from time import time - -from jinja2 import Environment - -from ansible.module_utils.six import integer_types, PY3 -from ansible.module_utils.six.moves import configparser - -try: - import argparse -except ImportError: - sys.exit('Error: This inventory script required "argparse" python module. Please install it or upgrade to python-2.7') - -try: - from pyVmomi import vim, vmodl - from pyVim.connect import SmartConnect, Disconnect -except ImportError: - sys.exit("ERROR: This inventory script required 'pyVmomi' Python module, it was not able to load it") - - -def regex_match(s, pattern): - '''Custom filter for regex matching''' - reg = re.compile(pattern) - if reg.match(s): - return True - else: - return False - - -def select_chain_match(inlist, key, pattern): - '''Get a key from a list of dicts, squash values to a single list, then filter''' - outlist = [x[key] for x in inlist] - outlist = list(itertools.chain(*outlist)) - outlist = [x for x in outlist if regex_match(x, pattern)] - return outlist - - -class VMwareMissingHostException(Exception): - pass - - -class VMWareInventory(object): - __name__ = 'VMWareInventory' - - guest_props = False - instances = [] - debug = False - load_dumpfile = None - write_dumpfile = None - maxlevel = 1 - lowerkeys = True - config = None - cache_max_age = None - cache_path_cache = None - cache_path_index = None - cache_dir = None - server = None - port = None - username = None - password = None - validate_certs = True - host_filters = [] - skip_keys = [] - groupby_patterns = [] - groupby_custom_field_excludes = [] - - safe_types = [bool, str, float, None] + list(integer_types) - iter_types = [dict, list] - - bad_types = ['Array', 'disabledMethod', 'declaredAlarmState'] - - vimTableMaxDepth = { - "vim.HostSystem": 2, - "vim.VirtualMachine": 2, - } - - custom_fields = {} - - # use jinja environments to allow for custom filters - env = Environment() - env.filters['regex_match'] = regex_match - env.filters['select_chain_match'] = select_chain_match - - # translation table for attributes to fetch for known vim types - - vimTable = { - vim.Datastore: ['_moId', 'name'], - vim.ResourcePool: ['_moId', 'name'], - vim.HostSystem: ['_moId', 'name'], - } - - @staticmethod - def _empty_inventory(): - return {"_meta": {"hostvars": {}}} - - def __init__(self, load=True): - self.inventory = VMWareInventory._empty_inventory() - - if load: - # Read settings and parse CLI arguments - self.parse_cli_args() - self.read_settings() - - # Check the cache - cache_valid = self.is_cache_valid() - - # Handle Cache - if self.args.refresh_cache or not cache_valid: - self.do_api_calls_update_cache() - else: - self.debugl('loading inventory from cache') - self.inventory = self.get_inventory_from_cache() - - def debugl(self, text): - if self.args.debug: - try: - text = str(text) - except UnicodeEncodeError: - text = text.encode('utf-8') - print('%s %s' % (datetime.datetime.now(), text)) - - def show(self): - # Data to print - self.debugl('dumping results') - data_to_print = None - if self.args.host: - data_to_print = self.get_host_info(self.args.host) - elif self.args.list: - # Display list of instances for inventory - data_to_print = self.inventory - return json.dumps(data_to_print, indent=2) - - def is_cache_valid(self): - ''' Determines if the cache files have expired, or if it is still valid ''' - - valid = False - - if os.path.isfile(self.cache_path_cache): - mod_time = os.path.getmtime(self.cache_path_cache) - current_time = time() - if (mod_time + self.cache_max_age) > current_time: - valid = True - - return valid - - def do_api_calls_update_cache(self): - ''' Get instances and cache the data ''' - self.inventory = self.instances_to_inventory(self.get_instances()) - self.write_to_cache(self.inventory) - - def write_to_cache(self, data): - ''' Dump inventory to json file ''' - with open(self.cache_path_cache, 'w') as f: - f.write(json.dumps(data, indent=2)) - - def get_inventory_from_cache(self): - ''' Read in jsonified inventory ''' - - jdata = None - with open(self.cache_path_cache, 'r') as f: - jdata = f.read() - return json.loads(jdata) - - def read_settings(self): - ''' Reads the settings from the vmware_inventory.ini file ''' - - scriptbasename = __file__ - scriptbasename = os.path.basename(scriptbasename) - scriptbasename = scriptbasename.replace('.py', '') - - defaults = {'vmware': { - 'server': '', - 'port': 443, - 'username': '', - 'password': '', - 'validate_certs': True, - 'ini_path': os.path.join(os.path.dirname(__file__), '%s.ini' % scriptbasename), - 'cache_name': 'ansible-vmware', - 'cache_path': '~/.ansible/tmp', - 'cache_max_age': 3600, - 'max_object_level': 1, - 'skip_keys': 'declaredalarmstate,' - 'disabledmethod,' - 'dynamicproperty,' - 'dynamictype,' - 'environmentbrowser,' - 'managedby,' - 'parent,' - 'childtype,' - 'resourceconfig', - 'alias_pattern': '{{ config.name + "_" + config.uuid }}', - 'host_pattern': '{{ guest.ipaddress }}', - 'host_filters': '{{ runtime.powerstate == "poweredOn" }}', - 'groupby_patterns': '{{ guest.guestid }},{{ "templates" if config.template else "guests"}}', - 'lower_var_keys': True, - 'custom_field_group_prefix': 'vmware_tag_', - 'groupby_custom_field_excludes': '', - 'groupby_custom_field': False} - } - - if PY3: - config = configparser.ConfigParser() - else: - config = configparser.SafeConfigParser() - - # where is the config? - vmware_ini_path = os.environ.get('VMWARE_INI_PATH', defaults['vmware']['ini_path']) - vmware_ini_path = os.path.expanduser(os.path.expandvars(vmware_ini_path)) - config.read(vmware_ini_path) - - if 'vmware' not in config.sections(): - config.add_section('vmware') - - # apply defaults - for k, v in defaults['vmware'].items(): - if not config.has_option('vmware', k): - config.set('vmware', k, str(v)) - - # where is the cache? - self.cache_dir = os.path.expanduser(config.get('vmware', 'cache_path')) - if self.cache_dir and not os.path.exists(self.cache_dir): - os.makedirs(self.cache_dir) - - # set the cache filename and max age - cache_name = config.get('vmware', 'cache_name') - self.cache_path_cache = self.cache_dir + "/%s.cache" % cache_name - self.debugl('cache path is %s' % self.cache_path_cache) - self.cache_max_age = int(config.getint('vmware', 'cache_max_age')) - - # mark the connection info - self.server = os.environ.get('VMWARE_SERVER', config.get('vmware', 'server')) - self.debugl('server is %s' % self.server) - self.port = int(os.environ.get('VMWARE_PORT', config.get('vmware', 'port'))) - self.username = os.environ.get('VMWARE_USERNAME', config.get('vmware', 'username')) - self.debugl('username is %s' % self.username) - self.password = os.environ.get('VMWARE_PASSWORD', config.get('vmware', 'password', raw=True)) - self.validate_certs = os.environ.get('VMWARE_VALIDATE_CERTS', config.get('vmware', 'validate_certs')) - if self.validate_certs in ['no', 'false', 'False', False]: - self.validate_certs = False - - self.debugl('cert validation is %s' % self.validate_certs) - - # behavior control - self.maxlevel = int(config.get('vmware', 'max_object_level')) - self.debugl('max object level is %s' % self.maxlevel) - self.lowerkeys = config.get('vmware', 'lower_var_keys') - if type(self.lowerkeys) != bool: - if str(self.lowerkeys).lower() in ['yes', 'true', '1']: - self.lowerkeys = True - else: - self.lowerkeys = False - self.debugl('lower keys is %s' % self.lowerkeys) - self.skip_keys = list(config.get('vmware', 'skip_keys').split(',')) - self.debugl('skip keys is %s' % self.skip_keys) - temp_host_filters = list(config.get('vmware', 'host_filters').split('}},')) - for host_filter in temp_host_filters: - host_filter = host_filter.rstrip() - if host_filter != "": - if not host_filter.endswith("}}"): - host_filter += "}}" - self.host_filters.append(host_filter) - self.debugl('host filters are %s' % self.host_filters) - - temp_groupby_patterns = list(config.get('vmware', 'groupby_patterns').split('}},')) - for groupby_pattern in temp_groupby_patterns: - groupby_pattern = groupby_pattern.rstrip() - if groupby_pattern != "": - if not groupby_pattern.endswith("}}"): - groupby_pattern += "}}" - self.groupby_patterns.append(groupby_pattern) - self.debugl('groupby patterns are %s' % self.groupby_patterns) - temp_groupby_custom_field_excludes = config.get('vmware', 'groupby_custom_field_excludes') - self.groupby_custom_field_excludes = [x.strip('"') for x in [y.strip("'") for y in temp_groupby_custom_field_excludes.split(",")]] - self.debugl('groupby exclude strings are %s' % self.groupby_custom_field_excludes) - - # Special feature to disable the brute force serialization of the - # virtual machine objects. The key name for these properties does not - # matter because the values are just items for a larger list. - if config.has_section('properties'): - self.guest_props = [] - for prop in config.items('properties'): - self.guest_props.append(prop[1]) - - # save the config - self.config = config - - def parse_cli_args(self): - ''' Command line argument processing ''' - - parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on PyVmomi') - parser.add_argument('--debug', action='store_true', default=False, - help='show debug info') - parser.add_argument('--list', action='store_true', default=True, - help='List instances (default: True)') - parser.add_argument('--host', action='store', - help='Get all the variables about a specific instance') - parser.add_argument('--refresh-cache', action='store_true', default=False, - help='Force refresh of cache by making API requests to VSphere (default: False - use cache files)') - parser.add_argument('--max-instances', default=None, type=int, - help='maximum number of instances to retrieve') - self.args = parser.parse_args() - - def get_instances(self): - ''' Get a list of vm instances with pyvmomi ''' - kwargs = {'host': self.server, - 'user': self.username, - 'pwd': self.password, - 'port': int(self.port)} - - if self.validate_certs and hasattr(ssl, 'SSLContext'): - context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) - context.verify_mode = ssl.CERT_REQUIRED - context.check_hostname = True - kwargs['sslContext'] = context - elif self.validate_certs and not hasattr(ssl, 'SSLContext'): - sys.exit('pyVim does not support changing verification mode with python < 2.7.9. Either update ' - 'python or use validate_certs=false.') - elif not self.validate_certs and hasattr(ssl, 'SSLContext'): - context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) - context.verify_mode = ssl.CERT_NONE - context.check_hostname = False - kwargs['sslContext'] = context - elif not self.validate_certs and not hasattr(ssl, 'SSLContext'): - # Python 2.7.9 < or RHEL/CentOS 7.4 < - pass - - return self._get_instances(kwargs) - - def _get_instances(self, inkwargs): - ''' Make API calls ''' - instances = [] - try: - si = SmartConnect(**inkwargs) - except ssl.SSLError as connection_error: - if '[SSL: CERTIFICATE_VERIFY_FAILED]' in str(connection_error) and self.validate_certs: - sys.exit("Unable to connect to ESXi server due to %s, " - "please specify validate_certs=False and try again" % connection_error) - - except Exception as exc: - self.debugl("Unable to connect to ESXi server due to %s" % exc) - sys.exit("Unable to connect to ESXi server due to %s" % exc) - - self.debugl('retrieving all instances') - if not si: - sys.exit("Could not connect to the specified host using specified " - "username and password") - atexit.register(Disconnect, si) - content = si.RetrieveContent() - - # Create a search container for virtualmachines - self.debugl('creating containerview for virtualmachines') - container = content.rootFolder - viewType = [vim.VirtualMachine] - recursive = True - containerView = content.viewManager.CreateContainerView(container, viewType, recursive) - children = containerView.view - for child in children: - # If requested, limit the total number of instances - if self.args.max_instances: - if len(instances) >= self.args.max_instances: - break - instances.append(child) - self.debugl("%s total instances in container view" % len(instances)) - - if self.args.host: - instances = [x for x in instances if x.name == self.args.host] - - instance_tuples = [] - for instance in instances: - if self.guest_props: - ifacts = self.facts_from_proplist(instance) - else: - ifacts = self.facts_from_vobj(instance) - instance_tuples.append((instance, ifacts)) - self.debugl('facts collected for all instances') - - try: - cfm = content.customFieldsManager - if cfm is not None and cfm.field: - for f in cfm.field: - if not f.managedObjectType or f.managedObjectType == vim.VirtualMachine: - self.custom_fields[f.key] = f.name - self.debugl('%d custom fields collected' % len(self.custom_fields)) - except vmodl.RuntimeFault as exc: - self.debugl("Unable to gather custom fields due to %s" % exc.msg) - except IndexError as exc: - self.debugl("Unable to gather custom fields due to %s" % exc) - - return instance_tuples - - def instances_to_inventory(self, instances): - ''' Convert a list of vm objects into a json compliant inventory ''' - self.debugl('re-indexing instances based on ini settings') - inventory = VMWareInventory._empty_inventory() - inventory['all'] = {} - inventory['all']['hosts'] = [] - for idx, instance in enumerate(instances): - # make a unique id for this object to avoid vmware's - # numerous uuid's which aren't all unique. - thisid = str(uuid.uuid4()) - idata = instance[1] - - # Put it in the inventory - inventory['all']['hosts'].append(thisid) - inventory['_meta']['hostvars'][thisid] = idata.copy() - inventory['_meta']['hostvars'][thisid]['ansible_uuid'] = thisid - - # Make a map of the uuid to the alias the user wants - name_mapping = self.create_template_mapping( - inventory, - self.config.get('vmware', 'alias_pattern') - ) - - # Make a map of the uuid to the ssh hostname the user wants - host_mapping = self.create_template_mapping( - inventory, - self.config.get('vmware', 'host_pattern') - ) - - # Reset the inventory keys - for k, v in name_mapping.items(): - - if not host_mapping or k not in host_mapping: - continue - - # set ansible_host (2.x) - try: - inventory['_meta']['hostvars'][k]['ansible_host'] = host_mapping[k] - # 1.9.x backwards compliance - inventory['_meta']['hostvars'][k]['ansible_ssh_host'] = host_mapping[k] - except Exception: - continue - - if k == v: - continue - - # add new key - inventory['all']['hosts'].append(v) - inventory['_meta']['hostvars'][v] = inventory['_meta']['hostvars'][k] - - # cleanup old key - inventory['all']['hosts'].remove(k) - inventory['_meta']['hostvars'].pop(k, None) - - self.debugl('pre-filtered hosts:') - for i in inventory['all']['hosts']: - self.debugl(' * %s' % i) - # Apply host filters - for hf in self.host_filters: - if not hf: - continue - self.debugl('filter: %s' % hf) - filter_map = self.create_template_mapping(inventory, hf, dtype='boolean') - for k, v in filter_map.items(): - if not v: - # delete this host - inventory['all']['hosts'].remove(k) - inventory['_meta']['hostvars'].pop(k, None) - - self.debugl('post-filter hosts:') - for i in inventory['all']['hosts']: - self.debugl(' * %s' % i) - - # Create groups - for gbp in self.groupby_patterns: - groupby_map = self.create_template_mapping(inventory, gbp) - for k, v in groupby_map.items(): - if v not in inventory: - inventory[v] = {} - inventory[v]['hosts'] = [] - if k not in inventory[v]['hosts']: - inventory[v]['hosts'].append(k) - - if self.config.get('vmware', 'groupby_custom_field'): - for k, v in inventory['_meta']['hostvars'].items(): - if 'customvalue' in v: - for tv in v['customvalue']: - newkey = None - field_name = self.custom_fields[tv['key']] if tv['key'] in self.custom_fields else tv['key'] - if field_name in self.groupby_custom_field_excludes: - continue - values = [] - keylist = map(lambda x: x.strip(), tv['value'].split(',')) - for kl in keylist: - try: - newkey = "%s%s_%s" % (self.config.get('vmware', 'custom_field_group_prefix'), str(field_name), kl) - newkey = newkey.strip() - except Exception as e: - self.debugl(e) - values.append(newkey) - for tag in values: - if not tag: - continue - if tag not in inventory: - inventory[tag] = {} - inventory[tag]['hosts'] = [] - if k not in inventory[tag]['hosts']: - inventory[tag]['hosts'].append(k) - - return inventory - - def create_template_mapping(self, inventory, pattern, dtype='string'): - ''' Return a hash of uuid to templated string from pattern ''' - mapping = {} - for k, v in inventory['_meta']['hostvars'].items(): - t = self.env.from_string(pattern) - newkey = None - try: - newkey = t.render(v) - newkey = newkey.strip() - except Exception as e: - self.debugl(e) - if not newkey: - continue - elif dtype == 'integer': - newkey = int(newkey) - elif dtype == 'boolean': - if newkey.lower() == 'false': - newkey = False - elif newkey.lower() == 'true': - newkey = True - elif dtype == 'string': - pass - mapping[k] = newkey - return mapping - - def facts_from_proplist(self, vm): - '''Get specific properties instead of serializing everything''' - - rdata = {} - for prop in self.guest_props: - self.debugl('getting %s property for %s' % (prop, vm.name)) - key = prop - if self.lowerkeys: - key = key.lower() - - if '.' not in prop: - # props without periods are direct attributes of the parent - vm_property = getattr(vm, prop) - if isinstance(vm_property, vim.CustomFieldsManager.Value.Array): - temp_vm_property = [] - for vm_prop in vm_property: - temp_vm_property.append({'key': vm_prop.key, - 'value': vm_prop.value}) - rdata[key] = temp_vm_property - else: - rdata[key] = vm_property - else: - # props with periods are subkeys of parent attributes - parts = prop.split('.') - total = len(parts) - 1 - - # pointer to the current object - val = None - # pointer to the current result key - lastref = rdata - - for idx, x in enumerate(parts): - - if isinstance(val, dict): - if x in val: - val = val.get(x) - elif x.lower() in val: - val = val.get(x.lower()) - else: - # if the val wasn't set yet, get it from the parent - if not val: - try: - val = getattr(vm, x) - except AttributeError as e: - self.debugl(e) - else: - # in a subkey, get the subprop from the previous attrib - try: - val = getattr(val, x) - except AttributeError as e: - self.debugl(e) - - # make sure it serializes - val = self._process_object_types(val) - - # lowercase keys if requested - if self.lowerkeys: - x = x.lower() - - # change the pointer or set the final value - if idx != total: - if x not in lastref: - lastref[x] = {} - lastref = lastref[x] - else: - lastref[x] = val - if self.args.debug: - self.debugl("For %s" % vm.name) - for key in list(rdata.keys()): - if isinstance(rdata[key], dict): - for ikey in list(rdata[key].keys()): - self.debugl("Property '%s.%s' has value '%s'" % (key, ikey, rdata[key][ikey])) - else: - self.debugl("Property '%s' has value '%s'" % (key, rdata[key])) - return rdata - - def facts_from_vobj(self, vobj, level=0): - ''' Traverse a VM object and return a json compliant data structure ''' - - # pyvmomi objects are not yet serializable, but may be one day ... - # https://github.com/vmware/pyvmomi/issues/21 - - # WARNING: - # Accessing an object attribute will trigger a SOAP call to the remote. - # Increasing the attributes collected or the depth of recursion greatly - # increases runtime duration and potentially memory+network utilization. - - if level == 0: - try: - self.debugl("get facts for %s" % vobj.name) - except Exception as e: - self.debugl(e) - - rdata = {} - - methods = dir(vobj) - methods = [str(x) for x in methods if not x.startswith('_')] - methods = [x for x in methods if x not in self.bad_types] - methods = [x for x in methods if not x.lower() in self.skip_keys] - methods = sorted(methods) - - for method in methods: - # Attempt to get the method, skip on fail - try: - methodToCall = getattr(vobj, method) - except Exception as e: - continue - - # Skip callable methods - if callable(methodToCall): - continue - - if self.lowerkeys: - method = method.lower() - - rdata[method] = self._process_object_types( - methodToCall, - thisvm=vobj, - inkey=method, - ) - - return rdata - - def _process_object_types(self, vobj, thisvm=None, inkey='', level=0): - ''' Serialize an object ''' - rdata = {} - - if type(vobj).__name__ in self.vimTableMaxDepth and level >= self.vimTableMaxDepth[type(vobj).__name__]: - return rdata - - if vobj is None: - rdata = None - elif type(vobj) in self.vimTable: - rdata = {} - for key in self.vimTable[type(vobj)]: - try: - rdata[key] = getattr(vobj, key) - except Exception as e: - self.debugl(e) - - elif issubclass(type(vobj), str) or isinstance(vobj, str): - if vobj.isalnum(): - rdata = vobj - else: - rdata = vobj.encode('utf-8').decode('utf-8') - elif issubclass(type(vobj), bool) or isinstance(vobj, bool): - rdata = vobj - elif issubclass(type(vobj), integer_types) or isinstance(vobj, integer_types): - rdata = vobj - elif issubclass(type(vobj), float) or isinstance(vobj, float): - rdata = vobj - elif issubclass(type(vobj), list) or issubclass(type(vobj), tuple): - rdata = [] - try: - vobj = sorted(vobj) - except Exception: - pass - - for idv, vii in enumerate(vobj): - if level + 1 <= self.maxlevel: - vid = self._process_object_types( - vii, - thisvm=thisvm, - inkey=inkey + '[' + str(idv) + ']', - level=(level + 1) - ) - - if vid: - rdata.append(vid) - - elif issubclass(type(vobj), dict): - pass - - elif issubclass(type(vobj), object): - methods = dir(vobj) - methods = [str(x) for x in methods if not x.startswith('_')] - methods = [x for x in methods if x not in self.bad_types] - methods = [x for x in methods if not inkey + '.' + x.lower() in self.skip_keys] - methods = sorted(methods) - - for method in methods: - # Attempt to get the method, skip on fail - try: - methodToCall = getattr(vobj, method) - except Exception as e: - continue - - if callable(methodToCall): - continue - - if self.lowerkeys: - method = method.lower() - if level + 1 <= self.maxlevel: - try: - rdata[method] = self._process_object_types( - methodToCall, - thisvm=thisvm, - inkey=inkey + '.' + method, - level=(level + 1) - ) - except vim.fault.NoPermission: - self.debugl("Skipping method %s (NoPermission)" % method) - else: - pass - - return rdata - - def get_host_info(self, host): - ''' Return hostvars for a single host ''' - - if host in self.inventory['_meta']['hostvars']: - return self.inventory['_meta']['hostvars'][host] - elif self.args.host and self.inventory['_meta']['hostvars']: - match = None - for k, v in self.inventory['_meta']['hostvars'].items(): - if self.inventory['_meta']['hostvars'][k]['name'] == self.args.host: - match = k - break - if match: - return self.inventory['_meta']['hostvars'][match] - else: - raise VMwareMissingHostException('%s not found' % host) - else: - raise VMwareMissingHostException('%s not found' % host) - - -if __name__ == "__main__": - # Run the script - print(VMWareInventory().show()) diff --git a/awx/plugins/library/scan_files.py b/awx/plugins/library/scan_files.py deleted file mode 100644 index b3b07ad64a9a..000000000000 --- a/awx/plugins/library/scan_files.py +++ /dev/null @@ -1,166 +0,0 @@ -#!/usr/bin/env python - -import os -import stat -from ansible.module_utils.basic import * # noqa - -DOCUMENTATION = ''' ---- -module: scan_files -short_description: Return file state information as fact data for a directory tree -description: - - Return file state information recursively for a directory tree on the filesystem -version_added: "1.9" -options: - path: - description: The path containing files to be analyzed - required: true - default: null - recursive: - description: scan this directory and all subdirectories - required: false - default: no - get_checksum: - description: Checksum files that you can access - required: false - default: false -requirements: [ ] -author: Matthew Jones -''' - -EXAMPLES = ''' -# Example fact output: -# host | success >> { -# "ansible_facts": { -# "files": [ -# { -# "atime": 1427313854.0755742, -# "checksum": "cf7566e6149ad9af91e7589e0ea096a08de9c1e5", -# "ctime": 1427129299.22948, -# "dev": 51713, -# "gid": 0, -# "inode": 149601, -# "isblk": false, -# "ischr": false, -# "isdir": false, -# "isfifo": false, -# "isgid": false, -# "islnk": false, -# "isreg": true, -# "issock": false, -# "isuid": false, -# "mode": "0644", -# "mtime": 1427112663.0321455, -# "nlink": 1, -# "path": "/var/log/dmesg.1.gz", -# "rgrp": true, -# "roth": true, -# "rusr": true, -# "size": 28, -# "uid": 0, -# "wgrp": false, -# "woth": false, -# "wusr": true, -# "xgrp": false, -# "xoth": false, -# "xusr": false -# }, -# { -# "atime": 1427314385.1155744, -# "checksum": "16fac7be61a6e4591a33ef4b729c5c3302307523", -# "ctime": 1427384148.5755742, -# "dev": 51713, -# "gid": 43, -# "inode": 149564, -# "isblk": false, -# "ischr": false, -# "isdir": false, -# "isfifo": false, -# "isgid": false, -# "islnk": false, -# "isreg": true, -# "issock": false, -# "isuid": false, -# "mode": "0664", -# "mtime": 1427384148.5755742, -# "nlink": 1, -# "path": "/var/log/wtmp", -# "rgrp": true, -# "roth": true, -# "rusr": true, -# "size": 48768, -# "uid": 0, -# "wgrp": true, -# "woth": false, -# "wusr": true, -# "xgrp": false, -# "xoth": false, -# "xusr": false -# }, -''' - - -def main(): - module = AnsibleModule( # noqa - argument_spec = dict(paths=dict(required=True, type='list'), - recursive=dict(required=False, default='no', type='bool'), - get_checksum=dict(required=False, default='no', type='bool'))) - files = [] - paths = module.params.get('paths') - for path in paths: - path = os.path.expanduser(path) - if not os.path.exists(path) or not os.path.isdir(path): - module.fail_json(msg = "Given path must exist and be a directory") - - get_checksum = module.params.get('get_checksum') - should_recurse = module.params.get('recursive') - if not should_recurse: - path_list = [os.path.join(path, subpath) for subpath in os.listdir(path)] - else: - path_list = [os.path.join(w_path, f) for w_path, w_names, w_file in os.walk(path) for f in w_file] - for filepath in path_list: - try: - st = os.stat(filepath) - except OSError: - continue - - mode = st.st_mode - d = { - 'path' : filepath, - 'mode' : "%04o" % stat.S_IMODE(mode), - 'isdir' : stat.S_ISDIR(mode), - 'ischr' : stat.S_ISCHR(mode), - 'isblk' : stat.S_ISBLK(mode), - 'isreg' : stat.S_ISREG(mode), - 'isfifo' : stat.S_ISFIFO(mode), - 'islnk' : stat.S_ISLNK(mode), - 'issock' : stat.S_ISSOCK(mode), - 'uid' : st.st_uid, - 'gid' : st.st_gid, - 'size' : st.st_size, - 'inode' : st.st_ino, - 'dev' : st.st_dev, - 'nlink' : st.st_nlink, - 'atime' : st.st_atime, - 'mtime' : st.st_mtime, - 'ctime' : st.st_ctime, - 'wusr' : bool(mode & stat.S_IWUSR), - 'rusr' : bool(mode & stat.S_IRUSR), - 'xusr' : bool(mode & stat.S_IXUSR), - 'wgrp' : bool(mode & stat.S_IWGRP), - 'rgrp' : bool(mode & stat.S_IRGRP), - 'xgrp' : bool(mode & stat.S_IXGRP), - 'woth' : bool(mode & stat.S_IWOTH), - 'roth' : bool(mode & stat.S_IROTH), - 'xoth' : bool(mode & stat.S_IXOTH), - 'isuid' : bool(mode & stat.S_ISUID), - 'isgid' : bool(mode & stat.S_ISGID), - } - if get_checksum and stat.S_ISREG(mode) and os.access(filepath, os.R_OK): - d['checksum'] = module.sha1(filepath) - files.append(d) - results = dict(ansible_facts=dict(files=files)) - module.exit_json(**results) - - -main() diff --git a/awx/plugins/library/scan_insights.py b/awx/plugins/library/scan_insights.py deleted file mode 100755 index f7b7919bca42..000000000000 --- a/awx/plugins/library/scan_insights.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env python - -from ansible.module_utils.basic import * # noqa - -DOCUMENTATION = ''' ---- -module: scan_insights -short_description: Return insights id as fact data -description: - - Inspects the /etc/redhat-access-insights/machine-id file for insights id and returns the found id as fact data -version_added: "2.3" -options: -requirements: [ ] -author: Chris Meyers -''' - -EXAMPLES = ''' -# Example fact output: -# host | success >> { -# "ansible_facts": { -# "insights": { -# "system_id": "4da7d1f8-14f3-4cdc-acd5-a3465a41f25d" -# }, ... } -''' - - -INSIGHTS_SYSTEM_ID_FILE='/etc/redhat-access-insights/machine-id' - - -def get_system_id(filname): - system_id = None - try: - f = open(INSIGHTS_SYSTEM_ID_FILE, "r") - except IOError: - return None - else: - try: - data = f.readline() - system_id = str(data) - except (IOError, ValueError): - pass - finally: - f.close() - if system_id: - system_id = system_id.strip() - return system_id - - -def main(): - module = AnsibleModule( # noqa - argument_spec = dict() - ) - - system_id = get_system_id(INSIGHTS_SYSTEM_ID_FILE) - - results = { - 'ansible_facts': { - 'insights': { - 'system_id': system_id - } - } - } - module.exit_json(**results) - - -main() diff --git a/awx/plugins/library/scan_packages.py b/awx/plugins/library/scan_packages.py deleted file mode 100755 index d0b544bb9f12..000000000000 --- a/awx/plugins/library/scan_packages.py +++ /dev/null @@ -1,111 +0,0 @@ -#!/usr/bin/env python - -from ansible.module_utils.basic import * # noqa - -DOCUMENTATION = ''' ---- -module: scan_packages -short_description: Return installed packages information as fact data -description: - - Return information about installed packages as fact data -version_added: "1.9" -options: -requirements: [ ] -author: Matthew Jones -''' - -EXAMPLES = ''' -# Example fact output: -# host | success >> { -# "ansible_facts": { -# "packages": { -# "libbz2-1.0": [ -# { -# "version": "1.0.6-5", -# "source": "apt", -# "arch": "amd64", -# "name": "libbz2-1.0" -# } -# ], -# "patch": [ -# { -# "version": "2.7.1-4ubuntu1", -# "source": "apt", -# "arch": "amd64", -# "name": "patch" -# } -# ], -# "gcc-4.8-base": [ -# { -# "version": "4.8.2-19ubuntu1", -# "source": "apt", -# "arch": "amd64", -# "name": "gcc-4.8-base" -# }, -# { -# "version": "4.9.2-19ubuntu1", -# "source": "apt", -# "arch": "amd64", -# "name": "gcc-4.8-base" -# } -# ] -# } -''' - - -def rpm_package_list(): - import rpm - trans_set = rpm.TransactionSet() - installed_packages = {} - for package in trans_set.dbMatch(): - package_details = dict(name=package[rpm.RPMTAG_NAME], - version=package[rpm.RPMTAG_VERSION], - release=package[rpm.RPMTAG_RELEASE], - epoch=package[rpm.RPMTAG_EPOCH], - arch=package[rpm.RPMTAG_ARCH], - source='rpm') - if package_details['name'] not in installed_packages: - installed_packages[package_details['name']] = [package_details] - else: - installed_packages[package_details['name']].append(package_details) - return installed_packages - - -def deb_package_list(): - import apt - apt_cache = apt.Cache() - installed_packages = {} - apt_installed_packages = [pk for pk in apt_cache.keys() if apt_cache[pk].is_installed] - for package in apt_installed_packages: - ac_pkg = apt_cache[package].installed - package_details = dict(name=package, - version=ac_pkg.version, - arch=ac_pkg.architecture, - source='apt') - if package_details['name'] not in installed_packages: - installed_packages[package_details['name']] = [package_details] - else: - installed_packages[package_details['name']].append(package_details) - return installed_packages - - -def main(): - module = AnsibleModule( # noqa - argument_spec = dict(os_family=dict(required=True)) - ) - ans_os = module.params['os_family'] - if ans_os in ('RedHat', 'Suse', 'openSUSE Leap'): - packages = rpm_package_list() - elif ans_os == 'Debian': - packages = deb_package_list() - else: - packages = None - - if packages is not None: - results = dict(ansible_facts=dict(packages=packages)) - else: - results = dict(skipped=True, msg="Unsupported Distribution") - module.exit_json(**results) - - -main() diff --git a/awx/plugins/library/scan_services.py b/awx/plugins/library/scan_services.py deleted file mode 100644 index 5d8ccdbb74a4..000000000000 --- a/awx/plugins/library/scan_services.py +++ /dev/null @@ -1,190 +0,0 @@ -#!/usr/bin/env python - -import re -from ansible.module_utils.basic import * # noqa - -DOCUMENTATION = ''' ---- -module: scan_services -short_description: Return service state information as fact data -description: - - Return service state information as fact data for various service management utilities -version_added: "1.9" -options: -requirements: [ ] -author: Matthew Jones -''' - -EXAMPLES = ''' -- monit: scan_services -# Example fact output: -# host | success >> { -# "ansible_facts": { -# "services": { -# "network": { -# "source": "sysv", -# "state": "running", -# "name": "network" -# }, -# "arp-ethers.service": { -# "source": "systemd", -# "state": "stopped", -# "name": "arp-ethers.service" -# } -# } -# } -''' - - -class BaseService(object): - - def __init__(self, module): - self.module = module - self.incomplete_warning = False - - -class ServiceScanService(BaseService): - - def gather_services(self): - services = {} - service_path = self.module.get_bin_path("service") - if service_path is None: - return None - initctl_path = self.module.get_bin_path("initctl") - chkconfig_path = self.module.get_bin_path("chkconfig") - - # sysvinit - if service_path is not None and chkconfig_path is None: - rc, stdout, stderr = self.module.run_command("%s --status-all 2>&1 | grep -E \"\\[ (\\+|\\-) \\]\"" % service_path, use_unsafe_shell=True) - for line in stdout.split("\n"): - line_data = line.split() - if len(line_data) < 4: - continue # Skipping because we expected more data - service_name = " ".join(line_data[3:]) - if line_data[1] == "+": - service_state = "running" - else: - service_state = "stopped" - services[service_name] = {"name": service_name, "state": service_state, "source": "sysv"} - - # Upstart - if initctl_path is not None and chkconfig_path is None: - p = re.compile(r'^\s?(?P.*)\s(?P\w+)\/(?P\w+)(\,\sprocess\s(?P[0-9]+))?\s*$') - rc, stdout, stderr = self.module.run_command("%s list" % initctl_path) - real_stdout = stdout.replace("\r","") - for line in real_stdout.split("\n"): - m = p.match(line) - if not m: - continue - service_name = m.group('name') - service_goal = m.group('goal') - service_state = m.group('state') - if m.group('pid'): - pid = m.group('pid') - else: - pid = None # NOQA - payload = {"name": service_name, "state": service_state, "goal": service_goal, "source": "upstart"} - services[service_name] = payload - - # RH sysvinit - elif chkconfig_path is not None: - #print '%s --status-all | grep -E "is (running|stopped)"' % service_path - p = re.compile( - r'(?P.*?)\s+[0-9]:(?Pon|off)\s+[0-9]:(?Pon|off)\s+[0-9]:(?Pon|off)\s+' - r'[0-9]:(?Pon|off)\s+[0-9]:(?Pon|off)\s+[0-9]:(?Pon|off)\s+[0-9]:(?Pon|off)') - rc, stdout, stderr = self.module.run_command('%s' % chkconfig_path, use_unsafe_shell=True) - # Check for special cases where stdout does not fit pattern - match_any = False - for line in stdout.split('\n'): - if p.match(line): - match_any = True - if not match_any: - p_simple = re.compile(r'(?P.*?)\s+(?Pon|off)') - match_any = False - for line in stdout.split('\n'): - if p_simple.match(line): - match_any = True - if match_any: - # Try extra flags " -l --allservices" needed for SLES11 - rc, stdout, stderr = self.module.run_command('%s -l --allservices' % chkconfig_path, use_unsafe_shell=True) - elif '--list' in stderr: - # Extra flag needed for RHEL5 - rc, stdout, stderr = self.module.run_command('%s --list' % chkconfig_path, use_unsafe_shell=True) - for line in stdout.split('\n'): - m = p.match(line) - if m: - service_name = m.group('service') - service_state = 'stopped' - if m.group('rl3') == 'on': - rc, stdout, stderr = self.module.run_command('%s %s status' % (service_path, service_name), use_unsafe_shell=True) - service_state = rc - if rc in (0,): - service_state = 'running' - #elif rc in (1,3): - else: - if 'root' in stderr or 'permission' in stderr.lower() or 'not in sudoers' in stderr.lower(): - self.incomplete_warning = True - continue - else: - service_state = 'stopped' - service_data = {"name": service_name, "state": service_state, "source": "sysv"} - services[service_name] = service_data - return services - - -class SystemctlScanService(BaseService): - - def systemd_enabled(self): - # Check if init is the systemd command, using comm as cmdline could be symlink - try: - f = open('/proc/1/comm', 'r') - except IOError: - # If comm doesn't exist, old kernel, no systemd - return False - for line in f: - if 'systemd' in line: - return True - return False - - def gather_services(self): - services = {} - if not self.systemd_enabled(): - return None - systemctl_path = self.module.get_bin_path("systemctl", opt_dirs=["/usr/bin", "/usr/local/bin"]) - if systemctl_path is None: - return None - rc, stdout, stderr = self.module.run_command("%s list-unit-files --type=service | tail -n +2 | head -n -2" % systemctl_path, use_unsafe_shell=True) - for line in stdout.split("\n"): - line_data = line.split() - if len(line_data) != 2: - continue - if line_data[1] == "enabled": - state_val = "running" - else: - state_val = "stopped" - services[line_data[0]] = {"name": line_data[0], "state": state_val, "source": "systemd"} - return services - - -def main(): - module = AnsibleModule(argument_spec = dict()) # noqa - service_modules = (ServiceScanService, SystemctlScanService) - all_services = {} - incomplete_warning = False - for svc_module in service_modules: - svcmod = svc_module(module) - svc = svcmod.gather_services() - if svc is not None: - all_services.update(svc) - if svcmod.incomplete_warning: - incomplete_warning = True - if len(all_services) == 0: - results = dict(skipped=True, msg="Failed to find any services. Sometimes this is due to insufficient privileges.") - else: - results = dict(ansible_facts=dict(services=all_services)) - if incomplete_warning: - results['msg'] = "WARNING: Could not find status for all services. Sometimes this is due to insufficient privileges." - module.exit_json(**results) - - -main() diff --git a/awx/plugins/library/win_scan_files.ps1 b/awx/plugins/library/win_scan_files.ps1 deleted file mode 100644 index 6d114dfcc8ff..000000000000 --- a/awx/plugins/library/win_scan_files.ps1 +++ /dev/null @@ -1,102 +0,0 @@ -#!powershell -# This file is part of Ansible -# -# Ansible is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Ansible. If not, see . - -# WANT_JSON -# POWERSHELL_COMMON - -$params = Parse-Args $args $true; - -$paths = Get-Attr $params "paths" $FALSE; -If ($paths -eq $FALSE) -{ - Fail-Json (New-Object psobject) "missing required argument: paths"; -} - -$get_checksum = Get-Attr $params "get_checksum" $false | ConvertTo-Bool; -$recursive = Get-Attr $params "recursive" $false | ConvertTo-Bool; - -function Date_To_Timestamp($start_date, $end_date) -{ - If($start_date -and $end_date) - { - Write-Output (New-TimeSpan -Start $start_date -End $end_date).TotalSeconds - } -} - -$files = @() - -ForEach ($path In $paths) -{ - "Path: " + $path - ForEach ($file in Get-ChildItem $path -Recurse: $recursive) - { - "File: " + $file.FullName - $fileinfo = New-Object psobject - Set-Attr $fileinfo "path" $file.FullName - $info = Get-Item $file.FullName; - $iscontainer = Get-Attr $info "PSIsContainer" $null; - $length = Get-Attr $info "Length" $null; - $extension = Get-Attr $info "Extension" $null; - $attributes = Get-Attr $info "Attributes" ""; - If ($info) - { - $accesscontrol = $info.GetAccessControl(); - } - Else - { - $accesscontrol = $null; - } - $owner = Get-Attr $accesscontrol "Owner" $null; - $creationtime = Get-Attr $info "CreationTime" $null; - $lastaccesstime = Get-Attr $info "LastAccessTime" $null; - $lastwritetime = Get-Attr $info "LastWriteTime" $null; - - $epoch_date = Get-Date -Date "01/01/1970" - If ($iscontainer) - { - Set-Attr $fileinfo "isdir" $TRUE; - } - Else - { - Set-Attr $fileinfo "isdir" $FALSE; - Set-Attr $fileinfo "size" $length; - } - Set-Attr $fileinfo "extension" $extension; - Set-Attr $fileinfo "attributes" $attributes.ToString(); - # Set-Attr $fileinfo "owner" $getaccesscontrol.Owner; - # Set-Attr $fileinfo "owner" $info.GetAccessControl().Owner; - Set-Attr $fileinfo "owner" $owner; - Set-Attr $fileinfo "creationtime" (Date_To_Timestamp $epoch_date $creationtime); - Set-Attr $fileinfo "lastaccesstime" (Date_To_Timestamp $epoch_date $lastaccesstime); - Set-Attr $fileinfo "lastwritetime" (Date_To_Timestamp $epoch_date $lastwritetime); - - If (($get_checksum) -and -not $fileinfo.isdir) - { - $hash = Get-FileChecksum($file.FullName); - Set-Attr $fileinfo "checksum" $hash; - } - - $files += $fileinfo - } -} - -$result = New-Object psobject @{ - ansible_facts = New-Object psobject @{ - files = $files - } -} - -Exit-Json $result; diff --git a/awx/plugins/library/win_scan_packages.ps1 b/awx/plugins/library/win_scan_packages.ps1 deleted file mode 100644 index 2ab3fdbec6be..000000000000 --- a/awx/plugins/library/win_scan_packages.ps1 +++ /dev/null @@ -1,66 +0,0 @@ -#!powershell -# This file is part of Ansible -# -# Ansible is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Ansible. If not, see . - -# WANT_JSON -# POWERSHELL_COMMON - -$uninstall_native_path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" -$uninstall_wow6432_path = "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" - -if ([System.IntPtr]::Size -eq 4) { - - # This is a 32-bit Windows system, so we only check for 32-bit programs, which will be - # at the native registry location. - - [PSObject []]$packages = Get-ChildItem -Path $uninstall_native_path | - Get-ItemProperty | - Select-Object -Property @{Name="name"; Expression={$_."DisplayName"}}, - @{Name="version"; Expression={$_."DisplayVersion"}}, - @{Name="publisher"; Expression={$_."Publisher"}}, - @{Name="arch"; Expression={ "Win32" }} | - Where-Object { $_.name } - -} else { - - # This is a 64-bit Windows system, so we check for 64-bit programs in the native - # registry location, and also for 32-bit programs under Wow6432Node. - - [PSObject []]$packages = Get-ChildItem -Path $uninstall_native_path | - Get-ItemProperty | - Select-Object -Property @{Name="name"; Expression={$_."DisplayName"}}, - @{Name="version"; Expression={$_."DisplayVersion"}}, - @{Name="publisher"; Expression={$_."Publisher"}}, - @{Name="arch"; Expression={ "Win64" }} | - Where-Object { $_.name } - - $packages += Get-ChildItem -Path $uninstall_wow6432_path | - Get-ItemProperty | - Select-Object -Property @{Name="name"; Expression={$_."DisplayName"}}, - @{Name="version"; Expression={$_."DisplayVersion"}}, - @{Name="publisher"; Expression={$_."Publisher"}}, - @{Name="arch"; Expression={ "Win32" }} | - Where-Object { $_.name } - -} - -$result = New-Object psobject @{ - ansible_facts = New-Object psobject @{ - packages = $packages - } - changed = $false -} - -Exit-Json $result; diff --git a/awx/plugins/library/win_scan_services.ps1 b/awx/plugins/library/win_scan_services.ps1 deleted file mode 100644 index 3de8ac4c9b01..000000000000 --- a/awx/plugins/library/win_scan_services.ps1 +++ /dev/null @@ -1,30 +0,0 @@ -#!powershell -# This file is part of Ansible -# -# Ansible is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Ansible. If not, see . - -# WANT_JSON -# POWERSHELL_COMMON - -$result = New-Object psobject @{ - ansible_facts = New-Object psobject @{ - services = Get-Service | - Select-Object -Property @{Name="name"; Expression={$_."DisplayName"}}, - @{Name="win_svc_name"; Expression={$_."Name"}}, - @{Name="state"; Expression={$_."Status".ToString().ToLower()}} - } - changed = $false -} - -Exit-Json $result; diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index a84b9403eed4..71881918a35c 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -5,12 +5,9 @@ import re # noqa import sys from datetime import timedelta -from celery.schedules import crontab # global settings from django.conf import global_settings -# ugettext lazy -from django.utils.translation import ugettext_lazy as _ # Update this module's local settings from the global settings module. this_module = sys.modules[__name__] @@ -94,7 +91,7 @@ def IS_TESTING(argv=None): USE_TZ = True STATICFILES_DIRS = ( - os.path.join(BASE_DIR, 'ui', 'static'), + os.path.join(BASE_DIR, 'ui_next', 'build', 'static'), os.path.join(BASE_DIR, 'static'), ) @@ -119,15 +116,19 @@ def IS_TESTING(argv=None): # Absolute filesystem path to the directory to host projects (with playbooks). # This directory should not be web-accessible. -PROJECTS_ROOT = os.path.join(BASE_DIR, 'projects') +PROJECTS_ROOT = '/var/lib/awx/projects/' + +# Absolute filesystem path to the directory to host collections for +# running inventory imports, isolated playbooks +AWX_ANSIBLE_COLLECTIONS_PATHS = os.path.join(BASE_DIR, 'vendor', 'awx_ansible_collections') # Absolute filesystem path to the directory for job status stdout (default for # development and tests, default for production defined in production.py). This # directory should not be web-accessible -JOBOUTPUT_ROOT = os.path.join(BASE_DIR, 'job_output') +JOBOUTPUT_ROOT = '/var/lib/awx/job_status/' # Absolute filesystem path to the directory to store logs -LOG_ROOT = os.path.join(BASE_DIR) +LOG_ROOT = '/var/log/tower/' # The heartbeat file for the tower scheduler SCHEDULE_METADATA_LOCATION = os.path.join(BASE_DIR, '.tower_cycle') @@ -159,13 +160,13 @@ def IS_TESTING(argv=None): REMOTE_HOST_HEADERS = ['REMOTE_ADDR', 'REMOTE_HOST'] # If Tower is behind a reverse proxy/load balancer, use this setting to -# whitelist the proxy IP addresses from which Tower should trust custom +# allow the proxy IP addresses from which Tower should trust custom # REMOTE_HOST_HEADERS header values # REMOTE_HOST_HEADERS = ['HTTP_X_FORWARDED_FOR', ''REMOTE_ADDR', 'REMOTE_HOST'] -# PROXY_IP_WHITELIST = ['10.0.1.100', '10.0.1.101'] +# PROXY_IP_ALLOWED_LIST = ['10.0.1.100', '10.0.1.101'] # If this setting is an empty list (the default), the headers specified by # REMOTE_HOST_HEADERS will be trusted unconditionally') -PROXY_IP_WHITELIST = [] +PROXY_IP_ALLOWED_LIST = [] CUSTOM_VENV_PATHS = [] @@ -195,9 +196,23 @@ def IS_TESTING(argv=None): # events into the database JOB_EVENT_WORKERS = 4 +# The number of seconds to buffer callback receiver bulk +# writes in memory before flushing via JobEvent.objects.bulk_create() +JOB_EVENT_BUFFER_SECONDS = .1 + +# The interval at which callback receiver statistics should be +# recorded +JOB_EVENT_STATISTICS_INTERVAL = 5 + # The maximum size of the job event worker queue before requests are blocked JOB_EVENT_MAX_QUEUE_SIZE = 10000 +# The number of job events to migrate per-transaction when moving from int -> bigint +JOB_EVENT_MIGRATION_CHUNK_SIZE = 1000000 + +# The maximum allowed jobs to start on a given task manager cycle +START_TASK_LIMIT = 100 + # Disallow sending session cookies over insecure connections SESSION_COOKIE_SECURE = True @@ -233,20 +248,20 @@ def IS_TESTING(argv=None): 'django.template.context_processors.static', 'django.template.context_processors.tz', 'django.contrib.messages.context_processors.messages', - 'awx.ui.context_processors.settings', - 'awx.ui.context_processors.version', + 'awx.ui.context_processors.csp', 'social_django.context_processors.backends', 'social_django.context_processors.login_redirect', ], 'loaders': [( 'django.template.loaders.cached.Loader', ('django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader',), + 'django.template.loaders.app_directories.Loader',), )], 'builtins': ['awx.main.templatetags.swagger'], }, 'DIRS': [ os.path.join(BASE_DIR, 'templates'), + os.path.join(BASE_DIR, 'ui_next', 'build'), ], }, ] @@ -302,7 +317,7 @@ def IS_TESTING(argv=None): 'awx.api.parsers.JSONParser', ), 'DEFAULT_RENDERER_CLASSES': ( - 'rest_framework.renderers.JSONRenderer', + 'awx.api.renderers.DefaultJSONRenderer', 'awx.api.renderers.BrowsableAPIRenderer', ), 'DEFAULT_METADATA_CLASS': 'awx.api.metadata.Metadata', @@ -380,9 +395,6 @@ def IS_TESTING(argv=None): # when trying to access a UI page that requries authentication. LOGIN_REDIRECT_OVERRIDE = '' -# If set, serve only minified JS for UI. -USE_MINIFIED_JS = False - # Default to skipping isolated host key checking (the initial connection will # hang on an interactive "The authenticity of host example.org can't be # established" message) @@ -400,30 +412,13 @@ def IS_TESTING(argv=None): # The time (in seconds) between the periodic isolated heartbeat status check AWX_ISOLATED_PERIODIC_CHECK = 600 -# Verbosity level for isolated node management tasks -AWX_ISOLATED_VERBOSITY = 0 - -# Memcached django cache configuration -# CACHES = { -# 'default': { -# 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', -# 'LOCATION': '127.0.0.1:11211', -# 'TIMEOUT': 864000, -# 'KEY_PREFIX': 'tower_dev', -# } -# } - - DEVSERVER_DEFAULT_ADDR = '0.0.0.0' DEVSERVER_DEFAULT_PORT = '8013' # Set default ports for live server tests. os.environ.setdefault('DJANGO_LIVE_TEST_SERVER_ADDRESS', 'localhost:9013-9199') -BROKER_DURABILITY = True -BROKER_POOL_LIMIT = None -BROKER_URL = 'amqp://guest:guest@localhost:5672//' -CELERY_DEFAULT_QUEUE = 'awx_private_queue' +BROKER_URL = 'unix:///var/run/redis/redis.sock' CELERYBEAT_SCHEDULE = { 'tower_scheduler': { 'task': 'awx.main.tasks.awx_periodic_scheduler', @@ -435,13 +430,9 @@ def IS_TESTING(argv=None): 'schedule': timedelta(seconds=60), 'options': {'expires': 50,} }, - 'purge_stdout_files': { - 'task': 'awx.main.tasks.purge_old_stdout_files', - 'schedule': timedelta(days=7) - }, 'gather_analytics': { 'task': 'awx.main.tasks.gather_analytics', - 'schedule': crontab(hour='*/6') + 'schedule': timedelta(minutes=5) }, 'task_manager': { 'task': 'awx.main.scheduler.tasks.run_task_manager', @@ -455,26 +446,13 @@ def IS_TESTING(argv=None): }, # 'isolated_heartbeat': set up at the end of production.py and development.py } -AWX_INCONSISTENT_TASK_INTERVAL = 60 * 3 - -AWX_CELERY_QUEUES_STATIC = [ - CELERY_DEFAULT_QUEUE, -] - -AWX_CELERY_BCAST_QUEUES_STATIC = [ - 'tower_broadcast_all', -] - -ASGI_AMQP = { - 'INIT_FUNC': 'awx.prepare_env', - 'MODEL': 'awx.main.models.channels.ChannelGroup', -} # Django Caching Configuration +DJANGO_REDIS_IGNORE_EXCEPTIONS = True CACHES = { 'default': { - 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', - 'LOCATION': 'memcached:11211', + 'BACKEND': 'django_redis.cache.RedisCache', + 'LOCATION': 'unix:/var/run/redis/redis.sock?db=1' }, } @@ -508,6 +486,7 @@ def IS_TESTING(argv=None): 'awx.sso.pipeline.update_user_orgs', 'awx.sso.pipeline.update_user_teams', ) +SAML_AUTO_CREATE_OBJECTS = True SOCIAL_AUTH_LOGIN_URL = '/' SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/sso/complete/' @@ -595,28 +574,12 @@ def IS_TESTING(argv=None): # Note: This setting may be overridden by database settings. AWX_COLLECTIONS_ENABLED = True -# Settings for primary galaxy server, should be set in the UI -PRIMARY_GALAXY_URL = '' -PRIMARY_GALAXY_USERNAME = '' -PRIMARY_GALAXY_TOKEN = '' -PRIMARY_GALAXY_PASSWORD = '' -PRIMARY_GALAXY_AUTH_URL = '' - -# Settings for the public galaxy server(s). -PUBLIC_GALAXY_ENABLED = True -PUBLIC_GALAXY_SERVER = { - 'id': 'galaxy', - 'url': 'https://galaxy.ansible.com' -} +# Follow symlinks when scanning for playbooks +AWX_SHOW_PLAYBOOK_LINKS = False # Applies to any galaxy server GALAXY_IGNORE_CERTS = False -# List of dicts of fallback (additional) Galaxy servers. If configured, these -# will be higher precedence than public Galaxy, but lower than primary Galaxy. -# Available options: 'id', 'url', 'username', 'password', 'token', 'auth_url' -FALLBACK_GALAXY_SERVERS = [] - # Enable bubblewrap support for running jobs (playbook runs only). # Note: This setting may be overridden by database settings. AWX_PROOT_ENABLED = True @@ -665,6 +628,8 @@ def IS_TESTING(argv=None): # Note: This setting may be overridden by database settings. INSIGHTS_TRACKING_STATE = False +# Last gather date for Analytics +AUTOMATION_ANALYTICS_LAST_GATHER = None # Default list of modules allowed for ad hoc commands. # Note: This setting may be overridden by database settings. @@ -690,161 +655,37 @@ def IS_TESTING(argv=None): 'win_user', ] -INV_ENV_VARIABLE_BLACKLIST = ("HOME", "USER", "_", "TERM") +INV_ENV_VARIABLE_BLOCKED = ("HOME", "USER", "_", "TERM") # ---------------- # -- Amazon EC2 -- # ---------------- - -# AWS does not appear to provide pretty region names via any API, so store the -# list of names here. The available region IDs will be pulled from boto. -# http://docs.aws.amazon.com/general/latest/gr/rande.html#ec2_region -EC2_REGION_NAMES = { - 'us-east-1': _('US East (Northern Virginia)'), - 'us-east-2': _('US East (Ohio)'), - 'us-west-2': _('US West (Oregon)'), - 'us-west-1': _('US West (Northern California)'), - 'ca-central-1': _('Canada (Central)'), - 'eu-central-1': _('EU (Frankfurt)'), - 'eu-west-1': _('EU (Ireland)'), - 'eu-west-2': _('EU (London)'), - 'ap-southeast-1': _('Asia Pacific (Singapore)'), - 'ap-southeast-2': _('Asia Pacific (Sydney)'), - 'ap-northeast-1': _('Asia Pacific (Tokyo)'), - 'ap-northeast-2': _('Asia Pacific (Seoul)'), - 'ap-south-1': _('Asia Pacific (Mumbai)'), - 'sa-east-1': _('South America (Sao Paulo)'), - 'us-gov-west-1': _('US West (GovCloud)'), - 'cn-north-1': _('China (Beijing)'), -} - -EC2_REGIONS_BLACKLIST = [ - 'us-gov-west-1', - 'cn-north-1', -] - -# Inventory variable name/values for determining if host is active/enabled. EC2_ENABLED_VAR = 'ec2_state' EC2_ENABLED_VALUE = 'running' - -# Inventory variable name containing unique instance ID. -EC2_INSTANCE_ID_VAR = 'ec2_id' - -# Filter for allowed group/host names when importing inventory from EC2. -EC2_GROUP_FILTER = r'^.+$' -EC2_HOST_FILTER = r'^.+$' +EC2_INSTANCE_ID_VAR = 'instance_id' EC2_EXCLUDE_EMPTY_GROUPS = True - # ------------ # -- VMware -- # ------------ -VMWARE_REGIONS_BLACKLIST = [] - -# Inventory variable name/values for determining whether a host is -# active in vSphere. VMWARE_ENABLED_VAR = 'guest.gueststate' VMWARE_ENABLED_VALUE = 'running' - -# Inventory variable name containing the unique instance ID. -VMWARE_INSTANCE_ID_VAR = 'config.instanceuuid' - -# Filter for allowed group and host names when importing inventory -# from VMware. -VMWARE_GROUP_FILTER = r'^.+$' -VMWARE_HOST_FILTER = r'^.+$' +VMWARE_INSTANCE_ID_VAR = 'config.instanceUuid, config.instanceuuid' VMWARE_EXCLUDE_EMPTY_GROUPS = True VMWARE_VALIDATE_CERTS = False + # --------------------------- # -- Google Compute Engine -- # --------------------------- - -# It's not possible to get zones in GCE without authenticating, so we -# provide a list here. -# Source: https://developers.google.com/compute/docs/zones -GCE_REGION_CHOICES = [ - ('us-east1-b', _('US East 1 (B)')), - ('us-east1-c', _('US East 1 (C)')), - ('us-east1-d', _('US East 1 (D)')), - ('us-east4-a', _('US East 4 (A)')), - ('us-east4-b', _('US East 4 (B)')), - ('us-east4-c', _('US East 4 (C)')), - ('us-central1-a', _('US Central (A)')), - ('us-central1-b', _('US Central (B)')), - ('us-central1-c', _('US Central (C)')), - ('us-central1-f', _('US Central (F)')), - ('us-west1-a', _('US West (A)')), - ('us-west1-b', _('US West (B)')), - ('us-west1-c', _('US West (C)')), - ('europe-west1-b', _('Europe West 1 (B)')), - ('europe-west1-c', _('Europe West 1 (C)')), - ('europe-west1-d', _('Europe West 1 (D)')), - ('europe-west2-a', _('Europe West 2 (A)')), - ('europe-west2-b', _('Europe West 2 (B)')), - ('europe-west2-c', _('Europe West 2 (C)')), - ('asia-east1-a', _('Asia East (A)')), - ('asia-east1-b', _('Asia East (B)')), - ('asia-east1-c', _('Asia East (C)')), - ('asia-southeast1-a', _('Asia Southeast (A)')), - ('asia-southeast1-b', _('Asia Southeast (B)')), - ('asia-northeast1-a', _('Asia Northeast (A)')), - ('asia-northeast1-b', _('Asia Northeast (B)')), - ('asia-northeast1-c', _('Asia Northeast (C)')), - ('australia-southeast1-a', _('Australia Southeast (A)')), - ('australia-southeast1-b', _('Australia Southeast (B)')), - ('australia-southeast1-c', _('Australia Southeast (C)')), -] -GCE_REGIONS_BLACKLIST = [] - -# Inventory variable name/value for determining whether a host is active -# in Google Compute Engine. GCE_ENABLED_VAR = 'status' GCE_ENABLED_VALUE = 'running' - -# Filter for allowed group and host names when importing inventory from -# Google Compute Engine. -GCE_GROUP_FILTER = r'^.+$' -GCE_HOST_FILTER = r'^.+$' GCE_EXCLUDE_EMPTY_GROUPS = True GCE_INSTANCE_ID_VAR = 'gce_id' # -------------------------------------- # -- Microsoft Azure Resource Manager -- # -------------------------------------- -# It's not possible to get zones in Azure without authenticating, so we -# provide a list here. -AZURE_RM_REGION_CHOICES = [ - ('eastus', _('US East')), - ('eastus2', _('US East 2')), - ('centralus', _('US Central')), - ('northcentralus', _('US North Central')), - ('southcentralus', _('US South Central')), - ('westcentralus', _('US West Central')), - ('westus', _('US West')), - ('westus2', _('US West 2')), - ('canadaeast', _('Canada East')), - ('canadacentral', _('Canada Central')), - ('brazilsouth', _('Brazil South')), - ('northeurope', _('Europe North')), - ('westeurope', _('Europe West')), - ('ukwest', _('UK West')), - ('uksouth', _('UK South')), - ('eastasia', _('Asia East')), - ('southestasia', _('Asia Southeast')), - ('australiaeast', _('Australia East')), - ('australiasoutheast', _('Australia Southeast')), - ('westindia', _('India West')), - ('southindia', _('India South')), - ('japaneast', _('Japan East')), - ('japanwest', _('Japan West')), - ('koreacentral', _('Korea Central')), - ('koreasouth', _('Korea South')), -] -AZURE_RM_REGIONS_BLACKLIST = [] - -AZURE_RM_GROUP_FILTER = r'^.+$' -AZURE_RM_HOST_FILTER = r'^.+$' AZURE_RM_ENABLED_VAR = 'powerstate' AZURE_RM_ENABLED_VALUE = 'running' AZURE_RM_INSTANCE_ID_VAR = 'id' @@ -855,8 +696,6 @@ def IS_TESTING(argv=None): # --------------------- OPENSTACK_ENABLED_VAR = 'status' OPENSTACK_ENABLED_VALUE = 'ACTIVE' -OPENSTACK_GROUP_FILTER = r'^.+$' -OPENSTACK_HOST_FILTER = r'^.+$' OPENSTACK_EXCLUDE_EMPTY_GROUPS = True OPENSTACK_INSTANCE_ID_VAR = 'openstack.id' @@ -865,8 +704,6 @@ def IS_TESTING(argv=None): # --------------------- RHV_ENABLED_VAR = 'status' RHV_ENABLED_VALUE = 'up' -RHV_GROUP_FILTER = r'^.+$' -RHV_HOST_FILTER = r'^.+$' RHV_EXCLUDE_EMPTY_GROUPS = True RHV_INSTANCE_ID_VAR = 'id' @@ -875,8 +712,6 @@ def IS_TESTING(argv=None): # --------------------- TOWER_ENABLED_VAR = 'remote_tower_enabled' TOWER_ENABLED_VALUE = 'true' -TOWER_GROUP_FILTER = r'^.+$' -TOWER_HOST_FILTER = r'^.+$' TOWER_EXCLUDE_EMPTY_GROUPS = True TOWER_INSTANCE_ID_VAR = 'remote_tower_id' @@ -885,29 +720,15 @@ def IS_TESTING(argv=None): # --------------------- SATELLITE6_ENABLED_VAR = 'foreman.enabled' SATELLITE6_ENABLED_VALUE = 'True' -SATELLITE6_GROUP_FILTER = r'^.+$' -SATELLITE6_HOST_FILTER = r'^.+$' SATELLITE6_EXCLUDE_EMPTY_GROUPS = True SATELLITE6_INSTANCE_ID_VAR = 'foreman.id' # SATELLITE6_GROUP_PREFIX and SATELLITE6_GROUP_PATTERNS defined in source vars -# --------------------- -# ----- CloudForms ----- -# --------------------- -CLOUDFORMS_ENABLED_VAR = 'cloudforms.power_state' -CLOUDFORMS_ENABLED_VALUE = 'on' -CLOUDFORMS_GROUP_FILTER = r'^.+$' -CLOUDFORMS_HOST_FILTER = r'^.+$' -CLOUDFORMS_EXCLUDE_EMPTY_GROUPS = True -CLOUDFORMS_INSTANCE_ID_VAR = 'cloudforms.id' - # --------------------- # ----- Custom ----- # --------------------- #CUSTOM_ENABLED_VAR = #CUSTOM_ENABLED_VALUE = -CUSTOM_GROUP_FILTER = r'^.+$' -CUSTOM_HOST_FILTER = r'^.+$' CUSTOM_EXCLUDE_EMPTY_GROUPS = False #CUSTOM_INSTANCE_ID_VAR = @@ -916,8 +737,6 @@ def IS_TESTING(argv=None): # --------------------- #SCM_ENABLED_VAR = #SCM_ENABLED_VALUE = -SCM_GROUP_FILTER = r'^.+$' -SCM_HOST_FILTER = r'^.+$' SCM_EXCLUDE_EMPTY_GROUPS = False #SCM_INSTANCE_ID_VAR = @@ -929,22 +748,8 @@ def IS_TESTING(argv=None): ACTIVITY_STREAM_ENABLED = True ACTIVITY_STREAM_ENABLED_FOR_INVENTORY_SYNC = False -# Internal API URL for use by inventory scripts and callback plugin. -INTERNAL_API_URL = 'http://127.0.0.1:%s' % DEVSERVER_DEFAULT_PORT - -PERSISTENT_CALLBACK_MESSAGES = True -USE_CALLBACK_QUEUE = True CALLBACK_QUEUE = "callback_tasks" -SCHEDULER_QUEUE = "scheduler" - -TASK_COMMAND_PORT = 6559 - -SOCKETIO_NOTIFICATION_PORT = 6557 -SOCKETIO_LISTEN_PORT = 8080 - -FACT_CACHE_PORT = 6564 - # Note: This setting may be overridden by database settings. ORG_ADMINS_CAN_SEE_ALL_USERS = True MANAGE_ORGANIZATION_AUTH = True @@ -962,12 +767,28 @@ def IS_TESTING(argv=None): 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 # The number of retry attempts for websocket session establishment # If you're encountering issues establishing websockets in clustered Tower, # raising this value can help CHANNEL_LAYER_RECEIVE_MAX_RETRY = 10 +ASGI_APPLICATION = "awx.main.routing.application" + +CHANNEL_LAYERS = { + "default": { + "BACKEND": "channels_redis.core.RedisChannelLayer", + "CONFIG": { + "hosts": [BROKER_URL], + "capacity": 10000, + "group_expiry": 157784760, # 5 years + }, + }, +} + # Logging configuration. LOGGING = { 'version': 1, @@ -1025,8 +846,9 @@ def IS_TESTING(argv=None): 'formatter': 'simple', }, 'external_logger': { - 'class': 'awx.main.utils.handlers.AWXProxyHandler', + 'class': 'awx.main.utils.handlers.RSysLogHandler', 'formatter': 'json', + 'address': '/var/run/awx-rsyslog/rsyslog.sock', 'filters': ['external_log_enabled', 'dynamic_level_filter'], }, 'tower_warnings': { @@ -1056,6 +878,15 @@ def IS_TESTING(argv=None): 'backupCount': 5, 'formatter':'dispatcher', }, + 'wsbroadcast': { + # don't define a level here, it's set by settings.LOG_AGGREGATOR_LEVEL + 'class': 'logging.handlers.RotatingFileHandler', + 'filters': ['require_debug_false', 'dynamic_level_filter'], + 'filename': os.path.join(LOG_ROOT, 'wsbroadcast.log'), + 'maxBytes': 1024 * 1024 * 5, # 5 MB + 'backupCount': 5, + 'formatter':'simple', + }, 'celery.beat': { 'class':'logging.StreamHandler', 'level': 'ERROR' @@ -1101,6 +932,14 @@ def IS_TESTING(argv=None): 'backupCount': 5, 'formatter':'simple', }, + 'isolated_manager': { + 'level': 'WARNING', + 'class':'logging.handlers.RotatingFileHandler', + 'filename': os.path.join(LOG_ROOT, 'isolated_manager.log'), + 'maxBytes': 1024 * 1024 * 5, # 5 MB + 'backupCount': 5, + 'formatter':'simple', + }, }, 'loggers': { 'django': { @@ -1110,13 +949,9 @@ def IS_TESTING(argv=None): 'handlers': ['console', 'file', 'tower_warnings'], 'level': 'WARNING', }, - 'celery': { # for celerybeat connection warnings - 'handlers': ['console', 'file', 'tower_warnings'], - 'level': 'WARNING', - }, - 'kombu': { + 'daphne': { 'handlers': ['console', 'file', 'tower_warnings'], - 'level': 'WARNING', + 'level': 'INFO', }, 'rest_framework.request': { 'handlers': ['console', 'file', 'tower_warnings'], @@ -1142,12 +977,23 @@ def IS_TESTING(argv=None): 'handlers': ['null'] }, 'awx.main.commands.run_callback_receiver': { - 'handlers': ['callback_receiver'], - 'level': 'INFO' # in debug mode, includes full callback data + 'handlers': ['callback_receiver'], # level handled by dynamic_level_filter }, 'awx.main.dispatch': { 'handlers': ['dispatcher'], }, + 'awx.main.consumers': { + 'handlers': ['console', 'file', 'tower_warnings'], + 'level': 'INFO', + }, + 'awx.main.wsbroadcast': { + 'handlers': ['wsbroadcast'], + }, + 'awx.isolated.manager': { + 'level': 'WARNING', + 'handlers': ['console', 'file', 'isolated_manager'], + 'propagate': True + }, 'awx.isolated.manager.playbooks': { 'handlers': ['management_playbooks'], 'propagate': False @@ -1160,6 +1006,11 @@ def IS_TESTING(argv=None): '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 @@ -1196,7 +1047,6 @@ def IS_TESTING(argv=None): }, } } -LOG_AGGREGATOR_AUDIT = False # Apply coloring to messages logged to the console COLOR_LOGS = False @@ -1221,6 +1071,9 @@ def IS_TESTING(argv=None): # AWX_REQUEST_PROFILE_WITH_DOT = False +# Allow profiling callback workers via SIGUSR1 +AWX_CALLBACK_PROFILE = False + # Delete temporary directories created to store playbook run-time AWX_CLEANUP_PATHS = True @@ -1234,9 +1087,34 @@ def IS_TESTING(argv=None): 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', - 'awx.main.middleware.ActivityStreamMiddleware', 'awx.sso.middleware.SocialAuthMiddleware', 'crum.CurrentRequestUserMiddleware', 'awx.main.middleware.URLModificationMiddleware', 'awx.main.middleware.SessionTimeoutMiddleware', ] + +# Secret header value to exchange for websockets responsible for distributing websocket messages. +# This needs to be kept secret and randomly generated +BROADCAST_WEBSOCKET_SECRET = '' + +# Port for broadcast websockets to connect to +# Note: that the clients will follow redirect responses +BROADCAST_WEBSOCKET_PORT = 443 + +# Whether or not broadcast websockets should check nginx certs when interconnecting +BROADCAST_WEBSOCKET_VERIFY_CERT = False + +# Connect to other AWX nodes using http or https +BROADCAST_WEBSOCKET_PROTOCOL = 'https' + +# All websockets that connect to the broadcast websocket endpoint will be put into this group +BROADCAST_WEBSOCKET_GROUP_NAME = 'broadcast-group_send' + +# Time wait before retrying connecting to a websocket broadcast tower node +BROADCAST_WEBSOCKET_RECONNECT_RETRY_RATE_SECONDS = 5 + +# How often websocket process will look for changes in the Instance table +BROADCAST_WEBSOCKET_NEW_INSTANCE_POLL_RATE_SECONDS = 10 + +# How often websocket process will generate stats +BROADCAST_WEBSOCKET_STATS_POLL_RATE_SECONDS = 5 diff --git a/awx/settings/development.py b/awx/settings/development.py index 184e7ab63b97..9846705fa548 100644 --- a/awx/settings/development.py +++ b/awx/settings/development.py @@ -21,7 +21,6 @@ # Load default settings. from .defaults import * # NOQA -# don't use memcache when running tests if "pytest" in sys.modules: CACHES = { 'default': { @@ -148,7 +147,10 @@ include(optional('/etc/tower/settings.py'), scope=locals()) include(optional('/etc/tower/conf.d/*.py'), scope=locals()) -BASE_VENV_PATH = "/venv/" +# Installed differently in Dockerfile compared to production versions +AWX_ANSIBLE_COLLECTIONS_PATHS = '/var/lib/awx/vendor/awx_ansible_collections' + +BASE_VENV_PATH = "/var/lib/awx/venv/" ANSIBLE_VENV_PATH = os.path.join(BASE_VENV_PATH, "ansible") AWX_VENV_PATH = os.path.join(BASE_VENV_PATH, "awx") @@ -176,6 +178,12 @@ if 'Docker Desktop' in os.getenv('OS', ''): os.environ['SDB_NOTIFY_HOST'] = 'docker.for.mac.host.internal' else: - os.environ['SDB_NOTIFY_HOST'] = os.popen('ip route').read().split(' ')[2] + try: + os.environ['SDB_NOTIFY_HOST'] = os.popen('ip route').read().split(' ')[2] + except Exception: + pass + +AWX_CALLBACK_PROFILE = True -WEBSOCKET_ORIGIN_WHITELIST = ['https://localhost:8043', 'https://localhost:3000'] +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 diff --git a/awx/settings/local_settings.py.docker_compose b/awx/settings/local_settings.py.docker_compose index 42e5a3cd7403..88ef90fd641c 100644 --- a/awx/settings/local_settings.py.docker_compose +++ b/awx/settings/local_settings.py.docker_compose @@ -12,7 +12,6 @@ # MISC PROJECT SETTINGS ############################################################################### import os -import urllib.parse import sys # Enable the following lines and install the browser extension to use Django debug toolbar @@ -49,65 +48,12 @@ if "pytest" in sys.modules: } } -# AMQP configuration. -BROKER_URL = "amqp://{}:{}@{}/{}".format(os.environ.get("RABBITMQ_USER"), - os.environ.get("RABBITMQ_PASS"), - os.environ.get("RABBITMQ_HOST"), - urllib.parse.quote(os.environ.get("RABBITMQ_VHOST", "/"), safe='')) - -CHANNEL_LAYERS = { - 'default': {'BACKEND': 'asgi_amqp.AMQPChannelLayer', - 'ROUTING': 'awx.main.routing.channel_routing', - 'CONFIG': {'url': BROKER_URL}} -} - -# Absolute filesystem path to the directory to host projects (with playbooks). -# This directory should NOT be web-accessible. -PROJECTS_ROOT = '/var/lib/awx/projects/' - -# Absolute filesystem path to the directory for job status stdout -# This directory should not be web-accessible -JOBOUTPUT_ROOT = os.path.join(BASE_DIR, 'job_status') +# Location for cross-development of inventory plugins +AWX_ANSIBLE_COLLECTIONS_PATHS = '/var/lib/awx/vendor/awx_ansible_collections' # The UUID of the system, for HA. SYSTEM_UUID = '00000000-0000-0000-0000-000000000000' -# Local time zone for this installation. Choices can be found here: -# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name -# although not all choices may be available on all operating systems. -# On Unix systems, a value of None will cause Django to use the same -# timezone as the operating system. -# If running in a Windows environment this must be set to the same as your -# system time zone. -USE_TZ = True -TIME_ZONE = 'UTC' - -# Language code for this installation. All choices can be found here: -# http://www.i18nguy.com/unicode/language-identifiers.html -LANGUAGE_CODE = 'en-us' - -# SECURITY WARNING: keep the secret key used in production secret! -# Hardcoded values can leak through source control. Consider loading -# the secret key from an environment variable or a file instead. -SECRET_KEY = 'p7z7g1ql4%6+(6nlebb6hdk7sd^&fnjpal308%n%+p^_e6vo1y' - -# 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. -REMOTE_HOST_HEADERS = ['REMOTE_ADDR', 'REMOTE_HOST'] - -# If Tower is behind a reverse proxy/load balancer, use this setting to -# whitelist the proxy IP addresses from which Tower should trust custom -# REMOTE_HOST_HEADERS header values -# REMOTE_HOST_HEADERS = ['HTTP_X_FORWARDED_FOR', ''REMOTE_ADDR', 'REMOTE_HOST'] -# PROXY_IP_WHITELIST = ['10.0.1.100', '10.0.1.101'] -# If this setting is an empty list (the default), the headers specified by -# REMOTE_HOST_HEADERS will be trusted unconditionally') -PROXY_IP_WHITELIST = [] - -# Define additional environment variables to be passed to ansible subprocesses -#AWX_TASK_ENV['FOO'] = 'BAR' - # If set, use -vvv for project updates instead of -v for more output. # PROJECT_UPDATE_VVV=True @@ -118,40 +64,6 @@ PROXY_IP_WHITELIST = [] # Enable logging to syslog. Setting level to ERROR captures 500 errors, # WARNING also logs 4xx responses. -LOGGING['handlers']['syslog'] = { - 'level': 'WARNING', - 'filters': ['require_debug_false'], - 'class': 'logging.NullHandler', - 'formatter': 'simple', -} - -LOGGING['loggers']['django.request']['handlers'] = ['console'] -LOGGING['loggers']['rest_framework.request']['handlers'] = ['console'] -LOGGING['loggers']['awx']['handlers'] = ['console', 'external_logger'] -LOGGING['loggers']['awx.main.commands.run_callback_receiver']['handlers'] = [] # propogates to awx -LOGGING['loggers']['awx.main.tasks']['handlers'] = ['console', 'external_logger'] -LOGGING['loggers']['awx.main.scheduler']['handlers'] = ['console', 'external_logger'] -LOGGING['loggers']['django_auth_ldap']['handlers'] = ['console'] -LOGGING['loggers']['social']['handlers'] = ['console'] -LOGGING['loggers']['system_tracking_migrations']['handlers'] = ['console'] -LOGGING['loggers']['rbac_migrations']['handlers'] = ['console'] -LOGGING['loggers']['awx.isolated.manager.playbooks']['handlers'] = ['console'] -LOGGING['handlers']['callback_receiver'] = {'class': 'logging.NullHandler'} -LOGGING['handlers']['fact_receiver'] = {'class': 'logging.NullHandler'} -LOGGING['handlers']['task_system'] = {'class': 'logging.NullHandler'} -LOGGING['handlers']['tower_warnings'] = {'class': 'logging.NullHandler'} -LOGGING['handlers']['rbac_migrations'] = {'class': 'logging.NullHandler'} -LOGGING['handlers']['system_tracking_migrations'] = {'class': 'logging.NullHandler'} -LOGGING['handlers']['management_playbooks'] = {'class': 'logging.NullHandler'} - - -# Enable the following lines to also log to a file. -#LOGGING['handlers']['file'] = { -# 'class': 'logging.FileHandler', -# 'filename': os.path.join(BASE_DIR, 'awx.log'), -# 'formatter': 'simple', -#} - # Enable the following lines to turn on lots of permissions-related logging. #LOGGING['loggers']['awx.main.access']['level'] = 'DEBUG' #LOGGING['loggers']['awx.main.signals']['level'] = 'DEBUG' @@ -164,77 +76,7 @@ LOGGING['handlers']['management_playbooks'] = {'class': 'logging.NullHandler'} #LOGGING['loggers']['django_auth_ldap']['handlers'] = ['console'] #LOGGING['loggers']['django_auth_ldap']['level'] = 'DEBUG' -############################################################################### -# SCM TEST SETTINGS -############################################################################### - -# Define these variables to enable more complete testing of project support for -# SCM updates. The test repositories listed do not have to contain any valid -# playbooks. - -try: - path = os.path.expanduser(os.path.expandvars('~/.ssh/id_rsa')) - TEST_SSH_KEY_DATA = open(path, 'rb').read() -except IOError: - TEST_SSH_KEY_DATA = '' - -TEST_GIT_USERNAME = '' -TEST_GIT_PASSWORD = '' -TEST_GIT_KEY_DATA = TEST_SSH_KEY_DATA -TEST_GIT_PUBLIC_HTTPS = 'https://github.com/ansible/ansible.github.com.git' -TEST_GIT_PRIVATE_HTTPS = 'https://github.com/ansible/product-docs.git' -TEST_GIT_PRIVATE_SSH = 'git@github.com:ansible/product-docs.git' - -TEST_HG_USERNAME = '' -TEST_HG_PASSWORD = '' -TEST_HG_KEY_DATA = TEST_SSH_KEY_DATA -TEST_HG_PUBLIC_HTTPS = 'https://bitbucket.org/cchurch/django-hotrunner' -TEST_HG_PRIVATE_HTTPS = '' -TEST_HG_PRIVATE_SSH = '' - -TEST_SVN_USERNAME = '' -TEST_SVN_PASSWORD = '' -TEST_SVN_PUBLIC_HTTPS = 'https://github.com/ansible/ansible.github.com' -TEST_SVN_PRIVATE_HTTPS = 'https://github.com/ansible/product-docs' - -# To test repo access via SSH login to localhost. -import getpass -try: - TEST_SSH_LOOPBACK_USERNAME = getpass.getuser() -except KeyError: - TEST_SSH_LOOPBACK_USERNAME = 'root' -TEST_SSH_LOOPBACK_PASSWORD = '' - -############################################################################### -# INVENTORY IMPORT TEST SETTINGS -############################################################################### - -# Define these variables to enable more complete testing of inventory import -# from cloud providers. - -# EC2 credentials -TEST_AWS_ACCESS_KEY_ID = '' -TEST_AWS_SECRET_ACCESS_KEY = '' -TEST_AWS_REGIONS = 'all' -# Check IAM STS credentials -TEST_AWS_SECURITY_TOKEN = '' - -# Rackspace credentials -TEST_RACKSPACE_USERNAME = '' -TEST_RACKSPACE_API_KEY = '' -TEST_RACKSPACE_REGIONS = 'all' - -# VMware credentials -TEST_VMWARE_HOST = '' -TEST_VMWARE_USER = '' -TEST_VMWARE_PASSWORD = '' - -# OpenStack credentials -TEST_OPENSTACK_HOST = '' -TEST_OPENSTACK_USER = '' -TEST_OPENSTACK_PASSWORD = '' -TEST_OPENSTACK_PROJECT = '' - -# Azure credentials. -TEST_AZURE_USERNAME = '' -TEST_AZURE_KEY_DATA = '' +BROADCAST_WEBSOCKET_SECRET = '🤖starscream🤖' +BROADCAST_WEBSOCKET_PORT = 8013 +BROADCAST_WEBSOCKET_VERIFY_CERT = False +BROADCAST_WEBSOCKET_PROTOCOL = 'http' diff --git a/awx/settings/local_settings.py.example b/awx/settings/local_settings.py.example deleted file mode 100644 index d9de08cc5ad2..000000000000 --- a/awx/settings/local_settings.py.example +++ /dev/null @@ -1,199 +0,0 @@ -# Copyright (c) 2015 Ansible, Inc. (formerly AnsibleWorks, Inc.) -# All Rights Reserved. - -# Local Django settings for AWX project. Rename to "local_settings.py" and -# edit as needed for your development environment. - -# All variables defined in awx/settings/development.py will already be loaded -# into the global namespace before this file is loaded, to allow for reading -# and updating the default settings as needed. - -############################################################################### -# MISC PROJECT SETTINGS -############################################################################### - -# Database settings to use PostgreSQL for development. -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'NAME': 'awx-dev', - 'USER': 'awx-dev', - 'PASSWORD': 'AWXsome1', - 'HOST': 'localhost', - 'PORT': '', - } -} - -# 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 is_testing(sys.argv): - DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'awx.sqlite3'), - 'TEST': { - # Test database cannot be :memory: for tests. - 'NAME': os.path.join(BASE_DIR, 'awx_test.sqlite3'), - }, - } - } - -# AMQP configuration. -BROKER_URL = 'amqp://guest:guest@localhost:5672' - -# Absolute filesystem path to the directory to host projects (with playbooks). -# This directory should NOT be web-accessible. -PROJECTS_ROOT = os.path.join(BASE_DIR, 'projects') - -# Absolute filesystem path to the directory for job status stdout -# This directory should not be web-accessible -JOBOUTPUT_ROOT = os.path.join(BASE_DIR, 'job_status') - -# The UUID of the system, for HA. -SYSTEM_UUID = '00000000-0000-0000-0000-000000000000' - -# Local time zone for this installation. Choices can be found here: -# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name -# although not all choices may be available on all operating systems. -# On Unix systems, a value of None will cause Django to use the same -# timezone as the operating system. -# If running in a Windows environment this must be set to the same as your -# system time zone. -TIME_ZONE = None - -# Language code for this installation. All choices can be found here: -# http://www.i18nguy.com/unicode/language-identifiers.html -LANGUAGE_CODE = 'en-us' - -# SECURITY WARNING: keep the secret key used in production secret! -# Hardcoded values can leak through source control. Consider loading -# the secret key from an environment variable or a file instead. -SECRET_KEY = 'p7z7g1ql4%6+(6nlebb6hdk7sd^&fnjpal308%n%+p^_e6vo1y' - -# 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. -REMOTE_HOST_HEADERS = ['REMOTE_ADDR', 'REMOTE_HOST'] - -# If Tower is behind a reverse proxy/load balancer, use this setting to -# whitelist the proxy IP addresses from which Tower should trust custom -# REMOTE_HOST_HEADERS header values -# REMOTE_HOST_HEADERS = ['HTTP_X_FORWARDED_FOR', ''REMOTE_ADDR', 'REMOTE_HOST'] -# PROXY_IP_WHITELIST = ['10.0.1.100', '10.0.1.101'] -# If this setting is an empty list (the default), the headers specified by -# REMOTE_HOST_HEADERS will be trusted unconditionally') -PROXY_IP_WHITELIST = [] - -# Define additional environment variables to be passed to ansible subprocesses -#AWX_TASK_ENV['FOO'] = 'BAR' - -# If set, use -vvv for project updates instead of -v for more output. -# PROJECT_UPDATE_VVV=True - -############################################################################### -# LOGGING SETTINGS -############################################################################### - -# Enable logging to syslog. Setting level to ERROR captures 500 errors, -# WARNING also logs 4xx responses. -LOGGING['handlers']['syslog'] = { - 'level': 'WARNING', - 'filters': [], - 'class': 'logging.handlers.SysLogHandler', - 'address': '/dev/log', - 'facility': 'local0', - 'formatter': 'simple', -} - -# Enable the following lines to also log to a file. -#LOGGING['handlers']['file'] = { -# 'class': 'logging.FileHandler', -# 'filename': os.path.join(BASE_DIR, 'awx.log'), -# 'formatter': 'simple', -#} - -# Enable the following lines to turn on lots of permissions-related logging. -#LOGGING['loggers']['awx.main.access']['level'] = 'DEBUG' -#LOGGING['loggers']['awx.main.signals']['level'] = 'DEBUG' -#LOGGING['loggers']['awx.main.permissions']['level'] = 'DEBUG' - -# Enable the following line to turn on database settings logging. -#LOGGING['loggers']['awx.conf']['level'] = 'DEBUG' - -# Enable the following lines to turn on LDAP auth logging. -#LOGGING['loggers']['django_auth_ldap']['handlers'] = ['console'] -#LOGGING['loggers']['django_auth_ldap']['level'] = 'DEBUG' - -############################################################################### -# SCM TEST SETTINGS -############################################################################### - -# Define these variables to enable more complete testing of project support for -# SCM updates. The test repositories listed do not have to contain any valid -# playbooks. - -try: - path = os.path.expanduser(os.path.expandvars('~/.ssh/id_rsa')) - TEST_SSH_KEY_DATA = file(path, 'rb').read() -except IOError: - TEST_SSH_KEY_DATA = '' - -TEST_GIT_USERNAME = '' -TEST_GIT_PASSWORD = '' -TEST_GIT_KEY_DATA = TEST_SSH_KEY_DATA -TEST_GIT_PUBLIC_HTTPS = 'https://github.com/ansible/ansible.github.com.git' -TEST_GIT_PRIVATE_HTTPS = 'https://github.com/ansible/product-docs.git' -TEST_GIT_PRIVATE_SSH = 'git@github.com:ansible/product-docs.git' - -TEST_HG_USERNAME = '' -TEST_HG_PASSWORD = '' -TEST_HG_KEY_DATA = TEST_SSH_KEY_DATA -TEST_HG_PUBLIC_HTTPS = 'https://bitbucket.org/cchurch/django-hotrunner' -TEST_HG_PRIVATE_HTTPS = '' -TEST_HG_PRIVATE_SSH = '' - -TEST_SVN_USERNAME = '' -TEST_SVN_PASSWORD = '' -TEST_SVN_PUBLIC_HTTPS = 'https://github.com/ansible/ansible.github.com' -TEST_SVN_PRIVATE_HTTPS = 'https://github.com/ansible/product-docs' - -# To test repo access via SSH login to localhost. -import getpass -TEST_SSH_LOOPBACK_USERNAME = getpass.getuser() -TEST_SSH_LOOPBACK_PASSWORD = '' - -############################################################################### -# INVENTORY IMPORT TEST SETTINGS -############################################################################### - -# Define these variables to enable more complete testing of inventory import -# from cloud providers. - -# EC2 credentials -TEST_AWS_ACCESS_KEY_ID = '' -TEST_AWS_SECRET_ACCESS_KEY = '' -TEST_AWS_REGIONS = 'all' -# Check IAM STS credentials -TEST_AWS_SECURITY_TOKEN = '' - - -# Rackspace credentials -TEST_RACKSPACE_USERNAME = '' -TEST_RACKSPACE_API_KEY = '' -TEST_RACKSPACE_REGIONS = 'all' - -# VMware credentials -TEST_VMWARE_HOST = '' -TEST_VMWARE_USER = '' -TEST_VMWARE_PASSWORD = '' - -# OpenStack credentials -TEST_OPENSTACK_HOST = '' -TEST_OPENSTACK_USER = '' -TEST_OPENSTACK_PASSWORD = '' -TEST_OPENSTACK_PROJECT = '' - -# Azure credentials. -TEST_AZURE_USERNAME = '' -TEST_AZURE_KEY_DATA = '' diff --git a/awx/settings/production.py b/awx/settings/production.py index b5d4ad443788..02681265e652 100644 --- a/awx/settings/production.py +++ b/awx/settings/production.py @@ -30,21 +30,11 @@ # See https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts ALLOWED_HOSTS = [] -# Production should only use minified JS for UI. -USE_MINIFIED_JS = True - -# URL used by inventory script and callback plugin to access API. -INTERNAL_API_URL = 'http://127.0.0.1:80' - -# Absolute filesystem path to the directory for job status stdout -# This directory should not be web-accessible -JOBOUTPUT_ROOT = '/var/lib/awx/job_status/' - # The heartbeat file for the tower scheduler SCHEDULE_METADATA_LOCATION = '/var/lib/awx/.tower_cycle' # Ansible base virtualenv paths and enablement -BASE_VENV_PATH = "/var/lib/awx/venv" +BASE_VENV_PATH = os.path.realpath("/var/lib/awx/venv") ANSIBLE_VENV_PATH = os.path.join(BASE_VENV_PATH, "ansible") # Tower base virtualenv paths and enablement @@ -52,14 +42,6 @@ AWX_ISOLATED_USERNAME = 'awx' -LOGGING['handlers']['tower_warnings']['filename'] = '/var/log/tower/tower.log' # noqa -LOGGING['handlers']['callback_receiver']['filename'] = '/var/log/tower/callback_receiver.log' # noqa -LOGGING['handlers']['dispatcher']['filename'] = '/var/log/tower/dispatcher.log' # noqa -LOGGING['handlers']['task_system']['filename'] = '/var/log/tower/task_system.log' # noqa -LOGGING['handlers']['management_playbooks']['filename'] = '/var/log/tower/management_playbooks.log' # noqa -LOGGING['handlers']['system_tracking_migrations']['filename'] = '/var/log/tower/tower_system_tracking_migrations.log' # noqa -LOGGING['handlers']['rbac_migrations']['filename'] = '/var/log/tower/tower_rbac_migrations.log' # noqa - # Store a snapshot of default settings at this point before loading any # customizable config files. DEFAULTS_SNAPSHOT = {} @@ -107,6 +89,7 @@ else: raise +# The below runs AFTER all of the custom settings are imported. CELERYBEAT_SCHEDULE.update({ # noqa 'isolated_heartbeat': { @@ -115,3 +98,5 @@ 'options': {'expires': AWX_ISOLATED_PERIODIC_CHECK * 2}, # noqa } }) + +DATABASES['default'].setdefault('OPTIONS', dict()).setdefault('application_name', f'{CLUSTER_HOST_ID}-{os.getpid()}-{" ".join(sys.argv)}'[:63]) # noqa diff --git a/awx/sso/conf.py b/awx/sso/conf.py index c408d72b4022..5f595517cc9d 100644 --- a/awx/sso/conf.py +++ b/awx/sso/conf.py @@ -515,6 +515,7 @@ def _register_ldap(append=None): help_text=_('TACACS+ session timeout value in seconds, 0 disables timeout.'), category=_('TACACS+'), category_slug='tacacsplus', + unit=_('seconds'), ) register( @@ -575,7 +576,7 @@ def _register_ldap(append=None): 'SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS', field_class=fields.StringListField, default=[], - label=_('Google OAuth2 Whitelisted Domains'), + 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'), @@ -919,6 +920,17 @@ 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, diff --git a/awx/sso/fields.py b/awx/sso/fields.py index dddd1ee6a124..38d8bde0d418 100644 --- a/awx/sso/fields.py +++ b/awx/sso/fields.py @@ -445,6 +445,8 @@ 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): @@ -464,7 +466,6 @@ def to_internal_value(self, data): if not data: return None - params = self.get_depends_on() or {} cls = find_class_in_modules(data) if not cls: return None @@ -474,12 +475,23 @@ def to_internal_value(self, data): # 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() - for attr in inspect.getargspec(cls.__init__).args[1:]: + + cls_args = inspect.getargspec(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] - return cls(**params_sanitized) + try: + return cls(**params_sanitized) + except TypeError: + self.fail('missing_parameters', dependency=list(self.depends_on)[0]) class LDAPGroupTypeParamsField(fields.DictField, DependsOnMixin): @@ -740,7 +752,9 @@ class SAMLOrgAttrField(HybridDictField): 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) + organization_alias = fields.CharField(required=False, allow_null=True) child = _Forbidden() diff --git a/awx/sso/pipeline.py b/awx/sso/pipeline.py index 212e3824baf6..3e739744748d 100644 --- a/awx/sso/pipeline.py +++ b/awx/sso/pipeline.py @@ -10,6 +10,7 @@ from social_core.exceptions import AuthException # Django +from django.core.exceptions import ObjectDoesNotExist from django.utils.translation import ugettext_lazy as _ from django.db.models import Q @@ -80,11 +81,18 @@ def _update_m2m_from_expression(user, related, expr, remove=True): def _update_org_from_attr(user, related, attr, remove, remove_admins, remove_auditors): from awx.main.models import Organization + from django.conf import settings org_ids = [] for org_name in attr: - org = Organization.objects.get_or_create(name=org_name)[0] + try: + if settings.SAML_AUTO_CREATE_OBJECTS: + org = Organization.objects.get_or_create(name=org_name)[0] + else: + org = Organization.objects.get(name=org_name) + except ObjectDoesNotExist: + continue org_ids.append(org.id) getattr(org, related).members.add(user) @@ -187,14 +195,36 @@ def update_user_teams_by_saml_attr(backend, details, user=None, *args, **kwargs) team_ids = [] for team_name_map in team_map.get('team_org_map', []): - team_name = team_name_map.get('team', '') + 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) + organization_alias = team_name_map.get('organization_alias', None) if team_name in saml_team_names: - if not team_name_map.get('organization', ''): + if not organization_name: # Settings field validation should prevent this. logger.error("organization name invalid for team {}".format(team_name)) continue - org = Organization.objects.get_or_create(name=team_name_map['organization'])[0] - team = Team.objects.get_or_create(name=team_name, organization=org)[0] + + if organization_alias: + organization_name = organization_alias + + try: + if settings.SAML_AUTO_CREATE_OBJECTS: + org = Organization.objects.get_or_create(name=organization_name)[0] + else: + org = Organization.objects.get(name=organization_name) + except ObjectDoesNotExist: + continue + + if team_alias: + team_name = team_alias + try: + if settings.SAML_AUTO_CREATE_OBJECTS: + team = Team.objects.get_or_create(name=team_name, organization=org)[0] + else: + team = Team.objects.get(name=team_name, organization=org) + except ObjectDoesNotExist: + continue team_ids.append(team.id) team.member_role.members.add(user) diff --git a/awx/sso/tests/functional/test_pipeline.py b/awx/sso/tests/functional/test_pipeline.py index 78a04a04817b..e69193975234 100644 --- a/awx/sso/tests/functional/test_pipeline.py +++ b/awx/sso/tests/functional/test_pipeline.py @@ -174,8 +174,15 @@ def orgs(self): return (o1, o2, o3) @pytest.fixture - def mock_settings(self): + def mock_settings(self, request): + fixture_args = request.node.get_closest_marker('fixture_args') + if fixture_args and 'autocreate' in fixture_args.kwargs: + autocreate = fixture_args.kwargs['autocreate'] + else: + autocreate = True + class MockSettings(): + SAML_AUTO_CREATE_OBJECTS = autocreate SOCIAL_AUTH_SAML_ORGANIZATION_ATTR = { 'saml_attr': 'memberOf', 'saml_admin_attr': 'admins', @@ -193,6 +200,10 @@ class MockSettings(): {'team': 'Red', 'organization': 'Default1'}, {'team': 'Green', 'organization': 'Default1'}, {'team': 'Green', 'organization': 'Default3'}, + { + 'team': 'Yellow', 'team_alias': 'Yellow_Alias', + 'organization': 'Default4', 'organization_alias': 'Default4_Alias' + }, ] } return MockSettings() @@ -285,3 +296,56 @@ def test_update_user_teams_by_saml_attr(self, orgs, users, kwargs, mock_settings assert Team.objects.get(name='Green', organization__name='Default1').member_role.members.count() == 3 assert Team.objects.get(name='Green', organization__name='Default3').member_role.members.count() == 3 + def test_update_user_teams_alias_by_saml_attr(self, orgs, users, kwargs, mock_settings): + with mock.patch('django.conf.settings', mock_settings): + u1 = users[0] + + # Test getting teams from attribute with team->org mapping + kwargs['response']['attributes']['groups'] = ['Yellow'] + + # Ensure team and org will be created + update_user_teams_by_saml_attr(None, None, u1, **kwargs) + + assert Team.objects.filter(name='Yellow', organization__name='Default4').count() == 0 + assert Team.objects.filter(name='Yellow_Alias', organization__name='Default4_Alias').count() == 1 + assert Team.objects.get( + name='Yellow_Alias', organization__name='Default4_Alias').member_role.members.count() == 1 + + @pytest.mark.fixture_args(autocreate=False) + def test_autocreate_disabled(self, users, kwargs, mock_settings): + kwargs['response']['attributes']['memberOf'] = ['Default1', 'Default2', 'Default3'] + kwargs['response']['attributes']['groups'] = ['Blue', 'Red', 'Green'] + with mock.patch('django.conf.settings', mock_settings): + for u in users: + update_user_orgs_by_saml_attr(None, None, u, **kwargs) + update_user_teams_by_saml_attr(None, None, u, **kwargs) + assert Organization.objects.count() == 0 + assert Team.objects.count() == 0 + + # precreate everything + o1 = Organization.objects.create(name='Default1') + o2 = Organization.objects.create(name='Default2') + o3 = Organization.objects.create(name='Default3') + Team.objects.create(name='Blue', organization_id=o1.id) + Team.objects.create(name='Blue', organization_id=o2.id) + Team.objects.create(name='Blue', organization_id=o3.id) + Team.objects.create(name='Red', organization_id=o1.id) + Team.objects.create(name='Green', organization_id=o1.id) + Team.objects.create(name='Green', organization_id=o3.id) + + for u in users: + update_user_orgs_by_saml_attr(None, None, u, **kwargs) + update_user_teams_by_saml_attr(None, None, u, **kwargs) + + assert o1.member_role.members.count() == 3 + assert o2.member_role.members.count() == 3 + assert o3.member_role.members.count() == 3 + + assert Team.objects.get(name='Blue', organization__name='Default1').member_role.members.count() == 3 + assert Team.objects.get(name='Blue', organization__name='Default2').member_role.members.count() == 3 + assert Team.objects.get(name='Blue', organization__name='Default3').member_role.members.count() == 3 + + assert Team.objects.get(name='Red', organization__name='Default1').member_role.members.count() == 3 + + assert Team.objects.get(name='Green', organization__name='Default1').member_role.members.count() == 3 + assert Team.objects.get(name='Green', organization__name='Default3').member_role.members.count() == 3 diff --git a/awx/sso/tests/unit/test_fields.py b/awx/sso/tests/unit/test_fields.py index c63e137776ff..6d7505e0221e 100644 --- a/awx/sso/tests/unit/test_fields.py +++ b/awx/sso/tests/unit/test_fields.py @@ -71,6 +71,14 @@ class TestSAMLTeamAttrField(): {'team': 'Engineering', 'organization': 'Ansible2'}, {'team': 'Engineering2', 'organization': 'Ansible'}, ]}, + {'remove': True, 'saml_attr': 'foobar', 'team_org_map': [ + { + 'team': 'Engineering', 'team_alias': 'Engineering Team', + 'organization': 'Ansible', 'organization_alias': 'Awesome Org' + }, + {'team': 'Engineering', 'organization': 'Ansible2'}, + {'team': 'Engineering2', 'organization': 'Ansible'}, + ]}, ]) def test_internal_value_valid(self, data): field = SAMLTeamAttrField() diff --git a/awx/sso/views.py b/awx/sso/views.py index fa248f634fa7..ddbc2cbd59d6 100644 --- a/awx/sso/views.py +++ b/awx/sso/views.py @@ -25,7 +25,7 @@ class BaseRedirectView(RedirectView): 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') + url = reverse('ui_next:index') if last_path: return '%s#%s' % (url, last_path) else: diff --git a/awx/ui/.eslintignore b/awx/ui/.eslintignore deleted file mode 100644 index f1daf7c9ab79..000000000000 --- a/awx/ui/.eslintignore +++ /dev/null @@ -1,19 +0,0 @@ -Gruntfile.js -karma.*.js -webpack.*.js -nightwatch.*.js - -etc -coverage -grunt-tasks -node_modules -po -static -templates - -client/src/**/*.js -client/assets/**/*.js -test/spec/**/*.js - -!client/src/app.start.js -!client/src/vendor.js diff --git a/awx/ui/.eslintrc.js b/awx/ui/.eslintrc.js deleted file mode 100644 index 27759bf2b98a..000000000000 --- a/awx/ui/.eslintrc.js +++ /dev/null @@ -1,72 +0,0 @@ -const path = require('path'); - -module.exports = { - root: true, - extends: [ - 'airbnb-base' - ], - plugins: [ - 'import', - 'disable' - ], - settings: { - 'import/resolver': { - webpack: { - config: path.join(__dirname, 'build/webpack.development.js') - } - }, - 'eslint-plugin-disable': { - paths: { - import: ['**/build/*.js'] - } - } - }, - env: { - browser: true, - node: true - }, - globals: { - angular: true, - d3: true, - $: true, - _: true, - codemirror: true, - jsyaml: true, - crypto: true - }, - rules: { - 'arrow-parens': 'off', - 'comma-dangle': 'off', - indent: ['error', 4, { - SwitchCase: 1 - }], - 'max-len': ['error', { - code: 100, - ignoreStrings: true, - ignoreTemplateLiterals: true, - }], - 'no-continue': 'off', - 'no-debugger': 'off', - 'no-mixed-operators': 'off', - 'no-param-reassign': 'off', - 'no-plusplus': 'off', - 'no-underscore-dangle': 'off', - 'no-use-before-define': 'off', - 'no-multiple-empty-lines': ['error', { max: 1 }], - 'object-curly-newline': 'off', - 'space-before-function-paren': ['error', 'always'], - 'no-trailing-spaces': ['error'], - 'prefer-destructuring': ['error', { - 'VariableDeclarator': { - 'array': false, - 'object': true - }, - 'AssignmentExpression': { - 'array': false, - 'object': true - } - }, { - 'enforceForRenamedProperties': false - }] - } -}; diff --git a/awx/ui/.jshintrc b/awx/ui/.jshintrc deleted file mode 100644 index 363d2e5ba871..000000000000 --- a/awx/ui/.jshintrc +++ /dev/null @@ -1,49 +0,0 @@ -{ - "browser": true, - "node": true, - "jquery": true, - "esnext": true, - "globalstrict": true, - "curly": true, - "immed": true, - "latedef": "nofunc", - "noarg": true, - "nonew": true, - "maxerr": 10000, - "notypeof": true, - "globals": { - "$ENV": true, - "require": true, - "global": true, - "beforeEach": false, - "inject": false, - "module": false, - "angular":false, - "alert":false, - "$AnsibleConfig":true, - "$basePath":true, - "jsyaml":false, - "_":false, - "d3":false, - "Donut3D":false, - "nv":false, - "it": false, - "xit": false, - "expect": false, - "context": false, - "describe": false, - "moment": false, - "spyOn": false, - "jasmine": false, - "dagre": false, - "crypto": false - }, - "strict": false, - "quotmark": false, - "trailing": true, - "undef": true, - "unused": true, - "eqeqeq": true, - "indent": 4, - "newcap": false -} diff --git a/awx/ui/.npmrc b/awx/ui/.npmrc deleted file mode 100644 index d883e4fa132d..000000000000 --- a/awx/ui/.npmrc +++ /dev/null @@ -1 +0,0 @@ -progress=false diff --git a/awx/ui/Gruntfile.js b/awx/ui/Gruntfile.js deleted file mode 100644 index f68892570808..000000000000 --- a/awx/ui/Gruntfile.js +++ /dev/null @@ -1,20 +0,0 @@ -module.exports = function(grunt) { - // Load grunt tasks & configurations automatically from dir grunt/ - require('load-grunt-tasks')(grunt); - // display task timings - require('time-grunt')(grunt); - - var options = { - config: { - src: './grunt-tasks/*.js' - }, - pkg: grunt.file.readJSON('package.json') - }; - - var configs = require('load-grunt-configs')(grunt, options); - - // Project configuration. - grunt.initConfig(configs); - grunt.loadNpmTasks('grunt-newer'); - grunt.loadNpmTasks('grunt-angular-gettext'); -}; diff --git a/awx/ui/README.md b/awx/ui/README.md deleted file mode 100644 index 462701216274..000000000000 --- a/awx/ui/README.md +++ /dev/null @@ -1,103 +0,0 @@ -# AWX UI - -## Requirements -- node.js 10.x LTS -- npm >=6.x -- bzip2, gcc-c++, git, make - -## Development -The API development server will need to be running. See [CONTRIBUTING.md](../../CONTRIBUTING.md). - -```shell -# Build ui for the devel environment - reachable at https://localhost:8043 -make ui-devel - -# Alternatively, start the ui development server. While running, the ui will be reachable -# at https://localhost:3000 and updated automatically when code changes. -make ui-docker - -# When using docker machine, use this command to start the ui development server instead. -DOCKER_MACHINE_NAME=default make ui-docker-machine -``` - -## Development with an external server -If you normally run awx on an external host/server (in this example, `awx.local`), -you'll need to reconfigure the webpack proxy slightly for `make ui-docker` to -work: - -```javascript -/awx/settings/development.py -+ -+CSRF_TRUSTED_ORIGINS = ['awx.local:8043'] - -awx/ui/build/webpack.watch.js -- host: '127.0.0.1', -+ host: '0.0.0.0', -+ disableHostCheck: true, - -/awx/ui/package.json -@@ -7,7 +7,7 @@ - "config": { - ... -+ "django_host": "awx.local" - }, -``` - -## Testing -```shell -# run linters -make jshint - -# run unit tests -make ui-test-ci - -# run e2e tests - see awx/ui/test/e2e for more information -npm --prefix awx/ui run e2e -``` -**Note**: Unit tests are run on your host machine and not in the development containers. - -## Adding dependencies -```shell -# add an exact development or build dependency -npm install --prefix awx/ui --save-dev --save-exact dev-package@1.2.3 - -# add an exact production dependency -npm install --prefix awx/ui --save --save-exact prod-package@1.23 - -# add the updated package.json and package-lock.json files to scm -git add awx/ui/package.json awx/ui/package-lock.json -``` - -## Removing dependencies -```shell -# remove a development or build dependency -npm uninstall --prefix awx/ui --save-dev dev-package - -# remove a production dependency -npm uninstall --prefix awx/ui --save prod-package -``` - -## Building for Production -```shell -# built files are placed in awx/ui/static -make ui-release -``` - -## Internationalization -Application strings marked for translation are extracted and used to generate `.pot` files using the following command: -```shell -# extract strings and generate .pot files -make pot -``` -To include the translations in the development environment, we compile them prior to building the ui: -```shell -# remove any prior ui builds -make clean-ui - -# compile the .pot files to javascript files usable by the application -make languages - -# build the ui with translations included -make ui-devel -``` -**Note**: Python 3.6 is required to compile the `.pot` files. diff --git a/awx/ui/__init__.py b/awx/ui/__init__.py index ac6a55435633..bfb3e776cd8e 100644 --- a/awx/ui/__init__.py +++ b/awx/ui/__init__.py @@ -2,3 +2,4 @@ # All Rights Reserved. default_app_config = 'awx.ui.apps.UIConfig' + diff --git a/awx/ui/apps.py b/awx/ui/apps.py index 40943c6f5317..5b8e5083c19c 100644 --- a/awx/ui/apps.py +++ b/awx/ui/apps.py @@ -7,3 +7,4 @@ class UIConfig(AppConfig): name = 'awx.ui' verbose_name = _('UI') + diff --git a/awx/ui/build/webpack.base.js b/awx/ui/build/webpack.base.js deleted file mode 100644 index f1e5ac8e0b13..000000000000 --- a/awx/ui/build/webpack.base.js +++ /dev/null @@ -1,235 +0,0 @@ -const path = require('path'); - -const webpack = require('webpack'); -const CleanWebpackPlugin = require('clean-webpack-plugin'); -const CopyWebpackPlugin = require('copy-webpack-plugin'); -const HtmlWebpackPlugin = require('html-webpack-plugin'); -const ExtractTextPlugin = require('extract-text-webpack-plugin'); - -const CLIENT_PATH = path.resolve(__dirname, '../client'); -const LIB_PATH = path.join(CLIENT_PATH, 'lib'); -const UI_PATH = path.resolve(__dirname, '..'); - -const ASSETS_PATH = path.join(CLIENT_PATH, 'assets'); -const COMPONENTS_PATH = path.join(LIB_PATH, 'components'); -const COVERAGE_PATH = path.join(UI_PATH, 'coverage'); -const FEATURES_PATH = path.join(CLIENT_PATH, 'features'); -const LANGUAGES_PATH = path.join(CLIENT_PATH, 'languages'); -const MODELS_PATH = path.join(LIB_PATH, 'models'); -const NODE_MODULES_PATH = path.join(UI_PATH, 'node_modules'); -const SERVICES_PATH = path.join(LIB_PATH, 'services'); -const SRC_PATH = path.join(CLIENT_PATH, 'src'); -const STATIC_PATH = path.join(UI_PATH, 'static'); -const TEST_PATH = path.join(UI_PATH, 'test'); -const THEME_PATH = path.join(LIB_PATH, 'theme'); - -const APP_ENTRY = path.join(SRC_PATH, 'app.js'); -const VENDOR_ENTRY = path.join(SRC_PATH, 'vendor.js'); -const INDEX_ENTRY = path.join(CLIENT_PATH, 'index.template.ejs'); -const INDEX_OUTPUT = path.join(UI_PATH, 'templates/ui/index.html'); -const INSTALL_RUNNING_ENTRY = path.join(CLIENT_PATH, 'installing.template.ejs'); -const INSTALL_RUNNING_OUTPUT = path.join(UI_PATH, 'templates/ui/installing.html'); -const THEME_ENTRY = path.join(LIB_PATH, 'theme', 'index.less'); -const OUTPUT = 'js/[name].[chunkhash].js'; -const CHUNKS = ['vendor', 'app']; - -const VENDOR = VENDOR_ENTRY; -const APP = [THEME_ENTRY, APP_ENTRY]; - -const base = { - entry: { - vendor: VENDOR, - app: APP - }, - output: { - path: STATIC_PATH, - publicPath: '', - filename: OUTPUT - }, - stats: { - children: false, - modules: false, - chunks: false, - excludeAssets: name => { - const chunkNames = `(${CHUNKS.join('|')})`; - const outputPattern = new RegExp(`${chunkNames}.[a-f0-9]+.(js|css)(|.map)$`, 'i'); - - return !outputPattern.test(name); - } - }, - module: { - rules: [ - { - test: /\.js$/, - use: { - loader: 'istanbul-instrumenter-loader', - options: { esModules: true } - }, - enforce: 'pre', - include: [ - /src\/network-ui\// - ] - }, - { - test: /\.js$/, - loader: 'babel-loader', - exclude: /node_modules/, - options: { - presets: [ - ['env', { - targets: { - browsers: ['last 2 versions'] - } - }] - ] - } - }, - { - test: /\.css$/, - use: ExtractTextPlugin.extract({ - use: { - loader: 'css-loader', - options: { - url: false - } - } - }) - }, - { - test: /lib\/theme\/index.less$/, - use: ExtractTextPlugin.extract({ - use: ['css-loader', 'less-loader'] - }) - }, - { - test: /\.html$/, - use: ['ngtemplate-loader', 'html-loader'], - include: [ - /lib\/components\//, - /features\//, - /src\// - ] - }, - { - test: /\.svg$/, - use: ['ngtemplate-loader', 'html-loader'], - include: [ - /lib\/components\//, - /features\//, - /src\// - ] - }, - { - test: /\.json$/, - loader: 'json-loader', - exclude: /node_modules/ - } - ] - }, - plugins: [ - new webpack.ProvidePlugin({ - jsyaml: 'js-yaml', - CodeMirror: 'codemirror', - jsonlint: 'codemirror.jsonlint' - }), - new ExtractTextPlugin('css/[name].[chunkhash].css'), - new CleanWebpackPlugin([STATIC_PATH, COVERAGE_PATH], { - root: UI_PATH, - verbose: false - }), - new CopyWebpackPlugin([ - { - from: path.join(ASSETS_PATH, 'fontcustom/**/*'), - to: path.join(STATIC_PATH, 'fonts/'), - flatten: true - }, - { - from: path.join(NODE_MODULES_PATH, 'components-font-awesome/fonts/*'), - to: path.join(STATIC_PATH, 'fonts/'), - flatten: true - }, - { - from: path.join(ASSETS_PATH, 'custom-theme/images.new/*'), - to: path.join(STATIC_PATH, 'images/'), - flatten: true - }, - { - from: path.join(LANGUAGES_PATH, '*'), - to: path.join(STATIC_PATH, 'languages'), - flatten: true - }, - { - from: ASSETS_PATH, - to: path.join(STATIC_PATH, 'assets') - }, - { - from: path.join(NODE_MODULES_PATH, 'angular-scheduler/lib/*.html'), - to: path.join(STATIC_PATH, 'lib'), - context: NODE_MODULES_PATH - }, - { - from: path.join(NODE_MODULES_PATH, 'angular-tz-extensions/tz/data/*'), - to: path.join(STATIC_PATH, 'lib/'), - context: NODE_MODULES_PATH - }, - { - from: path.join(SRC_PATH, '**/*.partial.html'), - to: path.join(STATIC_PATH, 'partials/'), - context: SRC_PATH - }, - { - from: path.join(SRC_PATH, 'partials', '*.html'), - to: STATIC_PATH, - context: SRC_PATH - }, - { - from: path.join(SRC_PATH, '*config.js'), - to: STATIC_PATH, - flatten: true - } - ]), - new HtmlWebpackPlugin({ - alwaysWriteToDisk: true, - template: INDEX_ENTRY, - filename: INDEX_OUTPUT, - inject: false, - chunks: CHUNKS, - chunksSortMode: chunk => (chunk.names[0] === 'vendor' ? -1 : 1) - }), - new HtmlWebpackPlugin({ - alwaysWriteToDisk: true, - template: INSTALL_RUNNING_ENTRY, - filename: INSTALL_RUNNING_OUTPUT, - inject: false, - chunks: CHUNKS, - chunksSortMode: chunk => (chunk.names[0] === 'vendor' ? -1 : 1) - }), - ], - resolve: { - alias: { - '~assets': ASSETS_PATH, - '~components': COMPONENTS_PATH, - '~features': FEATURES_PATH, - '~models': MODELS_PATH, - '~node_modules': NODE_MODULES_PATH, - '~services': SERVICES_PATH, - '~src': SRC_PATH, - '~test': TEST_PATH, - '~theme': THEME_PATH, - '~ui': UI_PATH, - d3$: '~node_modules/d3/d3.min.js', - 'codemirror.jsonlint$': '~node_modules/codemirror/addon/lint/json-lint.js', - jquery: '~node_modules/jquery/dist/jquery.js', - 'jquery-resize$': '~node_modules/javascript-detect-element-resize/jquery.resize.js', - select2$: '~node_modules/select2/dist/js/select2.full.min.js', - 'js-yaml$': '~node_modules/js-yaml/dist/js-yaml.min.js', - 'lr-infinite-scroll$': '~node_modules/lr-infinite-scroll/lrInfiniteScroll.js', - 'angular-tz-extensions$': '~node_modules/angular-tz-extensions/lib/angular-tz-extensions.js', - 'ng-toast-provider$': '~node_modules/ng-toast/src/scripts/provider.js', - 'ng-toast-directives$': '~node_modules/ng-toast/src/scripts/directives.js', - 'ng-toast$': '~node_modules/ng-toast/src/scripts/module.js' - } - } -}; - -module.exports = base; diff --git a/awx/ui/build/webpack.development.js b/awx/ui/build/webpack.development.js deleted file mode 100644 index 56e2d90b5147..000000000000 --- a/awx/ui/build/webpack.development.js +++ /dev/null @@ -1,9 +0,0 @@ -const merge = require('webpack-merge'); - -const base = require('./webpack.base'); - -const development = { - devtool: 'source-map' -}; - -module.exports = merge(base, development); diff --git a/awx/ui/build/webpack.production.js b/awx/ui/build/webpack.production.js deleted file mode 100644 index 4f57e23cc380..000000000000 --- a/awx/ui/build/webpack.production.js +++ /dev/null @@ -1,28 +0,0 @@ -const path = require('path'); - -const merge = require('webpack-merge'); -const webpack = require('webpack'); -const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); -const HtmlWebpackPlugin = require('html-webpack-plugin'); - -const base = require('./webpack.base'); - -const CLIENT_PATH = path.resolve(__dirname, '../client'); -const UI_PATH = path.resolve(__dirname, '..'); -const CHUNKS = ['vendor', 'app']; - -const production = { - plugins: [ - new UglifyJSPlugin({ - compress: true, - mangle: false - }), - new webpack.DefinePlugin({ - 'process.env': { - NODE_ENV: JSON.stringify('production') - } - }) - ] -}; - -module.exports = merge(base, production); diff --git a/awx/ui/build/webpack.test.js b/awx/ui/build/webpack.test.js deleted file mode 100644 index 8fb4e4e1c7b6..000000000000 --- a/awx/ui/build/webpack.test.js +++ /dev/null @@ -1,20 +0,0 @@ -const _ = require('lodash'); -const webpack = require('webpack'); - -const STATIC_URL = '/static/'; - -const development = require('./webpack.base'); - -const test = { - devtool: 'cheap-source-map', - plugins: [ - new webpack.DefinePlugin({ - $basePath: STATIC_URL - }) - ] -}; - -test.plugins = development.plugins.concat(test.plugins); - -module.exports = _.merge(development, test); - diff --git a/awx/ui/build/webpack.watch.js b/awx/ui/build/webpack.watch.js deleted file mode 100644 index 143058077ac3..000000000000 --- a/awx/ui/build/webpack.watch.js +++ /dev/null @@ -1,84 +0,0 @@ -const path = require('path'); - -const _ = require('lodash'); -const webpack = require('webpack'); -const merge = require('webpack-merge'); -const nodeObjectHash = require('node-object-hash'); -const HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); -const HtmlWebpackHarddiskPlugin = require('html-webpack-harddisk-plugin'); - -const TARGET_PORT = _.get(process.env, 'npm_package_config_django_port', 8043); -const TARGET_HOST = _.get(process.env, 'npm_package_config_django_host', 'https://localhost'); -const TARGET = `https://${TARGET_HOST}:${TARGET_PORT}`; -const OUTPUT = 'js/[name].js'; - -const development = require('./webpack.development'); - -const watch = { - cache: true, - devtool: 'cheap-source-map', - output: { - filename: OUTPUT - }, - module: { - rules: [ - { - test: /\.js$/, - enforce: 'pre', - exclude: /node_modules/, - loader: 'eslint-loader' - } - ] - }, - plugins: [ - new HtmlWebpackHarddiskPlugin(), - new HardSourceWebpackPlugin({ - cacheDirectory: 'node_modules/.cache/hard-source/[confighash]', - recordsPath: 'node_modules/.cache/hard-source/[confighash]/records.json', - configHash: config => nodeObjectHash({ sort: false }).hash(config), - environmentHash: { - root: process.cwd(), - directories: ['node_modules'], - files: ['package.json'] - } - }), - new webpack.HotModuleReplacementPlugin() - ], - devServer: { - hot: true, - inline: true, - contentBase: path.resolve(__dirname, '..', 'static'), - stats: 'minimal', - publicPath: '/static/', - host: '127.0.0.1', - https: true, - port: 3000, - clientLogLevel: 'none', - proxy: [{ - context: (pathname, req) => !(pathname === '/api/login/' && req.method === 'POST'), - target: TARGET, - secure: false, - ws: false, - bypass: req => req.originalUrl.includes('hot-update.json') - }, - { - context: '/api/login/', - target: TARGET, - secure: false, - ws: false, - headers: { - Host: `localhost:${TARGET_PORT}`, - Origin: TARGET, - Referer: `${TARGET}/` - } - }, - { - context: '/websocket', - target: TARGET, - secure: false, - ws: true - }] - } -}; - -module.exports = merge(development, watch); diff --git a/awx/ui/client/assets/LICENSE.txt b/awx/ui/client/assets/LICENSE.txt deleted file mode 100644 index d64569567334..000000000000 --- a/awx/ui/client/assets/LICENSE.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/awx/ui/client/assets/OpenSans-Bold.ttf b/awx/ui/client/assets/OpenSans-Bold.ttf deleted file mode 100644 index fd79d43bea02..000000000000 Binary files a/awx/ui/client/assets/OpenSans-Bold.ttf and /dev/null differ diff --git a/awx/ui/client/assets/OpenSans-BoldItalic.ttf b/awx/ui/client/assets/OpenSans-BoldItalic.ttf deleted file mode 100644 index 9bc800958a42..000000000000 Binary files a/awx/ui/client/assets/OpenSans-BoldItalic.ttf and /dev/null differ diff --git a/awx/ui/client/assets/OpenSans-ExtraBold.ttf b/awx/ui/client/assets/OpenSans-ExtraBold.ttf deleted file mode 100644 index 21f6f84a0799..000000000000 Binary files a/awx/ui/client/assets/OpenSans-ExtraBold.ttf and /dev/null differ diff --git a/awx/ui/client/assets/OpenSans-ExtraBoldItalic.ttf b/awx/ui/client/assets/OpenSans-ExtraBoldItalic.ttf deleted file mode 100644 index 31cb688340ef..000000000000 Binary files a/awx/ui/client/assets/OpenSans-ExtraBoldItalic.ttf and /dev/null differ diff --git a/awx/ui/client/assets/OpenSans-Italic.ttf b/awx/ui/client/assets/OpenSans-Italic.ttf deleted file mode 100644 index c90da48ff3b8..000000000000 Binary files a/awx/ui/client/assets/OpenSans-Italic.ttf and /dev/null differ diff --git a/awx/ui/client/assets/OpenSans-Light.ttf b/awx/ui/client/assets/OpenSans-Light.ttf deleted file mode 100644 index 0d381897da20..000000000000 Binary files a/awx/ui/client/assets/OpenSans-Light.ttf and /dev/null differ diff --git a/awx/ui/client/assets/OpenSans-LightItalic.ttf b/awx/ui/client/assets/OpenSans-LightItalic.ttf deleted file mode 100644 index 68299c4bc6b5..000000000000 Binary files a/awx/ui/client/assets/OpenSans-LightItalic.ttf and /dev/null differ diff --git a/awx/ui/client/assets/OpenSans-Regular.ttf b/awx/ui/client/assets/OpenSans-Regular.ttf deleted file mode 100644 index db433349b704..000000000000 Binary files a/awx/ui/client/assets/OpenSans-Regular.ttf and /dev/null differ diff --git a/awx/ui/client/assets/OpenSans-Semibold.ttf b/awx/ui/client/assets/OpenSans-Semibold.ttf deleted file mode 100644 index 1a7679e3949f..000000000000 Binary files a/awx/ui/client/assets/OpenSans-Semibold.ttf and /dev/null differ diff --git a/awx/ui/client/assets/OpenSans-SemiboldItalic.ttf b/awx/ui/client/assets/OpenSans-SemiboldItalic.ttf deleted file mode 100644 index 59b6d16b065f..000000000000 Binary files a/awx/ui/client/assets/OpenSans-SemiboldItalic.ttf and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images.new/animated-overlay.gif b/awx/ui/client/assets/custom-theme/images.new/animated-overlay.gif deleted file mode 100644 index d441f75ebfbd..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images.new/animated-overlay.gif and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images.new/ui-bg_flat_0_aaaaaa_40x100.png b/awx/ui/client/assets/custom-theme/images.new/ui-bg_flat_0_aaaaaa_40x100.png deleted file mode 100644 index e1a114b4e262..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images.new/ui-bg_flat_0_aaaaaa_40x100.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images.new/ui-bg_flat_100_3276b1_40x100.png b/awx/ui/client/assets/custom-theme/images.new/ui-bg_flat_100_3276b1_40x100.png deleted file mode 100644 index 3199c1d47942..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images.new/ui-bg_flat_100_3276b1_40x100.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images.new/ui-bg_flat_100_428bca_40x100.png b/awx/ui/client/assets/custom-theme/images.new/ui-bg_flat_100_428bca_40x100.png deleted file mode 100644 index 38940fe30de1..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images.new/ui-bg_flat_100_428bca_40x100.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images.new/ui-bg_flat_100_ffffff_40x100.png b/awx/ui/client/assets/custom-theme/images.new/ui-bg_flat_100_ffffff_40x100.png deleted file mode 100644 index 708e95ddc09e..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images.new/ui-bg_flat_100_ffffff_40x100.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images.new/ui-bg_flat_50_ffffff_40x100.png b/awx/ui/client/assets/custom-theme/images.new/ui-bg_flat_50_ffffff_40x100.png deleted file mode 100644 index cfaa03e8dd28..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images.new/ui-bg_flat_50_ffffff_40x100.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images.new/ui-bg_flat_55_fbec88_40x100.png b/awx/ui/client/assets/custom-theme/images.new/ui-bg_flat_55_fbec88_40x100.png deleted file mode 100644 index cb1dbf293659..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images.new/ui-bg_flat_55_fbec88_40x100.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images.new/ui-bg_flat_75_e5e3e3_40x100.png b/awx/ui/client/assets/custom-theme/images.new/ui-bg_flat_75_e5e3e3_40x100.png deleted file mode 100644 index 9d8b67a2e2ec..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images.new/ui-bg_flat_75_e5e3e3_40x100.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images.new/ui-bg_flat_75_ffffff_40x100.png b/awx/ui/client/assets/custom-theme/images.new/ui-bg_flat_75_ffffff_40x100.png deleted file mode 100644 index 399d1ba7ddc4..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images.new/ui-bg_flat_75_ffffff_40x100.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images.new/ui-bg_glass_55_fbf9ee_1x400.png b/awx/ui/client/assets/custom-theme/images.new/ui-bg_glass_55_fbf9ee_1x400.png deleted file mode 100644 index 9267153440df..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images.new/ui-bg_glass_55_fbf9ee_1x400.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images.new/ui-bg_glass_75_d4d0d0_1x400.png b/awx/ui/client/assets/custom-theme/images.new/ui-bg_glass_75_d4d0d0_1x400.png deleted file mode 100644 index f456416e018f..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images.new/ui-bg_glass_75_d4d0d0_1x400.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images.new/ui-bg_glass_85_f5f5f5_1x400.png b/awx/ui/client/assets/custom-theme/images.new/ui-bg_glass_85_f5f5f5_1x400.png deleted file mode 100644 index d69f282beed7..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images.new/ui-bg_glass_85_f5f5f5_1x400.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images.new/ui-bg_glass_95_fef1ec_1x400.png b/awx/ui/client/assets/custom-theme/images.new/ui-bg_glass_95_fef1ec_1x400.png deleted file mode 100644 index be40bcf3c30d..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images.new/ui-bg_glass_95_fef1ec_1x400.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images.new/ui-bg_gloss-wave_50_f5f5f5_500x100.png b/awx/ui/client/assets/custom-theme/images.new/ui-bg_gloss-wave_50_f5f5f5_500x100.png deleted file mode 100644 index e73bbb88df58..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images.new/ui-bg_gloss-wave_50_f5f5f5_500x100.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images.new/ui-bg_glow-ball_100_fff_600x600.png b/awx/ui/client/assets/custom-theme/images.new/ui-bg_glow-ball_100_fff_600x600.png deleted file mode 100644 index b170873d1f6d..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images.new/ui-bg_glow-ball_100_fff_600x600.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images.new/ui-bg_inset-hard_100_f5f5f5_1x100.png b/awx/ui/client/assets/custom-theme/images.new/ui-bg_inset-hard_100_f5f5f5_1x100.png deleted file mode 100644 index 2ffa648549ac..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images.new/ui-bg_inset-hard_100_f5f5f5_1x100.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images.new/ui-bg_inset-hard_100_fcfdfd_1x100.png b/awx/ui/client/assets/custom-theme/images.new/ui-bg_inset-hard_100_fcfdfd_1x100.png deleted file mode 100644 index 534a864605ca..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images.new/ui-bg_inset-hard_100_fcfdfd_1x100.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images.new/ui-icons_0088cc_256x240.png b/awx/ui/client/assets/custom-theme/images.new/ui-icons_0088cc_256x240.png deleted file mode 100644 index 5018f318bcce..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images.new/ui-icons_0088cc_256x240.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images.new/ui-icons_1778c3_256x240.png b/awx/ui/client/assets/custom-theme/images.new/ui-icons_1778c3_256x240.png deleted file mode 100644 index cbf7bc13a301..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images.new/ui-icons_1778c3_256x240.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images.new/ui-icons_217bc0_256x240.png b/awx/ui/client/assets/custom-theme/images.new/ui-icons_217bc0_256x240.png deleted file mode 100644 index 8d2b7e570444..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images.new/ui-icons_217bc0_256x240.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images.new/ui-icons_2e83ff_256x240.png b/awx/ui/client/assets/custom-theme/images.new/ui-icons_2e83ff_256x240.png deleted file mode 100644 index 84b601bf0f72..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images.new/ui-icons_2e83ff_256x240.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images.new/ui-icons_36454F_256x240.png b/awx/ui/client/assets/custom-theme/images.new/ui-icons_36454F_256x240.png deleted file mode 100644 index 28a8127a65cf..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images.new/ui-icons_36454F_256x240.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images.new/ui-icons_469bdd_256x240.png b/awx/ui/client/assets/custom-theme/images.new/ui-icons_469bdd_256x240.png deleted file mode 100644 index 5dff3f962cd7..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images.new/ui-icons_469bdd_256x240.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images.new/ui-icons_6da8d5_256x240.png b/awx/ui/client/assets/custom-theme/images.new/ui-icons_6da8d5_256x240.png deleted file mode 100644 index f7809f8566cd..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images.new/ui-icons_6da8d5_256x240.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images.new/ui-icons_cd0a0a_256x240.png b/awx/ui/client/assets/custom-theme/images.new/ui-icons_cd0a0a_256x240.png deleted file mode 100644 index ed5b6b0930f6..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images.new/ui-icons_cd0a0a_256x240.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images.new/ui-icons_d8e7f3_256x240.png b/awx/ui/client/assets/custom-theme/images.new/ui-icons_d8e7f3_256x240.png deleted file mode 100644 index 9b46228fb1e8..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images.new/ui-icons_d8e7f3_256x240.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images.new/ui-icons_fff_256x240.png b/awx/ui/client/assets/custom-theme/images.new/ui-icons_fff_256x240.png deleted file mode 100644 index 4f624bb2b193..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images.new/ui-icons_fff_256x240.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images/animated-overlay.gif b/awx/ui/client/assets/custom-theme/images/animated-overlay.gif deleted file mode 100644 index d441f75ebfbd..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images/animated-overlay.gif and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images/ui-bg_flat_0_aaaaaa_40x100.png b/awx/ui/client/assets/custom-theme/images/ui-bg_flat_0_aaaaaa_40x100.png deleted file mode 100644 index 8b3239ca8f32..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images/ui-bg_flat_0_aaaaaa_40x100.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images/ui-bg_flat_100_ffffff_40x100.png b/awx/ui/client/assets/custom-theme/images/ui-bg_flat_100_ffffff_40x100.png deleted file mode 100644 index 708e95ddc09e..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images/ui-bg_flat_100_ffffff_40x100.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images/ui-bg_flat_50_ffffff_40x100.png b/awx/ui/client/assets/custom-theme/images/ui-bg_flat_50_ffffff_40x100.png deleted file mode 100644 index cfaa03e8dd28..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images/ui-bg_flat_50_ffffff_40x100.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images/ui-bg_flat_55_fbec88_40x100.png b/awx/ui/client/assets/custom-theme/images/ui-bg_flat_55_fbec88_40x100.png deleted file mode 100644 index cb1dbf293659..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images/ui-bg_flat_55_fbec88_40x100.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images/ui-bg_flat_75_e5e3e3_40x100.png b/awx/ui/client/assets/custom-theme/images/ui-bg_flat_75_e5e3e3_40x100.png deleted file mode 100644 index 9d8b67a2e2ec..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images/ui-bg_flat_75_e5e3e3_40x100.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images/ui-bg_glass_75_d4d0d0_1x400.png b/awx/ui/client/assets/custom-theme/images/ui-bg_glass_75_d4d0d0_1x400.png deleted file mode 100644 index f456416e018f..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images/ui-bg_glass_75_d4d0d0_1x400.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images/ui-bg_glass_85_f5f5f5_1x400.png b/awx/ui/client/assets/custom-theme/images/ui-bg_glass_85_f5f5f5_1x400.png deleted file mode 100644 index d69f282beed7..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images/ui-bg_glass_85_f5f5f5_1x400.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images/ui-bg_glass_95_fef1ec_1x400.png b/awx/ui/client/assets/custom-theme/images/ui-bg_glass_95_fef1ec_1x400.png deleted file mode 100644 index fa5b93c0195b..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images/ui-bg_glass_95_fef1ec_1x400.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images/ui-bg_gloss-wave_50_f5f5f5_500x100.png b/awx/ui/client/assets/custom-theme/images/ui-bg_gloss-wave_50_f5f5f5_500x100.png deleted file mode 100644 index e73bbb88df58..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images/ui-bg_gloss-wave_50_f5f5f5_500x100.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images/ui-bg_inset-hard_100_f5f5f5_1x100.png b/awx/ui/client/assets/custom-theme/images/ui-bg_inset-hard_100_f5f5f5_1x100.png deleted file mode 100644 index 2ffa648549ac..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images/ui-bg_inset-hard_100_f5f5f5_1x100.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images/ui-bg_inset-hard_100_fcfdfd_1x100.png b/awx/ui/client/assets/custom-theme/images/ui-bg_inset-hard_100_fcfdfd_1x100.png deleted file mode 100644 index 534a864605ca..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images/ui-bg_inset-hard_100_fcfdfd_1x100.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images/ui-icons_0088cc_256x240.png b/awx/ui/client/assets/custom-theme/images/ui-icons_0088cc_256x240.png deleted file mode 100644 index 5018f318bcce..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images/ui-icons_0088cc_256x240.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images/ui-icons_217bc0_256x240.png b/awx/ui/client/assets/custom-theme/images/ui-icons_217bc0_256x240.png deleted file mode 100644 index 8d2b7e570444..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images/ui-icons_217bc0_256x240.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images/ui-icons_2e83ff_256x240.png b/awx/ui/client/assets/custom-theme/images/ui-icons_2e83ff_256x240.png deleted file mode 100644 index 84b601bf0f72..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images/ui-icons_2e83ff_256x240.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images/ui-icons_36454F_256x240.png b/awx/ui/client/assets/custom-theme/images/ui-icons_36454F_256x240.png deleted file mode 100644 index 28a8127a65cf..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images/ui-icons_36454F_256x240.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images/ui-icons_469bdd_256x240.png b/awx/ui/client/assets/custom-theme/images/ui-icons_469bdd_256x240.png deleted file mode 100644 index 5dff3f962cd7..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images/ui-icons_469bdd_256x240.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images/ui-icons_6da8d5_256x240.png b/awx/ui/client/assets/custom-theme/images/ui-icons_6da8d5_256x240.png deleted file mode 100644 index f7809f8566cd..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images/ui-icons_6da8d5_256x240.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images/ui-icons_cd0a0a_256x240.png b/awx/ui/client/assets/custom-theme/images/ui-icons_cd0a0a_256x240.png deleted file mode 100644 index ed5b6b0930f6..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images/ui-icons_cd0a0a_256x240.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/images/ui-icons_d8e7f3_256x240.png b/awx/ui/client/assets/custom-theme/images/ui-icons_d8e7f3_256x240.png deleted file mode 100644 index 9b46228fb1e8..000000000000 Binary files a/awx/ui/client/assets/custom-theme/images/ui-icons_d8e7f3_256x240.png and /dev/null differ diff --git a/awx/ui/client/assets/custom-theme/jquery-ui-1.10.3.custom.css b/awx/ui/client/assets/custom-theme/jquery-ui-1.10.3.custom.css deleted file mode 100644 index 31f7a97f99f5..000000000000 --- a/awx/ui/client/assets/custom-theme/jquery-ui-1.10.3.custom.css +++ /dev/null @@ -1,1183 +0,0 @@ -/*! jQuery UI - v1.10.3 - 2013-06-26 -* http://jqueryui.com -* Includes: jquery.ui.core.css, jquery.ui.resizable.css, jquery.ui.selectable.css, jquery.ui.accordion.css, jquery.ui.autocomplete.css, jquery.ui.button.css, jquery.ui.datepicker.css, jquery.ui.dialog.css, jquery.ui.menu.css, jquery.ui.progressbar.css, jquery.ui.slider.css, jquery.ui.spinner.css, jquery.ui.tabs.css, jquery.ui.tooltip.css -* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Lucida%20Grande%2CLucida%20Sans%2CArial%2Csans-serif&fwDefault=normal&fsDefault=14px&cornerRadius=5px&bgColorHeader=%23ffffff&bgTextureHeader=flat&bgImgOpacityHeader=50&borderColorHeader=%23a6c9e2&fcHeader=%2336454F&iconColorHeader=%2336454F&bgColorContent=%23fcfdfd&bgTextureContent=inset_hard&bgImgOpacityContent=100&borderColorContent=%23a6c9e2&fcContent=%23222222&iconColorContent=%23469bdd&bgColorDefault=%23ffffff&bgTextureDefault=flat&bgImgOpacityDefault=100&borderColorDefault=%23a6c9e2&fcDefault=%230088cc&iconColorDefault=%230088cc&bgColorHover=%23e5e3e3&bgTextureHover=flat&bgImgOpacityHover=75&borderColorHover=%23e3e3e3&fcHover=%23005580&iconColorHover=%23217bc0&bgColorActive=%23f5f5f5&bgTextureActive=inset_hard&bgImgOpacityActive=100&borderColorActive=%23e3e3e3&fcActive=%2336454F&iconColorActive=%2336454F&bgColorHighlight=%23fbec88&bgTextureHighlight=flat&bgImgOpacityHighlight=55&borderColorHighlight=%23fad42e&fcHighlight=%23363636&iconColorHighlight=%232e83ff&bgColorError=%23fef1ec&bgTextureError=glass&bgImgOpacityError=95&borderColorError=%23cd0a0a&fcError=%23cd0a0a&iconColorError=%23cd0a0a&bgColorOverlay=%23aaaaaa&bgTextureOverlay=flat&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=%23aaaaaa&bgTextureShadow=flat&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px -* Copyright 2013 jQuery Foundation and other contributors Licensed MIT */ - -/* Layout helpers -----------------------------------*/ -.ui-helper-hidden { - display: none; -} -.ui-helper-hidden-accessible { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - width: 1px; -} -.ui-helper-reset { - margin: 0; - padding: 0; - border: 0; - outline: 0; - line-height: 1.3; - text-decoration: none; - font-size: 100%; - list-style: none; -} -.ui-helper-clearfix:before, -.ui-helper-clearfix:after { - content: ""; - display: table; - border-collapse: collapse; -} -.ui-helper-clearfix:after { - clear: both; -} -.ui-helper-clearfix { - min-height: 0; /* support: IE7 */ -} -.ui-helper-zfix { - width: 100%; - height: 100%; - top: 0; - left: 0; - position: absolute; - opacity: 0; - filter:Alpha(Opacity=0); -} - -.ui-front { - z-index: 100; -} - - -/* Interaction Cues -----------------------------------*/ -.ui-state-disabled { - cursor: default !important; -} - - -/* Icons -----------------------------------*/ - -/* states and images */ -.ui-icon { - display: block; - text-indent: -99999px; - overflow: hidden; - background-repeat: no-repeat; -} - - -/* Misc visuals -----------------------------------*/ - -/* Overlays */ -.ui-widget-overlay { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; -} -.ui-resizable { - position: relative; -} -.ui-resizable-handle { - position: absolute; - font-size: 0.1px; - display: block; -} -.ui-resizable-disabled .ui-resizable-handle, -.ui-resizable-autohide .ui-resizable-handle { - display: none; -} -.ui-resizable-n { - cursor: n-resize; - height: 7px; - width: 100%; - top: -5px; - left: 0; -} -.ui-resizable-s { - cursor: s-resize; - height: 7px; - width: 100%; - bottom: -5px; - left: 0; -} -.ui-resizable-e { - cursor: e-resize; - width: 7px; - right: -5px; - top: 0; - height: 100%; -} -.ui-resizable-w { - cursor: w-resize; - width: 7px; - left: -5px; - top: 0; - height: 100%; -} -.ui-resizable-se { - cursor: se-resize; - width: 12px; - height: 12px; - right: 1px; - bottom: 1px; -} -.ui-resizable-sw { - cursor: sw-resize; - width: 9px; - height: 9px; - left: -5px; - bottom: -5px; -} -.ui-resizable-nw { - cursor: nw-resize; - width: 9px; - height: 9px; - left: -5px; - top: -5px; -} -.ui-resizable-ne { - cursor: ne-resize; - width: 9px; - height: 9px; - right: -5px; - top: -5px; -} -.ui-selectable-helper { - position: absolute; - z-index: 100; - border: 1px dotted black; -} -.ui-accordion .ui-accordion-header { - display: block; - cursor: pointer; - position: relative; - margin-top: 2px; - padding: .5em .5em .5em .7em; - min-height: 0; /* support: IE7 */ -} - -.ui-accordion-header { - font-weight: bold; -} - -.ui-accordion .ui-accordion-icons { - padding-left: 2.2em; -} -.ui-accordion .ui-accordion-noicons { - padding-left: .7em; -} -.ui-accordion .ui-accordion-icons .ui-accordion-icons { - padding-left: 2.2em; -} -.ui-accordion .ui-accordion-header .ui-accordion-header-icon { - position: absolute; - left: .5em; - top: 50%; - margin-top: -8px; -} -.ui-accordion .ui-accordion-content { - padding: 1em 2.2em; - border-top: 0; - overflow: auto; -} -.ui-autocomplete { - position: absolute; - top: 0; - left: 0; - cursor: default; -} -.ui-button { - display: inline-block; - position: relative; - padding: 0; - line-height: normal; - margin-right: .1em; - cursor: pointer; - vertical-align: middle; - text-align: center; - overflow: visible; /* removes extra width in IE */ -} -.ui-button, -.ui-button:link, -.ui-button:visited, -.ui-button:hover, -.ui-button:active { - text-decoration: none; -} -/* to make room for the icon, a width needs to be set here */ -.ui-button-icon-only { - width: 2.2em; -} -/* button elements seem to need a little more width */ -button.ui-button-icon-only { - width: 2.4em; -} -.ui-button-icons-only { - width: 3.4em; -} -button.ui-button-icons-only { - width: 3.7em; -} - -/* button text element */ -.ui-button .ui-button-text { - display: block; - line-height: normal; -} -.ui-button-text-only .ui-button-text { - padding: .4em 1em; -} -.ui-button-icon-only .ui-button-text, -.ui-button-icons-only .ui-button-text { - padding: .4em; - text-indent: -9999999px; -} -.ui-button-text-icon-primary .ui-button-text, -.ui-button-text-icons .ui-button-text { - padding: .4em 1em .4em 2.1em; -} -.ui-button-text-icon-secondary .ui-button-text, -.ui-button-text-icons .ui-button-text { - padding: .4em 2.1em .4em 1em; -} -.ui-button-text-icons .ui-button-text { - padding-left: 2.1em; - padding-right: 2.1em; -} -/* no icon support for input elements, provide padding by default */ -input.ui-button { - padding: .4em 1em; -} - -/* button icon element(s) */ -.ui-button-icon-only .ui-icon, -.ui-button-text-icon-primary .ui-icon, -.ui-button-text-icon-secondary .ui-icon, -.ui-button-text-icons .ui-icon, -.ui-button-icons-only .ui-icon { - position: absolute; - top: 50%; - margin-top: -8px; -} -.ui-button-icon-only .ui-icon { - left: 50%; - margin-left: -8px; -} -.ui-button-text-icon-primary .ui-button-icon-primary, -.ui-button-text-icons .ui-button-icon-primary, -.ui-button-icons-only .ui-button-icon-primary { - left: .5em; -} -.ui-button-text-icon-secondary .ui-button-icon-secondary, -.ui-button-text-icons .ui-button-icon-secondary, -.ui-button-icons-only .ui-button-icon-secondary { - right: .5em; -} - -/* button sets */ -.ui-buttonset { - margin-right: 7px; -} -.ui-buttonset .ui-button { - margin-left: 0; - margin-right: -.3em; -} - -/* workarounds */ -/* reset extra padding in Firefox, see h5bp.com/l */ -input.ui-button::-moz-focus-inner, -button.ui-button::-moz-focus-inner { - border: 0; - padding: 0; -} -.ui-datepicker { - width: 17em; - padding: .2em .2em 0; - display: none; -} -.ui-datepicker .ui-datepicker-header { - position: relative; - padding: .2em 0; -} -.ui-datepicker .ui-datepicker-prev, -.ui-datepicker .ui-datepicker-next { - position: absolute; - top: 2px; - width: 1.8em; - height: 1.8em; -} -.ui-datepicker .ui-datepicker-prev-hover, -.ui-datepicker .ui-datepicker-next-hover { - top: 1px; -} -.ui-datepicker .ui-datepicker-prev { - left: 2px; -} -.ui-datepicker .ui-datepicker-next { - right: 2px; -} -.ui-datepicker .ui-datepicker-prev-hover { - left: 1px; -} -.ui-datepicker .ui-datepicker-next-hover { - right: 1px; -} -.ui-datepicker .ui-datepicker-prev span, -.ui-datepicker .ui-datepicker-next span { - display: block; - position: absolute; - left: 50%; - margin-left: -8px; - top: 50%; - margin-top: -8px; -} -.ui-datepicker .ui-datepicker-title { - margin: 0 2.3em; - line-height: 1.8em; - text-align: center; -} -.ui-datepicker .ui-datepicker-title select { - font-size: 1em; - margin: 1px 0; -} -.ui-datepicker select.ui-datepicker-month-year { - width: 100%; -} -.ui-datepicker select.ui-datepicker-month, -.ui-datepicker select.ui-datepicker-year { - width: 49%; -} -.ui-datepicker table { - width: 100%; - font-size: .9em; - border-collapse: collapse; - margin: 0 0 .4em; -} -.ui-datepicker th { - padding: .7em .3em; - text-align: center; - font-weight: bold; - border: 0; -} -.ui-datepicker td { - border: 0; - padding: 1px; -} -.ui-datepicker td span, -.ui-datepicker td a { - display: block; - padding: .2em; - text-align: right; - text-decoration: none; -} -.ui-datepicker .ui-datepicker-buttonpane { - background-image: none; - margin: .7em 0 0 0; - padding: 0 .2em; - border-left: 0; - border-right: 0; - border-bottom: 0; -} -.ui-datepicker .ui-datepicker-buttonpane button { - float: right; - margin: .5em .2em .4em; - cursor: pointer; - padding: .2em .6em .3em .6em; - width: auto; - overflow: visible; -} -.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { - float: left; -} - -/* with multiple calendars */ -.ui-datepicker.ui-datepicker-multi { - width: auto; -} -.ui-datepicker-multi .ui-datepicker-group { - float: left; -} -.ui-datepicker-multi .ui-datepicker-group table { - width: 95%; - margin: 0 auto .4em; -} -.ui-datepicker-multi-2 .ui-datepicker-group { - width: 50%; -} -.ui-datepicker-multi-3 .ui-datepicker-group { - width: 33.3%; -} -.ui-datepicker-multi-4 .ui-datepicker-group { - width: 25%; -} -.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header, -.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { - border-left-width: 0; -} -.ui-datepicker-multi .ui-datepicker-buttonpane { - clear: left; -} -.ui-datepicker-row-break { - clear: both; - width: 100%; - font-size: 0; -} - -/* RTL support */ -.ui-datepicker-rtl { - direction: rtl; -} -.ui-datepicker-rtl .ui-datepicker-prev { - right: 2px; - left: auto; -} -.ui-datepicker-rtl .ui-datepicker-next { - left: 2px; - right: auto; -} -.ui-datepicker-rtl .ui-datepicker-prev:hover { - right: 1px; - left: auto; -} -.ui-datepicker-rtl .ui-datepicker-next:hover { - left: 1px; - right: auto; -} -.ui-datepicker-rtl .ui-datepicker-buttonpane { - clear: right; -} -.ui-datepicker-rtl .ui-datepicker-buttonpane button { - float: left; -} -.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current, -.ui-datepicker-rtl .ui-datepicker-group { - float: right; -} -.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header, -.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { - border-right-width: 0; - border-left-width: 1px; -} -.ui-dialog { - position: absolute; - top: 0; - left: 0; - padding: .2em; - outline: 0; -} -.ui-dialog .ui-dialog-titlebar { - padding: .4em 1em; - position: relative; -} -.ui-dialog .ui-dialog-title { - float: left; - margin: .1em 0; - white-space: nowrap; - width: 90%; - overflow: hidden; - text-overflow: ellipsis; -} -.ui-dialog .ui-dialog-titlebar-close { - position: absolute; - right: .3em; - top: 50%; - width: 21px; - margin: -10px 0 0 0; - padding: 1px; - height: 20px; -} -.ui-dialog .ui-dialog-content { - position: relative; - border: 0; - padding: .5em 1em; - background: none; - overflow: auto; -} -.ui-dialog .ui-dialog-buttonpane { - text-align: left; - border-width: 1px 0 0 0; - background-image: none; - margin-top: .5em; - padding: .3em 1em .5em .4em; -} -.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { - float: right; -} -.ui-dialog .ui-dialog-buttonpane button { - margin: .5em .4em .5em 0; - cursor: pointer; -} -.ui-dialog .ui-resizable-se { - width: 12px; - height: 12px; - right: -5px; - bottom: -5px; - background-position: 16px 16px; -} -.ui-draggable .ui-dialog-titlebar { - cursor: move; -} -.ui-menu { - list-style: none; - padding: 2px; - margin: 0; - display: block; - outline: none; -} -.ui-menu .ui-menu { - margin-top: -3px; - position: absolute; -} -.ui-menu .ui-menu-item { - margin: 0; - padding: 0; - width: 100%; - /* support: IE10, see #8844 */ - list-style-image: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7); -} -.ui-menu .ui-menu-divider { - margin: 5px -2px 5px -2px; - height: 0; - font-size: 0; - line-height: 0; - border-width: 1px 0 0 0; -} -.ui-menu .ui-menu-item a { - text-decoration: none; - display: block; - padding: 2px .4em; - line-height: 1.5; - min-height: 0; /* support: IE7 */ - font-weight: normal; -} -.ui-menu .ui-menu-item a.ui-state-focus, -.ui-menu .ui-menu-item a.ui-state-active { - font-weight: normal; - margin: -1px; -} - -.ui-menu .ui-state-disabled { - font-weight: normal; - margin: .4em 0 .2em; - line-height: 1.5; -} -.ui-menu .ui-state-disabled a { - cursor: default; -} - -/* icon support */ -.ui-menu-icons { - position: relative; -} -.ui-menu-icons .ui-menu-item a { - position: relative; - padding-left: 2em; -} - -/* left-aligned */ -.ui-menu .ui-icon { - position: absolute; - top: .2em; - left: .2em; -} - -/* right-aligned */ -.ui-menu .ui-menu-icon { - position: static; - float: right; -} -.ui-progressbar { - height: 2em; - text-align: left; - overflow: hidden; -} -.ui-progressbar .ui-progressbar-value { - margin: -1px; - height: 100%; -} -.ui-progressbar .ui-progressbar-overlay { - background: url("/static/images/animated-overlay.gif"); - height: 100%; - filter: alpha(opacity=25); - opacity: 0.25; -} -.ui-progressbar-indeterminate .ui-progressbar-value { - background-image: none; -} -.ui-slider { - position: relative; - text-align: left; -} -.ui-slider .ui-slider-handle { - position: absolute; - z-index: 2; - width: 1.2em; - height: 1.2em; - cursor: default; -} -.ui-slider .ui-slider-range { - position: absolute; - z-index: 1; - font-size: .7em; - display: block; - border: 0; - background-position: 0 0; -} - -/* For IE8 - See #6727 */ -.ui-slider.ui-state-disabled .ui-slider-handle, -.ui-slider.ui-state-disabled .ui-slider-range { - filter: inherit; -} - -.ui-slider-horizontal { - height: .8em; -} -.ui-slider-horizontal .ui-slider-handle { - top: -.3em; - margin-left: -.6em; -} -.ui-slider-horizontal .ui-slider-range { - top: 0; - height: 100%; -} -.ui-slider-horizontal .ui-slider-range-min { - left: 0; -} -.ui-slider-horizontal .ui-slider-range-max { - right: 0; -} - -.ui-slider-vertical { - width: .8em; - height: 100px; -} -.ui-slider-vertical .ui-slider-handle { - left: -.3em; - margin-left: 0; - margin-bottom: -.6em; -} -.ui-slider-vertical .ui-slider-range { - left: 0; - width: 100%; -} -.ui-slider-vertical .ui-slider-range-min { - bottom: 0; -} -.ui-slider-vertical .ui-slider-range-max { - top: 0; -} -.ui-spinner { - position: relative; - display: inline-block; - overflow: hidden; - padding: 0; - vertical-align: middle; -} -.ui-spinner-input { - border: none; - background: none; - color: inherit; - padding: 0; - margin: .2em 0; - vertical-align: middle; - margin-left: .4em; - margin-right: 22px; -} -.ui-spinner-button { - width: 16px; - height: 50%; - font-size: .5em; - padding: 0; - margin: 0; - text-align: center; - position: absolute; - cursor: default; - display: block; - overflow: hidden; - right: 0; -} -/* more specificity required here to overide default borders */ -.ui-spinner a.ui-spinner-button { - border-top: none; - border-bottom: none; - border-right: none; -} -/* vertical centre icon */ -.ui-spinner .ui-icon { - position: absolute; - margin-top: -8px; - top: 50%; - left: 0; -} -.ui-spinner-up { - top: 0; -} -.ui-spinner-down { - bottom: 0; -} - -/* TR overrides */ -.ui-spinner .ui-icon-triangle-1-s { - /* need to fix icons sprite */ - background-position: -65px -16px; -} -.ui-tabs { - position: relative;/* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ - padding: .2em; -} -.ui-tabs .ui-tabs-nav { - margin: 0; - padding: .2em .2em 0; -} -.ui-tabs .ui-tabs-nav li { - list-style: none; - float: left; - position: relative; - top: 0; - margin: 1px .2em 0 0; - border-bottom-width: 0; - padding: 0; - white-space: nowrap; -} -.ui-tabs .ui-tabs-nav li a { - float: left; - padding: .5em 1em; - text-decoration: none; -} -.ui-tabs .ui-tabs-nav li.ui-tabs-active { - margin-bottom: -1px; - padding-bottom: 1px; -} -.ui-tabs .ui-tabs-nav li.ui-tabs-active a, -.ui-tabs .ui-tabs-nav li.ui-state-disabled a, -.ui-tabs .ui-tabs-nav li.ui-tabs-loading a { - cursor: text; -} -.ui-tabs .ui-tabs-nav li a, /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */ -.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active a { - cursor: pointer; -} -.ui-tabs .ui-tabs-panel { - display: block; - border-width: 0; - padding: 1em 1.4em; - background: none; -} -.ui-tooltip { - padding: 8px; - position: absolute; - z-index: 9999; - max-width: 300px; - -webkit-box-shadow: 0 0 5px #aaa; - box-shadow: 0 0 5px #aaa; -} -body .ui-tooltip { - border-width: 2px; -} - -/* Component containers -----------------------------------*/ -.ui-widget { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 14px; -} -.ui-widget .ui-widget { - font-size: 1em; -} -.ui-widget input, -.ui-widget select, -.ui-widget textarea, -.ui-widget button { - /*font-family: Lucida Grande,Lucida Sans,Arial,sans-serif; - font-size: 1em;*/ -} -.ui-widget-content { - border: 1px solid #a6c9e2; - background: #fcfdfd url(/static/images/ui-bg_inset-hard_100_fcfdfd_1x100.png) 50% bottom repeat-x; - color: #36454F; - font-weight: normal; -} -.ui-widget-content a { - color: #0088cc; -} -.ui-widget-header { - border: 1px solid #a6c9e2; - background: #ffffff url(/static/images/ui-bg_flat_50_ffffff_40x100.png) 50% 50% repeat-x; - color: #36454F; - font-weight: bold; -} -.ui-widget-header a { - color: #36454F; -} - -/* Interaction states -----------------------------------*/ -.ui-state-default, -.ui-widget-content .ui-state-default, -.ui-widget-header .ui-state-default { - border: 1px solid #a6c9e2; - background: #ffffff url(/static/images/ui-bg_flat_100_ffffff_40x100.png) 50% 50% repeat-x; - font-weight: bold; - color: #0088cc; -} -.ui-state-default a, -.ui-state-default a:link, -.ui-state-default a:visited { - color: #0088cc; - text-decoration: none; -} -.ui-state-hover, -.ui-widget-content .ui-state-hover, -.ui-widget-header .ui-state-hover, -.ui-state-focus, -.ui-widget-content .ui-state-focus, -.ui-widget-header .ui-state-focus { - border: 1px solid #e3e3e3; - background: #e5e3e3 url(/static/images/ui-bg_flat_75_e5e3e3_40x100.png) 50% 50% repeat-x; - font-weight: bold; - color: #005580; -} -.ui-state-hover a, -.ui-state-hover a:hover, -.ui-state-hover a:link, -.ui-state-hover a:visited { - color: #005580; - text-decoration: none; -} -.ui-state-active, -.ui-widget-content .ui-state-active, -.ui-widget-header .ui-state-active { - border: 1px solid #e3e3e3; - background: #f5f5f5 url(/static/images/ui-bg_inset-hard_100_f5f5f5_1x100.png) 50% 50% repeat-x; - font-weight: bold; - color: #36454F; -} -.ui-state-active a, -.ui-state-active a:link, -.ui-state-active a:visited { - color: #36454F; - text-decoration: none; -} - -/* Interaction Cues -----------------------------------*/ -.ui-state-highlight, -.ui-widget-content .ui-state-highlight, -.ui-widget-header .ui-state-highlight { - border: 1px solid #fad42e; - background: #fbec88 url(/static/images/ui-bg_flat_55_fbec88_40x100.png) 50% 50% repeat-x; - color: #363636; -} -.ui-state-highlight a, -.ui-widget-content .ui-state-highlight a, -.ui-widget-header .ui-state-highlight a { - color: #363636; -} -.ui-state-error, -.ui-widget-content .ui-state-error, -.ui-widget-header .ui-state-error { - border: 1px solid #cd0a0a; - background: #fef1ec url(/static/images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x; - color: #cd0a0a; -} -.ui-state-error a, -.ui-widget-content .ui-state-error a, -.ui-widget-header .ui-state-error a { - color: #cd0a0a; -} -.ui-state-error-text, -.ui-widget-content .ui-state-error-text, -.ui-widget-header .ui-state-error-text { - color: #cd0a0a; -} -.ui-priority-primary, -.ui-widget-content .ui-priority-primary, -.ui-widget-header .ui-priority-primary { - font-weight: normal; -} -.ui-priority-secondary, -.ui-widget-content .ui-priority-secondary, -.ui-widget-header .ui-priority-secondary { - opacity: .7; - filter:Alpha(Opacity=70); - font-weight: bold; -} -.ui-state-disabled, -.ui-widget-content .ui-state-disabled, -.ui-widget-header .ui-state-disabled { - opacity: .35; - filter:Alpha(Opacity=35); - background-image: none; -} -.ui-state-disabled .ui-icon { - filter:Alpha(Opacity=35); /* For IE8 - See #6059 */ -} - -/* Icons -----------------------------------*/ - -/* states and images */ -.ui-icon { - width: 16px; - height: 16px; -} -.ui-icon, -.ui-widget-content .ui-icon { - background-image: url(/static/images/ui-icons_469bdd_256x240.png); -} -.ui-widget-header .ui-icon { - background-image: url(/static/images/ui-icons_36454F_256x240.png); -} -.ui-state-default .ui-icon { - background-image: url(/static/images/ui-icons_0088cc_256x240.png); -} -.ui-state-hover .ui-icon, -.ui-state-focus .ui-icon { - background-image: url(/static/images/ui-icons_217bc0_256x240.png); -} -.ui-state-active .ui-icon { - background-image: url(/static/images/ui-icons_36454F_256x240.png); -} -.ui-state-highlight .ui-icon { - background-image: url(/static/images/ui-icons_2e83ff_256x240.png); -} -.ui-state-error .ui-icon, -.ui-state-error-text .ui-icon { - background-image: url(/static/images/ui-icons_cd0a0a_256x240.png); -} - -/* positioning */ -.ui-icon-blank { background-position: 16px 16px; } -.ui-icon-carat-1-n { background-position: 0 0; } -.ui-icon-carat-1-ne { background-position: -16px 0; } -.ui-icon-carat-1-e { background-position: -32px 0; } -.ui-icon-carat-1-se { background-position: -48px 0; } -.ui-icon-carat-1-s { background-position: -64px 0; } -.ui-icon-carat-1-sw { background-position: -80px 0; } -.ui-icon-carat-1-w { background-position: -96px 0; } -.ui-icon-carat-1-nw { background-position: -112px 0; } -.ui-icon-carat-2-n-s { background-position: -128px 0; } -.ui-icon-carat-2-e-w { background-position: -144px 0; } -.ui-icon-triangle-1-n { background-position: 0 -16px; } -.ui-icon-triangle-1-ne { background-position: -16px -16px; } -.ui-icon-triangle-1-e { background-position: -32px -16px; } -.ui-icon-triangle-1-se { background-position: -48px -16px; } -.ui-icon-triangle-1-s { background-position: -64px -16px; } -.ui-icon-triangle-1-sw { background-position: -80px -16px; } -.ui-icon-triangle-1-w { background-position: -96px -16px; } -.ui-icon-triangle-1-nw { background-position: -112px -16px; } -.ui-icon-triangle-2-n-s { background-position: -128px -16px; } -.ui-icon-triangle-2-e-w { background-position: -144px -16px; } -.ui-icon-arrow-1-n { background-position: 0 -32px; } -.ui-icon-arrow-1-ne { background-position: -16px -32px; } -.ui-icon-arrow-1-e { background-position: -32px -32px; } -.ui-icon-arrow-1-se { background-position: -48px -32px; } -.ui-icon-arrow-1-s { background-position: -64px -32px; } -.ui-icon-arrow-1-sw { background-position: -80px -32px; } -.ui-icon-arrow-1-w { background-position: -96px -32px; } -.ui-icon-arrow-1-nw { background-position: -112px -32px; } -.ui-icon-arrow-2-n-s { background-position: -128px -32px; } -.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } -.ui-icon-arrow-2-e-w { background-position: -160px -32px; } -.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } -.ui-icon-arrowstop-1-n { background-position: -192px -32px; } -.ui-icon-arrowstop-1-e { background-position: -208px -32px; } -.ui-icon-arrowstop-1-s { background-position: -224px -32px; } -.ui-icon-arrowstop-1-w { background-position: -240px -32px; } -.ui-icon-arrowthick-1-n { background-position: 0 -48px; } -.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } -.ui-icon-arrowthick-1-e { background-position: -32px -48px; } -.ui-icon-arrowthick-1-se { background-position: -48px -48px; } -.ui-icon-arrowthick-1-s { background-position: -64px -48px; } -.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } -.ui-icon-arrowthick-1-w { background-position: -96px -48px; } -.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } -.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } -.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } -.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } -.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } -.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } -.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } -.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } -.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } -.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } -.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } -.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } -.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } -.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } -.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } -.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } -.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } -.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } -.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } -.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } -.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } -.ui-icon-arrow-4 { background-position: 0 -80px; } -.ui-icon-arrow-4-diag { background-position: -16px -80px; } -.ui-icon-extlink { background-position: -32px -80px; } -.ui-icon-newwin { background-position: -48px -80px; } -.ui-icon-refresh { background-position: -64px -80px; } -.ui-icon-shuffle { background-position: -80px -80px; } -.ui-icon-transfer-e-w { background-position: -96px -80px; } -.ui-icon-transferthick-e-w { background-position: -112px -80px; } -.ui-icon-folder-collapsed { background-position: 0 -96px; } -.ui-icon-folder-open { background-position: -16px -96px; } -.ui-icon-document { background-position: -32px -96px; } -.ui-icon-document-b { background-position: -48px -96px; } -.ui-icon-note { background-position: -64px -96px; } -.ui-icon-mail-closed { background-position: -80px -96px; } -.ui-icon-mail-open { background-position: -96px -96px; } -.ui-icon-suitcase { background-position: -112px -96px; } -.ui-icon-comment { background-position: -128px -96px; } -.ui-icon-person { background-position: -144px -96px; } -.ui-icon-print { background-position: -160px -96px; } -.ui-icon-trash { background-position: -176px -96px; } -.ui-icon-locked { background-position: -192px -96px; } -.ui-icon-unlocked { background-position: -208px -96px; } -.ui-icon-bookmark { background-position: -224px -96px; } -.ui-icon-tag { background-position: -240px -96px; } -.ui-icon-home { background-position: 0 -112px; } -.ui-icon-flag { background-position: -16px -112px; } -.ui-icon-calendar { background-position: -32px -112px; } -.ui-icon-cart { background-position: -48px -112px; } -.ui-icon-pencil { background-position: -64px -112px; } -.ui-icon-clock { background-position: -80px -112px; } -.ui-icon-disk { background-position: -96px -112px; } -.ui-icon-calculator { background-position: -112px -112px; } -.ui-icon-zoomin { background-position: -128px -112px; } -.ui-icon-zoomout { background-position: -144px -112px; } -.ui-icon-search { background-position: -160px -112px; } -.ui-icon-wrench { background-position: -176px -112px; } -.ui-icon-gear { background-position: -192px -112px; } -.ui-icon-heart { background-position: -208px -112px; } -.ui-icon-star { background-position: -224px -112px; } -.ui-icon-link { background-position: -240px -112px; } -.ui-icon-cancel { background-position: 0 -128px; } -.ui-icon-plus { background-position: -16px -128px; } -.ui-icon-plusthick { background-position: -32px -128px; } -.ui-icon-minus { background-position: -48px -128px; } -.ui-icon-minusthick { background-position: -64px -128px; } -.ui-icon-close { background-position: -80px -128px; } -.ui-icon-closethick { background-position: -96px -128px; } -.ui-icon-key { background-position: -112px -128px; } -.ui-icon-lightbulb { background-position: -128px -128px; } -.ui-icon-scissors { background-position: -144px -128px; } -.ui-icon-clipboard { background-position: -160px -128px; } -.ui-icon-copy { background-position: -176px -128px; } -.ui-icon-contact { background-position: -192px -128px; } -.ui-icon-image { background-position: -208px -128px; } -.ui-icon-video { background-position: -224px -128px; } -.ui-icon-script { background-position: -240px -128px; } -.ui-icon-alert { background-position: 0 -144px; } -.ui-icon-info { background-position: -16px -144px; } -.ui-icon-notice { background-position: -32px -144px; } -.ui-icon-help { background-position: -48px -144px; } -.ui-icon-check { background-position: -64px -144px; } -.ui-icon-bullet { background-position: -80px -144px; } -.ui-icon-radio-on { background-position: -96px -144px; } -.ui-icon-radio-off { background-position: -112px -144px; } -.ui-icon-pin-w { background-position: -128px -144px; } -.ui-icon-pin-s { background-position: -144px -144px; } -.ui-icon-play { background-position: 0 -160px; } -.ui-icon-pause { background-position: -16px -160px; } -.ui-icon-seek-next { background-position: -32px -160px; } -.ui-icon-seek-prev { background-position: -48px -160px; } -.ui-icon-seek-end { background-position: -64px -160px; } -.ui-icon-seek-start { background-position: -80px -160px; } -/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ -.ui-icon-seek-first { background-position: -80px -160px; } -.ui-icon-stop { background-position: -96px -160px; } -.ui-icon-eject { background-position: -112px -160px; } -.ui-icon-volume-off { background-position: -128px -160px; } -.ui-icon-volume-on { background-position: -144px -160px; } -.ui-icon-power { background-position: 0 -176px; } -.ui-icon-signal-diag { background-position: -16px -176px; } -.ui-icon-signal { background-position: -32px -176px; } -.ui-icon-battery-0 { background-position: -48px -176px; } -.ui-icon-battery-1 { background-position: -64px -176px; } -.ui-icon-battery-2 { background-position: -80px -176px; } -.ui-icon-battery-3 { background-position: -96px -176px; } -.ui-icon-circle-plus { background-position: 0 -192px; } -.ui-icon-circle-minus { background-position: -16px -192px; } -.ui-icon-circle-close { background-position: -32px -192px; } -.ui-icon-circle-triangle-e { background-position: -48px -192px; } -.ui-icon-circle-triangle-s { background-position: -64px -192px; } -.ui-icon-circle-triangle-w { background-position: -80px -192px; } -.ui-icon-circle-triangle-n { background-position: -96px -192px; } -.ui-icon-circle-arrow-e { background-position: -112px -192px; } -.ui-icon-circle-arrow-s { background-position: -128px -192px; } -.ui-icon-circle-arrow-w { background-position: -144px -192px; } -.ui-icon-circle-arrow-n { background-position: -160px -192px; } -.ui-icon-circle-zoomin { background-position: -176px -192px; } -.ui-icon-circle-zoomout { background-position: -192px -192px; } -.ui-icon-circle-check { background-position: -208px -192px; } -.ui-icon-circlesmall-plus { background-position: 0 -208px; } -.ui-icon-circlesmall-minus { background-position: -16px -208px; } -.ui-icon-circlesmall-close { background-position: -32px -208px; } -.ui-icon-squaresmall-plus { background-position: -48px -208px; } -.ui-icon-squaresmall-minus { background-position: -64px -208px; } -.ui-icon-squaresmall-close { background-position: -80px -208px; } -.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } -.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } -.ui-icon-grip-solid-vertical { background-position: -32px -224px; } -.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } -.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } -.ui-icon-grip-diagonal-se { background-position: -80px -224px; } - - -/* Misc visuals -----------------------------------*/ - -/* Corner radius */ -.ui-corner-all, -.ui-corner-top, -.ui-corner-left, -.ui-corner-tl { - border-top-left-radius: 5px; -} -.ui-corner-all, -.ui-corner-top, -.ui-corner-right, -.ui-corner-tr { - border-top-right-radius: 5px; -} -.ui-corner-all, -.ui-corner-bottom, -.ui-corner-left, -.ui-corner-bl { - border-bottom-left-radius: 5px; -} -.ui-corner-all, -.ui-corner-bottom, -.ui-corner-right, -.ui-corner-br { - border-bottom-right-radius: 5px; -} - -/* Overlays */ -.ui-widget-overlay { - background: #aaaaaa url(/static/images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; - opacity: .3; - filter: Alpha(Opacity=30); -} -.ui-widget-shadow { - margin: -8px 0 0 -8px; - padding: 8px; - background: #aaaaaa url(/static/images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; - opacity: .3; - filter: Alpha(Opacity=30); - border-radius: 8px; -} diff --git a/awx/ui/client/assets/custom-theme/jquery-ui-1.10.3.custom.min.css b/awx/ui/client/assets/custom-theme/jquery-ui-1.10.3.custom.min.css deleted file mode 100644 index d8794ea158d1..000000000000 --- a/awx/ui/client/assets/custom-theme/jquery-ui-1.10.3.custom.min.css +++ /dev/null @@ -1,358 +0,0 @@ -.ui-helper-hidden{display:none;} -.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;} -.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none;} -.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse;} -.ui-helper-clearfix:after{clear:both;} -.ui-helper-clearfix{min-height:0;} -.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:alpha(opacity=0);} -.ui-front{z-index:100;} -.ui-state-disabled{cursor:default !important;} -.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat;} -.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%;} -.ui-resizable{position:relative;} -.ui-resizable-handle{position:absolute;font-size:0.1px;display:block;} -.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none;} -.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0;} -.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0;} -.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%;} -.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%;} -.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px;} -.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px;} -.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px;} -.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px;} -.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black;} -.ui-accordion .ui-accordion-header{display:block;cursor:pointer;position:relative;margin-top:2px;padding:.5em .5em .5em .7em;min-height:0;} -.ui-accordion-header{font-weight:bold;} -.ui-accordion .ui-accordion-icons{padding-left:2.2em;} -.ui-accordion .ui-accordion-noicons{padding-left:.7em;} -.ui-accordion .ui-accordion-icons .ui-accordion-icons{padding-left:2.2em;} -.ui-accordion .ui-accordion-header .ui-accordion-header-icon{position:absolute;left:.5em;top:50%;margin-top:-8px;} -.ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;overflow:auto;} -.ui-autocomplete{position:absolute;top:0;left:0;cursor:default;} -.ui-button{display:inline-block;position:relative;padding:0;line-height:normal;margin-right:.1em;cursor:pointer;vertical-align:middle;text-align:center;overflow:visible;} -.ui-button,.ui-button:link,.ui-button:visited,.ui-button:hover,.ui-button:active{text-decoration:none;} -.ui-button-icon-only{width:2.2em;} -button.ui-button-icon-only{width:2.4em;} -.ui-button-icons-only{width:3.4em;} -button.ui-button-icons-only{width:3.7em;} -.ui-button .ui-button-text{display:block;line-height:normal;} -.ui-button-text-only .ui-button-text{padding:.4em 1em;} -.ui-button-icon-only .ui-button-text,.ui-button-icons-only .ui-button-text{padding:.4em;text-indent:-9999999px;} -.ui-button-text-icon-primary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 1em .4em 2.1em;} -.ui-button-text-icon-secondary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 2.1em .4em 1em;} -.ui-button-text-icons .ui-button-text{padding-left:2.1em;padding-right:2.1em;} -input.ui-button{padding:.4em 1em;} -.ui-button-icon-only .ui-icon,.ui-button-text-icon-primary .ui-icon,.ui-button-text-icon-secondary .ui-icon,.ui-button-text-icons .ui-icon,.ui-button-icons-only .ui-icon{position:absolute;top:50%;margin-top:-8px;} -.ui-button-icon-only .ui-icon{left:50%;margin-left:-8px;} -.ui-button-text-icon-primary .ui-button-icon-primary,.ui-button-text-icons .ui-button-icon-primary,.ui-button-icons-only .ui-button-icon-primary{left:.5em;} -.ui-button-text-icon-secondary .ui-button-icon-secondary,.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{right:.5em;} -.ui-buttonset{margin-right:7px;} -.ui-buttonset .ui-button{margin-left:0;margin-right:-0.3em;} -input.ui-button::-moz-focus-inner,button.ui-button::-moz-focus-inner{border:0;padding:0;} -.ui-datepicker{width:17em;padding:.2em .2em 0;display:none;} -.ui-datepicker .ui-datepicker-header{position:relative;padding:.2em 0;} -.ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-next{position:absolute;top:2px;width:1.8em;height:1.8em;} -.ui-datepicker .ui-datepicker-prev-hover,.ui-datepicker .ui-datepicker-next-hover{top:1px;} -.ui-datepicker .ui-datepicker-prev{left:2px;} -.ui-datepicker .ui-datepicker-next{right:2px;} -.ui-datepicker .ui-datepicker-prev-hover{left:1px;} -.ui-datepicker .ui-datepicker-next-hover{right:1px;} -.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{display:block;position:absolute;left:50%;margin-left:-8px;top:50%;margin-top:-8px;} -.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center;} -.ui-datepicker .ui-datepicker-title select{font-size:1em;margin:1px 0;} -.ui-datepicker select.ui-datepicker-month-year{width:100%;} -.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:49%;} -.ui-datepicker table{width:100%;font-size:.9em;border-collapse:collapse;margin:0 0 .4em;} -.ui-datepicker th{padding:.7em .3em;text-align:center;font-weight:bold;border:0;} -.ui-datepicker td{border:0;padding:1px;} -.ui-datepicker td span,.ui-datepicker td a{display:block;padding:.2em;text-align:right;text-decoration:none;} -.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:.7em 0 0 0;padding:0 .2em;border-left:0;border-right:0;border-bottom:0;} -.ui-datepicker .ui-datepicker-buttonpane button{float:right;margin:.5em .2em .4em;cursor:pointer;padding:.2em .6em .3em .6em;width:auto;overflow:visible;} -.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{float:left;} -.ui-datepicker.ui-datepicker-multi{width:auto;} -.ui-datepicker-multi .ui-datepicker-group{float:left;} -.ui-datepicker-multi .ui-datepicker-group table{width:95%;margin:0 auto .4em;} -.ui-datepicker-multi-2 .ui-datepicker-group{width:50%;} -.ui-datepicker-multi-3 .ui-datepicker-group{width:33.3%;} -.ui-datepicker-multi-4 .ui-datepicker-group{width:25%;} -.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0;} -.ui-datepicker-multi .ui-datepicker-buttonpane{clear:left;} -.ui-datepicker-row-break{clear:both;width:100%;font-size:0;} -.ui-datepicker-rtl{direction:rtl;} -.ui-datepicker-rtl .ui-datepicker-prev{right:2px;left:auto;} -.ui-datepicker-rtl .ui-datepicker-next{left:2px;right:auto;} -.ui-datepicker-rtl .ui-datepicker-prev:hover{right:1px;left:auto;} -.ui-datepicker-rtl .ui-datepicker-next:hover{left:1px;right:auto;} -.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:right;} -.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:left;} -.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,.ui-datepicker-rtl .ui-datepicker-group{float:right;} -.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px;} -.ui-dialog{position:absolute;top:0;left:0;padding:.2em;outline:0;} -.ui-dialog .ui-dialog-titlebar{padding:.4em 1em;position:relative;} -.ui-dialog .ui-dialog-title{float:left;margin:.1em 0;white-space:nowrap;width:90%;overflow:hidden;text-overflow:ellipsis;} -.ui-dialog .ui-dialog-titlebar-close{position:absolute;right:.3em;top:50%;width:21px;margin:-10px 0 0 0;padding:1px;height:20px;} -.ui-dialog .ui-dialog-content{position:relative;border:0;padding:.5em 1em;background:none;overflow:auto;} -.ui-dialog .ui-dialog-buttonpane{text-align:left;border-width:1px 0 0 0;background-image:none;margin-top:.5em;padding:.3em 1em .5em .4em;} -.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{float:right;} -.ui-dialog .ui-dialog-buttonpane button{margin:.5em .4em .5em 0;cursor:pointer;} -.ui-dialog .ui-resizable-se{width:12px;height:12px;right:-5px;bottom:-5px;background-position:16px 16px;} -.ui-draggable .ui-dialog-titlebar{cursor:move;} -.ui-menu{list-style:none;padding:2px;margin:0;display:block;outline:none;} -.ui-menu .ui-menu{margin-top:-3px;position:absolute;} -.ui-menu .ui-menu-item{margin:0;padding:0;width:100%;list-style-image:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);} -.ui-menu .ui-menu-divider{margin:5px -2px 5px -2px;height:0;font-size:0;line-height:0;border-width:1px 0 0 0;} -.ui-menu .ui-menu-item a{text-decoration:none;display:block;padding:2px .4em;line-height:1.5;min-height:0;font-weight:normal;} -.ui-menu .ui-menu-item a.ui-state-focus,.ui-menu .ui-menu-item a.ui-state-active{font-weight:normal;margin:-1px;} -.ui-menu .ui-state-disabled{font-weight:normal;margin:.4em 0 .2em;line-height:1.5;} -.ui-menu .ui-state-disabled a{cursor:default;} -.ui-menu-icons{position:relative;} -.ui-menu-icons .ui-menu-item a{position:relative;padding-left:2em;} -.ui-menu .ui-icon{position:absolute;top:.2em;left:.2em;} -.ui-menu .ui-menu-icon{position:static;float:right;} -.ui-progressbar{height:2em;text-align:left;overflow:hidden;} -.ui-progressbar .ui-progressbar-value{margin:-1px;height:100%;} -.ui-progressbar .ui-progressbar-overlay{background:url("/static/images/animated-overlay.gif");height:100%;filter:alpha(opacity=25);opacity:0.25;} -.ui-progressbar-indeterminate .ui-progressbar-value{background-image:none;} -.ui-slider{position:relative;text-align:left;} -.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default;} -.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0;} -.ui-slider.ui-state-disabled .ui-slider-handle,.ui-slider.ui-state-disabled .ui-slider-range{filter:inherit;} -.ui-slider-horizontal{height:.8em;} -.ui-slider-horizontal .ui-slider-handle{top:-0.3em;margin-left:-0.6em;} -.ui-slider-horizontal .ui-slider-range{top:0;height:100%;} -.ui-slider-horizontal .ui-slider-range-min{left:0;} -.ui-slider-horizontal .ui-slider-range-max{right:0;} -.ui-slider-vertical{width:.8em;height:100px;} -.ui-slider-vertical .ui-slider-handle{left:-0.3em;margin-left:0;margin-bottom:-0.6em;} -.ui-slider-vertical .ui-slider-range{left:0;width:100%;} -.ui-slider-vertical .ui-slider-range-min{bottom:0;} -.ui-slider-vertical .ui-slider-range-max{top:0;} -.ui-spinner{position:relative;display:inline-block;overflow:hidden;padding:0;vertical-align:middle;} -.ui-spinner-input{border:none;background:none;color:inherit;padding:0;margin:.2em 0;vertical-align:middle;margin-left:.4em;margin-right:22px;} -.ui-spinner-button{width:16px;height:50%;font-size:.5em;padding:0;margin:0;text-align:center;position:absolute;cursor:default;display:block;overflow:hidden;right:0;} -.ui-spinner a.ui-spinner-button{border-top:none;border-bottom:none;border-right:none;} -.ui-spinner .ui-icon{position:absolute;margin-top:-8px;top:50%;left:0;} -.ui-spinner-up{top:0;} -.ui-spinner-down{bottom:0;} -.ui-spinner .ui-icon-triangle-1-s{background-position:-65px -16px;} -.ui-tabs{position:relative;padding:.2em;} -.ui-tabs .ui-tabs-nav{margin:0;padding:.2em .2em 0;} -.ui-tabs .ui-tabs-nav li{list-style:none;float:left;position:relative;top:0;margin:1px .2em 0 0;border-bottom-width:0;padding:0;white-space:nowrap;} -.ui-tabs .ui-tabs-nav li a{float:left;padding:.5em 1em;text-decoration:none;} -.ui-tabs .ui-tabs-nav li.ui-tabs-active{margin-bottom:-1px;padding-bottom:1px;} -.ui-tabs .ui-tabs-nav li.ui-tabs-active a,.ui-tabs .ui-tabs-nav li.ui-state-disabled a,.ui-tabs .ui-tabs-nav li.ui-tabs-loading a{cursor:text;} -.ui-tabs .ui-tabs-nav li a,.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active a{cursor:pointer;} -.ui-tabs .ui-tabs-panel{display:block;border-width:0;padding:1em 1.4em;background:none;} -.ui-tooltip{padding:8px;position:absolute;z-index:9999;max-width:300px;-webkit-box-shadow:0 0 5px #aaa;box-shadow:0 0 5px #aaa;} -body .ui-tooltip{border-width:2px;} -.ui-widget{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;} -.ui-widget .ui-widget{font-size:1em;} -.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{} -.ui-widget-content{border:1px solid #a6c9e2;background:#fcfdfd url(/static/images/ui-bg_inset-hard_100_fcfdfd_1x100.png) 50% bottom repeat-x;color:#36454F;font-weight:normal;} -.ui-widget-content a{color:#0088cc;} -.ui-widget-header{border:1px solid #a6c9e2;background:#ffffff url(/static/images/ui-bg_flat_50_ffffff_40x100.png) 50% 50% repeat-x;color:#36454F;font-weight:bold;} -.ui-widget-header a{color:#36454F;} -.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #a6c9e2;background:#ffffff url(/static/images/ui-bg_flat_100_ffffff_40x100.png) 50% 50% repeat-x;font-weight:bold;color:#0088cc;} -.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#0088cc;text-decoration:none;} -.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #e3e3e3;background:#e5e3e3 url(/static/images/ui-bg_flat_75_e5e3e3_40x100.png) 50% 50% repeat-x;font-weight:bold;color:#005580;} -.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited{color:#005580;text-decoration:none;} -.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #e3e3e3;background:#f5f5f5 url(/static/images/ui-bg_inset-hard_100_f5f5f5_1x100.png) 50% 50% repeat-x;font-weight:bold;color:#36454F;} -.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#36454F;text-decoration:none;} -.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #fad42e;background:#fbec88 url(/static/images/ui-bg_flat_55_fbec88_40x100.png) 50% 50% repeat-x;color:#363636;} -.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#363636;} -.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #cd0a0a;background:#fef1ec url(/static/images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x;color:#cd0a0a;} -.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#cd0a0a;} -.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#cd0a0a;} -.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:normal;} -.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:alpha(opacity=70);font-weight:bold;} -.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:alpha(opacity=35);background-image:none;} -.ui-state-disabled .ui-icon{filter:alpha(opacity=35);} -.ui-icon{width:16px;height:16px;} -.ui-icon,.ui-widget-content .ui-icon{background-image:url(/static/images/ui-icons_469bdd_256x240.png);} -.ui-widget-header .ui-icon{background-image:url(/static/images/ui-icons_36454F_256x240.png);} -.ui-state-default .ui-icon{background-image:url(/static/images/ui-icons_0088cc_256x240.png);} -.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url(/static/images/ui-icons_217bc0_256x240.png);} -.ui-state-active .ui-icon{background-image:url(/static/images/ui-icons_36454F_256x240.png);} -.ui-state-highlight .ui-icon{background-image:url(/static/images/ui-icons_2e83ff_256x240.png);} -.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url(/static/images/ui-icons_cd0a0a_256x240.png);} -.ui-icon-blank{background-position:16px 16px;} -.ui-icon-carat-1-n{background-position:0 0;} -.ui-icon-carat-1-ne{background-position:-16px 0;} -.ui-icon-carat-1-e{background-position:-32px 0;} -.ui-icon-carat-1-se{background-position:-48px 0;} -.ui-icon-carat-1-s{background-position:-64px 0;} -.ui-icon-carat-1-sw{background-position:-80px 0;} -.ui-icon-carat-1-w{background-position:-96px 0;} -.ui-icon-carat-1-nw{background-position:-112px 0;} -.ui-icon-carat-2-n-s{background-position:-128px 0;} -.ui-icon-carat-2-e-w{background-position:-144px 0;} -.ui-icon-triangle-1-n{background-position:0 -16px;} -.ui-icon-triangle-1-ne{background-position:-16px -16px;} -.ui-icon-triangle-1-e{background-position:-32px -16px;} -.ui-icon-triangle-1-se{background-position:-48px -16px;} -.ui-icon-triangle-1-s{background-position:-64px -16px;} -.ui-icon-triangle-1-sw{background-position:-80px -16px;} -.ui-icon-triangle-1-w{background-position:-96px -16px;} -.ui-icon-triangle-1-nw{background-position:-112px -16px;} -.ui-icon-triangle-2-n-s{background-position:-128px -16px;} -.ui-icon-triangle-2-e-w{background-position:-144px -16px;} -.ui-icon-arrow-1-n{background-position:0 -32px;} -.ui-icon-arrow-1-ne{background-position:-16px -32px;} -.ui-icon-arrow-1-e{background-position:-32px -32px;} -.ui-icon-arrow-1-se{background-position:-48px -32px;} -.ui-icon-arrow-1-s{background-position:-64px -32px;} -.ui-icon-arrow-1-sw{background-position:-80px -32px;} -.ui-icon-arrow-1-w{background-position:-96px -32px;} -.ui-icon-arrow-1-nw{background-position:-112px -32px;} -.ui-icon-arrow-2-n-s{background-position:-128px -32px;} -.ui-icon-arrow-2-ne-sw{background-position:-144px -32px;} -.ui-icon-arrow-2-e-w{background-position:-160px -32px;} -.ui-icon-arrow-2-se-nw{background-position:-176px -32px;} -.ui-icon-arrowstop-1-n{background-position:-192px -32px;} -.ui-icon-arrowstop-1-e{background-position:-208px -32px;} -.ui-icon-arrowstop-1-s{background-position:-224px -32px;} -.ui-icon-arrowstop-1-w{background-position:-240px -32px;} -.ui-icon-arrowthick-1-n{background-position:0 -48px;} -.ui-icon-arrowthick-1-ne{background-position:-16px -48px;} -.ui-icon-arrowthick-1-e{background-position:-32px -48px;} -.ui-icon-arrowthick-1-se{background-position:-48px -48px;} -.ui-icon-arrowthick-1-s{background-position:-64px -48px;} -.ui-icon-arrowthick-1-sw{background-position:-80px -48px;} -.ui-icon-arrowthick-1-w{background-position:-96px -48px;} -.ui-icon-arrowthick-1-nw{background-position:-112px -48px;} -.ui-icon-arrowthick-2-n-s{background-position:-128px -48px;} -.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px;} -.ui-icon-arrowthick-2-e-w{background-position:-160px -48px;} -.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px;} -.ui-icon-arrowthickstop-1-n{background-position:-192px -48px;} -.ui-icon-arrowthickstop-1-e{background-position:-208px -48px;} -.ui-icon-arrowthickstop-1-s{background-position:-224px -48px;} -.ui-icon-arrowthickstop-1-w{background-position:-240px -48px;} -.ui-icon-arrowreturnthick-1-w{background-position:0 -64px;} -.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px;} -.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px;} -.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px;} -.ui-icon-arrowreturn-1-w{background-position:-64px -64px;} -.ui-icon-arrowreturn-1-n{background-position:-80px -64px;} -.ui-icon-arrowreturn-1-e{background-position:-96px -64px;} -.ui-icon-arrowreturn-1-s{background-position:-112px -64px;} -.ui-icon-arrowrefresh-1-w{background-position:-128px -64px;} -.ui-icon-arrowrefresh-1-n{background-position:-144px -64px;} -.ui-icon-arrowrefresh-1-e{background-position:-160px -64px;} -.ui-icon-arrowrefresh-1-s{background-position:-176px -64px;} -.ui-icon-arrow-4{background-position:0 -80px;} -.ui-icon-arrow-4-diag{background-position:-16px -80px;} -.ui-icon-extlink{background-position:-32px -80px;} -.ui-icon-newwin{background-position:-48px -80px;} -.ui-icon-refresh{background-position:-64px -80px;} -.ui-icon-shuffle{background-position:-80px -80px;} -.ui-icon-transfer-e-w{background-position:-96px -80px;} -.ui-icon-transferthick-e-w{background-position:-112px -80px;} -.ui-icon-folder-collapsed{background-position:0 -96px;} -.ui-icon-folder-open{background-position:-16px -96px;} -.ui-icon-document{background-position:-32px -96px;} -.ui-icon-document-b{background-position:-48px -96px;} -.ui-icon-note{background-position:-64px -96px;} -.ui-icon-mail-closed{background-position:-80px -96px;} -.ui-icon-mail-open{background-position:-96px -96px;} -.ui-icon-suitcase{background-position:-112px -96px;} -.ui-icon-comment{background-position:-128px -96px;} -.ui-icon-person{background-position:-144px -96px;} -.ui-icon-print{background-position:-160px -96px;} -.ui-icon-trash{background-position:-176px -96px;} -.ui-icon-locked{background-position:-192px -96px;} -.ui-icon-unlocked{background-position:-208px -96px;} -.ui-icon-bookmark{background-position:-224px -96px;} -.ui-icon-tag{background-position:-240px -96px;} -.ui-icon-home{background-position:0 -112px;} -.ui-icon-flag{background-position:-16px -112px;} -.ui-icon-calendar{background-position:-32px -112px;} -.ui-icon-cart{background-position:-48px -112px;} -.ui-icon-pencil{background-position:-64px -112px;} -.ui-icon-clock{background-position:-80px -112px;} -.ui-icon-disk{background-position:-96px -112px;} -.ui-icon-calculator{background-position:-112px -112px;} -.ui-icon-zoomin{background-position:-128px -112px;} -.ui-icon-zoomout{background-position:-144px -112px;} -.ui-icon-search{background-position:-160px -112px;} -.ui-icon-wrench{background-position:-176px -112px;} -.ui-icon-gear{background-position:-192px -112px;} -.ui-icon-heart{background-position:-208px -112px;} -.ui-icon-star{background-position:-224px -112px;} -.ui-icon-link{background-position:-240px -112px;} -.ui-icon-cancel{background-position:0 -128px;} -.ui-icon-plus{background-position:-16px -128px;} -.ui-icon-plusthick{background-position:-32px -128px;} -.ui-icon-minus{background-position:-48px -128px;} -.ui-icon-minusthick{background-position:-64px -128px;} -.ui-icon-close{background-position:-80px -128px;} -.ui-icon-closethick{background-position:-96px -128px;} -.ui-icon-key{background-position:-112px -128px;} -.ui-icon-lightbulb{background-position:-128px -128px;} -.ui-icon-scissors{background-position:-144px -128px;} -.ui-icon-clipboard{background-position:-160px -128px;} -.ui-icon-copy{background-position:-176px -128px;} -.ui-icon-contact{background-position:-192px -128px;} -.ui-icon-image{background-position:-208px -128px;} -.ui-icon-video{background-position:-224px -128px;} -.ui-icon-script{background-position:-240px -128px;} -.ui-icon-alert{background-position:0 -144px;} -.ui-icon-info{background-position:-16px -144px;} -.ui-icon-notice{background-position:-32px -144px;} -.ui-icon-help{background-position:-48px -144px;} -.ui-icon-check{background-position:-64px -144px;} -.ui-icon-bullet{background-position:-80px -144px;} -.ui-icon-radio-on{background-position:-96px -144px;} -.ui-icon-radio-off{background-position:-112px -144px;} -.ui-icon-pin-w{background-position:-128px -144px;} -.ui-icon-pin-s{background-position:-144px -144px;} -.ui-icon-play{background-position:0 -160px;} -.ui-icon-pause{background-position:-16px -160px;} -.ui-icon-seek-next{background-position:-32px -160px;} -.ui-icon-seek-prev{background-position:-48px -160px;} -.ui-icon-seek-end{background-position:-64px -160px;} -.ui-icon-seek-start{background-position:-80px -160px;} -.ui-icon-seek-first{background-position:-80px -160px;} -.ui-icon-stop{background-position:-96px -160px;} -.ui-icon-eject{background-position:-112px -160px;} -.ui-icon-volume-off{background-position:-128px -160px;} -.ui-icon-volume-on{background-position:-144px -160px;} -.ui-icon-power{background-position:0 -176px;} -.ui-icon-signal-diag{background-position:-16px -176px;} -.ui-icon-signal{background-position:-32px -176px;} -.ui-icon-battery-0{background-position:-48px -176px;} -.ui-icon-battery-1{background-position:-64px -176px;} -.ui-icon-battery-2{background-position:-80px -176px;} -.ui-icon-battery-3{background-position:-96px -176px;} -.ui-icon-circle-plus{background-position:0 -192px;} -.ui-icon-circle-minus{background-position:-16px -192px;} -.ui-icon-circle-close{background-position:-32px -192px;} -.ui-icon-circle-triangle-e{background-position:-48px -192px;} -.ui-icon-circle-triangle-s{background-position:-64px -192px;} -.ui-icon-circle-triangle-w{background-position:-80px -192px;} -.ui-icon-circle-triangle-n{background-position:-96px -192px;} -.ui-icon-circle-arrow-e{background-position:-112px -192px;} -.ui-icon-circle-arrow-s{background-position:-128px -192px;} -.ui-icon-circle-arrow-w{background-position:-144px -192px;} -.ui-icon-circle-arrow-n{background-position:-160px -192px;} -.ui-icon-circle-zoomin{background-position:-176px -192px;} -.ui-icon-circle-zoomout{background-position:-192px -192px;} -.ui-icon-circle-check{background-position:-208px -192px;} -.ui-icon-circlesmall-plus{background-position:0 -208px;} -.ui-icon-circlesmall-minus{background-position:-16px -208px;} -.ui-icon-circlesmall-close{background-position:-32px -208px;} -.ui-icon-squaresmall-plus{background-position:-48px -208px;} -.ui-icon-squaresmall-minus{background-position:-64px -208px;} -.ui-icon-squaresmall-close{background-position:-80px -208px;} -.ui-icon-grip-dotted-vertical{background-position:0 -224px;} -.ui-icon-grip-dotted-horizontal{background-position:-16px -224px;} -.ui-icon-grip-solid-vertical{background-position:-32px -224px;} -.ui-icon-grip-solid-horizontal{background-position:-48px -224px;} -.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px;} -.ui-icon-grip-diagonal-se{background-position:-80px -224px;} -.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:5px;} -.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:5px;} -.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:5px;} -.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:5px;} -.ui-widget-overlay{background:#aaaaaa url(/static/images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x;opacity:.3;filter:alpha(opacity=30);} -.ui-widget-shadow{margin:-8px 0 0 -8px;padding:8px;background:#aaaaaa url(/static/images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x;opacity:.3;filter:alpha(opacity=30);border-radius:8px;} diff --git a/awx/ui/client/assets/custom-theme/jquery-ui-1.10.3.custom.min.css.old b/awx/ui/client/assets/custom-theme/jquery-ui-1.10.3.custom.min.css.old deleted file mode 100644 index 73cc2d354782..000000000000 --- a/awx/ui/client/assets/custom-theme/jquery-ui-1.10.3.custom.min.css.old +++ /dev/null @@ -1,5 +0,0 @@ -/*! jQuery UI - v1.10.3 - 2013-06-26 -* http://jqueryui.com -* Includes: jquery.ui.core.css, jquery.ui.resizable.css, jquery.ui.selectable.css, jquery.ui.accordion.css, jquery.ui.autocomplete.css, jquery.ui.button.css, jquery.ui.datepicker.css, jquery.ui.dialog.css, jquery.ui.menu.css, jquery.ui.progressbar.css, jquery.ui.slider.css, jquery.ui.spinner.css, jquery.ui.tabs.css, jquery.ui.tooltip.css -* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Lucida%20Grande%2CLucida%20Sans%2CArial%2Csans-serif&fwDefault=normal&fsDefault=14px&cornerRadius=5px&bgColorHeader=%23ffffff&bgTextureHeader=flat&bgImgOpacityHeader=50&borderColorHeader=%23a6c9e2&fcHeader=%2336454F&iconColorHeader=%2336454F&bgColorContent=%23fcfdfd&bgTextureContent=inset_hard&bgImgOpacityContent=100&borderColorContent=%23a6c9e2&fcContent=%23222222&iconColorContent=%23469bdd&bgColorDefault=%23ffffff&bgTextureDefault=flat&bgImgOpacityDefault=100&borderColorDefault=%23a6c9e2&fcDefault=%230088cc&iconColorDefault=%230088cc&bgColorHover=%23e5e3e3&bgTextureHover=flat&bgImgOpacityHover=75&borderColorHover=%23e3e3e3&fcHover=%23005580&iconColorHover=%23217bc0&bgColorActive=%23f5f5f5&bgTextureActive=inset_hard&bgImgOpacityActive=100&borderColorActive=%23e3e3e3&fcActive=%2336454F&iconColorActive=%2336454F&bgColorHighlight=%23fbec88&bgTextureHighlight=flat&bgImgOpacityHighlight=55&borderColorHighlight=%23fad42e&fcHighlight=%23363636&iconColorHighlight=%232e83ff&bgColorError=%23fef1ec&bgTextureError=glass&bgImgOpacityError=95&borderColorError=%23cd0a0a&fcError=%23cd0a0a&iconColorError=%23cd0a0a&bgColorOverlay=%23aaaaaa&bgTextureOverlay=flat&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=%23aaaaaa&bgTextureShadow=flat&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px -* Copyright 2013 jQuery Foundation and other contributors Licensed MIT */.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-clearfix{min-height:0}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:.1px;display:block}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted #000}.ui-accordion .ui-accordion-header{display:block;cursor:pointer;position:relative;margin-top:2px;padding:.5em .5em .5em .7em;min-height:0}.ui-accordion .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-noicons{padding-left:.7em}.ui-accordion .ui-accordion-icons .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-header .ui-accordion-header-icon{position:absolute;left:.5em;top:50%;margin-top:-8px}.ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;overflow:auto}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default}.ui-button{display:inline-block;position:relative;padding:0;line-height:normal;margin-right:.1em;cursor:pointer;vertical-align:middle;text-align:center;overflow:visible}.ui-button,.ui-button:link,.ui-button:visited,.ui-button:hover,.ui-button:active{text-decoration:none}.ui-button-icon-only{width:2.2em}button.ui-button-icon-only{width:2.4em}.ui-button-icons-only{width:3.4em}button.ui-button-icons-only{width:3.7em}.ui-button .ui-button-text{display:block;line-height:normal}.ui-button-text-only .ui-button-text{padding:.4em 1em}.ui-button-icon-only .ui-button-text,.ui-button-icons-only .ui-button-text{padding:.4em;text-indent:-9999999px}.ui-button-text-icon-primary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 1em .4em 2.1em}.ui-button-text-icon-secondary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 2.1em .4em 1em}.ui-button-text-icons .ui-button-text{padding-left:2.1em;padding-right:2.1em}input.ui-button{padding:.4em 1em}.ui-button-icon-only .ui-icon,.ui-button-text-icon-primary .ui-icon,.ui-button-text-icon-secondary .ui-icon,.ui-button-text-icons .ui-icon,.ui-button-icons-only .ui-icon{position:absolute;top:50%;margin-top:-8px}.ui-button-icon-only .ui-icon{left:50%;margin-left:-8px}.ui-button-text-icon-primary .ui-button-icon-primary,.ui-button-text-icons .ui-button-icon-primary,.ui-button-icons-only .ui-button-icon-primary{left:.5em}.ui-button-text-icon-secondary .ui-button-icon-secondary,.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{right:.5em}.ui-buttonset{margin-right:7px}.ui-buttonset .ui-button{margin-left:0;margin-right:-.3em}input.ui-button::-moz-focus-inner,button.ui-button::-moz-focus-inner{border:0;padding:0}.ui-datepicker{width:17em;padding:.2em .2em 0;display:none}.ui-datepicker .ui-datepicker-header{position:relative;padding:.2em 0}.ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-next{position:absolute;top:2px;width:1.8em;height:1.8em}.ui-datepicker .ui-datepicker-prev-hover,.ui-datepicker .ui-datepicker-next-hover{top:1px}.ui-datepicker .ui-datepicker-prev{left:2px}.ui-datepicker .ui-datepicker-next{right:2px}.ui-datepicker .ui-datepicker-prev-hover{left:1px}.ui-datepicker .ui-datepicker-next-hover{right:1px}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{display:block;position:absolute;left:50%;margin-left:-8px;top:50%;margin-top:-8px}.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center}.ui-datepicker .ui-datepicker-title select{font-size:1em;margin:1px 0}.ui-datepicker select.ui-datepicker-month-year{width:100%}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:49%}.ui-datepicker table{width:100%;font-size:.9em;border-collapse:collapse;margin:0 0 .4em}.ui-datepicker th{padding:.7em .3em;text-align:center;font-weight:700;border:0}.ui-datepicker td{border:0;padding:1px}.ui-datepicker td span,.ui-datepicker td a{display:block;padding:.2em;text-align:right;text-decoration:none}.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:.7em 0 0;padding:0 .2em;border-left:0;border-right:0;border-bottom:0}.ui-datepicker .ui-datepicker-buttonpane button{float:right;margin:.5em .2em .4em;cursor:pointer;padding:.2em .6em .3em;width:auto;overflow:visible}.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{float:left}.ui-datepicker.ui-datepicker-multi{width:auto}.ui-datepicker-multi .ui-datepicker-group{float:left}.ui-datepicker-multi .ui-datepicker-group table{width:95%;margin:0 auto .4em}.ui-datepicker-multi-2 .ui-datepicker-group{width:50%}.ui-datepicker-multi-3 .ui-datepicker-group{width:33.3%}.ui-datepicker-multi-4 .ui-datepicker-group{width:25%}.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-buttonpane{clear:left}.ui-datepicker-row-break{clear:both;width:100%;font-size:0}.ui-datepicker-rtl{direction:rtl}.ui-datepicker-rtl .ui-datepicker-prev{right:2px;left:auto}.ui-datepicker-rtl .ui-datepicker-next{left:2px;right:auto}.ui-datepicker-rtl .ui-datepicker-prev:hover{right:1px;left:auto}.ui-datepicker-rtl .ui-datepicker-next:hover{left:1px;right:auto}.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:right}.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:left}.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,.ui-datepicker-rtl .ui-datepicker-group{float:right}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-dialog{position:absolute;top:0;left:0;padding:.2em;outline:0}.ui-dialog .ui-dialog-titlebar{padding:.4em 1em;position:relative}.ui-dialog .ui-dialog-title{float:left;margin:.1em 0;white-space:nowrap;width:90%;overflow:hidden;text-overflow:ellipsis}.ui-dialog .ui-dialog-titlebar-close{position:absolute;right:.3em;top:50%;width:21px;margin:-10px 0 0 0;padding:1px;height:20px}.ui-dialog .ui-dialog-content{position:relative;border:0;padding:.5em 1em;background:0;overflow:auto}.ui-dialog .ui-dialog-buttonpane{text-align:left;border-width:1px 0 0;background-image:none;margin-top:.5em;padding:.3em 1em .5em .4em}.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{float:right}.ui-dialog .ui-dialog-buttonpane button{margin:.5em .4em .5em 0;cursor:pointer}.ui-dialog .ui-resizable-se{width:12px;height:12px;right:-5px;bottom:-5px;background-position:16px 16px}.ui-draggable .ui-dialog-titlebar{cursor:move}.ui-menu{list-style:none;padding:2px;margin:0;display:block;outline:0}.ui-menu .ui-menu{margin-top:-3px;position:absolute}.ui-menu .ui-menu-item{margin:0;padding:0;width:100%;list-style-image:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)}.ui-menu .ui-menu-divider{margin:5px -2px 5px -2px;height:0;font-size:0;line-height:0;border-width:1px 0 0}.ui-menu .ui-menu-item a{text-decoration:none;display:block;padding:2px .4em;line-height:1.5;min-height:0;font-weight:400}.ui-menu .ui-menu-item a.ui-state-focus,.ui-menu .ui-menu-item a.ui-state-active{font-weight:400;margin:-1px}.ui-menu .ui-state-disabled{font-weight:400;margin:.4em 0 .2em;line-height:1.5}.ui-menu .ui-state-disabled a{cursor:default}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item a{position:relative;padding-left:2em}.ui-menu .ui-icon{position:absolute;top:.2em;left:.2em}.ui-menu .ui-menu-icon{position:static;float:right}.ui-progressbar{height:2em;text-align:left;overflow:hidden}.ui-progressbar .ui-progressbar-value{margin:-1px;height:100%}.ui-progressbar .ui-progressbar-overlay{background:url(images/animated-overlay.gif);height:100%;filter:alpha(opacity=25);opacity:.25}.ui-progressbar-indeterminate .ui-progressbar-value{background-image:none}.ui-slider{position:relative;text-align:left}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0}.ui-slider.ui-state-disabled .ui-slider-handle,.ui-slider.ui-state-disabled .ui-slider-range{filter:inherit}.ui-slider-horizontal{height:.8em}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-left:-.6em}.ui-slider-horizontal .ui-slider-range{top:0;height:100%}.ui-slider-horizontal .ui-slider-range-min{left:0}.ui-slider-horizontal .ui-slider-range-max{right:0}.ui-slider-vertical{width:.8em;height:100px}.ui-slider-vertical .ui-slider-handle{left:-.3em;margin-left:0;margin-bottom:-.6em}.ui-slider-vertical .ui-slider-range{left:0;width:100%}.ui-slider-vertical .ui-slider-range-min{bottom:0}.ui-slider-vertical .ui-slider-range-max{top:0}.ui-spinner{position:relative;display:inline-block;overflow:hidden;padding:0;vertical-align:middle}.ui-spinner-input{border:0;background:0;color:inherit;padding:0;margin:.2em 0;vertical-align:middle;margin-left:.4em;margin-right:22px}.ui-spinner-button{width:16px;height:50%;font-size:.5em;padding:0;margin:0;text-align:center;position:absolute;cursor:default;display:block;overflow:hidden;right:0}.ui-spinner a.ui-spinner-button{border-top:0;border-bottom:0;border-right:0}.ui-spinner .ui-icon{position:absolute;margin-top:-8px;top:50%;left:0}.ui-spinner-up{top:0}.ui-spinner-down{bottom:0}.ui-spinner .ui-icon-triangle-1-s{background-position:-65px -16px}.ui-tabs{position:relative;padding:.2em}.ui-tabs .ui-tabs-nav{margin:0;padding:.2em .2em 0}.ui-tabs .ui-tabs-nav li{list-style:none;float:left;position:relative;top:0;margin:1px .2em 0 0;border-bottom-width:0;padding:0;white-space:nowrap}.ui-tabs .ui-tabs-nav li a{float:left;padding:.5em 1em;text-decoration:none}.ui-tabs .ui-tabs-nav li.ui-tabs-active{margin-bottom:-1px;padding-bottom:1px}.ui-tabs .ui-tabs-nav li.ui-tabs-active a,.ui-tabs .ui-tabs-nav li.ui-state-disabled a,.ui-tabs .ui-tabs-nav li.ui-tabs-loading a{cursor:text}.ui-tabs .ui-tabs-nav li a,.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active a{cursor:pointer}.ui-tabs .ui-tabs-panel{display:block;border-width:0;padding:1em 1.4em;background:0}.ui-tooltip{padding:8px;position:absolute;z-index:9999;max-width:300px;-webkit-box-shadow:0 0 5px #aaa;box-shadow:0 0 5px #aaa}body .ui-tooltip{border-width:2px}.ui-widget{font-family:Lucida Grande,Lucida Sans,Arial,sans-serif;font-size:14px}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Lucida Grande,Lucida Sans,Arial,sans-serif;font-size:1em}.ui-widget-content{border:1px solid #a6c9e2;background:#fcfdfd url(images/ui-bg_inset-hard_100_fcfdfd_1x100.png) 50% bottom repeat-x;color:#222}.ui-widget-content a{color:#222}.ui-widget-header{border:1px solid #a6c9e2;background:#fff url(images/ui-bg_flat_50_ffffff_40x100.png) 50% 50% repeat-x;color:#36454F;font-weight:bold}.ui-widget-header a{color:#36454F}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #a6c9e2;background:#fff url(images/ui-bg_flat_100_ffffff_40x100.png) 50% 50% repeat-x;font-weight:normal;color:#08c}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#08c;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #e3e3e3;background:#e5e3e3 url(images/ui-bg_flat_75_e5e3e3_40x100.png) 50% 50% repeat-x;font-weight:normal;color:#005580}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited{color:#005580;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #e3e3e3;background:#f5f5f5 url(images/ui-bg_inset-hard_100_f5f5f5_1x100.png) 50% 50% repeat-x;font-weight:normal;color:#36454F}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#36454F;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #fad42e;background:#fbec88 url(images/ui-bg_flat_55_fbec88_40x100.png) 50% 50% repeat-x;color:#363636}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#363636}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #cd0a0a;background:#fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x;color:#cd0a0a}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#cd0a0a}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#cd0a0a}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url(images/ui-icons_469bdd_256x240.png)}.ui-widget-header .ui-icon{background-image:url(images/ui-icons_36454F_256x240.png)}.ui-state-default .ui-icon{background-image:url(images/ui-icons_0088cc_256x240.png)}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url(images/ui-icons_217bc0_256x240.png)}.ui-state-active .ui-icon{background-image:url(images/ui-icons_36454F_256x240.png)}.ui-state-highlight .ui-icon{background-image:url(images/ui-icons_2e83ff_256x240.png)}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url(images/ui-icons_cd0a0a_256x240.png)}.ui-icon-blank{background-position:16px 16px}.ui-icon-carat-1-n{background-position:0 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-64px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-64px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:0 -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:5px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:5px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:5px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:5px}.ui-widget-overlay{background:#aaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x;opacity:.3;filter:Alpha(Opacity=30)}.ui-widget-shadow{margin:-8px 0 0 -8px;padding:8px;background:#aaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x;opacity:.3;filter:Alpha(Opacity=30);border-radius:8px} \ No newline at end of file diff --git a/awx/ui/client/assets/favicon.ico b/awx/ui/client/assets/favicon.ico deleted file mode 100644 index 550879805122..000000000000 Binary files a/awx/ui/client/assets/favicon.ico and /dev/null differ diff --git a/awx/ui/client/assets/fontcustom/.fontcustom-manifest.json b/awx/ui/client/assets/fontcustom/.fontcustom-manifest.json deleted file mode 100644 index c50d13abe3a4..000000000000 --- a/awx/ui/client/assets/fontcustom/.fontcustom-manifest.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "checksum": { - "previous": "3dfbafd778b214fc5df2a64fe14fbfb30ba40e33282eedf0d98b5a613786db88", - "current": "3dfbafd778b214fc5df2a64fe14fbfb30ba40e33282eedf0d98b5a613786db88" - }, - "fonts": [ - "./fontcustom_3dfbafd778b214fc5df2a64fe14fbfb3.ttf", - "./fontcustom_3dfbafd778b214fc5df2a64fe14fbfb3.svg", - "./fontcustom_3dfbafd778b214fc5df2a64fe14fbfb3.woff", - "./fontcustom_3dfbafd778b214fc5df2a64fe14fbfb3.eot" - ], - "glyphs": { - "activity-stream": { - "codepoint": 61697, - "source": "new_icons/activity-stream.svg" - }, - "google": { - "codepoint": 61698, - "source": "new_icons/google.svg" - }, - "launch": { - "codepoint": 61699, - "source": "new_icons/launch.svg" - }, - "microsoft": { - "codepoint": 61700, - "source": "new_icons/microsoft.svg" - }, - "saml-02": { - "codepoint": 61701, - "source": "new_icons/saml-02.svg" - }, - "user": { - "codepoint": 61702, - "source": "new_icons/user.svg" - } - }, - "options": { - "autowidth": false, - "config": false, - "css_selector": ".icon-{{glyph}}", - "debug": false, - "font_ascent": 448, - "font_descent": 64, - "font_design_size": 16, - "font_em": 512, - "font_name": "fontcustom", - "force": false, - "input": { - "templates": "new_icons/", - "vectors": "new_icons/" - }, - "no_hash": false, - "output": { - "css": ".", - "fonts": ".", - "preview": "." - }, - "preprocessor_path": null, - "quiet": false, - "templates": [ - "css", - "preview" - ] - }, - "templates": [ - "./fontcustom.css", - "./fontcustom-preview.html" - ] -} \ No newline at end of file diff --git a/awx/ui/client/assets/fontcustom/fontcustom-preview.html b/awx/ui/client/assets/fontcustom/fontcustom-preview.html deleted file mode 100644 index bda47533bae8..000000000000 --- a/awx/ui/client/assets/fontcustom/fontcustom-preview.html +++ /dev/null @@ -1,293 +0,0 @@ - - - - fontcustom glyphs preview - - - - - - - - - -
-
-

fontcustom contains 6 glyphs:

- Toggle Preview Characters -
- - -
-
- PpPpPpPpPpPpPpPpPpPp -
-
- 12141618212436486072 -
-
- - -
-
- -
-
- PpPpPpPpPpPpPpPpPpPp -
-
- 12141618212436486072 -
-
- - -
-
- -
-
- PpPpPpPpPpPpPpPpPpPp -
-
- 12141618212436486072 -
-
- - -
-
- -
-
- PpPpPpPpPpPpPpPpPpPp -
-
- 12141618212436486072 -
-
- - -
-
- -
-
- PpPpPpPpPpPpPpPpPpPp -
-
- 12141618212436486072 -
-
- - -
-
- -
-
- PpPpPpPpPpPpPpPpPpPp -
-
- 12141618212436486072 -
-
- - -
-
- - - -
- - diff --git a/awx/ui/client/assets/fontcustom/fontcustom.css b/awx/ui/client/assets/fontcustom/fontcustom.css deleted file mode 100644 index 839abf9c9880..000000000000 --- a/awx/ui/client/assets/fontcustom/fontcustom.css +++ /dev/null @@ -1,51 +0,0 @@ -/* - Icon Font: fontcustom -*/ - -@font-face { - font-family: "fontcustom"; - src: url("/static/fonts/fontcustom_3dfbafd778b214fc5df2a64fe14fbfb3.eot"); - src: url("/static/fonts/fontcustom_3dfbafd778b214fc5df2a64fe14fbfb3.eot?#iefix") format("embedded-opentype"), - url("/static/fonts/fontcustom_3dfbafd778b214fc5df2a64fe14fbfb3.woff") format("woff"), - url("/static/fonts/fontcustom_3dfbafd778b214fc5df2a64fe14fbfb3.ttf") format("truetype"), - url("/static/fonts/fontcustom_3dfbafd778b214fc5df2a64fe14fbfb3.svg#fontcustom") format("svg"); - font-weight: normal; - font-style: normal; -} - -@media screen and (-webkit-min-device-pixel-ratio:0) { - @font-face { - font-family: "fontcustom"; - src: url("/static/fonts/fontcustom_3dfbafd778b214fc5df2a64fe14fbfb3.svg#fontcustom") format("svg"); - } -} - -[data-icon]:before { content: attr(data-icon); } - -[data-icon]:before, -.icon-activity-stream:before, -.icon-google:before, -.icon-launch:before, -.icon-microsoft:before, -.icon-saml-02:before, -.icon-user:before { - display: inline-block; - font-family: "fontcustom"; - font-style: normal; - font-weight: normal; - font-variant: normal; - line-height: 1; - text-decoration: inherit; - text-rendering: optimizeLegibility; - text-transform: none; - -moz-osx-font-smoothing: grayscale; - -webkit-font-smoothing: antialiased; - font-smoothing: antialiased; -} - -.icon-activity-stream:before { content: "\f101"; } -.icon-google:before { content: "\f102"; } -.icon-launch:before { content: "\f103"; } -.icon-microsoft:before { content: "\f104"; } -.icon-saml-02:before { content: "\f105"; } -.icon-user:before { content: "\f106"; } diff --git a/awx/ui/client/assets/fontcustom/fontcustom_3dfbafd778b214fc5df2a64fe14fbfb3.eot b/awx/ui/client/assets/fontcustom/fontcustom_3dfbafd778b214fc5df2a64fe14fbfb3.eot deleted file mode 100644 index 7d890d22c222..000000000000 Binary files a/awx/ui/client/assets/fontcustom/fontcustom_3dfbafd778b214fc5df2a64fe14fbfb3.eot and /dev/null differ diff --git a/awx/ui/client/assets/fontcustom/fontcustom_3dfbafd778b214fc5df2a64fe14fbfb3.svg b/awx/ui/client/assets/fontcustom/fontcustom_3dfbafd778b214fc5df2a64fe14fbfb3.svg deleted file mode 100644 index 20e687b6267f..000000000000 --- a/awx/ui/client/assets/fontcustom/fontcustom_3dfbafd778b214fc5df2a64fe14fbfb3.svg +++ /dev/null @@ -1,76 +0,0 @@ - - - - - -Created by FontForge 20120731 at Mon Nov 28 21:58:48 2016 - By Chris Church -Created by Chris Church with FontForge 2.0 (http://fontforge.sf.net) - - - - - - - - - - - - - - diff --git a/awx/ui/client/assets/fontcustom/fontcustom_3dfbafd778b214fc5df2a64fe14fbfb3.ttf b/awx/ui/client/assets/fontcustom/fontcustom_3dfbafd778b214fc5df2a64fe14fbfb3.ttf deleted file mode 100644 index b5bb62ffce4e..000000000000 Binary files a/awx/ui/client/assets/fontcustom/fontcustom_3dfbafd778b214fc5df2a64fe14fbfb3.ttf and /dev/null differ diff --git a/awx/ui/client/assets/fontcustom/fontcustom_3dfbafd778b214fc5df2a64fe14fbfb3.woff b/awx/ui/client/assets/fontcustom/fontcustom_3dfbafd778b214fc5df2a64fe14fbfb3.woff deleted file mode 100644 index 8fcaf0235fa5..000000000000 Binary files a/awx/ui/client/assets/fontcustom/fontcustom_3dfbafd778b214fc5df2a64fe14fbfb3.woff and /dev/null differ diff --git a/awx/ui/client/assets/fontcustom/new_icons/.fontcustom-manifest.json b/awx/ui/client/assets/fontcustom/new_icons/.fontcustom-manifest.json deleted file mode 100644 index 3177b5203467..000000000000 --- a/awx/ui/client/assets/fontcustom/new_icons/.fontcustom-manifest.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "checksum": { - "previous": "d77a9996ed04d45b02f5c06874cd36db7f5daa9833b1a6c36bef7029a767a34f", - "current": "d77a9996ed04d45b02f5c06874cd36db7f5daa9833b1a6c36bef7029a767a34f" - }, - "fonts": [ - "..//fontcustom_d77a9996ed04d45b02f5c06874cd36db.ttf", - "..//fontcustom_d77a9996ed04d45b02f5c06874cd36db.svg", - "..//fontcustom_d77a9996ed04d45b02f5c06874cd36db.woff", - "..//fontcustom_d77a9996ed04d45b02f5c06874cd36db.eot" - ], - "glyphs": { - "activity-stream": { - "codepoint": 61698, - "source": "./activity-stream.svg" - }, - "google": { - "codepoint": 61696, - "source": "./google.svg" - }, - "launch": { - "codepoint": 61699, - "source": "./launch.svg" - }, - "launch-circle": { - "codepoint": 61701, - "source": "./launch-circle.svg" - }, - "launch-new": { - "codepoint": 61703, - "source": "./launch-new.svg" - }, - "launch2": { - "codepoint": 61702, - "source": "./launch2.svg" - }, - "saml-02": { - "codepoint": 61697, - "source": "./saml-02.svg" - }, - "user": { - "codepoint": 61700, - "source": "./user.svg" - } - }, - "options": { - "autowidth": false, - "config": false, - "css_selector": ".icon-{{glyph}}", - "debug": false, - "font_ascent": 448, - "font_descent": 64, - "font_design_size": 16, - "font_em": 512, - "font_name": "fontcustom", - "force": true, - "input": { - "templates": ".", - "vectors": "." - }, - "no_hash": false, - "output": { - "css": "../", - "fonts": "../", - "preview": "../" - }, - "preprocessor_path": null, - "quiet": false, - "templates": [ - "css", - "preview" - ] - }, - "templates": [ - "../fontcustom.css", - "../fontcustom-preview.html" - ] -} \ No newline at end of file diff --git a/awx/ui/client/assets/fontcustom/new_icons/activity-stream.svg b/awx/ui/client/assets/fontcustom/new_icons/activity-stream.svg deleted file mode 100644 index 2915f904da76..000000000000 --- a/awx/ui/client/assets/fontcustom/new_icons/activity-stream.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - diff --git a/awx/ui/client/assets/fontcustom/new_icons/google.svg b/awx/ui/client/assets/fontcustom/new_icons/google.svg deleted file mode 100644 index 530a136d6cf1..000000000000 --- a/awx/ui/client/assets/fontcustom/new_icons/google.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - diff --git a/awx/ui/client/assets/fontcustom/new_icons/launch.svg b/awx/ui/client/assets/fontcustom/new_icons/launch.svg deleted file mode 100644 index 22eec6bab06e..000000000000 --- a/awx/ui/client/assets/fontcustom/new_icons/launch.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - diff --git a/awx/ui/client/assets/fontcustom/new_icons/microsoft.svg b/awx/ui/client/assets/fontcustom/new_icons/microsoft.svg deleted file mode 100644 index 934eb2b4b97b..000000000000 --- a/awx/ui/client/assets/fontcustom/new_icons/microsoft.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - diff --git a/awx/ui/client/assets/fontcustom/new_icons/saml-02.svg b/awx/ui/client/assets/fontcustom/new_icons/saml-02.svg deleted file mode 100644 index 1f33a6c8fc8c..000000000000 --- a/awx/ui/client/assets/fontcustom/new_icons/saml-02.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - diff --git a/awx/ui/client/assets/fontcustom/new_icons/user.svg b/awx/ui/client/assets/fontcustom/new_icons/user.svg deleted file mode 100644 index 7b38db83e65b..000000000000 --- a/awx/ui/client/assets/fontcustom/new_icons/user.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/awx/ui/client/assets/help/add_exception.png b/awx/ui/client/assets/help/add_exception.png deleted file mode 100644 index 4ba7bac86fb0..000000000000 Binary files a/awx/ui/client/assets/help/add_exception.png and /dev/null differ diff --git a/awx/ui/client/assets/help/confirm_exception.png b/awx/ui/client/assets/help/confirm_exception.png deleted file mode 100644 index 93b1fef7fe24..000000000000 Binary files a/awx/ui/client/assets/help/confirm_exception.png and /dev/null differ diff --git a/awx/ui/client/assets/help/groups001.png b/awx/ui/client/assets/help/groups001.png deleted file mode 100644 index 6a1c6f63aa75..000000000000 Binary files a/awx/ui/client/assets/help/groups001.png and /dev/null differ diff --git a/awx/ui/client/assets/help/groups002.png b/awx/ui/client/assets/help/groups002.png deleted file mode 100644 index 7476db19031b..000000000000 Binary files a/awx/ui/client/assets/help/groups002.png and /dev/null differ diff --git a/awx/ui/client/assets/help/groups003.png b/awx/ui/client/assets/help/groups003.png deleted file mode 100644 index c29bc66d3723..000000000000 Binary files a/awx/ui/client/assets/help/groups003.png and /dev/null differ diff --git a/awx/ui/client/assets/help/groups004.png b/awx/ui/client/assets/help/groups004.png deleted file mode 100644 index bbadb619b6ae..000000000000 Binary files a/awx/ui/client/assets/help/groups004.png and /dev/null differ diff --git a/awx/ui/client/assets/help/groups005.png b/awx/ui/client/assets/help/groups005.png deleted file mode 100644 index 805881f84b7f..000000000000 Binary files a/awx/ui/client/assets/help/groups005.png and /dev/null differ diff --git a/awx/ui/client/assets/help/groups006.png b/awx/ui/client/assets/help/groups006.png deleted file mode 100644 index b4dcac65d598..000000000000 Binary files a/awx/ui/client/assets/help/groups006.png and /dev/null differ diff --git a/awx/ui/client/assets/help/groups007.png b/awx/ui/client/assets/help/groups007.png deleted file mode 100644 index 3fbc3f799d06..000000000000 Binary files a/awx/ui/client/assets/help/groups007.png and /dev/null differ diff --git a/awx/ui/client/assets/help/groups008.png b/awx/ui/client/assets/help/groups008.png deleted file mode 100644 index fb7fef8c24fb..000000000000 Binary files a/awx/ui/client/assets/help/groups008.png and /dev/null differ diff --git a/awx/ui/client/assets/help/groups009.png b/awx/ui/client/assets/help/groups009.png deleted file mode 100644 index bbf962ac3381..000000000000 Binary files a/awx/ui/client/assets/help/groups009.png and /dev/null differ diff --git a/awx/ui/client/assets/help/groups010.png b/awx/ui/client/assets/help/groups010.png deleted file mode 100644 index 3ff39ef2da36..000000000000 Binary files a/awx/ui/client/assets/help/groups010.png and /dev/null differ diff --git a/awx/ui/client/assets/help/refresh_firefox.png b/awx/ui/client/assets/help/refresh_firefox.png deleted file mode 100644 index 47c401616570..000000000000 Binary files a/awx/ui/client/assets/help/refresh_firefox.png and /dev/null differ diff --git a/awx/ui/client/assets/help/socket_indicator.png b/awx/ui/client/assets/help/socket_indicator.png deleted file mode 100644 index eb37a6bf8cd1..000000000000 Binary files a/awx/ui/client/assets/help/socket_indicator.png and /dev/null differ diff --git a/awx/ui/client/assets/help/understand_the_risk.png b/awx/ui/client/assets/help/understand_the_risk.png deleted file mode 100644 index d668b2b99e9d..000000000000 Binary files a/awx/ui/client/assets/help/understand_the_risk.png and /dev/null differ diff --git a/awx/ui/client/assets/i_severity_critical.svg b/awx/ui/client/assets/i_severity_critical.svg deleted file mode 100644 index 996df323a1a0..000000000000 --- a/awx/ui/client/assets/i_severity_critical.svg +++ /dev/null @@ -1,61 +0,0 @@ - - - -image/svg+xml diff --git a/awx/ui/client/assets/i_severity_high.svg b/awx/ui/client/assets/i_severity_high.svg deleted file mode 100644 index 7bd2ba55c896..000000000000 --- a/awx/ui/client/assets/i_severity_high.svg +++ /dev/null @@ -1,61 +0,0 @@ - - - -image/svg+xml diff --git a/awx/ui/client/assets/i_severity_low.svg b/awx/ui/client/assets/i_severity_low.svg deleted file mode 100644 index 539664987d3f..000000000000 --- a/awx/ui/client/assets/i_severity_low.svg +++ /dev/null @@ -1,62 +0,0 @@ - - - -image/svg+xml diff --git a/awx/ui/client/assets/i_severity_med.svg b/awx/ui/client/assets/i_severity_med.svg deleted file mode 100644 index 33e3c19c3089..000000000000 --- a/awx/ui/client/assets/i_severity_med.svg +++ /dev/null @@ -1,62 +0,0 @@ - - - -image/svg+xml diff --git a/awx/ui/client/assets/merriweather-bold-webfont.woff b/awx/ui/client/assets/merriweather-bold-webfont.woff deleted file mode 100644 index a1b95b5c1f72..000000000000 Binary files a/awx/ui/client/assets/merriweather-bold-webfont.woff and /dev/null differ diff --git a/awx/ui/client/assets/merriweather-bold-webfont.woff2 b/awx/ui/client/assets/merriweather-bold-webfont.woff2 deleted file mode 100644 index d9bf97ef7562..000000000000 Binary files a/awx/ui/client/assets/merriweather-bold-webfont.woff2 and /dev/null differ diff --git a/awx/ui/client/assets/merriweather-bolditalic-webfont.woff b/awx/ui/client/assets/merriweather-bolditalic-webfont.woff deleted file mode 100644 index 1696f92a0413..000000000000 Binary files a/awx/ui/client/assets/merriweather-bolditalic-webfont.woff and /dev/null differ diff --git a/awx/ui/client/assets/merriweather-bolditalic-webfont.woff2 b/awx/ui/client/assets/merriweather-bolditalic-webfont.woff2 deleted file mode 100644 index 1f7a7ae20410..000000000000 Binary files a/awx/ui/client/assets/merriweather-bolditalic-webfont.woff2 and /dev/null differ diff --git a/awx/ui/client/assets/merriweather-heavyitalic-webfont.woff b/awx/ui/client/assets/merriweather-heavyitalic-webfont.woff deleted file mode 100644 index 6b3baeb97bf7..000000000000 Binary files a/awx/ui/client/assets/merriweather-heavyitalic-webfont.woff and /dev/null differ diff --git a/awx/ui/client/assets/merriweather-heavyitalic-webfont.woff2 b/awx/ui/client/assets/merriweather-heavyitalic-webfont.woff2 deleted file mode 100644 index cbce14b28a66..000000000000 Binary files a/awx/ui/client/assets/merriweather-heavyitalic-webfont.woff2 and /dev/null differ diff --git a/awx/ui/client/assets/merriweather-italic-webfont.woff b/awx/ui/client/assets/merriweather-italic-webfont.woff deleted file mode 100644 index 987146da8ba4..000000000000 Binary files a/awx/ui/client/assets/merriweather-italic-webfont.woff and /dev/null differ diff --git a/awx/ui/client/assets/merriweather-italic-webfont.woff2 b/awx/ui/client/assets/merriweather-italic-webfont.woff2 deleted file mode 100644 index 6a17eaa263a9..000000000000 Binary files a/awx/ui/client/assets/merriweather-italic-webfont.woff2 and /dev/null differ diff --git a/awx/ui/client/assets/merriweather-lightitalic-webfont.woff b/awx/ui/client/assets/merriweather-lightitalic-webfont.woff deleted file mode 100644 index 75146e6523ad..000000000000 Binary files a/awx/ui/client/assets/merriweather-lightitalic-webfont.woff and /dev/null differ diff --git a/awx/ui/client/assets/merriweather-lightitalic-webfont.woff2 b/awx/ui/client/assets/merriweather-lightitalic-webfont.woff2 deleted file mode 100644 index 5f42282de1b8..000000000000 Binary files a/awx/ui/client/assets/merriweather-lightitalic-webfont.woff2 and /dev/null differ diff --git a/awx/ui/client/assets/merriweather-regular-webfont.woff b/awx/ui/client/assets/merriweather-regular-webfont.woff deleted file mode 100644 index 4ca3dc66720b..000000000000 Binary files a/awx/ui/client/assets/merriweather-regular-webfont.woff and /dev/null differ diff --git a/awx/ui/client/assets/merriweather-regular-webfont.woff2 b/awx/ui/client/assets/merriweather-regular-webfont.woff2 deleted file mode 100644 index 92a537d0698a..000000000000 Binary files a/awx/ui/client/assets/merriweather-regular-webfont.woff2 and /dev/null differ diff --git a/awx/ui/client/assets/merriweather_light-webfont.woff b/awx/ui/client/assets/merriweather_light-webfont.woff deleted file mode 100644 index c171be2053fe..000000000000 Binary files a/awx/ui/client/assets/merriweather_light-webfont.woff and /dev/null differ diff --git a/awx/ui/client/assets/merriweather_light-webfont.woff2 b/awx/ui/client/assets/merriweather_light-webfont.woff2 deleted file mode 100644 index 1a200b5582ca..000000000000 Binary files a/awx/ui/client/assets/merriweather_light-webfont.woff2 and /dev/null differ diff --git a/awx/ui/client/assets/merriweather_ultrabold-webfont.woff b/awx/ui/client/assets/merriweather_ultrabold-webfont.woff deleted file mode 100644 index 7a2b7689289c..000000000000 Binary files a/awx/ui/client/assets/merriweather_ultrabold-webfont.woff and /dev/null differ diff --git a/awx/ui/client/assets/merriweather_ultrabold-webfont.woff2 b/awx/ui/client/assets/merriweather_ultrabold-webfont.woff2 deleted file mode 100644 index c2008e7b7f8e..000000000000 Binary files a/awx/ui/client/assets/merriweather_ultrabold-webfont.woff2 and /dev/null differ diff --git a/awx/ui/client/assets/ubuntu-r-webfont.woff b/awx/ui/client/assets/ubuntu-r-webfont.woff deleted file mode 100644 index 96cc74f9b287..000000000000 Binary files a/awx/ui/client/assets/ubuntu-r-webfont.woff and /dev/null differ diff --git a/awx/ui/client/assets/ubuntu.woff2 b/awx/ui/client/assets/ubuntu.woff2 deleted file mode 100644 index 2701e3f6cc10..000000000000 Binary files a/awx/ui/client/assets/ubuntu.woff2 and /dev/null differ diff --git a/awx/ui/client/assets/variables.less b/awx/ui/client/assets/variables.less deleted file mode 100644 index 97aadb5fd739..000000000000 --- a/awx/ui/client/assets/variables.less +++ /dev/null @@ -1,24 +0,0 @@ -// Default styles for AWX branding - -// Login modal icon -@login-max-width: 200px; -@login-margin: -30px 20px 10px -30px; - -// Main nav bar logo -@main-menu-width: 200px; -@main-menu-margin: auto; -@main-menu-max-width: initial; -@main-menu-max-height: initial; -@main-menu-height: initial; -@main-menu-margin-top-breakpoint: -55px; - - -// About modal logo -@about-modal-float: left; -@about-modal-width: 200px; -@about-modal-padding-top: initial; -@about-modal-margin-top: -60px; -@about-modal-margin-left: -50px; - -// Copyright text should be hidden -@copyright-text: 0; diff --git a/awx/ui/client/features/_index.less b/awx/ui/client/features/_index.less deleted file mode 100644 index 2769b29de76f..000000000000 --- a/awx/ui/client/features/_index.less +++ /dev/null @@ -1,17 +0,0 @@ -@import 'portalMode/_index'; -@import 'output/_index'; -@import 'credentials/_index'; - -/** @define Popup Modal after create new token and applicaiton and save form */ -.PopupModal { - display: flex; -} - -.PopupModal-label { - font-weight: bold; - width: 130px; -} - -.PopupModal-value { - width: 70%; -} \ No newline at end of file diff --git a/awx/ui/client/features/applications/add-applications.controller.js b/awx/ui/client/features/applications/add-applications.controller.js deleted file mode 100644 index d19c853009c9..000000000000 --- a/awx/ui/client/features/applications/add-applications.controller.js +++ /dev/null @@ -1,118 +0,0 @@ -function AddApplicationsController (models, $state, strings, $scope, Alert, $filter, i18n) { - const vm = this || {}; - - const { application, me, organization } = models; - const omit = [ - 'client_id', - 'client_secret', - 'created', - 'modified', - 'related', - 'skip_authorization', - 'summary_fields', - 'type', - 'url', - 'user' - ]; - - vm.mode = 'add'; - vm.strings = strings; - vm.panelTitle = strings.get('add.PANEL_TITLE'); - - vm.tab = { - details: { _active: true }, - users: { _disabled: true } - }; - - vm.form = application.createFormSchema('post', { omit }); - - vm.form.organization = { - type: 'field', - label: i18n._('Organization'), - id: 'organization' - }; - vm.form.description = { - type: 'String', - label: i18n._('Description'), - id: 'description' - }; - - vm.form.disabled = !application.isCreatable(); - - vm.form.organization._resource = 'organization'; - vm.form.organization._route = 'applications.add.organization'; - vm.form.organization._model = organization; - vm.form.organization._placeholder = strings.get('inputs.ORGANIZATION_PLACEHOLDER'); - - vm.form.name.required = true; - vm.form.organization.required = true; - - delete vm.form.name.help_text; - - vm.form.save = data => { - const hiddenData = { - user: me.get('id') - }; - - const payload = _.merge(data, hiddenData); - - return application.request('post', { data: payload }); - }; - - vm.form.onSaveSuccess = res => { - if (res.data && res.data.client_id) { - const name = res.data.name ? - `
-
- ${strings.get('add.NAME_LABEL')} -
-
- ${$filter('sanitize')(res.data.name)} -
-
` : ''; - const clientId = res.data.client_id ? - `
-
- ${strings.get('add.CLIENT_ID_LABEL')} -
-
- ${res.data.client_id} -
-
` : ''; - const clientSecret = res.data.client_secret ? - `
-
- ${strings.get('add.CLIENT_SECRECT_LABEL')} -
-
- ${res.data.client_secret} -
-
` : ''; - - Alert(strings.get('add.MODAL_HEADER'), ` - ${name} - ${clientId} - ${clientSecret} - `, null, null, null, null, null, true); - } - $state.go('applications.edit', { application_id: res.data.id }, { reload: true }); - }; - - $scope.$watch('organization', () => { - if ($scope.organization) { - vm.form.organization._idFromModal = $scope.organization; - } - }); -} - -AddApplicationsController.$inject = [ - 'resolvedModels', - '$state', - 'ApplicationsStrings', - '$scope', - 'Alert', - '$filter', - 'i18n' -]; - -export default AddApplicationsController; diff --git a/awx/ui/client/features/applications/add-edit-applications.view.html b/awx/ui/client/features/applications/add-edit-applications.view.html deleted file mode 100644 index 8593e537dcf4..000000000000 --- a/awx/ui/client/features/applications/add-edit-applications.view.html +++ /dev/null @@ -1,30 +0,0 @@ -
- - - - - {{:: vm.strings.get('tab.DETAILS') }} - {{:: vm.strings.get('tab.USERS') }} - - - - - - - - - - - - - - - - - - - - -
-
-
diff --git a/awx/ui/client/features/applications/applications.strings.js b/awx/ui/client/features/applications/applications.strings.js deleted file mode 100644 index f6fb73f56a44..000000000000 --- a/awx/ui/client/features/applications/applications.strings.js +++ /dev/null @@ -1,45 +0,0 @@ -function ApplicationsStrings (BaseString) { - BaseString.call(this, 'applications'); - - const { t } = this; - const ns = this.applications; - - ns.state = { - LIST_BREADCRUMB_LABEL: t.s('APPLICATIONS'), - ADD_BREADCRUMB_LABEL: t.s('CREATE APPLICATION'), - EDIT_BREADCRUMB_LABEL: t.s('EDIT APPLICATION'), - USER_LIST_BREADCRUMB_LABEL: t.s('TOKENS') - }; - - ns.tab = { - DETAILS: t.s('Details'), - USERS: t.s('Tokens') - }; - - ns.tooltips = { - ADD: t.s('Create a new Application') - }; - - ns.add = { - PANEL_TITLE: t.s('NEW APPLICATION'), - CLIENT_ID_LABEL: t.s('CLIENT ID'), - CLIENT_SECRECT_LABEL: t.s('CLIENT SECRET'), - MODAL_HEADER: t.s('APPLICATION INFORMATION'), - NAME_LABEL: t.s('NAME'), - }; - - ns.list = { - PANEL_TITLE: t.s('APPLICATIONS'), - ROW_ITEM_LABEL_EXPIRED: t.s('EXPIRATION'), - ROW_ITEM_LABEL_ORGANIZATION: t.s('ORG'), - ROW_ITEM_LABEL_MODIFIED: t.s('LAST MODIFIED') - }; - - ns.inputs = { - ORGANIZATION_PLACEHOLDER: t.s('SELECT AN ORGANIZATION') - }; -} - -ApplicationsStrings.$inject = ['BaseStringService']; - -export default ApplicationsStrings; diff --git a/awx/ui/client/features/applications/edit-applications.controller.js b/awx/ui/client/features/applications/edit-applications.controller.js deleted file mode 100644 index cf53a1abc323..000000000000 --- a/awx/ui/client/features/applications/edit-applications.controller.js +++ /dev/null @@ -1,104 +0,0 @@ -function EditApplicationsController (models, $state, strings, $scope) { - const vm = this || {}; - - const { me, application, organization } = models; - - const omit = [ - 'client_id', - 'client_secret', - 'created', - 'modified', - 'related', - 'skip_authorization', - 'summary_fields', - 'type', - 'url', - 'user' - ]; - const isEditable = application.isEditable(); - - vm.mode = 'edit'; - vm.strings = strings; - vm.panelTitle = application.get('name'); - - vm.tab = { - details: { - _active: true, - _go: 'applications.edit', - _params: { application_id: application.get('id') } - }, - users: { - _go: 'applications.edit.users', - _params: { application_id: application.get('id') } - } - }; - - $scope.$watch('$state.current.name', (value) => { - if (/applications.edit.users/.test(value)) { - vm.tab.details._active = false; - vm.tab.users._active = true; - } else { - vm.tab.details._active = true; - vm.tab.users._active = false; - } - }); - - $scope.$watch('organization', () => { - if ($scope.organization) { - vm.form.organization._idFromModal = $scope.organization; - } - }); - - if (isEditable) { - vm.form = application.createFormSchema('put', { omit }); - } else { - vm.form = application.createFormSchema({ omit }); - vm.form.disabled = !isEditable; - } - - vm.form.disabled = !isEditable; - - vm.form.name.required = true; - - const isOrgAdmin = _.some(me.get('related.admin_of_organizations.results'), (org) => org.id === organization.get('id')); - const isSuperuser = me.get('is_superuser'); - const isCurrentAuthor = Boolean(application.get('summary_fields.created_by.id') === me.get('id')); - vm.form.organization._disabled = true; - - if (isSuperuser || isOrgAdmin || (application.get('organization') === null && isCurrentAuthor)) { - vm.form.organization._disabled = false; - } - - vm.form.organization._resource = 'organization'; - vm.form.organization._model = organization; - vm.form.organization._route = 'applications.edit.organization'; - vm.form.organization._value = application.get('summary_fields.organization.id'); - vm.form.organization._displayValue = application.get('summary_fields.organization.name'); - vm.form.organization._placeholder = strings.get('inputs.ORGANIZATION_PLACEHOLDER'); - vm.form.organization.required = true; - - delete vm.form.name.help_text; - - vm.form.save = data => { - const hiddenData = { - user: me.get('id') - }; - - const payload = _.merge(data, hiddenData); - - return application.request('put', { data: payload }); - }; - - vm.form.onSaveSuccess = () => { - $state.go('applications.edit', { application_id: application.get('id') }, { reload: true }); - }; -} - -EditApplicationsController.$inject = [ - 'resolvedModels', - '$state', - 'ApplicationsStrings', - '$scope' -]; - -export default EditApplicationsController; diff --git a/awx/ui/client/features/applications/index.js b/awx/ui/client/features/applications/index.js deleted file mode 100644 index 9c12d2a3a2d2..000000000000 --- a/awx/ui/client/features/applications/index.js +++ /dev/null @@ -1,347 +0,0 @@ - -import AddController from './add-applications.controller'; -import EditController from './edit-applications.controller'; -import ListController from './list-applications.controller'; -import UserListController from './list-applications-users.controller'; -import ApplicationsStrings from './applications.strings'; - -const MODULE_NAME = 'at.features.applications'; - -const addEditTemplate = require('~features/applications/add-edit-applications.view.html'); -const listTemplate = require('~features/applications/list-applications.view.html'); -const indexTemplate = require('~features/applications/index.view.html'); -const userListTemplate = require('~features/applications/list-applications-users.view.html'); - -function ApplicationsDetailResolve ( - $q, - $stateParams, - Me, - Application, - Organization, - ProcessErrors, - strings -) { - const id = $stateParams.application_id; - - const promises = { - me: new Me('get').then((me) => me.extend('get', 'admin_of_organizations')) - }; - - if (!id) { - promises.application = new Application('options'); - promises.organization = new Organization(); - - return $q.all(promises); - } - - promises.application = new Application(['get', 'options'], [id, id]); - - return $q.all(promises) - .then(models => { - const orgId = models.application.get('organization'); - - const dependents = { - organization: new Organization('get', orgId) - }; - - return $q.all(dependents) - .then(related => { - models.organization = related.organization; - - return models; - }); - }) - .catch(({ data, status, config }) => { - ProcessErrors(null, data, status, null, { - hdr: strings.get('error.HEADER'), - msg: strings.get('error.CALL', { path: `${config.url}`, status }) - }); - return $q.reject(); - }); -} - -ApplicationsDetailResolve.$inject = [ - '$q', - '$stateParams', - 'MeModel', - 'ApplicationModel', - 'OrganizationModel', - 'ProcessErrors', - 'ApplicationsStrings' -]; - -function ApplicationsRun ($stateExtender, strings) { - $stateExtender.addState({ - name: 'applications', - route: '/applications', - ncyBreadcrumb: { - label: strings.get('state.LIST_BREADCRUMB_LABEL') - }, - data: { - activityStream: true, - activityStreamTarget: 'o_auth2_application' - }, - views: { - '@': { - templateUrl: indexTemplate, - }, - 'list@applications': { - templateUrl: listTemplate, - controller: ListController, - controllerAs: 'vm' - } - }, - searchPrefix: 'application', - params: { - application_search: { - value: { - page_size: 10, - order_by: 'name' - } - } - }, - resolve: { - resolvedModels: [ - 'ApplicationModel', - (Application) => { - const app = new Application(['options']); - return app; - } - ], - Dataset: [ - '$stateParams', - 'Wait', - 'GetBasePath', - 'QuerySet', - ($stateParams, Wait, GetBasePath, qs) => { - const searchParam = $stateParams.application_search; - const searchPath = GetBasePath('applications'); - - Wait('start'); - return qs.search(searchPath, searchParam) - .finally(() => { - Wait('stop'); - }); - } - ], - } - }); - - $stateExtender.addState({ - name: 'applications.add', - route: '/add', - ncyBreadcrumb: { - label: strings.get('state.ADD_BREADCRUMB_LABEL') - }, - data: { - activityStream: true, - activityStreamTarget: 'o_auth2_application' - }, - views: { - 'add@applications': { - templateUrl: addEditTemplate, - controller: AddController, - controllerAs: 'vm' - } - }, - resolve: { - resolvedModels: ApplicationsDetailResolve - } - }); - - $stateExtender.addState({ - name: 'applications.edit', - route: '/:application_id', - ncyBreadcrumb: { - label: strings.get('state.EDIT_BREADCRUMB_LABEL') - }, - data: { - activityStream: true, - activityStreamTarget: 'o_auth2_application', - activityStreamId: 'application_id' - }, - views: { - 'edit@applications': { - templateUrl: addEditTemplate, - controller: EditController, - controllerAs: 'vm' - } - }, - resolve: { - resolvedModels: ApplicationsDetailResolve - } - }); - - $stateExtender.addState({ - name: 'applications.add.organization', - url: '/organization?selected', - searchPrefix: 'organization', - params: { - organization_search: { - value: { - page_size: 5, - order_by: 'name', - role_level: 'admin_role' - }, - dynamic: true, - squash: '' - } - }, - data: { - basePath: 'organizations', - formChildState: true - }, - ncyBreadcrumb: { - skip: true - }, - views: { - 'organization@applications.add': { - templateProvider: (ListDefinition, generateList) => { - const html = generateList.build({ - mode: 'lookup', - list: ListDefinition, - input_type: 'radio' - }); - - return `${html}`; - } - } - }, - resolve: { - ListDefinition: ['OrganizationList', list => list], - Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath', - (list, qs, $stateParams, GetBasePath) => qs.search( - GetBasePath('organizations'), - $stateParams[`${list.iterator}_search`] - ) - ] - }, - onExit ($state) { - if ($state.transition) { - $('#form-modal').modal('hide'); - $('.modal-backdrop').remove(); - $('body').removeClass('modal-open'); - } - } - }); - - $stateExtender.addState({ - name: 'applications.edit.organization', - url: '/organization?selected', - searchPrefix: 'organization', - params: { - organization_search: { - value: { - page_size: 5, - order_by: 'name', - role_level: 'admin_role' - }, - dynamic: true, - squash: '' - } - }, - data: { - basePath: 'organizations', - formChildState: true - }, - ncyBreadcrumb: { - skip: true - }, - views: { - 'organization@applications.edit': { - templateProvider: (ListDefinition, generateList) => { - const html = generateList.build({ - mode: 'lookup', - list: ListDefinition, - input_type: 'radio' - }); - - return `${html}`; - } - } - }, - resolve: { - ListDefinition: ['OrganizationList', list => list], - Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath', - (list, qs, $stateParams, GetBasePath) => qs.search( - GetBasePath('organizations'), - $stateParams[`${list.iterator}_search`] - ) - ] - }, - onExit ($state) { - if ($state.transition) { - $('#form-modal').modal('hide'); - $('.modal-backdrop').remove(); - $('body').removeClass('modal-open'); - } - } - }); - - $stateExtender.addState({ - name: 'applications.edit.users', - route: '/users', - ncyBreadcrumb: { - label: strings.get('state.USER_LIST_BREADCRUMB_LABEL'), - parent: 'applications.edit' - }, - data: { - activityStream: true, - activityStreamTarget: 'o_auth2_application' - }, - views: { - 'userList@applications.edit': { - templateUrl: userListTemplate, - controller: UserListController, - controllerAs: 'vm' - } - }, - params: { - user_search: { - value: { - order_by: 'user__username', - page_size: 10 - }, - dynamic: true - } - }, - searchPrefix: 'user', - resolve: { - resolvedModels: [ - 'ApplicationModel', - (Application) => { - const app = new Application(['options']); - return app; - } - ], - Dataset: [ - '$stateParams', - 'Wait', - 'GetBasePath', - 'QuerySet', - ($stateParams, Wait, GetBasePath, qs) => { - const searchParam = $stateParams.user_search; - const searchPath = `${GetBasePath('applications')}${$stateParams.application_id}/tokens`; - - Wait('start'); - return qs.search(searchPath, searchParam) - .finally(() => { - Wait('stop'); - }); - } - ], - } - }); -} - -ApplicationsRun.$inject = [ - '$stateExtender', - 'ApplicationsStrings' -]; - -angular - .module(MODULE_NAME, []) - .service('ApplicationsStrings', ApplicationsStrings) - .run(ApplicationsRun); - -export default MODULE_NAME; diff --git a/awx/ui/client/features/applications/index.view.html b/awx/ui/client/features/applications/index.view.html deleted file mode 100644 index b4135fb7918f..000000000000 --- a/awx/ui/client/features/applications/index.view.html +++ /dev/null @@ -1,3 +0,0 @@ -
-
-
diff --git a/awx/ui/client/features/applications/list-applications-users.controller.js b/awx/ui/client/features/applications/list-applications-users.controller.js deleted file mode 100644 index bb7dc70eddb9..000000000000 --- a/awx/ui/client/features/applications/list-applications-users.controller.js +++ /dev/null @@ -1,106 +0,0 @@ -/** *********************************************** - * Copyright (c) 2018 Ansible, Inc. - * - * All Rights Reserved - ************************************************ */ -function ListApplicationsUsersController ( - $filter, - $scope, - Dataset, - strings, - $state, - GetBasePath -) { - const vm = this || {}; - vm.strings = strings; - - // smart-search - const name = 'users'; - const iterator = 'user'; - let paginateQuerySet = {}; - - vm.user_dataset = Dataset.data; - vm.users = Dataset.data.results; - vm.list = { iterator, name, basePath: 'applications' }; - vm.basePath = `${GetBasePath('applications')}${$state.params.application_id}/tokens`; - - $scope.$on('updateDataset', (e, dataset, queryset) => { - vm.user_dataset = dataset; - vm.users = dataset.results; - paginateQuerySet = queryset; - }); - - $scope.$watchCollection('$state.params', () => { - setToolbarSort(); - }); - - const toolbarSortDefault = { - label: `${strings.get('sort.USERNAME_ASCENDING')}`, - value: 'user__username' - }; - - vm.toolbarSortOptions = [ - toolbarSortDefault, - { label: `${strings.get('sort.USERNAME_DESCENDING')}`, value: '-user__username' }, - { label: `${strings.get('sort.CREATED_ASCENDING')}`, value: 'created' }, - { label: `${strings.get('sort.CREATED_DESCENDING')}`, value: '-created' }, - { label: `${strings.get('sort.MODIFIED_ASCENDING')}`, value: 'modified' }, - { label: `${strings.get('sort.MODIFIED_DESCENDING')}`, value: '-modified' }, - { label: `${strings.get('sort.EXPIRES_ASCENDING')}`, value: 'expires' }, - { label: `${strings.get('sort.EXPIRES_DESCENDING')}`, value: '-expires' } - ]; - - function setToolbarSort () { - const orderByValue = _.get($state.params, 'user_search.order_by'); - const sortValue = _.find(vm.toolbarSortOptions, (option) => option.value === orderByValue); - if (sortValue) { - vm.toolbarSortValue = sortValue; - } else { - vm.toolbarSortValue = toolbarSortDefault; - } - } - - vm.onToolbarSort = (sort) => { - vm.toolbarSortValue = sort; - const queryParams = Object.assign( - {}, - $state.params.user_search, - paginateQuerySet, - { order_by: sort.value } - ); - - // Update URL with params - $state.go('.', { - user_search: queryParams - }, { notify: false, location: 'replace' }); - }; - - vm.getLastUsed = user => { - const lastUsed = _.get(user, 'last_used'); - - if (!lastUsed) { - return undefined; - } - - let html = $filter('longDate')(lastUsed); - - const { username, id } = _.get(user, 'summary_fields.last_used', {}); - - if (username && id) { - html += ` by ${$filter('sanitize')(username)}`; - } - - return html; - }; -} - -ListApplicationsUsersController.$inject = [ - '$filter', - '$scope', - 'Dataset', - 'ApplicationsStrings', - '$state', - 'GetBasePath' -]; - -export default ListApplicationsUsersController; diff --git a/awx/ui/client/features/applications/list-applications-users.view.html b/awx/ui/client/features/applications/list-applications-users.view.html deleted file mode 100644 index 2ad4b85ce43b..000000000000 --- a/awx/ui/client/features/applications/list-applications-users.view.html +++ /dev/null @@ -1,43 +0,0 @@ -
- - -
- - - - -
- - - - - - -
-
-
- - diff --git a/awx/ui/client/features/applications/list-applications.controller.js b/awx/ui/client/features/applications/list-applications.controller.js deleted file mode 100644 index 254b31930b94..000000000000 --- a/awx/ui/client/features/applications/list-applications.controller.js +++ /dev/null @@ -1,170 +0,0 @@ -/** *********************************************** - * Copyright (c) 2018 Ansible, Inc. - * - * All Rights Reserved - ************************************************ */ -function ListApplicationsController ( - $filter, - $scope, - $state, - Dataset, - ProcessErrors, - Prompt, - resolvedModels, - strings, - Wait -) { - const vm = this || {}; - const application = resolvedModels; - let paginateQuerySet = {}; - - vm.strings = strings; - vm.activeId = $state.params.application_id; - - $scope.canAdd = application.options('actions.POST'); - - // smart-search - const name = 'applications'; - const iterator = 'application'; - const key = 'application_dataset'; - - $scope.list = { iterator, name, basePath: 'applications' }; - $scope.collection = { iterator }; - $scope[key] = Dataset.data; - vm.applicationsCount = Dataset.data.count; - $scope[name] = Dataset.data.results; - - $scope.$on('updateDataset', (e, dataset, queryset) => { - $scope[key] = dataset; - $scope[name] = dataset.results; - vm.applicationsCount = dataset.count; - // Remove paginateQuerySet once the page and page_size params - // are represented in the url. - paginateQuerySet = queryset; - }); - - vm.tooltips = { - add: strings.get('tooltips.ADD') - }; - - const toolbarSortDefault = { - label: `${strings.get('sort.NAME_ASCENDING')}`, - value: 'name' - }; - - vm.toolbarSortOptions = [ - toolbarSortDefault, - { label: `${strings.get('sort.NAME_DESCENDING')}`, value: '-name' }, - { label: `${strings.get('sort.CREATED_ASCENDING')}`, value: 'created' }, - { label: `${strings.get('sort.CREATED_DESCENDING')}`, value: '-created' } - ]; - - vm.toolbarSortValue = toolbarSortDefault; - - function setToolbarSort () { - const orderByValue = _.get($state.params, 'application_search.order_by'); - const sortValue = _.find(vm.toolbarSortOptions, (option) => option.value === orderByValue); - if (sortValue) { - vm.toolbarSortValue = sortValue; - } else { - vm.toolbarSortValue = toolbarSortDefault; - } - } - - $scope.$watch('$state.params', () => { - setToolbarSort(); - }, true); - - vm.onToolbarSort = (sort) => { - vm.toolbarSortValue = sort; - - const queryParams = Object.assign( - {}, - $state.params.application_search, - paginateQuerySet, - { order_by: sort.value } - ); - - // Update URL with params - $state.go('.', { - application_search: queryParams - }, { notify: false, location: 'replace' }); - }; - - vm.getModified = app => { - const modified = _.get(app, 'modified'); - - if (!modified) { - return undefined; - } - - let html = $filter('longDate')(modified); - - const { username, id } = _.get(app, 'summary_fields.modified_by', {}); - - if (username && id) { - html += ` by ${$filter('sanitize')(username)}`; - } - - return html; - }; - - vm.deleteApplication = (app) => { - const action = () => { - $('#prompt-modal').modal('hide'); - Wait('start'); - application.request('delete', app.id) - .then(() => { - let reloadListStateParams = null; - - if ($scope.applications.length === 1 && $state.params.application_search && - !_.isEmpty($state.params.application_search.page) && - $state.params.application_search.page !== '1') { - const page = `${(parseInt(reloadListStateParams - .application_search.page, 10) - 1)}`; - reloadListStateParams = _.cloneDeep($state.params); - reloadListStateParams.application_search.page = page; - } - - if (parseInt($state.params.application_id, 10) === app.id) { - $state.go('applications', reloadListStateParams, { reload: true }); - } else { - $state.go('.', reloadListStateParams, { reload: true }); - } - }) - .catch(({ data, status }) => { - ProcessErrors($scope, data, status, null, { - hdr: strings.get('error.HEADER'), - msg: strings.get('error.CALL', { path: `${application.path}${app.id}`, status }) - }); - }) - .finally(() => { - Wait('stop'); - }); - }; - - const deleteModalBody = `
${strings.get('deleteResource.CONFIRM', 'application')}
`; - - Prompt({ - hdr: strings.get('deleteResource.HEADER'), - resourceName: $filter('sanitize')(app.name), - body: deleteModalBody, - action, - actionText: strings.get('DELETE') - }); - }; -} - -ListApplicationsController.$inject = [ - '$filter', - '$scope', - '$state', - 'Dataset', - 'ProcessErrors', - 'Prompt', - 'resolvedModels', - 'ApplicationsStrings', - 'Wait' -]; - -export default ListApplicationsController; diff --git a/awx/ui/client/features/applications/list-applications.view.html b/awx/ui/client/features/applications/list-applications.view.html deleted file mode 100644 index e30c45e24912..000000000000 --- a/awx/ui/client/features/applications/list-applications.view.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - -
- - -
- -
-
- - - - -
- - - - - - -
-
- - -
-
-
-
- - -
diff --git a/awx/ui/client/features/credentials/_index.less b/awx/ui/client/features/credentials/_index.less deleted file mode 100644 index f968043d3cc1..000000000000 --- a/awx/ui/client/features/credentials/_index.less +++ /dev/null @@ -1,20 +0,0 @@ -.InputSourceLookup-selectedItem { - display: flex; - flex: 0 0 100%; - align-items: center; - min-height: 50px; - margin-top: 16px; - border-radius: 5px; - background-color: @default-no-items-bord; - border: 1px solid @default-border; -} - -.InputSourceLookup-selectedItemLabel { - color: @default-interface-txt; - text-transform: uppercase; - margin: 0 @at-space-2x; -} - -.InputSourceLookup-selectedItemText { - font-style: italic; -} diff --git a/awx/ui/client/features/credentials/add-edit-credentials.controller.js b/awx/ui/client/features/credentials/add-edit-credentials.controller.js deleted file mode 100644 index 3c9ea31fe906..000000000000 --- a/awx/ui/client/features/credentials/add-edit-credentials.controller.js +++ /dev/null @@ -1,656 +0,0 @@ -/* eslint camelcase: 0 */ -/* eslint arrow-body-style: 0 */ -function AddEditCredentialsController ( - models, - $state, - $scope, - strings, - componentsStrings, - ConfigService, - ngToast, - Wait, - $filter, - CredentialType, - GetBasePath, - Rest, -) { - const vm = this || {}; - const { - me, - credential, - credentialType, - organization, - isOrgEditableByUser, - sourceCredentials, - } = models; - - const omit = ['user', 'team', 'inputs']; - const isEditable = credential.isEditable(); - const isExternal = credentialType.get('kind') === 'external'; - const mode = $state.current.name.startsWith('credentials.add') ? 'add' : 'edit'; - - vm.mode = mode; - vm.strings = strings; - - if (mode === 'edit') { - vm.panelTitle = credential.get('name'); - vm.tab = { - details: { - _active: true, - _go: 'credentials.edit', - _params: { credential_id: credential.get('id') } - }, - permissions: { - _go: 'credentials.edit.permissions', - _params: { credential_id: credential.get('id') } - } - }; - - if (isEditable) { - vm.form = credential.createFormSchema('put', { omit }); - } else { - vm.form = credential.createFormSchema({ omit }); - vm.form.disabled = !isEditable; - } - - vm.form.organization._disabled = !isOrgEditableByUser; - // Only exists for permissions compatibility - $scope.credential_obj = credential.get(); - - vm.form.organization._resource = 'organization'; - vm.form.organization._model = organization; - vm.form.organization._route = 'credentials.edit.organization'; - vm.form.organization._value = credential.get('summary_fields.organization.id'); - vm.form.organization._displayValue = credential.get('summary_fields.organization.name'); - vm.form.organization._placeholder = strings.get('inputs.ORGANIZATION_PLACEHOLDER'); - - vm.form.credential_type._resource = 'credential_type'; - vm.form.credential_type._model = credentialType; - vm.form.credential_type._route = 'credentials.edit.credentialType'; - vm.form.credential_type._placeholder = strings.get('inputs.CREDENTIAL_TYPE_PLACEHOLDER'); - vm.form.credential_type._value = credentialType.get('id'); - vm.form.credential_type._displayValue = credentialType.get('name'); - vm.isTestable = (isEditable && credentialType.get('kind') === 'external'); - - if (credential.get('related.input_sources.results.length' > 0)) { - vm.form.credential_type._disabled = true; - } - - $scope.$watch('$state.current.name', (value) => { - if (/credentials.edit($|\.organization$|\.credentialType$)/.test(value)) { - vm.tab.details._active = true; - vm.tab.permissions._active = false; - } else { - vm.tab.permissions._active = true; - vm.tab.details._active = false; - } - }); - } else if (mode === 'add') { - vm.panelTitle = strings.get('add.PANEL_TITLE'); - vm.tab = { - details: { _active: true }, - permissions: { _disabled: true } - }; - vm.form = credential.createFormSchema('post', { - omit: ['user', 'team', 'inputs'] - }); - - vm.form._formName = 'credential'; - vm.form.disabled = !credential.isCreatable(); - - vm.form.organization._resource = 'organization'; - vm.form.organization._route = 'credentials.add.organization'; - vm.form.organization._model = organization; - vm.form.organization._placeholder = strings.get('inputs.ORGANIZATION_PLACEHOLDER'); - - vm.form.credential_type._resource = 'credential_type'; - vm.form.credential_type._route = 'credentials.add.credentialType'; - vm.form.credential_type._model = credentialType; - vm.form.credential_type._placeholder = strings.get('inputs.CREDENTIAL_TYPE_PLACEHOLDER'); - vm.isTestable = credentialType.get('kind') === 'external'; - } - - $scope.$watch('organization', () => { - if ($scope.organization) { - vm.form.organization._idFromModal = $scope.organization; - } - }); - - $scope.$watch('credential_type', () => { - if ($scope.credential_type) { - vm.form.credential_type._idFromModal = $scope.credential_type; - } - }); - - const gceFileInputSchema = { - id: 'gce_service_account_key', - type: 'file', - label: strings.get('inputs.GCE_FILE_INPUT_LABEL'), - help_text: strings.get('inputs.GCE_FILE_INPUT_HELP_TEXT'), - }; - - let gceFileInputPreEditValues; - - vm.form.inputs = { - _get ({ getSubmitData, check }) { - const apiConfig = ConfigService.get(); - - credentialType.mergeInputProperties(); - const fields = credential.assignInputGroupValues( - apiConfig, - credentialType, - sourceCredentials - ); - - if (credentialType.get('name') === 'Google Compute Engine') { - fields.splice(2, 0, gceFileInputSchema); - $scope.$watch(`vm.form.${gceFileInputSchema.id}._value`, gceOnFileInputChanged); - if (mode === 'edit') { - $scope.$watch('vm.form.ssh_key_data._isBeingReplaced', gceOnReplaceKeyChanged); - } - } - - vm.inputSources.initialItems = credential.get('related.input_sources.results'); - vm.inputSources.items = []; - vm.inputSources.changedInputFields = []; - if (credential.get('credential_type') === credentialType.get('id')) { - vm.inputSources.items = credential.get('related.input_sources.results'); - } - - if (mode === 'add') { - vm.isTestable = (models.me.get('is_superuser') && credentialType.get('kind') === 'external'); - } else { - vm.isTestable = (isEditable && credentialType.get('kind') === 'external'); - } - - vm.getSubmitData = getSubmitData; - vm.checkForm = check; - - return fields; - }, - _onRemoveTag ({ id }) { - vm.onInputSourceClear(id); - }, - _onInputLookup ({ id }) { - vm.onInputSourceOpen(id); - }, - _source: vm.form.credential_type, - _reference: 'vm.form.inputs', - _key: 'inputs', - border: true, - title: true, - }; - - vm.externalTest = { - form: { - inputs: { - _get: () => vm.externalTest.metadataInputs, - _reference: 'vm.form.inputs', - _key: 'inputs', - _source: { _value: {} }, - }, - }, - metadataInputs: null, - }; - vm.inputSources = { - tabs: { - credential: { - _active: true, - _disabled: false, - }, - metadata: { - _active: false, - _disabled: false, - } - }, - form: { - inputs: { - _get: () => vm.inputSources.metadataInputs, - _reference: 'vm.form.inputs', - _key: 'inputs', - _source: { _value: {} }, - }, - }, - field: null, - credentialTypeId: null, - credentialTypeName: null, - credentialId: null, - credentialName: null, - metadataInputs: null, - changedInputFields: [], - initialItems: credential.get('related.input_sources.results'), - items: credential.get('related.input_sources.results'), - }; - - function setInputSourceTab (name) { - const metaIsActive = name === 'metadata'; - vm.inputSources.tabs.credential._active = !metaIsActive; - vm.inputSources.tabs.credential._disabled = false; - vm.inputSources.tabs.metadata._active = metaIsActive; - vm.inputSources.tabs.metadata._disabled = false; - } - - function unsetInputSourceTabs () { - vm.inputSources.tabs.credential._active = false; - vm.inputSources.tabs.credential._disabled = false; - vm.inputSources.tabs.metadata._active = false; - vm.inputSources.tabs.metadata._disabled = false; - } - - vm.onInputSourceClear = (field) => { - vm.form[field].tagMode = true; - vm.form[field].asTag = false; - vm.form[field]._value = ''; - vm.form[field]._tagValue = ''; - vm.form[field]._isValid = true; - vm.form[field]._rejected = false; - vm.inputSources.items = vm.inputSources.items - .filter(({ input_field_name }) => input_field_name !== field); - vm.inputSources.changedInputFields.push(field); - }; - - vm.onInputSourceOpen = (field) => { - // We get here when the input source lookup modal for a field is opened. If source - // credential and metadata values for this field already exist in the initial API data - // or from it being set during a prior visit to the lookup, we initialize the lookup with - // these values here before opening it. - const sourceItem = vm.inputSources.items - .find(({ input_field_name }) => input_field_name === field); - if (sourceItem) { - const { source_credential, summary_fields } = sourceItem; - const { source_credential: { credential_type_id, name } } = summary_fields; - vm.inputSources.credentialId = source_credential; - vm.inputSources.credentialName = name; - vm.inputSources.credentialTypeId = credential_type_id; - vm.inputSources._value = credential_type_id; - } else { - vm.inputSources.credentialId = null; - vm.inputSources.credentialName = null; - vm.inputSources.credentialTypeId = null; - vm.inputSources._value = null; - } - - setInputSourceTab('credential'); - vm.inputSources.field = field; - }; - - vm.onInputSourceClose = () => { - // We get here if the lookup was closed or canceled so we clear the state for the lookup - // and metadata form without storing any changes. - vm.inputSources.field = null; - vm.inputSources.credentialId = null; - vm.inputSources.credentialName = null; - vm.inputSources.metadataInputs = null; - unsetInputSourceTabs(); - }; - - /** - * Extract the current set of input values from the metadata form and reshape them to a - * metadata object that can be sent to the api later or reloaded when re-opening the form. - */ - function getMetadataFormSubmitData ({ inputs }) { - return inputs._group.reduce((metadata, { id, _value }) => { - if (_value !== undefined) { - metadata[id] = _value; - } - return metadata; - }, {}); - } - - vm.onInputSourceNext = () => { - const { field, credentialId, credentialTypeId } = vm.inputSources; - Wait('start'); - new CredentialType('get', credentialTypeId) - .then(model => { - model.mergeInputProperties('metadata'); - vm.inputSources.metadataInputs = model.get('inputs.metadata'); - vm.inputSources.credentialTypeName = model.get('name'); - // Pre-populate the input values for the metadata form if state for this specific - // field_name->source_credential link already exists. This occurs one of two ways: - // - // 1. This field->source_credential link already exists in the API and so we're - // showing the current state as it exists on the backend. - // 2. The metadata form for this specific field->source_credential combination was - // set during a prior visit to this lookup and so we're reflecting the most - // recent set of (unsaved) metadata values provided by the user for this field. - // - // Note: Prior state for a given credential input field is only set for one source - // credential at a time. Linking a field to a source credential will remove all - // other prior input state for that field. - const [metavals] = vm.inputSources.items - .filter(({ input_field_name }) => input_field_name === field) - .filter(({ source_credential }) => source_credential === credentialId) - .map(({ metadata }) => metadata); - Object.keys(metavals || {}).forEach(key => { - const obj = vm.inputSources.metadataInputs.find(o => o.id === key); - if (obj) obj._value = metavals[key]; - }); - setInputSourceTab('metadata'); - }) - .finally(() => Wait('stop')); - }; - - vm.onInputSourceSelect = () => { - const { field, credentialId, credentialName, credentialTypeId } = vm.inputSources; - const metadata = getMetadataFormSubmitData(vm.inputSources.form); - // Remove any input source objects already stored for this field then store the metadata - // and currently selected source credential as a credential input source object that - // can be sent to the api later or reloaded into the form if it is reopened. - vm.inputSources.items = vm.inputSources.items - .filter(({ input_field_name }) => input_field_name !== field) - .concat([{ - metadata, - input_field_name: field, - source_credential: credentialId, - target_credential: credential.get('id'), - summary_fields: { - source_credential: { - name: credentialName, - credential_type_id: credentialTypeId - } - }, - }]); - // Record that this field was changed - vm.inputSources.changedInputFields.push(field); - // Now that we've extracted and stored the selected source credential and metadata values - // for this field, we clear the state for the source credential lookup and metadata form. - vm.inputSources.field = null; - vm.inputSources.metadataInputs = null; - unsetInputSourceTabs(); - // We've linked this field to a credential, so display value as a credential tag - vm.form[field]._value = ''; - vm.form[field]._tagValue = credentialName; - vm.form[field]._isValid = true; - vm.form[field].asTag = true; - vm.checkForm(); - }; - - vm.onInputSourceTabSelect = (name) => { - if (name === 'metadata') { - // Clicking on the metadata tab should have identical behavior to clicking the 'next' - // button, so we pass-through to the same handler here. - vm.onInputSourceNext(); - } else { - setInputSourceTab('credential'); - } - }; - - vm.onInputSourceItemSelect = ({ id, credential_type, name }) => { - vm.inputSources.credentialId = id; - vm.inputSources.credentialName = name; - vm.inputSources.credentialTypeId = credential_type; - vm.inputSources._value = credential_type; - }; - - vm.onInputSourceTest = () => { - // We get here if the test button on the metadata form for the field of a non-external - // credential was used. All input values for the external credential are already stored - // on the backend, so we are only testing how it works with a set of metadata before - // linking it. - const metadata = getMetadataFormSubmitData(vm.inputSources.form); - const name = $filter('sanitize')(vm.inputSources.credentialTypeName); - const endpoint = `${vm.inputSources.credentialId}/test/`; - return runTest({ name, model: credential, endpoint, data: { metadata } }); - }; - - function onExternalTestOpen () { - // We get here if test button on the top-level form for an external credential type was - // used. We load the metadata schema for this particular external credential type and - // use it to generate and open a form for submitting test values. - credentialType.mergeInputProperties('metadata'); - vm.externalTest.metadataInputs = credentialType.get('inputs.metadata'); - } - vm.form.secondary = onExternalTestOpen; - - vm.onExternalTestClose = () => { - // We get here if the metadata test form for an external credential type was canceled or - // closed so we clear the form state and close without submitting any data to the test api, - vm.externalTest.metadataInputs = null; - }; - - vm.onExternalTest = () => { - const name = $filter('sanitize')(credentialType.get('name')); - const { inputs } = vm.getSubmitData(); - const metadata = getMetadataFormSubmitData(vm.externalTest.form); - // We get here if the test button on the top-level form for an external credential type was - // used. We need to see if the currently selected credential type is the one loaded from - // the api when we initialized the view or if its type was changed on the form and hasn't - // been saved. If the credential type hasn't been changed, it means some of the input - // values for the credential may be stored in the backend and not in the form, so we need - // to use the test endpoint for the credential. If the credential type has been changed, - // the user must provide a complete set of input values for the credential to save their - // changes, so we use the generic test endpoint for the credental type as if we were - // testing a completely new and unsaved credential. - let model; - if (credential.get('credential_type') !== credentialType.get('id')) { - model = credentialType; - } else { - model = credential; - } - - const endpoint = `${model.get('id')}/test/`; - return runTest({ name, model, endpoint, data: { inputs, metadata } }); - }; - - vm.filterInputSourceCredentialResults = (data) => { - // If an external credential is changed to have a non-external `credential_type` while - // editing, we avoid showing a self-reference in the list of selectable external - // credentials for input fields by filtering it out here. - if (isExternal) { - data.results = data.results.filter(({ id }) => id !== credential.get('id')); - } - - // only show credentials we can use - data.results = data.results - .filter(({ summary_fields }) => summary_fields.user_capabilities.use); - - return data; - }; - - function runTest ({ name, model, endpoint, data: { inputs, metadata } }) { - return model.http.post({ url: endpoint, data: { inputs, metadata }, replace: false }) - .then(() => { - const icon = 'fa-check-circle'; - const msg = strings.get('edit.TEST_PASSED'); - const content = buildTestNotificationContent({ name, icon, msg }); - ngToast.success({ - content, - dismissButton: false, - dismissOnTimeout: true - }); - }) - .catch(({ data }) => { - const icon = 'fa-exclamation-triangle'; - const msg = data.inputs || strings.get('edit.TEST_FAILED'); - const content = buildTestNotificationContent({ name, icon, msg }); - ngToast.danger({ - content, - dismissButton: false, - dismissOnTimeout: true - }); - }); - } - - function buildTestNotificationContent ({ name, msg, icon }) { - const sanitize = $filter('sanitize'); - const content = `
-
- -
-
- ${sanitize(name)}: ${sanitize(msg)} -
-
`; - return content; - } - - function deleteInputSource ({ id }) { - Rest.setUrl(`${GetBasePath('credential_input_sources')}${id}/`); - return Rest.destroy(); - } - - function createInputSource (data) { - Rest.setUrl(GetBasePath('credential_input_sources')); - return Rest.post(data); - } - - function create (data) { - data.user = me.get('id'); - - if (_.get(data.inputs, gceFileInputSchema.id)) { - delete data.inputs[gceFileInputSchema.id]; - } - - const updatedLinkedFieldNames = vm.inputSources.items - .map(({ input_field_name }) => input_field_name); - const sourcesToAssociate = [...vm.inputSources.items]; - - // remove inputs with empty string values - let filteredInputs = _.omit(data.inputs, (value) => value === ''); - // remove inputs that are to be linked to an external credential - filteredInputs = _.omit(filteredInputs, updatedLinkedFieldNames); - data.inputs = filteredInputs; - - return credential.request('post', { data }) - .then(() => { - sourcesToAssociate.forEach(obj => { obj.target_credential = credential.get('id'); }); - return Promise.all(sourcesToAssociate.map(createInputSource)); - }); - } - - /** - * If a credential's `credential_type` is changed while editing, the inputs associated with - * the old type need to be cleared before saving the inputs associated with the new type. - * Otherwise inputs are merged together making the request invalid. - */ - function update (data) { - data.user = me.get('id'); - credential.unset('inputs'); - - if (_.get(data.inputs, gceFileInputSchema.id)) { - delete data.inputs[gceFileInputSchema.id]; - } - - const initialLinkedFieldNames = vm.inputSources.initialItems - .map(({ input_field_name }) => input_field_name); - const updatedLinkedFieldNames = vm.inputSources.items - .map(({ input_field_name }) => input_field_name); - - const fieldsToDisassociate = initialLinkedFieldNames - .filter(name => !updatedLinkedFieldNames.includes(name)) - .concat(updatedLinkedFieldNames) - .filter(name => vm.inputSources.changedInputFields.includes(name)); - const fieldsToAssociate = updatedLinkedFieldNames - .filter(name => vm.inputSources.changedInputFields.includes(name)); - - const sourcesToDisassociate = fieldsToDisassociate - .map(name => vm.inputSources.initialItems - .find(({ input_field_name }) => input_field_name === name)) - .filter(source => source !== undefined); - const sourcesToAssociate = fieldsToAssociate - .map(name => vm.inputSources.items - .find(({ input_field_name }) => input_field_name === name)) - .filter(source => source !== undefined); - - // remove inputs with empty string values - let filteredInputs = _.omit(data.inputs, (value) => value === ''); - // remove inputs that are to be linked to an external credential - filteredInputs = _.omit(filteredInputs, updatedLinkedFieldNames); - data.inputs = filteredInputs; - - return credential.request('put', { data }) - .then(() => Promise.all(sourcesToDisassociate.map(deleteInputSource))) - .then(() => Promise.all(sourcesToAssociate.map(createInputSource))); - } - - vm.form.save = data => { - if (mode === 'edit') { - return update(data); - } - return create(data); - }; - - vm.form.onSaveSuccess = () => { - $state.go('credentials.edit', { credential_id: credential.get('id') }, { reload: true }); - }; - - function gceOnReplaceKeyChanged (value) { - vm.form[gceFileInputSchema.id]._disabled = !value; - } - - function gceOnFileInputChanged (value, oldValue) { - if (value === oldValue) return; - - const gceFileIsLoaded = !!value; - const gceFileInputState = vm.form[gceFileInputSchema.id]; - const { obj, error } = gceParseFileInput(value); - - gceFileInputState._isValid = !error; - gceFileInputState._message = error ? componentsStrings.get('message.INVALID_INPUT') : ''; - - vm.form.project._disabled = gceFileIsLoaded; - vm.form.username._disabled = gceFileIsLoaded; - vm.form.ssh_key_data._disabled = gceFileIsLoaded; - vm.form.ssh_key_data._displayHint = !vm.form.ssh_key_data._disabled; - - if (gceFileIsLoaded) { - gceFileInputPreEditValues = Object.assign({}, { - project: vm.form.project._value, - ssh_key_data: vm.form.ssh_key_data._value, - username: vm.form.username._value - }); - - vm.form.project.asTag = false; - vm.form.project._value = _.get(obj, 'project_id', ''); - vm.inputSources.changedInputFields.push('project'); - vm.inputSources.items = vm.inputSources.items - .filter(({ input_field_name }) => input_field_name !== 'project'); - - vm.form.ssh_key_data.asTag = false; - vm.form.ssh_key_data._value = _.get(obj, 'private_key', ''); - vm.inputSources.changedInputFields.push('ssh_key_data'); - vm.inputSources.items = vm.inputSources.items - .filter(({ input_field_name }) => input_field_name !== 'ssh_key_data'); - - vm.form.username.asTag = false; - vm.form.username._value = _.get(obj, 'client_email', ''); - vm.inputSources.changedInputFields.push('username'); - vm.inputSources.items = vm.inputSources.items - .filter(({ input_field_name }) => input_field_name !== 'username'); - } else { - vm.form.project._value = gceFileInputPreEditValues.project; - vm.form.ssh_key_data._value = gceFileInputPreEditValues.ssh_key_data; - vm.form.username._value = gceFileInputPreEditValues.username; - } - } - - function gceParseFileInput (value) { - let obj; - let error; - - try { - obj = angular.fromJson(value); - } catch (err) { - error = err; - } - - return { obj, error }; - } -} - -AddEditCredentialsController.$inject = [ - 'resolvedModels', - '$state', - '$scope', - 'CredentialsStrings', - 'ComponentsStrings', - 'ConfigService', - 'ngToast', - 'Wait', - '$filter', - 'CredentialTypeModel', - 'GetBasePath', - 'Rest', -]; - -export default AddEditCredentialsController; diff --git a/awx/ui/client/features/credentials/add-edit-credentials.view.html b/awx/ui/client/features/credentials/add-edit-credentials.view.html deleted file mode 100644 index b642a6da0728..000000000000 --- a/awx/ui/client/features/credentials/add-edit-credentials.view.html +++ /dev/null @@ -1,66 +0,0 @@ -
-
- - - - - {{:: vm.strings.get('tab.DETAILS') }} - {{:: vm.strings.get('tab.PERMISSIONS') }} - - - - - - - - - - - - - - {{:: vm.strings.get('inputs.GROUP_TITLE') }} - - - - - - - - - - - - - - - - {{:: vm.strings.get('tab.DETAILS') }} - {{:: vm.strings.get('tab.PERMISSIONS') }} - - - -
-
-
- - -
diff --git a/awx/ui/client/features/credentials/credentials.strings.js b/awx/ui/client/features/credentials/credentials.strings.js deleted file mode 100644 index 55e19ca1522e..000000000000 --- a/awx/ui/client/features/credentials/credentials.strings.js +++ /dev/null @@ -1,55 +0,0 @@ -function CredentialsStrings (BaseString) { - BaseString.call(this, 'credentials'); - - const { t } = this; - const ns = this.credentials; - - ns.state = { - ADD_BREADCRUMB_LABEL: t.s('CREATE CREDENTIAL'), - EDIT_BREADCRUMB_LABEL: t.s('EDIT CREDENTIAL') - }; - - ns.tab = { - DETAILS: t.s('Details'), - PERMISSIONS: t.s('Permissions'), - }; - - ns.inputs = { - GROUP_TITLE: t.s('Type Details'), - ORGANIZATION_PLACEHOLDER: t.s('SELECT AN ORGANIZATION'), - CREDENTIAL_TYPE_PLACEHOLDER: t.s('SELECT A CREDENTIAL TYPE'), - GCE_FILE_INPUT_LABEL: t.s('Service Account JSON File'), - GCE_FILE_INPUT_HELP_TEXT: t.s('Provide account information using Google Compute Engine JSON credentials file.') - }; - - ns.externalTest = { - TITLE: t.s('Test External Credential') - }; - - ns.inputSources = { - TITLE: t.s('Set Input Source'), - CREDENTIAL: t.s('CREDENTIAL'), - METADATA: t.s('METADATA'), - NO_MATCH: t.s('No records matched your search.'), - NO_RECORDS: t.s('No external credentials available.'), - SELECTED: t.s('selected'), - NONE_SELECTED: t.s('No credential selected'), - }; - - ns.add = { - PANEL_TITLE: t.s('NEW CREDENTIAL') - }; - - ns.edit = { - TEST_PASSED: t.s('Test passed.'), - TEST_FAILED: t.s('Test failed.') - }; - - ns.permissions = { - TITLE: t.s('CREDENTIALS PERMISSIONS') - }; -} - -CredentialsStrings.$inject = ['BaseStringService']; - -export default CredentialsStrings; diff --git a/awx/ui/client/features/credentials/external-test-modal.component.js b/awx/ui/client/features/credentials/external-test-modal.component.js deleted file mode 100644 index 7b8ac82685ea..000000000000 --- a/awx/ui/client/features/credentials/external-test-modal.component.js +++ /dev/null @@ -1,27 +0,0 @@ -const templateUrl = require('~features/credentials/external-test-modal.partial.html'); - -function ExternalTestModalController (strings) { - const vm = this || {}; - - vm.strings = strings; - vm.title = strings.get('externalTest.TITLE'); - - vm.$onInit = () => { - vm.form.save = () => vm.onSubmit(); - }; -} - -ExternalTestModalController.$inject = [ - 'CredentialsStrings', -]; - -export default { - templateUrl, - controller: ExternalTestModalController, - controllerAs: 'vm', - bindings: { - onClose: '=', - onSubmit: '=', - form: '=', - }, -}; diff --git a/awx/ui/client/features/credentials/external-test-modal.partial.html b/awx/ui/client/features/credentials/external-test-modal.partial.html deleted file mode 100644 index 5f35774655c7..000000000000 --- a/awx/ui/client/features/credentials/external-test-modal.partial.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - {{::vm.strings.get('CLOSE')}} - - - {{::vm.strings.get('RUN')}} - - - - diff --git a/awx/ui/client/features/credentials/index.js b/awx/ui/client/features/credentials/index.js deleted file mode 100644 index 1bdec18f93e5..000000000000 --- a/awx/ui/client/features/credentials/index.js +++ /dev/null @@ -1,166 +0,0 @@ -import LegacyCredentials from './legacy.credentials'; -import AddEditController from './add-edit-credentials.controller'; -import CredentialsStrings from './credentials.strings'; -import InputSourceLookupComponent from './input-source-lookup.component'; -import ExternalTestModalComponent from './external-test-modal.component'; - -const MODULE_NAME = 'at.features.credentials'; - -const addEditTemplate = require('~features/credentials/add-edit-credentials.view.html'); - -function CredentialsResolve ( - $q, - $stateParams, - Me, - Credential, - CredentialType, - Organization, - ProcessErrors, - strings, - Rest, - GetBasePath, -) { - const id = $stateParams.credential_id; - - const promises = { - me: new Me('get').then((me) => me.extend('get', 'admin_of_organizations')) - }; - - if (!id) { - promises.credential = new Credential('options'); - promises.credentialType = new CredentialType(); - promises.organization = new Organization(); - promises.sourceCredentials = $q.resolve({ data: { count: 0, results: [] } }); - - return $q.all(promises); - } - - promises.credential = new Credential(['get', 'options'], [id, id]); - - return $q.all(promises) - .then(models => { - const typeId = models.credential.get('credential_type'); - const orgId = models.credential.get('organization'); - - Rest.setUrl(GetBasePath('credentials')); - const params = { target_input_sources__target_credential: id }; - const sourceCredentialsPromise = Rest.get({ params }); - - const dependents = { - credentialType: new CredentialType('get', typeId), - organization: new Organization('get', orgId), - credentialInputSources: models.credential.extend('GET', 'input_sources'), - sourceCredentials: sourceCredentialsPromise - }; - - dependents.isOrgCredAdmin = dependents.organization.then((org) => org.search({ role_level: 'credential_admin_role' })); - - return $q.all(dependents) - .then(related => { - models.credentialType = related.credentialType; - models.organization = related.organization; - models.sourceCredentials = related.sourceCredentials; - - const isOrgAdmin = _.some(models.me.get('related.admin_of_organizations.results'), (org) => org.id === models.organization.get('id')); - const isSuperuser = models.me.get('is_superuser'); - const isCurrentAuthor = Boolean(models.credential.get('summary_fields.created_by.id') === models.me.get('id')); - - models.isOrgEditableByUser = (isSuperuser || isOrgAdmin - || related.isOrgCredAdmin - || (models.credential.get('organization') === null && isCurrentAuthor)); - - return models; - }); - }).catch(({ data, status, config }) => { - ProcessErrors(null, data, status, null, { - hdr: strings.get('error.HEADER'), - msg: strings.get('error.CALL', { path: `${config.url}`, status }) - }); - return $q.reject(); - }); -} - -CredentialsResolve.$inject = [ - '$q', - '$stateParams', - 'MeModel', - 'CredentialModel', - 'CredentialTypeModel', - 'OrganizationModel', - 'ProcessErrors', - 'CredentialsStrings', - 'Rest', - 'GetBasePath', -]; - -function CredentialsRun ($stateExtender, legacy, strings) { - $stateExtender.addState({ - name: 'credentials.add', - route: '/add', - ncyBreadcrumb: { - label: strings.get('state.ADD_BREADCRUMB_LABEL') - }, - data: { - activityStream: true, - activityStreamTarget: 'credential' - }, - views: { - 'add@credentials': { - templateUrl: addEditTemplate, - controller: AddEditController, - controllerAs: 'vm' - } - }, - resolve: { - resolvedModels: CredentialsResolve - } - }); - - $stateExtender.addState({ - name: 'credentials.edit', - route: '/:credential_id', - ncyBreadcrumb: { - label: strings.get('state.EDIT_BREADCRUMB_LABEL') - }, - data: { - activityStream: true, - activityStreamTarget: 'credential', - activityStreamId: 'credential_id' - }, - views: { - 'edit@credentials': { - templateUrl: addEditTemplate, - controller: AddEditController, - controllerAs: 'vm' - } - }, - resolve: { - resolvedModels: CredentialsResolve - } - }); - - $stateExtender.addState(legacy.getStateConfiguration('list')); - $stateExtender.addState(legacy.getStateConfiguration('edit-permissions')); - $stateExtender.addState(legacy.getStateConfiguration('add-permissions')); - $stateExtender.addState(legacy.getStateConfiguration('add-organization')); - $stateExtender.addState(legacy.getStateConfiguration('edit-organization')); - $stateExtender.addState(legacy.getStateConfiguration('add-credential-type')); - $stateExtender.addState(legacy.getStateConfiguration('edit-credential-type')); -} - -CredentialsRun.$inject = [ - '$stateExtender', - 'LegacyCredentialsService', - 'CredentialsStrings' -]; - -angular - .module(MODULE_NAME, []) - .controller('AddEditController', AddEditController) - .service('LegacyCredentialsService', LegacyCredentials) - .service('CredentialsStrings', CredentialsStrings) - .component('atInputSourceLookup', InputSourceLookupComponent) - .component('atExternalCredentialTest', ExternalTestModalComponent) - .run(CredentialsRun); - -export default MODULE_NAME; diff --git a/awx/ui/client/features/credentials/index.view.html b/awx/ui/client/features/credentials/index.view.html deleted file mode 100644 index 964941c53ddf..000000000000 --- a/awx/ui/client/features/credentials/index.view.html +++ /dev/null @@ -1,6 +0,0 @@ -
-
- -
-
-
diff --git a/awx/ui/client/features/credentials/input-source-lookup.component.js b/awx/ui/client/features/credentials/input-source-lookup.component.js deleted file mode 100644 index b76baf043bfd..000000000000 --- a/awx/ui/client/features/credentials/input-source-lookup.component.js +++ /dev/null @@ -1,42 +0,0 @@ -const templateUrl = require('~features/credentials/input-source-lookup.partial.html'); - -function InputSourceLookupController (strings, wait) { - const vm = this || {}; - - vm.strings = strings; - vm.title = strings.get('inputSources.TITLE'); - - vm.$onInit = () => { - wait('start'); - vm.form.save = () => vm.onTest(); - }; - - vm.onReady = () => { - vm.isReady = true; - wait('stop'); - }; -} - -InputSourceLookupController.$inject = [ - 'CredentialsStrings', - 'Wait', -]; - -export default { - templateUrl, - controller: InputSourceLookupController, - controllerAs: 'vm', - bindings: { - tabs: '=', - onClose: '=', - onNext: '=', - onSelect: '=', - onTabSelect: '=', - onItemSelect: '=', - onTest: '=', - selectedId: '=', - selectedName: '=', - form: '=', - resultsFilter: '=', - }, -}; diff --git a/awx/ui/client/features/credentials/input-source-lookup.partial.html b/awx/ui/client/features/credentials/input-source-lookup.partial.html deleted file mode 100644 index 3c5b0478e294..000000000000 --- a/awx/ui/client/features/credentials/input-source-lookup.partial.html +++ /dev/null @@ -1,89 +0,0 @@ - - - - {{::vm.strings.get('inputSources.CREDENTIAL')}} - - - {{::vm.strings.get('inputSources.METADATA')}} - - -
-
- {{::vm.strings.get('inputSources.SELECTED')}} -
- -
- -
-
- {{::vm.strings.get('inputSources.NONE_SELECTED')}} -
-
-
- - - - - - - {{::vm.strings.get('TEST')}} - - - {{::vm.strings.get('CANCEL')}} - - - {{::vm.strings.get('NEXT')}} - - - {{::vm.strings.get('OK')}} - - - -
diff --git a/awx/ui/client/features/credentials/legacy.credentials.js b/awx/ui/client/features/credentials/legacy.credentials.js deleted file mode 100644 index cb1bd1625dde..000000000000 --- a/awx/ui/client/features/credentials/legacy.credentials.js +++ /dev/null @@ -1,355 +0,0 @@ -import ListController from '../../src/credentials/list/credentials-list.controller'; -import { N_ } from '../../src/i18n'; - -const indexTemplate = require('~features/credentials/index.view.html'); - -function LegacyCredentialsService () { - this.list = { - name: 'credentials', - route: '/credentials', - ncyBreadcrumb: { - label: N_('CREDENTIALS') - }, - data: { - activityStream: true, - activityStreamTarget: 'credential' - }, - views: { - '@': { - templateUrl: indexTemplate - }, - 'list@credentials': { - templateProvider (CredentialList, generateList) { - const html = generateList.build({ - list: CredentialList, - mode: 'edit' - }); - - return html; - }, - controller: ListController - } - }, - searchPrefix: 'credential', - resolve: { - Dataset: ['CredentialList', 'QuerySet', '$stateParams', 'GetBasePath', - (list, qs, $stateParams, GetBasePath) => { - const path = GetBasePath(list.basePath) || GetBasePath(list.name); - - return qs.search(path, $stateParams[`${list.iterator}_search`]); - } - ], - credentialType: ['CredentialTypeModel', CredentialType => new CredentialType('get')] - } - }; - - this.editPermissions = { - name: 'credentials.edit.permissions', - url: '/permissions?{permission_search:queryset}', - resolve: { - ListDefinition: () => ({ - name: 'permissions', - disabled: 'organization === undefined', - ngClick: 'organization === undefined || $state.go(\'credentials.edit.permissions\')', - awToolTip: '{{permissionsTooltip}}', - dataTipWatch: 'permissionsTooltip', - awToolTipTabEnabledInEditMode: true, - dataPlacement: 'right', - basePath: 'api/v2/credentials/{{$stateParams.id}}/access_list/', - search: { - order_by: 'username' - }, - type: 'collection', - title: N_('Permissions'), - iterator: 'permission', - index: false, - open: false, - actions: { - add: { - ngClick: '$state.go(\'.add\')', - label: N_('Add'), - awToolTip: N_('Add a permission'), - actionClass: 'at-Button--add', - actionId: 'button-add', - ngShow: '(credential_obj.summary_fields.user_capabilities.edit || canAdd)' - } - }, - fields: { - username: { - key: true, - label: N_('User'), - linkBase: 'users', - columnClass: 'col-lg-3 col-md-3 col-sm-3 col-xs-4' - }, - role: { - label: N_('Role'), - type: 'role', - nosort: true, - columnClass: 'col-lg-4 col-md-4 col-sm-4 col-xs-4' - }, - team_roles: { - label: N_('Team Roles'), - type: 'team_roles', - nosort: true, - columnClass: 'col-lg-5 col-md-5 col-sm-5 col-xs-4' - } - } - }), - Dataset: ['QuerySet', '$stateParams', (qs, $stateParams) => { - const id = $stateParams.credential_id; - const path = `api/v2/credentials/${id}/access_list/`; - - return qs.search(path, $stateParams.permission_search); - }] - }, - params: { - permission_search: { - value: { - page_size: '20', - order_by: 'username' - }, - dynamic: true, - squash: '' - } - }, - ncyBreadcrumb: { - parent: 'credentials.edit', - label: N_('PERMISSIONS') - }, - views: { - related: { - templateProvider (CredentialForm, GenerateForm) { - const html = GenerateForm.buildCollection({ - mode: 'edit', - related: 'permissions', - form: typeof (CredentialForm) === 'function' ? - CredentialForm() : CredentialForm - }); - return html; - }, - controller: 'PermissionsList' - } - } - }; - - this.addPermissions = { - name: 'credentials.edit.permissions.add', - url: '/add-permissions', - resolve: { - usersDataset: [ - 'addPermissionsUsersList', - 'QuerySet', - '$stateParams', - 'GetBasePath', - 'resourceData', - (list, qs, $stateParams, GetBasePath, resourceData) => { - let path; - - if (resourceData.data.organization) { - path = `${GetBasePath('organizations')}${resourceData.data.organization}/users`; - } else { - path = list.basePath || GetBasePath(list.name); - } - - return qs.search(path, $stateParams.user_search); - } - ], - teamsDataset: [ - 'addPermissionsTeamsList', - 'QuerySet', - '$stateParams', - 'GetBasePath', - 'resourceData', - (list, qs, $stateParams, GetBasePath, resourceData) => { - const path = GetBasePath(list.basePath) || GetBasePath(list.name); - const org = resourceData.data.organization; - - if (!org) { - return null; - } - - $stateParams[`${list.iterator}_search`].organization = org; - - return qs.search(path, $stateParams.team_search); - } - ], - resourceData: ['CredentialModel', '$stateParams', (Credential, $stateParams) => - new Credential('get', $stateParams.credential_id) - .then(credential => ({ data: credential.get() })) - ] - }, - params: { - user_search: { - value: { - order_by: 'username', - page_size: 5, - is_superuser: false - }, - dynamic: true - }, - team_search: { - value: { - order_by: 'name', - page_size: 5 - }, - dynamic: true - } - }, - ncyBreadcrumb: { - skip: true - }, - views: { - 'modal@credentials.edit': { - template: ` - - ` - } - }, - onExit: $state => { - if ($state.transition) { - $('#add-permissions-modal').modal('hide'); - $('.modal-backdrop').remove(); - $('body').removeClass('modal-open'); - } - } - }; - - this.lookupTemplateProvider = (ListDefinition, generateList) => { - const html = generateList.build({ - mode: 'lookup', - list: ListDefinition, - input_type: 'radio' - }); - - return `${html}`; - }; - - this.organization = { - url: '/organization?selected', - searchPrefix: 'organization', - params: { - organization_search: { - value: { - page_size: 5, - order_by: 'name', - role_level: 'credential_admin_role' - }, - dynamic: true, - squash: '' - } - }, - data: { - basePath: 'organizations', - formChildState: true - }, - ncyBreadcrumb: { - skip: true - }, - views: {}, - resolve: { - ListDefinition: ['OrganizationList', list => list], - Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath', - (list, qs, $stateParams, GetBasePath) => qs.search( - GetBasePath('organizations'), - $stateParams[`${list.iterator}_search`] - ) - ] - }, - onExit ($state) { - if ($state.transition) { - $('#form-modal').modal('hide'); - $('.modal-backdrop').remove(); - $('body').removeClass('modal-open'); - } - } - }; - - this.credentialType = { - url: '/credential_type?selected', - searchPrefix: 'credential_type', - params: { - credential_type_search: { - value: { - page_size: 5, - order_by: 'name' - }, - dynamic: true, - squash: '' - } - }, - data: { - basePath: 'credential_types', - formChildState: true - }, - ncyBreadcrumb: { - skip: true - }, - views: {}, - resolve: { - ListDefinition: ['CredentialTypesList', list => list], - Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath', - (list, qs, $stateParams, GetBasePath) => qs.search( - GetBasePath('credential_types'), - $stateParams[`${list.iterator}_search`] - ) - ] - }, - onExit ($state) { - if ($state.transition) { - $('#form-modal').modal('hide'); - $('.modal-backdrop').remove(); - $('body').removeClass('modal-open'); - } - } - }; - - this.getStateConfiguration = (name) => { - switch (name) { - case 'list': - return this.list; - case 'edit-permissions': - return this.editPermissions; - case 'add-permissions': - return this.addPermissions; - case 'add-organization': - this.organization.name = 'credentials.add.organization'; - this.organization.views['organization@credentials.add'] = { - templateProvider: this.lookupTemplateProvider - }; - - return this.organization; - case 'edit-organization': - this.organization.name = 'credentials.edit.organization'; - this.organization.views['organization@credentials.edit'] = { - templateProvider: this.lookupTemplateProvider - }; - - return this.organization; - case 'add-credential-type': - this.credentialType.name = 'credentials.add.credentialType'; - this.credentialType.views['credential_type@credentials.add'] = { - templateProvider: this.lookupTemplateProvider - }; - - return this.credentialType; - case 'edit-credential-type': - this.credentialType.name = 'credentials.edit.credentialType'; - this.credentialType.views['credential_type@credentials.edit'] = { - templateProvider: this.lookupTemplateProvider - }; - - return this.credentialType; - - default: - throw new Error(N_(`Legacy state configuration for ${name} does not exist`)); - } - }; -} - -export default LegacyCredentialsService; diff --git a/awx/ui/client/features/index.js b/awx/ui/client/features/index.js deleted file mode 100644 index b816d2c65de5..000000000000 --- a/awx/ui/client/features/index.js +++ /dev/null @@ -1,31 +0,0 @@ -import atLibServices from '~services'; -import atLibComponents from '~components'; -import atLibModels from '~models'; - -import atFeaturesApplications from '~features/applications'; -import atFeaturesCredentials from '~features/credentials'; -import atFeaturesOutput from '~features/output'; -import atFeaturesTemplates from '~features/templates'; -import atFeaturesUsers from '~features/users'; -import atFeaturesJobs from '~features/jobs'; -import atFeaturesPortalMode from '~features/portalMode'; -import atFeaturesProjects from '~features/projects'; - -const MODULE_NAME = 'at.features'; - -angular.module(MODULE_NAME, [ - atLibServices, - atLibComponents, - atLibModels, - atFeaturesApplications, - atFeaturesCredentials, - atFeaturesTemplates, - atFeaturesUsers, - atFeaturesJobs, - atFeaturesOutput, - atFeaturesTemplates, - atFeaturesPortalMode, - atFeaturesProjects -]); - -export default MODULE_NAME; diff --git a/awx/ui/client/features/jobs/index.controller.js b/awx/ui/client/features/jobs/index.controller.js deleted file mode 100644 index f0ab4e231542..000000000000 --- a/awx/ui/client/features/jobs/index.controller.js +++ /dev/null @@ -1,19 +0,0 @@ -function IndexJobsController ($scope, strings, dataset) { - const vm = this; - vm.strings = strings; - vm.count = dataset.data.count; - - $scope.$on('updateCount', (e, count) => { - if (typeof count === 'number') { - vm.count = count; - } - }); -} - -IndexJobsController.$inject = [ - '$scope', - 'JobsStrings', - 'Dataset' -]; - -export default IndexJobsController; diff --git a/awx/ui/client/features/jobs/index.js b/awx/ui/client/features/jobs/index.js deleted file mode 100644 index 99d91515d5b6..000000000000 --- a/awx/ui/client/features/jobs/index.js +++ /dev/null @@ -1,18 +0,0 @@ -import JobsStrings from './jobs.strings'; -import jobsRoute from './routes/jobs.route'; -import { jobsSchedulesRoute, jobsSchedulesEditRoute } from '../../src/scheduler/schedules.route'; -import jobsListController from './jobsList.controller'; - -const MODULE_NAME = 'at.features.jobs'; - -angular - .module(MODULE_NAME, []) - .service('JobsStrings', JobsStrings) - .controller('jobsListController', jobsListController) - .run(['$stateExtender', ($stateExtender) => { - $stateExtender.addState(jobsRoute); - $stateExtender.addState(jobsSchedulesRoute); - $stateExtender.addState(jobsSchedulesEditRoute); - }]); - -export default MODULE_NAME; diff --git a/awx/ui/client/features/jobs/index.view.html b/awx/ui/client/features/jobs/index.view.html deleted file mode 100644 index a1fa58df9e02..000000000000 --- a/awx/ui/client/features/jobs/index.view.html +++ /dev/null @@ -1,13 +0,0 @@ -
-
- -
-
- - -
-
-
-
-
-
diff --git a/awx/ui/client/features/jobs/jobs.strings.js b/awx/ui/client/features/jobs/jobs.strings.js deleted file mode 100644 index dbdfa0c69c50..000000000000 --- a/awx/ui/client/features/jobs/jobs.strings.js +++ /dev/null @@ -1,35 +0,0 @@ -function JobsStrings (BaseString) { - BaseString.call(this, 'jobs'); - - const { t } = this; - const ns = this.jobs; - - ns.list = { - PANEL_TITLE: t.s('JOBS'), - ROW_ITEM_LABEL_STARTED: t.s('Started'), - ROW_ITEM_LABEL_FINISHED: t.s('Finished'), - ROW_ITEM_LABEL_WORKFLOW_JOB: t.s('Workflow Job'), - ROW_ITEM_LABEL_LAUNCHED_BY: t.s('Launched By'), - ROW_ITEM_LABEL_JOB_TEMPLATE: t.s('Job Template'), - ROW_ITEM_LABEL_INVENTORY: t.s('Inventory'), - ROW_ITEM_LABEL_PROJECT: t.s('Project'), - ROW_ITEM_LABEL_CREDENTIALS: t.s('Credentials'), - ROW_ITEM_LABEL_WEBHOOK: t.s('Webhook'), - NO_RUNNING: t.s('There are no running jobs.'), - JOB: t.s('Job'), - STATUS_TOOLTIP: status => t.s('Job {{status}}. Click for details.', { status }), - SLICE_JOB: t.s('Slice Job'), - NEW: t.s('new'), - PENDING: t.s('pending'), - WAITING: t.s('waiting'), - RUNNING: t.s('running'), - SUCCESSFUL: t.s('successful'), - FAILED: t.s('failed'), - ERROR: t.s('error'), - CANCELED: t.s('canceled') - }; -} - -JobsStrings.$inject = ['BaseStringService']; - -export default JobsStrings; diff --git a/awx/ui/client/features/jobs/jobsList.controller.js b/awx/ui/client/features/jobs/jobsList.controller.js deleted file mode 100644 index 2a7225de6ae5..000000000000 --- a/awx/ui/client/features/jobs/jobsList.controller.js +++ /dev/null @@ -1,336 +0,0 @@ -/** *********************************************** - * Copyright (c) 2018 Ansible, Inc. - * - * All Rights Reserved - ************************************************ */ -const mapChoices = choices => Object.assign(...choices.map(([k, v]) => ({ [k]: v }))); - -function ListJobsController ( - $scope, - $state, - Dataset, - resolvedModels, - strings, - qs, - Prompt, - $filter, - ProcessErrors, - Wait, - Rest, - SearchBasePath, - $timeout -) { - const vm = this || {}; - const [unifiedJob] = resolvedModels; - - vm.strings = strings; - - // smart-search - const name = 'jobs'; - const iterator = 'job'; - let paginateQuerySet = {}; - - let launchModalOpen = false; - let refreshAfterLaunchClose = false; - let pendingRefresh = false; - let refreshTimerRunning = false; - - vm.searchBasePath = SearchBasePath; - - vm.list = { iterator, name }; - vm.job_dataset = Dataset.data; - vm.jobs = Dataset.data.results; - - $scope.$watch('$state.params', () => { - setToolbarSort(); - }, true); - - const toolbarSortDefault = { - label: `${strings.get('sort.FINISH_TIME_DESCENDING')}`, - value: '-finished' - }; - - function setToolbarSort () { - const orderByValue = _.get($state.params, 'job_search.order_by'); - const sortValue = _.find(vm.toolbarSortOptions, (option) => option.value === orderByValue); - if (sortValue) { - vm.toolbarSortValue = sortValue; - } else { - vm.toolbarSortValue = toolbarSortDefault; - } - } - - vm.toolbarSortOptions = [ - { label: `${strings.get('sort.NAME_ASCENDING')}`, value: 'name' }, - { label: `${strings.get('sort.NAME_DESCENDING')}`, value: '-name' }, - { label: `${strings.get('sort.FINISH_TIME_ASCENDING')}`, value: 'finished' }, - toolbarSortDefault, - { label: `${strings.get('sort.START_TIME_ASCENDING')}`, value: 'started' }, - { label: `${strings.get('sort.START_TIME_DESCENDING')}`, value: '-started' }, - { label: `${strings.get('sort.LAUNCHED_BY_ASCENDING')}`, value: 'created_by__id' }, - { label: `${strings.get('sort.LAUNCHED_BY_DESCENDING')}`, value: '-created_by__id' }, - { label: `${strings.get('sort.PROJECT_ASCENDING')}`, value: 'unified_job_template__project__id' }, - { label: `${strings.get('sort.PROJECT_DESCENDING')}`, value: '-unified_job_template__project__id' } - ]; - - vm.toolbarSortValue = toolbarSortDefault; - - // Temporary hack to retrieve $scope.querySet from the paginate directive. - // Remove this event listener once the page and page_size params - // are represented in the url. - $scope.$on('updateDataset', (event, dataset, queryset) => { - paginateQuerySet = queryset; - vm.jobs = dataset.results; - vm.job_dataset = dataset; - }); - - vm.onToolbarSort = (sort) => { - vm.toolbarSortValue = sort; - - const queryParams = Object.assign( - {}, - $state.params.job_search, - paginateQuerySet, - { order_by: sort.value } - ); - - // Update URL with params - $state.go('.', { - job_search: queryParams - }, { notify: false, location: 'replace' }); - }; - - $scope.$watch('vm.job_dataset.count', () => { - $scope.$emit('updateCount', vm.job_dataset.count, 'jobs'); - }); - - $scope.$on('ws-jobs', () => { - if (!launchModalOpen) { - if (!refreshTimerRunning) { - refreshJobs(); - } else { - pendingRefresh = true; - } - } else { - refreshAfterLaunchClose = true; - } - }); - - $scope.$on('launchModalOpen', (evt, isOpen) => { - evt.stopPropagation(); - if (!isOpen && refreshAfterLaunchClose) { - refreshAfterLaunchClose = false; - refreshJobs(); - } - launchModalOpen = isOpen; - }); - - if ($state.includes('instanceGroups')) { - vm.emptyListReason = strings.get('list.NO_RUNNING'); - } - - vm.isPortalMode = $state.includes('portalMode'); - - vm.jobTypes = mapChoices(unifiedJob.options('actions.GET.type.choices')); - - vm.buildCredentialTags = (credentials) => - credentials.map(credential => { - const icon = `${credential.kind}`; - const link = `/#/credentials/${credential.id}`; - const value = $filter('sanitize')(credential.name); - - return { icon, link, value }; - }); - - vm.getSecondaryTagLabel = (job) => { - if (job.job_slice_number && job.job_slice_count && job.job_slice_count > 1) { - return `${strings.get('list.SLICE_JOB')} ${job.job_slice_number}/${job.job_slice_count}`; - } - if (job.launch_type === 'webhook') { - return strings.get('list.ROW_ITEM_LABEL_WEBHOOK'); - } - return null; - }; - - vm.getTranslatedStatusString = (status) => { - switch (status) { - case 'new': - return strings.get('list.NEW'); - case 'pending': - return strings.get('list.PENDING'); - case 'waiting': - return strings.get('list.WAITING'); - case 'running': - return strings.get('list.RUNNING'); - case 'successful': - return strings.get('list.SUCCESSFUL'); - case 'failed': - return strings.get('list.FAILED'); - case 'error': - return strings.get('list.ERROR'); - case 'canceled': - return strings.get('list.CANCELED'); - default: - return status; - } - }; - - vm.getSref = ({ type, id }) => { - let sref; - - switch (type) { - case 'job': - sref = `output({type: 'playbook', id: ${id}})`; - break; - case 'ad_hoc_command': - sref = `output({type: 'command', id: ${id}})`; - break; - case 'system_job': - sref = `output({type: 'system', id: ${id}})`; - break; - case 'project_update': - sref = `output({type: 'project', id: ${id}})`; - break; - case 'inventory_update': - sref = `output({type: 'inventory', id: ${id}})`; - break; - case 'workflow_job': - sref = `workflowResults({id: ${id}})`; - break; - default: - sref = ''; - break; - } - - return sref; - }; - - vm.deleteJob = (job) => { - const action = () => { - $('#prompt-modal').modal('hide'); - Wait('start'); - Rest.setUrl(job.url); - Rest.destroy() - .then(() => { - let reloadListStateParams = null; - - if (vm.jobs.length === 1 && $state.params.job_search && - _.has($state, 'params.job_search.page') && - $state.params.job_search.page !== '1') { - reloadListStateParams = _.cloneDeep($state.params); - reloadListStateParams.job_search.page = - (parseInt(reloadListStateParams.job_search.page, 10) - 1).toString(); - } - - $state.go('.', reloadListStateParams, { reload: true }); - }) - .catch(({ data, status }) => { - ProcessErrors($scope, data, status, null, { - hdr: strings.get('error.HEADER'), - msg: strings.get('error.CALL', { path: `${job.url}`, status }) - }); - }) - .finally(() => { - Wait('stop'); - }); - }; - - const deleteModalBody = `
${strings.get('deleteResource.CONFIRM', 'job')}
`; - - Prompt({ - hdr: strings.get('deleteResource.HEADER'), - resourceName: $filter('sanitize')(job.name), - body: deleteModalBody, - action, - actionText: strings.get('DELETE'), - }); - }; - - vm.cancelJob = (job) => { - const action = () => { - $('#prompt-modal').modal('hide'); - Wait('start'); - Rest.setUrl(job.related.cancel); - Rest.post() - .then(() => { - let reloadListStateParams = null; - - if (vm.jobs.length === 1 && $state.params.job_search && - !_.isEmpty($state.params.job_search.page) && - $state.params.job_search.page !== '1') { - const page = `${(parseInt(reloadListStateParams - .job_search.page, 10) - 1)}`; - reloadListStateParams = _.cloneDeep($state.params); - reloadListStateParams.job_search.page = page; - } - - $state.go('.', reloadListStateParams, { reload: true }); - }) - .catch(({ data, status }) => { - ProcessErrors($scope, data, status, null, { - hdr: strings.get('error.HEADER'), - msg: strings.get('error.CALL', { path: `${job.url}`, status }) - }); - }) - .finally(() => { - Wait('stop'); - }); - }; - - const deleteModalBody = `
${strings.get('cancelJob.SUBMIT_REQUEST')}
`; - - Prompt({ - hdr: strings.get('cancelJob.HEADER'), - resourceName: $filter('sanitize')(job.name), - body: deleteModalBody, - action, - actionText: strings.get('cancelJob.CANCEL_JOB'), - cancelText: strings.get('cancelJob.RETURN') - }); - }; - - function refreshJobs () { - qs.search(SearchBasePath, $state.params.job_search, { 'X-WS-Session-Quiet': true }) - .then(({ data }) => { - vm.jobs = data.results; - vm.job_dataset = data; - }); - pendingRefresh = false; - refreshTimerRunning = true; - $timeout(() => { - if (pendingRefresh) { - refreshJobs(); - } else { - refreshTimerRunning = false; - } - }, 5000); - } - - vm.isCollapsed = true; - - vm.onCollapse = () => { - vm.isCollapsed = true; - }; - - vm.onExpand = () => { - vm.isCollapsed = false; - }; -} - -ListJobsController.$inject = [ - '$scope', - '$state', - 'Dataset', - 'resolvedModels', - 'JobsStrings', - 'QuerySet', - 'Prompt', - '$filter', - 'ProcessErrors', - 'Wait', - 'Rest', - 'SearchBasePath', - '$timeout' -]; - -export default ListJobsController; diff --git a/awx/ui/client/features/jobs/jobsList.view.html b/awx/ui/client/features/jobs/jobsList.view.html deleted file mode 100644 index 50b25c1aff08..000000000000 --- a/awx/ui/client/features/jobs/jobsList.view.html +++ /dev/null @@ -1,113 +0,0 @@ - -
- - -
- - - - - -
- -
- - -
- - - - - - -
-
-
- - - - - - - - - - - - - - -
-
- - - - - -
-
-
-
- - -
diff --git a/awx/ui/client/features/jobs/routes/hostCompletedJobs.route.js b/awx/ui/client/features/jobs/routes/hostCompletedJobs.route.js deleted file mode 100644 index 6d4034aa2857..000000000000 --- a/awx/ui/client/features/jobs/routes/hostCompletedJobs.route.js +++ /dev/null @@ -1,69 +0,0 @@ -import { N_ } from '../../../src/i18n'; -import jobsListController from '../jobsList.controller'; - -const jobsListTemplate = require('~features/jobs/jobsList.view.html'); - -export default { - url: '/completed_jobs', - params: { - job_search: { - value: { - page_size: '20', - job__hosts: '', - order_by: '-id' - }, - dynamic: true, - squash: '' - } - }, - data: { - socket: { - groups: { - jobs: ['status_changed'] - } - } - }, - ncyBreadcrumb: { - label: N_('COMPLETED JOBS') - }, - views: { - related: { - templateUrl: jobsListTemplate, - controller: jobsListController, - controllerAs: 'vm' - } - }, - resolve: { - resolvedModels: [ - 'UnifiedJobModel', - (UnifiedJob) => { - const models = [ - new UnifiedJob(['options']), - ]; - return Promise.all(models); - }, - ], - Dataset: [ - '$stateParams', - 'Wait', - 'GetBasePath', - 'QuerySet', - ($stateParams, Wait, GetBasePath, qs) => { - const hostId = $stateParams.host_id; - - const searchParam = _.assign($stateParams - .job_search, { job__hosts: hostId }); - - const searchPath = GetBasePath('unified_jobs'); - - Wait('start'); - return qs.search(searchPath, searchParam) - .finally(() => Wait('stop')); - } - ], - SearchBasePath: [ - 'GetBasePath', - (GetBasePath) => GetBasePath('unified_jobs') - ] - } -}; diff --git a/awx/ui/client/features/jobs/routes/instanceGroupJobs.route.js b/awx/ui/client/features/jobs/routes/instanceGroupJobs.route.js deleted file mode 100644 index 91de83b254c4..000000000000 --- a/awx/ui/client/features/jobs/routes/instanceGroupJobs.route.js +++ /dev/null @@ -1,131 +0,0 @@ -import listContainerController from '~src/instance-groups/jobs/instanceGroupsJobsListContainer.controller'; -import { N_ } from '../../../src/i18n'; -import jobsListController from '../jobsList.controller'; - -const jobsListTemplate = require('~features/jobs/jobsList.view.html'); -const listContainerTemplate = require('~src/instance-groups/jobs/instanceGroupsJobsListContainer.partial.html'); - -const instanceGroupJobsRoute = { - name: 'instanceGroups.jobs', - url: '/:instance_group_id/jobs', - ncyBreadcrumb: { - parent: 'instanceGroups.edit', - label: N_('JOBS') - }, - params: { - job_search: { - value: { - page_size: '10', - order_by: '-finished' - }, - dynamic: true - } - }, - views: { - 'instanceGroupsJobsContainer@instanceGroups': { - templateUrl: listContainerTemplate, - controller: listContainerController, - controllerAs: 'vm' - }, - 'jobsList@instanceGroups.jobs': { - templateUrl: jobsListTemplate, - controller: jobsListController, - controllerAs: 'vm' - }, - }, - resolve: { - resolvedModels: [ - 'UnifiedJobModel', - (UnifiedJob) => { - const models = [ - new UnifiedJob(['options']), - ]; - return Promise.all(models); - }, - ], - Dataset: [ - '$stateParams', - 'Wait', - 'GetBasePath', - 'QuerySet', - ($stateParams, Wait, GetBasePath, qs) => { - const groupId = $stateParams.instance_group_id; - - const searchParam = $stateParams.job_search; - - const searchPath = `api/v2/instance_groups/${groupId}/jobs`; - - Wait('start'); - return qs.search(searchPath, searchParam) - .finally(() => Wait('stop')); - } - ], - SearchBasePath: [ - '$stateParams', - ($stateParams) => `api/v2/instance_groups/${$stateParams.instance_group_id}/jobs` - ], - } -}; - -const containerGroupJobsRoute = { - name: 'instanceGroups.containerGroupJobs', - url: '/container_groups/:instance_group_id/jobs', - ncyBreadcrumb: { - parent: 'instanceGroups.editContainerGroup', - label: N_('JOBS') - }, - params: { - job_search: { - value: { - page_size: '10', - order_by: '-finished' - }, - dynamic: true - } - }, - views: { - 'containerGroupJobs@instanceGroups': { - templateUrl: listContainerTemplate, - controller: listContainerController, - controllerAs: 'vm' - }, - 'jobsList@instanceGroups.containerGroupJobs': { - templateUrl: jobsListTemplate, - controller: jobsListController, - controllerAs: 'vm' - }, - }, - resolve: { - resolvedModels: [ - 'UnifiedJobModel', - (UnifiedJob) => { - const models = [ - new UnifiedJob(['options']), - ]; - return Promise.all(models); - }, - ], - Dataset: [ - '$stateParams', - 'Wait', - 'QuerySet', - ($stateParams, Wait, qs) => { - const groupId = $stateParams.instance_group_id; - - const searchParam = $stateParams.job_search; - - const searchPath = `api/v2/instance_groups/${groupId}/jobs`; - - Wait('start'); - return qs.search(searchPath, searchParam) - .finally(() => Wait('stop')); - } - ], - SearchBasePath: [ - '$stateParams', - ($stateParams) => `api/v2/instance_groups/${$stateParams.instance_group_id}/jobs` - ], - } -}; - -export { instanceGroupJobsRoute, containerGroupJobsRoute }; diff --git a/awx/ui/client/features/jobs/routes/instanceJobs.route.js b/awx/ui/client/features/jobs/routes/instanceJobs.route.js deleted file mode 100644 index b88bbb4cd80f..000000000000 --- a/awx/ui/client/features/jobs/routes/instanceJobs.route.js +++ /dev/null @@ -1,68 +0,0 @@ -import listContainerController from '~src/instance-groups/jobs/instanceJobsListContainer.controller'; -import { N_ } from '../../../src/i18n'; -import jobsListController from '../jobsList.controller'; - -const jobsListTemplate = require('~features/jobs/jobsList.view.html'); -const listContainerTemplate = require('~src/instance-groups/jobs/instanceJobsListContainer.partial.html'); - -export default { - name: 'instanceGroups.instanceJobs', - url: '/:instance_group_id/instances/:instance_id/jobs', - ncyBreadcrumb: { - parent: 'instanceGroups.instances', - label: N_('JOBS') - }, - views: { - 'instanceJobsContainer@instanceGroups': { - templateUrl: listContainerTemplate, - controller: listContainerController, - controllerAs: 'vm' - }, - 'jobsList@instanceGroups.instanceJobs': { - templateUrl: jobsListTemplate, - controller: jobsListController, - controllerAs: 'vm' - }, - }, - params: { - job_search: { - value: { - page_size: '10', - order_by: '-finished' - }, - dynamic: true - }, - }, - resolve: { - resolvedModels: [ - 'UnifiedJobModel', - (UnifiedJob) => { - const models = [ - new UnifiedJob(['options']), - ]; - return Promise.all(models); - }, - ], - Dataset: [ - '$stateParams', - 'Wait', - 'GetBasePath', - 'QuerySet', - ($stateParams, Wait, GetBasePath, qs) => { - const instanceId = $stateParams.instance_id; - - const searchParam = $stateParams.job_search; - - const searchPath = `api/v2/instances/${instanceId}/jobs`; - - Wait('start'); - return qs.search(searchPath, searchParam) - .finally(() => Wait('stop')); - } - ], - SearchBasePath: [ - '$stateParams', - ($stateParams) => `api/v2/instances/${$stateParams.instance_id}/jobs` - ] - } -}; diff --git a/awx/ui/client/features/jobs/routes/inventoryCompletedJobs.route.js b/awx/ui/client/features/jobs/routes/inventoryCompletedJobs.route.js deleted file mode 100644 index 531e66390950..000000000000 --- a/awx/ui/client/features/jobs/routes/inventoryCompletedJobs.route.js +++ /dev/null @@ -1,76 +0,0 @@ -import { N_ } from '../../../src/i18n'; -import jobsListController from '../jobsList.controller'; - -const jobsListTemplate = require('~features/jobs/jobsList.view.html'); - -export default { - url: '/completed_jobs', - params: { - job_search: { - value: { - page_size: '20', - or__job__inventory: '', - or__adhoccommand__inventory: '', - or__inventoryupdate__inventory_source__inventory: '', - order_by: '-id' - }, - dynamic: true, - squash: '' - } - }, - data: { - socket: { - groups: { - jobs: ['status_changed'] - } - } - }, - ncyBreadcrumb: { - label: N_('JOBS') - }, - views: { - related: { - templateUrl: jobsListTemplate, - controller: jobsListController, - controllerAs: 'vm' - } - }, - resolve: { - resolvedModels: [ - 'UnifiedJobModel', - (UnifiedJob) => { - const models = [ - new UnifiedJob(['options']), - ]; - return Promise.all(models); - }, - ], - Dataset: [ - '$stateParams', - 'Wait', - 'GetBasePath', - 'QuerySet', - ($stateParams, Wait, GetBasePath, qs) => { - const inventoryId = $stateParams.inventory_id ? - $stateParams.inventory_id : $stateParams.smartinventory_id; - - const searchParam = _.assign($stateParams.job_search, { - or__job__inventory: inventoryId, - or__adhoccommand__inventory: inventoryId, - or__inventoryupdate__inventory_source__inventory: inventoryId, - or__workflowjob__inventory: inventoryId, - }); - - const searchPath = GetBasePath('unified_jobs'); - - Wait('start'); - return qs.search(searchPath, searchParam) - .finally(() => Wait('stop')); - } - ], - SearchBasePath: [ - 'GetBasePath', - (GetBasePath) => GetBasePath('unified_jobs') - ] - } -}; diff --git a/awx/ui/client/features/jobs/routes/jobs.route.js b/awx/ui/client/features/jobs/routes/jobs.route.js deleted file mode 100644 index 427d7d165d1d..000000000000 --- a/awx/ui/client/features/jobs/routes/jobs.route.js +++ /dev/null @@ -1,75 +0,0 @@ -import { N_ } from '../../../src/i18n'; -import indexController from '../index.controller'; - -const indexTemplate = require('~features/jobs/index.view.html'); -const jobsListTemplate = require('~features/jobs/jobsList.view.html'); - -export default { - searchPrefix: 'job', - name: 'jobs', - url: '/jobs', - ncyBreadcrumb: { - label: N_('JOBS') - }, - params: { - job_search: { - value: { - not__launch_type: 'sync', - order_by: '-finished' - }, - dynamic: true, - squash: false - } - }, - data: { - activityStream: true, - activityStreamTarget: 'job', - socket: { - groups: { - jobs: ['status_changed'], - schedules: ['changed'] - } - } - }, - resolve: { - resolvedModels: [ - 'UnifiedJobModel', - (UnifiedJob) => { - const models = [ - new UnifiedJob(['options']), - ]; - return Promise.all(models); - }, - ], - Dataset: [ - '$stateParams', - 'Wait', - 'GetBasePath', - 'QuerySet', - ($stateParams, Wait, GetBasePath, qs) => { - const searchParam = $stateParams.job_search; - const searchPath = GetBasePath('unified_jobs'); - - Wait('start'); - return qs.search(searchPath, searchParam) - .finally(() => Wait('stop')); - } - ], - SearchBasePath: [ - 'GetBasePath', - (GetBasePath) => GetBasePath('unified_jobs') - ] - }, - views: { - '@': { - templateUrl: indexTemplate, - controller: indexController, - controllerAs: 'vm' - }, - 'jobsList@jobs': { - templateUrl: jobsListTemplate, - controller: 'jobsListController', - controllerAs: 'vm' - } - } -}; diff --git a/awx/ui/client/features/jobs/routes/templateCompletedJobs.route.js b/awx/ui/client/features/jobs/routes/templateCompletedJobs.route.js deleted file mode 100644 index e1323714ccaa..000000000000 --- a/awx/ui/client/features/jobs/routes/templateCompletedJobs.route.js +++ /dev/null @@ -1,71 +0,0 @@ -import { N_ } from '../../../src/i18n'; -import jobsListController from '../jobsList.controller'; - -const jobsListTemplate = require('~features/jobs/jobsList.view.html'); - -export default { - url: '/completed_jobs', - name: 'templates.editJobTemplate.completed_jobs', - params: { - job_search: { - value: { - page_size: '20', - job__job_template: '', - order_by: '-id' - }, - dynamic: true, - squash: '' - } - }, - data: { - socket: { - groups: { - jobs: ['status_changed'] - } - } - }, - ncyBreadcrumb: { - label: N_('COMPLETED JOBS') - }, - views: { - related: { - templateUrl: jobsListTemplate, - controller: jobsListController, - controllerAs: 'vm' - } - }, - resolve: { - resolvedModels: [ - 'UnifiedJobModel', - (UnifiedJob) => { - const models = [ - new UnifiedJob(['options']), - ]; - return Promise.all(models); - }, - ], - Dataset: [ - '$stateParams', - 'Wait', - 'GetBasePath', - 'QuerySet', - ($stateParams, Wait, GetBasePath, qs) => { - const templateId = $stateParams.job_template_id ? - $stateParams.job_template_id : $stateParams.job_template_id; - - const searchParam = _.assign($stateParams - .job_search, { job__job_template: templateId }); - - const searchPath = GetBasePath('unified_jobs'); - - Wait('start'); - return qs.search(searchPath, searchParam) - .finally(() => Wait('stop')); - } - ], - SearchBasePath: [ - 'GetBasePath', - (GetBasePath) => GetBasePath('unified_jobs') - ] - } -}; diff --git a/awx/ui/client/features/jobs/routes/workflowJobTemplateCompletedJobs.route.js b/awx/ui/client/features/jobs/routes/workflowJobTemplateCompletedJobs.route.js deleted file mode 100644 index 258cf325d48b..000000000000 --- a/awx/ui/client/features/jobs/routes/workflowJobTemplateCompletedJobs.route.js +++ /dev/null @@ -1,70 +0,0 @@ -import { N_ } from '../../../src/i18n'; -import jobsListController from '../jobsList.controller'; - -const jobsListTemplate = require('~features/jobs/jobsList.view.html'); - -export default { - url: '/completed_jobs', - name: 'templates.editWorkflowJobTemplate.completed_jobs', - params: { - job_search: { - value: { - page_size: '20', - workflow_job__workflow_job_template: '', - order_by: '-id' - }, - dynamic: true, - squash: '' - } - }, - data: { - socket: { - groups: { - jobs: ['status_changed'] - } - } - }, - ncyBreadcrumb: { - label: N_('COMPLETED JOBS') - }, - views: { - related: { - templateUrl: jobsListTemplate, - controller: jobsListController, - controllerAs: 'vm' - } - }, - resolve: { - resolvedModels: [ - 'UnifiedJobModel', - (UnifiedJob) => { - const models = [ - new UnifiedJob(['options']), - ]; - return Promise.all(models); - }, - ], - Dataset: [ - '$stateParams', - 'Wait', - 'GetBasePath', - 'QuerySet', - ($stateParams, Wait, GetBasePath, qs) => { - const templateId = $stateParams.workflow_job_template_id; - - const searchParam = _.assign($stateParams - .job_search, { workflow_job__workflow_job_template: templateId }); - - const searchPath = GetBasePath('unified_jobs'); - - Wait('start'); - return qs.search(searchPath, searchParam) - .finally(() => Wait('stop')); - } - ], - SearchBasePath: [ - 'GetBasePath', - (GetBasePath) => GetBasePath('unified_jobs') - ] - } -}; diff --git a/awx/ui/client/features/output/_index.less b/awx/ui/client/features/output/_index.less deleted file mode 100644 index a4bef8756982..000000000000 --- a/awx/ui/client/features/output/_index.less +++ /dev/null @@ -1,474 +0,0 @@ -@import 'host-event/_index'; -.at-Stdout { - &-menuTop { - color: @at-gray-646972; - border: 1px solid @at-gray-b7; - border-top-left-radius: 4px; - border-top-right-radius: 4px; - border-bottom: none; - margin-top: 15px; - - & > div { - user-select: none; - } - } - - &-menuIconGroup { - & > p { - margin: 0; - } - - & > p:first-child { - font-size: 20px; - margin-right: 8px; - } - - & > p:last-child { - margin-top: 9px; - } - } - - &-menuIcon { - font-size: 12px; - padding: 10px; - cursor: pointer; - - &:hover { - color: @at-blue; - } - } - - &-menuIcon--md { - font-size: 14px; - padding: 10px; - cursor: pointer; - - &:hover { - color: @at-blue; - } - } - - &-menuIcon--lg { - font-size: 22px; - line-height: 12px; - font-weight: bold; - padding: 10px; - cursor: pointer; - - &:hover { - color: @at-blue; - } - } - - &-menuIcon--active { - font-size: 22px; - line-height: 12px; - font-weight: bold; - padding: 10px; - cursor: pointer; - color: @at-blue; - } - - &-row { - display: flex; - - &:hover { - background-color: white; - } - - &:hover div { - background-color: white; - } - - &--hidden { - display: none; - } - } - - &-row--clickable { - cursor: pointer; - } - - &-toggle { - background-color: @at-gray-eb; - color: @at-gray-646972; - display: flex; - flex: 0 0 30px; - font-size: 18px; - justify-content: center; - line-height: 12px; - - & > i { - cursor: pointer; - } - - user-select: none; - } - - &-line { - color: @at-gray-161b1f; - background-color: @at-gray-eb; - flex: 0 0 45px; - text-align: right; - vertical-align: top; - padding-right: 5px; - border-right: 1px solid @at-gray-b7; - user-select: none; - } - - &-line--clickable { - cursor: pointer; - } - - &-event { - .at-mixin-event(); - } - - &-time { - padding-right: 2ch; - font-size: 12px; - text-align: right; - user-select: none; - width: 11ch; - - & > span { - background-color: white; - border-radius: 4px; - padding: 1px 2px; - } - } - - &-wrapper { - display: flex; - flex-flow: column nowrap; - height: 100%; - } - - &-container { - background-color: @at-gray-f2; - border-radius: 0 0 4px 4px; - border: 1px solid @at-gray-b7; - color: @at-gray-161b1f; - display: flex; - flex-direction: column; - flex: 1; - font-family: monospace; - font-size: 15px; - height: 100%; - margin: 0; - overflow-y: scroll; - padding: 0; - - @media screen and (max-width: @breakpoint-md) { - max-height: calc(100vh - 30px); - } - } - - &-borderHeader { - .at-mixin-stdoutBorder(); - height: 10px; - } - - &-borderFooter { - .at-mixin-stdoutBorder(); - flex: 1; - } - - &--fullscreen { - grid-column-start: 1; - grid-column-end: 3; - } - - /* The ng-transclude tag that gets injected as a part of at-Panel was throwing off the height - of the panel after Bootstrap's move to display: flex. This seemed like the most concise fix*/ - ng-transclude { - display: flex; - flex-flow: column nowrap; - height: 100%; - } -} - -.at-mixin-event() { - padding: 0 10px; - white-space: pre-wrap; - word-break: break-all; - word-wrap: break-word; -} - -.at-mixin-stdoutBorder() { - background-color: @at-gray-eb; - border-right: 1px solid @at-gray-b7; - width: 75px; -} - -// Search --------------------------------------------------------------------------------- -@at-jobz-top-search-key: @at-space-2x; -@at-jobz-bottom-search-key: @at-space-3x; - -.jobz-searchKeyPaneContainer { - margin-top: @at-jobz-top-search-key; - margin-bottom: @at-jobz-bottom-search-key; -} - -.jobz-searchKeyPane { - // background-color: @at-gray-f6; - background-color: @login-notice-bg; - color: @login-notice-text; - border-radius: @at-border-radius; - border: 1px solid @at-gray-b7; - // color: @at-gray-646972; - padding: 6px @at-padding-input 6px @at-padding-input; -} - -.jobz-searchClearAllContainer { - .at-mixin-VerticallyCenter(); -} - -.jobz-searchClearAll { - font-size: 10px; -} - -.jobz-Button-searchKey { - .at-mixin-Button(); - - background-color: @at-blue; - border-color: at-color-button-border-default; - color: @at-white; - - &:hover, &:active { - color: @at-white; - background-color: @at-blue-hover; - box-shadow: none; - } - - &:focus { - color: @at-white; - } -} - -.jobz-tagz { - margin-top: @at-space; - display: flex; - width: 100%; - flex-wrap: wrap; - margin-left: -5px; -} - -// Status Bar ----------------------------------------------------------------------------- -.HostStatusBar { - display: flex; - flex: 0 0 auto; - width: 100%; - margin-bottom: 15px; -} - -.HostStatusBar-ok, -.HostStatusBar-changed, -.HostStatusBar-dark, -.HostStatusBar-failures, -.HostStatusBar-skipped, -.HostStatusBar-noData { - height: 15px; - border-top: 5px solid @default-bg; - border-bottom: 5px solid @default-bg; -} - -.HostStatusBar-ok { - background-color: @default-succ; - display: flex; - flex: 0 0 auto; -} - -.HostStatusBar-changed { - background-color: @default-warning; - flex: 0 0 auto; -} - -.HostStatusBar-dark { - background-color: @default-unreachable; - flex: 0 0 auto; -} - -.HostStatusBar-failures { - background-color: @default-err; - flex: 0 0 auto; -} - -.HostStatusBar-skipped { - background-color: @default-link; - flex: 0 0 auto; -} - -.HostStatusBar-noData { - background-color: @default-icon-hov; - flex: 1 0 auto; -} - -.HostStatusBar-tooltipLabel { - text-transform: uppercase; - margin-right: 15px; -} - -.HostStatusBar-tooltipBadge { - border-radius: 5px; - border: 1px solid @default-bg; -} - -.HostStatusBar-tooltipBadge--ok { - background-color: @default-succ; -} - -.HostStatusBar-tooltipBadge--dark { - background-color: @default-unreachable; -} - -.HostStatusBar-tooltipBadge--skipped { - background-color: @default-link; -} - -.HostStatusBar-tooltipBadge--changed { - background-color: @default-warning; -} - -.HostStatusBar-tooltipBadge--failures { - background-color: @default-err; - -} - -.HostStatusBar-tooltip.top { - margin-top: 4px; -} - -// Job Details --------------------------------------------------------------------------------- - -@breakpoint-md: 1200px; - -.JobResults-container { - display: grid; - grid-gap: 20px; - grid-template-columns: minmax(400px, 1fr) minmax(500px, 2fr); - grid-template-rows: minmax(500px, ~"calc(100vh - 130px)"); - - .at-Panel { - min-width: 0; - overflow-y: auto; - } -} - -.JobResults-detailsPanel { - display: flex; - flex-direction: column; -} - -.JobResults-panelHeader { - display: flex; - height: 30px; -} - -.JobResults-panelHeaderText { - color: @default-interface-txt; - flex: 1 0 auto; - font-size: 14px; - font-weight: bold; - margin-right: 10px; - text-transform: uppercase; -} - -.JobResults-panelHeaderButtonActions { - display: flex; -} - -.JobResults-resultRow { - width: 100%; - display: flex; - padding-bottom: 10px; - flex-wrap: wrap; -} - -.JobResults-resultRow div[id$='variables-container'] { - width: 100%; -} - -.JobResults-resultRowLabel { - text-transform: uppercase; - color: @default-interface-txt; - font-size: 12px; - font-weight: normal!important; - width: 30%; - margin-right: 20px; - - @media screen and (max-width: @breakpoint-md) { - flex: 2.5 0 auto; - } -} - -.JobResults-resultRowLabel--fullWidth { - width: 100%; - margin-right: 0px; -} - -.JobResults-resultRowText { - display: flex; - flex-flow: row wrap; - width: ~"calc(70% - 20px)"; - flex: 1 0 auto; - text-transform: none; - word-wrap: break-word; -} - -.JobResults-resultRowText--fullWidth { - width: 100%; -} - -.JobResults-expandArrow { - color: #D7D7D7; - font-size: 14px; - font-weight: bold; - margin-right: 10px; - text-transform: uppercase; - margin-left: 10px; -} - -.JobResults-resultRowText--instanceGroup { - display: flex; -} - -.JobResults-isolatedBadge { - align-items: center; - background-color: @default-list-header-bg; - border-radius: 5px; - color: @default-stdout-txt; - display: flex; - font-size: 10px; - height: 16px; - margin: 3px 0 0 10px; - padding: 0 10px; - text-transform: uppercase; -} - -.JobResults-statusResultIcon { - padding-left: 0px; - padding-right: 10px; -} - -.StandardOut-panelHeader { - flex: initial; -} - -.JobResults-seeMoreLess { - color: #337AB7; - margin: 4px 0px; - text-transform: uppercase; - padding: 2px 0px; - cursor: pointer; - border-radius: 5px; - font-size: 11px; -} - -@media screen and (max-width: @breakpoint-md) { - .JobResults-container { - display: flex; - flex-direction: column; - min-width: 400px; - } -} diff --git a/awx/ui/client/features/output/api.events.service.js b/awx/ui/client/features/output/api.events.service.js deleted file mode 100644 index 9da4f34ba8ae..000000000000 --- a/awx/ui/client/features/output/api.events.service.js +++ /dev/null @@ -1,137 +0,0 @@ -import { - API_MAX_PAGE_SIZE, - OUTPUT_ORDER_BY, - OUTPUT_PAGE_SIZE, -} from './constants'; - -const BASE_PARAMS = { - page_size: OUTPUT_PAGE_SIZE, - order_by: OUTPUT_ORDER_BY, -}; - -const merge = (...objs) => _.merge({}, ...objs); - -function JobEventsApiService ($http, $q) { - this.init = (endpoint, params) => { - this.endpoint = endpoint; - this.params = merge(BASE_PARAMS, params); - - this.state = { count: 0, maxCounter: 0 }; - this.cache = {}; - }; - - this.fetch = () => this.getLast() - .then(results => { - this.cache.last = results; - - return this; - }); - - this.clearCache = () => { - Object.keys(this.cache).forEach(key => { - delete this.cache[key]; - }); - }; - - this.pushMaxCounter = events => { - const maxCounter = Math.max(...events.map(({ counter }) => counter)); - - if (maxCounter > this.state.maxCounter) { - this.state.maxCounter = maxCounter; - } - - return maxCounter; - }; - - this.getFirst = () => { - const params = merge(this.params, { page: 1 }); - - return $http.get(this.endpoint, { params }) - .then(({ data }) => { - const { results, count } = data; - - this.state.count = count; - this.pushMaxCounter(results); - - return results; - }); - }; - - this.getPage = number => { - if (number < 1 || number > this.getLastPageNumber()) { - return $q.resolve([]); - } - - const params = merge(this.params, { page: number }); - - return $http.get(this.endpoint, { params }) - .then(({ data }) => { - const { results, count } = data; - - this.state.count = count; - this.pushMaxCounter(results); - - return results; - }); - }; - - this.getLast = () => { - if (this.cache.last) { - return $q.resolve(this.cache.last); - } - - const params = merge(this.params, { page: 1, order_by: `-${OUTPUT_ORDER_BY}` }); - - return $http.get(this.endpoint, { params }) - .then(({ data }) => { - const { results, count } = data; - - let rotated = results; - - if (count > OUTPUT_PAGE_SIZE) { - rotated = results.splice(count % OUTPUT_PAGE_SIZE); - - if (results.length > 0) { - rotated = results; - } - } - - this.state.count = count; - this.pushMaxCounter(results); - - return rotated; - }); - }; - - this.getRange = range => { - if (!range) { - return $q.resolve([]); - } - - const [low, high] = range; - - if (low > high) { - return $q.resolve([]); - } - - const params = merge(this.params, { counter__gte: [low], counter__lte: [high] }); - - params.page_size = API_MAX_PAGE_SIZE; - - return $http.get(this.endpoint, { params }) - .then(({ data }) => { - const { results } = data; - - this.pushMaxCounter(results); - - return results; - }); - }; - - this.getLastPageNumber = () => Math.ceil(this.state.count / OUTPUT_PAGE_SIZE); - this.getMaxCounter = () => this.state.maxCounter; -} - -JobEventsApiService.$inject = ['$http', '$q']; - -export default JobEventsApiService; diff --git a/awx/ui/client/features/output/constants.js b/awx/ui/client/features/output/constants.js deleted file mode 100644 index 3aa55fe87220..000000000000 --- a/awx/ui/client/features/output/constants.js +++ /dev/null @@ -1,51 +0,0 @@ -export const API_MAX_PAGE_SIZE = 200; -export const API_ROOT = '/api/v2/'; - -export const EVENT_START_TASK = 'playbook_on_task_start'; -export const EVENT_START_PLAY = 'playbook_on_play_start'; -export const EVENT_START_PLAYBOOK = 'playbook_on_start'; -export const EVENT_STATS_PLAY = 'playbook_on_stats'; - -export const HOST_STATUS_KEYS = ['dark', 'failures', 'changed', 'ok', 'skipped']; - -export const JOB_STATUS_COMPLETE = ['successful', 'failed', 'unknown']; -export const JOB_STATUS_INCOMPLETE = ['canceled', 'error']; -export const JOB_STATUS_UNSUCCESSFUL = ['failed'].concat(JOB_STATUS_INCOMPLETE); -export const JOB_STATUS_FINISHED = JOB_STATUS_COMPLETE.concat(JOB_STATUS_INCOMPLETE); - -export const OUTPUT_ANSI_COLORMAP = { - 0: '#000', - 1: '#A00', - 2: '#080', - 3: '#F0AD4E', - 4: '#00A', - 5: '#A0A', - 6: '#0AA', - 7: '#AAA', - 8: '#555', - 9: '#F55', - 10: '#5F5', - 11: '#FF5', - 12: '#55F', - 13: '#F5F', - 14: '#5FF', - 15: '#FFF' -}; -export const OUTPUT_ELEMENT_CONTAINER = '.at-Stdout-container'; -export const OUTPUT_ELEMENT_TBODY = '#atStdoutResultTable'; -export const OUTPUT_ELEMENT_LAST = '#atStdoutMenuLast'; -export const OUTPUT_MAX_BUFFER_LENGTH = 1000; -export const OUTPUT_MAX_LAG = 120; -export const OUTPUT_NO_COUNT_JOB_TYPES = ['ad_hoc_command', 'system_job', 'inventory_update']; -export const OUTPUT_ORDER_BY = 'counter'; -export const OUTPUT_PAGE_CACHE = true; -export const OUTPUT_PAGE_LIMIT = 5; -export const OUTPUT_PAGE_SIZE = 50; -export const OUTPUT_SCROLL_DELAY = 100; -export const OUTPUT_SCROLL_THRESHOLD = 0.1; -export const OUTPUT_SEARCH_DOCLINK = 'https://docs.ansible.com/ansible-tower/latest/html/userguide/search_sort.html'; -export const OUTPUT_SEARCH_FIELDS = ['changed', 'created', 'failed', 'host_name', 'stdout', 'task', 'role', 'playbook', 'play', 'start_line', 'end_line']; -export const OUTPUT_SEARCH_KEY_EXAMPLES = ['host_name:localhost', 'task:set', 'created:>=2000-01-01', 'start_line:>=9000']; -export const OUTPUT_EVENT_LIMIT = OUTPUT_PAGE_LIMIT * OUTPUT_PAGE_SIZE; - -export const WS_PREFIX = 'ws'; diff --git a/awx/ui/client/features/output/details.component.js b/awx/ui/client/features/output/details.component.js deleted file mode 100644 index 8f1c8ef36176..000000000000 --- a/awx/ui/client/features/output/details.component.js +++ /dev/null @@ -1,935 +0,0 @@ -const templateUrl = require('~features/output/details.partial.html'); - -let $http; -let $filter; -let $state; - -let error; -let parse; -let prompt; -let resource; -let strings; -let wait; - -let vm; - -function mapChoices (choices) { - if (!choices) return {}; - return Object.assign(...choices.map(([k, v]) => ({ [k]: v }))); -} - -function getStatusDetails (jobStatus) { - const unmapped = jobStatus || resource.model.get('status'); - - if (!unmapped) { - return null; - } - - const choices = mapChoices(resource.model.options('actions.GET.status.choices')); - const label = strings.get('labels.STATUS'); - - let icon; - let value; - - if (unmapped === 'unknown') { - icon = 'fa icon-job-pending'; - value = strings.get('details.UNKNOWN'); - } else { - icon = `fa icon-job-${unmapped}`; - value = choices[unmapped]; - } - - return { unmapped, label, icon, value }; -} - -function getStartDetails (started) { - const unfiltered = started || resource.model.get('started'); - const label = strings.get('labels.STARTED'); - - let value; - - if (unfiltered) { - value = $filter('longDate')(unfiltered); - } else { - value = strings.get('details.NOT_STARTED'); - } - - return { label, value }; -} - -function getFinishDetails (finished) { - const unfiltered = finished || resource.model.get('finished'); - const label = strings.get('labels.FINISHED'); - - let value; - - if (unfiltered) { - value = $filter('longDate')(unfiltered); - } else { - value = strings.get('details.NOT_FINISHED'); - } - - return { label, value }; -} - -function getModuleArgDetails () { - const value = resource.model.get('module_args'); - const label = strings.get('labels.MODULE_ARGS'); - - if (!value) { - return null; - } - - return { label, value }; -} - -function getJobTypeDetails () { - const unmapped = resource.model.get('job_type'); - - if (!unmapped) { - return null; - } - - const choices = mapChoices(resource.model.options('actions.GET.job_type.choices')); - - const label = strings.get('labels.JOB_TYPE'); - const value = choices[unmapped]; - - return { label, value }; -} - -function getVerbosityDetails () { - const verbosity = resource.model.get('verbosity'); - - if (!verbosity) { - return null; - } - - const choices = mapChoices(resource.model.options('actions.GET.verbosity.choices')); - - const label = strings.get('labels.VERBOSITY'); - const value = choices[verbosity]; - - return { label, value }; -} - -function getEnvironmentDetails (virtualenv) { - const customVirtualenv = virtualenv || resource.model.get('custom_virtualenv'); - - if (!customVirtualenv || customVirtualenv === '') { - return null; - } - - const label = strings.get('labels.ENVIRONMENT'); - const value = $filter('sanitize')(customVirtualenv); - - return { label, value }; -} - -function getSourceWorkflowJobDetails () { - const sourceWorkflowJob = resource.model.get('summary_fields.source_workflow_job'); - - if (!sourceWorkflowJob) { - return null; - } - - const label = strings.get('labels.SOURCE_WORKFLOW_JOB'); - const value = sourceWorkflowJob.name; - const link = `/#/workflows/${sourceWorkflowJob.id}`; - const tooltip = strings.get('tooltips.SOURCE_WORKFLOW_JOB'); - - return { label, value, link, tooltip }; -} - -function getSliceJobDetails () { - const count = resource.model.get('job_slice_count'); - - if (!count) { - return null; - } - - if (count === 1) { - return null; - } - - const number = resource.model.get('job_slice_number'); - - if (!number) { - return null; - } - - const label = strings.get('labels.SLICE_JOB'); - const offset = `${number}/${count}`; - const tooltip = strings.get('tooltips.SLICE_JOB_DETAILS'); - - if (label && offset && tooltip) { - return { label, offset, tooltip }; - } - return null; -} - -function getJobTemplateDetails () { - const jobTemplate = resource.model.get('summary_fields.job_template'); - - if (!jobTemplate) { - return null; - } - - const label = strings.get('labels.JOB_TEMPLATE'); - const link = `/#/templates/job_template/${jobTemplate.id}`; - const value = $filter('sanitize')(jobTemplate.name); - const tooltip = strings.get('tooltips.JOB_TEMPLATE'); - - return { label, link, value, tooltip }; -} - -function getInventorySourceDetails () { - if (!resource.model.has('summary_fields.inventory_source.source')) { - return null; - } - - if (resource.model.get('summary_fields.inventory_source.source') === 'scm') { - // we already show a SOURCE PROJECT item for scm inventory updates, so we - // skip the display of the standard source details item. - return null; - } - - const { source } = resource.model.get('summary_fields.inventory_source'); - const choices = mapChoices(resource.model.options('actions.GET.source.choices')); - - const label = strings.get('labels.SOURCE'); - const value = choices[source]; - - return { label, value }; -} - -function getOverwriteDetails () { - if (!resource.model.has('overwrite')) { - return null; - } - - const label = strings.get('labels.OVERWRITE'); - const value = resource.model.get('overwrite'); - - return { label, value }; -} - -function getOverwriteVarsDetails () { - if (!resource.model.has('overwrite_vars')) { - return null; - } - - const label = strings.get('labels.OVERWRITE_VARS'); - const value = resource.model.get('overwrite_vars'); - - return { label, value }; -} - -function getLicenseErrorDetails () { - if (!resource.model.has('license_error')) { - return null; - } - - const label = strings.get('labels.LICENSE_ERROR'); - const value = resource.model.get('license_error'); - - return { label, value }; -} - -function getHostLimitErrorDetails () { - if (!resource.model.has('org_host_limit_error')) { - return null; - } - - const label = strings.get('labels.HOST_LIMIT_ERROR'); - const tooltip = strings.get('tooltips.HOST_LIMIT'); - const value = resource.model.get('org_host_limit_error'); - - return { tooltip, label, value }; -} - -function getLaunchedByDetails () { - const createdBy = resource.model.get('summary_fields.created_by'); - const jobTemplate = resource.model.get('summary_fields.job_template'); - const workflowJobTemplate = resource.model.get('summary_fields.workflow_job_template'); - const relatedSchedule = resource.model.get('related.schedule'); - const schedule = resource.model.get('summary_fields.schedule'); - const launchType = resource.model.get('launch_type'); - - if (!createdBy && !schedule && !launchType) { - return null; - } - - const label = strings.get('labels.LAUNCHED_BY'); - - let link; - let tooltip; - let value; - - if (launchType === 'webhook' && jobTemplate) { - tooltip = strings.get('tooltips.WEBHOOK_JOB_TEMPLATE'); - link = `/#/templates/job_template/${jobTemplate.id}`; - value = strings.get('details.WEBHOOK'); - } else if (launchType === 'webhook' && workflowJobTemplate) { - tooltip = strings.get('tooltips.WEBHOOK_WORKFLOW_JOB_TEMPLATE'); - link = `/#/templates/workflow_job_template/${workflowJobTemplate.id}`; - value = strings.get('details.WEBHOOK'); - } else if (createdBy) { - tooltip = strings.get('tooltips.USER'); - link = `/#/users/${createdBy.id}`; - value = $filter('sanitize')(createdBy.username); - } else if (relatedSchedule && jobTemplate) { - tooltip = strings.get('tooltips.SCHEDULE'); - link = `/#/templates/job_template/${jobTemplate.id}/schedules/${schedule.id}`; - value = $filter('sanitize')(schedule.name); - } else if (schedule) { - tooltip = null; - link = null; - value = $filter('sanitize')(schedule.name); - } else { - return null; - } - - return { label, link, tooltip, value }; -} - -function getInventoryDetails () { - const inventory = resource.model.get('summary_fields.inventory'); - - if (!inventory) { - return null; - } - - const label = strings.get('labels.INVENTORY'); - const tooltip = strings.get('tooltips.INVENTORY'); - const value = $filter('sanitize')(inventory.name); - - let link; - - if (inventory.kind === 'smart') { - link = `/#/inventories/smart/${inventory.id}`; - } else { - link = `/#/inventories/inventory/${inventory.id}`; - } - - return { label, link, tooltip, value }; -} - -function getProjectDetails () { - const project = resource.model.get('summary_fields.project'); - - if (!project) { - return null; - } - - const label = strings.get('labels.PROJECT'); - const link = `/#/projects/${project.id}`; - const value = project.name; - const tooltip = strings.get('tooltips.PROJECT'); - - return { label, link, value, tooltip }; -} - -function getProjectStatusDetails (projectStatus) { - const project = resource.model.get('summary_fields.project'); - const jobStatus = projectStatus || resource.model.get('summary_fields.project_update.status'); - - if (!project) { - return null; - } - - return jobStatus; -} - -function getProjectUpdateDetails (updateId) { - const project = resource.model.get('summary_fields.project'); - const jobId = updateId || resource.model.get('summary_fields.project_update.id'); - - if (!project) { - return null; - } - - const link = `/#/jobs/project/${jobId}`; - const tooltip = strings.get('tooltips.PROJECT_UPDATE'); - - return { link, tooltip }; -} - -function getSCMBranchDetails (scmBranch) { - const label = strings.get('labels.SCM_BRANCH'); - const value = scmBranch || resource.model.get('scm_branch'); - - if (!value) { - return null; - } - - return { label, value }; -} - -function getSCMRefspecDetails (scmRefspec) { - const label = strings.get('labels.SCM_REFSPEC'); - const value = scmRefspec || resource.model.get('scm_refspec'); - - if (!value) { - return null; - } - - return { label, value }; -} - -function getInventoryScmDetails (updateId, updateStatus) { - const projectId = resource.model.get('summary_fields.source_project.id'); - const projectName = resource.model.get('summary_fields.source_project.name'); - const jobId = updateId || resource.model.get('source_project_update'); - const status = updateStatus || resource.model.get('summary_fields.inventory_source.status'); - - if (!projectId || !projectName) { - return null; - } - - const label = strings.get('labels.INVENTORY_SCM'); - const jobLink = `/#/jobs/project/${jobId}`; - const link = `/#/projects/${projectId}`; - const jobTooltip = strings.get('tooltips.INVENTORY_SCM_JOB'); - const tooltip = strings.get('tooltips.INVENTORY_SCM'); - const value = $filter('sanitize')(projectName); - - let icon; - - if (status === 'unknown') { - icon = 'fa icon-job-pending'; - } else { - icon = `fa icon-job-${status}`; - } - - return { label, link, icon, jobLink, jobTooltip, tooltip, value }; -} - -function getSCMRevisionDetails () { - const label = strings.get('labels.SCM_REVISION'); - const value = resource.model.get('scm_revision'); - - if (!value) { - return null; - } - - return { label, value }; -} - -function getPlaybookDetails () { - const label = strings.get('labels.PLAYBOOK'); - const value = resource.model.get('playbook'); - - if (!value) { - return null; - } - - return { label, value }; -} - -function getJobExplanationDetails () { - const explanation = resource.model.get('job_explanation'); - - if (!explanation) { - return null; - } - - const limit = 150; - const label = strings.get('labels.JOB_EXPLANATION'); - - let more = explanation; - - if (explanation.split(':')[0] === 'Previous Task Failed') { - const taskStringIndex = explanation.split(':')[0].length + 1; - const task = JSON.parse(explanation.substring(taskStringIndex)); - - more = `${task.job_type} failed for ${task.job_name} with ID ${task.job_id}`; - } - - const less = $filter('limitTo')(more, limit); - - const showMore = false; - const hasMoreToShow = more.length > limit; - - return { label, less, more, showMore, hasMoreToShow }; -} - -function getResultTracebackDetails (resultTraceback) { - const traceback = resultTraceback || resource.model.get('result_traceback'); - - if (!traceback) { - return null; - } - - const limit = 150; - const label = strings.get('labels.RESULT_TRACEBACK'); - - const more = traceback; - const less = $filter('limitTo')(more, limit); - - const showMore = false; - const hasMoreToShow = more.length > limit; - - return { label, less, more, showMore, hasMoreToShow }; -} - -function getCredentialDetails () { - let credentials = []; - let credentialTags = []; - - if (resource.model.get('type') === 'job') { - credentials = resource.model.get('summary_fields.credentials'); - } else { - const credential = resource.model.get('summary_fields.credential'); - if (credential) { - credentials.push(credential); - } - } - - if (!credentials || credentials.length < 1) { - return null; - } - - credentialTags = credentials.map((cred) => buildCredentialDetails(cred)); - - const label = strings.get('labels.CREDENTIAL'); - const value = credentialTags; - - return { label, value }; -} - -function buildCredentialDetails (credential) { - let icon; - if (credential.cloud) { - icon = 'cloud'; - } else { - icon = `${credential.kind}`; - } - - const link = `/#/credentials/${credential.id}`; - const tooltip = strings.get('tooltips.CREDENTIAL'); - const value = $filter('sanitize')(credential.name); - - return { icon, link, tooltip, value }; -} - -function getForkDetails () { - const label = strings.get('labels.FORKS'); - const value = resource.model.get('forks'); - - if (!value) { - return null; - } - - return { label, value }; -} - -function getLimitDetails () { - const label = strings.get('labels.LIMIT'); - const value = resource.model.get('limit'); - - if (!value) { - return null; - } - - return { label, value }; -} - -function getExecutionNodeDetails (node) { - const executionNode = node || resource.model.get('execution_node'); - - if (!executionNode) { - return null; - } - - const label = strings.get('labels.EXECUTION_NODE'); - const value = $filter('sanitize')(executionNode); - - return { label, value }; -} - -function getInstanceGroupDetails () { - const instanceGroup = resource.model.get('summary_fields.instance_group'); - - if (!instanceGroup) { - return null; - } - - const value = $filter('sanitize')(instanceGroup.name); - - let label = strings.get('labels.INSTANCE_GROUP'); - let link = `/#/instance_groups/${instanceGroup.id}`; - if (instanceGroup.is_containerized) { - label = strings.get('labels.CONTAINER_GROUP'); - link = `/#/instance_groups/container_group/${instanceGroup.id}`; - } - - let isolated = null; - if (instanceGroup.controller_id) { - isolated = strings.get('details.ISOLATED'); - } - - return { label, value, isolated, link }; -} - -function getJobTagDetails () { - const tagString = resource.model.get('job_tags'); - - let jobTags; - - if (tagString) { - jobTags = tagString.split(',').filter(tag => tag !== ''); - } else { - jobTags = []; - } - - if (jobTags.length < 1) { - return null; - } - - const label = strings.get('labels.JOB_TAGS'); - const more = false; - - const value = jobTags.map($filter('sanitize')); - - return { label, more, value }; -} - -function getSkipTagDetails () { - const tagString = resource.model.get('skip_tags'); - - let skipTags; - - if (tagString) { - skipTags = tagString.split(',').filter(tag => tag !== ''); - } else { - skipTags = []; - } - - if (skipTags.length < 1) { - return null; - } - - const more = false; - const label = strings.get('labels.SKIP_TAGS'); - const value = skipTags.map($filter('sanitize')); - - return { label, more, value }; -} - -function getExtraVarsDetails () { - const extraVars = resource.model.get('extra_vars'); - - if (!extraVars) { - return null; - } - - const label = strings.get('labels.EXTRA_VARS'); - const tooltip = strings.get('tooltips.EXTRA_VARS'); - const value = parse(extraVars); - const disabled = true; - const name = 'extra_vars'; - - return { label, tooltip, value, disabled, name }; -} - -function getArtifactsDetails (val) { - const artifacts = val || resource.model.get('artifacts'); - if (!artifacts || (Object.entries(artifacts).length === 0 && - artifacts.constructor === Object)) { - return null; - } - - const label = strings.get('labels.ARTIFACTS'); - const tooltip = strings.get('tooltips.ARTIFACTS'); - const value = parse(artifacts); - const disabled = true; - const name = 'artifacts'; - - return { label, tooltip, value, disabled, name }; -} - -function getLabelDetails () { - const jobLabels = _.get(resource.model.get('summary_fields.labels'), 'results', []); - - if (jobLabels.length < 1) { - return null; - } - - const label = strings.get('labels.LABELS'); - const more = false; - const value = jobLabels.map(({ name }) => name).map($filter('sanitize')); - const truncate = true; - const truncateLength = 5; - const hasMoreToShow = jobLabels.length > truncateLength; - - return { label, more, hasMoreToShow, value, truncate, truncateLength }; -} - -function createErrorHandler (path, action) { - return res => { - const hdr = strings.get('error.HEADER'); - const msg = strings.get('error.CALL', { path, action, status: res.status }); - - error(null, res.data, res.status, null, { hdr, msg }); - }; -} - -const ELEMENT_LABELS = '#job-results-labels'; -const ELEMENT_JOB_TAGS = '#job-results-job-tags'; -const ELEMENT_SKIP_TAGS = '#job-results-skip-tags'; -const ELEMENT_PROMPT_MODAL = '#prompt-modal'; -const TAGS_SLIDE_DISTANCE = 200; - -function showLabels () { - this.labels.truncate = !this.labels.truncate; - - const jobLabelsCount = _.get(resource.model.get('summary_fields.labels'), 'count'); - const maxCount = 50; - - if (this.labels.value.length === jobLabelsCount || this.labels.value.length >= maxCount) { - return; - } - - const config = { - params: { - page_size: maxCount - } - }; - - wait('start'); - resource.model.extend('get', 'labels', config) - .then((model) => { - const jobLabels = _.get(model.get('related.labels'), 'results', []); - this.labels.value = jobLabels.map(({ name }) => name).map($filter('sanitize')); - }) - .catch(createErrorHandler('get labels', 'GET')) - .finally(() => wait('stop')); -} - -function toggleLabels () { - if (!this.labels.more) { - $(ELEMENT_LABELS).slideUp(TAGS_SLIDE_DISTANCE); - this.labels.more = true; - } else { - $(ELEMENT_LABELS).slideDown(TAGS_SLIDE_DISTANCE); - this.labels.more = false; - } -} - -function toggleJobTags () { - if (!this.jobTags.more) { - $(ELEMENT_JOB_TAGS).slideUp(TAGS_SLIDE_DISTANCE); - this.jobTags.more = true; - } else { - $(ELEMENT_JOB_TAGS).slideDown(TAGS_SLIDE_DISTANCE); - this.jobTags.more = false; - } -} - -function toggleSkipTags () { - if (!this.skipTags.more) { - $(ELEMENT_SKIP_TAGS).slideUp(TAGS_SLIDE_DISTANCE); - this.skipTags.more = true; - } else { - $(ELEMENT_SKIP_TAGS).slideDown(TAGS_SLIDE_DISTANCE); - this.skipTags.more = false; - } -} - -function cancelJob () { - const actionText = strings.get('cancelJob.CANCEL_JOB'); - const hdr = strings.get('cancelJob.HEADER'); - const warning = strings.get('cancelJob.SUBMIT_REQUEST'); - const cancelText = strings.get('cancelJob.RETURN'); - - const id = resource.model.get('id'); - const name = $filter('sanitize')(resource.model.get('name')); - - const body = `
${warning}
`; - const resourceName = `#${id} ${name}`; - - const method = 'POST'; - const url = `${resource.model.path}${id}/cancel/`; - - const errorHandler = createErrorHandler('cancel job', method); - - const action = () => { - wait('start'); - $http({ method, url }) - .catch(errorHandler) - .finally(() => { - $(ELEMENT_PROMPT_MODAL).modal('hide'); - wait('stop'); - }); - }; - - prompt({ hdr, resourceName, body, actionText, action, cancelText }); -} - -function deleteJob () { - const actionText = strings.get('DELETE'); - const hdr = strings.get('deleteResource.HEADER'); - const warning = strings.get('deleteResource.CONFIRM', 'job'); - - const id = resource.model.get('id'); - const name = $filter('sanitize')(resource.model.get('name')); - - const body = `
${warning}
`; - const resourceName = `#${id} ${name}`; - - const method = 'DELETE'; - const url = `${resource.model.path}${id}/`; - - const errorHandler = createErrorHandler('delete job', method); - - const action = () => { - wait('start'); - $http({ method, url }) - .then(() => $state.go('jobs')) - .catch(errorHandler) - .finally(() => { - $(ELEMENT_PROMPT_MODAL).modal('hide'); - wait('stop'); - }); - }; - - prompt({ hdr, resourceName, body, actionText, action }); -} - -function JobDetailsController ( - _$http_, - _$filter_, - _$state_, - _error_, - _prompt_, - _strings_, - _wait_, - _parse_, - { subscribe }, -) { - vm = this || {}; - - $http = _$http_; - $filter = _$filter_; - $state = _$state_; - error = _error_; - - parse = _parse_; - prompt = _prompt_; - strings = _strings_; - wait = _wait_; - - let unsubscribe; - - vm.$onInit = () => { - resource = this.resource; // eslint-disable-line prefer-destructuring - vm.strings = strings; - - vm.status = getStatusDetails(); - vm.started = getStartDetails(); - vm.finished = getFinishDetails(); - vm.moduleArgs = getModuleArgDetails(); - vm.jobType = getJobTypeDetails(); - vm.jobTemplate = getJobTemplateDetails(); - vm.sourceWorkflowJob = getSourceWorkflowJobDetails(); - vm.sliceJobDetails = getSliceJobDetails(); - vm.inventory = getInventoryDetails(); - vm.project = getProjectDetails(); - vm.projectUpdate = getProjectUpdateDetails(); - vm.projectStatus = getProjectStatusDetails(); - vm.scmBranch = getSCMBranchDetails(); - vm.scmRefspec = getSCMRefspecDetails(); - vm.scmRevision = getSCMRevisionDetails(); - vm.inventoryScm = getInventoryScmDetails(); - vm.playbook = getPlaybookDetails(); - vm.resultTraceback = getResultTracebackDetails(); - vm.launchedBy = getLaunchedByDetails(); - vm.jobExplanation = getJobExplanationDetails(); - vm.verbosity = getVerbosityDetails(); - vm.environment = getEnvironmentDetails(); - vm.credentials = getCredentialDetails(); - vm.forks = getForkDetails(); - vm.limit = getLimitDetails(); - vm.executionNode = getExecutionNodeDetails(); - vm.instanceGroup = getInstanceGroupDetails(); - vm.jobTags = getJobTagDetails(); - vm.skipTags = getSkipTagDetails(); - vm.extraVars = getExtraVarsDetails(); - vm.artifacts = getArtifactsDetails(); - vm.labels = getLabelDetails(); - vm.inventorySource = getInventorySourceDetails(); - vm.overwrite = getOverwriteDetails(); - vm.overwriteVars = getOverwriteVarsDetails(); - vm.licenseError = getLicenseErrorDetails(); - vm.hostLimitError = getHostLimitErrorDetails(); - - // Relaunch and Delete Components - vm.job = angular.copy(_.get(resource.model, 'model.GET', {})); - vm.canDelete = resource.model.get('summary_fields.user_capabilities.delete'); - - vm.cancelJob = cancelJob; - vm.deleteJob = deleteJob; - vm.toggleJobTags = toggleJobTags; - vm.toggleSkipTags = toggleSkipTags; - vm.toggleLabels = toggleLabels; - vm.showLabels = showLabels; - - unsubscribe = subscribe(({ - status, - started, - finished, - scm, - scmBranch, - scmRefspec, - inventoryScm, - scmRevision, - instanceGroup, - environment, - artifacts, - executionNode, - resultTraceback - }) => { - vm.started = getStartDetails(started); - vm.finished = getFinishDetails(finished); - vm.projectUpdate = getProjectUpdateDetails(scm.id); - vm.projectStatus = getProjectStatusDetails(scm.status); - vm.scmBranch = getSCMBranchDetails(scmBranch); - vm.scmRefspec = getSCMRefspecDetails(scmRefspec); - vm.environment = getEnvironmentDetails(environment); - vm.artifacts = getArtifactsDetails(artifacts); - vm.executionNode = getExecutionNodeDetails(executionNode); - vm.inventoryScm = getInventoryScmDetails(inventoryScm.id, inventoryScm.status); - vm.resultTraceback = getResultTracebackDetails(resultTraceback); - vm.scmRevision = getSCMRevisionDetails(scmRevision); - vm.instanceGroup = getInstanceGroupDetails(instanceGroup); - vm.status = getStatusDetails(status); - vm.job.status = status; - }); - }; - - vm.$onDestroy = () => { - unsubscribe(); - }; -} - -JobDetailsController.$inject = [ - '$http', - '$filter', - '$state', - 'ProcessErrors', - 'Prompt', - 'OutputStrings', - 'Wait', - 'ParseVariableString', - 'OutputStatusService', -]; - -export default { - templateUrl, - controller: JobDetailsController, - controllerAs: 'vm', - bindings: { - resource: '<' - }, -}; diff --git a/awx/ui/client/features/output/details.partial.html b/awx/ui/client/features/output/details.partial.html deleted file mode 100644 index a06790688690..000000000000 --- a/awx/ui/client/features/output/details.partial.html +++ /dev/null @@ -1,473 +0,0 @@ - - -
-
{{:: vm.strings.get('details.HEADER')}}
- -
- - - - - - - - -
-
- - - -
- -
- - {{ vm.status.value }} -
-
- - -
- -
- {{ vm.jobExplanation.less }} - ... - - {{:: vm.strings.get('details.SHOW_MORE') }} - -
-
- {{ vm.jobExplanation.more }} - - {{:: vm.strings.get('details.SHOW_LESS') }} - -
-
- - -
- -
- {{ vm.licenseError.value }} -
-
- - -
- -
- {{ vm.hostLimitError.value }} -
-
- - -
- -
- {{ vm.started.value }} -
-
- - -
- -
- {{ vm.finished.value }} -
-
- - -
- -
{{ vm.moduleArgs.value }}
-
- - -
- -
- {{ vm.resultTraceback.less }} - ... - - {{:: vm.strings.get('details.SHOW_MORE') }} - -
-
- {{ vm.resultTraceback.more }} - - {{:: vm.strings.get('details.SHOW_LESS') }} - -
-
- - -
- - -
- - -
- -
{{ vm.jobType.value }}
-
- - -
- -
{{ vm.sliceJobDetails.offset }}
-
- - -
- - -
- {{ vm.launchedBy.value }} -
-
- - -
- - -
- - -
- - -
- - -
- -
{{ vm.scmBranch.value }}
-
- - -
- -
{{ vm.scmRefspec.value }}
-
- - -
- - -
- - -
- - -
- - -
- -
{{ vm.playbook.value }}
-
- - -
- -
- - -
-
- - -
- -
- {{ vm.inventorySource.value }} -
-
- - -
- -
- {{ vm.overwrite.value }} -
-
- - -
- -
- {{ vm.overwriteVars.value }} -
-
- - -
- -
{{ vm.forks.value }}
-
- - -
- -
{{ vm.limit.value }}
-
- - -
- -
{{ vm.verbosity.value }}
-
- - -
- -
{{ vm.environment.value }}
-
- - -
- -
{{ vm.executionNode.value }}
-
- - -
- -
- - {{ vm.instanceGroup.value }} - - - {{ vm.instanceGroup.isolated }} - -
-
- - -
- - -
- - - - - - - - - - -
- -
-
-
-
{{ label }}
-
- - {{:: vm.strings.get('details.SHOW_MORE') }} - -
-
-
-
{{ label }}
-
- - {{:: vm.strings.get('details.SHOW_LESS') }} - -
-
-
- - - - - - diff --git a/awx/ui/client/features/output/host-event/_index.less b/awx/ui/client/features/output/host-event/_index.less deleted file mode 100644 index f93f80a56d29..000000000000 --- a/awx/ui/client/features/output/host-event/_index.less +++ /dev/null @@ -1,193 +0,0 @@ -.noselect { - -webkit-touch-callout: none; /* iOS Safari */ - -webkit-user-select: none; /* Chrome/Safari/Opera */ - -khtml-user-select: none; /* Konqueror */ - -moz-user-select: none; /* Firefox */ - -ms-user-select: none; /* Internet Explorer/Edge */ - user-select: none; /* Non-prefixed version, currently - not supported by any browser */ -} - -@media screen and (min-width: 768px){ - .HostEvent .modal-dialog{ - width: 700px; - } -} -.HostEvent .CodeMirror{ - overflow-x: hidden; - max-height: none!important; -} - -.HostEvent-close:hover{ - color: @btn-txt; - background-color: @btn-bg-hov; -} - -.HostEvent-body{ - margin-bottom: 20px; -} -.HostEvent-tab { - color: @btn-txt; - background-color: @btn-bg; - font-size: 12px; - border: 1px solid @btn-bord; - height: 30px; - border-radius: 5px; - margin-right: 20px; - padding-left: 10px; - padding-right: 10px; - padding-bottom: 5px; - padding-top: 5px; - transition: background-color 0.2s; - text-transform: uppercase; - text-align: center; - white-space: nowrap; - .noselect; -} -.HostEvent-tab:hover { - color: @btn-txt; - background-color: @btn-bg-hov; - cursor: pointer; -} -.HostEvent-tab--selected{ - color: @btn-txt-sel!important; - background-color: @default-icon!important; - border-color: @default-icon!important; -} -.HostEvent-view--container{ - width: 100%; - display: flex; - flex-direction: row; - flex-wrap: nowrap; - justify-content: space-between; -} -.HostEvent .modal-footer{ - border: 0; - margin-top: 0px; - padding-top: 5px; -} -.HostEvent-controls{ - float: right; - button { - margin-left: 10px; - } -} -.HostEvent-status--ok{ - color: @green; -} -.HostEvent-status--unreachable{ - color: @unreachable; -} -.HostEvent-status--changed{ - color: @changed; -} -.HostEvent-status--failed{ - color: @default-err; -} -.HostEvent-status--skipped{ - color: @skipped; -} -.HostEvent-header{ - padding-bottom: 15px; -} -.HostEvent-title{ - color: @default-interface-txt; - font-weight: 600; - margin-bottom: 8px; -} -.HostEvent .modal-body{ - padding: 0px!important; - overflow-y: auto; -} -.HostEvent-nav{ - padding-top: 12px; - padding-bottom: 20px; -} -.HostEvent-field{ - margin-bottom: 8px; - flex: 0 1 12em; -} -.HostEvent-field--label{ - text-transform: uppercase; - flex: 0 1 80px; - max-width: 80px; - min-width: 80px; - font-size: 12px; - word-wrap: break-word; -} -.HostEvent-field{ - .OnePlusTwo-left--detailsRow; -} -.HostEvent-field--content{ - word-wrap: break-word; -} -.HostEvent-field--monospaceContent{ - font-family: monospace; -} -.HostEvent-button:disabled { - pointer-events: all!important; -} - -.HostEvent-stdout{ - height:200px; - width:100% -} - -.HostEvent-stdoutContainer { - height:200px; - overflow-y: scroll; - overflow-x: hidden; - border-radius: 5px; - border: 1px solid #ccc; - font-style: normal; - background-color: @default-no-items-bord; - font-family: Monaco, Menlo, Consolas, "Courier New", monospace; -} - -.HostEvent-numberColumnPreload { - background-color: @default-list-header-bg; - height: 198px; - border-right: 1px solid #ccc; - width: 30px; - position: fixed; -} - -.HostEvent-numberColumn { - background-color: @default-list-header-bg; - border-right: 1px solid #ccc; - border-bottom-left-radius: 5px; - color: #999; - font-family: Monaco, Menlo, Consolas, "Courier New", monospace; - position: fixed; - padding: 4px 3px 0 5px; - text-align: right; - white-space: nowrap; - width: 30px; -} - -.HostEvent-numberColumn--second{ - padding-top:0px; -} - -.HostEvent-stdoutColumn{ - overflow-y: hidden; - overflow-x: auto; - margin-left: 46px; - padding-top: 4px; - white-space: pre-wrap; - word-break: break-all; - word-wrap: break-word; -} - -.HostEvent-noJson{ - align-items: center; - background-color: @default-no-items-bord; - border: 1px solid @default-icon-hov; - border-radius: 5px; - color: @b7grey; - display: flex; - height: 200px; - justify-content: center; - text-transform: uppercase; - width: 100%; -} diff --git a/awx/ui/client/features/output/host-event/host-event-codemirror.partial.html b/awx/ui/client/features/output/host-event/host-event-codemirror.partial.html deleted file mode 100644 index e766c768a52e..000000000000 --- a/awx/ui/client/features/output/host-event/host-event-codemirror.partial.html +++ /dev/null @@ -1,3 +0,0 @@ - -
No JSON data returned by the module
diff --git a/awx/ui/client/features/output/host-event/host-event-modal.partial.html b/awx/ui/client/features/output/host-event/host-event-modal.partial.html deleted file mode 100644 index 3dcc12eb6e1c..000000000000 --- a/awx/ui/client/features/output/host-event/host-event-modal.partial.html +++ /dev/null @@ -1,72 +0,0 @@ - diff --git a/awx/ui/client/features/output/host-event/host-event-stderr.partial.html b/awx/ui/client/features/output/host-event/host-event-stderr.partial.html deleted file mode 100644 index e775d1768199..000000000000 --- a/awx/ui/client/features/output/host-event/host-event-stderr.partial.html +++ /dev/null @@ -1,8 +0,0 @@ -
-
-
-
1
-
-
-
- diff --git a/awx/ui/client/features/output/host-event/host-event-stdout.partial.html b/awx/ui/client/features/output/host-event/host-event-stdout.partial.html deleted file mode 100644 index 96df87b16330..000000000000 --- a/awx/ui/client/features/output/host-event/host-event-stdout.partial.html +++ /dev/null @@ -1,8 +0,0 @@ -
-
-
-
1
-
-
-
- diff --git a/awx/ui/client/features/output/host-event/host-event.controller.js b/awx/ui/client/features/output/host-event/host-event.controller.js deleted file mode 100644 index 87f698db9d84..000000000000 --- a/awx/ui/client/features/output/host-event/host-event.controller.js +++ /dev/null @@ -1,177 +0,0 @@ -function HostEventsController ( - $scope, - $state, - $filter, - HostEventService, - hostEvent, - OutputStrings -) { - $scope.processEventStatus = HostEventService.processEventStatus; - $scope.processResults = processResults; - $scope.isActiveState = isActiveState; - $scope.getActiveHostIndex = getActiveHostIndex; - $scope.closeHostEvent = closeHostEvent; - $scope.strings = OutputStrings; - - const sanitize = $filter('sanitize'); - - function init () { - hostEvent.event_name = hostEvent.event; - $scope.event = _.cloneDeep(hostEvent); - - // grab standard out & standard error if present from the host - // event's 'res' object, for things like Ansible modules. Small - // wrinkle in this implementation is that the stdout/stderr tabs - // should be shown if the `res` object has stdout/stderr keys, even - // if they're a blank string. The presence of these keys is - // potentially significant to a user. - if (_.has(hostEvent.event_data, 'task_action')) { - $scope.module_name = hostEvent.event_data.task_action; - } else if (!_.has(hostEvent.event_data, 'task_action')) { - $scope.module_name = 'No result found'; - } - - if (_.has(hostEvent.event_data, 'res.stdout')) { - if (hostEvent.event_data.res.stdout === '') { - $scope.stdout = ' '; - } else { - $scope.stdout = sanitize(hostEvent.event_data.res.stdout); - } - } - - if (_.has(hostEvent.event_data, 'res.stderr')) { - if (hostEvent.event_data.res.stderr === '') { - $scope.stderr = ' '; - } else { - $scope.stderr = sanitize(hostEvent.event_data.res.stderr); - } - } - - if (_.has(hostEvent.event_data, 'res')) { - $scope.json = hostEvent.event_data.res; - } - - if ($scope.module_name === 'debug' && - _.has(hostEvent.event_data, 'res.result.stdout')) { - $scope.stdout = sanitize(hostEvent.event_data.res.result.stdout); - } - if ($scope.module_name === 'yum' && - _.has(hostEvent.event_data, 'res.results') && - _.isArray(hostEvent.event_data.res.results)) { - const event = hostEvent.event_data.res.results; - $scope.stdout = sanitize(event[0]);// eslint-disable-line prefer-destructuring - } - // instantiate Codemirror - if ($state.current.name === 'output.host-event.json') { - try { - if (_.has(hostEvent.event_data, 'res')) { - initCodeMirror( - 'HostEvent-codemirror', - JSON.stringify($scope.json, null, 4), - { name: 'javascript', json: true } - ); - resize(); - } else { - $scope.no_json = true; - } - } catch (err) { - // element with id HostEvent-codemirror is not the view - // controlled by this instance of HostEventController - } - } else if ($state.current.name === 'output.host-event.stdout') { - try { - resize(); - } catch (err) { - // element with id HostEvent-codemirror is not the view - // controlled by this instance of HostEventController - } - } else if ($state.current.name === 'output.host-event.stderr') { - try { - resize(); - } catch (err) { - // element with id HostEvent-codemirror is not the view - // controlled by this instance of HostEventController - } - } - $('#HostEvent').modal('show'); - $('.modal-content').resizable({ - minHeight: 523, - minWidth: 600 - }); - $('.modal-dialog').draggable({ - cancel: '.HostEvent-view--container' - }); - - function resize () { - if ($state.current.name === 'output.host-event.json') { - const editor = $('.CodeMirror')[0].CodeMirror; - const height = $('.modal-dialog').height() - $('.HostEvent-header').height() - $('.HostEvent-details').height() - $('.HostEvent-nav').height() - $('.HostEvent-controls').height() - 120; - editor.setSize('100%', height); - } else if ($state.current.name === 'output.host-event.stdout' || $state.current.name === 'output.host-event.stderr') { - const height = $('.modal-dialog').height() - $('.HostEvent-header').height() - $('.HostEvent-details').height() - $('.HostEvent-nav').height() - $('.HostEvent-controls').height() - 120; - $('.HostEvent-stdout').width('100%'); - $('.HostEvent-stdout').height(height); - $('.HostEvent-stdoutContainer').height(height); - $('.HostEvent-numberColumnPreload').height(height); - } - } - - $('.modal-dialog').on('resize', resize); - - $('#HostEvent').on('hidden.bs.modal', $scope.closeHostEvent); - } - - function processResults (value) { - if (typeof value === 'object') { - return false; - } - return true; - } - - function initCodeMirror (el, data, mode) { - const container = document.getElementById(el); - const options = {}; - options.lineNumbers = true; - options.mode = mode; - options.readOnly = true; - options.scrollbarStyle = null; - const editor = CodeMirror.fromTextArea(// eslint-disable-line no-undef - container, - options - ); - editor.setSize('100%', 200); - editor.getDoc().setValue(data); - } - - function isActiveState (name) { - return $state.current.name === name; - } - - function getActiveHostIndex () { - function hostResultfilter (obj) { - return obj.id === $scope.event.id; - } - const result = $scope.hostResults.filter(hostResultfilter); - return $scope.hostResults.indexOf(result[0]); - } - - function closeHostEvent () { - // Unbind the listener so it doesn't fire when we close the modal via navigation - $('#HostEvent').off('hidden.bs.modal'); - $('#HostEvent').modal('hide'); - $state.go('output'); - } - $scope.init = init; - $scope.init(); -} - -HostEventsController.$inject = [ - '$scope', - '$state', - '$filter', - 'HostEventService', - 'hostEvent', - 'OutputStrings' -]; - -module.exports = HostEventsController; diff --git a/awx/ui/client/features/output/host-event/host-event.route.js b/awx/ui/client/features/output/host-event/host-event.route.js deleted file mode 100644 index aba9273327a3..000000000000 --- a/awx/ui/client/features/output/host-event/host-event.route.js +++ /dev/null @@ -1,72 +0,0 @@ -const HostEventModalTemplate = require('~features/output/host-event/host-event-modal.partial.html'); -const HostEventCodeMirrorTemplate = require('~features/output/host-event/host-event-codemirror.partial.html'); -const HostEventStdoutTemplate = require('~features/output/host-event/host-event-stdout.partial.html'); -const HostEventStderrTemplate = require('~features/output/host-event/host-event-stderr.partial.html'); - -function exit () { - // close the modal - // using an onExit event to handle cases where the user navs away - // using the url bar / back and not modal "X" - $('#HostEvent').modal('hide'); - // hacky way to handle user browsing away via URL bar - $('.modal-backdrop').remove(); - $('body').removeClass('modal-open'); -} - -function HostEventResolve (HostEventService, $stateParams) { - return HostEventService.getRelatedJobEvents($stateParams.id, $stateParams.type, { - id: $stateParams.eventId - }).then((response) => response.data.results[0]); -} - -HostEventResolve.$inject = [ - 'HostEventService', - '$stateParams', -]; - -const hostEventModal = { - name: 'output.host-event', - url: '/host-event/:eventId', - controller: 'HostEventsController', - templateUrl: HostEventModalTemplate, - abstract: false, - ncyBreadcrumb: { - skip: true - }, - resolve: { - hostEvent: HostEventResolve - }, - onExit: exit -}; - -const hostEventJson = { - name: 'output.host-event.json', - url: '/json', - controller: 'HostEventsController', - templateUrl: HostEventCodeMirrorTemplate, - ncyBreadcrumb: { - skip: true - }, -}; - -const hostEventStdout = { - name: 'output.host-event.stdout', - url: '/stdout', - controller: 'HostEventsController', - templateUrl: HostEventStdoutTemplate, - ncyBreadcrumb: { - skip: true - }, -}; - -const hostEventStderr = { - name: 'output.host-event.stderr', - url: '/stderr', - controller: 'HostEventsController', - templateUrl: HostEventStderrTemplate, - ncyBreadcrumb: { - skip: true - }, -}; - -export { hostEventJson, hostEventModal, hostEventStdout, hostEventStderr }; diff --git a/awx/ui/client/features/output/host-event/host-event.service.js b/awx/ui/client/features/output/host-event/host-event.service.js deleted file mode 100644 index 4709c1055f3d..000000000000 --- a/awx/ui/client/features/output/host-event/host-event.service.js +++ /dev/null @@ -1,92 +0,0 @@ -function HostEventService ( - Rest, - ProcessErrors, - GetBasePath, - $rootScope -) { - this.getUrl = (id, type, params) => { - const queryString = this.stringifyParams(params); - - let baseUrl; - let related; - - if (type === 'playbook') { - baseUrl = GetBasePath('jobs'); - related = 'job_events'; - } - - if (type === 'command') { - baseUrl = GetBasePath('ad_hoc_commands'); - related = 'events'; - } - - if (type === 'project') { - baseUrl = GetBasePath('project_updates'); - related = 'events'; - } - - return `${baseUrl}${id}/${related}/?${queryString}`; - }; - - // GET events related to a job run - // e.g. - // ?event=playbook_on_stats - // ?parent=206&event__startswith=runner&page_size=200&order=host_name,counter - this.getRelatedJobEvents = (id, type, params) => { - const url = this.getUrl(id, type, params); - Rest.setUrl(url); - return Rest.get() - .then(response => response) - .catch(({ data, status }) => { - ProcessErrors($rootScope, data, status, null, { hdr: 'Error!', - msg: `Call to ${url}. GET returned: ${status}` }); - }); - }; - - this.stringifyParams = params => { - function reduceFunction (result, value, key) { - return `${result}${key}=${value}&`; - } - return _.reduce(params, reduceFunction, ''); - }; - - // Generate a helper class for job_event statuses - // the stack for which status to display is - // unreachable > failed > changed > ok - // uses the API's runner events and convenience properties .failed .changed to determine status. - // see: job_event_callback.py for more filters to support - this.processEventStatus = event => { - const obj = {}; - if (event.event === 'runner_on_unreachable') { - obj.class = 'HostEvent-status--unreachable'; - obj.status = 'unreachable'; - } - // equiv to 'runner_on_error' && 'runner on failed' - if (event.failed) { - obj.class = 'HostEvent-status--failed'; - obj.status = 'failed'; - } - if (event.event === 'runner_on_ok' || event.event === 'runner_on_async_ok') { - obj.class = 'HostEvent-status--ok'; - obj.status = 'ok'; - } - // if both 'changed' and 'ok' are true, show 'changed' status - if (event.changed) { - obj.class = 'HostEvent-status--changed'; - obj.status = 'changed'; - } - if (event.event === 'runner_on_skipped') { - obj.class = 'HostEvent-status--skipped'; - obj.status = 'skipped'; - } - return obj; - }; -} - -HostEventService.$inject = [ - 'Rest', - 'ProcessErrors', - 'GetBasePath', - '$rootScope' -]; -export default HostEventService; diff --git a/awx/ui/client/features/output/host-event/index.js b/awx/ui/client/features/output/host-event/index.js deleted file mode 100644 index f00b0b36b4ce..000000000000 --- a/awx/ui/client/features/output/host-event/index.js +++ /dev/null @@ -1,26 +0,0 @@ -import { - hostEventModal, - hostEventJson, - hostEventStdout, - hostEventStderr -} from './host-event.route'; -import controller from './host-event.controller'; -import service from './host-event.service'; - -const MODULE_NAME = 'hostEvents'; - -function hostEventRun ($stateExtender) { - $stateExtender.addState(hostEventModal); - $stateExtender.addState(hostEventJson); - $stateExtender.addState(hostEventStdout); - $stateExtender.addState(hostEventStderr); -} -hostEventRun.$inject = [ - '$stateExtender' -]; - -angular.module(MODULE_NAME, []) - .controller('HostEventsController', controller) - .service('HostEventService', service) - .run(hostEventRun); -export default MODULE_NAME; diff --git a/awx/ui/client/features/output/index.controller.js b/awx/ui/client/features/output/index.controller.js deleted file mode 100644 index 7f171f728a99..000000000000 --- a/awx/ui/client/features/output/index.controller.js +++ /dev/null @@ -1,881 +0,0 @@ -/* eslint camelcase: 0 */ -import { - EVENT_START_PLAY, - EVENT_START_TASK, - OUTPUT_ELEMENT_LAST, - OUTPUT_PAGE_SIZE, -} from './constants'; - -let $q; -let $scope; -let $state; - -let resource; -let render; -let scroll; -let status; -let slide; -let stream; -let page; - -let vm; -const listeners = []; -let lockFrames = false; - -function onFrames (events) { - events = slide.pushFrames(events); - - if (lockFrames) { - return $q.resolve(); - } - - const popCount = events.length - render.getCapacity(); - - if (!vm.isFollowing && canStartFollowing()) { - startFollowing(); - } - - if (!vm.isFollowing && popCount > 0) { - return $q.resolve(); - } - - scroll.pause(); - - if (vm.isFollowing) { - scroll.scrollToBottom(); - } - - return render.popBack(popCount) - .then(() => { - if (vm.isFollowing) { - scroll.scrollToBottom(); - } - - return render.pushFront(events); - }) - .then(() => { - if (vm.isFollowing) { - scroll.scrollToBottom(); - } - - scroll.resume(); - - return $q.resolve(); - }); -} - -// -// Menu Controls (Running) -// - -function firstRange () { - if (scroll.isPaused()) { - return $q.resolve(); - } - - stopFollowing(); - lockFollow = true; - - if (slide.isOnFirstPage()) { - scroll.resetScrollPosition(); - - return $q.resolve(); - } - - scroll.pause(); - lockFrames = true; - - return render.clear() - .then(() => slide.getFirst()) - .then(results => render.pushFront(results)) - .then(() => slide.getNext()) - .then(results => { - const popCount = results.length - render.getCapacity(); - - return render.popBack(popCount) - .then(() => render.pushFront(results)); - }) - .finally(() => { - render.compile(); - scroll.resume(); - lockFollow = false; - }); -} - -function nextRange () { - if (vm.isFollowing) { - scroll.scrollToBottom(); - - return $q.resolve(); - } - - if (scroll.isPaused()) { - return $q.resolve(); - } - - scroll.pause(); - lockFrames = true; - - return slide.getNext() - .then(results => { - const popCount = results.length - render.getCapacity(); - - return render.popBack(popCount) - .then(() => render.pushFront(results)); - }) - .finally(() => { - render.compile(); - scroll.resume(); - lockFrames = false; - - return $q.resolve(); - }); -} - -function previousRange () { - if (scroll.isPaused()) { - return $q.resolve(); - } - - scroll.pause(); - stopFollowing(); - lockFrames = true; - - let initialPosition; - let popHeight; - - return slide.getPrevious() - .then(results => { - const popCount = results.length - render.getCapacity(); - initialPosition = scroll.getScrollPosition(); - - return render.popFront(popCount) - .then(() => { - popHeight = scroll.getScrollHeight(); - - return render.pushBack(results); - }); - }) - .then(() => { - const currentHeight = scroll.getScrollHeight(); - scroll.setScrollPosition(currentHeight - popHeight + initialPosition); - - return $q.resolve(); - }) - .finally(() => { - render.compile(); - scroll.resume(); - lockFrames = false; - - return $q.resolve(); - }); -} - -function lastRange () { - if (scroll.isPaused()) { - return $q.resolve(); - } - - scroll.pause(); - lockFrames = true; - - return render.clear() - .then(() => slide.getLast()) - .then(results => render.pushFront(results)) - .then(() => { - stream.setMissingCounterThreshold(slide.getTailCounter() + 1); - - scroll.scrollToBottom(); - lockFrames = false; - - return $q.resolve(); - }) - .finally(() => { - render.compile(); - scroll.resume(); - - return $q.resolve(); - }); -} - -function menuLastRange () { - if (vm.isFollowing) { - lockFollow = true; - stopFollowing(); - - return $q.resolve(); - } - - lockFollow = false; - - return lastRange() - .then(() => { - startFollowing(); - - return $q.resolve(); - }); -} - -let followOnce; -let lockFollow; -function canStartFollowing () { - if (lockFollow) { - return false; - } - - if (slide.isOnLastPage() && scroll.isBeyondLowerThreshold()) { - followOnce = false; - - return true; - } - - if (followOnce && // one-time activation from top of first page - scroll.isBeyondUpperThreshold()) { - followOnce = false; - - return true; - } - - return false; -} - -function startFollowing () { - if (vm.isFollowing) { - return; - } - - vm.isFollowing = true; - vm.followTooltip = vm.strings.get('tooltips.MENU_FOLLOWING'); -} - -function stopFollowing () { - if (!vm.isFollowing) { - return; - } - - scroll.unlock(); - scroll.unhide(); - - vm.isFollowing = false; - vm.followTooltip = vm.strings.get('tooltips.MENU_LAST'); -} - -// -// Menu Controls (Page Mode) -// - -function firstPage () { - if (scroll.isPaused()) { - return $q.resolve(); - } - - scroll.pause(); - - return render.clear() - .then(() => page.getFirst()) - .then(results => render.pushFront(results)) - .then(() => page.getNext()) - .then(results => { - const popCount = page.trimHead(); - - return render.popBack(popCount) - .then(() => render.pushFront(results)); - }) - .finally(() => { - render.compile(); - scroll.resume(); - - return $q.resolve(); - }); -} - -function lastPage () { - if (scroll.isPaused()) { - return $q.resolve(); - } - - scroll.pause(); - - return render.clear() - .then(() => page.getLast()) - .then(results => render.pushBack(results)) - .then(() => page.getPrevious()) - .then(results => { - const popCount = page.trimTail(); - - return render.popFront(popCount) - .then(() => render.pushBack(results)); - }) - .then(() => { - scroll.scrollToBottom(); - - return $q.resolve(); - }) - .finally(() => { - render.compile(); - scroll.resume(); - - return $q.resolve(); - }); -} - -function nextPage () { - if (scroll.isPaused()) { - return $q.resolve(); - } - - scroll.pause(); - - return page.getNext() - .then(results => { - const popCount = page.trimHead(); - - return render.popBack(popCount) - .then(() => render.pushFront(results)); - }) - .finally(() => { - render.compile(); - scroll.resume(); - }); -} - -function previousPage () { - if (scroll.isPaused()) { - return $q.resolve(); - } - - scroll.pause(); - - let initialPosition; - let popHeight; - - return page.getPrevious() - .then(results => { - const popCount = page.trimTail(); - initialPosition = scroll.getScrollPosition(); - - return render.popFront(popCount) - .then(() => { - popHeight = scroll.getScrollHeight(); - - return render.pushBack(results); - }); - }) - .then(() => { - const currentHeight = scroll.getScrollHeight(); - scroll.setScrollPosition(currentHeight - popHeight + initialPosition); - - return $q.resolve(); - }) - .finally(() => { - render.compile(); - scroll.resume(); - - return $q.resolve(); - }); -} - -// -// Menu Controls -// - -function first () { - if (vm.isProcessingFinished) { - return firstPage(); - } - - return firstRange(); -} - -function last () { - if (vm.isProcessingFinished) { - return lastPage(); - } - - return lastRange() - .then(() => previousRange()); -} - -function next () { - if (vm.isProcessingFinished) { - return nextPage(); - } - - return nextRange(); -} - -function previous () { - if (vm.isProcessingFinished) { - return previousPage(); - } - - return previousRange(); -} - -function menuLast () { - if (vm.isProcessingFinished) { - return lastPage(); - } - - return menuLastRange(); -} - -function down () { - scroll.moveDown(); - - if (scroll.isBeyondLowerThreshold()) { - next(); - } -} - -function up () { - scroll.moveUp(); - - if (scroll.isBeyondUpperThreshold()) { - previous(); - } -} - -function togglePanelExpand () { - vm.isPanelExpanded = !vm.isPanelExpanded; -} - -// -// Line Interaction -// - -const iconCollapsed = 'fa-angle-right'; -const iconExpanded = 'fa-angle-down'; -const iconSelector = '.at-Stdout-toggle > i'; -const lineCollapsed = 'at-Stdout-row--hidden'; - -function toggleCollapseAll () { - if (scroll.isPaused()) return; - - const records = Object.keys(render.records).map(key => render.records[key]); - const plays = records.filter(({ name }) => name === EVENT_START_PLAY); - const tasks = records.filter(({ name }) => name === EVENT_START_TASK); - - const orphanLines = records - .filter(({ level }) => level === 3) - .filter(({ parents }) => !records[parents[0]]); - - const orphanLineParents = orphanLines - .map(({ parents }) => ({ uuid: parents[0] })); - - plays.concat(tasks).forEach(({ uuid }) => { - const icon = $(`#${uuid} ${iconSelector}`); - - if (vm.isMenuCollapsed) { - icon.removeClass(iconCollapsed); - icon.addClass(iconExpanded); - } else { - icon.removeClass(iconExpanded); - icon.addClass(iconCollapsed); - } - }); - - tasks.concat(orphanLineParents).forEach(({ uuid }) => { - const lines = $(`.child-of-${uuid}`); - - if (vm.isMenuCollapsed) { - lines.removeClass(lineCollapsed); - } else { - lines.addClass(lineCollapsed); - } - }); - - vm.isMenuCollapsed = !vm.isMenuCollapsed; - render.setCollapseAll(vm.isMenuCollapsed); -} - -function toggleCollapse (uuid) { - if (scroll.isPaused()) return; - - const record = render.records[uuid]; - - if (record.name === EVENT_START_PLAY) { - togglePlayCollapse(uuid); - } - - if (record.name === EVENT_START_TASK) { - toggleTaskCollapse(uuid); - } -} - -function togglePlayCollapse (uuid) { - const record = render.records[uuid]; - const descendants = record.children || []; - - const icon = $(`#${uuid} ${iconSelector}`); - const lines = $(`.child-of-${uuid}`); - const taskIcons = $(`#${descendants.join(', #')}`).find(iconSelector); - - const isCollapsed = icon.hasClass(iconCollapsed); - - if (isCollapsed) { - icon.removeClass(iconCollapsed); - icon.addClass(iconExpanded); - - taskIcons.removeClass(iconExpanded); - taskIcons.addClass(iconCollapsed); - lines.removeClass(lineCollapsed); - - descendants - .map(item => $(`.child-of-${item}`)) - .forEach(line => line.addClass(lineCollapsed)); - } else { - icon.removeClass(iconExpanded); - icon.addClass(iconCollapsed); - - taskIcons.removeClass(iconExpanded); - taskIcons.addClass(iconCollapsed); - - lines.addClass(lineCollapsed); - - descendants - .map(item => $(`.child-of-${item}`)) - .forEach(line => line.addClass(lineCollapsed)); - } - - descendants - .map(item => render.records[item]) - .filter((descRecord) => descRecord && descRecord.name === EVENT_START_TASK) - .forEach(rec => { render.records[rec.uuid].isCollapsed = true; }); - - render.records[uuid].isCollapsed = !isCollapsed; -} - -function toggleTaskCollapse (uuid) { - const icon = $(`#${uuid} ${iconSelector}`); - const lines = $(`.child-of-${uuid}`); - - const isCollapsed = icon.hasClass(iconCollapsed); - - if (isCollapsed) { - icon.removeClass(iconCollapsed); - icon.addClass(iconExpanded); - lines.removeClass(lineCollapsed); - } else { - icon.removeClass(iconExpanded); - icon.addClass(iconCollapsed); - lines.addClass(lineCollapsed); - } - - render.records[uuid].isCollapsed = !isCollapsed; -} - -function showHostDetails (id, uuid) { - $state.go('output.host-event.json', { eventId: id, taskUuid: uuid }); -} - -function showMissingEvents (uuid) { - const record = render.records[uuid]; - - const min = Math.min(...record.counters); - const max = Math.min(Math.max(...record.counters), min + OUTPUT_PAGE_SIZE); - - const selector = `#${uuid}`; - const clicked = $(selector); - - return resource.events.getRange([min, max]) - .then(results => { - const counters = results.map(({ counter }) => counter); - - for (let i = min; i <= max; i++) { - if (counters.indexOf(i) < 0) { - results = results.filter(({ counter }) => counter < i); - break; - } - } - - let lines = 0; - let untrusted = ''; - - for (let i = 0; i <= results.length - 1; i++) { - const { html, count } = render.transformEvent(results[i]); - - lines += count; - untrusted += html; - - const shifted = render.records[uuid].counters.shift(); - delete render.uuids[shifted]; - } - - const trusted = render.trustHtml(untrusted); - const elements = angular.element(trusted); - - return render - .requestAnimationFrame(() => { - elements.insertBefore(clicked); - - if (render.records[uuid].counters.length === 0) { - clicked.remove(); - delete render.records[uuid]; - } - }) - .then(() => render.compile()) - .then(() => lines); - }); -} - -// -// Event Handling -// - -let streaming; -function stopListening () { - streaming = null; - - listeners.forEach(deregister => deregister()); - listeners.length = 0; -} - -function startListening () { - stopListening(); - - listeners.push($scope.$on(resource.ws.events, (scope, data) => handleJobEvent(data))); - listeners.push($scope.$on(resource.ws.status, (scope, data) => handleStatusEvent(data))); - - if (resource.model.get('type') === 'job') return; - if (resource.model.get('type') === 'project_update') return; - - listeners.push($scope.$on(resource.ws.summary, (scope, data) => handleSummaryEvent(data))); -} - -function handleJobEvent (data) { - streaming = streaming || resource.events - .getRange([Math.max(1, data.counter - 50), data.counter + 50]) - .then(results => { - results.push(data); - - const counters = results.map(({ counter }) => counter); - const min = Math.min(...counters); - const max = Math.max(...counters); - - const missing = []; - for (let i = min; i <= max; i++) { - if (counters.indexOf(i) < 0) { - missing.push(i); - } - } - - if (missing.length > 0) { - const maxMissing = Math.max(...missing); - results = results.filter(({ counter }) => counter > maxMissing); - } - - results.forEach(item => { - stream.pushJobEvent(item); - status.pushJobEvent(item); - }); - - stream.setMissingCounterThreshold(min); - - return $q.resolve(); - }); - - streaming - .then(() => { - stream.pushJobEvent(data); - status.pushJobEvent(data); - }); -} - -function handleStatusEvent (data) { - status.pushStatusEvent(data); -} - -function handleSummaryEvent (data) { - if (resource.model.get('id') !== data.unified_job_id) return; - if (!data.final_counter) return; - - stream.setFinalCounter(data.final_counter); -} - -// -// Search -// - -function reloadState (params) { - params.isPanelExpanded = vm.isPanelExpanded; - - return $state.transitionTo($state.current, params, { inherit: false, location: 'replace' }); -} - -// -// Debug Mode -// - -function clear () { - stopListening(); - render.clear(); - - followOnce = true; - lockFollow = false; - lockFrames = false; - - stream.bufferInit(); - status.init(resource); - slide.init(resource.events, render); - status.subscribe(data => { vm.status = data.status; }); - - startListening(); -} - -function OutputIndexController ( - _$q_, - _$scope_, - _$state_, - _resource_, - _scroll_, - _page_, - _render_, - _status_, - _slide_, - _stream_, - $filter, - strings, - $stateParams, -) { - const { isPanelExpanded, _debug } = $stateParams; - const isProcessingFinished = !_debug && _resource_.model.get('event_processing_finished'); - - $q = _$q_; - $scope = _$scope_; - $state = _$state_; - - resource = _resource_; - scroll = _scroll_; - render = _render_; - status = _status_; - stream = _stream_; - slide = _slide_; - page = _page_; - - vm = this || {}; - - // Panel - vm.title = $filter('sanitize')(resource.model.get('name')); - vm.status = resource.model.get('status'); - vm.strings = strings; - vm.resource = resource; - vm.reloadState = reloadState; - vm.isPanelExpanded = isPanelExpanded; - vm.isProcessingFinished = isProcessingFinished; - vm.togglePanelExpand = togglePanelExpand; - - // Stdout Navigation - vm.menu = { last: menuLast, first, down, up, clear }; - vm.isMenuCollapsed = false; - vm.isFollowing = false; - vm.toggleCollapseAll = toggleCollapseAll; - vm.toggleCollapse = toggleCollapse; - vm.showHostDetails = showHostDetails; - vm.showMissingEvents = showMissingEvents; - vm.toggleLineEnabled = resource.model.get('type') === 'job'; - vm.followTooltip = vm.strings.get('tooltips.MENU_LAST'); - vm.debug = _debug; - - render.requestAnimationFrame(() => { - render.init($scope, { toggles: vm.toggleLineEnabled }); - - status.init(resource); - page.init(resource.events); - slide.init(resource.events, render); - - scroll.init({ - next, - previous, - onThresholdLeave () { - followOnce = false; - lockFollow = false; - stopFollowing(); - - return $q.resolve(); - }, - }); - - let showFollowTip = true; - const rates = []; - stream.init({ - onFrames, - onFrameRate (rate) { - rates.push(rate); - rates.splice(0, rates.length - 5); - - if (rates.every(value => value === 1)) { - scroll.unlock(); - scroll.unhide(); - } - - if (rate > 1 && vm.isFollowing) { - scroll.lock(); - scroll.hide(); - - if (showFollowTip) { - showFollowTip = false; - $(OUTPUT_ELEMENT_LAST).trigger('mouseenter'); - } - } - }, - onStop () { - lockFollow = true; - stopFollowing(); - status.updateStats(); - status.dispatch(); - status.sync(); - scroll.unlock(); - scroll.unhide(); - render.compile(); - } - }); - - if (isProcessingFinished) { - followOnce = false; - lockFollow = true; - lockFrames = true; - stopListening(); - } else { - followOnce = true; - lockFollow = false; - lockFrames = false; - resource.events.clearCache(); - status.subscribe(data => { vm.status = data.status; }); - startListening(); - } - - if (_debug) { - return render.clear(); - } - - return last(); - }); - - $scope.$on('$destroy', () => { - stopListening(); - - render.clear(); - render.el.remove(); - slide.clear(); - stream.bufferInit(); - }); -} - -OutputIndexController.$inject = [ - '$q', - '$scope', - '$state', - 'resource', - 'OutputScrollService', - 'OutputPageService', - 'OutputRenderService', - 'OutputStatusService', - 'OutputSlideService', - 'OutputStreamService', - '$filter', - 'OutputStrings', - '$stateParams', -]; - -module.exports = OutputIndexController; diff --git a/awx/ui/client/features/output/index.js b/awx/ui/client/features/output/index.js deleted file mode 100644 index 0109877d9b5e..000000000000 --- a/awx/ui/client/features/output/index.js +++ /dev/null @@ -1,249 +0,0 @@ -/* eslint camelcase: 0 */ -import atLibModels from '~models'; -import atLibComponents from '~components'; - -import Strings from '~features/output/output.strings'; -import Controller from '~features/output/index.controller'; -import RenderService from '~features/output/render.service'; -import ScrollService from '~features/output/scroll.service'; -import StreamService from '~features/output/stream.service'; -import StatusService from '~features/output/status.service'; -import MessageService from '~features/output/message.service'; -import EventsApiService from '~features/output/api.events.service'; -import PageService from '~features/output/page.service'; -import SlideService from '~features/output/slide.service'; -import LegacyRedirect from '~features/output/legacy.route'; - -import DetailsComponent from '~features/output/details.component'; -import SearchComponent from '~features/output/search.component'; -import StatsComponent from '~features/output/stats.component'; -import HostEvent from './host-event/index'; - -import { - API_ROOT, - OUTPUT_ORDER_BY, - OUTPUT_PAGE_SIZE, - WS_PREFIX, -} from './constants'; - -const MODULE_NAME = 'at.features.output'; -const Template = require('~features/output/index.view.html'); - -function resolveResource ( - $state, - $stateParams, - Job, - ProjectUpdate, - AdHocCommand, - SystemJob, - WorkflowJob, - InventoryUpdate, - qs, - Wait, - Events, -) { - const { id, type, handleErrors, job_event_search } = $stateParams; - const { name, key } = getWebSocketResource(type); - - let Resource; - let related; - - switch (type) { - case 'project': - Resource = ProjectUpdate; - related = `project_updates/${id}/events/`; - break; - case 'playbook': - Resource = Job; - related = `jobs/${id}/job_events/`; - break; - case 'command': - Resource = AdHocCommand; - related = `ad_hoc_commands/${id}/events/`; - break; - case 'system': - Resource = SystemJob; - related = `system_jobs/${id}/events/`; - break; - case 'inventory': - Resource = InventoryUpdate; - related = `inventory_updates/${id}/events/`; - break; - // case 'workflow': - // todo: integrate workflow chart components into this view - // break; - default: - // Redirect - return null; - } - - const params = { - page_size: OUTPUT_PAGE_SIZE, - order_by: OUTPUT_ORDER_BY, - }; - - if (job_event_search) { - const query = qs.encodeQuerysetObject(qs.decodeArr(job_event_search)); - Object.assign(params, query); - } - - Events.init(`${API_ROOT}${related}`, params); - - Wait('start'); - const promise = Promise.all([new Resource(['get', 'options'], [id, id]), Events.fetch()]) - .then(([model, events]) => ({ - id, - type, - model, - events, - ws: { - events: `${WS_PREFIX}-${key}-${id}`, - status: `${WS_PREFIX}-${name}`, - summary: `${WS_PREFIX}-${name}-summary`, - }, - })); - - if (!handleErrors) { - return promise - .finally(() => Wait('stop')); - } - - return promise - .catch(({ data, status }) => { - qs.error(data, status); - - return $state.go($state.current, $state.params, { reload: true }); - }) - .finally(() => Wait('stop')); -} - -function resolveWebSocketConnection ($stateParams, SocketService) { - const { type, id } = $stateParams; - const { name, key } = getWebSocketResource(type); - - const state = { - data: { - socket: { - groups: { - [name]: ['status_changed', 'summary'], - [key]: [] - } - } - } - }; - - SocketService.addStateResolve(state, id); -} - -function getWebSocketResource (type) { - let name; - let key; - - switch (type) { - case 'system': - name = 'jobs'; - key = 'system_job_events'; - break; - case 'project': - name = 'jobs'; - key = 'project_update_events'; - break; - case 'command': - name = 'jobs'; - key = 'ad_hoc_command_events'; - break; - case 'inventory': - name = 'jobs'; - key = 'inventory_update_events'; - break; - case 'playbook': - name = 'jobs'; - key = 'job_events'; - break; - default: - throw new Error('Unsupported WebSocket type'); - } - - return { name, key }; -} - -function JobsRun ($stateRegistry, $filter, strings) { - const parent = 'jobs'; - const ncyBreadcrumb = { parent, label: strings.get('state.BREADCRUMB_DEFAULT') }; - const sanitize = $filter('sanitize'); - - const state = { - url: '/:type/:id?job_event_search?_debug', - name: 'output', - parent, - ncyBreadcrumb, - params: { - handleErrors: true, - isPanelExpanded: false, - }, - data: { - activityStream: false, - }, - views: { - '@': { - templateUrl: Template, - controller: Controller, - controllerAs: 'vm' - }, - }, - resolve: { - webSocketConnection: [ - '$stateParams', - 'SocketService', - resolveWebSocketConnection - ], - resource: [ - '$state', - '$stateParams', - 'JobModel', - 'ProjectUpdateModel', - 'AdHocCommandModel', - 'SystemJobModel', - 'WorkflowJobModel', - 'InventoryUpdateModel', - 'QuerySet', - 'Wait', - 'JobEventsApiService', - resolveResource - ], - breadcrumbLabel: [ - 'resource', - ({ model }) => { - ncyBreadcrumb.label = `${model.get('id')} - ${sanitize(model.get('name'))}`; - } - ], - }, - }; - - $stateRegistry.register(state); -} - -JobsRun.$inject = ['$stateRegistry', '$filter', 'OutputStrings']; - -angular - .module(MODULE_NAME, [ - atLibModels, - atLibComponents, - HostEvent - ]) - .service('OutputStrings', Strings) - .service('OutputScrollService', ScrollService) - .service('OutputRenderService', RenderService) - .service('OutputStreamService', StreamService) - .service('OutputStatusService', StatusService) - .service('OutputMessageService', MessageService) - .service('JobEventsApiService', EventsApiService) - .service('OutputPageService', PageService) - .service('OutputSlideService', SlideService) - .component('atJobSearch', SearchComponent) - .component('atJobStats', StatsComponent) - .component('atJobDetails', DetailsComponent) - .run(JobsRun) - .run(LegacyRedirect); - -export default MODULE_NAME; diff --git a/awx/ui/client/features/output/index.view.html b/awx/ui/client/features/output/index.view.html deleted file mode 100644 index ea6c31c2fca7..000000000000 --- a/awx/ui/client/features/output/index.view.html +++ /dev/null @@ -1,62 +0,0 @@ -
-
- - - - - - -
-
- - {{ vm.title }} -
- - - - -
-
- -
-
- - -
-
- -
-
- -
-
- -
-
- -
-
-
-
-
-
-
-
-
-
-
diff --git a/awx/ui/client/features/output/legacy.route.js b/awx/ui/client/features/output/legacy.route.js deleted file mode 100644 index 838cc5c25dd0..000000000000 --- a/awx/ui/client/features/output/legacy.route.js +++ /dev/null @@ -1,108 +0,0 @@ -function LegacyRedirect ($http, $stateRegistry) { - const destination = 'output'; - const routes = [ - { - name: 'legacyJobResult', - url: '/jobs/:id?job_event_search', - redirectTo: (trans) => { - const { - id, - job_event_search // eslint-disable-line camelcase - } = trans.params(); - - return { state: destination, params: { type: 'playbook', id, job_event_search } }; - } - }, - { - name: 'legacyAdHocJobStdout', - url: '/ad_hoc_commands/:id', - redirectTo: (trans) => { - const { id } = trans.params(); - return { state: destination, params: { type: 'command', id } }; - } - }, - { - name: 'legacyInventorySyncStdout', - url: '/inventory_sync/:id', - redirectTo: (trans) => { - const { id } = trans.params(); - return { state: destination, params: { type: 'inventory', id } }; - } - }, - { - name: 'legacyManagementJobStdout', - url: '/management_jobs/:id', - redirectTo: (trans) => { - const { id } = trans.params(); - return { state: destination, params: { type: 'system', id } }; - } - }, - { - name: 'legacyScmUpdateStdout', - url: '/scm_update/:id', - redirectTo: (trans) => { - const { id } = trans.params(); - return { state: destination, params: { type: 'project', id } }; - } - }, - { - name: 'legacySchedulesList', - url: '/jobs/schedules?schedule_search', - redirectTo: (trans) => { - const { - schedule_search // eslint-disable-line camelcase - } = trans.params(); - return { state: 'schedules', params: { schedule_search } }; - } - }, - { - name: 'legacySchedule', - url: '/jobs/schedules/:schedule_id?schedule_search', - redirectTo: (trans) => { - const { - schedule_id, // eslint-disable-line camelcase - schedule_search // eslint-disable-line camelcase - } = trans.params(); - return { state: 'schedules.edit', params: { schedule_id, schedule_search } }; - } - }, - { - name: 'workflowNodeRedirect', - url: '/workflow_node_results/:id', - redirectTo: (trans) => { - // The workflow job viewer uses this route for playbook job nodes. The provided id - // is used to lookup the corresponding unified job, which is then inspected to - // determine if we need to redirect to a split (workflow) job or a playbook job. - const { id } = trans.params(); - const endpoint = '/api/v2/unified_jobs/'; - - return $http.get(endpoint, { params: { id } }) - .then(({ data }) => { - const { results } = data; - const [obj] = results; - - if (obj) { - if (obj.type === 'workflow_job') { - return { state: 'workflowResults', params: { id } }; - } else if (obj.type === 'job') { - return { state: 'output', params: { type: 'playbook', id } }; - } else if (obj.type === 'inventory_update') { - return { state: 'output', params: { type: 'inventory', id } }; - } else if (obj.type === 'project_update') { - return { state: 'output', params: { type: 'project', id } }; - } - } - - return { state: 'jobs' }; - }) - .catch(() => ({ state: 'dashboard' })); - } - }, - ]; - - routes.forEach(state => $stateRegistry.register(state)); -} - -LegacyRedirect.$inject = ['$http', '$stateRegistry']; - -export default LegacyRedirect; diff --git a/awx/ui/client/features/output/message.service.js b/awx/ui/client/features/output/message.service.js deleted file mode 100644 index 7e15ff302fbd..000000000000 --- a/awx/ui/client/features/output/message.service.js +++ /dev/null @@ -1,41 +0,0 @@ -function MessageService () { - const listeners = {}; - const registry = {}; - - this.subscribe = (key, listener) => { - registry[key] = registry[key] || 0; - - listeners[key] = listeners[key] || {}; - listeners[key][registry[key]] = listener; - - const unsubscribe = this.createCallback(key, registry[key]); - - registry[key]++; - - return unsubscribe; - }; - - this.dispatch = (key, data) => { - if (!listeners[key]) { - return; - } - - const indices = Object.keys(listeners[key]); - - for (let i = 0; i < indices.length; i++) { - listeners[key][indices[i]](data); - } - }; - - this.createCallback = (key, index) => { - const callback = () => { - if (listeners[key]) { - delete listeners[key][index]; - } - }; - - return callback; - }; -} - -export default MessageService; diff --git a/awx/ui/client/features/output/output.strings.js b/awx/ui/client/features/output/output.strings.js deleted file mode 100644 index 47b2e74883b1..000000000000 --- a/awx/ui/client/features/output/output.strings.js +++ /dev/null @@ -1,143 +0,0 @@ -function OutputStrings (BaseString) { - BaseString.call(this, 'output'); - - const { t } = this; - const ns = this.output; - - ns.state = { - BREADCRUMB_DEFAULT: t.s('RESULTS'), - }; - - ns.status = { - RUNNING: t.s('The host status bar will update when the job is complete.'), - UNAVAILABLE: t.s('Host status information for this job is unavailable.'), - }; - - ns.tooltips = { - ARTIFACTS: t.s('Read-only view of artifacts added to the job template'), - CANCEL: t.s('Cancel'), - COLLAPSE_OUTPUT: t.s('Collapse Output'), - DELETE: t.s('Delete'), - DOWNLOAD_OUTPUT: t.s('Download Output'), - CREDENTIAL: t.s('View the Credential'), - EXPAND_OUTPUT: t.s('Expand Output'), - EXTRA_VARS: t.s('Read-only view of extra variables added to the job template'), - HOST_LIMIT: t.s('When this field is true, the job\'s inventory belongs to an organization that has exceeded it\'s limit of hosts as defined by the system administrator.'), - INVENTORY: t.s('View the Inventory'), - INVENTORY_SCM: t.s('View the Project'), - INVENTORY_SCM_JOB: t.s('View Project checkout results'), - JOB_TEMPLATE: t.s('View the Job Template'), - SLICE_JOB_DETAILS: t.s('Job is one of several from a JT that slices on inventory'), - PROJECT: t.s('View the Project'), - PROJECT_UPDATE: t.s('View Project checkout results'), - SCHEDULE: t.s('View the Schedule'), - SOURCE_WORKFLOW_JOB: t.s('View the source Workflow Job'), - USER: t.s('View the User'), - MENU_FIRST: t.s('Go to first page'), - MENU_DOWN: t.s('Get next page'), - MENU_UP: t.s('Get previous page'), - MENU_LAST: t.s('Go to last page of available output'), - MENU_FOLLOWING: t.s('Currently following output as it arrives. Click to unfollow'), - WEBHOOK_JOB_TEMPLATE: t.s('View the webhook configuration on the job template.'), - WEBHOOK_WORKFLOW_JOB_TEMPLATE: t.s('View the webhook configuration on the workflow job template.'), - }; - - ns.details = { - HEADER: t.s('Details'), - ISOLATED: t.s('Isolated'), - NOT_FINISHED: t.s('Not Finished'), - NOT_STARTED: t.s('Not Started'), - SHOW_LESS: t.s('Show Less'), - SHOW_MORE: t.s('Show More'), - UNKNOWN: t.s('Finished'), - WEBHOOK: t.s('Webhook'), - }; - - ns.labels = { - ARTIFACTS: t.s('Artifacts'), - CREDENTIAL: t.s('Credential'), - ENVIRONMENT: t.s('Environment'), - EXECUTION_NODE: t.s('Execution Node'), - EXTRA_VARS: t.s('Extra Variables'), - FINISHED: t.s('Finished'), - FORKS: t.s('Forks'), - HOST_LIMIT_ERROR: t.s('Host Limit Error'), - INSTANCE_GROUP: t.s('Instance Group'), - CONTAINER_GROUP: t.s('Container Group'), - INVENTORY: t.s('Inventory'), - INVENTORY_SCM: t.s('Source Project'), - JOB_EXPLANATION: t.s('Explanation'), - JOB_TAGS: t.s('Job Tags'), - JOB_TEMPLATE: t.s('Job Template'), - SLICE_JOB: t.s('Slice Job'), - JOB_TYPE: t.s('Job Type'), - LABELS: t.s('Labels'), - LAUNCHED_BY: t.s('Launched By'), - LICENSE_ERROR: t.s('License Error'), - LIMIT: t.s('Limit'), - MACHINE_CREDENTIAL: t.s('Machine Credential'), - MODULE_ARGS: t.s('Module Args'), - NAME: t.s('Name'), - OVERWRITE: t.s('Overwrite'), - OVERWRITE_VARS: t.s('Overwrite Vars'), - PLAYBOOK: t.s('Playbook'), - PROJECT: t.s('Project'), - SCM_BRANCH: t.s('Branch'), - SCM_REFSPEC: t.s('Refspec'), - RESULT_TRACEBACK: t.s('Error Details'), - SCM_REVISION: t.s('Revision'), - SKIP_TAGS: t.s('Skip Tags'), - SOURCE: t.s('Source'), - SOURCE_CREDENTIAL: t.s('Source Credential'), - SOURCE_WORKFLOW_JOB: t.s('Source Workflow'), - STARTED: t.s('Started'), - STATUS: t.s('Status'), - VERBOSITY: t.s('Verbosity'), - }; - - ns.search = { - ADDITIONAL_INFORMATION_HEADER: t.s('ADDITIONAL_INFORMATION'), - ADDITIONAL_INFORMATION: t.s('For additional information on advanced search syntax please see the Ansible Tower'), - CLEAR_ALL: t.s('CLEAR ALL'), - DOCUMENTATION: t.s('documentation'), - EXAMPLES: t.s('EXAMPLES'), - FIELDS: t.s('FIELDS'), - KEY: t.s('KEY'), - PLACEHOLDER_DEFAULT: t.s('SEARCH'), - PLACEHOLDER_RUNNING: t.s('JOB IS STILL RUNNING'), - REJECT_DEFAULT: t.s('Failed to update search results.'), - REJECT_INVALID: t.s('Invalid search filter provided.'), - }; - - ns.stats = { - ELAPSED: t.s('Elapsed'), - PLAYS: t.s('Plays'), - TASKS: t.s('Tasks'), - HOSTS: t.s('Hosts') - }; - - ns.stdout = { - BACK_TO_TOP: t.s('Back to Top'), - }; - - ns.host_event_modal = { - CREATED: t.s('CREATED'), - ID: t.s('ID'), - PLAY: t.s('PLAY'), - TASK: t.s('TASK'), - MODULE: t.s('MODULE'), - NO_RESULT_FOUND: t.s('No result found'), - STANDARD_OUT: t.s('Standard Out'), - STANDARD_ERROR: t.s('Standard Error') - }; - - ns.workflow_status = { - SUCCESSFUL: t.s('SUCCESSFUL'), - FAILED: t.s('FAILED'), - NO_JOBS_FINISHED: t.s('NO JOBS FINISHED') - }; -} - -OutputStrings.$inject = ['BaseStringService']; - -export default OutputStrings; diff --git a/awx/ui/client/features/output/page.service.js b/awx/ui/client/features/output/page.service.js deleted file mode 100644 index 1502d39a6350..000000000000 --- a/awx/ui/client/features/output/page.service.js +++ /dev/null @@ -1,156 +0,0 @@ -/* eslint camelcase: 0 */ -import { OUTPUT_PAGE_LIMIT } from './constants'; - -function PageService ($q) { - this.init = ({ getPage, getFirst, getLast, getLastPageNumber }) => { - this.api = { - getPage, - getFirst, - getLast, - getLastPageNumber, - }; - this.pages = {}; - this.state = { head: 0, tail: 0 }; - }; - - this.getNext = () => { - const lastPageNumber = this.api.getLastPageNumber(); - const number = Math.min(this.state.tail + 1, lastPageNumber); - - if (number < 1) { - return $q.resolve([]); - } - - if (number > lastPageNumber) { - return $q.resolve([]); - } - - let promise; - - if (this.pages[number]) { - promise = $q.resolve(this.pages[number]); - } else { - promise = this.api.getPage(number); - } - - return promise - .then(results => { - if (results.length <= 0) { - return $q.resolve([]); - } - - this.state.tail = number; - this.pages[number] = results; - - return $q.resolve(results); - }); - }; - - this.getPrevious = () => { - const number = Math.max(this.state.head - 1, 1); - - if (number < 1) { - return $q.resolve([]); - } - - if (number > this.api.getLastPageNumber()) { - return $q.resolve([]); - } - - let promise; - - if (this.pages[number]) { - promise = $q.resolve(this.pages[number]); - } else { - promise = this.api.getPage(number); - } - - return promise - .then(results => { - if (results.length <= 0) { - return $q.resolve([]); - } - - this.state.head = number; - this.pages[number] = results; - - return $q.resolve(results); - }); - }; - - this.getLast = () => this.api.getLast() - .then(results => { - if (results.length <= 0) { - return $q.resolve([]); - } - - const number = this.api.getLastPageNumber(); - - this.state.head = number; - this.state.tail = number; - this.pages[number] = results; - - return $q.resolve(results); - }); - - this.getFirst = () => this.api.getFirst() - .then(results => { - if (results.length <= 0) { - return $q.resolve([]); - } - - this.state.head = 1; - this.state.tail = 1; - this.pages[1] = results; - - return $q.resolve(results); - }); - - this.trimTail = () => { - const { tail, head } = this.state; - let popCount = 0; - - for (let i = tail; i > head; i--) { - if (!this.isOverCapacity()) { - break; - } - - if (this.pages[i]) { - popCount += this.pages[i].length; - } - - delete this.pages[i]; - - this.state.tail--; - } - - return popCount; - }; - - this.trimHead = () => { - const { head, tail } = this.state; - let popCount = 0; - - for (let i = head; i < tail; i++) { - if (!this.isOverCapacity()) { - break; - } - - if (this.pages[i]) { - popCount += this.pages[i].length; - } - - delete this.pages[i]; - - this.state.head++; - } - - return popCount; - }; - - this.isOverCapacity = () => this.state.tail - this.state.head > OUTPUT_PAGE_LIMIT; -} - -PageService.$inject = ['$q']; - -export default PageService; diff --git a/awx/ui/client/features/output/render.service.js b/awx/ui/client/features/output/render.service.js deleted file mode 100644 index 437dec589a9d..000000000000 --- a/awx/ui/client/features/output/render.service.js +++ /dev/null @@ -1,636 +0,0 @@ -import Ansi from 'ansi-to-html'; -import Entities from 'html-entities'; - -import { - EVENT_START_PLAY, - EVENT_STATS_PLAY, - EVENT_START_TASK, - OUTPUT_ANSI_COLORMAP, - OUTPUT_ELEMENT_TBODY, - OUTPUT_EVENT_LIMIT, -} from './constants'; - -const EVENT_GROUPS = [ - EVENT_START_TASK, - EVENT_START_PLAY, -]; - -const TIME_EVENTS = [ - EVENT_START_TASK, - EVENT_START_PLAY, - EVENT_STATS_PLAY, -]; - -const ansi = new Ansi({ stream: true, colors: OUTPUT_ANSI_COLORMAP }); -const entities = new Entities.AllHtmlEntities(); - -// https://github.com/chalk/ansi-regex -const pattern = [ - '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\\u0007)', - '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))' -].join('|'); - -const re = new RegExp(pattern); -const hasAnsi = input => re.test(input); - -let $scope; - -function JobRenderService ($q, $compile, $sce, $window) { - this.init = (_$scope_, { toggles }) => { - $scope = _$scope_; - this.setScope(); - - this.el = $(OUTPUT_ELEMENT_TBODY); - this.parent = null; - - this.state = { - head: 0, - tail: 0, - collapseAll: false, - toggleMode: toggles, - }; - - this.records = {}; - this.uuids = {}; - }; - - this.setCollapseAll = value => { - this.state.collapseAll = value; - Object.keys(this.records).forEach(key => { - this.records[key].isCollapsed = value; - }); - }; - - this.sortByCounter = (a, b) => { - if (a.counter > b.counter) { - return 1; - } - - if (a.counter < b.counter) { - return -1; - } - - return 0; - }; - - // - // Event Data Transformation / HTML Building - // - - this.appendEventGroup = events => { - let lines = 0; - let html = ''; - - events.sort(this.sortByCounter); - - for (let i = 0; i <= events.length - 1; i++) { - const current = events[i]; - - if (this.state.tail && current.counter !== this.state.tail + 1) { - const missing = this.appendMissingEventGroup(current); - - html += missing.html; - lines += missing.count; - } - - const eventLines = this.transformEvent(current); - - html += eventLines.html; - lines += eventLines.count; - } - - return { html, lines }; - }; - - this.appendMissingEventGroup = event => { - const tailUUID = this.uuids[this.state.tail]; - const tailRecord = this.records[tailUUID]; - - if (!tailRecord) { - return { html: '', count: 0 }; - } - - let uuid; - - if (tailRecord.isMissing) { - uuid = tailUUID; - } else { - uuid = `${event.counter}-${tailUUID}`; - this.records[uuid] = { uuid, counters: [], lineCount: 1, isMissing: true }; - } - - for (let i = this.state.tail + 1; i < event.counter; i++) { - this.records[uuid].counters.push(i); - this.uuids[i] = uuid; - } - - if (tailRecord.isMissing) { - return { html: '', count: 0 }; - } - - if (tailRecord.end === event.start_line) { - return { html: '', count: 0 }; - } - - const html = this.buildRowHTML(this.records[uuid]); - const count = 1; - - return { html, count }; - }; - - this.prependEventGroup = events => { - let lines = 0; - let html = ''; - - events.sort(this.sortByCounter); - - for (let i = events.length - 1; i >= 0; i--) { - const current = events[i]; - - if (this.state.head && current.counter !== this.state.head - 1) { - const missing = this.prependMissingEventGroup(current); - - html = missing.html + html; - lines += missing.count; - } - - const eventLines = this.transformEvent(current); - - html = eventLines.html + html; - lines += eventLines.count; - } - - return { html, lines }; - }; - - this.prependMissingEventGroup = event => { - const headUUID = this.uuids[this.state.head]; - const headRecord = this.records[headUUID]; - - if (!headRecord) { - return { html: '', count: 0 }; - } - - let uuid; - - if (headRecord.isMissing) { - uuid = headUUID; - } else { - uuid = `${headUUID}-${event.counter}`; - this.records[uuid] = { uuid, counters: [], lineCount: 1, isMissing: true }; - } - - for (let i = this.state.head - 1; i > event.counter; i--) { - this.records[uuid].counters.unshift(i); - this.uuids[i] = uuid; - } - - if (headRecord.isMissing) { - return { html: '', count: 0 }; - } - - if (event.end_line === headRecord.start) { - return { html: '', count: 0 }; - } - - const html = this.buildRowHTML(this.records[uuid]); - const count = 1; - - return { html, count }; - }; - - this.transformEvent = event => { - if (!event || event.stdout === null || event.stdout === undefined) { - return { html: '', count: 0 }; - } - - if (event.uuid && this.records[event.uuid] && !this.records[event.uuid]._isIncomplete) { - return { html: '', count: 0 }; - } - - const stdout = this.sanitize(event.stdout); - const lines = stdout.split('\r\n'); - const record = this.createRecord(event, lines); - - if (lines.length === 1 && lines[0] === '') { - // runner_on_start, runner_on_ok, and a few other events have an actual line count - // of 1 (stdout = '') and a claimed line count of 0 (end_line - start_line = 0). - // Since a zero-length string has an actual line count of 1, they'll still get - // rendered as blank lines unless we intercept them and add some special - // handling to remove them. - // - // Although we're not going to render the blank line, the actual line count of - // the zero-length stdout string, which is 1, has already been recorded at this - // point so we must also go back and set the event's recorded line length to 0 - // in order to avoid deleting too many lines when we need to pop or shift a - // page that contains this event off of the view. - this.records[record.uuid].lineCount = 0; - return { html: '', count: 0 }; - } - - let html = ''; - let count = lines.length; - let ln = event.start_line; - - for (let i = 0; i <= lines.length - 1; i++) { - ln++; - - const line = lines[i]; - const isLastLine = i === lines.length - 1; - - let row = this.buildRowHTML(record, ln, line); - - if (record && record.isTruncated && isLastLine) { - row += this.buildRowHTML(record); - count++; - } - - html += row; - } - - if (this.records[event.uuid]) { - this.records[event.uuid].lineCount = count; - } - - return { html, count }; - }; - - this.createRecord = (event, lines) => { - if (!event.counter) { - return null; - } - - if (!this.state.head || event.counter < this.state.head) { - this.state.head = event.counter; - } - - if (!this.state.tail || event.counter > this.state.tail) { - this.state.tail = event.counter; - } - - if (!event.uuid) { - this.uuids[event.counter] = event.counter; - this.records[event.counter] = { counters: [event.counter], lineCount: lines.length }; - - return this.records[event.counter]; - } - - let isClickable = false; - if (typeof event.host === 'number' || event.event_data && event.event_data.res) { - isClickable = true; - } else if (event.type === 'project_update_event' && - event.event !== 'runner_on_skipped' && - event.event_data.host) { - isClickable = true; - } - - const children = (this.records[event.uuid] && this.records[event.uuid].children) - ? this.records[event.uuid].children : []; - - const record = { - isClickable, - id: event.id, - line: event.start_line + 1, - name: event.event, - uuid: event.uuid, - level: event.event_level, - start: event.start_line, - end: event.end_line, - isTruncated: (event.end_line - event.start_line) > lines.length, - lineCount: lines.length, - isCollapsed: this.state.collapseAll, - counters: [event.counter], - children - }; - - if (event.parent_uuid) { - record.parents = this.getParentEvents(event.parent_uuid); - if (this.records[event.parent_uuid]) { - record.isCollapsed = this.records[event.parent_uuid].isCollapsed; - } - } - - if (record.isTruncated) { - record.truncatedAt = event.start_line + lines.length; - } - - if (EVENT_GROUPS.includes(event.event)) { - record.isParent = true; - - if (event.event_level === 1) { - this.parent = event.uuid; - } - - if (event.parent_uuid) { - if (this.records[event.parent_uuid]) { - if (this.records[event.parent_uuid].children) { - if (!this.records[event.parent_uuid].children.includes(event.uuid)) { - this.records[event.parent_uuid].children.push(event.uuid); - } - } else { - this.records[event.parent_uuid].children = [event.uuid]; - } - } else { - this.records[event.parent_uuid] = { - _isIncomplete: true, - children: [event.uuid] - }; - } - } - } - - if (TIME_EVENTS.includes(event.event)) { - record.time = this.getTimestamp(event.created); - record.line++; - } - - this.records[event.uuid] = record; - this.uuids[event.counter] = event.uuid; - - return record; - }; - - this.getParentEvents = (uuid, list) => { - list = list || []; - // always push its parent if exists - list.push(uuid); - // if we can get grandparent in current visible lines, we also push it - if (this.records[uuid] && this.records[uuid].parents) { - list = list.concat(this.records[uuid].parents); - } - - return list; - }; - - this.buildRowHTML = (record, ln, content) => { - let id = ''; - let icon = ''; - let timestamp = ''; - let tdToggle = ''; - let tdEvent = ''; - let classList = ''; - let directives = ''; - - if (record.isMissing) { - return `
-
-
...
`; - } - - content = content || ''; - - if (hasAnsi(content)) { - content = ansi.toHtml(content); - } - - if (record) { - if (this.state.toggleMode && record.isParent && record.line === ln) { - id = record.uuid; - - if (record.isCollapsed) { - icon = 'fa-angle-right'; - } else { - icon = 'fa-angle-down'; - } - - tdToggle = `
`; - } - - if (record.time && record.line === ln) { - timestamp = `${record.time}`; - } - - if (record.parents) { - classList = record.parents.reduce((list, uuid) => `${list} child-of-${uuid}`, ''); - } - } - - if (!tdEvent) { - tdEvent = `
${content}
`; - } - - if (!tdToggle) { - tdToggle = '
'; - } - - if (!ln) { - ln = '...'; - } - - if (record && record.isCollapsed) { - if (record.level === 3 || record.level === 0) { - classList += ' at-Stdout-row--hidden'; - } - } - - if (record && record.isClickable) { - classList += ' at-Stdout-row--clickable'; - directives = `ng-click="vm.showHostDetails('${record.id}', '${record.uuid}')"`; - } - - return ` -
- ${tdToggle} -
${ln}
-
${content}
-
${timestamp}
-
- `; - }; - - this.getTimestamp = created => { - const date = new Date(created); - const hour = date.getHours() < 10 ? `0${date.getHours()}` : date.getHours(); - const minute = date.getMinutes() < 10 ? `0${date.getMinutes()}` : date.getMinutes(); - const second = date.getSeconds() < 10 ? `0${date.getSeconds()}` : date.getSeconds(); - - return `${hour}:${minute}:${second}`; - }; - - // - // Element Operations - // - - this.remove = elements => this.requestAnimationFrame(() => elements.remove()); - - this.requestAnimationFrame = fn => $q(resolve => { - $window.requestAnimationFrame(() => { - if (fn) { - fn(); - } - - return resolve(); - }); - }); - - this.setScope = () => { - if (this.scope) this.scope.$destroy(); - delete this.scope; - - this.scope = $scope.$new(); - }; - - this.compile = () => { - this.setScope(); - $compile(this.el)(this.scope); - - return this.requestAnimationFrame(); - }; - - this.removeAll = () => { - const elements = this.el.contents(); - return this.remove(elements); - }; - - this.shift = lines => { - // We multiply by two here under the assumption that one element and one text node - // is generated for each line of output. - const count = (2 * lines) + 1; - const elements = this.el.contents().slice(0, count); - - return this.remove(elements); - }; - - this.pop = lines => { - // We multiply by two here under the assumption that one element and one text node - // is generated for each line of output. - const count = (2 * lines) + 1; - const elements = this.el.contents().slice(-count); - - return this.remove(elements); - }; - - this.prepend = events => { - if (events.length < 1) { - return $q.resolve(); - } - - const result = this.prependEventGroup(events); - const html = this.trustHtml(result.html); - - return this.requestAnimationFrame(() => this.el.prepend(html)) - .then(() => result.lines); - }; - - this.append = events => { - if (events.length < 1) { - return $q.resolve(); - } - - const result = this.appendEventGroup(events); - const html = this.trustHtml(result.html); - - return this.requestAnimationFrame(() => this.el.append(html)) - .then(() => result.lines); - }; - - this.trustHtml = html => $sce.getTrustedHtml($sce.trustAsHtml(html)); - this.sanitize = html => entities.encode(html); - - // - // Event Counter Methods - External code should prefer these. - // - - this.clear = () => this.removeAll() - .then(() => { - const head = this.getHeadCounter(); - const tail = this.getTailCounter(); - - for (let i = head; i <= tail; ++i) { - const uuid = this.uuids[i]; - - if (uuid) { - delete this.records[uuid]; - delete this.uuids[i]; - } - } - - this.state.head = 0; - this.state.tail = 0; - - return $q.resolve(); - }); - - this.pushFront = events => { - const tail = this.getTailCounter(); - - return this.append(events.filter(({ counter }) => counter > tail)); - }; - - this.pushBack = events => { - const head = this.getHeadCounter(); - const tail = this.getTailCounter(); - - return this.prepend(events.filter(({ counter }) => counter < head || counter > tail)); - }; - - this.popFront = count => { - if (!count || count <= 0) { - return $q.resolve(); - } - - const max = this.state.tail; - const min = max - count + 1; - - let lines = 0; - - for (let i = max; i >= min; --i) { - const uuid = this.uuids[i]; - - if (!uuid) { - continue; - } - - this.records[uuid].counters.pop(); - delete this.uuids[i]; - - if (this.records[uuid].counters.length === 0) { - lines += this.records[uuid].lineCount; - - delete this.records[uuid]; - this.state.tail--; - } - } - - return this.pop(lines); - }; - - this.popBack = count => { - if (!count || count <= 0) { - return $q.resolve(); - } - - const min = this.state.head; - const max = min + count - 1; - - let lines = 0; - - for (let i = min; i <= max; ++i) { - const uuid = this.uuids[i]; - - if (!uuid) { - continue; - } - - this.records[uuid].counters.shift(); - delete this.uuids[i]; - - if (this.records[uuid].counters.length === 0) { - lines += this.records[uuid].lineCount; - - delete this.records[uuid]; - this.state.head++; - } - } - - return this.shift(lines); - }; - - this.getHeadCounter = () => this.state.head; - this.getTailCounter = () => this.state.tail; - this.getCapacity = () => OUTPUT_EVENT_LIMIT - (this.getTailCounter() - this.getHeadCounter()); -} - -JobRenderService.$inject = ['$q', '$compile', '$sce', '$window']; - -export default JobRenderService; diff --git a/awx/ui/client/features/output/scroll.service.js b/awx/ui/client/features/output/scroll.service.js deleted file mode 100644 index b8b744492833..000000000000 --- a/awx/ui/client/features/output/scroll.service.js +++ /dev/null @@ -1,228 +0,0 @@ -import { - OUTPUT_ELEMENT_CONTAINER, - OUTPUT_ELEMENT_TBODY, - OUTPUT_SCROLL_DELAY, - OUTPUT_SCROLL_THRESHOLD, -} from './constants'; - -function JobScrollService ($q, $timeout) { - this.init = ({ next, previous, onThresholdLeave }) => { - this.el = $(OUTPUT_ELEMENT_CONTAINER); - this.chain = $q.resolve(); - this.timer = null; - - this.position = { - previous: 0, - current: 0 - }; - - this.threshold = { - previous: 0, - current: 0, - }; - - this.hooks = { - next, - previous, - onThresholdLeave, - }; - - this.state = { - paused: false, - locked: false, - hover: false, - thrash: 0, - }; - - this.el.scroll(this.listen); - this.el.mouseenter(this.onMouseEnter); - this.el.mouseleave(this.onMouseLeave); - }; - - this.onMouseEnter = () => { - this.state.hover = true; - }; - - this.onMouseLeave = () => { - this.state.hover = false; - }; - - this.listen = () => { - if (this.isPaused()) { - return; - } - - if (this.isLocked()) { - return; - } - - if (!this.state.hover) { - return; - } - - if (this.timer) { - $timeout.cancel(this.timer); - } - - this.timer = $timeout(this.register, OUTPUT_SCROLL_DELAY); - }; - - this.register = () => { - const position = this.getScrollPosition(); - const viewport = this.getScrollHeight() - this.getViewableHeight(); - - const threshold = position / viewport; - const downward = position > this.position.previous; - - const isBeyondUpperThreshold = threshold < OUTPUT_SCROLL_THRESHOLD; - const isBeyondLowerThreshold = (1 - threshold) < OUTPUT_SCROLL_THRESHOLD; - - const wasBeyondUpperThreshold = this.threshold.previous < OUTPUT_SCROLL_THRESHOLD; - const wasBeyondLowerThreshold = (1 - this.threshold.previous) < OUTPUT_SCROLL_THRESHOLD; - - const enteredUpperThreshold = isBeyondUpperThreshold && !wasBeyondUpperThreshold; - const enteredLowerThreshold = isBeyondLowerThreshold && !wasBeyondLowerThreshold; - const leftLowerThreshold = !isBeyondLowerThreshold && wasBeyondLowerThreshold; - - const transitions = []; - - if (position <= 0 || enteredUpperThreshold) { - transitions.push(this.hooks.onThresholdLeave); - transitions.push(this.hooks.previous); - } - - if (leftLowerThreshold) { - transitions.push(this.hooks.onThresholdLeave); - } - - if (threshold >= 1 || enteredLowerThreshold) { - transitions.push(this.hooks.next); - } - - if (!downward) { - transitions.reverse(); - } - - this.position.current = position; - this.threshold.current = threshold; - - transitions.forEach(promise => { - this.chain = this.chain.then(() => promise()); - }); - - return this.chain - .then(() => { - this.setScrollPosition(this.getScrollPosition()); - - return $q.resolve(); - }); - }; - - /** - * Move scroll position up by one page of visible content. - */ - this.moveUp = () => { - const position = this.getScrollPosition() - this.getViewableHeight(); - - this.setScrollPosition(position); - }; - - /** - * Move scroll position down by one page of visible content. - */ - this.moveDown = () => { - const position = this.getScrollPosition() + this.getViewableHeight(); - - this.setScrollPosition(position); - }; - - this.getScrollHeight = () => this.el[0].scrollHeight; - this.getViewableHeight = () => this.el[0].offsetHeight; - - /** - * Get the vertical scroll position. - * - * @returns {Number} - The number of pixels that are hidden from view above the scrollable area. - */ - this.getScrollPosition = () => this.el[0].scrollTop; - - this.setScrollPosition = position => { - const viewport = this.getScrollHeight() - this.getViewableHeight(); - - this.position.previous = this.position.current; - this.threshold.previous = this.position.previous / viewport; - this.position.current = position; - - this.el[0].scrollTop = position; - }; - - this.resetScrollPosition = () => { - this.threshold.previous = 0; - this.position.previous = 0; - this.position.current = 0; - - this.el[0].scrollTop = 0; - }; - - this.scrollToBottom = () => { - this.setScrollPosition(this.getScrollHeight()); - }; - - this.lock = () => { - this.state.locked = true; - }; - - this.unlock = () => { - this.state.locked = false; - }; - - this.pause = () => { - this.state.paused = true; - }; - - this.resume = () => { - this.state.paused = false; - }; - - this.hide = () => { - if (this.state.hidden) { - return; - } - - this.state.hidden = true; - this.el.css('overflow-y', 'hidden'); - }; - - this.unhide = () => { - if (!this.state.hidden) { - return; - } - - this.state.hidden = false; - this.el.css('overflow-y', 'auto'); - }; - - this.isBeyondLowerThreshold = () => { - const position = this.getScrollPosition(); - const viewport = this.getScrollHeight() - this.getViewableHeight(); - const threshold = position / viewport; - - return (1 - threshold) < OUTPUT_SCROLL_THRESHOLD; - }; - - this.isBeyondUpperThreshold = () => { - const position = this.getScrollPosition(); - const viewport = this.getScrollHeight() - this.getViewableHeight(); - const threshold = position / viewport; - - return threshold < OUTPUT_SCROLL_THRESHOLD; - }; - - this.isPaused = () => this.state.paused; - this.isLocked = () => this.state.locked; - this.isMissing = () => $(OUTPUT_ELEMENT_TBODY)[0].clientHeight < this.getViewableHeight(); -} - -JobScrollService.$inject = ['$q', '$timeout']; - -export default JobScrollService; diff --git a/awx/ui/client/features/output/search.component.js b/awx/ui/client/features/output/search.component.js deleted file mode 100644 index cb8299be89c5..000000000000 --- a/awx/ui/client/features/output/search.component.js +++ /dev/null @@ -1,147 +0,0 @@ -/* eslint camelcase: 0 */ -import { - OUTPUT_SEARCH_DOCLINK, - OUTPUT_SEARCH_FIELDS, - OUTPUT_SEARCH_KEY_EXAMPLES, -} from './constants'; - -const templateUrl = require('~features/output/search.partial.html'); - -let $state; -let qs; -let strings; - -let vm; - -function toggleSearchKey () { - vm.key = !vm.key; -} - -function getCurrentQueryset () { - const { job_event_search } = $state.params; - - return qs.decodeArr(job_event_search); -} - -function getSearchTags (queryset) { - return qs.createSearchTagsFromQueryset(queryset) - .filter(tag => !tag.startsWith('event')) - .filter(tag => !tag.startsWith('-event')) - .filter(tag => !tag.startsWith('page_size')) - .filter(tag => !tag.startsWith('order_by')); -} - -function reloadQueryset (queryset, rejection = strings.get('search.REJECT_DEFAULT')) { - const params = angular.copy($state.params); - const currentTags = vm.tags; - - params.handleErrors = false; - params.job_event_search = qs.encodeArr(queryset); - - vm.disabled = true; - vm.message = ''; - vm.tags = getSearchTags(queryset); - - return vm.reload(params) - .catch(() => { - vm.tags = currentTags; - vm.message = rejection; - vm.rejected = true; - vm.disabled = false; - }); -} - -const isFilterable = term => { - const field = term[0].split('.')[0].replace(/^-/, ''); - return (OUTPUT_SEARCH_FIELDS.indexOf(field) > -1); -}; - -function removeSearchTag (index) { - const searchTerm = vm.tags[index]; - - const currentQueryset = getCurrentQueryset(); - const modifiedQueryset = qs.removeTermsFromQueryset(currentQueryset, searchTerm, isFilterable); - - reloadQueryset(modifiedQueryset); -} - -function submitSearch () { - // empty input, not submit new search, return. - if (!vm.value) { - return; - } - - const currentQueryset = getCurrentQueryset(); - // check duplicate , see if search input already exists in current search tags - if (currentQueryset.search) { - if (currentQueryset.search.includes(vm.value)) { - return; - } - } - - const searchInputQueryset = qs.getSearchInputQueryset(vm.value, isFilterable); - const modifiedQueryset = qs.mergeQueryset(currentQueryset, searchInputQueryset); - - reloadQueryset(modifiedQueryset, strings.get('search.REJECT_INVALID')); -} - -function clearSearch () { - reloadQueryset(); -} - -function JobSearchController (_$state_, _qs_, _strings_, { subscribe }) { - $state = _$state_; - qs = _qs_; - strings = _strings_; - - vm = this || {}; - vm.strings = strings; - - vm.examples = OUTPUT_SEARCH_KEY_EXAMPLES; - vm.fields = OUTPUT_SEARCH_FIELDS; - vm.docLink = OUTPUT_SEARCH_DOCLINK; - vm.relatedFields = []; - - vm.clearSearch = clearSearch; - vm.toggleSearchKey = toggleSearchKey; - vm.removeSearchTag = removeSearchTag; - vm.submitSearch = submitSearch; - - let unsubscribe; - - vm.$onInit = () => { - vm.value = ''; - vm.message = ''; - vm.key = false; - vm.rejected = false; - vm.disabled = true; - vm.isJobActive = false; - vm.tags = getSearchTags(getCurrentQueryset()); - - unsubscribe = subscribe(({ running, event_processing_finished }) => { - const isJobActive = running || !event_processing_finished; - vm.disabled = isJobActive; - vm.isJobActive = isJobActive; - }); - }; - - vm.$onDestroy = () => { - unsubscribe(); - }; -} - -JobSearchController.$inject = [ - '$state', - 'QuerySet', - 'OutputStrings', - 'OutputStatusService', -]; - -export default { - templateUrl, - controller: JobSearchController, - controllerAs: 'vm', - bindings: { - reload: '=', - }, -}; diff --git a/awx/ui/client/features/output/search.partial.html b/awx/ui/client/features/output/search.partial.html deleted file mode 100644 index b6e6a7342f37..000000000000 --- a/awx/ui/client/features/output/search.partial.html +++ /dev/null @@ -1,74 +0,0 @@ - - -
-
- - - - - - -
-

{{ vm.message }}

-
- - - -
-
-
-
-
- {{:: vm.strings.get('search.EXAMPLES') }}: -
- -
-
-
- {{:: vm.strings.get('search.FIELDS') }}: - {{ field }}, -
-
- {{:: vm.strings.get('search.ADDITIONAL_INFORMATION_HEADER') }}: - {{:: vm.strings.get('search.ADDITIONAL_INFORMATION') }} - - {{:: vm.strings.get('search.DOCUMENTATION') }}. - -
-
-
diff --git a/awx/ui/client/features/output/slide.service.js b/awx/ui/client/features/output/slide.service.js deleted file mode 100644 index da1d9a40474b..000000000000 --- a/awx/ui/client/features/output/slide.service.js +++ /dev/null @@ -1,164 +0,0 @@ -/* eslint camelcase: 0 */ -import { - OUTPUT_MAX_BUFFER_LENGTH, - OUTPUT_PAGE_SIZE, -} from './constants'; - -function SlidingWindowService ($q) { - this.init = ({ getRange, getFirst, getLast, getMaxCounter }, storage) => { - const { getHeadCounter, getTailCounter } = storage; - - this.api = { - getRange, - getFirst, - getLast, - getMaxCounter, - }; - - this.storage = { - getHeadCounter, - getTailCounter, - }; - - this.buffer = { - events: [], - min: 0, - max: 0, - count: 0, - }; - - this.cache = { - first: null - }; - }; - - this.getBoundedRange = range => { - const bounds = [1, this.getMaxCounter()]; - - return [Math.max(range[0], bounds[0]), Math.min(range[1], bounds[1])]; - }; - - this.getNextRange = displacement => { - const tail = this.getTailCounter(); - - return this.getBoundedRange([tail + 1, tail + 1 + displacement]); - }; - - this.getPreviousRange = displacement => { - const head = this.getHeadCounter(); - - return this.getBoundedRange([head - 1 - displacement, head - 1]); - }; - - this.getNext = (displacement = OUTPUT_PAGE_SIZE) => { - const next = this.getNextRange(displacement); - - return this.api.getRange(next); - }; - - this.getPrevious = (displacement = OUTPUT_PAGE_SIZE) => { - const previous = this.getPreviousRange(displacement); - - return this.api.getRange(previous); - }; - - this.getFirst = () => { - if (this.cache.first) { - return $q.resolve(this.cache.first); - } - - return this.api.getFirst() - .then(events => { - if (events.length === OUTPUT_PAGE_SIZE) { - this.cache.first = events; - } - - return $q.resolve(events); - }); - }; - - this.getLast = () => this.getFrames() - .then(frames => { - if (frames.length > 0) { - return $q.resolve(frames); - } - - return this.api.getLast(); - }); - - this.pushFrames = events => { - const head = this.getHeadCounter(); - const tail = this.getTailCounter(); - const frames = this.buffer.events.concat(events); - - let min; - let max; - let count = 0; - - for (let i = frames.length - 1; i >= 0; i--) { - count++; - - if (count > OUTPUT_MAX_BUFFER_LENGTH) { - frames.splice(i, 1); - - count--; - continue; - } - - if (!min || frames[i].counter < min) { - min = frames[i].counter; - } - - if (!max || frames[i].counter > max) { - max = frames[i].counter; - } - } - - this.buffer.events.length = 0; - delete this.buffer.events; - - this.buffer.events = frames; - this.buffer.min = min; - this.buffer.max = max; - this.buffer.count = count; - - if (tail - head === 0) { - return frames; - } - - return frames.filter(({ counter }) => counter > tail); - }; - - this.clear = () => { - this.buffer.events.length = 0; - this.buffer.min = 0; - this.buffer.max = 0; - this.buffer.count = 0; - }; - - this.getFrames = () => $q.resolve(this.buffer.events); - - this.getMaxCounter = () => { - if (this.buffer.max && this.buffer.max > 1) { - return this.buffer.max; - } - - return this.api.getMaxCounter(); - }; - - this.isOnLastPage = () => { - if (this.buffer.min) { - return this.getTailCounter() >= this.buffer.min - 1; - } - - return this.getTailCounter() >= this.getMaxCounter() - OUTPUT_PAGE_SIZE; - }; - - this.isOnFirstPage = () => this.getHeadCounter() === 1; - this.getTailCounter = () => this.storage.getTailCounter(); - this.getHeadCounter = () => this.storage.getHeadCounter(); -} - -SlidingWindowService.$inject = ['$q']; - -export default SlidingWindowService; diff --git a/awx/ui/client/features/output/stats.component.js b/awx/ui/client/features/output/stats.component.js deleted file mode 100644 index 61f86d67092d..000000000000 --- a/awx/ui/client/features/output/stats.component.js +++ /dev/null @@ -1,84 +0,0 @@ -import { OUTPUT_NO_COUNT_JOB_TYPES } from './constants'; - -const templateUrl = require('~features/output/stats.partial.html'); - -let vm; - -function createStatsBarTooltip (key, count) { - const label = `${key}`; - const badge = `${count}`; - - return `${label}${badge}`; -} - -function JobStatsController (strings, { subscribe }) { - vm = this || {}; - vm.strings = strings; - - let unsubscribe; - - vm.tooltips = { - running: strings.get('status.RUNNING'), - unavailable: strings.get('status.UNAVAILABLE'), - }; - - vm.$onInit = () => { - vm.hideCounts = OUTPUT_NO_COUNT_JOB_TYPES.includes(vm.resource.model.get('type')); - vm.download = vm.resource.model.get('related.stdout'); - vm.tooltips.toggleExpand = vm.expanded ? - strings.get('tooltips.COLLAPSE_OUTPUT') : - strings.get('tooltips.EXPAND_OUTPUT'); - - unsubscribe = subscribe(({ running, elapsed, counts, hosts }) => { - vm.plays = counts.plays; - vm.tasks = counts.tasks; - vm.hosts = counts.hosts; - vm.elapsed = elapsed; - vm.running = running; - vm.setHostStatusCounts(hosts); - }); - }; - - vm.$onDestroy = () => { - unsubscribe(); - }; - - vm.setHostStatusCounts = counts => { - let statsAreAvailable; - - Object.keys(counts).forEach(key => { - const count = counts[key]; - const statusBarElement = $(`.HostStatusBar-${key}`); - - statusBarElement.css('flex', `${count} 0 auto`); - - vm.tooltips[key] = createStatsBarTooltip(key, count); - - if (count) statsAreAvailable = true; - }); - - vm.statsAreAvailable = statsAreAvailable; - }; - - vm.toggleExpanded = () => { - vm.expanded = !vm.expanded; - vm.tooltips.toggleExpand = vm.expanded ? - strings.get('tooltips.COLLAPSE_OUTPUT') : - strings.get('tooltips.EXPAND_OUTPUT'); - }; -} - -JobStatsController.$inject = [ - 'OutputStrings', - 'OutputStatusService', -]; - -export default { - templateUrl, - controller: JobStatsController, - controllerAs: 'vm', - bindings: { - resource: '<', - expanded: '=', - }, -}; diff --git a/awx/ui/client/features/output/stats.partial.html b/awx/ui/client/features/output/stats.partial.html deleted file mode 100644 index 1827c52888ae..000000000000 --- a/awx/ui/client/features/output/stats.partial.html +++ /dev/null @@ -1,86 +0,0 @@ - -
- {{:: vm.strings.get('stats.PLAYS')}} - ... - {{ vm.plays || 0 }} - - {{:: vm.strings.get('stats.TASKS')}} - ... - {{ vm.tasks || 0 }} - - {{:: vm.strings.get('stats.HOSTS')}} - ... - {{ vm.hosts || 1 }} - - {{:: vm.strings.get('stats.ELAPSED') }} - - {{ (vm.elapsed * 1000 || 0) | duration: "hh:mm:ss"}} - - - - - - - -
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/awx/ui/client/features/output/status.service.js b/awx/ui/client/features/output/status.service.js deleted file mode 100644 index f661d5b49160..000000000000 --- a/awx/ui/client/features/output/status.service.js +++ /dev/null @@ -1,371 +0,0 @@ -/* eslint camelcase: 0 */ -import { - EVENT_START_PLAYBOOK, - EVENT_STATS_PLAY, - EVENT_START_PLAY, - EVENT_START_TASK, - HOST_STATUS_KEYS, - JOB_STATUS_COMPLETE, - JOB_STATUS_INCOMPLETE, - JOB_STATUS_UNSUCCESSFUL, - JOB_STATUS_FINISHED, -} from './constants'; - -function JobStatusService (moment, message) { - this.dispatch = () => message.dispatch('status', this.state); - this.subscribe = listener => message.subscribe('status', listener); - - this.init = ({ model }) => { - this.model = model; - this.created = model.get('created'); - this.job = model.get('id'); - this.jobType = model.get('type'); - this.project = model.get('project'); - - this.active = false; - this.latestTime = null; - this.counter = -1; - - this.state = { - running: false, - anyFailed: false, - counts: { - plays: 0, - tasks: 0, - hosts: 0, - }, - hosts: {}, - status: model.get('status'), - elapsed: model.get('elapsed'), - started: model.get('started'), - finished: model.get('finished'), - environment: model.get('custom_virtualenv'), - artifacts: model.get('artifacts'), - scm: { - id: model.get('summary_fields.project_update.id'), - status: model.get('summary_fields.project_update.status') - }, - scmBranch: model.get('scm_branch'), - scmRefspec: model.get('scm_refspec'), - inventoryScm: { - id: model.get('source_project_update'), - status: model.get('summary_fields.inventory_source.status') - }, - event_processing_finished: model.get('event_processing_finished'), - }; - - this.initHostStatusCounts({ model }); - this.initPlaybookCounts({ model }); - - this.updateRunningState(); - this.dispatch(); - }; - - this.initHostStatusCounts = ({ model }) => { - if (model.has('host_status_counts')) { - this.setHostStatusCounts(model.get('host_status_counts')); - } else { - const hostStatusCounts = this.createHostStatusCounts(this.state.status); - - this.setHostStatusCounts(hostStatusCounts); - } - }; - - this.initPlaybookCounts = ({ model }) => { - if (model.has('playbook_counts')) { - this.setPlaybookCounts(model.get('playbook_counts')); - } else { - this.setPlaybookCounts({ task_count: 1, play_count: 1 }); - } - }; - - this.createHostStatusCounts = status => { - if (JOB_STATUS_UNSUCCESSFUL.includes(status)) { - return { failures: 1 }; - } - - if (JOB_STATUS_COMPLETE.includes(status)) { - return { ok: 1 }; - } - - return null; - }; - - this.pushStatusEvent = data => { - const isJobStatusEvent = (this.job === data.unified_job_id); - const isProjectStatusEvent = (this.project && (this.project === data.project_id)); - const isInventoryScmStatus = (this.model.get('source_project_update') === data.unified_job_id); - - if (isJobStatusEvent) { - this.setJobStatus(data.status); - if (JOB_STATUS_FINISHED.includes(data.status)) { - this.sync(); - } - this.dispatch(); - } else if (isProjectStatusEvent) { - this.setProjectStatus(data.status); - this.setProjectUpdateId(data.unified_job_id); - this.dispatch(); - } else if (isInventoryScmStatus) { - this.setInventoryScmStatus(data.status); - this.setInventoryScmId(data.unified_job_id); - this.dispatch(); - } - }; - - this.pushJobEvent = data => { - const isLatest = ((!this.counter) || (data.counter > this.counter)); - - let changed = false; - - if (!this.active && !(data.event === EVENT_STATS_PLAY)) { - this.active = true; - this.setJobStatus('running'); - changed = true; - } - - if (isLatest) { - this.counter = data.counter; - this.latestTime = data.created; - this.setElapsed(moment(data.created).diff(this.created, 'seconds')); - changed = true; - } - - if (data.event === EVENT_START_PLAYBOOK) { - this.setStarted(this.state.started || data.created); - changed = true; - } - - if (data.event === EVENT_START_PLAY) { - this.state.counts.plays++; - changed = true; - } - - if (data.event === EVENT_START_TASK) { - this.state.counts.tasks++; - changed = true; - } - - if (data.event === EVENT_STATS_PLAY) { - this.setStatsEvent(data); - changed = true; - } - - if (data.failed) { - this.state.anyFailed = true; - } - - if (changed) { - this.dispatch(); - } - }; - - this.isExpectingStatsEvent = () => (this.jobType === 'job') || - (this.jobType === 'project_update') || - (this.jobType === 'ad_hoc_command'); - - this.updateStats = () => { - this.updateHostCounts(); - - if (this.statsEvent) { - this.setFinished(this.statsEvent.created); - - const failures = _.get(this.statsEvent, ['event_data', 'failures'], {}); - const dark = _.get(this.statsEvent, ['event_data', 'dark'], {}); - - if (this.statsEvent.failed || - Object.keys(failures).length > 0 || - Object.keys(dark).length > 0) { - this.setJobStatus('failed'); - } else { - this.setJobStatus('successful'); - } - } else if (this.state.counter > -1) { - this.setJobStatus(this.state.anyFailed ? 'failed' : 'unknown'); - } else { - this.setJobStatus('unknown'); - } - }; - - this.updateRunningState = () => { - this.state.running = (Boolean(this.state.started) && !this.state.finished) || - (this.state.status === 'running') || - (this.state.status === 'pending') || - (this.state.status === 'waiting'); - }; - - this.updateHostCounts = () => { - const countedHostNames = []; - - const counts = Object.assign(...HOST_STATUS_KEYS.map(key => ({ [key]: 0 }))); - - HOST_STATUS_KEYS.forEach(key => { - const hostData = _.get(this.statsEvent, ['event_data', key], {}); - - Object.keys(hostData).forEach(hostName => { - const isAlreadyCounted = (countedHostNames.indexOf(hostName) > -1); - const shouldBeCounted = ((!isAlreadyCounted) && hostData[hostName] > 0); - - if (shouldBeCounted) { - countedHostNames.push(hostName); - counts[key]++; - } - }); - }); - - this.state.counts.hosts = countedHostNames.length; - this.setHostStatusCounts(counts); - }; - - this.setJobStatus = status => { - const isExpectingStats = this.isExpectingStatsEvent(); - const isIncomplete = JOB_STATUS_INCOMPLETE.includes(status); - const isFinished = JOB_STATUS_FINISHED.includes(status); - const isAlreadyFinished = JOB_STATUS_FINISHED.includes(this.state.status); - - if (isAlreadyFinished && !isFinished) { - return; - } - - if ((isExpectingStats && isIncomplete) || (!isExpectingStats && isFinished)) { - if (this.latestTime) { - if (!this.state.finished) { - this.setFinished(this.latestTime); - } - - if (!this.state.started && this.state.elapsed) { - this.setStarted(moment(this.latestTime) - .subtract(this.state.elapsed, 'seconds')); - } - } - } - - this.state.status = status; - this.updateRunningState(); - }; - - this.setElapsed = elapsed => { - if (!elapsed) return; - - this.state.elapsed = elapsed; - }; - - this.setStarted = started => { - if (!started) return; - - this.state.started = started; - this.updateRunningState(); - }; - - this.setProjectStatus = status => { - this.state.scm.status = status; - }; - - this.setProjectUpdateId = id => { - this.state.scm.id = id; - }; - - this.setInventoryScmStatus = status => { - this.state.inventoryScm.status = status; - }; - - this.setInventoryScmId = id => { - this.state.inventoryScm.id = id; - }; - - this.setFinished = time => { - if (!time) return; - - this.state.finished = time; - this.updateRunningState(); - }; - - this.setEnvironment = env => { - if (!env) return; - - this.state.environment = env; - }; - - this.setArtifacts = val => { - if (!val) return; - - this.state.artifacts = val; - }; - - this.setExecutionNode = node => { - if (!node) return; - - this.state.executionNode = node; - }; - - this.setStatsEvent = data => { - if (!data) return; - - this.statsEvent = data; - }; - - this.setResultTraceback = traceback => { - if (!traceback) return; - - this.state.resultTraceback = traceback; - }; - - this.setEventProcessingFinished = val => { - this.state.event_processing_finished = val; - }; - - this.setHostStatusCounts = counts => { - counts = counts || {}; - - HOST_STATUS_KEYS.forEach(key => { - counts[key] = counts[key] || 0; - }); - - if (!this.state.counts.hosts) { - this.state.counts.hosts = Object.keys(counts) - .reduce((sum, key) => sum + counts[key], 0); - } - - this.state.hosts = counts; - }; - - this.setPlaybookCounts = ({ play_count, task_count }) => { - this.state.counts.plays = play_count; - this.state.counts.tasks = task_count; - }; - - this.resetCounts = () => { - this.state.counts.plays = 0; - this.state.counts.tasks = 0; - this.state.counts.hosts = 0; - }; - - this.sync = () => { - const { model } = this; - - return model.http.get({ resource: model.get('id') }) - .then(() => { - this.setFinished(model.get('finished')); - this.setElapsed(model.get('elapsed')); - this.setStarted(model.get('started')); - this.setJobStatus(model.get('status')); - this.setEnvironment(model.get('custom_virtualenv')); - this.setArtifacts(model.get('artifacts')); - this.setExecutionNode(model.get('execution_node')); - this.setResultTraceback(model.get('result_traceback')); - this.setEventProcessingFinished(model.get('event_processing_finished')); - - this.initHostStatusCounts({ model }); - this.initPlaybookCounts({ model }); - - this.dispatch(); - }); - }; -} - -JobStatusService.$inject = [ - 'moment', - 'OutputMessageService', -]; - -export default JobStatusService; diff --git a/awx/ui/client/features/output/stream.service.js b/awx/ui/client/features/output/stream.service.js deleted file mode 100644 index 11198e07527f..000000000000 --- a/awx/ui/client/features/output/stream.service.js +++ /dev/null @@ -1,242 +0,0 @@ -/* eslint camelcase: 0 */ -import { - EVENT_STATS_PLAY, - OUTPUT_MAX_BUFFER_LENGTH, - OUTPUT_MAX_LAG, - OUTPUT_PAGE_SIZE, - OUTPUT_EVENT_LIMIT, -} from './constants'; - -const rx = []; - -function OutputStream ($q) { - this.init = ({ onFrames, onFrameRate, onStop }) => { - this.hooks = { - onFrames, - onFrameRate, - onStop, - }; - - this.bufferInit(); - }; - - this.bufferInit = () => { - rx.length = 0; - - this.counters = { - min: 1, - max: -1, - ready: -1, - final: null, - used: [], - missing: [], - total: 0, - length: 0, - }; - - this.state = { - ending: false, - ended: false, - overflow: false, - }; - - this.lag = 0; - this.chain = $q.resolve(); - - this.factors = this.calcFactors(OUTPUT_EVENT_LIMIT); - this.setFramesPerRender(); - }; - - this.calcFactors = size => { - const factors = [1]; - - if (size !== parseInt(size, 10) || size <= 1) { - return factors; - } - - for (let i = 2; i <= size / 2; i++) { - if (size % i === 0) { - factors.push(i); - } - } - - factors.push(size); - - return factors; - }; - - this.setFramesPerRender = () => { - const index = Math.floor((this.lag / OUTPUT_MAX_LAG) * this.factors.length); - const boundedIndex = Math.min(this.factors.length - 1, index); - - this.framesPerRender = this.factors[boundedIndex]; - this.hooks.onFrameRate(this.framesPerRender); - }; - - this.setMissingCounterThreshold = counter => { - if (counter > this.counters.min) { - this.counters.min = counter; - } - }; - - this.bufferAdd = event => { - const { counter } = event; - - if (counter > this.counters.max) { - this.counters.max = counter; - } - - let ready; - const used = []; - const missing = []; - - for (let i = this.counters.min; i <= this.counters.max; i++) { - if (this.counters.used.indexOf(i) === -1) { - if (i === counter) { - rx.push(event); - used.push(i); - this.counters.length += 1; - } else { - missing.push(i); - } - } else { - used.push(i); - } - } - - const excess = this.counters.length - OUTPUT_MAX_BUFFER_LENGTH; - this.state.overflow = (excess > 0); - - if (missing.length === 0) { - ready = this.counters.max; - } else if (this.state.overflow) { - ready = this.counters.min + this.framesPerRender; - } else { - ready = missing[0] - 1; - } - - this.counters.total += 1; - this.counters.ready = ready; - this.counters.used = used; - this.counters.missing = missing; - }; - - this.bufferEmpty = threshold => { - let removed = []; - - for (let i = rx.length - 1; i >= 0; i--) { - if (rx[i].counter <= threshold) { - removed = removed.concat(rx.splice(i, 1)); - } - } - - this.counters.min = threshold + 1; - this.counters.used = this.counters.used.filter(c => c > threshold); - this.counters.length = rx.length; - - return removed; - }; - - this.isReadyToRender = () => { - const { total } = this.counters; - const readyCount = this.getReadyCount(); - - if (readyCount <= 0) { - return false; - } - - if (this.state.ending) { - return true; - } - - if (total % this.framesPerRender === 0) { - return true; - } - - if (total < OUTPUT_PAGE_SIZE) { - if (readyCount % this.framesPerRender === 0) { - return true; - } - } - - return false; - }; - - this.pushJobEvent = data => { - this.lag++; - - this.chain = this.chain - .then(() => { - if (data.event === EVENT_STATS_PLAY) { - this.state.ending = true; - this.counters.final = data.counter; - } - - this.bufferAdd(data); - - if (this.counters.total % OUTPUT_PAGE_SIZE === 0) { - this.setFramesPerRender(); - } - - if (!this.isReadyToRender()) { - return $q.resolve(); - } - - const isLast = this.state.ending && (this.counters.ready >= this.counters.final); - const events = this.bufferEmpty(this.counters.ready); - - if (events.length > 0) { - return this.emitFrames(events, isLast); - } - - return $q.resolve(); - }) - .then(() => --this.lag); - - return this.chain; - }; - - this.setFinalCounter = counter => { - this.chain = this.chain - .then(() => { - this.state.ending = true; - this.counters.final = counter; - - if (counter > this.counters.ready) { - return $q.resolve(); - } - - const readyCount = this.getReadyCount(); - - let events = []; - if (readyCount > 0) { - events = this.bufferEmpty(this.counters.ready); - - return this.emitFrames(events, true); - } - - return $q.resolve(); - }); - - return this.chain; - }; - - this.emitFrames = (events, last) => this.hooks.onFrames(events) - .then(() => { - if (last) { - this.state.ending = false; - this.state.ended = true; - - this.hooks.onStop(); - } - - return $q.resolve(); - }); - - this.getMaxCounter = () => this.counters.max; - this.getReadyCount = () => this.counters.ready - this.counters.min + 1; -} - -OutputStream.$inject = ['$q']; - -export default OutputStream; diff --git a/awx/ui/client/features/portalMode/_index.less b/awx/ui/client/features/portalMode/_index.less deleted file mode 100644 index 418095942c78..000000000000 --- a/awx/ui/client/features/portalMode/_index.less +++ /dev/null @@ -1,69 +0,0 @@ -.PortalMode-container{ - display: flex; - flex-direction: row; - @media screen and(max-width: 900px){ - flex-direction: column; - height: 100%; - } -} - -.PortalMode-panel--left{ - .OnePlusOne-panel--left; -} - -.PortalMode-panel--right{ - .OnePlusOne-panel--right; - .List-tableHeader:last-of-type{ - text-align:left; - } -} - -.PortalMode-panel .List-header { - height: 14px; - margin-bottom: 20px; -} - -.PortalMode-panelHeader{ - .OnePlusOne-panelHeader; -} - - -.PortalMode-headerContainer { - display: flex; - align-items: center; - margin-bottom: 20px; - - .at-Panel-heading { - margin-bottom: 0; - } - - .FormToggle-container { - padding-bottom: 0; - } -} - -.PortalMode-filterHolder { - position: absolute; - right: 1px; - margin-right: 10px; - display: flex; - align-items: center; - justify-content: center; - - .btn.btn-xs { - padding: 1px 10px; - } -} - -.PortalMode-filterButton--edges { - &:first-child { - border-right: none; - } - &:last-child { - border-left: none; - } -} - -.PortalMode-refresh { - margin-left: 10px; -} diff --git a/awx/ui/client/features/portalMode/index.controller.js b/awx/ui/client/features/portalMode/index.controller.js deleted file mode 100644 index 05889bf1a5a7..000000000000 --- a/awx/ui/client/features/portalMode/index.controller.js +++ /dev/null @@ -1,27 +0,0 @@ -function IndexTemplatesController ($scope, $state, strings) { - const vm = this; - vm.strings = strings; - - $scope.filterUser = () => { - $state.go('portalMode.myJobs'); - }; - $scope.filterAll = () => { - $state.go('portalMode.allJobs'); - }; - - $scope.$on('updateCount', (e, count, resource) => { - if (resource === 'jobs') { - vm.jobsCount = count; - } else if (resource === 'templates') { - vm.templatesCount = count; - } - }); -} - -IndexTemplatesController.$inject = [ - '$scope', - '$state', - 'PortalModeStrings', -]; - -export default IndexTemplatesController; diff --git a/awx/ui/client/features/portalMode/index.js b/awx/ui/client/features/portalMode/index.js deleted file mode 100644 index e09c6e35e152..000000000000 --- a/awx/ui/client/features/portalMode/index.js +++ /dev/null @@ -1,18 +0,0 @@ -import PortalModeStrings from './portalMode.strings'; - -import templatesRoute from './routes/portalModeTemplatesList.route'; -import myJobsRoute from './routes/portalModeMyJobs.route'; -import allJobsRoute from './routes/portalModeAllJobs.route'; - -const MODULE_NAME = 'at.features.portalMode'; - -angular - .module(MODULE_NAME, []) - .service('PortalModeStrings', PortalModeStrings) - .run(['$stateExtender', ($stateExtender) => { - $stateExtender.addState(templatesRoute); - $stateExtender.addState(myJobsRoute); - $stateExtender.addState(allJobsRoute); - }]); - -export default MODULE_NAME; diff --git a/awx/ui/client/features/portalMode/index.view.html b/awx/ui/client/features/portalMode/index.view.html deleted file mode 100644 index e463df464cbc..000000000000 --- a/awx/ui/client/features/portalMode/index.view.html +++ /dev/null @@ -1,56 +0,0 @@ -
-
-
- -
- - -
-
-
-
- -
-
- - -
-
-
- - -
-
-
- -
-
-
-
-
-
-
-
-
-
diff --git a/awx/ui/client/features/portalMode/portalMode.strings.js b/awx/ui/client/features/portalMode/portalMode.strings.js deleted file mode 100644 index c1cdd893fd17..000000000000 --- a/awx/ui/client/features/portalMode/portalMode.strings.js +++ /dev/null @@ -1,15 +0,0 @@ -function PortalModeStrings (BaseString) { - BaseString.call(this, 'portalMode'); - - const { t } = this; - const ns = this.portalMode; - - ns.list = { - TEMPLATES_PANEL_TITLE: t.s('JOB TEMPLATES'), - JOBS_PANEL_TITLE: t.s('JOBS'), - }; -} - -PortalModeStrings.$inject = ['BaseStringService']; - -export default PortalModeStrings; diff --git a/awx/ui/client/features/portalMode/routes/portalModeAllJobs.route.js b/awx/ui/client/features/portalMode/routes/portalModeAllJobs.route.js deleted file mode 100644 index fa2396b4df4c..000000000000 --- a/awx/ui/client/features/portalMode/routes/portalModeAllJobs.route.js +++ /dev/null @@ -1,57 +0,0 @@ -import jobsListController from '../../jobs/jobsList.controller'; - -const jobsListTemplate = require('~features/jobs/jobsList.view.html'); - -export default { - name: 'portalMode.allJobs', - url: '/alljobs?{job_search:queryset}', - ncyBreadcrumb: { - skip: true - }, - params: { - job_search: { - value: { - page_size: '20', - order_by: '-finished' - }, - dynamic: true - } - }, - views: { - 'jobs@portalMode': { - templateUrl: jobsListTemplate, - controller: jobsListController, - controllerAs: 'vm' - } - }, - resolve: { - resolvedModels: [ - 'UnifiedJobModel', - (UnifiedJob) => { - const models = [ - new UnifiedJob(['options']), - ]; - return Promise.all(models); - }, - ], - Dataset: [ - '$stateParams', - 'Wait', - 'GetBasePath', - 'QuerySet', - ($stateParams, Wait, GetBasePath, qs) => { - const searchParam = $stateParams.job_search; - - const searchPath = GetBasePath('unified_jobs'); - - Wait('start'); - return qs.search(searchPath, searchParam) - .finally(() => Wait('stop')); - } - ], - SearchBasePath: [ - 'GetBasePath', - (GetBasePath) => GetBasePath('unified_jobs') - ] - } -}; diff --git a/awx/ui/client/features/portalMode/routes/portalModeMyJobs.route.js b/awx/ui/client/features/portalMode/routes/portalModeMyJobs.route.js deleted file mode 100644 index 61de256d402c..000000000000 --- a/awx/ui/client/features/portalMode/routes/portalModeMyJobs.route.js +++ /dev/null @@ -1,60 +0,0 @@ -import jobsListController from '../../jobs/jobsList.controller'; - -const jobsListTemplate = require('~features/jobs/jobsList.view.html'); - -export default { - name: 'portalMode.myJobs', - url: '/myjobs?{job_search:queryset}', - ncyBreadcrumb: { - skip: true - }, - params: { - job_search: { - value: { - page_size: '20', - order_by: '-finished', - created_by: null - }, - dynamic: true - } - }, - views: { - 'jobs@portalMode': { - templateUrl: jobsListTemplate, - controller: jobsListController, - controllerAs: 'vm' - } - }, - resolve: { - resolvedModels: [ - 'UnifiedJobModel', - (UnifiedJob) => { - const models = [ - new UnifiedJob(['options']), - ]; - return Promise.all(models); - }, - ], - Dataset: [ - '$stateParams', - 'Wait', - 'GetBasePath', - 'QuerySet', - '$rootScope', - ($stateParams, Wait, GetBasePath, qs, $rootScope) => { - const searchParam = _.assign($stateParams.job_search, { - created_by: $rootScope.current_user.id }); - - const searchPath = GetBasePath('unified_jobs'); - - Wait('start'); - return qs.search(searchPath, searchParam) - .finally(() => Wait('stop')); - } - ], - SearchBasePath: [ - 'GetBasePath', - (GetBasePath) => GetBasePath('unified_jobs') - ] - } -}; diff --git a/awx/ui/client/features/portalMode/routes/portalModeTemplatesList.route.js b/awx/ui/client/features/portalMode/routes/portalModeTemplatesList.route.js deleted file mode 100644 index 8255cfb16921..000000000000 --- a/awx/ui/client/features/portalMode/routes/portalModeTemplatesList.route.js +++ /dev/null @@ -1,70 +0,0 @@ -import { N_ } from '../../../src/i18n'; -import templatesListController from '../../templates/templatesList.controller'; -import indexController from '../index.controller'; - -const templatesListTemplate = require('~features/templates/templatesList.view.html'); -const indexTemplate = require('~features/portalMode/index.view.html'); - -export default { - name: 'portalMode', - url: '/portal', - reloadOnSearch: true, - ncyBreadcrumb: { - label: N_('MY VIEW') - }, - data: { - socket: { - groups: { - jobs: ['status_changed'] - } - } - }, - params: { - template_search: { - dynamic: true, - value: { - type: 'workflow_job_template,job_template', - }, - } - }, - searchPrefix: 'template', - views: { - '@': { - templateUrl: indexTemplate, - controller: indexController, - controllerAs: 'vm' - }, - 'templates@portalMode': { - templateUrl: templatesListTemplate, - controller: templatesListController, - controllerAs: 'vm' - } - }, - resolve: { - resolvedModels: [ - 'JobTemplateModel', - 'WorkflowJobTemplateModel', - (JobTemplate, WorkflowJobTemplate) => { - const models = [ - new JobTemplate(['options']), - new WorkflowJobTemplate(['options']), - ]; - return Promise.all(models); - }, - ], - Dataset: [ - '$stateParams', - 'Wait', - 'GetBasePath', - 'QuerySet', - ($stateParams, Wait, GetBasePath, qs) => { - const searchParam = $stateParams.template_search; - const searchPath = GetBasePath('unified_job_templates'); - - Wait('start'); - return qs.search(searchPath, searchParam) - .finally(() => Wait('stop')); - } - ], - } -}; diff --git a/awx/ui/client/features/projects/index.controller.js b/awx/ui/client/features/projects/index.controller.js deleted file mode 100644 index 4722753e9743..000000000000 --- a/awx/ui/client/features/projects/index.controller.js +++ /dev/null @@ -1,19 +0,0 @@ -function IndexProjectsController ($scope, strings, dataset) { - const vm = this; - vm.strings = strings; - vm.count = dataset.data.count; - - $scope.$on('updateCount', (e, count) => { - if (typeof count === 'number') { - vm.count = count; - } - }); -} - -IndexProjectsController.$inject = [ - '$scope', - 'ProjectsStrings', - 'Dataset', -]; - -export default IndexProjectsController; diff --git a/awx/ui/client/features/projects/index.js b/awx/ui/client/features/projects/index.js deleted file mode 100644 index c9e51f7fbcbb..000000000000 --- a/awx/ui/client/features/projects/index.js +++ /dev/null @@ -1,9 +0,0 @@ -import ProjectsStrings from './projects.strings'; - -const MODULE_NAME = 'at.features.projects'; - -angular - .module(MODULE_NAME, []) - .service('ProjectsStrings', ProjectsStrings); - -export default MODULE_NAME; diff --git a/awx/ui/client/features/projects/index.view.html b/awx/ui/client/features/projects/index.view.html deleted file mode 100644 index 13e18dfb283c..000000000000 --- a/awx/ui/client/features/projects/index.view.html +++ /dev/null @@ -1,12 +0,0 @@ -
-
- -
- - -
-
-
diff --git a/awx/ui/client/features/projects/projects.strings.js b/awx/ui/client/features/projects/projects.strings.js deleted file mode 100644 index ef20b8b7906d..000000000000 --- a/awx/ui/client/features/projects/projects.strings.js +++ /dev/null @@ -1,54 +0,0 @@ -function ProjectsStrings (BaseString) { - BaseString.call(this, 'projects'); - - const { t } = this; - const ns = this.projects; - - ns.list = { - PANEL_TITLE: t.s('PROJECTS'), - ROW_ITEM_LABEL_DESCRIPTION: t.s('DESCRIPTION'), - ROW_ITEM_LABEL_REVISION: t.s('REVISION'), - ROW_ITEM_LABEL_ORGANIZATION: t.s('ORGANIZATION'), - ROW_ITEM_LABEL_MODIFIED: t.s('LAST MODIFIED'), - ROW_ITEM_LABEL_USED: t.s('LAST USED'), - }; - - ns.update = { - GET_LATEST: t.s('Get latest SCM revision'), - UPDATE_RUNNING: t.s('SCM update currently running'), - MANUAL_PROJECT_NO_UPDATE: t.s('Manual projects do not require an SCM update'), - CANCEL_UPDATE_REQUEST: t.s('Your request to cancel the update was submitted to the task manager.'), - NO_UPDATE_INFO: t.s('There is no SCM update information available for this project. An update has not yet been completed. If you have not already done so, start an update for this project.'), - NO_PROJ_SCM_CONFIG: t.s('The selected project is not configured for SCM. To configure for SCM, edit the project and provide SCM settings, and then run an update.'), - NO_ACCESS_OR_COMPLETED_UPDATE: t.s('Either you do not have access or the SCM update process completed'), - NO_RUNNING_UPDATE: t.s('An SCM update does not appear to be running for project: '), - }; - - ns.alert = { - NO_UPDATE: t.s('No Updates Available'), - UPDATE_CANCEL: t.s('SCM Update Cancel'), - CANCEL_NOT_ALLOWED: t.s('Cancel Not Allowed'), - NO_SCM_CONFIG: t.s('No SCM Configuration'), - UPDATE_NOT_FOUND: t.s('Update Not Found'), - }; - - ns.status = { - NOT_CONFIG: t.s('Not configured for SCM'), - NEVER_UPDATE: t.s('No SCM updates have run for this project'), - UPDATE_QUEUED: t.s('Update queued. Click for details'), - UPDATE_RUNNING: t.s('Update running. Click for details'), - UPDATE_SUCCESS: t.s('Update succeeded. Click for details'), - UPDATE_FAILED: t.s('Update failed. Click for details'), - UPDATE_MISSING: t.s('Update missing. Click for details'), - UPDATE_CANCELED: t.s('Update canceled. Click for details'), - }; - - ns.error = { - HEADER: this.error.HEADER, - CALL: this.error.CALL, - }; -} - -ProjectsStrings.$inject = ['BaseStringService']; - -export default ProjectsStrings; diff --git a/awx/ui/client/features/projects/projectsList.controller.js b/awx/ui/client/features/projects/projectsList.controller.js deleted file mode 100644 index f90597ac984b..000000000000 --- a/awx/ui/client/features/projects/projectsList.controller.js +++ /dev/null @@ -1,508 +0,0 @@ -/** *********************************************** - * Copyright (c) 2018 Ansible, Inc. - * - * All Rights Reserved - ************************************************ */ - -const mapChoices = choices => Object.assign(...choices.map(([k, v]) => ({ [k]: v.toUpperCase() }))); - -function projectsListController ( - $filter, $scope, $rootScope, $state, $log, Dataset, Alert, Rest, - ProcessErrors, resolvedModels, strings, Wait, ngToast, - Prompt, GetBasePath, qs, ProjectUpdate, -) { - const vm = this || {}; - const [ProjectModel] = resolvedModels; - let paginateQuerySet = {}; - $scope.canAdd = ProjectModel.options('actions.POST'); - - vm.strings = strings; - vm.scm_choices = ProjectModel.options('actions.GET.scm_type.choices'); - vm.projectTypes = mapChoices(vm.scm_choices); - - // smart-search - vm.list = { - iterator: 'project', - name: 'projects', - basePath: 'projects', - }; - vm.dataset = Dataset.data; - vm.projects = Dataset.data.results; - - $scope.$watch('vm.dataset.count', () => { - $scope.$emit('updateCount', vm.dataset.count, 'projects'); - }); - // build tooltips - _.forEach(vm.projects, buildTooltips); - $rootScope.flashMessage = null; - - // when a project is added/deleted, rebuild tooltips - $scope.$watchCollection('vm.projects', () => { - _.forEach(vm.projects, buildTooltips); - }); - // show active item in the list - $scope.$watch('$state.params', () => { - const projectId = _.get($state.params, 'project_id'); - if ((projectId)) { - vm.activeId = parseInt($state.params.project_id, 10); - } else { - vm.activeId = ''; - } - setToolbarSort(); - }, true); - - function setToolbarSort () { - const orderByValue = _.get($state.params, 'project_search.order_by'); - const sortValue = _.find(vm.toolbarSortOptions, (option) => option.value === orderByValue); - if (sortValue) { - vm.toolbarSortValue = sortValue; - } else { - vm.toolbarSortValue = toolbarSortDefault; - } - } - - const toolbarSortDefault = { - label: `${strings.get('sort.NAME_ASCENDING')}`, - value: 'name' - }; - - vm.toolbarSortOptions = [ - toolbarSortDefault, - { label: `${strings.get('sort.NAME_DESCENDING')}`, value: '-name' }, - { label: `${strings.get('sort.MODIFIED_ASCENDING')}`, value: 'modified' }, - { label: `${strings.get('sort.MODIFIED_DESCENDING')}`, value: '-modified' }, - { label: `${strings.get('sort.LAST_USED_ASCENDING')}`, value: 'last_job_run' }, - { label: `${strings.get('sort.LAST_USED_DESCENDING')}`, value: '-last_job_run' }, - { label: `${strings.get('sort.ORGANIZATION_ASCENDING')}`, value: 'organization' }, - { label: `${strings.get('sort.ORGANIZATION_DESCENDING')}`, value: '-organization' } - ]; - - vm.toolbarSortValue = toolbarSortDefault; - - // Temporary hack to retrieve $scope.querySet from the paginate directive. - // Remove this event listener once the page and page_size params - // are represented in the url. - $scope.$on('updateDataset', (event, dataset, queryset) => { - vm.dataset = dataset; - vm.projects = dataset.results; - paginateQuerySet = queryset; - }); - - vm.onToolbarSort = (sort) => { - vm.toolbarSortValue = sort; - - const queryParams = Object.assign( - {}, - $state.params.project_search, - paginateQuerySet, - { order_by: sort.value } - ); - - // Update URL with params - $state.go('.', { - project_search: queryParams - }, { notify: false, location: 'replace' }); - }; - - $scope.$on('ws-jobs', (e, data) => { - $log.debug(data); - if (vm.projects) { - // Assuming we have a list of projects available - const project = vm.projects.find((p) => p.id === data.project_id); - if (project) { - // And we found the affected project - $log.debug(`Received event for project: ${project.name}`); - $log.debug(`Status changed to: ${data.status}`); - if (data.status === 'successful' || data.status === 'failed' || data.status === 'canceled') { - reloadList(); - } else { - project.scm_update_tooltip = vm.strings.get('update.UPDATE_RUNNING'); - } - project.status = data.status; - buildTooltips(project); - } - } - }); - - if ($scope.removeGoTojobResults) { - $scope.removeGoTojobResults(); - } - - $scope.removeGoTojobResults = $scope.$on('GoTojobResults', (e, data) => { - if (data.summary_fields.current_update || data.summary_fields.last_update) { - Wait('start'); - // Grab the id from summary_fields - const updateJobid = (data.summary_fields.current_update) ? - data.summary_fields.current_update.id : data.summary_fields.last_update.id; - - $state.go('output', { id: updateJobid, type: 'project' }, { reload: true }); - } else { - Alert(vm.strings.get('alert.NO_UPDATE'), vm.strings.get('update.NO_UPDATE_INFO'), 'alert-info'); - } - }); - - if ($scope.removeCancelUpdate) { - $scope.removeCancelUpdate(); - } - - $scope.removeCancelUpdate = $scope.$on('Cancel_Update', (e, url) => { - // Cancel the project update process - Rest.setUrl(url); - Rest.post() - .then(() => { - Alert(vm.strings.get('alert.UPDATE_CANCEL'), vm.strings.get('update.CANCEL_UPDATE_REQUEST'), 'alert-info'); - }) - .catch(createErrorHandler(url, 'POST')); - }); - - if ($scope.removeCheckCancel) { - $scope.removeCheckCancel(); - } - - $scope.removeCheckCancel = $scope.$on('Check_Cancel', (e, projectData) => { - // Check that we 'can' cancel the update - const url = projectData.related.cancel; - Rest.setUrl(url); - Rest.get() - .then(({ data }) => { - if (data.can_cancel) { - $scope.$emit('Cancel_Update', url); - } else { - Alert(vm.strings.get('alert.CANCEL_NOT_ALLOWED'), vm.strings.get('update.NO_ACCESS_OR_COMPLETED_UPDATE'), 'alert-info', null, null, null, null, true); - } - }) - .catch(createErrorHandler(url, 'GET')); - }); - - vm.showSCMStatus = (id) => { - // Refresh the project list - const project = vm.projects.find((p) => p.id === id); - - if ((!project.scm_type) || project.scm_type === 'Manual') { - Alert(vm.strings.get('alert.NO_SCM_CONFIG'), vm.strings.get('update.NO_PROJ_SCM_CONFIG'), 'alert-info'); - } else { - // Refresh what we have in memory - // to insure we're accessing the most recent status record - Rest.setUrl(project.url); - Rest.get() - .then(({ data }) => { - $scope.$emit('GoTojobResults', data); - }) - .catch(createErrorHandler(project.url, 'GET')); - } - }; - - vm.getLastModified = project => { - const modified = _.get(project, 'modified'); - - if (!modified) { - return undefined; - } - - const html = $filter('longDate')(modified); - - // NEED api to add field project.summary_fields.modified_by - - // const { username, id } = _.get(project, 'summary_fields.modified_by', {}); - - // if (username && id) { - // html += ` by ${$filter('sanitize')(username)}`; - // } - - return html; - }; - - vm.getLastUsed = project => { - const modified = _.get(project, 'last_job_run'); - - if (!modified) { - return undefined; - } - - const html = $filter('longDate')(modified); - - // NEED api to add last_job user information such as launch_by - - // const { id } = _.get(project, 'summary_fields.last_job', {}); - // if (id) { - // html += ` by - // ${$filter('sanitize')('placehoder')}`; - // } - return html; - }; - - vm.copyProject = project => { - Wait('start'); - ProjectModel - .create('get', project.id) - .then(model => model.copy()) - .then((copiedProj) => { - ngToast.success({ - content: ` -
-
- -
-
- ${vm.strings.get('SUCCESSFUL_CREATION', copiedProj.name)} -
-
`, - dismissButton: false, - dismissOnTimeout: true - }); - reloadList(); - }) - .catch(createErrorHandler('copy project', 'GET')) - .finally(() => Wait('stop')); - }; - - vm.deleteProject = (id, name) => { - const action = () => { - $('#prompt-modal').modal('hide'); - Wait('start'); - ProjectModel - .request('delete', id) - .then(() => { - let reloadListStateParams = null; - - if (vm.projects.length === 1 - && $state.params.project_search - && _.has($state, 'params.project_search.page') - && $state.params.project_search.page !== '1') { - reloadListStateParams = _.cloneDeep($state.params); - reloadListStateParams.project_search.page = - (parseInt(reloadListStateParams.project_search.page, 10) - 1).toString(); - } - - if (parseInt($state.params.project_id, 10) === id) { - $state.go('^', reloadListStateParams, { reload: true }); - } else { - $state.go('.', reloadListStateParams, { reload: true }); - } - }) - .catch(createErrorHandler(`${ProjectModel.path}${id}/`, 'DELETE')) - .finally(() => { - Wait('stop'); - }); - }; - - ProjectModel.getDependentResourceCounts(id) - .then((counts) => { - const invalidateRelatedLines = []; - let deleteModalBody = `
${vm.strings.get('deleteResource.CONFIRM', 'project')}
`; - - counts.forEach(countObj => { - if (countObj.count && countObj.count > 0) { - invalidateRelatedLines.push(`
${countObj.label}${countObj.count}
`); - } - }); - - if (invalidateRelatedLines && invalidateRelatedLines.length > 0) { - deleteModalBody = `
${vm.strings.get('deleteResource.USED_BY', 'project')} ${vm.strings.get('deleteResource.CONFIRM', 'project')}
`; - invalidateRelatedLines.forEach(invalidateRelatedLine => { - deleteModalBody += invalidateRelatedLine; - }); - } - - Prompt({ - hdr: vm.strings.get('DELETE'), - resourceName: $filter('sanitize')(name), - body: deleteModalBody, - action, - actionText: vm.strings.get('DELETE'), - }); - }); - }; - - vm.cancelUpdate = (project) => { - project.pending_cancellation = true; - Rest.setUrl(GetBasePath('projects') + project.id); - Rest.get() - .then(({ data }) => { - if (data.related.current_update) { - cancelSCMUpdate(data); - } else { - Alert(vm.strings.get('update.UPDATE_NOT_FOUND'), vm.strings.get('update.NO_RUNNING_UPDATE') + $filter('sanitize')(project.name), 'alert-info', undefined, undefined, undefined, undefined, true); - } - }) - .catch(createErrorHandler('get project', 'GET')); - }; - - vm.SCMUpdate = (id, event) => { - try { - $(event.target).tooltip('hide'); - } catch (e) { - // ignore - } - vm.projects.forEach((project) => { - if (project.id === id) { - if (project.scm_type === 'Manual' || (!project.scm_type)) { - // Do not respond. Button appears greyed out as if it is disabled. - // Not disabled though, because we need mouse over event - // to work. So user can click, but we just won't do anything. - // Alert('Missing SCM Setup', 'Before running an SCM update, - // edit the project and provide the SCM access information.', 'alert-info'); - } else if (project.status === 'updating' || project.status === 'running' || project.status === 'pending') { - // Alert('Update in Progress', 'The SCM update process is running. - // Use the Refresh button to monitor the status.', 'alert-info'); - } else { - ProjectUpdate({ scope: $scope, project_id: project.id }); - } - } - }); - }; - - function buildTooltips (project) { - project.statusIcon = getJobStatusIcon(project); - project.statusTip = getStatusTooltip(project); - project.scm_update_tooltip = vm.strings.get('update.GET_LATEST'); - project.scm_update_disabled = false; - - if (project.status === 'pending' || project.status === 'waiting') { - project.scm_update_disabled = true; - } - - if (project.status === 'failed' && project.summary_fields.last_update && project.summary_fields.last_update.status === 'canceled') { - project.statusTip = vm.strings.get('status.UPDATE_CANCELED'); - project.scm_update_disabled = true; - } - - if (project.status === 'running' || project.status === 'updating') { - project.scm_update_tooltip = vm.strings.get('update.UPDATE_RUNNING'); - project.scm_update_disabled = true; - } - - if (project.scm_type === 'manual') { - project.statusIcon = 'none'; - project.statusTip = vm.strings.get('status.NOT_CONFIG'); - project.scm_update_tooltip = vm.strings.get('update.MANUAL_PROJECT_NO_UPDATE'); - project.scm_update_disabled = true; - } - } - - function cancelSCMUpdate (projectData) { - Rest.setUrl(projectData.related.current_update); - Rest.get() - .then(({ data }) => { - $scope.$emit('Check_Cancel', data); - }) - .catch(createErrorHandler(projectData.related.current_update, 'GET')); - } - - function reloadList () { - Wait('start'); - const path = GetBasePath(vm.list.basePath) || GetBasePath(vm.list.name); - qs.search(path, $state.params.project_search) - .then((searchResponse) => { - vm.dataset = searchResponse.data; - vm.projects = vm.dataset.results; - }) - .finally(() => Wait('stop')); - } - - function createErrorHandler (path, action) { - return ({ data, status }) => { - const hdr = strings.get('error.HEADER'); - const msg = strings.get('error.CALL', { path, action, status }); - ProcessErrors($scope, data, status, null, { hdr, msg }); - }; - } - - function getJobStatusIcon (project) { - let icon = 'none'; - switch (project.status) { - case 'n/a': - case 'ok': - case 'never updated': - icon = 'none'; - break; - case 'pending': - case 'waiting': - case 'new': - icon = 'none'; - break; - case 'updating': - case 'running': - icon = 'running'; - break; - case 'successful': - icon = 'success'; - break; - case 'failed': - case 'missing': - case 'canceled': - icon = 'error'; - break; - default: - break; - } - return icon; - } - - function getStatusTooltip (project) { - let tooltip = ''; - switch (project.status) { - case 'n/a': - case 'ok': - case 'never updated': - tooltip = vm.strings.get('status.NEVER_UPDATE'); - break; - case 'pending': - case 'waiting': - case 'new': - tooltip = vm.strings.get('status.UPDATE_QUEUED'); - break; - case 'updating': - case 'running': - tooltip = vm.strings.get('status.UPDATE_RUNNING'); - break; - case 'successful': - tooltip = vm.strings.get('status.UPDATE_SUCCESS'); - break; - case 'failed': - tooltip = vm.strings.get('status.UPDATE_FAILED'); - break; - case 'missing': - tooltip = vm.strings.get('status.UPDATE_MISSING'); - break; - case 'canceled': - tooltip = vm.strings.get('status.UPDATE_CANCELED'); - break; - default: - break; - } - return tooltip; - } - - vm.isCollapsed = true; - - vm.onCollapse = () => { - vm.isCollapsed = true; - }; - - vm.onExpand = () => { - vm.isCollapsed = false; - }; -} - -projectsListController.$inject = [ - '$filter', - '$scope', - '$rootScope', - '$state', - '$log', - 'Dataset', - 'Alert', - 'Rest', - 'ProcessErrors', - 'resolvedModels', - 'ProjectsStrings', - 'Wait', - 'ngToast', - 'Prompt', - 'GetBasePath', - 'QuerySet', - 'ProjectUpdate', -]; - -export default projectsListController; diff --git a/awx/ui/client/features/projects/projectsList.view.html b/awx/ui/client/features/projects/projectsList.view.html deleted file mode 100644 index 7072435d9c5f..000000000000 --- a/awx/ui/client/features/projects/projectsList.view.html +++ /dev/null @@ -1,110 +0,0 @@ - -
- - -
- -
-
- - - - -
-
- - -
-
-
- -
-
- - - - - - -
-
-
-
-
- {{ :: vm.strings.get('list.ROW_ITEM_LABEL_REVISION') }} -
- -
- - - - - - - - -
-
-
-
- - -
\ No newline at end of file diff --git a/awx/ui/client/features/projects/routes/projectsList.route.js b/awx/ui/client/features/projects/routes/projectsList.route.js deleted file mode 100644 index d89b80c3f18d..000000000000 --- a/awx/ui/client/features/projects/routes/projectsList.route.js +++ /dev/null @@ -1,90 +0,0 @@ -import { N_ } from '../../../src/i18n'; -import projectsListController from '../projectsList.controller'; -import indexController from '../index.controller'; - -const indexTemplate = require('~features/projects/index.view.html'); -const projectsListTemplate = require('~features/projects/projectsList.view.html'); - -export default { - searchPrefix: 'project', - name: 'projects', - route: '/projects', - ncyBreadcrumb: { - label: N_('PROJECTS') - }, - data: { - activityStream: true, - activityStreamTarget: 'project', - socket: { - groups: { - jobs: ['status_changed'] - } - } - }, - params: { - project_search: { - dynamic: true, - } - }, - views: { - '@': { - templateUrl: indexTemplate, - controller: indexController, - controllerAs: 'vm' - }, - 'projectsList@projects': { - templateUrl: projectsListTemplate, - controller: projectsListController, - controllerAs: 'vm', - } - }, - resolve: { - CredentialTypes: ['Rest', '$stateParams', 'GetBasePath', 'ProcessErrors', - (Rest, $stateParams, GetBasePath, ProcessErrors) => { - const path = GetBasePath('credential_types'); - Rest.setUrl(path); - return Rest.get() - .then((data) => data.data.results) - .catch((response) => { - ProcessErrors(null, response.data, response.status, null, { - hdr: 'Error!', - msg: `Failed to get credential types. GET returned status: ${response.status}`, - }); - }); - } - ], - ConfigData: ['ConfigService', 'ProcessErrors', - (ConfigService, ProcessErrors) => ConfigService - .getConfig() - .then(response => response) - .catch(({ data, status }) => { - ProcessErrors(null, data, status, null, { - hdr: 'Error!', - msg: `Failed to get config. GET returned status: status: ${status}`, - }); - })], - Dataset: [ - '$stateParams', - 'Wait', - 'GetBasePath', - 'QuerySet', - ($stateParams, Wait, GetBasePath, qs) => { - const searchParam = $stateParams.project_search; - const searchPath = GetBasePath('projects'); - - Wait('start'); - return qs.search(searchPath, searchParam) - .finally(() => Wait('stop')); - } - ], - resolvedModels: [ - 'ProjectModel', - (Project) => { - const models = [ - new Project(['options']), - ]; - return Promise.all(models); - }, - ], - } -}; diff --git a/awx/ui/client/features/templates/index.controller.js b/awx/ui/client/features/templates/index.controller.js deleted file mode 100644 index bfbfeb84fd48..000000000000 --- a/awx/ui/client/features/templates/index.controller.js +++ /dev/null @@ -1,19 +0,0 @@ -function IndexTemplatesController ($scope, strings, dataset) { - const vm = this; - vm.strings = strings; - vm.count = dataset.data.count; - - $scope.$on('updateCount', (e, count) => { - if (typeof count === 'number') { - vm.count = count; - } - }); -} - -IndexTemplatesController.$inject = [ - '$scope', - 'TemplatesStrings', - 'Dataset' -]; - -export default IndexTemplatesController; diff --git a/awx/ui/client/features/templates/index.js b/awx/ui/client/features/templates/index.js deleted file mode 100644 index fd0a49b45a01..000000000000 --- a/awx/ui/client/features/templates/index.js +++ /dev/null @@ -1,9 +0,0 @@ -import TemplatesStrings from './templates.strings'; - -const MODULE_NAME = 'at.features.templates'; - -angular - .module(MODULE_NAME, []) - .service('TemplatesStrings', TemplatesStrings); - -export default MODULE_NAME; diff --git a/awx/ui/client/features/templates/index.view.html b/awx/ui/client/features/templates/index.view.html deleted file mode 100644 index 7812dcaa29c8..000000000000 --- a/awx/ui/client/features/templates/index.view.html +++ /dev/null @@ -1,18 +0,0 @@ -
-
- -
- - -
-
- - -
-
-
diff --git a/awx/ui/client/features/templates/routes/organizationsTemplatesList.route.js b/awx/ui/client/features/templates/routes/organizationsTemplatesList.route.js deleted file mode 100644 index 4d306b2857c6..000000000000 --- a/awx/ui/client/features/templates/routes/organizationsTemplatesList.route.js +++ /dev/null @@ -1,73 +0,0 @@ -import { N_ } from '../../../src/i18n'; -import templatesListController from '../templatesList.controller'; -import indexController from '../index.controller'; - -const indexTemplate = require('~features/templates/index.view.html'); -const templatesListTemplate = require('~features/templates/templatesList.view.html'); - -export default { - url: "/:organization_id/job_templates", - name: 'organizations.job_templates', - data: { - activityStream: true, - activityStreamTarget: 'template' - }, - params: { - template_search: { - dynamic: true, - value: { - type: 'job_template', - order_by: 'name', - page_size: '20', - or__jobtemplate__project__organization: null, - or__jobtemplate__inventory__organization: null - }, - } - }, - ncyBreadcrumb: { - label: N_("JOB TEMPLATES") - }, - views: { - 'form': { - templateUrl: indexTemplate, - controller: indexController, - controllerAs: 'vm' - }, - 'templatesList@organizations.job_templates': { - controller: templatesListController, - templateUrl: templatesListTemplate, - controllerAs: 'vm', - } - }, - resolve: { - resolvedModels: [ - 'JobTemplateModel', - 'WorkflowJobTemplateModel', - (JobTemplate, WorkflowJobTemplate) => { - const models = [ - new JobTemplate(['options']), - new WorkflowJobTemplate(['options']), - ]; - return Promise.all(models); - }, - ], - Dataset: [ - '$stateParams', - 'Wait', - 'GetBasePath', - 'QuerySet', - ($stateParams, Wait, GetBasePath, qs) => { - const searchPath = GetBasePath('unified_job_templates'); - - const searchParam = Object.assign( - $stateParams.template_search, { - or__jobtemplate__project__organization: $stateParams.organization_id, - or__jobtemplate__inventory__organization: $stateParams.organization_id} - ); - Wait('start'); - return qs.search(searchPath, searchParam) - .finally(() => Wait('stop')); - } - ], - } -}; diff --git a/awx/ui/client/features/templates/routes/projectsTemplatesList.route.js b/awx/ui/client/features/templates/routes/projectsTemplatesList.route.js deleted file mode 100644 index be10a69b0dbd..000000000000 --- a/awx/ui/client/features/templates/routes/projectsTemplatesList.route.js +++ /dev/null @@ -1,67 +0,0 @@ -import { N_ } from '../../../src/i18n'; -import templatesListController from '../templatesList.controller'; - -const templatesListTemplate = require('~features/templates/templatesList.view.html'); - -export default { - url: "/templates", - name: 'projects.edit.templates', - searchPrefix: 'template', - params: { - template_search: { - dynamic: true, - value: { - type: 'job_template', - order_by: 'name', - page_size: '20', - jobtemplate__project: null - }, - } - }, - data: { - socket: { - groups: { - jobs: ['status_changed'] - } - } - }, - ncyBreadcrumb: { - label: N_("JOB TEMPLATES") - }, - views: { - 'related': { - controller: templatesListController, - templateUrl: templatesListTemplate, - controllerAs: 'vm' - } - }, - resolve: { - resolvedModels: [ - 'JobTemplateModel', - 'WorkflowJobTemplateModel', - (JobTemplate, WorkflowJobTemplate) => { - const models = [ - new JobTemplate(['options']), - new WorkflowJobTemplate(['options']), - ]; - return Promise.all(models); - }, - ], - Dataset: [ - '$stateParams', - 'Wait', - 'GetBasePath', - 'QuerySet', - ($stateParams, Wait, GetBasePath, qs) => { - const searchPath = GetBasePath('unified_job_templates'); - - const searchParam = _.assign($stateParams.template_search, { - jobtemplate__project: $stateParams.project_id }); - - Wait('start'); - return qs.search(searchPath, searchParam) - .finally(() => Wait('stop')); - } - ], - } -}; diff --git a/awx/ui/client/features/templates/routes/templatesList.route.js b/awx/ui/client/features/templates/routes/templatesList.route.js deleted file mode 100644 index 4b17ab471d93..000000000000 --- a/awx/ui/client/features/templates/routes/templatesList.route.js +++ /dev/null @@ -1,68 +0,0 @@ -import { N_ } from '../../../src/i18n'; -import templatesListController from '../templatesList.controller'; -import indexController from '../index.controller'; - -const indexTemplate = require('~features/templates/index.view.html'); -const templatesListTemplate = require('~features/templates/templatesList.view.html'); - -export default { - name: 'templates', - route: '/templates', - ncyBreadcrumb: { - label: N_("TEMPLATES") - }, - data: { - activityStream: true, - activityStreamTarget: 'template' - }, - params: { - template_search: { - dynamic: true, - value: { - type: 'workflow_job_template,job_template', - order_by: 'name', - page_size: '20' - }, - } - }, - searchPrefix: 'template', - views: { - '@': { - templateUrl: indexTemplate, - controller: indexController, - controllerAs: 'vm' - }, - 'templatesList@templates': { - controller: templatesListController, - templateUrl: templatesListTemplate, - controllerAs: 'vm', - } - }, - resolve: { - resolvedModels: [ - 'JobTemplateModel', - 'WorkflowJobTemplateModel', - (JobTemplate, WorkflowJobTemplate) => { - const models = [ - new JobTemplate(['options']), - new WorkflowJobTemplate(['options']), - ]; - return Promise.all(models); - }, - ], - Dataset: [ - '$stateParams', - 'Wait', - 'GetBasePath', - 'QuerySet', - ($stateParams, Wait, GetBasePath, qs) => { - const searchParam = $stateParams.template_search; - const searchPath = GetBasePath('unified_job_templates'); - - Wait('start'); - return qs.search(searchPath, searchParam) - .finally(() => Wait('stop')); - } - ], - } -}; diff --git a/awx/ui/client/features/templates/templates.strings.js b/awx/ui/client/features/templates/templates.strings.js deleted file mode 100644 index 4c53a69acd10..000000000000 --- a/awx/ui/client/features/templates/templates.strings.js +++ /dev/null @@ -1,162 +0,0 @@ -function TemplatesStrings (BaseString) { - BaseString.call(this, 'templates'); - - const { t } = this; - const ns = this.templates; - - ns.state = { - LIST_BREADCRUMB_LABEL: t.s('TEMPLATES') - }; - - ns.list = { - PANEL_TITLE: t.s('TEMPLATES'), - ADD_DD_JT_LABEL: t.s('Job Template'), - ADD_DD_WF_LABEL: t.s('Workflow Template'), - OPEN_WORKFLOW_VISUALIZER: t.s('Click here to open the workflow visualizer'), - ROW_ITEM_LABEL_DESCRIPTION: t.s('Description'), - ROW_ITEM_LABEL_ACTIVITY: t.s('Activity'), - ROW_ITEM_LABEL_INVENTORY: t.s('Inventory'), - ROW_ITEM_LABEL_PROJECT: t.s('Project'), - ROW_ITEM_LABEL_CREDENTIALS: t.s('Credentials'), - ROW_ITEM_LABEL_MODIFIED: t.s('Last Modified'), - ROW_ITEM_LABEL_RAN: t.s('Last Ran'), - }; - - ns.prompt = { - INVENTORY: t.s('Inventory'), - CREDENTIAL: t.s('Credential'), - PROMPT: t.s('PROMPT'), - OTHER_PROMPTS: t.s('Other Prompts'), - SURVEY: t.s('Survey'), - PREVIEW: t.s('Preview'), - LAUNCH: t.s('LAUNCH'), - CONFIRM: t.s('CONFIRM'), - SELECTED: t.s('SELECTED'), - NO_CREDENTIALS_SELECTED: t.s('No credentials selected'), - NO_INVENTORY_SELECTED: t.s('No inventory selected'), - REVERT: t.s('REVERT'), - CREDENTIAL_TYPE: t.s('Credential Type'), - CREDENTIAL_PASSWORD_WARNING: t.s('Credentials that require passwords on launch are not permitted for template schedules and workflow nodes. The following credentials must be removed or replaced to proceed:'), - PASSWORDS_REQUIRED_HELP: t.s('Launching this job requires the passwords listed below. Enter each password before continuing.'), - PLEASE_ENTER_PASSWORD: t.s('Please enter a password.'), - credential_passwords: { - SSH_PASSWORD: t.s('SSH Password'), - PRIVATE_KEY_PASSPHRASE: t.s('Private Key Passphrase'), - PRIVILEGE_ESCALATION_PASSWORD: t.s('Privilege Escalation Password'), - VAULT_PASSWORD: t.s('Vault Password') - }, - SHOW_CHANGES: t.s('Show Changes'), - SHOW_CHANGES_HELP: t.s('If enabled, show the changes made by Ansible tasks, where supported. This is equivalent to Ansible’s --diff mode.'), - SKIP_TAGS: t.s('Skip Tags'), - SKIP_TAGS_HELP: t.s('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 Ansible Tower documentation for details on the usage of tags.'), - JOB_TAGS: t.s('Job Tags'), - JOB_TAGS_HELP: t.s('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 Ansible Tower documentation for details on the usage of tags.'), - LIMIT: t.s('Limit'), - LIMIT_HELP: t.s('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.'), - JOB_TYPE: t.s('Job Type'), - JOB_TYPE_HELP: t.s('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.'), - VERBOSITY: t.s('Verbosity'), - VERBOSITY_HELP: t.s('Control the level of output ansible will produce as the playbook executes.'), - CHOOSE_JOB_TYPE: t.s('Choose a job type'), - CHOOSE_VERBOSITY: t.s('Choose a verbosity'), - EXTRA_VARIABLES: t.s('Extra Variables'), - EXTRA_VARIABLES_HELP: t.s('

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.

JSON:
{
"somevar": "somevalue",
"password": "magic"
}
YAML:
---
somevar: somevalue
password: magic
'), - PLEASE_ENTER_ANSWER: t.s('Please enter an answer.'), - PLEASE_SELECT_VALUE: t.s('Please select a value'), - VALID_INTEGER: t.s('Please enter an answer that is a valid integer.'), - VALID_DECIMAL: t.s('Please enter an answer that is a decimal number.'), - PLAYBOOK_RUN: t.s('Playbook Run'), - SCM_BRANCH: t.s('SCM Branch'), - SCM_BRANCH_HELP: t.s('Branch to use in job run. Project default used if blank.'), - CHECK: t.s('Check'), - NO_CREDS_MATCHING_TYPE: t.s('No Credentials Matching This Type Have Been Created'), - CREDENTIAL_TYPE_MISSING: typeLabel => t.s('This job template has a default {{typeLabel}} credential which must be included or replaced before proceeding.', { typeLabel }) - }; - - ns.alert = { - MISSING_PARAMETER: t.s('Template parameter is missing.'), - NO_PERMISSION: t.s('You do not have permission to perform this action.'), - UNKNOWN_COPY: t.s('Unable to determine this template\'s type while copying.'), - UNKNOWN_DELETE: t.s('Unable to determine this template\'s type while deleting.'), - UNKNOWN_EDIT: t.s('Unable to determine this template\'s type while editing.'), - UNKNOWN_LAUNCH: t.s('Unable to determine this template\'s type while launching.'), - UNKNOWN_SCHEDULE: t.s('Unable to determine this template\'s type while scheduling.'), - }; - - ns.error = { - HEADER: this.error.HEADER, - CALL: this.error.CALL, - EDIT: t.s('Unable to edit template.'), - DELETE: t.s('Unable to delete template.'), - LAUNCH: t.s('Unable to launch template.'), - UNKNOWN: t.s('Unable to determine template type.'), - SCHEDULE: t.s('Unable to schedule job.'), - COPY: t.s('Unable to copy template.'), - INVALID: t.s('Resources are missing from this template.') - }; - - ns.warnings = { - WORKFLOW_RESTRICTED_COPY: t.s('You do not have access to all resources used by this workflow. Resources that you don\'t have access to will not be copied and will result in an incomplete workflow.') - }; - - ns.workflows = { - INVALID_JOB_TEMPLATE: t.s('This Job Template is missing a default inventory or project. This must be addressed in the Job Template form before this node can be saved.'), - CREDENTIAL_WITH_PASS: t.s('This Job Template has a credential that requires a password. Credentials requiring passwords on launch are not permitted on workflow nodes.') - }; - - ns.workflow_maker = { - DELETE_NODE_PROMPT_TEXT: t.s('Are you sure you want to delete this workflow node?'), - KEY: t.s('KEY'), - ON_SUCCESS: t.s('On Success'), - ON_FAILURE: t.s('On Failure'), - ALWAYS: t.s('Always'), - PAUSE: t.s('Wait For Approval'), - JOB_TEMPLATE: t.s('Job Template'), - PROJECT_SYNC: t.s('Project Sync'), - INVENTORY_SYNC: t.s('Inventory Sync'), - WORKFLOW: t.s('Workflow'), - TEMPLATE: t.s('Template'), - WARNING: t.s('Warning'), - TOTAL_NODES: t.s('TOTAL NODES'), - ADD_A_NODE: t.s('ADD A NODE'), - EDIT_TEMPLATE: t.s('EDIT TEMPLATE'), - JOBS: t.s('Jobs'), - PLEASE_CLICK_THE_START_BUTTON: t.s('Please click the start button to build your workflow.'), - PLEASE_HOVER_OVER_A_TEMPLATE: t.s('Please hover over a template for additional options.'), - EDIT_LINK_TOOLTIP: t.s('Click to edit link'), - VIEW_LINK_TOOLTIP: t.s('Click to view link'), - RUN: t.s('RUN'), - CHECK: t.s('CHECK'), - SELECT: t.s('SELECT'), - DELETED: t.s('DELETED'), - START: t.s('START'), - DETAILS: t.s('DETAILS'), - TITLE: t.s('WORKFLOW VISUALIZER'), - INVENTORY_WILL_OVERRIDE: t.s('The inventory of this node will be overridden by the parent workflow inventory.'), - INVENTORY_WILL_NOT_OVERRIDE: t.s('The inventory of this node will not be overridden by the parent workflow inventory.'), - INVENTORY_PROMPT_WILL_OVERRIDE: t.s('The inventory of this node will be overridden if a parent workflow inventory is provided at launch.'), - INVENTORY_PROMPT_WILL_NOT_OVERRIDE: t.s('The inventory of this node will not be overridden if a parent workflow inventory is provided at launch.'), - ADD_LINK: t.s('ADD LINK'), - EDIT_LINK: t.s('EDIT LINK'), - VIEW_LINK: t.s('VIEW LINK'), - NEW_LINK: t.s('Please click on an available node to form a new link.'), - UNLINK: t.s('UNLINK'), - READ_ONLY_PROMPT_VALUES: t.s('The following promptable values were provided when this node was created:'), - READ_ONLY_NO_PROMPT_VALUES: t.s('No promptable values were provided when this node was created.'), - UNSAVED_CHANGES_HEADER: t.s('WARNING: UNSAVED CHANGES'), - UNSAVED_CHANGES_PROMPT_TEXT: t.s('Are you sure you want to exit the Workflow Creator without saving your changes?'), - EXIT: t.s('EXIT'), - CANCEL: t.s('CANCEL'), - SAVE_AND_EXIT: t.s('SAVE & EXIT'), - APPROVAL: t.s('Approval'), - TIMEOUT_POPOVER: t.s('The amount of time to wait before this approval step is automatically denied. Defaults to 0 for no timeout.'), - TIMED_OUT: t.s('APPROVAL TIMED OUT'), - TIMEOUT: t.s('Timeout'), - APPROVED: t.s('APPROVED'), - DENIED: t.s('DENIED') - }; -} - -TemplatesStrings.$inject = ['BaseStringService']; - -export default TemplatesStrings; diff --git a/awx/ui/client/features/templates/templatesList.controller.js b/awx/ui/client/features/templates/templatesList.controller.js deleted file mode 100644 index 8c95aa745a46..000000000000 --- a/awx/ui/client/features/templates/templatesList.controller.js +++ /dev/null @@ -1,490 +0,0 @@ -/** *********************************************** - * Copyright (c) 2018 Ansible, Inc. - * - * All Rights Reserved - ************************************************ */ -const JOB_TEMPLATE_ALIASES = ['job_template', 'Job Template']; -const WORKFLOW_TEMPLATE_ALIASES = ['workflow_job_template', 'Workflow Job Template']; - -const isJobTemplate = ({ type }) => JOB_TEMPLATE_ALIASES.indexOf(type) > -1; -const isWorkflowTemplate = ({ type }) => WORKFLOW_TEMPLATE_ALIASES.indexOf(type) > -1; -const mapChoices = choices => Object.assign(...choices.map(([k, v]) => ({[k]: v}))); - -function ListTemplatesController( - $filter, - $scope, - $state, - Alert, - Dataset, - ProcessErrors, - Prompt, - resolvedModels, - strings, - Wait, - qs, - GetBasePath, - ngToast, - $timeout -) { - const vm = this || {}; - const [jobTemplate, workflowTemplate] = resolvedModels; - - const choices = workflowTemplate.options('actions.GET.type.choices') - .concat(jobTemplate.options('actions.GET.type.choices')); - - let launchModalOpen = false; - let refreshAfterLaunchClose = false; - let pendingRefresh = false; - let refreshTimerRunning = false; - let paginateQuerySet = {}; - - vm.strings = strings; - vm.templateTypes = mapChoices(choices); - vm.activeId = parseInt($state.params.job_template_id || $state.params.workflow_job_template_id); - vm.invalidTooltip = { - popover: { - text: strings.get('error.INVALID'), - on: 'mouseenter', - icon: 'fa-exclamation', - position: 'right', - arrowHeight: 15 - } - }; - - $scope.canAddJobTemplate = jobTemplate.options('actions.POST'); - $scope.canAddWorkflowJobTemplate = workflowTemplate.options('actions.POST'); - $scope.canAdd = ($scope.canAddJobTemplate || $scope.canAddWorkflowJobTemplate); - - // smart-search - vm.list = { - iterator: 'template', - name: 'templates' - }; - vm.dataset = Dataset.data; - vm.templates = Dataset.data.results; - vm.defaultParams = $state.params.template_search; - - const toolbarSortDefault = { - label: `${strings.get('sort.NAME_ASCENDING')}`, - value: 'name' - }; - - vm.toolbarSortOptions = [ - toolbarSortDefault, - { label: `${strings.get('sort.NAME_DESCENDING')}`, value: '-name' }, - { label: `${strings.get('sort.MODIFIED_ASCENDING')}`, value: 'modified' }, - { label: `${strings.get('sort.MODIFIED_DESCENDING')}`, value: '-modified' }, - { label: `${strings.get('sort.LAST_JOB_RUN_ASCENDING')}`, value: 'last_job_run' }, - { label: `${strings.get('sort.LAST_JOB_RUN_DESCENDING')}`, value: '-last_job_run' }, - { label: `${strings.get('sort.INVENTORY_ASCENDING')}`, value: 'job_template__inventory__id' }, - { label: `${strings.get('sort.INVENTORY_DESCENDING')}`, value: '-job_template__inventory__id' }, - { label: `${strings.get('sort.PROJECT_ASCENDING')}`, value: 'jobtemplate__project__id' }, - { label: `${strings.get('sort.PROJECT_DESCENDING')}`, value: '-jobtemplate__project__id' }, - ]; - - vm.toolbarSortValue = toolbarSortDefault; - - $scope.$on('updateDataset', (event, dataset, queryset) => { - paginateQuerySet = queryset; - }); - - vm.onToolbarSort = (sort) => { - vm.toolbarSortValue = sort; - - const queryParams = Object.assign( - {}, - $state.params.template_search, - paginateQuerySet, - { order_by: sort.value } - ); - - // Update params - $state.go('.', { - template_search: queryParams - }, { notify: false, location: 'replace' }); - }; - - $scope.$watch('vm.dataset.count', () => { - $scope.$emit('updateCount', vm.dataset.count, 'templates'); - }); - - $scope.$watch('$state.params', function(newValue, oldValue) { - const job_template_id = _.get($state.params, 'job_template_id'); - const workflow_job_template_id = _.get($state.params, 'workflow_job_template_id'); - - if((job_template_id || workflow_job_template_id)) { - vm.activeId = parseInt($state.params.job_template_id || $state.params.workflow_job_template_id); - } else { - vm.activeId = ""; - } - setToolbarSort(); - }, true); - - $scope.$on(`ws-jobs`, () => { - if (!launchModalOpen) { - if (!refreshTimerRunning) { - refreshTemplates(); - } else { - pendingRefresh = true; - } - } else { - refreshAfterLaunchClose = true; - } - }); - - $scope.$on('launchModalOpen', (evt, isOpen) => { - evt.stopPropagation(); - if (!isOpen && refreshAfterLaunchClose) { - refreshAfterLaunchClose = false; - refreshTemplates(); - } - launchModalOpen = isOpen; - }); - - vm.isInvalid = (template) => { - if(isJobTemplate(template)) { - return template.project === null || (template.inventory === null && template.ask_inventory_on_launch === false); - } else { - return false; - } - }; - - vm.isPortalMode = $state.includes('portalMode'); - - vm.openWorkflowVisualizer = template => { - const name = 'templates.editWorkflowJobTemplate.workflowMaker'; - const params = { workflow_job_template_id: template.id }; - const options = { reload: true }; - - $state.go(name, params, options); - }; - - vm.deleteTemplate = template => { - if (!template) { - Alert(strings.get('error.DELETE'), strings.get('alert.MISSING_PARAMETER')); - return; - } - - if (isWorkflowTemplate(template)) { - displayWorkflowTemplateDeletePrompt(template); - } else if (isJobTemplate(template)) { - jobTemplate.getDependentResourceCounts(template.id) - .then(counts => displayJobTemplateDeletePrompt(template, counts)); - } else { - Alert(strings.get('error.UNKNOWN'), strings.get('alert.UNKNOWN_DELETE')); - } - }; - - vm.copyTemplate = template => { - if (!template) { - Alert(strings.get('error.COPY'), strings.get('alert.MISSING_PARAMETER')); - return; - } - - if (isJobTemplate(template)) { - copyJobTemplate(template); - } else if (isWorkflowTemplate(template)) { - copyWorkflowTemplate(template); - } else { - Alert(strings.get('error.UNKNOWN'), strings.get('alert.UNKNOWN_COPY')); - } - }; - - vm.getModified = template => { - const modified = _.get(template, 'modified'); - - if (!modified) { - return undefined; - } - - let html = $filter('longDate')(modified); - - const { username, id } = _.get(template, 'summary_fields.modified_by', {}); - - if (username && id) { - html += ` by ${$filter('sanitize')(username)}`; - } - - return html; - }; - - vm.buildCredentialTags = (credentials) => { - return credentials.map(credential => { - const icon = `${credential.kind}`; - const link = `/#/credentials/${credential.id}`; - const tooltip = strings.get('tooltips.VIEW_THE_CREDENTIAL'); - const value = $filter('sanitize')(credential.name); - - return { icon, link, tooltip, value }; - }); - }; - - vm.getLastRan = template => { - const lastJobRun = _.get(template, 'last_job_run'); - - if (!lastJobRun) { - return undefined; - } - - let html = $filter('longDate')(lastJobRun); - - // TODO: uncomment and update when last job run user is returned by api - // const { username, id } = _.get(template, 'summary_fields.last_job_run_by', {}); - - // if (username && id) { - // html += ` by ${$filter('sanitize')(username)}`; - //} - - return html; - }; - - vm.getType = template => { - if(isJobTemplate(template)) { - return strings.get('list.ADD_DD_JT_LABEL'); - } else { - return strings.get('list.ADD_DD_WF_LABEL');; - } - }; - - function setToolbarSort () { - const orderByValue = _.get($state.params, 'template_search.order_by'); - const sortValue = _.find(vm.toolbarSortOptions, (option) => option.value === orderByValue); - if (sortValue) { - vm.toolbarSortValue = sortValue; - } else { - vm.toolbarSortValue = toolbarSortDefault; - } - } - - function refreshTemplates() { - Wait('start'); - let path = GetBasePath('unified_job_templates'); - qs.search(path, $state.params.template_search, { 'X-WS-Session-Quiet': true }) - .then(function(searchResponse) { - vm.dataset = searchResponse.data; - vm.templates = vm.dataset.results; - }) - .finally(() => Wait('stop')); - pendingRefresh = false; - refreshTimerRunning = true; - $timeout(() => { - if (pendingRefresh) { - refreshTemplates(); - } else { - refreshTimerRunning = false; - } - }, 5000); - } - - function createErrorHandler(path, action) { - return ({ data, status }) => { - const hdr = strings.get('error.HEADER'); - const msg = strings.get('error.CALL', { path, action, status }); - ProcessErrors($scope, data, status, null, { hdr, msg }); - }; - } - - function copyJobTemplate(template) { - Wait('start'); - jobTemplate - .create('get', template.id) - .then(model => model.copy()) - .then((copiedJT) => { - ngToast.success({ - content: ` -
-
- -
-
- ${strings.get('SUCCESSFUL_CREATION', copiedJT.name)} -
-
`, - dismissButton: false, - dismissOnTimeout: true - }); - refreshTemplates(); - }) - .catch(createErrorHandler('copy job template', 'POST')) - .finally(() => Wait('stop')); - } - - function copyWorkflowTemplate(template) { - Wait('start'); - workflowTemplate - .create('get', template.id) - .then(model => model.extend('get', 'copy')) - .then(model => { - const action = () => { - $('#prompt-modal').modal('hide'); - Wait('start'); - model.copy() - .then((copiedWFJT) => { - ngToast.success({ - content: ` -
-
- -
-
- ${strings.get('SUCCESSFUL_CREATION', copiedWFJT.name)} -
-
`, - dismissButton: false, - dismissOnTimeout: true - }); - refreshTemplates(); - }) - .catch(createErrorHandler('copy workflow', 'POST')) - .finally(() => Wait('stop')); - }; - - if (model.get('related.copy.can_copy_without_user_input')) { - action(); - } else if (model.get('related.copy.can_copy')) { - Prompt({ - action, - actionText: strings.get('COPY'), - body: buildWorkflowCopyPromptHTML(model.get('related.copy')), - class: 'Modal-primaryButton', - hdr: strings.get('listActions.COPY', template.name), - }); - } else { - Alert(strings.get('error.COPY'), strings.get('alert.NO_PERMISSION')); - } - }) - .catch(createErrorHandler('copy workflow', 'GET')) - .finally(() => Wait('stop')); - } - - function handleSuccessfulDelete(template) { - const { page } = _.get($state.params, 'template_search'); - let reloadListStateParams = null; - - if (vm.templates.length === 1 && page && page !== '1') { - reloadListStateParams = _.cloneDeep($state.params); - const pageNumber = (parseInt(reloadListStateParams.template_search.page, 0) - 1); - reloadListStateParams.template_search.page = pageNumber.toString(); - } - - if (parseInt($state.params.job_template_id, 0) === template.id) { - $state.go('templates', reloadListStateParams, { reload: true }); - } else if (parseInt($state.params.workflow_job_template_id, 0) === template.id) { - $state.go('templates', reloadListStateParams, { reload: true }); - } else { - $state.go('.', reloadListStateParams, { reload: true }); - } - } - - function displayJobTemplateDeletePrompt(template, counts) { - Prompt({ - action() { - $('#prompt-modal').modal('hide'); - Wait('start'); - jobTemplate - .request('delete', template.id) - .then(() => handleSuccessfulDelete(template)) - .catch(createErrorHandler('delete template', 'DELETE')) - .finally(() => Wait('stop')); - }, - hdr: strings.get('DELETE'), - resourceName: $filter('sanitize')(template.name), - body: buildJobTemplateDeletePromptHTML(counts), - }); - } - - function displayWorkflowTemplateDeletePrompt(template) { - Prompt({ - action() { - $('#prompt-modal').modal('hide'); - Wait('start'); - workflowTemplate - .request('delete', template.id) - .then(() => handleSuccessfulDelete(template)) - .catch(createErrorHandler('delete template', 'DELETE')) - .finally(() => Wait('stop')); - }, - hdr: strings.get('DELETE'), - resourceName: $filter('sanitize')(template.name), - body: strings.get('deleteResource.CONFIRM', 'workflow template'), - }); - } - - function buildJobTemplateDeletePromptHTML(counts) { - const buildCount = count => `${count}`; - const buildLabel = label => ` - ${$filter('sanitize')(label)}`; - const buildCountLabel = ({ count, label }) => `
- ${buildLabel(label)}${buildCount(count)}
`; - - const displayedCounts = counts.filter(({ count }) => count > 0); - - const html = ` - ${displayedCounts.length ? strings.get('deleteResource.USED_BY', 'job template') : ''} - ${strings.get('deleteResource.CONFIRM', 'job template')} - ${displayedCounts.map(buildCountLabel).join('')} - `; - - return html; - } - - function buildWorkflowCopyPromptHTML(data) { - const pull = (data, param) => _.get(data, param, []).map($filter('sanitize')); - - const credentials = pull(data, 'credentials_unable_to_copy'); - const inventories = pull(data, 'inventories_unable_to_copy'); - const templates = pull(data, 'templates_unable_to_copy'); - - const html = ` -
- ${strings.get('warnings.WORKFLOW_RESTRICTED_COPY')} -
-
- ${templates.length ? `
Unified Job Templates
    ` : ''} - ${templates.map(item => `
  • ${item}
  • `).join('')} - ${templates.length ? `
` : ''} -
-
- ${credentials.length ? `
Credentials
    ` : ''} - ${credentials.map(item => `
  • ${item}
  • `).join('')} - ${credentials.length ? `
` : ''} -
-
- ${inventories.length ? `
Inventories
    ` : ''} - ${inventories.map(item => `
  • ${item}
  • `).join('')} - ${inventories.length ? `
` : ''} -
- `; - - return html; - } - - vm.isCollapsed = true; - - vm.onCollapse = () => { - vm.isCollapsed = true; - }; - - vm.onExpand = () => { - vm.isCollapsed = false; - }; -} - -ListTemplatesController.$inject = [ - '$filter', - '$scope', - '$state', - 'Alert', - 'Dataset', - 'ProcessErrors', - 'Prompt', - 'resolvedModels', - 'TemplatesStrings', - 'Wait', - 'QuerySet', - 'GetBasePath', - 'ngToast', - '$timeout' -]; - -export default ListTemplatesController; diff --git a/awx/ui/client/features/templates/templatesList.view.html b/awx/ui/client/features/templates/templatesList.view.html deleted file mode 100644 index 8ed34895a71c..000000000000 --- a/awx/ui/client/features/templates/templatesList.view.html +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - -
-
-
- - - - - - -
-
- - - - - - - - -
-
-
- - - - - - - - - - -
-
- - - - - -
-
-
-
- - -
diff --git a/awx/ui/client/features/users/index.js b/awx/ui/client/features/users/index.js deleted file mode 100644 index b8f6a8052ff7..000000000000 --- a/awx/ui/client/features/users/index.js +++ /dev/null @@ -1,8 +0,0 @@ -import atFeaturesUsersTokens from '~features/users/tokens'; - -const MODULE_NAME = 'at.features.users'; - -angular - .module(MODULE_NAME, [atFeaturesUsersTokens]); - -export default MODULE_NAME; diff --git a/awx/ui/client/features/users/tokens/index.js b/awx/ui/client/features/users/tokens/index.js deleted file mode 100644 index 6d635b572d0b..000000000000 --- a/awx/ui/client/features/users/tokens/index.js +++ /dev/null @@ -1,9 +0,0 @@ -import TokensStrings from './tokens.strings'; - -const MODULE_NAME = 'at.features.users.tokens'; - -angular - .module(MODULE_NAME, []) - .service('TokensStrings', TokensStrings); - -export default MODULE_NAME; diff --git a/awx/ui/client/features/users/tokens/tokens.strings.js b/awx/ui/client/features/users/tokens/tokens.strings.js deleted file mode 100644 index 2f84642eaecb..000000000000 --- a/awx/ui/client/features/users/tokens/tokens.strings.js +++ /dev/null @@ -1,48 +0,0 @@ -function TokensStrings (BaseString) { - BaseString.call(this, 'tokens'); - - const { t } = this; - const ns = this.tokens; - - ns.state = { - LIST_BREADCRUMB_LABEL: t.s('TOKENS'), - ADD_BREADCRUMB_LABEL: t.s('CREATE TOKEN'), - USER_LIST_BREADCRUMB_LABEL: t.s('TOKENS') - }; - - ns.tab = { - DETAILS: t.s('Details') - }; - - ns.add = { - PANEL_TITLE: t.s('CREATE TOKEN'), - APP_PLACEHOLDER: t.s('SELECT AN APPLICATION'), - SCOPE_HELP_TEXT: t.s('Specify a scope for the token\'s access'), - TOKEN_MODAL_HEADER: t.s('TOKEN INFORMATION'), - TOKEN_LABEL: t.s('TOKEN'), - REFRESH_TOKEN_LABEL: t.s('REFRESH TOKEN'), - TOKEN_EXPIRES_LABEL: t.s('EXPIRES'), - ERROR_HEADER: t.s('COULD NOT CREATE TOKEN'), - ERROR_BODY_LABEL: t.s('Returned status:'), - LAST_USED_LABEL: t.s('by'), - DELETE_ACTION_LABEL: t.s('DELETE'), - SCOPE_PLACEHOLDER: t.s('Select a scope'), - SCOPE_READ_LABEL: t.s('Read'), - SCOPE_WRITE_LABEL: t.s('Write'), - APPLICATION_HELP_TEXT: t.s('Leaving this field blank will result in the creation of a Personal Access Token which is not linked to an Application.') - }; - - ns.list = { - ROW_ITEM_LABEL_DESCRIPTION: t.s('DESCRIPTION'), - ROW_ITEM_LABEL_EXPIRED: t.s('EXPIRATION'), - ROW_ITEM_LABEL_USED: t.s('LAST USED'), - ROW_ITEM_LABEL_SCOPE: t.s('SCOPE'), - ROW_ITEM_LABEL_APPLICATION: t.s('APPLICATION'), - PERSONAL_ACCESS_TOKEN: t.s('Personal Access Token'), - HEADER: appName => t.s('{{ appName }} Token', { appName }), - }; -} - -TokensStrings.$inject = ['BaseStringService']; - -export default TokensStrings; diff --git a/awx/ui/client/features/users/tokens/users-tokens-add-application.route.js b/awx/ui/client/features/users/tokens/users-tokens-add-application.route.js deleted file mode 100644 index 05e8a6dfec3f..000000000000 --- a/awx/ui/client/features/users/tokens/users-tokens-add-application.route.js +++ /dev/null @@ -1,72 +0,0 @@ -export default { - name: 'users.edit.tokens.add.application', - url: '/application?selected', - searchPrefix: 'application', - params: { - application_search: { - value: { - page_size: 5, - order_by: 'name' - }, - dynamic: true, - squash: '' - } - }, - data: { - basePath: 'applications' - }, - ncyBreadcrumb: { - skip: true - }, - views: { - 'application@users.edit.tokens.add': { - templateProvider: (ListDefinition, generateList) => { - const html = generateList.build({ - mode: 'lookup', - list: ListDefinition, - input_type: 'radio' - }); - - return `${html}`; - } - } - }, - resolve: { - ListDefinition: [() => ({ - name: 'applications', - iterator: 'application', - hover: true, - index: false, - fields: { - name: { - key: true, - label: 'Name', - columnClass: 'col-sm-6', - awToolTip: '{{application.description | sanitize}}', - dataPlacement: 'top' - }, - organization: { - label: 'Organization', - columnClass: 'col-sm-6', - key: false, - ngBind: 'application.summary_fields.organization.name', - sourceModel: 'organization', - includeModal: true - } - } - })], - Dataset: ['QuerySet', 'GetBasePath', '$stateParams', 'ListDefinition', - (qs, GetBasePath, $stateParams, list) => qs.search( - GetBasePath('applications'), - $stateParams[`${list.iterator}_search`] - ) - ] - }, - onExit ($state) { - if ($state.transition) { - $('#form-modal').modal('hide'); - $('.modal-backdrop').remove(); - $('body').removeClass('modal-open'); - } - } -}; diff --git a/awx/ui/client/features/users/tokens/users-tokens-add.controller.js b/awx/ui/client/features/users/tokens/users-tokens-add.controller.js deleted file mode 100644 index dcd47d2e37ab..000000000000 --- a/awx/ui/client/features/users/tokens/users-tokens-add.controller.js +++ /dev/null @@ -1,123 +0,0 @@ -function AddTokensController ( - models, $state, strings, Alert, Wait, - $filter, ProcessErrors, $scope, i18n -) { - const vm = this || {}; - const { application, token, user } = models; - - vm.mode = 'add'; - vm.strings = strings; - vm.panelTitle = strings.get('add.PANEL_TITLE'); - - vm.form = { - application: { - type: 'field', - label: i18n._('Application'), - id: 'application', - required: false, - help_text: strings.get('add.APPLICATION_HELP_TEXT'), - _resource: 'application', - _route: 'users.edit.tokens.add.application', - _model: application, - _placeholder: strings.get('add.APP_PLACEHOLDER') - }, - description: { - type: 'String', - label: i18n._('Description'), - id: 'description', - required: false - }, - scope: { - choices: [ - [null, ''], - ['read', strings.get('add.SCOPE_READ_LABEL')], - ['write', strings.get('add.SCOPE_WRITE_LABEL')] - ], - help_text: strings.get('add.SCOPE_HELP_TEXT'), - id: 'scope', - label: i18n._('Scope'), - required: true, - _component: 'at-input-select', - _data: [ - [null, ''], - ['read', strings.get('add.SCOPE_READ_LABEL')], - ['write', strings.get('add.SCOPE_WRITE_LABEL')] - ], - _exp: 'choice[1] for (index, choice) in state._data', - _format: 'selectFromOptions' - } - }; - - vm.form.save = payload => { - const postToken = _.has(payload, 'application') ? - user.postAuthorizedTokens({ - id: $state.params.user_id, - payload - }) : token.request('post', { data: payload }); - - return postToken - .then(({ data }) => { - const refreshHTML = data.refresh_token ? - `
-
- ${strings.get('add.REFRESH_TOKEN_LABEL')} -
-
- ${data.refresh_token} -
-
` : ''; - - Alert(strings.get('add.TOKEN_MODAL_HEADER'), ` -
-
- ${strings.get('add.TOKEN_LABEL')} -
-
- ${data.token} -
-
- ${refreshHTML} -
-
- ${strings.get('add.TOKEN_EXPIRES_LABEL')} -
-
- ${$filter('longDate')(data.expires)} -
-
- `, null, null, null, null, null, true); - Wait('stop'); - }) - .catch(({ data, status }) => { - ProcessErrors(null, data, status, null, { - hdr: strings.get('add.ERROR_HEADER'), - msg: `${strings.get('add.ERROR_BODY_LABEL')} ${status}` - }); - Wait('stop'); - }); - }; - - vm.form.onSaveSuccess = () => { - $state.go('^', { user_id: $state.params.user_id }, { reload: true }); - }; - - $scope.$watch('application', () => { - if ($scope.application) { - vm.form.application._idFromModal = $scope.application; - } - }); -} - -AddTokensController.$inject = [ - 'resolvedModels', - '$state', - 'TokensStrings', - 'Alert', - 'Wait', - '$filter', - 'ProcessErrors', - '$scope', - 'i18n' -]; - -export default AddTokensController; diff --git a/awx/ui/client/features/users/tokens/users-tokens-add.partial.html b/awx/ui/client/features/users/tokens/users-tokens-add.partial.html deleted file mode 100644 index 25bd01a21a62..000000000000 --- a/awx/ui/client/features/users/tokens/users-tokens-add.partial.html +++ /dev/null @@ -1,17 +0,0 @@ -
- - - - - - - - - - - - - - - - diff --git a/awx/ui/client/features/users/tokens/users-tokens-add.route.js b/awx/ui/client/features/users/tokens/users-tokens-add.route.js deleted file mode 100644 index 43a663433f64..000000000000 --- a/awx/ui/client/features/users/tokens/users-tokens-add.route.js +++ /dev/null @@ -1,62 +0,0 @@ -import { N_ } from '../../../src/i18n'; -import AddController from './users-tokens-add.controller'; - -const addTemplate = require('~features/users/tokens/users-tokens-add.partial.html'); - -function TokensDetailResolve ($q, Application, Token, User) { - const promises = {}; - - promises.application = new Application('options'); - promises.token = new Token('options'); - promises.user = new User('options'); - - return $q.all(promises); -} - -TokensDetailResolve.$inject = [ - '$q', - 'ApplicationModel', - 'TokenModel', - 'UserModel' -]; - -function isMeResolve ($rootScope, $stateParams, $state) { - // The user should not be able to add tokens for users other than - // themselves. Adding this redirect so that a user is not able to - // visit the add-token URL directly for a different user. - if (_.has($stateParams, 'user_id') && Number($stateParams.user_id) !== $rootScope.current_user.id) { - $state.go('users'); - } -} - -isMeResolve.$inject = [ - '$rootScope', - '$stateParams', - '$state' -]; - -export default { - url: '/add-token', - name: 'users.edit.tokens.add', - params: { - }, - data: { - activityStream: true, - activityStreamTarget: 'o_auth2_access_token', - noActivityStreamID: true - }, - ncyBreadcrumb: { - label: N_('CREATE TOKEN') - }, - views: { - 'preFormView@users': { - templateUrl: addTemplate, - controller: AddController, - controllerAs: 'vm' - } - }, - resolve: { - resolvedModels: TokensDetailResolve, - isMe: isMeResolve - } -}; diff --git a/awx/ui/client/features/users/tokens/users-tokens-list.controller.js b/awx/ui/client/features/users/tokens/users-tokens-list.controller.js deleted file mode 100644 index 27ae0514c599..000000000000 --- a/awx/ui/client/features/users/tokens/users-tokens-list.controller.js +++ /dev/null @@ -1,174 +0,0 @@ -/** *********************************************** - * Copyright (c) 2018 Ansible, Inc. - * - * All Rights Reserved - ************************************************ */ -function ListTokensController ( - $filter, - $scope, - $state, - Dataset, - strings, - ProcessErrors, - GetBasePath, - Prompt, - Wait, - models -) { - const vm = this || {}; - const { token } = models; - - vm.strings = strings; - vm.activeId = $state.params.token_id; - - $scope.canAdd = true; - - // smart-search - const name = 'tokens'; - const iterator = 'token'; - let paginateQuerySet = {}; - - vm.token_dataset = Dataset.data; - vm.tokens = Dataset.data.results; - vm.list = { iterator, name, basePath: 'tokens' }; - vm.basePath = `${GetBasePath('users')}${$state.params.user_id}/tokens`; - - $scope.$on('updateDataset', (e, dataset, queryset) => { - vm.token_dataset = dataset; - vm.tokens = dataset.results; - paginateQuerySet = queryset; - }); - - $scope.$watchCollection('$state.params', () => { - setToolbarSort(); - }); - - const toolbarSortDefault = { - label: `${strings.get('sort.NAME_ASCENDING')}`, - value: 'application__name' - }; - - vm.toolbarSortOptions = [ - toolbarSortDefault, - { label: `${strings.get('sort.NAME_DESCENDING')}`, value: '-application__name' }, - { label: `${strings.get('sort.CREATED_ASCENDING')}`, value: 'created' }, - { label: `${strings.get('sort.CREATED_DESCENDING')}`, value: '-created' }, - { label: `${strings.get('sort.MODIFIED_ASCENDING')}`, value: 'modified' }, - { label: `${strings.get('sort.MODIFIED_DESCENDING')}`, value: '-modified' }, - { label: `${strings.get('sort.EXPIRES_ASCENDING')}`, value: 'expires' }, - { label: `${strings.get('sort.EXPIRES_DESCENDING')}`, value: '-expires' } - ]; - - function setToolbarSort () { - const orderByValue = _.get($state.params, 'token_search.order_by'); - const sortValue = _.find(vm.toolbarSortOptions, (option) => option.value === orderByValue); - if (sortValue) { - vm.toolbarSortValue = sortValue; - } else { - vm.toolbarSortValue = toolbarSortDefault; - } - } - - vm.onToolbarSort = (sort) => { - vm.toolbarSortValue = sort; - const queryParams = Object.assign( - {}, - $state.params.token_search, - paginateQuerySet, - { order_by: sort.value } - ); - - // Update URL with params - $state.go('.', { - token_search: queryParams - }, { notify: false, location: 'replace' }); - }; - - vm.getScopeString = str => { - if (str === 'Read') { - return vm.strings.get('add.SCOPE_READ_LABEL'); - } else if (str === 'Write') { - return vm.strings.get('add.SCOPE_WRITE_LABEL'); - } - - return undefined; - }; - - vm.getLastUsed = tokenToCheck => { - const lastUsed = _.get(tokenToCheck, 'last_used'); - - if (!lastUsed) { - return undefined; - } - - let html = $filter('longDate')(lastUsed); - - const { username, id } = _.get(tokenToCheck, 'summary_fields.last_used', {}); - - if (username && id) { - html += ` ${strings.get('add.LAST_USED_LABEL')} ${$filter('sanitize')(username)}`; - } - - return html; - }; - - vm.deleteToken = (tok) => { - const action = () => { - $('#prompt-modal').modal('hide'); - Wait('start'); - token.request('delete', tok.id) - .then(() => { - let reloadListStateParams = null; - - if ($scope.vm.tokens.length === 1 && $state.params.token_search && - !_.isEmpty($state.params.token_search.page) && - $state.params.token_search.page !== '1') { - const page = `${(parseInt(reloadListStateParams - .token_search.page, 10) - 1)}`; - reloadListStateParams = _.cloneDeep($state.params); - reloadListStateParams.token_search.page = page; - } - - if (parseInt($state.params.token_id, 10) === tok.id) { - $state.go('^', reloadListStateParams, { reload: true }); - } else { - $state.go('.', reloadListStateParams, { reload: true }); - } - }).catch(({ data, status }) => { - ProcessErrors($scope, data, status, null, { - hdr: strings.get('error.HEADER'), - msg: strings.get('error.CALL', { path: `${GetBasePath('tokens')}${tok.id}`, status }) - }); - }).finally(() => { - Wait('stop'); - }); - }; - - const deleteModalBody = `
${strings.get('deleteResource.CONFIRM', 'token')}
`; - - Prompt({ - hdr: strings.get('deleteResource.HEADER'), - resourceName: _.has(tok, 'summary_fields.application.name') ? - strings.get('list.HEADER', tok.summary_fields.application.name) : - strings.get('list.PERSONAL_ACCESS_TOKEN'), - body: deleteModalBody, - action, - actionText: strings.get('add.DELETE_ACTION_LABEL') - }); - }; -} - -ListTokensController.$inject = [ - '$filter', - '$scope', - '$state', - 'Dataset', - 'TokensStrings', - 'ProcessErrors', - 'GetBasePath', - 'Prompt', - 'Wait', - 'resolvedModels' -]; - -export default ListTokensController; diff --git a/awx/ui/client/features/users/tokens/users-tokens-list.partial.html b/awx/ui/client/features/users/tokens/users-tokens-list.partial.html deleted file mode 100644 index 3b15855bd7f4..000000000000 --- a/awx/ui/client/features/users/tokens/users-tokens-list.partial.html +++ /dev/null @@ -1,72 +0,0 @@ -
- - -
- -
-
- - - - -
- - - - - - - - - - - - -
-
- - -
-
-
- - \ No newline at end of file diff --git a/awx/ui/client/features/users/tokens/users-tokens-list.route.js b/awx/ui/client/features/users/tokens/users-tokens-list.route.js deleted file mode 100644 index e370f6fb24e0..000000000000 --- a/awx/ui/client/features/users/tokens/users-tokens-list.route.js +++ /dev/null @@ -1,65 +0,0 @@ -import { N_ } from '../../../src/i18n'; - -import ListController from './users-tokens-list.controller'; - -const listTemplate = require('~features/users/tokens/users-tokens-list.partial.html'); - -function TokensListResolve ($q, Token) { - const promises = {}; - - promises.token = new Token('options'); - - return $q.all(promises); -} - -TokensListResolve.$inject = [ - '$q', - 'TokenModel', -]; - -export default { - url: '/tokens', - name: 'users.edit.tokens', - ncyBreadcrumb: { - label: N_('TOKENS') - }, - views: { - related: { - templateUrl: listTemplate, - controller: ListController, - controllerAs: 'vm' - } - }, - data: { - activityStream: true, - activityStreamTarget: 'o_auth2_access_token', - noActivityStreamID: true - }, - searchPrefix: 'token', - params: { - token_search: { - value: { - page_size: 10, - order_by: 'application__name' - } - } - }, - resolve: { - resolvedModels: TokensListResolve, - Dataset: [ - '$stateParams', - 'Wait', - 'GetBasePath', - 'QuerySet', - ($stateParams, Wait, GetBasePath, qs) => { - const searchParam = $stateParams.token_search; - const searchPath = `${GetBasePath('users')}${$stateParams.user_id}/tokens`; - Wait('start'); - return qs.search(searchPath, searchParam) - .finally(() => { - Wait('stop'); - }); - } - ], - } -}; diff --git a/awx/ui/client/index.template.ejs b/awx/ui/client/index.template.ejs deleted file mode 100644 index aafb1b9489ac..000000000000 --- a/awx/ui/client/index.template.ejs +++ /dev/null @@ -1,163 +0,0 @@ - - - - - - - - - - <% htmlWebpackPlugin.files.css.forEach(file => {%> - - <% }) %> - <% htmlWebpackPlugin.files.js.forEach(file => {%> - - <% }) %> - - - - - - -
-
-
-
- - - - - - - - - - - - - - - - -
-
-

working...

-
-
-
- - - diff --git a/awx/ui/client/installing.template.ejs b/awx/ui/client/installing.template.ejs deleted file mode 100644 index 432c42b48b57..000000000000 --- a/awx/ui/client/installing.template.ejs +++ /dev/null @@ -1,34 +0,0 @@ - - - - {% load staticfiles %} - - - - - - - <% htmlWebpackPlugin.files.css.forEach(file => {%> - - <% }) %> - <% htmlWebpackPlugin.files.js.forEach(file => {%> - - <% }) %> - - - -
- - is Upgrading - - -

is currently upgrading.

-

This page will refresh when complete.

-
-
- - \ No newline at end of file diff --git a/awx/ui/client/legacy/styles/angular-scheduler.less b/awx/ui/client/legacy/styles/angular-scheduler.less deleted file mode 100644 index 10216c2056a0..000000000000 --- a/awx/ui/client/legacy/styles/angular-scheduler.less +++ /dev/null @@ -1,220 +0,0 @@ -/*************************************************************************** - * Copyright (c) 2015 Ansible, Inc. - * - * Styling for angular-scheduler - * - */ - - /* - #schedules-form-container -inventory group add/edit dialog - */ - -#schedules-tab { - position: relative; - top: 0; - left: 0; -} - -#schedules-overlay { - display: none; - position: absolute; - top: 0; - left: 0; - z-index: 100; - background-color: @black; - opacity: 0; -} - -#schedules-list { - overflow-x: hidden; - overflow-y: auto; -} - -#schedules-form-container { - position: absolute; - top: 0; - left: 0; - display: none; - border: 1px solid #e5e5e5; - border-radius: 4px; - box-shadow: 3px 3px 6px 0 #666; - padding: 0 10px 15px 8px; - background-color: @white; - z-index: 200; -} - -#schedules-title { - border-bottom: 1px solid #e5e5e5; - padding-bottom: 8px; - margin-bottom: 10px; - margin-top: 0; - h4 { - display: inline-block; - margin: 0; - } - button { - display: inline-block; - } -} - -#schedules-form-container-body { - overflow-x: hidden; - overflow-y: auto; -} - -#schedules-form .form-group { - margin-bottom: 15px; -} - -#schedules-buttons { - height: 46px; - padding-top: 10px; - text-align: right; - border-top: 1px solid #A6C9E2; - margin-top: 5px; - a { - margin-right: 8px; - font-size: 12px; - } -} - -#schedules-detail { - display: none; -} - -#scheduler-modal-dialog, #schedules-form-container { - display: none; - overflow-x: hidden; - overflow-y: auto; - padding-top: 25px; - - form { - width: 100%; - } - - .sublabel { - font-weight: normal; - } - - #occurrence-label { - display: inline-block; - } - - .occurrence-list { - border: 1px solid @well-border; - padding: 8px 10px; - border-radius: 4px; - background-color: @well; - list-style: none; - margin-bottom: 5px; - } - - #date-choice { - display: inline-block; - margin-left: 15px; - font-size: 12px; - - .label-inline { - display: inline-block; - vertical-align: middle; - } - input { - margin-bottom: 2px; - height: 11px; - width: 10px; - } - .label-inline:first-child { - padding-bottom: 2px; - margin-right: 10px; - } - .label-inline:nth-child(3) { - margin-right: 10px; - } - } - - .ui-widget input { - font-size: 12px; - font-weight: normal; - text-align: center; - } - .scheduler-time-spinner { - width: 40px; - height: 24px; - } - .scheduler-spinner { - width: 50px; - height: 24px; - } - .fmt-help { - font-size: 12px; - font-weight: normal; - color: #999; - padding-left: 10px; - } - .error { - color: #dd1b16; - font-size: 12px; - margin-bottom: 0; - margin-top: 0; - padding-top: 3px; - } - .error-pull-up { - position: relative; - top: -20px; - } - .red-text { - color: #dd1b16; - } - .help-text { - font-size: 12px; - font-weight: normal; - color: #999; - margin-top: 5px; - } - .inline-label { - margin-left: 10px; - } - #scheduler-buttons { - margin-top: 20px; - } - .no-label { - padding-top: 25px; - } - .padding-top-slim { - padding-top: 5px; - } - .option-pad-left { - padding-left: 15px; - } - .option-pad-top { - padding-top: 15px; - } - .option-pad-bottom { - padding-bottom: 15px; - } - #monthlyOccurrence, #monthlyWeekDay { - margin-top: 5px; - } - select { - width: 100%; - } - .occurrence-list { - border: 1px solid @well-border; - padding: 8px 10px; - border-radius: 4px; - background-color: @well; - list-style: none; - margin-bottom: 5px; - } - - #weekdaySelect .btn-default:hover, - #weekdaySelect .btn-default:focus { - background-color: #fff; - -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); - box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); - } - - #weekdaySelect .btn-default.active:hover { - background-color: #e0e0e0; - } -} diff --git a/awx/ui/client/legacy/styles/animations.less b/awx/ui/client/legacy/styles/animations.less deleted file mode 100644 index 98a5a1f6f8ee..000000000000 --- a/awx/ui/client/legacy/styles/animations.less +++ /dev/null @@ -1,27 +0,0 @@ -/********************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * animations.css - * - * custom animation mixins for ansible-ui - * - */ - - -.pulsate(@duration: 1.5s) { - -webkit-animation:pulsate @duration linear infinite alternate; - -moz-animation:pulsate @duration linear infinite alternate; - animation:pulsate @duration linear infinite alternate; -} -@-webkit-keyframes pulsate { - 0% { -moz-transform: scale(.3); opacity: .2; } - 100% { -moz-transform: scale(1.1); opacity: 1; } -} -@-webkit-keyframes pulsate { - 0% { -webkit-transform: scale(.3); opacity: .2; } - 100% { -webkit-transform: scale(1.1); opacity: 1; } -} -@keyframes pulsate { - 0% { -webkit-transform: scale(.3); transform:scale(.3); opacity: .2;} - 100% { -webkit-transform: scale(1.1); transform:scale(1.1); opacity: 1;} -} diff --git a/awx/ui/client/legacy/styles/ansible-ui.less b/awx/ui/client/legacy/styles/ansible-ui.less deleted file mode 100644 index 6b8b788c4cf0..000000000000 --- a/awx/ui/client/legacy/styles/ansible-ui.less +++ /dev/null @@ -1,2268 +0,0 @@ -/********************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * ansible-ui.css - * - * custom styles for ansible-ui - * - */ - -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 400; - src: url(/static/assets/OpenSans-Regular.ttf); -} - -@font-face { - font-family: 'Open Sans'; - font-style: bold; - font-weight: 600; - src: url(/static/assets/OpenSans-Bold.ttf); -} - -/* Helper Classes */ -.pad-right-sm { padding-right: 10px; } -.pad-left-md { padding-left: 30px; } -.pad-left-sm { padding-left: 10px; } -.pad-left-lg { padding-left: 50px; } -.normal-weight { font-weight: normal; } -.small-text { font-size: 12px; font-weight: normal; } -.no-bullets { list-style: none; } -.nowrap { white-space: nowrap; } -.capitalize { text-transform: capitalize; } -.grey-txt { color: @grey; } -.text-center { text-align: center !important; } -.cursor-pointer { cursor: pointer } - -.red-txt, -a.red-txt:visited, -a.red-txt:hover, -a.red-txt:active { - color: @red; -} - -/* Used on inventory groups/hosts lists for long names */ -.ellipsis { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -blockquote { - font-size: 14px; -} - -.group-name { - display: inline-block; - width: 85%; -} - -a { - color: @blue-link; - text-decoration: none; -} - -a:hover, -a:focus { - color: @blue-dark; - text-decoration: none; -} -.btn{ - text-transform: uppercase; -} -/* Old style TB default button with grey background */ -.btn-grey { - color: @default-data-txt; - background-color: @d7grey; - border-color: @d7grey; -} - -.btn-grey:hover { - background-color: @default-bg; -} - -#cowsay { - padding-left: 30px; - max-width: 340px; - background-color: white; - border-style: none; -} - - -#about-modal-titlelogo{ - margin-bottom: 10px; - width: 160px; - height: 53px; -} - -#copyright-text{ - margin-top: 10px; - margin-bottom: 10px; -} - -/* Make buttons appear to be disabled, but allow mouse events */ -.btn-disabled { - opacity: 1; - -webkit-box-shadow: none; - box-shadow: none; -} - -.btn-disabled { - cursor: not-allowed; -} - -/* Bring primary (blue) buttons in line with link colors */ -.btn-primary { - background-color: @default-link; -} - -.btn-primary:hover { - background-color: @default-link-hov; -} - -/* List Actions column */ -.actions { - a { - font-size: 18px; - } - a:last-child { - margin-right: 0; - } - a:hover { - cursor: pointer; - } - .dropdown .caret { - border-top-color: @blue-link; - } -} - -// removing all the pesky outlines on buttons/links/etc. -a:focus, -a:active, -button:focus, -button:active, -i:focus, -i:active, -.btn:focus, -.btn:active:focus { - outline: 0; -} - -.jqstooltip{ - background-color: black !important; - border-radius:4px; - border: 1px solid black; -} - -.smart-status-tooltip{ - font-size: 12px; - font-family: 'Open Sans'; - background-color: black; - border-radius:4px; - span { - padding: 3px; - } -} - -.IdleModal-remainingSeconds{ - color: @default-err; -} - -#configure-schedules-tab { - position: relative; - top: 0; - left: 0; -} - -#configure-schedules-overlay { - display: none; - position: absolute; - top: 0; - left: 0; - z-index: 100; - background-color: @black; - opacity: 0; - } - -#configure-schedules-buttons{ - height: 46px; - padding-top: 10px; - text-align: right; - border-top: 1px solid @default-border; - margin-top: 5px; - a { - margin-right: 8px; - font-size: 12px; - } -} - -#configure-schedules-form-container { - position: absolute; - top: 0; - left: 0; - display: none; - border: 1px solid @default-border; - border-radius: 4px; - box-shadow: 3px 3px 6px 0 @default-dark; - padding: 0 10px 15px 8px; - background-color: @white; - z-index: 200; -} - -#configure-schedules-title { - border-bottom: 1px solid @default-border; - padding-bottom: 8px; - margin-bottom: 10px; - margin-top: 0; - h4 { - display: inline-block; - margin: 0; - } - button { - display: inline-block; - } -} - -#configure-schedules-list { - overflow-x: hidden; - overflow-y: auto; -} - -#configure-schedules-overlay { - display: none; - position: absolute; - top: 0; - left: 0; - z-index: 100; - background-color: @black; - opacity: 0; -} - -#configure-dialog, #configure-schedules-form-container { - display: none; - overflow-x: hidden; - overflow-y: auto; - padding-top: 25px; - - form { - width: 100%; - } - - .sublabel { - font-weight: normal; - } - - #occurrence-label { - display: inline-block; - } - - .occurrence-list { - border: 1px solid @well-border; - padding: 8px 10px; - border-radius: 4px; - background-color: @well; - list-style: none; - margin-bottom: 5px; - } - - #date-choice { - display: inline-block; - margin-left: 15px; - font-size: 12px; - - .label-inline { - display: inline-block; - vertical-align: middle; - } - input { - margin-bottom: 2px; - height: 11px; - width: 10px; - } - .label-inline:first-child { - padding-bottom: 2px; - margin-right: 10px; - } - .label-inline:nth-child(3) { - margin-right: 10px; - } - } -} -#home_groups_table .actions .cancel { padding-right: 3px; } - -.success-badge { - color: @default-bg; - background-color: @default-succ; -} - -/* Disable textarea re-sizing as a general rule */ -textarea { - resize: none; -} - -textarea.allowresize { - resize: both; -} - -/* Working... spinner */ -.spinny { - display: none; - position: fixed; - z-index: 2000; - width: 138px; - height: 50px; - text-align:center; - color: @d7grey; - background-color: @black; - border: 1px solid @grey; - border-radius: 6px; - padding-top: 10px; - - p { - padding-top: 0px; - font-size: 18px; - text-align: right; - margin-right: 10px; - } - - i { - float: left; - margin-left: 10px; - } -} - -.subtitle { - font-size: 16px; -} - -.license-version { - font-size: 18px; - color: @grey-txt; -} - -// #license_eula{ -// white-space: nowrap; -// } - -.modal-dialog .ui-accordion .ui-accordion-content { - overflow: hidden; -} - -.overlay { - display: none; - position: absolute; - top: 0; - left: 0; - z-index: 1080; - background-color: @black; - opacity: 0; -} - -/* TB tooltip overrides */ - .popover-body { - width: 100%; - padding: 0; - color: @default-bg; - - .table>tbody>tr>td { - padding-left: 0; - border-top: 1px solid @b7grey; - } - .popover-body_code-snippet{ - padding: 0 3px; - } - } - h3.popover-header, .popover-body, .popover-body blockquote, .popover-body a { - font-family: 'Open Sans', sans-serif; - font-size: 12px; - } - .flyout { - margin-bottom: 0; - } - .flyout thead> tr> th, .flyout tbody> tr> td, .flyout tbody> tr> td> a { - font-size: 12px; - } - .flyout tbody > tr:last-child > td { - padding-bottom: 0; - } - .popover-header { - padding: 0 0 5px 0; - background-color: @default-interface-txt; - color: @default-bg; - font-weight: 600; - border-bottom: none; - text-transform: uppercase; - } - .popover { - z-index: 2000; - min-width: 200px; - max-width: 325px; - background-color: @default-interface-txt; - color: @default-bg; //white - text-align: left; - padding: 10px; - font-weight: 400; - - code { - color: @default-data-txt; - background-color: @default-white-button-bord; - line-height: 18px; - } - - a { - color: @default-warning; - &:hover { - color: @default-warning-hov; - } - } - - p { - font-weight: 400; - } - - p:last-child { - margin-bottom: 0; - } - - table { - color: @default-bg; //white - } - } - .popover.right>.arrow:after { - border-right-color: @default-interface-txt; - } - .popover.left>.arrow:after { - border-left-color: @default-interface-txt; - } - .popover.bottom>.arrow:after { - border-bottom-color: @default-interface-txt; - } - .popover.top>.arrow:after { - border-top-color: @default-interface-txt; - } - .popover pre { - white-space: pre-wrap; - } - .popover-footer { - font-size: 12px; - margin-top: 10px; - text-align: right; - color: @grey; - .key { - color: @white; - background-color: @grey; - padding: 0 1px 1px 1px; - border-radius: 3px; - } - } - -.alert { - margin-top: 15px; - margin-bottom: 15px; -} - -hr { - border-color: @default-border; -} - -.help { - display: inline-block; -} - -.help-auto-off { - margin-top: 15px; - margin-bottom: 15px; - margin-left: 10px; - label { - font-weight: normal; - } -} - -.tab-content { - padding-top: 15px; -} - -.btn .caret { - border-top-color: @default-icon; -} - -.btn-light { - color: @default-data-txt; - background-color: @d7grey; - border-color: @d7grey; -} - -.refresh-grp { - display: inline-block; - text-align: right; - margin-left: 0; - margin-top: 0; - padding: 0; - line-height: normal; - - .refresh-msg { - font-size: 10px; - } -} - -.btn-light:hover { - color: @d7grey; - background-color: @default-icon; - border-color: @default-icon; -} - -/* Make a div or any element behave like pre. Use in conjunction with .mono-space */ -.pre { - white-space: pre; -} - - -dd { - margin-left: 15px; -} - -/* Use code-breakable in pop-over text to indent and wrap code segments */ - -.code-breakable { - padding-left: 10px; - word-wrap: break-word; -} - -.break { - word-break: break-all; -} - -.controls { - min-height: 15px; -} - -#navbar-container, .main-menu { - width: 100%; - background-color: @default-dark; -} - -.text-justify { - text-align: justify; -} - -.help-link, -.help-link:active, -.help-link:visited, -.ui-widget-content a.help-link, -.ui-widget-content a.help-link:active, -.ui-widget-content a.help-link:visited { - color: @default-icon; - text-decoration: none; - padding-left: 5px; -} - -.help-link:hover, -.ui-widget-content a.help-link:hover { - color: @default-interface-txt; - text-decoration: none; -} - -.login-header img { - width: 60%; -} - -.form-title { - display: inline-block; - width: 100%; - vertical-align: middle; - font-weight: bold; - padding-left: 15px; - margin-bottom: 10px; -} - -.form-cancel { - float: right; - margin-right: 10px; -} - -.form-title-hr { - margin-bottom: 20px; -} - -.form-horizontal .buttons { - margin-top: 25px; -} - -.label-text { - padding-right: 10px; -} - -.label-hint-text { - font-weight: normal; - color: @grey; -} -.label-hint-text:before { - /* for a line break before hintText */ - content: '\A'; - white-space: pre; -} - -#group_form #group_tabs { - margin-top: 25px; -} - -/* Outline required fields in Red when there is an error */ - .form-control.ng-dirty.ng-invalid, .form-control.ng-dirty.ng-invalid:focus { - border-color: @default-err; - outline: 0; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 5px rgba(255, 88, 80, 0.6); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 5px rgba(255, 88, 80, 0.6); - } - - .form-control.ng-dirty.ng-invalid + .select2 .select2-selection, - .form-control.ng-dirty.ng-invalid + .select2 .select2-selection:focus { - border-color: @default-err !important; - outline: 0 !important; - box-shadow: none !important; - } - - .form-control.ng-dirty.ng-pristine { - border-color: @b7grey; - box-shadow: none; - } - - .form-control.ng-dirty.ng-pristine:focus { - border-color: @default-link; - } -/* For some reason TB 3 RC1 does not provide an input-mini */ - -.input-mini { - height: 26px; - padding: 3px 8px; - font-size: 12px; - border-radius: 3px; -} - -.error { - font-size: 12px; - line-height: normal; - color: @red; -} - -.xsmall { - font-size: 12px; -} - -.note { - padding-top: 15px; - font-size: 12px; -} - -legend { - font-size: medium; - font-weight: bold; -} - -.navigation { - margin: 15px 0 15px 0; -} - -.footer-navigation { - margin: 10px 0 10px 0; -} - -.lookup-navigation { - margin: 15px 0 0 0; - /*padding-top: 20px;*/ - -} - -#lookup-modal-dialog { - overflow-x: hidden; - .instructions { - margin-top: 0; - margin-bottom: 20px; - } -} - -.related-footer { - margin: 10px 0 0 0; -} - -select.page-size { - width: 65px; - height: 24px; - font-size: 10px; -} - -.page-size-label { - margin-left: 15px; - font-size: 10.5px; - font-weight: normal; -} - -.accordion-heading { - font-weight: bold; - color: @blue-link; -} - -.accordion-heading i { - margin-right: 5px; -} - -.status-actions { - display: inline-block; - height: 25px; -} - -.status-spin { - display: inline-block; - margin-left: 15px; - font-size: 22px; - vertical-align: middle; -} - -/* Search Widget */ - - .search-widget label { - display: inline-block; - padding-right: 15px; - vertical-align: middle; - } - - #search-widget-spacer { - height: 20px; - } - -/* breadcrumbs */ -.nav-path { - padding: 5px 0 10px 0; - margin-right: 2px; - margin-bottom: 15px; - font-size: 14px; - font-weight: bold; - background-color: @default-no-items-bord; - border: 1px solid @d7grey; - border-radius: 6px; - box-shadow: 3px 3px 4px 0 @default-icon; - - .breadcrumb { - display: inline-block; - padding-bottom: 0; - padding-left: 0; - padding-right: 0; - margin-bottom: 0; - margin-left: 10px; - } - - .dropdown { - display: inline-block; - margin-right: 0; - paddding-right: 0; - - .toggle, .toggle:visited, .toggle:hover, .toggle:active { - color: @black; - } - - li a.active { - color: @grey; - } - - .crumb-icon { - font-size: 12px; - } - } -} - -.actions .dropdown { - display: inline-block; -} - -.greeting { - padding-right: 22px; -} - -.breadcrumb .active { - color: @black; -} - -.nav-tabs > li > a { - font-weight: bold; -} - -input[type="text"].field-mini-height { - height: 12px; - font-size: 10.5px; -} - -select.field-mini-height { - height: 22px; - font-size: 10.5px; -} - -.no-padding { - padding: 0; - margin: 0; -} - -input[type="checkbox"].checkbox-no-label { - margin-top: 10px; -} - -.checkbox-options { - font-weight: normal; -} - -/* Display list actions next to search widget */ -.list-actions { - display: flex; - height: 34px; - justify-content: flex-end; - margin-bottom: -34px; - text-align: right; - - .fa-lg { - vertical-align: -8%; - } -} - -.jqui-accordion { - .list-actions { - margin: 0; - } - /*.list-wrapper { - background-color: @well; - padding: 10px; - border-radius: 4px; - border: 1px solid @well-border; - }*/ - .ui-accordion-content { - padding-left: 15px; - padding-right: 15px; - } - .page-label { - margin-top: 5px; - } -} - -#home-list-actions { - margin-bottom: 15px; -} - -/* End Display list actions */ - - -/* Enable table-hover to work when table is in a well */ - -.table-hover tbody tr:hover > td, -.table-hover tbody tr:hover > th { - background-color: @default-bg; -} - -.table-hover-inverse tbody tr:hover > td, -.table-hover-inverse tbody tr:hover > th { - background-color: @active-color; -} - -.table > thead > tr > td.success, -.table > tbody > tr > td.success, -.table > tfoot > tr > td.success, -.table > thead > tr > th.success, -.table > tbody > tr > th.success, -.table > tfoot > tr > th.success, -.table > thead > tr.success > td, -.table > tbody > tr.success > td, -.table > tfoot > tr.success > td, -.table > thead > tr.success > th, -.table > tbody > tr.success > th, -.table > tfoot > tr.success > th { - background-color: @active-color; -} -.table-hover > tbody > tr > td.success:hover, -.table-hover > tbody > tr > th.success:hover, -.table-hover > tbody > tr.success:hover > td, -.table-hover > tbody > tr.success:hover > th { - background-color: @active-color; -} - -.table-summary thead > tr > th, -.table-summary tbody > tr > th, -.table-summary tfoot > tr > th, -.table-summary thead > tr > td, -.table-summary tbody > tr > td, -.table-summary tfoot > tr > td { - border-top: 1px solid @d7grey; -} - -.table-summary thead > tr > th { - border-bottom: 1px solid @d7grey; -} - -/* Table without row borders */ - -.table-no-border thead > tr > th, -.table-no-border tbody > tr > th, -.table-no-border tfoot > tr > th, -.table-no-border thead > tr > td, -.table-no-border tbody > tr > td, -.table-no-border tfoot > tr > td { - border-top: none; -} - -.table > tbody > tr > td{ - padding: 0.5em 0.6em; - &.actions{ - padding: 0px; - > a { - padding: 0.5em 0.6em; - display: inline-block; - } - } -} - -/* Less padding on .table-condensed */ -.table-condensed>tbody>tr>td:not(:last-child), -.table-condensed>thead>tr>th:not(:last-child) { - padding: 0.5em 20px 0.5em 0px; -} - -.table-condensed>tbody>tr>td:last-child, -.table-condensed>thead>tr>th:last-child { - padding: 0.5em 0px; -} - -.table.table-condensed.flyout { - thead>tr>th { - padding-left: 0; - border: none; - } -} - -/* Table info rows */ - -.loading-info { - color: @grey-txt; - font-weight: normal; - padding: 15px 0; -} - -/* Status Icons */ - - .license-expired, - .license-invalid, - .icon-failures-true, - .active-failures-true a, - .active-failures-true a:active, - .job-failed, - .job-error { - color: @default-err; - } - - .icon-failures-true a:hover { - color: @red; - } - - .job-failures-true { - padding-top: 5px; - color: @default-err; - } - - .job-event-status, - .license-status { - padding-top: 5px; - } - - - .job-new, - .license-valid, - .job-success, - .job-successful { - color: @green; - } - - .icon-host-all:before, - .icon-host-failed:before, - .icon-job-active:before, - .icon-job-running:before, - .icon-job-success:before, - .icon-job-successful:before, - .icon-job-changed:before, - .icon-job-ok:before, - .icon-job-OK:before, - .icon-job-failed:before, - .icon-job-skipped:before { - content: "\f111"; - } - - .icon-job-stopped:before, - .icon-job-error:before, - .icon-job-canceled:before, - .icon-job-stdout-download-tooltip:before, - .icon-job-unreachable:before, - .icon-job-failed:before { - content: "\f06a"; - } - - .icon-job-pending:before, - .icon-job-waiting:before, - .icon-job-new:before, - .icon-job-none:before, - .icon-job-no-matching-hosts:before { - content: "\f10c"; - } - - .icon-job-active, - .icon-job-running, - .icon-job-success, - .icon-job-successful, - .icon-job-ok, - .icon-job-OK { - color: @green; - } - - .icon-job-skipped { - color: @skipped; - } - - .icon-job-running { - .pulsate(); - } - - .icon-job-changed, - .job-changed { - color: @changed; - } - - .icon-host-failed, - .icon-job-stopped, - .icon-job-error, - .icon-job-failed, - .icon-job-stdout-download-tooltip, - .icon-job-canceled { - color: @red; - } - - .icon-host-all { - color: @at-blue; - } - - .icon-job-unreachable { - color: @unreachable; - } - - .icon-job-none, - .icon-job-pending, - .icon-job-waiting, - .icon-job-new, - .icon-job-no-matching-hosts { - color: @grey; - opacity: 0.45; - } - - .icon-schedule-enabled-true:before { - content: "\f04d"; - } - - .icon-schedule-enabled-false:before { - content: "\f04b"; - } - - .icon-socket-ok:before { - content: "\f111"; - color: @green; - } - .icon-socket-error:before { - content: "\f111"; - color: @red; - } - .icon-socket-connecting:before { - content: "\f042"; - color: @warning; - } - -/* job_events page */ - - #jobevents_table { - div.return-code { - display: inline-block; - margin-left: 10px; - } - textarea { - white-space: pre-wrap; - resize: vertical; - } - } - - #password-modal .alert-info { - margin-top: 0; - margin-bottom: 25px; - } - -/* Inventory job status badge */ - .failures-true { - background-color: @red; - color: @default-bg; - } - - .failures-false { - background-color: @green; - color: @default-bg; - } - -/* Cloud inventory status. i.e. inventory_source.status values */ - - .icon-cloud-na:before, - .icon-cloud-never:before, - .icon-cloud-updating:before, - .icon-cloud-running:before, - .icon-cloud-successful:before, - .icon-cloud-pending:before, - .icon-cloud-failed:before, - .icon-cloud-canceled:before, - .icon-cloud-error:before { - content: "\f0c2"; - } - - /*.icon-cloud-failed:before, - .icon-cloud-error:before { - content: "\f06a"; - }*/ - - .icon-cloud-na, - .icon-cloud-never, - a.icon-cloud-na:hover, - a.icon-cloud-never:hover { - color: @grey; - } - - .icon-cloud-updating, - .icon-cloud-running, - .icon-cloud-successful, - .icon-cloud-pending, - a.icon-cloud-updating:hover, - a.icon-cloud-successful:hover { - color: @green; - } - - .icon-cloud-failed, - .icon-cloud-error, - .icon-cloud-canceled, - a.icon-cloud-failed:hover { - color: @red; - } - - .icon-cloud-updating, - .icon-cloud-running, - .icon-cloud-pending { - .pulsate(); - } - - .icon-enabled-true:before { - content: "\f046"; - } - - .icon-enabled-true { - color: @green; - width: 14px; - - } - .icon-enabled-false:before { - content: "\f096"; - } - - .icon-enabled-false{ - color: @red; - width: 14px; - } - -/* Inventory cloud sourced? indicator */ - .icon-cloud-true:before { - content: "\f111"; - } - - .icon-cloud-false:before { - content: "\f111"; - } - - .error-color { - color:@red; - } - - .error-border { - border-color:@red; - } - - .connecting-color { - color: @warning; - } - - .ok-color, - .icon-cloud-true { - color: @green; - } - - .icon-cloud-false { - color: @grey; - } -/* end */ - - .field-success { - color: @default-succ; - } - - .field-success input { - border-color: @default-succ; - } - - .field-failure { - color: @red; - } - - .field-failure input { - border-color: @red; - } - - .field-badge { - font-size: 12px; - margin-right: 3px; - } - - .license-warning, - .license-demo { - color: @warning; - } - - .job-detail-status { - display: inline-block; - margin-top: 5px; - font-size: 15px; - } - - /*.form-items .search-widget { - margin-top: 15px; - }*/ - - .form-items .item-count { - display: inline-block; - margin-top: 25px; - font-size: small; - } - - .child-event a { - color: @black; - cursor: default; - } - /* Padding levels used on job events and inventory groups */ - .level { display: inline-block; } - .level-1 { padding-left: 15px; } - .level-2 { padding-left: 30px; } - .level-3 { padding-left: 45px; } - .level-4 { padding-left: 60px; } - .level-5 { padding-left: 75px; } - .level-6 { padding-left: 90px; } - .level-7 { padding-left: 105px; } - .level-8 { padding-left: 120px; } - .level-9 { padding-left: 135px; } - .level-10 { padding-left: 150px; } - - .level-3-detail { - padding-left: 80px; - } - - #job_events .control-group { - margin-top: 0; - margin-bottom: 10px; - } - -/* End Jobs Page */ - - -/* license modal */ - #license-modal-dialog { - overflow-x: hidden; - - input[readonly], - textarea[readonly] { - background-color: @default-border; - border: 1px solid @default-icon; - } - - .fa-external-link { - color: @grey; - font-size: 10px; - } - - .free-button { - background-color: @default-err; - border: 1px solid @default-err; - color: @white; - } - .free-button:hover { - background-color: @default-err-hov; - border: 1px solid @default-err-hov; - color: @white; - } - } - -/* Inventory nav links */ - .navigation-links { - - padding: 0; - margin-top: -10px; - - a { - margin-right: 15px; - } - - a:last-child { - margin-right: 20px; - } - } - -/* Dashboard */ - #home #container1.col-lg-6, - #home #container3.col-lg-6 { - padding-right: 7px; - } - - #home #container2.col-lg-6, - #home #container4.col-lg-6 { - padding-left: 7px; - } - - -/* Inventory Edit */ - - #hosts-container.col-lg-6 { - padding-left: 7px; - padding-right: 17px; - } - - #groups-container .well, - #hosts-container .well { - padding: 8px; - margin-bottom: 0; - } - - #home_groups_table i[class*="icon-job-"] { - margin-left: 5px; - } - - .selected { - font-weight: bold; - color: @blue-dark; - } - - .inventory-title { - font-size: 16px; - font-weight: bold; - } - - .active-row { - background-color: @white; - border-bottom: 1px solid @default-tertiary-bg; - border-right: 1px solid @default-tertiary-bg; - } - - .node-toggle, .node-no-toggle { - /* also used on job evetns */ - float: none; - padding-top: 3px; - padding-left: 0; - margin-right: 5px; - margin-left: 0; - } - - .node-no-toggle { - opacity: .30; - } - - .draggable-clone { - opacity: .60; - font-weight: bold; - /*z-index: 2000; - overflow: visible; - whitespace: wrap; - text-overflow: clip;*/ - } - - .droppable-hover { - background-color: @info; - color: @info-color; - padding: 6px; - border: 1px solid @info-border; - border-radius: 4px; - /*overflow: visible; - whitespace: wrap; - text-overflow: clip;*/ - } - - #group-delete-dialog .help-container, - #password-modal .help-container { - .help-link, - .help-link:active, - .help-link:visited { - color: @blue-link; - } - - .help-link:hover { - color: @blue-dark; - } - } - - - .btn-danger { - background-color: @red; - border-color: @red-focus; - } - - .btn-danger:hover, - .btn-danger:focus, - .btn-danger:active { - border-color: @red-focus; - background-color: @red-focus; - } - -// ad hoc permission checkbox -.squeeze.form-group { - margin-bottom: 10px; -} - -.disabled { - color: @grey; -} - -a.disabled:hover { - color: @grey; - cursor: not-allowed; -} -a.btn-disabled:hover { - cursor: not-allowed; -} - -/* Variable Editing */ - .parse-selection { - display: inline-block; - margin: 5px 0 8px 0; - font-size: 12px; - line-height: normal; - } - - .parse-selection input { - margin-left: 5px; - } - - .parse-select .parse-label { - margin-left: 3px; - color: @field-input-text; - } - - .parse-label { - color: @field-input-text; - font-weight: normal; - } - - .external-editor-link { - display: inline-block; - margin-left: 20px; - } - -.slider { - display: inline-block; - width: 100px; - margin: 0 10px; - vertical-align: middle; -} - -/* Sort link styles */ - -.list-header-noSort:hover.list-header:hover{ - cursor: default; -} - -.list-header:hover { - cursor: pointer; -} - -.list-header i { - margin-left: 10px; -} - -.list-header .icon-sort { - color: @default-icon; -} - -.list-header .icon-sort-down, -.list-header .icon-sort-up { - color: @black; -} - -/* job_events syles */ - -#jobevents_table .actions i { - padding-top: 0; - margin-right: 0; -} - -tr td button i { - padding-top: 0; - margin-right: 0; -} - -.event-form { - margin-top: 10px; - margin-bottom: 5px; - - label { - font-weight: normal; - } -} - -.event-detail-host { - padding-top: 10px; - padding-bottom: 5px; -} - -.form-section-title { - width: 100%; - margin-top: 0; - margin-bottom: 10px; - font-weight: bold; - border-bottom: 1px solid @default-border; -} - -.modal-backdrop, -.modal-backdrop.fade.in { - opacity: 0.6; - filter: alpha(opacity=60); -} - -.modal-body .ui-accordion .ui-accordion-content { - padding: 10px; -} - -/* overrides to TB modal */ -.modal-content { - padding: 20px; - box-shadow: 0 5px 15px rgba(0,0,0,.5); -} - -.modal-header { - color: @default-interface-txt; - white-space: nowrap; - width: 90%; - overflow: hidden; - text-overflow: ellipsis; - width: 100%; - border: none; - padding: 0; -} - -.modal { - border: 1px solid @black; - overflow-y: auto; -} - -.modal .SmartSearch-bar { - width: 100%; -} - -#alert-modal, #alert-modal2 { - z-index: 2100; -} - -.close { - color: @grey; - opacity: .7; - filter: alpha(opacity=70); -} - -.modal-header h3 { - margin: 0; - text-rendering: optimizeLegibility; - font-size: 15px; - color: @default-interface-txt; - font-weight: bold; - line-height: normal; - font-family: 'Open Sans', helvetica; - text-transform: uppercase; -} - -.modal-body { - min-height: 120px; - padding: 20px 0; - - .alert { - padding: 0px; - margin: 0; - word-wrap: break-word; - } - .alert-danger { - background-color: @default-bg; - border: none; - color: @default-interface-txt; - } -} - -#prompt-modal .modal-body { - padding-bottom: 30px; -} - -.skinny-modal .modal-body { - padding: 5px 10px 0 10px; -} - -.modal-footer { - padding: 0; - border: none; - margin-top: 0; - display: flex; - justify-content: flex-end; - - .btn.btn-primary { - text-transform: uppercase; - background-color: @default-succ; - border-color: @default-succ; - padding: 5px 15px; - cursor: pointer; - - &:hover { - background-color: @default-succ-hov; - border-color: @default-succ-hov; - } - - &:disabled { - background-color: @default-succ-disabled; - border-color: @default-succ-disabled; - } - } - - .btn + .btn { - margin: 0; - } -} - -/* form navigation */ -.navigation-buttons { - height: 40px; -} - - -/* PW progress bar */ -.pw-progress { margin-top: 10px; - - li { - line-height: normal; - margin-bottom: 10px; - } - ul:last-child { - margin-top: 10px; - } - -} - -/* Home page */ - -.failed-column { - a:link, a:visited { - color: @red; - } - a:hover { - color: @red-hover; - } -} - -/* Help modal dialog */ - -#help-modal-dialog { - overflow: hidden; - padding: 10px; - - img { - margin-top: 15px; - margin-bottom: 15px; - border: 1px solid @grey; - box-shadow: 3px 3px 5px 0 @grey; - } - - .img-container, - .icon-container { - width: 100%; - text-align: center; - } - - .icon-container { - margin-top: 15px; - margin-bottom: 15px; - } - - .help-box { - width: 100%; - margin-top: 15px; - border-radius: 6px; - color: @grey-txt; - font-size: 14px; - } - - .fa-rss { - transform:rotate(-45deg); - } -} - -/* job stdout */ - - #pre-container { - overflow: auto; - width: 100%; - border-radius: 4px; - margin: 0; - } - - -/* ng-cloak directive */ - -[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { - display: none !important; -} - - -/* Socket testing page */ -#sockets { - .section-title { - font-weight: bold; - color: @blue-link; - margin-top: 30px; - } - #test-container .section-title { - margin-top: 20px; - } - .well { - padding: 9px; - } - .message-section { - height: 150px; - overflow: auto; - } - .events-section { - margin-top: 40px; - .section-title { - margin-top: 0; - } - } - #event-message-container { - height: 200px; - } -} - -/* Large desktop */ - -@media (min-width: 1200px) { - - .delete-btn { - /* Used on job and project page to make cancel and delete buttons have an equal width */ - width: 60px; - } - - .label-text { - text-align: right; - } - -} - -@media (min-width: 768px) and (max-width: 1199px) { - - .level-1, - .level-2, - .level-3, - .level-4, - .level-5, - .level-6, - .level-7, - .level-8, - .level-9, - .level-10, - .level-3-detail { - padding-left: 0; - } - - .label-text { - text-align: left; - } - - .group-name { - width: 80%; - } - - #groups-container.col-lg-6 { - padding-right: 15px; - } - - #hosts-container.col-lg-6 { - margin-top: 15px; - padding-left: 15px; - padding-right: 15px; - .well { - margin-bottom: 0; - } - } - -} - -/* Landscape phone to portrait tablet */ - -@media (max-width: 767px) { - - /* Job events */ - - .level-1, - .level-2, - .level-3, - .level-4, - .level-5, - .level-6, - .level-7, - .level-8, - .level-9, - .level-10, - .level-3-detail { - padding-left: 0; - } - - table { - word-wrap: break-word; - table-layout: fixed; - } - - th.actions-column, - td.actions { - white-space: normal; - } - - td.actions .btn { - width: 75px; - margin-bottom: 5px; - } - - .group-name { - width: 80%; - } - - .label-text { - text-align: left; - } - - .list-action-label { - display: none; - } - - #groups-container.col-lg-6 { - padding-right: 15px; - } - - #hosts-container.col-lg-6 { - margin-top: 15px; - padding-left: 15px; - padding-right: 15px; - } -} - -// lists.less uses 600px as the breakpoint, doing same for consistency -@media (max-width: 600px) { - .list-actions { - text-align: left; - margin-bottom: 20px; - } -} - -.nvtooltip { - border-radius: 4px; - - td.value { - padding-right: 0px; - } - - p { - padding: 3px 0px; - } - - & > table > thead > tr > td { - font-weight: 700; - - &:first-child { - padding-top: 0; - } - } - - & > table > tbody > tr:last-child > td { - padding-bottom: 0; - } -} - -.nvd3 g.nv-groups path.nv-line { - stroke-width: 3px; -} - -.stdout-panel-body { - background-color: @default-list-header-bg; -} - -.job-stdout-panel { - margin: 0 15px; -} - -.show_input_button { - width: 73px; -} - -.red-text { - color: @red; -} - -.factDetailsNote { - margin-bottom: 10px; -} - -.inputSpacer { - margin-bottom: 25px; -} - -.cleanupStretcher { - margin: 0 -15px; -} - -.factDaysToKeepCompacter { - margin-bottom: 15px; -} - -.factDetailsHeader { - font-weight: bold; -} - -@media (max-width: 991px) { - .inputCompactMobile { - margin-bottom: 15px; - } -} - -#login-modal-body { - padding-bottom: 5px; -} - -.modal { - transition: all 0.3s ease-out !important; -} - -.modal.fade .modal-dialog { - transform: translate(0, 0); - margin: 100px auto; -} - -.modal-backdrop, .modal-backdrop.fade.show { - opacity: .25; -} - -.form-control { - border-color: @b7grey; - background-color: @fcgrey; - color: @default-data-txt; - transition: border-color 0.3s; - box-shadow: none; - font-size: 14px; - font-family: 'Open Sans', sans-serif; -} - -.form-control + .select2 .select2-selection { - border-color: @b7grey !important; - background-color: @fcgrey !important; - color: @default-data-txt !important; - transition: border-color 0.3s !important; - box-shadow: none !important; -} - -.form-control + .select2-container--disabled .select2-selection { - background-color: @ebgrey !important; -} - -.form-control:active, .form-control:focus { - box-shadow: none; - border-color: @default-link; -} - -.form-control:active + .select2 .select2-selection, .form-control:focus + .select2 .select2-selection { - box-shadow: none !important; - border-color: @default-link !important; -} - -.form-control.ng-dirty.ng-invalid, .form-control.ng-dirty.ng-invalid:focus { - box-shadow: none; -} - -.form-control.ng-dirty.ng-invalid + .select2 .select2-selection, .form-control.ng-dirty.ng-invalid:focus + .select2 .select2-selection { - box-shadow: none !important; -} - -.error { - opacity: 1; - transition: opacity 0.2s; -} - -.error.ng-hide-add { - display: none; -} - -.error.ng-hide { - opacity: 0; -} - -/* Overwrite select2 base styles for single/multiple selects so that match up with other form elements. Also overwrite disabled styles. */ -.select2-container--disabled,.select2-container--disabled .select2-selection--single,.select2-container--disabled .select2-selection--multiple { - cursor: not-allowed; - opacity: 100; - background-color: @ebgrey; - border-radius: 5px; -} - -.select2-container--default .select2-selection--single { - background-color: @fcgrey; - border: 1px solid @d7grey; - border-radius: 4px; -} - -.select2-container--default .select2-selection--multiple { - background-color: @field-secondary-bg; - border-radius: 4px; - border: 1px solid @d7grey; - cursor: text; - max-height: 135px; - overflow-y: auto; -} - -body.is-modalOpen { - overflow: hidden; -} - -input[type=file]:focus, input[type=radio]:focus, input[type=checkbox]:focus { - outline: 0 !important; -} - -.btn-success.disabled, .btn-success[disabled], fieldset[disabled] .btn-success, .btn-success.disabled:hover, .btn-success[disabled]:hover, fieldset[disabled] .btn-success:hover, .btn-success.disabled:focus, .btn-success[disabled]:focus, fieldset[disabled] .btn-success:focus, .btn-success.disabled:active, .btn-success[disabled]:active, fieldset[disabled] .btn-success:active, .btn-success.disabled.active, .btn-success[disabled].active, fieldset[disabled] .btn-success.active { - background-color: @d7grey; - color: @default-bg; - border: 0; -} - -.btn-success:hover, .btn-success:focus, .btn-success:active, .btn-success.active, .open .dropdown-toggle.btn-success { - border-color: @default-succ; -} - -a { - color: @default-link; -} - -a:hover { - color: @default-link-hov; -} - -.form-control:active, .form-control:focus { - border-color: @default-link; -} - -.nv-axislabel { - font-weight: bold !important; - fill: @db-graph-axis-label !important; - font-family: 'Open Sans' !important; -} - -.nv-axis text { - fill: @db-graph-axis-label !important; - font-family: 'Open Sans' !important; -} - -.select2-container--default .select2-selection--multiple .select2-selection__choice { - cursor: default; - float: left; - margin-right: 5px; - margin-top: 5px; - padding-left: 0px; - border-left-width: 0px; - border-bottom-width: 0px; - border-top-width: 0px; - padding-right: 10px; - border-right-width: 0px; - background-color: @default-link; - color: @default-bg; - border-radius: 5px; - line-height: 21px; - font-size: 13px; - white-space: pre-wrap; - word-wrap: break-word; - word-break: break-all; - white-space: normal; -} - -.select2-container--default .select2-selection--multiple .select2-selection__choice__remove { - cursor: pointer; - display: inline-block; - font-weight: bold; - margin-right: 8px !important; - padding: 0 6px; - color: @default-bg !important; - border-top-left-radius: 5px; - border-bottom-left-radius: 5px; - background-color: @default-link; -} - -.select2-selection { - & > .select2-selection__arrow { - border-left: none; - } -} - -.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover { - background-color: @default-err; -} - -#scheduled-jobs-tab .List-header { - display: none; -} - -.ui-widget { - font-family: 'Open Sans'; -} - -.WorkflowBadge{ - background-color: @b7grey; - border-radius: 10px; - color: @default-bg; - display: inline-block; - font-family: 'Open Sans'; - font-weight: bold; - font-style: normal; - font-size: x-small; - height: 14px; - margin-left: 5px; - padding-left: 2px; - width: 14px; -} - -button[disabled], -html input[disabled] { - cursor: not-allowed; -} - -.CodeMirror { - font-family: Monaco, Menlo, Consolas, "Courier New", monospace; -} - -.CodeMirror--disabled .CodeMirror.cm-s-default, -.CodeMirror--disabled .CodeMirror-line { - background-color: @default-no-items-bord; -} - -.CodeMirror--disabled .CodeMirror-gutter.CodeMirror-lint-markers, -.CodeMirror--disabled .CodeMirror-gutter.CodeMirror-linenumbers { - background-color: @default-list-header-bg; - color: @b7grey; -} - -.CodeMirror--disabled .CodeMirror-lines { - cursor: default; -} - -.CodeMirror--disabled .CodeMirror-cursors { - display: none; -} - -.btn-default.disabled,.btn-default[disabled],fieldset[disabled] .btn-default,.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled:active,.btn-default[disabled]:active,fieldset[disabled] .btn-default:active,.btn-default.disabled.active,.btn-default[disabled].active,fieldset[disabled] .btn-default.active { - border-color: @field-border; -} - -.btn-default { - background: @default-bg; - border-color: @b7grey; - color: @default-interface-txt; -} - -.select2-container--disabled .select2-selection, -.select2-container--disabled .select2-arrow { - background-color: @ebgrey; -} - -.btn.disabled,.btn[disabled],fieldset[disabled] .bt { - opacity: 0.65; -} - -.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled { - opacity: 1; - background-color: @ebgrey; -} - -input[disabled].ui-spinner-input { - background-color: @ebgrey; -} - -.CodeMirror-scroll { - margin-bottom: 0; - padding-bottom: 0; - margin-right: 0; - overflow: auto !important; - overflow-y: auto !important; - border-radius: 5px; -} - -.CodeMirror-lines { - margin-bottom: 20px; -} - -.btn-default { - border-color: @b7grey; -} - -.btn-default:hover, .btn-default:focus, .btn-default:active, .btn-default.active, .open .dropdown-toggle.btn-default { - background-color: @f2grey; - border-color: @b7grey; - color: @default-interface-txt; -} - -.ui-dialog .ui-dialog-content { - background: @default-bg; -} - -.ui-dialog.no-close button.close { - display: none; -} - -html { height: 100%; } - -body { - font-family: 'Open Sans', sans-serif; - font-weight: 400; - color: @default-data-txt; - background-color: @default-secondary-bg; -} - -.container-fluid { - padding-left: 20px; - padding-right: 20px; -} - -#content-container { - padding-bottom: 0px; -} - -.btn { - text-transform: uppercase; -} - -.ui-spinner-input { - margin-top: .3em; - margin-bottom: .3em; -} - -.Toast-wrapper { - display: flex; - max-width: 250px; - word-break: break-word; -} - -.Toast-icon { - display: flex; - align-items: center; - justify-content: center; -} diff --git a/awx/ui/client/legacy/styles/bootstrap-custom-overrides.less b/awx/ui/client/legacy/styles/bootstrap-custom-overrides.less deleted file mode 100644 index ab6020ce8605..000000000000 --- a/awx/ui/client/legacy/styles/bootstrap-custom-overrides.less +++ /dev/null @@ -1,191 +0,0 @@ -body { - font-family: 'Open Sans', sans-serif; - font-weight: 400; - color: @default-data-txt; - background-color: @default-secondary-bg; - font-size: 0.88rem; - /* - * Bootstrap fix that's causing a right margin and padding - * to appear whenever a modal is opened - * https://github.com/twbs/bootstrap/issues/27071 - */ - margin-right: 0px !important; - padding-right: 0px !important; -} - -.dropdown-toggle::after { - display:none; -} - -.tooltip.show { - opacity: 1; -} - -.tooltip { - font-family: 'Open Sans', sans-serif; - font-size: 0.75rem; - line-height: 1.4; -} - -.bs-tooltip-top .arrow::before, -.bs-tooltip-auto[x-placement^="top"] .arrow::before { - border-top-color: @default-stdout-txt; -} - -.bs-tooltip-bottom .arrow::before, -.bs-tooltip-auto[x-placement^="bottom"] .arrow::before { - border-bottom-color: @default-stdout-txt; -} - -.bs-tooltip-left .arrow::before, -.bs-tooltip-auto[x-placement^="left"] .arrow::before { - border-left-color: @default-stdout-txt; -} - -.bs-tooltip-right .arrow::before, -.bs-tooltip-auto[x-placement^="right"] .arrow::before { - border-right-color: @default-stdout-txt; -} - -/* this used to exist but was removed */ -.form-horizontal .form-group { - margin-left: -15px; - margin-right: -15px; -} - -label { - margin-bottom: 0.3125rem; -} - -.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 { - line-height: 1.1; -} - -p { - margin-bottom: 0.625rem; -} - -.dropdown-menu, .btn { - font-size: 0.88rem; -} - -.btn-xs, .btn-group-xs>.btn { - padding: 1px 5px; - font-size: 0.75rem; - line-height: 1.5; - border-radius: 3px; -} - -.btn-sm, .btn-group-sm>.btn { - padding: 5px 10px; - font-size: 0.75rem; - line-height: 1.5; - border-radius: 3px; -} - -button, html input[type=button], input[type=reset], input[type=submit] { - cursor: pointer; -} - -.dropdown-menu { - padding: .2rem 0; -} - -.dropdown-item { - padding: 0.19rem 0.625rem; -} - -@media (min-width: 768px) { - .modal-dialog { - width: 600px; - } -} - -.modal-dialog { - max-width: none; -} - -.modal-footer>:not(:last-child) { - margin-right: 1.25rem; -} - -.btn-group-vertical>.btn, .btn-group>.btn { - -ms-flex: 1 1 auto; - flex: 1 1 auto; -} - -.bs-popover-auto[x-placement^=right] .arrow::after, .bs-popover-right .arrow::after { - border-right-color: @default-stdout-txt; -} - -.bs-popover-auto[x-placement^=left] .arrow::after, .bs-popover-left .arrow::after { - border-left-color: @default-stdout-txt; -} - -.bs-popover-auto[x-placement^=top] .arrow::after, .bs-popover-top .arrow::after { - border-top-color: @default-stdout-txt; -} - -.bs-popover-auto[x-placement^=bottom] .arrow::after, .bs-popover-bottom .arrow::after { - border-bottom-color: @default-stdout-txt; -} - -a:not([href]):not([tabindex]) { - color: @default-link; -} - -a:not([href]):not([tabindex]):hover { - color: @default-link-hov; -} - -.dropdown-menu>li>a { - padding: 3px 10px; - display: block; - clear: both; - font-weight: 400; - line-height: 1.42857143; - white-space: nowrap; -} - -.dropdown-item.active, -.dropdown-item:active, -.dropdown-menu>li>a:hover, -.dropdown-menu>li>a:focus { - text-decoration: none; - color: @default-data-txt; - background-color: @f6grey; -} - -.radio-inline+.radio-inline, .checkbox-inline+.checkbox-inline { - margin-left: 10px; -} - -label { - font-weight: 700; -} - -.radio-inline, .checkbox-inline { - font-weight: 400; -} - -.btn-success{ - background: @default-succ; - border-color: transparent; - :hover{ - background: @default-succ-hov; - } - :disabled{ - background: @default-succ-disabled; - } -} -.btn-default{ - background: @btn-bg; - border-color: @btn-bord; - color: @btn-txt; - :hover{ - background: @btn-bg-hov; - } - :focus{ - background: @btn-bg-sel; - } -} diff --git a/awx/ui/client/legacy/styles/bootstrap-datepicker.less b/awx/ui/client/legacy/styles/bootstrap-datepicker.less deleted file mode 100644 index dd5f9490dc76..000000000000 --- a/awx/ui/client/legacy/styles/bootstrap-datepicker.less +++ /dev/null @@ -1,750 +0,0 @@ -/*! - * Datepicker for Bootstrap v1.4.0 (https://github.com/eternicode/bootstrap-datepicker) - * - * Copyright 2012 Stefan Petre - * Improvements by Andrew Rowls - * Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - */ -.datepicker { - padding: 4px; - border-radius: 4px; - direction: ltr; -} -.datepicker-inline { - width: 220px; -} -.datepicker.datepicker-rtl { - direction: rtl; -} -.datepicker.datepicker-rtl table tr td span { - float: right; -} -.datepicker-dropdown { - top: 0; - left: 0; -} -.datepicker-dropdown:before { - content: ''; - display: none; - border-left: 7px solid transparent; - border-right: 7px solid transparent; - border-bottom: 7px solid #ccc; - border-top: 0; - border-bottom-color: rgba(0, 0, 0, 0.2); - position: absolute; -} -.datepicker-dropdown:after { - content: ''; - display: none; - border-left: 6px solid transparent; - border-right: 6px solid transparent; - border-bottom: 6px solid #fff; - border-top: 0; - position: absolute; -} -.datepicker-dropdown.datepicker-orient-left:before { - left: 6px; -} -.datepicker-dropdown.datepicker-orient-left:after { - left: 7px; -} -.datepicker-dropdown.datepicker-orient-right:before { - right: 6px; -} -.datepicker-dropdown.datepicker-orient-right:after { - right: 7px; -} -.datepicker-dropdown.datepicker-orient-top:before { - top: -7px; -} -.datepicker-dropdown.datepicker-orient-top:after { - top: -6px; -} -.datepicker-dropdown.datepicker-orient-bottom:before { - bottom: -7px; - border-bottom: 0; - border-top: 7px solid #999; -} -.datepicker-dropdown.datepicker-orient-bottom:after { - bottom: -6px; - border-bottom: 0; - border-top: 6px solid #fff; -} -.datepicker.days .datepicker-days, -.datepicker.months .datepicker-months, -.datepicker.years .datepicker-years { - display: block; -} -.datepicker table { - margin: 0; - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} -.datepicker .datepicker-days .table-condensed tr td, -.datepicker .datepicker-days .table-condensed tr th { - text-align: center; - width: 30px; - height: 30px; - border-radius: 4px; - border: none; - padding: 0.5em 0.7em; -} -.table-striped .datepicker table tr td, -.table-striped .datepicker table tr th { - background-color: transparent; -} -.datepicker table tr td.day:hover, -.datepicker table tr td.day.focused { - background: #eeeeee; - cursor: pointer; -} -.datepicker table tr td.old, -.datepicker table tr td.new { - color: #999999; -} -.datepicker table tr td.disabled, -.datepicker table tr td.disabled:hover { - background: none; - color: #999999; - cursor: default; -} -.datepicker table tr td.today, -.datepicker table tr td.today:hover, -.datepicker table tr td.today.disabled, -.datepicker table tr td.today.disabled:hover { - color: #000000; - background-color: #ffdb99; - border-color: #ffb733; -} -.datepicker table tr td.today:hover, -.datepicker table tr td.today:hover:hover, -.datepicker table tr td.today.disabled:hover, -.datepicker table tr td.today.disabled:hover:hover, -.datepicker table tr td.today:focus, -.datepicker table tr td.today:hover:focus, -.datepicker table tr td.today.disabled:focus, -.datepicker table tr td.today.disabled:hover:focus, -.datepicker table tr td.today:active, -.datepicker table tr td.today:hover:active, -.datepicker table tr td.today.disabled:active, -.datepicker table tr td.today.disabled:hover:active, -.datepicker table tr td.today.active, -.datepicker table tr td.today:hover.active, -.datepicker table tr td.today.disabled.active, -.datepicker table tr td.today.disabled:hover.active, -.open .dropdown-toggle.datepicker table tr td.today, -.open .dropdown-toggle.datepicker table tr td.today:hover, -.open .dropdown-toggle.datepicker table tr td.today.disabled, -.open .dropdown-toggle.datepicker table tr td.today.disabled:hover { - color: #000000; - background-color: #ffcd70; - border-color: #f59e00; -} -.datepicker table tr td.today:active, -.datepicker table tr td.today:hover:active, -.datepicker table tr td.today.disabled:active, -.datepicker table tr td.today.disabled:hover:active, -.datepicker table tr td.today.active, -.datepicker table tr td.today:hover.active, -.datepicker table tr td.today.disabled.active, -.datepicker table tr td.today.disabled:hover.active, -.open .dropdown-toggle.datepicker table tr td.today, -.open .dropdown-toggle.datepicker table tr td.today:hover, -.open .dropdown-toggle.datepicker table tr td.today.disabled, -.open .dropdown-toggle.datepicker table tr td.today.disabled:hover { - background-image: none; -} -.datepicker table tr td.today.disabled, -.datepicker table tr td.today:hover.disabled, -.datepicker table tr td.today.disabled.disabled, -.datepicker table tr td.today.disabled:hover.disabled, -.datepicker table tr td.today[disabled], -.datepicker table tr td.today:hover[disabled], -.datepicker table tr td.today.disabled[disabled], -.datepicker table tr td.today.disabled:hover[disabled], -fieldset[disabled] .datepicker table tr td.today, -fieldset[disabled] .datepicker table tr td.today:hover, -fieldset[disabled] .datepicker table tr td.today.disabled, -fieldset[disabled] .datepicker table tr td.today.disabled:hover, -.datepicker table tr td.today.disabled:hover, -.datepicker table tr td.today:hover.disabled:hover, -.datepicker table tr td.today.disabled.disabled:hover, -.datepicker table tr td.today.disabled:hover.disabled:hover, -.datepicker table tr td.today[disabled]:hover, -.datepicker table tr td.today:hover[disabled]:hover, -.datepicker table tr td.today.disabled[disabled]:hover, -.datepicker table tr td.today.disabled:hover[disabled]:hover, -fieldset[disabled] .datepicker table tr td.today:hover, -fieldset[disabled] .datepicker table tr td.today:hover:hover, -fieldset[disabled] .datepicker table tr td.today.disabled:hover, -fieldset[disabled] .datepicker table tr td.today.disabled:hover:hover, -.datepicker table tr td.today.disabled:focus, -.datepicker table tr td.today:hover.disabled:focus, -.datepicker table tr td.today.disabled.disabled:focus, -.datepicker table tr td.today.disabled:hover.disabled:focus, -.datepicker table tr td.today[disabled]:focus, -.datepicker table tr td.today:hover[disabled]:focus, -.datepicker table tr td.today.disabled[disabled]:focus, -.datepicker table tr td.today.disabled:hover[disabled]:focus, -fieldset[disabled] .datepicker table tr td.today:focus, -fieldset[disabled] .datepicker table tr td.today:hover:focus, -fieldset[disabled] .datepicker table tr td.today.disabled:focus, -fieldset[disabled] .datepicker table tr td.today.disabled:hover:focus, -.datepicker table tr td.today.disabled:active, -.datepicker table tr td.today:hover.disabled:active, -.datepicker table tr td.today.disabled.disabled:active, -.datepicker table tr td.today.disabled:hover.disabled:active, -.datepicker table tr td.today[disabled]:active, -.datepicker table tr td.today:hover[disabled]:active, -.datepicker table tr td.today.disabled[disabled]:active, -.datepicker table tr td.today.disabled:hover[disabled]:active, -fieldset[disabled] .datepicker table tr td.today:active, -fieldset[disabled] .datepicker table tr td.today:hover:active, -fieldset[disabled] .datepicker table tr td.today.disabled:active, -fieldset[disabled] .datepicker table tr td.today.disabled:hover:active, -.datepicker table tr td.today.disabled.active, -.datepicker table tr td.today:hover.disabled.active, -.datepicker table tr td.today.disabled.disabled.active, -.datepicker table tr td.today.disabled:hover.disabled.active, -.datepicker table tr td.today[disabled].active, -.datepicker table tr td.today:hover[disabled].active, -.datepicker table tr td.today.disabled[disabled].active, -.datepicker table tr td.today.disabled:hover[disabled].active, -fieldset[disabled] .datepicker table tr td.today.active, -fieldset[disabled] .datepicker table tr td.today:hover.active, -fieldset[disabled] .datepicker table tr td.today.disabled.active, -fieldset[disabled] .datepicker table tr td.today.disabled:hover.active { - background-color: #ffdb99; - border-color: #ffb733; -} -.datepicker table tr td.today:hover:hover { - color: #000; -} -.datepicker table tr td.today.active:hover { - color: #fff; -} -.datepicker table tr td.range, -.datepicker table tr td.range:hover, -.datepicker table tr td.range.disabled, -.datepicker table tr td.range.disabled:hover { - background: #eeeeee; - border-radius: 0; -} -.datepicker table tr td.range.today, -.datepicker table tr td.range.today:hover, -.datepicker table tr td.range.today.disabled, -.datepicker table tr td.range.today.disabled:hover { - color: #000000; - background-color: #f7ca77; - border-color: #f1a417; - border-radius: 0; -} -.datepicker table tr td.range.today:hover, -.datepicker table tr td.range.today:hover:hover, -.datepicker table tr td.range.today.disabled:hover, -.datepicker table tr td.range.today.disabled:hover:hover, -.datepicker table tr td.range.today:focus, -.datepicker table tr td.range.today:hover:focus, -.datepicker table tr td.range.today.disabled:focus, -.datepicker table tr td.range.today.disabled:hover:focus, -.datepicker table tr td.range.today:active, -.datepicker table tr td.range.today:hover:active, -.datepicker table tr td.range.today.disabled:active, -.datepicker table tr td.range.today.disabled:hover:active, -.datepicker table tr td.range.today.active, -.datepicker table tr td.range.today:hover.active, -.datepicker table tr td.range.today.disabled.active, -.datepicker table tr td.range.today.disabled:hover.active, -.open .dropdown-toggle.datepicker table tr td.range.today, -.open .dropdown-toggle.datepicker table tr td.range.today:hover, -.open .dropdown-toggle.datepicker table tr td.range.today.disabled, -.open .dropdown-toggle.datepicker table tr td.range.today.disabled:hover { - color: #000000; - background-color: #f4bb51; - border-color: #bf800c; -} -.datepicker table tr td.range.today:active, -.datepicker table tr td.range.today:hover:active, -.datepicker table tr td.range.today.disabled:active, -.datepicker table tr td.range.today.disabled:hover:active, -.datepicker table tr td.range.today.active, -.datepicker table tr td.range.today:hover.active, -.datepicker table tr td.range.today.disabled.active, -.datepicker table tr td.range.today.disabled:hover.active, -.open .dropdown-toggle.datepicker table tr td.range.today, -.open .dropdown-toggle.datepicker table tr td.range.today:hover, -.open .dropdown-toggle.datepicker table tr td.range.today.disabled, -.open .dropdown-toggle.datepicker table tr td.range.today.disabled:hover { - background-image: none; -} -.datepicker table tr td.range.today.disabled, -.datepicker table tr td.range.today:hover.disabled, -.datepicker table tr td.range.today.disabled.disabled, -.datepicker table tr td.range.today.disabled:hover.disabled, -.datepicker table tr td.range.today[disabled], -.datepicker table tr td.range.today:hover[disabled], -.datepicker table tr td.range.today.disabled[disabled], -.datepicker table tr td.range.today.disabled:hover[disabled], -fieldset[disabled] .datepicker table tr td.range.today, -fieldset[disabled] .datepicker table tr td.range.today:hover, -fieldset[disabled] .datepicker table tr td.range.today.disabled, -fieldset[disabled] .datepicker table tr td.range.today.disabled:hover, -.datepicker table tr td.range.today.disabled:hover, -.datepicker table tr td.range.today:hover.disabled:hover, -.datepicker table tr td.range.today.disabled.disabled:hover, -.datepicker table tr td.range.today.disabled:hover.disabled:hover, -.datepicker table tr td.range.today[disabled]:hover, -.datepicker table tr td.range.today:hover[disabled]:hover, -.datepicker table tr td.range.today.disabled[disabled]:hover, -.datepicker table tr td.range.today.disabled:hover[disabled]:hover, -fieldset[disabled] .datepicker table tr td.range.today:hover, -fieldset[disabled] .datepicker table tr td.range.today:hover:hover, -fieldset[disabled] .datepicker table tr td.range.today.disabled:hover, -fieldset[disabled] .datepicker table tr td.range.today.disabled:hover:hover, -.datepicker table tr td.range.today.disabled:focus, -.datepicker table tr td.range.today:hover.disabled:focus, -.datepicker table tr td.range.today.disabled.disabled:focus, -.datepicker table tr td.range.today.disabled:hover.disabled:focus, -.datepicker table tr td.range.today[disabled]:focus, -.datepicker table tr td.range.today:hover[disabled]:focus, -.datepicker table tr td.range.today.disabled[disabled]:focus, -.datepicker table tr td.range.today.disabled:hover[disabled]:focus, -fieldset[disabled] .datepicker table tr td.range.today:focus, -fieldset[disabled] .datepicker table tr td.range.today:hover:focus, -fieldset[disabled] .datepicker table tr td.range.today.disabled:focus, -fieldset[disabled] .datepicker table tr td.range.today.disabled:hover:focus, -.datepicker table tr td.range.today.disabled:active, -.datepicker table tr td.range.today:hover.disabled:active, -.datepicker table tr td.range.today.disabled.disabled:active, -.datepicker table tr td.range.today.disabled:hover.disabled:active, -.datepicker table tr td.range.today[disabled]:active, -.datepicker table tr td.range.today:hover[disabled]:active, -.datepicker table tr td.range.today.disabled[disabled]:active, -.datepicker table tr td.range.today.disabled:hover[disabled]:active, -fieldset[disabled] .datepicker table tr td.range.today:active, -fieldset[disabled] .datepicker table tr td.range.today:hover:active, -fieldset[disabled] .datepicker table tr td.range.today.disabled:active, -fieldset[disabled] .datepicker table tr td.range.today.disabled:hover:active, -.datepicker table tr td.range.today.disabled.active, -.datepicker table tr td.range.today:hover.disabled.active, -.datepicker table tr td.range.today.disabled.disabled.active, -.datepicker table tr td.range.today.disabled:hover.disabled.active, -.datepicker table tr td.range.today[disabled].active, -.datepicker table tr td.range.today:hover[disabled].active, -.datepicker table tr td.range.today.disabled[disabled].active, -.datepicker table tr td.range.today.disabled:hover[disabled].active, -fieldset[disabled] .datepicker table tr td.range.today.active, -fieldset[disabled] .datepicker table tr td.range.today:hover.active, -fieldset[disabled] .datepicker table tr td.range.today.disabled.active, -fieldset[disabled] .datepicker table tr td.range.today.disabled:hover.active { - background-color: #f7ca77; - border-color: #f1a417; -} -.datepicker table tr td.selected, -.datepicker table tr td.selected:hover, -.datepicker table tr td.selected.disabled, -.datepicker table tr td.selected.disabled:hover { - color: #ffffff; - background-color: #999999; - border-color: #555555; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); -} -.datepicker table tr td.selected:hover, -.datepicker table tr td.selected:hover:hover, -.datepicker table tr td.selected.disabled:hover, -.datepicker table tr td.selected.disabled:hover:hover, -.datepicker table tr td.selected:focus, -.datepicker table tr td.selected:hover:focus, -.datepicker table tr td.selected.disabled:focus, -.datepicker table tr td.selected.disabled:hover:focus, -.datepicker table tr td.selected:active, -.datepicker table tr td.selected:hover:active, -.datepicker table tr td.selected.disabled:active, -.datepicker table tr td.selected.disabled:hover:active, -.datepicker table tr td.selected.active, -.datepicker table tr td.selected:hover.active, -.datepicker table tr td.selected.disabled.active, -.datepicker table tr td.selected.disabled:hover.active, -.open .dropdown-toggle.datepicker table tr td.selected, -.open .dropdown-toggle.datepicker table tr td.selected:hover, -.open .dropdown-toggle.datepicker table tr td.selected.disabled, -.open .dropdown-toggle.datepicker table tr td.selected.disabled:hover { - color: #ffffff; - background-color: #858585; - border-color: #373737; -} -.datepicker table tr td.selected:active, -.datepicker table tr td.selected:hover:active, -.datepicker table tr td.selected.disabled:active, -.datepicker table tr td.selected.disabled:hover:active, -.datepicker table tr td.selected.active, -.datepicker table tr td.selected:hover.active, -.datepicker table tr td.selected.disabled.active, -.datepicker table tr td.selected.disabled:hover.active, -.open .dropdown-toggle.datepicker table tr td.selected, -.open .dropdown-toggle.datepicker table tr td.selected:hover, -.open .dropdown-toggle.datepicker table tr td.selected.disabled, -.open .dropdown-toggle.datepicker table tr td.selected.disabled:hover { - background-image: none; -} -.datepicker table tr td.selected.disabled, -.datepicker table tr td.selected:hover.disabled, -.datepicker table tr td.selected.disabled.disabled, -.datepicker table tr td.selected.disabled:hover.disabled, -.datepicker table tr td.selected[disabled], -.datepicker table tr td.selected:hover[disabled], -.datepicker table tr td.selected.disabled[disabled], -.datepicker table tr td.selected.disabled:hover[disabled], -fieldset[disabled] .datepicker table tr td.selected, -fieldset[disabled] .datepicker table tr td.selected:hover, -fieldset[disabled] .datepicker table tr td.selected.disabled, -fieldset[disabled] .datepicker table tr td.selected.disabled:hover, -.datepicker table tr td.selected.disabled:hover, -.datepicker table tr td.selected:hover.disabled:hover, -.datepicker table tr td.selected.disabled.disabled:hover, -.datepicker table tr td.selected.disabled:hover.disabled:hover, -.datepicker table tr td.selected[disabled]:hover, -.datepicker table tr td.selected:hover[disabled]:hover, -.datepicker table tr td.selected.disabled[disabled]:hover, -.datepicker table tr td.selected.disabled:hover[disabled]:hover, -fieldset[disabled] .datepicker table tr td.selected:hover, -fieldset[disabled] .datepicker table tr td.selected:hover:hover, -fieldset[disabled] .datepicker table tr td.selected.disabled:hover, -fieldset[disabled] .datepicker table tr td.selected.disabled:hover:hover, -.datepicker table tr td.selected.disabled:focus, -.datepicker table tr td.selected:hover.disabled:focus, -.datepicker table tr td.selected.disabled.disabled:focus, -.datepicker table tr td.selected.disabled:hover.disabled:focus, -.datepicker table tr td.selected[disabled]:focus, -.datepicker table tr td.selected:hover[disabled]:focus, -.datepicker table tr td.selected.disabled[disabled]:focus, -.datepicker table tr td.selected.disabled:hover[disabled]:focus, -fieldset[disabled] .datepicker table tr td.selected:focus, -fieldset[disabled] .datepicker table tr td.selected:hover:focus, -fieldset[disabled] .datepicker table tr td.selected.disabled:focus, -fieldset[disabled] .datepicker table tr td.selected.disabled:hover:focus, -.datepicker table tr td.selected.disabled:active, -.datepicker table tr td.selected:hover.disabled:active, -.datepicker table tr td.selected.disabled.disabled:active, -.datepicker table tr td.selected.disabled:hover.disabled:active, -.datepicker table tr td.selected[disabled]:active, -.datepicker table tr td.selected:hover[disabled]:active, -.datepicker table tr td.selected.disabled[disabled]:active, -.datepicker table tr td.selected.disabled:hover[disabled]:active, -fieldset[disabled] .datepicker table tr td.selected:active, -fieldset[disabled] .datepicker table tr td.selected:hover:active, -fieldset[disabled] .datepicker table tr td.selected.disabled:active, -fieldset[disabled] .datepicker table tr td.selected.disabled:hover:active, -.datepicker table tr td.selected.disabled.active, -.datepicker table tr td.selected:hover.disabled.active, -.datepicker table tr td.selected.disabled.disabled.active, -.datepicker table tr td.selected.disabled:hover.disabled.active, -.datepicker table tr td.selected[disabled].active, -.datepicker table tr td.selected:hover[disabled].active, -.datepicker table tr td.selected.disabled[disabled].active, -.datepicker table tr td.selected.disabled:hover[disabled].active, -fieldset[disabled] .datepicker table tr td.selected.active, -fieldset[disabled] .datepicker table tr td.selected:hover.active, -fieldset[disabled] .datepicker table tr td.selected.disabled.active, -fieldset[disabled] .datepicker table tr td.selected.disabled:hover.active { - background-color: #999999; - border-color: #555555; -} -.datepicker table tr td.active, -.datepicker table tr td.active:hover, -.datepicker table tr td.active.disabled, -.datepicker table tr td.active.disabled:hover { - color: #ffffff; - background-color: #428bca; - border-color: #357ebd; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); -} -.datepicker table tr td.active:hover, -.datepicker table tr td.active:hover:hover, -.datepicker table tr td.active.disabled:hover, -.datepicker table tr td.active.disabled:hover:hover, -.datepicker table tr td.active:focus, -.datepicker table tr td.active:hover:focus, -.datepicker table tr td.active.disabled:focus, -.datepicker table tr td.active.disabled:hover:focus, -.datepicker table tr td.active:active, -.datepicker table tr td.active:hover:active, -.datepicker table tr td.active.disabled:active, -.datepicker table tr td.active.disabled:hover:active, -.datepicker table tr td.active.active, -.datepicker table tr td.active:hover.active, -.datepicker table tr td.active.disabled.active, -.datepicker table tr td.active.disabled:hover.active, -.open .dropdown-toggle.datepicker table tr td.active, -.open .dropdown-toggle.datepicker table tr td.active:hover, -.open .dropdown-toggle.datepicker table tr td.active.disabled, -.open .dropdown-toggle.datepicker table tr td.active.disabled:hover { - color: #ffffff; - background-color: #3276b1; - border-color: #285e8e; -} -.datepicker table tr td.active:active, -.datepicker table tr td.active:hover:active, -.datepicker table tr td.active.disabled:active, -.datepicker table tr td.active.disabled:hover:active, -.datepicker table tr td.active.active, -.datepicker table tr td.active:hover.active, -.datepicker table tr td.active.disabled.active, -.datepicker table tr td.active.disabled:hover.active, -.open .dropdown-toggle.datepicker table tr td.active, -.open .dropdown-toggle.datepicker table tr td.active:hover, -.open .dropdown-toggle.datepicker table tr td.active.disabled, -.open .dropdown-toggle.datepicker table tr td.active.disabled:hover { - background-image: none; -} -.datepicker table tr td.active.disabled, -.datepicker table tr td.active:hover.disabled, -.datepicker table tr td.active.disabled.disabled, -.datepicker table tr td.active.disabled:hover.disabled, -.datepicker table tr td.active[disabled], -.datepicker table tr td.active:hover[disabled], -.datepicker table tr td.active.disabled[disabled], -.datepicker table tr td.active.disabled:hover[disabled], -fieldset[disabled] .datepicker table tr td.active, -fieldset[disabled] .datepicker table tr td.active:hover, -fieldset[disabled] .datepicker table tr td.active.disabled, -fieldset[disabled] .datepicker table tr td.active.disabled:hover, -.datepicker table tr td.active.disabled:hover, -.datepicker table tr td.active:hover.disabled:hover, -.datepicker table tr td.active.disabled.disabled:hover, -.datepicker table tr td.active.disabled:hover.disabled:hover, -.datepicker table tr td.active[disabled]:hover, -.datepicker table tr td.active:hover[disabled]:hover, -.datepicker table tr td.active.disabled[disabled]:hover, -.datepicker table tr td.active.disabled:hover[disabled]:hover, -fieldset[disabled] .datepicker table tr td.active:hover, -fieldset[disabled] .datepicker table tr td.active:hover:hover, -fieldset[disabled] .datepicker table tr td.active.disabled:hover, -fieldset[disabled] .datepicker table tr td.active.disabled:hover:hover, -.datepicker table tr td.active.disabled:focus, -.datepicker table tr td.active:hover.disabled:focus, -.datepicker table tr td.active.disabled.disabled:focus, -.datepicker table tr td.active.disabled:hover.disabled:focus, -.datepicker table tr td.active[disabled]:focus, -.datepicker table tr td.active:hover[disabled]:focus, -.datepicker table tr td.active.disabled[disabled]:focus, -.datepicker table tr td.active.disabled:hover[disabled]:focus, -fieldset[disabled] .datepicker table tr td.active:focus, -fieldset[disabled] .datepicker table tr td.active:hover:focus, -fieldset[disabled] .datepicker table tr td.active.disabled:focus, -fieldset[disabled] .datepicker table tr td.active.disabled:hover:focus, -.datepicker table tr td.active.disabled:active, -.datepicker table tr td.active:hover.disabled:active, -.datepicker table tr td.active.disabled.disabled:active, -.datepicker table tr td.active.disabled:hover.disabled:active, -.datepicker table tr td.active[disabled]:active, -.datepicker table tr td.active:hover[disabled]:active, -.datepicker table tr td.active.disabled[disabled]:active, -.datepicker table tr td.active.disabled:hover[disabled]:active, -fieldset[disabled] .datepicker table tr td.active:active, -fieldset[disabled] .datepicker table tr td.active:hover:active, -fieldset[disabled] .datepicker table tr td.active.disabled:active, -fieldset[disabled] .datepicker table tr td.active.disabled:hover:active, -.datepicker table tr td.active.disabled.active, -.datepicker table tr td.active:hover.disabled.active, -.datepicker table tr td.active.disabled.disabled.active, -.datepicker table tr td.active.disabled:hover.disabled.active, -.datepicker table tr td.active[disabled].active, -.datepicker table tr td.active:hover[disabled].active, -.datepicker table tr td.active.disabled[disabled].active, -.datepicker table tr td.active.disabled:hover[disabled].active, -fieldset[disabled] .datepicker table tr td.active.active, -fieldset[disabled] .datepicker table tr td.active:hover.active, -fieldset[disabled] .datepicker table tr td.active.disabled.active, -fieldset[disabled] .datepicker table tr td.active.disabled:hover.active { - background-color: #428bca; - border-color: #357ebd; -} -.datepicker table tr td span { - display: block; - width: 23%; - height: 54px; - line-height: 54px; - float: left; - margin: 1%; - cursor: pointer; - border-radius: 4px; -} -.datepicker table tr td span:hover { - background: #eeeeee; -} -.datepicker table tr td span.disabled, -.datepicker table tr td span.disabled:hover { - background: none; - color: #999999; - cursor: default; -} -.datepicker table tr td span.active, -.datepicker table tr td span.active:hover, -.datepicker table tr td span.active.disabled, -.datepicker table tr td span.active.disabled:hover { - color: #ffffff; - background-color: #428bca; - border-color: #357ebd; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); -} -.datepicker table tr td span.active:hover, -.datepicker table tr td span.active:hover:hover, -.datepicker table tr td span.active.disabled:hover, -.datepicker table tr td span.active.disabled:hover:hover, -.datepicker table tr td span.active:focus, -.datepicker table tr td span.active:hover:focus, -.datepicker table tr td span.active.disabled:focus, -.datepicker table tr td span.active.disabled:hover:focus, -.datepicker table tr td span.active:active, -.datepicker table tr td span.active:hover:active, -.datepicker table tr td span.active.disabled:active, -.datepicker table tr td span.active.disabled:hover:active, -.datepicker table tr td span.active.active, -.datepicker table tr td span.active:hover.active, -.datepicker table tr td span.active.disabled.active, -.datepicker table tr td span.active.disabled:hover.active, -.open .dropdown-toggle.datepicker table tr td span.active, -.open .dropdown-toggle.datepicker table tr td span.active:hover, -.open .dropdown-toggle.datepicker table tr td span.active.disabled, -.open .dropdown-toggle.datepicker table tr td span.active.disabled:hover { - color: #ffffff; - background-color: #3276b1; - border-color: #285e8e; -} -.datepicker table tr td span.active:active, -.datepicker table tr td span.active:hover:active, -.datepicker table tr td span.active.disabled:active, -.datepicker table tr td span.active.disabled:hover:active, -.datepicker table tr td span.active.active, -.datepicker table tr td span.active:hover.active, -.datepicker table tr td span.active.disabled.active, -.datepicker table tr td span.active.disabled:hover.active, -.open .dropdown-toggle.datepicker table tr td span.active, -.open .dropdown-toggle.datepicker table tr td span.active:hover, -.open .dropdown-toggle.datepicker table tr td span.active.disabled, -.open .dropdown-toggle.datepicker table tr td span.active.disabled:hover { - background-image: none; -} -.datepicker table tr td span.active.disabled, -.datepicker table tr td span.active:hover.disabled, -.datepicker table tr td span.active.disabled.disabled, -.datepicker table tr td span.active.disabled:hover.disabled, -.datepicker table tr td span.active[disabled], -.datepicker table tr td span.active:hover[disabled], -.datepicker table tr td span.active.disabled[disabled], -.datepicker table tr td span.active.disabled:hover[disabled], -fieldset[disabled] .datepicker table tr td span.active, -fieldset[disabled] .datepicker table tr td span.active:hover, -fieldset[disabled] .datepicker table tr td span.active.disabled, -fieldset[disabled] .datepicker table tr td span.active.disabled:hover, -.datepicker table tr td span.active.disabled:hover, -.datepicker table tr td span.active:hover.disabled:hover, -.datepicker table tr td span.active.disabled.disabled:hover, -.datepicker table tr td span.active.disabled:hover.disabled:hover, -.datepicker table tr td span.active[disabled]:hover, -.datepicker table tr td span.active:hover[disabled]:hover, -.datepicker table tr td span.active.disabled[disabled]:hover, -.datepicker table tr td span.active.disabled:hover[disabled]:hover, -fieldset[disabled] .datepicker table tr td span.active:hover, -fieldset[disabled] .datepicker table tr td span.active:hover:hover, -fieldset[disabled] .datepicker table tr td span.active.disabled:hover, -fieldset[disabled] .datepicker table tr td span.active.disabled:hover:hover, -.datepicker table tr td span.active.disabled:focus, -.datepicker table tr td span.active:hover.disabled:focus, -.datepicker table tr td span.active.disabled.disabled:focus, -.datepicker table tr td span.active.disabled:hover.disabled:focus, -.datepicker table tr td span.active[disabled]:focus, -.datepicker table tr td span.active:hover[disabled]:focus, -.datepicker table tr td span.active.disabled[disabled]:focus, -.datepicker table tr td span.active.disabled:hover[disabled]:focus, -fieldset[disabled] .datepicker table tr td span.active:focus, -fieldset[disabled] .datepicker table tr td span.active:hover:focus, -fieldset[disabled] .datepicker table tr td span.active.disabled:focus, -fieldset[disabled] .datepicker table tr td span.active.disabled:hover:focus, -.datepicker table tr td span.active.disabled:active, -.datepicker table tr td span.active:hover.disabled:active, -.datepicker table tr td span.active.disabled.disabled:active, -.datepicker table tr td span.active.disabled:hover.disabled:active, -.datepicker table tr td span.active[disabled]:active, -.datepicker table tr td span.active:hover[disabled]:active, -.datepicker table tr td span.active.disabled[disabled]:active, -.datepicker table tr td span.active.disabled:hover[disabled]:active, -fieldset[disabled] .datepicker table tr td span.active:active, -fieldset[disabled] .datepicker table tr td span.active:hover:active, -fieldset[disabled] .datepicker table tr td span.active.disabled:active, -fieldset[disabled] .datepicker table tr td span.active.disabled:hover:active, -.datepicker table tr td span.active.disabled.active, -.datepicker table tr td span.active:hover.disabled.active, -.datepicker table tr td span.active.disabled.disabled.active, -.datepicker table tr td span.active.disabled:hover.disabled.active, -.datepicker table tr td span.active[disabled].active, -.datepicker table tr td span.active:hover[disabled].active, -.datepicker table tr td span.active.disabled[disabled].active, -.datepicker table tr td span.active.disabled:hover[disabled].active, -fieldset[disabled] .datepicker table tr td span.active.active, -fieldset[disabled] .datepicker table tr td span.active:hover.active, -fieldset[disabled] .datepicker table tr td span.active.disabled.active, -fieldset[disabled] .datepicker table tr td span.active.disabled:hover.active { - background-color: #428bca; - border-color: #357ebd; -} -.datepicker table tr td span.old, -.datepicker table tr td span.new { - color: #999999; -} -.datepicker .datepicker-switch { - width: 145px; -} -.datepicker thead tr:first-child th, -.datepicker tfoot tr th { - cursor: pointer; -} -.datepicker thead tr:first-child th:hover, -.datepicker tfoot tr th:hover { - background: #eeeeee; -} -.datepicker .cw { - font-size: 10px; - width: 12px; - padding: 0 2px 0 5px; - vertical-align: middle; -} -.datepicker thead tr:first-child .cw { - cursor: default; - background-color: transparent; -} -.input-group.date .input-group-addon { - cursor: pointer; -} -.input-daterange { - width: 100%; -} -.input-daterange input { - text-align: center; -} -.input-daterange input:first-child { - border-radius: 3px 0 0 3px; -} -.input-daterange input:last-child { - border-radius: 0 3px 3px 0; -} -.input-daterange .input-group-addon { - width: auto; - min-width: 16px; - padding: 4px 5px; - font-weight: normal; - line-height: 1.42857143; - text-align: center; - text-shadow: 0 1px 0 #fff; - vertical-align: middle; - background-color: #eeeeee; - border: solid #cccccc; - border-width: 1px 0; - margin-left: -5px; - margin-right: -5px; -} diff --git a/awx/ui/client/legacy/styles/codemirror.less b/awx/ui/client/legacy/styles/codemirror.less deleted file mode 100644 index 0a0a1ac47ab2..000000000000 --- a/awx/ui/client/legacy/styles/codemirror.less +++ /dev/null @@ -1,49 +0,0 @@ -/********************************************* - * Copyright (c) 2015 Ansible, Inc. - * Overrides to angular-codemirror - * - * - */ - -.CodeMirror { - height: auto; - overflow-x: auto; - overflow-y: hidden; - border: 1px solid @b7grey; -} - -.CodeMirror, -.CodeMirror-activeline-background { - background-color: @default-secondary-bg; -} - -/* Make sure code editor dialog is always at top of stack */ -[aria-describedby=af-code-editor-modal].ui-front { - z-index: 2050; -} - -.CodeMirror-lint-tooltip { - z-index: 2060; -} - -.CodeMirror-gutters { - border-color: @d7grey; - background-color: @default-no-items-bord; -} - -.CodeMirror-linenumber { - color: @at-gray-70; -} - -// Disabled -textarea[disabled="disabled"] + div[id*="-container"]{ - .CodeMirror-gutters { - border-color: @b7grey; - } - - .CodeMirror-gutter.CodeMirror-lint-markers, - .CodeMirror-gutter.CodeMirror-linenumbers { - background-color: @default-bg; - color: @default-interface-txt; - } -} diff --git a/awx/ui/client/legacy/styles/dashboard.less b/awx/ui/client/legacy/styles/dashboard.less deleted file mode 100644 index 2b1ee7c00e8c..000000000000 --- a/awx/ui/client/legacy/styles/dashboard.less +++ /dev/null @@ -1,114 +0,0 @@ -/********************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * dashboard.css - * - * custom styles for the new dashboard - * - */ - -.graph-wrapper { - width: 100%; -} - -.graph { - background-color: white; - // @include transition(width 2s ease-in-out, height 2s ease-in-out); - position: relative; - text-align: center; - width: 100%; - height: 100%; - margin: 0 auto; -} - -.job-status-graph, .host-count-graph{ - font: 10px sans-serif; -} - -.axis path, -.axis line { - fill: none; - stroke: #000; - shape-rendering: crispEdges; -} - -.line { - fill: none; - stroke: steelblue; - stroke-width: 1.5px; -} - - -.dashboard-jobs-list-container { - border: 1px solid @grey; - border-radius: 4px; - padding: 5px; -} - -/* -.graph-container{ - border: 1px solid @grey; - border-radius: 4px; - padding: 5px; - margin-bottom: 15px; -} -due to the login screen showing on top of the dashboard, we're hiding the borders until after the user logs in*/ - -.count-container{ - border: 1px solid @grey; - border-radius: 4px; - margin-left: 0px; - margin-right: 0px; - margin-bottom: 15px; -} - -#replacementImg{ - align:center; - width: 100px; - height: 100px; - -} - -#dashboard-tab-content{ - padding-top: 5px; -} - -#dashboard-tab-content .search-row #active-jobs-search-container{ - height: 35px; -} - - -#dashboard-tab-content table{ - margin-bottom: 10px; -} - -.dashboard-jobs-list-container { - #jobs_table, - #schedules_table { - table-layout:fixed; - } - #jobs_table .name-column, - #schedules_table .name-column { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } -} - -#dash-jobs-list{ - padding-bottom: 15px; -} - - @media (min-width: 769px) { - .left-side { - padding-right: 7px; - } - - .right-side { - padding-left: 7px; - } -} - -.m, .n{ - cursor:pointer; -} diff --git a/awx/ui/client/legacy/styles/event-viewer.less b/awx/ui/client/legacy/styles/event-viewer.less deleted file mode 100644 index ac575b4c0352..000000000000 --- a/awx/ui/client/legacy/styles/event-viewer.less +++ /dev/null @@ -1,60 +0,0 @@ -/********************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * event-viewer.less - * - * custom styles for EventViewer.js helper - * - */ - -#eventviewer-modal-dialog { - - textarea { - overflow: scroll; - } - pre { - overflow: scroll; - word-wrap: normal; - word-break: normal; - white-space: pre-wrap; - } -} - -table.eventviewer-status { - margin-top: 20px; - - .key { - width: 20%; - font-weight: bold; - } - .value i { - font-size: 12px; - } -} - -.nested-table { - border: none; - padding: 0; - table { - border-top: none; - border-bottom: none; - width: auto; - td { - vertical-align: top; - padding: 0 3px 3px 3px; - } - tr { - border-top: none; - border-bottom: 1px solid #ddd; - } - /*tr td:first-of-type { - width: auto; - }*/ - tr:last-of-type { - border-bottom: none; - } - .key { - font-weight: 400; - } - } -} \ No newline at end of file diff --git a/awx/ui/client/legacy/styles/fonts.less b/awx/ui/client/legacy/styles/fonts.less deleted file mode 100644 index e75c35f69973..000000000000 --- a/awx/ui/client/legacy/styles/fonts.less +++ /dev/null @@ -1,19 +0,0 @@ -.include-font(@family-name; @filename; @weight: normal; @style: normal) { - @font-face { - font-family: @family-name; - src: url("/static/assets/@{filename}.woff2") format('woff2'), - url("/static/assets/@{filename}.woff") format('woff'); - font-weight: @weight; - font-style: @style; - } -} - -.include-font('merriweather'; 'merriweather_light-webfont'; 200); -.include-font('merriweather'; 'merriweather-regular-webfont'); -.include-font('merriweather'; 'merriweather-bold-webfont'; bold); -.include-font('merriweather'; 'merriweather_ultrabold-webfont'; 800); -.include-font('merriweather'; 'merriweather-lightitalic-webfont'; 200; italic); -.include-font('merriweather'; 'merriweather-bolditalic-webfont'; bold; italic); -.include-font('merriweather'; 'merriweather-heavyitalic-webfont'; 800; italic); -.include-font('merriweather'; 'merriweather-italic-webfont'; normal; italic); - diff --git a/awx/ui/client/legacy/styles/forms.less b/awx/ui/client/legacy/styles/forms.less deleted file mode 100644 index ccfc95f41250..000000000000 --- a/awx/ui/client/legacy/styles/forms.less +++ /dev/null @@ -1,815 +0,0 @@ -/********************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * Forms.less - * - * custom styles for generated forms - * - */ - -.noselect { - -webkit-touch-callout: none; /* iOS Safari */ - -webkit-user-select: none; /* Chrome/Safari/Opera */ - -khtml-user-select: none; /* Konqueror */ - -moz-user-select: none; /* Firefox */ - -ms-user-select: none; /* Internet Explorer/Edge */ - user-select: none; /* Non-prefixed version, currently - not supported by any browser */ -} - -.Form { - display:flex; - flex-wrap:wrap; - flex-direction: row; -} - -.Form-header--fields { - flex: 1 1 auto; -} - -.Form-header-field { - margin-left: 10px; - flex: 1 1 auto; -} - -.Form-header { - display: flex; -} - -.Form-title { - flex: 0 1 auto; - color: @list-header-txt; - font-size: 14px; - font-weight: bold; - word-break: break-all; - max-width: 90%; - word-wrap: break-word; - margin-bottom: 20px; -} - -.Form-title--uppercase { - text-transform: uppercase; -} - -.Form-secondaryTitle { - color: @default-icon; - padding-bottom: 20px; - min-height: 40px; -} - -.Form-title--is_smartinventory, -.Form-title--is_superuser, -.Form-title--is_system_auditor, -.Form-title--is_ldap_user, -.Form-title--is_external_account, -.Form-title--roleType { - background-color: @default-list-header-bg; - border-radius: 5px; - color: @default-interface-txt; - font-size: 10px; - font-weight: 100; - height:15px; - margin-left: 10px; - margin-top: 2.25px; - padding: 0 10px; - text-transform: uppercase; -} - -.Form-title--last_login { - border-radius: 5px; - color: @default-interface-txt; - font-size: 10px; - font-weight: 100; - height:15px; - margin-left: 10px; - margin-top: 2.25px; - padding: 0 10px; - text-transform: uppercase; -} - -.Form-exitHolder { - justify-content: flex-end; - display:flex; -} - -.Form-exit { - cursor:pointer; - padding:0px; - border: none; - height:20px; - font-size: 20px; - background-color:@default-bg; - color:@d7grey; - transition: color 0.2s; - line-height:1; -} - -.Form-exit:hover { - color:@default-icon; -} - -.Form-tabHolder { - display: flex; - min-height: 30px; - flex-wrap:wrap; -} - -.Form-tabHolder--licenseSelected { - margin-bottom: -20px; -} - -.Form-tabs { - flex: 1 0 auto; - display: flex; -} - -.Form-tab { - color: @btn-txt; - background-color: @btn-bg; - font-size: 12px; - border: 1px solid @b7grey; - height: 30px; - border-radius: 5px; - margin-right: 20px; - margin-bottom: 20px; - padding-left: 10px; - padding-right: 10px; - padding-bottom: 5px; - padding-top: 5px; - transition: background-color 0.2s; - text-transform: uppercase; - text-align: center; - white-space: nowrap; - .noselect; -} - -.Form-tab--notitle { - margin-bottom: 0px; -} - -.Form-tab:hover { - color: @btn-txt; - background-color: @btn-bg-hov; - cursor: pointer; -} - -.Form-tab:active { - color: @btn-txt-sel; - background-color: @btn-bg-sel; - cursor: pointer; -} - -.Form-tab:focus { - color: @btn-txt-sel; -} - -.Form-tab.is-selected { - color: @btn-txt-sel; - background-color: @default-icon; - border-color: @default-icon; -} - -.Form-tab--disabled, -.Form-button--disabled { - opacity: 0.65; - color: @btn-txt; -} - -.Form-tab--disabled:hover, -.Form-button--disabled:hover { - color: @btn-txt; - background-color: @btn-bg; - cursor:not-allowed!important; -} - -.Form-tabSection { - display: none; - width: 0%; -} - -.Form-tabSection.is-selected { - width: 100%; - display: block; -} - -.Form-tabActions { - display: flex; -} - -.Form-formGroup { - flex: 1 0 auto; - margin-bottom: 20px; - width: 33%; - max-width: 33%; - padding-right: 30px; -} - -.Form-formGroup--fullWidth { - max-width: none !important; - width: 100% !important; - padding-right: 0px !important; - margin-top: 10px; -} - -.Form-formGroup--checkbox{ - display: flex; - margin-top: 10px; -} - -.Form-formGroup { - input.form-control { - background-color: @fcgrey; - border-color: @b7grey; - border-radius: 5px; - height: auto; - min-height: 30px; - padding: 0 10px; - } - - input.form-control[disabled], input.form-control[readonly], fieldset[disabled] input.form-control { - border-color: @b7grey; - background-color: @ebgrey; - } -} - -.Form-checkbox--subCheckbox { - font-size: 10px; - color: @default-stdout-txt; - text-transform: uppercase; - margin-top: 2px; - - input { - margin-top: 2px; - } -} - -.Form-textUneditable { - .Form-textInput { - border: none; - padding: 0; - } -} - -.Form-subForm { - width: 100%; - margin-bottom: 15px; - display: flex; - flex-wrap: wrap; - flex-direction: row; - justify-content: flex-start; - position: relative; -} - -.Form-subForm:before { - content: ''; - left: -20px; - position: absolute; - width: 5px; - background-color: @b7grey; - height: 100%; -} - -.Form-subForm--title { - font-weight: bold; - text-transform: uppercase; - color: @default-interface-txt; - font-size: small; - width: 100%; - float: left; - margin-bottom: 10px; -} - -.Form-textAreaLabel { - width:100%; - order: 1; -} - -.Form-formGroup.Form-textAreaLabel { - max-width: 100%; -} - -.Form-textArea { - border-radius: 5px; - color: @field-input-text; - background-color: @field-secondary-bg; - width:100%!important; -} - -.Form-textInput { - height: 30px; - background-color: @field-secondary-bg; - border-radius: 5px; - border:1px solid @field-border; - color: @field-input-text; -} - -.Form-textInput:active { - border:1px solid @field-border-sel; -} - -.Form-monospace { - font-family: Menlo,Monaco,Consolas,"Courier New",monospace!important; -} - -.Form-alertblock { - margin: 0; - font-size: 12px; - width: 100%; - padding: 20px; - margin-bottom: 15px; - border-radius: 4px; - border: 1px solid @login-notice-border; - background-color: @login-notice-bg; - color: @login-notice-text; -} - -.Button-primary--hollow { - border: 1px solid @default-link; - color: @default-link; - background: @default-bg; -} -.Button-primary--hollow:hover { - color: @default-link-hov; - border: 1px solid @default-link-hov; -} - -.ui-spinner { - height: 30px; - background-color: @fcgrey !important; - border-radius: 5px; - border:1px solid @field-border !important; - color: @field-input-text; - width:100% -} - -.ui-spinner-input { - color: @field-input-text; - background-color: @fcgrey; -} - -.ui-spinner-input:focus { - outline: none; -} - -.ui-spinner-button { - border-left:1px solid @field-border!important; - background-color: @field-button-bg !important; -} - -.ui-spinner-button:hover { - background-color:@field-button-hov !important; - cursor: pointer!important; -} - -.Form-inputButton { - border-color: @d7grey; - color: @default-data-txt; -} - -.Form-numberInputButton { - color: @default-icon!important; - font-size: 14px; - display: block; -} - -.Form-dropDown { - min-height: 30px !important; - border-radius: 5px !important; - border:1px solid @field-border!important; - color: @field-input-text!important; -} - -.select2-selection__arrow { - border-left:1px solid @field-border; - border-bottom-right-radius: 5px; - border-top-right-radius: 5px; - width: 30px!important; - height: 28px!important; -} - -.select2-container--disabled .select2-selection__arrow { - background-color: @ebgrey !important; -} - -.select2-results__option { - color: @field-label !important; - min-height: 33px; -} - -.select2-container--default .select2-results__option--highlighted[aria-selected] { - background-color: @field-button-hov !important; -} - -.select2-container--default .select2-results__option[aria-selected=true] { - background-color: @default-white-button-bord !important; -} - -.select2-container--default .select2-selection--single .select2-selection__arrow b { - border-color: @field-dropdown-icon transparent transparent transparent !important; -} - -.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b { - border-color: transparent transparent @field-dropdown-icon transparent!important; -} - -.select2-container--default.select2-container--open.select2-container--below .select2-selection--single { - border-bottom-left-radius: 0 !important; - border-bottom-right-radius: 0 !important; -} - -.select2-dropdown { - border:1px solid @field-border; - z-index: 1030; -} - -.select2-container--open .select2-dropdown--below { - margin-top: -1px; - border-top: 1px solid @field-border; -} - -.Form-dropDown:focus { - outline: none!important; -} - -.Form-dropDown--scmType { - width: 100%; -} - -.Form-browseButton, .Form-passwordButton { - height: 30px; - color: @default-interface-txt; - text-transform: uppercase; - line-height: 1.2; - padding: 6px 12px; - background-color: @default-bg; - border:1px solid @b7grey; -} - -.Form-passwordButton:hover { - cursor: pointer; - background-color: @f2grey; - border: 1px solid @b7grey; - color: @field-lookup-btn-icon; -} - -.Form-passwordButton:focus { - border: 1px solid @field-border; - background-color: @field-lookup-btn-hov-bg; -} - -.Form-passwordButton:active { - border: 1px solid @field-border; - background-color: @field-lookup-btn-hov-bg; -} - -.Form-lookupButton { - height: auto; - width: 30px; - color: @field-lookup-btn-icon!important; - font-size: 16px; - background-color: @field-lookup-btn-bg; - display: flex; - align-items: center; - justify-content: center; - border:1px solid @field-border; -} - -.Form-lookupButton:hover { - cursor: pointer; - background-color: @field-lookup-btn-hov-bg; - color: @default-interface-txt; -} - -.Form-lookupButton:active, -.Form-lookupButton:focus { - border: 1px solid @field-border; -} - -.CodeMirror { - min-height: initial !important; - max-height: initial !important; - border-radius: 5px; - font-style: normal; - color: @field-input-text; -} - -.CodeMirror-gutters { - background-color:@code-mirror-gutter !important; -} - -input[type='radio'] { - -webkit-appearance:none; - width:14px; - height:14px; - border:1px solid @radio-bg; - border-radius:50%; - outline:none; - vertical-align: sub; -} - -input[type='radio']:focus { - outline:none; -} - -input[type='radio']:hover { - box-shadow:0 0 5px 0px @btn-bg-hov inset; -} - -input[type='radio']:before { - content:''; - display:block; - width:65%; - height:60%; - margin: 20% auto; - border-radius:50%; -} - -input[type='radio']:checked:before { - background:@radio-bg; - outline:none; -} - -.Form-inputLabelContainer { - width: 100%; - display: block !important; -} - -.Form-inputLabelContainer[for=variables], -.Form-inputLabelContainer--codeMirror { - width: auto; - display: inline-block !important; -} - -.Form-mixedInputGroup { - display: flex; - width: 100%; - - .form-control { - flex-flow: row wrap; - padding: 0 12px !important; - } - - .input-group-btn { - display: flex; - width: auto; - } -} - -.FormToggle-container { - margin: 0 0 0 10px; - display: initial; - padding-bottom: 5px; - - label { - &:first-child { - border-right: none; - } - &:last-child { - border-left: none; - } - } - - input { - visibility: hidden; - position: absolute; - } - - .btn.btn-xs { - padding: 0px 10px; - } -} - -.Form-inputLabelContainer { - width: 100%; - display: block !important; -} - -.Form-inputLabelContainer[for=variables] { - width: 100%; - display: inline-block !important; -} - -.Form-inputLabel { - text-transform: uppercase; - color: @default-interface-txt; - font-weight: normal; - font-size: small; - width: 100%; - .noselect; -} - -.Form-labelAction { - text-transform: uppercase; - font-weight: normal; - font-size: 0.8em; - padding-left:5px; - float: right; - margin-top: 3px; - .noselect; -} - -.Form-buttons { - height: 30px; - display: flex; - justify-content: flex-end; - - button { - margin-left: 20px; - } - - .at-Tab { - margin-left: 0px; - } -} - -.Form-button { - margin-left: 4px; -} - -.Form-buttonDefault { - background-color: @default-bg; - color: @default-interface-txt; - border-color: @default-border; -} - -.Form-saveButton, .Form-launchButton { - background-color: @submit-button-bg; - color: @submit-button-text; - text-transform: uppercase; - transition: background-color 0.2s; - padding-left:15px; - padding-right: 15px; -} - -.Form-saveButton:disabled, .Form-launchButton:disabled { - background-color: @submit-button-bg-dis; -} - -.Form-saveButton--disabled { - background-color: @submit-button-bg-dis; - cursor: not-allowed; -} - -.Form-saveButton:hover, .Form-launchButton:hover { - background-color: @submit-button-bg-hov; - color: @submit-button-text; -} - -.Form-saveButton--disabled:hover { - background-color: @submit-button-bg-dis; -} - -.Form-cancelButton { - background-color: @default-bg; - color: @btn-txt; - text-transform: uppercase; - border-radius: 5px; - border: 1px solid @field-border; - transition: background-color 0.2s; - padding-left:15px; - padding-right: 15px; -} - -.Form-cancelButton:hover { - background-color: @btn-bg-hov; - color: @btn-txt; -} - -.Form-primaryButton { - background-color: @default-link; - color: @default-bg; - text-transform: uppercase; - padding-left:15px; - padding-right: 15px; - margin-right: 20px; - min-height: 30px; - margin-bottom: 20px; -} - -.Form-buttons .Form-primaryButton { - margin-right: 0; -} - -.Form-primaryButton:hover { - background-color: @default-link-hov; - color: @default-bg; -} - -.Form-primaryButton.Form-tab--disabled:hover, -.Form-primaryButton.Form-button--disabled:hover { - background-color: @default-link; -} - -.Form-primaryButton--noMargin { - margin-right: 0px; -} - -.Form-formGroup--singleColumn { - width: 100% !important; - padding-right: 0px; - max-width: 100% !important; -} - -.Form-checkbox { - float: right; -} - -.Form-subCheckbox { - margin-top: 5px; - font-size: small; - color: @default-interface-txt; - .noselect; -} - -.Form-textInput--variableHeight { - height: inherit; - min-height: 30px; - max-height: 120px; - overflow-y: auto; -} - -.Form-variableHeightButtonGroup { - display: flex; - height: auto; - width: 30px; -} - -.Form-requiredAsterisk { - color: @red; -} - -@media only screen and (max-width: 650px) { - .Form-formGroup { - flex: 1 0 auto; - margin-bottom: 25px; - max-width: 100%; - width: 100%; - padding-right: 50px; - } -} - -@media (min-width: 651px) and (max-width: 900px) { - .Form-formGroup { - flex: 1 0 auto; - margin-bottom: 25px; - max-width: 50%; - width: 50%; - padding-right: 50px; - } -} - -.action_column { - float: right; -} - -.alert-info { - display: flex; - align-items: center; - justify-content: center; - width: 100%; - height: 100px; - border-radius: 5px; - border: 1px solid @default-no-items-bord; - background-color: @default-no-items-bord; - color: @default-icon; - text-transform: uppercase; -} - -.alert-info--noTextTransform { - text-transform: none; -} - -.select2-results__option { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.select2-results__option:hover { - overflow-wrap: break-word; - white-space: normal; -} - -::-webkit-input-placeholder { /* Chrome/Opera/Safari */ - color: @b7grey; -} -::-moz-placeholder { /* Firefox 19+ */ - color: @b7grey; -} -:-ms-input-placeholder { /* IE 10+ */ - color: @b7grey; -} -:-moz-placeholder { /* Firefox 18- */ - color: @b7grey; -} - -.form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control { - border-color: @b7grey; - background-color: @ebgrey; -} - -.Form-checkbox { - label { - font-weight: 400; - } -} diff --git a/awx/ui/client/legacy/styles/inventory-edit.less b/awx/ui/client/legacy/styles/inventory-edit.less deleted file mode 100644 index b608f854c323..000000000000 --- a/awx/ui/client/legacy/styles/inventory-edit.less +++ /dev/null @@ -1,66 +0,0 @@ -/********************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * inventory-edit.less - * - * custom animation mixins for ansible-ui - * - */ -#inventory_edit { - #breadcrumbs .nav-path { - margin-bottom: 8px; - } - #hosts_table .actions{ - float: right; - } - #groups_table .actions{ - float:right; - } -} - -#group-copy-dialog, -#host-copy-dialog { - .highlight { - font-size: 16px; - font-weight: bold; - color: red; - padding-right: 5px; - } - .title { - font-weight: bold; - margin-bottom: 15px; - } - .well { - padding-left: 8px; - padding-right: 8px; - padding-top: 8px; - padding-bottom: 8px; - } - .page-row ul li a { - font-size: 12px; - } - .page-row .col-lg-8 { - width: 100%; - } - .page-row .col-md-8 { - width: 100%; - } -} - -#copy-group-radio-container .form-group { - margin-left: 20px; - margin-bottom: 10px; -} - -#copy-group-target-container .form-group { - margin-top: 10px; - margin-left: 20px; - margin-bottom: 15px; -} - -#group-list-container, -#hosts-container { - .page-row { - margin-top: 5px; - } -} diff --git a/awx/ui/client/legacy/styles/job-details.less b/awx/ui/client/legacy/styles/job-details.less deleted file mode 100644 index 5d79971bfc96..000000000000 --- a/awx/ui/client/legacy/styles/job-details.less +++ /dev/null @@ -1,567 +0,0 @@ -/********************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * job-details.less - * - * Styles for job details page - * - */ - - -@failed-hosts-color: @red; -@successful-hosts-color: @green; -@changed-hosts-color: @changed; -@skipped-hosts-color: @skipped; -@unreachable-hosts-color: @unreachable; - - -#host-events-modal-dialog { - overflow: hidden; - i[class*='icon-job'] { - font-size: 12px; - vertical-align: middle; - } - #search-form { - margin-left: 7px; - } - #host-events-search-name { - width: 200px; - padding-right: 15px; - } - #search-all-input-icons { - position: absolute; - right: 3px; - top: 3px; - a { - color: #a9a9a9; - } - a:hover { - color: @black; - } - } - #status-field { - margin-left: 15px; - } - #host-events-table { - margin-top: 15px; - } - #host-events { - height: 200px; - overflow: scroll; - tr:first-of-type { - border-top-color: @white; - td { - border-top-color: @white; - } - } - } - #fixed-table-header { - margin-bottom: 0; - } - #search-indicator { - margin-left: 15px; - } -} - -@media (max-width: 768px) { - #host-events-modal-dialog { - #search-form-input-icons { - position: absolute; - top: 30px; - left: 185px; - } - #status-field { - margin-left: 0; - } - .form-group { - margin-bottom: 15px; - } - } -} - -#jobs-detail { - - #play-help { - width: 1px; - height: 1px; - color: #fff; - } - - .job_summary { - .table { - margin-bottom: 0; - border: 1px solid @grey; - background-color: @white; - } - .table>tbody>tr>td { - border-top-color: @grey; - padding-bottom: 0; - } - .table>thead>tr>th { - border-bottom-color: @grey; - padding-bottom: 0; - height: 22px; - } - } - - .status-bar { - height: 16px; - overflow: hidden; - border-radius: 4px; - border: 1px solid @grey; - margin-top: 2px; - } - - .inner-bar { - display: inline-block; - overflow: hidden; - height: 16px; - text-align: center; - font-size: 12px; - font-weight: bold; - line-height: normal; - } - - .scroll-spinner { - display: none; - background-color: transparent; - color:#000; - float:right; - margin-right: 5px; - } - - #hostResultsMoreRows.scroll-spinner { - padding-top: 0; - position: relative; - top: 12px; - } - - #hostSummariesMoreRows.scroll-spinner { - margin-right: 0; - } - - .failed-hosts { - background-color: @failed-hosts-color; - } - .failed-hosts-color { - color: @failed-hosts-color; - } - .successful-hosts { - background-color: @successful-hosts-color; - } - .successful-hosts-color { - color: @successful-hosts-color; - } - .changed-hosts { - background-color: @changed-hosts-color; - } - .changed-hosts-color { - color: @changed-hosts-color; - } - .skipped-hosts, .no-matching-hosts { - background-color: @skipped-hosts-color; - } - .unreachable-hosts { - background-color: @unreachable-hosts-color; - } - .unreachable-hosts-color { - color: @unreachable-hosts-color; - } - - .job_well { - padding: 8px; - background-color: @white; - border: 1px solid @grey; - border-radius: 4px; - /*-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);*/ - } - - #hide-summary-button { - text-align: right; - margin-bottom: 15px; - } - - .section { - margin-bottom: 20px; - h5 { - margin-top: 0; - margin-bottom: 12px; - font-weight: bold; - } - } - - .section:last-child { - margin-bottom: 0; - } - - - #job_options { - height: 100px; - overflow-y: auto; - overflow-x: none; - } - - #job_plays, #job_tasks { - overflow-y: auto; - overflow-x: none; - } - - #breadcrumb-container { - padding-right: 15px; - .nav-path { - margin-bottom: 15px; - } - } - - #job-detail-container { - - .well { - overflow: hidden; - } - - #job-status-form { - label { - font-weight: bold; - } - .control-label { - padding-top: 0; - padding-right: 0; - text-align: left; - } - .form-group { - margin-bottom: 15px; - } - hr { - margin-top: 0; - } - .more-or-less { - font-size: 12px; - text-align: left; - margin-bottom: 15px; - } - #started-time, - #finished-time, - #elapsed-time { - display: inline-block; - } - #finished-time, - #elapsed-time { - padding-left: 15px; - } - } - - } - - #job-summary-container { - position: absolute; - top: 0; - right: 0; - padding-right: 15px; - padding-left: 7px; - width: 41.66666667%; - } - - .table-header { - font-weight: bold; - font-size: 12px; - .table>thead>tr>th { - border-bottom-color: @white; - } - .table { - margin-bottom: 0; - } - } - - .table-detail { - overflow-x: hidden; - overflow-y: auto; - background-color: @white; - min-height: 40px; - - .row { - border-top: 1px solid @grey; - } - .row:first-child { - border: none; - } - .loading-info { - padding-top: 5px; - padding-left: 3px; - } - .table { - margin-bottom: 0; - } - .table-condensed > tbody > tr > td { - padding-top: 0; - padding-bottom: 0; - } - } - - .status-column i { - font-size: 12px; - } - - #plays-table-header table, - #plays-table-detail table, - #tasks-table-detail table, - #tasks-table-header table, - #tasks-table-detail table { - table-layout:fixed; - } - - #plays-table-detail td, - #plays-table-header td, - #tasks-table-detail td, - #tasks-table-header td { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - #play-section { - .table-detail { - min-height: 40px; - } - } - - .title { - font-size: 14px; - font-weight: 600; - } - - .header { - width: 100%; - height: 28px; - padding-bottom: 5px; - - - .search-field { - display: inline-block; - position: relative; - float: right; - input { - width: 250px; - padding-right: 20px; - } - a { - position: absolute; - right: 3px; - top: 3px; - color: #a9a9a9; - } - a:hover { - color: @black; - } - } - } - - #summary-search-section { - .remove-left-padding { - padding-left: 0; - } - label { - text-align: left; - font-weight: normal; - padding-left: 0; - padding-right: 0; - padding-top: 6px; - } - #search_all_hosts_name { - width: 100%; - padding-left: 3px; - padding-right: 20px; - } - } - - #event-help-link { - margin-left: 3px; - font-size: 14px; - } - - .title-row { - margin-bottom: 15px; - margin-top: 20px; - } - - .search-form { - font-size: 12px; - .form-control { - font-size: 12px; - } - .form-group:nth-of-type(2) { - margin-left: 10px; - } - } - - .search-name { - .input-xs { - height: 25px; - } - } - - #search-all-input-icons { - position: absolute; - right: 3px; - top: 3px; - a { - color: #a9a9a9; - } - a:hover { - color: @black; - } - } - - label.small-label { - font-size: 12px; - } - - #task-hosts-section { - position: relative; - top: 0; - left: 0; - #hosts-table-header table { - table-layout: fixed; - } - #hosts-table-detail { - background-color: @white; - } - #hosts-table-detail table { - table-layout: fixed; - } - #hosts-table-detail td { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - } - - #hosts-summary-section { - .col-lg-4, .col-md-4, .col-sm-4, .col-xs-4 { - width: 33%; - } - .col-lg-2, .col-md-2, .col-sm-2, .col-xs-2 { - width: 16.5%; - } - .table-header { - font-size: 12px; - } - .table-detail { - height: 200px; - } - .name { - word-break: break-all; - } - .badge { - font-size: 12px; - } - .badge-column a { - width: 20%; - } - } - - // .legend-row { - // margin-bottom: 15px; - // } - - // .legend { - // display: inline-block; - // font-size: 12px; - // text-align: center; - // i { - // font-size: 10px; - // margin-left: 5px; - // } - // i:first-child { - // margin-left: 0; - // } - // } - - #graph-section { - position: relative; - .legend { - font-size: 12px; - font-family: 'Open Sans'; - } - rect { - stroke-width: 2; - } - .donut-tooltip { - display: none; - font-size: 12px; - font-family: 'Open Sans'; - left: 280px; - padding: 10px; - position: absolute; - text-align: center; - top: 85px; - width: 100px; - z-index: 10; - color: white; - background-color: black !important; - border-radius:4px; - border: 1px solid black; - } - } - - #graph-section svg{ - margin: 0 auto; - } - - path.slice{ - stroke-width:2px; - } - - polyline{ - opacity: .3; - stroke: black; - stroke-width: 2px; - fill: none; - } - - svg text.percent{ - fill:@black; - text-anchor:middle; - font-size:12px; - font-weight: bold; - } - - #pre-formatted-variables { - border-radius: 4px; - border: 1px solid @grey; - overflow: auto; - white-space: pre; - word-break: break-all; - word-wrap: break-word; - padding: 9.5px; - font-family: Fixed, monospace; - max-height: 200px; - } - - .footer-row { - height: 20px; - } - - .host_summary_row { - margin-top: 15px; - margin-bottom: -15px; - margin-left: 0; - } - - @media (max-width: 767px) { - #job-detail-container { - #job-status-form { - #finished-time, - #elapsed-time { - display: block; - } - #finished-time, - #elapsed-time { - padding-left: 0; - margin-left: 0; - padding-top: 15px; - } - } - } - } -} diff --git a/awx/ui/client/legacy/styles/jobs.less b/awx/ui/client/legacy/styles/jobs.less deleted file mode 100644 index 887a979d5acc..000000000000 --- a/awx/ui/client/legacy/styles/jobs.less +++ /dev/null @@ -1,99 +0,0 @@ -/********************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * jobs.less - * - * custom styles for the jobs pages - * - */ - -#jobs-page { - - .jobs-list-container { - border: 1px solid @grey; - border-radius: 4px; - padding: 5px; - } - - .job-list { - - i[class*="icon-job-"] { - font-size: 13px; - } - } - - #completed_jobs_table i[class*="icon-job-"] { - font-size: 13px; - } - - #completed_jobs_table, - #schedules_table, - #running_jobs_table, - #queued_jobs_table { - table-layout:fixed; - } - #completed_jobs_table .name-column, - #schedules_table .name-column, - #running_jobs_table .name-column, - #queued_jobs_table .name-column, - { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - .title { - font-weight: bold; - margin-top: 5px; - margin-left: 3px; - } - - #breadcrumbs .nav-path { - background-color: @white; - } - - #breadcrumb-list.breadcrumb { - background-color: @white; - } - - .List-noItems { - margin-top: 0; - } - -} - -@media (min-width: 1201px) { - #jobs-page { - .left-side { - padding-right: 7px; - } - - .right-side { - padding-left: 7px; - } - - .bottom-row { - margin-top: 15px; - } - } -} - -@media (max-width: 1200px) { - #jobs-page .jobs-list-container { - margin-top: 15px; - } - #jobs .jobs-list-container.completed { - margin-top: 0; - } - .title{ - margin-bottom: 8px; - } - #jobs-page { - .left-side { - padding-right: 15px; - } - .right-side { - padding-left: 15px; - } - } -} diff --git a/awx/ui/client/legacy/styles/jquery-ui-overrides.less b/awx/ui/client/legacy/styles/jquery-ui-overrides.less deleted file mode 100644 index 8656b3693fef..000000000000 --- a/awx/ui/client/legacy/styles/jquery-ui-overrides.less +++ /dev/null @@ -1,227 +0,0 @@ -/********************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * jquery-ui-overrides.less - * - * Additions to the custom-theme to make things - * look closer to Twitter Bootstrap - * - */ -table.ui-datepicker-calendar { - background-color: @well; -} - -/* Modal dialog */ -.ui-dialog-title { - font-size: 15px; - color: @default-interface-txt; - font-weight: bold; - line-height: normal; - font-family: 'Open Sans', helvetica; - text-transform: uppercase; -} -.ui-dialog { - .close { - font-size: 18px; - font-weight: bold; - font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; - line-height: 1; - opacity: 1; - text-shadow: 0 1px 0 @white; - color:@d7grey; - } - .close:hover{ - color:@default-icon; - } - .ui-widget { - font-family: 'Open Sans', sans-serif; - } - .ui-widget-header { - border-radius: 0; - border: none; - } - .ui-dialog-titlebar { - padding-bottom: 0; - padding-top: 12px; - - button.close i { - font-size: 24px; - } - } - .ui-dialog-titlebar .ui-state-default { - background-image: none; - background-color: @white; - border-color: @white; - color: #A9A9A9; - } - .mono-space { - font-family: Fixed, monospace; - } - textarea.resizable { - resize: vertical; - } - .ui-resizable-se { - right: 5px; - bottom: 5px; - background-position: -80px -224px; - color: @black; - } - - button.btn.btn-primary, - button.btn.btn-default { - padding: 5px 15px; - font-size: 12px; - line-height: 1.5; - transition: background-color 0.2s; - font-size: 12px; - } - - button.btn.btn-primary { - text-transform: uppercase; - background-color: @default-succ; - border-color: @default-succ; - - &:hover { - background-color: @default-succ-hov; - border-color: @default-succ-hov; - } - - &:disabled { - background-color: @default-succ-disabled; - border-color: @default-succ-disabled; - } - } - - button.btn.btn-default { - text-transform: uppercase; - border-color: @b7grey; - color: @default-interface-txt; - } - - .ui-dialog-buttonpane.ui-widget-content { - border: none; - margin: 0; - padding-top: 0; - padding-right: 8px; - } - - .input-group-btn.dropdown, .List-pagination, .List-tableHeader { - font-family: 'Open Sans', sans-serif; - } - -} -.ui-dialog-buttonpane > .ui-dialog-buttonset { - button { - margin-left: 20px; - } -} - -.ui-dialog-buttonset { - text-transform: uppercase; - - button.btn.btn-default.ui-state-hover, - button.btn.btn-default.ui-state-active, - button.btn.btn-default.ui-state-focus { - font-weight: normal; - } - button.btn.btn-primary.ui-state-hover, - button.btn.btn-primary.ui-state-active, - button.btn.btn-primary.ui-state-focus { - background-image: none; - color: @white; - background-color: @blue-dark; - border-color: #285e8e; - text-decoration: none; - font-weight: normal; - } - max-height: 48px; -} - -.ui-widget-overlay.ui-front { - background-image: none; - background-color: #000; - opacity: .6; -} - -.ui-dialog-content.ui-widget-content { - padding-top: 20px; -} - -.ui-widget-content { - background-image: none; - background-color: @default-bg; - - a, - a:visited, - a:active { - color: @blue-link; - text-decoration: none; - } - - a:hover, - a:focus { - color: @blue-dark; - text-decoration: none; - } - - .red-txt, - a.red-txt:visited, - a.red-txt:hover, - a.red-txt:active { - color: @red; - } - - .dropdown-menu>li>a { - color: @default-interface-txt; - } - - .pagination .active { - a, a:visited, a:active { - color: @white; - } - } -} - -.ui-state-default { - background-image: none; - background-color: @white; - border: 1px solid @grey; -} - -.ui-accordion-header-active { - background-color: #E8E8E8; -} - -/*.ui-state-active { - background-image: none; - background-color: #f5f5f5; -}*/ - -.ui-widget-content { - border: 1px solid @grey; -} - -.ui-spinner a.ui-spinner-button { - border-left: 1px solid #A6C9E2; -} - -.ui-front { - z-index: 1100; -} - -.ui-accordion .ui-accordion-header { - margin-top: 1px; -} - -.ui-spinner.ui-state-disabled, -.ui-state-disabled .ui-spinner-button { - cursor: not-allowed !important; -} - -.ui-state-disabled .ui-spinner-button:hover { - background-color: @default-bg !important; -} - -.ui-state-disabled.ui-widget-content { - background-color: @ebgrey !important; -} diff --git a/awx/ui/client/legacy/styles/lists.less b/awx/ui/client/legacy/styles/lists.less deleted file mode 100644 index 3e004461afe9..000000000000 --- a/awx/ui/client/legacy/styles/lists.less +++ /dev/null @@ -1,639 +0,0 @@ -/********************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * lists.less - * - * custom styles for generated lists - * - */ - -table, tbody { - border-collapse: collapse; -} - -.List-well { - margin-top: 20px; -} - -.List-table{ - width: 100%; - margin-top: 20px; - table-layout: fixed; -} - -.List-tableHeader{ - height: 34px; - font-size: 14px; - font-weight: normal; - text-transform: uppercase; - color: @list-header-txt; - padding-left: 15px; - padding-right: 15px; - border-bottom-width:0px!important; - align-items: center; - display: flex; -} - -.List-tableHeader--info, .List-tableHeader--actions { - justify-content: flex-end; -} - -.List-tableHeader:not([ng-click]) { - cursor: default !important; -} - -.List-tableHeaderSort { - color: @list-header-icon; -} - -.List-tableRow { - min-height: 44px; - font-size: 14px; - color: @list-item; - border-bottom: 1px solid @default-border; -} - -.List-tableRow:last-of-type { - border-bottom: none; -} - -.List-tableRow:hover { - background-color: @f2grey; -} - -.List-tableRow--selected { - background-color: @list-row-select-bord; -} - -.List-tableRow--disabled { - .List-tableCell, .List-tableCell * { - color: @b7grey; - cursor: not-allowed; - } -} - -.List-tableRow--disabled { - .List-actionButton:hover { - color: @list-action-icon; - background-color: @list-actn-bg !important; - } -} - -.List-tableRow--disabled { - .List-actionButtonCell * { - color: @default-err; - font-size: 11px; - text-transform: uppercase; - } -} - -.List-tableRow--invalid { - height: 40px; -} - -.List-tableRow--invalidBar { - align-items: center; - border-left: solid @at-space-2x @at-color-error; - color: @at-white; - display: flex; - height: 100%; - justify-content: center; - position: relative; - - i { - position: absolute; - left: -7px; - } -} - -.List-tableCell { - padding: 7px 15px; - border-top:0px!important; - word-break: break-word; - display: flex; - align-items: center; - height: 100%; -} - -.List-tableCell.description-column { - padding-top: 15px; - padding-bottom: 15px; -} - -.List-actionButtonCell { - padding-top:5px; - padding-right: 0px; - display:flex; - justify-content: flex-end; -} - -.List-actionButton { - font-size: 16px; - height: 30px; - min-width: 30px; - color: @list-action-icon; - background-color: @list-actn-bg; - border: none; - border-radius: 4px; - margin-left: 15px; -} - -.List-actionButton:hover { - background-color: @list-actn-bg-hov !important; - color: @list-actn-icn-hov; -} - -.List-actionButton + .btn-disabled { - &:hover { - color: @default-icon-hov; - background-color: @list-actn-bg !important; - } - color: @default-icon-hov; -} - -.List-actionButton--delete:hover { - background-color: @list-actn-del-bg-hov !important; -} - -.List-header { - display: flex; -} - -.List-title { - align-items: center; - flex: 1 0 auto; - display: flex; -} - -.List-titleBadge { - font-size: 11px; - font-weight: normal; - padding: 2px 10px; - height: 14px; - line-height: 10px; - margin: 0; - background-color: @list-title-badge; - border-radius: 5px; - color: @at-white; -} - -.List-titleBadge--selected { - background-color: @default-link; -} - -.List-titleText { - color: @list-title-txt; - font-size: 14px; - font-weight: bold; - margin-right: 10px; - line-height: 0.8; - text-transform: uppercase; -} - -.List-exitHolder { - justify-content: flex-end; - display:flex; -} - -.List-exit { - cursor:pointer; - padding:0px; - border: none; - height:20px; - font-size: 20px; - background-color:@default-bg; - color:@d7grey; - transition: color 0.2s; - line-height:1; -} - -.List-exit:hover{ - color:@default-icon; -} - -.List-actionHolder { - justify-content: flex-end; - display: flex; - // margin-bottom: 20px; - // float: right; -} - -@media screen and (max-width: 1200px) { - .List-actionHolder--rootGroups { - justify-content: flex-start; - margin-bottom: 55px; - } -} - -.List-actionHolder--leftAlign { - margin-left: 50%; - justify-content: flex-start; - button { - height: 34px; - } -} - -.List-actions { - align-items: center; - display: flex; - margin-bottom: -34px; - - .List-buttonDefault { - margin-left: 20px; - } - - .List-toggleButton { - height: 30px; - line-height: 14px; - } -} - -.List-auxAction { - align-items: center; - display: flex; - order: 1; - margin-left: 20px; -} - -.List-auxActionStream { - width: 200px; -} - -.List-buttonSubmit { - background-color: @submit-button-bg; - color: @submit-button-text; -} - -.List-buttonSubmit:hover, -.List-buttonSubmit:focus { - color: @submit-button-text; - background-color: @submit-button-bg-hov; -} - -.List-buttonDefault { - background-color: @btn-bg; - color: @btn-txt; - border-color: @b7grey; - height: 30px; - line-height: 14px; -} - -.List-buttonDefault:hover, -.List-buttonDefault:focus { - background-color: @btn-bg-hov; - color: @btn-txt; -} - -.List-buttonDefault[disabled] { - opacity: 0.65; -} - -.List-actionButton { - margin-left: 20px; -} - -.List-searchDropdown { - border-top-left-radius: 5px!important; - border-bottom-left-radius: 5px!important; - height: 34px!important; - border-color: @d7grey; - color: @default-icon; - background-color: @default-bg; - border-right: none; -} - -.List-searchDropdown:focus, -.List-searchDropdown:active, -.List-searchDropdown.active -{ - color: @default-icon; - background-color: @default-tertiary-bg; -} - -.List-searchDropdown:hover { - background-color: @default-tertiary-bg; - color: @default-icon; -} - -.List-searchDropdownCarat { - border-top-color: @default-icon!important; -} - -.List-searchInput { - background-color: @list-srch-inpt-bg!important; - font-size: 14px!important; - color: @list-srch-inpt-txt!important; - border-color: @list-srch-inpt-bord!important; - border-radius: 0px 5px 5px 0px!important; - padding-left: 15px!important; - height: 34px!important; - padding-right: 45px!important; - font-family: 'Open Sans', sans-serif; -} - -.List-searchInput:placeholder-shown { - color: @list-srch-inpt-ph-txt!important; - text-transform: uppercase; -} - -.List-searchInput:focus { - border-color: @list-srch-inpt-focus!important; -} - -.List-searchInputIcon { - height: 32px; - width: 32px; - border-left: 1px solid @list-srch-inpt-bord; - color: @list-srch-btn-icon!important; - float: right; - position: relative; - top: -33px; - left: -2px; - z-index: 10; - font-size: 16px; - background-color: @list-srch-btn-bg; - display: flex; - align-items: center; - justify-content: center; - border-top-right-radius: 5px; - border-bottom-right-radius: 5px; -} - -.List-searchInputIcon:hover { - cursor: pointer; - background-color: @list-srch-btn-hov-bg; - color: @list-srch-btn-icon; -} - -.List-searchNoResults { - color: @default-interface-txt; - margin-top: 20px; -} - -.List-noItems { - margin-top: 52px; - display: flex; - align-items: center; - justify-content: center; - width: 100%; - height: 200px; - border-radius: 5px; - border: 1px solid @d7grey; - background-color: @default-no-items-bord; - color: @list-no-items-txt; - text-transform: uppercase; - text-align: center; - padding: 10px; -} - -.modal-body > .List-noItems { - margin-top: 0px; -} - -.modal-body > .List-emptyHostFilter { - margin-top: 20px; -} - -.List-actionButton--selected, -.List-editButton--selected { - background-color: @list-actn-bg-hov !important; - color: @list-actn-icn-hov; -} - -.List-searchWidget { - height: 34px; -} - -.List-searchWidget--compact { - max-width: ~"calc(100% - 91px)"; - margin-top: 10px; -} - -.List-searchRow { - margin-bottom: 20px; -} - -.List-staticColumn--smallStatus { - width: 25px; - padding-right: 0px!important; -} - -.List-staticColumn--mediumStatus { - width: 53px; - padding-right: 0px!important; -} - -.List-staticColumn--toggle { - width: 55px; - padding-right: 0px !important; -} - -.List-staticColumn--schedulerTime { - max-width: 164px; -} - -.List-staticColumn--invalidBar { - width: 10px; - padding-right: 0px!important; -} - -.List-staticColumnAdjacent { - padding-left: 10px!important; -} - -.List-staticColumnAdjacent--monospace { - font-family: monospace; -} - -.List-titleLockup { - margin-left: 4px; - margin-right: 6px; - display: inline-block; - margin-top: 0px; - padding-bottom: 2px; - vertical-align: bottom; -} - -.List-titleLockup:before { - content: "\007C"; - color: #d7d7d7; - display: block; - font-size: 13px; -} - -.List-action--showTooltipOnDisabled { - display: inline-block; - cursor: not-allowed; -} - -.List-action--showTooltipOnDisabled .btn[disabled] { - pointer-events: none; -} - -.List-action--showTooltipOnDisabled.disabled { - cursor: not-allowed; -} - -.List-action--notificationAdd { - text-align: right; - font-size: 11px; -} - -.List-dropdownSuccess { - background-color: @submit-button-bg; - color: @submit-button-text; - border-color: @submit-button-bg-hov; -} - -.List-dropdownSuccess:hover, -.List-dropdownSuccess:focus { - color: @submit-button-text; - background-color: @submit-button-bg-hov; -} - -.List-infoCell { - display: flex; - justify-content: flex-end; - font-size: 0.8em; - cursor: pointer; - - .popover-content { - dl { - display: flex; - margin: 0; - } - dt, dd { - flex: 1 1 50%; - font-weight: inherit; - margin: 0; - } - } -} - -.List-actionsInner { - display: flex; -} - -@media (max-width: 991px) { - .List-searchWidget + .List-searchWidget { - margin-top: 20px; - } -} - -@media (max-width: 700px) { - .List-header { - flex-direction: column; - align-items: stretch; - } - .List-actionHolder { - justify-content: flex-start; - align-items: center; - flex: 1 0 auto; - margin-top: 12px; - } - .List-actions { - margin-bottom: 20px; - } - .List-well { - margin-top: 20px; - } - - .List-action { - margin-left: 20px; - } - - .List-actionsInner { - margin-left: -20px; - } -} - -.InventoryManage-container, .modal-body { - .List-header { - flex-direction: column; - align-items: stretch; - } - .List-actionHolder { - justify-content: flex-start; - align-items: center; - flex: 1 0 auto; - margin-top: 12px; - } - .List-actions { - margin-bottom: 20px; - } - .List-well { - margin-top: 20px; - } - .List-action:not(.ng-hide) ~ .List-action:not(.ng-hide) { - margin-left: 0; - } -} - -// Inventory Manage exceptions -.InventoryManage-container { - .List-actionHolder { - justify-content: flex-end; - } - .List-actions { - margin: 0 0 -34px 0; - } - .SmartSearch-searchTermContainer { - width: 100%; - } -} - -.List-defaultLayout { - display: grid; - grid-template-columns: 5px auto; -} - -.List-lookupLayout { - display: grid; - grid-template-columns: 26px auto; -} - -.List-staticColumnLayout--statusOrCheckbox { - display: grid; - grid-template-columns: 5px 25px auto; -} - -.List-staticColumnLayout--groups { - display: grid; - grid-template-columns: @at-space @at-space-5x @at-space-5x auto; -} - -.List-staticColumnLayout--hosts { - display: grid; - grid-template-columns: 5px 55px 25px auto; -} - -.List-staticColumnLayout--hostsWithCheckbox { - display: grid; - grid-template-columns: 5px 25px 55px 25px auto; -} - -.List-staticColumnLayout--schedules { - display: grid; - grid-template-columns: 5px 15px 55px auto; -} - -.List-staticColumnLayout--toggleOnOff { - display: grid; - grid-template-columns: 5px 55px auto; -} - -.List-table { - margin-top: 20px; -} - -.List-tableHeaderRow { - background-color: @list-header-bg; - border-top-left-radius: 5px; - border-top-right-radius: 5px; -} - -.List-centerEnd { - display: flex; - align-items: center; - justify-content: flex-end; -} diff --git a/awx/ui/client/legacy/styles/log-viewer.less b/awx/ui/client/legacy/styles/log-viewer.less deleted file mode 100644 index 0690205d9a78..000000000000 --- a/awx/ui/client/legacy/styles/log-viewer.less +++ /dev/null @@ -1,29 +0,0 @@ -/********************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * log-viewer.css - * - * custom styles for LogViewer.js helper - * - */ - -#logviewer-modal-dialog { - - textarea { - overflow: scroll; - } - pre { - overflow: scroll; - word-wrap: normal; - word-break: normal; - white-space: pre-wrap; - } -} - -table.logviewer-status { - margin-top: 20px; - - .fld-label { - font-weight: bold; - } -} diff --git a/awx/ui/client/legacy/styles/stdout.less b/awx/ui/client/legacy/styles/stdout.less deleted file mode 100644 index 061e90383a3f..000000000000 --- a/awx/ui/client/legacy/styles/stdout.less +++ /dev/null @@ -1,70 +0,0 @@ -/*************************************************************************** - * Copyright (c) 2015 Ansible, Inc. - * - * Styles for job stdout - * - */ - - #jobs-stdout { - margin-bottom: 0px; - - #job-status { - label { - margin-right: 15px; - } - margin-bottom: 15px; - } - - .scroll-spinner { - display: none; - background-color: transparent; - color:#000; - } - #stdoutMoreRowsTop { - position: absolute; - top: 10px; - right: 20px; - } - #stdoutMoreRowsBottom { - float: right; - padding-right: 15px; - } - #pre-container { - overflow-x: scroll; - overflow-y: auto; - padding: 10px; - } - - } - -.ansi_fore { color: #AAAAAA; } -.ansi_back { background-color: @black; } -.ansi1 { font-weight: bold; } -.ansi3 { font-weight: italic; } -.ansi4 { text-decoration: underline; } -.ansi9 { text-decoration: line-through; } -.ansi30 { color: @default-data-txt; } -.ansi31 { color: @default-err; } -.ansi1.ansi31 { - color: @default-unreachable; -} -.ansi32 { color: @default-succ; } -.ansi33 { color: @default-warning; } -.ansi34 { color: @default-link; } -.ansi35 { color: @default-magenta; } -.ansi36 { color: @default-cyan; } -.ansi37 { color: @default-bg; } -.ansi40 { background-color: @default-stdout-txt; } -.ansi41 { background-color: @default-err; } -.ansi42 { background-color: @default-succ; } -.ansi43 { background-color: @default-warning; } -.ansi44 { background-color: @default-link; } -.ansi45 { background-color: @default-magenta; } -.ansi46 { background-color: @default-cyan; } -.ansi47 { background-color: @default-bg; } - -#pre-container-content > span { - display: inline-block; - white-space: pre-wrap; - word-wrap: normal; -} diff --git a/awx/ui/client/legacy/styles/survey-maker.less b/awx/ui/client/legacy/styles/survey-maker.less deleted file mode 100644 index 176ee7694f1e..000000000000 --- a/awx/ui/client/legacy/styles/survey-maker.less +++ /dev/null @@ -1,92 +0,0 @@ -/********************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * survey-maker.css - * - * custom styles for the survey maker - * - */ - - -/** -* #survey_maker_question_area{ -* border: 1px solid; -* border-color: rgb(204,204,204); -* border-radius: 4px; -* padding: 15px; -* } -*/ - -.question_form{ - border: 1px solid; - border-color: rgb(204,204,204); - border-radius: 4px; - padding: 5px; - margin-bottom: 15px; - height: 500px; -} - -.survey_maker_question{ - border: 1px solid; - border-color: rgb(204,204,204); - border-radius: 4px; - margin-bottom: 10px; -} - -.question_final{ - border-top: 1px dashed; - border-color: rgb(204,204,204); - border-radius: 4px; - padding: 5px; - position: relative; - - .survey-maker-password{ - margin-left: 30px; - } - - .final{ - margin-left: 15px; - margin-top: 5px; - } - .question_title{ - opacity: 0.7; - } - .description{ - opacity: 0.7; - margin-left: 15px; - } - .input_area{ - opacity: 0.7; - } - .mc{ - margin-left: 18px; - margin-right: 7px; - } - -} - -.question_actions{ - opacity: 1.0; -} - -#new_question{ - margin-top: 5px; -} - -#add_question_btn{ - margin-top: 15px; - margin-bottom: 15px; -} - - -.survey_taker_input{ - - .mc{ - margin-left: 18px; - margin-right: 7px; - } -} - -.survey_taker_description{ - margin-bottom:10px -} diff --git a/awx/ui/client/legacy/styles/text-label.less b/awx/ui/client/legacy/styles/text-label.less deleted file mode 100644 index eca6e83cf494..000000000000 --- a/awx/ui/client/legacy/styles/text-label.less +++ /dev/null @@ -1,15 +0,0 @@ -.host-disabled-label { - &:after { - display: inline-block; - content: "disabled"; - border-radius: 3px; - color: @default-icon; - text-transform: uppercase; - font-size: .7em; - font-style: normal; - margin-left: 0.5em; - padding: 0.35em; - padding-bottom: 0.2em; - line-height: 1.1; - } -} diff --git a/awx/ui/client/lib/components/_index.less b/awx/ui/client/lib/components/_index.less deleted file mode 100644 index ea4da9a9c928..000000000000 --- a/awx/ui/client/lib/components/_index.less +++ /dev/null @@ -1,19 +0,0 @@ -@import 'action/_index'; -@import 'approvalsDrawer/_index'; -@import 'dialog/_index'; -@import 'input/_index'; -@import 'launchTemplateButton/_index'; -@import 'layout/_index'; -@import 'list/_index'; -@import 'modal/_index'; -@import 'panel/_index'; -@import 'popover/_index'; -@import 'relaunchButton/_index'; -@import 'tabs/_index'; -@import 'tag/_index'; -@import 'toggle-tag/_index'; -@import 'truncate/_index'; -@import 'utility/_index'; -@import 'code-mirror/_index'; -@import 'cards/_index'; -@import 'switch/_index'; diff --git a/awx/ui/client/lib/components/action/_index.less b/awx/ui/client/lib/components/action/_index.less deleted file mode 100644 index c5be9803a353..000000000000 --- a/awx/ui/client/lib/components/action/_index.less +++ /dev/null @@ -1,7 +0,0 @@ -.at-ActionGroup { - margin-top: @at-margin-panel; - - button { - margin-left: 15px; - } -} diff --git a/awx/ui/client/lib/components/action/action-button.directive.js b/awx/ui/client/lib/components/action/action-button.directive.js deleted file mode 100644 index 733faa6f975e..000000000000 --- a/awx/ui/client/lib/components/action/action-button.directive.js +++ /dev/null @@ -1,48 +0,0 @@ -const templateUrl = require('~components/action/action-button.partial.html'); - -function link (scope, element, attrs, controllers) { - const [actionButtonController] = controllers; - - actionButtonController.init(scope); -} - -function ActionButtonController () { - const vm = this || {}; - - vm.init = (scope) => { - const { variant } = scope; - - if (variant === 'primary') { - vm.color = 'success'; - vm.fill = ''; - } - - if (variant === 'secondary') { - vm.color = 'info'; - vm.fill = ''; - } - - if (variant === 'tertiary') { - vm.color = 'default'; - vm.fill = 'Hollow'; - } - }; -} - -function atActionButton () { - return { - restrict: 'E', - transclude: true, - replace: true, - templateUrl, - require: ['atActionButton'], - controller: ActionButtonController, - controllerAs: 'vm', - link, - scope: { - variant: '@', - } - }; -} - -export default atActionButton; diff --git a/awx/ui/client/lib/components/action/action-button.partial.html b/awx/ui/client/lib/components/action/action-button.partial.html deleted file mode 100644 index d9f9283351d8..000000000000 --- a/awx/ui/client/lib/components/action/action-button.partial.html +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/awx/ui/client/lib/components/action/action-group.directive.js b/awx/ui/client/lib/components/action/action-group.directive.js deleted file mode 100644 index 1974ab1d4331..000000000000 --- a/awx/ui/client/lib/components/action/action-group.directive.js +++ /dev/null @@ -1,16 +0,0 @@ -const templateUrl = require('~components/action/action-group.partial.html'); - -function atActionGroup () { - return { - restrict: 'E', - replace: true, - transclude: true, - templateUrl, - scope: { - col: '@', - pos: '@' - } - }; -} - -export default atActionGroup; diff --git a/awx/ui/client/lib/components/action/action-group.partial.html b/awx/ui/client/lib/components/action/action-group.partial.html deleted file mode 100644 index e0df9581ac88..000000000000 --- a/awx/ui/client/lib/components/action/action-group.partial.html +++ /dev/null @@ -1,5 +0,0 @@ -
-
- -
-
diff --git a/awx/ui/client/lib/components/approvalsDrawer/_index.less b/awx/ui/client/lib/components/approvalsDrawer/_index.less deleted file mode 100644 index a2c58c854ae6..000000000000 --- a/awx/ui/client/lib/components/approvalsDrawer/_index.less +++ /dev/null @@ -1,68 +0,0 @@ -.at-ApprovalsDrawer { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - // z-index of the nav header is 1040 - z-index: 1041; - background-color: rgba(0, 0, 0, 0.3); - - &--drawer { - position: absolute; - right: 0; - top: 0; - height: 100%; - width: 540px; - background-color: @default-bg; - padding: 20px; - overflow-y: scroll; - } - - &--header { - display: flex; - width: 100%; - margin-bottom: 20px; - } - - &--title { - flex: 1 0 auto; - color: @default-interface-txt; - font-size: 14px; - font-weight: bold; - } - - &--actionRow { - display: flex; - justify-content: flex-end; - width: 100%; - margin-top: 10px; - line-height: 30px; - - button { - margin-left: 15px; - } - } - - &--exit { - justify-content: flex-end; - display: flex; - - button { - height: 20px; - font-size: 20px; - color: @d7grey; - line-height: 1; - opacity: 1; - } - - button:hover{ - color: @default-icon; - opacity: 1; - } - } - - &--expires { - color: @default-err; - } -} diff --git a/awx/ui/client/lib/components/approvalsDrawer/approvalsDrawer.directive.js b/awx/ui/client/lib/components/approvalsDrawer/approvalsDrawer.directive.js deleted file mode 100644 index 47443c524ed0..000000000000 --- a/awx/ui/client/lib/components/approvalsDrawer/approvalsDrawer.directive.js +++ /dev/null @@ -1,76 +0,0 @@ -const templateUrl = require('~components/approvalsDrawer/approvalsDrawer.partial.html'); - -function AtApprovalsDrawerController (strings, Rest, GetBasePath, $rootScope) { - const vm = this || {}; - - const toolbarSortDefault = { - label: `${strings.get('sort.CREATED_ASCENDING')}`, - value: 'created' - }; - - vm.strings = strings; - vm.toolbarSortValue = toolbarSortDefault; - vm.queryset = { - page: 1, - page_size: 5, - order_by: 'created', - status: 'pending' - }; - vm.emptyListReason = vm.strings.get('approvals.NONE'); - - vm.toolbarSortOptions = [ - toolbarSortDefault, - { label: `${vm.strings.get('sort.CREATED_DESCENDING')}`, value: '-created' } - ]; - - const loadTheList = () => { - const queryParams = Object.keys(vm.queryset).map(key => `${key}=${vm.queryset[key]}`).join('&'); - Rest.setUrl(`${GetBasePath('workflow_approvals')}?${queryParams}`); - return Rest.get() - .then(({ data }) => { - vm.dataset = data; - vm.approvals = data.results; - vm.count = data.count; - $rootScope.pendingApprovalCount = data.count; - }); - }; - - loadTheList() - .then(() => { vm.listLoaded = true; }); - - vm.approve = (approval) => { - Rest.setUrl(`${GetBasePath('workflow_approvals')}${approval.id}/approve`); - Rest.post() - .then(() => loadTheList()); - }; - - vm.deny = (approval) => { - Rest.setUrl(`${GetBasePath('workflow_approvals')}${approval.id}/deny`); - Rest.post() - .then(() => loadTheList()); - }; - - vm.onToolbarSort = (sort) => { - vm.toolbarSortValue = sort; - vm.queryset.page = 1; - vm.queryset.order_by = sort.value; - loadTheList(); - }; -} - -AtApprovalsDrawerController.$inject = ['ComponentsStrings', 'Rest', 'GetBasePath', '$rootScope']; - -function atApprovalsDrawer () { - return { - restrict: 'E', - transclude: true, - templateUrl, - controller: AtApprovalsDrawerController, - controllerAs: 'vm', - scope: { - closeApprovals: '&' - }, - }; -} - -export default atApprovalsDrawer; diff --git a/awx/ui/client/lib/components/approvalsDrawer/approvalsDrawer.partial.html b/awx/ui/client/lib/components/approvalsDrawer/approvalsDrawer.partial.html deleted file mode 100644 index b9132e79cd5b..000000000000 --- a/awx/ui/client/lib/components/approvalsDrawer/approvalsDrawer.partial.html +++ /dev/null @@ -1,88 +0,0 @@ -
-
-
-
- - {{:: vm.strings.get('approvals.NOTIFICATIONS') }} - - - {{vm.count}} - -
-
- -
-
- - - - -
-
-
- - -
-
-
- - - - -
-
- - - - - - - -
-
-
-
{{:: vm.strings.get('approvals.CONTINUE') }}
- - -
-
-
-
-
- - -
-
diff --git a/awx/ui/client/lib/components/cards/_index.less b/awx/ui/client/lib/components/cards/_index.less deleted file mode 100644 index b85e7ef50efe..000000000000 --- a/awx/ui/client/lib/components/cards/_index.less +++ /dev/null @@ -1,51 +0,0 @@ -/* Cards Group */ -.at-CardContainer { - padding: 20px; -} - -.at-CardGroup { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - grid-gap: 20px; -} - -/* Card */ -.at-Card { - min-height: 103px; - display: flex; - flex-direction: column; - flex: 1; - background-color: @at-white; - text-decoration: none; - text-align: center; - padding: 20px; - border-left: 3px solid @at-white; - border-radius: 2px; - box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12); - transition: box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1); - cursor: pointer; -} - -.at-Card:hover { - background-color: @at-color-body-background; - border-left: 3px solid @at-blue; - box-shadow: 0px 2px 4px -1px rgba(0, 0, 0, 0.2), 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12); -} - -.at-Card-title { - font-size: 14px; - color: @at-gray-70; - font-weight: bold; - line-height: 14px; -} - -.at-Card-text { - font-size: 12px; - color: @at-gray-161b1f; -} - -/* Spacers */ - -.at-Card--spacer { - min-height: 15px; -} \ No newline at end of file diff --git a/awx/ui/client/lib/components/cards/card.directive.js b/awx/ui/client/lib/components/cards/card.directive.js deleted file mode 100644 index f81c712f8b36..000000000000 --- a/awx/ui/client/lib/components/cards/card.directive.js +++ /dev/null @@ -1,14 +0,0 @@ -const templateUrl = require('~components/cards/card.partial.html'); - -function atCard () { - return { - restrict: 'E', - transclude: true, - templateUrl, - scope: { - title: '@', - }, - }; -} - -export default atCard; diff --git a/awx/ui/client/lib/components/cards/card.partial.html b/awx/ui/client/lib/components/cards/card.partial.html deleted file mode 100644 index 4136e44fab8e..000000000000 --- a/awx/ui/client/lib/components/cards/card.partial.html +++ /dev/null @@ -1,5 +0,0 @@ - - {{ title }} - - - \ No newline at end of file diff --git a/awx/ui/client/lib/components/cards/group.directive.js b/awx/ui/client/lib/components/cards/group.directive.js deleted file mode 100644 index e2f61e4543bb..000000000000 --- a/awx/ui/client/lib/components/cards/group.directive.js +++ /dev/null @@ -1,11 +0,0 @@ -const templateUrl = require('~components/cards/group.partial.html'); - -function atCardGroup () { - return { - restrict: 'E', - transclude: true, - templateUrl, - }; -} - -export default atCardGroup; diff --git a/awx/ui/client/lib/components/cards/group.partial.html b/awx/ui/client/lib/components/cards/group.partial.html deleted file mode 100644 index aa07696b1281..000000000000 --- a/awx/ui/client/lib/components/cards/group.partial.html +++ /dev/null @@ -1,4 +0,0 @@ -
-
-
-
\ No newline at end of file diff --git a/awx/ui/client/lib/components/code-mirror/_index.less b/awx/ui/client/lib/components/code-mirror/_index.less deleted file mode 100644 index 98c258d5282c..000000000000 --- a/awx/ui/client/lib/components/code-mirror/_index.less +++ /dev/null @@ -1,89 +0,0 @@ -.noselect { - -webkit-touch-callout: none; /* iOS Safari */ - -webkit-user-select: none; /* Chrome/Safari/Opera */ - -khtml-user-select: none; /* Konqueror */ - -moz-user-select: none; /* Firefox */ - -ms-user-select: none; /* Internet Explorer/Edge */ - user-select: none; /* Non-prefixed version, currently - not supported by any browser */ -} - -.atCodeMirror-label{ - display: flex; - width: 100%; - margin-bottom: 5px; -} - -.atCodeMirror-labelLeftSide{ - flex: 1 0 auto; -} - -.atCodeMirror-labelRightSide{ - display: flex; - align-items: center; -} - -.atCodeMirror-labelText{ - text-transform: uppercase; - color: #707070; - font-weight: normal; - font-size: small; - padding-right: 5px; - width: 100%; -} - -.atCodeMirror-toggleContainer{ - margin: 0 0 0 10px; - display: initial; - padding-bottom: 5px; -} - -.atCodeMirror-expandTextContainer{ - flex: 1 0 auto; - text-align: right; - font-weight: normal; - color: @default-link; - cursor: pointer; - font-size: 12px; -} - -.CodeMirror-modal .modal-dialog{ - width: calc(~"100% - 200px"); - height: calc(~"100vh - 80px"); -} - -@media screen and (min-width: 768px){ - .NetworkingExtraVars .modal-dialog{ - width: 700px; - } -} - -.CodeMirror-modal .modal-dialog{ - width: calc(~"100% - 200px"); - height: calc(~"100vh - 80px"); -} - -.CodeMirror-modal .modal-content{ - height: 100%; -} - -.CodeMirror-modal .CodeMirror { - max-height: calc(~"100vh - 230px"); -} - -.NetworkingExtraVars .CodeMirror{ - overflow-x: hidden; -} - -.CodeMirror-modalControls{ - float: right; - margin-top: 15px; - button { - margin-left: 10px; - } -} - -.atCodeMirror-badge{ - display: initial; - margin-right: 20px; -} diff --git a/awx/ui/client/lib/components/code-mirror/code-mirror.directive.js b/awx/ui/client/lib/components/code-mirror/code-mirror.directive.js deleted file mode 100644 index 4ca2af09f2a0..000000000000 --- a/awx/ui/client/lib/components/code-mirror/code-mirror.directive.js +++ /dev/null @@ -1,138 +0,0 @@ -const templateUrl = require('~components/code-mirror/code-mirror.partial.html'); - -const CodeMirrorModalID = '#CodeMirror-modal'; - -function atCodeMirrorController ( - $scope, - strings, - ParseTypeChange -) { - const vm = this; - const variablesName = `${$scope.name}_variables`; - - function init () { - if ($scope.disabled === 'true') { - $scope.disabled = true; - } else if ($scope.disabled === 'false') { - $scope.disabled = false; - } - $scope.variables = sanitizeVars($scope.variables); - $scope.parseType = 'yaml'; - - $scope.variablesName = variablesName; - $scope[variablesName] = $scope.variables; - ParseTypeChange({ - scope: $scope, - variable: variablesName, - parse_variable: 'parseType', - field_id: `${$scope.name}_variables`, - readOnly: $scope.disabled - }); - - $scope.$watch(variablesName, () => { - $scope.variables = $scope[variablesName]; - }); - } - - function expand () { - vm.expanded = true; - } - - function close (varsFromModal, parseTypeFromModal) { - $scope.variables = varsFromModal; - $scope[variablesName] = $scope.variables; - $scope.parseType = parseTypeFromModal; - // New set of variables from the modal, reinit codemirror - ParseTypeChange({ - scope: $scope, - variable: variablesName, - parse_variable: 'parseType', - field_id: `${$scope.name}_variables`, - readOnly: $scope.disabled - }); - $(CodeMirrorModalID).off('hidden.bs.modal'); - $(CodeMirrorModalID).modal('hide'); - $('.popover').popover('hide'); - vm.expanded = false; - } - - // Adding this function b/c sometimes extra vars are returned to the - // UI as yaml (ex: "foo: bar"), and other times as a - // json-object-string (ex: "{"foo": "bar"}"). The latter typically - // occurs when host vars were system generated and not user-input - // (such as adding a cloud host); - function sanitizeVars (str) { - // Quick function to test if the host vars are a json-object-string, - // by testing if they can be converted to a JSON object w/o error. - function IsJsonString (varStr) { - try { - JSON.parse(varStr); - } catch (e) { - return false; - } - return true; - } - - if (typeof str === 'undefined') { - return '---'; - } - if (typeof str !== 'string') { - const yamlStr = jsyaml.safeDump(str); - // jsyaml.safeDump doesn't process an empty object correctly - if (yamlStr === '{}\n') { - return '---'; - } - return yamlStr; - } - if (str === '' || str === '{}') { - return '---'; - } else if (IsJsonString(str)) { - str = JSON.parse(str); - return jsyaml.safeDump(str); - } - return str; - } - - vm.name = $scope.name; - vm.strings = strings; - vm.expanded = false; - vm.close = close; - vm.expand = expand; - vm.variablesName = variablesName; - vm.parseType = $scope.parseType; - if ($scope.init) { - $scope.init = init; - } - angular.element(document).ready(() => { - init(); - }); -} - -atCodeMirrorController.$inject = [ - '$scope', - 'CodeMirrorStrings', - 'ParseTypeChange' -]; - -function atCodeMirrorTextarea () { - return { - restrict: 'E', - replace: true, - transclude: true, - templateUrl, - controller: atCodeMirrorController, - controllerAs: 'vm', - scope: { - disabled: '@', - label: '@', - labelClass: '@', - tooltip: '@', - tooltipPlacement: '@', - variables: '=', - name: '@', - init: '=' - } - }; -} - -export default atCodeMirrorTextarea; diff --git a/awx/ui/client/lib/components/code-mirror/code-mirror.partial.html b/awx/ui/client/lib/components/code-mirror/code-mirror.partial.html deleted file mode 100644 index df8a141fd94e..000000000000 --- a/awx/ui/client/lib/components/code-mirror/code-mirror.partial.html +++ /dev/null @@ -1,65 +0,0 @@ -
-
-
- - {{ label || vm.strings.get('code_mirror.label.VARIABLES') }} - - - - - - -
-
- - -
-
-
-
{{ vm.strings.get('label.EXPAND') }}
-
- - - -
diff --git a/awx/ui/client/lib/components/code-mirror/code-mirror.strings.js b/awx/ui/client/lib/components/code-mirror/code-mirror.strings.js deleted file mode 100644 index 3180e908b235..000000000000 --- a/awx/ui/client/lib/components/code-mirror/code-mirror.strings.js +++ /dev/null @@ -1,55 +0,0 @@ -function CodeMirrorStrings (BaseString) { - BaseString.call(this, 'code_mirror'); - - const { t } = this; - const ns = this.code_mirror; - - ns.label = { - EXTRA_VARIABLES: t.s('EXTRA VARIABLES'), - VARIABLES: t.s('VARIABLES'), - EXPAND: t.s('EXPAND'), - YAML: t.s('YAML'), - JSON: t.s('JSON'), - READONLY: t.s('READ ONLY') - }; - - ns.tooltip = { - TOOLTIP: t.s(` -

- Enter inventory variables using either JSON or YAML - syntax. Use the radio button to toggle between the two. -

- JSON: -
-
- { -
"somevar": "somevalue", -
"password": "magic" -
- } -
- YAML: -
-
- --- -
somevar: somevalue -
password: magic -
-
-

- View JSON examples at - www.json.org -

-

- View YAML examples at - - docs.ansible.com -

`), - TOOLTIP_TITLE: t.s('EXTRA VARIABLES'), - JOB_RESULTS: t.s('Read-only view of extra variables added to the job template.') - }; -} - -CodeMirrorStrings.$inject = ['BaseStringService']; - -export default CodeMirrorStrings; diff --git a/awx/ui/client/lib/components/code-mirror/index.js b/awx/ui/client/lib/components/code-mirror/index.js deleted file mode 100644 index 21d4aedc098c..000000000000 --- a/awx/ui/client/lib/components/code-mirror/index.js +++ /dev/null @@ -1,12 +0,0 @@ -import codemirror from './code-mirror.directive'; -import modal from './modal/code-mirror-modal.directive'; -import strings from './code-mirror.strings'; - -const MODULE_NAME = 'at.code.mirror'; - -angular.module(MODULE_NAME, []) - .directive('atCodeMirror', codemirror) - .directive('atCodeMirrorModal', modal) - .service('CodeMirrorStrings', strings); - -export default MODULE_NAME; diff --git a/awx/ui/client/lib/components/code-mirror/modal/code-mirror-modal.directive.js b/awx/ui/client/lib/components/code-mirror/modal/code-mirror-modal.directive.js deleted file mode 100644 index 3c0ad60a07cf..000000000000 --- a/awx/ui/client/lib/components/code-mirror/modal/code-mirror-modal.directive.js +++ /dev/null @@ -1,100 +0,0 @@ -const templateUrl = require('~components/code-mirror/modal/code-mirror-modal.partial.html'); - -const CodeMirrorModalID = '#CodeMirror-modal'; -const ModalHeight = '#CodeMirror-modal .modal-dialog'; -const ModalHeader = '.atCodeMirror-label'; -const ModalFooter = '.CodeMirror-modalControls'; - -function atCodeMirrorModalController ( - $scope, - strings, - ParseTypeChange -) { - const vm = this; - function resize () { - if ($scope.disabled === 'true') { - $scope.disabled = true; - } else if ($scope.disabled === 'false') { - $scope.disabled = false; - } - const editor = $(`${CodeMirrorModalID} .CodeMirror`)[0]; - if (editor) { - const height = $(ModalHeight).height() - $(ModalHeader).height() - - $(ModalFooter).height() - 100; - editor.CodeMirror.setSize('100%', height); - } - } - - function toggle () { - $scope.parseTypeChange('modalParseType', 'modalVars'); - setTimeout(resize, 0); - } - - $scope.close = () => { - $scope.closeFn({ - values: $scope.modalVars, - parseType: $scope.modalParseType, - }); - }; - - function init () { - if ($scope.disabled === 'true') { - $scope.disabled = true; - } else if ($scope.disabled === 'false') { - $scope.disabled = false; - } - $(CodeMirrorModalID).modal('show'); - ParseTypeChange({ - scope: $scope, - variable: 'modalVars', - parse_variable: 'modalParseType', - field_id: 'variables_modal', - readOnly: $scope.disabled - }); - resize(); - $(CodeMirrorModalID).on('hidden.bs.modal', $scope.close); - $(`${CodeMirrorModalID} .modal-dialog`).resizable({ - minHeight: 523, - minWidth: 600 - }); - $(`${CodeMirrorModalID} .modal-dialog`).on('resize', resize); - } - - vm.strings = strings; - vm.toggle = toggle; - if ($scope.init) { - $scope.init = init; - } - angular.element(document).ready(() => { - init($scope.variablesName, $scope.name); - }); -} - -atCodeMirrorModalController.$inject = [ - '$scope', - 'CodeMirrorStrings', - 'ParseTypeChange', -]; - -function atCodeMirrorModal () { - return { - restrict: 'E', - replace: true, - transclude: true, - templateUrl, - controller: atCodeMirrorModalController, - controllerAs: 'vm', - scope: { - disabled: '@', - label: '@', - labelClass: '@', - tooltip: '@', - modalVars: '=', - modalParseType: '=', - name: '@', - closeFn: '&' - } - }; -} - -export default atCodeMirrorModal; diff --git a/awx/ui/client/lib/components/code-mirror/modal/code-mirror-modal.partial.html b/awx/ui/client/lib/components/code-mirror/modal/code-mirror-modal.partial.html deleted file mode 100644 index b773412788ca..000000000000 --- a/awx/ui/client/lib/components/code-mirror/modal/code-mirror-modal.partial.html +++ /dev/null @@ -1,71 +0,0 @@ - diff --git a/awx/ui/client/lib/components/components.strings.js b/awx/ui/client/lib/components/components.strings.js deleted file mode 100644 index 436e0b3dda23..000000000000 --- a/awx/ui/client/lib/components/components.strings.js +++ /dev/null @@ -1,138 +0,0 @@ -function ComponentsStrings (BaseString) { - BaseString.call(this, 'components'); - - const { t } = this; - const ns = this.components; - - ns.REPLACE = t.s('Replace'); - ns.REVERT = t.s('Revert'); - ns.ENCRYPTED = t.s('ENCRYPTED'); - ns.OPTIONS = t.s('OPTIONS'); - ns.SHOW = t.s('Show'); - ns.HIDE = t.s('Hide'); - - ns.message = { - REQUIRED_INPUT_MISSING: t.s('Please enter a value.'), - INVALID_INPUT: t.s('Invalid input for this type.') - }; - - ns.file = { - PLACEHOLDER: t.s('CHOOSE A FILE') - }; - - ns.form = { - SUBMISSION_ERROR_TITLE: t.s('Unable to Submit'), - SUBMISSION_ERROR_MESSAGE: t.s('Unexpected server error. View the console for more information'), - SUBMISSION_ERROR_PREFACE: t.s('Unexpected Error') - }; - - ns.group = { - UNSUPPORTED_ERROR_PREFACE: t.s('Unsupported input type') - }; - - ns.label = { - PROMPT_ON_LAUNCH: t.s('Prompt on launch') - }; - - ns.select = { - UNSUPPORTED_TYPE_ERROR: t.s('Unsupported display model type'), - EMPTY_PLACEHOLDER: t.s('NO OPTIONS AVAILABLE') - }; - - ns.textarea = { - SSH_KEY_HINT: t.s('HINT: Drag and drop private file on the field below.') - }; - - ns.lookup = { - NOT_FOUND: t.s('That value was not found. Please enter or select a valid value.') - }; - - ns.truncate = { - DEFAULT: t.s('Copy full revision to clipboard.'), - COPIED: t.s('Copied to clipboard.') - }; - - ns.toggle = { - VIEW_MORE: t.s('VIEW MORE'), - VIEW_LESS: t.s('VIEW LESS') - }; - - ns.tooltips = { - VIEW_THE_CREDENTIAL: t.s('View the Credential'), - }; - - ns.layout = { - CURRENT_USER_LABEL: t.s('Logged in as'), - VIEW_DOCS: t.s('View Documentation'), - LOGOUT: t.s('Logout'), - DASHBOARD: t.s('Dashboard'), - JOBS: t.s('Jobs'), - SCHEDULES: t.s('Schedules'), - MY_VIEW: t.s('My View'), - PROJECTS: t.s('Projects'), - CREDENTIALS: t.s('Credentials'), - CREDENTIAL_TYPES: t.s('Credential Types'), - INVENTORIES: t.s('Inventories'), - TEMPLATES: t.s('Templates'), - ORGANIZATIONS: t.s('Organizations'), - USERS: t.s('Users'), - TEAMS: t.s('Teams'), - INVENTORY_SCRIPTS: t.s('Inventory Scripts'), - NOTIFICATIONS: t.s('Notifications'), - MANAGEMENT_JOBS: t.s('Management Jobs'), - INSTANCES: t.s('Instances'), - INSTANCE_GROUPS: t.s('Instance Groups'), - APPLICATIONS: t.s('Applications'), - SETTINGS: t.s('Settings'), - ABOUT: t.s('About'), - COPYRIGHT: t.s('Copyright © 2019 Red Hat, Inc.'), - VIEWS_HEADER: t.s('Views'), - RESOURCES_HEADER: t.s('Resources'), - ACCESS_HEADER: t.s('Access'), - ADMINISTRATION_HEADER: t.s('Administration'), - AUTHENTICATION: t.s('Authentication'), - SYSTEM: t.s('System'), - USER_INTERFACE: t.s('User Interface'), - LICENSE: t.s('License') - }; - - ns.relaunch = { - DEFAULT: t.s('Relaunch using the same parameters'), - HOSTS: t.s('Relaunch using host parameters'), - DROPDOWN_TITLE: t.s('Relaunch On'), - ALL: t.s('All'), - FAILED: t.s('Failed') - }; - - ns.launchTemplate = { - DEFAULT: t.s('Start a job using this template'), - DISABLED: t.s('Please save before launching this template.'), - BUTTON_LABEL: t.s('LAUNCH') - }; - - ns.list = { - DEFAULT_EMPTY_LIST: t.s('Please add items to this list.') - }; - - ns.toolbar = { - COMPACT: t.s('Compact'), - EXPANDED: t.s('Expanded'), - SORT_BY: t.s('SORT BY') - }; - - ns.approvals = { - APPROVAL: t.s('APPROVAL'), - NONE: t.s('There are no jobs awaiting approval'), - APPROVE: t.s('APPROVE'), - DENY: t.s('DENY'), - CONTINUE: t.s('Continue workflow job?'), - NOTIFICATIONS: t.s('NOTIFICATIONS'), - WORKFLOW_TEMPLATE: t.s('Workflow Template'), - EXPIRES: t.s('Expires:'), - EXPIRES_NEVER: t.s('Expires: Never') - }; -} - -ComponentsStrings.$inject = ['BaseStringService']; - -export default ComponentsStrings; diff --git a/awx/ui/client/lib/components/dialog/_index.less b/awx/ui/client/lib/components/dialog/_index.less deleted file mode 100644 index 60ff25c93513..000000000000 --- a/awx/ui/client/lib/components/dialog/_index.less +++ /dev/null @@ -1,43 +0,0 @@ -.at-Dialog { - display: block; - border: none; - opacity: 1; - background: rgba(0, 0, 0, 0.3); - animation-name: at-DialogFadeIn; - animation-iteration-count: 1; - animation-timing-function: ease-in; - animation-duration: 0.3s; -} - -@keyframes at-DialogFadeIn { - 0% { opacity: 0; background: rgba(0, 0, 0, 0); } - 100% { opacity: 1; background: rgba(0, 0, 0, 0.3); } -} - -.at-Dialog-body { - font-size: @at-font-size; - padding: @at-padding-panel 0; -} - -.at-Dialog-dismiss { - .at-mixin-ButtonIcon(); - font-size: @at-font-size-modal-dismiss; - color: @at-color-icon-dismiss; - text-align: right; -} - -.at-Dialog-heading { - margin: 0; - overflow: visible; - - & > .at-Dialog-dismiss { - margin: 0; - } -} - -.at-Dialog-title { - margin: 0; - padding: 0; - - .at-mixin-Heading(@at-font-size-modal-heading); -} diff --git a/awx/ui/client/lib/components/dialog/dialog.component.js b/awx/ui/client/lib/components/dialog/dialog.component.js deleted file mode 100644 index 888c47033c39..000000000000 --- a/awx/ui/client/lib/components/dialog/dialog.component.js +++ /dev/null @@ -1,11 +0,0 @@ -const templateUrl = require('~components/dialog/dialog.partial.html'); - -export default { - templateUrl, - controllerAs: 'vm', - transclude: true, - bindings: { - title: '=', - onClose: '=', - }, -}; diff --git a/awx/ui/client/lib/components/dialog/dialog.partial.html b/awx/ui/client/lib/components/dialog/dialog.partial.html deleted file mode 100644 index 639a2fed9714..000000000000 --- a/awx/ui/client/lib/components/dialog/dialog.partial.html +++ /dev/null @@ -1,19 +0,0 @@ - diff --git a/awx/ui/client/lib/components/form/action.directive.js b/awx/ui/client/lib/components/form/action.directive.js deleted file mode 100644 index ab8d6e72a384..000000000000 --- a/awx/ui/client/lib/components/form/action.directive.js +++ /dev/null @@ -1,78 +0,0 @@ -const templateUrl = require('~components/form/action.partial.html'); - -function link (scope, element, attrs, controllers) { - const [formController, actionController] = controllers; - - actionController.init(formController, scope); -} - -function atFormActionController ($state, strings) { - const vm = this || {}; - - let form; - let scope; - - vm.init = (_form_, _scope_) => { - form = _form_; - scope = _scope_; - - switch (scope.type) { - case 'cancel': - vm.setCancelDefaults(); - break; - case 'save': - vm.setSaveDefaults(); - break; - case 'secondary': - vm.setSecondaryDefaults(); - break; - default: - vm.setCustomDefaults(); - } - - form.register('action', scope); - }; - - vm.setCancelDefaults = () => { - scope.text = strings.get('CANCEL'); - scope.fill = 'Hollow'; - scope.color = 'default'; - scope.action = () => { $state.go(scope.to || '^'); }; - }; - - vm.setSaveDefaults = () => { - scope.text = strings.get('SAVE'); - scope.fill = ''; - scope.color = 'success'; - scope.action = () => { form.submit(); }; - }; - - vm.setSecondaryDefaults = () => { - scope.text = strings.get('TEST'); - scope.fill = ''; - scope.color = 'info'; - scope.action = () => { form.submitSecondary(); }; - }; -} - -atFormActionController.$inject = ['$state', 'ComponentsStrings']; - -function atFormAction () { - return { - restrict: 'E', - transclude: true, - replace: true, - require: ['^^atForm', 'atFormAction'], - templateUrl, - controller: atFormActionController, - controllerAs: 'vm', - link, - scope: { - state: '=', - type: '@', - to: '@' - } - }; -} - -export default atFormAction; diff --git a/awx/ui/client/lib/components/form/action.partial.html b/awx/ui/client/lib/components/form/action.partial.html deleted file mode 100644 index 78c89d0f91ae..000000000000 --- a/awx/ui/client/lib/components/form/action.partial.html +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/awx/ui/client/lib/components/form/form.directive.js b/awx/ui/client/lib/components/form/form.directive.js deleted file mode 100644 index 7a4d7154d857..000000000000 --- a/awx/ui/client/lib/components/form/form.directive.js +++ /dev/null @@ -1,248 +0,0 @@ -const templateUrl = require('~components/form/form.partial.html'); - -function atFormLink (scope, el, attrs, controllers) { - const formController = controllers[0]; - const form = el[0]; - - scope.ns = 'form'; - scope[scope.ns] = { modal: {} }; - - formController.init(scope, form); -} - -function AtFormController (eventService, strings) { - const vm = this || {}; - - let scope; - let modal; - let form; - - vm.components = []; - vm.state = { - isValid: false, - disabled: false, - value: {}, - }; - - vm.init = (_scope_, _form_) => { - scope = _scope_; - form = _form_; - ({ modal } = scope[scope.ns]); - - vm.state.disabled = scope.state.disabled; - vm.setListeners(); - }; - - vm.register = (category, component) => { - component.category = category; - component.form = vm.state; - - if (category === 'input') { - scope.state[component.state.id] = component.state; - } - - vm.components.push(component); - }; - - vm.setListeners = () => { - const listeners = eventService.addListeners([ - [form, 'keypress', vm.submitOnEnter] - ]); - - scope.$on('$destroy', () => eventService.remove(listeners)); - }; - - vm.submitOnEnter = event => { - if (event.key !== 'Enter' || event.srcElement.type === 'textarea') { - return; - } - - event.preventDefault(); - scope.$apply(vm.submit); - }; - - vm.getSubmitData = () => vm.components - .filter(component => component.category === 'input') - .reduce((values, component) => { - if (component.state._value === undefined) { - return values; - } - - if (component.state._format === 'selectFromOptions') { - values[component.state.id] = component.state._value[0]; - } else if (component.state._key && typeof component.state._value === 'object') { - values[component.state.id] = component.state._value[component.state._key]; - } else if (component.state._group) { - values[component.state._key] = values[component.state._key] || {}; - values[component.state._key][component.state.id] = component.state._value; - } else { - values[component.state.id] = component.state._value; - } - - return values; - }, {}); - - vm.submitSecondary = () => { - if (!vm.state.isValid) { - return; - } - const data = vm.getSubmitData(); - scope.state.secondary(data); - }; - - vm.submit = () => { - if (!vm.state.isValid) { - return; - } - - vm.state.disabled = true; - - const data = vm.getSubmitData(); - - scope.state.save(data) - .then(scope.state.onSaveSuccess) - .catch(err => vm.onSaveError(err)) - .finally(() => { vm.state.disabled = false; }); - }; - - vm.onSaveError = err => { - let handled; - - if (err.status === 400) { - handled = vm.handleValidationError(err.data); - } - - if (err.status === 500) { - handled = vm.handleUnexpectedError(err); - } - - if (!handled) { - let message; - const title = strings.get('form.SUBMISSION_ERROR_TITLE'); - const preface = strings.get('form.SUBMISSION_ERROR_PREFACE'); - - if (typeof err.data === 'object') { - message = JSON.stringify(err.data); - } if (_.has(err, 'data.__all__')) { - if (typeof err.data.__all__ === 'object' && Array.isArray(err.data.__all__)) { - message = JSON.stringify(err.data.__all__[0]); - } else { - message = JSON.stringify(err.data.__all__); - } - } else { - message = err.data; - } - - modal.show(title, `${preface}: ${message}`); - } - }; - - vm.handleUnexpectedError = () => { - const title = strings.get('form.SUBMISSION_ERROR_TITLE'); - const message = strings.get('form.SUBMISSION_ERROR_MESSAGE'); - - modal.show(title, message); - - return true; - }; - - vm.handleValidationError = errors => { - const errorMessageSet = vm.setValidationMessages(errors); - - if (errorMessageSet) { - vm.check(); - } - - return errorMessageSet; - }; - - vm.setValidationMessages = (errors, errorSet) => { - let errorMessageSet = errorSet || false; - - Object.keys(errors).forEach(id => { - if (!Array.isArray(errors[id]) && typeof errors[id] === 'object') { - errorMessageSet = vm.setValidationMessages(errors[id], errorMessageSet); - - return; - } - - vm.components - .filter(component => component.category === 'input') - .filter(component => errors[component.state.id]) - .forEach(component => { - errorMessageSet = true; - - component.state._rejected = true; - component.state._message = errors[component.state.id].join(' '); - }); - }); - - return errorMessageSet; - }; - - vm.validate = () => { - let isValid = true; - - for (let i = 0; i < vm.components.length; i++) { - if (vm.components[i].category !== 'input') { - continue; - } - - if (vm.components[i].state.asTag) { - continue; - } - - if (!vm.components[i].state._isValid) { - isValid = false; - break; - } - } - - return isValid; - }; - - vm.check = () => { - const isValid = vm.validate(); - - if (isValid !== vm.state.isValid) { - vm.state.isValid = isValid; - } - - if (isValid !== scope.state.isValid) { - scope.state.isValid = isValid; - } - }; - - vm.deregisterInputGroup = components => { - for (let i = 0; i < components.length; i++) { - for (let j = 0; j < vm.components.length; j++) { - if (components[i] === vm.components[j].state) { - vm.components.splice(j, 1); - delete scope.state[components[i].id]; - break; - } - } - } - }; -} - -AtFormController.$inject = ['EventService', 'ComponentsStrings']; - -function atForm () { - return { - restrict: 'E', - replace: true, - transclude: true, - require: ['atForm'], - templateUrl, - controller: AtFormController, - controllerAs: 'vm', - link: atFormLink, - scope: { - state: '=', - autocomplete: '@' - } - }; -} - -export default atForm; diff --git a/awx/ui/client/lib/components/form/form.partial.html b/awx/ui/client/lib/components/form/form.partial.html deleted file mode 100644 index 3d45276cd665..000000000000 --- a/awx/ui/client/lib/components/form/form.partial.html +++ /dev/null @@ -1,7 +0,0 @@ -
-
- -
- - -
diff --git a/awx/ui/client/lib/components/index.js b/awx/ui/client/lib/components/index.js deleted file mode 100644 index cc3cd55bb03c..000000000000 --- a/awx/ui/client/lib/components/index.js +++ /dev/null @@ -1,110 +0,0 @@ -import atLibServices from '~services'; - -import actionGroup from '~components/action/action-group.directive'; -import actionButton from '~components/action/action-button.directive'; -import approvalsDrawer from '~components/approvalsDrawer/approvalsDrawer.directive'; -import dialog from '~components/dialog/dialog.component'; -import divider from '~components/utility/divider.directive'; -import dynamicSelect from '~components/input/dynamic-select.directive'; -import form from '~components/form/form.directive'; -import formAction from '~components/form/action.directive'; -import inputCheckbox from '~components/input/checkbox.directive'; -import inputFile from '~components/input/file.directive'; -import inputGroup from '~components/input/group.directive'; -import inputLabel from '~components/input/label.directive'; -import inputLookup from '~components/input/lookup.directive'; -import inputMessage from '~components/input/message.directive'; -import inputSecret from '~components/input/secret.directive'; -import inputSelect from '~components/input/select.directive'; -import inputSlider from '~components/input/slider.directive'; -import inputText from '~components/input/text.directive'; -import inputTextarea from '~components/input/textarea.directive'; -import inputTextareaSecret from '~components/input/textarea-secret.directive'; -import launchTemplate from '~components/launchTemplateButton/launchTemplateButton.component'; -import layout from '~components/layout/layout.directive'; -import list from '~components/list/list.directive'; -import lookupList from '~components/lookup-list/lookup-list.component'; -import modal from '~components/modal/modal.directive'; -import panel from '~components/panel/panel.directive'; -import panelBody from '~components/panel/body.directive'; -import panelHeading from '~components/panel/heading.directive'; -import popover from '~components/popover/popover.directive'; -import relaunch from '~components/relaunchButton/relaunchButton.component'; -import row from '~components/list/row.directive'; -import rowItem from '~components/list/row-item.directive'; -import rowAction from '~components/list/row-action.directive'; -import sideNav from '~components/layout/side-nav.directive'; -import sideNavItem from '~components/layout/side-nav-item.directive'; -import tab from '~components/tabs/tab.directive'; -import tabGroup from '~components/tabs/group.directive'; -import tag from '~components/tag/tag.directive'; -import toggleTag from '~components/toggle-tag/toggle-tag.directive'; -import toolbar from '~components/list/list-toolbar.directive'; -import topNavItem from '~components/layout/top-nav-item.directive'; -import truncate from '~components/truncate/truncate.directive'; -import atCodeMirror from '~components/code-mirror'; -import atSyntaxHighlight from '~components/syntax-highlight'; -import card from '~components/cards/card.directive'; -import cardGroup from '~components/cards/group.directive'; -import atSwitch from '~components/switch/switch.directive'; - -import BaseInputController from '~components/input/base.controller'; -import ComponentsStrings from '~components/components.strings'; - -const MODULE_NAME = 'at.lib.components'; - -angular - .module(MODULE_NAME, [ - atLibServices, - atCodeMirror, - atSyntaxHighlight, - ]) - .directive('atActionGroup', actionGroup) - .directive('atActionButton', actionButton) - .directive('atApprovalsDrawer', approvalsDrawer) - .component('atDialog', dialog) - .directive('atDivider', divider) - .directive('atDynamicSelect', dynamicSelect) - .directive('atForm', form) - .directive('atFormAction', formAction) - .directive('atInputCheckbox', inputCheckbox) - .directive('atInputFile', inputFile) - .directive('atInputGroup', inputGroup) - .directive('atInputLabel', inputLabel) - .directive('atInputLookup', inputLookup) - .directive('atInputMessage', inputMessage) - .directive('atInputSecret', inputSecret) - .directive('atInputSelect', inputSelect) - .directive('atInputSlider', inputSlider) - .directive('atInputText', inputText) - .directive('atInputTextarea', inputTextarea) - .directive('atInputTextareaSecret', inputTextareaSecret) - .component('atLaunchTemplate', launchTemplate) - .directive('atLayout', layout) - .directive('atList', list) - .component('atLookupList', lookupList) - .directive('atListToolbar', toolbar) - .component('atRelaunch', relaunch) - .directive('atRow', row) - .directive('atRowItem', rowItem) - .directive('atRowAction', rowAction) - .directive('atModal', modal) - .directive('atPanel', panel) - .directive('atPanelBody', panelBody) - .directive('atPanelHeading', panelHeading) - .directive('atPopover', popover) - .directive('atSideNav', sideNav) - .directive('atSideNavItem', sideNavItem) - .directive('atTab', tab) - .directive('atTabGroup', tabGroup) - .directive('atTag', tag) - .directive('atToggleTag', toggleTag) - .directive('atTopNavItem', topNavItem) - .directive('atTruncate', truncate) - .directive('atCard', card) - .directive('atCardGroup', cardGroup) - .directive('atSwitch', atSwitch) - .service('BaseInputController', BaseInputController) - .service('ComponentsStrings', ComponentsStrings); - -export default MODULE_NAME; diff --git a/awx/ui/client/lib/components/input/_index.less b/awx/ui/client/lib/components/input/_index.less deleted file mode 100644 index 68be7f59d05d..000000000000 --- a/awx/ui/client/lib/components/input/_index.less +++ /dev/null @@ -1,359 +0,0 @@ -.at-Input { - .at-mixin-Placeholder(@at-color-input-placeholder); - - height: @at-height-input; - background: @at-color-input-background; - border-radius: @at-border-radius; - color: @at-color-input-text; - padding: 0 @at-padding-input; - - &, &:active { - border-color: @at-color-input-border; - } - - &:focus { - border-color: @at-color-input-focus; - } - - &[readonly] { - background: @at-color-input-readonly; - } - - &[disabled] { - background: @at-color-input-disabled; - } -} - -.at-InputCheckbox { - margin: 0; - padding: 0; - display: block; - min-height: 20px; - - & > label { - font-weight: normal; - - & > input[type=checkbox] { - height: @at-height-input; - margin: 0; - padding: 0; - float: left; - } - - & > p { - margin: 0; - padding: 0 0 0 @at-padding-panel; - line-height: @at-line-height-tall; - } - } -} - -.at-InputContainer { - margin-top: @at-margin-panel; -} - -.at-Input-button { - .at-mixin-InputButton(); -} - -.at-Input-button--fixed-xs { - .at-mixin-InputButton(); - min-width: @at-width-input-button-sm; - height: @at-height-input; -} - -.at-Input-button--fixed-sm { - .at-mixin-InputButton(); - min-width: @at-width-input-button-md; - height: @at-height-input; -} - -.at-Input-button--fixed-md { - .at-mixin-InputButton(); - display: inherit; - min-width: @at-width-input-button-md; - height: @at-height-textarea; -} - -.at-Input-button--long-sm { - .at-mixin-InputButton(); - max-width: @at-width-input-button-md; - min-height: @at-height-textarea; -} - -.at-Input-button--active { - .at-mixin-ButtonColor(at-color-info, at-color-default); -} - -.at-Input--focus { - border-color: @at-color-input-focus; -} - -.at-Input--rejected { - &, &:focus { - border-color: @at-color-input-error; - } -} - -.at-InputFile--hidden { - position: absolute; - height: 100%; - width: 100%; - left: 0; - z-index: -2; - opacity: 0; -} - -.at-InputFile--drag { - z-index: 3; -} - -.at-InputGroup { - padding: 0; - margin: @at-margin-panel 0 0 0; -} - -.at-InputGroup-border { - position: absolute; - width: 5px; - height: 100%; - background: @at-color-panel-border; - left: -5px; -} - -.at-InputGroup-title { - .at-mixin-Heading(@at-font-size-panel-inset-heading); - margin: 0 0 0 @at-margin-panel-inset; -} - -.at-InputGroup-divider { - clear: both; - margin: 0; - padding: 0; - height: @at-height-divider; -} - -.at-InputLabel { - display: inline-block; - width: 100%; -} - -.at-InputLabel-name { - color: @at-color-form-label; - font-size: @at-font-size-form-label; - font-weight: @at-font-weight-body; - text-transform: uppercase; -} - -.at-InputLabel-hint { - margin-left: @at-margin-form-label-hint; - color: @at-color-input-hint; - font-size: @at-font-size-help-text; - font-weight: @at-font-weight-body; - line-height: @at-line-height-short; -} - -.at-InputLabel-checkbox { - margin: 0; - padding: 0; -} - -.at-InputLabel-checkboxLabel { - margin-bottom: 0; - - & > input[type=checkbox] { - margin: 0 3px 0 0; - } - - & > p { - font-size: @at-font-size-help-text; - color: @at-color-form-label; - font-weight: @at-font-weight-body; - display: inline; - margin: 0; - padding: 0; - } -} - -.at-InputMessage--rejected { - font-size: @at-font-size-help-text; - color: @at-color-error; - margin: @at-margin-input-message 0 0 0; - padding: 0; -} - -.at-InputLabel-required { - color: @at-color-error; - font-weight: @at-font-weight-heading; - font-size: @at-font-size-form-label; - margin: 0; -} - -.at-InputSelect { - position: relative; - width: 100%; - - & > i { - font-size: @at-font-size-button; - position: absolute; - z-index: 3; - pointer-events: none; - top: @at-height-input / 3; - right: @at-height-input / 3; - color: @at-color-input-icon; - } -} - -.at-InputSelect-input { - position: relative; - z-index: 2; - pointer-events: none; -} - -.at-InputSelect-select { - height: @at-height-input; - cursor: pointer; - position: absolute; - z-index: 1; - top: 0; - - & > optgroup { - text-transform: uppercase; - - & > option { - text-transform: none; - } - } -} - -.at-InputTextarea { - .at-mixin-FontFixedWidth(); - min-height: @at-height-textarea; - padding: 6px @at-padding-input 0 @at-padding-input; -} - -.at-InputLookup { - display: flex; - - .at-InputLookup-button { - .at-mixin-InputButton(); - border-radius: @at-border-radius 0 0 @at-border-radius; - border-right: none; - flex: 0 0 35px; - height: auto; - min-height: 30px - } - - .at-InputLookup-tagContainer { - .at-mixin-Border; - display: flex; - flex-flow: row wrap; - padding: 0 10px; - width: 100%; - } - - .at-InputLookup-button + .at-Input, - .at-InputLookup-tagContainer { - border-radius: 0 @at-border-radius @at-border-radius 0; - } -} - -.at-InputSlider { - display: flex; - padding: 5px 0; - - p { - color: @at-color-form-label; - font-size: @at-font-size-help-text; - font-weight: @at-font-weight-body; - margin: 0 0 0 10px; - padding: 0; - width: 50px; - } - - input[type=range] { - -webkit-appearance: none; - width: 100%; - background: transparent; - height: 20px; - border-right: 1px solid @at-color-input-slider-track; - border-left: 1px solid @at-color-input-slider-track; - - &:focus { - outline: none; - } - - &::-webkit-slider-runnable-track { - background: @at-color-input-slider-track; - cursor: pointer; - height: 1px; - width: 100%; - } - - &::-webkit-slider-thumb { - -webkit-appearance: none; - background-color: @at-color-input-slider-thumb; - border-radius: 50%; - border: none; - cursor: pointer; - height: 16px; - margin-top: -7px; - width: 16px; - } - - } - - input[type=range]::-moz-range-thumb { - -webkit-appearance: none; - background-color: @at-color-input-slider-thumb; - border-radius: 50%; - border: none; - cursor: pointer; - height: 16px; - width: 16px; - } - - input[type=range]::-moz-range-track { - background: @at-color-input-slider-track; - cursor: pointer; - height: 1px; - width: 100%; - } - - input[type=range][disabled] { - &::-webkit-slider-thumb { - background: @at-color-disabled; - border: solid 1px @at-color-disabled; - cursor: not-allowed; - } - } -} - -.at-InputGroup-container { - .row { - margin: 0; - } -} - -.at-InputTaggedTextarea { - .at-mixin-FontFixedWidth(); - min-height: @at-height-textarea; - padding: 6px @at-padding-input 0 @at-padding-input; - border-radius: @at-border-radius; -} - -.at-InputTagContainer { - display: flex; - width: 100%; - flex-wrap: wrap; - - .TagComponent { - max-height: @at-space-4x; - } - - .TagComponent-name { - align-self: auto; - word-break: break-all; - font-family: 'Open Sans', sans-serif; - } -} diff --git a/awx/ui/client/lib/components/input/base.controller.js b/awx/ui/client/lib/components/input/base.controller.js deleted file mode 100644 index eabd536e3423..000000000000 --- a/awx/ui/client/lib/components/input/base.controller.js +++ /dev/null @@ -1,145 +0,0 @@ -function BaseInputController (strings) { - // Default values are universal. Don't translate. - const PROMPT_ON_LAUNCH_VALUE = 'ASK'; - const ENCRYPTED_VALUE = '$encrypted$'; - - return function extend (type, scope, element, form) { - const vm = this; - - vm.strings = strings; - - scope.state = scope.state || {}; - - scope.state._touched = false; - scope.state._required = scope.state.required || false; - scope.state._isValid = scope.state._isValid || false; - scope.state._disabled = scope.state._disabled || false; - scope.state._activeModel = scope.state._activeModel || '_value'; - - if (scope.state.ask_at_runtime) { - scope.state._displayPromptOnLaunch = true; - } - - if (typeof scope.state._value !== 'undefined') { - scope.state._edit = true; - scope.state._preEditValue = scope.state._value; - - if (scope.state._value === PROMPT_ON_LAUNCH_VALUE) { - scope.state._promptOnLaunch = true; - scope.state._disabled = true; - scope.state._activeModel = '_displayValue'; - } - - if (scope.state._value === ENCRYPTED_VALUE) { - scope.state._displayRevertReplace = true; - scope.state._enableToggle = true; - scope.state._disabled = true; - scope.state._isBeingReplaced = false; - scope.state._activeModel = '_displayValue'; - } - } else if (typeof scope.state.default !== 'undefined') { - scope.state._value = scope.state.default; - } - - form.register(type, scope); - - if (scope.form && scope.form.disabled) { - scope.state._enableToggle = false; - } - - vm.validate = () => { - let isValid = true; - let message = ''; - - if (scope.state.asTag) { - return (isValid, message); - } - - if (scope.state._value || scope.state._displayValue) { - scope.state._touched = true; - } - - if (scope.state._required && (!scope.state._value || !scope.state._value[0]) && - !scope.state._displayValue) { - isValid = false; - message = vm.strings.get('message.REQUIRED_INPUT_MISSING'); - } else if (scope.state._validate) { - const result = scope.state._validate(scope.state._value); - - if (!result.isValid) { - isValid = false; - message = result.message || vm.strings.get('message.INVALID_INPUT'); - } - } - - return { - isValid, - message - }; - }; - - vm.updateValidationState = result => { - if (!scope.state._touched && scope.state._required) { - return; - } - - scope.state._rejected = !result.isValid; - scope.state._isValid = result.isValid; - scope.state._message = result.message; - - form.check(); - }; - - vm.check = result => { - result = result || vm.validate(); - - vm.updateValidationState(result); - }; - - vm.onRevertReplaceToggle = () => { - if (!scope.state._isBeingReplaced) { - scope.state._buttonText = vm.strings.get('REPLACE'); - scope.state._disabled = true; - scope.state._enableToggle = true; - scope.state._value = scope.state._preEditValue; - scope.state._activeModel = '_displayValue'; - scope.state._placeholder = vm.strings.get('ENCRYPTED'); - vm.check(); - } else { - scope.state._buttonText = vm.strings.get('REVERT'); - scope.state._disabled = false; - scope.state._enableToggle = false; - scope.state._activeModel = '_value'; - scope.state._value = ''; - scope.state._placeholder = ''; - vm.check(); - } - if (scope.form && scope.form.disabled) { - scope.state._enableToggle = false; - } - }; - - vm.togglePromptOnLaunch = () => { - if (scope.state._promptOnLaunch) { - scope.state._value = PROMPT_ON_LAUNCH_VALUE; - scope.state._activeModel = '_displayValue'; - scope.state._disabled = true; - scope.state._enableToggle = false; - } else if (scope.state._isBeingReplaced === false) { - scope.state._disabled = true; - scope.state._enableToggle = true; - scope.state._value = scope.state._preEditValue; - } else { - scope.state._activeModel = '_value'; - scope.state._disabled = false; - scope.state._value = ''; - } - - vm.check(); - }; - }; -} - -BaseInputController.$inject = ['ComponentsStrings']; - -export default BaseInputController; diff --git a/awx/ui/client/lib/components/input/checkbox.directive.js b/awx/ui/client/lib/components/input/checkbox.directive.js deleted file mode 100644 index 00fcdcf05c71..000000000000 --- a/awx/ui/client/lib/components/input/checkbox.directive.js +++ /dev/null @@ -1,46 +0,0 @@ -const templateUrl = require('~components/input/checkbox.partial.html'); - -function atInputCheckboxLink (scope, element, attrs, controllers) { - const formController = controllers[0]; - const inputController = controllers[1]; - - if (scope.tab === '1') { - element.find('input')[0].focus(); - } - - inputController.init(scope, element, formController); -} - -function AtInputCheckboxController (baseInputController) { - const vm = this || {}; - - vm.init = (scope, element, form) => { - baseInputController.call(vm, 'input', scope, element, form); - scope.label = scope.state.label; - scope.state.label = vm.strings.get('OPTIONS'); - - vm.check(); - }; -} - -AtInputCheckboxController.$inject = ['BaseInputController']; - -function atInputCheckbox () { - return { - restrict: 'E', - transclude: true, - replace: true, - require: ['^^atForm', 'atInputCheckbox'], - templateUrl, - controller: AtInputCheckboxController, - controllerAs: 'vm', - link: atInputCheckboxLink, - scope: { - state: '=', - col: '@', - tab: '@' - } - }; -} - -export default atInputCheckbox; diff --git a/awx/ui/client/lib/components/input/checkbox.partial.html b/awx/ui/client/lib/components/input/checkbox.partial.html deleted file mode 100644 index df171e5cb593..000000000000 --- a/awx/ui/client/lib/components/input/checkbox.partial.html +++ /dev/null @@ -1,17 +0,0 @@ -
-
- -
- -
- -
-
diff --git a/awx/ui/client/lib/components/input/dynamic-select.directive.js b/awx/ui/client/lib/components/input/dynamic-select.directive.js deleted file mode 100644 index ee3bca40ed74..000000000000 --- a/awx/ui/client/lib/components/input/dynamic-select.directive.js +++ /dev/null @@ -1,49 +0,0 @@ -const templateUrl = require('~components/input/dynamic-select.partial.html'); - -function atDynamicSelectLink (scope, element, attrs, controllers) { - const [formController, inputController] = controllers; - - inputController.init(scope, element, formController); -} - -function AtDynamicSelectController (baseInputController, CreateSelect2) { - const vm = this || {}; - - let scope; - - vm.init = (_scope_, _element_, form) => { - baseInputController.call(vm, 'input', _scope_, _element_, form); - scope = _scope_; - CreateSelect2({ - element: `#${scope.state._formId}_${scope.state.id}_dynamic_select`, - model: 'state._value', - multiple: false, - addNew: true, - scope, - options: 'state._data' - }); - vm.check(); - }; -} - -AtDynamicSelectController.$inject = ['BaseInputController', 'CreateSelect2']; - -function atDynamicSelect () { - return { - restrict: 'E', - transclude: true, - replace: true, - require: ['^^at-form', 'atDynamicSelect'], - templateUrl, - controller: AtDynamicSelectController, - controllerAs: 'vm', - link: atDynamicSelectLink, - scope: { - state: '=', - col: '@', - tab: '@' - } - }; -} - -export default atDynamicSelect; diff --git a/awx/ui/client/lib/components/input/dynamic-select.partial.html b/awx/ui/client/lib/components/input/dynamic-select.partial.html deleted file mode 100644 index b1b5eaf616d4..000000000000 --- a/awx/ui/client/lib/components/input/dynamic-select.partial.html +++ /dev/null @@ -1,16 +0,0 @@ -
-
- -
- -
- -
-
diff --git a/awx/ui/client/lib/components/input/file.directive.js b/awx/ui/client/lib/components/input/file.directive.js deleted file mode 100644 index 42aa2c4fba1c..000000000000 --- a/awx/ui/client/lib/components/input/file.directive.js +++ /dev/null @@ -1,94 +0,0 @@ -const templateUrl = require('~components/input/file.partial.html'); - -function atInputFileLink (scope, element, attrs, controllers) { - const formController = controllers[0]; - const inputController = controllers[1]; - - if (scope.tab === '1') { - element.find('input')[0].focus(); - } - - inputController.init(scope, element, formController); -} - -function AtInputFileController (baseInputController, eventService) { - const vm = this || {}; - - let input; - let scope; - - vm.init = (_scope_, element, form) => { - baseInputController.call(vm, 'input', _scope_, element, form); - - scope = _scope_; - input = element.find('input')[0]; // eslint-disable-line prefer-destructuring - - vm.listeners = vm.setFileListeners(input); - - vm.check(); - }; - - vm.onButtonClick = () => { - if (scope.state._value) { - vm.removeFile(); - } else { - input.click(); - } - }; - - vm.setFileListeners = inputEl => eventService.addListeners([ - [inputEl, 'change', event => vm.handleFileChangeEvent(inputEl, event)] - ]); - - vm.handleFileChangeEvent = (element, event) => { - if (element.files.length > 0) { - const reader = new FileReader(); - - reader.onload = () => vm.readFile(reader, event); - reader.readAsText(element.files[0]); - } else { - scope.$apply(vm.removeFile); - } - }; - - vm.readFile = (reader, event) => { - scope.$apply(() => { - scope.state._value = reader.result; - scope.state._displayValue = event.target.files[0].name; - - vm.check(); - }); - }; - - vm.removeFile = () => { - delete scope.state._value; - delete scope.state._displayValue; - - input.value = ''; - }; -} - -AtInputFileController.$inject = [ - 'BaseInputController', - 'EventService' -]; - -function atInputFile () { - return { - restrict: 'E', - transclude: true, - replace: true, - require: ['^^atForm', 'atInputFile'], - templateUrl, - controller: AtInputFileController, - controllerAs: 'vm', - link: atInputFileLink, - scope: { - state: '=', - col: '@', - tab: '@' - } - }; -} - -export default atInputFile; diff --git a/awx/ui/client/lib/components/input/file.partial.html b/awx/ui/client/lib/components/input/file.partial.html deleted file mode 100644 index 7e42af2390c7..000000000000 --- a/awx/ui/client/lib/components/input/file.partial.html +++ /dev/null @@ -1,25 +0,0 @@ -
-
- - -
- - - - -
- -
-
diff --git a/awx/ui/client/lib/components/input/group.directive.js b/awx/ui/client/lib/components/input/group.directive.js deleted file mode 100644 index 5272b6cbe7d3..000000000000 --- a/awx/ui/client/lib/components/input/group.directive.js +++ /dev/null @@ -1,209 +0,0 @@ -const templateUrl = require('~components/input/group.partial.html'); - -function atInputGroupLink (scope, el, attrs, controllers) { - const groupController = controllers[0]; - const formController = controllers[1]; - const element = el[0].getElementsByClassName('at-InputGroup-container')[0]; - - groupController.init(scope, formController, element); -} - -function AtInputGroupController ($scope, $compile) { - const vm = this || {}; - - let form; - let scope; - let state; - let source; - let element; - let formId; - - vm.init = (_scope_, _form_, _element_) => { - form = _form_; - scope = _scope_; - element = _element_; - state = scope.state || {}; - source = state._source; - ({ formId } = scope); - - $scope.$watch('state._source._value', vm.update); - }; - - vm.isValidSource = () => { - if (!source._value || source._value === state._value) { - return false; - } - - return true; - }; - - vm.update = () => { - if (state._group) { - vm.clear(); - } - - if (!vm.isValidSource()) { - return; - } - - state._value = source._value; - - const inputs = state._get(form); - const group = vm.createComponentConfigs(inputs); - - vm.insert(group); - state._group = group; - }; - - vm.createComponentConfigs = inputs => { - const group = []; - - if (inputs) { - inputs.forEach((input, i) => { - input = Object.assign(input, vm.getComponentType(input)); - - group.push(Object.assign({ - _element: vm.createComponent(input, i), - _key: 'inputs', - _group: true, - _groupIndex: i, - _onInputLookup: state._onInputLookup, - _onRemoveTag: state._onRemoveTag, - }, input)); - }); - } - - return group; - }; - - vm.getComponentType = input => { - const config = { - _formId: formId - }; - - if (input.type === 'string') { - if (input._isDynamic) { - config._component = 'at-dynamic-select'; - config._format = 'array'; - config._data = input._choices; - config._exp = 'choice for (index, choice) in state._data'; - config._isDynamic = true; - } else if (!input.multiline) { - if (input.secret) { - config._component = 'at-input-secret'; - } else { - config._component = 'at-input-text'; - } - } else { - config._expand = true; - - if (input.secret) { - config._component = 'at-input-textarea-secret'; - input.format = 'ssh_private_key'; - } else { - config._component = 'at-input-textarea'; - } - } - - if (input.format === 'ssh_private_key') { - config._format = 'ssh-key'; - } - } else if (input.type === 'number') { - config._component = 'at-input-number'; - } else if (input.type === 'boolean') { - config._component = 'at-input-checkbox'; - } else if (input.type === 'file') { - config._component = 'at-input-file'; - } - - if (input.choices) { - config._component = 'at-input-select'; - config._format = 'array'; - config._data = input.choices; - config._exp = 'choice for (index, choice) in state._data'; - } - - if (!config._component) { - const preface = vm.strings.get('group.UNSUPPORTED_ERROR_PREFACE'); - throw new Error(`${preface}: ${input.type}`); - } - - return config; - }; - - vm.insert = group => { - const container = document.createElement('div'); - container.className = 'row'; - let col = 1; - const colPerRow = 12 / scope.col; - let isDivided = true; - - group.forEach((input, i) => { - if (input._expand && !isDivided) { - container.appendChild(vm.createDivider()[0]); - } - - container.appendChild(input._element[0]); - - if ((input._expand || col % colPerRow === 0) && i !== group.length - 1) { - container.appendChild(vm.createDivider()[0]); - isDivided = true; - col = 0; - } else { - isDivided = false; - } - - col++; - }); - - element.appendChild(container); - }; - - vm.createComponent = (input, index) => { - const tabindex = Number(scope.tab) + index; - const col = input._expand ? 12 : scope.col; - const component = angular.element(`<${input._component} col="${col}" tab="${tabindex}" - state="${state._reference}._group[${index}]" id="${formId}_${input.id}_group"> - `); - - $compile(component)(scope.$parent); - return component; - }; - - vm.createDivider = () => { - const divider = angular.element(''); - $compile(divider[0])(scope.$parent); - - return divider; - }; - - vm.clear = () => { - form.deregisterInputGroup(state._group); - element.innerHTML = ''; - state._group = undefined; - state._value = undefined; - }; -} - -AtInputGroupController.$inject = ['$scope', '$compile']; - -function atInputGroup () { - return { - restrict: 'E', - replace: true, - transclude: true, - require: ['atInputGroup', '^^atForm'], - templateUrl, - controller: AtInputGroupController, - controllerAs: 'vm', - link: atInputGroupLink, - scope: { - state: '=', - col: '@', - tab: '@', - formId: '@' - } - }; -} - -export default atInputGroup; diff --git a/awx/ui/client/lib/components/input/group.partial.html b/awx/ui/client/lib/components/input/group.partial.html deleted file mode 100644 index c32d29bb1ed0..000000000000 --- a/awx/ui/client/lib/components/input/group.partial.html +++ /dev/null @@ -1,13 +0,0 @@ -
-
-
-
-
-

- -

-
-
-
-
-
diff --git a/awx/ui/client/lib/components/input/label.directive.js b/awx/ui/client/lib/components/input/label.directive.js deleted file mode 100644 index ea1fafd23a57..000000000000 --- a/awx/ui/client/lib/components/input/label.directive.js +++ /dev/null @@ -1,11 +0,0 @@ -const templateUrl = require('~components/input/label.partial.html'); - -function atInputLabel () { - return { - restrict: 'E', - replace: true, - templateUrl - }; -} - -export default atInputLabel; diff --git a/awx/ui/client/lib/components/input/label.partial.html b/awx/ui/client/lib/components/input/label.partial.html deleted file mode 100644 index 5f4cab5ee9dd..000000000000 --- a/awx/ui/client/lib/components/input/label.partial.html +++ /dev/null @@ -1,15 +0,0 @@ - diff --git a/awx/ui/client/lib/components/input/lookup.directive.js b/awx/ui/client/lib/components/input/lookup.directive.js deleted file mode 100644 index 43810d179602..000000000000 --- a/awx/ui/client/lib/components/input/lookup.directive.js +++ /dev/null @@ -1,152 +0,0 @@ -const templateUrl = require('~components/input/lookup.partial.html'); - -const DEFAULT_DEBOUNCE = 250; -const DEFAULT_KEY = 'name'; - -function atInputLookupLink (scope, element, attrs, controllers) { - const formController = controllers[0]; - const inputController = controllers[1]; - - if (scope.tab === '1') { - element.find('input')[0].focus(); - } - - inputController.init(scope, element, formController); -} - -function AtInputLookupController (baseInputController, $q, $state) { - const vm = this || {}; - - let scope; - let model; - let search; - - vm.init = (_scope_, element, form) => { - baseInputController.call(vm, 'input', _scope_, element, form); - - scope = _scope_; - model = scope.state._model; - scope.state._debounce = scope.state._debounce || DEFAULT_DEBOUNCE; - search = scope.state._search || { - key: DEFAULT_KEY, - config: { - unique: true - } - }; - - // This should get triggered when the user selects something in the lookup modal and - // hits save to close the modal. This won't get triggered when the user types in - // a value in the input. - scope.$watch('state._idFromModal', () => { - if (scope.state._idFromModal && - (scope.state._idFromModal !== scope.state._value) - ) { - vm.search({ id: scope.state._idFromModal }); - } - }); - - vm.check(); - }; - - vm.lookup = () => { - const params = {}; - - if (scope.state._value && scope.state._isValid) { - params.selected = scope.state._value; - } - - $state.go(scope.state._route, params); - }; - - vm.reset = () => { - scope.state._idFromModal = undefined; - scope.state._value = undefined; - scope[scope.state._resource] = undefined; - }; - - vm.searchAfterDebounce = () => { - vm.isDebouncing = true; - - vm.debounce = window.setTimeout(() => { - vm.isDebouncing = false; - vm.search(); - }, scope.state._debounce); - }; - - vm.resetDebounce = () => { - clearTimeout(vm.debounce); - vm.searchAfterDebounce(); - }; - - vm.search = (searchParams) => { - scope.state._touched = true; - - if (!scope.state._required && - scope.state._displayValue === '' && - !scope.state._idFromModal - ) { - scope.state._value = null; - return vm.check({ isValid: true }); - } - searchParams = searchParams || { [search.key]: scope.state._displayValue }; - - return model.search(searchParams, search.config) - .then(found => { - if (!found) { - vm.reset(); - return; - } - scope[scope.state._resource] = model.get('id'); - scope.state._value = model.get('id'); - scope.state._displayValue = model.get('name'); - scope.state._idFromModal = undefined; - }) - .catch(() => vm.reset()) - .finally(() => { - const isValid = scope.state._value !== undefined; - const message = isValid ? '' : vm.strings.get('lookup.NOT_FOUND'); - - vm.check({ isValid, message }); - }); - }; - - vm.searchOnInput = () => { - if (vm.isDebouncing) { - vm.resetDebounce(); - - return; - } - - vm.searchAfterDebounce(); - }; - - vm.removeTag = (tagToRemove) => { - _.remove(scope.state._value, (tag) => tag === tagToRemove); - }; -} - -AtInputLookupController.$inject = [ - 'BaseInputController', - '$q', - '$state' -]; - -function atInputLookup () { - return { - restrict: 'E', - transclude: true, - replace: true, - require: ['^^atForm', 'atInputLookup'], - templateUrl, - controller: AtInputLookupController, - controllerAs: 'vm', - link: atInputLookupLink, - scope: { - state: '=', - col: '@', - tab: '@' - } - }; -} - -export default atInputLookup; diff --git a/awx/ui/client/lib/components/input/lookup.partial.html b/awx/ui/client/lib/components/input/lookup.partial.html deleted file mode 100644 index 5a951cdb3119..000000000000 --- a/awx/ui/client/lib/components/input/lookup.partial.html +++ /dev/null @@ -1,33 +0,0 @@ -
-
- - -
- - - - -
- - -
-
-
- - -
-
diff --git a/awx/ui/client/lib/components/input/message.directive.js b/awx/ui/client/lib/components/input/message.directive.js deleted file mode 100644 index 6e4dba6da344..000000000000 --- a/awx/ui/client/lib/components/input/message.directive.js +++ /dev/null @@ -1,11 +0,0 @@ -const templateUrl = require('~components/input/message.partial.html'); - -function atInputMessage () { - return { - restrict: 'E', - replace: true, - templateUrl - }; -} - -export default atInputMessage; diff --git a/awx/ui/client/lib/components/input/message.partial.html b/awx/ui/client/lib/components/input/message.partial.html deleted file mode 100644 index cb73ab962022..000000000000 --- a/awx/ui/client/lib/components/input/message.partial.html +++ /dev/null @@ -1,4 +0,0 @@ -

- {{ state._message }} -

- diff --git a/awx/ui/client/lib/components/input/secret.directive.js b/awx/ui/client/lib/components/input/secret.directive.js deleted file mode 100644 index de16f122691a..000000000000 --- a/awx/ui/client/lib/components/input/secret.directive.js +++ /dev/null @@ -1,90 +0,0 @@ -const templateUrl = require('~components/input/secret.partial.html'); - -function atInputSecretLink (scope, element, attrs, controllers) { - const formController = controllers[0]; - const inputController = controllers[1]; - - if (scope.tab === '1') { - element.find('input')[0].focus(); - } - - inputController.init(scope, element, formController); -} - -function AtInputSecretController (baseInputController) { - const vm = this || {}; - - let scope; - - vm.init = (_scope_, element, form) => { - baseInputController.call(vm, 'input', _scope_, element, form); - - scope = _scope_; - scope.type = 'password'; - scope.state._show = false; - scope.state._showHideText = vm.strings.get('SHOW'); - - if (!scope.state._value || scope.state._promptOnLaunch) { - scope.mode = 'input'; - } else { - scope.mode = 'encrypted'; - scope.state._placeholder = vm.strings.get('ENCRYPTED'); - scope.state._buttonText = vm.strings.get('REPLACE'); - } - - vm.check(); - }; - - vm.toggleRevertReplace = () => { - scope.state._isBeingReplaced = !scope.state._isBeingReplaced; - - vm.onRevertReplaceToggle(); - - if (scope.state._isBeingReplaced) { - if (scope.type !== 'password') { - vm.toggleShowHide(); - } - } - }; - - vm.toggleShowHide = () => { - if (scope.type === 'password') { - scope.type = 'text'; - scope.state._show = true; - scope.state._showHideText = vm.strings.get('HIDE'); - } else { - scope.type = 'password'; - scope.state._show = false; - scope.state._showHideText = vm.strings.get('SHOW'); - } - }; - - vm.onLookupClick = () => { - if (scope.state._onInputLookup) { - const { id, label, required, type } = scope.state; - scope.state._onInputLookup({ id, label, required, type }); - } - }; -} - -AtInputSecretController.$inject = ['BaseInputController']; - -function atInputSecret () { - return { - restrict: 'E', - transclude: true, - replace: true, - require: ['^^atForm', 'atInputSecret'], - templateUrl, - controller: AtInputSecretController, - controllerAs: 'vm', - link: atInputSecretLink, - scope: { - state: '=', - col: '@', - tab: '@' - } - }; -} - -export default atInputSecret; diff --git a/awx/ui/client/lib/components/input/secret.partial.html b/awx/ui/client/lib/components/input/secret.partial.html deleted file mode 100644 index dc3e85055291..000000000000 --- a/awx/ui/client/lib/components/input/secret.partial.html +++ /dev/null @@ -1,71 +0,0 @@ -
-
- - -
- - - - -
- - -
-
- - - - - - - -
- - -
-
diff --git a/awx/ui/client/lib/components/input/select.directive.js b/awx/ui/client/lib/components/input/select.directive.js deleted file mode 100644 index ddc0e23766e7..000000000000 --- a/awx/ui/client/lib/components/input/select.directive.js +++ /dev/null @@ -1,96 +0,0 @@ -const templateUrl = require('~components/input/select.partial.html'); - -function atInputSelectLink (scope, element, attrs, controllers) { - const [formController, inputController] = controllers; - - inputController.init(scope, element, formController); -} - -function AtInputSelectController (baseInputController, eventService) { - const vm = this || {}; - - let scope; - let element; - let input; - let select; - - vm.init = (_scope_, _element_, form) => { - baseInputController.call(vm, 'input', _scope_, _element_, form); - - scope = _scope_; - element = _element_; - [input] = element.find('input'); - [select] = element.find('select'); - - if (scope.tab === '1') { - select.focus(); - } - - if (!scope.state._data || scope.state._data.length === 0) { - scope.state._disabled = true; - scope.state._placeholder = vm.strings.get('select.EMPTY_PLACEHOLDER'); - } - - vm.setListeners(); - vm.check(); - - if (scope.state._value) { - vm.updateDisplayModel(); - } - }; - - vm.setListeners = () => { - const listeners = eventService.addListeners([ - [input, 'focus', () => select.focus], - [select, 'mousedown', () => scope.$apply(() => { scope.open = !scope.open; })], - [select, 'focus', () => input.classList.add('at-Input--focus')], - [select, 'change', () => scope.$apply(() => { - scope.open = false; - vm.updateDisplayModel(); - vm.check(); - })], - [select, 'blur', () => { - input.classList.remove('at-Input--focus'); - scope.open = scope.open && false; - }] - ]); - - scope.$on('$destroy', () => eventService.remove(listeners)); - }; - - vm.updateDisplayModel = () => { - if (scope.state._format === 'selectFromOptions') { - scope.displayModel = scope.state._value[1]; - } else if (scope.state._format === 'array') { - scope.displayModel = scope.state._value; - } else if (scope.state._format === 'objects') { - scope.displayModel = scope.state._value[scope.state._display]; - } else if (scope.state._format === 'grouped-object') { - scope.displayModel = scope.state._value[scope.state._display]; - } else { - throw new Error(vm.strings.get('select.UNSUPPORTED_TYPE_ERROR')); - } - }; -} - -AtInputSelectController.$inject = ['BaseInputController', 'EventService']; - -function atInputSelect () { - return { - restrict: 'E', - transclude: true, - replace: true, - require: ['^^at-form', 'atInputSelect'], - templateUrl, - controller: AtInputSelectController, - controllerAs: 'vm', - link: atInputSelectLink, - scope: { - state: '=', - col: '@', - tab: '@' - } - }; -} - -export default atInputSelect; diff --git a/awx/ui/client/lib/components/input/select.partial.html b/awx/ui/client/lib/components/input/select.partial.html deleted file mode 100644 index f3ba5e845a48..000000000000 --- a/awx/ui/client/lib/components/input/select.partial.html +++ /dev/null @@ -1,26 +0,0 @@ -
-
- - -
- - - - - -
- - -
-
diff --git a/awx/ui/client/lib/components/input/slider.directive.js b/awx/ui/client/lib/components/input/slider.directive.js deleted file mode 100644 index a2e1b8c28e47..000000000000 --- a/awx/ui/client/lib/components/input/slider.directive.js +++ /dev/null @@ -1,38 +0,0 @@ -const templateUrl = require('~components/input/slider.partial.html'); - -function atInputSliderLink (scope, element, attrs, controllers) { - const [formController, inputController] = controllers; - - inputController.init(scope, element, formController); -} - -function atInputSliderController (baseInputController) { - const vm = this || {}; - - vm.init = (_scope_, _element_, form) => { - baseInputController.call(vm, 'input', _scope_, _element_, form); - - vm.check(); - }; -} - -atInputSliderController.$inject = ['BaseInputController']; - -function atInputSlider () { - return { - restrict: 'E', - require: ['^^atForm', 'atInputSlider'], - replace: true, - templateUrl, - controller: atInputSliderController, - controllerAs: 'vm', - link: atInputSliderLink, - scope: { - state: '=?', - col: '@', - tab: '@' - } - }; -} - -export default atInputSlider; diff --git a/awx/ui/client/lib/components/input/slider.partial.html b/awx/ui/client/lib/components/input/slider.partial.html deleted file mode 100644 index e07abdadfacd..000000000000 --- a/awx/ui/client/lib/components/input/slider.partial.html +++ /dev/null @@ -1,14 +0,0 @@ -
-
- -
- -

{{ state._value }}%

-
-
-
diff --git a/awx/ui/client/lib/components/input/text.directive.js b/awx/ui/client/lib/components/input/text.directive.js deleted file mode 100644 index c24c807cd433..000000000000 --- a/awx/ui/client/lib/components/input/text.directive.js +++ /dev/null @@ -1,57 +0,0 @@ -const templateUrl = require('~components/input/text.partial.html'); - -function atInputTextLink (scope, element, attrs, controllers) { - const formController = controllers[0]; - const inputController = controllers[1]; - - if (scope.tab === '1') { - const el = element.find('input')[0]; - if (el) { - el.focus(); - } - } - - inputController.init(scope, element, formController); -} - -function AtInputTextController (baseInputController) { - const vm = this || {}; - - let scope; - - vm.init = (_scope_, element, form) => { - baseInputController.call(vm, 'input', _scope_, element, form); - scope = _scope_; - - scope.$watch('state._value', () => vm.check()); - }; - - vm.onLookupClick = () => { - if (scope.state._onInputLookup) { - const { id, label, required, type } = scope.state; - scope.state._onInputLookup({ id, label, required, type }); - } - }; -} - -AtInputTextController.$inject = ['BaseInputController']; - -function atInputText () { - return { - restrict: 'E', - transclude: true, - replace: true, - require: ['^^atForm', 'atInputText'], - templateUrl, - controller: AtInputTextController, - controllerAs: 'vm', - link: atInputTextLink, - scope: { - state: '=', - col: '@', - tab: '@' - } - }; -} - -export default atInputText; diff --git a/awx/ui/client/lib/components/input/text.partial.html b/awx/ui/client/lib/components/input/text.partial.html deleted file mode 100644 index 63fc73ec6a87..000000000000 --- a/awx/ui/client/lib/components/input/text.partial.html +++ /dev/null @@ -1,51 +0,0 @@ -
-
- -
- - - - -
- - -
-
- -
- - -
-
diff --git a/awx/ui/client/lib/components/input/textarea-secret.directive.js b/awx/ui/client/lib/components/input/textarea-secret.directive.js deleted file mode 100644 index fb4b29d870a2..000000000000 --- a/awx/ui/client/lib/components/input/textarea-secret.directive.js +++ /dev/null @@ -1,129 +0,0 @@ -const templateUrl = require('~components/input/textarea-secret.partial.html'); - -function atInputTextareaSecretLink (scope, element, attrs, controllers) { - const [formController, inputController] = controllers; - - if (scope.tab === '1') { - element.find('textarea')[0].focus(); - } - - inputController.init(scope, element, formController); -} - -function AtInputTextareaSecretController (baseInputController, eventService) { - const vm = this || {}; - - let scope; - let textarea; - let input; - - vm.init = (_scope_, element, form) => { - baseInputController.call(vm, 'input', _scope_, element, form); - - scope = _scope_; - - [textarea] = element.find('textarea'); - - if (scope.state.format === 'ssh_private_key') { - scope.ssh = true; - scope.state._hint = scope.state._hint || vm.strings.get('textarea.SSH_KEY_HINT'); - [input] = element.find('input'); - } - - if (scope.state._value) { - scope.state._buttonText = vm.strings.get('REPLACE'); - scope.state._placeholder = vm.strings.get('ENCRYPTED'); - } else if (scope.state.format === 'ssh_private_key') { - vm.listeners = vm.setFileListeners(textarea, input); - scope.state._displayHint = true; - } - - vm.check(); - - scope.$watch('state[state._activeModel]', () => vm.check()); - scope.$watch('state._isBeingReplaced', () => vm.onIsBeingReplacedChanged()); - }; - - vm.onIsBeingReplacedChanged = () => { - if (!scope.state) return; - if (!scope.state._touched) return; - - vm.onRevertReplaceToggle(); - - if (scope.state._isBeingReplaced) { - scope.state._placeholder = ''; - scope.state._displayHint = true; - vm.listeners = vm.setFileListeners(textarea, input); - } else { - scope.state._displayHint = false; - scope.state._placeholder = vm.strings.get('ENCRYPTED'); - - if (vm.listeners) { - eventService.remove(vm.listeners); - } - } - }; - - vm.setFileListeners = (textareaEl, inputEl) => eventService.addListeners([ - [textareaEl, 'dragenter', event => { - event.stopPropagation(); - event.preventDefault(); - scope.$apply(() => { scope.drag = true; }); - }], - - [inputEl, 'dragleave', event => { - event.stopPropagation(); - event.preventDefault(); - scope.$apply(() => { scope.drag = false; }); - }], - - [inputEl, 'change', event => { - const reader = new FileReader(); - - reader.onload = () => vm.readFile(reader, event); - reader.readAsText(inputEl.files[0]); - }] - ]); - - vm.readFile = (reader) => { - scope.$apply(() => { - scope.state._value = reader.result; - vm.check(); - scope.drag = false; - input.value = ''; - }); - }; - - vm.onLookupClick = () => { - if (scope.state._onInputLookup) { - const { id, label, required, type } = scope.state; - scope.state._onInputLookup({ id, label, required, type }); - } - }; -} - -AtInputTextareaSecretController.$inject = [ - 'BaseInputController', - 'EventService', - 'ComponentsStrings' -]; - -function atInputTextareaSecret () { - return { - restrict: 'E', - transclude: true, - replace: true, - require: ['^^atForm', 'atInputTextareaSecret'], - templateUrl, - controller: AtInputTextareaSecretController, - controllerAs: 'vm', - link: atInputTextareaSecretLink, - scope: { - state: '=', - col: '@', - tab: '@' - } - }; -} - -export default atInputTextareaSecret; diff --git a/awx/ui/client/lib/components/input/textarea-secret.partial.html b/awx/ui/client/lib/components/input/textarea-secret.partial.html deleted file mode 100644 index 2fd5c3773c0c..000000000000 --- a/awx/ui/client/lib/components/input/textarea-secret.partial.html +++ /dev/null @@ -1,68 +0,0 @@ -
-
- -
-
- -
- -
-
- - -
-
- -
New lines are not supported in this field
-
diff --git a/awx/ui/client/lib/components/tabs/_index.less b/awx/ui/client/lib/components/tabs/_index.less deleted file mode 100644 index 80b9eec5c688..000000000000 --- a/awx/ui/client/lib/components/tabs/_index.less +++ /dev/null @@ -1,28 +0,0 @@ -.at-Tab { - margin: 0 @at-margin-item-column 0 0; - font-size: @at-font-size-body; - line-height: 1; -} - -.at-Tab--active { - &, &:hover, &:active, &:focus { - color: @at-color-tab-text-default-active; - background-color: @at-color-tab-default-active; - border-color: @at-color-tab-border-default-active; - cursor: default; - } -} - -.at-Tab--disabled { - &, &:hover, &:active, &:focus { - background-color: @at-color-tab-default-disabled; - color: @at-color-tab-text-default-disabled; - border-color: @at-color-tab-border-default-disabled; - opacity: 0.65; - cursor: not-allowed; - } -} - -.at-TabGroup--padBelow { - margin-bottom: 20px; -} diff --git a/awx/ui/client/lib/components/tabs/group.directive.js b/awx/ui/client/lib/components/tabs/group.directive.js deleted file mode 100644 index d3c55a295fb4..000000000000 --- a/awx/ui/client/lib/components/tabs/group.directive.js +++ /dev/null @@ -1,36 +0,0 @@ -const templateUrl = require('~components/tabs/group.partial.html'); - -function AtTabGroupController () { - const vm = this; - - vm.tabs = []; - - vm.register = tab => { - tab.active = true; - - vm.tabs.push(tab); - }; - - vm.clearActive = () => { - vm.tabs.forEach((tab) => { - tab.state._active = false; - }); - }; -} - -function atTabGroup () { - return { - restrict: 'E', - replace: true, - require: 'atTabGroup', - transclude: true, - templateUrl, - controller: AtTabGroupController, - controllerAs: 'vm', - scope: { - state: '=' - } - }; -} - -export default atTabGroup; diff --git a/awx/ui/client/lib/components/tabs/group.partial.html b/awx/ui/client/lib/components/tabs/group.partial.html deleted file mode 100644 index 325fb36cc5a1..000000000000 --- a/awx/ui/client/lib/components/tabs/group.partial.html +++ /dev/null @@ -1,3 +0,0 @@ -
- -
diff --git a/awx/ui/client/lib/components/tabs/tab.directive.js b/awx/ui/client/lib/components/tabs/tab.directive.js deleted file mode 100644 index 85dc5f4d2c48..000000000000 --- a/awx/ui/client/lib/components/tabs/tab.directive.js +++ /dev/null @@ -1,57 +0,0 @@ -const templateUrl = require('~components/tabs/tab.partial.html'); - -function atTabLink (scope, el, attrs, controllers) { - const groupController = controllers[0]; - const tabController = controllers[1]; - - tabController.init(scope, groupController); -} - -function AtTabController ($state) { - const vm = this; - - let scope; - let group; - - vm.init = (_scope_, _group_) => { - scope = _scope_; - group = _group_; - - group.register(scope); - }; - - vm.handleClick = () => { - if (scope.state._disabled || scope.state._active) { - return; - } - if (scope.state._go) { - $state.go(scope.state._go, scope.state._params, { reload: true }); - return; - } - group.clearActive(); - scope.state._active = true; - if (scope.state._onClickActivate) { - scope.state._onClickActivate(); - } - }; -} - -AtTabController.$inject = ['$state']; - -function atTab () { - return { - restrict: 'E', - replace: true, - transclude: true, - require: ['^^atTabGroup', 'atTab'], - templateUrl, - controller: AtTabController, - controllerAs: 'vm', - link: atTabLink, - scope: { - state: '=' - } - }; -} - -export default atTab; diff --git a/awx/ui/client/lib/components/tabs/tab.partial.html b/awx/ui/client/lib/components/tabs/tab.partial.html deleted file mode 100644 index 5b77e0284182..000000000000 --- a/awx/ui/client/lib/components/tabs/tab.partial.html +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/awx/ui/client/lib/components/tag/_index.less b/awx/ui/client/lib/components/tag/_index.less deleted file mode 100644 index d53a5e19e695..000000000000 --- a/awx/ui/client/lib/components/tag/_index.less +++ /dev/null @@ -1,81 +0,0 @@ -.TagComponent { - color: @at-white; - cursor: default; - background: @at-blue; - border-radius: @at-space; - font-size: 12px; - display: flex; - flex-direction: row; - align-content: center; - min-height: @at-space-4x; - overflow: hidden; - max-width: 200px; - margin: @at-space 0; - - &:last-of-type { - margin-right: 20px; - } -} - -.TagComponent-name { - color: @at-white; - margin: 2px @at-space-2x; - align-self: center; - /* fallback for FF < 68 which doesn't support `break-word` */ - word-break: break-all; - word-break: break-word; - - &:hover, - &:focus { - color: @at-white; - } -} - -.TagComponent-icon { - .at-mixin-VerticallyCenter(); - line-height: 20px; - margin-left: @at-space-2x; - - &--cloud:before, - &--aws:before, - &--tower:before, - &--azure_rm:before, - { - content: '\f0c2'; - } - - &--insights:before { - content: '\f129'; - } - - &--net:before { - content: '\f0e8'; - } - - &--scm:before { - content: '\f126'; - } - - &--ssh:before { - content: '\f084'; - } - - &--vault:before { - content: '\f187'; - } - - &--external:before { - content: '\f14c' - } -} - -.TagComponent-button { - padding: 0 @at-space; - .at-mixin-VerticallyCenter(); -} - -.TagComponent-button:hover { - cursor: pointer; - border-color: @at-color-error; - background-color: @at-color-error; -} diff --git a/awx/ui/client/lib/components/tag/tag.directive.js b/awx/ui/client/lib/components/tag/tag.directive.js deleted file mode 100644 index f18ae9dce92f..000000000000 --- a/awx/ui/client/lib/components/tag/tag.directive.js +++ /dev/null @@ -1,18 +0,0 @@ -const templateUrl = require('~components/tag/tag.partial.html'); - -function atTag () { - return { - restrict: 'E', - replace: true, - transclude: true, - templateUrl, - scope: { - tag: '=', - icon: '@?', - link: '@?', - removeTag: '&?', - }, - }; -} - -export default atTag; diff --git a/awx/ui/client/lib/components/tag/tag.partial.html b/awx/ui/client/lib/components/tag/tag.partial.html deleted file mode 100644 index f16da4da4248..000000000000 --- a/awx/ui/client/lib/components/tag/tag.partial.html +++ /dev/null @@ -1,8 +0,0 @@ -
-
- {{ tag }} -
{{ tag }}
-
- -
-
\ No newline at end of file diff --git a/awx/ui/client/lib/components/toggle-tag/_index.less b/awx/ui/client/lib/components/toggle-tag/_index.less deleted file mode 100644 index 18639c564818..000000000000 --- a/awx/ui/client/lib/components/toggle-tag/_index.less +++ /dev/null @@ -1,19 +0,0 @@ -.ToggleComponent-wrapper { - line-height: initial; -} - -.ToggleComponent-container { - display: flex; - flex-wrap: wrap; -} - -.ToggleComponent-button { - border: none; - background: transparent; - color: @at-blue; - font-size: @at-font-size; - - &:hover { - color: @at-blue-hover; - } -} \ No newline at end of file diff --git a/awx/ui/client/lib/components/toggle-tag/constants.js b/awx/ui/client/lib/components/toggle-tag/constants.js deleted file mode 100644 index f3a4951c9cb2..000000000000 --- a/awx/ui/client/lib/components/toggle-tag/constants.js +++ /dev/null @@ -1,2 +0,0 @@ -export const TRUNCATE_LENGTH = 5; -export const IS_TRUNCATED = true; diff --git a/awx/ui/client/lib/components/toggle-tag/toggle-tag.directive.js b/awx/ui/client/lib/components/toggle-tag/toggle-tag.directive.js deleted file mode 100644 index 4fec4cd7949b..000000000000 --- a/awx/ui/client/lib/components/toggle-tag/toggle-tag.directive.js +++ /dev/null @@ -1,32 +0,0 @@ -import { IS_TRUNCATED, TRUNCATE_LENGTH } from './constants'; - -const templateUrl = require('~components/toggle-tag/toggle-tag.partial.html'); - -function controller (strings) { - const vm = this; - vm.truncatedLength = TRUNCATE_LENGTH; - vm.isTruncated = IS_TRUNCATED; - vm.strings = strings; - - vm.toggle = () => { - vm.isTruncated = !vm.isTruncated; - }; -} - -controller.$inject = ['ComponentsStrings']; - -function atToggleTag () { - return { - restrict: 'E', - replace: true, - transclude: true, - controller, - controllerAs: 'vm', - templateUrl, - scope: { - tags: '=', - }, - }; -} - -export default atToggleTag; diff --git a/awx/ui/client/lib/components/toggle-tag/toggle-tag.partial.html b/awx/ui/client/lib/components/toggle-tag/toggle-tag.partial.html deleted file mode 100644 index fada04154ef3..000000000000 --- a/awx/ui/client/lib/components/toggle-tag/toggle-tag.partial.html +++ /dev/null @@ -1,21 +0,0 @@ -
-
-
-
- -
- -
-
-
- -
- -
-
-
-
- -
-
-
diff --git a/awx/ui/client/lib/components/truncate/_index.less b/awx/ui/client/lib/components/truncate/_index.less deleted file mode 100644 index b4e4cb9dd5db..000000000000 --- a/awx/ui/client/lib/components/truncate/_index.less +++ /dev/null @@ -1,35 +0,0 @@ -.at-Truncate { - display: inline-block; - - .at-Truncate-text { - font-family: monospace, Courier, "Courier New", "Open Sans", sans-serif; - } - - .at-Truncate-tag { - display: inherit; - } - - .at-Truncate-copy { - color: @at-gray-b7; - cursor: pointer; - margin: 0 20px 0 0; - display: inherit; - - i:hover { - color: @at-blue; - } - } - - .at-Truncate-textarea { - background: transparent; - border: none; - box-shadow: none; - height: 2em; - left: 0px; - outline: none; - padding: 0px; - position: fixed; - top: 0px; - width: 2em; - } -} diff --git a/awx/ui/client/lib/components/truncate/truncate.directive.js b/awx/ui/client/lib/components/truncate/truncate.directive.js deleted file mode 100644 index 8cc3df8bef28..000000000000 --- a/awx/ui/client/lib/components/truncate/truncate.directive.js +++ /dev/null @@ -1,66 +0,0 @@ -const templateUrl = require('~components/truncate/truncate.partial.html'); - -function atTruncateLink (scope, el, attr, ctrl) { - const truncateController = ctrl; - const { string } = attr; - const { maxlength } = attr; - - truncateController.init(el, string, maxlength); -} - -function AtTruncateController (strings) { - const vm = this; - let el; - let string; - let maxlength; - vm.strings = strings; - - vm.init = (_el_, _string_, _maxlength_) => { - el = _el_; - string = _string_; - maxlength = _maxlength_; - vm.truncatedString = string.substring(0, maxlength); - }; - - vm.copyToClipboard = () => { - vm.tooltip.popover.text = vm.strings.get('truncate.COPIED'); - - const textarea = el[0].getElementsByClassName('at-Truncate-textarea')[0]; - textarea.value = string; - textarea.select(); - - document.execCommand('copy'); - }; - - vm.tooltip = { - popover: { - text: vm.strings.get('truncate.DEFAULT'), - on: 'mouseover', - position: 'top', - icon: 'fa fa-clone', - resetOnExit: true, - click: vm.copyToClipboard - } - }; -} - -AtTruncateController.$inject = ['ComponentsStrings']; - -function atTruncate () { - return { - restrict: 'E', - replace: true, - transclude: true, - templateUrl, - controller: AtTruncateController, - controllerAs: 'vm', - link: atTruncateLink, - scope: { - state: '=', - maxLength: '@', - string: '@' - } - }; -} - -export default atTruncate; diff --git a/awx/ui/client/lib/components/truncate/truncate.partial.html b/awx/ui/client/lib/components/truncate/truncate.partial.html deleted file mode 100644 index aa329908eb4d..000000000000 --- a/awx/ui/client/lib/components/truncate/truncate.partial.html +++ /dev/null @@ -1,9 +0,0 @@ -
-
- {{vm.truncatedString}} -
-
- -
- -
diff --git a/awx/ui/client/lib/components/utility/_index.less b/awx/ui/client/lib/components/utility/_index.less deleted file mode 100644 index f9851bef2be7..000000000000 --- a/awx/ui/client/lib/components/utility/_index.less +++ /dev/null @@ -1,5 +0,0 @@ -.at-Divider { - clear: both; - margin: 0; - padding: 0; -} diff --git a/awx/ui/client/lib/components/utility/divider.directive.js b/awx/ui/client/lib/components/utility/divider.directive.js deleted file mode 100644 index 8aa71fad5014..000000000000 --- a/awx/ui/client/lib/components/utility/divider.directive.js +++ /dev/null @@ -1,12 +0,0 @@ -const templateUrl = require('~components/utility/divider.partial.html'); - -function atPanelBody () { - return { - restrict: 'E', - replace: true, - templateUrl, - scope: false - }; -} - -export default atPanelBody; diff --git a/awx/ui/client/lib/components/utility/divider.partial.html b/awx/ui/client/lib/components/utility/divider.partial.html deleted file mode 100644 index 514695d55ca1..000000000000 --- a/awx/ui/client/lib/components/utility/divider.partial.html +++ /dev/null @@ -1 +0,0 @@ -
diff --git a/awx/ui/client/lib/models/AdHocCommand.js b/awx/ui/client/lib/models/AdHocCommand.js deleted file mode 100644 index 9f259a929ade..000000000000 --- a/awx/ui/client/lib/models/AdHocCommand.js +++ /dev/null @@ -1,44 +0,0 @@ -let $http; -let BaseModel; - -function getRelaunch (params) { - const req = { - method: 'GET', - url: `${this.path}${params.id}/relaunch/` - }; - - return $http(req); -} - -function postRelaunch (params) { - const req = { - method: 'POST', - url: `${this.path}${params.id}/relaunch/` - }; - - return $http(req); -} - -function AdHocCommandModel (method, resource, config) { - BaseModel.call(this, 'ad_hoc_commands'); - - this.Constructor = AdHocCommandModel; - this.postRelaunch = postRelaunch.bind(this); - this.getRelaunch = getRelaunch.bind(this); - - return this.create(method, resource, config); -} - -function AdHocCommandModelLoader (_$http_, _BaseModel_) { - $http = _$http_; - BaseModel = _BaseModel_; - - return AdHocCommandModel; -} - -AdHocCommandModelLoader.$inject = [ - '$http', - 'BaseModel', -]; - -export default AdHocCommandModelLoader; diff --git a/awx/ui/client/lib/models/Application.js b/awx/ui/client/lib/models/Application.js deleted file mode 100644 index 2a364eb9e739..000000000000 --- a/awx/ui/client/lib/models/Application.js +++ /dev/null @@ -1,80 +0,0 @@ -let Base; - -function createFormSchema (method, config) { - function mungeSelectFromOptions (configObj, value) { - configObj.choices = [[null, '']].concat(configObj.choices); - configObj._data = configObj.choices; - configObj._exp = 'choice[1] for choice in state._data'; - configObj._format = 'selectFromOptions'; - - configObj._data.forEach((val, i) => { - if (val[0] === value) { - configObj._value = configObj._data[i]; - } - }); - - return configObj; - } - - if (!config) { - config = method; - method = 'GET'; - } - - const schema = Object.assign({}, this.options(`actions.${method.toUpperCase()}`)); - - if (config && config.omit) { - config.omit.forEach(key => delete schema[key]); - } - - Object.keys(schema).forEach(key => { - schema[key].id = key; - - if (this.has(key) && schema[key].type !== 'choice') { - schema[key]._value = this.get(key); - } - - if (schema[key].type === 'choice') { - schema[key] = mungeSelectFromOptions(schema[key], this.get(key)); - } - }); - - // necessary because authorization_grant_type is not changeable on update - if (method === 'put') { - schema.authorization_grant_type = mungeSelectFromOptions(Object.assign({}, this - .options('actions.GET.authorization_grant_type')), this - .get('authorization_grant_type')); - - schema.authorization_grant_type._required = false; - schema.authorization_grant_type._disabled = true; - } - - return schema; -} - -function setDependentResources () { - this.dependentResources = []; -} - -function ApplicationModel (method, resource, config) { - // TODO: change to applications - Base.call(this, 'applications'); - - this.Constructor = ApplicationModel; - this.createFormSchema = createFormSchema.bind(this); - this.setDependentResources = setDependentResources.bind(this); - - return this.create(method, resource, config); -} - -function ApplicationModelLoader (BaseModel) { - Base = BaseModel; - - return ApplicationModel; -} - -ApplicationModelLoader.$inject = [ - 'BaseModel', -]; - -export default ApplicationModelLoader; diff --git a/awx/ui/client/lib/models/Base.js b/awx/ui/client/lib/models/Base.js deleted file mode 100644 index e5425a333583..000000000000 --- a/awx/ui/client/lib/models/Base.js +++ /dev/null @@ -1,738 +0,0 @@ -let $http; -let $q; -let cache; -let strings; - -function request (method, resource, config) { - let req = this.parseRequestConfig(method, resource, config); - - if (Array.isArray(req.method)) { - const promises = req.method.map((_method, i) => { - const _resource = Array.isArray(req.resource) ? req.resource[i] : req.resource; - - req = this.parseRequestConfig(_method, _resource, config); - - if (this.isCacheable(req)) { - return this.requestWithCache(req); - } - - return this.request(req); - }); - - return $q.all(promises); - } - - if (this.isCacheable(req)) { - return this.requestWithCache(req); - } - - return this.http[req.method](req); -} - -function requestWithCache (config) { - const key = cache.createKey(config.method, this.path, config.resource); - - return cache.get(key) - .then(data => { - if (data) { - this.model[config.method.toUpperCase()] = data; - - return data; - } - - return this.http[config.method](config) - .then(res => { - cache.put(key, res.data); - - return res; - }); - }); -} - -/** - * Intended to be useful in searching and filtering results using params - * supported by the API. - * - * @arg {Object} params - An object of keys and values to to format and - * to the URL as a query string. Refer to the API documentation for the - * resource in use for specifics. - * @arg {Object} config - Configuration specific to the UI to accommodate - * common use cases. - * - * @yields {boolean} - Indicating a match has been found. If so, the results - * are set on the model. - */ -function search (params = {}, config = {}, headers = {}) { - const req = { - method: 'GET', - url: this.path, - headers - }; - - if (typeof params === 'string') { - req.url = '?params'; - } else if (Array.isArray(params)) { - req.url += `?${params.join('&')}`; - } else { - req.params = params; - } - - return $http(req) - .then(({ data }) => { - if (!data.count) { - return false; - } - - if (config.unique) { - if (data.count !== 1) { - return false; - } - - [this.model.GET] = data.results; - } else { - this.model.GET = data; - } - - return true; - }); -} - -function httpGet (config = {}) { - const req = { - method: 'GET', - url: this.path - }; - - if (config.params) { - req.params = config.params; - - if (config.params.page_size) { - this.page.size = config.params.page_size; - this.page.current = 1; - - if (config.pageCache) { - this.page.cachedPages = this.page.cachedPages || {}; - this.page.cache = this.page.cache || {}; - this.page.limit = config.pageLimit || false; - - if (!_.has(this.page.cachedPages, 'root')) { - this.page.cachedPages.root = []; - } - - if (!_.has(this.page.cache, 'root')) { - this.page.cache.root = {}; - } - } - } - } - - if (typeof config.resource === 'object') { - this.model.GET = config.resource; - - return $q.resolve(); - } else if (config.resource) { - req.url = `${this.path}${config.resource}/`; - } - - return $http(req) - .then(res => { - this.model.GET = res.data; - - if (config.pageCache) { - this.page.cache.root[this.page.current] = res.data.results; - this.page.cachedPages.root.push(this.page.current); - this.page.count = res.data.count; - this.page.last = Math.ceil(res.data.count / this.page.size); - } - - return res; - }); -} - -function httpPost (config = {}) { - const req = { - method: 'POST', - url: this.path, - data: config.data, - }; - - if (config.url) { - req.url = `${this.path}${config.url}`; - } - - if (!('replace' in config)) { - config.replace = true; - } - - return $http(req) - .then(res => { - if (config.replace) { - this.model.GET = res.data; - } - return res; - }); -} - -function httpPatch (config = {}) { - const req = { - method: 'PUT', - url: `${this.path}${this.get('id')}/`, - data: config.changes - }; - - return $http(req); -} - -function httpPut (config = {}) { - const model = _.merge(this.get(), config.data); - - const req = { - method: 'PUT', - url: `${this.path}${this.get('id')}/`, - data: model - }; - - return $http(req); -} - -function httpOptions (config = {}) { - const req = { - method: 'OPTIONS', - url: this.path - }; - - if (config.resource) { - req.url = `${this.path}${config.resource}/`; - } - - return $http(req) - .then(res => { - this.model.OPTIONS = res.data; - - return res; - }); -} - -function httpDelete (config = {}) { - const req = { - method: 'DELETE', - url: this.path - }; - - if (config.resource) { - req.url = `${this.path}${config.resource}/`; - } - - return $http(req); -} - -function options (keys) { - return this.find('options', keys); -} - -function get (keys) { - return this.find('get', keys); -} - -function unset (method, keys) { - if (!keys) { - keys = method; - method = 'GET'; - } - - method = method.toUpperCase(); - keys = keys.split('.'); - - if (!keys.length) { - delete this.model[method]; - } else if (keys.length === 1) { - delete this.model[method][keys[0]]; - } else { - const property = keys.splice(-1); - keys = keys.join('.'); - - const model = this.find(method, keys); - delete model[property]; - } -} - -function set (method, keys, value) { - if (!value) { - value = keys; - keys = method; - method = 'GET'; - } - - keys = keys.split('.'); - - if (keys.length === 1) { - this.model[keys[0]] = value; - } else { - const property = keys.splice(-1); - keys = keys.join('.'); - - const model = this.find(method, keys); - - model[property] = value; - } -} - -function match (method, key, value) { - if (!value) { - value = key; - key = method; - method = 'GET'; - } - - const model = this.model[method.toUpperCase()]; - - if (!model) { - return null; - } - - if (!model.results) { - if (model[key] === value) { - return model; - } - - return null; - } - - const result = model.results.filter(object => object[key] === value); - - return result.length === 0 ? null : result[0]; -} - -function find (method, keys) { - let value = this.model[method.toUpperCase()]; - - if (!keys) { - return value; - } - - try { - keys = keys.split('.'); - - keys.forEach(key => { - const bracketIndex = key.indexOf('['); - const hasArray = bracketIndex !== -1; - - if (!hasArray) { - value = value[key]; - return; - } - - if (bracketIndex === 0) { - value = value[Number(key.substring(1, key.length - 1))]; - return; - } - - const prop = key.substring(0, bracketIndex); - const index = Number(key.substring(bracketIndex + 1, key.length - 1)); - - value = value[prop][index]; - }); - } catch (err) { - return undefined; - } - - return value; -} - -function has (method, keys) { - if (!keys) { - keys = method; - method = 'GET'; - } - - method = method.toUpperCase(); - - let value; - switch (method) { - case 'OPTIONS': - value = this.options(keys); - break; - default: - value = this.get(keys); - } - - return value !== undefined && value !== null; -} - -function extend (method, related, config = {}) { - const req = this.parseRequestConfig(method.toUpperCase(), config); - - if (_.get(config, 'params.page_size')) { - this.page.size = config.params.page_size; - this.page.current = 1; - - if (config.pageCache) { - this.page.cachedPages = this.page.cachedPages || {}; - this.page.cache = this.page.cache || {}; - this.page.limit = config.pageLimit || false; - - if (!_.has(this.page.cachedPages, `related.${related}`)) { - _.set(this.page.cachedPages, `related.${related}`, []); - } - - if (!_.has(this.page.cache, `related.${related}`)) { - _.set(this.page.cache, `related.${related}`, []); - } - } - } - - if (this.has(req.method, `related.${related}`)) { - req.url = this.get(`related.${related}`); - - Object.assign(req, config); - - return $http(req) - .then(({ data }) => { - this.set(req.method, `related.${related}`, data); - - if (config.pageCache) { - this.page.cache.related[related][this.page.current] = data.results; - this.page.cachedPages.related[related].push(this.page.current); - this.page.count = data.count; - this.page.last = Math.ceil(data.count / this.page.size); - } - - return this; - }); - } - - return Promise.reject(new Error(`No related property, ${related}, exists`)); -} - -function updateCount (count) { - this.page.count = count; - this.page.last = Math.ceil(count / this.page.size); - - return this.page.last; -} - -function goToPage (config) { - const params = config.params || {}; - const { page } = config; - - let url; - let key; - let pageNumber; - let pageCache; - let pagesInCache; - - if (config.related) { - url = `${this.endpoint}${config.related}/`; - key = `related.${config.related}`; - } else { - url = this.endpoint; - key = 'root'; - } - - params.page_size = this.page.size; - - if (page === 'next') { - pageNumber = this.page.current + 1; - } else if (page === 'previous') { - pageNumber = this.page.current - 1; - } else if (page === 'first') { - pageNumber = 1; - } else if (page === 'last') { - pageNumber = this.page.last; - } else { - pageNumber = page; - } - - if (pageNumber < 1 || pageNumber > this.page.last) { - return Promise.resolve(null); - } - - this.page.current = pageNumber; - - if (this.page.cache) { - pageCache = _.get(this.page.cache, key); - pagesInCache = _.get(this.page.cachedPages, key); - - if (_.has(pageCache, pageNumber)) { - return Promise.resolve({ - results: pageCache[pageNumber], - page: pageNumber - }); - } - } - - params.page_size = this.page.size; - params.page = pageNumber; - - const req = { - method: 'GET', - url, - params - }; - - return $http(req) - .then(({ data }) => { - if (pageCache) { - pageCache[pageNumber] = data.results; - pagesInCache.push(pageNumber); - - if (pagesInCache.length > this.page.limit) { - const pageToDelete = pagesInCache.shift(); - - delete pageCache[pageToDelete]; - } - } - - return { - results: data.results, - page: pageNumber - }; - }); -} - -function next (config = {}) { - config.page = 'next'; - - return this.goToPage(config); -} - -function prev (config = {}) { - config.page = 'previous'; - - return this.goToPage(config); -} - -function normalizePath (resource) { - const version = '/api/v2/'; - - return `${version}${resource}/`; -} - -function isEditable () { - let canEdit = this.get('summary_fields.user_capabilities.edit'); - - if (canEdit === undefined) { - canEdit = true; - } - - if (canEdit) { - return true; - } - - if (this.has('options', 'actions.PUT')) { - return true; - } - - return false; -} - -function isCreatable () { - if (this.has('options', 'actions.POST')) { - return true; - } - - return false; -} - -function isCacheable () { - if (this.settings.cache === true) { - return true; - } - - return false; -} - -function graft (id) { - let item = this.get('results').filter(result => result.id === id); - - item = item ? item[0] : undefined; - - if (!item) { - return undefined; - } - - return new this.Constructor('get', item, true); -} - -function getDependentResourceCounts (id) { - this.setDependentResources(id); - - const promises = []; - - this.dependentResources.forEach(resource => { - promises.push(resource.model.request('get', { params: resource.params }) - .then(res => ({ - label: resource.model.label, - count: res.data.count - }))); - }); - - return Promise.all(promises); -} - -function copy () { - if (!this.has('POST', 'related.copy')) { - return Promise.reject(new Error('No related property, copy, exists')); - } - - const date = new Date(); - const name = `${this.get('name')}@${date.toLocaleTimeString()}`; - - const url = `${this.path}${this.get('id')}/copy/`; - - const req = { - url, - method: 'POST', - data: { name } - }; - - return $http(req).then(res => res.data); -} - -/** - * `create` is called on instantiation of every model. Models can be - * instantiated empty or with `GET` and/or `OPTIONS` requests that yield data. - * If model data already exists a new instance can be created (see: `graft`) - * with existing data. - * - * @arg {string=} method - Populate the model with `GET` or `OPTIONS` data. - * @arg {(string|Object)=} resource - An `id` reference to a particular - * resource or an existing model's data. - * @arg {config=} config - Create a new instance from existing model data. - * - * @returns {(Object|Promise)} - Returns a reference to the model instance - * if an empty instance or graft is created. Otherwise, a promise yielding - * a model instance is returned. - */ -function create (method, resource, config) { - const req = this.parseRequestConfig(method, resource, config); - - if (!req || !req.method) { - return this; - } - - if (req.resource) { - this.setEndpoint(req.resource); - } - - this.promise = this.request(req); - - if (req.graft) { - return this; - } - - return this.promise - .then(() => this); -} - -function setEndpoint (resource) { - if (Array.isArray(resource)) { - this.endpoint = `${this.path}${resource[0]}/`; - } else { - this.endpoint = `${this.path}${resource}/`; - } -} - -function parseRequestConfig (method, resource, config) { - if (!method) { - return null; - } - - let req = {}; - - if (Array.isArray(method)) { - if (Array.isArray(resource)) { - req.resource = resource; - } else if (resource === null) { - req.resource = undefined; - } else if (typeof resource === 'object') { - req = resource; - } - - req.method = method; - } else if (typeof method === 'string') { - if (resource === null) { - req.resource = undefined; - } else if (typeof resource === 'object') { - req = resource; - } else { - req.resource = resource; - } - - req.method = method; - } else if (typeof method === 'object') { - req = method; - } else { - req = config; - req.method = method; - req.resource = resource === null ? undefined : resource; - } - - return req; -} - -/** - * Base functionality for API interaction. - * - * @arg {string} resource - The API resource for the model extending BaseModel to - * use. - * @arg {Object=} settings - Configuration applied to all instances of the - * extending model. - * @arg {boolean=} settings.cache - Cache the model data. - * - */ -function BaseModel (resource, settings) { - this.create = create; - this.find = find; - this.get = get; - this.goToPage = goToPage; - this.graft = graft; - this.has = has; - this.isEditable = isEditable; - this.isCacheable = isCacheable; - this.isCreatable = isCreatable; - this.match = match; - this.next = next; - this.normalizePath = normalizePath; - this.options = options; - this.parseRequestConfig = parseRequestConfig; - this.prev = prev; - this.request = request; - this.requestWithCache = requestWithCache; - this.search = search; - this.set = set; - this.setEndpoint = setEndpoint; - this.unset = unset; - this.extend = extend; - this.copy = copy; - this.getDependentResourceCounts = getDependentResourceCounts; - this.updateCount = updateCount; - - this.http = { - get: httpGet.bind(this), - options: httpOptions.bind(this), - patch: httpPatch.bind(this), - post: httpPost.bind(this), - put: httpPut.bind(this), - delete: httpDelete.bind(this) - }; - - this.page = {}; - this.model = {}; - this.path = this.normalizePath(resource); - this.label = strings.get(`${resource}.LABEL`); - this.settings = settings || {}; -} - -function BaseModelLoader (_$http_, _$q_, _cache_, ModelsStrings) { - $http = _$http_; - $q = _$q_; - cache = _cache_; - strings = ModelsStrings; - - return BaseModel; -} - -BaseModelLoader.$inject = ['$http', '$q', 'CacheService', 'ModelsStrings']; - -export default BaseModelLoader; diff --git a/awx/ui/client/lib/models/Config.js b/awx/ui/client/lib/models/Config.js deleted file mode 100644 index 9f2e3ab87a35..000000000000 --- a/awx/ui/client/lib/models/Config.js +++ /dev/null @@ -1,39 +0,0 @@ -let $log; -let Base; - -function getTruncatedVersion () { - let version; - - try { - [version] = this.get('version').split('-'); - } catch (err) { - $log.error(err); - } - - return version; -} - -function isOpen () { - return this.get('license_info.license_type') === 'open'; -} - -function ConfigModel (method, resource, config) { - Base.call(this, 'config', { cache: true }); - - this.Constructor = ConfigModel; - this.getTruncatedVersion = getTruncatedVersion; - this.isOpen = isOpen; - - return this.create(method, resource, config); -} - -function ConfigModelLoader (BaseModel, _$log_) { - Base = BaseModel; - $log = _$log_; - - return ConfigModel; -} - -ConfigModelLoader.$inject = ['BaseModel', '$log']; - -export default ConfigModelLoader; diff --git a/awx/ui/client/lib/models/Credential.js b/awx/ui/client/lib/models/Credential.js deleted file mode 100644 index 27b6a04533c4..000000000000 --- a/awx/ui/client/lib/models/Credential.js +++ /dev/null @@ -1,164 +0,0 @@ -/* eslint camelcase: 0 */ -const ENCRYPTED_VALUE = '$encrypted$'; - -let Base; -let Project; -let JobTemplate; -let Inventory; -let InventorySource; - -function createFormSchema (method, config) { - if (!config) { - config = method; - method = 'GET'; - } - - const schema = Object.assign({}, this.options(`actions.${method.toUpperCase()}`)); - - if (config && config.omit) { - config.omit.forEach(key => delete schema[key]); - } - - Object.keys(schema).forEach(key => { - schema[key].id = key; - - if (this.has(key)) { - schema[key]._value = this.get(key); - } - }); - - return schema; -} - -function assignInputGroupValues (apiConfig, credentialType, sourceCredentials) { - let inputs = credentialType.get('inputs.fields'); - - if (!inputs) { - return []; - } - - if (this.has('credential_type')) { - if (credentialType.get('id') !== this.get('credential_type')) { - inputs.forEach(field => { - field.tagMode = this.isEditable() && credentialType.get('kind') !== 'external'; - }); - return inputs; - } - } - - inputs = inputs.map(input => { - const value = this.get(`inputs.${input.id}`); - - input._value = value; - input._encrypted = value === ENCRYPTED_VALUE; - - return input; - }); - - if (credentialType.get('namespace') === 'ssh') { - const become = inputs.find((field) => field.id === 'become_method'); - become._isDynamic = true; - become._choices = Array.from(apiConfig.become_methods, method => method[0]); - // Add the value to the choices if it doesn't exist in the preset list - if (become._value && become._value !== '') { - const optionMatches = become._choices - .findIndex((option) => option === become._value); - if (optionMatches === -1) { - become._choices.push(become._value); - } - } - } - - const linkedFieldNames = (this.get('related.input_sources.results') || []) - .map(({ input_field_name }) => input_field_name); - - inputs = inputs.map((field) => { - field.tagMode = this.isEditable() && credentialType.get('kind') !== 'external'; - if (linkedFieldNames.includes(field.id)) { - field.tagMode = true; - field.asTag = true; - const { summary_fields } = this.get('related.input_sources.results') - .find(({ input_field_name }) => input_field_name === field.id); - field._tagValue = summary_fields.source_credential.name; - - const { source_credential: { id } } = summary_fields; - const src = sourceCredentials.data.results.find(obj => obj.id === id); - const canRemove = _.get(src, ['summary_fields', 'user_capabilities', 'delete'], false); - - if (!canRemove) { - field._disabled = true; - } - } - - return field; - }); - - return inputs; -} - -function setDependentResources (id) { - this.dependentResources = [ - { - model: new Project(), - params: { - credential: id - } - }, - { - model: new JobTemplate(), - params: { - credentials__id: id, - ask_credential_on_launch: false - } - }, - { - model: new Inventory(), - params: { - insights_credential: id - } - }, - { - model: new InventorySource(), - params: { - credentials__id: id - } - } - ]; -} - -function CredentialModel (method, resource, config) { - Base.call(this, 'credentials'); - - this.Constructor = CredentialModel; - this.createFormSchema = createFormSchema.bind(this); - this.assignInputGroupValues = assignInputGroupValues.bind(this); - this.setDependentResources = setDependentResources.bind(this); - - return this.create(method, resource, config); -} - -function CredentialModelLoader ( - BaseModel, - ProjectModel, - JobTemplateModel, - InventoryModel, - InventorySourceModel -) { - Base = BaseModel; - Project = ProjectModel; - JobTemplate = JobTemplateModel; - Inventory = InventoryModel; - InventorySource = InventorySourceModel; - - return CredentialModel; -} - -CredentialModelLoader.$inject = [ - 'BaseModel', - 'ProjectModel', - 'JobTemplateModel', - 'InventoryModel', - 'InventorySourceModel' -]; - -export default CredentialModelLoader; diff --git a/awx/ui/client/lib/models/CredentialType.js b/awx/ui/client/lib/models/CredentialType.js deleted file mode 100644 index 38c44cd845e3..000000000000 --- a/awx/ui/client/lib/models/CredentialType.js +++ /dev/null @@ -1,68 +0,0 @@ -let Base; -let Credential; - -function categorizeByKind () { - const group = {}; - - this.get('results').forEach(result => { - group[result.kind] = group[result.kind] || []; - group[result.kind].push(result); - }); - - return Object.keys(group).map(category => ({ - data: group[category], - category - })); -} - -function mergeInputProperties (key = 'fields') { - if (!this.has(`inputs.${key}`)) { - return undefined; - } - - const required = this.get('inputs.required'); - - return this.get(`inputs.${key}`).forEach((field, i) => { - if (!required || required.indexOf(field.id) === -1) { - this.set(`inputs.${key}[${i}].required`, false); - } else { - this.set(`inputs.${key}[${i}].required`, true); - } - }); -} - -function setDependentResources (id) { - this.dependentResources = [ - { - model: new Credential(), - params: { - credential_type: id - } - } - ]; -} - -function CredentialTypeModel (method, resource, config) { - Base.call(this, 'credential_types'); - - this.Constructor = CredentialTypeModel; - this.categorizeByKind = categorizeByKind.bind(this); - this.mergeInputProperties = mergeInputProperties.bind(this); - this.setDependentResources = setDependentResources.bind(this); - - return this.create(method, resource, config); -} - -function CredentialTypeModelLoader (BaseModel, CredentialModel) { - Base = BaseModel; - Credential = CredentialModel; - - return CredentialTypeModel; -} - -CredentialTypeModelLoader.$inject = [ - 'BaseModel', - 'CredentialModel' -]; - -export default CredentialTypeModelLoader; diff --git a/awx/ui/client/lib/models/Instance.js b/awx/ui/client/lib/models/Instance.js deleted file mode 100644 index 09b7df0547e7..000000000000 --- a/awx/ui/client/lib/models/Instance.js +++ /dev/null @@ -1,47 +0,0 @@ -let Base; - -function createFormSchema (method, config) { - if (!config) { - config = method; - method = 'GET'; - } - - const schema = Object.assign({}, this.options(`actions.${method.toUpperCase()}`)); - - if (config && config.omit) { - config.omit.forEach(key => delete schema[key]); - } - - Object.keys(schema).forEach(key => { - schema[key].id = key; - - if (this.has(key)) { - schema[key]._value = this.get(key); - } - }); - - return schema; -} - -function InstanceModel (method, resource, config) { - // Base takes two args: resource and settings - // resource is the string endpoint - Base.call(this, 'instances'); - - this.Constructor = InstanceModel; - this.createFormSchema = createFormSchema.bind(this); - - return this.create(method, resource, config); -} - -function InstanceModelLoader (BaseModel) { - Base = BaseModel; - - return InstanceModel; -} - -InstanceModelLoader.$inject = [ - 'BaseModel' -]; - -export default InstanceModelLoader; diff --git a/awx/ui/client/lib/models/InstanceGroup.js b/awx/ui/client/lib/models/InstanceGroup.js deleted file mode 100644 index cc82432c422d..000000000000 --- a/awx/ui/client/lib/models/InstanceGroup.js +++ /dev/null @@ -1,47 +0,0 @@ -let Base; - -function createFormSchema (method, config) { - if (!config) { - config = method; - method = 'GET'; - } - - const schema = Object.assign({}, this.options(`actions.${method.toUpperCase()}`)); - - if (config && config.omit) { - config.omit.forEach(key => delete schema[key]); - } - - Object.keys(schema).forEach(key => { - schema[key].id = key; - - if (this.has(key)) { - schema[key]._value = this.get(key); - } - }); - - return schema; -} - -function InstanceGroupModel (method, resource, config) { - // Base takes two args: resource and settings - // resource is the string endpoint - Base.call(this, 'instance_groups'); - - this.Constructor = InstanceGroupModel; - this.createFormSchema = createFormSchema.bind(this); - - return this.create(method, resource, config); -} - -function InstanceGroupModelLoader (BaseModel) { - Base = BaseModel; - - return InstanceGroupModel; -} - -InstanceGroupModelLoader.$inject = [ - 'BaseModel' -]; - -export default InstanceGroupModelLoader; diff --git a/awx/ui/client/lib/models/Inventory.js b/awx/ui/client/lib/models/Inventory.js deleted file mode 100644 index 828dac105570..000000000000 --- a/awx/ui/client/lib/models/Inventory.js +++ /dev/null @@ -1,45 +0,0 @@ -let Base; -let JobTemplate; -let WorkflowJobTemplate; - -function setDependentResources (id) { - this.dependentResources = [ - { - model: new JobTemplate(), - params: { - inventory: id - } - }, - { - model: new WorkflowJobTemplate(), - params: { - inventory: id - } - } - ]; -} - -function InventoryModel (method, resource, config) { - Base.call(this, 'inventories'); - - this.Constructor = InventoryModel; - this.setDependentResources = setDependentResources.bind(this); - - return this.create(method, resource, config); -} - -function InventoryModelLoader (BaseModel, JobTemplateModel, WorkflowJobTemplateModel) { - Base = BaseModel; - JobTemplate = JobTemplateModel; - WorkflowJobTemplate = WorkflowJobTemplateModel; - - return InventoryModel; -} - -InventoryModelLoader.$inject = [ - 'BaseModel', - 'JobTemplateModel', - 'WorkflowJobTemplateModel', -]; - -export default InventoryModelLoader; diff --git a/awx/ui/client/lib/models/InventoryScript.js b/awx/ui/client/lib/models/InventoryScript.js deleted file mode 100644 index 1b759410abd3..000000000000 --- a/awx/ui/client/lib/models/InventoryScript.js +++ /dev/null @@ -1,36 +0,0 @@ -let Base; -let InventorySource; - -function setDependentResources (id) { - this.dependentResources = [ - { - model: new InventorySource(), - params: { - source_script: id - } - } - ]; -} - -function InventoryScriptModel (method, resource, config) { - Base.call(this, 'inventory_scripts'); - - this.Constructor = InventoryScriptModel; - this.setDependentResources = setDependentResources.bind(this); - - return this.create(method, resource, config); -} - -function InventoryScriptModelLoader (BaseModel, InventorySourceModel) { - Base = BaseModel; - InventorySource = InventorySourceModel; - - return InventoryScriptModel; -} - -InventoryScriptModelLoader.$inject = [ - 'BaseModel', - 'InventorySourceModel' -]; - -export default InventoryScriptModelLoader; diff --git a/awx/ui/client/lib/models/InventorySource.js b/awx/ui/client/lib/models/InventorySource.js deleted file mode 100644 index 0d68a1178007..000000000000 --- a/awx/ui/client/lib/models/InventorySource.js +++ /dev/null @@ -1,63 +0,0 @@ -let Base; -let WorkflowJobTemplateNode; -let $http; - -function setDependentResources (id) { - this.dependentResources = [ - { - model: new WorkflowJobTemplateNode(), - params: { - unified_job_template: id - } - } - ]; -} - -function getUpdate (id) { - const req = { - method: 'GET', - url: `${this.path}${id}/update/` - }; - - return $http(req); -} - -function postUpdate (id) { - const req = { - method: 'POST', - url: `${this.path}${id}/update/` - }; - - return $http(req); -} - -function InventorySourceModel (method, resource, config) { - Base.call(this, 'inventory_sources'); - - this.Constructor = InventorySourceModel; - this.setDependentResources = setDependentResources.bind(this); - this.getUpdate = getUpdate.bind(this); - this.postUpdate = postUpdate.bind(this); - - return this.create(method, resource, config); -} - -function InventorySourceModelLoader ( - BaseModel, - WorkflowJobTemplateNodeModel, - _$http_ -) { - Base = BaseModel; - WorkflowJobTemplateNode = WorkflowJobTemplateNodeModel; - $http = _$http_; - - return InventorySourceModel; -} - -InventorySourceModelLoader.$inject = [ - 'BaseModel', - 'WorkflowJobTemplateNodeModel', - '$http' -]; - -export default InventorySourceModelLoader; diff --git a/awx/ui/client/lib/models/InventoryUpdate.js b/awx/ui/client/lib/models/InventoryUpdate.js deleted file mode 100644 index 37951911d7e2..000000000000 --- a/awx/ui/client/lib/models/InventoryUpdate.js +++ /dev/null @@ -1,21 +0,0 @@ -let BaseModel; - -function InventoryUpdateModel (method, resource, config) { - BaseModel.call(this, 'inventory_updates'); - - this.Constructor = InventoryUpdateModel; - - return this.create(method, resource, config); -} - -function InventoryUpdateModelLoader (_BaseModel_) { - BaseModel = _BaseModel_; - - return InventoryUpdateModel; -} - -InventoryUpdateModelLoader.$inject = [ - 'BaseModel' -]; - -export default InventoryUpdateModelLoader; diff --git a/awx/ui/client/lib/models/Job.js b/awx/ui/client/lib/models/Job.js deleted file mode 100644 index 7e2f017826d8..000000000000 --- a/awx/ui/client/lib/models/Job.js +++ /dev/null @@ -1,59 +0,0 @@ -let $http; -let BaseModel; - -function getRelaunch (params) { - const req = { - method: 'GET', - url: `${this.path}${params.id}/relaunch/` - }; - - return $http(req); -} - -function postRelaunch (params) { - const req = { - method: 'POST', - url: `${this.path}${params.id}/relaunch/` - }; - - if (params.relaunchData) { - req.data = params.relaunchData; - } - - return $http(req); -} - -function getCredentials (id) { - const req = { - method: 'GET', - url: `${this.path}${id}/credentials/` - }; - - return $http(req); -} - -function JobModel (method, resource, config) { - BaseModel.call(this, 'jobs'); - - this.Constructor = JobModel; - - this.postRelaunch = postRelaunch.bind(this); - this.getRelaunch = getRelaunch.bind(this); - this.getCredentials = getCredentials.bind(this); - - return this.create(method, resource, config); -} - -function JobModelLoader (_$http_, _BaseModel_) { - $http = _$http_; - BaseModel = _BaseModel_; - - return JobModel; -} - -JobModelLoader.$inject = [ - '$http', - 'BaseModel', -]; - -export default JobModelLoader; diff --git a/awx/ui/client/lib/models/JobEvent.js b/awx/ui/client/lib/models/JobEvent.js deleted file mode 100644 index 1c71ba9c546f..000000000000 --- a/awx/ui/client/lib/models/JobEvent.js +++ /dev/null @@ -1,19 +0,0 @@ -let BaseModel; - -function JobEventModel (method, resource, config) { - BaseModel.call(this, 'job_events'); - - this.Constructor = JobEventModel; - - return this.create(method, resource, config); -} - -function JobEventModelLoader (_BaseModel_) { - BaseModel = _BaseModel_; - - return JobEventModel; -} - -JobEventModel.$inject = ['BaseModel']; - -export default JobEventModelLoader; diff --git a/awx/ui/client/lib/models/JobTemplate.js b/awx/ui/client/lib/models/JobTemplate.js deleted file mode 100644 index b5a0dae16ecd..000000000000 --- a/awx/ui/client/lib/models/JobTemplate.js +++ /dev/null @@ -1,120 +0,0 @@ -let Base; -let WorkflowJobTemplateNode; -let $http; - -function optionsLaunch (id) { - const req = { - method: 'OPTIONS', - url: `${this.path}${id}/launch/` - }; - - return $http(req); -} - -function getLaunch (id) { - const req = { - method: 'GET', - url: `${this.path}${id}/launch/` - }; - - return $http(req) - .then(res => { - this.model.launch.GET = res.data; - - return res; - }); -} - -function postLaunch (params) { - const req = { - method: 'POST', - url: `${this.path}${params.id}/launch/` - }; - - if (params.launchData) { - req.data = params.launchData; - } - - return $http(req); -} - -function getSurveyQuestions (id) { - const req = { - method: 'GET', - url: `${this.path}${id}/survey_spec/` - }; - - return $http(req); -} - -function getLaunchConf () { - // this method is just a pass-through to the underlying launch GET data - // we use it to make the access patterns consistent across both types of - // templates - return this.model.launch.GET; -} - -function canLaunchWithoutPrompt () { - const launchData = this.getLaunchConf(); - - return ( - launchData.can_start_without_user_input && - !launchData.ask_inventory_on_launch && - !launchData.ask_credential_on_launch && - !launchData.ask_verbosity_on_launch && - !launchData.ask_job_type_on_launch && - !launchData.ask_limit_on_launch && - !launchData.ask_tags_on_launch && - !launchData.ask_skip_tags_on_launch && - !launchData.ask_variables_on_launch && - !launchData.ask_diff_mode_on_launch && - !launchData.ask_scm_branch_on_launch && - !launchData.survey_enabled && - launchData.variables_needed_to_start.length === 0 - ); -} - -function setDependentResources (id) { - this.dependentResources = [ - { - model: new WorkflowJobTemplateNode(), - params: { - unified_job_template: id - } - } - ]; -} - -function JobTemplateModel (method, resource, config) { - Base.call(this, 'job_templates'); - - this.Constructor = JobTemplateModel; - this.setDependentResources = setDependentResources.bind(this); - this.optionsLaunch = optionsLaunch.bind(this); - this.getLaunch = getLaunch.bind(this); - this.postLaunch = postLaunch.bind(this); - this.getSurveyQuestions = getSurveyQuestions.bind(this); - this.getLaunchConf = getLaunchConf.bind(this); - this.canLaunchWithoutPrompt = canLaunchWithoutPrompt.bind(this); - - this.model.launch = {}; - - return this.create(method, resource, config); -} - -function JobTemplateModelLoader (BaseModel, WorkflowJobTemplateNodeModel, _$http_) { - Base = BaseModel; - WorkflowJobTemplateNode = WorkflowJobTemplateNodeModel; - $http = _$http_; - - return JobTemplateModel; -} - -JobTemplateModelLoader.$inject = [ - 'BaseModel', - 'WorkflowJobTemplateNodeModel', - '$http', - '$state' -]; - -export default JobTemplateModelLoader; diff --git a/awx/ui/client/lib/models/Me.js b/awx/ui/client/lib/models/Me.js deleted file mode 100644 index 221a29da3410..000000000000 --- a/awx/ui/client/lib/models/Me.js +++ /dev/null @@ -1,27 +0,0 @@ -let Base; - -function MeModel (method, resource, config) { - Base.call(this, 'me'); - - this.Constructor = MeModel; - - return this.create(method, resource, config) - .then(() => { - if (this.has('results')) { - _.merge(this.model.GET, this.get('results[0]')); - this.unset('results'); - } - - return this; - }); -} - -function MeModelLoader (BaseModel) { - Base = BaseModel; - - return MeModel; -} - -MeModelLoader.$inject = ['BaseModel']; - -export default MeModelLoader; diff --git a/awx/ui/client/lib/models/NotificationTemplate.js b/awx/ui/client/lib/models/NotificationTemplate.js deleted file mode 100644 index 6418a7184d5d..000000000000 --- a/awx/ui/client/lib/models/NotificationTemplate.js +++ /dev/null @@ -1,21 +0,0 @@ -let Base; - -function NotificationTemplateModel (method, resource, config) { - Base.call(this, 'notification_templates'); - - this.Constructor = NotificationTemplateModel; - - return this.create(method, resource, config); -} - -function NotificationTemplateModelLoader (BaseModel) { - Base = BaseModel; - - return NotificationTemplateModel; -} - -NotificationTemplateModelLoader.$inject = [ - 'BaseModel' -]; - -export default NotificationTemplateModelLoader; diff --git a/awx/ui/client/lib/models/Organization.js b/awx/ui/client/lib/models/Organization.js deleted file mode 100644 index 6209889fba33..000000000000 --- a/awx/ui/client/lib/models/Organization.js +++ /dev/null @@ -1,36 +0,0 @@ -let Base; -let Credential; - -function setDependentResources (id) { - this.dependentResources = [ - { - model: new Credential(), - params: { - organization: id - } - } - ]; -} - -function OrganizationModel (method, resource, config) { - Base.call(this, 'organizations'); - - this.Constructor = OrganizationModel; - this.setDependentResources = setDependentResources.bind(this); - - return this.create(method, resource, config); -} - -function OrganizationModelLoader (BaseModel, CredentialModel) { - Base = BaseModel; - Credential = CredentialModel; - - return OrganizationModel; -} - -OrganizationModelLoader.$inject = [ - 'BaseModel', - 'CredentialModel' -]; - -export default OrganizationModelLoader; diff --git a/awx/ui/client/lib/models/Project.js b/awx/ui/client/lib/models/Project.js deleted file mode 100644 index 02606c3dcd7c..000000000000 --- a/awx/ui/client/lib/models/Project.js +++ /dev/null @@ -1,83 +0,0 @@ -let Base; -let JobTemplate; -let WorkflowJobTemplateNode; -let InventorySource; -let $http; - -function setDependentResources (id) { - this.dependentResources = [ - { - model: new JobTemplate(), - params: { - project: id - } - }, - { - model: new WorkflowJobTemplateNode(), - params: { - unified_job_template: id - } - }, - { - model: new InventorySource(), - params: { - source_project: id - } - } - ]; -} - -function getUpdate (id) { - const req = { - method: 'GET', - url: `${this.path}${id}/update/` - }; - - return $http(req); -} - -function postUpdate (id) { - const req = { - method: 'POST', - url: `${this.path}${id}/update/` - }; - - return $http(req); -} - -function ProjectModel (method, resource, config) { - Base.call(this, 'projects'); - - this.Constructor = ProjectModel; - this.setDependentResources = setDependentResources.bind(this); - this.getUpdate = getUpdate.bind(this); - this.postUpdate = postUpdate.bind(this); - - return this.create(method, resource, config); -} - -function ProjectModelLoader ( - BaseModel, - JobTemplateModel, - WorkflowJobTemplateNodeModel, - InventorySourceModel, - _$http_ -) { - Base = BaseModel; - JobTemplate = JobTemplateModel; - WorkflowJobTemplateNode = WorkflowJobTemplateNodeModel; - InventorySource = InventorySourceModel; - $http = _$http_; - - return ProjectModel; -} - -ProjectModelLoader.$inject = [ - 'BaseModel', - 'JobTemplateModel', - 'WorkflowJobTemplateNodeModel', - 'InventorySourceModel', - '$http' -]; - -export default ProjectModelLoader; diff --git a/awx/ui/client/lib/models/ProjectUpdate.js b/awx/ui/client/lib/models/ProjectUpdate.js deleted file mode 100644 index a8b1ae1fe9bf..000000000000 --- a/awx/ui/client/lib/models/ProjectUpdate.js +++ /dev/null @@ -1,21 +0,0 @@ -let BaseModel; - -function ProjectUpdateModel (method, resource, config) { - BaseModel.call(this, 'project_updates'); - - this.Constructor = ProjectUpdateModel; - - return this.create(method, resource, config); -} - -function ProjectUpdateModelLoader (_BaseModel_) { - BaseModel = _BaseModel_; - - return ProjectUpdateModel; -} - -ProjectUpdateModelLoader.$inject = [ - 'BaseModel' -]; - -export default ProjectUpdateModelLoader; diff --git a/awx/ui/client/lib/models/Schedule.js b/awx/ui/client/lib/models/Schedule.js deleted file mode 100644 index b6bd3cf60f3c..000000000000 --- a/awx/ui/client/lib/models/Schedule.js +++ /dev/null @@ -1,38 +0,0 @@ -let Base; -let $http; - -function postCredential (params) { - const req = { - method: 'POST', - url: `${this.path}${params.id}/credentials/` - }; - - if (params.data) { - req.data = params.data; - } - - return $http(req); -} - -function ScheduleModel (method, resource, config) { - Base.call(this, 'schedules'); - - this.Constructor = ScheduleModel; - this.postCredential = postCredential.bind(this); - - return this.create(method, resource, config); -} - -function ScheduleModelLoader (BaseModel, _$http_) { - Base = BaseModel; - $http = _$http_; - - return ScheduleModel; -} - -ScheduleModelLoader.$inject = [ - 'BaseModel', - '$http' -]; - -export default ScheduleModelLoader; diff --git a/awx/ui/client/lib/models/SystemJob.js b/awx/ui/client/lib/models/SystemJob.js deleted file mode 100644 index cc092ff8f4a7..000000000000 --- a/awx/ui/client/lib/models/SystemJob.js +++ /dev/null @@ -1,19 +0,0 @@ -let BaseModel; - -function SystemJobModel (method, resource, config) { - BaseModel.call(this, 'system_jobs'); - - this.Constructor = SystemJobModel; - - return this.create(method, resource, config); -} - -function SystemJobModelLoader (_BaseModel_) { - BaseModel = _BaseModel_; - - return SystemJobModel; -} - -SystemJobModelLoader.$inject = ['BaseModel']; - -export default SystemJobModelLoader; diff --git a/awx/ui/client/lib/models/Token.js b/awx/ui/client/lib/models/Token.js deleted file mode 100644 index c4dfaf1937b1..000000000000 --- a/awx/ui/client/lib/models/Token.js +++ /dev/null @@ -1,26 +0,0 @@ -let Base; - -function setDependentResources () { - this.dependentResources = []; -} - -function TokenModel (method, resource, config) { - Base.call(this, 'tokens'); - - this.Constructor = TokenModel; - this.setDependentResources = setDependentResources.bind(this); - - return this.create(method, resource, config); -} - -function TokenModelLoader (BaseModel) { - Base = BaseModel; - - return TokenModel; -} - -TokenModelLoader.$inject = [ - 'BaseModel', -]; - -export default TokenModelLoader; diff --git a/awx/ui/client/lib/models/UnifiedJob.js b/awx/ui/client/lib/models/UnifiedJob.js deleted file mode 100644 index 13078f8fa2fc..000000000000 --- a/awx/ui/client/lib/models/UnifiedJob.js +++ /dev/null @@ -1,21 +0,0 @@ -let Base; - -function UnifiedJobModel (method, resource, config) { - Base.call(this, 'unified_jobs'); - - this.Constructor = UnifiedJobModel; - - return this.create(method, resource, config); -} - -function UnifiedJobModelLoader (BaseModel) { - Base = BaseModel; - - return UnifiedJobModel; -} - -UnifiedJobModelLoader.$inject = [ - 'BaseModel' -]; - -export default UnifiedJobModelLoader; diff --git a/awx/ui/client/lib/models/UnifiedJobTemplate.js b/awx/ui/client/lib/models/UnifiedJobTemplate.js deleted file mode 100644 index fb1e241fd3db..000000000000 --- a/awx/ui/client/lib/models/UnifiedJobTemplate.js +++ /dev/null @@ -1,111 +0,0 @@ -let BaseModel; -let WorkflowJobTemplateNode; -let $http; - -function optionsLaunch (id) { - const req = { - method: 'OPTIONS', - url: `${this.path}${id}/launch/` - }; - - return $http(req); -} - -function getLaunch (id) { - const req = { - method: 'GET', - url: `${this.path}${id}/launch/` - }; - - return $http(req) - .then(res => { - this.model.launch.GET = res.data; - - return res; - }); -} - -function postLaunch (params) { - const req = { - method: 'POST', - url: `${this.path}${params.id}/launch/` - }; - - if (params.launchData) { - req.data = params.launchData; - } - - return $http(req); -} - -function getSurveyQuestions (id) { - const req = { - method: 'GET', - url: `${this.path}${id}/survey_spec/` - }; - - return $http(req); -} - -function canLaunchWithoutPrompt () { - const launchData = this.model.launch.GET; - - return ( - launchData.can_start_without_user_input && - !launchData.ask_inventory_on_launch && - !launchData.ask_credential_on_launch && - !launchData.ask_verbosity_on_launch && - !launchData.ask_job_type_on_launch && - !launchData.ask_limit_on_launch && - !launchData.ask_tags_on_launch && - !launchData.ask_skip_tags_on_launch && - !launchData.ask_variables_on_launch && - !launchData.ask_diff_mode_on_launch && - !launchData.ask_scm_branch_on_launch && - !launchData.survey_enabled - ); -} - -function setDependentResources (id) { - this.dependentResources = [ - { - model: new WorkflowJobTemplateNode(), - params: { - unified_job_template: id - } - } - ]; -} - -function UnifiedJobTemplateModel (method, resource, graft) { - BaseModel.call(this, 'unified_job_templates'); - - this.Constructor = UnifiedJobTemplateModel; - this.setDependentResources = setDependentResources.bind(this); - this.optionsLaunch = optionsLaunch.bind(this); - this.getLaunch = getLaunch.bind(this); - this.postLaunch = postLaunch.bind(this); - this.getSurveyQuestions = getSurveyQuestions.bind(this); - this.canLaunchWithoutPrompt = canLaunchWithoutPrompt.bind(this); - - this.model.launch = {}; - - return this.create(method, resource, graft); -} - -function UnifiedJobTemplateModelLoader (_BaseModel_, WorkflowJobTemplateNodeModel, _$http_) { - BaseModel = _BaseModel_; - WorkflowJobTemplateNode = WorkflowJobTemplateNodeModel; - $http = _$http_; - - return UnifiedJobTemplateModel; -} - -UnifiedJobTemplateModelLoader.$inject = [ - 'BaseModel', - 'WorkflowJobTemplateNodeModel', - '$http', - '$state' -]; - -export default UnifiedJobTemplateModelLoader; diff --git a/awx/ui/client/lib/models/User.js b/awx/ui/client/lib/models/User.js deleted file mode 100644 index da521255cb30..000000000000 --- a/awx/ui/client/lib/models/User.js +++ /dev/null @@ -1,40 +0,0 @@ -let Base; -let $http; - -function postAuthorizedTokens (params) { - const req = { - method: 'POST', - url: `${this.path}${params.id}/authorized_tokens/` - }; - - if (params.payload) { - req.data = params.payload; - } - - return $http(req); -} - -function UserModel (method, resource, config) { - Base.call(this, 'users'); - - this.Constructor = UserModel; - this.postAuthorizedTokens = postAuthorizedTokens.bind(this); - - this.model.launch = {}; - - return this.create(method, resource, config); -} - -function UserModelLoader (BaseModel, _$http_) { - Base = BaseModel; - $http = _$http_; - - return UserModel; -} - -UserModelLoader.$inject = [ - 'BaseModel', - '$http' -]; - -export default UserModelLoader; diff --git a/awx/ui/client/lib/models/WorkflowJob.js b/awx/ui/client/lib/models/WorkflowJob.js deleted file mode 100644 index 02ea3582cc16..000000000000 --- a/awx/ui/client/lib/models/WorkflowJob.js +++ /dev/null @@ -1,34 +0,0 @@ -let Base; -let $http; - -function postRelaunch (params) { - const req = { - method: 'POST', - url: `${this.path}${params.id}/relaunch/` - }; - - return $http(req); -} - -function WorkflowJobModel (method, resource, config) { - Base.call(this, 'workflow_jobs'); - - this.Constructor = WorkflowJobModel; - this.postRelaunch = postRelaunch.bind(this); - - return this.create(method, resource, config); -} - -function WorkflowJobModelLoader (BaseModel, _$http_) { - Base = BaseModel; - $http = _$http_; - - return WorkflowJobModel; -} - -WorkflowJobModelLoader.$inject = [ - 'BaseModel', - '$http' -]; - -export default WorkflowJobModelLoader; diff --git a/awx/ui/client/lib/models/WorkflowJobTemplate.js b/awx/ui/client/lib/models/WorkflowJobTemplate.js deleted file mode 100644 index 06d9e9deccdc..000000000000 --- a/awx/ui/client/lib/models/WorkflowJobTemplate.js +++ /dev/null @@ -1,96 +0,0 @@ -/* eslint camelcase: 0 */ -let Base; -let $http; - -function optionsLaunch (id) { - const req = { - method: 'OPTIONS', - url: `${this.path}${id}/launch/` - }; - - return $http(req); -} - -function getLaunch (id) { - const req = { - method: 'GET', - url: `${this.path}${id}/launch/` - }; - - return $http(req) - .then(res => { - this.model.launch.GET = res.data; - - return res; - }); -} - -function postLaunch (params) { - const req = { - method: 'POST', - url: `${this.path}${params.id}/launch/` - }; - - if (params.launchData) { - req.data = params.launchData; - } - - return $http(req); -} - -function getSurveyQuestions (id) { - const req = { - method: 'GET', - url: `${this.path}${id}/survey_spec/` - }; - - return $http(req); -} - -function getLaunchConf () { - return this.model.launch.GET; -} - -function canLaunchWithoutPrompt () { - const launchData = this.getLaunchConf(); - - return ( - launchData.can_start_without_user_input && - !launchData.ask_inventory_on_launch && - !launchData.ask_variables_on_launch && - !launchData.ask_limit_on_launch && - !launchData.ask_scm_branch_on_launch && - !launchData.survey_enabled && - launchData.variables_needed_to_start.length === 0 - ); -} - -function WorkflowJobTemplateModel (method, resource, config) { - Base.call(this, 'workflow_job_templates'); - - this.Constructor = WorkflowJobTemplateModel; - this.optionsLaunch = optionsLaunch.bind(this); - this.getLaunch = getLaunch.bind(this); - this.postLaunch = postLaunch.bind(this); - this.getSurveyQuestions = getSurveyQuestions.bind(this); - this.getLaunchConf = getLaunchConf.bind(this); - this.canLaunchWithoutPrompt = canLaunchWithoutPrompt.bind(this); - - this.model.launch = {}; - - return this.create(method, resource, config); -} - -function WorkflowJobTemplateModelLoader (BaseModel, _$http_) { - Base = BaseModel; - $http = _$http_; - - return WorkflowJobTemplateModel; -} - -WorkflowJobTemplateModelLoader.$inject = [ - 'BaseModel', - '$http', -]; - -export default WorkflowJobTemplateModelLoader; diff --git a/awx/ui/client/lib/models/WorkflowJobTemplateNode.js b/awx/ui/client/lib/models/WorkflowJobTemplateNode.js deleted file mode 100644 index c08cbd2e8cc8..000000000000 --- a/awx/ui/client/lib/models/WorkflowJobTemplateNode.js +++ /dev/null @@ -1,21 +0,0 @@ -let Base; - -function WorkflowJobTemplateNodeModel (method, resource, config) { - Base.call(this, 'workflow_job_template_nodes'); - - this.Constructor = WorkflowJobTemplateNodeModel; - - return this.create(method, resource, config); -} - -function WorkflowJobTemplateNodeModelLoader (BaseModel) { - Base = BaseModel; - - return WorkflowJobTemplateNodeModel; -} - -WorkflowJobTemplateNodeModelLoader.$inject = [ - 'BaseModel' -]; - -export default WorkflowJobTemplateNodeModelLoader; diff --git a/awx/ui/client/lib/models/index.js b/awx/ui/client/lib/models/index.js deleted file mode 100644 index 6d8cc142e9c9..000000000000 --- a/awx/ui/client/lib/models/index.js +++ /dev/null @@ -1,72 +0,0 @@ -import atLibServices from '~services'; - -import AdHocCommand from '~models/AdHocCommand'; -import Application from '~models/Application'; -import Base from '~models/Base'; -import Config from '~models/Config'; -import Credential from '~models/Credential'; -import CredentialType from '~models/CredentialType'; -import Instance from '~models/Instance'; -import InstanceGroup from '~models/InstanceGroup'; -import Inventory from '~models/Inventory'; -import InventoryScript from '~models/InventoryScript'; -import InventorySource from '~models/InventorySource'; -import InventoryUpdate from '~models/InventoryUpdate'; -import Job from '~models/Job'; -import JobEvent from '~models/JobEvent'; -import JobTemplate from '~models/JobTemplate'; -import Me from '~models/Me'; -import NotificationTemplate from '~models/NotificationTemplate'; -import Organization from '~models/Organization'; -import Project from '~models/Project'; -import ProjectUpdate from '~models/ProjectUpdate'; -import Schedule from '~models/Schedule'; -import SystemJob from '~models/SystemJob'; -import Token from '~models/Token'; -import UnifiedJob from '~models/UnifiedJob'; -import UnifiedJobTemplate from '~models/UnifiedJobTemplate'; -import User from '~models/User'; -import WorkflowJob from '~models/WorkflowJob'; -import WorkflowJobTemplate from '~models/WorkflowJobTemplate'; -import WorkflowJobTemplateNode from '~models/WorkflowJobTemplateNode'; - -import ModelsStrings from '~models/models.strings'; - -const MODULE_NAME = 'at.lib.models'; - -angular - .module(MODULE_NAME, [ - atLibServices - ]) - .service('AdHocCommandModel', AdHocCommand) - .service('ApplicationModel', Application) - .service('BaseModel', Base) - .service('ConfigModel', Config) - .service('CredentialModel', Credential) - .service('CredentialTypeModel', CredentialType) - .service('InstanceGroupModel', InstanceGroup) - .service('InstanceModel', Instance) - .service('InventoryModel', Inventory) - .service('InventoryScriptModel', InventoryScript) - .service('InventorySourceModel', InventorySource) - .service('InventoryUpdateModel', InventoryUpdate) - .service('JobEventModel', JobEvent) - .service('JobModel', Job) - .service('JobTemplateModel', JobTemplate) - .service('MeModel', Me) - .service('ModelsStrings', ModelsStrings) - .service('NotificationTemplate', NotificationTemplate) - .service('OrganizationModel', Organization) - .service('ProjectModel', Project) - .service('ProjectUpdateModel', ProjectUpdate) - .service('ScheduleModel', Schedule) - .service('SystemJobModel', SystemJob) - .service('TokenModel', Token) - .service('UnifiedJobModel', UnifiedJob) - .service('UnifiedJobTemplateModel', UnifiedJobTemplate) - .service('UserModel', User) - .service('WorkflowJobModel', WorkflowJob) - .service('WorkflowJobTemplateModel', WorkflowJobTemplate) - .service('WorkflowJobTemplateNodeModel', WorkflowJobTemplateNode); - -export default MODULE_NAME; diff --git a/awx/ui/client/lib/models/models.strings.js b/awx/ui/client/lib/models/models.strings.js deleted file mode 100644 index c9385e3db5c6..000000000000 --- a/awx/ui/client/lib/models/models.strings.js +++ /dev/null @@ -1,56 +0,0 @@ -function ModelsStrings (BaseString) { - BaseString.call(this, 'models'); - - const { t } = this; - const ns = this.models; - - ns.credentials = { - LABEL: t.s('Credentials') - }; - - ns.credential_types = { - LABEL: t.s('Credential Types') - }; - - ns.inventories = { - LABEL: t.s('Inventories') - }; - - ns.inventory_scripts = { - LABEL: t.s('Inventory Scripts') - - }; - - ns.inventory_sources = { - LABEL: t.s('Inventory Sources') - - }; - - ns.job_templates = { - LABEL: t.s('Job Templates') - - }; - - ns.organizations = { - LABEL: t.s('Organizations') - - }; - - ns.projects = { - LABEL: t.s('Projects') - - }; - - ns.workflow_job_templates = { - LABEL: t.s('Workflow Job Templates') - }; - - ns.workflow_job_template_nodes = { - LABEL: t.s('Workflow Job Template Nodes') - - }; -} - -ModelsStrings.$inject = ['BaseStringService']; - -export default ModelsStrings; diff --git a/awx/ui/client/lib/services/app.strings.js b/awx/ui/client/lib/services/app.strings.js deleted file mode 100644 index a84489730830..000000000000 --- a/awx/ui/client/lib/services/app.strings.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * This service is used to access the app-wide strings defined in BaseStringService. - */ -function AppStrings (BaseString) { - BaseString.call(this, 'app'); -} - -AppStrings.$inject = ['BaseStringService']; - -export default AppStrings; diff --git a/awx/ui/client/lib/services/base-string.service.js b/awx/ui/client/lib/services/base-string.service.js deleted file mode 100644 index 3e7b3b7cd717..000000000000 --- a/awx/ui/client/lib/services/base-string.service.js +++ /dev/null @@ -1,184 +0,0 @@ -import defaults from '~assets/default.strings.json'; - -let i18n; -let $filter; - -function BaseStringService (namespace) { - const ERROR_NO_NAMESPACE = 'BaseString cannot be extended without providing a namespace'; - - if (!namespace) { - throw new Error(ERROR_NO_NAMESPACE); - } - - this[namespace] = {}; - this.t = {}; - - /** - * To translate a singular string by itself or a string with context data, use `translate`. - * For brevity, this is renamed as `t.s` (as in "translate singular"). `t.s` serves a dual - * purpose -- it's to mark strings for translation so they appear in the `.pot` file after - * the grunt-angular-gettext task is run AND it's used to fetch the translated string at - * runtime. - * - * NOTE: View ui/src/i18n.js for where these i18n methods are defined. i18n is a wrapper around - * the library angular-gettext. - * - * @arg {string} string - The string to be translated - * @arg {object=} context - A data object used to populate dynamic context data in a string. - * - * @returns {string} The translated string or the original string in the even the translation - * does not exist. - */ - this.t.s = i18n.translate; - - /** - * To translate a plural string use `t.p`. The `count` supplied will determine whether the - * singular or plural string is returned. - * - * @arg {number} count - The count of the plural object - * @arg {string} singular - The singular version of the string to be translated - * @arg {string} plural - The plural version of the string to be translated - * @arg {object=} context - A data object used to populate dynamic context data in a string. - * - * @returns {string} The translated string or the original string in the even the translation - * does not exist. - */ - this.t.p = i18n.translatePlural; - - const { t } = this; - - /* - * These strings are globally relevant and configured to give priority to values in - * default.strings.json and fall back to defaults defined inline. - */ - this.BRAND_NAME = defaults.BRAND_NAME || 'AWX'; - this.PENDO_API_KEY = defaults.PENDO_API_KEY || ''; - - /* - * Globally relevant strings should be defined here to avoid duplication of content across the - * the project. - */ - this.CANCEL = t.s('CANCEL'); - this.CLOSE = t.s('CLOSE'); - this.SAVE = t.s('SAVE'); - this.SELECT = t.s('SELECT'); - this.OK = t.s('OK'); - this.RUN = t.s('RUN'); - this.NEXT = t.s('NEXT'); - this.SHOW = t.s('SHOW'); - this.HIDE = t.s('HIDE'); - this.ON = t.s('ON'); - this.OFF = t.s('OFF'); - this.YAML = t.s('YAML'); - this.JSON = t.s('JSON'); - this.DELETE = t.s('DELETE'); - this.COPY = t.s('COPY'); - this.YES = t.s('YES'); - this.CLOSE = t.s('CLOSE'); - this.TEST = t.s('TEST'); - this.SUCCESSFUL_CREATION = resource => t.s('{{ resource }} successfully created', { resource: $filter('sanitize')(resource) }); - - this.deleteResource = { - HEADER: t.s('Delete'), - USED_BY: resourceType => t.s('The {{ resourceType }} is currently being used by other resources.', { resourceType }), - UNAVAILABLE: resourceType => t.s('Deleting this {{ resourceType }} will make the following resources unavailable.', { resourceType }), - CONFIRM: resourceType => t.s('Are you sure you want to delete this {{ resourceType }}?', { resourceType }) - }; - - this.cancelJob = { - HEADER: t.s('Cancel'), - SUBMIT_REQUEST: t.s('Are you sure you want to submit the request to cancel this job?'), - CANCEL_JOB: t.s('Cancel Job'), - RETURN: t.s('Return') - }; - - this.error = { - HEADER: t.s('Error!'), - CALL: ({ path, action, status }) => t.s('Call to {{ path }} failed. {{ action }} returned status: {{ status }}.', { path, action, status }), - }; - - this.listActions = { - COPY: resourceType => t.s('Copy {{resourceType}}', { resourceType }), - DELETE: resourceType => t.s('Delete the {{resourceType}}', { resourceType }), - CANCEL: resourceType => t.s('Cancel the {{resourceType}}', { resourceType }) - }; - - this.sort = { - NAME_ASCENDING: t.s('Name (Ascending)'), - NAME_DESCENDING: t.s('Name (Descending)'), - CREATED_ASCENDING: t.s('Created (Ascending)'), - CREATED_DESCENDING: t.s('Created (Descending)'), - MODIFIED_ASCENDING: t.s('Modified (Ascending)'), - MODIFIED_DESCENDING: t.s('Modified (Descending)'), - EXPIRES_ASCENDING: t.s('Expires (Ascending)'), - EXPIRES_DESCENDING: t.s('Expires (Descending)'), - LAST_JOB_RUN_ASCENDING: t.s('Last Run (Ascending)'), - LAST_JOB_RUN_DESCENDING: t.s('Last Run (Descending)'), - LAST_USED_ASCENDING: t.s('Last Used (Ascending)'), - LAST_USED_DESCENDING: t.s('Last Used (Descending)'), - USERNAME_ASCENDING: t.s('Username (Ascending)'), - USERNAME_DESCENDING: t.s('Username (Descending)'), - START_TIME_ASCENDING: t.s('Start Time (Ascending)'), - START_TIME_DESCENDING: t.s('Start Time (Descending)'), - FINISH_TIME_ASCENDING: t.s('Finish Time (Ascending)'), - FINISH_TIME_DESCENDING: t.s('Finish Time (Descending)'), - UUID_ASCENDING: t.s('UUID (Ascending)'), - UUID_DESCENDING: t.s('UUID (Descending)'), - LAUNCHED_BY_ASCENDING: t.s('Launched By (Ascending)'), - LAUNCHED_BY_DESCENDING: t.s('Launched By (Descending)'), - INVENTORY_ASCENDING: t.s('Inventory (Ascending)'), - INVENTORY_DESCENDING: t.s('Inventory (Descending)'), - PROJECT_ASCENDING: t.s('Project (Ascending)'), - PROJECT_DESCENDING: t.s('Project (Descending)'), - ORGANIZATION_ASCENDING: t.s('Organization (Ascending)'), - ORGANIZATION_DESCENDING: t.s('Organization (Descending)'), - CAPACITY_ASCENDING: t.s('Capacity (Ascending)'), - CAPACITY_DESCENDING: t.s('Capacity (Descending)') - }; - - this.ALERT = ({ header, body }) => t.s('{{ header }} {{ body }}', { header, body }); - - /** - * This getter searches the extending class' namespace first for a match then falls back to - * the more globally relevant strings defined here. Strings with with dots as delimeters are - * supported to give flexibility to extending classes to nest strings as necessary. - * - * - * The `t.s` and `t.p` calls should only be used where strings are defined in - * .strings.js` files. To use translated strings elsewhere, access them through this - * common interface. - * - * @arg {string} name - The property name of the string (e.g. 'CANCEL') - * @arg {number=} count - A count of objects referenced in your plural string - * @arg {object=} context - An object containing data to use in the interpolation of the string - */ - this.get = (name, ...args) => { - const keys = name.split('.'); - let value; - - keys.forEach(key => { - if (!value) { - value = this[namespace][key] || this[key]; - } else { - value = value[key]; - } - }); - - if (!value || typeof value === 'string') { - return value; - } - - return value(...args); - }; -} - -function BaseStringServiceLoader (_i18n_, _$filter_) { - i18n = _i18n_; - $filter = _$filter_; - - return BaseStringService; -} - -BaseStringServiceLoader.$inject = ['i18n', '$filter']; - -export default BaseStringServiceLoader; diff --git a/awx/ui/client/lib/services/cache.service.js b/awx/ui/client/lib/services/cache.service.js deleted file mode 100644 index 783b0e0d4cb3..000000000000 --- a/awx/ui/client/lib/services/cache.service.js +++ /dev/null @@ -1,32 +0,0 @@ -function CacheService ($cacheFactory, $q) { - const cache = $cacheFactory('api'); - - this.put = (key, data) => cache.put(key, data); - this.get = (key) => $q.resolve(cache.get(key)); - - this.remove = (key) => { - if (!key) { - return cache.removeAll(); - } - - return cache.remove(key); - }; - - this.createKey = (method, path, resource) => { - let key = `${method.toUpperCase()}.${path}`; - - if (resource) { - if (resource.id) { - key += `${resource.id}/`; - } else if (Number(resource)) { - key += `${resource}/`; - } - } - - return key; - }; -} - -CacheService.$inject = ['$cacheFactory', '$q']; - -export default CacheService; diff --git a/awx/ui/client/lib/services/event.service.js b/awx/ui/client/lib/services/event.service.js deleted file mode 100644 index d7b7a4053805..000000000000 --- a/awx/ui/client/lib/services/event.service.js +++ /dev/null @@ -1,37 +0,0 @@ -function EventService () { - this.addListeners = list => { - const listeners = []; - - list.forEach(args => listeners.push(this.addListener(...args))); - - return listeners; - }; - - this.addListener = (el, name, fn) => { - const listener = { - fn, - name, - el - }; - - if (Array.isArray(name)) { - name.forEach(e => listener.el.addEventListener(e, listener.fn)); - } else { - listener.el.addEventListener(listener.name, listener.fn); - } - - return listener; - }; - - this.remove = listeners => { - listeners.forEach(listener => { - if (Array.isArray(listener.name)) { - listener.name.forEach(name => listener.el.removeEventListener(name, listener.fn)); - } else { - listener.el.removeEventListener(listener.name, listener.fn); - } - }); - }; -} - -export default EventService; diff --git a/awx/ui/client/lib/services/index.js b/awx/ui/client/lib/services/index.js deleted file mode 100644 index 39ed1d82990c..000000000000 --- a/awx/ui/client/lib/services/index.js +++ /dev/null @@ -1,17 +0,0 @@ -import AppStrings from '~services/app.strings'; -import BaseStringService from '~services/base-string.service'; -import CacheService from '~services/cache.service'; -import EventService from '~services/event.service'; - -const MODULE_NAME = 'at.lib.services'; - -angular - .module(MODULE_NAME, [ - 'I18N' - ]) - .service('AppStrings', AppStrings) - .service('BaseStringService', BaseStringService) - .service('CacheService', CacheService) - .service('EventService', EventService); - -export default MODULE_NAME; diff --git a/awx/ui/client/lib/theme/_global.less b/awx/ui/client/lib/theme/_global.less deleted file mode 100644 index 01690ab2dd5f..000000000000 --- a/awx/ui/client/lib/theme/_global.less +++ /dev/null @@ -1,73 +0,0 @@ -/** - * For styles that are used in more than one place throughout the application. - * - * 1. Buttons - * - */ - -// 1. Buttons ------------------------------------------------------------------------------------- - -.at-Button--success { - .at-mixin-Button(); - .at-mixin-ButtonColor('at-color-success', 'at-color-default'); - - &[disabled] { - background: @at-color-disabled; - border-color: @at-color-disabled; - } -} - -.at-Button--add { - &:extend(.at-Button--success all); - &:before { - content: "+"; - font-size: 20px; - } - border: none; - display: inline-flex; - margin-left: @at-space-4x; - - &[aria-expanded="true"] { - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - } -} - -.at-Button--info { - .at-mixin-Button(); - .at-mixin-ButtonColor('at-color-info', 'at-color-default'); -} - -.at-Button--error { - .at-mixin-Button(); - .at-mixin-ButtonColor('at-color-error', 'at-color-default'); -} - -.at-ButtonHollow--default { - .at-mixin-Button(); - .at-mixin-ButtonHollow( - 'at-color-default', - 'at-color-button-border-default', - 'at-color-button-text-default' - ); -} - -.at-ButtonIcon { - padding: 4px @at-padding-button-horizontal; - font-size: @at-font-size-body; -} - -.at-ButtonIcon-noborder { - padding: 4px @at-padding-button-horizontal; - font-size: @at-font-size-body; - .at-mixin-Button(); - .at-mixin-ButtonHollow( - 'at-color-default', - 'at-color-default', - 'at-color-button-text-default' - ); -} - -.at-Button--expand { - width: 100%; -} diff --git a/awx/ui/client/lib/theme/_mixins.less b/awx/ui/client/lib/theme/_mixins.less deleted file mode 100644 index 3c3f0c66a5a5..000000000000 --- a/awx/ui/client/lib/theme/_mixins.less +++ /dev/null @@ -1,117 +0,0 @@ -.at-mixin-Placeholder (@color) { - &:-moz-placeholder { - color: @color; - } - &:-ms-input-placeholder { - color: @color; - } - &::-webkit-input-placeholder { - color: @color; - } -} - -.at-mixin-Heading (@size) { - color: @at-color-body-text; - font-size: @size; - font-weight: @at-font-weight-heading; - line-height: @at-line-height-short; - text-transform: uppercase; - margin: 0; - padding: 0; -} - -.at-mixin-Border (@color: @at-border-default-color) { - border-width: @at-border-default-width; - border-style: @at-border-default-style; - border-color: @color -} - -.at-mixin-Button () { - border-radius: @at-border-radius; - height: @at-height-input; - padding: @at-padding-button-vertical @at-padding-button-horizontal; - font-size: @at-font-size-body; - line-height: 1; -} - -.at-mixin-InputButton () { - height: @at-height-button; - padding: 0 @at-padding-button-horizontal; - - &, &:active, &:hover, &:focus { - color: @at-color-button-text-default; - border-color: @at-color-input-border; - background-color: @at-color-input-button; - cursor: pointer; - } - - &:hover { - background-color: @at-color-input-button-hover; - } -} - -.at-mixin-ButtonColor (@background, @color, @hover: '@{background}-hover') { - background-color: @@background; - border-color: @@background; - - &, &:hover, &:focus { - color: @@color; - } - - &:hover, &:focus { - background-color: @@hover; - border-color: @@hover; - } - - &[disabled] { - background-color: fade(@@background, 60%); - } -} - -.at-mixin-ButtonHollow (@bg, @border, @text) { - @hover: '@{bg}-hover'; - - background-color: @@bg; - color: @@text; - border-color: @@border; - - &:hover, &:active { - color: @@text; - background-color: @@hover; - box-shadow: none; - } - - &:focus { - color: @@text; - background-color: @@hover; - border-color: @@border; - cursor: default; - } - - &[disabled] { - opacity: 0.65; - } -} - -.at-mixin-ButtonIcon () { - line-height: @at-line-height-short; - - & > i { - cursor: pointer; - transition: color @at-transition-icon-button; - } - - & > i:hover { - color: @at-color-icon-hover - } -} - -.at-mixin-FontFixedWidth () { - font-family: Menlo, Monaco, Consolas, "Courier New", monospace; -} - -.at-mixin-VerticallyCenter () { - display: flex; - flex-direction: column; - justify-content: center; -} \ No newline at end of file diff --git a/awx/ui/client/lib/theme/_resets.less b/awx/ui/client/lib/theme/_resets.less deleted file mode 100644 index e7070fa42d83..000000000000 --- a/awx/ui/client/lib/theme/_resets.less +++ /dev/null @@ -1,5 +0,0 @@ -// TODO (remove override on cleanup): - -.at-Panel-heading:hover { - cursor: default; -} diff --git a/awx/ui/client/lib/theme/_utility.less b/awx/ui/client/lib/theme/_utility.less deleted file mode 100644 index b3663b74a336..000000000000 --- a/awx/ui/client/lib/theme/_utility.less +++ /dev/null @@ -1,30 +0,0 @@ -.at-u-noSpace { - margin: 0; - padding: 0; -} - -.at-u-flat { - padding-top: 0; - padding-bottom: 0; - margin-top: 0; - margin-bottom: 0; -} - -.at-u-thin { - padding-left: 0; - padding-right: 0; - margin-left: 0; - margin-right: 0; -} - -.at-u-clear { - clear: both; -} - -.at-u-noBorder { - border: none; -} - -.at-u-floatRight { - float: right -} diff --git a/awx/ui/client/lib/theme/_variables.less b/awx/ui/client/lib/theme/_variables.less deleted file mode 100644 index 826daf98305e..000000000000 --- a/awx/ui/client/lib/theme/_variables.less +++ /dev/null @@ -1,335 +0,0 @@ -/** - * All base variables used. These should only be referenced by the contextual variables defined - * below. In development, unless you are intentionally making a fundamental change, these variables - * should not be modified, removed, or added to. - * - * These variables should not be used directly in development of components or features. If an - * alias doesn't exist for the context you're working within, check with UX to create a new alias - * or to define a more applicable alias. - * - * The goal is for UX to define the contexts for the contextual variables, so it's easy to make - * modifications like, "Change heading text to be smaller" or "Make all warnings a lighter shade - * of orange" - * - * 1. Colors - * 2. Typography - * 3. Layout - * 4. Breakpoints - * - */ - -// 1. Colors -------------------------------------------------------------------------------------- - -@at-gray-fc: #fcfcfc; -@at-gray-fa: #fafafa; -@at-gray-f2: #f2f2f2; -@at-gray-f6: #f6f6f6; -@at-gray-eb: #ebebeb; -@at-gray-e1: #e1e1e1; -@at-gray-d7: #d7d7d7; -@at-gray-b7: #b7b7b7; -@at-gray-a9: #a9a9a9; -@at-gray-646972: #646972; -@at-gray-76: #767676; -@at-gray-60: #606060; -@at-gray-70: #707070; -@at-gray-48: #484848; -@at-gray-161b1f: #161b1f; - -@at-white: #ffffff; -@at-white-hover: #f2f2f2; - -@at-blue: #337ab7; -@at-blue-hover: #286090; - -@at-green: #5cb85c; -@at-green-hover: #449d44; - -@at-orange: #f0ad4e; -@at-orange-hover: #ec971f; - -@at-red: #d9534f; -@at-red-hover: #c9302c; - -@at-red-bright: #ff0000; -@at-red-bright-hover: #d81f1f; - -// 2. Typography ---------------------------------------------------------------------------------- - -@at-font-size: 12px; -@at-font-size-2x: 13px; -@at-font-size-3x: 14px; -@at-font-size-4x: 16px; -@at-font-size-5x: 20px; - -@at-font-weight: 400; -@at-font-weight-2x: 700; - -// 3. Layout -------------------------------------------------------------------------------------- - -@at-space: 5px; -@at-space-2x: 10px; -@at-space-3x: 15px; -@at-space-4x: 20px; -@at-space-5x: 25px; -@at-space-10x: 50px; - -// 4. Breakpoints --------------------------------------------------------------------------------- - -@at-breakpoint-sm: 700px; - -/** - * All variables used in the UI. Use these variables directly during the development of components - * and features. Be sure the context of the variable name applies to the work that's being done. - * For example, it wouldn't make sense to use `@at-input-height` to describe the height of a - * button. Either add an alias if it makes sense to use the same base variable, or add a new - * base variable to reference. - * - * Keep in mind the goal is to be able to modify an item by referencing its context instead of - * an arbitrary variable name. For example, tt should be a simple change when an ask comes in to - * "increase the height of inputs" - * - * 1. Colors - * 2. Typography - * 3. Layout - * 4. Buttons - * 5. Misc - * 6. Breakpoints - * - */ - -// 1. Colors -------------------------------------------------------------------------------------- - -@at-color-default: @at-white; -@at-color-default-hover: @at-white-hover; - -@at-color-unreachable: @at-red-bright; -@at-color-unreachable-hover: @at-red-bright-hover; - -@at-color-error: @at-red; -@at-color-error-hover: @at-red-hover; - -@at-color-warning: @at-orange; -@at-color-warning-hover: @at-orange-hover; - -@at-color-info: @at-blue; -@at-color-info-hover: @at-blue-hover; - -@at-color-success: @at-green; -@at-color-success-hover: @at-green-hover; - -@at-color-disabled: @at-gray-b7; - -@at-color-body-background-dark: @at-gray-70; -@at-color-body-background-light: @at-gray-eb; -@at-color-body-text-dark: @at-white; -@at-color-body-background: @at-gray-fc; -@at-color-body-text: @at-gray-70; - -@at-color-button-border-default: @at-gray-b7; -@at-color-button-text-default: @at-gray-70; - -@at-color-tab-default-active: @at-gray-646972; -@at-color-tab-border-default-active: @at-gray-646972; -@at-color-tab-text-default-active: @at-white; - -@at-color-tab-default-disabled: @at-white; -@at-color-tab-border-default-disabled: @at-gray-b7; -@at-color-tab-text-default-disabled: @at-gray-70; - -@at-color-form-label: @at-gray-70; - -@at-color-input-background: @at-gray-fc; -@at-color-input-border: @at-gray-b7; -@at-color-input-button: @at-white; -@at-color-input-button-hover: @at-gray-f2; -@at-color-input-disabled: @at-gray-eb; -@at-color-input-readonly: @at-color-input-background; - -@at-color-input-error: @at-color-error; -@at-color-input-focus: @at-color-info; -@at-color-input-hint: @at-gray-646972; -@at-color-input-icon: @at-gray-b7; -@at-color-input-placeholder: @at-gray-646972; -@at-color-input-text: @at-gray-161b1f; -@at-color-input-slider-thumb: @at-blue; -@at-color-input-slider-track: @at-gray-b7; - -@at-color-icon-dismiss: @at-gray-d7; -@at-color-icon-popover: @at-gray-646972; -@at-color-icon-hover: @at-gray-646972; - -@at-color-panel-heading: @at-gray-70; -@at-color-panel-border: @at-gray-b7; - -@at-color-search-key-active: @at-blue; - -@at-color-table-header-background: @at-gray-eb; -@at-color-line-separator: @at-gray-e1; - -@at-color-top-nav-background: @at-white; -@at-color-top-nav-border-bottom: @at-gray-b7; -@at-color-top-nav-item-text: @at-gray-70; -@at-color-top-nav-item-icon: @at-gray-646972; -@at-color-top-nav-item-icon-socket-outline: @at-white; -@at-color-top-nav-item-background-hover: @at-gray-fa; -@at-color-side-nav-background: @at-gray-48; -@at-color-side-nav-content: @at-white; -@at-color-side-nav-item-background-hover: #aaa; -@at-color-side-nav-item-border-hover: @at-white; -@at-color-side-nav-item-icon: @at-white; -@at-color-side-nav-item-spacer: @at-gray-d7; -@at-color-side-nav-space-collapsed-border: @at-gray-b7; - -@at-color-list-empty-border: @at-gray-d7; -@at-color-list-empty-background: @at-gray-f6; -@at-color-list-empty: @at-gray-646972; -@at-color-list-border: @at-gray-b7; -@at-color-list-row-item-tag-background: @at-gray-eb; -@at-color-list-row-item-tag: @at-gray-60; -@at-color-list-row-item-label: @at-gray-646972; -@at-color-list-row-action-background: @at-white; -@at-color-list-row-action-icon: @at-gray-646972; -@at-color-list-row-action-hover: @at-blue; -@at-color-list-row-action-hover-danger: @at-red; -@at-color-list-row-action-icon-hover: @at-white; -@at-color-list-row-item-tag-primary-background: @at-blue; -@at-color-list-row-item-tag-primary: @at-white; - -// 2. Typography ---------------------------------------------------------------------------------- - -@at-font-size-body: @at-font-size-3x; -@at-font-size-button: @at-font-size; -@at-font-size-breadcrumb: @at-font-size-3x; -@at-font-size-form-label: @at-font-size-2x; -@at-font-size-help-text: @at-font-size; -@at-font-size-icon: @at-font-size-4x; -@at-font-size-input: @at-font-size-3x; -@at-font-size-panel-heading: @at-font-size-3x; -@at-font-size-panel-inset-heading: @at-font-size-2x; -@at-font-size-modal-heading: @at-font-size-3x; -@at-font-size-modal-dismiss: @at-font-size-3x; -@at-font-size-navigation: @at-font-size-3x; -@at-font-size-table-heading: @at-font-size-3x; -@at-font-size-menu-icon: @at-font-size-5x; -@at-font-size-side-nav-icon: 19px; -@at-font-size-side-nav-space: 11px; -@at-font-size-list-row-item-tag: 10px; -@at-font-size-list-row-action: 19px; -@at-font-size-list-row-action-icon: 19px; -@at-font-size-jumbotron-heading: 24px; -@at-font-size-jumbotron-text: @at-font-size-4x; - -@at-font-weight-body: @at-font-weight; -@at-font-weight-heading: @at-font-weight-2x; - -// 3. Layout -------------------------------------------------------------------------------------- - -@at-padding-button-horizontal: @at-space-2x; -@at-padding-button-vertical: @at-space; -@at-padding-inset: @at-space-3x; -@at-padding-panel: @at-space-4x; -@at-padding-popover: @at-space-2x; -@at-padding-well: @at-space-2x; -@at-padding-input: @at-space-2x; -@at-padding-top-nav-item-sides: @at-space-4x; -@at-padding-side-nav-item-icon: @at-space-3x; -@at-padding-side-nav-item-icon: 10px 15px; -@at-padding-side-nav-item-spacer: 10px 10px 25px 15px; -@at-padding-bottom-side-nav-toggle-mobile: 15px; -@at-padding-top-side-nav-toggle: 5px; -@at-padding-left-side-nav-toggle-icon: 15px; -@at-padding-left-side-nav-item-icon: 10px; -@at-padding-left-side-nav-item-icon-expanded: 15px; -@at-padding-left-side-nav-item-icon-no-tooltip: 18px; -@at-padding-between-side-nav-icon-text: @at-space-3x; -@at-padding-list-empty: @at-space-2x; -@at-padding-list-row-item-tag: 3px 9px; -@at-padding-list-row-action: 7px; -@at-padding-list-row: 10px 20px 10px 10px; -@at-padding-bottom-instances-wrap: 30px; - -@at-margin-input-message: @at-space; -@at-margin-item-column: @at-space-3x; -@at-margin-panel: @at-space-4x; -@at-margin-panel-inset: @at-space-3x; -@at-margin-popover: @at-space-2x; -@at-margin-tag: @at-space-2x; -@at-margin-form-label: @at-space; -@at-margin-form-label-hint: @at-space-2x; -@at-margin-top-nav-item-between-icon-and-name: @at-space-2x; -@at-margin-top-nav-item-icon-socket-top-makeup: -3px; -@at-margin-side-nav-space-collapsed: 5px 0; - -@at-margin-top-search-key: @at-space-2x; - -@at-margin-top-list: @at-space-4x; -@at-margin-bottom-list-toolbar: @at-space-4x; -@at-margin-above-list-toolbar: @at-space-4x; -@at-margin-left-toolbar-action: @at-space-4x; -@at-margin-left-toolbar-carat: @at-space; -@at-margin-bottom-list-header: @at-space; -@at-margin-left-list-row-item-tag: @at-space-2x; -@at-margin-top-list-row-item-tag: 2.25px; -@at-margin-left-list-row-action: @at-space-4x; -@at-margin-right-list-row-item-tag-icon: 8px; -@at-margin-left-list-row-item-tag-container: -10px; -@at-margin-list-row-action-mobile: 10px; -@at-margin-right-list-row-item-status: @at-space-2x; -@at-margin-right-list-row-item-inline: @at-space-4x; -@at-margin-right-list-row-item-inline-label: @at-space-2x; - -@at-height-divider: @at-margin-panel; -@at-height-input: 30px; -@at-height-textarea: 144px; -@at-height-button: 30px; -@at-height-tab: 30px; -@at-height-top-nav: 60px; -@at-height-top-nav-item-icon: 21px; -@at-height-top-nav-item-icon-socket: 18px; -@at-height-side-nav-item-icon: 19px; -@at-height-side-nav-spacer: 20px; -@at-height-top-side-nav-makeup: 55px; -@at-height-list-empty: 200px; -@at-height-toolbar-action: 30px; -@at-height-list-row-item: 27px; -@at-height-list-row-action: 30px; -@at-height-side-nav-toggle-mobile: 40px; - -@at-width-input-button-sm: 72px; -@at-width-input-button-md: 84px; -@at-width-collapsed-side-nav: 55px; -@at-width-expanded-side-nav: 190px; -@at-width-list-row-item-label: 120px; -@at-width-list-row-action: 30px; -@at-width-side-nav-toggle-mobile: 50px; - -@at-line-height-list-row-item-header: @at-space-3x; -@at-line-height-list-row-item-labels: 17px; - -// 4. Transitions --------------------------------------------------------------------------------- - -@at-transition-icon-button: 0.2s; - -// 5. Misc ---------------------------------------------------------------------------------------- - -@at-border-radius: 5px; -@at-border-default-style: solid; -@at-border-default-width: 1px; -@at-border-default-color: @at-gray-b7; -@at-border-style-list-active-indicator: 5px solid @at-color-info; -@at-popover-maxwidth: 320px; -@at-line-height-short: 0.9; -@at-line-height-tall: 2; -@at-line-height: 24px; -@at-highlight-left-border-size: 5px; -@at-highlight-left-border-margin-makeup: -5px; -@at-z-index-nav: 1040; -@at-z-index-side-nav: 1030; -@at-line-height-list-row-item-tag: 14px; - -// 6. Breakpoints --------------------------------------------------------------------------------- - -@at-breakpoint-mobile-layout: @at-breakpoint-sm; -@at-breakpoint-compact-list: @at-breakpoint-sm; -@at-breakpoint-instances-wrap: 1036px; diff --git a/awx/ui/client/lib/theme/index.less b/awx/ui/client/lib/theme/index.less deleted file mode 100644 index c7baad45e421..000000000000 --- a/awx/ui/client/lib/theme/index.less +++ /dev/null @@ -1,160 +0,0 @@ -// Dependency Variables -@import '../../../node_modules/components-font-awesome/less/variables'; - -// App-specific Legacy Variables -@import '../../src/shared/branding/colors.default.less'; -@import '../../src/shared/branding/colors'; - -/** - * Override Variables - * - * NOTE: Used in conditional build scenarios and will need to persist after any refactoring effort. - */ -@import '../../assets/variables'; - -/** - * Legacy Styles - * - * NOTE: Styles below are a mix of 3rd-party dependencies and in-house code. For the 3rd-party - * stuff, we'd be better off managing them via npm where possible. - */ -@import '../../legacy/styles/fonts'; -@import '../../legacy/styles/animations'; -@import '../../legacy/styles/jquery-ui-overrides'; -@import '../../legacy/styles/codemirror'; -@import '../../legacy/styles/angular-scheduler'; -@import '../../legacy/styles/log-viewer'; -@import '../../legacy/styles/event-viewer'; -@import '../../legacy/styles/job-details'; -@import '../../legacy/styles/jobs'; -@import '../../legacy/styles/inventory-edit'; -@import '../../legacy/styles/stdout'; -@import '../../legacy/styles/lists'; -@import '../../legacy/styles/forms'; -@import '../../legacy/styles/dashboard'; -@import '../../legacy/styles/survey-maker'; -@import '../../legacy/styles/text-label'; -@import '../../legacy/styles/bootstrap-datepicker'; -@import '../../legacy/styles/ansible-ui'; -@import '../../legacy/styles/bootstrap-custom-overrides'; - -// Legacy Utilities -@import '../../src/shared/utilities/alerts'; -@import '../../src/shared/utilities/hidden'; -@import '../../src/shared/utilities/icons'; -@import '../../src/shared/utilities/layer'; -@import '../../src/shared/utilities/truncated-text'; -@import '../../src/shared/utilities/unbold'; -@import '../../src/shared/utilities/wordwrap'; - -// Legacy Layout -@import '../../src/shared/layouts/one-plus-one'; -@import '../../src/shared/layouts/one-plus-two'; - -/** - * Legacy Features - * - * NOTE: "dot" namespacing interferes with Less' ability to infer the .less suffix, so it's - * explicitly added to the import statements below. - */ -@import '../../src/about/about.block.less'; -@import '../../src/access/rbac-role-column/roleList.block.less'; -@import '../../src/access/add-rbac.block.less'; -@import '../../src/activity-stream/streamDetailModal/streamDetailModal.block.less'; -@import '../../src/activity-stream/activitystream.block.less'; -@import '../../src/bread-crumb/bread-crumb.block.less'; -@import '../../src/configuration/settings.block.less'; -@import '../../src/credentials/ownerList.block.less'; -@import '../../src/home/dashboard/counts/dashboard-counts.block.less'; -@import '../../src/home/dashboard/graphs/dashboard-graphs.block.less'; -@import '../../src/home/dashboard/lists/dashboard-list.block.less'; -@import '../../src/home/dashboard/dashboard.block.less'; -@import '../../src/instance-groups/capacity-bar/capacity-bar.block.less'; -@import '../../src/instance-groups/capacity-adjuster/capacity-adjuster.block.less'; -@import '../../src/instance-groups/instance-group.block.less'; -@import '../../src/instance-groups/instances/instance-modal.block.less'; -@import '../../src/inventories-hosts/inventories/insights/insights.block.less'; -@import '../../src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.block.less'; -@import '../../src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.block.less'; -@import '../../src/inventories-hosts/inventories/inventories.block.less'; -@import '../../src/inventories-hosts/shared/associate-groups/associate-groups.block.less'; -@import '../../src/inventories-hosts/shared/associate-hosts/associate-hosts.block.less'; -@import '../../src/license/license.block.less'; -@import '../../src/login/loginModal/thirdPartySignOn/thirdPartySignOn.block.less'; -@import '../../src/login/loginModal/loginModal.block.less'; -@import '../../src/login/loginModal/loginModalNotice.block.less'; -@import '../../src/management-jobs/card/mgmtcards.block.less'; -@import '../../src/notifications/notifications.block.less'; -@import '../../src/organizations/linkout/addUsers/addUsers.block.less'; -@import '../../src/organizations/orgcards.block.less'; -@import '../../src/scheduler/repeatFrequencyOptions.block.less'; -@import '../../src/scheduler/schedulerForm.block.less'; -@import '../../src/scheduler/schedulerDatePicker.block.less'; -@import '../../src/scheduler/schedulerFormDetail.block.less'; -@import '../../src/scheduler/schedulertime.block.less'; -@import '../../src/scheduler/spinnerInput.block.less'; -@import '../../src/shared/container/container.block.less'; -@import '../../src/shared/detail-nav/detail-nav.block.less'; -@import '../../src/shared/icon/icon.block.less'; -@import '../../src/shared/instance-groups-multiselect/instance-groups.block.less'; -@import '../../src/shared/lookup/lookup-modal.block.less'; -@import '../../src/shared/modal/modal'; -@import '../../src/shared/multi-select-preview/multi-select-preview.block.less'; -@import '../../src/shared/paginate/paginate.block.less'; -@import '../../src/shared/prompt/prompt'; -@import '../../src/shared/smart-search/smart-search.block.less'; -@import '../../src/shared/button.block.less'; -@import '../../src/shared/download-standard-out.block.less'; -@import '../../src/shared/media-object.block.less'; -@import '../../src/shared/text-label'; -@import '../../src/shared/upgrade/upgrade.block.less'; -@import '../../src/smart-status/smart-status.block.less'; -@import '../../src/workflow-results/standard-out.block.less'; -@import '../../src/templates/prompt/prompt.block.less'; -@import '../../src/templates/job_templates/multi-credential/multi-credential.block.less'; -@import '../../src/templates/job_templates/webhook-credential/webhook-credential.block.less'; -@import '../../src/templates/labels/labelsList.block.less'; -@import '../../src/templates/survey-maker/survey-maker.block.less'; -@import '../../src/templates/survey-maker/survey-maker.block.less'; -@import '../../src/templates/survey-maker/shared/survey-controls.block.less'; -@import '../../src/templates/survey-maker/survey-maker.block.less'; -@import '../../src/templates/workflows/workflow.block.less'; -@import '../../src/templates/workflows/workflow-chart/workflow-chart.block.less'; -@import '../../src/templates/workflows/workflow-controls/workflow-controls.block.less'; -@import '../../src/templates/workflows/workflow-maker/workflow-maker.block.less'; -@import '../../src/tooltip/tooltip.block.less'; -@import '../../src/workflow-results/workflow-status-bar/workflow-status-bar.block.less'; -@import '../../src/workflow-results/workflow-results.block.less'; - -/** - * App-wide style - * - * NOTE: Variables, mixins, and classes below are useful in more than one place across the - * application. When working with Less, if the need for a variable, mixin, class, etc exists in - * more than one location, take a moment to move it to this more general location for easy reuse - * and to avoid duplication. - */ -@import '_variables'; -@import '_mixins'; -@import '_utility'; -@import '_global'; - -/** - * Component and Feature style - * - * NOTE: These index files are aggregation points for components and features. To view the more - * granular imports, view the contents of these files. Variables, classes, etc defined within - * these specific files ought to have no use elsewhere. As we shift to leverage components, very - * few feature-specific styles will exist. - */ -@import '../components/_index'; -@import '../../features/_index'; - -/* - * Resets - * - * NOTE: In some cases, the legacy classes override dependency styles explicitly. In those cases, - * it's necessary to override the overrides. This particular file will only be relevant during - * the transition. - */ -@import '_resets'; diff --git a/awx/ui/client/src/about/about.block.less b/awx/ui/client/src/about/about.block.less deleted file mode 100644 index 3b9a0ad00ae4..000000000000 --- a/awx/ui/client/src/about/about.block.less +++ /dev/null @@ -1,60 +0,0 @@ -/** @define About */ -.About-ansibleVersion, -.About-cowsayCode { - font-family: Monaco, Menlo, Consolas, "Courier New", monospace; -} - -.About-cowsayContainer { - width: 340px; - margin: 0 auto; -} -.About-cowsayCode { - background-color: @default-bg; - padding-left: 30px; - border-style: none; - max-width: 340px; - padding-left: 30px; -} -.About-modalHeader { - border: none; - padding-bottom: 0px; -} -.About-modalDialog { - max-width: 500px; -} - -.About-modalBody { - padding-top: 0px; - padding-bottom: 0px; -} - -.About-brandImg { - float: @about-modal-float; - width: @about-modal-width; - padding-top: @about-modal-padding-top; - margin-top: @about-modal-margin-top; - margin-left: @about-modal-margin-left; -} - -.About-close { - position: absolute; - top: 15px; - right: 15px; - z-index: 10; -} - -.About-modalFooter { - clear: both; -} - -.About-footerText { - text-align: right; - color: @default-interface-txt; - margin: 0; - font-size: 12px; - padding-top: 10px; -} - -.About-ansibleVersion { - color: @default-data-txt; -} diff --git a/awx/ui/client/src/about/about.controller.js b/awx/ui/client/src/about/about.controller.js deleted file mode 100644 index 868adf01dc94..000000000000 --- a/awx/ui/client/src/about/about.controller.js +++ /dev/null @@ -1,31 +0,0 @@ -export default ['$rootScope', '$scope', '$location', 'ConfigService', 'lastPath', - ($rootScope, $scope, $location, ConfigService, lastPath) => { - - ConfigService.getConfig() - .then(function(config){ - $scope.version = config.version.split('-')[0]; - $scope.ansible_version = config.ansible_version; - $scope.subscription = config.license_info.subscription_name; - $scope.speechBubble = createSpeechBubble($rootScope.BRAND_NAME, config.version); - $('#about-modal').modal('show'); - }); - - $('#about-modal').on('hidden.bs.modal', () => $location.url(lastPath)); - - function createSpeechBubble (brand, version) { - let text = `${brand} ${version}`; - let top = ''; - let bottom = ''; - - for (let i = 0; i < text.length; i++) { - top += '_'; - bottom += '-'; - } - - top = ` __${top}__ \n`; - text = `< ${text} >\n`; - bottom = ` --${bottom}-- `; - - return top + text + bottom; - } -}]; diff --git a/awx/ui/client/src/about/about.partial.html b/awx/ui/client/src/about/about.partial.html deleted file mode 100644 index 8f11c9eb972d..000000000000 --- a/awx/ui/client/src/about/about.partial.html +++ /dev/null @@ -1,35 +0,0 @@ - diff --git a/awx/ui/client/src/configuration/forms/settings-form.controller.js b/awx/ui/client/src/configuration/forms/settings-form.controller.js deleted file mode 100644 index 7fe61999ba2c..000000000000 --- a/awx/ui/client/src/configuration/forms/settings-form.controller.js +++ /dev/null @@ -1,613 +0,0 @@ -import defaultStrings from '~assets/default.strings.json'; - -export default [ - '$scope', '$rootScope', '$state', '$stateParams', '$q', - 'SettingsService', 'SettingsUtils', 'CreateDialog', 'i18n', 'ProcessErrors', 'Store', - 'Wait', 'configDataResolve', 'ToJSON', 'ConfigService', - //Form definitions - 'configurationAzureForm', - 'configurationGithubForm', - 'configurationGithubOrgForm', - 'configurationGithubTeamForm', - 'configurationGoogleForm', - 'configurationLdapForm', - 'configurationLdap1Form', - 'configurationLdap2Form', - 'configurationLdap3Form', - 'configurationLdap4Form', - 'configurationLdap5Form', - 'configurationRadiusForm', - 'configurationTacacsForm', - 'configurationSamlForm', - 'systemActivityStreamForm', - 'systemLoggingForm', - 'systemMiscForm', - 'ConfigurationJobsForm', - 'ConfigurationUiForm', - 'ngToast', - function( - $scope, $rootScope, $state, $stateParams, $q, - SettingsService, SettingsUtils, CreateDialog, i18n, ProcessErrors, Store, - Wait, configDataResolve, ToJSON, ConfigService, - //Form definitions - configurationAzureForm, - configurationGithubForm, - configurationGithubOrgForm, - configurationGithubTeamForm, - configurationGoogleForm, - configurationLdapForm, - configurationLdap1Form, - configurationLdap2Form, - configurationLdap3Form, - configurationLdap4Form, - configurationLdap5Form, - configurationRadiusForm, - configurationTacacsForm, - configurationSamlForm, - systemActivityStreamForm, - systemLoggingForm, - systemMiscForm, - ConfigurationJobsForm, - ConfigurationUiForm, - ngToast - ) { - const vm = this; - - vm.product = defaultStrings.BRAND_NAME; - vm.activeTab = $stateParams.form; - - const formDefs = { - 'azure': configurationAzureForm, - 'github': configurationGithubForm, - 'github_org': configurationGithubOrgForm, - 'github_team': configurationGithubTeamForm, - 'google_oauth': configurationGoogleForm, - 'ldap': configurationLdapForm, - 'ldap1': configurationLdap1Form, - 'ldap2': configurationLdap2Form, - 'ldap3': configurationLdap3Form, - 'ldap4': configurationLdap4Form, - 'ldap5': configurationLdap5Form, - 'radius': configurationRadiusForm, - 'tacacs': configurationTacacsForm, - 'saml': configurationSamlForm, - 'activity_stream': systemActivityStreamForm, - 'logging': systemLoggingForm, - 'misc': systemMiscForm, - 'jobs': ConfigurationJobsForm, - 'ui': ConfigurationUiForm - }; - - $scope.configDataResolve = configDataResolve; - $scope.formDefs = formDefs; - - // check if it's auditor, show messageBar - $scope.show_auditor_bar = false; - if($rootScope.user_is_system_auditor && Store('show_auditor_bar') !== false) { - $scope.show_auditor_bar = true; - } else { - $scope.show_auditor_bar = false; - } - - var populateFromApi = function() { - SettingsService.getCurrentValues() - .then(function(data) { - // these two values need to be unnested from the - // OAUTH2_PROVIDER key - data.ACCESS_TOKEN_EXPIRE_SECONDS = data - .OAUTH2_PROVIDER.ACCESS_TOKEN_EXPIRE_SECONDS; - data.REFRESH_TOKEN_EXPIRE_SECONDS = data - .OAUTH2_PROVIDER.REFRESH_TOKEN_EXPIRE_SECONDS; - data.AUTHORIZATION_CODE_EXPIRE_SECONDS = data - .OAUTH2_PROVIDER.AUTHORIZATION_CODE_EXPIRE_SECONDS; - var currentKeys = _.keys(data); - $scope.requiredLogValues = {}; - $scope.originalSettings = data; - _.each(currentKeys, function(key) { - if(key === "LOG_AGGREGATOR_HOST") { - $scope.requiredLogValues.LOG_AGGREGATOR_HOST = data[key]; - } - - if(key === "LOG_AGGREGATOR_TYPE") { - $scope.requiredLogValues.LOG_AGGREGATOR_TYPE = data[key]; - } - - if (data[key] !== null && typeof data[key] === 'object') { - if (Array.isArray(data[key])) { - //handle arrays - //having to do this particular check b/c - // we want the options w/o a space, and - // the ConfigurationUtils.arrayToList() - // does a string.split(', ') w/ an extra space - // behind the comma. - - const isLdap = (key.indexOf("AUTH_LDAP") !== -1); - const isLdapUserSearch = isLdap && (key.indexOf("USER_SEARCH") !== -1); - const isLdapGroupSearch = isLdap && (key.indexOf("GROUP_SEARCH") !== -1); - - if(key === "AD_HOC_COMMANDS"){ - $scope[key] = data[key]; - } else if (isLdapUserSearch || isLdapGroupSearch) { - $scope[key] = JSON.stringify(data[key]); - } else { - $scope[key] = SettingsUtils.arrayToList(data[key], key); - } - - } else { - //handle nested objects - if(SettingsUtils.isEmpty(data[key])) { - $scope[key] = '{}'; - } else { - $scope[key] = JSON.stringify(data[key]); - } - } - } else { - $scope[key] = data[key]; - } - }); - $scope.$broadcast('populated', data); - }); - }; - - populateFromApi(); - - var formTracker = { - lastForm: '', - currentForm: '', - currentAuth: '', - currentSystem: '', - setCurrent: function(form) { - this.lastForm = this.currentForm; - this.currentForm = form; - }, - getCurrent: function() { - return this.currentForm; - }, - currentFormName: function() { - return 'configuration_' + this.currentForm + '_template_form'; - }, - setCurrentAuth: function(form) { - this.currentAuth = form; - this.setCurrent(this.currentAuth); - }, - setCurrentSystem: function(form) { - this.currentSystem = form; - this.setCurrent(this.currentSystem); - }, - }; - - var triggerModal = function(msg, title, buttons) { - if ($scope.removeModalReady) { - $scope.removeModalReady(); - } - $scope.removeModalReady = $scope.$on('ModalReady', function() { - // $('#lookup-save-button').attr('disabled', 'disabled'); - $('#FormModal-dialog').dialog('open'); - }); - - $('#FormModal-dialog').html(msg); - - CreateDialog({ - scope: $scope, - buttons: buttons, - width: 600, - height: 200, - minWidth: 500, - title: title, - id: 'FormModal-dialog', - resizable: false, - callback: 'ModalReady' - }); - }; - - function loginUpdate() { - // Updates the logo and app config so that logos and info are properly shown - // on logout after modifying. - if($scope.CUSTOM_LOGO) { - $rootScope.custom_logo = $scope.CUSTOM_LOGO; - global.$AnsibleConfig.custom_logo = true; - } else { - $rootScope.custom_logo = ''; - global.$AnsibleConfig.custom_logo = false; - } - - if($scope.CUSTOM_LOGIN_INFO) { - $rootScope.custom_login_info = $scope.CUSTOM_LOGIN_INFO; - global.$AnsibleConfig.custom_login_info = $scope.CUSTOM_LOGIN_INFO; - } else { - $rootScope.custom_login_info = ''; - global.$AnsibleConfig.custom_login_info = false; - } - - Store('AnsibleConfig', global.$AnsibleConfig); - - $scope.$broadcast('loginUpdated'); - } - - $scope.resetValue = function(key) { - Wait('start'); - var payload = {}; - if (key === 'ACCESS_TOKEN_EXPIRE_SECONDS' || key === 'REFRESH_TOKEN_EXPIRE_SECONDS' || key === 'AUTHORIZATION_CODE_EXPIRE_SECONDS') { - // the reset for these two keys needs to be nested under OAUTH2_PROVIDER - if (payload.OAUTH2_PROVIDER === undefined) { - payload.OAUTH2_PROVIDER = { - ACCESS_TOKEN_EXPIRE_SECONDS: $scope.ACCESS_TOKEN_EXPIRE_SECONDS, - REFRESH_TOKEN_EXPIRE_SECONDS: $scope.REFRESH_TOKEN_EXPIRE_SECONDS, - AUTHORIZATION_CODE_EXPIRE_SECONDS: $scope.AUTHORIZATION_CODE_EXPIRE_SECONDS - }; - } - payload.OAUTH2_PROVIDER[key] = $scope.configDataResolve[key].default; - } else { - payload[key] = $scope.configDataResolve[key].default; - } - SettingsService.patchConfiguration(payload) - .then(function() { - $scope[key] = $scope.configDataResolve[key].default; - - if(key === "LOG_AGGREGATOR_HOST" || key === "LOG_AGGREGATOR_TYPE") { - $scope.requiredLogValues[key] = $scope.configDataResolve[key].default; - } - - if($scope[key + '_field'].type === "select"){ - // We need to re-instantiate the Select2 element - // after resetting the value. Example: - $scope.$broadcast(key+'_populated', null, false); - if(key === "AD_HOC_COMMANDS"){ - $scope.$broadcast(key+'_reverted', null, false); - } - } - else if($scope[key + '_field'].reset === "CUSTOM_LOGO"){ - $scope.$broadcast(key+'_reverted'); - } - else if($scope[key + '_field'].hasOwnProperty('codeMirror')){ - const isLdap = (key.indexOf("AUTH_LDAP") !== -1); - - const isLdapGroupTypeParams = isLdap && (key.indexOf("GROUP_TYPE_PARAMS") !== -1); - const isLdapUserSearch = isLdap && (key.indexOf("USER_SEARCH") !== -1); - const isLdapGroupSearch = isLdap && (key.indexOf("GROUP_SEARCH") !== -1); - - if (isLdapGroupTypeParams) { - $scope[key] = JSON.stringify($scope.configDataResolve[key].default); - } else if (isLdapUserSearch || isLdapGroupSearch) { - $scope[key] = '[]'; - } else { - $scope[key] = '{}'; - } - $scope.$broadcast('codeMirror_populated', key); - } - loginUpdate(); - }) - .catch(function(data) { - ProcessErrors($scope, data.error, data.status, formDefs[formTracker.getCurrent()], - { - hdr: ` - ${ i18n._('Error!')} `, - msg: i18n._('There was an error resetting value. Returned status: ') + data.error.detail - }); - - }) - .finally(function() { - Wait('stop'); - }); - }; - - function clearApiErrors() { - var currentForm = formDefs[formTracker.getCurrent()]; - for (var fld in currentForm.fields) { - if (currentForm.fields[fld].sourceModel) { - $scope[currentForm.fields[fld].sourceModel + '_' + currentForm.fields[fld].sourceField + '_api_error'] = ''; - $('[name="' + currentForm.fields[fld].sourceModel + '_' + currentForm.fields[fld].sourceField + '"]').removeClass('ng-invalid'); - } else if (currentForm.fields[fld].realName) { - $scope[currentForm.fields[fld].realName + '_api_error'] = ''; - $('[name="' + currentForm.fields[fld].realName + '"]').removeClass('ng-invalid'); - } else { - $scope[fld + '_api_error'] = ''; - $('[name="' + fld + '"]').removeClass('ng-invalid'); - } - } - if (!$scope.$$phase) { - $scope.$digest(); - } - } - - // Some dropdowns are listed as "list" type in the API even though they're a dropdown: - var multiselectDropdowns = ['AD_HOC_COMMANDS']; - - var getFormPayload = function() { - var keys = _.keys(formDefs[formTracker.getCurrent()].fields); - var payload = {}; - _.each(keys, function(key) { - if (key === 'ACCESS_TOKEN_EXPIRE_SECONDS' || key === 'REFRESH_TOKEN_EXPIRE_SECONDS' || key === 'AUTHORIZATION_CODE_EXPIRE_SECONDS') { - // These two values need to be POSTed nested under the OAUTH2_PROVIDER key - if (payload.OAUTH2_PROVIDER === undefined) { - payload.OAUTH2_PROVIDER = { - ACCESS_TOKEN_EXPIRE_SECONDS: $scope.ACCESS_TOKEN_EXPIRE_SECONDS, - REFRESH_TOKEN_EXPIRE_SECONDS: $scope.REFRESH_TOKEN_EXPIRE_SECONDS, - AUTHORIZATION_CODE_EXPIRE_SECONDS: $scope.AUTHORIZATION_CODE_EXPIRE_SECONDS - }; - } - payload.OAUTH2_PROVIDER[key] = $scope[key]; - } else if($scope.configDataResolve[key].type === 'choice' || multiselectDropdowns.indexOf(key) !== -1) { - //Parse dropdowns and dropdowns labeled as lists - if($scope[key] === null) { - payload[key] = null; - } else if(!_.isEmpty($scope[`${key}_values`])) { - if(multiselectDropdowns.indexOf(key) !== -1) { - // Handle AD_HOC_COMMANDS - payload[key] = $scope[`${key}_values`]; - } else { - payload[key] = _.map($scope[key], 'value').join(','); - } - } else { - if(multiselectDropdowns.indexOf(key) !== -1) { - // Default AD_HOC_COMMANDS to an empty list - payload[key] = $scope[key].value || []; - } else { - if ($scope[key]) { - payload[key] = $scope[key].value; - } - } - } - } else if($scope.configDataResolve[key].type === 'list' && $scope[key] !== null) { - // Parse lists - payload[key] = SettingsUtils.listToArray($scope[key], key); - } - else if($scope.configDataResolve[key].type === 'nested object') { - if(!$scope[key]) { - payload[key] = {}; - } else { - // payload[key] = JSON.parse($scope[key]); - payload[key] = ToJSON($scope.parseType, - $scope[key]); - } - } - else { - // Everything else - if (key !== 'LOG_AGGREGATOR_TCP_TIMEOUT' || - ($scope.LOG_AGGREGATOR_PROTOCOL && - ($scope.LOG_AGGREGATOR_PROTOCOL.value === 'https' || - $scope.LOG_AGGREGATOR_PROTOCOL.value === 'tcp'))) { - payload[key] = $scope[key]; - } - } - }); - return payload; - }; - - vm.formSave = function() { - var saveDeferred = $q.defer(); - clearApiErrors(); - Wait('start'); - const payload = getFormPayload(); - SettingsService.patchConfiguration(getFormPayload()) - .then(function(data) { - loginUpdate(); - - $scope.requiredLogValues.LOG_AGGREGATOR_HOST = $scope.LOG_AGGREGATOR_HOST; - $scope.requiredLogValues.LOG_AGGREGATOR_TYPE = $scope.LOG_AGGREGATOR_TYPE; - - saveDeferred.resolve(data); - $scope[formTracker.currentFormName()].$setPristine(); - ngToast.success({ - timeout: 2000, - dismissButton: false, - dismissOnTimeout: true, - content: `` + - i18n._('Save Complete') - }); - if(payload.PENDO_TRACKING_STATE && payload.PENDO_TRACKING_STATE !== $scope.originalSettings.PENDO_TRACKING_STATE) { - // Refreshing the page will pull in the new config and - // properly set pendo up/shut it off depending on the - // action - location.reload(); - } - }) - .catch(function(data) { - ProcessErrors($scope, data.data, data.status, formDefs[formTracker.getCurrent()], - { - hdr: ` - ${ i18n._('Error!')} `, - msg: i18n._('Failed to save settings. Returned status: ') + data.status - }); - saveDeferred.reject(data); - }) - .finally(function() { - Wait('stop'); - }); - - return saveDeferred.promise; - }; - - vm.formCancel = function() { - if ($scope[formTracker.currentFormName()].$dirty === true) { - var msg = i18n._('You have unsaved changes. Would you like to proceed without saving?'); - var title = i18n._('Warning: Unsaved Changes'); - var buttons = [{ - label: i18n._("Discard changes"), - "class": "btn Form-cancelButton", - "id": "formmodal-cancel-button", - onClick: function() { - $('#FormModal-dialog').dialog('close'); - $state.go('settings'); - } - }, { - label: i18n._("Save changes"), - onClick: function() { - vm.formSave() - .then(function() { - $('#FormModal-dialog').dialog('close'); - $state.go('settings'); - }); - }, - "class": "btn btn-primary", - "id": "formmodal-save-button" - }]; - triggerModal(msg, title, buttons); - } else { - $state.go('settings'); - } - }; - - vm.resetAllConfirm = function() { - var buttons = [{ - label: i18n._("Cancel"), - "class": "btn btn-default", - "id": "formmodal-cancel-button", - onClick: function() { - $('#FormModal-dialog').dialog('close'); - } - }, { - label: i18n._("Confirm Reset"), - onClick: function() { - resetAll(); - $('#FormModal-dialog').dialog('close'); - }, - "class": "btn btn-primary", - "id": "formmodal-reset-button" - }]; - var msg = i18n._('This will reset all configuration values to their factory defaults. Are you sure you want to proceed?'); - var title = i18n._('Confirm factory reset'); - triggerModal(msg, title, buttons); - }; - - vm.closeMessageBar = function() { - var msg = 'Are you sure you want to hide the notification bar?'; - var title = 'Warning: Closing notification bar'; - var buttons = [{ - label: "Cancel", - "class": "btn Form-cancelButton", - "id": "formmodal-cancel-button", - onClick: function() { - $('#FormModal-dialog').dialog('close'); - } - }, { - label: "OK", - onClick: function() { - $('#FormModal-dialog').dialog('close'); - updateMessageBarPrefs(); - }, - "class": "btn btn-primary", - "id": "formmodal-save-button" - }]; - triggerModal(msg, title, buttons); - }; - - vm.getCurrentFormTitle = function() { - switch($stateParams.form) { - case 'auth': - return 'AUTHENTICATION'; - case 'jobs': - return 'JOBS'; - case 'system': - return 'SYSTEM'; - case 'ui': - return 'USER INTERFACE'; - case 'license': - return 'LICENSE'; - } - }; - - $scope.toggleForm = function(key) { - if($rootScope.user_is_system_auditor) { - // Block system auditors from making changes - event.preventDefault(); - return; - } - - $scope[key] = !$scope[key]; - Wait('start'); - var payload = {}; - payload[key] = $scope[key]; - SettingsService.patchConfiguration(payload) - .then(function() { - //TODO consider updating form values with returned data here - }) - .catch(function(data) { - //Change back on unsuccessful update - $scope[key] = !$scope[key]; - ProcessErrors($scope, data.data, data.status, formDefs[formTracker.getCurrent()], - { - hdr: ` - ${ i18n._('Error!')} `, - msg: i18n._('Failed to save toggle settings. Returned status: ') + data.status - }); - }) - .finally(function() { - Wait('stop'); - }); - }; - - function resetAll () { - var keys = _.keys(formDefs[formTracker.getCurrent()].fields); - var payload = {}; - clearApiErrors(); - _.each(keys, function(key) { - if (key === 'ACCESS_TOKEN_EXPIRE_SECONDS' || key === 'REFRESH_TOKEN_EXPIRE_SECONDS' || key === 'AUTHORIZATION_CODE_EXPIRE_SECONDS') { - // the reset for these two keys needs to be nested under OAUTH2_PROVIDER - if (payload.OAUTH2_PROVIDER === undefined) { - payload.OAUTH2_PROVIDER = { - ACCESS_TOKEN_EXPIRE_SECONDS: $scope.ACCESS_TOKEN_EXPIRE_SECONDS, - REFRESH_TOKEN_EXPIRE_SECONDS: $scope.REFRESH_TOKEN_EXPIRE_SECONDS, - AUTHORIZATION_CODE_EXPIRE_SECONDS: $scope.AUTHORIZATION_CODE_EXPIRE_SECONDS - }; - } - payload.OAUTH2_PROVIDER[key] = $scope.configDataResolve[key].default; - } else { - payload[key] = $scope.configDataResolve[key].default; - } - }); - - Wait('start'); - SettingsService.patchConfiguration(payload) - .then(function() { - populateFromApi(); - $scope[formTracker.currentFormName()].$setPristine(); - - let keys = _.keys(formDefs[formTracker.getCurrent()].fields); - _.each(keys, function(key) { - $scope[key] = $scope.configDataResolve[key].default; - if($scope[key + '_field'].type === "select"){ - // We need to re-instantiate the Select2 element - // after resetting the value. Example: - $scope.$broadcast(key+'_populated', null, false); - if(key === "AD_HOC_COMMANDS"){ - $scope.$broadcast(key+'_reverted', null, false); - } - } - else if($scope[key + '_field'].reset === "CUSTOM_LOGO"){ - $scope.$broadcast(key+'_reverted'); - } - else if($scope[key + '_field'].hasOwnProperty('codeMirror')){ - $scope[key] = '{}'; - $scope.$broadcast('codeMirror_populated', key); - } - }); - - }) - .catch(function(data) { - ProcessErrors($scope, data.error, data.status, formDefs[formTracker.getCurrent()], - { - hdr: ` - ${ i18n._('Error!')} `, - msg: i18n._('There was an error resetting values. Returned status: ') + data.error.detail - }); - }) - .finally(function() { - Wait('stop'); - }); - } - - function updateMessageBarPrefs () { - $scope.show_auditor_bar = false; - Store('show_auditor_bar', $scope.show_auditor_bar); - } - - angular.extend(vm, { - formTracker: formTracker, - getFormPayload: getFormPayload, - populateFromApi: populateFromApi, - triggerModal: triggerModal, - }); - } -]; diff --git a/awx/ui/client/src/configuration/forms/settings-form.partial.html b/awx/ui/client/src/configuration/forms/settings-form.partial.html deleted file mode 100644 index cd279af19dff..000000000000 --- a/awx/ui/client/src/configuration/forms/settings-form.partial.html +++ /dev/null @@ -1,19 +0,0 @@ -
- - System auditors have read-only permissions in this section. - -
- -
-
-
-
{{ vm.getCurrentFormTitle() }}
-
-
-
-
-
-
-
-
-
\ No newline at end of file diff --git a/awx/ui/client/src/configuration/forms/settings-form.route.js b/awx/ui/client/src/configuration/forms/settings-form.route.js deleted file mode 100644 index 20ca06fd7f73..000000000000 --- a/awx/ui/client/src/configuration/forms/settings-form.route.js +++ /dev/null @@ -1,76 +0,0 @@ -import {templateUrl} from '../../shared/template-url/template-url.factory'; -import { N_ } from '../../i18n'; -import SettingsFormController from './settings-form.controller'; - -// Import form controllers -import SettingsAuthController from './auth-form/configuration-auth.controller'; -import SettingsJobsController from './jobs-form/configuration-jobs.controller'; -import SettingsSystemController from './system-form/configuration-system.controller'; -import SettingsUiController from './ui-form/configuration-ui.controller'; - -export default { - name: 'settings.form', - route: '/:form', - ncyBreadcrumb: { - label: N_("{{ vm.getCurrentFormTitle() }}") - }, - views: { - '@': { - templateUrl: templateUrl('configuration/forms/settings-form'), - controller: SettingsFormController, - controllerAs: 'vm' - }, - 'auth@settings.form': { - templateUrl: templateUrl('configuration/forms/auth-form/configuration-auth'), - controller: SettingsAuthController, - controllerAs: 'authVm' - }, - 'jobs@settings.form': { - templateUrl: templateUrl('configuration/forms/jobs-form/configuration-jobs'), - controller: SettingsJobsController, - controllerAs: 'jobsVm' - }, - 'system@settings.form': { - templateUrl: templateUrl('configuration/forms/system-form/configuration-system'), - controller: SettingsSystemController, - controllerAs: 'systemVm' - }, - 'ui@settings.form': { - templateUrl: templateUrl('configuration/forms/ui-form/configuration-ui'), - controller: SettingsUiController, - controllerAs: 'uiVm' - }, - 'license@settings.form': { - templateUrl: templateUrl('license/license'), - controller: 'licenseController' - }, - }, - onEnter: ['$state', 'ConfigService', '$stateParams', (state, configService, stateParams) => { - return configService.getConfig() - .then(config => { - if (_.get(config, 'license_info.license_type') === 'open' && stateParams.form === 'license') { - return state.go('settings'); - } - }); - }], - resolve: { - rhCreds: ['Rest', 'GetBasePath', function(Rest, GetBasePath) { - Rest.setUrl(`${GetBasePath('settings')}system/`); - return Rest.get() - .then(({data}) => { - const rhCreds = {}; - if (data.REDHAT_USERNAME && data.REDHAT_USERNAME !== "") { - rhCreds.REDHAT_USERNAME = data.REDHAT_USERNAME; - } - - if (data.REDHAT_PASSWORD && data.REDHAT_PASSWORD !== "") { - rhCreds.REDHAT_PASSWORD = data.REDHAT_PASSWORD; - } - - return rhCreds; - }).catch(() => { - return {}; - }); - }] - } -}; \ No newline at end of file diff --git a/awx/ui/client/src/configuration/forms/system-form/configuration-system.controller.js b/awx/ui/client/src/configuration/forms/system-form/configuration-system.controller.js deleted file mode 100644 index 1b78d85685d8..000000000000 --- a/awx/ui/client/src/configuration/forms/system-form/configuration-system.controller.js +++ /dev/null @@ -1,232 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default [ - '$rootScope', '$scope', '$stateParams', - 'systemActivityStreamForm', - 'systemLoggingForm', - 'systemMiscForm', - 'SettingsUtils', - 'CreateSelect2', - 'GenerateForm', - 'i18n', - 'Rest', - 'ProcessErrors', - 'ngToast', - '$filter', - function( - $rootScope, $scope, $stateParams, - systemActivityStreamForm, - systemLoggingForm, - systemMiscForm, - SettingsUtils, - CreateSelect2, - GenerateForm, - i18n, - Rest, - ProcessErrors, - ngToast, - $filter - ) { - var systemVm = this; - - var generator = GenerateForm; - var formTracker = $scope.$parent.vm.formTracker; - var activeSystemForm = 'misc'; - - if ($stateParams.form === 'system') { - formTracker.setCurrentSystem(activeSystemForm); - } - - var activeForm = function(tab) { - if(!_.get($scope.$parent, [formTracker.currentFormName(), '$dirty'])) { - systemVm.activeSystemForm = tab; - formTracker.setCurrentSystem(systemVm.activeSystemForm); - } else { - var msg = i18n._('You have unsaved changes. Would you like to proceed without saving?'); - var title = i18n._('Warning: Unsaved Changes'); - var buttons = [{ - label: i18n._('Discard changes'), - "class": "btn Form-cancelButton", - "id": "formmodal-cancel-button", - onClick: function() { - $scope.$parent.vm.populateFromApi(); - $scope.$parent[formTracker.currentFormName()].$setPristine(); - systemVm.activeSystemForm = tab; - formTracker.setCurrentSystem(systemVm.activeSystemForm); - $('#FormModal-dialog').dialog('close'); - } - }, { - label: i18n._('Save changes'), - onClick: function() { - $scope.$parent.vm.formSave() - .then(function() { - $scope.$parent[formTracker.currentFormName()].$setPristine(); - $scope.$parent.vm.populateFromApi(); - systemVm.activeSystemForm = tab; - formTracker.setCurrentSystem(systemVm.activeSystemForm); - $('#FormModal-dialog').dialog('close'); - }); - }, - "class": "btn btn-primary", - "id": "formmodal-save-button" - }]; - $scope.$parent.vm.triggerModal(msg, title, buttons); - } - formTracker.setCurrentSystem(systemVm.activeSystemForm); - }; - - var dropdownOptions = [ - {label: i18n._('Misc. System'), value: 'misc'}, - {label: i18n._('Activity Stream'), value: 'activity_stream'}, - {label: i18n._('Logging'), value: 'logging'}, - ]; - - var systemForms = [{ - formDef: systemLoggingForm, - id: 'system-logging-form' - }, { - formDef: systemActivityStreamForm, - id: 'system-activity-stream-form' - }, { - formDef: systemMiscForm, - id: 'system-misc-form' - }]; - - var forms = _.map(systemForms, 'formDef'); - _.each(forms, function(form) { - var keys = _.keys(form.fields); - _.each(keys, function(key) { - if($scope.configDataResolve[key].type === 'choice') { - // Create options for dropdowns - var optionsGroup = key + '_options'; - $scope.$parent.$parent[optionsGroup] = []; - _.each($scope.configDataResolve[key].choices, function(choice){ - $scope.$parent.$parent[optionsGroup].push({ - name: choice[0], - label: choice[1], - value: choice[0] - }); - }); - } - addFieldInfo(form, key); - }); - // Disable the save button for system auditors - form.buttons.save.disabled = $rootScope.user_is_system_auditor; - }); - - function addFieldInfo(form, key) { - _.extend(form.fields[key], { - awPopOver: ($scope.configDataResolve[key].defined_in_file) ? - null: $scope.configDataResolve[key].help_text, - label: $scope.configDataResolve[key].label, - name: key, - toggleSource: key, - dataPlacement: 'top', - placeholder: SettingsUtils.formatPlaceholder($scope.configDataResolve[key].placeholder, key) || null, - dataTitle: $scope.configDataResolve[key].label, - required: $scope.configDataResolve[key].required, - ngDisabled: $rootScope.user_is_system_auditor, - disabled: $scope.configDataResolve[key].disabled || null, - readonly: $scope.configDataResolve[key].readonly || null, - definedInFile: $scope.configDataResolve[key].defined_in_file || null - }); - } - - $scope.$parent.$parent.parseType = 'json'; - - _.each(systemForms, function(form) { - generator.inject(form.formDef, { - id: form.id, - mode: 'edit', - scope: $scope.$parent.$parent, - related: true, - noPanel: true - }); - }); - - var dropdownRendered = false; - - $scope.$on('populated', function() { - populateLogAggregator(false); - }); - - $scope.$on('LOG_AGGREGATOR_TYPE_populated', function(e, data, flag) { - populateLogAggregator(flag); - }); - - $scope.$on('LOG_AGGREGATOR_PROTOCOL_populated', function(e, data, flag) { - populateLogAggregator(flag); - }); - - function populateLogAggregator(flag){ - - if($scope.$parent.$parent.LOG_AGGREGATOR_TYPE !== null) { - $scope.$parent.$parent.LOG_AGGREGATOR_TYPE = _.find($scope.$parent.$parent.LOG_AGGREGATOR_TYPE_options, { value: $scope.$parent.$parent.LOG_AGGREGATOR_TYPE }); - } - - if($scope.$parent.$parent.LOG_AGGREGATOR_PROTOCOL !== null) { - $scope.$parent.$parent.LOG_AGGREGATOR_PROTOCOL = _.find($scope.$parent.$parent.LOG_AGGREGATOR_PROTOCOL_options, { value: $scope.$parent.$parent.LOG_AGGREGATOR_PROTOCOL }); - } - - if($scope.$parent.$parent.LOG_AGGREGATOR_LEVEL !== null) { - $scope.$parent.$parent.LOG_AGGREGATOR_LEVEL = _.find($scope.$parent.$parent.LOG_AGGREGATOR_LEVEL_options, { value: $scope.$parent.$parent.LOG_AGGREGATOR_LEVEL }); - } - - if(flag !== undefined){ - dropdownRendered = flag; - } - - if(!dropdownRendered) { - dropdownRendered = true; - CreateSelect2({ - element: '#configuration_logging_template_LOG_AGGREGATOR_TYPE', - multiple: false, - placeholder: i18n._('Select types'), - }); - $scope.$parent.$parent.configuration_logging_template_form.LOG_AGGREGATOR_TYPE.$setPristine(); - $scope.$parent.$parent.configuration_logging_template_form.LOG_AGGREGATOR_PROTOCOL.$setPristine(); - $scope.$parent.$parent.configuration_logging_template_form.LOG_AGGREGATOR_LEVEL.$setPristine(); - } - } - - $scope.$parent.vm.testLogging = function() { - Rest.setUrl("/api/v2/settings/logging/test/"); - Rest.post($scope.$parent.vm.getFormPayload()) - .then(() => { - ngToast.success({ - content: `` + - i18n._('Log aggregator test successful.') - }); - }) - .catch(({data, status}) => { - if (status === 500) { - ngToast.danger({ - content: '' + - i18n._('Log aggregator test failed.
Detail: ') + $filter('sanitize')(data.error), - additionalClasses: "LogAggregator-failedNotification" - }); - } else { - ProcessErrors($scope, data, status, null, - { - hdr: i18n._('Error!'), - msg: i18n._('There was an error testing the ' + - 'log aggregator. Returned status: ') + - status - }); - } - }); - }; - - angular.extend(systemVm, { - activeForm: activeForm, - activeSystemForm: activeSystemForm, - dropdownOptions: dropdownOptions, - systemForms: systemForms - }); - } -]; diff --git a/awx/ui/client/src/configuration/forms/system-form/configuration-system.partial.html b/awx/ui/client/src/configuration/forms/system-form/configuration-system.partial.html deleted file mode 100644 index 0a32b40b9511..000000000000 --- a/awx/ui/client/src/configuration/forms/system-form/configuration-system.partial.html +++ /dev/null @@ -1,46 +0,0 @@ -
-
-
-
-
- {{opt.label}} -
-
-
-
- -
-
-
-
- -
-
-
-
- -
-
-
-
- - -
-
- -
-
-
-
-
diff --git a/awx/ui/client/src/configuration/forms/system-form/sub-forms/system-activity-stream.form.js b/awx/ui/client/src/configuration/forms/system-form/sub-forms/system-activity-stream.form.js deleted file mode 100644 index 2cc39299ef14..000000000000 --- a/awx/ui/client/src/configuration/forms/system-form/sub-forms/system-activity-stream.form.js +++ /dev/null @@ -1,39 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - export default ['i18n', function(i18n) { - return { - name: 'configuration_activity_stream_template', - showActions: true, - showHeader: false, - - fields: { - ACTIVITY_STREAM_ENABLED: { - type: 'toggleSwitch', - }, - ACTIVITY_STREAM_ENABLED_FOR_INVENTORY_SYNC: { - type: 'toggleSwitch' - } - }, - - buttons: { - reset: { - ngShow: '!user_is_system_auditor', - ngClick: 'vm.resetAllConfirm()', - label: i18n._('Revert all to default'), - class: 'Form-resetAll' - }, - cancel: { - ngClick: 'vm.formCancel()', - }, - save: { - ngClick: 'vm.formSave()', - ngDisabled: true - } - } - }; - } -]; diff --git a/awx/ui/client/src/configuration/forms/system-form/sub-forms/system-logging.form.js b/awx/ui/client/src/configuration/forms/system-form/sub-forms/system-logging.form.js deleted file mode 100644 index 388cfb00f85b..000000000000 --- a/awx/ui/client/src/configuration/forms/system-form/sub-forms/system-logging.form.js +++ /dev/null @@ -1,93 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - export default ['i18n', function(i18n) { - return { - name: 'configuration_logging_template', - showActions: true, - showHeader: false, - - fields: { - LOG_AGGREGATOR_HOST: { - type: 'text', - reset: 'LOG_AGGREGATOR_HOST' - }, - LOG_AGGREGATOR_PORT: { - type: 'text', - reset: 'LOG_AGGREGATOR_PORT' - }, - LOG_AGGREGATOR_TYPE: { - type: 'select', - reset: 'LOG_AGGREGATOR_TYPE', - ngOptions: 'type.label for type in LOG_AGGREGATOR_TYPE_options track by type.value', - }, - LOG_AGGREGATOR_USERNAME: { - type: 'text', - reset: 'LOG_AGGREGATOR_USERNAME' - }, - LOG_AGGREGATOR_PASSWORD: { - type: 'sensitive', - hasShowInputButton: true, - reset: 'LOG_AGGREGATOR_PASSWORD' - }, - LOG_AGGREGATOR_LOGGERS: { - type: 'textarea', - reset: 'LOG_AGGREGATOR_LOGGERS' - }, - LOG_AGGREGATOR_INDIVIDUAL_FACTS: { - type: 'toggleSwitch', - }, - LOG_AGGREGATOR_PROTOCOL: { - type: 'select', - reset: 'LOG_AGGREGATOR_PROTOCOL', - ngOptions: 'type.label for type in LOG_AGGREGATOR_PROTOCOL_options track by type.value', - disableChooseOption: true - }, - LOG_AGGREGATOR_TCP_TIMEOUT: { - type: 'text', - reset: 'LOG_AGGREGATOR_TCP_TIMEOUT', - ngShow: 'LOG_AGGREGATOR_PROTOCOL.value === "tcp" || LOG_AGGREGATOR_PROTOCOL.value === "https"', - awRequiredWhen: { - reqExpression: "LOG_AGGREGATOR_PROTOCOL.value === 'tcp' || LOG_AGGREGATOR_PROTOCOL.value === 'https'", - init: "false" - }, - }, - LOG_AGGREGATOR_LEVEL: { - type: 'select', - reset: 'LOG_AGGREGATOR_LEVEL', - ngOptions: 'type.label for type in LOG_AGGREGATOR_LEVEL_options track by type.value', - disableChooseOption: true - }, - LOG_AGGREGATOR_VERIFY_CERT: { - type: 'toggleSwitch', - ngShow: "LOG_AGGREGATOR_PROTOCOL.value === 'https'" - } - }, - - buttons: { - reset: { - ngShow: '!user_is_system_auditor', - ngClick: 'vm.resetAllConfirm()', - label: i18n._('Revert all to default'), - class: 'Form-resetAll' - }, - testLogging: { - ngClick: 'vm.testLogging()', - label: i18n._('Test'), - class: 'btn-primary', - ngDisabled: 'configuration_logging_template_form.$invalid' - }, - cancel: { - ngClick: 'vm.formCancel()', - }, - save: { - ngClick: 'vm.formSave()', - ngDisabled: true - } - } - }; - } -]; diff --git a/awx/ui/client/src/configuration/forms/system-form/sub-forms/system-misc.form.js b/awx/ui/client/src/configuration/forms/system-form/sub-forms/system-misc.form.js deleted file mode 100644 index 636d4de7f3f1..000000000000 --- a/awx/ui/client/src/configuration/forms/system-form/sub-forms/system-misc.form.js +++ /dev/null @@ -1,102 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['i18n', function(i18n) { - return { - name: 'configuration_misc_template', - showHeader: false, - showActions: true, - - fields: { - TOWER_URL_BASE: { - type: 'text', - reset: 'TOWER_URL_BASE', - }, - ORG_ADMINS_CAN_SEE_ALL_USERS: { - type: 'toggleSwitch', - }, - MANAGE_ORGANIZATION_AUTH: { - type: 'toggleSwitch', - }, - SESSION_COOKIE_AGE: { - type: 'number', - integer: true, - min: 61, - reset: 'SESSION_COOKIE_AGE', - }, - SESSIONS_PER_USER: { - type: 'number', - integer: true, - spinner: true, - min: -1, - reset: 'SESSIONS_PER_USER', - }, - AUTH_BASIC_ENABLED: { - type: 'toggleSwitch', - }, - ALLOW_OAUTH2_FOR_EXTERNAL_USERS: { - type: 'toggleSwitch', - }, - LOGIN_REDIRECT_OVERRIDE: { - type: 'text', - reset: 'LOGIN_REDIRECT_OVERRIDE' - }, - ACCESS_TOKEN_EXPIRE_SECONDS: { - type: 'text', - reset: 'ACCESS_TOKEN_EXPIRE_SECONDS' - }, - REFRESH_TOKEN_EXPIRE_SECONDS: { - type: 'text', - reset: 'REFRESH_TOKEN_EXPIRE_SECONDS' - }, - AUTHORIZATION_CODE_EXPIRE_SECONDS: { - type: 'text', - reset: 'AUTHORIZATION_CODE_EXPIRE_SECONDS' - }, - REMOTE_HOST_HEADERS: { - type: 'textarea', - reset: 'REMOTE_HOST_HEADERS' - }, - CUSTOM_VENV_PATHS: { - type: 'textarea', - reset: 'CUSTOM_VENV_PATHS' - }, - INSIGHTS_TRACKING_STATE: { - type: 'toggleSwitch' - }, - REDHAT_USERNAME: { - type: 'text', - reset: 'REDHAT_USERNAME', - }, - REDHAT_PASSWORD: { - type: 'sensitive', - hasShowInputButton: true, - reset: 'REDHAT_PASSWORD', - }, - AUTOMATION_ANALYTICS_URL: { - type: 'text', - reset: 'AUTOMATION_ANALYTICS_URL', - } - }, - - buttons: { - reset: { - ngShow: '!user_is_system_auditor', - ngClick: 'vm.resetAllConfirm()', - label: i18n._('Revert all to default'), - class: 'Form-resetAll' - }, - cancel: { - ngClick: 'vm.formCancel()', - }, - save: { - ngClick: 'vm.formSave()', - ngDisabled: true - } - } - }; -} -]; diff --git a/awx/ui/client/src/configuration/forms/ui-form/configuration-ui.controller.js b/awx/ui/client/src/configuration/forms/ui-form/configuration-ui.controller.js deleted file mode 100644 index f35e8b609048..000000000000 --- a/awx/ui/client/src/configuration/forms/ui-form/configuration-ui.controller.js +++ /dev/null @@ -1,107 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - export default [ - '$scope', - '$rootScope', - 'ConfigurationUiForm', - 'CreateSelect2', - 'GenerateForm', - 'i18n', - '$stateParams', - function( - $scope, - $rootScope, - ConfigurationUiForm, - CreateSelect2, - GenerateForm, - i18n, - $stateParams - ) { - var generator = GenerateForm; - var form = ConfigurationUiForm; - - const formTracker = $scope.$parent.vm.formTracker; - if ($stateParams.form === 'ui') { - formTracker.setCurrentAuth('ui'); - } - - var keys = _.keys(form.fields); - _.each(keys, function(key) { - if($scope.configDataResolve[key].type === 'choice') { - // Create options for dropdowns - var optionsGroup = key + '_options'; - $scope.$parent.$parent[optionsGroup] = []; - _.each($scope.configDataResolve[key].choices, function(choice){ - $scope.$parent.$parent[optionsGroup].push({ - name: choice[0], - label: choice[1], - value: choice[0] - }); - }); - } - addFieldInfo(form, key); - }); - - // Disable the save button for system auditors - form.buttons.save.disabled = $rootScope.user_is_system_auditor; - - function addFieldInfo(form, key) { - _.extend(form.fields[key], { - awPopOver: ($scope.configDataResolve[key].defined_in_file) ? - null: $scope.configDataResolve[key].help_text, - label: $scope.configDataResolve[key].label, - name: key, - toggleSource: key, - dataPlacement: 'top', - dataTitle: $scope.configDataResolve[key].label, - required: $scope.configDataResolve[key].required, - ngDisabled: $rootScope.user_is_system_auditor, - disabled: $scope.configDataResolve[key].disabled || null, - readonly: $scope.configDataResolve[key].readonly || null, - definedInFile: $scope.configDataResolve[key].defined_in_file || null - }); - } - - generator.inject(form, { - id: 'configure-ui-form', - mode: 'edit', - scope: $scope.$parent.$parent, - related: true, - noPanel: true - }); - - // Flag to avoid re-rendering and breaking Select2 dropdowns on tab switching - var dropdownRendered = false; - - function populatePendoTrackingState(flag){ - if($scope.$parent.$parent.PENDO_TRACKING_STATE !== null) { - $scope.$parent.$parent.PENDO_TRACKING_STATE = _.find($scope.$parent.$parent.PENDO_TRACKING_STATE_options, { value: $scope.$parent.$parent.PENDO_TRACKING_STATE }); - } - - if(flag !== undefined){ - dropdownRendered = flag; - } - - if(!dropdownRendered) { - dropdownRendered = true; - CreateSelect2({ - element: '#configuration_ui_template_PENDO_TRACKING_STATE', - multiple: false, - placeholder: i18n._('Select commands') - }); - } - } - - $scope.$on('PENDO_TRACKING_STATE_populated', function(e, data, flag) { - populatePendoTrackingState(flag); - }); - - $scope.$on('populated', function(){ - populatePendoTrackingState(false); - }); - } - ]; diff --git a/awx/ui/client/src/configuration/forms/ui-form/configuration-ui.form.js b/awx/ui/client/src/configuration/forms/ui-form/configuration-ui.form.js deleted file mode 100644 index 4bb9fd34018f..000000000000 --- a/awx/ui/client/src/configuration/forms/ui-form/configuration-ui.form.js +++ /dev/null @@ -1,49 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['i18n', function(i18n) { - return { - showHeader: false, - name: 'configuration_ui_template', - showActions: true, - - fields: { - PENDO_TRACKING_STATE: { - type: 'select', - ngChange: 'changedPendo()', - ngOptions: 'choice.label for choice in PENDO_TRACKING_STATE_options track by choice.value', - reset: 'PENDO_TRACKING_STATE' - }, - CUSTOM_LOGO: { - type: 'custom', - reset: 'CUSTOM_LOGO', - control: `` - }, - CUSTOM_LOGIN_INFO: { - type: 'textarea', - reset: 'CUSTOM_LOGIN_INFO', - rows: 6 - } - }, - - buttons: { - reset: { - ngShow: '!user_is_system_auditor', - ngClick: 'vm.resetAllConfirm()', - label: i18n._('Revert all to default'), - class: 'Form-resetAll' - }, - cancel: { - ngClick: 'vm.formCancel()', - }, - save: { - ngClick: 'vm.formSave()', - ngDisabled: true - } - } - }; -} -]; diff --git a/awx/ui/client/src/configuration/forms/ui-form/configuration-ui.partial.html b/awx/ui/client/src/configuration/forms/ui-form/configuration-ui.partial.html deleted file mode 100644 index 82ab146e93a8..000000000000 --- a/awx/ui/client/src/configuration/forms/ui-form/configuration-ui.partial.html +++ /dev/null @@ -1,8 +0,0 @@ -
-
-
-
-
-
-
-
diff --git a/awx/ui/client/src/configuration/main.js b/awx/ui/client/src/configuration/main.js deleted file mode 100644 index 809f3312fdbe..000000000000 --- a/awx/ui/client/src/configuration/main.js +++ /dev/null @@ -1,74 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import settingsService from './settings.service'; -import settingsUtils from './settingsUtils.service'; - -// Import forms -//authorization sub-forms -import configurationAzureForm from './forms/auth-form/sub-forms/auth-azure.form.js'; -import configurationGithubForm from './forms/auth-form/sub-forms/auth-github.form.js'; -import configurationGithubOrgForm from './forms/auth-form/sub-forms/auth-github-org.form'; -import configurationGithubTeamForm from './forms/auth-form/sub-forms/auth-github-team.form'; -import configurationGoogleForm from './forms/auth-form/sub-forms/auth-google-oauth2.form'; -import configurationLdapForm from './forms/auth-form/sub-forms/auth-ldap.form.js'; -import configurationLdap1Form from './forms/auth-form/sub-forms/auth-ldap1.form.js'; -import configurationLdap2Form from './forms/auth-form/sub-forms/auth-ldap2.form.js'; -import configurationLdap3Form from './forms/auth-form/sub-forms/auth-ldap3.form.js'; -import configurationLdap4Form from './forms/auth-form/sub-forms/auth-ldap4.form.js'; -import configurationLdap5Form from './forms/auth-form/sub-forms/auth-ldap5.form.js'; -import configurationRadiusForm from './forms/auth-form/sub-forms/auth-radius.form.js'; -import configurationTacacsForm from './forms/auth-form/sub-forms/auth-tacacs.form.js'; -import configurationSamlForm from './forms/auth-form/sub-forms/auth-saml.form'; - -//system sub-forms -import systemActivityStreamForm from './forms/system-form/sub-forms/system-activity-stream.form.js'; -import systemLoggingForm from './forms/system-form/sub-forms/system-logging.form.js'; -import systemMiscForm from './forms/system-form/sub-forms/system-misc.form.js'; - -import configurationJobsForm from './forms/jobs-form/configuration-jobs.form'; -import configurationUiForm from './forms/ui-form/configuration-ui.form'; - -// Wrapper form route -import settingsFormRoute from './forms/settings-form.route'; - -import settingsRoute from './settings.route'; -import settingsController from './settings.controller.js'; - -export default -angular.module('configuration', []) - .controller('SettingsController', settingsController) - //auth forms - .factory('configurationAzureForm', configurationAzureForm) - .factory('configurationGithubForm', configurationGithubForm) - .factory('configurationGithubOrgForm', configurationGithubOrgForm) - .factory('configurationGithubTeamForm', configurationGithubTeamForm) - .factory('configurationGoogleForm', configurationGoogleForm) - .factory('configurationLdapForm', configurationLdapForm) - .factory('configurationLdap1Form', configurationLdap1Form) - .factory('configurationLdap2Form', configurationLdap2Form) - .factory('configurationLdap3Form', configurationLdap3Form) - .factory('configurationLdap4Form', configurationLdap4Form) - .factory('configurationLdap5Form', configurationLdap5Form) - .factory('configurationRadiusForm', configurationRadiusForm) - .factory('configurationTacacsForm', configurationTacacsForm) - .factory('configurationSamlForm', configurationSamlForm) - //system forms - .factory('systemActivityStreamForm', systemActivityStreamForm) - .factory('systemLoggingForm', systemLoggingForm) - .factory('systemMiscForm', systemMiscForm) - - //other forms - .factory('ConfigurationJobsForm', configurationJobsForm) - .factory('ConfigurationUiForm', configurationUiForm) - - //helpers and services - .factory('SettingsUtils', settingsUtils) - .service('SettingsService', settingsService) - .run(['$stateExtender', function($stateExtender) { - $stateExtender.addState(settingsFormRoute); - $stateExtender.addState(settingsRoute); - }]); diff --git a/awx/ui/client/src/configuration/settings.block.less b/awx/ui/client/src/configuration/settings.block.less deleted file mode 100644 index 33cafba674f7..000000000000 --- a/awx/ui/client/src/configuration/settings.block.less +++ /dev/null @@ -1,195 +0,0 @@ -.Form-resetValue, .Form-resetAll { - text-transform: uppercase; - font-weight: normal; - cursor: pointer; - font-size: 10px; -} - -.Form-resetValue { - float: right; -} - -.Form-resetAll { - border: none; - padding: 0; - background-color: @white; - margin-right: auto; - color: @default-link; - - &:hover { - color: @default-link-hov; - } -} - -.Form-tab { - min-width: 77px; -} - -.Form-button--left { - margin-right: auto; - margin-left: 0; -} - -.Form-nav--dropdownContainer { - align-items: center; - width: 100%; - margin: 0 0 22px auto; - display: flex; - justify-content: flex-start; - border-bottom: 1px solid @at-gray-b7; - padding-bottom: 22px; -} - -.Form-nav--ldapDropdownContainer { - align-items: center; - width: 100%; - margin: 0 0 auto auto; - display: flex; -} - -.Form-nav--dropdown { - width: 285px; -} - -.Form-nav--dropdown .select2-container { - margin: 0; -} - -.Form-nav--dropdownLabel { - text-transform: uppercase; - color: @default-interface-txt; - font-size: 14px; - font-weight: bold; - padding-right: 15px; -} - -.Form-tabRow { - display: flex; -} - -input.Form-filePicker { - width: 0.1px; - height: 0.1px; - opacity: 0; - overflow: hidden; - position: absolute; - z-index: -1; -} -label#filePickerButton { - cursor: pointer; - background-color: #fff; - color: @default-interface-txt; - margin-bottom: 0; -} -input#filePickerText { - cursor: default; - border-radius: 0 5px 5px 0; - background-color: #fff; -} - -.Form-filePicker--selectedFile { - margin: 12px 0; -} - -.Form-filePicker--thumbnail { - max-height: 40px; - max-width: 40px; -} - -// Messagebar for system auditor role notifications -.Section-messageBar { - width: 120%; - margin-left: -20px; - padding: 10px 10px 10px 20px; - color: @white; - background-color: @default-link; -} - -.Section-messageBar-text { - margin-left: @at-space-2x; -} - -.Section-messageBar-warning { - color: @at-color-warning; -} - -.Section-messageBar--close { - position: absolute; - right: 0; - background: none; - border: none; - color: @info-close; -} - -.Section-messageBar--close:hover { - color: @white; -} - -//Needed to show the not-allowed cursor over a Codemirror instance -.Form-formGroup--disabled { - cursor: not-allowed; - position: relative; - display: inline-block; - - // Filepicker disabling - .Form-browseButton, .Form-filePicker--textBox { - pointer-events: none; - cursor: not-allowed; - } - - // Adding explanatory tooltips for disabled fields - // Borrows styling from .popover - .Form-tooltip--disabled { - visibility: hidden; - background-color: @default-interface-txt; - color: @default-bg; - text-align: center; - border-radius: 6px; - - position: absolute; - z-index: 1; - width: 200px; - bottom: 110%; - left: 50%; - margin-left: -100px; - - background-clip: padding-box; - border: 1px solid rgba(0,0,0,.2); - -webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2); - box-shadow: 0 5px 10px rgba(0,0,0,.2); - white-space: normal; - - padding: 9px 14px; - font-size: 12px; - font-weight: bold; - } - - &:hover .Form-tooltip--disabled { - visibility: visible; - } - - .Form-tooltip--disabled::after { - content: " "; - position: absolute; - top: 100%; - left: 50%; - margin-left: -11px; - border-width: 11px; - border-style: solid; - border-color: @default-interface-txt transparent transparent transparent; - } -} - -.LogAggregator-failedNotification{ - max-width: 500px; - word-break: break-word; -} - -hr { - height: 1px; -} - -.ConfigureTower-errorIcon{ - margin-right:5px; - color:@red; -} diff --git a/awx/ui/client/src/configuration/settings.controller.js b/awx/ui/client/src/configuration/settings.controller.js deleted file mode 100644 index 88ea0745cd11..000000000000 --- a/awx/ui/client/src/configuration/settings.controller.js +++ /dev/null @@ -1,13 +0,0 @@ -import defaultStrings from '~assets/default.strings.json'; - -export default [ '$state', - function ($state) { - const vm = this; - - vm.product = defaultStrings.BRAND_NAME; - - vm.goToCard = (card) => { - $state.go('settings.form', { form: card }); - }; - } -]; diff --git a/awx/ui/client/src/configuration/settings.partial.html b/awx/ui/client/src/configuration/settings.partial.html deleted file mode 100644 index ebfb5b5eb051..000000000000 --- a/awx/ui/client/src/configuration/settings.partial.html +++ /dev/null @@ -1,17 +0,0 @@ - - - Enable simplified login for your Tower applications - - - Update settings pertaining to Jobs within Tower - - - Define system-level features and functions - - - Set preferences for data collection, logos, and logins - - - View and edit your license information - - \ No newline at end of file diff --git a/awx/ui/client/src/configuration/settings.route.js b/awx/ui/client/src/configuration/settings.route.js deleted file mode 100644 index e975a5f6ba68..000000000000 --- a/awx/ui/client/src/configuration/settings.route.js +++ /dev/null @@ -1,40 +0,0 @@ -import { N_ } from '../i18n'; -import {templateUrl} from '../shared/template-url/template-url.factory'; -import SettingsController from './settings.controller'; -// Import form controllers - -export default { - name: 'settings', - route: '/settings', - ncyBreadcrumb: { - label: N_("SETTINGS") - }, - resolve: { - configDataResolve: ['SettingsService', function(SettingsService){ - return SettingsService.getConfigurationOptions(); - }], - features: ['CheckLicense', '$rootScope', - function(CheckLicense, $rootScope) { - if($rootScope.licenseMissing === undefined){ - return CheckLicense.notify(); - } - - }], - config: ['ConfigService', 'CheckLicense', '$rootScope', - function(ConfigService, CheckLicense, $rootScope) { - ConfigService.delete(); - return ConfigService.getConfig() - .then(function(config){ - $rootScope.licenseMissing = (CheckLicense.valid(config.license_info) === false) ? true : false; - return config; - }); - }], - }, - views: { - '': { - templateUrl: templateUrl('configuration/settings'), - controller: SettingsController, - controllerAs: 'vm' - } - } -}; \ No newline at end of file diff --git a/awx/ui/client/src/configuration/settings.service.js b/awx/ui/client/src/configuration/settings.service.js deleted file mode 100644 index 0c74a4fe9f4e..000000000000 --- a/awx/ui/client/src/configuration/settings.service.js +++ /dev/null @@ -1,107 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['GetBasePath', '$q', 'Rest', 'i18n', - function(GetBasePath, $q, Rest, i18n) { - var url = GetBasePath('settings') + 'all'; - - return { - getConfigurationOptions: function() { - var deferred = $q.defer(); - var returnData = {}; - - Rest.setUrl(url); - Rest.options() - .then(({data}) => { - // Compare GET actions with PUT actions and flag discrepancies - // for disabling in the UI - // - // since OAUTH2_PROVIDER returns two of the keys in a nested format, - // we need to split those out into the root of the options payload - // in order for them to be consumed - var appendOauth2ProviderKeys = (optsFromAPI) => { - var unnestOauth2ProviderKey = (key, help_text, label, parentKey) => { - optsFromAPI[key] = _.cloneDeep(optsFromAPI[parentKey]); - optsFromAPI[key].label = label; - optsFromAPI[key].help_text = help_text; - optsFromAPI[key].type = optsFromAPI[parentKey].child.type; - optsFromAPI[key].min_value = optsFromAPI[parentKey].child.min_value; - if (optsFromAPI[parentKey].default) { - optsFromAPI[key].default = optsFromAPI[parentKey].default[key]; - } - delete optsFromAPI[key].child; - }; - if (optsFromAPI.OAUTH2_PROVIDER) { - unnestOauth2ProviderKey('ACCESS_TOKEN_EXPIRE_SECONDS', - i18n._('The duration (in seconds) access tokens remain valid since their creation.'), - i18n._('Access Token Expiration'), - 'OAUTH2_PROVIDER'); - unnestOauth2ProviderKey('REFRESH_TOKEN_EXPIRE_SECONDS', - i18n._('The duration (in seconds) refresh tokens remain valid after the expiration of their associated access token.'), - i18n._('Refresh Token Expiration'), - 'OAUTH2_PROVIDER'); - unnestOauth2ProviderKey('AUTHORIZATION_CODE_EXPIRE_SECONDS', - i18n._('The duration (in seconds) authorization codes remain valid since their creation.'), - i18n._('Authorization Code Expiration'), - 'OAUTH2_PROVIDER'); - } - return optsFromAPI; - }; - var getActions = appendOauth2ProviderKeys(data.actions.GET); - var getKeys = _.keys(getActions); - var putActions = data.actions.PUT ? appendOauth2ProviderKeys(data.actions.PUT) : {}; - - _.each(getKeys, function(key) { - if(putActions && putActions[key]) { - returnData[key] = putActions[key]; - } else { - returnData[key] = _.extend(getActions[key], { - required: false, - disabled: true - }); - } - }); - - deferred.resolve(returnData); - }) - .catch(({error}) => { - deferred.reject(error); - }); - - return deferred.promise; - }, - - patchConfiguration: function(body) { - var deferred = $q.defer(); - - Rest.setUrl(url); - Rest.patch(body) - .then(({data}) => { - deferred.resolve(data); - }) - .catch((error) => { - deferred.reject(error); - }); - - return deferred.promise; - }, - - getCurrentValues: function() { - var deferred = $q.defer(); - Rest.setUrl(url); - Rest.get() - .then(({data}) => { - deferred.resolve(data); - }) - .catch((error) => { - deferred.reject(error); - }); - - return deferred.promise; - } - }; - } -]; diff --git a/awx/ui/client/src/configuration/settingsUtils.service.js b/awx/ui/client/src/configuration/settingsUtils.service.js deleted file mode 100644 index e9d448aff5b6..000000000000 --- a/awx/ui/client/src/configuration/settingsUtils.service.js +++ /dev/null @@ -1,112 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['$q', - function($q) { - - return { - listToArray: function(input) { - var payload; - - if (input.indexOf('[') !== -1) { - try { - payload = JSON.parse(input); - - if (!Array.isArray(payload)) { - payload = []; - } - } catch(err) { - payload = []; - } - } else if (input.indexOf('\n') !== -1) { - //Parse multiline input - payload = input.replace(/^\s+|\s+$/g, "").split('\n'); - } else { - if (input === '' || input === '{}') { - payload = []; - } else { - payload = input.replace(/^\s+|\s+$/g, "") - .split(/\s*,\s*/); - } - } - return payload; - }, - - arrayToList: function(input) { - var multiLineInput = false; - _.each(input, function(statement) { - if (statement.indexOf(',') !== -1) { - multiLineInput = true; - } - }); - if (multiLineInput === false) { - return input.join(', '); - } else { - return input.join('\n'); - } - }, - - isEmpty: function(map) { - for (var key in map) { - if (map.hasOwnProperty(key)) { - return false; - } - } - return true; - }, - - formatPlaceholder: function(input) { - if (input !== null && typeof input === 'object') { - if (Array.isArray(input)) { - var multiLineInput = false; - _.each(input, function(statement) { - if (statement.indexOf(',') !== -1) { - multiLineInput = true; - } - }); - if (multiLineInput === false) { - return input.join(', '); - } else { - return input.join('\n'); - } - } else { - return JSON.stringify(input); - } - } else { - return input; - } - }, - - imageProcess: function(file) { - var deferred = $q.defer(); - var SIZELIMIT = 1000000; // 1 MB - var ACCEPTEDFORMATS = ['image/png', 'image/gif', 'image/jpeg']; //Basic check - - if(file.size < SIZELIMIT && ACCEPTEDFORMATS.indexOf(file.type) !== -1) { - var reader = new FileReader(); - reader.readAsDataURL(file); - reader.onload = function () { - deferred.resolve(reader.result); - }; - reader.onerror = function () { - deferred.reject('File could not be parsed'); - }; - } else { - var error = 'Error: '; - if(file.size > SIZELIMIT) { - error += 'Must be under ' + SIZELIMIT / 1000000 + 'MB. '; - } - if(ACCEPTEDFORMATS.indexOf(file.type) === -1) { - error += 'Wrong file type - must be png, gif, or jpg.'; - } - deferred.reject(error); - } - return deferred.promise; - } - - }; - } -]; diff --git a/awx/ui/client/src/credential-types/add/add.controller.js b/awx/ui/client/src/credential-types/add/add.controller.js deleted file mode 100644 index 2d4441496265..000000000000 --- a/awx/ui/client/src/credential-types/add/add.controller.js +++ /dev/null @@ -1,124 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['Rest', 'Wait', - 'CredentialTypesForm', 'ProcessErrors', 'GetBasePath', - 'GenerateForm', '$scope', '$state', 'Alert', 'GetChoices', 'ParseTypeChange', 'ToJSON', 'CreateSelect2', 'i18n', - function(Rest, Wait, - CredentialTypesForm, ProcessErrors, GetBasePath, - GenerateForm, $scope, $state, Alert, GetChoices, ParseTypeChange, ToJSON, CreateSelect2, i18n - ) { - var form = CredentialTypesForm, - url = GetBasePath('credential_types'); - - init(); - - function init() { - - // for add, don't show ssh - $scope.$on('loadCredentialKindOptions', function() { - $scope.credential_kind_options = $scope.credential_kind_options - .filter(val => val.value === 'net' || - val.value === 'cloud'); - }); - - // Load the list of options for Kind - $scope.$parent.optionsDefer.promise - .then(function(options) { - GetChoices({ - scope: $scope, - url: url, - field: 'kind', - variable: 'credential_kind_options', - options: options, - callback: 'loadCredentialKindOptions' - }); - - const docs_url = 'https://docs.ansible.com/ansible-tower/latest/html/userguide/credential_types.html#getting-started-with-credential-types'; - const docs_help_text = `

${i18n._('Getting Started with Credential Types')}`; - - const api_inputs_help_text = _.get(options, 'actions.POST.inputs.help_text', "Specification for credential type inputs."); - const api_injectors_help_text = _.get(options, 'actions.POST.injectors.help_text', "Specification for credential type injector."); - - $scope.inputs_help_text = api_inputs_help_text + docs_help_text; - $scope.injectors_help_text = api_injectors_help_text + docs_help_text; - - if (!options.actions.POST) { - $state.go("^"); - Alert('Permission Error', 'You do not have permission to add a credential type.', 'alert-info'); - } - }); - - // apply form definition's default field values - GenerateForm.applyDefaults(form, $scope); - - // @issue @jmitchell - this setting probably collides with new RBAC can* implementation? - $scope.canEdit = true; - - var callback = function() { - // Make sure the form controller knows there was a change - $scope[form.name + '_form'].$setDirty(); - }; - $scope.parseTypeInputs = 'yaml'; - $scope.parseTypeInjectors = 'yaml'; - ParseTypeChange({ - scope: $scope, - field_id: 'credential_type_inputs', - variable: 'inputs', - onChange: callback, - parse_variable: 'parseTypeInputs' - }); - ParseTypeChange({ - scope: $scope, - field_id: 'credential_type_injectors', - variable: 'injectors', - onChange: callback, - parse_variable: 'parseTypeInjectors' - }); - - CreateSelect2({ - element: '#credential_type_kind', - multiple: false, - }); - } - - // Save - $scope.formSave = function() { - GenerateForm.clearApiErrors($scope); - Wait('start'); - Rest.setUrl(url); - var inputs = ToJSON($scope.parseTypeInputs, $scope.inputs); - var injectors = ToJSON($scope.parseTypeInjectors, $scope.injectors); - if (inputs === null) { - inputs = {}; - } - if (injectors === null) { - injectors = {}; - } - Rest.post({ - name: $scope.name, - description: $scope.description, - kind: "cloud", - inputs: inputs, - injectors: injectors - }) - .then(({data}) => { - $state.go('credentialTypes.edit', { credential_type_id: data.id }, { reload: true }); - Wait('stop'); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, form, { - hdr: 'Error!', - msg: 'Failed to add new credential type. PUT returned status: ' + status - }); - }); - }; - - $scope.formCancel = function() { - $state.go('^'); - }; - } -]; diff --git a/awx/ui/client/src/credential-types/add/main.js b/awx/ui/client/src/credential-types/add/main.js deleted file mode 100644 index 9344da0e9856..000000000000 --- a/awx/ui/client/src/credential-types/add/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import controller from './add.controller'; - -export default - angular.module('credentialTypesAdd', []) - .controller('CredentialTypesAddController', controller); diff --git a/awx/ui/client/src/credential-types/credential-types.form.js b/awx/ui/client/src/credential-types/credential-types.form.js deleted file mode 100644 index 6f554654788d..000000000000 --- a/awx/ui/client/src/credential-types/credential-types.form.js +++ /dev/null @@ -1,84 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name forms.function:CredentialType - * @description This form is for adding/editing a credential type -*/ - -export default ['i18n', function(i18n) { - return { - - addTitle: i18n._('NEW CREDENTIAL TYPE'), - editTitle: '{{ name }}', - name: 'credential_type', - basePath: 'credential_types', - stateTree: 'credentialTypes', - breadcrumbName: i18n._('CREDENTIAL TYPE'), - showActions: true, - - // TODO: update fields to be the schema for credential types instead of inventory scripts - fields: { - name: { - label: i18n._('Name'), - type: 'text', - ngDisabled: '!(credential_type.summary_fields.user_capabilities.edit || canAdd)', - required: true, - capitalize: false - }, - description: { - label: i18n._('Description'), - type: 'text', - ngDisabled: '!(credential_type.summary_fields.user_capabilities.edit || canAdd)' - }, - inputs: { - label: i18n._('Input Configuration'), - class: 'Form-textAreaLabel Form-formGroup--fullWidth', - type: 'textarea', - rows: 6, - default: '---', - showParseTypeToggle: true, - parseTypeName: 'parseTypeInputs', - awPopOverWatch: "inputs_help_text", - dataTitle: i18n._('Input Configuration'), - dataPlacement: 'right', - dataContainer: "body", - ngDisabled: '!(credential_type.summary_fields.user_capabilities.edit || canAdd)' - }, - injectors: { - label: i18n._('Injector Configuration'), - class: 'Form-textAreaLabel Form-formGroup--fullWidth', - type: 'textarea', - rows: 6, - default: '---', - showParseTypeToggle: true, - parseTypeName: 'parseTypeInjectors', - awPopOverWatch: "injectors_help_text", - dataTitle: i18n._('Injector Configuration'), - dataPlacement: 'right', - dataContainer: "body", - ngDisabled: '!(credential_type.summary_fields.user_capabilities.edit || canAdd)' - }, - }, - - buttons: { //for now always generates - -
- - - - diff --git a/awx/ui/client/src/instance-groups/instances/instances-list.partial.html b/awx/ui/client/src/instance-groups/instances/instances-list.partial.html deleted file mode 100644 index 08d2e28dc325..000000000000 --- a/awx/ui/client/src/instance-groups/instances/instances-list.partial.html +++ /dev/null @@ -1,95 +0,0 @@ - - - - - {{:: vm.strings.get('tab.DETAILS') }} - {{:: vm.strings.get('tab.INSTANCES') }} - {{:: vm.strings.get('tab.JOBS') }} - - - -
- - - -
- -
-
-
- - - - -
- -
-
-
- - -
- - - - -
-
-
- - - - -
-
-
-
-
- - -
diff --git a/awx/ui/client/src/instance-groups/instances/instances.controller.js b/awx/ui/client/src/instance-groups/instances/instances.controller.js deleted file mode 100644 index 566161d85000..000000000000 --- a/awx/ui/client/src/instance-groups/instances/instances.controller.js +++ /dev/null @@ -1,196 +0,0 @@ -function InstancesController ($scope, $state, $http, $transitions, models, strings, Dataset, ProcessErrors) { - const { instanceGroup } = models; - const vm = this || {}; - let paginateQuerySet = {}; - vm.strings = strings; - vm.panelTitle = instanceGroup.get('name'); - vm.instance_group_id = instanceGroup.get('id'); - vm.policy_instance_list = instanceGroup.get('policy_instance_list'); - vm.isSuperuser = $scope.$root.user_is_superuser; - - let tabs = {}; - let addInstancesRoute =""; - if ($state.is("instanceGroups.instances")) { - tabs={ state: { - details: { - _go: 'instanceGroups.edit' - }, - instances: { - _active: true, - _go: 'instanceGroups.instances' - }, - jobs: { - _go: 'instanceGroups.jobs' - } - } - }; - addInstancesRoute = 'instanceGroups.instances.modal.add'; - } else if ($state.is("instanceGroups.containerGroupInstances")) { - tabs={ - state: { - details: { - _go: 'instanceGroups.editContainerGroup' - }, - instances: { - _active: true, - _go: 'instanceGroups.containerGroupInstances' - }, - jobs: { - _go: 'instanceGroups.containerGroupJobs' - } - } - }; - addInstancesRoute = 'instanceGroups.containerGroupInstances.modal.add'; - } - - vm.list = { - name: 'instances', - iterator: 'instance', - basePath: `/api/v2/instance_groups/${vm.instance_group_id}/instances/` - }; - vm.instance_dataset = Dataset.data; - vm.instances = Dataset.data.results; - - const toolbarSortDefault = { - label: `${strings.get('sort.NAME_ASCENDING')}`, - value: 'hostname' - }; - - vm.addInstances = () => { - - return $state.go(`${addInstancesRoute}`); - }; - - - vm.toolbarSortValue = toolbarSortDefault; - vm.toolbarSortOptions = [ - toolbarSortDefault, - { label: `${strings.get('sort.NAME_DESCENDING')}`, value: '-hostname' }, - { label: `${strings.get('sort.UUID_ASCENDING')}`, value: 'uuid' }, - { label: `${strings.get('sort.UUID_DESCENDING')}`, value: '-uuid' }, - { label: `${strings.get('sort.CREATED_ASCENDING')}`, value: 'created' }, - { label: `${strings.get('sort.CREATED_DESCENDING')}`, value: '-created' }, - { label: `${strings.get('sort.MODIFIED_ASCENDING')}`, value: 'modified' }, - { label: `${strings.get('sort.MODIFIED_DESCENDING')}`, value: '-modified' }, - { label: `${strings.get('sort.CAPACITY_ASCENDING')}`, value: 'capacity' }, - { label: `${strings.get('sort.CAPACITY_DESCENDING')}`, value: '-capacity' } - ]; - - const removeStateParamsListener = $scope.$watchCollection('$state.params', () => { - setToolbarSort(); - }); - - function setToolbarSort () { - const orderByValue = _.get($state.params, 'instance_search.order_by'); - const sortValue = _.find(vm.toolbarSortOptions, (option) => option.value === orderByValue); - if (sortValue) { - vm.toolbarSortValue = sortValue; - } else { - vm.toolbarSortValue = toolbarSortDefault; - } - } - - vm.onToolbarSort = (sort) => { - vm.toolbarSortValue = sort; - - const queryParams = Object.assign( - {}, - $state.params.instance_search, - paginateQuerySet, - { order_by: sort.value } - ); - - $state.go('.', { - instance_search: queryParams - }, { notify: false, location: 'replace' }); - }; - - const tabObj = {}; - const params = { instance_group_id: instanceGroup.get('id') }; - - tabObj.details = { _go: tabs.state.details._go, _params: params }; - tabObj.instances = { _go: tabs.state.instances._go, _params: params, _active: true }; - tabObj.jobs = { _go: tabs.state.jobs._go, _params: params }; - vm.tab = tabObj; - - - vm.tooltips = { - add: strings.get('tooltips.ASSOCIATE_INSTANCES') - }; - - vm.rowAction = { - toggle: { - _disabled: !vm.isSuperuser - }, - capacity_adjustment: { - _disabled: !vm.isSuperuser - } - }; - - vm.toggle = (toggled) => { - const instance = _.find(vm.instances, ['id', toggled.id]); - instance.enabled = !instance.enabled; - - const data = { - "capacity_adjustment": instance.capacity_adjustment, - "enabled": instance.enabled - }; - - const req = { - method: 'PUT', - url: instance.url, - data - }; - $http(req).then(vm.onSaveSuccess) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, null, { - hdr: 'Error!', - msg: 'Call failed. Return status: ' + status - }); - }); - }; - - vm.onSaveSuccess = () => { - $state.transitionTo($state.current, $state.params, { - reload: true, location: true, inherit: false, notify: true - }); - }; - - $scope.isActive = function(id) { - let selected = parseInt($state.params.instance_id); - return id === selected; - }; - - const removeUpdateDatasetListener = $scope.$on('updateDataset', (e, dataset, queryset) => { - vm.instances = dataset.results; - vm.instance_dataset = dataset; - paginateQuerySet = queryset; - }); - - const removeStateChangeListener = $transitions.onSuccess({}, function(trans) { - if (trans.to().name === 'instanceGroups.instances.modal.add') { - removeUpdateDatasetListener(); - removeStateChangeListener(); - removeStateParamsListener(); - } - }); - - $scope.$on('$destroy', function() { - removeUpdateDatasetListener(); - removeStateChangeListener(); - removeStateParamsListener(); - }); -} - -InstancesController.$inject = [ - '$scope', - '$state', - '$http', - '$transitions', - 'resolvedModels', - 'InstanceGroupsStrings', - 'Dataset', - 'ProcessErrors', -]; - -export default InstancesController; diff --git a/awx/ui/client/src/instance-groups/jobs/instanceGroupsJobsListContainer.controller.js b/awx/ui/client/src/instance-groups/jobs/instanceGroupsJobsListContainer.controller.js deleted file mode 100644 index bffe4d6064ab..000000000000 --- a/awx/ui/client/src/instance-groups/jobs/instanceGroupsJobsListContainer.controller.js +++ /dev/null @@ -1,61 +0,0 @@ - -function InstanceGroupJobsContainerController ($scope, strings, $state) { - const vm = this || {}; - const instanceGroupId = $state.params.instance_group_id; - - let tabs = {}; - if ($state.is('instanceGroups.jobs')) { - tabs = { - state: { - details: { - _go: 'instanceGroups.edit' - }, - instances: { - _go: 'instanceGroups.instances' - }, - jobs: { - _active: true, - _go: 'instanceGroups.jobs' - } - } - }; - } else if ($state.is('instanceGroups.containerGroupJobs')) { - tabs = { - state: { - details: { - _go: 'instanceGroups.editContainerGroup' - }, - instances: { - _go: 'instanceGroups.containerGroupInstances' - }, - jobs: { - _active: true, - _go: 'instanceGroups.containerGroupJobs' - } - } - }; - } - - vm.panelTitle = strings.get('jobs.PANEL_TITLE'); - vm.strings = strings; - const tabObj = {}; - - tabObj.details = { _go: tabs.state.details._go, _params: { instance_group_id: parseInt(instanceGroupId) } }; - tabObj.instances = { _go: tabs.state.instances._go, _params: { instance_group_id: parseInt(instanceGroupId) } }; - tabObj.jobs = { _go: tabs.state.jobs._go, _params: { instance_group_id: parseInt(instanceGroupId) }, _active: true }; - vm.tab = tabObj; - - $scope.$on('updateCount', (e, count) => { - if (typeof count === 'number') { - vm.count = count; - } - }); -} - -InstanceGroupJobsContainerController.$inject = [ - '$scope', - 'InstanceGroupsStrings', - '$state' -]; - -export default InstanceGroupJobsContainerController; diff --git a/awx/ui/client/src/instance-groups/jobs/instanceGroupsJobsListContainer.partial.html b/awx/ui/client/src/instance-groups/jobs/instanceGroupsJobsListContainer.partial.html deleted file mode 100644 index de8808bda8a6..000000000000 --- a/awx/ui/client/src/instance-groups/jobs/instanceGroupsJobsListContainer.partial.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - {{:: vm.strings.get('tab.DETAILS') }} - {{:: vm.strings.get('tab.INSTANCES') }} - {{:: vm.strings.get('tab.JOBS') }} - -
-
diff --git a/awx/ui/client/src/instance-groups/jobs/instanceJobsListContainer.controller.js b/awx/ui/client/src/instance-groups/jobs/instanceJobsListContainer.controller.js deleted file mode 100644 index 7e661ab41d5b..000000000000 --- a/awx/ui/client/src/instance-groups/jobs/instanceJobsListContainer.controller.js +++ /dev/null @@ -1,23 +0,0 @@ - -function InstanceGroupJobsContainerController ($scope, strings) { - const vm = this || {}; - - init(); - function init() { - vm.panelTitle = strings.get('jobs.PANEL_TITLE'); - vm.strings = strings; - } - - $scope.$on('updateCount', (e, count) => { - if (typeof count === 'number') { - vm.count = count; - } - }); -} - -InstanceGroupJobsContainerController.$inject = [ - '$scope', - 'InstanceGroupsStrings' -]; - -export default InstanceGroupJobsContainerController; diff --git a/awx/ui/client/src/instance-groups/jobs/instanceJobsListContainer.partial.html b/awx/ui/client/src/instance-groups/jobs/instanceJobsListContainer.partial.html deleted file mode 100644 index 744b4af2569f..000000000000 --- a/awx/ui/client/src/instance-groups/jobs/instanceJobsListContainer.partial.html +++ /dev/null @@ -1,5 +0,0 @@ - - - -
-
diff --git a/awx/ui/client/src/instance-groups/list/instance-groups-list.controller.js b/awx/ui/client/src/instance-groups/list/instance-groups-list.controller.js deleted file mode 100644 index 1433231dd936..000000000000 --- a/awx/ui/client/src/instance-groups/list/instance-groups-list.controller.js +++ /dev/null @@ -1,164 +0,0 @@ -export default [ - '$rootScope', - '$scope', - '$filter', - '$state', - 'Alert', - 'resolvedModels', - 'Dataset', - 'InstanceGroupsStrings', - 'ProcessErrors', - 'Prompt', - 'Wait', - function( - $rootScope, - $scope, - $filter, - $state, - Alert, - resolvedModels, - Dataset, - strings, - ProcessErrors, - Prompt, - Wait - ) { - const vm = this; - const { instanceGroup } = resolvedModels; - let paginateQuerySet = {}; - - vm.strings = strings; - vm.isSuperuser = $scope.$root.user_is_superuser; - - init(); - - function init(){ - $rootScope.breadcrumb.instance_group_name = $filter('sanitize')(instanceGroup.get('name')); - $scope.list = { - iterator: 'instance_group', - name: 'instance_groups' - }; - - $scope.collection = { - basePath: 'instance_groups', - iterator: 'instance_group' - }; - - $scope[`${$scope.list.iterator}_dataset`] = Dataset.data; - $scope[$scope.list.name] = $scope[`${$scope.list.iterator}_dataset`].results; - $scope.instanceGroupCount = Dataset.data.count; - - $scope.$on('updateDataset', function(e, dataset, queryset) { - $scope[`${$scope.list.iterator}_dataset`] = dataset; - $scope[$scope.list.name] = dataset.results; - paginateQuerySet = queryset; - }); - } - - $scope.$watchCollection('$state.params', () => { - vm.activeId = parseInt($state.params.instance_group_id); - setToolbarSort(); - }); - - const toolbarSortDefault = { - label: `${strings.get('sort.NAME_ASCENDING')}`, - value: 'name' - }; - - vm.toolbarSortOptions = [ - toolbarSortDefault, - { label: `${strings.get('sort.NAME_DESCENDING')}`, value: '-name' }, - { label: `${strings.get('sort.CREATED_ASCENDING')}`, value: 'created' }, - { label: `${strings.get('sort.CREATED_DESCENDING')}`, value: '-created' }, - { label: `${strings.get('sort.MODIFIED_ASCENDING')}`, value: 'modified' }, - { label: `${strings.get('sort.MODIFIED_DESCENDING')}`, value: '-modified' } - ]; - - vm.toolbarSortValue = toolbarSortDefault; - - function setToolbarSort () { - const orderByValue = _.get($state.params, 'instance_group_search.order_by'); - const sortValue = _.find(vm.toolbarSortOptions, (option) => option.value === orderByValue); - if (sortValue) { - vm.toolbarSortValue = sortValue; - } else { - vm.toolbarSortValue = toolbarSortDefault; - } - } - - vm.onToolbarSort = (sort) => { - vm.toolbarSortValue = sort; - - const queryParams = Object.assign( - {}, - $state.params.instance_group_search, - paginateQuerySet, - { order_by: sort.value } - ); - - // Update URL with params - $state.go('.', { - instance_group_search: queryParams - }, { notify: false, location: 'replace' }); - }; - - vm.tooltips = { - add: strings.get('tooltips.ADD_INSTANCE_GROUP') - }; - - vm.rowAction = { - trash: instance_group => { - return vm.isSuperuser && instance_group.name !== 'tower' && !instance_group.is_controller && !instance_group.is_isolated; - } - }; - - vm.deleteInstanceGroup = instance_group => { - if (!instance_group) { - Alert(strings.get('error.DELETE'), strings.get('alert.MISSING_PARAMETER')); - return; - } - - Prompt({ - action() { - $('#prompt-modal').modal('hide'); - Wait('start'); - instanceGroup - .request('delete', instance_group.id) - .then(() => handleSuccessfulDelete(instance_group)) - .catch(createErrorHandler('delete instance group', 'DELETE')) - .finally(() => Wait('stop')); - }, - hdr: strings.get('DELETE'), - resourceName: $filter('sanitize')(instance_group.name), - body: `${strings.get('deleteResource.CONFIRM', 'instance group')}` - }); - }; - - function handleSuccessfulDelete(instance_group) { - let reloadListStateParams = null; - - if($scope.instance_groups.length === 1 && $state.params.instance_group_search && _.has($state, 'params.instance_group_search.page') && $state.params.instance_group_search.page !== '1') { - reloadListStateParams = _.cloneDeep($state.params); - reloadListStateParams.instance_group_search.page = (parseInt(reloadListStateParams.instance_group_search.page)-1).toString(); - } - - if (parseInt($state.params.instance_group_id, 0) === instance_group.id) { - $state.go('instanceGroups', reloadListStateParams, { reload: true }); - } else { - $state.go('.', reloadListStateParams, { reload: true }); - } - } - - function createErrorHandler(path, action) { - return ({ data, status }) => { - const hdr = strings.get('error.HEADER'); - const msg = strings.get('error.CALL', { path, action, status }); - ProcessErrors($scope, data, status, null, { hdr, msg }); - }; - } - - $scope.createInstanceGroup = () => { - $state.go('instanceGroups.add'); - }; - } -]; diff --git a/awx/ui/client/src/instance-groups/list/instance-groups-list.partial.html b/awx/ui/client/src/instance-groups/list/instance-groups-list.partial.html deleted file mode 100644 index 892d0874c9a4..000000000000 --- a/awx/ui/client/src/instance-groups/list/instance-groups-list.partial.html +++ /dev/null @@ -1,137 +0,0 @@ - - - - - - - - - - - -
-
-
- - - - -
-
-
- {{vm.strings.get('container.BADGE_TEXT') }} -
-
-
-
-
-
- {{vm.strings.get('instance.BADGE_TEXT') }} -
-
-
-
-
- - - - - - -
-
-
-
-
- - -
-
-
-
-
- - -
-
-
-
- - -
- diff --git a/awx/ui/client/src/instance-groups/main.js b/awx/ui/client/src/instance-groups/main.js deleted file mode 100644 index 1c0d80bbf7cb..000000000000 --- a/awx/ui/client/src/instance-groups/main.js +++ /dev/null @@ -1,470 +0,0 @@ -import { - templateUrl -} from '../shared/template-url/template-url.factory'; -import CapacityAdjuster from './capacity-adjuster/capacity-adjuster.directive'; -import AddContainerGroup from './container-groups/add-container-group.view.html'; -import EditContainerGroupController from './container-groups/edit-container-group.controller'; -import AddContainerGroupController from './container-groups/add-container-group.controller'; -import CapacityBar from './capacity-bar/capacity-bar.directive'; -import instanceGroupsMultiselect from '../shared/instance-groups-multiselect/instance-groups.directive'; -import instanceGroupsModal from '../shared/instance-groups-multiselect/instance-groups-modal/instance-groups-modal.directive'; - -import AddEditTemplate from './add-edit/add-edit-instance-groups.view.html'; -import AddInstanceGroupController from './add-edit/add-instance-group.controller'; -import EditInstanceGroupController from './add-edit/edit-instance-group.controller'; - -import InstanceGroupsTemplate from './list/instance-groups-list.partial.html'; -import InstanceGroupsListController from './list/instance-groups-list.controller'; - -import InstancesTemplate from './instances/instances-list.partial.html'; -import InstanceListController from './instances/instances.controller'; - -import InstanceModalTemplate from './instances/instance-modal.partial.html'; -import InstanceModalController from './instances/instance-modal.controller.js'; - -import list from './instance-groups.list'; -import service from './instance-groups.service'; - -import InstanceGroupsStrings from './instance-groups.strings'; - -import {instanceGroupJobsRoute, containerGroupJobsRoute} from '~features/jobs/routes/instanceGroupJobs.route.js'; -import instanceJobsRoute from '~features/jobs/routes/instanceJobs.route.js'; - - -const MODULE_NAME = 'instanceGroups'; - -function InstanceGroupsResolve($q, $stateParams, InstanceGroup, Credential, Instance, ProcessErrors, strings) { - const instanceGroupId = $stateParams.instance_group_id; - const instanceId = $stateParams.instance_id; - let promises = {}; - - if (!instanceGroupId && !instanceId) { - promises.instanceGroup = new InstanceGroup(['get', 'options']); - promises.credential = new Credential(['get', 'options']); - return $q.all(promises); - } - - if (instanceGroupId && instanceId) { - promises.instance = new Instance(['get', 'options'], [instanceId, instanceId]) - .then((instance) => instance.extend('get', 'jobs', { - params: { - page_size: "10", - order_by: "-finished" - } - })); - return $q.all(promises); - } - - promises.instanceGroup = new InstanceGroup(['get', 'options'], [instanceGroupId, instanceGroupId]) - .then((instanceGroup) => instanceGroup.extend('get', 'jobs', { - params: { - page_size: "10", - order_by: "-finished" - } - })) - .then((instanceGroup) => instanceGroup.extend('get', 'instances')); - - promises.credential = new Credential(); - - return $q.all(promises) - .then(models => models) - .catch(({ - data, - status, - config - }) => { - ProcessErrors(null, data, status, null, { - hdr: strings.get('error.HEADER'), - msg: strings.get('error.CALL', { - path: `${config.url}`, - status - }) - }); - return $q.reject(); - }); -} - -InstanceGroupsResolve.$inject = [ - '$q', - '$stateParams', - 'InstanceGroupModel', - 'CredentialModel', - 'InstanceModel', - 'ProcessErrors', - 'InstanceGroupsStrings' -]; - -function InstanceGroupsRun($stateExtender, strings) { - $stateExtender.addState({ - name: 'instanceGroups', - url: '/instance_groups', - searchPrefix: 'instance_group', - ncyBreadcrumb: { - label: strings.get('state.INSTANCE_GROUPS_BREADCRUMB_LABEL') - }, - params: { - instance_group_search: { - value: { - page_size: '10', - order_by: 'name' - }, - dynamic: true - } - }, - data: { - alwaysShowRefreshButton: true, - }, - views: { - '@': { - templateUrl: templateUrl('./instance-groups/instance-groups'), - }, - 'list@instanceGroups': { - templateUrl: InstanceGroupsTemplate, - controller: 'InstanceGroupsListController', - controllerAs: 'vm' - } - }, - resolve: { - resolvedModels: InstanceGroupsResolve, - Dataset: ['InstanceGroupList', 'QuerySet', '$stateParams', 'GetBasePath', - function (list, qs, $stateParams, GetBasePath) { - let path = GetBasePath(list.basePath) || GetBasePath(list.name); - return qs.search(path, $stateParams[`${list.iterator}_search`]); - } - ] - } - }); - - $stateExtender.addState({ - name: 'instanceGroups.add', - url: '/add', - ncyBreadcrumb: { - label: strings.get('state.ADD_BREADCRUMB_LABEL') - }, - params: { - instance_search: { - value: { - order_by: 'hostname', - page_size: '10' - } - } - }, - views: { - 'add@instanceGroups': { - templateUrl: AddEditTemplate, - controller: AddInstanceGroupController, - controllerAs: 'vm' - } - }, - resolve: { - resolvedModels: InstanceGroupsResolve, - Dataset: [ - '$stateParams', - 'GetBasePath', - 'QuerySet', - ($stateParams, GetBasePath, qs) => { - const searchParams = $stateParams.instance_search; - const searchPath = GetBasePath('instances'); - return qs.search(searchPath, searchParams); - } - ] - } - }); - $stateExtender.addState({ - name: 'instanceGroups.addContainerGroup', - url: '/container_group', - views: { - 'addContainerGroup@instanceGroups': { - templateUrl: AddContainerGroup, - controller: AddContainerGroupController, - controllerAs: 'vm' - } - }, - resolve: { - resolvedModels: InstanceGroupsResolve, - DataSet: ['Rest', 'GetBasePath', (Rest, GetBasePath) => { - Rest.setUrl(`${GetBasePath('instance_groups')}`); - return Rest.options(); - }] - }, - ncyBreadcrumb: { - label: strings.get('state.ADD_CONTAINER_GROUP_BREADCRUMB_LABEL') - }, - }); - - $stateExtender.addState({ - name: 'instanceGroups.addContainerGroup.credentials', - url: '/credential?selected', - searchPrefix: 'credential', - params: { - credential_search: { - value: { - credential_type__kind: 'kubernetes', - order_by: 'name', - page_size: 5, - }, - dynamic: true, - squash: '' - } - }, - data: { - basePath: 'credentials', - formChildState: true - }, - ncyBreadcrumb: { - skip: true - }, - views: { - 'credentials@instanceGroups.addContainerGroup': { - templateProvider: (ListDefinition, generateList) => { - const html = generateList.build({ - mode: 'lookup', - list: ListDefinition, - input_type: 'radio' - }); - return `${html}`; - } - } - }, - resolve: { - ListDefinition: ['CredentialList', list => list], - Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath', (list, qs, $stateParams, GetBasePath) => { - - - const searchPath = GetBasePath('credentials'); - return qs.search( - searchPath, - $stateParams[`${list.iterator}_search`] - ); - }] - }, - onExit ($state) { - if ($state.transition) { - $('#form-modal').modal('hide'); - $('.modal-backdrop').remove(); - $('body').removeClass('modal-open'); - } - } - }); - - $stateExtender.addState({ - name: 'instanceGroups.editContainerGroup', - url: '/container_group/:instance_group_id', - views: { - 'editContainerGroup@instanceGroups': { - templateUrl: AddContainerGroup, - controller: EditContainerGroupController, - controllerAs: 'vm' - } - }, - - resolve: { - resolvedModels: InstanceGroupsResolve, - EditContainerGroupDataset: ['GetBasePath', 'QuerySet', '$stateParams', - function (GetBasePath, qs, $stateParams) { - let path = `${GetBasePath('instance_groups')}${$stateParams.instance_group_id}`; - return qs.search(path, $stateParams); - } - ], - }, - ncyBreadcrumb: { - label: '{{breadcrumb.instance_group_name}}' - }, - }); - - $stateExtender.addState({ - name: 'instanceGroups.editContainerGroup.credentials', - url: '/credential?selected', - searchPrefix: 'credential', - params: { - credential_search: { - value: { - credential_type__kind: 'kubernetes', - order_by: 'name', - page_size: 5, - }, - dynamic: true, - squash: '' - } - }, - data: { - basePath: 'credentials', - formChildState: true - }, - views: { - 'credentials@instanceGroups.editContainerGroup': { - templateProvider: (ListDefinition, generateList) => { - const html = generateList.build({ - mode: 'lookup', - list: ListDefinition, - input_type: 'radio' - }); - return `${html}`; - } - } - }, - resolve: { - ListDefinition: ['CredentialList', list => list], - Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath', (list, qs, $stateParams, GetBasePath) => { - const searchPath = GetBasePath('credentials'); - return qs.search( - searchPath, - $stateParams[`${list.iterator}_search`] - ); - }] - }, - onExit ($state) { - if ($state.transition) { - $('#form-modal').modal('hide'); - $('.modal-backdrop').remove(); - $('body').removeClass('modal-open'); - } - } - }); - - $stateExtender.addState({ - name: 'instanceGroups.edit', - route: '/:instance_group_id', - ncyBreadcrumb: { - label: '{{breadcrumb.instance_group_name}}' - }, - params: { - instance_search: { - value: { - order_by: 'hostname', - page_size: '10' - } - } - }, - views: { - 'edit@instanceGroups': { - templateUrl: AddEditTemplate, - controller: EditInstanceGroupController, - controllerAs: 'vm' - } - }, - resolve: { - resolvedModels: InstanceGroupsResolve, - Dataset: [ - '$stateParams', - 'GetBasePath', - 'QuerySet', - ($stateParams, GetBasePath, qs) => { - const searchParams = $stateParams.instance_search; - const searchPath = GetBasePath('instances'); - return qs.search(searchPath, searchParams); - } - ] - } - }); - - $stateExtender.addState({ - name: 'instanceGroups.instances', - url: '/:instance_group_id/instances', - searchPrefix: 'instance', - ncyBreadcrumb: { - parent: 'instanceGroups.edit', - label: strings.get('state.INSTANCES_BREADCRUMB_LABEL') - }, - params: { - instance_search: { - value: { - order_by: 'hostname', - page_size: '10' - }, - dynamic: true - } - }, - views: { - 'instances@instanceGroups': { - templateUrl: InstancesTemplate, - controller: 'InstanceListController', - controllerAs: 'vm' - } - }, - resolve: { - resolvedModels: InstanceGroupsResolve, - Dataset: ['GetBasePath', 'QuerySet', '$stateParams', - function (GetBasePath, qs, $stateParams) { - let path = `${GetBasePath('instance_groups')}${$stateParams.instance_group_id}/instances`; - return qs.search(path, $stateParams[`instance_search`]); - } - ], - } - }); - - $stateExtender.addState({ - name: 'instanceGroups.instances.modal', - abstract: true, - ncyBreadcrumb: { - skip: true, - }, - views: { - "modal": { - template: ` - `, - } - } - }); - - $stateExtender.addState({ - name: 'instanceGroups.instances.modal.add', - url: '/add', - ncyBreadcrumb: { - skip: true, - }, - searchPrefix: 'add_instance', - params: { - add_instance_search: { - value: { - page_size: '10', - order_by: 'hostname' - }, - dynamic: true - } - }, - views: { - "modal": { - templateUrl: InstanceModalTemplate, - controller: InstanceModalController, - controllerAs: 'vm' - } - }, - resolve: { - resolvedModels: InstanceGroupsResolve, - Dataset: ['GetBasePath', 'QuerySet', '$stateParams', - function (GetBasePath, qs, $stateParams) { - let path = `${GetBasePath('instances')}`; - return qs.search(path, $stateParams[`add_instance_search`]); - } - ], - routeData: [function () { - return "instanceGroups.instances"; - }] - } - }); - - $stateExtender.addState(instanceJobsRoute); - $stateExtender.addState(instanceGroupJobsRoute); - $stateExtender.addState(containerGroupJobsRoute); -} - -InstanceGroupsRun.$inject = [ - '$stateExtender', - 'InstanceGroupsStrings', - 'Rest' -]; - -angular.module(MODULE_NAME, []) - .service('InstanceGroupsService', service) - .factory('InstanceGroupList', list) - .controller('InstanceGroupsListController', InstanceGroupsListController) - .controller('InstanceListController', InstanceListController) - .directive('instanceGroupsMultiselect', instanceGroupsMultiselect) - .directive('instanceGroupsModal', instanceGroupsModal) - .directive('capacityAdjuster', CapacityAdjuster) - .directive('capacityBar', CapacityBar) - .service('InstanceGroupsStrings', InstanceGroupsStrings) - .run(InstanceGroupsRun); - -export default MODULE_NAME; diff --git a/awx/ui/client/src/inventories-hosts/hosts/edit/host-edit.controller.js b/awx/ui/client/src/inventories-hosts/hosts/edit/host-edit.controller.js deleted file mode 100644 index ab304cb37c87..000000000000 --- a/awx/ui/client/src/inventories-hosts/hosts/edit/host-edit.controller.js +++ /dev/null @@ -1,46 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - export default - ['$scope', '$state', 'HostsService', 'host', '$rootScope', - function($scope, $state, HostsService, host, $rootScope){ - $scope.parseType = 'yaml'; - $scope.formCancel = function(){ - $state.go('^', null, {reload: true}); - }; - $scope.toggleHostEnabled = function(){ - $scope.host.enabled = !$scope.host.enabled; - }; - $scope.toggleEnabled = function(){ - $scope.host.enabled = !$scope.host.enabled; - }; - $scope.groupsTab = function(){ - let id = $scope.host.summary_fields.inventory.id; - $state.go('hosts.edit.nested_groups', {inventory_id: id}); - }; - $scope.formSave = function(){ - var host = { - id: $scope.host.id, - variables: $scope.variables === '---' || $scope.variables === '{}' ? null : $scope.variables, - name: $scope.name, - description: $scope.description, - enabled: $scope.host.enabled - }; - HostsService.put(host).then(function(){ - $state.go('.', null, {reload: true}); - }); - - }; - var init = function(){ - $scope.host = host.data; - $rootScope.breadcrumb.host_name = host.data.name; - $scope.name = host.data.name; - $scope.description = host.data.description; - $scope.variables = host.data.variables; - }; - - init(); - }]; diff --git a/awx/ui/client/src/inventories-hosts/hosts/edit/main.js b/awx/ui/client/src/inventories-hosts/hosts/edit/main.js deleted file mode 100644 index 2f0c5aee3988..000000000000 --- a/awx/ui/client/src/inventories-hosts/hosts/edit/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import controller from './host-edit.controller'; - -export default -angular.module('hostsEdit', []) - .controller('HostEditController', controller); diff --git a/awx/ui/client/src/inventories-hosts/hosts/host.form.js b/awx/ui/client/src/inventories-hosts/hosts/host.form.js deleted file mode 100644 index e8d42123a389..000000000000 --- a/awx/ui/client/src/inventories-hosts/hosts/host.form.js +++ /dev/null @@ -1,130 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name forms.function:Hosts - * @description This form is for adding/editing a host on the inventory page -*/ - -export default ['i18n', -function(i18n) { - return { - - addTitle: i18n._('CREATE HOST'), - editTitle: '{{ host.name }}', - name: 'host', - basePath: 'hosts', - well: false, - formLabelSize: 'col-lg-3', - formFieldSize: 'col-lg-9', - iterator: 'host', - detailsClick: "$state.go('hosts.edit', null, {reload:true})", - activeEditState: 'hosts.edit', - stateTree: 'hosts', - headerFields:{ - enabled: { - class: 'Form-header-field', - ngClick: 'toggleHostEnabled(host)', - type: 'toggle', - awToolTip: "

" + - i18n._("Indicates if a host is available and should be included in running jobs.") + - "

" + - i18n._("For hosts that are part of an external" + - " inventory, this may be" + - " reset by the inventory sync process.") + - "

", - dataTitle: i18n._('Host Enabled'), - } - }, - fields: { - name: { - label: i18n._('Host Name'), - type: 'text', - required: true, - awPopOver: "

" + - i18n._("Provide a host name, ip address, or ip address:port. Examples include:") + - "

" + - "
myserver.domain.com
" + - "127.0.0.1
" + - "10.1.0.140:25
" + - "server.example.com:25" + - "
", - dataTitle: i18n._('Host Name'), - dataPlacement: 'right', - dataContainer: 'body', - ngDisabled: '!(host.summary_fields.user_capabilities.edit || canAdd)' - }, - description: { - label: i18n._('Description'), - ngDisabled: '!(host.summary_fields.user_capabilities.edit || canAdd)', - type: 'text' - }, - variables: { - label: i18n._('Variables'), - type: 'code_mirror', - variables: 'variables', - class: 'Form-formGroup--fullWidth', - "default": "---", - awPopOver: "

" + i18n._("Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two.") + "

" + - "JSON:
\n" + - "
{
 "somevar": "somevalue",
 "password": "magic"
}
\n" + - "YAML:
\n" + - "
---
somevar: somevalue
password: magic
\n" + - '

' + i18n.sprintf(i18n._('View JSON examples at %s'), 'www.json.org') + '

' + - '

' + i18n.sprintf(i18n._('View YAML examples at %s'), 'docs.ansible.com') + '

', - } - }, - - buttons: { - cancel: { - ngClick: 'formCancel()', - ngShow: '(host.summary_fields.user_capabilities.edit || canAdd)' - }, - close: { - ngClick: 'formCancel()', - ngShow: '!(host.summary_fields.user_capabilities.edit || canAdd)' - }, - save: { - ngClick: 'formSave()', - ngDisabled: true, - ngShow: '(host.summary_fields.user_capabilities.edit || canAdd)' - } - }, - - related: { - ansible_facts: { - name: 'ansible_facts', - awToolTip: i18n._('Please save before viewing facts.'), - dataPlacement: 'top', - title: i18n._('Facts'), - skipGenerator: true - }, - groups: { - name: 'groups', - awToolTip: i18n._('Please save before defining groups.'), - dataPlacement: 'top', - ngClick: "$state.go('hosts.edit.groups')", - title: i18n._('Groups'), - iterator: 'group', - skipGenerator: true - }, - insights: { - name: 'insights', - awToolTip: i18n._('Please save before viewing Insights.'), - dataPlacement: 'top', - title: i18n._('Insights'), - skipGenerator: true, - ngIf: "host.insights_system_id!==null && host.summary_fields.inventory.hasOwnProperty('insights_credential_id')" - }, - completed_jobs: { - name: 'completed_jobs', - title: i18n._('Completed Jobs'), - skipGenerator: true - } - } - }; - }]; diff --git a/awx/ui/client/src/inventories-hosts/hosts/host.list.js b/awx/ui/client/src/inventories-hosts/hosts/host.list.js deleted file mode 100644 index 9cf8f9827489..000000000000 --- a/awx/ui/client/src/inventories-hosts/hosts/host.list.js +++ /dev/null @@ -1,124 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['i18n', function(i18n) { - return { - name: 'hosts', - iterator: 'host', - editTitle: '{{ selected_group }}', - singleSearchParam: { - param: 'host_filter' - }, - showTitle: false, - well: true, - index: false, - hover: true, - hasChildren: true, - 'class': 'table-no-border', - trackBy: 'host.id', - basePath: 'hosts', - title: false, - actionHolderClass: 'List-actionHolder', - layoutClass: 'List-staticColumnLayout--hostsWithCheckbox', - staticColumns: [ - { - field: 'toggleHost', - content: { - label: '', - columnClass: 'List-staticColumn--toggle', - type: "toggle", - ngClick: "toggleHost($event, host)", - awToolTip: "

" + - i18n._("Indicates if a host is available and should be included in running jobs.") + - "

" + - i18n._("For hosts that are part of an external" + - " inventory, this flag may be" + - " reset by the inventory sync process.") + - "

", - dataPlacement: "right", - nosort: true, - } - }, { - field: 'active_failures', - content: { - label: '', - iconOnly: true, - nosort: true, - // do not remove this ng-click directive - // the list generator case to handle fields without ng-click - // cannot handle the aw-* directives - ngClick: 'noop()', - awPopOver: "{{ host.job_status_html }}", - dataTitle: "{{ host.job_status_title }}", - awToolTip: "{{ host.badgeToolTip }}", - dataPlacement: 'top', - icon: "{{ 'fa icon-job-' + host.active_failures }}", - id: 'active-failures-action', - columnClass: 'status-column List-staticColumn--smallStatus' - } - } - ], - - fields: { - name: { - key: true, - label: i18n._('Name'), - ngClick: "editHost(host.id)", - columnClass: 'col-sm-4', - dataHostId: "{{ host.id }}", - dataType: "host", - }, - inventory: { - label: i18n._('Inventory'), - sourceModel: 'inventory', - sourceField: 'name', - columnClass: 'd-none d-sm-flex col-sm-4 elllipsis', - ngClick: "editInventory(host)" - } - }, - - fieldActions: { - - columnClass: 'col-sm-4 col-xs-5 text-right', - edit: { - ngClick: "editHost(host.id)", - icon: 'icon-edit', - awToolTip: i18n._('Edit host'), - dataPlacement: 'top', - ngShow: 'host.summary_fields.user_capabilities.edit' - }, - view: { - ngClick: "editHost(host.id)", - awToolTip: i18n._('View host'), - dataPlacement: 'top', - ngShow: '!host.summary_fields.user_capabilities.edit' - } - }, - - actions: { - refresh: { - mode: 'all', - awToolTip: i18n._("Refresh the page"), - ngClick: "refreshGroups()", - ngShow: "socketStatus == 'error'", - actionClass: 'btn List-buttonDefault', - buttonContent: i18n._('REFRESH') - }, - smart_inventory: { - mode: 'all', - ngClick: "smartInventory()", - awToolTip: "{{ smartInventoryButtonTooltip }}", - dataTipWatch: 'smartInventoryButtonTooltip', - actionClass: 'btn List-buttonDefault', - buttonContent: i18n._('SMART INVENTORY'), - ngShow: 'canAdd && (hosts.length > 0 || !(searchTags | isEmpty))', - dataPlacement: "top", - ngDisabled: '!enableSmartInventoryButton', - showTipWhenDisabled: true - } - } - }; -}]; diff --git a/awx/ui/client/src/inventories-hosts/hosts/hosts.partial.html b/awx/ui/client/src/inventories-hosts/hosts/hosts.partial.html deleted file mode 100644 index d8e5e684e8fb..000000000000 --- a/awx/ui/client/src/inventories-hosts/hosts/hosts.partial.html +++ /dev/null @@ -1,106 +0,0 @@ -
- -
-
-
-
-
-
INVENTORIES
-
HOSTS
-
-
-
-
-
-
-
-
-
-
- - -
- -
-
No records matched your search.
-
-
NO HOSTS HAVE BEEN CREATED
-
-
-
-
-
-
-
-
-
-
-
Actions
-
-
-
-
-
- -
-
-
- - - -
-
-
-
- -
-
-
- {{ host.description }} -
-
- -
-
- - - -
-
-
-
-
-
-
- - -
-
- diff --git a/awx/ui/client/src/inventories-hosts/hosts/list/host-list.controller.js b/awx/ui/client/src/inventories-hosts/hosts/list/host-list.controller.js deleted file mode 100644 index 52d82796b96d..000000000000 --- a/awx/ui/client/src/inventories-hosts/hosts/list/host-list.controller.js +++ /dev/null @@ -1,108 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - -function HostsList($scope, HostsList, $rootScope, GetBasePath, - rbacUiControlService, Dataset, $state, $filter, Prompt, Wait, - HostsService, SetStatus, canAdd, $transitions, InventoryHostsStrings) { - - let list = HostsList; - - init(); - - function init(){ - $scope.canAdd = canAdd; - $scope.enableSmartInventoryButton = false; - $scope.smartInventoryButtonTooltip = InventoryHostsStrings.get('smartinventorybutton.DISABLED_INSTRUCTIONS'); - $scope.strings = InventoryHostsStrings; - - // Search init - $scope.list = list; - $scope[`${list.iterator}_dataset`] = Dataset.data; - $scope[list.name] = $scope[`${list.iterator}_dataset`].results; - - $rootScope.flashMessage = null; - - $scope.$watchCollection(list.name, function() { - $scope[list.name] = _.map($scope.hosts, function(value) { - value.inventory_name = value.summary_fields.inventory.name; - value.inventory_id = value.summary_fields.inventory.id; - return value; - }); - setJobStatus(); - }); - - $transitions.onSuccess({}, function(trans) { - if(trans.params('to') && trans.params('to').host_search) { - let hasMoreThanDefaultKeys = false; - angular.forEach(trans.params('to').host_search, function(value, key) { - if(key !== 'order_by' && key !== 'page_size' && key !== 'page') { - hasMoreThanDefaultKeys = true; - } - }); - $scope.enableSmartInventoryButton = hasMoreThanDefaultKeys ? true : false; - $scope.smartInventoryButtonTooltip = hasMoreThanDefaultKeys ? InventoryHostsStrings.get('smartinventorybutton.ENABLED_INSTRUCTIONS') : InventoryHostsStrings.get('smartinventorybutton.DISABLED_INSTRUCTIONS'); - } - else { - $scope.enableSmartInventoryButton = false; - $scope.smartInventoryButtonTooltip = InventoryHostsStrings.get('smartinventorybutton.DISABLED_INSTRUCTIONS'); - } - }); - - } - - function setJobStatus(){ - _.forEach($scope.hosts, function(value) { - SetStatus({ - scope: $scope, - host: value - }); - }); - } - - $scope.createHost = function(){ - $state.go('hosts.add'); - }; - $scope.editHost = function(id){ - $state.go('hosts.edit', {host_id: id}); - }; - $scope.goToInsights = function(id){ - $state.go('hosts.edit.insights', {host_id:id}); - }; - $scope.toggleHost = function(event, host) { - try { - $(event.target).tooltip('hide'); - } catch (e) { - // ignore - } - - host.enabled = !host.enabled; - HostsService.patch(host.id, { - enabled: host.enabled - }); - }; - - $scope.smartInventory = function() { - $state.go('inventories.addSmartInventory', {hostfilter: JSON.stringify({"host_filter":`${$state.params.host_search.host_filter}`})}); - }; - - $scope.editInventory = function(host) { - if(host.summary_fields && host.summary_fields.inventory) { - if(host.summary_fields.inventory.kind && host.summary_fields.inventory.kind === 'smart') { - $state.go('inventories.editSmartInventory', {smartinventory_id: host.inventory}); - } - else { - $state.go('inventories.edit', {inventory_id: host.inventory}); - } - } - }; - -} - -export default ['$scope', 'HostsList', '$rootScope', 'GetBasePath', - 'rbacUiControlService', 'Dataset', '$state', '$filter', 'Prompt', 'Wait', - 'HostsService', 'SetStatus', 'canAdd', '$transitions', 'InventoryHostsStrings', HostsList -]; diff --git a/awx/ui/client/src/inventories-hosts/hosts/list/main.js b/awx/ui/client/src/inventories-hosts/hosts/list/main.js deleted file mode 100644 index 068289849492..000000000000 --- a/awx/ui/client/src/inventories-hosts/hosts/list/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import controller from './host-list.controller'; - -export default -angular.module('hostsList', []) - .controller('HostListController', controller); diff --git a/awx/ui/client/src/inventories-hosts/hosts/main.js b/awx/ui/client/src/inventories-hosts/hosts/main.js deleted file mode 100644 index 575a2a8e4954..000000000000 --- a/awx/ui/client/src/inventories-hosts/hosts/main.js +++ /dev/null @@ -1,117 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - import hostEdit from './edit/main'; - import hostList from './list/main'; - import HostsList from './host.list'; - import HostsForm from './host.form'; - import { templateUrl } from '../../shared/template-url/template-url.factory'; - import { N_ } from '../../i18n'; - import ansibleFactsRoute from '../shared/ansible-facts/ansible-facts.route'; - import insightsRoute from '../inventories/insights/insights.route'; - import hostGroupsRoute from './related/groups/hosts-related-groups.route'; - import hostGroupsAssociateRoute from './related/groups/hosts-related-groups-associate.route'; - import hostCompletedJobsRoute from '~features/jobs/routes/hostCompletedJobs.route.js'; - import hostGroups from './related/groups/main'; - -export default -angular.module('host', [ - hostEdit.name, - hostList.name, - hostGroups.name - ]) - .factory('HostsForm', HostsForm) - .factory('HostsList', HostsList) - .config(['$stateProvider', 'stateDefinitionsProvider', '$stateExtenderProvider', - function($stateProvider, stateDefinitionsProvider, $stateExtenderProvider) { - let stateDefinitions = stateDefinitionsProvider.$get(), - stateExtender = $stateExtenderProvider.$get(); - - let generateHostStates = function(){ - let hostTree = stateDefinitions.generateTree({ - parent: 'hosts', // top-most node in the generated tree (will replace this state definition) - modes: ['edit'], - list: 'HostsList', - form: 'HostsForm', - controllers: { - edit: 'HostEditController' - }, - breadcrumbs: { - edit: '{{breadcrumb.host_name}}' - }, - urls: { - list: '/hosts' - }, - data: { - activityStream: true, - activityStreamTarget: 'host' - }, - resolve: { - edit: { - host: ['Rest', '$stateParams', 'GetBasePath', - function(Rest, $stateParams, GetBasePath) { - let path = GetBasePath('hosts') + $stateParams.host_id; - Rest.setUrl(path); - return Rest.get(); - } - ] - }, - list: { - canAdd: ['rbacUiControlService', function(rbacUiControlService) { - return rbacUiControlService.canAdd('hosts') - .then(function(res) { - return res.canAdd; - }) - .catch(function() { - return false; - }); - }] - } - }, - views: { - '@': { - templateUrl: templateUrl('inventories-hosts/hosts/hosts'), - controller: 'HostListController' - } - }, - ncyBreadcrumb: { - label: N_('HOSTS') - } - }); - - let hostAnsibleFacts = _.cloneDeep(ansibleFactsRoute); - hostAnsibleFacts.name = 'hosts.edit.ansible_facts'; - - let hostInsights = _.cloneDeep(insightsRoute); - hostInsights.name = 'hosts.edit.insights'; - - let hostCompletedJobs = _.cloneDeep(hostCompletedJobsRoute); - hostCompletedJobs.name = 'hosts.edit.completed_jobs'; - - return Promise.all([ - hostTree - ]).then((generated) => { - return { - states: _.reduce(generated, (result, definition) => { - return result.concat(definition.states); - }, [ - stateExtender.buildDefinition(hostAnsibleFacts), - stateExtender.buildDefinition(hostInsights), - stateExtender.buildDefinition(hostGroupsRoute), - stateExtender.buildDefinition(hostGroupsAssociateRoute), - stateExtender.buildDefinition(hostCompletedJobs) - ]) - }; - }); - }; - - $stateProvider.state({ - name: 'hosts.**', - url: '/hosts', - lazyLoad: () => generateHostStates() - }); - } - ]); diff --git a/awx/ui/client/src/inventories-hosts/hosts/related/groups/hosts-related-groups-associate.route.js b/awx/ui/client/src/inventories-hosts/hosts/related/groups/hosts-related-groups-associate.route.js deleted file mode 100644 index 9ed6dbfa0b9b..000000000000 --- a/awx/ui/client/src/inventories-hosts/hosts/related/groups/hosts-related-groups-associate.route.js +++ /dev/null @@ -1,32 +0,0 @@ -export default { - name: 'hosts.edit.groups.associate', - squashSearchUrl: true, - url: '/associate?inventory_id', - ncyBreadcrumb:{ - skip:true - }, - views: { - 'modal@hosts.edit': { - templateProvider: function() { - return ``; - }, - controller: function($scope, $q, GroupsService, $state){ - $scope.associateGroups = function(selectedItems){ - var deferred = $q.defer(); - return $q.all( _.map(selectedItems, (selectedItem) => GroupsService.associateHost({id: parseInt($state.params.host_id)}, selectedItem.id)) ) - .then( () =>{ - deferred.resolve(); - }, (error) => { - deferred.reject(error); - }); - }; - } - } - }, - onExit: function($state) { - if ($state.transition) { - $('#associate-groups-modal').modal('hide'); - $('body').removeClass('modal-open'); - } - }, -}; diff --git a/awx/ui/client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.controller.js b/awx/ui/client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.controller.js deleted file mode 100644 index 81ae0ff8822a..000000000000 --- a/awx/ui/client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.controller.js +++ /dev/null @@ -1,96 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - export default - ['$scope', '$rootScope', '$state', '$stateParams', 'HostsRelatedGroupsList', 'InventoryUpdate', - 'CancelSourceUpdate', 'rbacUiControlService', 'GetBasePath', - 'GetHostsStatusMsg', 'Dataset', 'Find', 'QuerySet', 'inventoryData', 'host', 'GroupsService', - function($scope, $rootScope, $state, $stateParams, HostsRelatedGroupsList, InventoryUpdate, - CancelSourceUpdate, rbacUiControlService, GetBasePath, - GetHostsStatusMsg, Dataset, Find, qs, inventoryData, host, GroupsService){ - - let list = HostsRelatedGroupsList; - - init(); - - function init(){ - $scope.inventory_id = inventoryData.id; - $scope.canAdd = false; - - rbacUiControlService.canAdd(GetBasePath('inventory') + $scope.inventory_id + "/groups") - .then(function(canAdd) { - $scope.canAdd = canAdd; - }); - - // Search init - $scope.list = list; - $scope[`${list.iterator}_dataset`] = Dataset.data; - $scope[list.name] = $scope[`${list.iterator}_dataset`].results; - - $scope.$watchCollection(list.name, function(){ - _.forEach($scope[list.name], buildStatusIndicators); - }); - } - - function buildStatusIndicators(group){ - if (group === undefined || group === null) { - group = {}; - } - - let hosts_status; - - hosts_status = GetHostsStatusMsg({ - active_failures: group.hosts_with_active_failures, - total_hosts: group.total_hosts, - inventory_id: $scope.inventory_id, - group_id: group.id - }); - _.assign(group, - {hosts_status_tip: hosts_status.tooltip}, - {hosts_status_class: hosts_status.class}); - } - - $scope.editGroup = function(id){ - $state.go('inventories.edit.groups.edit', {inventory_id: $scope.inventory_id, group_id: id}); - }; - - $scope.goToGroupGroups = function(id){ - $state.go('inventories.edit.groups.edit.nested_groups', {inventory_id: $scope.inventory_id, group_id: id}); - }; - - $scope.associateGroup = function() { - $state.go('.associate', {inventory_id: $scope.inventory_id}); - }; - - $scope.disassociateHost = function(group){ - $scope.disassociateGroup = {}; - angular.extend($scope.disassociateGroup, group); - $('#host-disassociate-modal').modal('show'); - }; - - $scope.confirmDisassociate = function(){ - - $('#host-disassociate-modal').off('hidden.bs.modal').on('hidden.bs.modal', function () { - $('#host-disassociate-modal').off('hidden.bs.modal'); - - let reloadListStateParams = null; - - if($scope.groups.length === 1 && $state.params.group_search && !_.isEmpty($state.params.group_search.page) && $state.params.group_search.page !== '1') { - reloadListStateParams = _.cloneDeep($state.params); - reloadListStateParams.group_search.page = (parseInt(reloadListStateParams.group_search.page)-1).toString(); - } - - $state.go('.', reloadListStateParams, {reload: true}); - }); - - GroupsService.disassociateHost(host.id, $scope.disassociateGroup.id).then(() => { - $state.go($state.current, null, {reload: true}); - $('#host-disassociate-modal').modal('hide'); - $('body').removeClass('modal-open'); - $('.modal-backdrop').remove(); - }); - - }; - }]; diff --git a/awx/ui/client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.list.js b/awx/ui/client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.list.js deleted file mode 100644 index ee7b5cb56047..000000000000 --- a/awx/ui/client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.list.js +++ /dev/null @@ -1,77 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['i18n', function(i18n) { - return { - name: 'groups', - iterator: 'group', - editTitle: '{{ host.name }}', - well: true, - wellOverride: true, - index: false, - hover: true, - trackBy: 'group.id', - basePath: 'api/v2/hosts/{{$stateParams.host_id}}/groups/', - layoutClass: 'List-staticColumnLayout--statusOrCheckbox', - staticColumns: [ - { - field: 'failed_hosts', - content: { - label: '', - nosort: true, - mode: 'all', - iconOnly: true, - awToolTip: "{{ group.hosts_status_tip }}", - dataPlacement: "top", - icon: "{{ 'fa icon-job-' + group.hosts_status_class }}", - columnClass: 'status-column' - } - } - ], - - fields: { - name: { - label: i18n._('Groups'), - key: true, - ngClick: "goToGroupGroups(group.id)", - columnClass: 'col-lg-6 col-md-6 col-sm-6 col-xs-6', - } - }, - - actions: { - refresh: { - mode: 'all', - awToolTip: i18n._("Refresh the page"), - ngClick: "refreshGroups()", - ngShow: "socketStatus == 'error'", - actionClass: 'btn List-buttonDefault', - buttonContent: i18n._('REFRESH') - }, - associate: { - mode: 'all', - ngClick: "associateGroup()", - awToolTip: i18n._("Associate this host with a new group"), - actionClass: 'at-Button--add', - actionId: 'button-add', - ngShow: 'canAdd', - dataPlacement: "top", - } - }, - - fieldActions: { - columnClass: 'col-lg-6 col-md-6 col-sm-6 col-xs-6 text-right', - "delete": { - //label: 'Delete', - mode: 'all', - ngClick: "disassociateHost(group)", - awToolTip: i18n._('Disassociate group'), - iconClass: 'fa fa-times', - dataPlacement: "top", - ngShow: "group.summary_fields.user_capabilities.delete" - } - } - }; -}]; diff --git a/awx/ui/client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.partial.html b/awx/ui/client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.partial.html deleted file mode 100644 index 94038cfb834d..000000000000 --- a/awx/ui/client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.partial.html +++ /dev/null @@ -1,34 +0,0 @@ - diff --git a/awx/ui/client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.route.js b/awx/ui/client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.route.js deleted file mode 100644 index fd21ff7b1d60..000000000000 --- a/awx/ui/client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.route.js +++ /dev/null @@ -1,62 +0,0 @@ -import { N_ } from '../../../../i18n'; -import {templateUrl} from '../../../../shared/template-url/template-url.factory'; - -export default { - name: "hosts.edit.groups", - url: "/groups?{group_search:queryset}", - resolve: { - Dataset: ['HostsRelatedGroupsList', 'QuerySet', '$stateParams', 'GetBasePath', '$interpolate', '$rootScope', - (list, qs, $stateParams, GetBasePath, $interpolate, $rootScope) => { - // allow related list definitions to use interpolated $rootScope / $stateParams in basePath field - let path, interpolator; - if (GetBasePath(list.basePath)) { - path = GetBasePath(list.basePath); - } else { - interpolator = $interpolate(list.basePath); - path = interpolator({ $rootScope: $rootScope, $stateParams: $stateParams }); - } - return qs.search(path, $stateParams[`${list.iterator}_search`]); - } - ], - host: ['$stateParams', 'HostsService', function($stateParams, HostsService) { - if($stateParams.host_id){ - return HostsService.get({ id: $stateParams.host_id }).then(function(res) { - return res.data.results[0]; - }); - } - }], - inventoryData: ['InventoriesService', '$stateParams', 'host', function(InventoriesService, $stateParams, host) { - var id = ($stateParams.inventory_id) ? $stateParams.inventory_id : host.summary_fields.inventory.id; - return InventoriesService.getInventory(id).then(res => res.data); - }] - }, - params: { - group_search: { - value: { - page_size: "20", - order_by: "name" - }, - dynamic: true, - squash: "" - } - }, - ncyBreadcrumb: { - parent: "hosts.edit", - label: N_("GROUPS") - }, - views: { - 'related': { - templateProvider: function(HostsRelatedGroupsList, generateList, $templateRequest) { - let html = generateList.build({ - list: HostsRelatedGroupsList, - mode: 'edit' - }); - - return $templateRequest(templateUrl('inventories-hosts/hosts/related/groups/hosts-related-groups')).then((template) => { - return html.concat(template); - }); - }, - controller: 'HostsRelatedGroupsController' - } - } -}; diff --git a/awx/ui/client/src/inventories-hosts/hosts/related/groups/main.js b/awx/ui/client/src/inventories-hosts/hosts/related/groups/main.js deleted file mode 100644 index 7caaa49f4122..000000000000 --- a/awx/ui/client/src/inventories-hosts/hosts/related/groups/main.js +++ /dev/null @@ -1,14 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import controller from './hosts-related-groups.controller'; -import hostGroupsDefinition from './hosts-related-groups.list'; - - -export default - angular.module('hostGroups', []) - .factory('HostsRelatedGroupsList', hostGroupsDefinition) - .controller('HostsRelatedGroupsController', controller); diff --git a/awx/ui/client/src/inventories-hosts/inventories/adhoc/adhoc-credential.route.js b/awx/ui/client/src/inventories-hosts/inventories/adhoc/adhoc-credential.route.js deleted file mode 100644 index c62ba4d5479d..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/adhoc/adhoc-credential.route.js +++ /dev/null @@ -1,52 +0,0 @@ -export default { - searchPrefix: 'credential', - url: '/credential', - data: { - formChildState: true - }, - params: { - credential_search: { - value: { - page_size: '5', - credential_type: null - }, - squash: true, - dynamic: true - } - }, - ncyBreadcrumb: { - skip: true - }, - views: { - 'related': { - templateProvider: function(ListDefinition, generateList) { - let list_html = generateList.build({ - mode: 'lookup', - list: ListDefinition, - input_type: 'radio' - }); - return `${list_html}`; - - } - } - }, - resolve: { - ListDefinition: ['CredentialList', function(CredentialList) { - let list = _.cloneDeep(CredentialList); - return list; - }], - Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath', - (list, qs, $stateParams, GetBasePath) => { - let path = GetBasePath(list.name) || GetBasePath(list.basePath); - return qs.search(path, $stateParams[`${list.iterator}_search`]); - } - ] - }, - onExit: function($state) { - if ($state.transition) { - $('#form-modal').modal('hide'); - $('.modal-backdrop').remove(); - $('body').removeClass('modal-open'); - } - } -}; diff --git a/awx/ui/client/src/inventories-hosts/inventories/adhoc/adhoc.controller.js b/awx/ui/client/src/inventories-hosts/inventories/adhoc/adhoc.controller.js deleted file mode 100644 index f4cc2b447fab..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/adhoc/adhoc.controller.js +++ /dev/null @@ -1,320 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name controllers.function:Adhoc - * @description This controller controls the adhoc form creation, command launching and navigating to standard out after command has been succesfully ran. -*/ -function adhocController($q, $scope, $stateParams, - $state, CheckPasswords, PromptForPasswords, CreateLaunchDialog, CreateSelect2, adhocForm, - GenerateForm, Rest, ProcessErrors, GetBasePath, GetChoices, - KindChange, Wait, ParseTypeChange, machineCredentialType) { - - // this is done so that we can access private functions for testing, but - // we don't want to populate the "public" scope with these internal - // functions - var privateFn = {}; - this.privateFn = privateFn; - - var id = $stateParams.inventory_id ? $stateParams.inventory_id : $stateParams.smartinventory_id, - hostPattern = $stateParams.pattern; - - // note: put any urls that the controller will use in here!!!! - privateFn.setAvailableUrls = function() { - return { - adhocUrl: GetBasePath('inventory') + id + '/ad_hoc_commands/', - inventoryUrl: GetBasePath('inventory') + id + '/', - machineCredentialUrl: GetBasePath('credentials') + '?credential_type__namespace=ssh' - }; - }; - - var urls = privateFn.setAvailableUrls(); - - // set the default options for the selects of the adhoc form - privateFn.setFieldDefaults = function(verbosity_options, forks_default) { - var verbosity; - for (verbosity in verbosity_options) { - if (verbosity_options[verbosity].isDefault) { - $scope.verbosity = verbosity_options[verbosity]; - } - } - if (forks_default !== 0) { - $("#forks-number").spinner("value", forks_default); - $scope.forks = forks_default; - } - }; - - // set when "working" starts and stops - privateFn.setLoadingStartStop = function() { - var asyncHelper = {}, - formReadyPromise = 0; - - Wait('start'); - - if (asyncHelper.removeChoicesReady) { - asyncHelper.removeChoicesReady(); - } - asyncHelper.removeChoicesReady = $scope.$on('adhocFormReady', - isFormDone); - - // check to see if all requests have completed - function isFormDone() { - formReadyPromise++; - - if (formReadyPromise === 2) { - privateFn.setFieldDefaults($scope.adhoc_verbosity_options, - $scope.forks_field.default); - - CreateSelect2({ - element: '#adhoc_module_name', - multiple: false - }); - - CreateSelect2({ - element: '#adhoc_verbosity', - multiple: false - }); - - Wait('stop'); - } - } - }; - - // set the arguments help to watch on change of the module - privateFn.instantiateArgumentHelp = function() { - $scope.$watch('module_name', function(val) { - if (val) { - // give the docs for the selected module in the popover - $scope.argsPopOver = '

These arguments are used with the ' + - 'specified module. You can find information about the ' + - val.value + ' module here.

'; - } else { - // no module selected - $scope.argsPopOver = "

These arguments are used with the" + - " specified module.

"; - } - }, true); - - // initially set to the same as no module selected - $scope.argsPopOver = "

These arguments are used with the " + - "specified module.

"; - }; - - // pre-populate host patterns from the inventory page and - // delete the value off of rootScope - privateFn.instantiateHostPatterns = function(hostPattern) { - $scope.limit = hostPattern; - $scope.providedHostPatterns = $scope.limit; - }; - - // call helpers to initialize lookup and select fields through get - // requests - privateFn.initializeFields = function(machineCredentialUrl, adhocUrl) { - - // setup module name select - GetChoices({ - scope: $scope, - url: adhocUrl, - field: 'module_name', - variable: 'adhoc_module_options', - callback: 'adhocFormReady' - }); - - // setup verbosity options select - GetChoices({ - scope: $scope, - url: adhocUrl, - field: 'verbosity', - variable: 'adhoc_verbosity_options', - callback: 'adhocFormReady' - }); - }; - - // instantiate all variables on scope for display in the partial - privateFn.initializeForm = function(id, urls, hostPattern) { - // inject the adhoc command form - GenerateForm.inject(adhocForm, - { mode: 'add', related: true, scope: $scope }); - - // set when "working" starts and stops - privateFn.setLoadingStartStop(); - - // put the inventory id on scope for the partial to use - $scope.inv_id = id; - - // set the arguments help to watch on change of the module - privateFn.instantiateArgumentHelp(); - - // pre-populate host patterns from the inventory page and - // delete the value off of rootScope - privateFn.instantiateHostPatterns(hostPattern); - - privateFn.initializeFields(urls.machineCredentialUrl, urls.adhocUrl); - }; - - privateFn.initializeForm(id, urls, hostPattern); - - // init codemirror - $scope.extra_vars = '---'; - $scope.parseType = 'yaml'; - $scope.envParseType = 'yaml'; - ParseTypeChange({ scope: $scope, field_id: 'adhoc_extra_vars' , variable: "extra_vars"}); - - $scope.toggleForm = function(key) { - $scope[key] = !$scope[key]; - }; - - $scope.formCancel = function(){ - $state.go('^'); - }; - - // remove all data input into the form and reset the form back to defaults - $scope.formReset = function () { - GenerateForm.reset(); - - // pre-populate host patterns from the inventory page and - // delete the value off of rootScope - privateFn.instantiateHostPatterns($scope.providedHostPatterns); - - KindChange({ scope: $scope, form: adhocForm, reset: false }); - - // set the default options for the selects of the adhoc form - privateFn.setFieldDefaults($scope.adhoc_verbosity_options, - $scope.forks_default); - }; - - // launch the job with the provided form data - $scope.launchJob = function () { - var adhocUrl = GetBasePath('inventory') + id + - '/ad_hoc_commands/', fld, data={}, html; - - html = '
'; - - // stub the payload with defaults from DRF - data = { - "job_type": "run", - "limit": "", - "credential": "", - "module_name": "command", - "module_args": "", - "forks": 0, - "verbosity": 0, - "extra_vars": "", - "privilege_escalation": "" - }; - - GenerateForm.clearApiErrors($scope); - - // populate data with the relevant form values - for (fld in adhocForm.fields) { - if (adhocForm.fields[fld].type === 'select') { - data[fld] = $scope[fld].value; - } else if ($scope[fld]) { - data[fld] = $scope[fld]; - } - } - - Wait('start'); - - if ($scope.removeStartAdhocRun) { - $scope.removeStartAdhocRun(); - } - $scope.removeStartAdhocRun = $scope.$on('StartAdhocRun', function() { - var password; - for (password in $scope.passwords) { - data[$scope.passwords[password]] = $scope[ - $scope.passwords[password] - ]; - } - // Launch the adhoc job - Rest.setUrl(GetBasePath('inventory') + id + '/ad_hoc_commands/'); - Rest.post(data) - .then(({data}) => { - Wait('stop'); - $state.go('output', {id: data.id, type: 'command'}); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, adhocForm, { - hdr: 'Error!', - msg: 'Failed to launch adhoc command. POST ' + - 'returned status: ' + status }); - }); - }); - - if ($scope.removeCreateLaunchDialog) { - $scope.removeCreateLaunchDialog(); - } - $scope.removeCreateLaunchDialog = $scope.$on('CreateLaunchDialog', - function(e, html, url) { - CreateLaunchDialog({ - scope: $scope, - html: html, - url: url, - callback: 'StartAdhocRun' - }); - }); - - if ($scope.removePromptForPasswords) { - $scope.removePromptForPasswords(); - } - $scope.removePromptForPasswords = $scope.$on('PromptForPasswords', - function(e, passwords_needed_to_start,html, url) { - PromptForPasswords({ scope: $scope, - passwords: passwords_needed_to_start, - callback: 'CreateLaunchDialog', - html: html, - url: url - }); - }); - - if ($scope.removeContinueCred) { - $scope.removeContinueCred(); - } - $scope.removeContinueCred = $scope.$on('ContinueCred', function(e, - passwords) { - if(passwords.length>0){ - $scope.passwords_needed_to_start = passwords; - // only go through the password prompting steps if there are - // passwords to prompt for - $scope.$emit('PromptForPasswords', passwords, html, adhocUrl); - } else { - // if not, go straight to trying to run the job. - $scope.$emit('StartAdhocRun', adhocUrl); - } - }); - - // start adhoc launching routine - CheckPasswords({ - scope: $scope, - credential: $scope.credential, - callback: 'ContinueCred' - }); - }; - - $scope.lookupCredential = function(){ - $state.go('.credential', { - credential_search: { - credential_type: machineCredentialType, - page_size: '5', - page: '1' - } - }); - }; - -} - -export default ['$q', '$scope', '$stateParams', - '$state', 'CheckPasswords', 'PromptForPasswords', 'CreateLaunchDialog', 'CreateSelect2', - 'adhocForm', 'GenerateForm', 'Rest', 'ProcessErrors', 'GetBasePath', - 'GetChoices', 'KindChange', 'Wait', 'ParseTypeChange', 'machineCredentialType', - adhocController]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/adhoc/adhoc.form.js b/awx/ui/client/src/inventories-hosts/inventories/adhoc/adhoc.form.js deleted file mode 100644 index 1be324754ac4..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/adhoc/adhoc.form.js +++ /dev/null @@ -1,167 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name forms.function:Adhoc - * @description This form is for executing an adhoc command -*/ - -export default ['i18n', function(i18n) { - return { - addTitle: i18n._('EXECUTE COMMAND'), - name: 'adhoc', - well: true, - forceListeners: true, - - fields: { - module_name: { - label: i18n._('Module'), - excludeModal: true, - type: 'select', - ngOptions: 'module.label for module in adhoc_module_options' + - ' track by module.value', - ngChange: 'moduleChange()', - required: true, - awPopOver: i18n._('These are the modules that {{BRAND_NAME}} supports running commands against.'), - dataTitle: i18n._('Module'), - dataPlacement: 'right', - dataContainer: 'body' - }, - module_args: { - label: 'Arguments', - type: 'text', - awPopOverWatch: 'argsPopOver', - awPopOver: '{{ argsPopOver }}', - dataTitle: i18n._('Arguments'), - dataPlacement: 'right', - dataContainer: 'body', - autocomplete: false - }, - limit: { - label: i18n._('Limit'), - type: 'text', - - awPopOver: '

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 ' + - 'here.

', - dataTitle: i18n._('Limit'), - dataPlacement: 'right', - dataContainer: 'body' - }, - credential: { - label: i18n._('Machine Credential'), - type: 'lookup', - list: 'CredentialList', - basePath: 'credentials', - sourceModel: 'credential', - sourceField: 'name', - class: 'squeeze', - ngClick: 'lookupCredential()', - awPopOver: '

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.

', - dataTitle: i18n._('Credential'), - dataPlacement: 'right', - dataContainer: 'body', - awRequiredWhen: { - reqExpression: 'credRequired', - init: 'false' - } - }, - verbosity: { - label: i18n._('Verbosity'), - excludeModal: true, - type: 'select', - ngOptions: 'verbosity.label for verbosity in ' + - 'adhoc_verbosity_options ' + - 'track by verbosity.value', - required: true, - awPopOver:'

These are the verbosity levels for standard ' + - 'out of the command run that are supported.', - dataTitle: i18n._('Verbosity'), - dataPlacement: 'right', - dataContainer: 'body', - "default": 1 - }, - forks: { - label: i18n._('Forks'), - id: 'forks-number', - type: 'number', - integer: true, - min: 1, - spinner: true, - 'class': "input-small", - column: 1, - awPopOver: '

' + i18n.sprintf(i18n._('The number of parallel or simultaneous processes to use while executing the playbook. Inputting no value will use ' + - 'the default value from the %sansible configuration file%s.'), '' + - '', '') +'

', - placeholder: 'DEFAULT', - dataTitle: i18n._('Forks'), - dataPlacement: 'right', - dataContainer: "body" - }, - diff_mode: { - label: i18n._('Show Changes'), - type: 'toggleSwitch', - toggleSource: 'diff_mode', - dataTitle: i18n._('Show Changes'), - dataPlacement: 'right', - dataContainer: 'body', - awPopOver: "

" + i18n._("If enabled, show the changes made by Ansible tasks, where supported. This is equivalent to Ansible’s --diff mode.") + "

", - }, - become_enabled: { - label: i18n._('Enable Privilege Escalation'), - type: 'checkbox', - column: 2, - awPopOver: "

If enabled, run this playbook as an administrator. This is the equivalent of passing the --become option to the ansible command.

", - dataPlacement: 'right', - dataTitle: i18n._('Become Privilege Escalation'), - dataContainer: "body" - }, - extra_vars: { - label: i18n._('Extra Variables'), - type: 'textarea', - class: 'Form-textAreaLabel Form-formGroup--fullWidth', - rows: 6, - "default": "---", - column: 2, - awPopOver: "

" + i18n.sprintf(i18n._("Pass extra command line variables. This is the %s or %s command line parameter " + - "for %s. Provide key/value pairs using either YAML or JSON."), '-e', '--extra-vars', 'ansible') + "

" + - "JSON:
\n" + - "
{
 \"somevar\": \"somevalue\",
 \"password\": \"magic\"
}
\n" + - "YAML:
\n" + - "
---
somevar: somevalue
password: magic
\n", - dataTitle: i18n._('Extra Variables'), - dataPlacement: 'right', - dataContainer: "body" - } - }, - buttons: { - reset: { - ngClick: 'formReset()', - ngDisabled: true, - label: i18n._('Reset'), - 'class': 'btn btn-sm Form-cancelButton' - }, - launch: { - label: i18n._('Save'), - ngClick: 'launchJob()', - ngDisabled: true, - 'class': 'btn btn-sm List-buttonSubmit launchButton' - } - }, - - related: {} - }; -}]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/adhoc/adhoc.partial.html b/awx/ui/client/src/inventories-hosts/inventories/adhoc/adhoc.partial.html deleted file mode 100644 index 7d2a01483605..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/adhoc/adhoc.partial.html +++ /dev/null @@ -1 +0,0 @@ -
diff --git a/awx/ui/client/src/inventories-hosts/inventories/adhoc/adhoc.route.js b/awx/ui/client/src/inventories-hosts/inventories/adhoc/adhoc.route.js deleted file mode 100644 index 81ef6fa4c972..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/adhoc/adhoc.route.js +++ /dev/null @@ -1,50 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - import {templateUrl} from '../../../shared/template-url/template-url.factory'; - import { N_ } from '../../../i18n'; - - function ResolveMachineCredentialType (GetBasePath, Rest, ProcessErrors) { - Rest.setUrl(GetBasePath('credential_types') + '?kind=ssh'); - - return Rest.get() - .then(({ data }) => { - return data.results[0].id; - }) - .catch(({ data, status }) => { - ProcessErrors(null, data, status, null, { - hdr: 'Error!', - msg: 'Failed to get credential type data: ' + status - }); - }); -} - -ResolveMachineCredentialType.$inject = ['GetBasePath', 'Rest', 'ProcessErrors']; - -export default { - url: '/adhoc', - params:{ - pattern: { - value: 'all', - squash: true - } - }, - data: { - formChildState: true - }, - views: { - 'adhocForm@inventories': { - templateUrl: templateUrl('inventories-hosts/inventories/adhoc/adhoc'), - controller: 'adhocController' - } - }, - ncyBreadcrumb: { - label: N_("RUN COMMAND") - }, - resolve: { - machineCredentialType: ResolveMachineCredentialType, - } -}; diff --git a/awx/ui/client/src/inventories-hosts/inventories/adhoc/main.js b/awx/ui/client/src/inventories-hosts/inventories/adhoc/main.js deleted file mode 100644 index 1931e8f1d153..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/adhoc/main.js +++ /dev/null @@ -1,7 +0,0 @@ -import adhocController from './adhoc.controller'; -import form from './adhoc.form'; - -export default - angular.module('adhoc', []) - .controller('adhocController', adhocController) - .factory('adhocForm', form); diff --git a/awx/ui/client/src/inventories-hosts/inventories/insights/insights.block.less b/awx/ui/client/src/inventories-hosts/inventories/insights/insights.block.less deleted file mode 100644 index 6f3684ef5963..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/insights/insights.block.less +++ /dev/null @@ -1,145 +0,0 @@ -.InsightsLastCheck{ - display: flex; - justify-content: flex-end; - padding-bottom: 20px; - align-items: baseline; -} - -.InsightsNav{ - width: 100%; - display: flex; - border: 1px solid #B7B7B7; - border-radius:5px; - flex-wrap: wrap; - font-size: 14px; - font-weight: bold; -} - -.InsightsNav-rightSide{ - align-items: center; - display: flex; - flex: 1 0 auto; - flex-wrap: wrap; - max-width: 100%; - padding-left: 10px; -} - -.InsightsNav-leftSide{ - align-items: center; - display: flex; - flex: 1 0 auto; - justify-content: flex-end; - flex-wrap: wrap; - max-width: 100%; - padding-right: 10px; -} - -.InsightsNav-badgeTitle{ - color: #707070; - font-size: 14px; - margin-right: 10px; - font-weight: normal; - text-transform: uppercase; - margin-left: 10px; -} - -.InsightsIcon{ - height: 30px; - width:30px; -} - -.InsightsIcon-warning{ - color:@default-warning; - padding-right: 7px; -} - -.InsightsNav-anchor{ - display:flex; - align-items: center; - cursor:pointer; - height: 40px; - padding-right:10px; -} - -.InsightsNav-anchor.is-currentFilter{ - background-color: @f2grey; - padding-top: 5px; - border-bottom: 5px solid @b7grey; -} - -.InsightsNav-anchor:hover{ - background-color: @f2grey; - padding-top: 5px; - border-bottom: 5px solid @b7grey; -} - -.InsightsNav-totalIssues{ - background-color: @default-link; - color: @default-bg; -} - -.InsightsNav-criticalIssues{ - background-color: @default-err; -} - -.InsightsNav-highIssues{ - background-color:@default-warning; -} - -.InsightsNav-mediumIssues{ - background-color: @insights-yellow; -} - -.InsightsNav-lowIssues{ - background-color: @default-succ; -} - -.InsightsNav-solvableBadge{ - background-color: @b7grey; -} - -.InsightsNav-refresh{ - color: @default-icon; - cursor: pointer; - margin-left: 10px; - margin-right: 10px; -} - -.InsightsNav-refresh:hover{ - color: @default-link; -} - -.InsightsBody-missingIssues{ - color: @default-icon; - margin: 10px 0px 10px 0px; -} - -.InsightsRow{ - margin-top:10px; -} -.InsightsRow-title{ - display: flex; - align-items: center; -} - -.InsightsRow-description{ - font-size:14px; - font-weight: bold; - padding-left: 5px; -} - -.InsightsRow-category{ - margin-left: 10px; -} - -.InsightsRow-body{ - padding-left: 35px; -} - -.InsightsRow-plan{ - padding-left: 35px; -} - -.Insights-cancelButton{ - margin-left: 0px!important; -} diff --git a/awx/ui/client/src/inventories-hosts/inventories/insights/insights.controller.js b/awx/ui/client/src/inventories-hosts/inventories/insights/insights.controller.js deleted file mode 100644 index 5d2daa58b633..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/insights/insights.controller.js +++ /dev/null @@ -1,56 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default [ 'InsightsData', '$scope', 'moment', '$state', 'InventoryData', - 'InsightsService', 'CanRemediate', 'InsightsStrings', -function (data, $scope, moment, $state, InventoryData, InsightsService, - CanRemediate, strings) { - - $scope.strings = strings; - - function init() { - $scope.reports = (data && data.reports) ? data.reports : []; - $scope.reports_dataset = (data) ? data : {}; - $scope.currentFilter = "total"; - $scope.solvable_count = filter('solvable').length; - $scope.not_solvable_count = filter('not_solvable').length; - $scope.critical_count = filter('critical').length; - $scope.high_count = filter('high').length; - $scope.med_count = filter('medium').length; - $scope.low_count =filter('low').length; - let a = moment(), b = moment($scope.reports_dataset.last_check_in); - $scope.last_check_in = a.diff(b, 'hours'); - $scope.inventory = (InventoryData) ? InventoryData : {}; - $scope.insights_credential = (InventoryData && InventoryData.summary_fields && - InventoryData.summary_fields.insights_credential && InventoryData.summary_fields.insights_credential.id) ? - InventoryData.summary_fields.insights_credential.id : null; - $scope.canRemediate = CanRemediate; - $scope.platformId = $scope.reports_dataset.platform_id; - } - - function filter(str){ - return InsightsService.filter(str, $scope.reports_dataset.reports); - } - - init(); - - $scope.filterReports = function(str){ - $scope.currentFilter = str; - $scope.reports = filter(str); - }; - - $scope.viewDataInInsights = function(){ - window.open(`https://cloud.redhat.com/insights/inventory/${$scope.platformId}/insights`, '_blank'); - }; - - $scope.remediateInventory = function(inv_id, insights_credential){ - $state.go('templates.addJobTemplate', {inventory_id: inv_id, credential_id: insights_credential}); - }; - - $scope.formCancel = function(){ - $state.go('inventories', null, {reload: true}); - }; -}]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/insights/insights.partial.html b/awx/ui/client/src/inventories-hosts/inventories/insights/insights.partial.html deleted file mode 100644 index 0d696f1ede67..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/insights/insights.partial.html +++ /dev/null @@ -1,93 +0,0 @@ -
- - This machine has not checked in with Insights in {{last_check_in}} hours -
-
-
-
-
Total Issues
- {{reports_dataset.reports.length}} -
- -
-
Critical
- {{critical_count}} -
-
-
High
- {{high_count}} -
-
-
Medium
- {{med_count}} -
-
-
Low
- {{low_count}} -
-
-
- -
-
Solvable With Playbook
- {{solvable_count}} -
-
-
No Remediation Playbook Available
- {{not_solvable_count}} -
-
- - -
-
-
- -
-
- No data is available. There are no issues to report. -
-
- The Insights Credential for {{inventory.name}} was not found. -
-
-
- - - - -
ISSUE: {{report.rule.description}}
- {{report.rule.category}} -
-
{{report.rule.summary}}
-
-
-
-
-
- -
- - - -
diff --git a/awx/ui/client/src/inventories-hosts/inventories/insights/insights.route.js b/awx/ui/client/src/inventories-hosts/inventories/insights/insights.route.js deleted file mode 100644 index cd116fa06294..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/insights/insights.route.js +++ /dev/null @@ -1,98 +0,0 @@ -import {templateUrl} from '../../../shared/template-url/template-url.factory'; -import { N_ } from '../../../i18n'; - -export default { - url: '/insights', - ncyBreadcrumb: { - label: N_("INSIGHTS") - }, - views: { - 'related': { - controller: 'InsightsController', - templateUrl: templateUrl('inventories-hosts/inventories/insights/insights') - } - }, - resolve: { - InsightsData: ['Rest', '$stateParams', 'GetBasePath', 'ProcessErrors', - (Rest, $stateParams, GetBasePath, ProcessErrors) => { - var path = `${GetBasePath('hosts')}${$stateParams.host_id}/insights`; - Rest.setUrl(path); - return Rest.get() - .then(function(data) { - return (data.data.insights_content); - }).catch(function(response) { - ProcessErrors(null, response.data, response.status, null, { - hdr: 'Error!', - msg: 'Failed to get insights info. GET returned status: ' + - response.status - }); - }); - } - ], - InventoryData: ['Rest', '$stateParams', 'GetBasePath', 'ProcessErrors', 'resourceData', - (Rest, $stateParams, GetBasePath, ProcessErrors, resourceData) => { - if(resourceData.data.type === "host"){ - var path = `${GetBasePath('inventory')}${resourceData.data.inventory}`; - Rest.setUrl(path); - return Rest.get() - .then(function(data) { - return (data.data); - }).catch(function(response) { - ProcessErrors(null, response.data, response.status, null, { - hdr: 'Error!', - msg: 'Failed to get insights info. GET returned status: ' + - response.status - }); - }); - } - else if(resourceData.data.type === 'inventory'){ - return resourceData.data; - } - } - ], - checkProjectPermission: ['InventoryData', '$stateParams', 'Rest', 'GetBasePath', - function(InventoryData, $stateParams, Rest, GetBasePath){ - if(_.has(InventoryData, 'summary_fields.insights_credential')){ - let credential_id = InventoryData.summary_fields.insights_credential.id, - path = `${GetBasePath('projects')}?credential__id=${credential_id}&role_level=use_role`; - Rest.setUrl(path); - return Rest.get().then(({data}) => { - if (data.results.length > 0){ - return true; - } - else { - return false; - } - }).catch(() => { - return false; - }); - } - else { - return false; - } - }], - checkInventoryPermission: ['InventoryData', '$stateParams', 'Rest', 'GetBasePath', - function(InventoryData, $stateParams, Rest, GetBasePath){ - if(_.has(InventoryData, 'summary_fields.insights_credential')){ - let path = `${GetBasePath('inventory')}${InventoryData.id}/?role_level=use_role`; - Rest.setUrl(path); - return Rest.get().then(() => { - return true; - }).catch(() => { - return false; - }); - } - else { - return false; - } - }], - CanRemediate: ['checkProjectPermission', 'checkInventoryPermission', - function(checkProjectPermission, checkInventoryPermission){ - // the user can remediate an insights - // inv if the user has "use" permission on - // an insights project and the inventory - // being edited: - return checkProjectPermission === true && checkInventoryPermission === true; - }] - } -}; diff --git a/awx/ui/client/src/inventories-hosts/inventories/insights/insights.service.js b/awx/ui/client/src/inventories-hosts/inventories/insights/insights.service.js deleted file mode 100644 index 36082a71ed79..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/insights/insights.service.js +++ /dev/null @@ -1,36 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default [ () => { - var val = { - filter: function(str, reports_dataset){ - let filteredSet; - if(str === "total"){ - filteredSet = reports_dataset; - } - if(str === "solvable"){ - filteredSet = _.filter(reports_dataset, (report)=>{return (report.maintenance_actions.length > 0);}); - } - if(str === "not_solvable"){ - filteredSet = _.filter(reports_dataset, (report)=>{return (report.maintenance_actions.length === 0);}); - } - if(str === "critical"){ - filteredSet = _.filter(reports_dataset, (report)=>{return (report.rule.severity === 'CRITICAL');}); - } - if(str === "high"){ - filteredSet = _.filter(reports_dataset, (report)=>{return (report.rule.severity === 'ERROR');}); - } - if(str === "medium"){ - filteredSet = _.filter(reports_dataset, (report)=>{return (report.rule.severity === 'WARN');}); - } - if(str === "low"){ - filteredSet = _.filter(reports_dataset, (report)=>{return (report.rule.severity === 'INFO');}); - } - return filteredSet; - } - }; - return val; -}]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/insights/insights.strings.js b/awx/ui/client/src/inventories-hosts/inventories/insights/insights.strings.js deleted file mode 100644 index 26055effa204..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/insights/insights.strings.js +++ /dev/null @@ -1,14 +0,0 @@ -function InsightsStrings (BaseString) { - BaseString.call(this, 'instanceGroups'); - - const { t } = this; - const ns = this.instanceGroups; - - ns.tooltips = { - REFRESH_INSIGHTS: t.s('Refresh Insights'), - }; -} - -InsightsStrings.$inject = ['BaseStringService']; - -export default InsightsStrings; diff --git a/awx/ui/client/src/inventories-hosts/inventories/insights/main.js b/awx/ui/client/src/inventories-hosts/inventories/insights/main.js deleted file mode 100644 index 24a264a6f73b..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/insights/main.js +++ /dev/null @@ -1,17 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import controller from './insights.controller'; -import planFilter from './plan-filter'; -import service from './insights.service'; -import strings from './insights.strings'; - -export default -angular.module('insightsDashboard', []) - .filter('planFilter', planFilter) - .controller('InsightsController', controller) - .service('InsightsService', service) - .service('InsightsStrings', strings); diff --git a/awx/ui/client/src/inventories-hosts/inventories/insights/plan-filter.js b/awx/ui/client/src/inventories-hosts/inventories/insights/plan-filter.js deleted file mode 100644 index df023b75aae6..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/insights/plan-filter.js +++ /dev/null @@ -1,16 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - export default function(){ - return function(plan) { - if(plan === null || plan === undefined){ - return "PLAN: Not Available CREATE A NEW PLAN IN INSIGHTS"; - } else { - let name = (plan.name === null) ? "Unnamed Plan" : plan.name; - return `${name} (${plan.id})`; - } - }; - } diff --git a/awx/ui/client/src/inventories-hosts/inventories/inventories.block.less b/awx/ui/client/src/inventories-hosts/inventories/inventories.block.less deleted file mode 100644 index 60facf6c2c8c..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/inventories.block.less +++ /dev/null @@ -1,12 +0,0 @@ -.Inventories-hostStatus { - margin-left: 10px; -} -#inventories-panel { - .completed_jobsList.List-well { - margin: 0; - - .List-noItems { - margin: 0; - } - } -} diff --git a/awx/ui/client/src/inventories-hosts/inventories/inventories.partial.html b/awx/ui/client/src/inventories-hosts/inventories/inventories.partial.html deleted file mode 100644 index c9343ba6360e..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/inventories.partial.html +++ /dev/null @@ -1,21 +0,0 @@ -
- -
-
-
-
-
-
-
-
-
-
-
-
INVENTORIES
-
HOSTS
-
-
-
-
-
-
diff --git a/awx/ui/client/src/inventories-hosts/inventories/inventories.route.js b/awx/ui/client/src/inventories-hosts/inventories/inventories.route.js deleted file mode 100644 index 2fb842500956..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/inventories.route.js +++ /dev/null @@ -1,66 +0,0 @@ -import {templateUrl} from '../../shared/template-url/template-url.factory'; -import { N_ } from '../../i18n'; - -export default { - name: 'inventories', // top-most node in the generated tree (will replace this state definition) - route: '/inventories', - ncyBreadcrumb: { - label: N_('INVENTORIES') - }, - data: { - activityStream: true, - activityStreamTarget: 'inventory', - socket: { - "groups": { - inventories: ["status_changed"] - } - } - }, - views: { - '@': { - templateUrl: templateUrl('inventories-hosts/inventories/inventories') - }, - 'list@inventories': { - templateProvider: function(InventoryList, generateList) { - let html = generateList.build({ - list: InventoryList, - mode: 'edit' - }); - return html; - }, - controller: 'InventoryListController' - } - }, - searchPrefix: 'inventory', - resolve: { - Dataset: ['InventoryList', 'QuerySet', '$stateParams', 'GetBasePath', - function(list, qs, $stateParams, GetBasePath) { - let path = GetBasePath(list.basePath) || GetBasePath(list.name); - return qs.search(path, $stateParams[`${list.iterator}_search`]); - } - ], - canAdd: ['rbacUiControlService', function(rbacUiControlService) { - return rbacUiControlService.canAdd('inventory') - .then(function(res) { - return res.canAdd; - }) - .catch(function() { - return false; - }); - }], - InstanceGroupsData: ['Rest', 'GetBasePath', 'ProcessErrors', (Rest, GetBasePath, ProcessErrors) => { - const url = GetBasePath('instance_groups'); - Rest.setUrl(url); - return Rest.get() - .then(({data}) => { - return data.results.map((i) => ({name: i.name, id: i.id})); - }) - .catch(({data, status}) => { - ProcessErrors(null, data, status, null, { - hdr: 'Error!', - msg: 'Failed to get instance groups info. GET returned status: ' + status - }); - }); - }] - } -}; diff --git a/awx/ui/client/src/inventories-hosts/inventories/inventory.list.js b/awx/ui/client/src/inventories-hosts/inventories/inventory.list.js deleted file mode 100644 index e19c4d7e00fa..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/inventory.list.js +++ /dev/null @@ -1,136 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - -export default ['i18n', function(i18n) { - return { - - name: 'inventories', - iterator: 'inventory', - selectTitle: i18n._('Add Inventories'), - editTitle: i18n._('INVENTORIES'), - listTitle: i18n._('INVENTORIES'), - selectInstructions: i18n.sprintf(i18n._("Click on a row to select it, and click Finished when done. Click the %s button to create a new inventory."), " "), - index: false, - hover: true, - basePath: 'inventory', - title: false, - disableRow: "{{ inventory.pending_deletion }}", - disableRowValue: 'inventory.pending_deletion', - layoutClass: 'List-staticColumnLayout--toggleOnOff', - staticColumns: [ - { - field: 'status', - content: { - label: '', - nosort: true, - ngClick: "null", - iconOnly: true, - excludeModal: true, - template: ``, - icons: [{ - icon: "{{ 'icon-cloud-' + inventory.syncStatus }}", - awToolTip: "{{ inventory.syncTip }}", - awTipPlacement: "right", - ngClick: "showSourceSummary($event, inventory.id)", - ngClass: "inventory.launch_class" - },{ - icon: "{{ 'icon-job-' + inventory.hostsStatus }}", - awToolTip: false, - ngClick: "showHostSummary($event, inventory.id)" - }] - } - } - ], - - fields: { - name: { - key: true, - label: i18n._('Name'), - columnClass: 'col-md-4 col-sm-4 col-xs-8', - modalColumnClass: 'col-md-12', - awToolTip: "{{ inventory.description | sanitize }}", - awTipPlacement: "top", - uiSref: '{{inventory.linkToDetails}}' - }, - kind: { - label: i18n._('Type'), - ngBind: 'inventory.kind_label', - columnClass: 'd-none d-sm-flex col-sm-2' - }, - organization: { - label: i18n._('Organization'), - ngBind: 'inventory.summary_fields.organization.name', - linkTo: '/#/organizations/{{ inventory.organization }}', - sourceModel: 'organization', - sourceField: 'name', - excludeModal: true, - columnClass: 'd-none d-sm-flex col-md-3 col-sm-2' - } - }, - - actions: { - add: { - mode: 'all', // One of: edit, select, all - type: 'buttonDropdown', - basePaths: ['inventories'], - awToolTip: i18n._('Create a new inventory'), - actionClass: 'at-Button--add', - actionId: 'button-add', - options: [ - { - optionContent: i18n._('Inventory'), - optionSref: 'inventories.add', - ngShow: 'canAddInventory' - }, - { - optionContent: i18n._('Smart Inventory'), - optionSref: 'inventories.addSmartInventory', - ngShow: 'canAddInventory' - } - ], - ngShow: 'canAddInventory' - } - }, - - fieldActions: { - columnClass: 'col-md-3 col-sm-4 col-xs-4', - edit: { - label: i18n._('Edit'), - ngClick: 'editInventory(inventory)', - awToolTip: i18n._('Edit inventory'), - dataPlacement: 'top', - ngShow: '!inventory.pending_deletion && inventory.summary_fields.user_capabilities.edit' - }, - copy: { - label: i18n._('Copy'), - ngClick: 'copyInventory(inventory)', - awToolTip: "{{ inventory.copyTip }}", - dataTipWatch: "inventory.copyTip", - dataPlacement: 'top', - ngShow: '!inventory.pending_deletion && inventory.summary_fields.user_capabilities.copy', - ngClass: 'inventory.copyClass' - }, - view: { - label: i18n._('View'), - ngClick: 'editInventory(inventory)', - awToolTip: i18n._('View inventory'), - dataPlacement: 'top', - ngShow: '!inventory.summary_fields.user_capabilities.edit' - }, - "delete": { - label: i18n._('Delete'), - ngClick: "deleteInventory(inventory.id, inventory.name)", - awToolTip: i18n._('Delete inventory'), - dataPlacement: 'top', - ngShow: '!inventory.pending_deletion && inventory.summary_fields.user_capabilities.delete' - - }, - pending_deletion: { - label: i18n._('Pending Delete'), - } - } - };}]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.block.less b/awx/ui/client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.block.less deleted file mode 100644 index 325fbf2b25c7..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.block.less +++ /dev/null @@ -1,3 +0,0 @@ -.HostSummaryPopover-noSourceSummary { - margin-left: 15px; -} diff --git a/awx/ui/client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.controller.js b/awx/ui/client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.controller.js deleted file mode 100644 index 8ed36170ce19..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.controller.js +++ /dev/null @@ -1,29 +0,0 @@ -export default [ '$scope', 'Empty', 'Wait', 'GetBasePath', 'Rest', 'ProcessErrors', - function($scope, Empty, Wait, GetBasePath, Rest, ProcessErrors) { - - $scope.gatherRecentJobs = function(event) { - if (!Empty($scope.inventory.id)) { - if ($scope.inventory.total_hosts > 0) { - Wait('start'); - - let url = GetBasePath('unified_jobs') + '?'; - url += `&or__job__inventory=${$scope.inventory.id}`; - url += `&or__workflowjob__inventory=${$scope.inventory.id}`; - url += `&failed=${$scope.inventory.has_active_failures ? "true" : "false"}`; - url += "&order_by=-finished&page_size=5"; - - Rest.setUrl(url); - Rest.get() - .then(({data}) => { - $scope.generateTable(data, event); - }) - .catch(({data, status}) => { - ProcessErrors( $scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + ' failed. GET returned: ' + status - }); - }); - } - } - }; - } -]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.directive.js b/awx/ui/client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.directive.js deleted file mode 100644 index 9dc9bccb7f80..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.directive.js +++ /dev/null @@ -1,120 +0,0 @@ -export default ['templateUrl', 'Wait', '$filter', '$compile', 'i18n', '$log', - function(templateUrl, Wait, $filter, $compile, i18n, $log) { - return { - restrict: 'E', - replace: false, - scope: { - inventory: '=' - }, - controller: 'HostSummaryPopoverController', - templateUrl: templateUrl('inventories-hosts/inventories/list/host-summary-popover/host-summary-popover'), - link: function(scope) { - - function ellipsis(a) { - if (a.length > 20) { - return a.substr(0,20) + '...'; - } - return a; - } - - function attachElem(event, html, title) { - var elem = $(event.target).parent(); - - try { - elem.tooltip('hide'); - elem.popover('dispose'); - } - catch(err) { - $log.debug(err); - } - $('.popover').each(function() { - // remove lingering popover
. Seems to be a bug in TB3 RC1 - $(this).remove(); - }); - $('.tooltip').each( function() { - // close any lingering tooltips - $(this).hide(); - }); - elem.attr({ - "aw-pop-over": html, - "data-popover-title": title, - "data-placement": "right" }); - elem.removeAttr('ng-click'); - $compile(elem)(scope); - scope.triggerPopover(event); - } - - scope.generateTable = function(data, event){ - var html, title = (scope.inventory.has_active_failures) ? i18n._("Recent Failed Jobs") : i18n._("Recent Successful Jobs"); - Wait('stop'); - if (data.count > 0) { - html = ` - - - - - - - - - - `; - - data.results.forEach(function(row) { - let href = ''; - switch (row.type) { - case 'job': - case 'ad_hoc_command': - case 'system_job': - case 'project_update': - case 'inventory_update': - href = `#/jobs/${row.id}`; - break; - case 'workflow_job': - href = `#/workflows/${row.id}`; - break; - default: - break; - } - if ((scope.inventory.has_active_failures && row.status === 'failed') || (!scope.inventory.has_active_failures && row.status === 'successful')) { - html += ` - - - - - - `; - } - }); - html += `
${i18n._("Status")}${i18n._("Finished")}${i18n._("Name")}
- - - - ${($filter('longDate')(row.finished))} - ${$filter('sanitize')(ellipsis(row.name))} -
`; - } - else { - html = `

${i18n._("No recent job data available for this inventory.")}

`; - } - - attachElem(event, html, title); - }; - - scope.showHostSummary = function(event) { - try{ - var elem = $(event.target).parent(); - // if the popover is visible already, then exit the function here - if(elem.data()['bs.popover'].tip().hasClass('in')){ - return; - } - } - catch(err){ - scope.gatherRecentJobs(event); - } - }; - - } - }; - } -]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.partial.html b/awx/ui/client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.partial.html deleted file mode 100644 index bc0030a34409..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.partial.html +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/awx/ui/client/src/inventories-hosts/inventories/list/host-summary-popover/main.js b/awx/ui/client/src/inventories-hosts/inventories/list/host-summary-popover/main.js deleted file mode 100644 index e2b88a1d06de..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/list/host-summary-popover/main.js +++ /dev/null @@ -1,7 +0,0 @@ -import directive from './host-summary-popover.directive'; -import controller from './host-summary-popover.controller'; - -export default -angular.module('HostSummaryPopoverModule', []) - .directive('hostSummaryPopover', directive) - .controller('HostSummaryPopoverController', controller); diff --git a/awx/ui/client/src/inventories-hosts/inventories/list/inventory-list.controller.js b/awx/ui/client/src/inventories-hosts/inventories/list/inventory-list.controller.js deleted file mode 100644 index 85174c26520c..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/list/inventory-list.controller.js +++ /dev/null @@ -1,199 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name controllers.function:Inventories - * @description This controller's for the Inventory page - */ - -function InventoriesList($scope, - $filter, qs, InventoryList, Prompt, - ProcessErrors, GetBasePath, Wait, $state, - Dataset, canAdd, i18n, Inventory, InventoryHostsStrings, - ngToast) { - - let inventory = new Inventory(); - - let list = InventoryList, - defaultUrl = GetBasePath('inventory'); - - init(); - - function init(){ - $scope.canAddInventory = canAdd; - - $scope.$watchCollection(list.name, function(){ - _.forEach($scope[list.name], processInventoryRow); - }); - - // Search init - $scope.list = list; - $scope[`${list.iterator}_dataset`] = Dataset.data; - $scope[list.name] = $scope[`${list.iterator}_dataset`].results; - } - - function processInventoryRow(inventory) { - inventory.launch_class = ""; - - if (inventory.has_inventory_sources) { - inventory.copyTip = i18n._('Inventories with sources cannot be copied'); - inventory.copyClass = "btn-disabled"; - if (inventory.inventory_sources_with_failures > 0) { - inventory.syncStatus = 'error'; - inventory.syncTip = inventory.inventory_sources_with_failures + i18n._(' sources with sync failures. Click for details'); - } - else { - inventory.syncStatus = 'successful'; - inventory.syncTip = i18n._('No inventory sync failures. Click for details.'); - } - } - else { - inventory.copyTip = i18n._('Copy Inventory'); - inventory.copyClass = ""; - inventory.syncStatus = 'na'; - inventory.syncTip = i18n._('Not configured for inventory sync.'); - inventory.launch_class = "btn-disabled"; - } - - if (inventory.has_active_failures) { - inventory.hostsStatus = 'error'; - inventory.hostsTip = inventory.hosts_with_active_failures + i18n._(' hosts with failures. Click for details.'); - } - else if (inventory.total_hosts) { - inventory.hostsStatus = 'successful'; - inventory.hostsTip = i18n._('No hosts with failures. Click for details.'); - } - else { - inventory.hostsStatus = 'none'; - inventory.hostsTip = i18n._('Inventory contains 0 hosts.'); - } - - inventory.kind_label = inventory.kind === '' ? 'Inventory' : (inventory.kind === 'smart' ? i18n._('Smart Inventory'): i18n._('Inventory')); - - inventory.linkToDetails = (inventory.kind && inventory.kind === 'smart') ? `inventories.editSmartInventory({smartinventory_id:${inventory.id}})` : `inventories.edit({inventory_id:${inventory.id}})`; - } - - $scope.copyInventory = inventory => { - if (!inventory.has_inventory_sources) { - Wait('start'); - new Inventory('get', inventory.id) - .then(model => model.copy()) - .then(copiedInv => { - ngToast.success({ - content: ` -
-
- -
-
- ${InventoryHostsStrings.get('SUCCESSFUL_CREATION', copiedInv.name)} -
-
`, - dismissButton: false, - dismissOnTimeout: true - }); - $state.go('.', null, { reload: true }); - }) - .catch(({ data, status }) => { - const params = { hdr: 'Error!', msg: `Call to copy failed. Return status: ${status}` }; - ProcessErrors($scope, data, status, null, params); - }) - .finally(() => Wait('stop')); - } - }; - - $scope.editInventory = function (inventory, reload) { - const goOptions = reload ? { reload: true } : null; - if(inventory.kind && inventory.kind === 'smart') { - $state.go('inventories.editSmartInventory', {smartinventory_id: inventory.id}, goOptions); - } - else { - $state.go('inventories.edit', {inventory_id: inventory.id}, goOptions); - } - }; - - $scope.deleteInventory = function (id, name) { - var action = function () { - var url = defaultUrl + id + '/'; - Wait('start'); - $('#prompt-modal').modal('hide'); - inventory.request('delete', id) - .then(() => { - Wait('stop'); - }) - .catch(({data, status}) => { - ProcessErrors( $scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status - }); - }); - }; - - inventory.getDependentResourceCounts(id) - .then((counts) => { - const invalidateRelatedLines = []; - let deleteModalBody = `
${InventoryHostsStrings.get('deleteResource.CONFIRM', 'inventory')}
`; - - counts.forEach(countObj => { - if(countObj.count && countObj.count > 0) { - invalidateRelatedLines.push(`
${countObj.label}${countObj.count}
`); - } - }); - - if (invalidateRelatedLines && invalidateRelatedLines.length > 0) { - deleteModalBody = `
${InventoryHostsStrings.get('deleteResource.USED_BY', 'inventory')} ${InventoryHostsStrings.get('deleteResource.CONFIRM', 'inventory')}
`; - invalidateRelatedLines.forEach(invalidateRelatedLine => { - deleteModalBody += invalidateRelatedLine; - }); - } - - deleteModalBody += '
Note: ' + i18n._('The inventory will be in a pending status until the final delete is processed.') + '
'; - - Prompt({ - hdr: i18n._('Delete'), - resourceName: $filter('sanitize')(name), - body: deleteModalBody, - action: action, - actionText: i18n._('DELETE') - }); - }); - }; - - $scope.$on(`ws-inventories`, function(e, data){ - let inventory = $scope.inventories.find((inventory) => inventory.id === data.inventory_id); - if (data.status === 'pending_deletion' && inventory !== undefined) { - inventory.pending_deletion = true; - } - if (data.status === 'deleted') { - let reloadListStateParams = _.cloneDeep($state.params); - - if($scope.inventories.length === 1 && $state.params.inventory_search && _.hasIn($state, 'params.inventory_search.page') && $state.params.inventory_search.page !== '1') { - reloadListStateParams.inventory_search.page = (parseInt(reloadListStateParams.inventory_search.page)-1).toString(); - } - - if (parseInt($state.params.inventory_id) === data.inventory_id || parseInt($state.params.smartinventory_id) === data.inventory_id) { - $state.go("inventories", reloadListStateParams, {reload: true}); - } else { - Wait('start'); - $state.go('.', reloadListStateParams); - const path = GetBasePath($scope.list.basePath) || GetBasePath($scope.list.name); - qs.search(path, reloadListStateParams.inventory_search) - .then((searchResponse) => { - $scope.inventories_dataset = searchResponse.data; - $scope.inventories = searchResponse.data.results; - }) - .finally(() => Wait('stop')); - } - } - }); -} - -export default ['$scope', - '$filter', 'QuerySet', 'InventoryList', 'Prompt', - 'ProcessErrors', 'GetBasePath', 'Wait', - '$state', 'Dataset', 'canAdd', 'i18n', 'InventoryModel', - 'InventoryHostsStrings', 'ngToast', InventoriesList -]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/list/main.js b/awx/ui/client/src/inventories-hosts/inventories/list/main.js deleted file mode 100644 index 4df9ff834f70..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/list/main.js +++ /dev/null @@ -1,16 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import controller from './inventory-list.controller'; -import hostSummaryPopover from './host-summary-popover/main'; -import sourceSummaryPopover from './source-summary-popover/main'; - -export default -angular.module('InventoryList', [ - hostSummaryPopover.name, - sourceSummaryPopover.name - ]) - .controller('InventoryListController', controller); diff --git a/awx/ui/client/src/inventories-hosts/inventories/list/source-summary-popover/main.js b/awx/ui/client/src/inventories-hosts/inventories/list/source-summary-popover/main.js deleted file mode 100644 index 4b6659f51029..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/list/source-summary-popover/main.js +++ /dev/null @@ -1,7 +0,0 @@ -import directive from './source-summary-popover.directive'; -import controller from './source-summary-popover.controller'; - -export default -angular.module('SourceSummaryPopoverModule', []) - .directive('sourceSummaryPopover', directive) - .controller('SourceSummaryPopoverController', controller); diff --git a/awx/ui/client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.controller.js b/awx/ui/client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.controller.js deleted file mode 100644 index 70ae7aeacda3..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.controller.js +++ /dev/null @@ -1,27 +0,0 @@ -export default [ '$scope', 'Wait', 'Empty', 'Rest', 'ProcessErrors', '$state', - function($scope, Wait, Empty, Rest, ProcessErrors, $state) { - - $scope.gatherSourceJobs = function(event) { - if (!Empty($scope.inventory.id)) { - Wait('start'); - Rest.setUrl($scope.inventory.related.inventory_sources + '?order_by=-last_job_run&page_size=5'); - Rest.get() - .then(({data}) => { - $scope.generateTable(data, event); - }) - .catch(({data, status}) => { - ProcessErrors( $scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + $scope.inventory.related.inventory_sources + ' failed. GET returned status: ' + status - }); - }); - } - }; - - $scope.viewJob = function(url) { - // Pull the id out of the URL - var id = url.replace(/^\//, '').split('/')[3]; - $state.go('output', { id, type: 'inventory' } ); - }; - - } -]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.directive.js b/awx/ui/client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.directive.js deleted file mode 100644 index 0310e41146e7..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.directive.js +++ /dev/null @@ -1,98 +0,0 @@ -export default ['templateUrl', '$compile', 'Wait', '$filter', 'i18n', - function(templateUrl, $compile, Wait, $filter, i18n) { - return { - restrict: 'E', - replace: false, - scope: { - inventory: '=' - }, - controller: 'SourceSummaryPopoverController', - templateUrl: templateUrl('inventories-hosts/inventories/list/source-summary-popover/source-summary-popover'), - link: function(scope) { - - function ellipsis(a) { - if (a.length > 20) { - return a.substr(0,20) + '...'; - } - return a; - } - - function attachElem(event, html, title) { - var elem = $(event.target).parent(); - try { - elem.tooltip('hide'); - elem.popover('dispose'); - } - catch(err) { - //ignore - } - $('.popover').each(function() { - // remove lingering popover
. Seems to be a bug in TB3 RC1 - $(this).remove(); - }); - $('.tooltip').each( function() { - // close any lingering tool tipss - $(this).hide(); - }); - elem.attr({ - "aw-pop-over": html, - "data-popover-title": title, - "data-placement": "right" }); - elem.removeAttr('ng-click'); - $compile(elem)(scope); - scope.triggerPopover(event); - } - - scope.generateTable = function(data, event) { - var html, title; - - Wait('stop'); - - // Build the html for our popover - html = "\n"; - html += "\n"; - html += ""; - html += ""; - html += ""; - html += ""; - html += ""; - html += "\n"; - html += "\n"; - data.results.forEach( function(row) { - if (row.related.last_update) { - html += ""; - html += ``; - html += ""; - html += ""; - html += "\n"; - } - else { - html += ""; - html += ``; - html += ""; - html += ""; - html += "\n"; - } - }); - html += "\n"; - html += "
" + i18n._("Status") + "" + i18n._("Last Sync") + "" + i18n._("Source") + "
" + ($filter('longDate')(row.last_updated)) + "" + $filter('sanitize')(ellipsis(row.name)) + "
NA" + $filter('sanitize')(ellipsis(row.name)) + "
\n"; - title = i18n._("Sync Status"); - attachElem(event, html, title); - }; - - scope.showSourceSummary = function(event) { - try{ - var elem = $(event.target).parent(); - // if the popover is visible already, then exit the function here - if(elem.data()['bs.popover'].tip().hasClass('in')){ - return; - } - } - catch(err){ - scope.gatherSourceJobs(event); - } - }; - } - }; - } -]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.partial.html b/awx/ui/client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.partial.html deleted file mode 100644 index 87fd35ef0084..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.partial.html +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/awx/ui/client/src/inventories-hosts/inventories/main.js b/awx/ui/client/src/inventories-hosts/inventories/main.js deleted file mode 100644 index a3160d7b1ff1..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/main.js +++ /dev/null @@ -1,441 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ -import { N_ } from '../../i18n'; - -import adhoc from './adhoc/main'; -import group from './related/groups/main'; -import sources from './related/sources/main'; -import relatedHost from './related/hosts/main'; -import inventoryList from './list/main'; -import InventoryList from './inventory.list'; -import adHocRoute from './adhoc/adhoc.route'; -import insights from './insights/main'; -import completedJobsRoute from '~features/jobs/routes/inventoryCompletedJobs.route.js'; -import inventorySourceEditRoute from './related/sources/edit/sources-edit.route'; -import inventorySourceEditNotificationsRoute from './related/sources/edit/sources-notifications.route'; -import inventorySourceAddRoute from './related/sources/add/sources-add.route'; -import inventorySourceListRoute from './related/sources/list/sources-list.route'; -import inventorySourceListScheduleRoute from './related/sources/list/schedule/sources-schedule.route'; -import inventorySourceListScheduleAddRoute from './related/sources/list/schedule/sources-schedule-add.route'; -import inventorySourceListScheduleEditRoute from './related/sources/list/schedule/sources-schedule-edit.route'; -import adhocCredentialRoute from './adhoc/adhoc-credential.route'; -import inventoryGroupsList from './related/groups/list/groups-list.route'; -import inventoryGroupsAdd from './related/groups/add/groups-add.route'; -import inventoryGroupsEdit from './related/groups/edit/groups-edit.route'; -import groupNestedGroupsRoute from './related/groups/related/nested-groups/group-nested-groups.route'; -import hostNestedGroupsRoute from './related/hosts/related/nested-groups/host-nested-groups.route'; -import nestedGroupsAdd from './related/groups/related/nested-groups/group-nested-groups-add.route'; -import nestedHostsRoute from './related/groups/related/nested-hosts/group-nested-hosts.route'; -import inventoryHosts from './related/hosts/related-host.route'; -import smartInventoryHosts from './smart-inventory/smart-inventory-hosts.route'; -import inventoriesList from './inventories.route'; -import inventoryHostsAdd from './related/hosts/add/host-add.route'; -import inventoryHostsEdit from './related/hosts/edit/standard-host-edit.route'; -import smartInventoryHostsEdit from './related/hosts/edit/smart-host-edit.route'; -import ansibleFactsRoute from '../shared/ansible-facts/ansible-facts.route'; -import insightsRoute from './insights/insights.route'; -import inventorySourcesCredentialRoute from './related/sources/lookup/sources-lookup-credential.route'; -import inventorySourcesInventoryScriptRoute from './related/sources/lookup/sources-lookup-inventory-script.route'; -import inventorySourcesProjectRoute from './related/sources/lookup/sources-lookup-project.route'; -import SmartInventory from './smart-inventory/main'; -import StandardInventory from './standard-inventory/main'; -import hostNestedGroupsAssociateRoute from './related/hosts/related/nested-groups/host-nested-groups-associate.route'; -import groupNestedGroupsAssociateRoute from './related/groups/related/nested-groups/group-nested-groups-associate.route'; -import nestedHostsAssociateRoute from './related/groups/related/nested-hosts/group-nested-hosts-associate.route'; -import nestedHostsAddRoute from './related/groups/related/nested-hosts/group-nested-hosts-add.route'; -import hostCompletedJobsRoute from '~features/jobs/routes/hostCompletedJobs.route.js'; - -export default -angular.module('inventory', [ - adhoc.name, - group.name, - sources.name, - relatedHost.name, - inventoryList.name, - insights.name, - SmartInventory.name, - StandardInventory.name, - ]) - .factory('InventoryList', InventoryList) - .config(['$stateProvider', 'stateDefinitionsProvider', '$stateExtenderProvider', - function($stateProvider, stateDefinitionsProvider, $stateExtenderProvider) { - let stateDefinitions = stateDefinitionsProvider.$get(), - stateExtender = $stateExtenderProvider.$get(); - - function generateInventoryStates() { - - let standardInventoryAdd = stateDefinitions.generateTree({ - name: 'inventories.add', // top-most node in the generated tree (will replace this state definition) - url: '/inventory/add', - modes: ['add'], - form: 'InventoryForm', - controllers: { - add: 'InventoryAddController' - }, - resolve: { - add: { - canAdd: ['rbacUiControlService', '$state', function(rbacUiControlService, $state) { - return rbacUiControlService.canAdd('inventory') - .then(function(res) { - return res.canAdd; - }) - .catch(function() { - $state.go('inventories'); - }); - }] - } - } - }); - - let standardInventoryEdit = stateDefinitions.generateTree({ - name: 'inventories.edit', - url: '/inventory/:inventory_id', - modes: ['edit'], - form: 'InventoryForm', - controllers: { - edit: 'InventoryEditController' - }, - breadcrumbs: { - edit: '{{breadcrumb.inventory_name}}' - }, - data: { - activityStream: true, - activityStreamTarget: 'inventory' - }, - resolve: { - edit: { - smartInventoryRedirect: ['resourceData', '$state', '$stateParams', - function(resourceData, $state, $stateParams){ - if(resourceData.data.kind === "smart"){ - $state.go("inventories.editSmartInventory", {"smartinventory_id": $stateParams.inventory_id}, {reload: true}); - } - }], - InstanceGroupsData: ['$stateParams', 'Rest', 'GetBasePath', 'ProcessErrors', - function($stateParams, Rest, GetBasePath, ProcessErrors){ - let path = `${GetBasePath('inventory')}${$stateParams.inventory_id}/instance_groups/`; - Rest.setUrl(path); - return Rest.get() - .then(({data}) => { - if (data.results.length > 0) { - return data.results; - } - }) - .catch(({data, status}) => { - ProcessErrors(null, data, status, null, { - hdr: 'Error!', - msg: 'Failed to get instance groups. GET returned ' + - 'status: ' + status - }); - }); - }], - checkProjectPermission: ['resourceData', '$stateParams', 'Rest', 'GetBasePath', 'credentialTypesLookup', - function(resourceData, $stateParams, Rest, GetBasePath, credentialTypesLookup){ - if(_.has(resourceData, 'data.summary_fields.insights_credential')){ - return credentialTypesLookup() - .then(kinds => { - let insightsKind = kinds.insights; - let path = `${GetBasePath('projects')}?credential__credential_type=${insightsKind}&role_level=use_role`; - Rest.setUrl(path); - return Rest.get().then(({data}) => { - if (data.results.length > 0){ - return true; - } - else { - return false; - } - }).catch(() => { - return false; - }); - }); - } - else { - return false; - } - }], - checkInventoryPermission: ['resourceData', '$stateParams', 'Rest', 'GetBasePath', - function(resourceData, $stateParams, Rest, GetBasePath){ - if(_.has(resourceData, 'data.summary_fields.insights_credential')){ - let path = `${GetBasePath('inventory')}${$stateParams.inventory_id}/?role_level=use_role`; - Rest.setUrl(path); - return Rest.get().then(() => { - return true; - }).catch(() => { - return false; - }); - } - else { - return false; - } - }], - CanRemediate: ['checkProjectPermission', 'checkInventoryPermission', - function(checkProjectPermission, checkInventoryPermission){ - // the user can remediate an insights - // inv if the user has "use" permission on - // an insights project and the inventory - // being edited: - return checkProjectPermission === true && checkInventoryPermission === true; - }] - }, - - } - }); - - let smartInventoryAdd = stateDefinitions.generateTree({ - name: 'inventories.addSmartInventory', // top-most node in the generated tree (will replace this state definition) - url: '/smart/add?hostfilter', - modes: ['add'], - form: 'smartInventoryForm', - controllers: { - add: 'SmartInventoryAddController' - }, - resolve: { - add: { - canAdd: ['rbacUiControlService', '$state', function(rbacUiControlService, $state) { - return rbacUiControlService.canAdd('inventory') - .then(function(res) { - return res.canAdd; - }) - .catch(function() { - $state.go('inventories'); - }); - }] - } - } - }); - - let smartInventoryEdit = stateDefinitions.generateTree({ - name: 'inventories.editSmartInventory', - url: '/smart/:smartinventory_id', - modes: ['edit'], - form: 'smartInventoryForm', - controllers: { - edit: 'SmartInventoryEditController' - }, - breadcrumbs: { - edit: '{{breadcrumb.inventory_name}}' - }, - data: { - activityStream: true, - activityStreamTarget: 'inventory', - activityStreamId: 'smartinventory_id' - }, - resolve: { - edit: { - InstanceGroupsData: ['$stateParams', 'Rest', 'GetBasePath', 'ProcessErrors', - function($stateParams, Rest, GetBasePath, ProcessErrors){ - let path = `${GetBasePath('inventory')}${$stateParams.smartinventory_id}/instance_groups/`; - Rest.setUrl(path); - return Rest.get() - .then(({data}) => { - if (data.results.length > 0) { - return data.results; - } - }) - .catch(({data, status}) => { - ProcessErrors(null, data, status, null, { - hdr: 'Error!', - msg: 'Failed to get instance groups. GET returned ' + - 'status: ' + status - }); - }); - }] - } - } - }); - - let relatedHostsAnsibleFacts = _.cloneDeep(ansibleFactsRoute); - relatedHostsAnsibleFacts.name = 'inventories.edit.hosts.edit.ansible_facts'; - - let relatedHostsInsights = _.cloneDeep(insightsRoute); - relatedHostsInsights.name = 'inventories.edit.hosts.edit.insights'; - - let addSourceCredential = _.cloneDeep(inventorySourcesCredentialRoute); - addSourceCredential.name = 'inventories.edit.inventory_sources.add.credential'; - - let addSourceInventoryScript = _.cloneDeep(inventorySourcesInventoryScriptRoute); - addSourceInventoryScript.name = 'inventories.edit.inventory_sources.add.inventory_script'; - addSourceInventoryScript.url = '/inventory_script'; - - let editSourceCredential = _.cloneDeep(inventorySourcesCredentialRoute); - editSourceCredential.name = 'inventories.edit.inventory_sources.edit.credential'; - - let addSourceProject = _.cloneDeep(inventorySourcesProjectRoute); - addSourceProject.name = 'inventories.edit.inventory_sources.add.project'; - addSourceProject.url = '/project'; - - let editSourceProject = _.cloneDeep(inventorySourcesProjectRoute); - editSourceProject.name = 'inventories.edit.inventory_sources.edit.project'; - editSourceProject.url = '/project'; - - let editSourceInventoryScript = _.cloneDeep(inventorySourcesInventoryScriptRoute); - editSourceInventoryScript.name = 'inventories.edit.inventory_sources.edit.inventory_script'; - editSourceInventoryScript.url = '/inventory_script'; - - let inventoryCompletedJobsRoute = _.cloneDeep(completedJobsRoute); - inventoryCompletedJobsRoute.name = 'inventories.edit.completed_jobs'; - - let smartInventoryCompletedJobsRoute = _.cloneDeep(completedJobsRoute); - smartInventoryCompletedJobsRoute.name = 'inventories.editSmartInventory.completed_jobs'; - - let inventoryAdhocRoute = _.cloneDeep(adHocRoute); - inventoryAdhocRoute.name = 'inventories.edit.adhoc'; - - let smartInventoryAdhocRoute = _.cloneDeep(adHocRoute); - smartInventoryAdhocRoute.name = 'inventories.editSmartInventory.adhoc'; - - let inventoryAdhocCredential = _.cloneDeep(adhocCredentialRoute); - inventoryAdhocCredential.name = 'inventories.edit.adhoc.credential'; - - let smartInventoryAdhocCredential = _.cloneDeep(adhocCredentialRoute); - smartInventoryAdhocCredential.name = 'inventories.editSmartInventory.adhoc.credential'; - - let relatedHostCompletedJobs = _.cloneDeep(hostCompletedJobsRoute); - relatedHostCompletedJobs.name = 'inventories.edit.hosts.edit.completed_jobs'; - - let inventoryRootGroupsList = _.cloneDeep(inventoryGroupsList); - inventoryRootGroupsList.name = "inventories.edit.rootGroups"; - inventoryRootGroupsList.url = "/root_groups?{group_search:queryset}", - inventoryRootGroupsList.ncyBreadcrumb.label = N_("ROOT GROUPS");// jshint ignore:line - inventoryRootGroupsList.resolve.listDefinition = ['GroupList', (list) => { - const rootGroupList = _.cloneDeep(list); - rootGroupList.basePath = 'api/v2/inventories/{{$stateParams.inventory_id}}/root_groups/'; - rootGroupList.fields.name.uiSref = "inventories.edit.rootGroups.edit({group_id:group.id})"; - return rootGroupList; - }]; - - let inventoryRootGroupsAdd = _.cloneDeep(inventoryGroupsAdd); - inventoryRootGroupsAdd.name = "inventories.edit.rootGroups.add"; - inventoryRootGroupsAdd.ncyBreadcrumb.parent = "inventories.edit.rootGroups"; - - let inventoryRootGroupsEdit = _.cloneDeep(inventoryGroupsEdit); - inventoryRootGroupsEdit.name = "inventories.edit.rootGroups.edit"; - inventoryRootGroupsEdit.ncyBreadcrumb.parent = "inventories.edit.rootGroups"; - inventoryRootGroupsEdit.views = { - 'groupForm@inventories': { - templateProvider: function(GenerateForm, GroupForm) { - let form = _.cloneDeep(GroupForm); - form.activeEditState = 'inventories.edit.rootGroups.edit'; - form.detailsClick = "$state.go('inventories.edit.rootGroups.edit')"; - form.parent = 'inventories.edit.rootGroups'; - form.related.nested_groups.ngClick = "$state.go('inventories.edit.rootGroups.edit.nested_groups')"; - form.related.nested_hosts.ngClick = "$state.go('inventories.edit.rootGroups.edit.nested_hosts')"; - - return GenerateForm.buildHTML(form, { - mode: 'edit', - related: false - }); - }, - controller: 'GroupEditController' - } - }; - inventoryGroupsEdit.views = { - 'groupForm@inventories': { - templateProvider: function(GenerateForm, GroupForm) { - let form = GroupForm; - - return GenerateForm.buildHTML(form, { - mode: 'edit', - related: false - }); - }, - controller: 'GroupEditController' - } - }; - - let rootGroupNestedGroupsRoute = _.cloneDeep(groupNestedGroupsRoute); - rootGroupNestedGroupsRoute.name = 'inventories.edit.rootGroups.edit.nested_groups'; - rootGroupNestedGroupsRoute.ncyBreadcrumb.parent = "inventories.edit.rootGroups.edit"; - - let rootNestedGroupsAdd = _.cloneDeep(nestedGroupsAdd); - rootNestedGroupsAdd.name = "inventories.edit.rootGroups.edit.nested_groups.add"; - rootNestedGroupsAdd.ncyBreadcrumb.parent = "inventories.edit.groups.edit.nested_groups"; - - let rootGroupNestedGroupsAssociateRoute = _.cloneDeep(groupNestedGroupsAssociateRoute); - rootGroupNestedGroupsAssociateRoute.name = 'inventories.edit.rootGroups.edit.nested_groups.associate'; - - let rootGroupNestedHostsRoute = _.cloneDeep(nestedHostsRoute); - rootGroupNestedHostsRoute.name = 'inventories.edit.rootGroups.edit.nested_hosts'; - rootGroupNestedHostsRoute.ncyBreadcrumb.parent = "inventories.edit.rootGroups.edit"; - - let rootNestedHostsAdd = _.cloneDeep(nestedHostsAddRoute); - rootNestedHostsAdd.name = "inventories.edit.rootGroups.edit.nested_hosts.add"; - rootNestedHostsAdd.ncyBreadcrumb.parent = "inventories.edit.rootGroups.edit.nested_hosts"; - - let rootGroupNestedHostsAssociateRoute = _.cloneDeep(nestedHostsAssociateRoute); - rootGroupNestedHostsAssociateRoute.name = 'inventories.edit.rootGroups.edit.nested_hosts.associate'; - - return Promise.all([ - standardInventoryAdd, - standardInventoryEdit, - smartInventoryAdd, - smartInventoryEdit - ]).then((generated) => { - return { - states: _.reduce(generated, (result, definition) => { - return result.concat(definition.states); - }, [ - stateExtender.buildDefinition(inventoriesList), - stateExtender.buildDefinition(inventoryAdhocRoute), - stateExtender.buildDefinition(smartInventoryAdhocRoute), - stateExtender.buildDefinition(inventoryAdhocCredential), - stateExtender.buildDefinition(smartInventoryAdhocCredential), - stateExtender.buildDefinition(inventorySourceListScheduleRoute), - stateExtender.buildDefinition(inventorySourceListScheduleAddRoute), - stateExtender.buildDefinition(inventorySourceListScheduleEditRoute), - stateExtender.buildDefinition(relatedHostsAnsibleFacts), - stateExtender.buildDefinition(relatedHostsInsights), - stateExtender.buildDefinition(inventoryGroupsList), - stateExtender.buildDefinition(inventoryRootGroupsList), - stateExtender.buildDefinition(inventoryGroupsAdd), - stateExtender.buildDefinition(rootNestedGroupsAdd), - stateExtender.buildDefinition(inventoryRootGroupsAdd), - stateExtender.buildDefinition(inventoryGroupsEdit), - stateExtender.buildDefinition(inventoryRootGroupsEdit), - stateExtender.buildDefinition(groupNestedGroupsRoute), - stateExtender.buildDefinition(rootGroupNestedGroupsRoute), - stateExtender.buildDefinition(nestedHostsRoute), - stateExtender.buildDefinition(rootGroupNestedHostsRoute), - stateExtender.buildDefinition(inventoryHosts), - stateExtender.buildDefinition(smartInventoryHosts), - stateExtender.buildDefinition(inventoryHostsAdd), - stateExtender.buildDefinition(inventoryHostsEdit), - stateExtender.buildDefinition(smartInventoryHostsEdit), - stateExtender.buildDefinition(hostNestedGroupsRoute), - stateExtender.buildDefinition(inventorySourceListRoute), - stateExtender.buildDefinition(inventorySourceAddRoute), - stateExtender.buildDefinition(inventorySourceEditRoute), - stateExtender.buildDefinition(inventorySourceEditNotificationsRoute), - stateExtender.buildDefinition(inventoryCompletedJobsRoute), - stateExtender.buildDefinition(smartInventoryCompletedJobsRoute), - stateExtender.buildDefinition(addSourceCredential), - stateExtender.buildDefinition(addSourceInventoryScript), - stateExtender.buildDefinition(editSourceCredential), - stateExtender.buildDefinition(editSourceInventoryScript), - stateExtender.buildDefinition(addSourceProject), - stateExtender.buildDefinition(editSourceProject), - stateExtender.buildDefinition(groupNestedGroupsAssociateRoute), - stateExtender.buildDefinition(rootGroupNestedGroupsAssociateRoute), - stateExtender.buildDefinition(hostNestedGroupsAssociateRoute), - stateExtender.buildDefinition(nestedHostsAssociateRoute), - stateExtender.buildDefinition(rootGroupNestedHostsAssociateRoute), - stateExtender.buildDefinition(nestedGroupsAdd), - stateExtender.buildDefinition(nestedHostsAddRoute), - stateExtender.buildDefinition(rootNestedHostsAdd), - stateExtender.buildDefinition(relatedHostCompletedJobs) - ]) - }; - }); - - } - - $stateProvider.state({ - name: 'inventories.**', - url: '/inventories', - reloadOnSearch: true, - lazyLoad: () => generateInventoryStates() - }); - } - ]); diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/add/groups-add.controller.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/add/groups-add.controller.js deleted file mode 100644 index 80a1d917b738..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/add/groups-add.controller.js +++ /dev/null @@ -1,62 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['$state', '$stateParams', '$scope', 'GroupForm', - 'ParseTypeChange', 'GenerateForm', 'inventoryData', 'GroupsService', - 'GetChoices', 'GetBasePath', 'CreateSelect2', - 'rbacUiControlService', 'ToJSON', - function($state, $stateParams, $scope, GroupForm, ParseTypeChange, - GenerateForm, inventoryData, GroupsService, GetChoices, - GetBasePath, CreateSelect2, rbacUiControlService, - ToJSON) { - - let form = GroupForm; - init(); - - function init() { - // apply form definition's default field values - GenerateForm.applyDefaults(form, $scope); - - rbacUiControlService.canAdd(GetBasePath('inventory') + $stateParams.inventory_id + "/groups") - .then(function(canAdd) { - $scope.canAdd = canAdd; - }); - $scope.parseType = 'yaml'; - $scope.envParseType = 'yaml'; - ParseTypeChange({ - scope: $scope, - field_id: 'group_group_variables', - variable: 'group_variables', - }); - } - - $scope.formCancel = function() { - $state.go('^'); - }; - - $scope.formSave = function() { - var json_data; - json_data = ToJSON($scope.parseType, $scope.group_variables, true); - - var group = { - variables: json_data, - name: $scope.name, - description: $scope.description, - inventory: inventoryData.id - }; - - GroupsService.post(group).then(res => { - if ($stateParams.group_id && _.has(res, 'data')) { - return GroupsService.associateGroup(res.data, $stateParams.group_id) - .then(() => $state.go('^', null, { reload: true })); - } else if(_.has(res, 'data.id')){ - $state.go('^.edit', { group_id: res.data.id }, { reload: true }); - } - }); - - }; - } -]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/add/groups-add.route.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/add/groups-add.route.js deleted file mode 100644 index 7f9e58ff9f68..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/add/groups-add.route.js +++ /dev/null @@ -1,22 +0,0 @@ -import { N_ } from '../../../../../i18n'; - -export default { - name: "inventories.edit.groups.add", - url: "/add", - ncyBreadcrumb: { - parent: "inventories.edit.groups", - label: N_("CREATE GROUP") - }, - views: { - 'groupForm@inventories': { - templateProvider: function(GenerateForm, GroupForm) { - let form = GroupForm; - return GenerateForm.buildHTML(form, { - mode: 'add', - related: false - }); - }, - controller: 'GroupAddController' - } - }, -}; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/add/main.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/add/main.js deleted file mode 100644 index 8de2bc98de29..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/add/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import controller from './groups-add.controller'; - -export default -angular.module('groupAdd', []) - .controller('GroupAddController', controller); diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/edit/groups-edit.controller.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/edit/groups-edit.controller.js deleted file mode 100644 index a1870431598e..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/edit/groups-edit.controller.js +++ /dev/null @@ -1,61 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['$state', '$stateParams', '$scope', 'ParseVariableString', 'rbacUiControlService', 'ToJSON', - 'ParseTypeChange', 'GroupsService', 'GetChoices', 'GetBasePath', 'CreateSelect2', 'groupData', '$rootScope', - function($state, $stateParams, $scope, ParseVariableString, rbacUiControlService, ToJSON, - ParseTypeChange, GroupsService, GetChoices, GetBasePath, CreateSelect2, groupData, $rootScope) { - - init(); - - function init() { - rbacUiControlService.canAdd(GetBasePath('inventory') + $stateParams.inventory_id + "/groups") - .then(function(canAdd) { - $scope.canAdd = canAdd; - }); - - $scope = angular.extend($scope, groupData); - - $rootScope.breadcrumb.group_name = groupData.name; - - $scope.$watch('summary_fields.user_capabilities.edit', function(val) { - $scope.canAdd = val; - }); - - // init codemirror(s) - $scope.group_variables = $scope.variables === null || $scope.variables === '' ? '---' : ParseVariableString($scope.variables); - $scope.parseType = 'yaml'; - $scope.envParseType = 'yaml'; - - - ParseTypeChange({ - scope: $scope, - field_id: 'group_group_variables', - variable: 'group_variables', - readOnly: !$scope.summary_fields.user_capabilities.edit - }); - } - - $scope.formCancel = function() { - $state.go('^'); - }; - - $scope.formSave = function() { - var json_data; - json_data = ToJSON($scope.parseType, $scope.group_variables, true); - // group fields - var group = { - variables: json_data, - name: $scope.name, - description: $scope.description, - inventory: $scope.inventory, - id: groupData.id - }; - GroupsService.put(group).then(() => $state.go($state.current, null, { reload: true })); - }; - - } -]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/edit/groups-edit.route.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/edit/groups-edit.route.js deleted file mode 100644 index 6d1a670168ae..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/edit/groups-edit.route.js +++ /dev/null @@ -1,13 +0,0 @@ -export default { - name: "inventories.edit.groups.edit", - url: "/edit/:group_id", - ncyBreadcrumb: { - parent: "inventories.edit.groups", - label: "{{breadcrumb.group_name}}" - }, - resolve: { - groupData: ['$stateParams', 'GroupsService', function($stateParams, GroupsService) { - return GroupsService.get({ id: $stateParams.group_id }).then(response => response.data.results[0]); - }] - } -}; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/edit/main.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/edit/main.js deleted file mode 100644 index 0c52e2d6b974..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/edit/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import controller from './groups-edit.controller'; - -export default -angular.module('groupEdit', []) - .controller('GroupEditController', controller); diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/factories/get-hosts-status-msg.factory.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/factories/get-hosts-status-msg.factory.js deleted file mode 100644 index 43ddedb58494..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/factories/get-hosts-status-msg.factory.js +++ /dev/null @@ -1,33 +0,0 @@ -export default - ['i18n', function(i18n) { - return function(params) { - var active_failures = params.active_failures, - total_hosts = params.total_hosts, - tip, failures, html_class; - - // Return values for use on host status indicator - - if (active_failures > 0) { - tip = total_hosts + ((total_hosts === 1) ? ' host' : ' hosts') + '. ' + active_failures + i18n._(' with failed jobs.'); - html_class = 'error'; - failures = true; - } else { - failures = false; - if (total_hosts === 0) { - // no hosts - tip = i18n._("Contains 0 hosts."); - html_class = 'none'; - } else { - // many hosts with 0 failures - tip = total_hosts + ((total_hosts === 1) ? ' host' : ' hosts') + '. ' + i18n._('No job failures'); - html_class = 'success'; - } - } - - return { - tooltip: tip, - failures: failures, - 'class': html_class - }; - }; - }]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/groups.form.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/groups.form.js deleted file mode 100644 index 74448958b0d4..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/groups.form.js +++ /dev/null @@ -1,103 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name forms.function:Groups - * @description This form is for adding/editing a Group on the inventory page -*/ - -export default ['i18n', -function(i18n){ - return { - addTitle: i18n._('CREATE GROUP'), - editTitle: '{{ name }}', - showTitle: true, - name: 'group', - basePath: 'groups', - parent: 'inventories.edit.groups', - // the parent node this generated state definition tree expects to attach to - stateTree: 'inventories', - // form generator inspects the current state name to determine whether or not to set an active (.is-selected) class on a form tab - // this setting is optional on most forms, except where the form's edit state name is not parentStateName.edit - activeEditState: 'inventories.edit.groups.edit', - detailsClick: "$state.go('inventories.edit.groups.edit')", - well: false, - tabs: true, - fields: { - name: { - label: i18n._('Name'), - type: 'text', - ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)', - required: true, - tab: 'properties' - }, - description: { - label: i18n._('Description'), - type: 'text', - ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)', - tab: 'properties' - }, - group_variables: { - realName: 'variables', - label: i18n._('Variables'), - type: 'textarea', - class: 'Form-textAreaLabel Form-formGroup--fullWidth', - rows: 6, - 'default': '---', - dataTitle: i18n._('Group Variables'), - dataPlacement: 'right', - parseTypeName: 'parseType', - awPopOver: "

Variables defined here apply to all child groups and hosts.

" + - "

Enter variables using either JSON or YAML syntax. Use the " + - "radio button to toggle between the two.

" + - "JSON:
\n" + - "
{
  \"somevar\": \"somevalue\",
 \"password\": \"magic\"
}
\n" + - "YAML:
\n" + - "
---
somevar: somevalue
password: magic
\n" + - '

View JSON examples at www.json.org

' + - '

View YAML examples at docs.ansible.com

', - dataContainer: 'body', - tab: 'properties' - } - }, - - buttons: { - cancel: { - ngClick: 'formCancel()', - ngShow: '(group_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - close: { - ngClick: 'formCancel()', - ngShow: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - save: { - ngClick: 'formSave()', - ngDisabled: true, - ngShow: '(group_obj.summary_fields.user_capabilities.edit || canAdd)' - } - }, - related: { - nested_groups: { - name: 'nested_groups', - awToolTip: i18n._('Please save before defining groups.'), - dataPlacement: 'top', - ngClick: "$state.go('inventories.edit.groups.edit.nested_groups')", - title: i18n._('Groups'), - iterator: 'nested_group' - }, - nested_hosts: { - name: 'nested_hosts', - awToolTip: i18n._('Please save before defining hosts.'), - dataPlacement: 'top', - ngClick: "$state.go('inventories.edit.groups.edit.nested_hosts')", - include: "NestedHostsListDefinition", - title: i18n._('Hosts'), - iterator: 'nested_hosts' - }, - } - }; -}]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/groups.list.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/groups.list.js deleted file mode 100644 index 14b46e671577..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/groups.list.js +++ /dev/null @@ -1,129 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - export default ['i18n', function(i18n) { - return { - name: 'groups', - iterator: 'group', - editTitle: '{{ inventory.name }}', - well: true, - wellOverride: true, - index: false, - hover: true, - multiSelect: true, - trackBy: 'group.id', - basePath: 'api/v2/inventories/{{$stateParams.inventory_id}}/groups/', - layoutClass: 'List-staticColumnLayout--groups', - actionHolderClass: 'List-actionHolder List-actionHolder--rootGroups', - staticColumns: [ - { - field: 'failed_hosts', - content: { - label: '', - nosort: true, - mode: 'all', - iconOnly: true, - awToolTip: "{{ group.hosts_status_tip }}", - dataPlacement: "top", - icon: "{{ 'fa icon-job-' + group.hosts_status_class }}", - columnClass: 'status-column' - } - } - ], - - fields: { - name: { - label: i18n._('Groups'), - key: true, - uiSref: "inventories.edit.groups.edit({group_id:group.id})", - columnClass: 'col-lg-10 col-md-10 col-sm-10 col-xs-10', - } - }, - - actions: { - refresh: { - mode: 'all', - awToolTip: i18n._("Refresh the page"), - ngClick: "refreshGroups()", - ngShow: "socketStatus == 'error'", - actionClass: 'btn List-buttonDefault', - buttonContent: i18n._('REFRESH') - }, - groupsToggle: { - mode: 'all', - type: 'toggle', - buttons: [ - { - text: i18n._('ALL GROUPS'), - ngClick: "$state.go('inventories.edit.groups')", - ngClass: "{'btn-primary': $state.includes('inventories.edit.groups'), 'Button-primary--hollow': $state.includes('inventories.edit.rootGroups')}" - }, - { - text: i18n._('ROOT GROUPS'), - ngClick: "$state.go('inventories.edit.rootGroups')", - ngClass: "{'btn-primary': $state.includes('inventories.edit.rootGroups'), 'Button-primary--hollow': $state.includes('inventories.edit.groups')}" - } - ] - }, - launch: { - mode: 'all', - ngDisabled: '!groupsSelected', - ngClick: 'setAdhocPattern()', - awToolTip: i18n._("Select an inventory source by clicking the check box beside it. The inventory source can be a single group or a selection of multiple groups."), - dataPlacement: 'top', - actionClass: 'btn List-buttonDefault', - buttonContent: i18n._('RUN COMMANDS'), - showTipWhenDisabled: true, - tooltipInnerClass: "Tooltip-wide", - ngShow: 'canAdhoc' - // TODO: set up a tip watcher and change text based on when - // things are selected/not selected. This is started and - // commented out in the inventory controller within the watchers. - // awToolTip: "{{ adhocButtonTipContents }}", - // dataTipWatch: "adhocButtonTipContents" - }, - create: { - mode: 'all', - ngClick: "createGroup()", - awToolTip: i18n._("Create a new group"), - actionClass: 'at-Button--add', - actionId: 'button-add', - ngShow: 'canAdd', - dataPlacement: "top", - } - }, - - fieldActions: { - - columnClass: 'col-lg-2 col-md-2 col-sm-2 col-xs-2 text-right', - - edit: { - //label: 'Edit', - mode: 'all', - ngClick: "editGroup(group.id)", - awToolTip: i18n._('Edit group'), - dataPlacement: "top", - ngShow: "group.summary_fields.user_capabilities.edit" - }, - view: { - //label: 'Edit', - mode: 'all', - ngClick: "editGroup(group.id)", - awToolTip: i18n._('View group'), - dataPlacement: "top", - ngShow: "!group.summary_fields.user_capabilities.edit" - }, - "delete": { - //label: 'Delete', - mode: 'all', - ngClick: "deleteGroup(group)", - awToolTip: i18n._('Delete group'), - dataPlacement: "top", - ngShow: "group.summary_fields.user_capabilities.delete" - } - } - }; -}]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/list/groups-list.controller.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/list/groups-list.controller.js deleted file mode 100644 index f375e757eb91..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/list/groups-list.controller.js +++ /dev/null @@ -1,213 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - export default - ['$scope', '$state', '$stateParams', 'listDefinition', 'InventoryUpdate', - 'GroupsService', 'CancelSourceUpdate', - 'GetHostsStatusMsg', 'Dataset', 'inventoryData', 'canAdd', - 'InventoryHostsStrings', '$transitions', - function($scope, $state, $stateParams, listDefinition, InventoryUpdate, - GroupsService, CancelSourceUpdate, - GetHostsStatusMsg, Dataset, inventoryData, canAdd, - InventoryHostsStrings, $transitions){ - - let list = listDefinition; - - init(); - - function init(){ - $scope.inventory_id = $stateParams.inventory_id; - $scope.canAdhoc = inventoryData.summary_fields.user_capabilities.adhoc; - $scope.canAdd = canAdd; - - $scope.strings = { - deleteModal: {} - }; - - // Search init - $scope.list = list; - $scope[`${list.iterator}_dataset`] = Dataset.data; - $scope[list.name] = $scope[`${list.iterator}_dataset`].results; - - if($state.current.name === "inventories.edit.groups") { - $scope.rowBeingEdited = $state.params.group_id; - $scope.listBeingEdited = "groups"; - } - - $scope.inventory_id = $stateParams.inventory_id; - - $scope.$watchCollection(list.name, function(){ - _.forEach($scope[list.name], processRow); - }); - - $scope.$on('selectedOrDeselected', function(e, value) { - let item = value.value; - - if (value.isSelected) { - if(!$scope.groupsSelected) { - $scope.groupsSelected = []; - } - $scope.groupsSelected.push(item); - } else { - _.remove($scope.groupsSelected, { id: item.id }); - if($scope.groupsSelected.length === 0) { - $scope.groupsSelected = null; - } - } - }); - - } - - function processRow(group){ - if (group === undefined || group === null) { - group = {}; - } - - angular.forEach($scope.groupsSelected, function(selectedGroup){ - if(selectedGroup.id === group.id) { - group.isSelected = true; - } - }); - - let hosts_status; - - hosts_status = GetHostsStatusMsg({ - active_failures: group.hosts_with_active_failures, - total_hosts: group.total_hosts, - inventory_id: $scope.inventory_id, - group_id: group.id - }); - _.assign(group, - {hosts_status_tip: hosts_status.tooltip}, - {hosts_status_class: hosts_status.class}); - } - - $scope.createGroup = function(){ - if ($state.includes('inventories.edit.groups')) { - $state.go('inventories.edit.groups.add'); - } else if ($state.includes('inventories.edit.rootGroups')) { - $state.go('inventories.edit.rootGroups.add'); - } - }; - $scope.editGroup = function(id){ - if ($state.includes('inventories.edit.groups')) { - $state.go('inventories.edit.groups.edit', {group_id: id}); - } else if ($state.includes('inventories.edit.rootGroups')) { - $state.go('inventories.edit.rootGroups.edit', {group_id: id}); - } - }; - $scope.goToGroupGroups = function(id){ - $state.go('inventories.edit.groups.edit.nested_groups', {group_id: id}); - }; - $scope.deleteGroup = function(group){ - $scope.toDelete = {}; - $scope.strings.deleteModal = {}; - angular.extend($scope.toDelete, group); - if($scope.toDelete.total_groups === 0 && $scope.toDelete.total_hosts === 0) { - // This group doesn't have any child groups or hosts - the user is just trying to delete - // the group - $scope.deleteOption = "delete"; - } - else { - $scope.strings.deleteModal.group = InventoryHostsStrings.get('deletegroup.GROUP', $scope.toDelete.total_groups); - $scope.strings.deleteModal.host = InventoryHostsStrings.get('deletegroup.HOST', $scope.toDelete.total_hosts); - - if($scope.toDelete.total_groups === 0 || $scope.toDelete.total_hosts === 0) { - if($scope.toDelete.total_groups === 0) { - $scope.strings.deleteModal.deleteGroupsHosts = InventoryHostsStrings.get('deletegroup.DELETE_HOST', $scope.toDelete.total_hosts); - $scope.strings.deleteModal.promoteGroupsHosts = InventoryHostsStrings.get('deletegroup.PROMOTE_HOST', $scope.toDelete.total_hosts); - } - else if($scope.toDelete.total_hosts === 0) { - $scope.strings.deleteModal.deleteGroupsHosts = InventoryHostsStrings.get('deletegroup.DELETE_GROUP', $scope.toDelete.total_groups); - $scope.strings.deleteModal.promoteGroupsHosts = InventoryHostsStrings.get('deletegroup.PROMOTE_GROUP', $scope.toDelete.total_groups); - } - } - else { - $scope.strings.deleteModal.deleteGroupsHosts = InventoryHostsStrings.get('deletegroup.DELETE_GROUPS_AND_HOSTS', {groups: $scope.toDelete.total_groups, hosts: $scope.toDelete.total_hosts}); - $scope.strings.deleteModal.promoteGroupsHosts = InventoryHostsStrings.get('deletegroup.PROMOTE_GROUPS_AND_HOSTS', {groups: $scope.toDelete.total_groups, hosts: $scope.toDelete.total_hosts}); - } - } - - $('#group-delete-modal').modal('show'); - }; - $scope.confirmDelete = function(){ - let reloadListStateParams = null; - - if($scope.groups.length === 1 && $state.params.group_search && _.has($state, 'params.group_search.page') && $state.params.group_search.page !== '1') { - reloadListStateParams = _.cloneDeep($state.params); - reloadListStateParams.group_search.page = (parseInt(reloadListStateParams.group_search.page)-1).toString(); - } - - switch($scope.deleteOption){ - case 'promote': - GroupsService.promote($scope.toDelete.id, $stateParams.inventory_id) - .then(() => { - if (parseInt($state.params.group_id) === $scope.toDelete.id) { - $state.go("^", reloadListStateParams, {reload: true}); - } else { - $state.go($state.current, reloadListStateParams, {reload: true}); - } - setTimeout(function(){ - $('#group-delete-modal').modal('hide'); - $('body').removeClass('modal-open'); - $('.modal-backdrop').remove(); - }, 1000); - }); - break; - default: - GroupsService.delete($scope.toDelete.id).then(() => { - if (parseInt($state.params.group_id) === $scope.toDelete.id) { - $state.go("^", reloadListStateParams, {reload: true}); - } else { - $state.go($state.current, reloadListStateParams, {reload: true}); - } - setTimeout(function(){ - $('#group-delete-modal').modal('hide'); - $('body').removeClass('modal-open'); - $('.modal-backdrop').remove(); - }, 1000); - }); - } - }; - $scope.updateGroup = function(group) { - GroupsService.getInventorySource({group: group.id}).then(res =>InventoryUpdate({ - scope: $scope, - group_id: group.id, - url: res.data.results[0].related.update, - group_name: group.name, - group_source: res.data.results[0].source - })); - }; - - $scope.cancelUpdate = function (id) { - CancelSourceUpdate({ scope: $scope, id: id }); - }; - - var cleanUpStateChangeListener = $transitions.onSuccess({}, function(trans) { - if (trans.to().name === "inventories.edit.groups.edit") { - $scope.rowBeingEdited = trans.params('to').group_id; - $scope.listBeingEdited = "groups"; - } - else { - delete $scope.rowBeingEdited; - delete $scope.listBeingEdited; - } - }); - - // Remove the listener when the scope is destroyed to avoid a memory leak - $scope.$on('$destroy', function() { - cleanUpStateChangeListener(); - }); - - $scope.setAdhocPattern = function(){ - var pattern = _($scope.groupsSelected) - .map(function(item){ - return item.name; - }).value().join(':'); - - $state.go('inventories.edit.adhoc', {pattern: pattern}); - }; - - }]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html b/awx/ui/client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html deleted file mode 100644 index 23579b228595..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html +++ /dev/null @@ -1,57 +0,0 @@ - diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/list/groups-list.route.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/list/groups-list.route.js deleted file mode 100644 index 2ad7a90a0abd..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/list/groups-list.route.js +++ /dev/null @@ -1,73 +0,0 @@ -import { N_ } from '../../../../../i18n'; -import {templateUrl} from '../../../../../shared/template-url/template-url.factory'; - -export default { - name: "inventories.edit.groups", - url: "/groups?{group_search:queryset}", - resolve: { - listDefinition: ['GroupList', (list) => list], - Dataset: ['listDefinition', 'QuerySet', '$stateParams', 'GetBasePath', '$interpolate', '$rootScope', - (list, qs, $stateParams, GetBasePath, $interpolate, $rootScope) => { - // allow related list definitions to use interpolated $rootScope / $stateParams in basePath field - let path, interpolator; - if (GetBasePath(list.basePath)) { - path = GetBasePath(list.basePath); - } else { - interpolator = $interpolate(list.basePath); - path = interpolator({ $rootScope: $rootScope, $stateParams: $stateParams }); - } - return qs.search(path, $stateParams[`${list.iterator}_search`]); - } - ], - inventoryData: ['InventoriesService', '$stateParams', function(InventoriesService, $stateParams) { - return InventoriesService.getInventory($stateParams.inventory_id).then(res => res.data); - }], - canAdd: ['rbacUiControlService', '$state', 'GetBasePath', '$stateParams', function(rbacUiControlService, $state, GetBasePath, $stateParams) { - return rbacUiControlService.canAdd(GetBasePath('inventory') + $stateParams.inventory_id + "/groups") - .then(function(res) { - return res.canAdd; - }) - .catch(function() { - return false; - }); - }] - }, - params: { - group_search: { - value: { - page_size: "20", - order_by: "name" - }, - dynamic: true, - squash: "" - } - }, - ncyBreadcrumb: { - parent: "inventories.edit", - label: N_("ALL GROUPS") - }, - views: { - 'related': { - templateProvider: function(listDefinition, generateList, $templateRequest, $stateParams, GetBasePath) { - let list = _.cloneDeep(listDefinition); - if($stateParams && $stateParams.group) { - list.basePath = GetBasePath('groups') + _.last($stateParams.group) + '/children'; - } - else { - //reaches here if the user is on the root level group - list.basePath = GetBasePath('inventory') + $stateParams.inventory_id + '/groups'; - } - - let html = generateList.build({ - list: list, - mode: 'edit' - }); - // Include the custom group delete modal template - return $templateRequest(templateUrl('inventories-hosts/inventories/related/groups/list/groups-list')).then((template) => { - return html.concat(template); - }); - }, - controller: 'GroupsListController' - } - } -}; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/list/main.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/list/main.js deleted file mode 100644 index 6c030c986904..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/list/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import controller from './groups-list.controller'; - -export default - angular.module('groupsList', []) - .controller('GroupsListController', controller); diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/main.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/main.js deleted file mode 100644 index 4edf6ada5d7c..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/main.js +++ /dev/null @@ -1,26 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import groupList from './list/main'; -import groupAdd from './add/main'; -import groupEdit from './edit/main'; -import groupFormDefinition from './groups.form'; -import groupListDefinition from './groups.list'; -import GetHostsStatusMsg from './factories/get-hosts-status-msg.factory'; -import nestedGroups from './related/nested-groups/main'; -import nestedHosts from './related/nested-hosts/main'; - -export default - angular.module('group', [ - groupList.name, - groupAdd.name, - groupEdit.name, - nestedGroups.name, - nestedHosts.name - ]) - .factory('GroupForm', groupFormDefinition) - .factory('GroupList', groupListDefinition) - .factory('GetHostsStatusMsg', GetHostsStatusMsg); diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-add.controller.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-add.controller.js deleted file mode 100644 index 25633199bd0f..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-add.controller.js +++ /dev/null @@ -1,58 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['$state', '$stateParams', '$scope', 'NestedGroupForm', - 'ParseTypeChange', 'GenerateForm', 'inventoryData', 'GroupsService', - 'GetChoices', 'GetBasePath', 'CreateSelect2', - 'rbacUiControlService', 'ToJSON', 'canAdd', - function($state, $stateParams, $scope, NestedGroupForm, ParseTypeChange, - GenerateForm, inventoryData, GroupsService, GetChoices, - GetBasePath, CreateSelect2, rbacUiControlService, - ToJSON, canAdd) { - - let form = NestedGroupForm; - init(); - - function init() { - // apply form definition's default field values - GenerateForm.applyDefaults(form, $scope); - $scope.canAdd = canAdd; - $scope.parseType = 'yaml'; - $scope.envParseType = 'yaml'; - ParseTypeChange({ - scope: $scope, - field_id: 'nested_group_nested_group_variables', - variable: 'nested_group_variables', - }); - } - - $scope.formCancel = function() { - $state.go('^'); - }; - - $scope.formSave = function() { - var json_data; - json_data = ToJSON($scope.parseType, $scope.nested_group_variables, true); - - var group = { - variables: json_data, - name: $scope.name, - description: $scope.description, - inventory: inventoryData.id - }; - - GroupsService.post(group).then(res => { - if ($stateParams.group_id && _.has(res, 'data')) { - return GroupsService.associateGroup(res.data, $stateParams.group_id) - .then(() => $state.go('^', null, { reload: true })); - } else if(_.has(res, 'data.id')){ - $state.go('^.edit', { group_id: res.data.id }, { reload: true }); - } - }); - - }; - } -]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-add.route.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-add.route.js deleted file mode 100644 index 2aba06a2904c..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-add.route.js +++ /dev/null @@ -1,33 +0,0 @@ -import { N_ } from '../../../../../../i18n'; - -export default { - name: "inventories.edit.groups.edit.nested_groups.add", - url: "/add", - ncyBreadcrumb: { - parent: "inventories.edit.groups.edit.nested_groups", - label: N_("CREATE GROUP") - }, - views: { - 'nestedGroupForm@inventories': { - templateProvider: function(GenerateForm, NestedGroupForm) { - let form = NestedGroupForm; - return GenerateForm.buildHTML(form, { - mode: 'add', - related: false - }); - }, - controller: 'NestedGroupsAddController' - } - }, - resolve: { - canAdd: ['rbacUiControlService', '$state', 'GetBasePath', '$stateParams', function(rbacUiControlService, $state, GetBasePath, $stateParams) { - return rbacUiControlService.canAdd(GetBasePath('inventory') + $stateParams.inventory_id + "/groups") - .then(function(res) { - return res.canAdd; - }) - .catch(function() { - return false; - }); - }] - } -}; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-associate.route.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-associate.route.js deleted file mode 100644 index 0df6fdc481b4..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-associate.route.js +++ /dev/null @@ -1,32 +0,0 @@ -export default { - name: 'inventories.edit.groups.edit.nested_groups.associate', - squashSearchUrl: true, - url: '/associate', - ncyBreadcrumb:{ - skip:true - }, - views: { - 'modal@^.^': { - templateProvider: function() { - return ``; - }, - controller: function($scope, $q, GroupsService, $state){ - $scope.associateGroups = function(selectedItems){ - var deferred = $q.defer(); - return $q.all( _.map(selectedItems, (selectedItem) => GroupsService.associateGroup({id: selectedItem.id}, $state.params.group_id)) ) - .then( () =>{ - deferred.resolve(); - }, (error) => { - deferred.reject(error); - }); - }; - } - } - }, - onExit: function($state) { - if ($state.transition) { - $('#associate-groups-modal').modal('hide'); - $('body').removeClass('modal-open'); - } - }, -}; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-disassociate.partial.html b/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-disassociate.partial.html deleted file mode 100644 index 4b1db398831e..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-disassociate.partial.html +++ /dev/null @@ -1,35 +0,0 @@ - diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-list.controller.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-list.controller.js deleted file mode 100644 index 65961998d6b3..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-list.controller.js +++ /dev/null @@ -1,166 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - export default - ['$scope', '$rootScope', '$state', '$stateParams', 'NestedGroupListDefinition', 'InventoryUpdate', - 'GroupsService', 'CancelSourceUpdate', 'rbacUiControlService', 'GetBasePath', - 'GetHostsStatusMsg', 'Dataset', 'Find', 'QuerySet', 'inventoryData', 'canAdd', 'groupData', 'ProcessErrors', - '$transitions', - function($scope, $rootScope, $state, $stateParams, NestedGroupListDefinition, InventoryUpdate, - GroupsService, CancelSourceUpdate, rbacUiControlService, GetBasePath, - GetHostsStatusMsg, Dataset, Find, qs, inventoryData, canAdd, groupData, ProcessErrors, - $transitions){ - - let list = NestedGroupListDefinition; - - init(); - - function init(){ - $scope.inventory_id = $stateParams.inventory_id; - $scope.canAdhoc = inventoryData.summary_fields.user_capabilities.adhoc; - $scope.canAdd = canAdd; - $scope.disassociateFrom = groupData; - - // Search init - $scope.list = list; - $scope[`${list.iterator}_dataset`] = Dataset.data; - $scope[list.name] = $scope[`${list.iterator}_dataset`].results; - - if($state.current.name === "inventories.edit.groups.edit.nested_groups.edit") { - $scope.rowBeingEdited = $state.params.group_id; - $scope.listBeingEdited = "groups"; - } - - $scope.inventory_id = $stateParams.inventory_id; - - $scope.$watchCollection(list.name, function(){ - _.forEach($scope[list.name], processRow); - }); - - $scope.$on('selectedOrDeselected', function(e, value) { - let item = value.value; - - if (value.isSelected) { - if(!$scope.groupsSelected) { - $scope.groupsSelected = []; - } - $scope.groupsSelected.push(item); - } else { - _.remove($scope.groupsSelected, { id: item.id }); - if($scope.groupsSelected.length === 0) { - $scope.groupsSelected = null; - } - } - }); - - } - - function processRow(group){ - if (group === undefined || group === null) { - group = {}; - } - - angular.forEach($scope.groupsSelected, function(selectedGroup){ - if(selectedGroup.id === group.id) { - group.isSelected = true; - } - }); - - let hosts_status; - - hosts_status = GetHostsStatusMsg({ - active_failures: group.hosts_with_active_failures, - total_hosts: group.total_hosts, - inventory_id: $scope.inventory_id, - group_id: group.id - }); - _.assign(group, - {hosts_status_tip: hosts_status.tooltip}, - {hosts_status_class: hosts_status.class}); - } - - $scope.disassociateGroup = function(group){ - $scope.toDisassociate = {}; - angular.extend($scope.toDisassociate, group); - $('#group-disassociate-modal').modal('show'); - }; - - $scope.confirmDisassociate = function(){ - - // Bind an even listener for the modal closing. Trying to $state.go() before the modal closes - // will mean that these two things are running async and the modal may not finish closing before - // the state finishes transitioning. - $('#group-disassociate-modal').off('hidden.bs.modal').on('hidden.bs.modal', function () { - // Remove the event handler so that we don't end up with multiple bindings - $('#group-disassociate-modal').off('hidden.bs.modal'); - - let reloadListStateParams = null; - - if($scope.nested_groups.length === 1 && $state.params.nested_group_search && !_.isEmpty($state.params.nested_group_search.page) && $state.params.nested_group_search.page !== '1') { - reloadListStateParams = _.cloneDeep($state.params); - reloadListStateParams.nested_group_search.page = (parseInt(reloadListStateParams.nested_group_search.page)-1).toString(); - } - - // Reload the inventory manage page and show that the group has been removed - $state.go('.', reloadListStateParams, {reload: true}); - }); - - let closeModal = function(){ - $('#group-disassociate-modal').modal('hide'); - $('body').removeClass('modal-open'); - $('.modal-backdrop').remove(); - }; - - GroupsService.disassociateGroup($scope.toDisassociate.id, $scope.disassociateFrom.id) - .then(() => { - closeModal(); - }).catch((error) => { - closeModal(); - ProcessErrors(null, error.data, error.status, null, { - hdr: 'Error!', - msg: 'Failed to disassociate group from parent group: POST returned status' + - error.status - }); - }); - }; - - $scope.editGroup = function(id){ - if ($state.includes('inventories.edit.groups')) { - $state.go('inventories.edit.groups.edit', {group_id: id}); - } else if ($state.includes('inventories.edit.rootGroups')) { - $state.go('inventories.edit.rootGroups.edit', {group_id: id}); - } - }; - - $scope.goToGroupGroups = function(id){ - $state.go('inventories.edit.groups.edit.nested_groups', {group_id: id}); - }; - - var cleanUpStateChangeListener = $transitions.onSuccess({}, function(trans) { - if (trans.to().name === "inventories.edit.groups.edit.nested_groups.edit") { - $scope.rowBeingEdited = trans.params('to').group_id; - $scope.listBeingEdited = "groups"; - } - else { - delete $scope.rowBeingEdited; - delete $scope.listBeingEdited; - } - }); - - // Remove the listener when the scope is destroyed to avoid a memory leak - $scope.$on('$destroy', function() { - cleanUpStateChangeListener(); - }); - - $scope.setAdhocPattern = function(){ - var pattern = _($scope.groupsSelected) - .map(function(item){ - return item.name; - }).value().join(':'); - - $state.go('inventories.edit.adhoc', {pattern: pattern}); - }; - - }]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js deleted file mode 100644 index adc2cab8aba0..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js +++ /dev/null @@ -1,94 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name forms.function:Groups - * @description This form is for adding/editing a Group on the inventory page -*/ - -export default ['i18n', -function(i18n){ - return { - addTitle: i18n._('CREATE GROUP'), - editTitle: '{{ name }}', - showTitle: true, - name: 'nested_group', - iterator: "nested_group", - basePath: 'groups', - parent: 'inventories.edit.groups.edit.nested_groups', - // the parent node this generated state definition tree expects to attach to - stateTree: 'inventories', - // form generator inspects the current state name to determine whether or not to set an active (.is-selected) class on a form tab - // this setting is optional on most forms, except where the form's edit state name is not parentStateName.edit - activeEditState: 'inventories.edit.groups.edit.nested_groups.edit', - detailsClick: "$state.go('inventories.edit.groups.edit.nested_groups.edit')", - well: false, - tabs: true, - fields: { - name: { - label: i18n._('Name'), - type: 'text', - ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)', - required: true, - tab: 'properties' - }, - description: { - label: i18n._('Description'), - type: 'text', - ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)', - tab: 'properties' - }, - nested_group_variables: { - realName: 'variables', - label: i18n._('Variables'), - type: 'textarea', - class: 'Form-textAreaLabel Form-formGroup--fullWidth', - rows: 6, - 'default': '---', - dataTitle: i18n._('Group Variables'), - dataPlacement: 'right', - parseTypeName: 'parseType', - awPopOver: "

Variables defined here apply to all child groups and hosts.

" + - "

Enter variables using either JSON or YAML syntax. Use the " + - "radio button to toggle between the two.

" + - "JSON:
\n" + - "
{
  \"somevar\": \"somevalue\",
 \"password\": \"magic\"
}
\n" + - "YAML:
\n" + - "
---
somevar: somevalue
password: magic
\n" + - '

View JSON examples at www.json.org

' + - '

View YAML examples at docs.ansible.com

', - dataContainer: 'body', - tab: 'properties' - } - }, - - buttons: { - cancel: { - ngClick: 'formCancel()', - ngShow: '(group_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - close: { - ngClick: 'formCancel()', - ngShow: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - save: { - ngClick: 'formSave()', - ngDisabled: true, - ngShow: '(group_obj.summary_fields.user_capabilities.edit || canAdd)' - } - }, - related: { - nested_groups: { - name: 'related_groups', - ngClick: "$state.go('inventories.edit.groups.edit.related_groups')", - title: i18n._('Groups'), - iterator: 'related_group' - }, - - } - }; -}]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js deleted file mode 100644 index be7f9b5b083f..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js +++ /dev/null @@ -1,122 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - export default ['i18n', function(i18n) { - return { - name: 'nested_groups', - iterator: 'nested_group', - editTitle: '{{ inventory.name }}', - well: true, - wellOverride: true, - index: false, - hover: true, - multiSelect: true, - trackBy: 'nested_group.id', - basePath: 'api/v2/groups/{{$stateParams.group_id}}/children/', - layoutClass: 'List-staticColumnLayout--groups', - staticColumns: [ - { - field: 'failed_hosts', - content: { - label: '', - nosort: true, - mode: 'all', - iconOnly: true, - awToolTip: "{{ nested_group.hosts_status_tip }}", - dataPlacement: "top", - icon: "{{ 'fa icon-job-' + nested_group.hosts_status_class }}", - columnClass: 'status-column' - } - } - ], - - fields: { - name: { - label: i18n._('Groups'), - key: true, - uiSref: "inventories.edit.groups.edit({group_id:nested_group.id})", - columnClass: 'col-lg-6 col-md-6 col-sm-6 col-xs-6', - } - }, - - actions: { - refresh: { - mode: 'all', - awToolTip: i18n._("Refresh the page"), - ngClick: "refreshGroups()", - ngShow: "socketStatus == 'error'", - actionClass: 'btn List-buttonDefault', - buttonContent: i18n._('REFRESH') - }, - launch: { - mode: 'all', - ngDisabled: '!groupsSelected', - ngClick: 'setAdhocPattern()', - awToolTip: i18n._("Select an inventory source by clicking the check box beside it. The inventory source can be a single group or host, a selection of multiple hosts, or a selection of multiple groups."), - dataPlacement: 'top', - actionClass: 'btn List-buttonDefault', - buttonContent: i18n._('RUN COMMANDS'), - showTipWhenDisabled: true, - tooltipInnerClass: "Tooltip-wide", - ngShow: 'canAdhoc' - // TODO: set up a tip watcher and change text based on when - // things are selected/not selected. This is started and - // commented out in the inventory controller within the watchers. - // awToolTip: "{{ adhocButtonTipContents }}", - // dataTipWatch: "adhocButtonTipContents" - }, - add: { - mode: 'all', - type: 'buttonDropdown', - awToolTip: i18n._("Add a group"), - actionClass: 'at-Button--add', - actionId: 'button-add', - ngShow: 'canAdd', - dataPlacement: "top", - options: [ - { - optionContent: i18n._('Existing Group'), - optionSref: '.associate', - ngShow: 'canAdd' - }, - { - optionContent: i18n._('New Group'), - optionSref: '.add', - ngShow: 'canAdd' - } - ], - } - }, - - fieldActions: { - - columnClass: 'col-lg-6 col-md-6 col-sm-6 col-xs-6 text-right', - - edit: { - mode: 'all', - ngClick: "editGroup(nested_group.id)", - awToolTip: i18n._('Edit group'), - dataPlacement: "top", - ngShow: "nested_group.summary_fields.user_capabilities.edit" - }, - view: { - mode: 'all', - ngClick: "editGroup(nested_group.id)", - awToolTip: i18n._('View group'), - dataPlacement: "top", - ngShow: "!nested_group.summary_fields.user_capabilities.edit" - }, - "delete": { - mode: 'all', - ngClick: "disassociateGroup(nested_group)", - awToolTip: i18n._('Disassociate group'), - iconClass: 'fa fa-times', - dataPlacement: "top", - ngShow: "nested_group.summary_fields.user_capabilities.delete" - } - } - }; -}]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.route.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.route.js deleted file mode 100644 index 66495716aca7..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.route.js +++ /dev/null @@ -1,80 +0,0 @@ -import {templateUrl} from '../../../../../../shared/template-url/template-url.factory'; -import { N_ } from '../../../../../../i18n'; - -export default { - name: 'inventories.edit.groups.edit.nested_groups', - url: "/nested_groups?{nested_group_search:queryset}", - params: { - nested_group_search: { - value: { - page_size: "20", - order_by: "name" - }, - dynamic: true, - squash: "" - } - }, - ncyBreadcrumb: { - parent: "inventories.edit.groups.edit", - label: N_("ASSOCIATED GROUPS") - }, - views: { - // 'related@inventories.edit.groups.edit': { - 'related': { - templateProvider: function(NestedGroupListDefinition, generateList, $templateRequest) { - let list = _.cloneDeep(NestedGroupListDefinition); - - let html = generateList.build({ - list: list, - mode: 'edit' - }); - - return $templateRequest(templateUrl('inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-disassociate')).then((template) => { - return html.concat(template); - }); - }, - controller: 'NestedGroupsListController' - } - }, - resolve: { - Dataset: ['NestedGroupListDefinition', 'QuerySet', '$stateParams', 'GetBasePath', '$interpolate', '$rootScope', - (list, qs, $stateParams, GetBasePath, $interpolate, $rootScope) => { - // allow related list definitions to use interpolated $rootScope / $stateParams in basePath field - let path, interpolator; - if (GetBasePath(list.basePath)) { - path = GetBasePath(list.basePath); - } else { - interpolator = $interpolate(list.basePath); - path = interpolator({ $rootScope: $rootScope, $stateParams: $stateParams }); - } - if($stateParams.group_id){ - path = GetBasePath('groups') + $stateParams.group_id + '/children'; - } - else if($stateParams.host_id){ - path = GetBasePath('hosts') + $stateParams.host_id + '/all_groups'; - } - return qs.search(path, $stateParams[`${list.iterator}_search`]); - } - ], - host: ['$stateParams', 'HostsService', function($stateParams, HostsService) { - if($stateParams.host_id){ - return HostsService.get({ id: $stateParams.host_id }).then(function(res) { - return res.data.results[0]; - }); - } - }], - inventoryData: ['InventoriesService', '$stateParams', 'host', function(InventoriesService, $stateParams, host) { - var id = ($stateParams.inventory_id) ? $stateParams.inventory_id : host.summary_fields.inventory.id; - return InventoriesService.getInventory(id).then(res => res.data); - }], - canAdd: ['rbacUiControlService', '$state', 'GetBasePath', '$stateParams', function(rbacUiControlService, $state, GetBasePath, $stateParams) { - return rbacUiControlService.canAdd(GetBasePath('inventory') + $stateParams.inventory_id + "/groups") - .then(function(res) { - return res.canAdd; - }) - .catch(function() { - return false; - }); - }] - } -}; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/main.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/main.js deleted file mode 100644 index 445854d12095..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-groups/main.js +++ /dev/null @@ -1,17 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import nestedGroupListDefinition from './group-nested-groups.list'; -import controller from './group-nested-groups-list.controller'; -import addController from './group-nested-groups-add.controller'; -import NestedGroupForm from './group-nested-groups.form'; - -export default - angular.module('nestedGroups', []) - .factory('NestedGroupForm', NestedGroupForm) - .factory('NestedGroupListDefinition', nestedGroupListDefinition) - .controller('NestedGroupsListController', controller) - .controller('NestedGroupsAddController', addController); diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-add.controller.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-add.controller.js deleted file mode 100644 index 85019412e2d1..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-add.controller.js +++ /dev/null @@ -1,49 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['$state', '$stateParams', '$scope', 'RelatedHostsFormDefinition', 'ParseTypeChange', - 'GenerateForm', 'HostsService', 'ToJSON', 'canAdd', 'GroupsService', - function($state, $stateParams, $scope, RelatedHostsFormDefinition, ParseTypeChange, - GenerateForm, HostsService, ToJSON, canAdd, GroupsService) { - - init(); - - function init() { - $scope.canAdd = canAdd; - $scope.parseType = 'yaml'; - $scope.host = { enabled: true }; - // apply form definition's default field values - GenerateForm.applyDefaults(RelatedHostsFormDefinition, $scope); - - ParseTypeChange({ - scope: $scope, - field_id: 'host_host_variables', - variable: 'host_variables', - parse_variable: 'parseType' - }); - } - $scope.formCancel = function() { - $state.go('^'); - }; - $scope.toggleHostEnabled = function() { - $scope.host.enabled = !$scope.host.enabled; - }; - $scope.formSave = function(){ - var json_data = ToJSON($scope.parseType, $scope.host_variables, true), - params = { - variables: json_data,// $scope.variables === '---' || $scope.variables === '{}' ? null : $scope.variables, - name: $scope.name, - description: $scope.description, - enabled: $scope.host.enabled, - inventory: $stateParams.inventory_id - }; - HostsService.post(params).then(function(res) { - return GroupsService.associateHost(res.data, $stateParams.group_id) - .then(() => $state.go('^', null, { reload: true })); - }); - }; - } -]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-add.route.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-add.route.js deleted file mode 100644 index 438aa4a5f2d5..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-add.route.js +++ /dev/null @@ -1,33 +0,0 @@ -import { N_ } from '../../../../../../i18n'; - -export default { - name: "inventories.edit.groups.edit.nested_hosts.add", - url: "/add", - ncyBreadcrumb: { - parent: "inventories.edit.groups.edit.nested_hosts", - label: N_("CREATE HOST") - }, - views: { - 'hostForm@inventories': { - templateProvider: function(GenerateForm, RelatedHostsFormDefinition, NestedHostsFormDefinition, $stateParams) { - let form = ($stateParams.group_id) ? NestedHostsFormDefinition : RelatedHostsFormDefinition; - return GenerateForm.buildHTML(form, { - mode: 'add', - related: false - }); - }, - controller: 'NestedHostsAddController' - } - }, - resolve: { - canAdd: ['rbacUiControlService', 'GetBasePath', '$stateParams', function(rbacUiControlService, GetBasePath, $stateParams) { - return rbacUiControlService.canAdd(GetBasePath('inventory') + $stateParams.inventory_id + "/hosts") - .then(function(res) { - return res.canAdd; - }) - .catch(function() { - return false; - }); - }] - } -}; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-associate.route.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-associate.route.js deleted file mode 100644 index 220a5a428521..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-associate.route.js +++ /dev/null @@ -1,32 +0,0 @@ -export default { - name: 'inventories.edit.groups.edit.nested_hosts.associate', - squashSearchUrl: true, - url: '/associate', - ncyBreadcrumb:{ - skip:true - }, - views: { - 'modal@^.^': { - templateProvider: function() { - return ``; - }, - controller: function($scope, $q, GroupsService, $state){ - $scope.associateHosts = function(selectedItems){ - var deferred = $q.defer(); - return $q.all( _.map(selectedItems, (selectedItem) => GroupsService.associateHost({id: selectedItem.id}, $state.params.group_id)) ) - .then( () =>{ - deferred.resolve(); - }, (error) => { - deferred.reject(error); - }); - }; - } - } - }, - onExit: function($state) { - if ($state.transition) { - $('#associate-groups-modal').modal('hide'); - $('body').removeClass('modal-open'); - } - }, -}; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-disassociate.partial.html b/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-disassociate.partial.html deleted file mode 100644 index cf22da701eb6..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-disassociate.partial.html +++ /dev/null @@ -1,35 +0,0 @@ - diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-list.controller.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-list.controller.js deleted file mode 100644 index 07fe46ea9582..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-list.controller.js +++ /dev/null @@ -1,169 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['$scope', 'NestedHostsListDefinition', '$rootScope', 'GetBasePath', - 'rbacUiControlService', 'Dataset', '$state', '$filter', 'Prompt', 'Wait', - 'HostsService', 'SetStatus', 'canAdd', 'GroupsService', 'ProcessErrors', 'groupData', 'inventoryData', 'InventoryHostsStrings', - '$transitions', - function($scope, NestedHostsListDefinition, $rootScope, GetBasePath, - rbacUiControlService, Dataset, $state, $filter, Prompt, Wait, - HostsService, SetStatus, canAdd, GroupsService, ProcessErrors, groupData, inventoryData, InventoryHostsStrings, - $transitions) { - - let list = NestedHostsListDefinition; - - init(); - - function init(){ - $scope.canAdd = canAdd; - $scope.enableSmartInventoryButton = false; - $scope.disassociateFrom = groupData; - $scope.smartInventoryButtonTooltip = InventoryHostsStrings.get('smartinventorybutton.DISABLED_INSTRUCTIONS'); - - // Search init - $scope.list = list; - $scope[`${list.iterator}_dataset`] = Dataset.data; - $scope[list.name] = $scope[`${list.iterator}_dataset`].results; - - $scope.inventory_obj = inventoryData; - - $rootScope.flashMessage = null; - - $scope.$watchCollection(list.name, function() { - $scope[list.name] = _.map($scope.nested_hosts, function(value) { - angular.forEach(value.summary_fields.groups.results, function(directParentGroup) { - if(directParentGroup.id === parseInt($state.params.group_id)) { - value.can_disassociate = true; - } - }); - angular.forEach($scope.hostsSelected, function(selectedHost){ - if(selectedHost.id === value.id) { - value.isSelected = true; - } - }); - return value; - }); - setJobStatus(); - }); - - $transitions.onSuccess({}, function(trans) { - if(trans.params('to') && trans.params('to').host_search) { - let hasMoreThanDefaultKeys = false; - angular.forEach(trans.params('to').host_search, function(value, key) { - if(key !== 'order_by' && key !== 'page_size' && key !== 'page') { - hasMoreThanDefaultKeys = true; - } - }); - $scope.enableSmartInventoryButton = hasMoreThanDefaultKeys ? true : false; - $scope.smartInventoryButtonTooltip = hasMoreThanDefaultKeys ? InventoryHostsStrings.get('smartinventorybutton.ENABLED_INSTRUCTIONS') : InventoryHostsStrings.get('smartinventorybutton.DISABLED_INSTRUCTIONS'); - } - else { - $scope.enableSmartInventoryButton = false; - $scope.smartInventoryButtonTooltip = InventoryHostsStrings.get('smartinventorybutton.DISABLED_INSTRUCTIONS'); - } - }); - - $scope.$on('selectedOrDeselected', function(e, value) { - let item = value.value; - - if (value.isSelected) { - if(!$scope.hostsSelected) { - $scope.hostsSelected = []; - } - $scope.hostsSelected.push(item); - } else { - _.remove($scope.hostsSelected, { id: item.id }); - if($scope.hostsSelected.length === 0) { - $scope.hostsSelected = null; - } - } - }); - - } - - function setJobStatus(){ - _.forEach($scope.nested_hosts, function(value) { - SetStatus({ - scope: $scope, - host: value - }); - }); - } - - $scope.associateHost = function(){ - $state.go('inventories.edit.groups.edit.nested_hosts.associate'); - }; - $scope.editHost = function(id){ - $state.go('inventories.edit.hosts.edit', {host_id: id}); - }; - $scope.disassociateHost = function(host){ - $scope.toDisassociate = {}; - angular.extend($scope.toDisassociate, host); - $('#host-disassociate-modal').modal('show'); - }; - - $scope.confirmDisassociate = function(){ - - // Bind an even listener for the modal closing. Trying to $state.go() before the modal closes - // will mean that these two things are running async and the modal may not finish closing before - // the state finishes transitioning. - $('#host-disassociate-modal').off('hidden.bs.modal').on('hidden.bs.modal', function () { - // Remove the event handler so that we don't end up with multiple bindings - $('#host-disassociate-modal').off('hidden.bs.modal'); - - let reloadListStateParams = null; - - if($scope.nested_hosts.length === 1 && $state.params.nested_host_search && !_.isEmpty($state.params.nested_host_search.page) && $state.params.nested_host_search.page !== '1') { - reloadListStateParams = _.cloneDeep($state.params); - reloadListStateParams.nested_host_search.page = (parseInt(reloadListStateParams.nested_host_search.page)-1).toString(); - } - - // Reload the inventory manage page and show that the group has been removed - $state.go('.', reloadListStateParams, {reload: true}); - }); - - let closeModal = function(){ - $('#host-disassociate-modal').modal('hide'); - $('body').removeClass('modal-open'); - $('.modal-backdrop').remove(); - }; - - GroupsService.disassociateHost($scope.toDisassociate.id, $scope.disassociateFrom.id) - .then(() => { - closeModal(); - }).catch((error) => { - closeModal(); - ProcessErrors(null, error.data, error.status, null, { - hdr: 'Error!', - msg: 'Failed to disassociate host from group: POST returned status' + - error.status - }); - }); - }; - - $scope.toggleHost = function(event, host) { - try { - $(event.target).tooltip('hide'); - } catch (e) { - // ignore - } - - host.enabled = !host.enabled; - - HostsService.put(host).then(function(){ - $state.go($state.current, null, {reload: true}); - }); - }; - - $scope.setAdhocPattern = function(){ - var pattern = _($scope.hostsSelected) - .map(function(item){ - return item.name; - }).value().join(':'); - - $state.go('inventories.edit.adhoc', {pattern: pattern}); - }; -}]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js deleted file mode 100644 index 4f9d8512ab5c..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js +++ /dev/null @@ -1,118 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name forms.function:Hosts - * @description This form is for adding/editing a host on the inventory page -*/ - -export default ['i18n', -function(i18n) { - return { - - addTitle: i18n._('CREATE HOST'), - editTitle: '{{ host.name }}', - name: 'host', - basePath: 'hosts', - well: false, - formLabelSize: 'col-lg-3', - formFieldSize: 'col-lg-9', - iterator: 'host', - activeEditState: 'inventories.edit.groups.edit.nested_hosts.edit', - stateTree: 'inventories.edit.groups.edit.nested_hosts', - headerFields:{ - enabled: { - class: 'Form-header-field', - ngClick: 'toggleHostEnabled(host)', - type: 'toggle', - awToolTip: "

" + - i18n._("Indicates if a host is available and should be included in running jobs.") + - "

" + - i18n._("For hosts that are part of an external" + - " inventory, this may be" + - " reset by the inventory sync process.") + - "

", - dataTitle: i18n._('Host Enabled'), - } - }, - fields: { - name: { - label: i18n._('Host Name'), - type: 'text', - required: true, - awPopOver: "

" + - i18n._("Provide a host name, ip address, or ip address:port. Examples include:") + - "

" + - "
myserver.domain.com
" + - "127.0.0.1
" + - "10.1.0.140:25
" + - "server.example.com:25" + - "
", - dataTitle: i18n._('Host Name'), - dataPlacement: 'right', - dataContainer: 'body', - ngDisabled: '!(host.summary_fields.user_capabilities.edit || canAdd)' - }, - description: { - label: i18n._('Description'), - ngDisabled: '!(host.summary_fields.user_capabilities.edit || canAdd)', - type: 'text' - }, - host_variables: { - label: i18n._('Variables'), - type: 'textarea', - rows: 6, - class: 'Form-formGroup--fullWidth', - "default": "---", - awPopOver: "

" + i18n._("Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two.") + "

" + - "JSON:
\n" + - "
{
 \"somevar\": \"somevalue\",
 \"password\": \"magic\"
}
\n" + - "YAML:
\n" + - "
---
somevar: somevalue
password: magic
\n" + - '

' + i18n.sprintf(i18n._('View JSON examples at %s'), 'www.json.org') + '

' + - '

' + i18n.sprintf(i18n._('View YAML examples at %s'), 'docs.ansible.com') + '

', - dataTitle: i18n._('Host Variables'), - dataPlacement: 'right', - dataContainer: 'body' - } - }, - - buttons: { - cancel: { - ngClick: 'formCancel()', - ngShow: '(host.summary_fields.user_capabilities.edit || canAdd)' - }, - close: { - ngClick: 'formCancel()', - ngShow: '!(host.summary_fields.user_capabilities.edit || canAdd)' - }, - save: { - ngClick: 'formSave()', - ngDisabled: true, - ngShow: '(host.summary_fields.user_capabilities.edit || canAdd)' - } - }, - - related: { - ansible_facts: { - name: 'ansible_facts', - awToolTip: i18n._('Please save before viewing facts.'), - dataPlacement: 'top', - title: i18n._('Facts'), - skipGenerator: true - }, - nested_groups: { - name: 'nested_groups', - awToolTip: i18n._('Please save before defining groups.'), - dataPlacement: 'top', - ngClick: "$state.go('inventories.edit.groups.edit.nested_hosts.edit.nested_groups')", - title: i18n._('Groups'), - iterator: 'nested_group' - } - } - }; - }]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js deleted file mode 100644 index d0722dcf9e42..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js +++ /dev/null @@ -1,155 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['i18n', function(i18n) { - return { - name: 'nested_hosts', - iterator: 'nested_host', - editTitle: '{{ nested_host.name }}', // i don't think this is correct - // showTitle: false, - well: true, - wellOverride: true, - index: false, - hover: true, - // hasChildren: true, - multiSelect: true, - trackBy: 'nested_host.id', - basePath: 'api/v2/groups/{{$stateParams.group_id}}/all_hosts/', - layoutClass: 'List-staticColumnLayout--hostsWithCheckbox', - staticColumns: [ - { - field: 'toggleHost', - content: { - ngDisabled: '!nested_host.summary_fields.user_capabilities.edit', - label: '', - columnClass: 'List-staticColumn--toggle', - type: "toggle", - ngClick: "toggleHost($event, nested_host)", - awToolTip: "

" + - i18n._("Indicates if a host is available and should be included in running jobs.") + - "

" + - i18n._("For hosts that are part of an external" + - " inventory, this flag may be" + - " reset by the inventory sync process.") + - "

", - dataPlacement: "right", - nosort: true, - } - }, - { - field: 'active_failures', - content: { - label: '', - iconOnly: true, - nosort: true, - // do not remove this ng-click directive - // the list generator case to handle fields without ng-click - // cannot handle the aw-* directives - ngClick: 'noop()', - awPopOver: "{{ nested_host.job_status_html }}", - dataTitle: "{{ nested_host.job_status_title }}", - awToolTip: "{{ nested_host.badgeToolTip }}", - dataPlacement: 'top', - icon: "{{ 'fa icon-job-' + nested_host.active_failures }}", - id: 'active-failures-action', - columnClass: 'status-column List-staticColumn--smallStatus' - } - } - ], - - fields: { - name: { - key: true, - label: i18n._('Hosts'), - uiSref: "inventories.edit.hosts.edit({host_id: nested_host.id})", - ngClass: "{ 'host-disabled-label': !nested_host.enabled }", - columnClass: 'col-lg-4 col-md-8 col-sm-8 col-xs-7', - dataHostId: "{{ nested_host.id }}", - dataType: "nested_host", - }, - description: { - label: i18n._('Description'), - columnClass: 'd-none d-lg-flex col-lg-4', - template: ` -
- {{ nested_host.description }} -
- ` - }, - }, - - fieldActions: { - - columnClass: 'col-lg-4 col-md-4 col-sm-4 col-xs-5 text-right', - edit: { - ngClick: "editHost(nested_host.id)", - icon: 'icon-edit', - awToolTip: i18n._('Edit host'), - dataPlacement: 'top', - ngShow: 'nested_host.summary_fields.user_capabilities.edit' - }, - view: { - ngClick: "editHost(nested_host.id)", - awToolTip: i18n._('View host'), - dataPlacement: 'top', - ngShow: '!nested_host.summary_fields.user_capabilities.edit' - }, - "delete": { - //label: 'Delete', - ngClick: "disassociateHost(nested_host)", - iconClass: 'fa fa-times', - awToolTip: i18n._('Disassociate host'), - dataPlacement: 'top', - ngShow: 'nested_host.summary_fields.user_capabilities.delete' - } - }, - - actions: { - launch: { - mode: 'all', - ngDisabled: '!hostsSelected', - ngClick: 'setAdhocPattern()', - awToolTip: i18n._("Select an inventory source by clicking the check box beside it. The inventory source can be a single group or host, a selection of multiple hosts, or a selection of multiple groups."), - dataPlacement: 'top', - actionClass: 'btn List-buttonDefault', - buttonContent: i18n._('RUN COMMANDS'), - showTipWhenDisabled: true, - tooltipInnerClass: "Tooltip-wide", - // TODO: we don't always want to show this - ngShow: 'inventory_obj.summary_fields.user_capabilities.adhoc' - }, - refresh: { - mode: 'all', - awToolTip: i18n._("Refresh the page"), - ngClick: "refreshGroups()", - ngShow: "socketStatus == 'error'", - actionClass: 'btn List-buttonDefault', - buttonContent: i18n._('REFRESH') - }, - add: { - mode: 'all', - type: 'buttonDropdown', - awToolTip: i18n._("Add a host"), - actionClass: 'at-Button--add', - actionId: 'button-add', - ngShow: 'canAdd', - dataPlacement: "top", - options: [ - { - optionContent: i18n._('Existing Host'), - optionSref: '.associate', - ngShow: 'canAdd' - }, - { - optionContent: i18n._('New Host'), - optionSref: '.add', - ngShow: 'canAdd' - } - ], - } - } - }; -}]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.route.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.route.js deleted file mode 100644 index 456da422d936..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.route.js +++ /dev/null @@ -1,66 +0,0 @@ -import { N_ } from '../../../../../../i18n'; -import {templateUrl} from '../../../../../../shared/template-url/template-url.factory'; - -export default { - name: "inventories.edit.groups.edit.nested_hosts", - url: "/nested_hosts?{nested_host_search:queryset}", - params: { - nested_host_search: { - value: { - page_size: "20", - order_by: "name" - }, - dynamic: true, - squash: "" - } - }, - ncyBreadcrumb: { - parent: "inventories.edit.groups.edit", - label: N_("ASSOCIATED HOSTS") - }, - views: { - // 'related@inventories.edit.groups.edit': { - 'related': { - templateProvider: function(NestedHostsListDefinition, generateList, $templateRequest) { - let list = _.cloneDeep(NestedHostsListDefinition); - - let html = generateList.build({ - list: list, - mode: 'edit' - }); - return $templateRequest(templateUrl('inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-disassociate')).then((template) => { - return html.concat(template); - }); - }, - controller: 'NestedHostsListController' - } - }, - resolve: { - Dataset: ['NestedHostsListDefinition', 'QuerySet', '$stateParams', 'GetBasePath', '$interpolate', '$rootScope', - (list, qs, $stateParams, GetBasePath, $interpolate, $rootScope) => { - // allow related list definitions to use interpolated $rootScope / $stateParams in basePath field - let path, interpolator; - if (GetBasePath(list.basePath)) { - path = GetBasePath(list.basePath); - } else { - interpolator = $interpolate(list.basePath); - path = interpolator({ $rootScope: $rootScope, $stateParams: $stateParams }); - } - path = `api/v2/groups/${$stateParams.group_id}/all_hosts`; - return qs.search(path, $stateParams[`${list.iterator}_search`]); - } - ], - inventoryData: ['InventoriesService', '$stateParams', function(InventoriesService, $stateParams) { - return InventoriesService.getInventory($stateParams.inventory_id).then(res => res.data); - }], - canAdd: ['rbacUiControlService', function(rbacUiControlService) { - return rbacUiControlService.canAdd('hosts') - .then(function(res) { - return res.canAdd; - }) - .catch(function() { - return false; - }); - }] - } -}; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/main.js b/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/main.js deleted file mode 100644 index 6905795f3d8a..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/main.js +++ /dev/null @@ -1,17 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import nestedHostsListDefinition from './group-nested-hosts.list'; -import nestedHostsFormDefinition from './group-nested-hosts.form'; -import controller from './group-nested-hosts-list.controller'; -import addController from './group-nested-hosts-add.controller'; - -export default - angular.module('nestedHosts', []) - .factory('NestedHostsListDefinition', nestedHostsListDefinition) - .factory('NestedHostsFormDefinition', nestedHostsFormDefinition) - .controller('NestedHostsAddController', addController) - .controller('NestedHostsListController', controller); diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/add/host-add.controller.js b/awx/ui/client/src/inventories-hosts/inventories/related/hosts/add/host-add.controller.js deleted file mode 100644 index 37f3eefc8f7a..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/add/host-add.controller.js +++ /dev/null @@ -1,41 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['$state', '$stateParams', '$scope', 'RelatedHostsFormDefinition', - 'GenerateForm', 'HostsService', 'ToJSON', 'canAdd', - function($state, $stateParams, $scope, RelatedHostsFormDefinition, - GenerateForm, HostsService, ToJSON, canAdd) { - - init(); - - function init() { - $scope.canAdd = canAdd; - $scope.host = { enabled: true }; - // apply form definition's default field values - GenerateForm.applyDefaults(RelatedHostsFormDefinition, $scope); - } - $scope.formCancel = function() { - $state.go('^'); - }; - $scope.toggleHostEnabled = function() { - $scope.host.enabled = !$scope.host.enabled; - }; - $scope.formSave = function(){ - var json_data = ToJSON($scope.parseType, $scope.host_variables, true), - params = { - variables: json_data,// $scope.variables === '---' || $scope.variables === '{}' ? null : $scope.variables, - name: $scope.name, - description: $scope.description, - enabled: $scope.host.enabled, - inventory: $stateParams.inventory_id - }; - HostsService.post(params).then(function(res) { - $state.go('^.edit', { host_id: res.data.id }, { reload: true }); - }) - .catch(function(){}); - }; - } -]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/add/host-add.route.js b/awx/ui/client/src/inventories-hosts/inventories/related/hosts/add/host-add.route.js deleted file mode 100644 index 7621eeb045c4..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/add/host-add.route.js +++ /dev/null @@ -1,22 +0,0 @@ -import { N_ } from '../../../../../i18n'; - -export default { - name: "inventories.edit.hosts.add", - url: "/add", - ncyBreadcrumb: { - parent: "inventories.edit.hosts", - label: N_("CREATE HOST") - }, - views: { - 'hostForm@inventories': { - templateProvider: function(GenerateForm, RelatedHostsFormDefinition) { - let form = RelatedHostsFormDefinition; - return GenerateForm.buildHTML(form, { - mode: 'add', - related: false - }); - }, - controller: 'RelatedHostAddController' - } - }, -}; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/add/main.js b/awx/ui/client/src/inventories-hosts/inventories/related/hosts/add/main.js deleted file mode 100644 index 7a55327c9f5b..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/add/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import controller from './host-add.controller'; - -export default -angular.module('relatedHostsAdd', []) - .controller('RelatedHostAddController', controller); diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/edit/host-edit.controller.js b/awx/ui/client/src/inventories-hosts/inventories/related/hosts/edit/host-edit.controller.js deleted file mode 100644 index 098a6113a2fc..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/edit/host-edit.controller.js +++ /dev/null @@ -1,43 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - export default - ['$scope', '$state', 'HostsService', 'host', '$rootScope', - function($scope, $state, HostsService, host, $rootScope){ - $scope.isSmartInvHost = $state.includes('inventories.editSmartInventory.hosts.edit'); - $scope.parseType = 'yaml'; - $scope.formCancel = function(){ - $state.go('^', null, {reload: true}); - }; - $scope.toggleHostEnabled = function(){ - $scope.host.enabled = !$scope.host.enabled; - }; - $scope.toggleEnabled = function(){ - $scope.host.enabled = !$scope.host.enabled; - }; - $scope.formSave = function(){ - var host = { - id: $scope.host.id, - variables: $scope.host_variables === '---' || $scope.host_variables === '{}' ? null : $scope.host_variables, - name: $scope.name, - description: $scope.description, - enabled: $scope.host.enabled - }; - HostsService.put(host).then(function(){ - $state.go('.', null, {reload: true}); - }); - - }; - var init = function(){ - $scope.host = host; - $scope.name = host.name; - $rootScope.breadcrumb.host_name = host.name; - $scope.description = host.description; - $scope.host_variables = host.variables; - }; - - init(); - }]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/edit/main.js b/awx/ui/client/src/inventories-hosts/inventories/related/hosts/edit/main.js deleted file mode 100644 index 9536f686dc51..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/edit/main.js +++ /dev/null @@ -1,10 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ -import controller from './host-edit.controller'; - -export default -angular.module('relatedHostEdit', []) - .controller('RelatedHostEditController', controller); diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/edit/smart-host-edit.route.js b/awx/ui/client/src/inventories-hosts/inventories/related/hosts/edit/smart-host-edit.route.js deleted file mode 100644 index 033e6054cae9..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/edit/smart-host-edit.route.js +++ /dev/null @@ -1,29 +0,0 @@ -export default { - name: "inventories.editSmartInventory.hosts.edit", - url: "/edit/:host_id", - ncyBreadcrumb: { - parent: "inventories.editSmartInventory.hosts", - label: "{{breadcrumb.host_name}}" - }, - views: { - 'hostForm@inventories': { - templateProvider: function(GenerateForm, RelatedHostsFormDefinition) { - let form = _.cloneDeep(RelatedHostsFormDefinition); - form.stateTree = 'inventories.editSmartInventory.hosts'; - delete form.related; - return GenerateForm.buildHTML(form, { - mode: 'edit', - related: false - }); - }, - controller: 'RelatedHostEditController' - } - }, - resolve: { - host: ['$stateParams', 'InventoriesService', function($stateParams, InventoriesService) { - return InventoriesService.getHost($stateParams.smartinventory_id, $stateParams.host_id).then(function(res) { - return res.data.results[0]; - }); - }] - } -}; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/edit/standard-host-edit.route.js b/awx/ui/client/src/inventories-hosts/inventories/related/hosts/edit/standard-host-edit.route.js deleted file mode 100644 index 432037658ff4..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/edit/standard-host-edit.route.js +++ /dev/null @@ -1,25 +0,0 @@ -export default { - name: "inventories.edit.hosts.edit", - url: "/edit/:host_id", - ncyBreadcrumb: { - parent: "inventories.edit.hosts", - label: "{{breadcrumb.host_name}}" - }, - views: { - 'hostForm@inventories': { - templateProvider: function(GenerateForm, RelatedHostsFormDefinition) { - let form = RelatedHostsFormDefinition; - return GenerateForm.buildHTML(form, { - mode: 'edit', - related: false - }); - }, - controller: 'RelatedHostEditController' - } - }, - resolve: { - host: ['$stateParams', 'HostsService', function($stateParams, HostsService) { - return HostsService.get({ id: $stateParams.host_id }).then((response) => response.data.results[0]); - }] - } -}; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/list/host-list.controller.js b/awx/ui/client/src/inventories-hosts/inventories/related/hosts/list/host-list.controller.js deleted file mode 100644 index cf794632471a..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/list/host-list.controller.js +++ /dev/null @@ -1,167 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -// import HostsService from './../hosts/host.service'; -export default ['$scope', 'ListDefinition', '$rootScope', 'GetBasePath', - 'rbacUiControlService', 'Dataset', '$state', '$filter', 'Prompt', 'Wait', - 'HostsService', 'SetStatus', 'canAdd', 'i18n', 'InventoryHostsStrings', '$transitions', - function($scope, ListDefinition, $rootScope, GetBasePath, - rbacUiControlService, Dataset, $state, $filter, Prompt, Wait, - HostsService, SetStatus, canAdd, i18n, InventoryHostsStrings, $transitions) { - - let list = ListDefinition; - - init(); - - function init(){ - $scope.canAdd = canAdd; - $scope.enableSmartInventoryButton = false; - $scope.smartInventoryButtonTooltip = InventoryHostsStrings.get('smartinventorybutton.DISABLED_INSTRUCTIONS'); - - // Search init - $scope.list = list; - $scope[`${list.iterator}_dataset`] = Dataset.data; - $scope[list.name] = $scope[`${list.iterator}_dataset`].results; - - $rootScope.flashMessage = null; - - $scope.$watchCollection(list.name, function() { - $scope[list.name] = _.map($scope.hosts, function(value) { - value.inventory_name = value.summary_fields.inventory.name; - value.inventory_id = value.summary_fields.inventory.id; - angular.forEach($scope.hostsSelected, function(selectedHost){ - if(selectedHost.id === value.id) { - value.isSelected = true; - } - }); - return value; - }); - setJobStatus(); - }); - - $transitions.onSuccess({}, function(trans) { - if(trans.params('to') && trans.params('to').host_search) { - let hasMoreThanDefaultKeys = false; - angular.forEach(trans.params('to').host_search, function(value, key) { - if(key !== 'order_by' && key !== 'page_size' && key !== 'page') { - hasMoreThanDefaultKeys = true; - } - }); - $scope.enableSmartInventoryButton = hasMoreThanDefaultKeys ? true : false; - $scope.smartInventoryButtonTooltip = hasMoreThanDefaultKeys ? InventoryHostsStrings.get('smartinventorybutton.ENABLED_INSTRUCTIONS') : InventoryHostsStrings.get('smartinventorybutton.DISABLED_INSTRUCTIONS'); - } - else { - $scope.enableSmartInventoryButton = false; - $scope.smartInventoryButtonTooltip = InventoryHostsStrings.get('smartinventorybutton.DISABLED_INSTRUCTIONS'); - } - }); - - $scope.$on('selectedOrDeselected', function(e, value) { - let item = value.value; - - if (value.isSelected) { - if(!$scope.hostsSelected) { - $scope.hostsSelected = []; - } - $scope.hostsSelected.push(item); - } else { - _.remove($scope.hostsSelected, { id: item.id }); - if($scope.hostsSelected.length === 0) { - $scope.hostsSelected = null; - } - } - }); - - } - - function setJobStatus(){ - _.forEach($scope.hosts, function(value) { - SetStatus({ - scope: $scope, - host: value - }); - }); - } - - $scope.createHost = function(){ - $state.go('inventories.edit.hosts.add'); - }; - $scope.editHost = function(host){ - if($state.includes('inventories.edit.hosts')) { - $state.go('inventories.edit.hosts.edit', {host_id: host.id}); - } - else if($state.includes('inventories.editSmartInventory.hosts')) { - $state.go('inventories.editSmartInventory.hosts.edit', {host_id: host.id}); - } - }; - $scope.goToInsights = function(host){ - $state.go('inventories.edit.hosts.edit.insights', {inventory_id: host.inventory_id, host_id:host.id}); - }; - $scope.deleteHost = function(id, name){ - var body = '
' + i18n._('Are you sure you want to permanently delete the host below from the inventory?') + '
' + $filter('sanitize')(name) + '
'; - var action = function(){ - delete $rootScope.promptActionBtnClass; - Wait('start'); - HostsService.delete(id).then(() => { - $('#prompt-modal').modal('hide'); - - let reloadListStateParams = null; - - if($scope.hosts.length === 1 && $state.params.host_search && _.has($state, 'params.host_search.page') && $state.params.host_search.page !== '1') { - reloadListStateParams = _.cloneDeep($state.params); - reloadListStateParams.host_search.page = (parseInt(reloadListStateParams.host_search.page)-1).toString(); - } - - if (parseInt($state.params.host_id) === id) { - $state.go('^', reloadListStateParams, {reload: true}); - } else { - $state.go('.', reloadListStateParams, {reload: true}); - } - Wait('stop'); - }); - }; - // Prompt depends on having $rootScope.promptActionBtnClass available... - Prompt({ - hdr: i18n._('Delete Host'), - body: body, - action: action, - actionText: i18n._('DELETE'), - }); - $rootScope.promptActionBtnClass = 'Modal-errorButton'; - }; - - $scope.toggleHost = function(event, host) { - try { - $(event.target).tooltip('hide'); - } catch (e) { - // ignore - } - - host.enabled = !host.enabled; - - HostsService.put(host).then(function(){ - $state.go($state.current, null, {reload: true}); - }); - }; - - $scope.smartInventory = function() { - $state.go('inventories.addSmartInventory'); - }; - - $scope.setAdhocPattern = function(){ - var pattern = _($scope.hostsSelected) - .map(function(item){ - return item.name; - }).value().join(':'); - - if($state.includes('inventories.edit')) { - $state.go('inventories.edit.adhoc', {pattern: pattern}); - } - else if($state.includes('inventories.editSmartInventory')) { - $state.go('inventories.editSmartInventory.adhoc', {pattern: pattern}); - } - }; -}]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/list/main.js b/awx/ui/client/src/inventories-hosts/inventories/related/hosts/list/main.js deleted file mode 100644 index 6cb8d2dda557..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/list/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import RelatedHostListController from './host-list.controller'; - -export default -angular.module('relatedHostList', []) - .controller('RelatedHostListController', RelatedHostListController); diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/main.js b/awx/ui/client/src/inventories-hosts/inventories/related/hosts/main.js deleted file mode 100644 index 08b0cdc5b6ea..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/main.js +++ /dev/null @@ -1,24 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - import relatedHostAdd from './add/main'; - import relatedHostEdit from './edit/main'; - import relatedHostList from './list/main'; - import relatedHostsListDefinition from './related-host.list'; - import relatedHostsFormDefinition from './related-host.form'; - import relatedGroupsLabels from './related-groups-labels/main'; - import nestedGroups from './related/nested-groups/main'; - -export default -angular.module('relatedHost', [ - relatedHostAdd.name, - relatedHostEdit.name, - relatedHostList.name, - relatedGroupsLabels.name, - nestedGroups.name - ]) - .factory('RelatedHostsFormDefinition', relatedHostsFormDefinition) - .factory('RelatedHostsListDefinition', relatedHostsListDefinition); diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/main.js b/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/main.js deleted file mode 100644 index a3be739b8aa7..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import relatedGroupsabelsList from './relatedGroupsLabelsList.directive'; - -export default - angular.module('relatedGroupsLabels', []) - .directive('relatedGroupsLabelsList', relatedGroupsabelsList); diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.block.less b/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.block.less deleted file mode 100644 index 092a13a04182..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.block.less +++ /dev/null @@ -1,5 +0,0 @@ -.RelatedGroupsLabelsCell{ - width:100%; - display: flex; - align-items: center; -} diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.directive.js b/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.directive.js deleted file mode 100644 index 7ecdee5973e4..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.directive.js +++ /dev/null @@ -1,104 +0,0 @@ -/* jshint unused: vars */ -export default - [ 'templateUrl', - 'Wait', - 'Rest', - 'GetBasePath', - 'ProcessErrors', - 'Prompt', - '$q', - '$filter', - '$state', - 'i18n', - function(templateUrl, Wait, Rest, GetBasePath, ProcessErrors, Prompt, $q, $filter, $state, i18n) { - return { - restrict: 'E', - scope: false, - templateUrl: templateUrl('inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList'), - link: function(scope, element, attrs) { - scope.showDelete = attrs.showDelete === 'true'; - scope.seeMoreInactive = true; - - var getNext = function(data, arr, resolve) { - Rest.setUrl(data.next); - Rest.get() - .then(({data}) => { - if (data.next) { - getNext(data, arr.concat(data.results), resolve); - } else { - resolve.resolve(arr.concat(data.results)); - } - }); - }; - - scope.seeMore = function () { - var seeMoreResolve = $q.defer(); - Rest.setUrl(`${scope[scope.$parent.list.iterator].related.groups}?order_by=id`); - Rest.get() - .then(({data}) => { - if (data.next) { - getNext(data, data.results, seeMoreResolve); - } else { - seeMoreResolve.resolve(data.results); - } - }); - - seeMoreResolve.promise.then(function (groups) { - scope.related_groups = groups; - scope.seeMoreInactive = false; - }); - }; - - scope.seeLess = function() { - // Trim the groups array back down to 5 items - scope.related_groups = scope.related_groups.slice(0, 5); - // Re-set the seeMoreInteractive flag so that the "See More" will be displayed - scope.seeMoreInactive = true; - }; - - scope.deleteLabel = function(host, group) { - var action = function () { - $('#prompt-modal').modal('hide'); - scope.seeMoreInactive = true; - Wait('start'); - let url = `${GetBasePath('groups')}${group.id}/hosts`; - if(url) { - Rest.setUrl(url); - Rest.post({"disassociate": true, "id": host.id}) - .then(() => { - Wait('stop'); - $state.go('.', null, {reload: true}); - }) - .catch(({data, status}) => { - Wait('stop'); - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Could not disassociate host from group. Call to ' + url + ' failed. DELETE returned status: ' + status }); - }); - } - }; - - Prompt({ - hdr: i18n._('Remove host from ') + group.name , - body: '
' + i18n._('Confirm the removal of the') + ' ' + $filter('sanitize')(host.name) + ' ' + i18n._('from the') + ' ' + $filter('sanitize')(group.name) + ' ' + i18n._('group') + '.
', - action: action, - actionText: i18n._('REMOVE') - }); - }; - - scope.$watchCollection(scope.$parent.list.iterator, function() { - // To keep the array of groups fresh, we need to set up a watcher - otherwise, the - // array will get set initially and then never be updated as groups are removed - if (scope[scope.$parent.list.iterator].summary_fields.groups){ - scope.related_groups = scope[scope.$parent.list.iterator].summary_fields.groups.results; - scope.count = scope[scope.$parent.list.iterator].summary_fields.groups.count; - } - else{ - scope.related_groups = null; - scope.count = null; - } - }); - - } - }; - } - ]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.partial.html b/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.partial.html deleted file mode 100644 index 8b40988fdd94..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.partial.html +++ /dev/null @@ -1,14 +0,0 @@ -
-
- {{ related_group.name }} -
-
- -
-
-
View More
-
View Less
diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related-host.form.js b/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related-host.form.js deleted file mode 100644 index 1e1b935fa70c..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related-host.form.js +++ /dev/null @@ -1,140 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name forms.function:Hosts - * @description This form is for adding/editing a host on the inventory page -*/ - -export default ['i18n', -function(i18n) { - return { - - addTitle: i18n._('CREATE HOST'), - editTitle: '{{ host.name }}', - name: 'host', - basePath: 'hosts', - well: false, - formLabelSize: 'col-lg-3', - formFieldSize: 'col-lg-9', - iterator: 'host', - detailsClick: "$state.go('inventories.edit.hosts.edit', null, {reload:true})", - stateTree: 'inventories.edit.hosts', - headerFields:{ - enabled: { - class: 'Form-header-field', - ngClick: 'toggleHostEnabled(host)', - type: 'toggle', - awToolTip: "

" + - i18n._("Indicates if a host is available and should be included in running jobs.") + - "

" + - i18n._("For hosts that are part of an external" + - " inventory, this may be" + - " reset by the inventory sync process.") + - "

", - dataTitle: i18n._('Host Enabled'), - dataPlacement: "right", - ngDisabled: '!host.summary_fields.user_capabilities.edit || isSmartInvHost' - } - }, - fields: { - name: { - label: i18n._('Host Name'), - type: 'text', - required: true, - awPopOver: "

" + - i18n._("Provide a host name, ip address, or ip address:port. Examples include:") + - "

" + - "
myserver.domain.com
" + - "127.0.0.1
" + - "10.1.0.140:25
" + - "server.example.com:25" + - "
", - dataTitle: i18n._('Host Name'), - dataPlacement: 'right', - dataContainer: 'body', - ngDisabled: '!(host.summary_fields.user_capabilities.edit || canAdd) || isSmartInvHost' - }, - description: { - label: i18n._('Description'), - ngDisabled: '!(host.summary_fields.user_capabilities.edit || canAdd) || isSmartInvHost', - type: 'text' - }, - host_variables: { - label: i18n._('Variables'), - type: 'code_mirror', - class: 'Form-formGroup--fullWidth', - "default": "---", - variables: 'host_variables', - awPopOver: `

${i18n._("Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two.")}

- JSON:
-
- {
 "somevar": "somevalue",
 "password": "magic"
} -
- YAML:
-
- ---
- somevar: somevalue
- password: magic
-
-

${i18n.sprintf(i18n._( - 'View JSON examples at %s'), - 'www.json.org' - )}

-

${i18n.sprintf(i18n._('View YAML examples at %s'), 'docs.ansible.com')}

`, - ngDisabled: '!(host.summary_fields.user_capabilities.edit || canAdd) || isSmartInvHost' - } - }, - - buttons: { - cancel: { - ngClick: 'formCancel()', - ngShow: '(host.summary_fields.user_capabilities.edit || canAdd) && !isSmartInvHost' - }, - close: { - ngClick: 'formCancel()', - ngShow: '!(host.summary_fields.user_capabilities.edit || canAdd) || isSmartInvHost' - }, - save: { - ngClick: 'formSave()', - ngDisabled: true, - ngShow: '(host.summary_fields.user_capabilities.edit || canAdd) && !isSmartInvHost' - } - }, - - related: { - ansible_facts: { - name: 'ansible_facts', - awToolTip: i18n._('Please save before viewing facts.'), - dataPlacement: 'top', - title: i18n._('Facts'), - skipGenerator: true - }, - nested_groups: { - name: 'nested_groups', - awToolTip: i18n._('Please save before defining groups.'), - dataPlacement: 'top', - ngClick: "$state.go('inventories.edit.hosts.edit.nested_groups')", - title: i18n._('Groups'), - iterator: 'nested_group' - }, - insights: { - name: 'insights', - awToolTip: i18n._('Please save before viewing Insights.'), - dataPlacement: 'top', - title: i18n._('Insights'), - skipGenerator: true, - ngIf: "host.insights_system_id!==null && host.summary_fields.inventory.hasOwnProperty('insights_credential_id')" - }, - completed_jobs: { - name: 'completed_jobs', - title: i18n._('Completed Jobs'), - skipGenerator: true - } - } - }; - }]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related-host.list.js b/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related-host.list.js deleted file mode 100644 index fdef44a350eb..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related-host.list.js +++ /dev/null @@ -1,147 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['i18n', function(i18n) { - return { - name: 'hosts', - iterator: 'host', - editTitle: '{{ selected_group }}', - showTitle: false, - well: true, - wellOverride: true, - index: false, - hover: true, - multiSelect: true, - trackBy: 'host.id', - basePath: 'api/v2/inventories/{{$stateParams.inventory_id}}/hosts/', - layoutClass: 'List-staticColumnLayout--hostsWithCheckbox', - staticColumns: [ - { - field: 'toggleHost', - content: { - ngDisabled: '!host.summary_fields.user_capabilities.edit', - label: '', - type: "toggle", - ngClick: "toggleHost($event, host)", - awToolTip: "

" + - i18n._("Indicates if a host is available and should be included in running jobs.") + - "

" + - i18n._("For hosts that are part of an external" + - " inventory, this may be" + - " reset by the inventory sync process.") + - "

", - dataPlacement: "right", - nosort: true, - } - }, - { - field: 'active_failures', - content: { - label: '', - iconOnly: true, - nosort: true, - // do not remove this ng-click directive - // the list generator case to handle fields without ng-click - // cannot handle the aw-* directives - ngClick: 'noop()', - awPopOver: "{{ host.job_status_html }}", - dataTitle: "{{ host.job_status_title }}", - awToolTip: "{{ host.badgeToolTip }}", - dataPlacement: 'top', - icon: "{{ 'fa icon-job-' + host.active_failures }}", - id: 'active-failures-action', - columnClass: 'status-column' - } - } - ], - - fields: { - name: { - key: true, - label: i18n._('Hosts'), - uiSref: ".edit({inventory_id: host.inventory_id,host_id: host.id})", - ngClass: "{ 'host-disabled-label': !host.enabled }", - columnClass: 'col-lg-3 col-md-3 col-sm-3 col-xs-7', - dataHostId: "{{ host.id }}", - dataType: "host", - }, - description: { - label: i18n._('Description'), - columnClass: 'd-none d-lg-flex col-lg-3', - template: ` -
- {{ host.description }} -
- ` - }, - groups: { - label: i18n._("Related Groups"), - type: 'related_groups', - nosort: true, - showDelete: true, - columnClass: 'd-none d-lg-flex List-tableCell col-lg-3' - } - }, - - fieldActions: { - - columnClass: 'col-lg-3 col-md-6 col-sm-4 col-xs-5 text-right', - edit: { - ngClick: "editHost(host)", - icon: 'icon-edit', - awToolTip: i18n._('Edit host'), - dataPlacement: 'top', - ngShow: 'host.summary_fields.user_capabilities.edit' - }, - insights: { - ngClick: "goToInsights(host)", - icon: 'fa-info', - awToolTip: i18n._('View Insights Data'), - dataPlacement: 'top', - ngShow: 'host.insights_system_id && host.summary_fields.inventory.hasOwnProperty("insights_credential_id")', - ngClass: "{'List-actionButton--selected': $stateParams['host_id'] == host.id && $state.is('inventories.edit.hosts.edit.insights')}" - }, - view: { - ngClick: "editHost(host)", - awToolTip: i18n._('View host'), - dataPlacement: 'top', - ngShow: '!host.summary_fields.user_capabilities.edit' - }, - "delete": { - ngClick: "deleteHost(host.id, host.name)", - icon: 'icon-trash', - awToolTip: i18n._('Delete host'), - dataPlacement: 'top', - ngShow: 'host.summary_fields.user_capabilities.delete' - } - }, - - actions: { - launch: { - mode: 'all', - ngDisabled: '!hostsSelected', - ngClick: 'setAdhocPattern()', - awToolTip: i18n._("Select an inventory source by clicking the check box beside it. The inventory source can be a single host or a selection of multiple hosts."), - dataPlacement: 'top', - actionClass: 'btn List-buttonDefault', - buttonContent: i18n._('RUN COMMANDS'), - showTipWhenDisabled: true, - tooltipInnerClass: "Tooltip-wide", - // TODO: we don't always want to show this - ngShow: 'inventory_obj.summary_fields.user_capabilities.adhoc' - }, - create: { - mode: 'all', - ngClick: "createHost()", - awToolTip: i18n._("Create a new host"), - actionClass: 'at-Button--add', - actionId: 'button-add', - ngShow: 'canAdd', - dataPlacement: "top", - } - } - }; -}]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related-host.route.js b/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related-host.route.js deleted file mode 100644 index 0c8f6cb23a89..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related-host.route.js +++ /dev/null @@ -1,83 +0,0 @@ -import { N_ } from '../../../../i18n'; - -export default { - name: "inventories.edit.hosts", - url: "/hosts?{host_search:queryset}", - params: { - host_search: { - value: { - page_size: "20", - order_by: "name" - }, - dynamic: true, - squash:"" - } - }, - ncyBreadcrumb: { - parent: "inventories.edit", - label: N_("HOSTS") - }, - views: { - 'related': { - templateProvider: function(ListDefinition, generateList, $stateParams, GetBasePath) { - let list = _.cloneDeep(ListDefinition); - if($stateParams && $stateParams.group) { - list.basePath = GetBasePath('groups') + _.last($stateParams.group) + '/all_hosts'; - } - else { - //reaches here if the user is on the root level group - list.basePath = GetBasePath('inventory') + $stateParams.inventory_id + '/hosts'; - } - let html = generateList.build({ - list: list, - mode: 'edit' - }); - return html; - }, - controller: 'RelatedHostListController' - } - }, - resolve: { - ListDefinition: ['RelatedHostsListDefinition', '$stateParams', 'GetBasePath', (RelatedHostsListDefinition, $stateParams, GetBasePath) => { - let list = _.cloneDeep(RelatedHostsListDefinition); - list.basePath = GetBasePath('inventory') + $stateParams.inventory_id + '/hosts'; - return list; - }], - Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath', '$interpolate', '$rootScope', - (list, qs, $stateParams, GetBasePath, $interpolate, $rootScope) => { - // allow related list definitions to use interpolated $rootScope / $stateParams in basePath field - let path, interpolator; - if (GetBasePath(list.basePath)) { - path = GetBasePath(list.basePath); - } else { - interpolator = $interpolate(list.basePath); - path = interpolator({ $rootScope: $rootScope, $stateParams: $stateParams }); - } - return qs.search(path, $stateParams[`${list.iterator}_search`]); - } - ], - hostsUrl: ['InventoriesService', '$stateParams', function(InventoriesService, $stateParams) { - return $stateParams.group && $stateParams.group.length > 0 ? - // nested context - provide all hosts managed by nodes - InventoriesService.childHostsUrl(_.last($stateParams.group)) : - // root context - provide all hosts in an inventory - InventoriesService.rootHostsUrl($stateParams.inventory_id); - }], - hostsDataset: ['ListDefinition', 'QuerySet', '$stateParams', 'hostsUrl', (list, qs, $stateParams, hostsUrl) => { - let path = hostsUrl; - return qs.search(path, $stateParams[`${list.iterator}_search`]); - }], - inventoryData: ['InventoriesService', '$stateParams', function(InventoriesService, $stateParams) { - return InventoriesService.getInventory($stateParams.inventory_id).then(res => res.data); - }], - canAdd: ['rbacUiControlService', function(rbacUiControlService) { - return rbacUiControlService.canAdd('hosts') - .then(function(res) { - return res.canAdd; - }) - .catch(function() { - return false; - }); - }] - } -}; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-associate.route.js b/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-associate.route.js deleted file mode 100644 index c710a7675485..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-associate.route.js +++ /dev/null @@ -1,32 +0,0 @@ -export default { - name: 'inventories.edit.hosts.edit.nested_groups.associate', - squashSearchUrl: true, - url: '/associate', - ncyBreadcrumb:{ - skip:true - }, - views: { - 'modal@inventories.edit.hosts.edit': { - templateProvider: function() { - return ``; - }, - controller: function($scope, $q, GroupsService, $state){ - $scope.associateGroups = function(selectedItems){ - var deferred = $q.defer(); - return $q.all( _.map(selectedItems, (selectedItem) => GroupsService.associateHost({id: parseInt($state.params.host_id)}, selectedItem.id)) ) - .then( () =>{ - deferred.resolve(); - }, (error) => { - deferred.reject(error); - }); - }; - } - } - }, - onExit: function($state) { - if ($state.transition) { - $('#associate-groups-modal').modal('hide'); - $('body').removeClass('modal-open'); - } - }, -}; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-disassociate.partial.html b/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-disassociate.partial.html deleted file mode 100644 index 5f68d4dc51db..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-disassociate.partial.html +++ /dev/null @@ -1,36 +0,0 @@ - diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-list.controller.js b/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-list.controller.js deleted file mode 100644 index 72a841dc7627..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-list.controller.js +++ /dev/null @@ -1,134 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - export default - ['$scope', '$rootScope', '$state', '$stateParams', 'HostNestedGroupListDefinition', 'InventoryUpdate', - 'GroupsService', 'CancelSourceUpdate', 'rbacUiControlService', 'GetBasePath', - 'GetHostsStatusMsg', 'Dataset', 'Find', 'QuerySet', 'inventoryData', 'canAdd', 'ProcessErrors', 'host', - function($scope, $rootScope, $state, $stateParams, HostNestedGroupListDefinition, InventoryUpdate, - GroupsService, CancelSourceUpdate, rbacUiControlService, GetBasePath, - GetHostsStatusMsg, Dataset, Find, qs, inventoryData, canAdd, ProcessErrors, host){ - - let list = HostNestedGroupListDefinition; - - init(); - - function init(){ - $scope.toDisassociate = host; - $scope.inventory_id = $stateParams.inventory_id; - $scope.canAdhoc = inventoryData.summary_fields.user_capabilities.adhoc; - $scope.canAdd = canAdd; - - // Search init - $scope.list = list; - $scope[`${list.iterator}_dataset`] = Dataset.data; - $scope[list.name] = $scope[`${list.iterator}_dataset`].results; - - $scope.$watchCollection(list.name, function(){ - _.forEach($scope[list.name], buildStatusIndicators); - }); - - $scope.$on('selectedOrDeselected', function(e, value) { - let item = value.value; - - if (value.isSelected) { - if(!$scope.groupsSelected) { - $scope.groupsSelected = []; - } - $scope.groupsSelected.push(item); - } else { - _.remove($scope.groupsSelected, { id: item.id }); - if($scope.groupsSelected.length === 0) { - $scope.groupsSelected = null; - } - } - }); - - } - - function buildStatusIndicators(group){ - if (group === undefined || group === null) { - group = {}; - } - - let hosts_status; - - hosts_status = GetHostsStatusMsg({ - active_failures: group.hosts_with_active_failures, - total_hosts: group.total_hosts, - inventory_id: $scope.inventory_id, - group_id: group.id - }); - _.assign(group, - {hosts_status_tip: hosts_status.tooltip}, - {hosts_status_class: hosts_status.class}); - } - - $scope.associateGroup = function() { - $state.go('.associate'); - }; - - $scope.disassociateGroup = function(group){ - $scope.disassociateFrom = group; - $('#group-disassociate-modal').modal('show'); - }; - - $scope.confirmDisassociate = function(){ - - // Bind an even listener for the modal closing. Trying to $state.go() before the modal closes - // will mean that these two things are running async and the modal may not finish closing before - // the state finishes transitioning. - $('#group-disassociate-modal').off('hidden.bs.modal').on('hidden.bs.modal', function () { - // Remove the event handler so that we don't end up with multiple bindings - $('#group-disassociate-modal').off('hidden.bs.modal'); - - let reloadListStateParams = null; - - if($scope.nested_groups.length === 1 && $state.params.nested_group_search && !_.isEmpty($state.params.nested_group_search.page) && $state.params.nested_group_search.page !== '1') { - reloadListStateParams = _.cloneDeep($state.params); - reloadListStateParams.nested_group_search.page = (parseInt(reloadListStateParams.nested_group_search.page)-1).toString(); - } - - // Reload the inventory manage page and show that the group has been removed - $state.go('.', reloadListStateParams, {reload: true}); - }); - - let closeModal = function(){ - $('#group-disassociate-modal').modal('hide'); - $('body').removeClass('modal-open'); - $('.modal-backdrop').remove(); - }; - - GroupsService.disassociateHost($scope.toDisassociate.id, $scope.disassociateFrom.id) - .then(() => { - closeModal(); - }).catch((error) => { - closeModal(); - ProcessErrors(null, error.data, error.status, null, { - hdr: 'Error!', - msg: 'Failed to disassociate group from parent group: POST returned status' + - error.status - }); - }); - }; - - $scope.editGroup = function(id){ - $state.go('inventories.edit.groups.edit', {group_id: id}); - }; - - $scope.goToGroupGroups = function(id){ - $state.go('inventories.edit.groups.edit.nested_groups', {group_id: id}); - }; - - $scope.setAdhocPattern = function(){ - var pattern = _($scope.groupsSelected) - .map(function(item){ - return item.name; - }).value().join(':'); - - $state.go('^.^.^.adhoc', {pattern: pattern}); - }; - - }]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js b/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js deleted file mode 100644 index edb206cffb13..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js +++ /dev/null @@ -1,110 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - export default ['i18n', function(i18n) { - return { - name: 'nested_groups', - iterator: 'nested_group', - editTitle: '{{ inventory.name }}', - well: true, - wellOverride: true, - index: false, - hover: true, - multiSelect: true, - trackBy: 'nested_group.id', - basePath: 'api/v2/hosts/{{$stateParams.host_id}}/all_groups/', - layoutClass: 'List-staticColumnLayout--groups', - staticColumns: [ - { - field: 'failed_hosts', - content: { - label: '', - nosort: true, - mode: 'all', - iconOnly: true, - awToolTip: "{{ nested_group.hosts_status_tip }}", - dataPlacement: "top", - icon: "{{ 'fa icon-job-' + nested_group.hosts_status_class }}", - columnClass: 'status-column' - } - } - ], - - fields: { - name: { - label: i18n._('Groups'), - key: true, - ngClick: "goToGroupGroups(nested_group.id)", - columnClass: 'col-lg-6 col-md-6 col-sm-6 col-xs-6', - } - }, - - actions: { - refresh: { - mode: 'all', - awToolTip: i18n._("Refresh the page"), - ngClick: "refreshGroups()", - ngShow: "socketStatus == 'error'", - actionClass: 'btn List-buttonDefault', - buttonContent: i18n._('REFRESH') - }, - launch: { - mode: 'all', - ngDisabled: '!groupsSelected', - ngClick: 'setAdhocPattern()', - awToolTip: i18n._("Select an inventory source by clicking the check box beside it. The inventory source can be a single group or host, a selection of multiple hosts, or a selection of multiple groups."), - dataPlacement: 'top', - actionClass: 'btn List-buttonDefault', - buttonContent: i18n._('RUN COMMANDS'), - showTipWhenDisabled: true, - tooltipInnerClass: "Tooltip-wide", - ngShow: 'canAdhoc' - // TODO: set up a tip watcher and change text based on when - // things are selected/not selected. This is started and - // commented out in the inventory controller within the watchers. - // awToolTip: "{{ adhocButtonTipContents }}", - // dataTipWatch: "adhocButtonTipContents" - }, - associate: { - mode: 'all', - ngClick: 'associateGroup()', - awToolTip: i18n._("Associate an existing group"), - actionClass: 'at-Button--add', - actionId: 'button-add', - ngShow: 'canAdd', - dataPlacement: "top", - } - }, - - fieldActions: { - - columnClass: 'col-lg-6 col-md-6 col-sm-6 col-xs-6 text-right', - - edit: { - mode: 'all', - ngClick: "editGroup(nested_group.id)", - awToolTip: i18n._('Edit group'), - dataPlacement: "top", - ngShow: "nested_group.summary_fields.user_capabilities.edit" - }, - view: { - mode: 'all', - ngClick: "editGroup(nested_group.id)", - awToolTip: i18n._('View group'), - dataPlacement: "top", - ngShow: "!nested_group.summary_fields.user_capabilities.edit" - }, - "delete": { - mode: 'all', - ngClick: "disassociateGroup(nested_group)", - awToolTip: i18n._('Disassociate group'), - iconClass: 'fa fa-times', - dataPlacement: "top", - ngShow: "nested_group.summary_fields.user_capabilities.delete" - } - } - }; -}]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.route.js b/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.route.js deleted file mode 100644 index 7f3a82a4930c..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.route.js +++ /dev/null @@ -1,78 +0,0 @@ -import {templateUrl} from '../../../../../../shared/template-url/template-url.factory'; -import { N_ } from '../../../../../../i18n'; - -export default { - name: 'inventories.edit.hosts.edit.nested_groups', - url: "/nested_groups?{nested_group_search:queryset}", - params: { - nested_group_search: { - value: { - page_size: "20", - order_by: "name" - }, - dynamic: true, - squash: "" - } - }, - ncyBreadcrumb: { - parent: "inventories.edit.hosts.edit", - label: N_("ASSOCIATED GROUPS") - }, - views: { - // 'related@inventories.edit.groups.edit': { - 'related': { - templateProvider: function(HostNestedGroupListDefinition, generateList, $templateRequest) { - let list = _.cloneDeep(HostNestedGroupListDefinition); - - let html = generateList.build({ - list: list, - mode: 'edit' - }); - - return $templateRequest(templateUrl('inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-disassociate')).then((template) => { - return html.concat(template); - }); - }, - controller: 'HostNestedGroupsListController' - } - }, - resolve: { - Dataset: ['HostNestedGroupListDefinition', 'QuerySet', '$stateParams', 'GetBasePath', '$interpolate', '$rootScope', - (list, qs, $stateParams, GetBasePath, $interpolate, $rootScope) => { - // allow related list definitions to use interpolated $rootScope / $stateParams in basePath field - let path, interpolator; - if (GetBasePath(list.basePath)) { - path = GetBasePath(list.basePath); - } else { - interpolator = $interpolate(list.basePath); - path = interpolator({ $rootScope: $rootScope, $stateParams: $stateParams }); - } - if($stateParams.group_id){ - path = GetBasePath('groups') + $stateParams.group_id + '/children'; - } - else if($stateParams.host_id){ - path = GetBasePath('hosts') + $stateParams.host_id + '/all_groups'; - } - return qs.search(path, $stateParams[`${list.iterator}_search`]); - } - ], - host: ['$stateParams', 'HostsService', function($stateParams, HostsService) { - if($stateParams.host_id){ - return HostsService.get({ id: $stateParams.host_id }).then((res) => res.data.results[0]); - } - }], - inventoryData: ['InventoriesService', '$stateParams', 'host', function(InventoriesService, $stateParams, host) { - var id = ($stateParams.inventory_id) ? $stateParams.inventory_id : host.summary_fields.inventory.id; - return InventoriesService.getInventory(id).then(res => res.data); - }], - canAdd: ['rbacUiControlService', '$state', 'GetBasePath', '$stateParams', function(rbacUiControlService, $state, GetBasePath, $stateParams) { - return rbacUiControlService.canAdd(GetBasePath('inventory') + $stateParams.inventory_id + "/groups") - .then(function(res) { - return res.canAdd; - }) - .catch(function() { - return false; - }); - }] - } -}; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/main.js b/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/main.js deleted file mode 100644 index d2ec22fe62c8..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/main.js +++ /dev/null @@ -1,13 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import HostNestedGroupListDefinition from './host-nested-groups.list'; -import HostNestedGroupsListController from './host-nested-groups-list.controller'; - -export default - angular.module('hostNestedGroups', []) - .factory('HostNestedGroupListDefinition', HostNestedGroupListDefinition) - .controller('HostNestedGroupsListController', HostNestedGroupsListController); diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/sources/add/main.js b/awx/ui/client/src/inventories-hosts/inventories/related/sources/add/main.js deleted file mode 100644 index 04cfc8ba9ad3..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/sources/add/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import controller from './sources-add.controller'; - -export default -angular.module('sourcesAdd', []) - .controller('SourcesAddController', controller); diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js b/awx/ui/client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js deleted file mode 100644 index 9e254fd7e504..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js +++ /dev/null @@ -1,337 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['$state', 'ConfigData', '$scope', 'SourcesFormDefinition', 'ParseTypeChange', - 'GenerateForm', 'inventoryData', 'GetChoices', - 'GetBasePath', 'CreateSelect2', 'GetSourceTypeOptions', - 'SourcesService', 'Empty', 'Wait', 'Rest', 'Alert', 'ProcessErrors', - 'inventorySourcesOptions', '$rootScope', 'i18n', 'InventorySourceModel', 'InventoryHostsStrings', - function($state, ConfigData, $scope, SourcesFormDefinition, ParseTypeChange, - GenerateForm, inventoryData, GetChoices, - GetBasePath, CreateSelect2, GetSourceTypeOptions, - SourcesService, Empty, Wait, Rest, Alert, ProcessErrors, - inventorySourcesOptions,$rootScope, i18n, InventorySource, InventoryHostsStrings) { - - let form = SourcesFormDefinition; - $scope.mode = 'add'; - // apply form definition's default field values - GenerateForm.applyDefaults(form, $scope, true); - $scope.canAdd = inventorySourcesOptions.actions.POST; - $scope.envParseType = 'yaml'; - const virtualEnvs = ConfigData.custom_virtualenvs || []; - $scope.custom_virtualenvs_options = virtualEnvs; - - GetChoices({ - scope: $scope, - field: 'source_regions', - variable: 'rax_regions', - choice_name: 'rax_region_choices', - options: inventorySourcesOptions - }); - - GetChoices({ - scope: $scope, - field: 'source_regions', - variable: 'ec2_regions', - choice_name: 'ec2_region_choices', - options: inventorySourcesOptions - }); - - GetChoices({ - scope: $scope, - field: 'source_regions', - variable: 'gce_regions', - choice_name: 'gce_region_choices', - options: inventorySourcesOptions - }); - - GetChoices({ - scope: $scope, - field: 'source_regions', - variable: 'azure_regions', - choice_name: 'azure_rm_region_choices', - options: inventorySourcesOptions - }); - - // Load options for group_by - GetChoices({ - scope: $scope, - field: 'group_by', - variable: 'ec2_group_by', - choice_name: 'ec2_group_by_choices', - options: inventorySourcesOptions - }); - - initRegionSelect(); - - GetChoices({ - scope: $scope, - field: 'verbosity', - variable: 'verbosity_options', - options: inventorySourcesOptions - }); - - CreateSelect2({ - element: '#inventory_source_verbosity', - multiple: false - }); - - $scope.verbosity = $scope.verbosity_options[1]; - - CreateSelect2({ - element: '#inventory_source_custom_virtualenv', - multiple: false, - opts: $scope.custom_virtualenvs_options - }); - - GetSourceTypeOptions({ - scope: $scope, - variable: 'source_type_options' - }); - - const inventorySource = new InventorySource(); - - var getInventoryFiles = function (project) { - var url; - - if (!Empty(project)) { - url = GetBasePath('projects') + project + '/inventories/'; - Wait('start'); - Rest.setUrl(url); - Rest.get() - .then(({data}) => { - $scope.inventory_files = data; - $scope.inventory_files.push("/ (project root)"); - CreateSelect2({ - element:'#inventory-file-select', - addNew: true, - multiple: false, - scope: $scope, - options: 'inventory_files', - model: 'inventory_file' - }); - Wait('stop'); - }) - .catch(() => { - Alert('Cannot get inventory files', 'Unable to retrieve the list of inventory files for this project.', 'alert-info'); - Wait('stop'); - }); - } - }; - - // Register a watcher on project_name - if ($scope.getInventoryFilesUnregister) { - $scope.getInventoryFilesUnregister(); - } - $scope.getInventoryFilesUnregister = $scope.$watch('project', function (newValue, oldValue) { - if (newValue !== oldValue) { - getInventoryFiles(newValue); - } - }); - - $scope.lookupCredential = function(){ - // For most source type selections, we filter for 1-1 matches to credential_type namespace. - let searchKey = 'credential_type__namespace'; - let searchValue = $scope.source.value; - - // SCM and custom source types are more generic in terms of the credentials they - // accept - any cloud or user-defined credential type can be used. We filter for - // these using the credential_type kind field, which categorizes all cloud and - // user-defined credentials as 'cloud'. - if ($scope.source.value === 'scm') { - searchKey = 'credential_type__kind'; - searchValue = 'cloud'; - } - - if ($scope.source.value === 'custom') { - searchKey = 'credential_type__kind'; - searchValue = 'cloud'; - } - - // When the selection is 'ec2' we actually want to filter for the 'aws' namespace. - if ($scope.source.value === 'ec2') { - searchValue = 'aws'; - } - - $state.go('.credential', { - credential_search: { - [searchKey]: searchValue, - page_size: '5', - page: '1' - } - }); - }; - - $scope.lookupProject = function(){ - $state.go('.project', { - project_search: { - page_size: '5', - page: '1' - } - }); - }; - - $scope.sourceChange = function(source) { - source = (source && source.value) ? source.value : ''; - if ($scope.source.value === "scm" && $scope.source.value === "custom") { - $scope.credentialBasePath = GetBasePath('credentials') + '?credential_type__kind__in=cloud,network'; - } - else{ - $scope.credentialBasePath = (source === 'ec2') ? GetBasePath('credentials') + '?credential_type__namespace=aws' : GetBasePath('credentials') + (source === '' ? '' : '?credential_type__namespace=' + (source)); - } - if (source === 'ec2' || source === 'custom' || source === 'vmware' || source === 'openstack' || source === 'scm' || source === 'cloudforms' || source === "satellite6" || source === "azure_rm") { - $scope.envParseType = 'yaml'; - - var varName; - if (source === 'scm') { - varName = 'custom_variables'; - } else { - varName = source + '_variables'; - } - - $scope[varName] = $scope[varName] === (null || undefined) ? '---' : $scope[varName]; - ParseTypeChange({ - scope: $scope, - field_id: varName, - variable: varName, - parse_variable: 'envParseType' - }); - } - - if (source === 'scm') { - $scope.projectBasePath = GetBasePath('projects') + '?not__status=never updated'; - } - - // reset fields - $scope.group_by_choices = source === 'ec2' ? $scope.ec2_group_by : null; - // azure_rm regions choices are keyed as "azure" in an OPTIONS request to the inventory_sources endpoint - $scope.source_region_choices = source === 'azure_rm' ? $scope.azure_regions : $scope[source + '_regions']; - $scope.cloudCredentialRequired = source !== '' && source !== 'scm' && source !== 'custom' && source !== 'ec2' ? true : false; - $scope.source_regions = null; - $scope.credential = null; - $scope.credential_name = null; - $scope.group_by = null; - $scope.group_by_choices = []; - $scope.overwrite_vars = false; - initRegionSelect(); - }; - // region / source options callback - - $scope.$on('sourceTypeOptionsReady', function() { - CreateSelect2({ - element: '#inventory_source_source', - multiple: false - }); - }); - - function initRegionSelect(){ - CreateSelect2({ - element: '#inventory_source_source_regions', - multiple: true - }); - - let add_new = false; - if( _.get($scope, 'source') === 'ec2' || _.get($scope.source, 'value') === 'ec2') { - $scope.group_by_choices = $scope.ec2_group_by; - $scope.groupByPopOver = "

" + i18n._("Select which groups to create automatically. ") + - $rootScope.BRAND_NAME + i18n._(" will create group names similar to the following examples based on the options selected:") + "

    " + - "
  • " + i18n._("Availability Zone:") + "zones » us-east-1b
  • " + - "
  • " + i18n._("Image ID:") + "images » ami-b007ab1e
  • " + - "
  • " + i18n._("Instance ID:") + "instances » i-ca11ab1e
  • " + - "
  • " + i18n._("Instance Type:") + "types » type_m1_medium
  • " + - "
  • " + i18n._("Key Name:") + "keys » key_testing
  • " + - "
  • " + i18n._("Region:") + "regions » us-east-1
  • " + - "
  • " + i18n._("Security Group:") + "security_groups » security_group_default
  • " + - "
  • " + i18n._("Tags:") + "tags » tag_Name » tag_Name_host1
  • " + - "
  • " + i18n._("VPC ID:") + "vpcs » vpc-5ca1ab1e
  • " + - "
  • " + i18n._("Tag None:") + "tags » tag_none
  • " + - "

" + i18n._("If blank, all groups above are created except") + "" + i18n._("Instance ID") + ".

"; - - $scope.instanceFilterPopOver = "

" + i18n._("Provide a comma-separated list of filter expressions. ") + - i18n._("Hosts are imported to ") + $rootScope.BRAND_NAME + i18n._(" when ") + "" + i18n._("ANY") + "" + i18n._(" of the filters match.") + "

" + - i18n._("Limit to hosts having a tag:") + "
\n" + - "
tag-key=TowerManaged
\n" + - i18n._("Limit to hosts using either key pair:") + "
\n" + - "
key-name=staging, key-name=production
\n" + - i18n._("Limit to hosts where the Name tag begins with ") + "" + i18n._("test") + ":
\n" + - "
tag:Name=test*
\n" + - "

" + i18n._("View the ") + "" + i18n._("Describe Instances documentation") + " " + - i18n._("for a complete list of supported filters.") + "

"; - } - if( _.get($scope, 'source') === 'vmware' || _.get($scope.source, 'value') === 'vmware') { - add_new = true; - $scope.group_by_choices = []; - $scope.group_by = $scope.group_by_choices; - $scope.groupByPopOver = i18n._("Specify which groups to create automatically. Group names will be created similar to the options selected. If blank, all groups above are created. Refer to Ansible Tower documentation for more detail."); - $scope.instanceFilterPopOver = i18n._("Provide a comma-separated list of filter expressions. Hosts are imported when all of the filters match. Refer to Ansible Tower documentation for more detail."); - } - if( _.get($scope, 'source') === 'tower' || _.get($scope.source, 'value') === 'tower') { - $scope.instanceFilterPopOver = i18n._("Provide the named URL encoded name or id of the remote Tower inventory to be imported."); - } - CreateSelect2({ - element: '#inventory_source_group_by', - multiple: true, - addNew: add_new - }); - } - - $scope.formCancel = function() { - $state.go('^'); - }; - - $scope.formSave = function() { - var params; - - params = { - name: $scope.name, - description: $scope.description, - inventory: inventoryData.id, - instance_filters: $scope.instance_filters, - source_script: $scope.inventory_script, - credential: $scope.credential, - overwrite: $scope.overwrite, - overwrite_vars: $scope.overwrite_vars, - update_on_launch: $scope.update_on_launch, - verbosity: $scope.verbosity.value, - update_cache_timeout: $scope.update_cache_timeout || 0, - custom_virtualenv: $scope.custom_virtualenv || null, - // comma-delimited strings - group_by: SourcesService.encodeGroupBy($scope.source, $scope.group_by), - source_regions: _.map($scope.source_regions, 'value').join(','), - }; - - if ($scope.source) { - let source_vars = $scope.source.value === 'scm' ? $scope.custom_variables : $scope[$scope.source.value + '_variables']; - params.source_vars = source_vars === '---' || source_vars === '{}' ? null : source_vars; - params.source = $scope.source.value; - if ($scope.source.value === 'scm') { - params.update_on_project_update = $scope.update_on_project_update; - params.source_project = $scope.project; - - if ($scope.inventory_file === '/ (project root)') { - params.source_path = ""; - } else { - params.source_path = $scope.inventory_file; - } - } - } else { - params.source = null; - } - - inventorySource.request('post', { - data: params - }).then((response) => { - let inventory_source_id = response.data.id; - $state.go('^.edit', {inventory_source_id: inventory_source_id}, {reload: true}); - }).catch(({ data, status, config }) => { - ProcessErrors($scope, data, status, null, { - hdr: 'Error!', - msg: InventoryHostsStrings.get('error.CALL', { path: `${config.url}`, status }) - }); - }); - }; - } -]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/sources/add/sources-add.route.js b/awx/ui/client/src/inventories-hosts/inventories/related/sources/add/sources-add.route.js deleted file mode 100644 index dc53ded31af2..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/sources/add/sources-add.route.js +++ /dev/null @@ -1,40 +0,0 @@ -import { N_ } from '../../../../../i18n'; - -export default { - name: "inventories.edit.inventory_sources.add", - url: "/add", - ncyBreadcrumb: { - parent: "inventories.edit.inventory_sources", - label: N_("CREATE INVENTORY SOURCE") - }, - views: { - 'sourcesForm@inventories': { - templateProvider: function(GenerateForm, SourcesFormDefinition) { - let form = SourcesFormDefinition; - return GenerateForm.buildHTML(form, { - mode: 'add', - related: false - }); - }, - controller: 'SourcesAddController' - } - }, - resolve: { - inventorySourcesOptions: ['InventoriesService', '$stateParams', function(InventoriesService, $stateParams) { - return InventoriesService.inventorySourcesOptions($stateParams.inventory_id) - .then(function(res) { - return res.data; - }); - }], - ConfigData: ['ConfigService', 'ProcessErrors', 'i18n', (ConfigService, ProcessErrors, i18n) => { - return ConfigService.getConfig() - .then(response => response) - .catch(({data, status}) => { - ProcessErrors(null, data, status, null, { - hdr: i18n._('Error!'), - msg: i18n._('Failed to get config. GET returned status: ') + status - }); - }); - }] - } -}; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/sources/edit/main.js b/awx/ui/client/src/inventories-hosts/inventories/related/sources/edit/main.js deleted file mode 100644 index 8c68bc554407..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/sources/edit/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import controller from './sources-edit.controller'; - -export default -angular.module('sourcesEdit', []) - .controller('SourcesEditController', controller); diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js b/awx/ui/client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js deleted file mode 100644 index 40dc4fc97063..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js +++ /dev/null @@ -1,436 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['$state', '$scope', 'ParseVariableString', 'ParseTypeChange', - 'GetChoices', 'GetBasePath', 'CreateSelect2', 'GetSourceTypeOptions', - 'SourcesService', 'inventoryData', 'inventorySourcesOptions', 'Empty', - 'Wait', 'Rest', 'Alert', '$rootScope', 'i18n', 'InventoryHostsStrings', - 'ProcessErrors', 'inventorySource', 'isNotificationAdmin', 'ConfigData', - function($state, $scope, ParseVariableString, ParseTypeChange, - GetChoices, GetBasePath, CreateSelect2, GetSourceTypeOptions, - SourcesService, inventoryData, inventorySourcesOptions, Empty, - Wait, Rest, Alert, $rootScope, i18n, InventoryHostsStrings, - ProcessErrors, inventorySource, isNotificationAdmin, ConfigData) { - - const inventorySourceData = inventorySource.get(); - - // To toggle notifications a user needs to have a read role on the inventory - // _and_ have at least a notification template admin role on an org. - // If the user has gotten this far it's safe to say they have - // at least read access to the inventory - $scope.sufficientRoleForNotifToggle = isNotificationAdmin; - $scope.sufficientRoleForNotif = isNotificationAdmin || $scope.user_is_system_auditor; - $scope.projectBasePath = GetBasePath('projects') + '?not__status=never updated'; - $scope.canAdd = inventorySourcesOptions.actions.POST; - const virtualEnvs = ConfigData.custom_virtualenvs || []; - $scope.custom_virtualenvs_options = virtualEnvs; - // instantiate expected $scope values from inventorySourceData - _.assign($scope, - {credential: inventorySourceData.credential}, - {overwrite: inventorySourceData.overwrite}, - {overwrite_vars: inventorySourceData.overwrite_vars}, - {update_on_launch: inventorySourceData.update_on_launch}, - {update_cache_timeout: inventorySourceData.update_cache_timeout}, - {instance_filters: inventorySourceData.instance_filters}, - {inventory_script: inventorySourceData.source_script}, - {verbosity: inventorySourceData.verbosity}); - - $scope.inventory_source_obj = inventorySourceData; - $scope.breadcrumb.inventory_source_name = inventorySourceData.name; - if (inventorySourceData.credential) { - $scope.credential_name = inventorySourceData.summary_fields.credential.name; - } - - if(inventorySourceData.source === 'scm') { - $scope.project = inventorySourceData.source_project; - $scope.project_name = inventorySourceData.summary_fields.source_project.name; - updateSCMProject(); - } - - // display custom inventory_script name - if (inventorySourceData.source === 'custom' && inventorySourceData.summary_fields.source_script) { - $scope.inventory_script_name = inventorySourceData.summary_fields.source_script.name; - } - $scope = angular.extend($scope, inventorySourceData); - - $scope.$watch('summary_fields.user_capabilities.edit', function(val) { - $scope.canAdd = val; - }); - - $scope.$on('sourceTypeOptionsReady', function() { - $scope.source = _.find($scope.source_type_options, { value: inventorySourceData.source }); - var source = $scope.source && $scope.source.value ? $scope.source.value : null; - $scope.cloudCredentialRequired = source !== '' && source !== 'scm' && source !== 'custom' && source !== 'ec2' ? true : false; - CreateSelect2({ - element: '#inventory_source_source', - multiple: false - }); - - if (source === 'ec2' || source === 'custom' || - source === 'vmware' || source === 'openstack' || - source === 'scm' || source === 'cloudforms' || - source === 'satellite6' || source === 'azure_rm') { - - var varName; - if (source === 'scm') { - varName = 'custom_variables'; - } else { - varName = source + '_variables'; - } - - $scope[varName] = ParseVariableString(inventorySourceData - .source_vars); - - ParseTypeChange({ - scope: $scope, - field_id: varName, - variable: varName, - parse_variable: 'envParseType', - readOnly: !$scope.summary_fields.user_capabilities.edit - }); - } - }); - - $scope.envParseType = 'yaml'; - - GetSourceTypeOptions({ - scope: $scope, - variable: 'source_type_options' - }); - GetChoices({ - scope: $scope, - field: 'source_regions', - variable: 'rax_regions', - choice_name: 'rax_region_choices', - options: inventorySourcesOptions - }); - GetChoices({ - scope: $scope, - field: 'source_regions', - variable: 'ec2_regions', - choice_name: 'ec2_region_choices', - options: inventorySourcesOptions - }); - GetChoices({ - scope: $scope, - field: 'source_regions', - variable: 'gce_regions', - choice_name: 'gce_region_choices', - options: inventorySourcesOptions - }); - GetChoices({ - scope: $scope, - field: 'source_regions', - variable: 'azure_regions', - choice_name: 'azure_rm_region_choices', - options: inventorySourcesOptions - }); - GetChoices({ - scope: $scope, - field: 'group_by', - variable: 'ec2_group_by', - choice_name: 'ec2_group_by_choices', - options: inventorySourcesOptions - }); - - var source = $scope.source === 'azure_rm' ? 'azure' : $scope.source; - var regions = inventorySourceData.source_regions.split(','); - // azure_rm regions choices are keyed as "azure" in an OPTIONS request to the inventory_sources endpoint - $scope.source_region_choices = $scope[source + '_regions']; - - // the API stores azure regions as all-lowercase strings - but the azure regions received from OPTIONS are Snake_Cased - if (source === 'azure') { - $scope.source_regions = _.map(regions, (region) => _.find($scope[source + '_regions'], (o) => o.value.toLowerCase() === region)); - } - // all other regions are 1-1 - else { - $scope.source_regions = _.map(regions, (region) => _.find($scope[source + '_regions'], (o) => o.value === region)); - } - initRegionSelect(); - - GetChoices({ - scope: $scope, - field: 'verbosity', - variable: 'verbosity_options', - options: inventorySourcesOptions - }); - - var i; - for (i = 0; i < $scope.verbosity_options.length; i++) { - if ($scope.verbosity_options[i].value === $scope.verbosity) { - $scope.verbosity = $scope.verbosity_options[i]; - } - } - - CreateSelect2({ - element: '#inventory_source_custom_virtualenv', - multiple: false, - opts: $scope.custom_virtualenvs_options - }); - - initVerbositySelect(); - - $scope.$watch('verbosity', initVerbositySelect); - - // Register a watcher on project_name - if ($scope.getInventoryFilesUnregister) { - $scope.getInventoryFilesUnregister(); - } - $scope.getInventoryFilesUnregister = $scope.$watch('project', function (newValue, oldValue) { - if (newValue !== oldValue) { - updateSCMProject(); - } - }); - - function initVerbositySelect(){ - CreateSelect2({ - element: '#inventory_source_verbosity', - multiple: false - }); - } - - function sync_inventory_file_select2() { - CreateSelect2({ - element:'#inventory-file-select', - addNew: true, - multiple: false, - scope: $scope, - options: 'inventory_files', - model: 'inventory_file' - }); - - // TODO: figure out why the inventory file model is being set to - // dirty - } - - function updateSCMProject() { - var url; - - if (!Empty($scope.project)) { - url = GetBasePath('projects') + $scope.project + '/inventories/'; - Wait('start'); - Rest.setUrl(url); - Rest.get() - .then(({data}) => { - $scope.inventory_files = data; - $scope.inventory_files.push("/ (project root)"); - - if (inventorySourceData.source_path !== "") { - $scope.inventory_file = inventorySourceData.source_path; - if ($scope.inventory_files.indexOf($scope.inventory_file) < 0) { - $scope.inventory_files.push($scope.inventory_file); - } - } else { - $scope.inventory_file = "/ (project root)"; - } - sync_inventory_file_select2(); - Wait('stop'); - }) - .catch(() => { - Alert('Cannot get inventory files', 'Unable to retrieve the list of inventory files for this project.', 'alert-info'); - Wait('stop'); - }); - } - } - - function initRegionSelect() { - CreateSelect2({ - element: '#inventory_source_source_regions', - multiple: true - }); - - let add_new = false; - if( _.get($scope, 'source') === 'ec2' || _.get($scope.source, 'value') === 'ec2') { - $scope.group_by_choices = $scope.ec2_group_by; - let group_by = inventorySourceData.group_by.split(','); - $scope.group_by = _.map(group_by, (item) => _.find($scope.ec2_group_by, { value: item })); - - $scope.groupByPopOver = "

" + i18n._("Select which groups to create automatically. ") + - $rootScope.BRAND_NAME + i18n._(" will create group names similar to the following examples based on the options selected:") + "

    " + - "
  • " + i18n._("Availability Zone:") + "zones » us-east-1b
  • " + - "
  • " + i18n._("Image ID:") + "images » ami-b007ab1e
  • " + - "
  • " + i18n._("Instance ID:") + "instances » i-ca11ab1e
  • " + - "
  • " + i18n._("Instance Type:") + "types » type_m1_medium
  • " + - "
  • " + i18n._("Key Name:") + "keys » key_testing
  • " + - "
  • " + i18n._("Region:") + "regions » us-east-1
  • " + - "
  • " + i18n._("Security Group:") + "security_groups » security_group_default
  • " + - "
  • " + i18n._("Tags:") + "tags » tag_Name » tag_Name_host1
  • " + - "
  • " + i18n._("VPC ID:") + "vpcs » vpc-5ca1ab1e
  • " + - "
  • " + i18n._("Tag None:") + "tags » tag_none
  • " + - "

" + i18n._("If blank, all groups above are created except") + "" + i18n._("Instance ID") + ".

"; - - - $scope.instanceFilterPopOver = "

" + i18n._("Provide a comma-separated list of filter expressions. ") + - i18n._("Hosts are imported to ") + $rootScope.BRAND_NAME + i18n._(" when ") + "" + i18n._("ANY") + "" + i18n._(" of the filters match.") + "

" + - i18n._("Limit to hosts having a tag:") + "
\n" + - "
tag-key=TowerManaged
\n" + - i18n._("Limit to hosts using either key pair:") + "
\n" + - "
key-name=staging, key-name=production
\n" + - i18n._("Limit to hosts where the Name tag begins with ") + "" + i18n._("test") + ":
\n" + - "
tag:Name=test*
\n" + - "

" + i18n._("View the ") + "" + i18n._("Describe Instances documentation") + " " + - i18n._("for a complete list of supported filters.") + "

"; - - } - if( _.get($scope, 'source') === 'vmware' || _.get($scope.source, 'value') === 'vmware') { - add_new = true; - $scope.group_by_choices = (inventorySourceData.group_by) ? inventorySourceData.group_by.split(',') - .map((i) => ({name: i, label: i, value: i})) : []; - $scope.group_by = $scope.group_by_choices; - $scope.groupByPopOver = i18n._(`Specify which groups to create automatically. Group names will be created similar to the options selected. If blank, all groups above are created. Refer to Ansible Tower documentation for more detail.`); - $scope.instanceFilterPopOver = i18n._(`Provide a comma-separated list of filter expressions. Hosts are imported when all of the filters match. Refer to Ansible Tower documentation for more detail.`); - } - if( _.get($scope, 'source') === 'tower' || _.get($scope.source, 'value') === 'tower') { - $scope.instanceFilterPopOver = i18n._(`Provide the named URL encoded name or id of the remote Tower inventory to be imported.`); - } - CreateSelect2({ - element: '#inventory_source_group_by', - multiple: true, - addNew: add_new - }); - } - - $scope.lookupProject = function(){ - $state.go('.project', { - project_search: { - page_size: '5', - page: '1' - } - }); - }; - - $scope.lookupCredential = function(){ - // For most source type selections, we filter for 1-1 matches to credential_type namespace. - let searchKey = 'credential_type__namespace'; - let searchValue = $scope.source.value; - - // SCM and custom source types are more generic in terms of the credentials they - // accept - any cloud or user-defined credential type can be used. We filter for - // these using the credential_type kind field, which categorizes all cloud and - // user-defined credentials as 'cloud'. - if ($scope.source.value === 'scm') { - searchKey = 'credential_type__kind'; - searchValue = 'cloud'; - } - - if ($scope.source.value === 'custom') { - searchKey = 'credential_type__kind'; - searchValue = 'cloud'; - } - - // When the selection is 'ec2' we actually want to filter for the 'aws' namespace. - if ($scope.source.value === 'ec2') { - searchValue = 'aws'; - } - - $state.go('.credential', { - credential_search: { - [searchKey]: searchValue, - page_size: '5', - page: '1' - } - }); - }; - - $scope.formCancel = function() { - $state.go('^'); - }; - $scope.formSave = function() { - var params; - - params = { - id: inventorySourceData.id, - name: $scope.name, - description: $scope.description, - inventory: inventoryData.id, - instance_filters: $scope.instance_filters, - source_script: $scope.inventory_script, - credential: $scope.credential, - overwrite: $scope.overwrite, - overwrite_vars: $scope.overwrite_vars, - update_on_launch: $scope.update_on_launch, - update_cache_timeout: $scope.update_cache_timeout || 0, - verbosity: $scope.verbosity.value, - custom_virtualenv: $scope.custom_virtualenv || null, - // comma-delimited strings - group_by: SourcesService.encodeGroupBy($scope.source, $scope.group_by), - source_regions: _.map($scope.source_regions, 'value').join(',') - }; - - if ($scope.source) { - let source_vars = $scope.source.value === 'scm' ? $scope.custom_variables : $scope[$scope.source.value + '_variables']; - params.source_vars = source_vars === '---' || source_vars === '{}' ? null : source_vars; - params.source = $scope.source.value; - if ($scope.source.value === 'scm') { - params.update_on_project_update = $scope.update_on_project_update; - params.source_project = $scope.project; - - if ($scope.inventory_file === '/ (project root)') { - params.source_path = ""; - } else { - params.source_path = $scope.inventory_file; - } - } - } else { - params.source = null; - } - - inventorySource.request('put', { - data: params - }).then(() => { - $state.go('.', null, { reload: true }); - }).catch(({ data, status, config }) => { - ProcessErrors($scope, data, status, null, { - hdr: 'Error!', - msg: InventoryHostsStrings.get('error.CALL', { path: `${config.url}`, status }) - }); - }); - }; - - $scope.sourceChange = function(source) { - source = (source && source.value) ? source.value : ''; - if ($scope.source.value === "scm" && $scope.source.value === "custom") { - $scope.credentialBasePath = GetBasePath('credentials') + '?credential_type__kind__in=cloud,network'; - } - else{ - $scope.credentialBasePath = (source === 'ec2') ? GetBasePath('credentials') + '?credential_type__namespace=aws' : GetBasePath('credentials') + (source === '' ? '' : 'credential_type__namespace=' + (source)); - } - if (source === 'ec2' || source === 'custom' || source === 'vmware' || source === 'openstack' || source === 'scm' || source === 'cloudforms' || source === "satellite6") { - $scope.envParseType = 'yaml'; - - var varName; - if (source === 'scm') { - varName = 'custom_variables'; - } else { - varName = source + '_variables'; - } - - $scope[varName] = $scope[varName] === (null || undefined) ? '---' : $scope[varName]; - ParseTypeChange({ - scope: $scope, - field_id: varName, - variable: varName, - parse_variable: 'envParseType' - }); - } - - // reset fields - $scope.group_by_choices = source === 'ec2' ? $scope.ec2_group_by : null; - // azure_rm regions choices are keyed as "azure" in an OPTIONS request to the inventory_sources endpoint - $scope.source_region_choices = source === 'azure_rm' ? $scope.azure_regions : $scope[source + '_regions']; - $scope.cloudCredentialRequired = source !== '' && source !== 'scm' && source !== 'custom' && source !== 'ec2' ? true : false; - $scope.source_regions = null; - $scope.credential = null; - $scope.credential_name = null; - $scope.group_by = null; - $scope.group_by_choices = []; - $scope.overwrite_vars = false; - - initRegionSelect(); - - }; - } -]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.route.js b/awx/ui/client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.route.js deleted file mode 100644 index e66cb0b69160..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.route.js +++ /dev/null @@ -1,53 +0,0 @@ -export default { - name: "inventories.edit.inventory_sources.edit", - url: "/edit/:inventory_source_id", - ncyBreadcrumb: { - parent: "inventories.edit.inventory_sources", - label: '{{breadcrumb.inventory_source_name}}' - }, - views: { - 'groupForm@inventories': { - templateProvider: function(GenerateForm, SourcesFormDefinition) { - let form = SourcesFormDefinition; - return GenerateForm.buildHTML(form, { - mode: 'edit', - related: false - }); - }, - controller: 'SourcesEditController' - } - }, - resolve: { - inventorySource: ['InventorySourceModel', '$stateParams', (InventorySource, $stateParams) => { - return new InventorySource('get', $stateParams.inventory_source_id); - }], - inventorySourcesOptions: ['InventoriesService', '$stateParams', function(InventoriesService, $stateParams) { - return InventoriesService.inventorySourcesOptions($stateParams.inventory_id) - .then((response) => response.data); - }], - isNotificationAdmin: ['Rest', 'ProcessErrors', 'GetBasePath', 'i18n', - function(Rest, ProcessErrors, GetBasePath, i18n) { - Rest.setUrl(`${GetBasePath('organizations')}?role_level=notification_admin_role&page_size=1`); - return Rest.get() - .then(({data}) => { - return data.count > 0; - }) - .catch(({data, status}) => { - ProcessErrors(null, data, status, null, { - hdr: i18n._('Error!'), - msg: i18n._('Failed to get organizations for which this user is a notification administrator. GET returned ') + status - }); - }); - }], - ConfigData: ['ConfigService', 'ProcessErrors', 'i18n', (ConfigService, ProcessErrors, i18n) => { - return ConfigService.getConfig() - .then(response => response) - .catch(({data, status}) => { - ProcessErrors(null, data, status, null, { - hdr: i18n._('Error!'), - msg: i18n._('Failed to get config. GET returned status: ') + status - }); - }); - }] - } -}; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/sources/edit/sources-notifications.route.js b/awx/ui/client/src/inventories-hosts/inventories/related/sources/edit/sources-notifications.route.js deleted file mode 100644 index b739f2287d77..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/sources/edit/sources-notifications.route.js +++ /dev/null @@ -1,89 +0,0 @@ -import { N_ } from '../../../../../i18n'; - -export default { - searchPrefix: 'notification', - name: "inventories.edit.inventory_sources.edit.notifications", - url: `/notifications`, - ncyBreadcrumb: { - parent: "inventories.edit.inventory_sources.edit", - label: N_("NOTIFICATIONS") - }, - params: { - [ 'notification_search']: { - value: { order_by: 'name' } - } - }, - views: { - 'related': { - templateProvider: function(FormDefinition, GenerateForm, $stateParams, SourcesFormDefinition) { - var form, html; - if($stateParams && $stateParams.inventory_source_id){ - form = SourcesFormDefinition; - } - else { - form = typeof(FormDefinition) === 'function' ? - FormDefinition() : FormDefinition; - } - html = GenerateForm.buildCollection({ - mode: 'edit', - related: `notifications`, - form: form - }); - return html; - }, - controller: ['$scope', 'NotificationsList', 'Dataset', 'ToggleNotification', 'NotificationsListInit', 'GetBasePath', '$stateParams', - function($scope, list, Dataset, ToggleNotification, NotificationsListInit, GetBasePath, $stateParams) { - var params = $stateParams, - id = params.inventory_source_id, - url = GetBasePath('inventory_sources'); - - function init() { - $scope.list = list; - $scope[`${list.iterator}_dataset`] = Dataset.data; - $scope[list.name] = $scope[`${list.iterator}_dataset`].results; - - - NotificationsListInit({ - scope: $scope, - url: url, - id: id - }); - - $scope.$watch(`${list.iterator}_dataset`, function() { - // The list data has changed and we need to update which notifications are on/off - $scope.$emit('relatednotifications'); - }); - } - - $scope.toggleNotification = function(event, notifier_id, column) { - var notifier = this.notification; - try { - $(event.target).tooltip('hide'); - } - catch(e) { - // ignore - } - ToggleNotification({ - scope: $scope, - url: url + id, - notifier: notifier, - column: column, - callback: 'NotificationRefresh' - }); - }; - - init(); - - } - ] - } - }, - resolve: { - Dataset: ['NotificationsList', 'QuerySet', '$stateParams', 'GetBasePath', - (list, qs, $stateParams, GetBasePath) => { - let path = GetBasePath(list.basePath); - return qs.search(path, $stateParams[`${list.iterator}_search`]); - } - ] - } -}; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/sources/factories/cancel-source-update.factory.js b/awx/ui/client/src/inventories-hosts/inventories/related/sources/factories/cancel-source-update.factory.js deleted file mode 100644 index 97388e03f27e..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/sources/factories/cancel-source-update.factory.js +++ /dev/null @@ -1,63 +0,0 @@ -export default - function CancelSourceUpdate(Empty, Rest, ProcessErrors, Alert, Wait, Find) { - return function(params) { - var scope = params.scope, - id = params.id, - inventory_source = params.inventory_source; - - // Cancel the update process - if (Empty(inventory_source)) { - inventory_source = Find({ list: scope.inventory_sources, key: 'id', val: id }); - scope.selected_inventory_source_id = inventory_source.id; - } - - if (inventory_source && (inventory_source.status === 'running' || inventory_source.status === 'pending')) { - // We found the inventory_source, and there is a running update - Wait('start'); - Rest.setUrl(inventory_source.url); - Rest.get() - .then(({data}) => { - // Check that we have access to cancelling an update - var url = (data.related.current_update) ? data.related.current_update : data.related.last_update; - url += 'cancel/'; - Rest.setUrl(url); - Rest.get() - .then(({data}) => { - if (data.can_cancel) { - // Cancel the update process - Rest.setUrl(url); - Rest.post() - .then(() => { - Wait('stop'); - //Alert('Inventory Sync Canceled', 'Request to cancel the sync process was submitted to the task manger. ' + - // 'Click the button to monitor the status.', 'alert-info'); - }) - .catch(({data, status}) => { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + ' failed. POST status: ' + status - }); - }); - } - else { - Wait('stop'); - } - }) - .catch(({data, status}) => { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + ' failed. GET status: ' + status - }); - }); - }) - .catch(({data, status}) => { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + inventory_source.url + ' failed. GET status: ' + status - }); - }); - } - }; - } - -CancelSourceUpdate.$inject = - [ 'Empty', 'Rest', 'ProcessErrors', - 'Alert', 'Wait', 'Find' - ]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/sources/factories/get-source-type-options.factory.js b/awx/ui/client/src/inventories-hosts/inventories/related/sources/factories/get-source-type-options.factory.js deleted file mode 100644 index 33e9b83ca746..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/sources/factories/get-source-type-options.factory.js +++ /dev/null @@ -1,37 +0,0 @@ -export default - function GetSourceTypeOptions(Rest, ProcessErrors, GetBasePath) { - return function(params) { - var scope = params.scope, - variable = params.variable; - - if (scope[variable] === undefined) { - scope[variable] = []; - Rest.setUrl(GetBasePath('inventory_sources')); - Rest.options() - .then(({data}) => { - var i, choices = data.actions.GET.source.choices; - for (i = 0; i < choices.length; i++) { - if (choices[i][0] !== 'file' && choices[i][0] !== "") { - scope[variable].push({ - label: choices[i][1], - value: choices[i][0] - }); - } - } - scope.cloudCredentialRequired = false; - scope.$emit('sourceTypeOptionsReady'); - }) - .catch(({data, status}) => { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to retrieve options for inventory_sources.source. OPTIONS status: ' + status - }); - }); - } - }; - } - -GetSourceTypeOptions.$inject = - [ 'Rest', - 'ProcessErrors', - 'GetBasePath' - ]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js b/awx/ui/client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js deleted file mode 100644 index 772af4c68b39..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js +++ /dev/null @@ -1,61 +0,0 @@ -export default - function GetSyncStatusMsg(i18n) { - return function(params) { - var status = params.status, - launch_class = '', - launch_tip = i18n._('Start sync process'), - schedule_tip = i18n._('Schedule inventory syncs'), - stat, stat_class, status_tip; - - stat = status; - stat_class = stat; - - switch (status) { - case 'never updated': - stat = 'never'; - stat_class = 'na'; - status_tip = i18n._('Sync not performed. Click') + ' ' + i18n._('to start it now.'); - break; - case 'none': - case 'ok': - case '': - launch_class = 'btn-disabled'; - stat = 'n/a'; - stat_class = 'na'; - status_tip = i18n._('Cloud source not configured. Click') + ' ' + i18n._('to update.'); - launch_tip = i18n._('Cloud source not configured.'); - break; - case 'canceled': - status_tip = i18n._('Sync canceled. Click to view log.'); - break; - case 'failed': - status_tip = i18n._('Sync failed. Click to view log.'); - break; - case 'successful': - status_tip = i18n._('Sync completed. Click to view log.'); - break; - case 'pending': - status_tip = i18n._('Sync pending.'); - launch_class = "btn-disabled"; - launch_tip = "Sync pending"; - break; - case 'updating': - case 'running': - launch_class = "btn-disabled"; - launch_tip = i18n._("Sync running"); - status_tip = i18n._("Sync running. Click to view log."); - break; - } - - return { - "class": stat_class, - "tooltip": status_tip, - "status": stat, - "launch_class": launch_class, - "launch_tip": launch_tip, - "schedule_tip": schedule_tip - }; - }; - } - -GetSyncStatusMsg.$inject = ['i18n']; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/sources/factories/view-update-status.factory.js b/awx/ui/client/src/inventories-hosts/inventories/related/sources/factories/view-update-status.factory.js deleted file mode 100644 index 5b928e5cf590..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/sources/factories/view-update-status.factory.js +++ /dev/null @@ -1,35 +0,0 @@ -export default - function ViewUpdateStatus($state, Rest, ProcessErrors, Alert, Wait, Empty, Find) { - return function(params) { - var scope = params.scope, - inventory_source_id = params.inventory_source_id, - inventory_source = Find({ list: scope.inventory_sources, key: 'id', val: inventory_source_id }); - - if (inventory_source) { - if (Empty(inventory_source.status) || inventory_source.status === "never updated") { - Alert('No Status Available', '
An inventory sync has not been performed for the selected group. Start the process by ' + - 'clicking the button.
', 'alert-info', null, null, null, null, true); - } else { - Wait('start'); - Rest.setUrl(inventory_source.url); - Rest.get() - .then(({data}) => { - // Get the ID from the correct summary field - var update_id = (data.summary_fields.current_update) ? data.summary_fields.current_update.id : data.summary_fields.last_update.id; - - $state.go('output', { id: update_id, type: 'inventory' }); - }) - .catch(({data, status}) => { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to retrieve inventory source: ' + inventory_source.url + - ' GET returned status: ' + status }); - }); - } - } - }; - } - -ViewUpdateStatus.$inject = - [ '$state', 'Rest', 'ProcessErrors', - 'Alert', 'Wait', 'Empty', 'Find' - ]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/sources/list/main.js b/awx/ui/client/src/inventories-hosts/inventories/related/sources/list/main.js deleted file mode 100644 index 2c7c3b62f848..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/sources/list/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import controller from './sources-list.controller'; - -export default - angular.module('sourcesList', []) - .controller('SourcesListController', controller); diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/sources/list/schedule/sources-schedule-add.route.js b/awx/ui/client/src/inventories-hosts/inventories/related/sources/list/schedule/sources-schedule-add.route.js deleted file mode 100644 index 0b3877437802..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/sources/list/schedule/sources-schedule-add.route.js +++ /dev/null @@ -1,17 +0,0 @@ -import { N_ } from '../../../../../../i18n'; -import {templateUrl} from '../../../../../../shared/template-url/template-url.factory'; - -export default { - name: 'inventories.edit.inventory_sources.edit.schedules.add', - url: '/add', - ncyBreadcrumb: { - parent: 'inventories.edit.inventory_sources.edit.schedules', - label: N_("CREATE SCHEDULE") - }, - views: { - 'scheduler@inventories': { - controller: 'schedulerAddController', - templateUrl: templateUrl("scheduler/schedulerForm") - } - } -}; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/sources/list/schedule/sources-schedule-edit.route.js b/awx/ui/client/src/inventories-hosts/inventories/related/sources/list/schedule/sources-schedule-edit.route.js deleted file mode 100644 index 2c956fd6cd26..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/sources/list/schedule/sources-schedule-edit.route.js +++ /dev/null @@ -1,18 +0,0 @@ -import {templateUrl} from '../../../../../../shared/template-url/template-url.factory'; -import editScheduleResolve from '../../../../../../scheduler/editSchedule.resolve'; - -export default { - name: 'inventories.edit.inventory_sources.edit.schedules.edit', - url: '/:schedule_id', - ncyBreadcrumb: { - parent: 'inventories.edit.inventory_sources.edit.schedules', - label: "{{breadcrumb.schedule_name}}" - }, - views: { - 'scheduler@inventories': { - templateUrl: templateUrl("scheduler/schedulerForm"), - controller: 'schedulerEditController', - } - }, - resolve: editScheduleResolve() -}; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/sources/list/schedule/sources-schedule.route.js b/awx/ui/client/src/inventories-hosts/inventories/related/sources/list/schedule/sources-schedule.route.js deleted file mode 100644 index 89cd27dffeab..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/sources/list/schedule/sources-schedule.route.js +++ /dev/null @@ -1,54 +0,0 @@ -import { N_ } from '../../../../../../i18n'; - -export default { - searchPrefix: 'schedule', - name: 'inventories.edit.inventory_sources.edit.schedules', - url: '/schedules', - ncyBreadcrumb: { - parent: 'inventories.edit.inventory_sources.edit', - label: N_('SCHEDULES') - }, - views: { - 'related': { - templateProvider: function(ScheduleList, generateList){ - let html = generateList.build({ - list: ScheduleList, - mode: 'edit' - }); - return html; - }, - controller: 'schedulerListController' - } - }, - resolve: { - Dataset: ['ScheduleList', 'QuerySet', '$stateParams', 'GetBasePath', 'inventorySource', - function(list, qs, $stateParams, GetBasePath, inventorySource) { - let path = `${inventorySource.get().related.schedules}`; - return qs.search(path, $stateParams[`${list.iterator}_search`]); - } - ], - ParentObject: ['inventorySource', function(inventorySource) { - return inventorySource.get(); - }], - UnifiedJobsOptions: ['Rest', 'GetBasePath', '$stateParams', '$q', - function(Rest, GetBasePath, $stateParams, $q) { - Rest.setUrl(GetBasePath('unified_jobs')); - var val = $q.defer(); - Rest.options() - .then(function(data) { - val.resolve(data.data); - }, function(data) { - val.reject(data); - }); - return val.promise; - }], - ScheduleList: ['SchedulesList', 'inventorySource', - (SchedulesList, inventorySource) => { - let list = _.cloneDeep(SchedulesList); - list.basePath = `${inventorySource.get().related.schedules}`; - list.title = false; - return list; - } - ] - } -}; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js b/awx/ui/client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js deleted file mode 100644 index e1ba3dfd75a8..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js +++ /dev/null @@ -1,231 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - export default - ['$scope', '$rootScope', '$state', '$stateParams', 'SourcesListDefinition', - 'InventoryUpdate', 'CancelSourceUpdate', - 'ViewUpdateStatus', 'rbacUiControlService', 'GetBasePath', - 'GetSyncStatusMsg', 'Dataset', 'Find', 'QuerySet', - 'inventoryData', '$filter', 'Prompt', 'Wait', 'SourcesService', 'inventorySourceOptions', - 'canAdd', 'hasSyncableSources', 'i18n', 'InventoryHostsStrings', 'InventorySourceModel', 'ProcessErrors', - function($scope, $rootScope, $state, $stateParams, SourcesListDefinition, - InventoryUpdate, CancelSourceUpdate, - ViewUpdateStatus, rbacUiControlService, GetBasePath, GetSyncStatusMsg, - Dataset, Find, qs, inventoryData, $filter, Prompt, - Wait, SourcesService, inventorySourceOptions, canAdd, hasSyncableSources, i18n, - InventoryHostsStrings, InventorySource, ProcessErrors){ - - let inventorySource = new InventorySource(); - - let list = SourcesListDefinition; - var inventory_source; - - init(); - - function init(){ - $scope.inventory_id = $stateParams.inventory_id; - $scope.canAdhoc = inventoryData.summary_fields.user_capabilities.adhoc; - $scope.canAdd = canAdd; - $scope.showSyncAll = hasSyncableSources; - - // Search init - $scope.list = list; - $scope[`${list.iterator}_dataset`] = Dataset.data; - $scope[list.name] = $scope[`${list.iterator}_dataset`].results; - - $scope.inventory_id = $stateParams.inventory_id; - _.forEach($scope[list.name], buildStatusIndicators); - optionsRequestDataProcessing(); - - $scope.$on(`ws-jobs`, function(e, data){ - inventory_source = Find({ list: $scope.inventory_sources, key: 'id', val: data.inventory_source_id }); - - if (inventory_source === undefined || inventory_source === null) { - inventory_source = {}; - } - - if(data.status === 'failed' || data.status === 'successful'){ - let path = GetBasePath('inventory') + $stateParams.inventory_id + '/inventory_sources'; - - qs.search(path, $state.params[`${list.iterator}_search`]) - .then((searchResponse)=> { - $scope[`${list.iterator}_dataset`] = searchResponse.data; - $scope[list.name] = $scope[`${list.iterator}_dataset`].results; - _.forEach($scope[list.name], buildStatusIndicators); - optionsRequestDataProcessing(); - }); - } else { - var status = GetSyncStatusMsg({ - status: data.status - }); - inventory_source.status = data.status; - inventory_source.status_class = status.class; - inventory_source.status_tooltip = status.tooltip; - inventory_source.launch_tooltip = status.launch_tip; - inventory_source.launch_class = status.launch_class; - } - }); - - $scope.$watchCollection(`${$scope.list.name}`, function() { - _.forEach($scope[list.name], buildStatusIndicators); - optionsRequestDataProcessing(); - }); - } - - function optionsRequestDataProcessing(){ - if ($scope[list.name] !== undefined) { - $scope[list.name].forEach(function(item, item_idx) { - var itm = $scope[list.name][item_idx]; - - // Set the item source label - if (list.fields.source && inventorySourceOptions && inventorySourceOptions.hasOwnProperty('source')) { - inventorySourceOptions.source.choices.forEach(function(choice) { - if (choice[0] === item.source) { - itm.source_label = choice[1]; - } - }); - } - }); - } - } - - function buildStatusIndicators(inventory_source){ - if (inventory_source === undefined || inventory_source === null) { - inventory_source = {}; - } - - let inventory_source_status; - - inventory_source_status = GetSyncStatusMsg({ - status: inventory_source.status, - has_inventory_sources: inventory_source.has_inventory_sources, - source: ( (inventory_source) ? inventory_source.source : null ) - }); - _.assign(inventory_source, - {status_class: inventory_source_status.class}, - {status_tooltip: inventory_source_status.tooltip}, - {launch_tooltip: inventory_source_status.launch_tip}, - {launch_class: inventory_source_status.launch_class}, - {source: inventory_source ? inventory_source.source : null}, - {status: inventory_source ? inventory_source.status : null}); - } - - $scope.createSource = function(){ - $state.go('inventories.edit.inventory_sources.add'); - }; - $scope.editSource = function(id){ - $state.go('inventories.edit.inventory_sources.edit', {inventory_source_id: id}); - }; - $scope.deleteSource = function(inventory_source){ - var action = function(){ - $rootScope.promptActionBtnClass = "Modal-errorButton--sourcesDelete"; - Wait('start'); - let hostDelete = SourcesService.deleteHosts(inventory_source.id).catch(({data, status}) => { - $('#prompt-modal').modal('hide'); - Wait('stop'); - ProcessErrors($scope, data, status, null, - { - hdr: i18n._('Error!'), - msg: i18n._('There was an error deleting inventory source hosts. Returned status: ') + - status - }); - }); - let groupDelete = SourcesService.deleteGroups(inventory_source.id).catch(({data, status}) => { - $('#prompt-modal').modal('hide'); - Wait('stop'); - ProcessErrors($scope, data, status, null, - { - hdr: i18n._('Error!'), - msg: i18n._('There was an error deleting inventory source groups. Returned status: ') + - status - }); - }); - Promise.all([hostDelete, groupDelete]).then(() => { - SourcesService.delete(inventory_source.id).then(() => { - $('#prompt-modal').modal('hide'); - delete $rootScope.promptActionBtnClass; - let reloadListStateParams = null; - - if($scope.inventory_sources.length === 1 && $state.params.inventory_source_search && !_.isEmpty($state.params.inventory_source_search.page) && $state.params.inventory_source_search.page !== '1') { - reloadListStateParams = _.cloneDeep($state.params); - reloadListStateParams.inventory_source_search.page = (parseInt(reloadListStateParams.inventory_source_search.page)-1).toString(); - } - if (parseInt($state.params.inventory_source_id) === inventory_source.id) { - $state.go('^', reloadListStateParams, {reload: true}); - } else { - $state.go('.', reloadListStateParams, {reload: true}); - } - Wait('stop'); - }) - .catch(({data, status}) => { - $('#prompt-modal').modal('hide'); - Wait('stop'); - ProcessErrors($scope, data, status, null, - { - hdr: i18n._('Error!'), - msg: i18n._('There was an error deleting inventory source. Returned status: ') + - status - }); - }); - }); - }; - - inventorySource.getDependentResourceCounts(inventory_source.id) - .then((counts) => { - const invalidateRelatedLines = []; - let deleteModalBody = `
${InventoryHostsStrings.get('deleteResource.CONFIRM', 'inventory source')}
`; - - counts.forEach(countObj => { - if(countObj.count && countObj.count > 0) { - invalidateRelatedLines.push(`
${countObj.label}${countObj.count}
`); - } - }); - - if (invalidateRelatedLines && invalidateRelatedLines.length > 0) { - deleteModalBody = `
${InventoryHostsStrings.get('deleteResource.USED_BY', 'inventory source')} ${InventoryHostsStrings.get('deleteResource.CONFIRM', 'inventory source')}
`; - invalidateRelatedLines.forEach(invalidateRelatedLine => { - deleteModalBody += invalidateRelatedLine; - }); - } - - Prompt({ - hdr: i18n._('Delete Source'), - resourceName: $filter('sanitize')(inventory_source.name), - body: deleteModalBody, - action: action, - actionText: i18n._('DELETE') - }); - $rootScope.promptActionBtnClass = 'Modal-errorButton'; - }); - - }; - - $scope.updateSource = function(inventory_source) { - InventoryUpdate({ - scope: $scope, - url: inventory_source.related.update - }); - }; - - $scope.cancelUpdate = function (id) { - CancelSourceUpdate({ scope: $scope, id: id }); - }; - - $scope.viewUpdateStatus = function (id) { - ViewUpdateStatus({ - scope: $scope, - inventory_source_id: id - }); - }; - - $scope.syncAllSources = function() { - InventoryUpdate({ - scope: $scope, - url: inventoryData.related.update_inventory_sources, - updateAllSources: true - }); - }; - - }]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html b/awx/ui/client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html deleted file mode 100644 index 7a6ef41a07b8..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html +++ /dev/null @@ -1,79 +0,0 @@ - diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/sources/list/sources-list.route.js b/awx/ui/client/src/inventories-hosts/inventories/related/sources/list/sources-list.route.js deleted file mode 100644 index 0bc50c9e7571..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/sources/list/sources-list.route.js +++ /dev/null @@ -1,90 +0,0 @@ -import { N_ } from '../../../../../i18n'; - -export default { - name: "inventories.edit.inventory_sources", - url: "/inventory_sources?{inventory_source_search:queryset}", - params: { - inventory_source_search: { - value: { - page_size: "20", - order_by: "name", - not__source: "" - }, - dynamic: true, - squash: "" - } - }, - data: { - socket: { - groups: { - jobs: ["status_changed"], - inventories: ["status_changed"] - } - } - }, - ncyBreadcrumb: { - parent: "inventories.edit", - label: N_("SOURCES") - }, - views: { - 'related': { - templateProvider: function(SourcesListDefinition, generateList) { - let list = _.cloneDeep(SourcesListDefinition); - let html = generateList.build({ - list: list, - mode: 'edit' - }); - return html; - }, - controller: 'SourcesListController' - } - }, - resolve: { - inventorySourceOptions: ['SourcesService', (SourcesService) => { - return SourcesService.options().then(response => response.data.actions.GET); - }], - Dataset: ['SourcesListDefinition', 'QuerySet', '$stateParams', 'GetBasePath', '$interpolate', '$rootScope', - (list, qs, $stateParams, GetBasePath, $interpolate, $rootScope) => { - // allow related list definitions to use interpolated $rootScope / $stateParams in basePath field - let path, interpolator; - if (GetBasePath(list.basePath)) { - path = GetBasePath(list.basePath); - } else { - interpolator = $interpolate(list.basePath); - path = interpolator({ $rootScope: $rootScope, $stateParams: $stateParams }); - } - return qs.search(path, $stateParams[`${list.iterator}_search`]); - } - ], - inventoryData: ['InventoriesService', '$stateParams', function(InventoriesService, $stateParams) { - return InventoriesService.getInventory($stateParams.inventory_id).then(res => res.data); - }], - canAdd: ['rbacUiControlService', 'GetBasePath', '$stateParams', function(rbacUiControlService, GetBasePath, $stateParams) { - return rbacUiControlService.canAdd(GetBasePath('inventory') + $stateParams.inventory_id + "/inventory_sources") - .then(function(res) { - return res.canAdd; - }) - .catch(function() { - return false; - }); - }], - hasSyncableSources: ['InventoriesService', '$stateParams', function(InventoriesService, $stateParams) { - return InventoriesService.updateInventorySourcesGet($stateParams.inventory_id) - .then(function(res) { - let canUpdateFound = false; - if(res.data && res.data.length > 0) { - res.data.forEach(function(source) { - if(source.can_update) { - canUpdateFound = true; - } - }); - } - - return canUpdateFound; - }) - .catch(function() { - return false; - }); - }] - } -}; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/sources/lookup/sources-lookup-credential.route.js b/awx/ui/client/src/inventories-hosts/inventories/related/sources/lookup/sources-lookup-credential.route.js deleted file mode 100644 index a61e96725ee4..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/sources/lookup/sources-lookup-credential.route.js +++ /dev/null @@ -1,55 +0,0 @@ -export default { - params: { - credential_search: { - value: { - page_size:"5", - order_by:"name", - role_level:"use_role", - kind: null, - credential_type__kind: null - }, - dynamic:true, - squash:"" - } - }, - data: { - basePath:"credentials", - formChildState:true - }, - ncyBreadcrumb: { - skip: true - }, - views: { - 'modal': { - templateProvider: function(ListDefinition, generateList) { - let list_html = generateList.build({ - mode: 'lookup', - list: ListDefinition, - input_type: 'radio' - }); - return `${list_html}`; - - } - } - }, - resolve: { - ListDefinition: ['CredentialList', function(list) { - return list; - }], - Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath', '$transition$', - (list, qs, $stateParams, GetBasePath, $transition$) => { - const toState = $transition$.to(); - toState.params.credential_search.value.credential_type__namespace = _.get($stateParams, 'credential_search.credential_type__namespace', null); - toState.params.credential_search.value.credential_type__kind = _.get($stateParams, 'credential_search.credential_type__kind', null); - return qs.search(GetBasePath('credentials'), $stateParams[`${list.iterator}_search`]); - } - ] - }, - onExit: function($state) { - if ($state.transition) { - $('#form-modal').modal('hide'); - $('.modal-backdrop').remove(); - $('body').removeClass('modal-open'); - } - } -}; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/sources/lookup/sources-lookup-inventory-script.route.js b/awx/ui/client/src/inventories-hosts/inventories/related/sources/lookup/sources-lookup-inventory-script.route.js deleted file mode 100644 index c2a9ab71b940..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/sources/lookup/sources-lookup-inventory-script.route.js +++ /dev/null @@ -1,65 +0,0 @@ -export default { - params: { - inventory_script_search: { - value: { - page_size: "5", - order_by: "name", - role_level: "admin_role", - }, - dynamic: true, - squash: "" - } - }, - data: { - basePath: "inventory_scripts", - formChildState: true - }, - ncyBreadcrumb: { - skip: true - }, - views: { - 'modal': { - templateProvider: function(ListDefinition, generateList) { - let list_html = generateList.build({ - mode: 'lookup', - list: ListDefinition, - input_type: 'radio' - }); - return `${list_html}`; - - } - } - }, - resolve: { - ListDefinition: ['InventoryScriptsList', function(list) { - return list; - }], - OrganizationId: ['ListDefinition', 'InventoriesService', '$stateParams', '$rootScope', - function(list, InventoriesService, $stateParams, $rootScope){ - if($rootScope.$$childTail && - $rootScope.$$childTail.$resolve && - $rootScope.$$childTail.$resolve.hasOwnProperty('inventoryData')){ - return $rootScope.$$childTail.$resolve.inventoryData.summary_fields.organization.id; - } - else { - return InventoriesService.getInventory($stateParams.inventory_id).then(res => res.data.summary_fields.organization.id); - } - }], - Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath', '$interpolate', '$rootScope', '$state', 'OrganizationId', - (list, qs, $stateParams, GetBasePath, $interpolate, $rootScope, $state, OrganizationId) => { - - $stateParams[`${list.iterator}_search`].role_level = "admin_role"; - $stateParams[`${list.iterator}_search`].organization = OrganizationId; - - return qs.search(GetBasePath('inventory_scripts'), $stateParams[`${list.iterator}_search`]); - } - ] - }, - onExit: function($state) { - if ($state.transition) { - $('#form-modal').modal('hide'); - $('.modal-backdrop').remove(); - $('body').removeClass('modal-open'); - } - } -}; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/sources/lookup/sources-lookup-project.route.js b/awx/ui/client/src/inventories-hosts/inventories/related/sources/lookup/sources-lookup-project.route.js deleted file mode 100644 index 53869691a73f..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/sources/lookup/sources-lookup-project.route.js +++ /dev/null @@ -1,51 +0,0 @@ -export default { - params: { - project_search: { - value: { - page_size:"5", - order_by:"name", - not__status:"never updated", - role_level:"use_role", - }, - dynamic:true, - squash:"" - } - }, - data: { - basePath:"projects", - formChildState:true - }, - ncyBreadcrumb: { - skip: true - }, - views: { - 'modal': { - templateProvider: function(ListDefinition, generateList) { - let list_html = generateList.build({ - mode: 'lookup', - list: ListDefinition, - input_type: 'radio' - }); - return `${list_html}`; - - } - } - }, - resolve: { - ListDefinition: ['ProjectList', function(list) { - return list; - }], - Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath', - (list, qs, $stateParams, GetBasePath) => { - return qs.search(GetBasePath('projects'), $stateParams[`${list.iterator}_search`]); - } - ] - }, - onExit: function($state) { - if ($state.transition) { - $('#form-modal').modal('hide'); - $('.modal-backdrop').remove(); - $('body').removeClass('modal-open'); - } - } -}; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/sources/main.js b/awx/ui/client/src/inventories-hosts/inventories/related/sources/main.js deleted file mode 100644 index 51eafc26c66f..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/sources/main.js +++ /dev/null @@ -1,30 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import sourcesList from './list/main'; -import sourcesAdd from './add/main'; -import sourcesEdit from './edit/main'; -import sourcesFormDefinition from './sources.form'; -import sourcesListDefinition from './sources.list'; -import service from './sources.service'; -import GetSyncStatusMsg from './factories/get-sync-status-msg.factory'; -import ViewUpdateStatus from './factories/view-update-status.factory'; -import CancelSourceUpdate from './factories/cancel-source-update.factory'; -import GetSourceTypeOptions from './factories/get-source-type-options.factory'; - -export default - angular.module('sources', [ - sourcesList.name, - sourcesAdd.name, - sourcesEdit.name - ]) - .factory('SourcesFormDefinition', sourcesFormDefinition) - .factory('SourcesListDefinition', sourcesListDefinition) - .factory('GetSyncStatusMsg', GetSyncStatusMsg) - .factory('ViewUpdateStatus', ViewUpdateStatus) - .factory('CancelSourceUpdate', CancelSourceUpdate) - .factory('GetSourceTypeOptions', GetSourceTypeOptions) - .service('SourcesService', service); diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/sources/sources.form.js b/awx/ui/client/src/inventories-hosts/inventories/related/sources/sources.form.js deleted file mode 100644 index 5df5ca3f8a26..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/sources/sources.form.js +++ /dev/null @@ -1,442 +0,0 @@ -/************************************************* - * Copyright (c) 2019 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['NotificationsList', 'i18n', function(NotificationsList, i18n){ - - var notifications_object = { - generateList: true, - include: "NotificationsList", - ngIf: "(sufficientRoleForNotif) && !(inventory_source_obj.source === undefined || inventory_source_obj.source === '')", - ngClick: "$state.go('inventories.edit.inventory_sources.edit.notifications')" - }; - let clone = _.clone(NotificationsList); - notifications_object = angular.extend(clone, notifications_object); - return { - addTitle: i18n._('CREATE SOURCE'), - editTitle: '{{ name }}', - showTitle: true, - name: 'inventory_source', - basePath: 'inventory_sources', - parent: 'inventories.edit.sources', - // the parent node this generated state definition tree expects to attach to - stateTree: 'inventories', - tabs: true, - // form generator inspects the current state name to determine whether or not to set an active (.is-selected) class on a form tab - // this setting is optional on most forms, except where the form's edit state name is not parentStateName.edit - activeEditState: 'inventories.edit.inventory_sources.edit', - detailsClick: "$state.go('inventories.edit.inventory_sources.edit')", - well: false, - subFormTitles: { - sourceSubForm: i18n._('Source Details'), - }, - fields: { - name: { - label: i18n._('Name'), - type: 'text', - ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', - required: true, - tab: 'properties' - }, - description: { - label: i18n._('Description'), - type: 'text', - ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', - tab: 'properties' - }, - source: { - label: i18n._('Source'), - type: 'select', - required: true, - ngOptions: 'source.label for source in source_type_options track by source.value', - ngChange: 'sourceChange(source)', - ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', - ngModel: 'source', - hasSubForm: true - }, - custom_virtualenv: { - label: i18n._('Ansible Environment'), - type: 'select', - defaultText: i18n._('Use Default Environment'), - ngOptions: 'venv for venv in custom_virtualenvs_options track by venv', - - awPopOver: "

" + i18n._("Select the custom Python virtual environment for this inventory source sync to run on.") + "

", - dataTitle: i18n._('Ansible Environment'), - dataContainer: 'body', - dataPlacement: 'right', - ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', - ngShow: 'custom_virtualenvs_options.length > 1' - }, - credential: { - label: i18n._('Credential'), - type: 'lookup', - list: 'CredentialList', - basePath: 'credentials', - ngShow: "source && source.value !== ''", - sourceModel: 'credential', - sourceField: 'name', - ngClick: 'lookupCredential()', - awRequiredWhen: { - reqExpression: "cloudCredentialRequired", - init: "false" - }, - subForm: 'sourceSubForm', - ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', - watchBasePath: "credentialBasePath" - }, - project: { - // initializes a default value for this search param - // search params with default values set will not generate user-interactable search tags - label: i18n._('Project'), - type: 'lookup', - list: 'ProjectList', - basePath: 'projects', - ngShow: "source && source.value === 'scm'", - sourceModel: 'project', - sourceField: 'name', - ngClick: 'lookupProject()', - awRequiredWhen: { - reqExpression: "source && source.value === 'scm'", - init: "false" - }, - ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', - watchBasePath: "projectBasePath", - subForm: 'sourceSubForm' - }, - inventory_file: { - label: i18n._('Inventory File'), - type:'select', - defaultText: i18n._('Choose an inventory file'), - ngOptions: 'file for file in inventory_files track by file', - ngShow: "source && source.value === 'scm'", - ngDisabled: "!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd) || disableInventoryFileBecausePermissionDenied", - id: 'inventory-file-select', - awRequiredWhen: { - reqExpression: "source && source.value === 'scm'", - init: "true" - }, - column: 1, - awPopOver: "

" + i18n._("Select the inventory file to be synced by this source. " + - "You can select from the dropdown or enter a file within the input.") + "

", - dataTitle: i18n._('Inventory File'), - dataPlacement: 'right', - dataContainer: "body", - includeInventoryFileNotFoundError: true, - subForm: 'sourceSubForm' - }, - source_regions: { - label: i18n._('Regions'), - type: 'select', - ngOptions: 'source.label for source in source_region_choices track by source.value', - multiSelect: true, - ngShow: "source && (source.value == 'rax' || source.value == 'ec2' || source.value == 'gce' || source.value == 'azure_rm')", - dataTitle: i18n._('Source Regions'), - dataPlacement: 'right', - awPopOver: "

" + i18n._("Click on the regions field to see a list of regions for your cloud provider. You can select multiple regions, or choose") + - "" + i18n._("All") + " " + i18n._("to include all regions. Only Hosts associated with the selected regions will be updated.") + "

", - dataContainer: 'body', - ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', - subForm: 'sourceSubForm' - }, - instance_filters: { - label: i18n._("Instance Filters"), - type: 'text', - ngShow: "source && (source.value == 'ec2' || source.value == 'vmware' || source.value == 'tower')", - dataTitle: i18n._('Instance Filters'), - dataPlacement: 'right', - awPopOverWatch: 'instanceFilterPopOver', - awPopOver: '{{ instanceFilterPopOver }}', - dataContainer: 'body', - ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', - subForm: 'sourceSubForm' - }, - group_by: { - label: i18n._('Only Group By'), - type: 'select', - ngShow: "source && (source.value == 'ec2' || source.value == 'vmware')", - ngOptions: 'source.label for source in group_by_choices track by source.value', - multiSelect: true, - dataTitle: i18n._("Only Group By"), - dataPlacement: 'right', - awPopOverWatch: 'groupByPopOver', - awPopOver: '{{ groupByPopOver }}', - dataContainer: 'body', - ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', - subForm: 'sourceSubForm' - }, - inventory_script: { - label : i18n._("Custom Inventory Script"), - type: 'lookup', - basePath: 'inventory_scripts', - list: 'InventoryScriptsList', - ngShow: "source && source.value === 'custom'", - sourceModel: 'inventory_script', - sourceField: 'name', - awRequiredWhen: { - reqExpression: "source && source.value === 'custom'", - init: "false" - }, - ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', - subForm: 'sourceSubForm' - }, - custom_variables: { - id: 'custom_variables', - label: i18n._('Environment Variables'), //"{{vars_label}}" , - ngShow: "source && source.value=='custom' || source.value === 'scm'", - type: 'textarea', - class: 'Form-textAreaLabel Form-formGroup--fullWidth', - rows: 6, - 'default': '---', - parseTypeName: 'envParseType', - dataTitle: i18n._("Environment Variables"), - dataPlacement: 'right', - awPopOver: "

" + i18n._("Provide environment variables to pass to the custom inventory script.") + "

" + - "

" + i18n._("Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.") + "

" + - i18n._("JSON:") + "
\n" + - "
{
 \"somevar\": \"somevalue\",
 \"password\": \"magic\"
}
\n" + - i18n._("YAML:") + "
\n" + - "
---
somevar: somevalue
password: magic
\n" + - "

" + i18n._("View JSON examples at ") + 'www.json.org

' + - "

" + i18n._("View YAML examples at ") + 'docs.ansible.com

', - dataContainer: 'body', - subForm: 'sourceSubForm' - }, - ec2_variables: { - id: 'ec2_variables', - label: i18n._('Source Variables'), //"{{vars_label}}" , - ngShow: "source && source.value == 'ec2'", - type: 'textarea', - class: 'Form-textAreaLabel Form-formGroup--fullWidth', - rows: 6, - 'default': '---', - parseTypeName: 'envParseType', - dataTitle: i18n._("Source Variables"), - dataPlacement: 'right', - awPopOver: "

" + i18n._("Override variables found in ec2.ini and used by the inventory update script. For a detailed description of these variables ") + - "" + - i18n._("view ec2.ini in the Ansible github repo.") + "

" + - "

" + i18n._("Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.") + "

" + - i18n._("JSON:") + "
\n" + - "
{
 \"somevar\": \"somevalue\",
 \"password\": \"magic\"
}
\n" + - i18n._("YAML:") + "
\n" + - "
---
somevar: somevalue
password: magic
\n" + - "

" + i18n._("View JSON examples at ") + 'www.json.org

' + - "

" + i18n._("View YAML examples at ") + 'docs.ansible.com

', - dataContainer: 'body', - subForm: 'sourceSubForm' - }, - vmware_variables: { - id: 'vmware_variables', - label: i18n._('Source Variables'), //"{{vars_label}}" , - ngShow: "source && source.value == 'vmware'", - type: 'textarea', - class: 'Form-textAreaLabel Form-formGroup--fullWidth', - rows: 6, - 'default': '---', - parseTypeName: 'envParseType', - dataTitle: i18n._("Source Variables"), - dataPlacement: 'right', - awPopOver: "

" + i18n._("Override variables found in vmware.ini and used by the inventory update script. For a detailed description of these variables ") + - "" + - i18n._("view vmware_inventory.ini in the Ansible github repo.") + "

" + - "

" + i18n._("Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.") + "

" + - i18n._("JSON:") + "
\n" + - "
{
 \"somevar\": \"somevalue\",
 \"password\": \"magic\"
}
\n" + - i18n._("YAML:") + "
\n" + - "
---
somevar: somevalue
password: magic
\n" + - "

" + i18n._("View JSON examples at ") + 'www.json.org

' + - "

" + i18n._("View YAML examples at ") + 'docs.ansible.com

', - dataContainer: 'body', - subForm: 'sourceSubForm' - }, - openstack_variables: { - id: 'openstack_variables', - label: i18n._('Source Variables'), //"{{vars_label}}" , - ngShow: "source && source.value == 'openstack'", - type: 'textarea', - class: 'Form-textAreaLabel Form-formGroup--fullWidth', - rows: 6, - 'default': '---', - parseTypeName: 'envParseType', - dataTitle: i18n._("Source Variables"), - dataPlacement: 'right', - awPopOver: i18n._(`Override variables found in openstack.yml and used by the inventory update script. For an example variable configuration - - view openstack.yml in the Ansible github repo. Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax.`), - dataContainer: 'body', - subForm: 'sourceSubForm' - }, - cloudforms_variables: { - id: 'cloudforms_variables', - label: i18n._('Source Variables'), - ngShow: "source && source.value == 'cloudforms'", - type: 'textarea', - class: 'Form-textAreaLabel Form-formGroup--fullWidth', - rows: 6, - 'default': '---', - parseTypeName: 'envParseType', - dataTitle: i18n._("Source Variables"), - dataPlacement: 'right', - awPopOver: i18n._(`Override variables found in cloudforms.ini and used by the inventory update script. For an example variable configuration - - view cloudforms.ini in the Ansible github repo. Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax.`), - dataContainer: 'body', - subForm: 'sourceSubForm' - }, - satellite6_variables: { - id: 'satellite6_variables', - label: i18n._('Source Variables'), - ngShow: "source && source.value == 'satellite6'", - type: 'textarea', - class: 'Form-textAreaLabel Form-formGroup--fullWidth', - rows: 6, - 'default': '---', - parseTypeName: 'envParseType', - dataTitle: i18n._("Source Variables"), - dataPlacement: 'right', - awPopOver: i18n._(`Override variables found in foreman.ini and used by the inventory update script. For an example variable configuration - - view foreman.ini in the Ansible github repo. Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax.`), - dataContainer: 'body', - subForm: 'sourceSubForm' - }, - azure_rm_variables: { - id: 'azure_rm_variables', - label: i18n._('Source Variables'), //"{{vars_label}}" , - ngShow: "source && source.value == 'azure_rm'", - type: 'textarea', - class: 'Form-textAreaLabel Form-formGroup--fullWidth', - rows: 6, - 'default': '---', - parseTypeName: 'envParseType', - dataTitle: i18n._("Source Variables"), - dataPlacement: 'right', - awPopOver: "

" + i18n._("Override variables found in azure_rm.ini and used by the inventory update script. For a detailed description of these variables ") + - "" + - i18n._("view azure_rm.ini in the Ansible github repo.") + "

" + - "

" + i18n._("Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two.") + "

" + - i18n._("JSON:") + "
\n" + - "
{
 \"somevar\": \"somevalue\",
 \"password\": \"magic\"
}
\n" + - i18n._("YAML:") + "
\n" + - "
---
somevar: somevalue
password: magic
\n" + - "

" + i18n._("View JSON examples at ") + 'www.json.org

' + - "

" + i18n._("View YAML examples at ") + 'docs.ansible.com

', - dataContainer: 'body', - subForm: 'sourceSubForm' - }, - verbosity: { - label: i18n._('Verbosity'), - type: 'select', - ngOptions: 'v.label for v in verbosity_options track by v.value', - ngShow: "source && (source.value !== '' && source.value !== null)", - disableChooseOption: true, - column: 1, - awPopOver: "

" + i18n._("Control the level of output ansible will produce for inventory source update jobs.") + "

", - dataTitle: i18n._('Verbosity'), - dataPlacement: 'right', - dataContainer: "body", - ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', - subForm: 'sourceSubForm' - }, - checkbox_group: { - label: i18n._('Update Options'), - type: 'checkbox_group', - ngShow: "source && (source.value !== '' && source.value !== null)", - subForm: 'sourceSubForm', - fields: [{ - name: 'overwrite', - label: i18n._('Overwrite'), - type: 'checkbox', - ngShow: "source.value !== '' && source.value !== null", - awPopOver: "

" + i18n._("If checked, any hosts and groups that were previously present on the external source but are now removed will be removed from the Tower 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.") + '

' + - i18n._("When not checked, local child hosts and groups not found on the external source will remain untouched by the inventory update process.") + "

", - dataTitle: i18n._('Overwrite'), - dataContainer: 'body', - dataPlacement: 'right', - ngDisabled: "(!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd))" - }, { - name: 'overwrite_vars', - label: i18n._('Overwrite Variables'), - type: 'checkbox', - ngShow: "source.value !== '' && source.value !== null", - awPopOver: "

" + i18n._("If checked, all variables for child groups and hosts will be removed and replaced by those found on the external source.") + '

' + - i18n._("When not checked, a merge will be performed, combining local variables with those found on the external source.") + "

", - dataTitle: i18n._('Overwrite Variables'), - dataContainer: 'body', - dataPlacement: 'right', - ngDisabled: "(!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd))" - }, { - name: 'update_on_launch', - label: i18n._('Update on Launch'), - type: 'checkbox', - ngShow: "source.value !== '' && source.value !== null", - awPopOver: "

" + i18n._("Each time a job runs using this inventory, " + - "refresh the inventory from the selected source before executing job tasks.") + "

", - dataTitle: i18n._('Update on Launch'), - dataContainer: 'body', - dataPlacement: 'right', - ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)' - }, { - name: 'update_on_project_update', - label: i18n._('Update on Project Update'), - type: 'checkbox', - ngShow: "source.value === 'scm'", - awPopOver: "

" + i18n._("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.") + "

", - dataTitle: i18n._('Update on Project Update'), - dataContainer: 'body', - dataPlacement: 'right', - ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)' - }] - }, - update_cache_timeout: { - label: i18n._("Cache Timeout") + " " + i18n._("(seconds)") + "", - id: 'source-cache-timeout', - type: 'number', - ngDisabled: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)', - integer: true, - min: 0, - ngShow: "source && source.value !== '' && update_on_launch", - spinner: true, - "default": 0, - awPopOver: "

" + i18n._("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.") + "

", - dataTitle: i18n._('Cache Timeout'), - dataPlacement: 'right', - dataContainer: "body", - subForm: 'sourceSubForm' - } - }, - - buttons: { - cancel: { - ngClick: 'formCancel()', - ngShow: '(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - close: { - ngClick: 'formCancel()', - ngShow: '!(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - save: { - ngClick: 'formSave()', - ngDisabled: true, - ngShow: '(inventory_source_obj.summary_fields.user_capabilities.edit || canAdd)' - } - }, - - related: { - notifications: notifications_object, - schedules: { - title: i18n._('Schedules'), - skipGenerator: true, - ngClick: "$state.go('inventories.edit.inventory_sources.edit.schedules')" - } - } - - }; - -}]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/sources/sources.list.js b/awx/ui/client/src/inventories-hosts/inventories/related/sources/sources.list.js deleted file mode 100644 index 4350de582375..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/sources/sources.list.js +++ /dev/null @@ -1,127 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - export default ['i18n', function(i18n) { - return { - name: 'inventory_sources', - iterator: 'inventory_source', - editTitle: '{{ inventory_source.name }}', - well: true, - wellOverride: true, - index: false, - hover: true, - trackBy: 'inventory_source.id', - basePath: 'api/v2/inventories/{{$stateParams.inventory_id}}/inventory_sources/', - layoutClass: 'List-staticColumnLayout--statusOrCheckbox', - staticColumns: [ - { - field: 'sync_status', - content: { - label: '', - nosort: true, - mode: 'all', - iconOnly: true, - ngClick: 'viewUpdateStatus(inventory_source.id)', - awToolTip: "{{ inventory_source.status_tooltip }}", - dataTipWatch: "inventory_source.status_tooltip", - icon: "{{ 'fa icon-cloud-' + inventory_source.status_class }}", - ngClass: "inventory_source.status_class", - dataPlacement: "top", - } - } - ], - - fields: { - name: { - label: i18n._('Sources'), - key: true, - uiSref: "inventories.edit.inventory_sources.edit({inventory_source_id:inventory_source.id})", - columnClass: 'col-lg-4 col-md-4 col-sm-4 col-xs-4', - }, - source: { - label: i18n._('Type'), - ngBind: 'inventory_source.source_label', - columnClass: 'col-lg-4 col-md-4 col-sm-4 col-xs-4' - } - }, - - actions: { - refresh: { - mode: 'all', - awToolTip: i18n._("Refresh the page"), - ngClick: "refreshGroups()", - ngShow: "socketStatus == 'error'", - actionClass: 'btn List-buttonDefault', - buttonContent: i18n._('REFRESH') - }, - sync_all: { - mode: 'all', - awToolTip: i18n._("Sync all inventory sources"), - ngClick: "syncAllSources()", - ngShow: "showSyncAll", - actionClass: 'btn List-buttonDefault', - buttonContent: i18n._('SYNC ALL'), - dataPlacement: "top" - }, - create: { - mode: 'all', - ngClick: "createSource()", - awToolTip: i18n._("Create a new source"), - actionClass: 'at-Button--add', - actionId: 'button-add', - ngShow: 'canAdd', - dataPlacement: "top", - } - }, - - fieldActions: { - - columnClass: 'col-lg-4 col-md-4 col-sm-4 col-xs-4 text-right', - - edit: { - mode: 'all', - ngClick: "editSource(inventory_source.id)", - awToolTip: i18n._('Edit source'), - dataPlacement: "top", - ngShow: "inventory_source.summary_fields.user_capabilities.edit" - }, - source_update: { - mode: 'all', - ngClick: 'updateSource(inventory_source)', - awToolTip: "{{ inventory_source.launch_tooltip }}", - dataTipWatch: "inventory_source.launch_tooltip", - ngShow: "(inventory_source.status !== 'running' && inventory_source.status " + - "!== 'pending' && inventory_source.status !== 'updating') && inventory_source.summary_fields.user_capabilities.start", - ngClass: "inventory_source.launch_class", - dataPlacement: "top", - }, - cancel: { - mode: 'all', - ngClick: "cancelUpdate(inventory_source.id)", - awToolTip: i18n._("Cancel sync process"), - 'class': 'red-txt', - ngShow: "(inventory_source.status == 'running' || inventory_source.status == 'pending' " + - "|| inventory_source.status == 'updating') && inventory_source.summary_fields.user_capabilities.start", - dataPlacement: "top", - iconClass: "fa fa-minus-circle" - }, - view: { - mode: 'all', - ngClick: "editSource(inventory_source.id)", - awToolTip: i18n._('View source'), - dataPlacement: "top", - ngShow: "!inventory_source.summary_fields.user_capabilities.edit" - }, - "delete": { - mode: 'all', - ngClick: "deleteSource(inventory_source)", - awToolTip: i18n._('Delete source'), - dataPlacement: "top", - ngShow: "inventory_source.summary_fields.user_capabilities.delete" - } - } - }; -}]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/related/sources/sources.service.js b/awx/ui/client/src/inventories-hosts/inventories/related/sources/sources.service.js deleted file mode 100644 index ce65a7dae52f..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/related/sources/sources.service.js +++ /dev/null @@ -1,154 +0,0 @@ -export default -['$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', 'Wait', function($rootScope, Rest, GetBasePath, ProcessErrors, Wait){ - return { - stringifyParams: function(params){ - return _.reduce(params, (result, value, key) => { - return result + key + '=' + value + '&'; - }, ''); - }, - // cute abstractions via fn.bind() - url: function(){ - return ''; - }, - error: function(data) { - ProcessErrors($rootScope, data.data, data.status, null, { hdr: 'Error!', - msg: 'Call to ' + this.url + '. GET returned: ' + data.status }); - }, - success: function(data){ - return data; - }, - // HTTP methods - get: function(params){ - Wait('start'); - this.url = GetBasePath('inventory_sources') + '?' + this.stringifyParams(params); - Rest.setUrl(this.url); - return Rest.get() - .then(this.success.bind(this)) - .catch(this.error.bind(this)) - .finally(Wait('stop')); - }, - post: function(inventory_source){ - Wait('start'); - this.url = GetBasePath('inventory_sources'); - Rest.setUrl(this.url); - return Rest.post(inventory_source) - .then(this.success.bind(this)) - .catch(this.error.bind(this)) - .finally(Wait('stop')); - }, - put: function(inventory_source){ - Wait('start'); - this.url = GetBasePath('inventory_sources') + inventory_source.id; - Rest.setUrl(this.url); - return Rest.put(inventory_source) - .then(this.success.bind(this)) - .catch(this.error.bind(this)) - .finally(Wait('stop')); - }, - delete: function(id){ - Wait('start'); - this.url = GetBasePath('inventory_sources') + id; - Rest.setUrl(this.url); - return Rest.destroy() - .then(this.success.bind(this)) - .catch(this.error.bind(this)) - .finally(Wait('stop')); - }, - options: function(){ - this.url = GetBasePath('inventory_sources'); - Rest.setUrl(this.url); - return Rest.options() - .then(this.success.bind(this)) - .catch(this.error.bind(this)); - }, - getCredential: function(id){ - Wait('start'); - this.url = GetBasePath('credentials') + id; - Rest.setUrl(this.url); - return Rest.get() - .then(this.success.bind(this)) - .catch(this.error.bind(this)) - .finally(Wait('stop')); - }, - getInventorySource: function(params){ - Wait('start'); - this.url = GetBasePath('inventory_sources') + '?' + this.stringifyParams(params); - Rest.setUrl(this.url); - return Rest.get() - .then(this.success.bind(this)) - .catch(this.error.bind(this)) - .finally(Wait('stop')); - }, - putInventorySource: function(params, url){ - Wait('start'); - this.url = url; - Rest.setUrl(this.url); - return Rest.put(params) - .then(this.success.bind(this)) - .catch(this.error.bind(this)) - .finally(Wait('stop')); - }, - // these relationship setters could be consolidated, but verbosity makes the operation feel more clear @ controller level - associateGroup: function(group, target){ - Wait('start'); - this.url = GetBasePath('groups') + target + '/children/'; - Rest.setUrl(this.url); - return Rest.post(group) - .then(this.success.bind(this)) - .catch(this.error.bind(this)) - .finally(Wait('stop')); - }, - disassociateGroup: function(group, parent){ - Wait('start'); - this.url = GetBasePath('groups') + parent + '/children/'; - Rest.setUrl(this.url); - return Rest.post({id: group, disassociate: 1}) - .then(this.success.bind(this)) - .catch(this.error.bind(this)) - .finally(Wait('stop')); - }, - promote: function(group, inventory){ - Wait('start'); - this.url = GetBasePath('inventory') + inventory + '/groups/'; - Rest.setUrl(this.url); - return Rest.post({id: group, disassociate: 1}) - .then(this.success.bind(this)) - .catch(this.error.bind(this)) - .finally(Wait('stop')); - }, - encodeGroupBy(source, group_by){ - source = source && source.value ? source.value : ''; - if(source === 'ec2'){ - return _.map(group_by, 'value').join(','); - } - - if(source === 'vmware'){ - group_by = _.map(group_by, (i) => {return i.value;}); - $("#inventory_source_group_by").siblings(".select2").first().find(".select2-selection__choice").each(function(optionIndex, option){ - group_by.push(option.title); - }); - group_by = (Array.isArray(group_by)) ? _.uniq(group_by).join() : ""; - return group_by; - } - else { - return; - } - }, - deleteHosts(id) { - this.url = GetBasePath('inventory_sources') + id + '/hosts/'; - Rest.setUrl(this.url); - return Rest.destroy() - .then(this.success.bind(this)) - .catch(this.error.bind(this)) - .finally(); - }, - deleteGroups(id) { - this.url = GetBasePath('inventory_sources') + id + '/groups/'; - Rest.setUrl(this.url); - return Rest.destroy() - .then(this.success.bind(this)) - .catch(this.error.bind(this)) - .finally(); - } - }; -}]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/add/main.js b/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/add/main.js deleted file mode 100644 index 911492557f47..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/add/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import controller from './smart-inventory-add.controller'; - -export default -angular.module('smartInventoryAdd', []) - .controller('SmartInventoryAddController', controller); diff --git a/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/add/smart-inventory-add.controller.js b/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/add/smart-inventory-add.controller.js deleted file mode 100644 index 1f21e3844f03..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/add/smart-inventory-add.controller.js +++ /dev/null @@ -1,102 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name controllers.function:Inventories - * @description This controller's for the Inventory page - */ - -function SmartInventoryAdd($scope, $location, - GenerateForm, smartInventoryForm, rbacUiControlService, Rest, Alert, ProcessErrors, - GetBasePath, ParseTypeChange, Wait, ToJSON, - $state, canAdd, InstanceGroupsService) { - - $scope.canAdd = canAdd; - - // Inject dynamic view - var defaultUrl = GetBasePath('inventory'), - form = smartInventoryForm; - - init(); - - function init() { - $scope.canEditOrg = true; - form.formLabelSize = null; - form.formFieldSize = null; - - // apply form definition's default field values - GenerateForm.applyDefaults(form, $scope); - - $scope.parseType = 'yaml'; - ParseTypeChange({ - scope: $scope, - variable: 'smartinventory_variables', - parse_variable: 'parseType', - field_id: 'smartinventory_smartinventory_variables' - }); - - $scope.smart_hosts = $state.params.hostfilter ? JSON.parse($state.params.hostfilter) : ''; - } - - // Save - $scope.formSave = function() { - Wait('start'); - try { - let fld, data = {}; - - for (fld in form.fields) { - data[fld] = $scope[fld]; - } - - data.variables = ToJSON($scope.parseType, $scope.smartinventory_variables, true); - - data.host_filter = decodeURIComponent($scope.smart_hosts.host_filter); - - data.kind = "smart"; - - Rest.setUrl(defaultUrl); - Rest.post(data) - .then(({data}) => { - const inventory_id = data.id, - instance_group_url = data.related.instance_groups; - - InstanceGroupsService.addInstanceGroups(instance_group_url, $scope.instance_groups) - .then(() => { - Wait('stop'); - $state.go('inventories.editSmartInventory', {smartinventory_id: inventory_id}, {reload: true}); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, form, { - hdr: 'Error!', - msg: 'Failed to post instance groups. POST returned ' + - 'status: ' + status - }); - }); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, form, { - hdr: 'Error!', - msg: 'Failed to add new inventory. Post returned status: ' + status - }); - }); - } catch (err) { - Wait('stop'); - Alert("Error", "Error parsing inventory variables. Parser returned: " + err); - } - - }; - - $scope.formCancel = function() { - $state.go('inventories'); - }; -} - -export default ['$scope', '$location', - 'GenerateForm', 'smartInventoryForm', 'rbacUiControlService', 'Rest', 'Alert', - 'ProcessErrors', 'GetBasePath', 'ParseTypeChange', - 'Wait', 'ToJSON', '$state', 'canAdd', 'InstanceGroupsService', SmartInventoryAdd -]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/edit/main.js b/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/edit/main.js deleted file mode 100644 index b5c59c9e6d82..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/edit/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import controller from './smart-inventory-edit.controller'; - -export default -angular.module('smartInventoryEdit', []) - .controller('SmartInventoryEditController', controller); diff --git a/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/edit/smart-inventory-edit.controller.js b/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/edit/smart-inventory-edit.controller.js deleted file mode 100644 index 42f7f272239c..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/edit/smart-inventory-edit.controller.js +++ /dev/null @@ -1,110 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -function SmartInventoryEdit($scope, $location, - $stateParams, InventoryForm, Rest, ProcessErrors, - GetBasePath, ParseTypeChange, Wait, ToJSON, - ParseVariableString, $state, OrgAdminLookup, resourceData, - $rootScope, InstanceGroupsService, InstanceGroupsData) { - - // Inject dynamic view - var defaultUrl = GetBasePath('inventory'), - form = InventoryForm, - inventory_id = $stateParams.smartinventory_id, - inventoryData = resourceData.data, - instance_group_url = inventoryData.related.instance_groups; - init(); - - function init() { - form.formLabelSize = null; - form.formFieldSize = null; - $scope.inventory_id = inventory_id; - - $scope = angular.extend($scope, inventoryData); - - $scope.smartinventory_variables = inventoryData.variables === null || inventoryData.variables === '' ? '---' : ParseVariableString(inventoryData.variables); - $scope.organization_name = inventoryData.summary_fields.organization.name; - $scope.instance_groups = InstanceGroupsData; - - $scope.$watch('inventory_obj.summary_fields.user_capabilities.edit', function(val) { - if (val === false) { - $scope.canAdd = false; - } - }); - - $scope.parseType = 'yaml'; - - $scope.inventory_obj = inventoryData; - $rootScope.breadcrumb.inventory_name = inventoryData.name; - - ParseTypeChange({ - scope: $scope, - variable: 'smartinventory_variables', - parse_variable: 'parseType', - field_id: 'smartinventory_smartinventory_variables', - readOnly: !$scope.inventory_obj.summary_fields.user_capabilities.edit - }); - - OrgAdminLookup.checkForAdminAccess({organization: inventoryData.organization}) - .then(function(canEditOrg){ - $scope.canEditOrg = canEditOrg; - }); - - $scope.smart_hosts = { - host_filter: encodeURIComponent($scope.host_filter) - }; - } - - // Save - $scope.formSave = function() { - Wait('start'); - - let fld, data = {}; - - for (fld in form.fields) { - data[fld] = $scope[fld]; - } - - data.variables = ToJSON($scope.parseType, $scope.smartinventory_variables, true); - data.host_filter = decodeURIComponent($scope.smart_hosts.host_filter); - data.kind = "smart"; - - Rest.setUrl(defaultUrl + inventory_id + '/'); - Rest.put(data) - .then(() => { - InstanceGroupsService.editInstanceGroups(instance_group_url, $scope.instance_groups) - .then(() => { - Wait('stop'); - $state.go($state.current, {}, { reload: true }); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, form, { - hdr: 'Error!', - msg: 'Failed to update instance groups. POST returned status: ' + status - }); - }); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, form, { - hdr: 'Error!', - msg: 'Failed to update inventory. PUT returned status: ' + status - }); - }); - }; - - $scope.formCancel = function() { - $state.go('inventories'); - }; - -} - -export default [ '$scope', '$location', - '$stateParams', 'InventoryForm', 'Rest', - 'ProcessErrors', 'GetBasePath', 'ParseTypeChange', 'Wait', - 'ToJSON', 'ParseVariableString', - '$state', 'OrgAdminLookup', 'resourceData', - '$rootScope', 'InstanceGroupsService', 'InstanceGroupsData', SmartInventoryEdit -]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/main.js b/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/main.js deleted file mode 100644 index 0f8a57560f47..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/main.js +++ /dev/null @@ -1,20 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import smartInventoryAdd from './add/main'; -import smartInventoryEdit from './edit/main'; -import smartInventoryForm from './smart-inventory.form'; -import smartInventoryHostFilter from './smart-inventory-host-filter/smart-inventory-host-filter.directive'; -import hostFilterModal from './smart-inventory-host-filter/host-filter-modal/host-filter-modal.directive'; - -export default -angular.module('smartInventory', [ - smartInventoryAdd.name, - smartInventoryEdit.name - ]) - .factory('smartInventoryForm', smartInventoryForm) - .directive('smartInventoryHostFilter', smartInventoryHostFilter) - .directive('hostFilterModal', hostFilterModal); diff --git a/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal.directive.js b/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal.directive.js deleted file mode 100644 index aa65ad09be6c..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal.directive.js +++ /dev/null @@ -1,94 +0,0 @@ -export default ['templateUrl', function(templateUrl) { - return { - restrict: 'E', - scope: { - hostFilter: '=', - organization: '=' - }, - templateUrl: templateUrl('inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal'), - link: function(scope, element) { - - $('#host-filter-modal').on('hidden.bs.modal', function () { - $('#host-filter-modal').off('hidden.bs.modal'); - $(element).remove(); - }); - - scope.showModal = function() { - $('#host-filter-modal').modal('show'); - }; - - scope.destroyModal = function() { - $('#host-filter-modal').modal('hide'); - }; - - }, - controller: ['$scope', 'QuerySet', 'GetBasePath', 'HostsList', '$compile', 'generateList', 'i18n', function($scope, qs, GetBasePath, HostsList, $compile, GenerateList, i18n) { - - function init() { - - $scope.host_default_params = { - order_by: 'name', - page_size: 5, - inventory__organization: $scope.organization - }; - - $scope.host_queryset = _.merge({ - order_by: 'name', - page_size: 5, - inventory__organization: $scope.organization - }, $scope.hostFilter ? $scope.hostFilter : {}); - - // Fire off the initial search - qs.search(GetBasePath('hosts'), $scope.host_queryset) - .then(res => { - $scope.host_dataset = res.data; - $scope.hosts = $scope.host_dataset.results; - - let hostList = _.cloneDeep(HostsList); - delete hostList.staticColumns; - delete hostList.fields.toggleHost; - delete hostList.fields.active_failures; - delete hostList.fields.name.ngClick; - hostList.fields.name.columnClass = 'col-sm-6'; - hostList.fields.name.noLink = true; - hostList.well = false; - delete hostList.fields.inventory.ngClick; - hostList.fields.inventory.columnClass = 'col-sm-6'; - hostList.fields.inventory.ngBind = 'host.summary_fields.inventory.name'; - hostList.emptyListText = i18n._('Perform a search above to define a host filter'); - hostList.layoutClass = 'List-defaultLayout'; - hostList.alwaysShowSearch = true; - hostList.emptyListClass = 'List-noItems List-emptyHostFilter'; - let html = GenerateList.build({ - list: hostList, - input_type: 'host-filter-modal-body', - hideViewPerPage: true - }); - - $scope.list = hostList; - - $('#host-filter-modal-body').append($compile(html)($scope)); - - $scope.showModal(); - }); - } - - init(); - - $scope.cancelForm = function() { - $scope.destroyModal(); - }; - - $scope.saveForm = function() { - // Strip defaults out of the state params copy - angular.forEach(Object.keys($scope.host_default_params), function(value) { - delete $scope.host_queryset[value]; - }); - - $scope.hostFilter = angular.copy($scope.host_queryset); - - $scope.destroyModal(); - }; - }] - }; -}]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal.partial.html b/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal.partial.html deleted file mode 100644 index a44c80b32fe3..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal.partial.html +++ /dev/null @@ -1,21 +0,0 @@ - diff --git a/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/smart-inventory-host-filter.controller.js b/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/smart-inventory-host-filter.controller.js deleted file mode 100644 index 1c28668debc2..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/smart-inventory-host-filter.controller.js +++ /dev/null @@ -1,38 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['$scope', 'QuerySet', 'InventoryHostsStrings', - function($scope, qs, InventoryHostsStrings) { - $scope.hostFilterTags = []; - - $scope.$watch('organization', function(){ - if($scope.hasEditPermissions) { - $scope.filterTooltip = $scope.organization ? InventoryHostsStrings.get('smartinventories.hostfilter.INSTRUCTIONS') : InventoryHostsStrings.get('smartinventories.hostfilter.MISSING_ORG'); - } - else { - $scope.filterTooltip = InventoryHostsStrings.get('smartinventories.hostfilter.MISSING_PERMISSIONS'); - } - }); - - $scope.$watch('hostFilter', function(){ - $scope.hostFilterTags = []; - - if($scope.hostFilter && $scope.hostFilter !== '') { - let hostFilterCopy = angular.copy($scope.hostFilter); - - let searchParam = hostFilterCopy.host_filter.split('%20and%20'); - delete hostFilterCopy.host_filter; - - $.each(searchParam, function(index, param) { - let paramParts = decodeURIComponent(param).split(/=(.+)/); - $scope.hostFilterTags.push(qs.decodeParam(paramParts[1], paramParts[0])); - }); - - $scope.hostFilterTags = $scope.hostFilterTags.concat(qs.stripDefaultParams(hostFilterCopy)); - } - }); - } -]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/smart-inventory-host-filter.directive.js b/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/smart-inventory-host-filter.directive.js deleted file mode 100644 index dd60d0e736a3..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/smart-inventory-host-filter.directive.js +++ /dev/null @@ -1,27 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import smartInventoryHostFilterController from './smart-inventory-host-filter.controller'; - -export default ['templateUrl', '$compile', - function(templateUrl, $compile) { - return { - scope: { - hostFilter: '=', - hasEditPermissions: '=', - organization: '=' - }, - restrict: 'E', - templateUrl: templateUrl('inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/smart-inventory-host-filter'), - controller: smartInventoryHostFilterController, - link: function(scope) { - scope.openHostFilterModal = function() { - $('#content-container').append($compile('')(scope)); - }; - } - }; - } -]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/smart-inventory-host-filter.partial.html b/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/smart-inventory-host-filter.partial.html deleted file mode 100644 index 8c39970b35db..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/smart-inventory-host-filter.partial.html +++ /dev/null @@ -1,13 +0,0 @@ -
- - - - - - {{tag}} - - -
- diff --git a/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-hosts.route.js b/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-hosts.route.js deleted file mode 100644 index aac38ff707f4..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-hosts.route.js +++ /dev/null @@ -1,70 +0,0 @@ -import { N_ } from '../../../i18n'; - -export default { - name: "inventories.editSmartInventory.hosts", - url: "/hosts?{host_search:queryset}", - params: { - host_search: { - value: { - page_size: "20", - order_by: "name" - }, - dynamic: true, - squash:"" - } - }, - ncyBreadcrumb: { - label: N_("HOSTS") - }, - views: { - 'related': { - templateProvider: function(ListDefinition, generateList) { - let list = _.cloneDeep(ListDefinition); - let html = generateList.build({ - list: list, - mode: 'edit' - }); - return html; - }, - controller: 'RelatedHostListController' - } - }, - resolve: { - ListDefinition: ['RelatedHostsListDefinition', '$stateParams', 'GetBasePath', (RelatedHostsListDefinition, $stateParams, GetBasePath) => { - let list = _.cloneDeep(RelatedHostsListDefinition); - list.basePath = GetBasePath('inventory') + $stateParams.smartinventory_id + '/hosts'; - delete list.actions.create; - delete list.fields.groups; - delete list.fieldActions.delete; - delete list.fieldActions.edit; - delete list.fieldActions.view.ngShow; - let toggleHost = list.staticColumns.find((el) => { return el.field === 'toggleHost'; }); - toggleHost.content.ngDisabled = true; - list.fields.name.columnClass = 'col-lg-8 col-md-11 col-sm-8 col-xs-7'; - return list; - }], - Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath', '$interpolate', '$rootScope', - (list, qs, $stateParams, GetBasePath, $interpolate, $rootScope) => { - // allow related list definitions to use interpolated $rootScope / $stateParams in basePath field - let path, interpolator; - if (GetBasePath(list.basePath)) { - path = GetBasePath(list.basePath); - } else { - interpolator = $interpolate(list.basePath); - path = interpolator({ $rootScope: $rootScope, $stateParams: $stateParams }); - } - return qs.search(path, $stateParams[`${list.iterator}_search`]); - } - ], - hostsUrl: ['InventoriesService', '$stateParams', function(InventoriesService, $stateParams) { - return InventoriesService.rootHostsUrl($stateParams.smartinventory_id); - }], - hostsDataset: ['ListDefinition', 'QuerySet', '$stateParams', 'hostsUrl', (list, qs, $stateParams, hostsUrl) => { - let path = hostsUrl; - return qs.search(path, $stateParams[`${list.iterator}_search`]); - }], - inventoryData: ['InventoriesService', '$stateParams', function(InventoriesService, $stateParams) { - return InventoriesService.getInventory($stateParams.smartinventory_id).then(res => res.data); - }] - } -}; diff --git a/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js b/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js deleted file mode 100644 index 3999bc957892..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js +++ /dev/null @@ -1,162 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['i18n', function(i18n) { - return { - - addTitle: i18n._('NEW SMART INVENTORY'), - editTitle: '{{ name }}', - name: 'smartinventory', - basePath: 'inventory', - breadcrumbName: i18n._('SMART INVENTORY'), - stateTree: 'inventories', - activeEditState: 'inventories.editSmartInventory', - detailsClick: "$state.go('inventories.editSmartInventory')", - - fields: { - name: { - label: i18n._('Name'), - type: 'text', - required: true, - capitalize: false, - ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - description: { - label: i18n._('Description'), - type: 'text', - ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - organization: { - label: i18n._('Organization'), - type: 'lookup', - basePath: 'organizations', - list: 'OrganizationList', - sourceModel: 'organization', - sourceField: 'name', - required: true, - ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd) || !canEditOrg', - awLookupWhen: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd) && canEditOrg' - }, - smart_hosts: { - label: i18n._('Smart Host Filter'), - type: 'custom', - control: '', - awPopOver: "

" + i18n._("Populate the hosts for this inventory by using a search filter.") + "

" + i18n._("Example: ansible_facts.ansible_distribution:\"RedHat\"") + "

" + i18n._("Refer to the Ansible Tower documentation for further syntax and examples.") + "

", - dataTitle: i18n._('Smart Host Filter'), - dataPlacement: 'right', - dataContainer: 'body', - required: true - }, - instance_groups: { - label: i18n._('Instance Groups'), - type: 'custom', - awPopOver: "

" + i18n._("Select the Instance Groups for this Inventory to run on.") + "

", - dataTitle: i18n._('Instance Groups'), - dataPlacement: 'right', - dataContainer: 'body', - control: '', - }, - smartinventory_variables: { - label: i18n._('Variables'), - type: 'textarea', - class: 'Form-formGroup--fullWidth', - rows: 6, - "default": "---", - awPopOver: "

" + i18n._("Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two.") + "

" + - "JSON:
\n" + - "
{
 \"somevar\": \"somevalue\",
 \"password\": \"magic\"
}
\n" + - "YAML:
\n" + - "
---
somevar: somevalue
password: magic
\n" + - '

' + i18n.sprintf(i18n._('View JSON examples at %s'), 'www.json.org') + '

' + - '

' + i18n.sprintf(i18n._('View YAML examples at %s'), 'docs.ansible.com') + '

', - dataTitle: i18n._('Inventory Variables'), - dataPlacement: 'right', - dataContainer: 'body', - ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' // TODO: get working - } - }, - - buttons: { - cancel: { - ngClick: 'formCancel()', - ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - close: { - ngClick: 'formCancel()', - ngShow: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - save: { - ngClick: 'formSave()', - ngDisabled: true, - ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' - } - }, - related: { - permissions: { - name: 'permissions', - awToolTip: i18n._('Please save before assigning permissions.'), - dataPlacement: 'top', - basePath: 'api/v2/inventories/{{$stateParams.smartinventory_id}}/access_list/', - type: 'collection', - title: i18n._('Permissions'), - iterator: 'permission', - index: false, - open: false, - search: { - order_by: 'username' - }, - actions: { - add: { - label: i18n._('Add'), - ngClick: "$state.go('.add')", - awToolTip: i18n._('Add a permission'), - actionClass: 'at-Button--add', - actionId: 'button-add', - ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' - - } - }, - fields: { - username: { - key: true, - label: i18n._('User'), - linkBase: 'users', - columnClass: 'col-sm-3 col-xs-4' - }, - role: { - label: i18n._('Role'), - type: 'role', - nosort: true, - columnClass: 'col-sm-4 col-xs-4' - }, - team_roles: { - label: i18n._('Team Roles'), - type: 'team_roles', - nosort: true, - columnClass: 'col-sm-5 col-xs-4' - } - }, - ngClick: "$state.go('inventories.editSmartInventory.permissions');" - }, - hosts: { - name: 'hosts', - awToolTip: i18n._('Please save before viewing hosts.'), - dataPlacement: 'top', - include: "RelatedHostsListDefinition", - title: i18n._('Hosts'), - iterator: 'host', - ngClick: "$state.go('inventories.editSmartInventory.hosts');", - skipGenerator: true - }, - completed_jobs: { - title: i18n._('Completed Jobs'), - skipGenerator: true, - ngClick: "$state.go('inventories.editSmartInventory.completed_jobs')" - } - } - - }; - }]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/standard-inventory/add/inventory-add.controller.js b/awx/ui/client/src/inventories-hosts/inventories/standard-inventory/add/inventory-add.controller.js deleted file mode 100644 index 469a976dc225..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/standard-inventory/add/inventory-add.controller.js +++ /dev/null @@ -1,91 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name controllers.function:Inventories - * @description This controller's for the Inventory page - */ - -function InventoriesAdd($scope, $location, - GenerateForm, InventoryForm, rbacUiControlService, Rest, Alert, ProcessErrors, - GetBasePath, ParseTypeChange, Wait, ToJSON, - $state, canAdd, CreateSelect2, InstanceGroupsService) { - - $scope.canAdd = canAdd; - - // Inject dynamic view - var defaultUrl = GetBasePath('inventory'), - form = InventoryForm; - - init(); - - function init() { - $scope.canEditOrg = true; - form.formLabelSize = null; - form.formFieldSize = null; - - // apply form definition's default field values - GenerateForm.applyDefaults(form, $scope); - } - - // Save - $scope.formSave = function() { - Wait('start'); - try { - var fld, data; - - data = {}; - for (fld in form.fields) { - if (form.fields[fld].realName) { - data[form.fields[fld].realName] = $scope[fld]; - } else { - data[fld] = $scope[fld]; - } - } - - Rest.setUrl(defaultUrl); - Rest.post(data) - .then(({data}) => { - const inventory_id = data.id, - instance_group_url = data.related.instance_groups; - - InstanceGroupsService.addInstanceGroups(instance_group_url, $scope.instance_groups) - .then(() => { - Wait('stop'); - $state.go('inventories.edit', {inventory_id: inventory_id}, {reload: true}); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, form, { - hdr: 'Error!', - msg: 'Failed to post instance groups. POST returned ' + - 'status: ' + status - }); - }); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, form, { - hdr: 'Error!', - msg: 'Failed to add new inventory. Post returned status: ' + status - }); - }); - } catch (err) { - Wait('stop'); - Alert("Error", "Error parsing inventory variables. Parser returned: " + err); - } - - }; - - $scope.formCancel = function() { - $state.go('inventories'); - }; -} - -export default ['$scope', '$location', - 'GenerateForm', 'InventoryForm', 'rbacUiControlService', 'Rest', 'Alert', - 'ProcessErrors', 'GetBasePath', 'ParseTypeChange', - 'Wait', 'ToJSON', '$state','canAdd', 'CreateSelect2', 'InstanceGroupsService', InventoriesAdd -]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/standard-inventory/add/main.js b/awx/ui/client/src/inventories-hosts/inventories/standard-inventory/add/main.js deleted file mode 100644 index 2e477aa96c1f..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/standard-inventory/add/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import controller from './inventory-add.controller'; - -export default -angular.module('InventoryAdd', []) - .controller('InventoryAddController', controller); diff --git a/awx/ui/client/src/inventories-hosts/inventories/standard-inventory/edit/inventory-edit.controller.js b/awx/ui/client/src/inventories-hosts/inventories/standard-inventory/edit/inventory-edit.controller.js deleted file mode 100644 index ce2d8e7ea787..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/standard-inventory/edit/inventory-edit.controller.js +++ /dev/null @@ -1,112 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name controllers.function:Inventories - * @description This controller's for the Inventory page - */ - -function InventoriesEdit($scope, $location, - $stateParams, InventoryForm, Rest, ProcessErrors, - GetBasePath, ParseTypeChange, Wait, ToJSON, - ParseVariableString, $state, OrgAdminLookup, $rootScope, resourceData, - CreateSelect2, InstanceGroupsService, InstanceGroupsData, CanRemediate) { - - // Inject dynamic view - let defaultUrl = GetBasePath('inventory'), - form = InventoryForm, - fld, data, - inventoryData = resourceData.data, - instance_group_url = inventoryData.related.instance_groups; - - init(); - - function init() { - form.formLabelSize = null; - form.formFieldSize = null; - - $scope = angular.extend($scope, inventoryData); - - $scope.insights_credential_name = (inventoryData.summary_fields.insights_credential && inventoryData.summary_fields.insights_credential.name) ? inventoryData.summary_fields.insights_credential.name : null; - $scope.insights_credential = (inventoryData.summary_fields.insights_credential && inventoryData.summary_fields.insights_credential.id) ? inventoryData.summary_fields.insights_credential.id : null; - $scope.is_insights = (inventoryData.summary_fields.insights_credential && inventoryData.summary_fields.insights_credential.id) ? true : false; - $scope.organization_name = inventoryData.summary_fields.organization.name; - $scope.inventory_variables = inventoryData.variables === null || inventoryData.variables === '' ? '---' : ParseVariableString(inventoryData.variables); - $scope.parseType = 'yaml'; - $scope.instance_groups = InstanceGroupsData; - $scope.canRemediate = CanRemediate; - - OrgAdminLookup.checkForRoleLevelAdminAccess(inventoryData.organization, 'inventory_admin_role') - .then(function(canEditOrg){ - $scope.canEditOrg = canEditOrg; - }); - - $scope.inventory_obj = inventoryData; - $scope.inventory_name = inventoryData.name; - $rootScope.breadcrumb.inventory_name = inventoryData.name; - - $scope.$watch('inventory_obj.summary_fields.user_capabilities.edit', function(val) { - if (val === false) { - $scope.canAdd = false; - } - }); - } - - // Save - $scope.formSave = function() { - Wait('start'); - - data = {}; - for (fld in form.fields) { - if (form.fields[fld].realName) { - data[form.fields[fld].realName] = $scope[fld]; - } else { - data[fld] = $scope[fld]; - } - } - - Rest.setUrl(defaultUrl + $stateParams.inventory_id + '/'); - Rest.put(data) - .then(() => { - InstanceGroupsService.editInstanceGroups(instance_group_url, $scope.instance_groups) - .then(() => { - Wait('stop'); - $state.go($state.current, {}, { reload: true }); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, form, { - hdr: 'Error!', - msg: 'Failed to update instance groups. POST returned status: ' + status - }); - }); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, form, { - hdr: 'Error!', - msg: 'Failed to update inventory. PUT returned status: ' + status - }); - }); - }; - - $scope.formCancel = function() { - $state.go('inventories'); - }; - - $scope.remediateInventory = function(inv_id, insights_credential){ - $state.go('templates.addJobTemplate', {inventory_id: inv_id, credential_id: insights_credential}); - }; - -} - -export default ['$scope', '$location', - '$stateParams', 'InventoryForm', 'Rest', - 'ProcessErrors', 'GetBasePath', 'ParseTypeChange', 'Wait', - 'ToJSON', 'ParseVariableString', - '$state', 'OrgAdminLookup', '$rootScope', 'resourceData', 'CreateSelect2', - 'InstanceGroupsService', 'InstanceGroupsData', 'CanRemediate', - InventoriesEdit, -]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/standard-inventory/edit/main.js b/awx/ui/client/src/inventories-hosts/inventories/standard-inventory/edit/main.js deleted file mode 100644 index 130a5e8b4b97..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/standard-inventory/edit/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import controller from './inventory-edit.controller'; - -export default - angular.module('InventoryEdit', []) - .controller('InventoryEditController', controller); diff --git a/awx/ui/client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js b/awx/ui/client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js deleted file mode 100644 index 3cf7629f1d4c..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js +++ /dev/null @@ -1,181 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name forms.function:Inventories - * @description This form is for adding/editing an inventory - */ - -export default ['i18n', -function(i18n) { - return { - - addTitle: i18n._('NEW INVENTORY'), - editTitle: '{{ inventory_name }}', - name: 'inventory', - basePath: 'inventory', - // the top-most node of this generated state tree - stateTree: 'inventories', - tabs: true, - - fields: { - name: { - realName: 'name', - label: i18n._('Name'), - type: 'text', - required: true, - capitalize: false, - ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - description: { - realName: 'description', - label: i18n._('Description'), - type: 'text', - ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - organization: { - label: i18n._('Organization'), - type: 'lookup', - basePath: 'organizations', - list: 'OrganizationList', - sourceModel: 'organization', - sourceField: 'name', - required: true, - ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd) || !canEditOrg', - awLookupWhen: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd) && canEditOrg' - }, - insights_credential: { - label: i18n._('Insights Credential'), - type: 'lookup', - list: 'CredentialList', - basePath: 'credentials', - sourceModel: 'insights_credential', - sourceField: 'name', - ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd) || !canEditOrg', - }, - instance_groups: { - label: i18n._('Instance Groups'), - type: 'custom', - awPopOver: i18n._('Select the Instance Groups for this Inventory to run on. Refer to the Ansible Tower documentation for more detail.'), - dataTitle: i18n._('Instance Groups'), - dataPlacement: 'right', - dataContainer: 'body', - control: '', - }, - variables: { - label: i18n._('Variables'), - type: 'code_mirror', - class: 'Form-formGroup--fullWidth', - variables: 'variables', - awPopOver: i18n._('Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax.'), - ngDisabled: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' // TODO: get working - } - }, - - buttons: { - cancel: { - ngClick: 'formCancel()', - ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - close: { - ngClick: 'formCancel()', - ngShow: '!(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - save: { - ngClick: 'formSave()', - ngDisabled: true, - ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' - } - }, - related: { - permissions: { - name: 'permissions', - awToolTip: i18n._('Please save before assigning permissions.'), - dataPlacement: 'top', - basePath: 'api/v2/inventories/{{$stateParams.inventory_id}}/access_list/', - type: 'collection', - title: i18n._('Permissions'), - iterator: 'permission', - index: false, - open: false, - search: { - order_by: 'username' - }, - actions: { - add: { - label: i18n._('Add'), - ngClick: "$state.go('.add')", - awToolTip: i18n._('Add a permission'), - actionClass: 'at-Button--add', - actionId: 'button-add', - ngShow: '(inventory_obj.summary_fields.user_capabilities.edit || canAdd)' - - } - }, - fields: { - username: { - key: true, - label: i18n._('User'), - linkBase: 'users', - columnClass: 'col-sm-3 col-xs-4' - }, - role: { - label: i18n._('Role'), - type: 'role', - nosort: true, - columnClass: 'col-sm-4 col-xs-4' - }, - team_roles: { - label: i18n._('Team Roles'), - type: 'team_roles', - nosort: true, - columnClass: 'col-sm-5 col-xs-4' - } - } - }, - groups: { - name: 'groups', - awToolTip: i18n._('Please save before creating groups.'), - dataPlacement: 'top', - include: "GroupList", - title: i18n._('Groups'), - iterator: 'group', - tabSelected: `$state.includes('inventories.edit.groups') || $state.includes('inventories.edit.rootGroups')`, - skipGenerator: true - }, - hosts: { - name: 'hosts', - awToolTip: i18n._('Please save before creating hosts.'), - dataPlacement: 'top', - include: "RelatedHostsListDefinition", - title: i18n._('Hosts'), - iterator: 'host', - skipGenerator: true - }, - inventory_sources: { - name: 'inventory_sources', - awToolTip: i18n._('Please save before defining inventory sources.'), - dataPlacement: 'top', - title: i18n._('Sources'), - iterator: 'inventory_source', - skipGenerator: true - }, - completed_jobs: { - title: i18n._('Completed Jobs'), - skipGenerator: true - } - }, - relatedButtons: { - remediate_inventory: { - ngClick: 'remediateInventory(id, insights_credential)', - ngShow: "is_insights && mode !== 'add' && canRemediate && ($state.is('inventories.edit') || $state.is('inventories.edit.hosts'))", - label: i18n._('Remediate Inventory'), - class: 'Form-primaryButton' - } - } - - };}]; diff --git a/awx/ui/client/src/inventories-hosts/inventories/standard-inventory/main.js b/awx/ui/client/src/inventories-hosts/inventories/standard-inventory/main.js deleted file mode 100644 index 1b2f8d8be51d..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventories/standard-inventory/main.js +++ /dev/null @@ -1,16 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - import inventoryAdd from './add/main'; - import inventoryEdit from './edit/main'; - import InventoryForm from './inventory.form'; - -export default -angular.module('standardInventory', [ - inventoryAdd.name, - inventoryEdit.name - ]) - .factory('InventoryForm', InventoryForm); diff --git a/awx/ui/client/src/inventories-hosts/inventory-hosts.block.less b/awx/ui/client/src/inventories-hosts/inventory-hosts.block.less deleted file mode 100644 index e4663663fbf4..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventory-hosts.block.less +++ /dev/null @@ -1,10 +0,0 @@ -#hosts-panel { - .List-noItems { - margin-top: 0px; - } - .groupsList { - .List-noItems { - margin-top: 52px; - } - } -} \ No newline at end of file diff --git a/awx/ui/client/src/inventories-hosts/inventory-hosts.strings.js b/awx/ui/client/src/inventories-hosts/inventory-hosts.strings.js deleted file mode 100644 index e6352e53e2ce..000000000000 --- a/awx/ui/client/src/inventories-hosts/inventory-hosts.strings.js +++ /dev/null @@ -1,53 +0,0 @@ -function InventoryHostsStrings (BaseString) { - BaseString.call(this, 'inventory-hosts'); - - let t = this.t; - let ns = this['inventory-hosts']; - - ns.deletegroup = { - GROUP: count => t.p(count, 'group', 'groups'), - HOST: count => t.p(count, 'host', 'hosts'), - PROMOTE_GROUPS_AND_HOSTS: data => t.s('Promote {{ group }} and {{ host }}', { - group: this.get('deletegroup.GROUP', data.groups), - host: this.get('deletegroup.HOST', data.hosts) - }), - DELETE_GROUPS_AND_HOSTS: data => t.s('Delete {{ group }} and {{ host }}', { - group: this.get('deletegroup.GROUP', data.groups), - host: this.get('deletegroup.HOST', data.hosts) - }), - PROMOTE_GROUP: count => t.p(count, 'Promote group', 'Promote groups'), - DELETE_GROUP: count => t.p(count, 'Delete group', 'Delete groups'), - PROMOTE_HOST: count => t.p(count, 'Promote host', 'Promote hosts'), - DELETE_HOST: count => t.p(count, 'Delete host', 'Delete hosts'), - }; - - ns.inventory = { - EDIT_HOST: t.s('Edit host'), - VIEW_HOST: t.s('View host'), - VIEW_INSIGHTS: t.s('View Insights Data') - }; - - ns.hostList = { - DISABLED_TOGGLE_TOOLTIP: () => t.s('{{ str1 }}

{{ str2 }}

', { - str1: t.s('Indicates if a host is available and should be included in running jobs.'), - str2: t.s('For hosts that are part of an external inventory, this may be reset by the inventory sync process.') - }) - }; - - ns.smartinventories = { - hostfilter: { - MISSING_ORG: t.s('Please select an organization before editing the host filter.'), - INSTRUCTIONS: t.s('Please click the icon to edit the host filter.'), - MISSING_PERMISSIONS: t.s('You do not have sufficient permissions to edit the host filter.') - } - }; - - ns.smartinventorybutton = { - DISABLED_INSTRUCTIONS: t.s("Please enter at least one search term to create a new Smart Inventory."), - ENABLED_INSTRUCTIONS: t.s("Create a new Smart Inventory from search results.

Note: changing the organization of the Smart Inventory could change the hosts included in the Smart Inventory.") - }; -} - -InventoryHostsStrings.$inject = ['BaseStringService']; - -export default InventoryHostsStrings; diff --git a/awx/ui/client/src/inventories-hosts/main.js b/awx/ui/client/src/inventories-hosts/main.js deleted file mode 100644 index de837d2049af..000000000000 --- a/awx/ui/client/src/inventories-hosts/main.js +++ /dev/null @@ -1,18 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - import hosts from './hosts/main'; - import inventories from './inventories/main'; - import shared from './shared/main'; - import InventoryHostsStrings from './inventory-hosts.strings'; - -export default -angular.module('inventories-hosts', [ - hosts.name, - inventories.name, - shared.name - ]) - .service('InventoryHostsStrings', InventoryHostsStrings); diff --git a/awx/ui/client/src/inventories-hosts/shared/ansible-facts/ansible-facts.controller.js b/awx/ui/client/src/inventories-hosts/shared/ansible-facts/ansible-facts.controller.js deleted file mode 100644 index a4ab5f10249a..000000000000 --- a/awx/ui/client/src/inventories-hosts/shared/ansible-facts/ansible-facts.controller.js +++ /dev/null @@ -1,20 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -function AnsibleFacts($scope, Facts) { - - function init() { - $scope.facts = Facts; - let rows = (_.isEmpty(Facts)) ? 6 : 20; - $("#host_facts").attr("rows", rows); - $scope.parseType = 'yaml'; - } - - init(); - -} - -export default ['$scope', 'Facts', AnsibleFacts]; diff --git a/awx/ui/client/src/inventories-hosts/shared/ansible-facts/ansible-facts.partial.html b/awx/ui/client/src/inventories-hosts/shared/ansible-facts/ansible-facts.partial.html deleted file mode 100644 index 7976a5cb5d58..000000000000 --- a/awx/ui/client/src/inventories-hosts/shared/ansible-facts/ansible-facts.partial.html +++ /dev/null @@ -1,12 +0,0 @@ - -
- -
- diff --git a/awx/ui/client/src/inventories-hosts/shared/ansible-facts/ansible-facts.route.js b/awx/ui/client/src/inventories-hosts/shared/ansible-facts/ansible-facts.route.js deleted file mode 100644 index 045fe535ddbc..000000000000 --- a/awx/ui/client/src/inventories-hosts/shared/ansible-facts/ansible-facts.route.js +++ /dev/null @@ -1,27 +0,0 @@ -import {templateUrl} from '../../../shared/template-url/template-url.factory'; -import { N_ } from '../../../i18n'; - -export default { - url: '/ansible_facts', - ncyBreadcrumb: { - label: N_("FACTS") - }, - views: { - 'related': { - controller: 'AnsibleFactsController', - templateUrl: templateUrl('inventories-hosts/shared/ansible-facts/ansible-facts') - } - }, - resolve: { - Facts: ['$stateParams', 'GetBasePath', 'Rest', - function($stateParams, GetBasePath, Rest) { - let ansibleFactsUrl = GetBasePath('hosts') + $stateParams.host_id + '/ansible_facts'; - Rest.setUrl(ansibleFactsUrl); - return Rest.get() - .then(({data}) => { - return data; - }); - } - ] - } -}; diff --git a/awx/ui/client/src/inventories-hosts/shared/ansible-facts/main.js b/awx/ui/client/src/inventories-hosts/shared/ansible-facts/main.js deleted file mode 100644 index 506652953f9e..000000000000 --- a/awx/ui/client/src/inventories-hosts/shared/ansible-facts/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import controller from './ansible-facts.controller'; - -export default -angular.module('AnsibleFacts', []) - .controller('AnsibleFactsController', controller); diff --git a/awx/ui/client/src/inventories-hosts/shared/associate-groups/associate-groups.block.less b/awx/ui/client/src/inventories-hosts/shared/associate-groups/associate-groups.block.less deleted file mode 100644 index 6aa733a6e28b..000000000000 --- a/awx/ui/client/src/inventories-hosts/shared/associate-groups/associate-groups.block.less +++ /dev/null @@ -1,12 +0,0 @@ -.AssociateGroups-modalBody { - padding-top: 0px; -} -.AssociateGroups-backDrop { - width: 100vw; - height: 100vh; - position: fixed; - top: 0; - left: 0; - opacity: 0; - transition: 0.5s opacity; -} diff --git a/awx/ui/client/src/inventories-hosts/shared/associate-groups/associate-groups.controller.js b/awx/ui/client/src/inventories-hosts/shared/associate-groups/associate-groups.controller.js deleted file mode 100644 index 03e5fe71583f..000000000000 --- a/awx/ui/client/src/inventories-hosts/shared/associate-groups/associate-groups.controller.js +++ /dev/null @@ -1,113 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - export default ['$scope', '$rootScope', 'ProcessErrors', 'GetBasePath', 'generateList', - '$state', 'Rest', '$q', 'Wait', '$window', 'QuerySet', 'GroupList', 'i18n', - function($scope, $rootScope, ProcessErrors, GetBasePath, generateList, - $state, Rest, $q, Wait, $window, qs, GroupList, i18n) { - $scope.$on("linkLists", function() { - - init(); - - function init(){ - $scope.associate_group_default_params = { - order_by: 'name', - page_size: 5 - }; - - $scope.associate_group_queryset = { - order_by: 'name', - page_size: 5 - }; - - if ($state.params.group_id) { - $scope.associate_group_default_params.not__id = $state.params.group_id; - $scope.associate_group_queryset.not__id = $state.params.group_id; - $scope.associate_group_default_params.not__parents = $state.params.group_id; - $scope.associate_group_queryset.not__parents = $state.params.group_id; - } else if ($state.params.host_id) { - $scope.associate_group_default_params.not__hosts = $state.params.host_id; - $scope.associate_group_queryset.not__hosts = $state.params.host_id; - } - - let list = _.cloneDeep(GroupList); - list.basePath = GetBasePath('inventory') + $state.params.inventory_id + '/groups'; - list.iterator = 'associate_group'; - list.name = 'associate_groups'; - list.multiSelect = true; - list.fields.name.ngClick = 'linkoutGroup(associate_group)'; - list.trackBy = 'associate_group.id'; - list.multiSelectPreview = { - selectedRows: 'selectedItems', - availableRows: 'associate_groups' - }; - list.emptyListText = i18n._('No groups to add'); - delete list.actions; - delete list.fieldActions; - delete list.fields.failed_hosts; - list.well = false; - $scope.list = list; - - // Fire off the initial search - qs.search(list.basePath, $scope.associate_group_default_params) - .then(function(res) { - $scope.associate_group_dataset = res.data; - $scope.associate_groups = $scope.associate_group_dataset.results; - - let html = generateList.build({ - list: list, - mode: 'edit', - title: false, - hideViewPerPage: true - }); - - $scope.compileList(html); - - $scope.$watchCollection('associate_groups', function () { - if($scope.selectedItems) { - $scope.associate_groups.forEach(function(row, i) { - if ($scope.selectedItems.filter(function(e) { return e.id === row.id; }).length > 0) { - $scope.associate_groups[i].isSelected = true; - } - }); - } - }); - - }); - - $scope.selectedItems = []; - $scope.$on('selectedOrDeselected', function(e, value) { - let item = value.value; - - if (value.isSelected) { - $scope.selectedItems.push(item); - } - else { - // _.remove() Returns the new array of removed elements. - // This will pull all the values out of the array that don't - // match the deselected item effectively removing it - $scope.selectedItems = _.remove($scope.selectedItems, function(selectedItem) { - return selectedItem.id !== item.id; - }); - } - }); - } - - $scope.linkGroups = function() { - $scope.saveFunction({selectedItems: $scope.selectedItems}) - .then(() =>{ - $scope.closeModal(); - }).catch(() => { - $scope.closeModal(); - }); - - }; - - $scope.linkoutGroup = function(group) { - $window.open('/#/inventories/inventory/' + group.inventory + '/groups/edit/' + group.id,'_blank'); - }; - }); - }]; diff --git a/awx/ui/client/src/inventories-hosts/shared/associate-groups/associate-groups.directive.js b/awx/ui/client/src/inventories-hosts/shared/associate-groups/associate-groups.directive.js deleted file mode 100644 index 7b0595352c59..000000000000 --- a/awx/ui/client/src/inventories-hosts/shared/associate-groups/associate-groups.directive.js +++ /dev/null @@ -1,60 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ -import controller from './associate-groups.controller'; - -/* jshint unused: vars */ -export default ['templateUrl', 'Wait', '$compile', '$state', - function(templateUrl, Wait, $compile, $state) { - return { - restrict: 'E', - transclude: true, - scope: { - saveFunction: '&' - }, - controller: controller, - templateUrl: templateUrl('inventories-hosts/shared/associate-groups/associate-groups'), - link: function(scope, element, attrs, controller, transcludefn) { - - $("body").addClass("is-modalOpen"); - - //$("body").append(element); - - Wait('start'); - - scope.$broadcast("linkLists"); - - setTimeout(function() { - $('#associate-groups-modal').modal("show"); - }, 200); - - $('.modal[aria-hidden=false]').each(function () { - if ($(this).attr('id') !== 'associate-groups-modal') { - $(this).modal('hide'); - } - }); - - scope.closeModal = function() { - $("body").removeClass("is-modalOpen"); - $('#associate-groups-modal').on('hidden.bs.modal', - function () { - $('.AddUsers').remove(); - }); - $('#associate-groups-modal').modal('hide'); - - $state.go('^', null, {reload: true}); - }; - - scope.compileList = function(html) { - $('#associate-groups-list').append($compile(html)(scope)); - }; - - Wait('stop'); - - window.scrollTo(0,0); - } - }; - } -]; diff --git a/awx/ui/client/src/inventories-hosts/shared/associate-groups/associate-groups.partial.html b/awx/ui/client/src/inventories-hosts/shared/associate-groups/associate-groups.partial.html deleted file mode 100644 index c720e6429ab2..000000000000 --- a/awx/ui/client/src/inventories-hosts/shared/associate-groups/associate-groups.partial.html +++ /dev/null @@ -1,21 +0,0 @@ - diff --git a/awx/ui/client/src/inventories-hosts/shared/associate-hosts/associate-hosts.block.less b/awx/ui/client/src/inventories-hosts/shared/associate-hosts/associate-hosts.block.less deleted file mode 100644 index 68ea70336999..000000000000 --- a/awx/ui/client/src/inventories-hosts/shared/associate-hosts/associate-hosts.block.less +++ /dev/null @@ -1,12 +0,0 @@ -.AssociateHosts-modalBody { - padding-top: 0px; -} -.AssociateHosts-backDrop { - width: 100vw; - height: 100vh; - position: fixed; - top: 0; - left: 0; - opacity: 0; - transition: 0.5s opacity; -} diff --git a/awx/ui/client/src/inventories-hosts/shared/associate-hosts/associate-hosts.controller.js b/awx/ui/client/src/inventories-hosts/shared/associate-hosts/associate-hosts.controller.js deleted file mode 100644 index a72f93790825..000000000000 --- a/awx/ui/client/src/inventories-hosts/shared/associate-hosts/associate-hosts.controller.js +++ /dev/null @@ -1,112 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - export default ['$scope', '$rootScope', 'ProcessErrors', 'GetBasePath', 'generateList', - '$state', 'Rest', '$q', 'Wait', '$window', 'QuerySet', 'RelatedHostsListDefinition', 'i18n', - function($scope, $rootScope, ProcessErrors, GetBasePath, generateList, - $state, Rest, $q, Wait, $window, qs, RelatedHostsListDefinition, i18n) { - $scope.$on("linkLists", function() { - - init(); - - function init(){ - $scope.associate_host_default_params = { - order_by: 'name', - page_size: 5 - }; - - $scope.associate_host_queryset = { - order_by: 'name', - page_size: 5 - }; - - if ($state.params.group_id) { - $scope.associate_host_default_params.not__groups = $state.params.group_id; - $scope.associate_host_queryset.not__groups = $state.params.group_id; - } - - let list = _.cloneDeep(RelatedHostsListDefinition); - list.basePath = GetBasePath('inventory') + $state.params.inventory_id + '/hosts'; - list.iterator = 'associate_host'; - list.name = 'associate_hosts'; - list.multiSelect = true; - list.fields.name.ngClick = 'linkoutHost(associate_host)'; - list.fields.name.ngClass = "{ 'host-disabled-label': !associate_host.enabled }"; - list.fields.name.dataHostId = "{{ associate_host.id }}"; - list.trackBy = 'associate_host.id'; - list.multiSelectPreview = { - selectedRows: 'selectedItems', - availableRows: 'associate_hosts' - }; - list.emptyListText = i18n._('No hosts to add'); - delete list.fields.toggleHost; - delete list.fields.active_failures; - delete list.fields.groups; - delete list.actions; - delete list.fieldActions; - list.well = false; - $scope.list = list; - - // Fire off the initial search - qs.search(list.basePath, $scope.associate_host_default_params) - .then(function(res) { - $scope.associate_host_dataset = res.data; - $scope.associate_hosts = $scope.associate_host_dataset.results; - - let html = generateList.build({ - list: list, - mode: 'edit', - title: false, - hideViewPerPage: true - }); - - $scope.compileList(html); - - $scope.$watchCollection('associate_hosts', function () { - if($scope.selectedItems) { - $scope.associate_hosts.forEach(function(row, i) { - if ($scope.selectedItems.filter(function(e) { return e.id === row.id; }).length > 0) { - $scope.associate_hosts[i].isSelected = true; - } - }); - } - }); - - }); - - $scope.selectedItems = []; - $scope.$on('selectedOrDeselected', function(e, value) { - let item = value.value; - - if (value.isSelected) { - $scope.selectedItems.push(item); - } - else { - // _.remove() Returns the new array of removed elements. - // This will pull all the values out of the array that don't - // match the deselected item effectively removing it - $scope.selectedItems = _.remove($scope.selectedItems, function(selectedItem) { - return selectedItem.id !== item.id; - }); - } - }); - } - - $scope.linkhosts = function() { - $scope.saveFunction({selectedItems: $scope.selectedItems}) - .then(() =>{ - $scope.closeModal(); - }).catch(() => { - $scope.closeModal(); - }); - - }; - - $scope.linkoutHost = function(host) { - $window.open('/#/inventories/inventory/' + host.inventory + '/hosts/edit/' + host.id,'_blank'); - }; - }); - }]; diff --git a/awx/ui/client/src/inventories-hosts/shared/associate-hosts/associate-hosts.directive.js b/awx/ui/client/src/inventories-hosts/shared/associate-hosts/associate-hosts.directive.js deleted file mode 100644 index 8cfbdb10941b..000000000000 --- a/awx/ui/client/src/inventories-hosts/shared/associate-hosts/associate-hosts.directive.js +++ /dev/null @@ -1,60 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ -import controller from './associate-hosts.controller'; - -/* jshint unused: vars */ -export default ['templateUrl', 'Wait', '$compile', '$state', - function(templateUrl, Wait, $compile, $state) { - return { - restrict: 'E', - transclude: true, - scope: { - saveFunction: '&' - }, - controller: controller, - templateUrl: templateUrl('inventories-hosts/shared/associate-hosts/associate-hosts'), - link: function(scope, element, attrs, controller, transcludefn) { - - $("body").addClass("is-modalOpen"); - - //$("body").append(element); - - Wait('start'); - - scope.$broadcast("linkLists"); - - setTimeout(function() { - $('#associate-hosts-modal').modal("show"); - }, 200); - - $('.modal[aria-hidden=false]').each(function () { - if ($(this).attr('id') !== 'associate-hosts-modal') { - $(this).modal('hide'); - } - }); - - scope.closeModal = function() { - $("body").removeClass("is-modalOpen"); - $('#associate-hosts-modal').on('hidden.bs.modal', - function () { - $('.AddUsers').remove(); - }); - $('#associate-hosts-modal').modal('hide'); - - $state.go('^', null, {reload: true}); - }; - - scope.compileList = function(html) { - $('#associate-hosts-list').append($compile(html)(scope)); - }; - - Wait('stop'); - - window.scrollTo(0,0); - } - }; - } -]; diff --git a/awx/ui/client/src/inventories-hosts/shared/associate-hosts/associate-hosts.partial.html b/awx/ui/client/src/inventories-hosts/shared/associate-hosts/associate-hosts.partial.html deleted file mode 100644 index 574fc4106660..000000000000 --- a/awx/ui/client/src/inventories-hosts/shared/associate-hosts/associate-hosts.partial.html +++ /dev/null @@ -1,21 +0,0 @@ - diff --git a/awx/ui/client/src/inventories-hosts/shared/factories/set-enabled-msg.factory.js b/awx/ui/client/src/inventories-hosts/shared/factories/set-enabled-msg.factory.js deleted file mode 100644 index a31b8f73fa43..000000000000 --- a/awx/ui/client/src/inventories-hosts/shared/factories/set-enabled-msg.factory.js +++ /dev/null @@ -1,16 +0,0 @@ -export default - function SetEnabledMsg(i18n) { - return function(host) { - if (host.has_inventory_sources) { - // Inventory sync managed, so not clickable - host.enabledToolTip = (host.enabled) ? i18n._('Host is available') : i18n._('Host is not available'); - } - else { - // Clickable - host.enabledToolTip = (host.enabled) ? i18n._('Host is available. Click to toggle.') : i18n._('Host is not available. Click to toggle.'); - } - }; - } - -SetEnabledMsg.$inject = - [ 'i18n', ]; diff --git a/awx/ui/client/src/inventories-hosts/shared/factories/set-status.factory.js b/awx/ui/client/src/inventories-hosts/shared/factories/set-status.factory.js deleted file mode 100644 index aea5b5671f9f..000000000000 --- a/awx/ui/client/src/inventories-hosts/shared/factories/set-status.factory.js +++ /dev/null @@ -1,103 +0,0 @@ -export default - function SetStatus($filter, SetEnabledMsg, Empty, i18n) { - return function(params) { - var scope = params.scope, - host = params.host, - i, html, title; - - function ellipsis(a) { - if (a.length > 25) { - return a.substr(0,25) + '...'; - } - return a; - } - - function noRecentJobs() { - title = i18n._('No job data'); - html = "

" + i18n._("No recent job data available for this host.") + "

\n"; - } - - function setMsg(host) { - var j, job, jobs; - - if (host.has_active_failures === true || (host.has_active_failures === false && host.last_job !== null)) { - if (host.has_active_failures === true) { - host.badgeToolTip = i18n._('Most recent job failed. Click to view jobs.'); - host.active_failures = 'error'; - } - else { - host.badgeToolTip = i18n._("Most recent job successful. Click to view jobs."); - host.active_failures = 'successful'; - } - if (host.summary_fields.recent_jobs.length > 0) { - // build html table of job status info - jobs = host.summary_fields.recent_jobs.sort( - function(a,b) { - // reverse numerical order - return -1 * (a - b); - }); - title = "Recent Jobs"; - html = "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - html += "\n"; - for (j=0; j < jobs.length; j++) { - job = jobs[j]; - html += "\n"; - - // SmartStatus-tooltips are named --success whereas icon-job uses successful - var iconStatus = (job.status === 'successful') ? 'success' : 'failed'; - - html += "\n"; - - html += "\n"; - - html += "\n"; - - html += "\n"; - } - html += "\n"; - html += "
" + i18n._("Status") + "" + i18n._("Finished") + "" + i18n._("Name") + "
" + ($filter('longDate')(job.finished)).replace(/ /,'
') + "
" + $filter('sanitize')(ellipsis(job.name)) + "
\n"; - } - else { - noRecentJobs(); - } - } - else if (host.has_active_failures === false && host.last_job === null) { - host.badgeToolTip = i18n._("No job data available."); - host.active_failures = 'none'; - noRecentJobs(); - } - host.job_status_html = html; - host.job_status_title = title; - } - - if (!Empty(host)) { - // update single host - setMsg(host); - SetEnabledMsg(host); - } - else { - // update all hosts - for (i=0; i < scope.hosts.length; i++) { - setMsg(scope.hosts[i]); - SetEnabledMsg(scope.hosts[i]); - } - } - }; - } - -SetStatus.$inject = - [ '$filter', - 'SetEnabledMsg', - 'Empty', - 'i18n' - ]; diff --git a/awx/ui/client/src/inventories-hosts/shared/groups.service.js b/awx/ui/client/src/inventories-hosts/shared/groups.service.js deleted file mode 100644 index 0c6e1f8bef3c..000000000000 --- a/awx/ui/client/src/inventories-hosts/shared/groups.service.js +++ /dev/null @@ -1,131 +0,0 @@ -export default - ['$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', 'Wait', function($rootScope, Rest, GetBasePath, ProcessErrors, Wait){ - return { - stringifyParams: function(params){ - return _.reduce(params, (result, value, key) => { - return result + key + '=' + value + '&'; - }, ''); - }, - // cute abstractions via fn.bind() - url: function(){ - return ''; - }, - error: function(data) { - ProcessErrors($rootScope, data.data, data.status, null, { hdr: 'Error!', - msg: 'Call to ' + this.url + '. GET returned: ' + data.status }); - }, - success: function(data){ - return data; - }, - // HTTP methods - get: function(params){ - Wait('start'); - this.url = GetBasePath('groups') + '?' + this.stringifyParams(params); - Rest.setUrl(this.url); - return Rest.get() - .then(this.success.bind(this)) - .catch(this.error.bind(this)) - .finally(Wait('stop')); - }, - post: function(group){ - Wait('start'); - this.url = GetBasePath('groups'); - Rest.setUrl(this.url); - return Rest.post(group) - .then(this.success.bind(this)) - .catch(this.error.bind(this)) - .finally(Wait('stop')); - }, - put: function(group){ - Wait('start'); - this.url = GetBasePath('groups') + group.id; - Rest.setUrl(this.url); - return Rest.put(group) - .then(this.success.bind(this)) - .catch(this.error.bind(this)) - .finally(Wait('stop')); - }, - delete: function(id){ - Wait('start'); - this.url = GetBasePath('groups') + id; - Rest.setUrl(this.url); - return Rest.destroy() - .then(this.success.bind(this)) - .catch(this.error.bind(this)) - .finally(Wait('stop')); - }, - getCredential: function(id){ - Wait('start'); - this.url = GetBasePath('credentials') + id; - Rest.setUrl(this.url); - return Rest.get() - .then(this.success.bind(this)) - .catch(this.error.bind(this)) - .finally(Wait('stop')); - }, - getInventorySource: function(params){ - Wait('start'); - this.url = GetBasePath('inventory_sources') + '?' + this.stringifyParams(params); - Rest.setUrl(this.url); - return Rest.get() - .then(this.success.bind(this)) - .catch(this.error.bind(this)) - .finally(Wait('stop')); - }, - putInventorySource: function(params, url){ - Wait('start'); - this.url = url; - Rest.setUrl(this.url); - return Rest.put(params) - .then(this.success.bind(this)) - .catch(this.error.bind(this)) - .finally(Wait('stop')); - }, - // these relationship setters could be consolidated, but verbosity makes the operation feel more clear @ controller level - associateGroup: function(group, target){ - Wait('start'); - this.url = GetBasePath('groups') + target + '/children/'; - Rest.setUrl(this.url); - return Rest.post(group) - .then(this.success.bind(this)) - .catch(this.error.bind(this)) - .finally(Wait('stop')); - }, - disassociateGroup: function(group, parent){ - Wait('start'); - this.url = GetBasePath('groups') + parent + '/children/'; - Rest.setUrl(this.url); - return Rest.post({id: group, disassociate: 1}) - .then(this.success.bind(this)) - .catch(this.error.bind(this)) - .finally(Wait('stop')); - }, - associateHost: function(host, target){ - Wait('start'); - this.url = GetBasePath('groups') + target + '/hosts/'; - Rest.setUrl(this.url); - return Rest.post(host) - .then(this.success.bind(this)) - .catch(this.error.bind(this)) - .finally(Wait('stop')); - }, - disassociateHost: function(host, group){ - Wait('start'); - this.url = GetBasePath('groups') + group + '/hosts/'; - Rest.setUrl(this.url); - return Rest.post({id: host, disassociate: 1}) - .then(this.success.bind(this)) - .catch(this.error.bind(this)) - .finally(Wait('stop')); - }, - promote: function(group, inventory){ - Wait('start'); - this.url = GetBasePath('inventory') + inventory + '/groups/'; - Rest.setUrl(this.url); - return Rest.post({id: group, disassociate: 1}) - .then(this.success.bind(this)) - .catch(this.error.bind(this)) - .finally(Wait('stop')); - } - }; - }]; diff --git a/awx/ui/client/src/inventories-hosts/shared/hosts.service.js b/awx/ui/client/src/inventories-hosts/shared/hosts.service.js deleted file mode 100644 index fc4af5acc8be..000000000000 --- a/awx/ui/client/src/inventories-hosts/shared/hosts.service.js +++ /dev/null @@ -1,74 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - export default - ['$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', 'Wait', - function($rootScope, Rest, GetBasePath, ProcessErrors, Wait){ - return { - stringifyParams: function(params){ - return _.reduce(params, (result, value, key) => { - return result + key + '=' + value + '&'; - }, ''); - }, - // cute abstractions via fn.bind() - url: function(){ - return ''; - }, - error: function(data) { - ProcessErrors($rootScope, data.data, data.status, null, { hdr: 'Error!', - msg: 'Call to ' + this.url + '. GET returned: ' + data.status }); - }, - success: function(data){ - return data; - }, - // HTTP methods - get: function(params){ - Wait('start'); - this.url = GetBasePath('hosts') + '?' + this.stringifyParams(params); - Rest.setUrl(this.url); - return Rest.get() - .then(this.success.bind(this)) - .catch(this.error.bind(this)) - .finally(Wait('stop')); - }, - patch: function(id, data){ - Wait('start'); - this.url = GetBasePath('hosts') + id; - Rest.setUrl(this.url); - return Rest.patch(data) - .then(this.success.bind(this)) - .catch(this.error.bind(this)) - .finally(Wait('stop')); - }, - post: function(host){ - Wait('start'); - this.url = GetBasePath('hosts'); - Rest.setUrl(this.url); - return Rest.post(host) - .then(this.success.bind(this)) - .catch(this.error.bind(this)) - .finally(Wait('stop')); - }, - put: function(host){ - Wait('start'); - this.url = GetBasePath('hosts') + host.id; - Rest.setUrl(this.url); - return Rest.put(host) - .then(this.success.bind(this)) - .catch(this.error.bind(this)) - .finally(Wait('stop')); - }, - delete: function(id){ - Wait('start'); - this.url = GetBasePath('hosts') + id; - Rest.setUrl(this.url); - return Rest.destroy() - .then(this.success.bind(this)) - .catch(this.error.bind(this)) - .finally(Wait('stop')); - } - }; - }]; diff --git a/awx/ui/client/src/inventories-hosts/shared/inventories.service.js b/awx/ui/client/src/inventories-hosts/shared/inventories.service.js deleted file mode 100644 index b6dab8728ab8..000000000000 --- a/awx/ui/client/src/inventories-hosts/shared/inventories.service.js +++ /dev/null @@ -1,81 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - export default - ['$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', 'Wait', - function($rootScope, Rest, GetBasePath, ProcessErrors, Wait){ - return { - // cute abstractions via fn.bind() - url: function(){ - return ''; - }, - error: function(data, status) { - ProcessErrors($rootScope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + this.url + '. GET returned: ' + status }); - }, - success: function(data){ - return data; - }, - // data getters - getInventory: function(id){ - Wait('start'); - this.url = GetBasePath('inventory') + id; - Rest.setUrl(this.url); - return Rest.get() - .then(this.success.bind(this)) - .catch(this.error.bind(this)) - .finally(Wait('stop')); - }, - getBreadcrumbs: function(groups){ - Wait('start'); - this.url = GetBasePath('groups') + '?' + _.map(groups, function(item){ - return '&or__id=' + item; - }).join(''); - Rest.setUrl(this.url); - return Rest.get() - .then(this.success.bind(this)) - .catch(this.error.bind(this)) - .finally(Wait('stop')); - }, - rootHostsUrl: function(id){ - var url = GetBasePath('inventory') + id + '/hosts'; - return url; - }, - childHostsUrl: function(id){ - var url = GetBasePath('groups') + id + '/all_hosts'; - return url; - }, - childGroupsUrl: function(id){ - var url = GetBasePath('groups') + id + '/children'; - return url; - }, - groupsUrl: function(id){ - var url = GetBasePath('inventory') + id+ '/groups'; - return url; - }, - inventorySourcesOptions: function(inventoryId) { - this.url = GetBasePath('inventory') + inventoryId + '/inventory_sources'; - Rest.setUrl(this.url); - return Rest.options() - .then(this.success.bind(this)) - .catch(this.error.bind(this)); - }, - updateInventorySourcesGet: function(inventoryId) { - this.url = GetBasePath('inventory') + inventoryId + '/update_inventory_sources'; - Rest.setUrl(this.url); - return Rest.get() - .then(this.success.bind(this)) - .catch(this.error.bind(this)); - }, - getHost: function(inventoryId, hostId) { - this.url = GetBasePath('inventory') + inventoryId + '/hosts?id=' + hostId; - Rest.setUrl(this.url); - return Rest.get() - .then(this.success.bind(this)) - .catch(this.error.bind(this)); - } - }; - }]; diff --git a/awx/ui/client/src/inventories-hosts/shared/main.js b/awx/ui/client/src/inventories-hosts/shared/main.js deleted file mode 100644 index 37075ddc524c..000000000000 --- a/awx/ui/client/src/inventories-hosts/shared/main.js +++ /dev/null @@ -1,26 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import SetStatus from './factories/set-status.factory'; -import SetEnabledMsg from './factories/set-enabled-msg.factory'; -import ansibleFacts from './ansible-facts/main'; -import InventoriesService from './inventories.service'; -import GroupsService from './groups.service'; -import HostsService from './hosts.service'; -import associateGroups from './associate-groups/associate-groups.directive'; -import associateHosts from './associate-hosts/associate-hosts.directive'; - -export default -angular.module('inventoriesHostsFactories', [ - ansibleFacts.name -]) - .factory('SetStatus', SetStatus) - .factory('SetEnabledMsg', SetEnabledMsg) - .service('HostsService', HostsService) - .service('InventoriesService', InventoriesService) - .service('GroupsService', GroupsService) - .directive('associateGroups', associateGroups) - .directive('associateHosts', associateHosts); diff --git a/awx/ui/client/src/inventory-scripts/add/add.controller.js b/awx/ui/client/src/inventory-scripts/add/add.controller.js deleted file mode 100644 index 60c6714d703e..000000000000 --- a/awx/ui/client/src/inventory-scripts/add/add.controller.js +++ /dev/null @@ -1,62 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['Rest', 'Wait', - 'InventoryScriptsForm', 'ProcessErrors', 'GetBasePath', - 'GenerateForm', '$scope', '$state', 'Alert', - function(Rest, Wait, - InventoryScriptsForm, ProcessErrors, GetBasePath, - GenerateForm, $scope, $state, Alert - ) { - var form = InventoryScriptsForm, - url = GetBasePath('inventory_scripts'); - - init(); - - function init() { - Rest.setUrl(url); - Rest.options() - .then(({data}) => { - if (!data.actions.POST) { - $state.go("^"); - Alert('Permission Error', 'You do not have permission to add an inventory script.', 'alert-info'); - } - }); - - // apply form definition's default field values - GenerateForm.applyDefaults(form, $scope); - - // @issue @jmitchell - this setting probably collides with new RBAC can* implementation? - $scope.canEdit = true; - } - - // Save - $scope.formSave = function() { - Wait('start'); - Rest.setUrl(url); - Rest.post({ - name: $scope.name, - description: $scope.description, - organization: $scope.organization, - script: $scope.script - }) - .then(() => { - $state.go('inventoryScripts', null, { reload: true }); - Wait('stop'); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, form, { - hdr: 'Error!', - msg: 'Failed to add new inventory script. POST returned status: ' + status - }); - }); - }; - - $scope.formCancel = function() { - $state.go('^'); - }; - } -]; diff --git a/awx/ui/client/src/inventory-scripts/add/main.js b/awx/ui/client/src/inventory-scripts/add/main.js deleted file mode 100644 index 11d9659f64d4..000000000000 --- a/awx/ui/client/src/inventory-scripts/add/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import controller from './add.controller'; - -export default - angular.module('inventoryScriptsAdd', []) - .controller('InventoryScriptsAddController', controller); diff --git a/awx/ui/client/src/inventory-scripts/edit/edit.controller.js b/awx/ui/client/src/inventory-scripts/edit/edit.controller.js deleted file mode 100644 index 5bd7ee8f19ac..000000000000 --- a/awx/ui/client/src/inventory-scripts/edit/edit.controller.js +++ /dev/null @@ -1,79 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['Rest', 'Wait', - 'InventoryScriptsForm', 'ProcessErrors', 'GetBasePath', - 'GenerateForm', 'inventory_scriptData', - '$scope', '$state', - function( - Rest, Wait, InventoryScriptsForm, ProcessErrors, GetBasePath, - GenerateForm, inventory_scriptData, - $scope, $state - ) { - var generator = GenerateForm, - data = inventory_scriptData, - id = inventory_scriptData.id, - form = InventoryScriptsForm, - master = {}, - url = GetBasePath('inventory_scripts'); - - init(); - - function init() { - $scope.inventory_script = inventory_scriptData; - - $scope.$watch('inventory_script_obj.summary_fields.user_capabilities.edit', function(val) { - if (val === false) { - $scope.canAdd = false; - } - }); - - var fld; - for (fld in form.fields) { - if (data[fld]) { - $scope[fld] = data[fld]; - master[fld] = data[fld]; - } - - if (form.fields[fld].sourceModel && data.summary_fields && - data.summary_fields[form.fields[fld].sourceModel]) { - $scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] = - data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField]; - master[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] = - data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField]; - } - } - - } - - $scope.formSave = function() { - generator.clearApiErrors($scope); - Wait('start'); - Rest.setUrl(url + id + '/'); - Rest.put({ - name: $scope.name, - description: $scope.description, - organization: $scope.organization, - script: $scope.script - }) - .then(() => { - $state.go('inventoryScripts', null, { reload: true }); - Wait('stop'); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, form, { - hdr: 'Error!', - msg: 'Failed to add new inventory script. PUT returned status: ' + status - }); - }); - }; - - $scope.formCancel = function() { - $state.go('inventoryScripts'); - }; - - } -]; diff --git a/awx/ui/client/src/inventory-scripts/edit/main.js b/awx/ui/client/src/inventory-scripts/edit/main.js deleted file mode 100644 index b51e421206e7..000000000000 --- a/awx/ui/client/src/inventory-scripts/edit/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import controller from './edit.controller'; - -export default - angular.module('inventoryScriptsEdit', []) - .controller('InventoryScriptsEditController', controller); diff --git a/awx/ui/client/src/inventory-scripts/inventory-scripts.form.js b/awx/ui/client/src/inventory-scripts/inventory-scripts.form.js deleted file mode 100644 index 0dbf97a0ba9f..000000000000 --- a/awx/ui/client/src/inventory-scripts/inventory-scripts.form.js +++ /dev/null @@ -1,82 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name forms.function:CustomInventory - * @description This form is for adding/editing an organization -*/ - -export default ['i18n', function(i18n) { - return { - - addTitle: i18n._('NEW CUSTOM INVENTORY'), - editTitle: '{{ name }}', - name: 'inventory_script', - basePath: 'inventory_scripts', - stateTree: 'inventoryScripts', - // I18N for "CREATE INVENTORY_SCRIPT" - // on /#/inventory_scripts/add - breadcrumbName: i18n._('INVENTORY SCRIPT'), - showActions: true, - - fields: { - name: { - label: i18n._('Name'), - type: 'text', - ngDisabled: '!(inventory_script_obj.summary_fields.user_capabilities.edit || canAdd)', - required: true, - capitalize: false - }, - description: { - label: i18n._('Description'), - type: 'text', - ngDisabled: '!(inventory_script_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - organization: { - label: i18n._('Organization'), - type: 'lookup', - list: 'OrganizationList', - basePath: 'organizations', - required: true, - sourceModel: 'organization', - sourceField: 'name', - ngDisabled: '!(inventory_script_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - script: { - label: i18n._('Custom Script'), - type: 'textarea', - class: 'Form-formGroup--fullWidth', - elementClass: 'Form-monospace', - required: true, - awDropFile: true, - ngDisabled: '!(inventory_script_obj.summary_fields.user_capabilities.edit || canAdd)', - ngTrim: false, - rows: 10, - awPopOver: i18n._('Drag and drop your custom inventory script file here or create one in the field to import your custom inventory. Refer to the Ansible Tower documentation for example syntax.'), - dataTitle: i18n._('Custom Script'), - dataPlacement: 'right', - dataContainer: "body" - }, - }, - - buttons: { //for now always generates
\n"; - - // Add the related confirm field - if (field.associated) { - fld = field.associated; - field = form.fields[field.associated]; - scope[fld] = ''; - html += "
\n"; - html += "\n"; - html += '*'; - html += "Please confirm the password.\n"; - html += (field.awPassMatch) ? "This value does not match the password you entered previously. Please confirm that password.
\n" : ""; - html += "
\n"; - html += "
\n"; - } - }); - - scope.$emit(callback, html, url); - - // Password change - scope.clearPWConfirm = function (fld) { - // If password value changes, make sure password_confirm must be re-entered - scope[fld] = ''; - scope.job_launch_form[fld].$setValidity('awpassmatch', false); - scope.checkStatus(); - }; - - scope.checkStatus = function() { - if (!scope.job_launch_form.$invalid) { - $('#password-accept-button').removeAttr('disabled'); - } - else { - $('#password-accept-button').attr({ "disabled": "disabled" }); - } - }; - }; - } - -PromptForPasswords.$inject = - [ 'CredentialForm' ]; diff --git a/awx/ui/client/src/job-submission/main.js b/awx/ui/client/src/job-submission/main.js deleted file mode 100644 index 052173de5e10..000000000000 --- a/awx/ui/client/src/job-submission/main.js +++ /dev/null @@ -1,27 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import LaunchJob from './job-submission-factories/launchjob.factory'; -import AdhocRun from './job-submission-factories/adhoc-run.factory.js'; -import CheckPasswords from './job-submission-factories/check-passwords.factory'; -import CreateLaunchDialog from './job-submission-factories/create-launch-dialog.factory'; -import InventoryUpdate from './job-submission-factories/inventory-update.factory'; -import ProjectUpdate from './job-submission-factories/project-update.factory'; -import PromptForPasswords from './job-submission-factories/prompt-for-passwords.factory'; -import awPasswordMin from './job-submission-directives/aw-password-min.directive'; -import awPasswordMax from './job-submission-directives/aw-password-max.directive'; - -export default - angular.module('jobSubmission', []) - .factory('LaunchJob', LaunchJob) - .factory('AdhocRun', AdhocRun) - .factory('CheckPasswords', CheckPasswords) - .factory('CreateLaunchDialog', CreateLaunchDialog) - .factory('InventoryUpdate', InventoryUpdate) - .factory('ProjectUpdate', ProjectUpdate) - .factory('PromptForPasswords', PromptForPasswords) - .directive('awPasswordMin', awPasswordMin) - .directive('awPasswordMax', awPasswordMax); diff --git a/awx/ui/client/src/license/checkLicense.factory.js b/awx/ui/client/src/license/checkLicense.factory.js deleted file mode 100644 index e76f1ea0b76b..000000000000 --- a/awx/ui/client/src/license/checkLicense.factory.js +++ /dev/null @@ -1,69 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default - ['$state', '$rootScope', 'Rest', 'GetBasePath', - 'ConfigService', '$q', - function($state, $rootScope, Rest, GetBasePath, - ConfigService, $q){ - return { - get: function() { - var config = ConfigService.get(); - return config.license_info; - }, - - post: function(payload, eula){ - var defaultUrl = GetBasePath('config'); - Rest.setUrl(defaultUrl); - var data = payload; - data.eula_accepted = eula; - - return Rest.post(JSON.stringify(data)) - .then((response) =>{ - return response.data; - }) - .catch(({data}) => { - return $q.reject(data); - }); - }, - - valid: function(license) { - if (!license.valid_key){ - return false; - } - return true; - }, - - test: function(event){ - var license = this.get(); - if(license === null || !$rootScope.license_tested){ - if(this.valid(license) === false) { - $rootScope.licenseMissing = true; - $state.go('license'); - if(event){ - event.preventDefault(); - } - } - else { - $rootScope.licenseMissing = false; - } - } - else if(this.valid(license) === false) { - $rootScope.licenseMissing = true; - $state.go('license'); - if(event){ - event.preventDefault(); - } - } - else { - $rootScope.licenseMissing = false; - } - return; - } - - }; - } - ]; diff --git a/awx/ui/client/src/license/fileOnChange.directive.js b/awx/ui/client/src/license/fileOnChange.directive.js deleted file mode 100644 index 2e044413087b..000000000000 --- a/awx/ui/client/src/license/fileOnChange.directive.js +++ /dev/null @@ -1,16 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default - [function(){ - return { - restrict: 'A', - link: function(scope, el, attrs){ - var onChange = scope.$eval(attrs.fileOnChange); - el.bind('change', onChange); - } - }; - }]; diff --git a/awx/ui/client/src/license/license.block.less b/awx/ui/client/src/license/license.block.less deleted file mode 100644 index f86d323ab888..000000000000 --- a/awx/ui/client/src/license/license.block.less +++ /dev/null @@ -1,290 +0,0 @@ -/* -* Style conventions -* .ModuleName-component-subComponent -* Naming describes components of the view -*/ -.License-container{ - .OnePlusTwo-container; -} - -.License-container--missing { - max-width: 800px; - margin: 0 auto; - padding: 0 20px; -} - -.License-field--label{ - .OnePlusTwo-left--detailsLabel; -} -.License-fileName{ - padding-left: 20px; -} -.License-management .CodeMirror-scroll{ - min-height: 140px; -} -.License-file textarea{ - display: block; - width: 100%; -} -.License-file--left { - display: flex; - flex:1; - overflow: hidden; -} -.License-file--middle { - display: flex; - flex: 0 0 auto; - padding: 0px 20px; - flex-direction: column; -} -.License-file--right { - display: flex; - flex:1; -} -.License-submit--success.ng-hide-add, .License-submit--success.ng-hide-remove { - transition: all ease-in-out 0.5s; -} -.License-submit--success{ - opacity: 1; - transition: all ease-in-out 0.5s; -} -.License-submit--success.ng-hide{ - opacity: 0; -} - -.License-eulaNotice{ - font-size: 12px; - width: 100%; - max-height: 129px; - padding: 15px; - padding-top: 10px; - margin-bottom: 10px; - border-radius: 4px; - border: 1px solid @login-notice-border; - background-color: @login-notice-bg; - color: @login-notice-text; - overflow-y: scroll; - overflow-x: visible; - white-space: pre-line; -} - -.License-field label{ - width: 155px; -} -.License-field--content{ - .OnePlusTwo-left--detailsContent; - text-transform: capitalize; -} -.License-field--key { - text-transform: none; -} -.License-field{ - .OnePlusTwo-left--detailsRow; -} -.License-field + .License-field { - margin-top: 20px; -} -.License-greenText{ - color: @submit-button-bg; - padding-right: 10px; -} -.License-redText{ - color: @default-err; - padding-right: 10px; -} -.License-fields{ - .OnePlusTwo-left--details; -} - -.License-titleText { - .OnePlusTwo-panelHeader; -} -.License-management{ - display: flex; -} -.License-management--missingLicense{ - height: auto; -} -.License-downloadLicenseButton{ - margin-bottom: 10px; - color:@default-bg; -} -.License-downloadLicenseButton:hover{ - background-color: @default-link-hov !important; - color:@default-bg !important; -} -.License-downloadLicenseButton:focus{ - background-color: @default-link-hov !important; - color:@default-bg !important; -} - -@media (min-width: 900px) { - .License-details { - margin-right: 20px; - } -} - -.License-submit--success, .License-submit--failure { - margin: 0; -} -.License-file--container { - display: flex; - input[type=file] { - display: none; - } -} -.License-upgradeText { - margin: 20px 0px; -} -.License-body { - margin-top: 25px; -} -.License-subTitleText { - text-transform: uppercase; - margin: 20px 0px 15px 0px; - color: @default-interface-txt; -} -.License-helperText { - color: @default-interface-txt; -} -.License-input--fake{ - border-top-right-radius: 4px !important; - border-bottom-right-radius: 4px !important; -} - -.License-detailsGroup { - margin-bottom: 20px; -} - -.License-analyticsCheckbox { - padding-top: 5px; -} - -.License-analyticsCheckboxGroup { - padding: 10px 0; - font-weight: bold; -} - -.License-separator { - display: flex; - flex: 1; - background: linear-gradient(#d7d7d7, #d7d7d7) no-repeat center/2px 100%; -} - -.License-licenseStepHelp { - font-size: 12px; - font-style: italic; - margin-bottom: 10px; -} - -.License-filePicker { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; -} - -.License-rhCredField { - margin-bottom: 10px; -} - -.License-label { - color: @field-label; - font-weight: 400; -} - -.License-action { - display: flex; - flex-direction: row; - align-content:flex-end; -} - -.License-actionError { - flex: 1; -} - -.License-subSelectorModal { - height: 100%; - width: 100%; - position: fixed; - top: 0; - left: 0; - background: rgba(0, 0, 0, 0.3); - z-index: 1040; - display: flex; - align-items: center; - justify-content: center; -} - -.License-modal { - width: 750px; -} - -.License-modalBody { - border: 1px solid @b7grey; - max-height: 550px; - overflow: scroll; - border-radius: 4px; -} - -.License-modalRow { - display: flex; - padding: 10px; -} - -.License-modalRow:not(:last-of-type) { - border-bottom: 1px solid @b7grey; -} - -.License-modalRowRadio { - flex: 0 0 40px; - display: flex; - align-items: center; -} - -.License-trialTag { - font-weight: 100; - background-color: #ebebeb; - border-radius: 5px; - color: #606060; - font-size: 10px; - margin-right: 10px; - padding: 3px 9px; - line-height: 14px; - word-break: keep-all; - display: inline-flex; -} - -.License-introText { - margin-bottom: 10px; -} - -.License-getLicensesButton { - display: flex; - justify-content: flex-end; - margin-bottom: 20px; -} - -.License-checkboxLabel { - margin-left: 5px; - font-weight: normal; -} - -.License-modalRowDetails { - flex: 1; -} - -.License-modalRowDetailsLabel { - font-weight: normal; - width: 100%; -} - -.License-modalRowDetailsRow { - margin-bottom: 10px; -} - -.License-modalRowDetails--50 { - display: flex; - flex-basis: 50%; - align-items: center; - line-height: 21px; -} diff --git a/awx/ui/client/src/license/license.controller.js b/awx/ui/client/src/license/license.controller.js deleted file mode 100644 index 9e344dd2e411..000000000000 --- a/awx/ui/client/src/license/license.controller.js +++ /dev/null @@ -1,234 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import {N_} from "../i18n"; - -export default - ['Wait', '$state', '$scope', '$rootScope', 'ProcessErrors', 'CheckLicense', 'moment', '$timeout', 'Rest', - '$window', 'ConfigService', 'pendoService', 'insightsEnablementService', 'i18n', 'config', 'rhCreds', 'GetBasePath', - function(Wait, $state, $scope, $rootScope, ProcessErrors, CheckLicense, moment, $timeout, Rest, - $window, ConfigService, pendoService, insightsEnablementService, i18n, config, rhCreds, GetBasePath) { - - const calcDaysRemaining = function(seconds) { - // calculate the number of days remaining on the license - let duration = moment.duration(seconds, 'seconds').asDays(); - - duration = Math.floor(duration); - if(duration < 0){ - duration = 0; - } - - duration = (duration!==1) ? `${duration} Days` : `${duration} Day`; - - return duration; - }; - - const calcExpiresOn = function(seconds) { - // calculate the expiration date of the license - return moment.unix(seconds).calendar(); - }; - - const reset = function() { - $scope.newLicense.eula = undefined; - $scope.rhCreds = {}; - $scope.selectedLicense = {}; - }; - - const initVars = (config) => { - // license/license.partial.html compares fileName - $scope.fileName = N_("No file selected."); - - if ($rootScope.licenseMissing) { - $scope.title = $rootScope.BRAND_NAME + i18n._(" License"); - } else { - $scope.title = i18n._("License Management"); - } - - $scope.license = config; - $scope.license.version = config.version.split('-')[0]; - $scope.time = {}; - $scope.time.remaining = calcDaysRemaining($scope.license.license_info.time_remaining); - $scope.time.expiresOn = calcExpiresOn($scope.license.license_info.license_date); - $scope.valid = CheckLicense.valid($scope.license.license_info); - $scope.compliant = $scope.license.license_info.compliant; - $scope.selectedLicense = {}; - $scope.newLicense = { - pendo: true, - insights: true - }; - - $scope.rhCreds = {}; - - if (rhCreds.REDHAT_USERNAME && rhCreds.REDHAT_USERNAME !== "") { - $scope.rhCreds.username = rhCreds.REDHAT_USERNAME; - } - - if (rhCreds.REDHAT_PASSWORD && rhCreds.REDHAT_PASSWORD !== "") { - $scope.rhCreds.password = rhCreds.REDHAT_PASSWORD; - $scope.showPlaceholderPassword = true; - } - }; - - const updateRHCreds = (config) => { - Rest.setUrl(`${GetBasePath('settings')}system/`); - Rest.get() - .then(({data}) => { - initVars(config); - - if (data.REDHAT_USERNAME && data.REDHAT_USERNAME !== "") { - $scope.rhCreds.username = data.REDHAT_USERNAME; - } - - if (data.REDHAT_PASSWORD && data.REDHAT_PASSWORD !== "") { - $scope.rhCreds.password = data.REDHAT_PASSWORD; - $scope.showPlaceholderPassword = true; - } - }).catch(() => { - initVars(config); - }); - }; - - initVars(config); - - $scope.getKey = function(event) { - // Mimic HTML5 spec, show filename - $scope.fileName = event.target.files[0].name; - // Grab the key from the raw license file - const raw = new FileReader(); - // readAsFoo runs async - raw.onload = function() { - try { - $scope.newLicense.file = JSON.parse(raw.result); - } catch(err) { - ProcessErrors($rootScope, null, null, null, - {msg: i18n._('Invalid file format. Please upload valid JSON.')}); - } - }; - - try { - raw.readAsText(event.target.files[0]); - } catch(err) { - ProcessErrors($rootScope, null, null, null, - {msg: i18n._('Invalid file format. Please upload valid JSON.')}); - } - }; - - // HTML5 spec doesn't provide a way to customize file input css - // So we hide the default input, show our own, and simulate clicks to the hidden input - $scope.fakeClick = function() { - if($scope.user_is_superuser && (!$scope.rhCreds.username || $scope.rhCreds.username === '') && (!$scope.rhCreds.password || $scope.rhCreds.password === '')) { - $('#License-file').click(); - } - }; - - $scope.downloadLicense = function() { - $window.open('https://www.ansible.com/license', '_blank'); - }; - - $scope.replacePassword = () => { - if ($scope.user_is_superuser && !$scope.newLicense.file) { - $scope.showPlaceholderPassword = false; - $scope.rhCreds.password = ""; - $timeout(() => { - $('.tooltip').remove(); - $('#rh-password').focus(); - }); - } - }; - - $scope.lookupLicenses = () => { - if ($scope.rhCreds.username && $scope.rhCreds.password) { - Wait('start'); - ConfigService.getSubscriptions($scope.rhCreds.username, $scope.rhCreds.password) - .then(({data}) => { - Wait('stop'); - if (data && data.length > 0) { - $scope.rhLicenses = data; - if ($scope.selectedLicense.fullLicense) { - $scope.selectedLicense.modalKey = $scope.selectedLicense.fullLicense.license_key; - } - $scope.showLicenseModal = true; - } else { - ProcessErrors($scope, data, status, null, { - hdr: i18n._('No Licenses Found'), - msg: i18n._('We were unable to locate licenses associated with this account') - }); - } - }) - .catch(({data, status}) => { - Wait('stop'); - ProcessErrors($scope, data, status, null, { - hdr: i18n._('Error Fetching Licenses') - }); - }); - } - }; - - $scope.confirmLicenseSelection = () => { - $scope.showLicenseModal = false; - $scope.selectedLicense.fullLicense = $scope.rhLicenses.find((license) => { - return license.license_key === $scope.selectedLicense.modalKey; - }); - $scope.selectedLicense.modalKey = undefined; - }; - - $scope.cancelLicenseLookup = () => { - $scope.showLicenseModal = false; - $scope.selectedLicense.modalKey = undefined; - }; - - $scope.submit = function() { - Wait('start'); - let payload = {}; - if ($scope.newLicense.file) { - payload = $scope.newLicense.file; - } else if ($scope.selectedLicense.fullLicense) { - payload = $scope.selectedLicense.fullLicense; - } - - CheckLicense.post(payload, $scope.newLicense.eula) - .then((licenseInfo) => { - reset(); - - ConfigService.delete(); - ConfigService.getConfig(licenseInfo) - .then(function(config) { - - if ($rootScope.licenseMissing === true) { - if ($scope.newLicense.pendo) { - pendoService.updatePendoTrackingState('detailed'); - pendoService.issuePendoIdentity(); - } else { - pendoService.updatePendoTrackingState('off'); - } - - if ($scope.newLicense.insights) { - insightsEnablementService.updateInsightsTrackingState(true); - } else { - insightsEnablementService.updateInsightsTrackingState(false); - } - - $state.go('dashboard', { - licenseMissing: false - }); - } else { - updateRHCreds(config); - $scope.success = true; - $rootScope.licenseMissing = false; - // for animation purposes - const successTimeout = setTimeout(function() { - $scope.success = false; - clearTimeout(successTimeout); - }, 4000); - } - }); - }).catch((err) => { - ProcessErrors($scope, err, null, null, { - hdr: i18n._('Error Applying License') - }); - }); - }; -}]; diff --git a/awx/ui/client/src/license/license.partial.html b/awx/ui/client/src/license/license.partial.html deleted file mode 100644 index 100c73b36b64..000000000000 --- a/awx/ui/client/src/license/license.partial.html +++ /dev/null @@ -1,271 +0,0 @@ -
-
-
-
Details
-
-
-
License
-
- Valid License - Invalid License -
-
-
-
Version
-
- {{license.version}} -
-
-
-
License Type
-
- {{license.license_info.license_type}} -
-
-
-
Subscription
-
- {{license.license_info.subscription_name}} -
-
-
-
License Key
-
- {{license.license_info.license_key}} -
-
-
-
Expires On
-
- {{time.expiresOn}} -
-
-
-
Time Remaining
-
- {{time.remaining}} -
-
-
-
Hosts Available
-
- {{license.license_info.available_instances}} -
-
-
-
Hosts Available
-
Unlimited
-
-
-
Hosts Used
-
- {{license.license_info.current_instances}} -
-
-
-
Hosts Remaining
-
- {{license.license_info.free_instances}} -
-
-
-
If you are ready to upgrade, please contact us by clicking the button below
- -
-
-
-
-
{{title}}
-
-
Welcome to Ansible Tower! Please complete the steps below to acquire a license.
-
-
-
-
- - 1 - - - Please click the button below to visit Ansible's website to get a Tower license key. - -
- - - -
- - 2 - - - Choose your license file, agree to the End User License Agreement, and click submit. - -
-
- * - License -
-
Upload a license file
-
- Browse - {{fileName|translate}} - -
-
-
-
-
-
OR
-
-
-
-
-
- - Provide your Red Hat customer credentials and you can choose from a list of your available licenses. The credentials you use will be stored for future use in retrieving renewal or expanded licenses. You can update or remove them in SETTINGS > SYSTEM. - -
-
- - -
-
- -
- - - - -
-
- -
-
-
- GET LICENSES -
-
-
- Selected -
- {{selectedLicense.fullLicense.subscription_name}} -
-
-
-
-
- * - End User License Agreement -
-
{{ license.eula }}
-
-
- -
-
-
- Tracking and Analytics -
-
- - - By default, Tower collects and transmits analytics data on Tower usage to Red Hat. There are two categories of data collected by Tower. For more information, see this Tower documentation page. Uncheck the following boxes to disable this feature. - -
-
- -
-
- -
-
-
-
-
- Save successful! -
-
- -
-
-
-
-
-
-
- -
diff --git a/awx/ui/client/src/license/license.route.js b/awx/ui/client/src/license/license.route.js deleted file mode 100644 index 4c1251ca6ab5..000000000000 --- a/awx/ui/client/src/license/license.route.js +++ /dev/null @@ -1,65 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import {templateUrl} from '../shared/template-url/template-url.factory'; -import { N_ } from '../i18n'; -import _ from 'lodash'; - -export default { - name: 'license', - route: '/license', - templateUrl: templateUrl('license/license'), - controller: 'licenseController', - data: {}, - ncyBreadcrumb: { - label: N_('LICENSE') - }, - onEnter: ['$state', 'ConfigService', (state, configService) => { - return configService.getConfig() - .then(config => { - if (_.get(config, 'license_info.license_type') === 'open') { - return state.go('setup'); - } - }); - }], - resolve: { - features: ['CheckLicense', '$rootScope', - function(CheckLicense, $rootScope) { - if($rootScope.licenseMissing === undefined){ - return CheckLicense.notify(); - } - } - ], - config: ['ConfigService', 'CheckLicense', '$rootScope', - function(ConfigService, CheckLicense, $rootScope) { - ConfigService.delete(); - return ConfigService.getConfig() - .then(function(config){ - $rootScope.licenseMissing = (CheckLicense.valid(config.license_info) === false) ? true : false; - return config; - }); - } - ], - rhCreds: ['Rest', 'GetBasePath', function(Rest, GetBasePath) { - Rest.setUrl(`${GetBasePath('settings')}system/`); - return Rest.get() - .then(({data}) => { - const rhCreds = {}; - if (data.REDHAT_USERNAME && data.REDHAT_USERNAME !== "") { - rhCreds.REDHAT_USERNAME = data.REDHAT_USERNAME; - } - - if (data.REDHAT_PASSWORD && data.REDHAT_PASSWORD !== "") { - rhCreds.REDHAT_PASSWORD = data.REDHAT_PASSWORD; - } - - return rhCreds; - }).catch(() => { - return {}; - }); - }] - }, -}; diff --git a/awx/ui/client/src/license/main.js b/awx/ui/client/src/license/main.js deleted file mode 100644 index 90c5ff36f307..000000000000 --- a/awx/ui/client/src/license/main.js +++ /dev/null @@ -1,19 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import route from './license.route'; -import controller from './license.controller'; -import CheckLicense from './checkLicense.factory'; -import fileOnChange from './fileOnChange.directive'; - -export default - angular.module('license', []) - .controller('licenseController', controller) - .directive('fileOnChange', fileOnChange) - .factory('CheckLicense', CheckLicense) - .run(['$stateExtender', function($stateExtender) { - $stateExtender.addState(route); - }]); diff --git a/awx/ui/client/src/login/authenticationServices/authentication.service.js b/awx/ui/client/src/login/authenticationServices/authentication.service.js deleted file mode 100644 index e57ffa18f60c..000000000000 --- a/awx/ui/client/src/login/authenticationServices/authentication.service.js +++ /dev/null @@ -1,176 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - - /** - * @ngdoc function - * @name shared.function:AuthService - * @description AuthService.js - * - * User authentication functions - * - */ - -export default - ['$http', '$rootScope', '$cookies', 'GetBasePath', 'Store', '$q', - '$injector', '$location', - function ($http, $rootScope, $cookies, GetBasePath, Store, $q, - $injector, $location) { - return { - setToken: function (token, expires) { - $cookies.remove('token_expires'); - $cookies.remove('userLoggedIn'); - - $cookies.put('token_expires', expires); - $cookies.put('userLoggedIn', true); - $cookies.put('sessionExpired', false); - - $rootScope.userLoggedIn = true; - $rootScope.userLoggedOut = false; - $rootScope.token_expires = expires; - $rootScope.sessionExpired = false; - }, - - isUserLoggedIn: function () { - if ($rootScope.userLoggedIn === undefined) { - // Browser refresh may have occurred - $rootScope.userLoggedIn = ($cookies.get('userLoggedIn') === 'true'); - $rootScope.sessionExpired = ($cookies.get('sessionExpired') === 'true'); - } - return $rootScope.userLoggedIn; - }, - retrieveToken: function (username, password) { - return $http({ - method: 'POST', - url: `/api/login/`, - data: `username=${encodeURIComponent(username)}&password=${encodeURIComponent(password)}&next=%2fapi%2f`, - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - } - }); - }, - deleteToken: function () { - return $http({ - method: 'GET', - url: '/api/logout/' - }); - }, - - logout: function () { - // the following puts our primary scope up for garbage collection, which - // should prevent content flash from the prior user. - - var x, - deferred = $q.defer(), - ConfigService = $injector.get('ConfigService'), - SocketService = $injector.get('SocketService'), - scope = angular.element(document.getElementById('main-view')).scope(); - - this.deleteToken().then(() => { - if(scope){ - scope.$destroy(); - } - - if($cookies.get('lastPath')==='/portal'){ - $cookies.put( 'lastPath', '/portal'); - $rootScope.lastPath = '/portal'; - } - else if ($cookies.get('lastPath') !== '/home' || $cookies.get('lastPath') !== '/' || $cookies.get('lastPath') !== '/login' || $cookies.get('lastPath') !== '/logout'){ - // do nothing - $rootScope.lastPath = $cookies.get('lastPath'); - } - else { - // your last path was home - $cookies.remove('lastPath'); - $rootScope.lastPath = '/home'; - } - x = Store('sessionTime'); - if ($rootScope.current_user && x && x[$rootScope.current_user.id]) { - x[$rootScope.current_user.id].loggedIn = false; - } - Store('sessionTime', x); - - if ($cookies.getObject('current_user')) { - $rootScope.lastUser = $cookies.getObject('current_user').id; - } - ConfigService.delete(); - SocketService.disconnect(); - $cookies.remove('token_expires'); - $cookies.remove('current_user'); - $cookies.put('userLoggedIn', false); - $cookies.put('sessionExpired', false); - $cookies.putObject('current_user', {}); - $rootScope.current_user = {}; - $rootScope.license_tested = undefined; - $rootScope.userLoggedIn = false; - $rootScope.sessionExpired = false; - $rootScope.licenseMissing = true; - $rootScope.token_expires = null; - $rootScope.login_username = null; - $rootScope.login_password = null; - $rootScope.userLoggedOut = true; - $rootScope.pendingApprovalCount = 0; - if ($rootScope.sessionTimer) { - $rootScope.sessionTimer.clearTimers(); - } - deferred.resolve(); - }); - - return deferred.promise; - - }, - - licenseTested: function () { - var license, result; - if ($rootScope.license_tested !== undefined) { - result = $rootScope.license_tested; - } else { - // User may have hit browser refresh - license = Store('license'); - $rootScope.version = license.version; - if (license && license.tested !== undefined) { - result = license.tested; - } else { - result = false; - } - } - return result; - }, - - getUser: function () { - return $http({ - method: 'GET', - url: GetBasePath('me') - }); - }, - - setUserInfo: function (response) { - // store the response values in $rootScope so we can get to them later - $rootScope.current_user = response.results[0]; - if ($location.protocol() === 'https') { - $cookies.putObject('current_user', response.results[0], {secure: true}); //keep in session cookie in the event of browser refresh - } else { - $cookies.putObject('current_user', response.results[0], {secure: false}); - } - }, - - restoreUserInfo: function () { - $rootScope.current_user = $cookies.getObject('current_user'); - }, - - getUserInfo: function (key) { - // Access values returned from the Me API call - var cu; - if ($rootScope.current_user) { - return $rootScope.current_user[key]; - } - this.restoreUserInfo(); - cu = $cookies.getObject('current_user'); - return cu[key]; - } - }; - } -]; diff --git a/awx/ui/client/src/login/authenticationServices/insightsEnablement.service.js b/awx/ui/client/src/login/authenticationServices/insightsEnablement.service.js deleted file mode 100644 index b76d378f2b28..000000000000 --- a/awx/ui/client/src/login/authenticationServices/insightsEnablement.service.js +++ /dev/null @@ -1,27 +0,0 @@ -/************************************************* -* Copyright (c) 2015 Ansible, Inc. -* -* All Rights Reserved -*************************************************/ - - -export default ['$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', - function ($rootScope, Rest, GetBasePath, ProcessErrors) { - return { - updateInsightsTrackingState: function(tracking_type) { - if (tracking_type === true || tracking_type === false) { - Rest.setUrl(`${GetBasePath('settings')}system`); - Rest.patch({ INSIGHTS_TRACKING_STATE: tracking_type }) - .catch(function ({data, status}) { - ProcessErrors($rootScope, data, status, null, { - hdr: 'Error!', - msg: 'Failed to patch INSIGHTS_TRACKING_STATE in settings: ' + - status }); - }); - } else { - throw new Error(`Can't update insights data enabled in settings to - "${tracking_type}"`); - } - } - }; - }]; diff --git a/awx/ui/client/src/login/authenticationServices/isAdmin.factory.js b/awx/ui/client/src/login/authenticationServices/isAdmin.factory.js deleted file mode 100644 index 41b1b29447be..000000000000 --- a/awx/ui/client/src/login/authenticationServices/isAdmin.factory.js +++ /dev/null @@ -1,18 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name - * @description - * - * - */ - - export default - ['$rootScope', function($rootScope) { - return function() { return ($rootScope.current_user && $rootScope.current_user.is_superuser); }; - }]; diff --git a/awx/ui/client/src/login/authenticationServices/main.js b/awx/ui/client/src/login/authenticationServices/main.js deleted file mode 100644 index 373ecdbd07e9..000000000000 --- a/awx/ui/client/src/login/authenticationServices/main.js +++ /dev/null @@ -1,19 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import authenticationService from './authentication.service'; -import isAdmin from './isAdmin.factory'; -import timer from './timer.factory'; -import pendoService from './pendo.service'; -import insightsEnablementService from './insightsEnablement.service'; - -export default - angular.module('authentication', []) - .factory('Authorization', authenticationService) - .factory('IsAdmin', isAdmin) - .factory('Timer', timer) - .service('pendoService', pendoService) - .service('insightsEnablementService', insightsEnablementService); diff --git a/awx/ui/client/src/login/authenticationServices/pendo.service.js b/awx/ui/client/src/login/authenticationServices/pendo.service.js deleted file mode 100644 index a7296f6d8b4e..000000000000 --- a/awx/ui/client/src/login/authenticationServices/pendo.service.js +++ /dev/null @@ -1,148 +0,0 @@ -/************************************************* -* Copyright (c) 2015 Ansible, Inc. -* -* All Rights Reserved -*************************************************/ - - -export default ['$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', '$q', 'ConfigService', '$log', - 'AppStrings', - function ($rootScope, Rest, GetBasePath, ProcessErrors, $q, ConfigService, $log, AppStrings) { - return { - setPendoOptions: function (config) { - const tower_version = config.version.split('-')[0]; - const trial = (config.trial) ? config.trial : false; - let options = { - apiKey: AppStrings.get('PENDO_API_KEY'), - visitor: { - id: null, - role: null, - }, - account: { - id: null, - planLevel: config.license_type, - planPrice: config.instance_count, - creationDate: config.license_date, - trial: trial, - tower_version: tower_version, - ansible_version: config.ansible_version - } - }; - - if (config.analytics_status === 'detailed') { - this.setDetailed(options, config); - } else if (config.analytics_status === 'anonymous') { - this.setAnonymous(options); - } - - return options; - }, - - // Detailed mode sends: - // VisitorId: userid+hash of license_key - // AccountId: hash of license_key from license - setDetailed: function(options, config) { - // config.deployment_id is a hash of the tower license_key - options.visitor.id = $rootScope.current_user.id + '@' + config.deployment_id; - options.account.id = config.deployment_id; - }, - - // Anonymous mode sends: - // VisitorId: - // AccountId: - setAnonymous: function (options) { - options.visitor.id = 0; - options.account.id = "tower.ansible.com"; - }, - - setRole: function(options) { - const deferred = $q.defer(); - - if ($rootScope.current_user.is_superuser === true) { - options.visitor.role = 'admin'; - deferred.resolve(options); - } else { - Rest.setUrl(GetBasePath('users') + $rootScope.current_user.id + - '/admin_of_organizations/'); - Rest.get() - .then(function (response) { - if (response.data.count > 0) { - options.visitor.role = "orgadmin"; - deferred.resolve(options); - } else { - options.visitor.role = "user"; - deferred.resolve(options); - } - }) - .catch(function (response) { - ProcessErrors($rootScope, response.data, response.status, null, { - hdr: 'Error!', - msg: 'Failed to get admin of org user list. GET returned status: ' + - response.status }); - deferred.reject('Could not resolve pendo role.'); - }); - } - - return deferred.promise; - }, - - bootstrap: function(){ - /* jshint ignore:start */ - (function(p,e,n,d,o){var v,w,x,y,z;o=p[d]=p[d]||{};o._q=[]; - v=['initialize','identify','updateOptions','pageLoad'];for(w=0,x=v.length;w - - diff --git a/awx/ui/client/src/login/loginModal/loginModal.block.less b/awx/ui/client/src/login/loginModal/loginModal.block.less deleted file mode 100644 index 574c1d4621bb..000000000000 --- a/awx/ui/client/src/login/loginModal/loginModal.block.less +++ /dev/null @@ -1,156 +0,0 @@ -/** @define LoginModal */ -.LoginModal-backDrop { - width: 100vw; - height: 100vh; - position: fixed; - top: 0; - left: 0; - z-index: 1041; - opacity: 0; - transition: 0.5s opacity; -} - -.LoginModal-backDrop.is-loggedOut { - opacity: 0.2; -} - -.LoginModal-dialog { - margin: 30px auto; - margin-top: 95px; -} - -.LoginModal-content { - max-width: 550px; - margin-left: auto; - margin-right: auto; - border: 0; - box-shadow: none; - background-color: #ddd; - border-radius: 4px; - opacity: 0; - transition: opacity 0.5s; - z-index: 1042; - position: relative; -} - -.LoginModal-content.is-loggedOut { - opacity: 1; - transition: opacity 0.5s; -} - -.LoginModal-header { - text-align: left; - background-color: #bbb; - border-bottom: 0; - border-top-right-radius: 4px; - border-top-left-radius: 4px; -} - -.LoginModal-body { - padding-top: 15px; - padding-bottom: 0px; - padding-left: 20px; - padding-right: 20px; -} - -.LoginModal-logoImage { - max-width: 112px; - margin: 20px 20px 10px 20px; -} - -.LoginModal-logoImage--notCustom { - max-width: @login-max-width; -} - -.LoginModal-alert { - margin-bottom: 20px; - font-size: 16px; - font-weight: normal; - color: @login-alert; - transition: opacity 0.2s; -} - -.LoginModal-alert--error { - color: @login-alert-error; - padding-bottom: 4px; -} - -.LoginModal-alert { - opacity: 1; - display: flex; -} - -.LoginModal-alert.ng-hide { - display: none; - opacity: 0; -} - -.LoginModal-alert.ng-hide-add { - display: none; - opacity: 0; -} - -.LoginModal-alert.ng-hide-remove { - display: block; -} - -.LoginModal-alertIcon { - display: flex; - align-items: center; - line-height: 19px; -} - -.LoginModal-alertIcon:before { - margin-right: 10px; -} - -.LoginModal-formGroup { - margin-bottom: 20px; -} - -.LoginModal-label { - color: @field-label; - font-weight: 400; -} - -.LoginModal-field { - color: @field-input-text; - background-color: @field-bg; - border: 1px solid @field-border; -} - -.LoginModal-footer { - display: flex; - flex-wrap: wrap-reverse; - align-items: center; - padding: 20px; - padding-top: 5px; - padding-bottom: 0px; - margin-top: 20px; -} - -.LoginModal-footerBlock { - flex: 1 0 auto; - margin-bottom: 20px; - display: flex; - max-width: 100%; -} - -.LoginModal-footerBlock--submit { - flex: initial; - margin-left: auto; - padding-left: 20px; -} - -.LoginModal-signInButton { - transition: background-color 0.2s; - background-color: @submit-button-bg; - color: @submit-button-text; - font-size: 0.875rem; -} - -.LoginModal-signInButton:hover, -.LoginModal-signInButton:focus { - color: @submit-button-text; - background-color: @submit-button-bg-hov; -} diff --git a/awx/ui/client/src/login/loginModal/loginModal.controller.js b/awx/ui/client/src/login/loginModal/loginModal.controller.js deleted file mode 100644 index da8f2bb1f6e2..000000000000 --- a/awx/ui/client/src/login/loginModal/loginModal.controller.js +++ /dev/null @@ -1,188 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name controllers.function:Authentication - * @description - * Controller for handling /#/login and /#/logout routes. - * - * (app.js) verifies the user is authenticated and that the user session is not expired. If either condition is not true, - * the user is redirected to /#/login and the Authentication controller. - * - * Methods for checking the session state are found in [js/shared/AuthService.js](/static/docs/api/shared.function:AuthService), which is referenced here as Authorization. - * - * #Login Modal Dialog - * - * The modal dialog prompting for username and password is found in templates/ui/index.html. - *``` - * - * - *``` - * HTML for the login form is generated, compiled and injected into
by the controller. This is done to associate the form with the controller's scope. Because - *
is outside of the ng-view container, it gets associated with $rootScope by default. In the controller we create a new scope using $rootScope.$new() and associate - * that with the login form. Doing this each time the controller is instantiated insures the form is clean and not pre-populated with a prior user's username and password. - * - * Just before the release of 2.0 a bug was discovered where clicking logout and then immediately clicking login without providing a username and password would successfully log - * the user back into the app. Implementing the above approach fixed this, forcing a new username/password to be entered each time the login dialog appears. - * - * - * @Usage - * This is usage information. - */ - -export default ['$log', '$cookies', '$rootScope', 'ProcessErrors', - '$location', 'Authorization', 'Alert', 'Wait', 'Timer', - 'Empty', '$scope', 'pendoService', 'ConfigService', - 'CheckLicense', 'SocketService', 'Rest', 'GetBasePath', 'i18n', - function ($log, $cookies, $rootScope, ProcessErrors, - $location, Authorization, Alert, Wait, Timer, - Empty, scope, pendoService, ConfigService, - CheckLicense, SocketService, Rest, GetBasePath, i18n) { - var lastPath, lastUser, sessionExpired, loginAgain, preAuthUrl; - - loginAgain = function() { - setTimeout(function() { - $location.path('/logout'); - }, 1000); - }; - - scope.sessionExpired = (Empty($rootScope.sessionExpired)) ? $cookies.get('sessionExpired') : $rootScope.sessionExpired; - scope.login_username = ''; - scope.login_password = ''; - - - lastPath = function () { - return (Empty($rootScope.lastPath)) ? $cookies.get('lastPath') : $rootScope.lastPath; - }; - - lastUser = function(){ - if(!Empty($rootScope.lastUser) && $rootScope.lastUser === $rootScope.current_user.id){ - return true; - } - else { - return false; - } - }; - - preAuthUrl = $rootScope.preAuthUrl; - - $log.debug('User session expired: ' + sessionExpired); - $log.debug('Last URL: ' + lastPath()); - - $rootScope.loginConfig.promise.then(function () { - if ($AnsibleConfig.custom_logo) { - scope.customLogo = $rootScope.custom_logo; - scope.customLogoPresent = true; - } else { - scope.customLogo = "logo-login.svg"; - scope.customLogoPresent = false; - } - scope.customLoginInfo = $AnsibleConfig.custom_login_info; - scope.customLoginInfoPresent = (scope.customLoginInfo) ? true : false; - }); - - if (scope.removeAuthorizationGetLicense) { - scope.removeAuthorizationGetLicense(); - } - scope.removeAuthorizationGetLicense = scope.$on('AuthorizationGetLicense', function() { - ConfigService.getConfig().then(function(){ - CheckLicense.test(); - pendoService.issuePendoIdentity(); - Wait("stop"); - if(!Empty(preAuthUrl)){ - $location.path(preAuthUrl); - delete $rootScope.preAuthUrl; - } - else { - if (lastPath() && lastUser()) { - // Go back to most recent navigation path - $location.path(lastPath()); - } else { - $location.url('/home'); - } - } - }) - .catch(function () { - Wait('stop'); - Alert('Error', 'Failed to access license information. GET returned status: ' + status, 'alert-danger', loginAgain); - }); - }); - - if (scope.removeAuthorizationGetUser) { - scope.removeAuthorizationGetUser(); - } - scope.removeAuthorizationGetUser = scope.$on('AuthorizationGetUser', function() { - // Get all the profile/access info regarding the logged in user - Authorization.getUser() - .then(({data}) => { - Authorization.setUserInfo(data); - Timer.init().then(function(timer){ - $rootScope.sessionTimer = timer; - SocketService.init(); - $rootScope.user_is_superuser = data.results[0].is_superuser; - $rootScope.user_is_system_auditor = data.results[0].is_system_auditor; - scope.$emit('AuthorizationGetLicense'); - }); - - Rest.setUrl(`${GetBasePath('workflow_approvals')}?status=pending&page_size=1`); - Rest.get() - .then(({data}) => { - $rootScope.pendingApprovalCount = data.count; - }) - .catch(({data, status}) => { - ProcessErrors({}, data, status, null, { - hdr: i18n._('Error!'), - msg: i18n._('Failed to get workflow jobs pending approval. GET returned status: ') + status - }); - }); - }) - .catch(({data, status}) => { - Authorization.logout().then( () => { - Wait('stop'); - Alert('Error', 'Failed to access user information. GET returned status: ' + status, 'alert-danger', loginAgain); - }); - }); - }); - - // Call the API to get an auth token - scope.systemLogin = function (username, password) { - $('.api-error').empty(); - if (Empty(username) || Empty(password)) { - scope.reset(); - scope.attemptFailed = true; - $('#login-username').focus(); - } else { - Wait('start'); - Authorization.retrieveToken(username, password) - .then(function (data) { - $('#login-modal').modal('hide'); - Authorization.setToken(data.data.expires); - scope.$emit('AuthorizationGetUser'); - }, - function (data) { - var key; - Wait('stop'); - if (data && data.data && data.data.non_field_errors && data.data.non_field_errors.length === 0) { - // show field specific errors returned by the API - for (key in data.data) { - scope[key + 'Error'] = data.data[key][0]; - } - } else { - scope.reset(); - scope.attemptFailed = true; - $('#login-username').focus(); - } - }); - } - return false; - }; -}]; diff --git a/awx/ui/client/src/login/loginModal/loginModal.directive.js b/awx/ui/client/src/login/loginModal/loginModal.directive.js deleted file mode 100644 index f9b3f8866b73..000000000000 --- a/awx/ui/client/src/login/loginModal/loginModal.directive.js +++ /dev/null @@ -1,58 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ -import authenticationController from './loginModal.controller'; - -/* jshint unused: vars */ -export default - [ 'templateUrl', - 'Wait', - function(templateUrl, Wait) { - return { - restrict: 'E', - scope: true, - controller: authenticationController, - templateUrl: templateUrl('login/loginModal/loginModal'), - link: function(scope, element, attrs) { - var setLoginFocus = function () { - // Need to clear out any open dialog windows that might be open when this modal opens. - $('#login-username').focus(); - }; - - setLoginFocus(); - - // Hide any lingering modal dialogs - $('.modal[aria-hidden=false]').each(function () { - if ($(this).attr('id') !== 'login-modal') { - $(this).modal('hide'); - } - }); - - // Just in case, make sure the wait widget is not active - // and scroll the window to the top - Wait('stop'); - window.scrollTo(0,0); - - // Set focus to username field - $('#login-modal').on('shown.bs.modal', function () { - setLoginFocus(); - }); - - $('#login-password').bind('keypress', function (e) { - var code = (e.keyCode ? e.keyCode : e.which); - if (code === 13) { - $('#login-button').click(); - } - }); - - scope.reset = function () { - $('#login-form input').each(function () { - $(this).val(''); - }); - }; - } - }; - } - ]; diff --git a/awx/ui/client/src/login/loginModal/loginModal.partial.html b/awx/ui/client/src/login/loginModal/loginModal.partial.html deleted file mode 100644 index 7f409512a35e..000000000000 --- a/awx/ui/client/src/login/loginModal/loginModal.partial.html +++ /dev/null @@ -1,120 +0,0 @@ -
- -
-
-
-
-
- - -
-
-
- Welcome to Ansible {{BRAND_NAME}}!  Please sign in. -
-
- -
- Your session timed out due to inactivity. Please sign in. -
-
-
- -
- You have been logged out. Please sign in. -
-
-
- -
- Maximum per-user sessions reached. Please sign in. -
-
-
- -
- Invalid username and/or password. Please try again. -
-
-
- -
- {{ thirdPartyAttemptFailed }} -
-
-
-
- -
- -
- Please enter a username. -
-
-
-
-
- -
- -
- Please enter a password. -
-
-
-
-
-
NOTICE
{{ customLoginInfo | sanitize }}
-
- -
-
-
-
\ No newline at end of file diff --git a/awx/ui/client/src/login/loginModal/loginModalNotice.block.less b/awx/ui/client/src/login/loginModal/loginModalNotice.block.less deleted file mode 100644 index 57cb035e8c29..000000000000 --- a/awx/ui/client/src/login/loginModal/loginModalNotice.block.less +++ /dev/null @@ -1,23 +0,0 @@ -/** @define LoginModalNotice */ -.LoginModalNotice { - font-size: 12px; - width: 100%; - max-height: 129px; - padding: 15px; - padding-top: 10px; - margin-bottom: 15px; - border-radius: 4px; - border: 1px solid @login-notice-border; - background-color: @login-notice-bg; - color: @login-notice-text; - overflow-y: scroll; - overflow-x: visible; - white-space: pre-wrap; -} - -.LoginModalNotice-title { - color: @login-notice-title; - font-weight: bold; - padding-bottom: 4px; - font-size: 14px; -} diff --git a/awx/ui/client/src/login/loginModal/main.js b/awx/ui/client/src/login/loginModal/main.js deleted file mode 100644 index 3d063d4e1c80..000000000000 --- a/awx/ui/client/src/login/loginModal/main.js +++ /dev/null @@ -1,13 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import thirdPartySignOn from './thirdPartySignOn/main'; - -import loginModalDirective from './loginModal.directive'; - -export default - angular.module('loginModal', [thirdPartySignOn.name]) - .directive('loginModal', loginModalDirective); diff --git a/awx/ui/client/src/login/loginModal/thirdPartySignOn/main.js b/awx/ui/client/src/login/loginModal/thirdPartySignOn/main.js deleted file mode 100644 index e6f5f08b64c7..000000000000 --- a/awx/ui/client/src/login/loginModal/thirdPartySignOn/main.js +++ /dev/null @@ -1,13 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import thirdPartySignOnDirective from './thirdPartySignOn.directive'; -import thirdPartySignOnService from './thirdPartySignOn.service'; - -export default - angular.module('thirdPartySignOn', []) - .directive('thirdPartySignOn', thirdPartySignOnDirective) - .factory('thirdPartySignOnService', thirdPartySignOnService); diff --git a/awx/ui/client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.block.less b/awx/ui/client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.block.less deleted file mode 100644 index d61006bf505d..000000000000 --- a/awx/ui/client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.block.less +++ /dev/null @@ -1,51 +0,0 @@ -/** @define ThirdPartySignOn */ - -.ThirdPartySignOn { - display: flex; - align-items: center; -} - -.ThirdPartySignOn-label { - flex: initial; - color: @third-party-label; -} - -.ThirdPartySignOn-item { - margin-left: 15px; -} - -.ThirdPartySignOn-button { - transition: color 0.2s; - flex: 1 0 auto; - height: 30px; - width: 30px; - border-radius: 50%; - color: @third-party-btn-text; - background-color: @third-party-btn-bg; - border: 0; - padding: 0; -} - -.ThirdPartySignOn-button--error { - color: @third-party-error; -} - -.ThirdPartySignOn-button:hover { - color: @third-party-btn-hov; -} - -.ThirdPartySignOn-button--error:hover { - color: @third-party-error-hov; -} - -.ThirdPartySignOn-icon { - font-size: 35px; -} - -.ThirdPartySignOn-icon--gitHub { - margin-top: -3px; -} - -.ThirdPartySignOn-icon--fontCustom { - font-size: 30px; -} diff --git a/awx/ui/client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.controller.js b/awx/ui/client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.controller.js deleted file mode 100644 index 8222eb8d4438..000000000000 --- a/awx/ui/client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.controller.js +++ /dev/null @@ -1,36 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name controllers.function:thirdPartySignOn - * @description - * Controller for handling third party supported login options. - */ - -export default ['$window', '$scope', 'thirdPartySignOnService', - function ($window, $scope, thirdPartySignOnService) { - - thirdPartySignOnService( - {scope: $scope, url: "api/v2/auth/"}).then(function (data) { - if (data && data.options && data.options.length > 0) { - $scope.thirdPartyLoginSupported = true; - $scope.loginItems = data.options; - } else { - $scope.thirdPartyLoginSupported = false; - } - - if (data && data.error) { - $scope.$parent.thirdPartyAttemptFailed = data.error; - } - }); - - $scope.goTo = function(link) { - // this is used because $location only lets you navigate inside - // the "/#/" path, and these are API urls. - $window.location.href = link; - }; - }]; diff --git a/awx/ui/client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.directive.js b/awx/ui/client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.directive.js deleted file mode 100644 index 91c6a085c241..000000000000 --- a/awx/ui/client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.directive.js +++ /dev/null @@ -1,23 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/* jshint unused: vars */ - -import thirdPartySignOnController from './thirdPartySignOn.controller'; - -export default - [ 'templateUrl', - function(templateUrl) { - return { - restrict: 'E', - scope: true, - controller: thirdPartySignOnController, - templateUrl: templateUrl('login/loginModal/thirdPartySignOn/thirdPartySignOn'), - link: function(scope, element, attrs) { - } - }; - } - ]; diff --git a/awx/ui/client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.partial.html b/awx/ui/client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.partial.html deleted file mode 100644 index 9413193570ff..000000000000 --- a/awx/ui/client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.partial.html +++ /dev/null @@ -1,13 +0,0 @@ -
-
- SIGN IN WITH -
-
- -
-
diff --git a/awx/ui/client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js b/awx/ui/client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js deleted file mode 100644 index afc2c97a1770..000000000000 --- a/awx/ui/client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js +++ /dev/null @@ -1,121 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name helpers.function:Permissions - * @description - * Gets the configured auth types based on the configurations set in the server - * - */ - - export default - ['$http', '$log', 'i18n', function($http, $log, i18n) { - return function (params) { - var url = params.url; - - return $http({ - method: 'GET', - url: url, - }).then(function (data) { - var options = [], - error = ""; - - function parseAzure(option) { - var newOption = {}; - - newOption.type = "azure"; - newOption.icon = "ThirdPartySignOn-icon--fontCustom icon-microsoft"; - newOption.link = option.login_url; - newOption.tooltip = i18n.sprintf(i18n._("Sign in with %s"), "Azure AD"); - - return newOption; - } - - function parseGoogle(option) { - var newOption = {}; - - newOption.type = "google"; - newOption.icon = "ThirdPartySignOn-icon--fontCustom icon-google"; - newOption.link = option.login_url; - newOption.tooltip = i18n.sprintf(i18n._("Sign in with %s"), "Google"); - - return newOption; - } - - function parseGithub(option, key) { - var newOption = {}; - - newOption.type = "github"; - newOption.icon = "fa-github ThirdPartySignOn-icon--gitHub"; - newOption.link = option.login_url; - newOption.tooltip = i18n.sprintf(i18n._("Sign in with %s"), "GitHub"); - - // if this is a GitHub team or org, add that to - // the tooltip - if (key.split("-")[1]){ - if (key.split("-")[1] === "team") { - newOption.tooltip = i18n.sprintf(i18n._("Sign in with %s Teams"), "GitHub"); - } else if (key.split("-")[1] === "org") { - newOption.tooltip = i18n.sprintf(i18n._("Sign in with %s Organizations"), "GitHub"); - } - } - - return newOption; - } - - function parseSaml(option, key) { - var newOption = {}; - - newOption.type = "saml"; - newOption.icon = "ThirdPartySignOn-icon--fontCustom icon-saml-02"; - newOption.link = option.login_url; - newOption.tooltip = i18n.sprintf(i18n._("Sign in with %s"), "SAML"); - - // add the idp of the saml type to the tooltip - if (key.split(":")[1]){ - newOption.tooltip += " (" + key.split(":")[1] + ")"; - } - - return newOption; - } - - function parseLoginOption(option, key) { - var finalOption; - - // set up the particular tooltip, icon, etc. - // needed by the login type - if (key.split("-")[0] === "azuread") { - finalOption = parseAzure(option, key); - } else if (key.split("-")[0] === "google") { - finalOption = parseGoogle(option, key); - } else if (key.split("-")[0] === "github") { - finalOption = parseGithub(option, key); - } else if (key.split(":")[0] === "saml") { - finalOption = parseSaml(option, key); - } - - // set the button to error red and set the error message to be passed to the login modal. - if (option.error) { - finalOption.button = "ThirdPartySignOn-button--error"; - error = option.error; - } - - options.push(finalOption); - } - - // iterate over each login option passed from the API - _.forEach(data.data, parseLoginOption); - - // return the options and the error to be utilized - // by the loginModal - return {"options": options, "error": error}; - }) - .catch(function (data) { - $log.error('Failed to get third-party login types. Returned status: ' + data.status ); - }); - }; - }]; diff --git a/awx/ui/client/src/login/logout.route.js b/awx/ui/client/src/login/logout.route.js deleted file mode 100644 index 47da767ec50d..000000000000 --- a/awx/ui/client/src/login/logout.route.js +++ /dev/null @@ -1,22 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -// import {templateUrl} from '../../shared/template-url/template-url.factory'; - -export default { - name: 'signOut', - route: '/logout', - controller: ['Authorization', '$state', function(Authorization, $state) { - Authorization.logout().then( () =>{ - $state.go('signIn'); - }); - - }], - ncyBreadcrumb: { - skip: true - }, - templateUrl: '/static/partials/blank.html' -}; diff --git a/awx/ui/client/src/login/main.js b/awx/ui/client/src/login/main.js deleted file mode 100644 index 4835feebd3d6..000000000000 --- a/awx/ui/client/src/login/main.js +++ /dev/null @@ -1,18 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import authentication from './authenticationServices/main'; -import loginModal from './loginModal/main'; - -import loginRoute from './login.route'; -import logoutRoute from './logout.route'; - -export default - angular.module('login', [authentication.name, loginModal.name]) - .run(['$stateExtender', function($stateExtender) { - $stateExtender.addState(loginRoute); - $stateExtender.addState(logoutRoute); - }]); diff --git a/awx/ui/client/src/management-jobs/card/card.controller.js b/awx/ui/client/src/management-jobs/card/card.controller.js deleted file mode 100644 index d266900ea598..000000000000 --- a/awx/ui/client/src/management-jobs/card/card.controller.js +++ /dev/null @@ -1,168 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - -export default - [ 'Wait', 'CreateDialog', 'GetBasePath' , - 'Rest' , - 'ProcessErrors', '$rootScope', '$state', - '$scope', 'CreateSelect2', 'i18n', '$transitions', - function( Wait, CreateDialog, GetBasePath, - Rest, ProcessErrors, - $rootScope, $state, $scope, - CreateSelect2, i18n, $transitions) { - - var defaultUrl = GetBasePath('system_job_templates') + "?order_by=name"; - - var getManagementJobs = function(){ - Rest.setUrl(defaultUrl); - Rest.get() - .then(({data}) => { - $scope.mgmtCards = data.results; - Wait('stop'); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, null, {hdr: i18n._('Error!'), - msg: i18n.sprintf(i18n._('Call to %s failed. Return status: %d'), (defaultUrl === undefined) ? "undefined" : defaultUrl, status )}); - }); - }; - getManagementJobs(); - - // This handles the case where the user refreshes the management job notifications page. - if($state.current.name === 'managementJobsList.notifications') { - $scope.activeCard = parseInt($state.params.management_id); - $scope.cardAction = "notifications"; - } - - $scope.goToNotifications = function(card){ - $state.transitionTo('managementJobsList.notifications',{ - card: card, - management_id: card.id - }); - }; - - var launchManagementJob = function (defaultUrl){ - var data = {}; - Rest.setUrl(defaultUrl); - Rest.post(data) - .then(({data}) => { - Wait('stop'); - $state.go('output', { id: data.system_job, type: 'system' }, { reload: true }); - }) - .catch(({data, status}) => { - let template_id = $scope.job_template_id; - template_id = (template_id === undefined) ? "undefined" : i18n.sprintf("%d", template_id); - ProcessErrors($scope, data, status, null, { hdr: i18n._('Error!'), - msg: i18n.sprintf(i18n._('Failed updating job %s with variables. POST returned: %d'), template_id, status) }); - }); - }; - - $scope.submitJob = function (id, name) { - Wait('start'); - defaultUrl = GetBasePath('system_job_templates')+id+'/launch/'; - var noModalJobs = ['Cleanup Expired Sessions', 'Cleanup Expired OAuth 2 Tokens']; - if (noModalJobs.includes(name)) { - launchManagementJob(defaultUrl, name); - } else { - - CreateDialog({ - id: 'prompt-for-days', - title: name, - scope: $scope, - width: 500, - height: 300, - minWidth: 200, - callback: 'PromptForDays', - resizable: false, - onOpen: function(){ - $scope.$watch('prompt_for_days_form.$invalid', function(invalid) { - if (invalid === true) { - $('#prompt-for-days-launch').prop("disabled", true); - } else { - $('#prompt-for-days-launch').prop("disabled", false); - } - }); - - let fieldScope = $scope.$parent; - fieldScope.days_to_keep = 30; - $scope.prompt_for_days_form.$setPristine(); - $scope.prompt_for_days_form.$invalid = false; - }, - buttons: [ - { - "label": "Cancel", - "onClick": function() { - $(this).dialog('close'); - - }, - "class": "btn btn-default", - "id": "prompt-for-days-cancel" - }, - { - "label": "Launch", - "onClick": function() { - const extra_vars = {"days": $scope.days_to_keep }, - data = {}; - data.extra_vars = JSON.stringify(extra_vars); - - Rest.setUrl(defaultUrl); - Rest.post(data) - .then(({data}) => { - Wait('stop'); - $("#prompt-for-days").dialog("close"); - // $("#configure-dialog").dialog('close'); - $state.go('output', { id: data.system_job, type: 'system' }, { reload: true }); - }) - .catch(({data, status}) => { - let template_id = $scope.job_template_id; - template_id = (template_id === undefined) ? "undefined" : i18n.sprintf("%d", template_id); - ProcessErrors($scope, data, status, null, { hdr: i18n._('Error!'), - msg: i18n.sprintf(i18n._('Failed updating job %s with variables. POST returned: %d'), template_id, status) }); - }); - }, - "class": "btn btn-primary", - "id": "prompt-for-days-launch" - } - ] - }); - } - - if ($scope.removePromptForDays) { - $scope.removePromptForDays(); - } - $scope.removePromptForDays = $scope.$on('PromptForDays', function() { - // $('#configure-dialog').dialog('close'); - $('#prompt-for-days').show(); - $('#prompt-for-days').dialog('open'); - Wait('stop'); - }); - }; - - $scope.configureSchedule = function(id) { - $state.transitionTo('managementJobsList.schedule', { - id: id - }); - }; - - var cleanUpStateChangeListener = $transitions.onSuccess({}, function(trans) { - if(trans.to().name === "managementJobsList") { - // We are on the management job list view - nothing needs to be highlighted - delete $scope.activeCard; - delete $scope.cardAction; - } - else if(trans.to().name === "managementJobsList.notifications") { - // We are on the notifications view - update the active card and the action - $scope.activeCard = parseInt(trans.params('to').management_id); - $scope.cardAction = "notifications"; - } - }); - - // Remove the listener when the scope is destroyed to avoid a memory leak - $scope.$on('$destroy', function() { - cleanUpStateChangeListener(); - }); - } - ]; diff --git a/awx/ui/client/src/management-jobs/card/card.partial.html b/awx/ui/client/src/management-jobs/card/card.partial.html deleted file mode 100644 index fb16248b4b49..000000000000 --- a/awx/ui/client/src/management-jobs/card/card.partial.html +++ /dev/null @@ -1,45 +0,0 @@ -
- -
-
-
-
- MANAGEMENT JOBS -
- - {{ mgmtCards.length }} - -
-
-
- -
-

{{ card.name }}

-
- - - -
-
- - -

{{card.description || "Place organization description here"}}

- -
-
-
diff --git a/awx/ui/client/src/management-jobs/card/card.route.js b/awx/ui/client/src/management-jobs/card/card.route.js deleted file mode 100644 index c5292813f157..000000000000 --- a/awx/ui/client/src/management-jobs/card/card.route.js +++ /dev/null @@ -1,21 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import {templateUrl} from '../../shared/template-url/template-url.factory'; -import {N_} from "../../i18n"; - -export default { - name: 'managementJobsList', - route: '/management_jobs', - templateUrl: templateUrl('management-jobs/card/card'), - controller: 'managementJobsCardController', - data: { - activityStream: false, - }, - ncyBreadcrumb: { - label: N_('MANAGEMENT JOBS') - }, -}; diff --git a/awx/ui/client/src/management-jobs/card/main.js b/awx/ui/client/src/management-jobs/card/main.js deleted file mode 100644 index e21f7f7e7303..000000000000 --- a/awx/ui/client/src/management-jobs/card/main.js +++ /dev/null @@ -1,15 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import route from './card.route'; -import controller from './card.controller'; - -export default - angular.module('managementJobsCard', []) - .controller('managementJobsCardController', controller) - .run(['$stateExtender', function($stateExtender) { - $stateExtender.addState(route); - }]); diff --git a/awx/ui/client/src/management-jobs/card/mgmtcards.block.less b/awx/ui/client/src/management-jobs/card/mgmtcards.block.less deleted file mode 100644 index c79a2dc42a7e..000000000000 --- a/awx/ui/client/src/management-jobs/card/mgmtcards.block.less +++ /dev/null @@ -1,116 +0,0 @@ -.MgmtCards { - display: flex; - flex-flow: row wrap; -} - -.MgmtCards-card { - background-color: @default-bg; - padding: 20px; - border-radius: 5px; - border: 1px solid @b7grey; - align-items: baseline; - margin-top: 20px; - margin-right: 20px; - width: ~"calc(33.3% - 14px)"; -} - -.MgmtCards-card:nth-child(3n) { - margin-right: 0px; -} - -.MgmtCards-card--selected { - padding-left: 16px; - border-left: 5px solid @default-link; -} - -.MgmtCards-card--promptElements{ - padding-top: 10px; -} - -.MgmtCards-promptText{ - color:@default-interface-txt; -} - -.MgmtCards-header { - display: flex; - flex-wrap: nowrap; - align-items: baseline; - width: 100%; -} - -.MgmtCards-label { - margin-top: 0px; - font-size: 14px; - font-weight: bold; - color: @default-interface-txt; - margin-bottom: 25px; - overflow: hidden; - text-overflow: ellipsis; -} - -.MgmtCards-actionItems { - margin-left: auto; - display: flex; - flex-wrap: nowrap; -} - -.MgmtCards-actionItem { - margin-left: 15px; -} - -.MgmtCards-description { - margin-bottom: 20px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.MgmtCards-links { - width: 100%; - display: flex; - flex-wrap: wrap; - justify-content: space-between; -} - -.MgmtCards-link { - flex: initial; - width: ~"calc(50% - 20px)"; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - margin-bottom: 20px; -} - -.MgmtCards-linkBadge { - margin-right: 10px; -} - -@media (max-width: 840px) { - .MgmtCards-card { - width: 100%; - margin-right: 0px; - } -} - -@media (min-width: 840px) and (max-width: 1240px) { - .MgmtCards-card { - width: ~"calc(50% - 10px)"; - } - - .MgmtCards-card:nth-child(2n) { - margin-right: 0px; - } - - .MgmtCards-card:nth-child(3n) { - margin-right: 20px; - } - } - -#prompt-for-days-facts, #prompt-for-days { - overflow-x: hidden; - font-family: "Open Sans"; - .label-text { - text-transform: uppercase; - font-weight: normal; - } -} diff --git a/awx/ui/client/src/management-jobs/main.js b/awx/ui/client/src/management-jobs/main.js deleted file mode 100644 index ef291e1ee5d9..000000000000 --- a/awx/ui/client/src/management-jobs/main.js +++ /dev/null @@ -1,16 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import managementJobsCard from './card/main'; -import managementJobsScheduler from './scheduler/main'; -import managementJobsNotifications from './notifications/main'; - -export default - angular.module('managementJobs', [ - managementJobsCard.name, - managementJobsScheduler.name, - managementJobsNotifications.name - ]); diff --git a/awx/ui/client/src/management-jobs/notifications/main.js b/awx/ui/client/src/management-jobs/notifications/main.js deleted file mode 100644 index 91b92b53731e..000000000000 --- a/awx/ui/client/src/management-jobs/notifications/main.js +++ /dev/null @@ -1,15 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import route from './notification.route'; -import controller from './notification.controller'; - -export default - angular.module('managementJobsNotifications', []) - .controller('managementJobsNotificationsController', controller) - .run(['$stateExtender', function($stateExtender) { - $stateExtender.addState(route); - }]); diff --git a/awx/ui/client/src/management-jobs/notifications/notification.controller.js b/awx/ui/client/src/management-jobs/notifications/notification.controller.js deleted file mode 100644 index 62e8dff8a3fd..000000000000 --- a/awx/ui/client/src/management-jobs/notifications/notification.controller.js +++ /dev/null @@ -1,54 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default - [ 'NotificationsList', 'GetBasePath', 'ToggleNotification', 'NotificationsListInit', - '$stateParams', 'Dataset', '$scope', - function( - NotificationsList, GetBasePath, ToggleNotification, NotificationsListInit, - $stateParams, Dataset, $scope) { - var defaultUrl = GetBasePath('system_job_templates'), - list = NotificationsList, - id = $stateParams.management_id; - - function init() { - $scope.list = list; - $scope[`${list.iterator}_dataset`] = Dataset.data; - $scope[list.name] = $scope[`${list.iterator}_dataset`].results; - - NotificationsListInit({ - scope: $scope, - url: defaultUrl, - id: id - }); - - $scope.$watch(`${list.iterator}_dataset`, function() { - // The list data has changed and we need to update which notifications are on/off - $scope.$emit('relatednotifications'); - }); - } - - $scope.toggleNotification = function(event, notifier_id, column) { - var notifier = this.notification; - try { - $(event.target).tooltip('hide'); - } - catch(e) { - // ignore - } - ToggleNotification({ - scope: $scope, - url: defaultUrl + id, - notifier: notifier, - column: column, - callback: 'NotificationRefresh' - }); - }; - - init(); - - } - ]; diff --git a/awx/ui/client/src/management-jobs/notifications/notification.route.js b/awx/ui/client/src/management-jobs/notifications/notification.route.js deleted file mode 100644 index a6b1f30d8a77..000000000000 --- a/awx/ui/client/src/management-jobs/notifications/notification.route.js +++ /dev/null @@ -1,48 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - import { N_ } from '../../i18n'; - -export default { - name: 'managementJobsList.notifications', - route: '/:management_id/notifications', - params: { - notification_search: {} - }, - searchPrefix: 'notification', - views: { - '@managementJobsList': { - controller: 'managementJobsNotificationsController', - templateProvider: function(NotificationsList, generateList, ParentObject, $filter) { - // include name of parent resource in listTitle - NotificationsList.listTitle = `${$filter('sanitize')(ParentObject.name)}
` + N_('Notifications'); - let html = generateList.build({ - list: NotificationsList, - mode: 'edit' - }); - html = generateList.wrapPanel(html); - return generateList.insertFormView() + html; - } - } - }, - resolve: { - Dataset: ['NotificationsList', 'QuerySet', '$stateParams', 'GetBasePath', - function(list, qs, $stateParams, GetBasePath) { - let path = `${GetBasePath('notification_templates')}`; - return qs.search(path, $stateParams[`${list.iterator}_search`]); - } - ], - ParentObject: ['$stateParams', 'Rest', 'GetBasePath', function($stateParams, Rest, GetBasePath) { - let path = `${GetBasePath('system_job_templates')}${$stateParams.management_id}`; - Rest.setUrl(path); - return Rest.get(path).then((res) => res.data); - }] - }, - ncyBreadcrumb: { - parent: 'managementJobsList', - label: N_('NOTIFICATIONS') - } -}; diff --git a/awx/ui/client/src/management-jobs/notifications/notifications.partial.html b/awx/ui/client/src/management-jobs/notifications/notifications.partial.html deleted file mode 100644 index edde1a7f3062..000000000000 --- a/awx/ui/client/src/management-jobs/notifications/notifications.partial.html +++ /dev/null @@ -1,4 +0,0 @@ -
-
-
-
diff --git a/awx/ui/client/src/management-jobs/scheduler/main.js b/awx/ui/client/src/management-jobs/scheduler/main.js deleted file mode 100644 index 0fff48415fdf..000000000000 --- a/awx/ui/client/src/management-jobs/scheduler/main.js +++ /dev/null @@ -1,107 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - -import { templateUrl } from '../../shared/template-url/template-url.factory'; -import controller from '../../scheduler/schedulerList.controller'; -import addController from '../../scheduler/schedulerAdd.controller'; -import editController from '../../scheduler/schedulerEdit.controller'; -import { N_ } from '../../i18n'; -import editScheduleResolve from '../../scheduler/editSchedule.resolve'; - - -export default -angular.module('managementJobScheduler', []) - .controller('managementJobController', controller) - .controller('managementJobAddController', addController) - .controller('managementJobEditController', editController) - .run(['$stateExtender', function($stateExtender) { - $stateExtender.addState({ - searchPrefix: 'schedule', - name: 'managementJobsList.schedule', - route: '/management_jobs/:id/schedules', - ncyBreadcrumb: { - parent: 'managementJobsList', - label: N_('SCHEDULES') - }, - views: { - '@managementJobsList': { - templateProvider: function(ScheduleList, generateList, ParentObject, $filter) { - // include name of parent resource in listTitle - ScheduleList.listTitle = `${$filter('sanitize')(ParentObject.name)}
` + N_('SCHEDULES'); - let html = generateList.build({ - list: ScheduleList, - mode: 'edit' - }); - html = generateList.wrapPanel(html); - return generateList.insertFormView() + html; - }, - controller: 'managementJobController', - } - }, - resolve: { - Dataset: ['ScheduleList', 'QuerySet', '$stateParams', 'GetBasePath', - function(list, qs, $stateParams, GetBasePath) { - let path = `${GetBasePath('system_job_templates')}${$stateParams.id}/schedules`; - return qs.search(path, $stateParams[`${list.iterator}_search`]); - } - ], - ParentObject: ['$stateParams', 'Rest', 'GetBasePath', function($stateParams, Rest, GetBasePath) { - let path = `${GetBasePath('system_job_templates')}${$stateParams.id}`; - Rest.setUrl(path); - return Rest.get(path).then((res) => res.data); - }], - UnifiedJobsOptions: ['Rest', 'GetBasePath', '$stateParams', '$q', - function(Rest, GetBasePath, $stateParams, $q) { - Rest.setUrl(GetBasePath('unified_jobs')); - var val = $q.defer(); - Rest.options() - .then(function(data) { - val.resolve(data.data); - }, function(data) { - val.reject(data); - }); - return val.promise; - }], - ScheduleList: ['SchedulesList', 'GetBasePath', '$stateParams', - (SchedulesList, GetBasePath, $stateParams) => { - let list = _.cloneDeep(SchedulesList); - list.basePath = GetBasePath('system_job_templates') + $stateParams.id + '/schedules'; - return list; - } - ] - } - }); - $stateExtender.addState({ - name: 'managementJobsList.schedule.add', - route: '/add', - ncyBreadcrumb: { - parent: 'managementJobsList.schedule', - label: N_('CREATE SCHEDULED JOB') - }, - views: { - 'form': { - templateUrl: templateUrl('management-jobs/scheduler/schedulerForm'), - controller: 'managementJobAddController', - } - } - }); - $stateExtender.addState({ - name: 'managementJobsList.schedule.edit', - route: '/edit/:schedule_id', - ncyBreadcrumb: { - parent: 'managementJobsList.schedule', - label: N_('EDIT SCHEDULED JOB') - }, - views: { - 'form': { - templateUrl: templateUrl('management-jobs/scheduler/schedulerForm'), - controller: 'managementJobEditController' - } - }, - resolve: editScheduleResolve() - }); - }]); diff --git a/awx/ui/client/src/management-jobs/scheduler/schedulerForm.partial.html b/awx/ui/client/src/management-jobs/scheduler/schedulerForm.partial.html deleted file mode 100644 index 58dfbff4c9f1..000000000000 --- a/awx/ui/client/src/management-jobs/scheduler/schedulerForm.partial.html +++ /dev/null @@ -1,639 +0,0 @@ -
-
-
{{ schedulerName || "ADD SCHEDULE"}}
-
{{ schedulerName || "EDIT SCHEDULE"}}
-
-
- -
-
-
-
- -
- - -
- A schedule name is required. -
-
-
- - - -
-
-
-
- -
- - - : - - - - : - - - -
-
- The time must be in HH24:MM:SS format. -
-
-
- - -
-
- - -
-
-
-
- - -
A value is required.
-
This is not a valid number.
-
Please input a number greater than 1.
-
-
- Frequency Details -
-
-
- - - -
- Please provide a value between 1 and 999. -
-
-
-
- -
- -
- The day must be between 1 and 31. -
-
-
-
- -
-
- - -
-
-
-
- * - -
-
- - -
-
- The day must be between 1 and 31. -
-
-
-
- -
-
- - - -
-
-
- -
-
- - - - - - - -
-
-
- Please select one or more days. -
-
-
- -
- -
-
-
- - -
- Please provide a value between 1 and 999. -
-
-
- - - -
- Please provide a valid date. -
-
-
-
-
-
-
-

- The scheduler options are invalid or incomplete. -

-
-
- -
- {{ rrule_nlp_description }} -
-
- -
- - - -
-
-
    -
  • - {{ occurrence.utc }} -
  • -
-
    -
  • - {{ occurrence.local }} -
  • -
-
- -
- - - -
-
-
\ No newline at end of file diff --git a/awx/ui/client/src/ng-console.js b/awx/ui/client/src/ng-console.js deleted file mode 100644 index 98c186ffeca8..000000000000 --- a/awx/ui/client/src/ng-console.js +++ /dev/null @@ -1,52 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -// THIS FILE ONLY INCLUDED IN DEBUG BUILDS -// -// To use: -// -// Open a console in Chrome DevTools and load this -// script with: -// -// require('tower/ng-console'); -// -// Then go to the Elements tab and drill down to any -// element within the angular app. Go back to the console -// and you can access the scope for the selected element -// using the variable $scope. -// -var ngAppElem = angular.element(document.querySelector('[ng-app]') || document); - -window.injector = ngAppElem.injector(); -window.inject = window.injector.invoke; -window.$rootScope = ngAppElem.scope(); - -// getService('auth') will create a variable `auth` assigned to the service `auth`. -// -window.getService = function getService(serviceName) { - window.inject([serviceName, function (s) {window[serviceName] = s;}]); -}; - -Object.defineProperty(window, '$scope', { - get: function () { - var elem = angular.element(window.__commandLineAPI.$0); - return elem.isolateScope() || elem.scope(); - }, -}); - -/** - * USAGE - * - * First copy the script and paste it in Chrome DevTools in Sources -> left pane -> Snippets. - * Then, after loading an Angular page, right click on the snippet and choose "run". - * Afterwards, you have the following available in the console: - * - * 1) $rootScope - * 2) inject(function ($q, $compile) { ...use $q and $compile here... }); - * 3) click on an element in DevTools; now $scope in the console points at the element scope (isolate if one exists). - * - * Enjoy! - */ diff --git a/awx/ui/client/src/notifications/add/add.controller.js b/awx/ui/client/src/notifications/add/add.controller.js deleted file mode 100644 index 0e453481b017..000000000000 --- a/awx/ui/client/src/notifications/add/add.controller.js +++ /dev/null @@ -1,289 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['Rest', 'Wait', 'NotificationsFormObject', - 'ProcessErrors', 'GetBasePath', 'Alert', - 'GenerateForm', '$scope', '$state', 'CreateSelect2', 'GetChoices', - 'NotificationsTypeChange', 'ParseTypeChange', 'i18n', 'MessageUtils', '$filter', - function( - Rest, Wait, NotificationsFormObject, - ProcessErrors, GetBasePath, Alert, - GenerateForm, $scope, $state, CreateSelect2, GetChoices, - NotificationsTypeChange, ParseTypeChange, i18n, - MessageUtils, $filter - ) { - - var generator = GenerateForm, - form = NotificationsFormObject, - url = GetBasePath('notification_templates'), - defaultMessages = {}; - - init(); - - function init() { - $scope.customize_messages = false; - Rest.setUrl(GetBasePath('notification_templates')); - Rest.options() - .then(({data}) => { - if (!data.actions.POST) { - $state.go("^"); - Alert('Permission Error', 'You do not have permission to add a notification template.', 'alert-info'); - } - defaultMessages = data.actions.GET.messages; - MessageUtils.setMessagesOnScope($scope, null, defaultMessages); - }); - // apply form definition's default field values - GenerateForm.applyDefaults(form, $scope); - - GetChoices({ - scope: $scope, - url: url, - field: 'notification_type', - variable: 'notification_type_options', - callback: 'choicesReady' - }); - } - - if ($state.params && $state.params.organization_id) { - let id = $state.params.organization_id, - url = GetBasePath('organizations') + id + '/'; - - Rest.setUrl(url); - Rest.get() - .then(({data}) => { - $scope.organization_name = data.name; - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, form, { - hdr: 'Error!', - msg: `Failed to retrieve organization. GET status: ${status}` - }); - }); - } - - if ($scope.removeChoicesReady) { - $scope.removeChoicesReady(); - } - $scope.removeChoicesReady = $scope.$on('choicesReady', function() { - var i; - for (i = 0; i < $scope.notification_type_options.length; i++) { - if ($scope.notification_type_options[i].value === '') { - $scope.notification_type_options[i].value = "manual"; - break; - } - } - CreateSelect2({ - element: '#notification_template_notification_type', - multiple: false - }); - - $scope.hipchatColors = [ - {'id': 'gray', 'name': i18n._('Gray')}, - {'id': 'green', 'name': i18n._('Green')}, - {'id': 'purple', 'name': i18n._('Purple')}, - {'id': 'red', 'name': i18n._('Red')}, - {'id': 'yellow', 'name': i18n._('Yellow')}, - {'id': 'random', 'name': i18n._('Random')} - ]; - CreateSelect2({ - element: '#notification_template_color', - multiple: false - }); - - $scope.httpMethodChoices = [ - {'id': 'POST', 'name': i18n._('POST')}, - {'id': 'PUT', 'name': i18n._('PUT')}, - ]; - CreateSelect2({ - element: '#notification_template_http_method', - multiple: false, - }); - }); - - $scope.$watch('headers', function validate_headers(str) { - try { - const headers = typeof str === 'string' ? JSON.parse(str) : str; - if (_.isObject(headers) && !_.isArray(headers)) { - let valid = true; - for (let k in headers) { - if (_.isObject(headers[k])) { - valid = false; - } - if (headers[k] === null) { - valid = false; - } - } - $scope.notification_template_form.headers.$setValidity('json', valid); - return; - } - } catch (err) {} - - $scope.notification_template_form.headers.$setValidity('json', false); - }); - - $scope.typeChange = function() { - for (var fld in form.fields) { - if (form.fields[fld] && form.fields[fld].subForm) { - if (form.fields[fld].type === 'checkbox_group' && form.fields[fld].fields) { - // Need to loop across the groups fields to null them out - for (var i = 0; i < form.fields[fld].fields.length; i++) { - // Pull the name out of the object (array of objects) - var subFldName = form.fields[fld].fields[i].name; - $scope[subFldName] = null; - $scope.notification_template_form[subFldName].$setPristine(); - } - } else { - $scope.notification_template_form[fld].$setPristine(); - } - } - } - - NotificationsTypeChange.getDetailFields($scope.notification_type.value).forEach(function(field) { - $scope[field[0]] = field[1]; - }); - - - $scope.parse_type = 'json'; - if (!$scope.headers) { - $scope.headers = "{\n}"; - } - ParseTypeChange({ - scope: $scope, - parse_variable: 'parse_type', - variable: 'headers', - field_id: 'notification_template_headers' - }); - }; - - $scope.$watch('customize_messages', (value) => { - if (value) { - $scope.$broadcast('reset-code-mirror', { - customize_messages: $scope.customize_messages, - }); - } - }); - $scope.toggleForm = function(key) { - $scope[key] = !$scope[key]; - }; - $scope.$watch('notification_type', (newValue, oldValue = {}) => { - if (newValue) { - MessageUtils.updateDefaultsOnScope( - $scope, - defaultMessages[oldValue.value], - defaultMessages[newValue.value] - ); - $scope.$broadcast('reset-code-mirror', { - customize_messages: $scope.customize_messages, - }); - } - }); - - $scope.emailOptionsChange = function () { - if ($scope.email_options === 'use_ssl') { - if ($scope.use_ssl) { - $scope.email_options = null; - $scope.use_ssl = false; - return; - } - - $scope.use_ssl = true; - $scope.use_tls = false; - } - else if ($scope.email_options === 'use_tls') { - if ($scope.use_tls) { - $scope.email_options = null; - $scope.use_tls = false; - return; - } - - $scope.use_ssl = false; - $scope.use_tls = true; - } - }; - - // Save - $scope.formSave = function() { - var params, - v = $scope.notification_type.value; - - generator.clearApiErrors($scope); - params = { - "name": $scope.name, - "description": $scope.description, - "organization": $scope.organization, - "messages": MessageUtils.getMessagesObj($scope, defaultMessages), - "notification_type": v, - "notification_configuration": {} - }; - - function processValue(value, i, field) { - if (field.type === 'textarea') { - if (field.name === 'headers') { - if (typeof $scope[i] === 'string') { - $scope[i] = JSON.parse($scope[i]); - } - } - else if (field.name === 'annotation_tags' && $scope.notification_type.value === "grafana" && value === null) { - $scope[i] = null; - } - else { - $scope[i] = $scope[i] ? $scope[i].toString().split('\n') : ""; - } - } - if (field.type === 'checkbox') { - $scope[i] = Boolean($scope[i]); - } - if (field.type === 'number') { - $scope[i] = Number($scope[i]); - } - if (i === "username" && $scope.notification_type.value === "email" && (value === null || !value - )) { - $scope[i] = ""; - } - if (field.type === 'sensitive' && (value === null || !value - )) { - $scope[i] = ""; - } - return $scope[i]; - } - - params.notification_configuration = _.fromPairs(Object.keys(form.fields) - .filter(i => (form.fields[i].ngShow && form.fields[i].ngShow.indexOf(v) > -1)) - .map(i => [i, processValue($scope[i], i, form.fields[i])])); - - delete params.notification_configuration.email_options; - - for(var j = 0; j < form.fields.email_options.options.length; j++) { - if(form.fields.email_options.options[j].ngShow && form.fields.email_options.options[j].ngShow.indexOf(v) > -1) { - params.notification_configuration[form.fields.email_options.options[j].value] = Boolean($scope[form.fields.email_options.options[j].value]); - } - } - - Wait('start'); - Rest.setUrl(url); - Rest.post(params) - .then(() => { - $state.go('notifications', {}, { reload: true }); - Wait('stop'); - }) - .catch(({ data, status }) => { - let description = 'POST returned status: ' + status; - if (data && data.messages && data.messages.length > 0) { - description = _.uniq(data.messages).join(', '); - } - ProcessErrors($scope, data, status, form, { - hdr: 'Error!', - msg: $filter('sanitize')('Failed to add new notifier. ' + description + '.') - }); - }); - }; - - $scope.formCancel = function() { - $state.go('notifications'); - }; - - } -]; diff --git a/awx/ui/client/src/notifications/add/main.js b/awx/ui/client/src/notifications/add/main.js deleted file mode 100644 index 104849060bb1..000000000000 --- a/awx/ui/client/src/notifications/add/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import controller from './add.controller'; - -export default - angular.module('notificationsAdd', []) - .controller('notificationsAddController', controller); diff --git a/awx/ui/client/src/notifications/edit/edit.controller.js b/awx/ui/client/src/notifications/edit/edit.controller.js deleted file mode 100644 index 68748b0698c0..000000000000 --- a/awx/ui/client/src/notifications/edit/edit.controller.js +++ /dev/null @@ -1,378 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['Rest', 'Wait', - 'NotificationsFormObject', 'ProcessErrors', 'GetBasePath', - 'GenerateForm', - 'notification_template', - '$scope', '$state', 'GetChoices', 'CreateSelect2', 'Empty', - 'NotificationsTypeChange', 'ParseTypeChange', 'i18n', - 'MessageUtils', '$filter', - function( - Rest, Wait, - NotificationsFormObject, ProcessErrors, GetBasePath, - GenerateForm, - notification_template, - $scope, $state, GetChoices, CreateSelect2, Empty, - NotificationsTypeChange, ParseTypeChange, i18n, - MessageUtils, $filter - ) { - var generator = GenerateForm, - id = notification_template.id, - form = NotificationsFormObject, - master = {}, - url = GetBasePath('notification_templates'), - defaultMessages = {}; - - init(); - - function init() { - $scope.notification_template = notification_template; - - $scope.$watch('notification_template.summary_fields.user_capabilities.edit', function(val) { - if (val === false) { - $scope.canAdd = false; - } - }); - - Rest.setUrl(GetBasePath('notification_templates')); - Rest.options() - .then(({data}) => { - defaultMessages = data.actions.GET.messages; - }); - - GetChoices({ - scope: $scope, - url: url, - field: 'notification_type', - variable: 'notification_type_options', - callback: 'choicesReady' - }); - } - - if ($scope.removeChoicesReady) { - $scope.removeChoicesReady(); - } - $scope.removeChoicesReady = $scope.$on('choicesReady', function() { - var i; - for (i = 0; i < $scope.notification_type_options.length; i++) { - if ($scope.notification_type_options[i].value === '') { - $scope.notification_type_options[i].value = "manual"; - break; - } - } - - Wait('start'); - Rest.setUrl(url + id + '/'); - Rest.get() - .then(({data}) => { - var fld; - for (fld in form.fields) { - if (data[fld]) { - $scope[fld] = data[fld]; - master[fld] = data[fld]; - } - - if(form.fields[fld].type === 'radio_group') { - if(data.notification_configuration.use_ssl === true){ - $scope.email_options = "use_ssl"; - master.email_options = "use_ssl"; - $scope.use_ssl = true; - master.use_ssl = true; - $scope.use_tls = false; - master.use_tls = false; - } - if(data.notification_configuration.use_tls === true){ - $scope.email_options = "use_tls"; - master.email_options = "use_tls"; - $scope.use_ssl = false; - master.use_ssl = false; - $scope.use_tls = true; - master.use_tls = true; - } - } - else { - if (data.notification_configuration.timeout === null || - !data.notification_configuration.timeout){ - $scope.timeout = 30; - } - if (data.notification_configuration[fld]) { - $scope[fld] = data.notification_configuration[fld]; - master[fld] = data.notification_configuration[fld]; - - if (form.fields[fld].type === 'textarea') { - if (form.fields[fld].name === 'headers') { - $scope[fld] = JSON.stringify($scope[fld], null, 2); - } else { - $scope[fld] = $scope[fld].join('\n'); - } - } - } - - if (form.fields[fld].sourceModel && data.summary_fields && - data.summary_fields[form.fields[fld].sourceModel]) { - $scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] = - data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField]; - master[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] = - data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField]; - } - } - } - data.notification_type = (Empty(data.notification_type)) ? '' : data.notification_type; - for (var i = 0; i < $scope.notification_type_options.length; i++) { - if ($scope.notification_type_options[i].value === data.notification_type) { - $scope.notification_type = $scope.notification_type_options[i]; - break; - } - } - - master.notification_type = $scope.notification_type; - CreateSelect2({ - element: '#notification_template_notification_type', - multiple: false - }); - - $scope.hipchatColors = [ - {'id': 'gray', 'name': i18n._('Gray')}, - {'id': 'green', 'name': i18n._('Green')}, - {'id': 'purple', 'name': i18n._('Purple')}, - {'id': 'red', 'name': i18n._('Red')}, - {'id': 'yellow', 'name': i18n._('Yellow')}, - {'id': 'random', 'name': i18n._('Random')} - ]; - CreateSelect2({ - element: '#notification_template_color', - multiple: false - }); - - $scope.httpMethodChoices = [ - {'id': 'POST', 'name': i18n._('POST')}, - {'id': 'PUT', 'name': i18n._('PUT')}, - ]; - CreateSelect2({ - element: '#notification_template_http_method', - multiple: false, - }); - - NotificationsTypeChange.getDetailFields($scope.notification_type.value).forEach(function(field) { - $scope[field[0]] = field[1]; - }); - $scope.notification_obj = data; - - $scope.parse_type = 'json'; - if (!$scope.headers) { - $scope.headers = "{\n}"; - } - - ParseTypeChange({ - scope: $scope, - parse_variable: 'parse_type', - variable: 'headers', - field_id: 'notification_template_headers', - readOnly: !$scope.notification_template.summary_fields.user_capabilities.edit - }); - - MessageUtils.setMessagesOnScope($scope, data.messages, defaultMessages); - - Wait('stop'); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, form, { - hdr: 'Error!', - msg: 'Failed to retrieve notification: ' + id + '. GET status: ' + status - }); - }); - }); - - $scope.$watch('headers', function validate_headers(str) { - try { - const headers = typeof str === 'string' ? JSON.parse(str) : str; - if (_.isObject(headers) && !_.isArray(headers)) { - let valid = true; - for (let k in headers) { - if (_.isObject(headers[k])) { - valid = false; - } - if (headers[k] === null) { - valid = false; - } - } - $scope.notification_template_form.headers.$setValidity('json', valid); - return; - } - } catch (err) {} - - $scope.notification_template_form.headers.$setValidity('json', false); - }); - - $scope.typeChange = function() { - for (var fld in form.fields) { - if (form.fields[fld] && form.fields[fld].subForm) { - if (form.fields[fld].type === 'checkbox_group' && form.fields[fld].fields) { - // Need to loop across the groups fields to null them out - for (var i = 0; i < form.fields[fld].fields.length; i++) { - // Pull the name out of the object (array of objects) - var subFldName = form.fields[fld].fields[i].name; - $scope[subFldName] = null; - $scope.notification_template_form[subFldName].$setPristine(); - } - } - if ($scope.timeout === null || !$scope.timeout) { - $scope.timeout = 30; - } - else { - $scope[fld] = null; - $scope.notification_template_form[fld].$setPristine(); - } - } - } - - NotificationsTypeChange.getDetailFields($scope.notification_type.value).forEach(function(field) { - $scope[field[0]] = field[1]; - }); - - $scope.parse_type = 'json'; - if (!$scope.headers) { - $scope.headers = "{\n}"; - } - ParseTypeChange({ - scope: $scope, - parse_variable: 'parse_type', - variable: 'headers', - field_id: 'notification_template_headers', - readOnly: !$scope.notification_template.summary_fields.user_capabilities.edit - }); - }; - - $scope.$watch('customize_messages', (value) => { - if (value) { - $scope.$broadcast('reset-code-mirror', { - customize_messages: $scope.customize_messages, - }); - } - }); - $scope.toggleForm = function(key) { - $scope[key] = !$scope[key]; - }; - $scope.$watch('notification_type', (newValue, oldValue = {}) => { - if (newValue) { - MessageUtils.updateDefaultsOnScope( - $scope, - defaultMessages[oldValue.value], - defaultMessages[newValue.value] - ); - $scope.$broadcast('reset-code-mirror', { - customize_messages: $scope.customize_messages, - }); - } - }); - - $scope.emailOptionsChange = function () { - if ($scope.email_options === 'use_ssl') { - if ($scope.use_ssl) { - $scope.email_options = null; - $scope.use_ssl = false; - return; - } - - $scope.use_ssl = true; - $scope.use_tls = false; - } - else if ($scope.email_options === 'use_tls') { - if ($scope.use_tls) { - $scope.email_options = null; - $scope.use_tls = false; - return; - } - - $scope.use_ssl = false; - $scope.use_tls = true; - } - }; - - $scope.formSave = function() { - var params, - v = $scope.notification_type.value; - - generator.clearApiErrors($scope); - params = { - "name": $scope.name, - "description": $scope.description, - "organization": $scope.organization, - "messages": MessageUtils.getMessagesObj($scope, defaultMessages), - "notification_type": v, - "notification_configuration": {} - }; - - function processValue(value, i, field) { - if (field.type === 'textarea') { - if (field.name === 'headers') { - if (typeof $scope[i] === 'string') { - $scope[i] = JSON.parse($scope[i]); - } - } - else if (field.name === 'annotation_tags' && $scope.notification_type.value === "grafana" && value === null) { - $scope[i] = null; - } - else { - $scope[i] = $scope[i].toString().split('\n'); - } - } - if (field.type === 'checkbox') { - $scope[i] = Boolean($scope[i]); - } - if (field.type === 'number') { - $scope[i] = Number($scope[i]); - } - const isUsernameIncluded = ( - $scope.notification_type.value === 'email' || - $scope.notification_type.value === 'webhook' - ); - if (i === "username" && isUsernameIncluded && - (value === null || value === undefined)) { - $scope[i] = ""; - } - if (field.type === 'sensitive' && (value === null || value === undefined)) { - $scope[i] = ""; - } - return $scope[i]; - } - - params.notification_configuration = _.fromPairs(Object.keys(form.fields) - .filter(i => (form.fields[i].ngShow && form.fields[i].ngShow.indexOf(v) > -1)) - .map(i => [i, processValue($scope[i], i, form.fields[i])])); - - delete params.notification_configuration.email_options; - - params.notification_configuration.use_ssl = Boolean($scope.use_ssl); - params.notification_configuration.use_tls = Boolean($scope.use_tls); - - Wait('start'); - Rest.setUrl(url + id + '/'); - Rest.put(params) - .then(() => { - $state.go('notifications', {}, { reload: true }); - Wait('stop'); - }) - .catch(({ data, status }) => { - let description = 'PUT returned status: ' + status; - if (data && data.messages && data.messages.length > 0) { - description = _.uniq(data.messages).join(', '); - } - ProcessErrors($scope, data, status, form, { - hdr: 'Error!', - msg: $filter('sanitize')('Failed to update notifier. ' + description + '.') - }); - }); - }; - - - $scope.formCancel = function() { - $state.go('notifications'); - }; - - } -]; diff --git a/awx/ui/client/src/notifications/edit/main.js b/awx/ui/client/src/notifications/edit/main.js deleted file mode 100644 index 55865ede89a6..000000000000 --- a/awx/ui/client/src/notifications/edit/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import controller from './edit.controller'; - -export default - angular.module('notificationsEdit', []) - .controller('notificationsEditController', controller); diff --git a/awx/ui/client/src/notifications/main.js b/awx/ui/client/src/notifications/main.js deleted file mode 100644 index 9eb6ad0e7059..000000000000 --- a/awx/ui/client/src/notifications/main.js +++ /dev/null @@ -1,100 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - -import notificationTemplatesList from './notification-templates-list/main'; -import notificationsAdd from './add/main'; -import notificationsEdit from './edit/main'; - -import list from './notificationTemplates.list'; -import form from './notificationTemplates.form'; -import notificationsList from './notifications.list'; -import toggleNotification from './shared/toggle-notification.factory'; -import notificationsListInit from './shared/notification-list-init.factory'; -import typeChange from './shared/type-change.service'; -import messageUtils from './shared/message-utils.service'; -import { N_ } from '../i18n'; - -export default -angular.module('notifications', [ - notificationTemplatesList.name, - notificationsAdd.name, - notificationsEdit.name - ]) - .factory('NotificationTemplatesList', list) - .factory('NotificationsFormObject', form) - .factory('NotificationsList', notificationsList) - .factory('ToggleNotification', toggleNotification) - .factory('NotificationsListInit', notificationsListInit) - .service('NotificationsTypeChange', typeChange) - .service('MessageUtils', messageUtils) - .config(['$stateProvider', 'stateDefinitionsProvider', - function($stateProvider, stateDefinitionsProvider) { - let stateDefinitions = stateDefinitionsProvider.$get(); - - // lazily generate a tree of substates which will replace this node in ui-router's stateRegistry - // see: stateDefinition.factory for usage documentation - $stateProvider.state({ - name: 'notifications.**', - url: '/notification_templates', - ncyBreadcrumb: { - label: N_("NOTIFICATIONS") - }, - lazyLoad: () => stateDefinitions.generateTree({ - parent: 'notifications', // top-most node in the generated tree - modes: ['add', 'edit'], // form nodes to generate - list: 'NotificationTemplatesList', - form: 'NotificationsFormObject', - controllers: { - list: 'notificationTemplatesListController', - add: 'notificationsAddController', - edit: 'notificationsEditController' - }, - urls: { - add: '/add?organization_id' - }, - resolve: { - edit: { - notification_template: ['$state', '$stateParams', '$q', - 'Rest', 'GetBasePath', 'ProcessErrors', - function($state, $stateParams, $q, rest, getBasePath, ProcessErrors) { - if ($stateParams.notification_template) { - return $q.when($stateParams.notification_template); - } - - var notificationTemplateId = $stateParams.notification_template_id; - - var url = getBasePath('notification_templates') + notificationTemplateId + '/'; - rest.setUrl(url); - return rest.get() - .then(function(res) { - if (_.get(res, ['data', 'notification_type'] === 'webhook') && - _.get(res, ['data', 'notification_configuration', 'http_method'])) { - res.data.notification_configuration.http_method = res.data.notification_configuration.http_method.toUpperCase(); - } - return res.data; - }).catch(function(response) { - ProcessErrors(null, response.data, response.status, null, { - hdr: 'Error!', - msg: 'Failed to get inventory script info. GET returned status: ' + - response.status - }); - }); - } - ] - } - }, - data: { - activityStream: true, - activityStreamTarget: 'notification_template' - }, - ncyBreadcrumb: { - label: N_('NOTIFICATIONS') - } - }) - }); - } - ]); diff --git a/awx/ui/client/src/notifications/notification-templates-list/add-notifications-action.partial.html b/awx/ui/client/src/notifications/notification-templates-list/add-notifications-action.partial.html deleted file mode 100644 index e6c2484fe110..000000000000 --- a/awx/ui/client/src/notifications/notification-templates-list/add-notifications-action.partial.html +++ /dev/null @@ -1,4 +0,0 @@ -
-
GO TO NOTIFICATIONS TO
-
ADD A NEW TEMPLATE
-
diff --git a/awx/ui/client/src/notifications/notification-templates-list/list.controller.js b/awx/ui/client/src/notifications/notification-templates-list/list.controller.js deleted file mode 100644 index 8cf2a635d8f2..000000000000 --- a/awx/ui/client/src/notifications/notification-templates-list/list.controller.js +++ /dev/null @@ -1,239 +0,0 @@ - /************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - export default ['$scope', 'Wait', 'NotificationTemplatesList', - 'GetBasePath', 'Rest', 'ProcessErrors', 'Prompt', '$state', - 'ngToast', '$filter', 'Dataset', 'rbacUiControlService', - 'i18n', 'NotificationTemplate', 'AppStrings', - function( - $scope, Wait, NotificationTemplatesList, - GetBasePath, Rest, ProcessErrors, Prompt, $state, - ngToast, $filter, Dataset, rbacUiControlService, - i18n, NotificationTemplate, AppStrings) { - - var defaultUrl = GetBasePath('notification_templates'), - list = NotificationTemplatesList; - - init(); - - function init() { - $scope.canAdd = false; - - rbacUiControlService.canAdd("notification_templates") - .then(function(params) { - $scope.canAdd = params.canAdd; - }); - - // search init - $scope.list = list; - $scope[`${list.iterator}_dataset`] = Dataset.data; - $scope[list.name] = $scope[`${list.iterator}_dataset`].results; - } - - $scope.$on(`notification_template_options`, function(event, data){ - $scope.options = data.data.actions.GET; - optionsRequestDataProcessing(); - }); - - $scope.$watchCollection("notification_templates", function() { - optionsRequestDataProcessing(); - } - ); - // iterate over the list and add fields like type label, after the - // OPTIONS request returns, or the list is sorted/paginated/searched. - function optionsRequestDataProcessing(){ - $scope[list.name].forEach(function(item, item_idx) { - var itm = $scope[list.name][item_idx]; - // Set the item type label - if (list.fields.notification_type && $scope.options && - $scope.options.hasOwnProperty('notification_type')) { - $scope.options.notification_type.choices.forEach(function(choice) { - if (choice[0] === item.notification_type) { - itm.type_label = choice[1]; - var recent_notifications = itm.summary_fields.recent_notifications; - itm.status = recent_notifications && recent_notifications.length > 0 ? recent_notifications[0].status : "none"; - } - }); - } - setStatus(itm); - }); - } - - function setStatus(notification_template) { - var html, recent_notifications = notification_template.summary_fields.recent_notifications; - if (recent_notifications.length > 0) { - html = "\n"; - html += "\n"; - html += ""; - html += ""; - html += ""; - html += "\n"; - html += "\n"; - html += "\n"; - - recent_notifications.forEach(function(row) { - html += "\n"; - html += ``; - html += "\n"; - html += "\n"; - }); - html += "\n"; - html += "
" + i18n._("Status") + "" + i18n._("Time") + "
" + ($filter('longDate')(row.created)).replace(/ /, '
') + "
\n"; - } else { - html = "

" + i18n._("No recent notifications.") + "

\n"; - } - notification_template.template_status_html = html; - } - - $scope.copyNotification = notificationTemplate => { - Wait('start'); - new NotificationTemplate('get', notificationTemplate.id) - .then(model => model.copy()) - .then((copiedNotification) => { - ngToast.success({ - content: ` -
-
- -
-
- ${AppStrings.get('SUCCESSFUL_CREATION', copiedNotification.name)} -
-
`, - dismissButton: false, - dismissOnTimeout: true - }); - $state.go('.', null, { reload: true }); - }) - .catch(({ data, status }) => { - const params = { hdr: 'Error!', msg: `Call to copy failed. Return status: ${status}` }; - ProcessErrors($scope, data, status, null, params); - }) - .finally(() => Wait('stop')); - }; - - $scope.testNotification = function() { - var name = $filter('sanitize')(this.notification_template.name), - pending_retries = 25; - - Rest.setUrl(defaultUrl + this.notification_template.id + '/test/'); - Rest.post({}) - .then(function(data) { - if (data && data.data && data.data.notification) { - Wait('start'); - // Using a setTimeout here to wait for the - // notification to be processed and for a status - // to be returned from the API. - retrieveStatus(data.data.notification); - } else { - ProcessErrors($scope, data, status, null, { - hdr: 'Error!', - msg: 'Call to notification templates failed. Notification returned status: ' + status - }); - } - }) - .catch(function() { - ngToast.danger({ - content: ` ${name}: ` + i18n._('Notification Failed.'), - }); - }); - - function retrieveStatus(id) { - setTimeout(function() { - let url = GetBasePath('notifications') + id; - Rest.setUrl(url); - Rest.get() - .then(function(res) { - if (res && res.data && res.data.status && res.data.status === "successful") { - ngToast.success({ - content: ` ${name}: Notification sent.` - }); - $state.reload(); - } else if (res && res.data && res.data.status && res.data.status === "failed" && res.data.error === "timed out") { - ngToast.danger({ - content: `
${name}: ${i18n._("Notification timed out.")}
` - }); - $state.reload(); - } else if (res && res.data && res.data.status && res.data.status === "failed") { - ngToast.danger({ - content: `
${name}: Notification failed.
${$filter('sanitize')(res.data.error)}
` - }); - $state.reload(); - } else if (res && res.data && res.data.status && res.data.status === "pending" && pending_retries > 0) { - pending_retries--; - retrieveStatus(id); - } else { - Wait('stop'); - ProcessErrors($scope, null, status, null, { - hdr: 'Error!', - msg: 'Call to test notifications failed.' - }); - } - - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, null, { - hdr: 'Error!', - msg: 'Failed to get ' + url + '. GET status: ' + status - }); - }); - }, 5000); - } - }; - - - $scope.addNotification = function() { - $state.go('notifications.add'); - }; - - $scope.editNotification = function() { - $state.go('notifications.edit', { - notification_template_id: this.notification_template.id, - notification_template: this.notification_templates - }); - }; - - $scope.deleteNotification = function(id, name) { - var action = function() { - $('#prompt-modal').modal('hide'); - Wait('start'); - var url = defaultUrl + id + '/'; - Rest.setUrl(url); - Rest.destroy() - .then(() => { - - let reloadListStateParams = null; - - if($scope.notification_templates.length === 1 && $state.params.notification_template_search && _.has($state, 'params.notification_template_search.page') && $state.params.notification_template_search.page !== '1') { - reloadListStateParams = _.cloneDeep($state.params); - reloadListStateParams.notification_template_search.page = (parseInt(reloadListStateParams.notification_template_search.page)-1).toString(); - } - - if (parseInt($state.params.notification_template_id) === id) { - $state.go("^", reloadListStateParams, { reload: true }); - } else { - $state.go('.', reloadListStateParams, {reload: true}); - } - Wait('stop'); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, null, { - hdr: 'Error!', - msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status - }); - }); - }; - - Prompt({ - hdr: i18n._('Delete'), - resourceName: $filter('sanitize')(name), - body: '
' + i18n._('Are you sure you want to delete this notification template?') + '
', - action: action, - actionText: i18n._('DELETE') - }); - }; - } - ]; diff --git a/awx/ui/client/src/notifications/notification-templates-list/main.js b/awx/ui/client/src/notifications/notification-templates-list/main.js deleted file mode 100644 index 47adeea176c9..000000000000 --- a/awx/ui/client/src/notifications/notification-templates-list/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import controller from './list.controller'; - -export default - angular.module('notificationTemplatesList', []) - .controller('notificationTemplatesListController', controller); diff --git a/awx/ui/client/src/notifications/notificationTemplates.form.js b/awx/ui/client/src/notifications/notificationTemplates.form.js deleted file mode 100644 index 29f1764a726e..000000000000 --- a/awx/ui/client/src/notifications/notificationTemplates.form.js +++ /dev/null @@ -1,784 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name forms.function:CustomInventory - * @description This form is for adding/editing an organization -*/ - -export default ['i18n', function(i18n) { - return { - - addTitle: i18n._('NEW NOTIFICATION TEMPLATE'), - editTitle: '{{ name }}', - name: 'notification_template', - // I18N for "CREATE NOTIFICATION_TEMPLATE" - // on /#/notification_templates/add - breadcrumbName: i18n._('NOTIFICATION TEMPLATE'), - stateTree: 'notifications', - basePath: 'notification_templates', - showActions: true, - subFormTitles: { - typeSubForm: i18n._('Type Details'), - }, - - - fields: { - name: { - label: i18n._('Name'), - type: 'text', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)', - required: true, - capitalize: false - }, - description: { - label: i18n._('Description'), - type: 'text', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - organization: { - label: i18n._('Organization'), - type: 'lookup', - list: 'OrganizationList', - basePath: 'organizations', - sourceModel: 'organization', - sourceField: 'name', - required: true, - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - notification_type: { - label: i18n._('Type'), - type: 'select', - required: true, - class: 'NotificationsForm-typeSelect', - ngOptions: 'type.label for type in notification_type_options track by type.value', - ngChange: 'typeChange()', - hasSubForm: true, - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - username: { - label: i18n._('Username'), - type: 'text', - ngShow: "notification_type.value == 'email' || notification_type.value == 'webhook' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - password: { - labelBind: 'passwordLabel', - type: 'sensitive', - hasShowInputButton: true, - awRequiredWhen: { - reqExpression: "password_required" , - init: "false" - }, - ngShow: "notification_type.value == 'email' || notification_type.value == 'irc' || notification_type.value == 'webhook' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - host: { - label: i18n._('Host'), - type: 'text', - awRequiredWhen: { - reqExpression: "email_required", - init: "false" - }, - ngShow: "notification_type.value == 'email' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - recipients: { - label: i18n._('Recipient List'), - type: 'textarea', - rows: 3, - awPopOver: i18n._('Enter one email address per line to create a recipient list for this type of notification.'), - dataTitle: i18n._('Recipient List'), - dataPlacement: 'right', - dataContainer: "body", - awRequiredWhen: { - reqExpression: "email_required", - init: "false" - }, - ngShow: "notification_type.value == 'email' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - sender: { - label: i18n._('Sender Email'), - type: 'text', - awRequiredWhen: { - reqExpression: "email_required", - init: "false" - }, - ngShow: "notification_type.value == 'email' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - port: { - labelBind: 'portLabel', - type: 'number', - integer: true, - spinner: true, - 'class': "input-small", - min: 0, - awRequiredWhen: { - reqExpression: "port_required", - init: "false" - }, - ngShow: "notification_type.value == 'email' || notification_type.value == 'irc'", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - timeout: { - label: i18n._('Timeout'), - type: 'number', - integer: true, - default: 30, - min: 1, - max: 120, - spinner: true, - dataTitle: i18n._('Timeout'), - dataPlacement: 'right', - dataContainer: 'body', - awRequiredWhen: { - reqExpression: "email_required", - init: "false" - }, - awPopOver: "

" + i18n._("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.") + "

", - ngShow: "notification_type.value == 'email' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - channels: { - label: i18n._('Destination Channels'), - type: 'textarea', - rows: 3, - awPopOver: i18n._('Enter one Slack channel per line. The pound symbol (#) is required for channels.'), - dataTitle: i18n._('Destination Channels'), - dataPlacement: 'right', - dataContainer: "body", - awRequiredWhen: { - reqExpression: "channel_required", - init: "false" - }, - ngShow: "notification_type.value == 'slack'", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - rooms: { - label: i18n._('Destination Channels'), - type: 'textarea', - rows: 3, - awPopOver: i18n._('Enter one HipChat channel per line. The pound symbol (#) is not required.'), - dataTitle: i18n._('Destination Channels'), - dataPlacement: 'right', - dataContainer: "body", - awRequiredWhen: { - reqExpression: "room_required", - init: "false" - }, - ngShow: "notification_type.value == 'hipchat'", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - token: { - labelBind: 'tokenLabel', - type: 'sensitive', - hasShowInputButton: true, - awRequiredWhen: { - reqExpression: "token_required", - init: "false" - }, - ngShow: "notification_type.value == 'slack' || notification_type.value == 'pagerduty' || notification_type.value == 'hipchat'", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - account_token: { - label: i18n._('Account Token'), - type: 'sensitive', - hasShowInputButton: true, - awRequiredWhen: { - reqExpression: "twilio_required", - init: "false" - }, - ngShow: "notification_type.value == 'twilio' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - from_number: { - label: i18n._('Source Phone Number'), - dataTitle: i18n._('Source Phone Number'), - type: 'text', - awPopOver: i18n._('Enter the number associated with the "Messaging Service" in Twilio in the format +18005550199.'), - awRequiredWhen: { - reqExpression: "twilio_required", - init: "false" - }, - ngShow: "notification_type.value == 'twilio' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - to_numbers: { - label: i18n._('Destination SMS Number'), - dataTitle: i18n._('Destination SMS Number'), - type: 'textarea', - rows: 3, - awPopOver: i18n._('Enter one phone number per line to specify where to route SMS messages.'), - dataPlacement: 'right', - dataContainer: "body", - awRequiredWhen: { - reqExpression: "twilio_required", - init: "false" - }, - ngShow: "notification_type.value == 'twilio' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - account_sid: { - label: i18n._('Account SID'), - type: 'text', - awRequiredWhen: { - reqExpression: "twilio_required", - init: "false" - }, - ngShow: "notification_type.value == 'twilio' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - subdomain: { - label: i18n._('Pagerduty subdomain'), - type: 'text', - awRequiredWhen: { - reqExpression: "pagerduty_required", - init: "false" - }, - ngShow: "notification_type.value == 'pagerduty' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - service_key: { - label: i18n._('API Service/Integration Key'), - type: 'text', - awRequiredWhen: { - reqExpression: "pagerduty_required", - init: "false" - }, - ngShow: "notification_type.value == 'pagerduty' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - client_name: { - label: i18n._('Client Identifier'), - type: 'text', - awRequiredWhen: { - reqExpression: "pagerduty_required", - init: "false" - }, - ngShow: "notification_type.value == 'pagerduty' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - grafana_url: { - label: i18n._('Grafana URL'), - type: 'text', - awPopOver: i18n._('The base URL of the Grafana server - the /api/annotations endpoint will be added automatically to the base Grafana URL.'), - placeholder: 'https://grafana.com', - dataPlacement: 'right', - dataContainer: "body", - awRequiredWhen: { - reqExpression: "grafana_required", - init: "false" - }, - ngShow: "notification_type.value == 'grafana' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - grafana_key: { - label: i18n._('Grafana API Key'), - type: 'sensitive', - hasShowInputButton: true, - name: 'grafana_key', - awRequiredWhen: { - reqExpression: "grafana_required", - init: "false" - }, - ngShow: "notification_type.value == 'grafana' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - dashboardId: { - label: i18n._('ID of the Dashboard (optional)'), - type: 'number', - integer: true, - ngShow: "notification_type.value == 'grafana' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - panelId: { - label: i18n._('ID of the Panel (optional)'), - type: 'number', - integer: true, - ngShow: "notification_type.value == 'grafana' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - annotation_tags: { - label: i18n._('Tags for the Annotation (optional)'), - dataTitle: i18n._('Tags for the Annotation'), - type: 'textarea', - name: 'annotation_tags', - rows: 3, - placeholder: 'ansible', - awPopOver: i18n._('Enter one Annotation Tag per line, without commas.'), - ngShow: "notification_type.value == 'grafana' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - grafana_no_verify_ssl: { - label: i18n._('Disable SSL Verification'), - type: 'checkbox', - ngShow: "notification_type.value == 'grafana' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - api_url: { - label: i18n._('API URL'), - type: 'text', - placeholder: 'https://mycompany.hipchat.com', - awRequiredWhen: { - reqExpression: "hipchat_required", - init: "false" - }, - ngShow: "notification_type.value == 'hipchat' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - message_from: { - label: i18n._('Notification Label'), - type: 'text', - awRequiredWhen: { - reqExpression: "hipchat_required", - init: "false" - }, - ngShow: "notification_type.value == 'hipchat' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - color: { - label: i18n._('Notification Color'), - dataTitle: i18n._('Notification Color'), - type: 'select', - ngOptions: 'color.id as color.name for color in hipchatColors', - awPopOver: i18n._('Specify a notification color. Acceptable colors are: yellow, green, red purple, gray or random.'), - awRequiredWhen: { - reqExpression: "hipchat_required", - init: "false" - }, - ngShow: "notification_type.value == 'hipchat' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - notify: { - label: i18n._('Notify Channel'), - type: 'checkbox', - ngShow: "notification_type.value == 'hipchat' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - url: { - label: i18n._('Target URL'), - type: 'text', - awRequiredWhen: { - reqExpression: "webhook_required", - init: "false" - }, - ngShow: "notification_type.value == 'webhook' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - disable_ssl_verification: { - label: i18n._('Disable SSL Verification'), - type: 'checkbox', - ngShow: "notification_type.value == 'webhook' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - headers: { - label: i18n._('HTTP Headers'), - dataTitle: i18n._('HTTP Headers'), - type: 'textarea', - name: 'headers', - rows: 5, - 'class': 'Form-formGroup--fullWidth', - awRequiredWhen: { - reqExpression: "webhook_required", - init: "false" - }, - awPopOver: i18n._('Specify HTTP Headers in JSON format. Refer to the Ansible Tower documentation for example syntax.'), - dataPlacement: 'right', - ngShow: "notification_type.value == 'webhook' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - http_method: { - label: i18n._('HTTP Method'), - dataTitle: i18n._('HTTP Method'), - type: 'select', - ngOptions: 'choice.id as choice.name for choice in httpMethodChoices', - default: 'POST', - awPopOver: i18n._('Specify an HTTP method for the webhook. Acceptable choices are: POST or PUT'), - awRequiredWhen: { - reqExpression: "webhook_required", - init: "false" - }, - ngShow: "notification_type.value == 'webhook' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - mattermost_url: { - label: i18n._('Target URL'), - type: 'text', - awRequiredWhen: { - reqExpression: "mattermost_required", - init: "false" - }, - ngShow: "notification_type.value == 'mattermost' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - mattermost_username: { - label: i18n._('Username'), - type: 'text', - ngShow: "notification_type.value == 'mattermost' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - mattermost_channel: { - label: i18n._('Channel'), - type: 'text', - ngShow: "notification_type.value == 'mattermost' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - mattermost_icon_url: { - label: i18n._('Icon URL'), - type: 'text', - ngShow: "notification_type.value == 'mattermost' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - mattermost_no_verify_ssl: { - label: i18n._('Disable SSL Verification'), - type: 'checkbox', - ngShow: "notification_type.value == 'mattermost' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - rocketchat_url: { - label: i18n._('Target URL'), - type: 'text', - awRequiredWhen: { - reqExpression: "rocketchat_required", - init: "false" - }, - ngShow: "notification_type.value == 'rocketchat' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - rocketchat_username: { - label: i18n._('Username'), - type: 'text', - ngShow: "notification_type.value == 'rocketchat' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - rocketchat_icon_url: { - label: i18n._('Icon URL'), - type: 'text', - ngShow: "notification_type.value == 'rocketchat' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - rocketchat_no_verify_ssl: { - label: i18n._('Disable SSL Verification'), - type: 'checkbox', - ngShow: "notification_type.value == 'rocketchat' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - server: { - label: i18n._('IRC Server Address'), - type: 'text', - awRequiredWhen: { - reqExpression: "irc_required", - init: "false" - }, - ngShow: "notification_type.value == 'irc' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - nickname: { - label: i18n._('IRC Nick'), - type: 'text', - awRequiredWhen: { - reqExpression: "irc_required", - init: "false" - }, - ngShow: "notification_type.value == 'irc' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - targets: { - label: i18n._('Destination Channels or Users'), - type: 'textarea', - rows: 3, - awPopOver: i18n._('Enter one IRC channel or username per line. The pound symbol (#) for channels, and the at (@) symbol for users, are not required.'), - dataTitle: i18n._('Destination Channels or Users'), - dataPlacement: 'right', - dataContainer: "body", - awRequiredWhen: { - reqExpression: "irc_required", - init: "false" - }, - ngShow: "notification_type.value == 'irc' ", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - use_ssl: { - label: i18n._('SSL Connection'), - type: 'checkbox', - ngShow: "notification_type.value == 'irc'", - subForm: 'typeSubForm', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)' - }, - email_options: { - label: i18n._('Options'), - type: 'radio_group', - subForm: 'typeSubForm', - ngShow: "notification_type.value == 'email'", - ngClick: "emailOptionsChange()", - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)', - options: [{ - value: 'use_tls', - label: i18n._('Use TLS'), - ngShow: "notification_type.value == 'email' ", - labelClass: 'Form-inputLabel' - }, { - value: 'use_ssl', - label: i18n._('Use SSL'), - ngShow: "notification_type.value == 'email'", - labelClass: 'Form-inputLabel' - }] - }, - hex_color: { - label: i18n._('Notification Color'), - dataTitle: i18n._('Notification Color'), - type: 'text', - subForm: 'typeSubForm', - ngShow: "notification_type.value == 'slack' ", - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)', - awPopOver: i18n._('Specify a notification color. Acceptable colors are hex color code (example: #3af or #789abc) .') - }, - customize_messages: { - label: i18n._('Customize messages…'), - type: 'toggleSwitch', - toggleSource: 'customize_messages', - class: 'Form-formGroup--fullWidth', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)', - }, - custom_message_description: { - type: 'alertblock', - ngShow: "customize_messages", - alertTxt: i18n._('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 }}, or attributes of the job such as ' + - '{{ job.status }}. You may apply a number of possible ' + - 'variables in the message. Refer to the ' + - 'Ansible Tower documentation for more details.'), - closeable: false - }, - started_message: { - label: i18n._('Start Message'), - class: 'Form-formGroup--fullWidth', - type: 'syntax_highlight', - mode: 'jinja2', - default: '', - ngShow: "customize_messages && notification_type.value != 'webhook'", - rows: 2, - oneLine: 'true', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)', - }, - started_body: { - label: i18n._('Start Message Body'), - class: 'Form-formGroup--fullWidth', - type: 'syntax_highlight', - mode: 'jinja2', - default: '', - ngShow: "customize_messages && " + - "(notification_type.value == 'email' " + - "|| notification_type.value == 'pagerduty' " + - "|| notification_type.value == 'webhook')", - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)', - }, - success_message: { - label: i18n._('Success Message'), - class: 'Form-formGroup--fullWidth', - type: 'syntax_highlight', - mode: 'jinja2', - default: '', - ngShow: "customize_messages && notification_type.value != 'webhook'", - rows: 2, - oneLine: 'true', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)', - }, - success_body: { - label: i18n._('Success Message Body'), - class: 'Form-formGroup--fullWidth', - type: 'syntax_highlight', - mode: 'jinja2', - default: '', - ngShow: "customize_messages && " + - "(notification_type.value == 'email' " + - "|| notification_type.value == 'pagerduty' " + - "|| notification_type.value == 'webhook')", - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)', - }, - error_message: { - label: i18n._('Error Message'), - class: 'Form-formGroup--fullWidth', - type: 'syntax_highlight', - mode: 'jinja2', - default: '', - ngShow: "customize_messages && notification_type.value != 'webhook'", - rows: 2, - oneLine: 'true', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)', - }, - error_body: { - label: i18n._('Error Message Body'), - class: 'Form-formGroup--fullWidth', - type: 'syntax_highlight', - mode: 'jinja2', - default: '', - ngShow: "customize_messages && " + - "(notification_type.value == 'email' " + - "|| notification_type.value == 'pagerduty' " + - "|| notification_type.value == 'webhook')", - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)', - }, - approved_message: { - label: i18n._('Workflow Approved Message'), - class: 'Form-formGroup--fullWidth', - type: 'syntax_highlight', - mode: 'jinja2', - default: '', - ngShow: "customize_messages && notification_type.value != 'webhook'", - rows: 2, - oneLine: 'true', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)', - }, - approved_body: { - label: i18n._('Workflow Approved Message Body'), - class: 'Form-formGroup--fullWidth', - type: 'syntax_highlight', - mode: 'jinja2', - default: '', - ngShow: "customize_messages && " + - "(notification_type.value == 'email' " + - "|| notification_type.value == 'pagerduty' " + - "|| notification_type.value == 'webhook')", - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)', - }, - denied_message: { - label: i18n._('Workflow Denied Message'), - class: 'Form-formGroup--fullWidth', - type: 'syntax_highlight', - mode: 'jinja2', - default: '', - ngShow: "customize_messages && notification_type.value != 'webhook'", - rows: 2, - oneLine: 'true', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)', - }, - denied_body: { - label: i18n._('Workflow Denied Message Body'), - class: 'Form-formGroup--fullWidth', - type: 'syntax_highlight', - mode: 'jinja2', - default: '', - ngShow: "customize_messages && " + - "(notification_type.value == 'email' " + - "|| notification_type.value == 'pagerduty' " + - "|| notification_type.value == 'webhook')", - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)', - }, - running_message: { - label: i18n._('Workflow Running Message'), - class: 'Form-formGroup--fullWidth', - type: 'syntax_highlight', - mode: 'jinja2', - default: '', - ngShow: "customize_messages && notification_type.value != 'webhook'", - rows: 2, - oneLine: 'true', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)', - }, - running_body: { - label: i18n._('Workflow Running Message Body'), - class: 'Form-formGroup--fullWidth', - type: 'syntax_highlight', - mode: 'jinja2', - default: '', - ngShow: "customize_messages && " + - "(notification_type.value == 'email' " + - "|| notification_type.value == 'pagerduty' " + - "|| notification_type.value == 'webhook')", - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)', - }, - timed_out_message: { - label: i18n._('Workflow Timed Out Message'), - class: 'Form-formGroup--fullWidth', - type: 'syntax_highlight', - mode: 'jinja2', - default: '', - ngShow: "customize_messages && notification_type.value != 'webhook'", - rows: 2, - oneLine: 'true', - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)', - }, - timed_out_body: { - label: i18n._('Workflow Timed Out Message Body'), - class: 'Form-formGroup--fullWidth', - type: 'syntax_highlight', - mode: 'jinja2', - default: '', - ngShow: "customize_messages && " + - "(notification_type.value == 'email' " + - "|| notification_type.value == 'pagerduty' " + - "|| notification_type.value == 'webhook')", - ngDisabled: '!(notification_template.summary_fields.user_capabilities.edit || canAdd)', - }, - }, - - buttons: { //for now always generates -
- - -
- - - - diff --git a/awx/ui/client/src/organizations/linkout/addUsers/main.js b/awx/ui/client/src/organizations/linkout/addUsers/main.js deleted file mode 100644 index 83604f2c7704..000000000000 --- a/awx/ui/client/src/organizations/linkout/addUsers/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import addUsersDirective from './addUsers.directive'; - -export default - angular.module('AddUsers', []) - .directive('addUsers', addUsersDirective); diff --git a/awx/ui/client/src/organizations/linkout/controllers/organizations-admins.controller.js b/awx/ui/client/src/organizations/linkout/controllers/organizations-admins.controller.js deleted file mode 100644 index fe5cc6a06be4..000000000000 --- a/awx/ui/client/src/organizations/linkout/controllers/organizations-admins.controller.js +++ /dev/null @@ -1,78 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['$stateParams', '$scope', 'Rest', '$state', - '$compile', 'Wait', 'OrgAdminList', 'OrgAdminsDataset', 'i18n', - 'Prompt', 'ProcessErrors', 'GetBasePath', '$filter', - function($stateParams, $scope, Rest, $state, - $compile, Wait, OrgAdminList, OrgAdminsDataset, i18n, - Prompt, ProcessErrors, GetBasePath, $filter) { - - var orgBase = GetBasePath('organizations'); - - init(); - - function init() { - // search init - $scope.list = OrgAdminList; - $scope.user_dataset = OrgAdminsDataset.data; - $scope.users = $scope.user_dataset.results; - - Rest.setUrl(orgBase + $stateParams.organization_id); - Rest.get() - .then(({data}) => { - $scope.organization_name = data.name; - $scope.name = data.name; - $scope.org_id = data.id; - $scope.canAddAdmins = data.summary_fields.user_capabilities.edit; - - $scope.orgRelatedUrls = data.related; - - }); - } - - $scope.addUsers = function() { - $compile("")($scope); - }; - - $scope.editUser = function(id) { - $state.go('users.edit', { user_id: id }); - }; - - $scope.deleteUser = function(id, name) { - var action = function() { - $('#prompt-modal').modal('hide'); - Wait('start'); - var url = orgBase + $stateParams.organization_id + '/admins/'; - Rest.setUrl(url); - Rest.post({ - id: id, - disassociate: true - }).then(() => { - $state.go('.', null, { reload: true }); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, null, { - hdr: 'Error!', - msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status - }); - }); - }; - - Prompt({ - hdr: i18n._('Delete'), - body: `
${i18n._('Are you sure you want to remove the following administrator from this organization?')}
` + $filter('sanitize')(name) + '
', - action: action, - actionText: i18n._('DELETE') - }); - }; - - $scope.formCancel = function() { - $state.go('organizations'); - }; - - } -]; diff --git a/awx/ui/client/src/organizations/linkout/controllers/organizations-inventories.controller.js b/awx/ui/client/src/organizations/linkout/controllers/organizations-inventories.controller.js deleted file mode 100644 index 236e2b0146c8..000000000000 --- a/awx/ui/client/src/organizations/linkout/controllers/organizations-inventories.controller.js +++ /dev/null @@ -1,265 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['$scope', '$rootScope', '$location', - '$stateParams', '$compile', '$filter', 'Rest', 'InventoryList', - 'OrgInventoryDataset', 'OrgInventoryList', - 'ProcessErrors', 'GetBasePath', 'Wait', 'Find', 'Empty', '$state', 'i18n', - function($scope, $rootScope, $location, - $stateParams, $compile, $filter, Rest, InventoryList, - Dataset, OrgInventoryList, - ProcessErrors, GetBasePath, Wait, - Find, Empty, $state, i18n) { - - var list = OrgInventoryList, - orgBase = GetBasePath('organizations'); - - init(); - - function init() { - // search init - $scope.list = list; - $scope[`${list.iterator}_dataset`] = Dataset.data; - $scope[list.name] = $scope[`${list.iterator}_dataset`].results; - - $rootScope.flashMessage = null; - Rest.setUrl(orgBase + $stateParams.organization_id); - Rest.get() - .then(({data}) => { - - $scope.organization_name = data.name; - $scope.name = data.name; - $scope.org_id = data.id; - $scope.orgRelatedUrls = data.related; - - }); - - $scope.$watch('inventories', ()=>{ - _.forEach($scope.inventories, processInventoryRow); - }); - } - - function processInventoryRow(item) { - if (item.has_inventory_sources) { - if (item.inventory_sources_with_failures > 0) { - item.syncStatus = 'error'; - item.syncTip = item.inventory_sources_with_failures + i18n._(' groups with sync failures. Click for details'); - } else { - item.syncStatus = 'successful'; - item.syncTip = i18n._('No inventory sync failures. Click for details.'); - } - } else { - item.syncStatus = 'na'; - item.syncTip = i18n._('Not configured for inventory sync.'); - item.launch_class = "btn-disabled"; - } - if (item.has_active_failures) { - item.hostsStatus = 'eritemror'; - item.hostsTip = item.hosts_with_active_failures + i18n._(' hosts with failures. Click for details.'); - } else if (item.total_hosts) { - item.hostsStatus = 'successful'; - item.hostsTip = i18n._('No hosts with failures. Click for details.'); - } else { - item.hostsStatus = 'none'; - item.hostsTip = i18n._('Inventory contains 0 hosts.'); - } - - item.kind_label = item.kind === '' ? i18n._('Inventory') : (item.kind === 'smart' ? i18n._('Smart Inventory'): i18n._('Inventory')); - item.linkToDetails = (item.kind && item.kind === 'smart') ? `inventories.editSmartInventory({smartinventory_id:${item.id}})` : `inventories.edit({inventory_id:${item.id}})`; - - return item; - } - - function ellipsis(a) { - if (a.length > 20) { - return a.substr(0, 20) + '...'; - } - return a; - } - - function attachElem(event, html, title) { - var elem = $(event.target).parent(); - try { - elem.tooltip('hide'); - elem.popover('dispose'); - } catch (err) { - //ignore - } - $('.popover').each(function() { - // remove lingering popover
. Seems to be a bug in TB3 RC1 - $(this).remove(); - }); - $('.tooltip').each(function() { - // close any lingering tool tipss - $(this).hide(); - }); - elem.attr({ - "aw-pop-over": html, - "data-popover-title": title, - "data-placement": "right" - }); - $compile(elem)($scope); - elem.on('shown.bs.popover', function() { - $('.popover').each(function() { - $compile($(this))($scope); //make nested directives work! - }); - $('.popover-content, .popover-title').click(function() { - elem.popover('hide'); - }); - }); - elem.popover('show'); - } - - if ($scope.removeHostSummaryReady) { - $scope.removeHostSummaryReady(); - } - $scope.removeHostSummaryReady = $scope.$on('HostSummaryReady', function(e, event, data) { - - var html, title = "Recent Jobs"; - Wait('stop'); - if (data.count > 0) { - html = "\n"; - html += "\n"; - html += ""; - html += ""; - html += ""; - html += ""; - html += "\n"; - html += "\n"; - html += "\n"; - - data.results.forEach(function(row) { - html += "\n"; - html += "\n"; - html += ""; - html += ""; - html += "\n"; - }); - html += "\n"; - html += "
StatusFinishedName
" + ($filter('longDate')(row.finished)).replace(/ /, '
') + "
" + ellipsis(row.name) + "
\n"; - } else { - html = "

No recent job data available for this inventory.

\n"; - } - attachElem(event, html, title); - }); - - if ($scope.removeGroupSummaryReady) { - $scope.removeGroupSummaryReady(); - } - $scope.removeGroupSummaryReady = $scope.$on('GroupSummaryReady', function(e, event, inventory, data) { - var html, title; - - Wait('stop'); - - // Build the html for our popover - html = "\n"; - html += "\n"; - html += ""; - html += ""; - html += ""; - html += ""; - html += ""; - html += "\n"; - html += "\n"; - data.results.forEach(function(row) { - if (row.related.last_update) { - html += ""; - html += ""; - html += ""; - html += ""; - html += "\n"; - } else { - html += ""; - html += ""; - html += ""; - html += ""; - html += "\n"; - } - }); - html += "\n"; - html += "
StatusLast SyncGroup
" + ($filter('longDate')(row.last_updated)).replace(/ /, '
') + "
" + ellipsis(row.summary_fields.group.name) + "
NA" + ellipsis(row.summary_fields.group.name) + "
\n"; - title = i18n._("Sync Status"); - attachElem(event, html, title); - }); - - $scope.showGroupSummary = function(event, id) { - var inventory; - if (!Empty(id)) { - inventory = Find({ list: $scope.inventories, key: 'id', val: id }); - if (inventory.syncStatus !== 'na') { - Wait('start'); - Rest.setUrl(inventory.related.inventory_sources + '?or__source=ec2&or__source=rax&order_by=-last_job_run&page_size=5'); - Rest.get() - .then(({data}) => { - $scope.$emit('GroupSummaryReady', event, inventory, data); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, null, { - hdr: 'Error!', - msg: 'Call to ' + inventory.related.inventory_sources + ' failed. GET returned status: ' + status - }); - }); - } - } - }; - - $scope.showHostSummary = function(event, id) { - var url, inventory; - if (!Empty(id)) { - inventory = Find({ list: $scope.inventories, key: 'id', val: id }); - if (inventory.total_hosts > 0) { - Wait('start'); - url = GetBasePath('jobs') + "?type=job&inventory=" + id + "&failed="; - url += (inventory.has_active_failures) ? 'true' : "false"; - url += "&order_by=-finished&page_size=5"; - Rest.setUrl(url); - Rest.get() - .then(({data}) => { - $scope.$emit('HostSummaryReady', event, data); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, null, { - hdr: 'Error!', - msg: 'Call to ' + url + ' failed. GET returned: ' + status - }); - }); - } - } - }; - - $scope.viewJob = function(url) { - // Pull the id out of the URL - var id = url.replace(/^\//, '').split('/')[3]; - $state.go('output', { id: id, type: 'inventory' }); - - }; - - $scope.editInventory = function (inventory) { - if(inventory.kind && inventory.kind === 'smart') { - $state.go('inventories.editSmartInventory', {smartinventory_id: inventory.id}); - } - else { - $state.go('inventories.edit', {inventory_id: inventory.id}); - } - }; - - // Failed jobs link. Go to the jobs tabs, find all jobs for the inventory and sort by status - $scope.viewJobs = function(id) { - $location.url('/jobs/?inventory__int=' + id); - }; - - $scope.viewFailedJobs = function(id) { - $location.url('/jobs/?inventory__int=' + id + '&status=failed'); - }; - - $scope.formCancel = function() { - $state.go('organizations'); - }; - - } -]; diff --git a/awx/ui/client/src/organizations/linkout/controllers/organizations-job-templates.controller.js b/awx/ui/client/src/organizations/linkout/controllers/organizations-job-templates.controller.js deleted file mode 100644 index d01049ed21ab..000000000000 --- a/awx/ui/client/src/organizations/linkout/controllers/organizations-job-templates.controller.js +++ /dev/null @@ -1,76 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['$scope', '$rootScope', - '$stateParams', 'Rest', 'ProcessErrors', - 'GetBasePath', 'Wait', - '$state', 'OrgJobTemplateList', 'OrgJobTemplateDataset', 'QuerySet', - function($scope, $rootScope, - $stateParams, Rest, ProcessErrors, - GetBasePath, Wait, - $state, OrgJobTemplateList, Dataset, qs) { - - var list = OrgJobTemplateList, - orgBase = GetBasePath('organizations'); - - $scope.$on(`ws-jobs`, function () { - let path = GetBasePath(list.basePath) || GetBasePath(list.name); - qs.search(path, $state.params[`${list.iterator}_search`]) - .then(function(searchResponse) { - $scope[`${list.iterator}_dataset`] = searchResponse.data; - $scope[list.name] = $scope[`${list.iterator}_dataset`].results; - }); - }); - - init(); - - function init() { - // search init - $scope.list = list; - $scope[`${list.iterator}_dataset`] = Dataset.data; - $scope[list.name] = $scope[`${list.iterator}_dataset`].results; - Rest.setUrl(orgBase + $stateParams.organization_id); - Rest.get() - .then(({data}) => { - $scope.organization_name = data.name; - $scope.name = data.name; - $scope.org_id = data.id; - - $scope.orgRelatedUrls = data.related; - }); - } - - $scope.$on(`${list.iterator}_options`, function(event, data){ - $scope.options = data.data.actions.GET; - optionsRequestDataProcessing(); - }); - - $scope.$watchCollection(`${$scope.list.name}`, function() { - optionsRequestDataProcessing(); - } - ); - // iterate over the list and add fields like type label, after the - // OPTIONS request returns, or the list is sorted/paginated/searched - function optionsRequestDataProcessing(){ - $scope[list.name].forEach(function(item, item_idx) { - var itm = $scope[list.name][item_idx]; - - // Set the item type label - if (list.fields.type && $scope.options && $scope.options.hasOwnProperty('type')) { - $scope.options.type.choices.forEach(function(choice) { - if (choice[0] === item.type) { - itm.type_label = choice[1]; - } - }); - } - }); - } - - $scope.editJobTemplate = function(id) { - $state.go('templates.editJobTemplate', { job_template_id: id }); - }; - } -]; diff --git a/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js b/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js deleted file mode 100644 index 5e444a0eed18..000000000000 --- a/awx/ui/client/src/organizations/linkout/controllers/organizations-projects.controller.js +++ /dev/null @@ -1,309 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['$scope', '$rootScope', '$log', '$stateParams', 'Rest', 'Alert', - 'OrgProjectList', 'OrgProjectDataset', 'ProcessErrors', 'GetBasePath', - 'ProjectUpdate', 'Wait', 'GetChoices', 'Empty', 'Find', 'GetProjectIcon', - 'GetProjectToolTip', '$filter', '$state', 'i18n', - function($scope, $rootScope, $log, $stateParams, Rest, Alert, - OrgProjectList, Dataset, ProcessErrors, GetBasePath, ProjectUpdate, - Wait, GetChoices, Empty, Find, GetProjectIcon, GetProjectToolTip, $filter, - $state, i18n) { - - var list = OrgProjectList, - projUrl, - choiceCount = 0, - orgBase = GetBasePath('organizations'), - projBase = GetBasePath('projects'); - - - function updateStatus() { - if ($scope.projects) { - $scope.projects.forEach(function(project, i) { - $scope.projects[i].statusIcon = GetProjectIcon(project.status); - $scope.projects[i].statusTip = GetProjectToolTip(project.status); - $scope.projects[i].scm_update_tooltip = i18n._("Get latest SCM revision"); - $scope.projects[i].scm_type_class = ""; - - if (project.status === 'failed' && project.summary_fields.last_update && project.summary_fields.last_update.status === 'canceled') { - $scope.projects[i].statusTip = i18n._('Canceled. Click for details'); - } - - if (project.status === 'running' || project.status === 'updating') { - $scope.projects[i].scm_update_tooltip = i18n._("SCM update currently running"); - $scope.projects[i].scm_type_class = "btn-disabled"; - } - - if ($scope.project_scm_type_options) { - $scope.project_scm_type_options.forEach(function(type) { - if (type.value === project.scm_type) { - $scope.projects[i].scm_type = type.label; - if (type.label === 'Manual') { - $scope.projects[i].scm_update_tooltip = i18n._('Manual projects do not require an SCM update'); - $scope.projects[i].scm_type_class = 'btn-disabled'; - $scope.projects[i].statusTip = 'Not configured for SCM'; - $scope.projects[i].statusIcon = 'none'; - } - } - }); - } - }); - } - } - - init(); - - function init() { - // search init - $scope.list = list; - $scope[`${list.iterator}_dataset`] = Dataset.data; - $scope[list.name] = $scope[`${list.iterator}_dataset`].results; - - $rootScope.flashMessage = null; - - $scope.$on('choicesReadyProjectList', function() { - Wait('stop'); - updateStatus(); - }); - } - - $scope.$on(`${list.iterator}_options`, function(event, data){ - $scope.options = data.data.actions.GET; - optionsRequestDataProcessing(); - }); - - $scope.$watchCollection(`${$scope.list.name}`, function() { - optionsRequestDataProcessing(); - updateStatus(); - }); - - // iterate over the list and add fields like type label, after the - // OPTIONS request returns, or the list is sorted/paginated/searched - function optionsRequestDataProcessing(){ - $scope[list.name].forEach(function(item, item_idx) { - var itm = $scope[list.name][item_idx]; - - // Set the item type label - if (list.fields.scm_type && $scope.options && - $scope.options.hasOwnProperty('scm_type')) { - $scope.options.scm_type.choices.forEach(function(choice) { - if (choice[0] === item.scm_type) { - itm.type_label = choice[1]; - } - }); - } - - // buildTooltips(itm); - - }); - } - - // Go out and get the organization - Rest.setUrl(orgBase + $stateParams.organization_id); - Rest.get() - .then(({data}) => { - $scope.organization_name = data.name; - $scope.name = data.name; - $scope.org_id = data.id; - - $scope.orgRelatedUrls = data.related; - - - $scope.$on('ws-jobs', function(e, data) { - var project; - $log.debug(data); - if ($scope.projects) { - // Assuming we have a list of projects available - project = Find({ list: $scope.projects, key: 'id', val: data.project_id }); - if (project) { - // And we found the affected project - $log.debug('Received event for project: ' + project.name); - $log.debug('Status changed to: ' + data.status); - if (!(data.status === 'successful' || data.status === 'failed')) { - project.scm_update_tooltip = i18n._("SCM update currently running"); - project.scm_type_class = "btn-disabled"; - } - project.status = data.status; - project.statusIcon = GetProjectIcon(data.status); - project.statusTip = GetProjectToolTip(data.status); - } - } - }); - - if ($scope.removeChoicesHere) { - $scope.removeChoicesHere(); - } - $scope.removeChoicesHere = $scope.$on('choicesCompleteProjectList', function() { - - list.fields.scm_type.searchOptions = $scope.project_scm_type_options; - list.fields.status.searchOptions = $scope.project_status_options; - - if ($stateParams.scm_type && $stateParams.status) { - // Request coming from home page. User wants all errors for an scm_type - projUrl += '?status=' + $stateParams.status; - } - }); - - if ($scope.removeChoicesReadyList) { - $scope.removeChoicesReadyList(); - } - $scope.removeChoicesReadyList = $scope.$on('choicesReadyProjectList', function() { - choiceCount++; - if (choiceCount === 2) { - $scope.$emit('choicesCompleteProjectList'); - } - }); - - // Load options for status --used in search - GetChoices({ - scope: $scope, - url: projBase, - field: 'status', - variable: 'project_status_options', - callback: 'choicesReadyProjectList' - }); - - // Load the list of options for Kind - GetChoices({ - scope: $scope, - url: projBase, - field: 'scm_type', - variable: 'project_scm_type_options', - callback: 'choicesReadyProjectList' - }); - - }); - - $scope.editProject = function(id) { - $state.go('projects.edit', { project_id: id }); - }; - - if ($scope.removeGoTojobResults) { - $scope.removeGoTojobResults(); - } - $scope.removeGoTojobResults = $scope.$on('GoTojobResults', function(e, data) { - if (data.summary_fields.current_update || data.summary_fields.last_update) { - - Wait('start'); - - // Grab the id from summary_fields - var id = (data.summary_fields.current_update) ? data.summary_fields.current_update.id : data.summary_fields.last_update.id; - - $state.go('output', { id: id, type: 'project' }); - - } else { - Alert(i18n._('No Updates Available'), i18n._('There is no SCM update information available for this project. An update has not yet been completed. If you have not already done so, start an update for this project.'), 'alert-info'); - } - }); - - $scope.showSCMStatus = function(id) { - // Refresh the project list - var project = Find({ list: $scope.projects, key: 'id', val: id }); - if (Empty(project.scm_type) || project.scm_type === 'Manual') { - Alert(i18n._('No SCM Configuration'), i18n._('The selected project is not configured for SCM. To configure for SCM, edit the project and provide SCM settings and then run an update.'), 'alert-info'); - } else { - // Refresh what we have in memory to insure we're accessing the most recent status record - Rest.setUrl(project.url); - Rest.get() - .then(({data}) => { - $scope.$emit('GoTojobResults', data); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, null, { - hdr: 'Error!', - msg: 'Project lookup failed. GET returned: ' + status - }); - }); - } - }; - - if ($scope.removeCancelUpdate) { - $scope.removeCancelUpdate(); - } - $scope.removeCancelUpdate = $scope.$on('Cancel_Update', function(e, url) { - // Cancel the project update process - Rest.setUrl(url); - Rest.post() - .then(() => { - Alert(i18n._('SCM Update Cancel'), i18n._('Your request to cancel the update was submitted to the task manager.'), 'alert-info'); - $scope.refresh(); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url + ' failed. POST status: ' + status }); - }); - }); - - if ($scope.removeCheckCancel) { - $scope.removeCheckCancel(); - } - $scope.removeCheckCancel = $scope.$on('Check_Cancel', function(e, data) { - // Check that we 'can' cancel the update - var url = data.related.cancel; - Rest.setUrl(url); - Rest.get() - .then(({data}) => { - if (data.can_cancel) { - $scope.$emit('Cancel_Update', url); - } else { - Alert(i18n._('Cancel Not Allowed'), `
${i18n._('Either you do not have access or the SCM update process completed. Click the ')} - ${i18n._('Refresh')} ${i18n._('button to view the latest status.')}
`, 'alert-info', null, null, null, null, true); - } - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url + ' failed. GET status: ' + status }); - }); - }); - - $scope.cancelUpdate = function(id, name) { - Rest.setUrl(GetBasePath("projects") + id); - Rest.get() - .then(({data}) => { - if (data.related.current_update) { - Rest.setUrl(data.related.current_update); - Rest.get() - .then(({data}) => { - $scope.$emit('Check_Cancel', data); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, null, { - hdr: 'Error!', - msg: 'Call to ' + data.related.current_update + ' failed. GET status: ' + status - }); - }); - } else { - Alert(i18n._('Update Not Found'), `
${i18n._('An SCM update does not appear to be running for project: ')} ${$filter('sanitize')(name)}. ${i18n._('Click the')} ${i18n._('Refresh')}${i18n._('button to view the latest status.')}
`, - 'alert-info', undefined, undefined, undefined, undefined, true); - } - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, null, { - hdr: 'Error!', - msg: 'Call to get project failed. GET status: ' + status - }); - }); - }; - - $scope.SCMUpdate = function(project_id, event) { - try { - $(event.target).tooltip('hide'); - } catch (e) { - // ignore - } - $scope.projects.forEach(function(project) { - if (project.id === project_id) { - if (!((project.scm_type === "Manual" || Empty(project.scm_type)) || (project.status === 'updating' || project.status === 'running' || project.status === 'pending'))) { - ProjectUpdate({ scope: $scope, project_id: project.id }); - } - } - }); - }; - - $scope.formCancel = function() { - $state.go('organizations'); - }; - - } -]; diff --git a/awx/ui/client/src/organizations/linkout/controllers/organizations-teams.controller.js b/awx/ui/client/src/organizations/linkout/controllers/organizations-teams.controller.js deleted file mode 100644 index c4b9435d1d93..000000000000 --- a/awx/ui/client/src/organizations/linkout/controllers/organizations-teams.controller.js +++ /dev/null @@ -1,51 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['$scope', '$stateParams', 'OrgTeamList', 'Rest', 'OrgTeamsDataset', - 'GetBasePath', '$state', - function($scope, $stateParams, OrgTeamList, Rest, Dataset, - GetBasePath, $state) { - - var list = OrgTeamList, - orgBase = GetBasePath('organizations'); - - init(); - - function init() { - // search init - $scope.list = list; - $scope[`${list.iterator}_dataset`] = Dataset.data; - $scope[list.name] = $scope[`${list.iterator}_dataset`].results; - - $scope.$watchCollection(list.name, function() { - function setOrganizationName(organization) { - organization.organization_name = organization.summary_fields.organization.name; - return organization; - } - _.forEach($scope.teams, (team) => setOrganizationName(team)); - }); - - Rest.setUrl(orgBase + $stateParams.organization_id); - Rest.get() - .then(({data}) => { - - $scope.organization_name = data.name; - $scope.name = data.name; - $scope.org_id = data.id; - - $scope.orgRelatedUrls = data.related; - }); - } - - $scope.editTeam = function(id) { - $state.go('teams.edit', { team_id: id }); - }; - - $scope.formCancel = function() { - $state.go('organizations'); - }; - } -]; diff --git a/awx/ui/client/src/organizations/linkout/controllers/organizations-users.controller.js b/awx/ui/client/src/organizations/linkout/controllers/organizations-users.controller.js deleted file mode 100644 index b5676fb782e5..000000000000 --- a/awx/ui/client/src/organizations/linkout/controllers/organizations-users.controller.js +++ /dev/null @@ -1,77 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['$stateParams', '$scope', 'OrgUserList','Rest', '$state', - '$compile', 'Wait', 'OrgUsersDataset', - 'Prompt', 'ProcessErrors', 'GetBasePath', '$filter', 'i18n', - function($stateParams, $scope, OrgUserList, Rest, $state, - $compile, Wait, OrgUsersDataset, Prompt, ProcessErrors, - GetBasePath, $filter, i18n) { - - var orgBase = GetBasePath('organizations'); - - init(); - - function init() { - // search init - $scope.list = OrgUserList; - $scope.user_dataset = OrgUsersDataset.data; - $scope.users = $scope.user_dataset.results; - - Rest.setUrl(orgBase + $stateParams.organization_id); - Rest.get() - .then(({data}) => { - $scope.organization_name = data.name; - $scope.name = data.name; - $scope.org_id = data.id; - - $scope.orgRelatedUrls = data.related; - - }); - } - - $scope.addUsers = function() { - $compile("")($scope); - }; - - $scope.editUser = function(id) { - $state.go('users.edit', { user_id: id }); - }; - - $scope.deleteUser = function(id, name) { - var action = function() { - $('#prompt-modal').modal('hide'); - Wait('start'); - var url = orgBase + $stateParams.organization_id + '/users/'; - Rest.setUrl(url); - Rest.post({ - id: id, - disassociate: true - }).then(() => { - $state.go('.', null, { reload: true }); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, null, { - hdr: 'Error!', - msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status - }); - }); - }; - - Prompt({ - hdr: i18n._('Delete'), - body: `
${i18n._('Are you sure you want to remove the following user from this organization?')}
` + $filter('sanitize')(name) + '
', - action: action, - actionText: i18n._('DELETE') - }); - }; - - $scope.formCancel = function() { - $state.go('organizations'); - }; - - } -]; diff --git a/awx/ui/client/src/organizations/linkout/main.js b/awx/ui/client/src/organizations/linkout/main.js deleted file mode 100644 index e6d31a156c0d..000000000000 --- a/awx/ui/client/src/organizations/linkout/main.js +++ /dev/null @@ -1,3 +0,0 @@ -import AddUsers from './addUsers/main'; - -export default angular.module('organizationsLinkout', [AddUsers.name]); diff --git a/awx/ui/client/src/organizations/linkout/organizations-linkout.route.js b/awx/ui/client/src/organizations/linkout/organizations-linkout.route.js deleted file mode 100644 index 8074e96d06aa..000000000000 --- a/awx/ui/client/src/organizations/linkout/organizations-linkout.route.js +++ /dev/null @@ -1,270 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import OrganizationsJobTemplatesRoute from '~features/templates/routes/organizationsTemplatesList.route'; - -import OrganizationsAdmins from './controllers/organizations-admins.controller'; -import OrganizationsInventories from './controllers/organizations-inventories.controller'; -import OrganizationsProjects from './controllers/organizations-projects.controller'; -import OrganizationsTeams from './controllers/organizations-teams.controller'; -import OrganizationsUsers from './controllers/organizations-users.controller'; -import { N_ } from '../../i18n'; - -let lists = [{ - name: 'organizations.users', - url: '/:organization_id/users', - searchPrefix: 'user', - views: { - 'form': { - controller: OrganizationsUsers, - templateProvider: function(OrgUserList, generateList) { - let html = generateList.build({ - list: OrgUserList, - mode: 'edit', - cancelButton: true - }); - return generateList.wrapPanel(html); - }, - } - }, - params: { - user_search: { - value: { - order_by: 'username' - }, - dynamic: true - } - }, - ncyBreadcrumb: { - parent: "organizations.edit", - label: N_("USERS") - }, - - data: { - activityStream: true, - activityStreamTarget: 'organization' - }, - resolve: { - OrgUsersDataset: ['OrgUserList', 'QuerySet', '$stateParams', 'GetBasePath', - function(list, qs, $stateParams, GetBasePath) { - let path = GetBasePath(list.basePath) || list.basePath; - return qs.search(path, $stateParams.org_user_search); - } - ], - OrgUserList: ['UserList', 'GetBasePath', '$stateParams', 'i18n', function(UserList, GetBasePath, $stateParams, i18n) { - let list = _.cloneDeep(UserList); - delete list.actions.add; - list.basePath = `${GetBasePath('organizations')}${$stateParams.organization_id}/users`; - list.searchRowActions = { - add: { - awToolTip: i18n._('Add existing user to organization'), - actionClass: 'at-Button--add', - actionId: 'button-add', - ngClick: 'addUsers()' - } - }; - return list; - }] - } -}, { - name: 'organizations.teams', - url: '/:organization_id/teams', - searchPrefix: 'team', - views: { - 'form': { - controller: OrganizationsTeams, - templateProvider: function(OrgTeamList, generateList) { - let html = generateList.build({ - list: OrgTeamList, - mode: 'edit', - cancelButton: true - }); - return generateList.wrapPanel(html); - }, - }, - }, - data: { - activityStream: true, - activityStreamTarget: 'organization' - }, - ncyBreadcrumb: { - parent: "organizations.edit", - label: N_("TEAMS") - }, - resolve: { - OrgTeamList: ['TeamList', 'GetBasePath', '$stateParams', 'i18n', function(TeamList, GetBasePath, $stateParams, i18n) { - let list = _.cloneDeep(TeamList); - delete list.actions.add; - // @issue Why is the delete action unavailable in this view? - delete list.fieldActions.delete; - list.listTitle = i18n._('Teams') + ` | {{ name }}`; - list.basePath = `${GetBasePath('organizations')}${$stateParams.organization_id}/teams`; - list.emptyListText = `${i18n._('This list is populated by teams added from the')} ${N_('Teams')} ${N_('section')}`; - return list; - }], - OrgTeamsDataset: ['OrgTeamList', 'QuerySet', '$stateParams', 'GetBasePath', - function(list, qs, $stateParams, GetBasePath) { - let path = GetBasePath(list.basePath) || list.basePath; - return qs.search(path, $stateParams.team_search); - } - ] - } -}, { - name: 'organizations.inventories', - url: '/:organization_id/inventories', - searchPrefix: 'inventory', - views: { - 'form': { - controller: OrganizationsInventories, - templateProvider: function(OrgInventoryList, generateList) { - let html = generateList.build({ - list: OrgInventoryList, - mode: 'edit', - cancelButton: true - }); - return generateList.wrapPanel(html); - }, - }, - }, - data: { - activityStream: true, - activityStreamTarget: 'organization' - }, - ncyBreadcrumb: { - parent: "organizations.edit", - label: N_("INVENTORIES") - }, - resolve: { - OrgInventoryList: ['InventoryList', 'GetBasePath', '$stateParams', 'i18n', function(InventoryList, GetBasePath, $stateParams, i18n) { - let list = _.cloneDeep(InventoryList); - delete list.actions.add; - // @issue Why is the delete action unavailable in this view? - delete list.fieldActions.delete; - list.title = true; - list.listTitle = i18n._('Inventories') + ` | {{ name }}`; - list.basePath = `${GetBasePath('organizations')}${$stateParams.organization_id}/inventories`; - list.emptyListText = `${i18n._("This list is populated by inventories added from the")} ${N_("Inventories")} ${N_("section")}`; - return list; - }], - OrgInventoryDataset: ['OrgInventoryList', 'QuerySet', '$stateParams', 'GetBasePath', - function(list, qs, $stateParams, GetBasePath) { - let path = GetBasePath(list.basePath) || list.basePath; - return qs.search(path, $stateParams.inventory_search); - } - ] - } -}, { - name: 'organizations.projects', - url: '/:organization_id/projects', - searchPrefix: 'project', - views: { - 'form': { - controller: OrganizationsProjects, - templateProvider: function(OrgProjectList, generateList) { - let html = generateList.build({ - list: OrgProjectList, - mode: 'edit', - cancelButton: true - }); - return generateList.wrapPanel(html); - }, - }, - }, - data: { - activityStream: true, - activityStreamTarget: 'organization' - }, - ncyBreadcrumb: { - parent: "organizations.edit", - label: N_("PROJECTS") - }, - resolve: { - OrgProjectList: ['ProjectList', 'GetBasePath', '$stateParams', 'i18n', function(ProjectList, GetBasePath, $stateParams, i18n) { - let list = _.cloneDeep(ProjectList); - delete list.actions; - // @issue Why is the delete action unavailable in this view? - delete list.fieldActions.delete; - list.listTitle = i18n._('Projects') + ` | {{ name }}`; - list.basePath = `${GetBasePath('organizations')}${$stateParams.organization_id}/projects`; - list.emptyListText = `${i18n._("This list is populated by projects added from the")} ${N_("Projects")} ${N_("section")}`; - return list; - }], - OrgProjectDataset: ['OrgProjectList', 'QuerySet', '$stateParams', 'GetBasePath', - function(list, qs, $stateParams, GetBasePath) { - let path = GetBasePath(list.basePath) || list.basePath; - return qs.search(path, $stateParams.project_search); - } - ] - } -}, { - name: 'organizations.admins', - url: '/:organization_id/admins', - searchPrefix: 'user', - params: { - user_search: { - value: { - order_by: 'username' - }, - dynamic: true - }, - add_user_search: { - value: { - order_by: 'username', - page_size: '5', - }, - dynamic: true, - squash: true - } - }, - views: { - 'form': { - controller: OrganizationsAdmins, - templateProvider: function(OrgAdminList, generateList) { - let html = generateList.build({ - list: OrgAdminList, - mode: 'edit', - cancelButton: true - }); - return generateList.wrapPanel(html); - }, - } - }, - data: { - activityStream: true, - activityStreamTarget: 'organization' - }, - ncyBreadcrumb: { - parent: "organizations.edit", - label: N_("ADMINS") - }, - resolve: { - OrgAdminsDataset: ['OrgAdminList', 'QuerySet', '$stateParams', 'GetBasePath', - function(list, qs, $stateParams, GetBasePath) { - let path = GetBasePath(list.basePath) || list.basePath; - return qs.search(path, $stateParams[`user_search`]); - } - ], - OrgAdminList: ['UserList', 'GetBasePath', '$stateParams', 'i18n', function(UserList, GetBasePath, $stateParams, i18n) { - let list = _.cloneDeep(UserList); - delete list.actions.add; - list.basePath = `${GetBasePath('organizations')}${$stateParams.organization_id}/admins`; - list.searchRowActions = { - add: { - awToolTip: i18n._('Add existing user to organization as administrator'), - actionClass: 'at-Button--add', - ngClick: 'addUsers()', - ngShow:'canAddAdmins' - } - }; - list.listTitle = i18n._('Admins') + ` | {{ name }}`; - return list; - }] - } -}]; - -lists.push(OrganizationsJobTemplatesRoute); - -export default lists; diff --git a/awx/ui/client/src/organizations/list/organizations-list.controller.js b/awx/ui/client/src/organizations/list/organizations-list.controller.js deleted file mode 100644 index edec123ddece..000000000000 --- a/awx/ui/client/src/organizations/list/organizations-list.controller.js +++ /dev/null @@ -1,207 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - -export default ['$stateParams', '$scope', '$rootScope', - 'Rest', 'OrganizationList', 'Prompt', 'OrganizationModel', - 'ProcessErrors', 'GetBasePath', 'Wait', '$state', - 'rbacUiControlService', '$filter', 'Dataset', 'i18n', - 'AppStrings', - function($stateParams, $scope, $rootScope, - Rest, OrganizationList, Prompt, Organization, - ProcessErrors, GetBasePath, Wait, $state, - rbacUiControlService, $filter, Dataset, i18n, - AppStrings - ) { - - var defaultUrl = GetBasePath('organizations'), - list = OrganizationList; - - $scope.canAdd = false; - - rbacUiControlService.canAdd("organizations") - .then(function(params) { - $scope.canAdd = params.canAdd; - }); - $scope.orgCount = Dataset.data.count; - - // search init - $scope.list = list; - $scope[`${list.iterator}_dataset`] = Dataset.data; - $scope[list.name] = $scope[`${list.iterator}_dataset`].results; - - $scope.orgCards = parseCardData($scope[list.name]); - $rootScope.flashMessage = null; - - // grab the pagination elements, move, destroy list generator elements - $('#organization-pagination').appendTo('#OrgCards'); - $('#organizations tag-search').appendTo('.OrgCards-search'); - $('#organizations-list').remove(); - - function parseCardData(cards) { - return cards.map(function(card) { - var val = {}; - val.user_capabilities = card.summary_fields.user_capabilities; - val.name = card.name; - val.id = card.id; - val.description = card.description || undefined; - val.links = []; - val.links.push({ - sref: `organizations.edit.users({organization_id: ${card.id}})`, - srefOpts: { inherit: false }, - name: i18n._("USERS"), - count: card.summary_fields.related_field_counts.users, - activeMode: 'users' - }); - val.links.push({ - sref: `organizations.teams({organization_id: ${card.id}})`, - srefOpts: { inherit: false }, - name: i18n._("TEAMS"), - count: card.summary_fields.related_field_counts.teams, - activeMode: 'teams' - }); - val.links.push({ - sref: `organizations.inventories({organization_id: ${card.id}})`, - srefOpts: { inherit: false }, - name: i18n._("INVENTORIES"), - count: card.summary_fields.related_field_counts.inventories, - activeMode: 'inventories' - }); - val.links.push({ - sref: `organizations.projects({organization_id: ${card.id}})`, - srefOpts: { inherit: false }, - name: i18n._("PROJECTS"), - count: card.summary_fields.related_field_counts.projects, - activeMode: 'projects' - }); - val.links.push({ - sref: `organizations.job_templates({organization_id: ${card.id}, or__jobtemplate__project__organization: ${card.id}, or__jobtemplate__inventory__organization: ${card.id}})`, - srefOpts: { inherit: false }, - name: i18n._("JOB TEMPLATES"), - count: card.summary_fields.related_field_counts.job_templates, - activeMode: 'job_templates' - }); - val.links.push({ - sref: `organizations.admins({organization_id: ${card.id}})`, - srefOpts: { inherit: false }, - name: i18n._("ADMINS"), - count: card.summary_fields.related_field_counts.admins, - activeMode: 'admins' - }); - return val; - }); - } - - $scope.$on("ReloadOrgListView", function() { - Rest.setUrl($scope.current_url); - Rest.get() - .then(({data}) => $scope.organizations = data.results) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, null, { - hdr: 'Error!', - msg: 'Call to ' + defaultUrl + ' failed. DELETE returned status: ' + status - }); - }); - }); - - - $scope.$watchCollection('organizations', function(value){ - $scope.orgCards = parseCardData(value); - }); - - if ($scope.removePostRefresh) { - $scope.removePostRefresh(); - } - - $scope.$watchCollection(`${list.iterator}_dataset`, function(data) { - $scope[list.name] = data.results; - $scope.orgCards = parseCardData($scope[list.name]); - $scope.orgCount = data.count; - }); - - $scope.addOrganization = function() { - $state.transitionTo('organizations.add'); - }; - - $scope.editOrganization = function(id) { - $state.transitionTo('organizations.edit', { - organization_id: id - }); - }; - - function isDeletedOrganizationBeingEdited(deleted_organization_id, editing_organization_id) { - if (editing_organization_id === undefined) { - return false; - } - if (deleted_organization_id === editing_organization_id) { - return true; - } - return false; - } - - $scope.deleteOrganization = function(id, name) { - - var action = function() { - $('#prompt-modal').modal('hide'); - Wait('start'); - var url = defaultUrl + id + '/'; - Rest.setUrl(url); - Rest.destroy() - .then(() => { - Wait('stop'); - - let reloadListStateParams = null; - - if($scope.organizations.length === 1 && $state.params.organization_search && _.has($state, 'params.organization_search.page') && parseInt($state.params.organization_search.page).toString() !== '1') { - reloadListStateParams = _.cloneDeep($state.params); - reloadListStateParams.organization_search.page = (parseInt(reloadListStateParams.organization_search.page)-1).toString(); - } - - if (isDeletedOrganizationBeingEdited(id, parseInt($stateParams.organization_id)) === true) { - $state.go('^', reloadListStateParams, { reload: true }); - } else { - $state.go('.', reloadListStateParams, { reload: true }); - } - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, null, { - hdr: 'Error!', - msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status - }); - }); - }; - - const organization = new Organization(); - - organization.getDependentResourceCounts(id) - .then((counts) => { - const invalidateRelatedLines = []; - let deleteModalBody = `
${AppStrings.get('deleteResource.CONFIRM', 'organization')}
`; - - counts.forEach(countObj => { - if(countObj.count && countObj.count > 0) { - invalidateRelatedLines.push(`
${countObj.label}${countObj.count}
`); - } - }); - - if (invalidateRelatedLines && invalidateRelatedLines.length > 0) { - deleteModalBody = `
${AppStrings.get('deleteResource.UNAVAILABLE', 'organization')} ${AppStrings.get('deleteResource.CONFIRM', 'organization')}
`; - invalidateRelatedLines.forEach(invalidateRelatedLine => { - deleteModalBody += invalidateRelatedLine; - }); - } - - Prompt({ - hdr: i18n._('Delete'), - resourceName: $filter('sanitize')(name), - body: deleteModalBody, - action: action, - actionText: i18n._('DELETE') - }); - }); - }; - } -]; diff --git a/awx/ui/client/src/organizations/list/organizations-list.partial.html b/awx/ui/client/src/organizations/list/organizations-list.partial.html deleted file mode 100644 index ff61befa28fe..000000000000 --- a/awx/ui/client/src/organizations/list/organizations-list.partial.html +++ /dev/null @@ -1,102 +0,0 @@ -
-
-
-
-
-
- ORGANIZATIONS -
- - {{ orgCount }} - -
- -
- -
-
-
- -
-
- - - -
PLEASE ADD ITEMS TO THIS LIST
- -
-
-
-

- - {{ card.name }} - -

-
- - - -
-
- -
-
- - -
-
-
diff --git a/awx/ui/client/src/organizations/main.js b/awx/ui/client/src/organizations/main.js deleted file mode 100644 index 8540261ede9b..000000000000 --- a/awx/ui/client/src/organizations/main.js +++ /dev/null @@ -1,157 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import { templateUrl } from '../shared/template-url/template-url.factory'; -import OrganizationsList from './list/organizations-list.controller'; -import OrganizationsAdd from './add/organizations-add.controller'; -import OrganizationsEdit from './edit/organizations-edit.controller'; -import organizationsLinkout from './linkout/main'; -import OrganizationsLinkoutStates from './linkout/organizations-linkout.route'; -import OrganizationForm from './organizations.form'; -import OrganizationList from './organizations.list'; -import { N_ } from '../i18n'; - - -export default -angular.module('Organizations', [ - organizationsLinkout.name - ]) - .controller('OrganizationsList', OrganizationsList) - .controller('OrganizationsAdd', OrganizationsAdd) - .controller('OrganizationsEdit', OrganizationsEdit) - .factory('OrganizationForm', OrganizationForm) - .factory('OrganizationList', OrganizationList) - .config(['$stateProvider', 'stateDefinitionsProvider', '$stateExtenderProvider', - function($stateProvider, stateDefinitionsProvider, $stateExtenderProvider) { - let stateExtender = $stateExtenderProvider.$get(), - stateDefinitions = stateDefinitionsProvider.$get(); - - // lazily generate a tree of substates which will replace this node in ui-router's stateRegistry - // see: stateDefinition.factory for usage documentation - $stateProvider.state({ - name: 'organizations.**', - url: '/organizations', - lazyLoad: () => stateDefinitions.generateTree({ - parent: 'organizations', // top-most node in the generated tree - modes: ['add', 'edit'], // form nodes to generate - list: 'OrganizationList', - form: 'OrganizationForm', - controllers: { - list: 'OrganizationsList', - add: 'OrganizationsAdd', - edit: 'OrganizationsEdit' - }, - templates: { - list: templateUrl('organizations/list/organizations-list') - }, - ncyBreadcrumb: { - label: N_('ORGANIZATIONS') - }, - data: { - activityStream: true, - activityStreamTarget: 'organization' - }, - resolve: { - add: { - ConfigData: ['ConfigService', 'ProcessErrors', (ConfigService, ProcessErrors) => { - return ConfigService.getConfig() - .then(response => response) - .catch(({data, status}) => { - ProcessErrors(null, data, status, null, { - hdr: 'Error!', - msg: 'Failed to get config. GET returned status: ' + - 'status: ' + status - }); - }); - - }] - }, - edit: { - ConfigData: ['ConfigService', 'ProcessErrors', (ConfigService, ProcessErrors) => { - return ConfigService.getConfig() - .then(response => response) - .catch(({data, status}) => { - ProcessErrors(null, data, status, null, { - hdr: 'Error!', - msg: 'Failed to get config. GET returned status: ' + - 'status: ' + status - }); - }); - }], - InstanceGroupsData: ['$stateParams', 'Rest', 'GetBasePath', 'ProcessErrors', - function($stateParams, Rest, GetBasePath, ProcessErrors){ - let path = `${GetBasePath('organizations')}${$stateParams.organization_id}/instance_groups/`; - Rest.setUrl(path); - return Rest.get() - .then(({data}) => { - if (data.results.length > 0) { - return data.results; - } - }) - .catch(({data, status}) => { - ProcessErrors(null, data, status, null, { - hdr: 'Error!', - msg: 'Failed to get instance groups. GET returned ' + - 'status: ' + status - }); - }); - }], - isOrgAuditor: ['Rest', 'ProcessErrors', 'GetBasePath', 'i18n', '$stateParams', - function(Rest, ProcessErrors, GetBasePath, i18n, $stateParams) { - Rest.setUrl(`${GetBasePath('organizations')}?role_level=auditor_role&id=${$stateParams.organization_id}`); - return Rest.get() - .then(({data}) => { - return data.count > 0; - }) - .catch(({data, status}) => { - ProcessErrors(null, data, status, null, { - hdr: i18n._('Error!'), - msg: i18n._('Failed while checking to see if user is a notification administrator of this organization. GET returned ') + status - }); - }); - }], - isOrgAdmin: ['ProcessErrors', 'i18n', '$stateParams', 'OrgAdminLookup', - function(ProcessErrors, i18n, $stateParams, OrgAdminLookup) { - return OrgAdminLookup.checkForAdminAccess({organization: $stateParams.organization_id}) - .then(function(isOrgAdmin){ - return isOrgAdmin; - }) - .catch(({data, status}) => { - ProcessErrors(null, data, status, null, { - hdr: i18n._('Error!'), - msg: i18n._('Failed while checking to see if user is an administrator of this organization. GET returned ') + status - }); - }); - - }], - isNotificationAdmin: ['Rest', 'ProcessErrors', 'GetBasePath', 'i18n', - function(Rest, ProcessErrors, GetBasePath, i18n) { - Rest.setUrl(`${GetBasePath('organizations')}?role_level=notification_admin_role&page_size=1`); - return Rest.get() - .then(({data}) => { - return data.count > 0; - }) - .catch(({data, status}) => { - ProcessErrors(null, data, status, null, { - hdr: i18n._('Error!'), - msg: i18n._('Failed to get organizations for which this user is a notification admin. GET returned ') + status - }); - }); - }], - } - } - // concat manually-defined state definitions with generated defintions - }).then((generated) => { - let linkoutDefinitions = _.map(OrganizationsLinkoutStates, (state) => stateExtender.buildDefinition(state)); - return { - states: _(generated.states) - .concat(linkoutDefinitions) - .value() - }; - }) - }); - } - ]); diff --git a/awx/ui/client/src/organizations/organizations.block.less b/awx/ui/client/src/organizations/organizations.block.less deleted file mode 100644 index 9075f485cacc..000000000000 --- a/awx/ui/client/src/organizations/organizations.block.less +++ /dev/null @@ -1,5 +0,0 @@ -#organizations { - .List-noItems { - margin-top: 20px; - } -} \ No newline at end of file diff --git a/awx/ui/client/src/organizations/organizations.form.js b/awx/ui/client/src/organizations/organizations.form.js deleted file mode 100644 index db53cb63cf14..000000000000 --- a/awx/ui/client/src/organizations/organizations.form.js +++ /dev/null @@ -1,195 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name forms.function:Organizations - * @description This form is for adding/editing an organization -*/ - -export default ['NotificationsList', 'i18n', - function(NotificationsList, i18n) { - return function() { - var OrganizationFormObject = { - - addTitle: i18n._('NEW ORGANIZATION'), //Title in add mode - editTitle: '{{ name }}', //Title in edit mode - name: 'organization', //entity or model name in singular form - stateTree: 'organizations', - tabs: true, - - fields: { - name: { - label: i18n._('Name'), - type: 'text', - ngDisabled: '!(organization_obj.summary_fields.user_capabilities.edit || canAdd)', - required: true, - capitalize: false - }, - description: { - label: i18n._('Description'), - type: 'text', - ngDisabled: '!(organization_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - instance_groups: { - label: i18n._('Instance Groups'), - type: 'custom', - awPopOver: "

" + i18n._("Select the Instance Groups for this Organization to run on.") + "

", - dataTitle: i18n._('Instance Groups'), - dataContainer: 'body', - dataPlacement: 'right', - control: '', - }, - custom_virtualenv: { - label: i18n._('Ansible Environment'), - defaultText: i18n._('Use Default Environment'), - type: 'select', - ngOptions: 'venv for venv in custom_virtualenvs_options track by venv', - awPopOver: "

" + i18n._("Select the custom Python virtual environment for this organization to run on.") + "

", - dataTitle: i18n._('Ansible Environment'), - dataContainer: 'body', - dataPlacement: 'right', - ngDisabled: '!(organization_obj.summary_fields.user_capabilities.edit || canAdd)', - ngShow: 'custom_virtualenvs_visible' - }, - max_hosts: { - label: i18n._('Max Hosts'), - type: 'number', - integer: true, - min: 0, - max: 2147483647, - default: 0, - spinner: true, - dataTitle: i18n._('Max Hosts'), - dataPlacement: 'right', - dataContainer: 'body', - awPopOver: "

" + i18n._("The maximum number of hosts allowed to be managed by this organization. Value defaults to 0 which means no limit. Refer to the Ansible documentation for more details.") + "

", - ngDisabled: '!current_user.is_superuser', - ngShow: 'BRAND_NAME === "Tower"' - } - }, - - buttons: { //for now always generates -
- - - - - -
-
-
{{name || "New Job Template"}}
SURVEY
- -
-
- -
-
-
-
-
-
-
-
-
-
-
-
PREVIEW
-
-
PLEASE ADD A SURVEY PROMPT.
-
    -
  • - -
    - -
    - -
    - {{question.question_description}} -
    -
    - - -   - - - -
    - - -
    - -
    -
  • -
  • - Drop question here to reorder -
  • -
-
-
-
- - - - -
-
-
- -
-
- diff --git a/awx/ui/client/src/projects/add/projects-add.controller.js b/awx/ui/client/src/projects/add/projects-add.controller.js deleted file mode 100644 index 1983d4d4651c..000000000000 --- a/awx/ui/client/src/projects/add/projects-add.controller.js +++ /dev/null @@ -1,208 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['$scope', '$location', '$stateParams', 'GenerateForm', - 'ProjectsForm', 'Rest', 'Alert', 'ProcessErrors', 'GetBasePath', - 'GetProjectPath', 'GetChoices', 'Wait', '$state', 'CreateSelect2', 'i18n', - 'ConfigData', 'resolvedModels', 'scmCredentialType', 'insightsCredentialType', - function($scope, $location, $stateParams, GenerateForm, ProjectsForm, Rest, - Alert, ProcessErrors, GetBasePath, GetProjectPath, GetChoices, Wait, $state, - CreateSelect2, i18n, ConfigData, resolvedModels, scmCredentialType, insightsCredentialType) { - - let form = ProjectsForm(), - base = $location.path().replace(/^\//, '').split('/')[0], - defaultUrl = GetBasePath('projects'), - master = {}; - - init(); - - function init() { - $scope.canEditOrg = true; - const virtualEnvs = ConfigData.custom_virtualenvs || []; - $scope.custom_virtualenvs_options = virtualEnvs; - - const [ProjectModel] = resolvedModels; - $scope.canAdd = ProjectModel.options('actions.POST'); - - Rest.setUrl(GetBasePath('projects')); - Rest.options() - .then(({data}) => { - if (!data.actions.POST) { - $state.go("^"); - Alert(i18n._('Permission Error'), i18n._('You do not have permission to add a project.'), 'alert-info'); - } - }); - - CreateSelect2({ - element: '#project_custom_virtualenv', - multiple: false, - opts: $scope.custom_virtualenvs_options - }); - - // apply form definition's default field values - GenerateForm.applyDefaults(form, $scope); - } - - GetProjectPath({ scope: $scope, master: master }); - - if ($scope.removeChoicesReady) { - $scope.removeChoicesReady(); - } - $scope.removeChoicesReady = $scope.$on('choicesReady', function() { - var i; - for (i = 0; i < $scope.scm_type_options.length; i++) { - if ($scope.scm_type_options[i].value === '') { - $scope.scm_type_options[i].value = "manual"; - //$scope.scm_type = $scope.scm_type_options[i]; - break; - } - } - - CreateSelect2({ - element: '#project_scm_type', - multiple: false - }); - - $scope.scmRequired = false; - master.scm_type = $scope.scm_type; - }); - - // Load the list of options for Kind - GetChoices({ - scope: $scope, - url: defaultUrl, - field: 'scm_type', - variable: 'scm_type_options', - callback: 'choicesReady' - }); - CreateSelect2({ - element: '#local-path-select', - multiple: false - }); - - // Save - $scope.formSave = function() { - var i, fld, url, data = {}; - data = {}; - for (fld in form.fields) { - if (form.fields[fld].type === 'checkbox_group') { - for (i = 0; i < form.fields[fld].fields.length; i++) { - data[form.fields[fld].fields[i].name] = $scope[form.fields[fld].fields[i].name]; - } - } else { - if (form.fields[fld].type !== 'alertblock') { - data[fld] = $scope[fld]; - } - } - } - - if ($scope.scm_type.value === "manual") { - data.scm_type = ""; - data.local_path = $scope.local_path.value; - } else { - data.scm_type = $scope.scm_type.value; - delete data.local_path; - } - - url = (base === 'teams') ? GetBasePath('teams') + $stateParams.team_id + '/projects/' : defaultUrl; - Wait('start'); - Rest.setUrl(url); - Rest.post(data) - .then(({data}) => { - $scope.addedItem = data.id; - $state.go('projects.edit', { project_id: data.id }, { reload: true }); - }) - .catch(({data, status}) => { - Wait('stop'); - ProcessErrors($scope, data, status, form, { hdr: i18n._('Error!'), - msg: i18n._('Failed to create new project. POST returned status: ') + status }); - }); - }; - - $scope.scmChange = function() { - // When an scm_type is set, path is not required - if ($scope.scm_type) { - $scope.pathRequired = ($scope.scm_type.value === 'manual') ? true : false; - $scope.scmRequired = ($scope.scm_type.value !== 'manual') ? true : false; - $scope.scmBranchLabel = i18n._('SCM Branch'); - $scope.scmRefspecLabel = i18n._('SCM Refspec'); - // Dynamically update popover values - if ($scope.scm_type.value) { - if(($scope.lookupType === 'insights_credential' && $scope.scm_type.value !== 'insights') || ($scope.lookupType === 'scm_credential' && $scope.scm_type.value === 'insights')) { - $scope.credential = null; - $scope.credential_name = ''; - } - switch ($scope.scm_type.value) { - case 'git': - $scope.credentialLabel = "SCM " + i18n._("Credential"); - $scope.urlPopover = '

' + - i18n._('Example URLs for GIT SCM include:') + - '

  • https://github.com/ansible/ansible.git
  • ' + - '
  • git@github.com:ansible/ansible.git
  • git://servername.example.com/ansible.git
' + - '

' + i18n.sprintf(i18n._('%sNote:%s 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.'), '', ''); - $scope.credRequired = false; - $scope.lookupType = 'scm_credential'; - $scope.scmBranchLabel = i18n._('SCM Branch/Tag/Commit'); - break; - case 'svn': - $scope.credentialLabel = "SCM " + i18n._("Credential"); - $scope.urlPopover = '

' + i18n._('Example URLs for Subversion SCM include:') + '

' + - '
  • https://github.com/ansible/ansible
  • svn://servername.example.com/path
  • ' + - '
  • svn+ssh://servername.example.com/path
'; - $scope.credRequired = false; - $scope.lookupType = 'scm_credential'; - $scope.scmBranchLabel = i18n._('Revision #'); - break; - case 'hg': - $scope.credentialLabel = "SCM " + i18n._("Credential"); - $scope.urlPopover = '

' + i18n._('Example URLs for Mercurial SCM include:') + '

' + - '
  • https://bitbucket.org/username/project
  • ssh://hg@bitbucket.org/username/project
  • ' + - '
  • ssh://server.example.com/path
' + - '

' + i18n.sprintf(i18n._('%sNote:%s Mercurial does not support password authentication for SSH. ' + - 'Do not put the username and key in the URL. ' + - 'If using Bitbucket and SSH, do not supply your Bitbucket username.'), '', ''); - $scope.credRequired = false; - $scope.lookupType = 'scm_credential'; - $scope.scmBranchLabel = i18n._('SCM Branch/Tag/Revision'); - break; - case 'insights': - $scope.pathRequired = false; - $scope.scmRequired = false; - $scope.credRequired = true; - $scope.credentialLabel = "Credential"; - $scope.lookupType = 'insights_credential'; - break; - default: - $scope.credentialLabel = "SCM " + i18n._("Credential"); - $scope.urlPopover = '

' + i18n._('URL popover text') + '

'; - $scope.credRequired = false; - $scope.lookupType = 'scm_credential'; - } - } - } - }; - $scope.formCancel = function() { - $state.go('projects'); - }; - $scope.lookupCredential = function(){ - // Perform a lookup on the credential_type. Git, Mercurial, and Subversion - // all use SCM as their credential type. - let lookupCredentialType = scmCredentialType; - if ($scope.scm_type.value === 'insights') { - lookupCredentialType = insightsCredentialType; - } - $state.go('.credential', { - credential_search: { - credential_type: lookupCredentialType, - page_size: '5', - page: '1' - } - }); - }; - } -]; diff --git a/awx/ui/client/src/projects/edit/projects-edit.controller.js b/awx/ui/client/src/projects/edit/projects-edit.controller.js deleted file mode 100644 index a793425d2679..000000000000 --- a/awx/ui/client/src/projects/edit/projects-edit.controller.js +++ /dev/null @@ -1,341 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['$scope', '$rootScope', '$stateParams', 'ProjectsForm', 'Rest', - 'Alert', 'ProcessErrors', 'GenerateForm', 'Prompt', 'isNotificationAdmin', - 'GetBasePath', 'GetProjectPath', 'Authorization', 'GetChoices', 'Empty', - 'Wait', 'ProjectUpdate', '$state', 'CreateSelect2', 'ToggleNotification', - 'i18n', 'OrgAdminLookup', 'ConfigData', 'scmCredentialType', 'insightsCredentialType', - function($scope, $rootScope, $stateParams, ProjectsForm, Rest, Alert, - ProcessErrors, GenerateForm, Prompt, isNotificationAdmin, GetBasePath, - GetProjectPath, Authorization, GetChoices, Empty, Wait, ProjectUpdate, - $state, CreateSelect2, ToggleNotification, i18n, OrgAdminLookup, - ConfigData, scmCredentialType, insightsCredentialType) { - - let form = ProjectsForm(), - defaultUrl = GetBasePath('projects') + $stateParams.project_id + '/', - master = {}, - id = $stateParams.project_id; - - $scope.project_local_paths = []; - $scope.base_dir = ''; - const virtualEnvs = ConfigData.custom_virtualenvs || []; - $scope.custom_virtualenvs_options = virtualEnvs; - - $scope.$watch('project_obj.summary_fields.user_capabilities.edit', function(val) { - if (val === false) { - $scope.canAdd = false; - } - }); - - if ($scope.pathsReadyRemove) { - $scope.pathsReadyRemove(); - } - $scope.pathsReadyRemove = $scope.$on('pathsReady', function () { - CreateSelect2({ - element: '#local-path-select', - multiple: false - }); - }); - - // After the project is loaded, retrieve each related set - if ($scope.projectLoadedRemove) { - $scope.projectLoadedRemove(); - } - $scope.projectLoadedRemove = $scope.$on('projectLoaded', function() { - GetProjectPath({ scope: $scope, master: master }); - - $scope.pathRequired = ($scope.scm_type.value === 'manual') ? true : false; - $scope.scmRequired = ($scope.scm_type.value !== 'manual') ? true : false; - $scope.scmBranchLabel = ($scope.scm_type.value === 'svn') ? 'Revision #' : 'SCM Branch'; - Wait('stop'); - - $scope.scmChange(); - }); - - if ($scope.removeChoicesReady) { - $scope.removeChoicesReady(); - } - $scope.removeChoicesReady = $scope.$on('choicesReady', function() { - let i; - for (i = 0; i < $scope.scm_type_options.length; i++) { - if ($scope.scm_type_options[i].value === '') { - $scope.scm_type_options[i].value = "manual"; - break; - } - } - // Retrieve detail record and prepopulate the form - Rest.setUrl(defaultUrl); - Rest.get({ params: { id: id } }) - .then(({data}) => { - var fld, i; - for (fld in form.fields) { - if (form.fields[fld].type === 'checkbox_group') { - for (i = 0; i < form.fields[fld].fields.length; i++) { - $scope[form.fields[fld].fields[i].name] = data[form.fields[fld].fields[i].name]; - master[form.fields[fld].fields[i].name] = data[form.fields[fld].fields[i].name]; - } - } else { - if (data[fld] !== undefined) { - $scope[fld] = data[fld]; - master[fld] = data[fld]; - } - } - if (form.fields[fld].sourceModel && data.summary_fields && - data.summary_fields[form.fields[fld].sourceModel]) { - $scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] = - data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField]; - master[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] = - data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField]; - } - } - - data.scm_type = (Empty(data.scm_type)) ? 'manual' : data.scm_type; - for (i = 0; i < $scope.scm_type_options.length; i++) { - if ($scope.scm_type_options[i].value === data.scm_type) { - $scope.scm_type = $scope.scm_type_options[i]; - break; - } - } - - if ($scope.scm_type.value !== 'manual') { - $scope.pathRequired = false; - $scope.scmRequired = true; - } else { - $scope.pathRequired = true; - $scope.scmRequired = false; - } - - master.scm_type = $scope.scm_type; - CreateSelect2({ - element: '#project_scm_type', - multiple: false - }); - - $scope.scmBranchLabel = ($scope.scm_type.value === 'svn') ? 'Revision #' : 'SCM Branch'; - $scope.scm_update_tooltip = i18n._("Get latest SCM revision"); - $scope.scm_type_class = ""; - if (data.status === 'running' || data.status === 'updating') { - $scope.scm_update_tooltip = i18n._("SCM update currently running"); - $scope.scm_type_class = "btn-disabled"; - } - if (Empty(data.scm_type)) { - $scope.scm_update_tooltip = i18n._('Manual projects do not require an SCM update'); - $scope.scm_type_class = "btn-disabled"; - } - - OrgAdminLookup.checkForRoleLevelAdminAccess(data.organization, 'project_admin_role') - .then(function(canEditOrg){ - $scope.canEditOrg = canEditOrg; - }); - - CreateSelect2({ - element: '#project_custom_virtualenv', - multiple: false, - opts: $scope.custom_virtualenvs_options - }); - - $scope.project_obj = data; - // To toggle notifications a user needs to have an admin role on the project - // _and_ have at least a notification template admin role on an org. - // Only users with admin role on the project can edit it which is why we - // look at that user_capability - $scope.sufficientRoleForNotifToggle = isNotificationAdmin && data.summary_fields.user_capabilities.edit; - $scope.sufficientRoleForNotif = isNotificationAdmin || $scope.user_is_system_auditor; - $scope.name = data.name; - $scope.breadcrumb.project_name = data.name; - $scope.$emit('projectLoaded'); - Wait('stop'); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, form, { hdr: i18n._('Error!'), - msg: i18n.sprintf(i18n._('Failed to retrieve project: %s. GET status: '), id) + status - }); - }); - }); - - // Load the list of options for Kind - Wait('start'); - GetChoices({ - url: GetBasePath('projects'), - scope: $scope, - field: 'scm_type', - variable: 'scm_type_options', - callback: 'choicesReady' - }); - - $scope.toggleNotification = function(event, id, column) { - var notifier = this.notification; - try { - $(event.target).tooltip('hide'); - } catch (e) { - // ignore - } - ToggleNotification({ - scope: $scope, - url: $scope.project_obj.url, - notifier: notifier, - column: column, - callback: 'NotificationRefresh' - }); - }; - - // Save changes to the parent - $scope.formSave = function() { - var fld, i, params; - GenerateForm.clearApiErrors($scope); - Wait('start'); - $rootScope.flashMessage = null; - params = {}; - for (fld in form.fields) { - if (form.fields[fld].type === 'checkbox_group') { - for (i = 0; i < form.fields[fld].fields.length; i++) { - params[form.fields[fld].fields[i].name] = $scope[form.fields[fld].fields[i].name]; - } - } else { - if (form.fields[fld].type !== 'alertblock') { - params[fld] = $scope[fld]; - } - } - } - - if ($scope.scm_type.value === "manual") { - params.scm_type = ""; - params.local_path = $scope.local_path.value; - } else { - params.scm_type = $scope.scm_type.value; - delete params.local_path; - } - - Rest.setUrl(defaultUrl); - Rest.put(params) - .then(() => { - Wait('stop'); - $state.go($state.current, {}, { reload: true }); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, form, { hdr: i18n._('Error!'), msg: i18n.sprintf(i18n._('Failed to update project: %s. PUT status: '), id) + status }); - }); - }; - - // Related set: Delete button - $scope['delete'] = function(set, itm_id, name, title) { - var action = function() { - var url = GetBasePath('projects') + id + '/' + set + '/'; - $rootScope.flashMessage = null; - Rest.setUrl(url); - Rest.post({ id: itm_id, disassociate: 1 }) - .then(() => { - $('#prompt-modal').modal('hide'); - }) - .catch(({data, status}) => { - $('#prompt-modal').modal('hide'); - ProcessErrors($scope, data, status, null, { hdr: i18n._('Error!'), msg: i18n.sprintf(i18n._('Call to %s failed. POST returned status: '), url) + status }); - }); - }; - - Prompt({ - hdr: i18n._('Delete'), - body: '
' + i18n.sprintf(i18n._('Are you sure you want to remove the %s below from %s?'), title, $scope.name) + '
' + '
' + name + '
', - action: action, - actionText: i18n._('DELETE') - }); - }; - - $scope.scmChange = function() { - if ($scope.scm_type) { - $scope.pathRequired = ($scope.scm_type.value === 'manual') ? true : false; - $scope.scmRequired = ($scope.scm_type.value !== 'manual') ? true : false; - $scope.scmBranchLabel = i18n._('SCM Branch'); - $scope.scmRefspecLabel = i18n._('SCM Refspec'); - - // Dynamically update popover values - if ($scope.scm_type.value) { - if(($scope.lookupType === 'insights_credential' && $scope.scm_type.value !== 'insights') || ($scope.lookupType === 'scm_credential' && $scope.scm_type.value === 'insights')) { - $scope.credential = null; - $scope.credential_name = ''; - } - switch ($scope.scm_type.value) { - case 'git': - $scope.credentialLabel = "SCM " + i18n._("Credential"); - $scope.urlPopover = '

' + i18n._('Example URLs for GIT SCM include:') + '

  • https://github.com/ansible/ansible.git
  • ' + - '
  • git@github.com:ansible/ansible.git
  • git://servername.example.com/ansible.git
' + - '

' + i18n.sprintf(i18n._('%sNote:%s 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.'), '', ''); - $scope.credRequired = false; - $scope.lookupType = 'scm_credential'; - $scope.scmBranchLabel = i18n._('SCM Branch/Tag/Commit'); - break; - case 'svn': - $scope.credentialLabel = "SCM " + i18n._("Credential"); - $scope.urlPopover = '

' + i18n._('Example URLs for Subversion SCM include:') + '

' + - '
  • https://github.com/ansible/ansible
  • svn://servername.example.com/path
  • ' + - '
  • svn+ssh://servername.example.com/path
'; - $scope.credRequired = false; - $scope.lookupType = 'scm_credential'; - $scope.scmBranchLabel = i18n._('Revision #'); - break; - case 'hg': - $scope.credentialLabel = "SCM " + i18n._("Credential"); - $scope.urlPopover = '

' + i18n._('Example URLs for Mercurial SCM include:') + '

' + - '
  • https://bitbucket.org/username/project
  • ssh://hg@bitbucket.org/username/project
  • ' + - '
  • ssh://server.example.com/path
' + - '

' + i18n.sprintf(i18n._('%sNote:%s Mercurial does not support password authentication for SSH. ' + - 'Do not put the username and key in the URL. ' + - 'If using Bitbucket and SSH, do not supply your Bitbucket username.'), '', ''); - $scope.credRequired = false; - $scope.lookupType = 'scm_credential'; - $scope.scmBranchLabel = i18n._('SCM Branch/Tag/Revision'); - break; - case 'insights': - $scope.pathRequired = false; - $scope.scmRequired = false; - $scope.credRequired = true; - $scope.credentialLabel = "Credential"; - $scope.lookupType = 'insights_credential'; - break; - default: - $scope.credentialLabel = "SCM " + i18n._("Credential"); - $scope.urlPopover = '

' + i18n._('URL popover text'); - $scope.credRequired = false; - $scope.lookupType = 'scm_credential'; - } - } - } - }; - - $scope.lookupCredential = function(){ - // Perform a lookup on the credential_type. Git, Mercurial, and Subversion - // all use SCM as their credential type. - let lookupCredentialType = scmCredentialType; - if ($scope.scm_type.value === 'insights') { - lookupCredentialType = insightsCredentialType; - } - $state.go('.credential', { - credential_search: { - credential_type: lookupCredentialType, - page_size: '5', - page: '1' - } - }); - }; - - $scope.SCMUpdate = function() { - if ($scope.project_obj.scm_type === "Manual" || Empty($scope.project_obj.scm_type)) { - // ignore - } else if ($scope.project_obj.status === 'updating' || $scope.project_obj.status === 'running' || $scope.project_obj.status === 'pending') { - Alert(i18n._('Update in Progress'), i18n._('The SCM update process is running.'), 'alert-info'); - } else { - ProjectUpdate({ scope: $scope, project_id: $scope.project_obj.id }); - } - }; - - $scope.formCancel = function() { - $state.transitionTo('projects'); - }; - } -]; diff --git a/awx/ui/client/src/projects/factories/get-project-icon.factory.js b/awx/ui/client/src/projects/factories/get-project-icon.factory.js deleted file mode 100644 index 5234041e38d5..000000000000 --- a/awx/ui/client/src/projects/factories/get-project-icon.factory.js +++ /dev/null @@ -1,30 +0,0 @@ -export default - function GetProjectIcon() { - return function(status) { - var result = ''; - switch (status) { - case 'n/a': - case 'ok': - case 'never updated': - result = 'none'; - break; - case 'pending': - case 'waiting': - case 'new': - result = 'none'; - break; - case 'updating': - case 'running': - result = 'running'; - break; - case 'successful': - result = 'success'; - break; - case 'failed': - case 'missing': - case 'canceled': - result = 'error'; - } - return result; - }; - } diff --git a/awx/ui/client/src/projects/factories/get-project-path.factory.js b/awx/ui/client/src/projects/factories/get-project-path.factory.js deleted file mode 100644 index 6d8f7adeddde..000000000000 --- a/awx/ui/client/src/projects/factories/get-project-path.factory.js +++ /dev/null @@ -1,78 +0,0 @@ -export default - function GetProjectPath(i18n, Rest, GetBasePath, ProcessErrors) { - return function(params) { - var scope = params.scope, - master = params.master; - - function arraySort(data) { - //Sort nodes by name - var i, j, names = [], - newData = []; - for (i = 0; i < data.length; i++) { - names.push(data[i].value); - } - names.sort(); - for (j = 0; j < names.length; j++) { - for (i = 0; i < data.length; i++) { - if (data[i].value === names[j]) { - newData.push(data[i]); - } - } - } - return newData; - } - - scope.showMissingPlaybooksAlert = false; - - Rest.setUrl(GetBasePath('config')); - Rest.get() - .then(({data}) => { - var opts = [], i; - if (data.project_local_paths) { - for (i = 0; i < data.project_local_paths.length; i++) { - opts.push({ - label: data.project_local_paths[i], - value: data.project_local_paths[i] - }); - } - } - if (scope.local_path) { - // List only includes paths not assigned to projects, so add the - // path assigned to the current project. - opts.push({ - label: scope.local_path, - value: scope.local_path - }); - } - scope.project_local_paths = arraySort(opts); - if (scope.local_path) { - for (i = 0; scope.project_local_paths.length; i++) { - if (scope.project_local_paths[i].value === scope.local_path) { - scope.local_path = scope.project_local_paths[i]; - break; - } - } - } - scope.base_dir = data.project_base_dir || i18n._('You do not have access to view this property'); - master.local_path = scope.local_path; - master.base_dir = scope.base_dir; // Keep in master object so that it doesn't get - // wiped out on form reset. - if (opts.length === 0) { - // trigger display of alert block when scm_type == manual - scope.showMissingPlaybooksAlert = true; - } - scope.$emit('pathsReady'); - }) - .catch(({data, status}) => { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: i18n._('Failed to access API config. GET status: ') + status }); - }); - }; - } - -GetProjectPath.$inject = - [ 'i18n', - 'Rest', - 'GetBasePath', - 'ProcessErrors' - ]; diff --git a/awx/ui/client/src/projects/factories/get-project-tool-tip.factory.js b/awx/ui/client/src/projects/factories/get-project-tool-tip.factory.js deleted file mode 100644 index 834a6c9d99ac..000000000000 --- a/awx/ui/client/src/projects/factories/get-project-tool-tip.factory.js +++ /dev/null @@ -1,38 +0,0 @@ -export default - function GetProjectToolTip(i18n) { - return function(status) { - var result = ''; - switch (status) { - case 'n/a': - case 'ok': - case 'never updated': - result = i18n._('No SCM updates have run for this project'); - break; - case 'pending': - case 'waiting': - case 'new': - result = i18n._('Update queued. Click for details'); - break; - case 'updating': - case 'running': - result = i18n._('Update running. Click for details'); - break; - case 'successful': - result = i18n._('Update succeeded. Click for details'); - break; - case 'failed': - result = i18n._('Update failed. Click for details'); - break; - case 'missing': - result = i18n._('Update missing. Click for details'); - break; - case 'canceled': - result = i18n._('Update canceled. Click for details'); - break; - } - return result; - }; - } - -GetProjectToolTip.$inject = - [ 'i18n' ]; diff --git a/awx/ui/client/src/projects/main.js b/awx/ui/client/src/projects/main.js deleted file mode 100644 index 2f45552b45a1..000000000000 --- a/awx/ui/client/src/projects/main.js +++ /dev/null @@ -1,162 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import ProjectsAdd from './add/projects-add.controller'; -import ProjectsEdit from './edit/projects-edit.controller'; -import ProjectsForm from './projects.form'; -import ProjectList from './projects.list'; -import GetProjectPath from './factories/get-project-path.factory'; -import GetProjectIcon from './factories/get-project-icon.factory'; -import GetProjectToolTip from './factories/get-project-tool-tip.factory'; -import { - projectsSchedulesListRoute, - projectsSchedulesAddRoute, - projectsSchedulesEditRoute -} from '../scheduler/schedules.route'; - -import ProjectsTemplatesRoute from '~features/templates/routes/projectsTemplatesList.route'; -import projectsListRoute from '~features/projects/routes/projectsList.route.js'; - - -function ResolveScmCredentialType (GetBasePath, Rest, ProcessErrors) { - Rest.setUrl(GetBasePath('credential_types') + '?kind=scm'); - - return Rest.get() - .then(({ data }) => { - return data.results[0].id; - }) - .catch(({ data, status }) => { - ProcessErrors(null, data, status, null, { - hdr: 'Error!', - msg: 'Failed to get credential type data: ' + status - }); - }); -} - -function ResolveInsightsCredentialType (GetBasePath, Rest, ProcessErrors) { - Rest.setUrl(GetBasePath('credential_types') + '?name=Insights'); - - return Rest.get() - .then(({ data }) => { - return data.results[0].id; - }) - .catch(({ data, status }) => { - ProcessErrors(null, data, status, null, { - hdr: 'Error!', - msg: 'Failed to get credential type data: ' + status - }); - }); -} - -ResolveScmCredentialType.$inject = ['GetBasePath', 'Rest', 'ProcessErrors']; -ResolveInsightsCredentialType.$inject = ['GetBasePath', 'Rest', 'ProcessErrors']; - - -export default -angular.module('Projects', []) - .controller('ProjectsAdd', ProjectsAdd) - .controller('ProjectsEdit', ProjectsEdit) - .factory('GetProjectPath', GetProjectPath) - .factory('GetProjectIcon', GetProjectIcon) - .factory('GetProjectToolTip', GetProjectToolTip) - .factory('ProjectsForm', ProjectsForm) - .factory('ProjectList', ProjectList) - .config(['$stateProvider', 'stateDefinitionsProvider', '$stateExtenderProvider', - function($stateProvider, stateDefinitionsProvider,$stateExtenderProvider) { - let stateDefinitions = stateDefinitionsProvider.$get(); - let stateExtender = $stateExtenderProvider.$get(); - - const projectsAddName = 'projects.add'; - const projectsEditName = 'projects.edit'; - - function generateStateTree() { - let projectAdd = stateDefinitions.generateTree({ - name: projectsAddName, - url: '/add', - modes: ['add'], - form: 'ProjectsForm', - controllers: { - add: 'ProjectsAdd', - }, - }) - .then(res => { - const stateIndex = res.states.findIndex(s => s.name === projectsAddName); - - res.states[stateIndex].resolve.scmCredentialType = ResolveScmCredentialType; - res.states[stateIndex].resolve.insightsCredentialType = ResolveInsightsCredentialType; - - return res; - }); - - let projectEdit = stateDefinitions.generateTree({ - name: projectsEditName, - url: '/:project_id', - modes: ['edit'], - form: 'ProjectsForm', - controllers: { - edit: 'ProjectsEdit', - }, - data: { - activityStream: true, - activityStreamTarget: 'project', - activityStreamId: 'project_id' - }, - breadcrumbs: { - edit: '{{breadcrumb.project_name}}' - }, - resolve: { - edit: { - isNotificationAdmin: ['Rest', 'ProcessErrors', 'GetBasePath', 'i18n', - function(Rest, ProcessErrors, GetBasePath, i18n) { - Rest.setUrl(`${GetBasePath('organizations')}?role_level=notification_admin_role&page_size=1`); - return Rest.get() - .then(({data}) => { - return data.count > 0; - }) - .catch(({data, status}) => { - ProcessErrors(null, data, status, null, { - hdr: i18n._('Error!'), - msg: i18n._('Failed to get organizations for which this user is a notification administrator. GET returned ') + status - }); - }); - }] - } - } - }) - .then(res => { - const stateIndex = res.states.findIndex(s => s.name === projectsEditName); - - res.states[stateIndex].resolve.scmCredentialType = ResolveScmCredentialType; - res.states[stateIndex].resolve.insightsCredentialType = ResolveInsightsCredentialType; - - return res; - }); - - return Promise.all([ - projectAdd, - projectEdit, - ]).then((generated) => { - return { - states: _.reduce(generated, (result, definition) => { - return result.concat(definition.states); - }, [ - stateExtender.buildDefinition(projectsListRoute), - stateExtender.buildDefinition(ProjectsTemplatesRoute), - stateExtender.buildDefinition(projectsSchedulesListRoute), - stateExtender.buildDefinition(projectsSchedulesAddRoute), - stateExtender.buildDefinition(projectsSchedulesEditRoute) - ]) - }; - }); - } - let stateTree = { - name: 'projects.**', - url: '/projects', - lazyLoad: () => generateStateTree() - }; - $stateProvider.state(stateTree); - } - ]); diff --git a/awx/ui/client/src/projects/projects.form.js b/awx/ui/client/src/projects/projects.form.js deleted file mode 100644 index 3ba529749e76..000000000000 --- a/awx/ui/client/src/projects/projects.form.js +++ /dev/null @@ -1,342 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name forms.function:Projects - * @description This form is for adding/editing projects -*/ - -export default ['i18n', 'NotificationsList', 'TemplateList', - function(i18n, NotificationsList, TemplateList) { - return function() { - var projectsFormObj = { - addTitle: i18n._('NEW PROJECT'), - editTitle: '{{ name }}', - name: 'project', - basePath: 'projects', - // the top-most node of generated state tree - stateTree: 'projects', - forceListeners: true, - tabs: true, - subFormTitles: { - sourceSubForm: i18n._('Source Details'), - }, - fields: { - name: { - label: i18n._('Name'), - type: 'text', - ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)', - required: true, - capitalize: false - }, - description: { - label: i18n._('Description'), - type: 'text', - ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - organization: { - label: i18n._('Organization'), - type: 'lookup', - list: 'OrganizationList', - sourceModel: 'organization', - basePath: 'organizations', - sourceField: 'name', - dataTitle: i18n._('Organization'), - required: true, - dataContainer: 'body', - dataPlacement: 'right', - ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd) || !canEditOrg', - awLookupWhen: '(project_obj.summary_fields.user_capabilities.edit || canAdd) && canEditOrg' - }, - scm_type: { - label: i18n._('SCM Type'), - type: 'select', - class: 'Form-dropDown--scmType', - defaultText: i18n._('Choose an SCM Type'), - ngOptions: 'type.label for type in scm_type_options track by type.value', - ngChange: 'scmChange()', - required: true, - hasSubForm: true, - ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - missing_path_alert: { - type: 'alertblock', - ngShow: "showMissingPlaybooksAlert && scm_type.value == 'manual'", - alertTxt: '

WARNING: There are no available playbook directories in {{ 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 {{BRAND_NAME}} directly retrieve your playbooks from source control using the SCM Type option above.

', - closeable: false - }, - base_dir: { - label: i18n._('Project Base Path'), - type: 'text', - class: 'Form-textUneditable', - showonly: true, - ngShow: "scm_type.value == 'manual' " , - awPopOver: '

' + i18n._('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.') + '

' + - '

' + i18n.sprintf(i18n._('Change %s when deploying {{BRAND_NAME}} to change this location.'), 'PROJECTS_ROOT') + '

', - dataTitle: i18n._('Project Base Path'), - dataContainer: 'body', - dataPlacement: 'right', - ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - local_path: { - label: i18n._('Playbook Directory'), - type: 'select', - id: 'local-path-select', - ngOptions: 'path.label for path in project_local_paths', - awRequiredWhen: { - reqExpression: "pathRequired", - init: false - }, - ngShow: "scm_type.value == 'manual' && !showMissingPlaybooksAlert", - awPopOver: '

' + i18n._('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.') + '

', - dataTitle: i18n._('Project Path'), - dataContainer: 'body', - dataPlacement: 'right', - ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - scm_url: { - label: i18n._('SCM URL'), - type: 'text', - ngShow: "scm_type && scm_type.value !== 'manual' && scm_type.value !== 'insights' ", - awRequiredWhen: { - reqExpression: "scmRequired", - init: false - }, - subForm: 'sourceSubForm', - hideSubForm: "scm_type.value === 'manual'", - awPopOverWatch: "urlPopover", - awPopOver: "set in controllers/projects", - dataTitle: i18n._('SCM URL'), - dataContainer: 'body', - dataPlacement: 'right', - ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - scm_branch: { - labelBind: "scmBranchLabel", - type: 'text', - ngShow: "scm_type && scm_type.value !== 'manual' && scm_type.value !== 'insights'", - ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)', - awPopOver: '

' + i18n._("Branch to checkout. In addition to branches, you can input tags, commit hashes, and arbitrary refs. Some commit hashes and refs may not be availble unless you also provide a custom refspec.") + '

', - dataTitle: i18n._('SCM Branch'), - subForm: 'sourceSubForm', - }, - scm_refspec: { - labelBind: "scmRefspecLabel", - type: 'text', - ngShow: "scm_type && scm_type.value === 'git'", - ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)', - awPopOver: '

' + i18n._('A refspec to fetch (passed to the Ansible git module). This parameter allows access to references via the branch field not otherwise available.') + '

' + - '

' + i18n._('NOTE: This field assumes the remote name is "origin".') + '

' + - '

' + i18n._('Examples include:') + '

' + - '

  • refs/*:refs/remotes/origin/*
  • ' + - '
  • refs/pull/62/head:refs/remotes/origin/pull/62/head
' + - '

' + i18n._('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`.') + - '

' + - '

' + i18n._('For more information, refer to the') + ' ' + i18n._('Ansible Tower Documentation') + '.

', - dataTitle: i18n._('SCM Refspec'), - subForm: 'sourceSubForm', - }, - credential: { - labelBind: 'credentialLabel', - type: 'lookup', - basePath: 'credentials', - list: 'CredentialList', - search: { - credential_type: null - }, - ngClick: 'lookupCredential()', - awRequiredWhen: { - reqExpression: "credRequired", - init: false - }, - ngShow: "scm_type && scm_type.value !== 'manual'", - sourceModel: 'credential', - awLookupType: '{{lookupType}}', - sourceField: 'name', - ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)', - subForm: 'sourceSubForm' - }, - checkbox_group: { - label: i18n._('SCM Update Options'), - type: 'checkbox_group', - ngShow: "scm_type && scm_type.value !== 'manual'", - subForm: 'sourceSubForm', - fields: [{ - name: 'scm_clean', - label: i18n._('Clean'), - type: 'checkbox', - awPopOver: '

' + i18n._('Remove any local modifications prior to performing an update.') + '

', - dataTitle: i18n._('SCM Clean'), - dataContainer: 'body', - dataPlacement: 'right', - labelClass: 'checkbox-options stack-inline', - ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)' - }, { - name: 'scm_delete_on_update', - label: i18n._('Delete on Update'), - type: 'checkbox', - awPopOver: '

' + i18n._('Delete the local repository in its entirety prior to performing an update.') + '

' + i18n._('Depending on the size of the ' + - 'repository this may significantly increase the amount of time required to complete an update.') + '

', - dataTitle: i18n._('SCM Delete'), - dataContainer: 'body', - dataPlacement: 'right', - labelClass: 'checkbox-options stack-inline', - ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)' - }, { - name: 'scm_update_on_launch', - label: i18n._('Update Revision on Launch'), - type: 'checkbox', - awPopOver: '

' + i18n._('Each time a job runs using this project, update the revision of the project prior to starting the job.') + '

', - dataTitle: i18n._('SCM Update'), - dataContainer: 'body', - dataPlacement: 'right', - labelClass: 'checkbox-options stack-inline', - ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - { - name: 'allow_override', - label: i18n._('Allow branch override'), - type: 'checkbox', - awPopOver: '

' + i18n._('Allow changing the SCM branch or revision in a job template that uses this project.') + '

', - dataTitle: i18n._('Allow branch override'), - dataContainer: 'body', - dataPlacement: 'right', - labelClass: 'checkbox-options stack-inline', - ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)', - ngShow: "scm_type && scm_type.value !== 'insights'", - }] - }, - scm_update_cache_timeout: { - label: i18n.sprintf(i18n._('Cache Timeout%s (seconds)%s'), '', ''), - id: 'scm-cache-timeout', - type: 'number', - integer: true, - min: 0, - ngShow: "scm_update_on_launch && scm_type.value !== 'manual'", - spinner: true, - "default": '0', - awPopOver: '

' + i18n._('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.') + '

', - dataTitle: i18n._('Cache Timeout'), - dataPlacement: 'right', - dataContainer: "body", - ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)', - subForm: 'sourceSubForm' - }, - custom_virtualenv: { - label: i18n._('Ansible Environment'), - type: 'select', - defaultText: i18n._('Use Default Environment'), - ngOptions: 'venv for venv in custom_virtualenvs_options track by venv', - awPopOver: "

" + i18n._("Select the custom Python virtual environment for this project to run on.") + "

", - dataTitle: i18n._('Ansible Environment'), - dataContainer: 'body', - dataPlacement: 'right', - ngDisabled: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)', - ngShow: 'custom_virtualenvs_options.length > 1' - }, - }, - - buttons: { - cancel: { - ngClick: 'formCancel()', - ngShow: '(project_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - close: { - ngClick: 'formCancel()', - ngShow: '!(project_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - save: { - ngClick: 'formSave()', - ngDisabled: true, - ngShow: '(project_obj.summary_fields.user_capabilities.edit || canAdd)' - } - }, - - related: { - permissions: { - name: 'permissions', - awToolTip: i18n._('Please save before assigning permissions.'), - djangoModel: 'access_list', - dataPlacement: 'top', - basePath: 'api/v2/projects/{{$stateParams.project_id}}/access_list/', - search: { - order_by: 'username' - }, - type: 'collection', - title: i18n._('Permissions'), - iterator: 'permission', - index: false, - open: false, - actions: { - add: { - ngClick: "$state.go('.add')", - label: i18n._('Add'), - awToolTip: i18n._('Add a permission'), - actionClass: 'at-Button--add', - actionId: 'button-add', - ngShow: '(project_obj.summary_fields.user_capabilities.edit || canAdd)' - } - }, - - fields: { - username: { - key: true, - label: i18n._('User'), - linkBase: 'users', - columnClass: 'col-sm-3 col-xs-4' - }, - role: { - label: i18n._('Role'), - type: 'role', - nosort: true, - columnClass: 'col-sm-4 col-xs-4', - }, - team_roles: { - label: i18n._('Team Roles'), - type: 'team_roles', - nosort: true, - columnClass: 'col-sm-5 col-xs-4', - } - } - }, - notifications: { - include: "NotificationsList", - }, - templates: { - include: "TemplateList", - }, - schedules: { - title: i18n._('Schedules'), - skipGenerator: true, - ngClick: "$state.go('projects.edit.schedules')" - } - } - - }; - - var itm; - - for (itm in projectsFormObj.related) { - if (projectsFormObj.related[itm].include === "TemplateList") { - projectsFormObj.related[itm] = _.clone(TemplateList); - projectsFormObj.related[itm].title = i18n._('Job Templates'); - projectsFormObj.related[itm].skipGenerator = true; - } - if (projectsFormObj.related[itm].include === "NotificationsList") { - projectsFormObj.related[itm] = NotificationsList; - projectsFormObj.related[itm].generateList = true; // tell form generator to call list generator and inject a list - } - } - return projectsFormObj; -};}]; diff --git a/awx/ui/client/src/projects/projects.list.js b/awx/ui/client/src/projects/projects.list.js deleted file mode 100644 index 9efc89bd6714..000000000000 --- a/awx/ui/client/src/projects/projects.list.js +++ /dev/null @@ -1,134 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['i18n', function(i18n) { - return { - - name: 'projects', - iterator: 'project', - basePath: 'projects', - selectTitle: i18n._('Add Project'), - editTitle: i18n._('PROJECTS'), - listTitle: i18n._('PROJECTS'), - selectInstructions: '

Select existing projects by clicking each project or checking the related checkbox. When finished, click the blue ' + - 'Select button, located bottom right.

Create a new project by clicking the button.

', - index: false, - hover: true, - emptyListText: i18n._('No Projects Have Been Created'), - layoutClass: 'List-staticColumnLayout--statusOrCheckbox', - staticColumns: [ - { - field: 'status', - content: { - label: '', - iconOnly: true, - ngClick: 'showSCMStatus(project.id)', - awToolTip: '{{ project.statusTip }}', - dataTipWatch: 'project.statusTip', - dataPlacement: 'right', - icon: "icon-job-{{ project.statusIcon }}", - columnClass: "List-staticColumn--smallStatus", - nosort: true, - excludeModal: true - } - } - ], - - fields: { - name: { - key: true, - label: i18n._('Name'), - columnClass: "col-md-3 col-sm-6 List-staticColumnAdjacent", - modalColumnClass: 'col-md-8', - awToolTip: '{{project.description | sanitize}}', - dataPlacement: 'top' - }, - scm_type: { - label: i18n._('Type'), - ngBind: 'project.type_label', - excludeModal: true, - columnClass: 'col-md-2 col-sm-2' - }, - scm_revision: { - label: i18n._('Revision'), - excludeModal: true, - columnClass: 'd-none d-md-flex col-md-2', - type: 'revision' - }, - last_updated: { - label: i18n._('Last Updated'), - filter: "longDate", - columnClass: "d-none d-md-flex col-md-2", - excludeModal: true - } - }, - - actions: { - refresh: { - mode: 'all', - awToolTip: i18n._("Refresh the page"), - ngClick: "refresh()", - ngShow: "socketStatus === 'error'", - actionClass: 'btn List-buttonDefault', - buttonContent: i18n._('REFRESH') - }, - add: { - mode: 'all', // One of: edit, select, all - ngClick: 'addProject()', - awToolTip: i18n._('Create a new project'), - actionClass: 'at-Button--add', - actionId: 'button-add', - ngShow: "canAdd" - } - }, - - fieldActions: { - - columnClass: 'col-md-3 col-sm-4', - edit: { - ngClick: "editProject(project.id)", - awToolTip: i18n._('Edit the project'), - dataPlacement: 'top', - ngShow: "project.summary_fields.user_capabilities.edit" - }, - scm_update: { - ngClick: 'SCMUpdate(project.id, $event)', - awToolTip: "{{ project.scm_update_tooltip }}", - dataTipWatch: "project.scm_update_tooltip", - ngClass: "project.scm_type_class", - dataPlacement: 'top', - ngShow: "project.summary_fields.user_capabilities.start" - }, - copy: { - label: i18n._('Copy'), - ngClick: 'copyProject(project)', - "class": 'btn-danger btn-xs', - awToolTip: i18n._('Copy project'), - dataPlacement: 'top', - ngShow: 'project.summary_fields.user_capabilities.copy' - }, - view: { - ngClick: "editProject(project.id)", - awToolTip: i18n._('View the project'), - dataPlacement: 'top', - ngShow: "!project.summary_fields.user_capabilities.edit", - icon: 'fa-eye', - }, - "delete": { - ngClick: "deleteProject(project.id, project.name)", - awToolTip: i18n._('Delete the project'), - ngShow: "(project.status !== 'updating' && project.status !== 'running' && project.status !== 'pending' && project.status !== 'waiting') && project.summary_fields.user_capabilities.delete", - dataPlacement: 'top' - }, - cancel: { - ngClick: "cancelUpdate(project)", - awToolTip: i18n._('Cancel the SCM update'), - ngShow: "(project.status == 'updating' || project.status == 'running' || project.status == 'pending' || project.status == 'waiting') && project.summary_fields.user_capabilities.start", - dataPlacement: 'top', - ngDisabled: "project.pending_cancellation || project.status == 'canceled'" - } - } - };}]; diff --git a/awx/ui/client/src/rest/get-choices.factory.js b/awx/ui/client/src/rest/get-choices.factory.js deleted file mode 100644 index e9f7e4398da3..000000000000 --- a/awx/ui/client/src/rest/get-choices.factory.js +++ /dev/null @@ -1,57 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name helpers.function:Permissions - * @description - * Gets permission type labels from the API and sets them as the permissions labels on the relevant radio buttons - * - */ - - export default - ['Rest', 'ProcessErrors', function(Rest, ProcessErrors) { - return function (params) { - var scope = params.scope, - url = params.url, - field = params.field, - options = params.options; - - if (!options) { - // Auto populate the field if there is only one result - Rest.setUrl(url); - return Rest.options() - .then(function (data) { - data = data.data; - var choices = data.actions.GET[field].choices; - - // manually add the adhoc label to the choices object if - // the permission_type field - if (field === "permission_type") { - choices.push(["adhoc", - data.actions.GET.run_ad_hoc_commands.help_text]); - } - - return choices; - }) - .catch(function (data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to get ' + field + ' labels. Options requrest returned status: ' + status }); - }); - } else { - var choices = options.actions.GET[field].choices; - - // manually add the adhoc label to the choices object if - // the permission_type field - if (field === "permission_type") { - choices.push(["adhoc", - options.actions.GET.run_ad_hoc_commands.help_text]); - } - - return choices; - } - }; - }]; diff --git a/awx/ui/client/src/rest/get-labels.factory.js b/awx/ui/client/src/rest/get-labels.factory.js deleted file mode 100644 index 2b20ca70bb4a..000000000000 --- a/awx/ui/client/src/rest/get-labels.factory.js +++ /dev/null @@ -1,26 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name helpers.function:Permissions - * @description - * Gets permission type labels from the API and sets them as the permissions labels on the relevant radio buttons - * - */ - - export default - [function() { - return function (params) { - // convert the choices from the API from the format - // [["read", "Read Inventory"], ...] to - // {read: "Read Inventory", ...} - return params.choices.reduce(function(obj, kvp) { - obj[kvp[0]] = kvp[1]; - return obj; - }, {}); - }; - }]; diff --git a/awx/ui/client/src/rest/interceptors.service.js b/awx/ui/client/src/rest/interceptors.service.js deleted file mode 100644 index 4fdb1f0279ae..000000000000 --- a/awx/ui/client/src/rest/interceptors.service.js +++ /dev/null @@ -1,42 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - export default - [ '$rootScope', '$q', '$injector', '$cookies', - function ($rootScope, $q, $injector, $cookies) { - return { - request: function (config) { - config.headers['X-Requested-With'] = 'XMLHttpRequest'; - if (['GET', 'HEAD', 'OPTIONS'].indexOf(config.method)===-1) { - config.headers['X-CSRFToken'] = $cookies.get('csrftoken'); - } - return config; - }, - response: function(config) { - if(config.headers('Session-Timeout') !== null){ - $rootScope.loginConfig.promise.then(function () { - $AnsibleConfig.session_timeout = Number(config.headers('Session-Timeout')); - }); - } - return config; - }, - responseError: function(rejection){ - if(rejection && rejection.data && rejection.data.detail && rejection.data.detail === "Maximum per-user sessions reached"){ - $rootScope.sessionTimer.expireSession('session_limit'); - var state = $injector.get('$state'); - state.go('signOut'); - return $q.reject(rejection); - } - return $q.reject(rejection); - } - }; - }]; diff --git a/awx/ui/client/src/rest/main.js b/awx/ui/client/src/rest/main.js deleted file mode 100644 index 5466355a1212..000000000000 --- a/awx/ui/client/src/rest/main.js +++ /dev/null @@ -1,20 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import restServicesFactory from './restServices.factory'; -import interceptors from './interceptors.service'; -import fieldChoices from './get-choices.factory'; -import fieldLabels from './get-labels.factory'; - -export default - angular.module('RestServices', []) - .config(['$httpProvider', function($httpProvider) { - $httpProvider.interceptors.push('RestInterceptor'); - }]) - .factory('Rest', restServicesFactory) - .service('RestInterceptor', interceptors) - .factory('fieldChoices', fieldChoices) - .factory('fieldLabels', fieldLabels); diff --git a/awx/ui/client/src/rest/restServices.factory.js b/awx/ui/client/src/rest/restServices.factory.js deleted file mode 100644 index b65a9a1c9644..000000000000 --- a/awx/ui/client/src/rest/restServices.factory.js +++ /dev/null @@ -1,210 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name shared.function:RestServices - * @description - * - * A wrapper for angular's $http service. Post user authentication API requests should go through Rest rather than directly using $http. The goal is to decouple - * the application from $http, allowing Rest or anything that provides the same methods to handle setting authentication headers and performing tasks such as checking - * for an expired authentication token. Having this buffer between the application and $http will prove useful should the authentication scheme change. - * - * #.setUrl() - * - * Before calling an action methods (i.e. get, put, post, destroy, options) to send the request, call setUrl. Pass a string containing the URL endpoint and any parameters. - * Note that $http will automaticall encode the URL, replacing spaces and special characters with appropriate %hex codes. Example URL values might include: - * - * ``` - * /api/v2/inventories/9/ - * /api/v2/credentials/?name=SSH Key&credential_type__namespace=ssh - * ``` - * - * When constructing the URL be sure to use the GetBasePath() method found in js/shared/Utilities.js. GetBasePath uses the response objects from /api and - * /api//to construct the base portion of the path. This way the API version number and base endpoints are not hard-coded within the application. - * - * #Action methods: .get(), put(), .post(), .destroy(), options() - * - * Use the method matching the REST action to be performed. In the case of put, post and destroy a JSON object can be passed as a parameter. If included, - * it will be sent as part of the request. - * - * In every case a call to an action method returns a promise object, allowing the caller to pass reponse functions to the promise methods. The functions can inspect - * the respoinse details and initiate action. For example: - * - * ``` - * var url = GetBasePath('inventories') + $stateParams.id + '/'; - * Rest.setUrl(url); - * Rest.get() - * .then(({data}) => { - * // review the data object and take action - * }) - * .error(function(status, data) { - * // handle the error - typically a call to ProcessErrors() found in js/shared/Utitlties.js - * }); - * ``` - * - * ##Options Reqeusts - * - * options() requests are used by the GetChoices() method found in js/shared/Utilities.js. Sending an Options request to an API endpoint returns an object that includes - * possible values for fields that are typically presented in the UI as dropdowns or <select> elements. GetChoices will inspect the response object for the request - * field and return an array of { label: 'Choice Label', value: 'choice 1' } objects. - * - */ - -export default - ['$http', '$rootScope', '$q', - function ($http, $rootScope, $q) { - return { - - headers: {}, - - setUrl: function (url) { - // Ensure that a trailing slash is present at the end of the url (before query params, etc) - this.url = url.replace(/\/?(\?|#|$)/, '/$1'); - }, - checkExpired: function () { - return ($rootScope.sessionTimer) ? $rootScope.sessionTimer.isExpired() : false; - }, - pReplace: function () { - //in our url, replace :xx params with a value, assuming - //we can find it in user supplied params. - var key, rgx; - for (key in this.params) { - rgx = new RegExp("\\:" + key, 'gm'); - if (rgx.test(this.url)) { - this.url = this.url.replace(rgx, this.params[key]); - delete this.params[key]; - } - } - }, - createResponse: function (data, status) { - // Simulate an http response when a token error occurs - // http://stackoverflow.com/questions/18243286/angularjs-promises-simulate-http-promises - - var promise = $q.reject({ - data: data, - status: status - }); - promise.success = function (fn) { - promise.then(function (response) { - fn(response.data, response.status); - }, null); - return promise; - }; - promise.error = function (fn) { - promise.then(null, function (response) { - fn(response.data, response.status); - }); - return promise; - }; - return promise; - }, - - setHeader: function (hdr) { - // Pass in { key: value } pairs to be added to the header - for (var h in hdr) { - this.headers[h] = hdr[h]; - } - }, - get: function (args) { - args = (args) ? args : {}; - this.params = (args.params) ? args.params : null; - this.pReplace(); - var expired = this.checkExpired(); - if (expired) { - return this.createResponse({ - detail: 'Session is expired' - }, 401); - } else { - return $http({ - method: 'GET', - url: this.url, - headers: this.headers, - params: this.params - }); - } - }, - post: function (data) { - var expired = this.checkExpired(); - if (expired) { - return this.createResponse({ - detail: 'Session is expired' - }, 401); - } else { - return $http({ - method: 'POST', - url: this.url, - headers: this.headers, - data: data - }); - } - }, - put: function (data) { - var expired = this.checkExpired(); - if (expired) { - return this.createResponse({ - detail: 'Session is expired' - }, 401); - } else { - return $http({ - method: 'PUT', - url: this.url, - headers: this.headers, - data: data - }); - } - }, - patch: function (data) { - var expired = this.checkExpired(); - if (expired) { - return this.createResponse({ - detail: 'Session is expired' - }, 401); - } else { - return $http({ - method: 'PATCH', - url: this.url, - headers: this.headers, - data: data - }); - } - }, - destroy: function (data) { - var expired = this.checkExpired(); - if (expired) { - return this.createResponse({ - detail: 'Session is expired' - }, 401); - } else { - return $http({ - method: 'DELETE', - url: this.url, - headers: this.headers, - data: data - }); - } - }, - options: function (cache) { - var params, - expired = this.checkExpired(); - if (expired) { - return this.createResponse({ - detail: 'Session is expired' - }, 401); - } else { - params = { - method: 'OPTIONS', - url: this.url, - headers: this.headers, - data: '', - cache: (cache ? true : false) - }; - return $http(params); - } - } - }; - } - ]; diff --git a/awx/ui/client/src/scheduler/editSchedule.resolve.js b/awx/ui/client/src/scheduler/editSchedule.resolve.js deleted file mode 100644 index f65dc17b9c3d..000000000000 --- a/awx/ui/client/src/scheduler/editSchedule.resolve.js +++ /dev/null @@ -1,38 +0,0 @@ -function editScheduleResolve () { - const resolve = { - scheduleResolve: ['Rest', '$stateParams', 'GetBasePath', 'ProcessErrors', - (Rest, $stateParams, GetBasePath, ProcessErrors) => { - var path = `${GetBasePath('schedules')}${parseInt($stateParams.schedule_id)}/`; - Rest.setUrl(path); - return Rest.get() - .then(function(data) { - return (data.data); - }).catch(function(response) { - ProcessErrors(null, response.data, response.status, null, { - hdr: 'Error!', - msg: 'Failed to get schedule info. GET returned status: ' + - response.status - }); - }); - } - ], - timezonesResolve: ['Rest', '$stateParams', 'GetBasePath', 'ProcessErrors', - (Rest, $stateParams, GetBasePath, ProcessErrors) => { - var path = `${GetBasePath('schedules')}zoneinfo`; - Rest.setUrl(path); - return Rest.get() - .then(function(data) { - return (data.data); - }).catch(function(response) { - ProcessErrors(null, response.data, response.status, null, { - hdr: 'Error!', - msg: 'Failed to get zoneinfo. GET returned status: ' + - response.status - }); - }); - } - ] - }; - return resolve; -} -export default editScheduleResolve; diff --git a/awx/ui/client/src/scheduler/factories/delete-schedule.factory.js b/awx/ui/client/src/scheduler/factories/delete-schedule.factory.js deleted file mode 100644 index f766fe083cd7..000000000000 --- a/awx/ui/client/src/scheduler/factories/delete-schedule.factory.js +++ /dev/null @@ -1,70 +0,0 @@ -export default - function DeleteSchedule(GetBasePath, Rest, Wait, $state, - ProcessErrors, Prompt, Find, $location, $filter, i18n) { - return function(params) { - var scope = params.scope, - id = params.id, - callback = params.callback, - action, schedule, list, url, hdr; - - if (scope.schedules) { - list = scope.schedules; - } - else if (scope.scheduled_jobs) { - list = scope.scheduled_jobs; - } - - url = GetBasePath('schedules') + id + '/'; - schedule = Find({list: list, key: 'id', val: id }); - hdr = 'Delete Schedule'; - - action = function () { - Wait('start'); - Rest.setUrl(url); - Rest.destroy() - .then(() => { - $('#prompt-modal').modal('hide'); - scope.$emit(callback, id); - - let reloadListStateParams = null; - - if(scope.schedules.length === 1 && $state.params.schedule_search && _.has($state, 'params.schedule_search.page') && $state.params.schedule_search.page !== '1') { - reloadListStateParams = _.cloneDeep($state.params); - reloadListStateParams.schedule_search.page = (parseInt(reloadListStateParams.schedule_search.page)-1).toString(); - } - - if (parseInt($state.params.schedule_id) === id) { - $state.go('^', reloadListStateParams, {reload: true}); - } - else{ - $state.go('.', reloadListStateParams, {reload: true}); - } - }) - .catch(({data, status}) => { - try { - $('#prompt-modal').modal('hide'); - } - catch(e) { - // ignore - } - ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url + - ' failed. DELETE returned: ' + status }); - }); - }; - - Prompt({ - hdr: hdr, - resourceName: $filter('sanitize')(schedule.name), - body: `
${i18n._('Are you sure you want to delete this schedule?')}
`, - action: action, - actionText: i18n._('DELETE'), - backdrop: false - }); - }; - } - -DeleteSchedule.$inject = - [ 'GetBasePath','Rest', 'Wait', '$state', - 'ProcessErrors', 'Prompt', 'Find', '$location', - '$filter', 'i18n' - ]; diff --git a/awx/ui/client/src/scheduler/factories/r-rule-to-api.factory.js b/awx/ui/client/src/scheduler/factories/r-rule-to-api.factory.js deleted file mode 100644 index 9c86205559a8..000000000000 --- a/awx/ui/client/src/scheduler/factories/r-rule-to-api.factory.js +++ /dev/null @@ -1,30 +0,0 @@ -export default - function RRuleToAPI() { - - // This function removes the 'Z' from the UNTIL portion of the - // rrule. The API will default to using the timezone that is - // specified in the TZID as the locale for the UNTIL. - function parseOutZ (rrule) { - let until = rrule.split('UNTIL='); - if(_.has(until, '1')){ - rrule = until[0]; - until = until[1].replace('Z', ''); - return `${rrule}UNTIL=${until}`; - } else { - return rrule; - } - } - - - return function(rrule, scope) { - let localTime = scope.schedulerLocalTime; - let timeZone = scope.schedulerTimeZone.name; - - let response = rrule.replace(/(^.*(?=DTSTART))(DTSTART.*?)(=.*?;)(.*$)/, (str, p1, p2, p3, p4) => { - return p2 + ';TZID=' + timeZone + ':' + localTime + ' ' + 'RRULE:' + p4; - }); - - response = parseOutZ(response); - return response; - }; - } diff --git a/awx/ui/client/src/scheduler/factories/schedule-post.factory.js b/awx/ui/client/src/scheduler/factories/schedule-post.factory.js deleted file mode 100644 index b4338ab63fa8..000000000000 --- a/awx/ui/client/src/scheduler/factories/schedule-post.factory.js +++ /dev/null @@ -1,164 +0,0 @@ -export default - function SchedulePost(Rest, ProcessErrors, RRuleToAPI, Wait, $q, Schedule, PromptService) { - return function(params) { - var scope = params.scope, - url = params.url, - scheduler = params.scheduler, - mode = params.mode, - scheduleData = (params.schedule) ? params.schedule : {}, - promptData = params.promptData, - priorCredentials = params.priorCredentials ? params.priorCredentials : [], - newSchedule, rrule, extra_vars; - const deferred = $q.defer(); - if (scheduler.isValid()) { - Wait('start'); - newSchedule = scheduler.getValue(); - rrule = scheduler.getRRule(); - scheduleData.name = newSchedule.name; - scheduleData.rrule = RRuleToAPI(rrule.toString(), scope); - scheduleData.description = (/error/.test(rrule.toText())) ? '' : rrule.toText(); - - if (scope.askDaysToKeep) { - extra_vars = { - "days" : scope.scheduler_form.schedulerPurgeDays.$viewValue - }; - scheduleData.extra_data = JSON.stringify(extra_vars); - } - else if(scope.extraVars){ - scheduleData.extra_data = scope.parseType === 'yaml' ? - (scope.extraVars === '---' ? "" : jsyaml.safeLoad(scope.extraVars)) : scope.extraVars; - } - - if(promptData) { - scheduleData = PromptService.bundlePromptDataForSaving({ - promptData: promptData, - dataToSave: scheduleData - }); - } - - Rest.setUrl(url); - if (mode === 'add') { - Rest.post(scheduleData) - .then(({data}) => { - if(_.get(promptData, 'launchConf.ask_credential_on_launch')){ - // This finds the credentials that were selected in the prompt but don't occur - // in the template defaults - const credentialsToPost = promptData.prompts.credentials.value.filter(function(credFromPrompt) { - const defaultCreds = promptData.launchConf.defaults.credentials ? promptData.launchConf.defaults.credentials : []; - return !defaultCreds.some(function(defaultCred) { - return credFromPrompt.id === defaultCred.id; - }); - }); - - const promises = []; - const schedule = new Schedule(); - - credentialsToPost.forEach((credentialToPost) => { - promises.push(schedule.postCredential({ - id: data.id, - data: { - id: credentialToPost.id - } - })); - }); - - $q.all(promises) - .then(() => { - Wait('stop'); - deferred.resolve(); - }); - } else { - Wait('stop'); - deferred.resolve(); - } - }) - .catch(({data, status}) => { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'POST to ' + url + ' returned: ' + status }); - - deferred.reject(); - }); - } - else { - Rest.put(scheduleData) - .then(({data}) => { - if(_.get(promptData, 'launchConf.ask_credential_on_launch')){ - const credentialsNotInPriorCredentials = promptData.prompts.credentials.value.filter(function(credFromPrompt) { - const defaultCreds = promptData.launchConf.defaults.credentials ? promptData.launchConf.defaults.credentials : []; - return !defaultCreds.some(function(defaultCred) { - return credFromPrompt.id === defaultCred.id; - }); - }); - - const credentialsToAdd = credentialsNotInPriorCredentials.filter(function(credNotInPrior) { - return !priorCredentials.some(function(priorCred) { - return credNotInPrior.id === priorCred.id; - }); - }); - - const credentialsToRemove = priorCredentials.filter(function(priorCred) { - return !credentialsNotInPriorCredentials.some(function(credNotInPrior) { - return priorCred.id === credNotInPrior.id; - }); - }); - - const promises = []; - const schedule = new Schedule(); - - credentialsToAdd.forEach((credentialToAdd) => { - promises.push(schedule.postCredential({ - id: data.id, - data: { - id: credentialToAdd.id - } - })); - }); - - credentialsToRemove.forEach((credentialToRemove) => { - promises.push(schedule.postCredential({ - id: data.id, - data: { - id: credentialToRemove.id, - disassociate: true - } - })); - }); - - $q.all(promises) - .then(() => { - Wait('stop'); - deferred.resolve(); - }); - } else { - Wait('stop'); - deferred.resolve(); - } - - Wait('stop'); - deferred.resolve(scheduleData); - }) - .catch(({data, status}) => { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'POST to ' + url + ' returned: ' + status }); - - deferred.reject(); - }); - } - } - else { - deferred.reject(); - } - - return deferred.promise; - }; - } - -SchedulePost.$inject = - [ 'Rest', - 'ProcessErrors', - 'RRuleToAPI', - 'Wait', - '$q', - 'ScheduleModel', - 'PromptService' - ]; diff --git a/awx/ui/client/src/scheduler/factories/toggle-schedule.factory.js b/awx/ui/client/src/scheduler/factories/toggle-schedule.factory.js deleted file mode 100644 index 5c2b04e395c2..000000000000 --- a/awx/ui/client/src/scheduler/factories/toggle-schedule.factory.js +++ /dev/null @@ -1,46 +0,0 @@ -export default - function ToggleSchedule(Wait, GetBasePath, ProcessErrors, Rest, $state) { - return function(params) { - var scope = params.scope, - id = params.id, - url = GetBasePath('schedules') + id +'/'; - - // Perform the update - if (scope.removeScheduleFound) { - scope.removeScheduleFound(); - } - scope.removeScheduleFound = scope.$on('ScheduleFound', function(e, data) { - data.enabled = (data.enabled) ? false : true; - Rest.put(data) - .then(() => { - Wait('stop'); - $state.go('.', null, {reload: true}); - }) - .catch(({data, status}) => { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to update schedule ' + id + ' PUT returned: ' + status }); - }); - }); - - Wait('start'); - - // Get the schedule - Rest.setUrl(url); - Rest.get() - .then(({data}) => { - scope.$emit('ScheduleFound', data); - }) - .catch(({data, status}) => { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to retrieve schedule ' + id + ' GET returned: ' + status }); - }); - }; - } - -ToggleSchedule.$inject = - [ 'Wait', - 'GetBasePath', - 'ProcessErrors', - 'Rest', - '$state' - ]; diff --git a/awx/ui/client/src/scheduler/main.js b/awx/ui/client/src/scheduler/main.js deleted file mode 100644 index 80592b2e594b..000000000000 --- a/awx/ui/client/src/scheduler/main.js +++ /dev/null @@ -1,31 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import listController from './schedulerList.controller'; -import addController from './schedulerAdd.controller'; -import editController from './schedulerEdit.controller'; -import schedulerDatePicker from './schedulerDatePicker.directive'; -import DeleteSchedule from './factories/delete-schedule.factory'; -import RRuleToAPI from './factories/r-rule-to-api.factory'; -import SchedulePost from './factories/schedule-post.factory'; -import ToggleSchedule from './factories/toggle-schedule.factory'; -import SchedulesList from './schedules.list'; -import ScheduledJobsList from './scheduled-jobs.list'; -import SchedulerStrings from './scheduler.strings'; - -export default - angular.module('scheduler', []) - .controller('schedulerListController', listController) - .controller('schedulerAddController', addController) - .controller('schedulerEditController', editController) - .factory('DeleteSchedule', DeleteSchedule) - .factory('RRuleToAPI', RRuleToAPI) - .factory('SchedulePost', SchedulePost) - .factory('ToggleSchedule', ToggleSchedule) - .factory('SchedulesList', SchedulesList) - .factory('ScheduledJobsList', ScheduledJobsList) - .directive('schedulerDatePicker', schedulerDatePicker) - .service('SchedulerStrings', SchedulerStrings); diff --git a/awx/ui/client/src/scheduler/repeatFrequencyOptions.block.less b/awx/ui/client/src/scheduler/repeatFrequencyOptions.block.less deleted file mode 100644 index ae9dafb7723e..000000000000 --- a/awx/ui/client/src/scheduler/repeatFrequencyOptions.block.less +++ /dev/null @@ -1,176 +0,0 @@ -/** @define RepeatFrequencyOptions */ - -.RepeatFrequencyOptions { - width: ~"calc(100% + 21px)"; - padding: 20px; - border-left: 5px solid @default-border; - margin-left: -20px; - padding-left: 17px; - padding-right: 0px; - padding-top: 0px; - padding-bottom: 0px; - align-items: flex-start; -} - -.RepeatFrequencyOptions-formGroup { - padding-right: 0px; -} - -@media (min-width: 901px) { - .RepeatFrequencyOptions-formGroup { - flex: 0 0 auto; - margin-bottom: 25px; - width: ~"calc(33% - 32px)"; - margin-right: 50px; - } - - .RepeatFrequencyOptions-formGroup:nth-child(3n+3) { - flex: 0 0 auto; - margin-bottom: 25px; - margin-right: 0px; - } -} - -@media (min-width: 651px) and (max-width: 900px) { - .RepeatFrequencyOptions-formGroup { - flex: 0 0 auto; - margin-bottom: 25px; - width: ~"calc(50% - 25px)"; - margin-right: 50px; - } - - .RepeatFrequencyOptions-formGroup:nth-child(2n+2) { - flex: 0 0 auto; - margin-bottom: 25px; - margin-right: 0px; - } -} - -@media (max-width: 650px) { - .RepeatFrequencyOptions-formGroup { - flex: 0 0 auto; - margin-bottom: 25px; - width: 100%; - margin-right: 0px; - } -} - -.RepeatFrequencyOptions-label { - width: 100%; - margin-top: -2px; - text-transform: uppercase; - font-weight: bold; - color: @default-interface-txt; - font-size: 13px; - margin-left: -20px; - border-left: 5px solid @default-border; - padding-left: 15px; - padding-bottom: 15px; -} - -.RepeatFrequencyOptions-weekButtonContainer { - width: 100%; -} - -.RepeatFrequencyOptions-weekButtonGroup { - height: 27px; - width: 100%; - display: flex; -} - -.RepeatFrequencyOptions-weekButton { - height: 27px; - padding-top: 2px; - padding-right: 4px; - padding-left: 4px; - flex: 1; -} - -.RepeatFrequencyOptions-weekButton.active { - background-color: @default-list-header-bg !important; - border-color: @d7grey !important; -} - -.RepeatFrequencyOptions-everyGroup { - display: flex; - flex-wrap: wrap; - align-items: baseline; -} - -.RepeatFrequencyOptions-inlineLabel { - font-weight: normal; - color: @default-interface-txt; - text-transform: uppercase; - flex: initial; - width: 54px; - margin-left: 10px; - font-size: 13px; -} - -.RepeatFrequencyOptions-occurencesGroup { - width: 100%; -} - -.RepeatFrequencyOptions-radioLabel { - margin-top: 0px; - margin-bottom: 5px; -} - -.RepeatFrequencyOptions-everyLabel { - margin-top: 2px; -} - -.RepeatFrequencyOptions-spacedSelect, -.RepeatFrequencyOptions-spacedSelect ~ .select2 { - margin-bottom: 10px; -} - -.RepeatFrequencyOptions-subFormBorderFixer { - height: 25px; - width: 5px; - background: @default-bg; - margin-left: -20px; - margin-top: -25px; - margin-right: 50px; -} - -.RepeatFrequencyOptions-monthlyOccurence, -.RepeatFrequencyOptions-yearlyOccurence { - text-transform: capitalize; -} - -.RepeatFrequencyOptions-error { - flex: initial; - width: 100%; -} - -.RepeatFrequencyOptions-nameBorderErrorFix { - border-color: @default-err !important; -} - -.RepeatFrequencyOptions-inputGroup { - display: flex; - justify-content: space-between; -} - -.RepeatFrequencyOptions-inputGroup--thirds > .select2 { - width: ~"calc(33% - 3px)" !important; -} - -.RepeatFrequencyOptions-inputGroup--halves > .select2 { - width: ~"calc(50% - 3px)" !important; -} - -.RepeatFrequencyOptions-inputGroup--halvesWithNumber > .select2 { - width: ~"calc(50% - 3px)" !important; - margin-right: 7px; -} - -.RepeatFrequencyOptions-inputGroup--halvesWithSelect > .select2 { - width: ~"calc(50% - 3px)" !important; - margin-left: 7px; -} - -.RepeatFrequencyOptions-inputGroup--halvesWithSelect > .RepeatFrequencyOptions-number { - width: ~"calc(50% - 3px)" !important; -} diff --git a/awx/ui/client/src/scheduler/scheduled-jobs.list.js b/awx/ui/client/src/scheduler/scheduled-jobs.list.js deleted file mode 100644 index 10df571fc849..000000000000 --- a/awx/ui/client/src/scheduler/scheduled-jobs.list.js +++ /dev/null @@ -1,91 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - -export default ['i18n', function(i18n) { - return { - - name: 'schedules', - iterator: 'schedule', - editTitle: i18n._('SCHEDULED JOBS'), - listTitle: i18n._('SCHEDULED JOBS'), - hover: true, - emptyListText: i18n._('No schedules exist'), - layoutClass: 'List-staticColumnLayout--toggleOnOff', - staticColumns: [ - { - field: 'enabled', - content: { - label: '', - type: 'toggle', - ngClick: "toggleSchedule($event, schedule.id)", - nosort: true, - awToolTip: "{{ schedule.play_tip }}", - ngDisabled: "!schedule.summary_fields.user_capabilities.edit", - dataTipWatch: "schedule.play_tip", - dataPlacement: 'top' - } - } - ], - - fields: { - name: { - label: i18n._('Name'), - columnClass: 'col-xl-4 col-lg-5 col-md-5 col-sm-7 List-staticColumnAdjacent', - sourceModel: 'unified_job_template', - sourceField: 'name', - // ngBind: 'schedule.summary_fields.unified_job_template.name', - uiSref: "{{schedule.linkToDetails}}", - awToolTip: "{{ schedule.nameTip | sanitize}}", - dataTipWatch: 'schedule.nameTip', - dataPlacement: "top", - }, - type: { - label: i18n._('Type'), - noLink: true, - columnClass: "d-none d-md-flex col-md-2", - sourceModel: 'unified_job_template', - sourceField: 'unified_job_type', - ngBind: 'schedule.type_label', - searchField: 'unified_job_template__polymorphic_ctype__model' - }, - next_run: { - label: i18n._('Next Run'), - noLink: true, - columnClass: "d-none d-md-flex col-xl-3 col-lg-2 col-md-3", - filter: "longDate", - key: true - } - }, - - actions: { }, - - fieldActions: { - - columnClass: 'col-xl-3 col-lg-3 col-md-2 col-sm-5', - "edit": { - mode: "all", - ngClick: "editSchedule(schedule)", - awToolTip: i18n._("Edit the schedule"), - dataPlacement: "top", - ngShow: 'schedule.summary_fields.user_capabilities.edit' - }, - "view": { - mode: "all", - ngClick: "editSchedule(schedule)", - awToolTip: i18n._("View the schedule"), - dataPlacement: "top", - ngShow: '!schedule.summary_fields.user_capabilities.edit' - }, - "delete": { - mode: 'all', - ngClick: 'deleteSchedule(schedule.id)', - awToolTip: i18n._('Delete the schedule'), - dataPlacement: 'top', - ngShow: 'schedule.summary_fields.user_capabilities.delete' - } - } - };}]; diff --git a/awx/ui/client/src/scheduler/scheduler.partial.html b/awx/ui/client/src/scheduler/scheduler.partial.html deleted file mode 100644 index 4e4208112316..000000000000 --- a/awx/ui/client/src/scheduler/scheduler.partial.html +++ /dev/null @@ -1,4 +0,0 @@ -
-
-
-
diff --git a/awx/ui/client/src/scheduler/scheduler.strings.js b/awx/ui/client/src/scheduler/scheduler.strings.js deleted file mode 100644 index b32b4289b961..000000000000 --- a/awx/ui/client/src/scheduler/scheduler.strings.js +++ /dev/null @@ -1,74 +0,0 @@ -function SchedulerStrings (BaseString) { - BaseString.call(this, 'scheduler'); - - const { t } = this; - const ns = this.scheduler; - - ns.state = { - CREATE_SCHEDULE: t.s('CREATE SCHEDULE'), - EDIT_SCHEDULE: t.s('EDIT SCHEDULE') - }; - - ns.list = { - CLICK_TO_EDIT: t.s('Click to edit schedule.'), - SCHEDULE_IS_ACTIVE: t.s('Schedule is active.'), - SCHEDULE_IS_ACTIVE_CLICK_TO_STOP: t.s('Schedule is active. Click to stop.'), - SCHEDULE_IS_STOPPED: t.s('Schedule is stopped.'), - SCHEDULE_IS_STOPPED_CLICK_TO_STOP: t.s('Schedule is stopped. Click to activate.') - }; - - ns.form = { - NAME: t.s('Name'), - NAME_REQUIRED_MESSAGE: t.s('A schedule name is required.'), - START_DATE: t.s('Start Date'), - START_TIME: t.s('Start Time'), - START_TIME_ERROR_MESSAGE: t.s('The time must be in HH24:MM:SS format.'), - LOCAL_TIME_ZONE: t.s('Local Time Zone'), - REPEAT_FREQUENCY: t.s('Repeat frequency'), - FREQUENCY_DETAILS: t.s('Frequency Details'), - EVERY: t.s('Every'), - REPEAT_FREQUENCY_ERROR_MESSAGE: t.s('Please provide a value between 1 and 999.'), - ON_DAY: t.s('on day'), - MONTH_DAY_ERROR_MESSAGE: t.s('The day must be between 1 and 31.'), - ON_THE: t.s('on the'), - ON: t.s('on'), - ON_DAYS: t.s('on days'), - SUN: t.s('Sun'), - MON: t.s('Mon'), - TUE: t.s('Tue'), - WED: t.s('Wed'), - THU: t.s('Thu'), - FRI: t.s('Fri'), - SAT: t.s('Sat'), - WEEK_DAY_ERROR_MESSAGE: t.s('Please select one or more days.'), - END: t.s('End'), - OCCURENCES: t.s('Occurrences'), - END_DATE: t.s('End Date'), - PROVIDE_VALID_DATE: t.s('Please provide a valid date.'), - END_TIME: t.s('End Time'), - SCHEDULER_OPTIONS_ARE_INVALID: t.s('The scheduler options are invalid, incomplete, or a date is in the past.'), - SCHEDULE_DESCRIPTION: t.s('Schedule Description'), - LIMITED_TO_FIRST_TEN: t.s('Limited to first 10'), - DATE_FORMAT: t.s('Date format'), - EXTRA_VARIABLES: t.s('Extra Variables'), - PROMPT: t.s('Prompt'), - CLOSE: t.s('Close'), - CANCEL: t.s('Cancel'), - SAVE: t.s('Save'), - WARNING: t.s('Warning'), - CREDENTIAL_REQUIRES_PASSWORD_WARNING: t.s('This Job Template has a default credential that requires a password before launch. Adding or editing schedules is prohibited while this credential is selected. To add or edit a schedule, credentials that require a password must be removed from the Job Template.'), - SCHEDULE_NAME: t.s('Schedule name'), - HH24: t.s('HH24'), - MM: t.s('MM'), - SS: t.s('SS'), - DAYS_DATA: t.s('Days of data to keep') - }; - - ns.prompt = { - CONFIRM: t.s('CONFIRM') - }; -} - -SchedulerStrings.$inject = ['BaseStringService']; - -export default SchedulerStrings; diff --git a/awx/ui/client/src/scheduler/schedulerAdd.controller.js b/awx/ui/client/src/scheduler/schedulerAdd.controller.js deleted file mode 100644 index 803a4c473e90..000000000000 --- a/awx/ui/client/src/scheduler/schedulerAdd.controller.js +++ /dev/null @@ -1,475 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['$filter', '$state', '$stateParams', '$http', 'Wait', - '$scope', '$rootScope', 'CreateSelect2', 'ParseTypeChange', 'GetBasePath', - 'Rest', 'ParentObject', 'JobTemplateModel', '$q', 'Empty', 'SchedulePost', - 'ProcessErrors', 'SchedulerInit', '$location', 'PromptService', 'RRuleToAPI', 'moment', - 'WorkflowJobTemplateModel', 'SchedulerStrings', 'rbacUiControlService', 'Alert', - function($filter, $state, $stateParams, $http, Wait, - $scope, $rootScope, CreateSelect2, ParseTypeChange, GetBasePath, - Rest, ParentObject, JobTemplate, $q, Empty, SchedulePost, - ProcessErrors, SchedulerInit, $location, PromptService, RRuleToAPI, moment, - WorkflowJobTemplate, SchedulerStrings, rbacUiControlService, Alert - ) { - - var base = $scope.base || $location.path().replace(/^\//, '').split('/')[0], - scheduler, - job_type; - - /* - * Normally if "ask_*" checkboxes are checked in a job template settings, - * shouldShowPromptButton() returns True to show the "PROMPT" button. - * However, extra_vars("ask_variables_on_launch") does not use this and - * displays a separate text area within the add/edit page for input. - * We exclude "ask_variables_on_launch" from shouldShowPromptButton() here. - */ - const shouldShowPromptButton = (launchConf) => launchConf.survey_enabled || - launchConf.ask_inventory_on_launch || - launchConf.ask_credential_on_launch || - launchConf.ask_verbosity_on_launch || - launchConf.ask_job_type_on_launch || - launchConf.ask_limit_on_launch || - launchConf.ask_tags_on_launch || - launchConf.ask_skip_tags_on_launch || - launchConf.ask_diff_mode_on_launch || - launchConf.credential_needed_to_start || - launchConf.ask_scm_branch_on_launch || - launchConf.variables_needed_to_start.length !== 0; - - var schedule_url = ParentObject.related.schedules || `${ParentObject.related.inventory_source}schedules`; - if (ParentObject){ - $scope.parentObject = ParentObject; - let scheduleEndpoint = ParentObject.endpoint|| ParentObject.related.schedules || `${ParentObject.related.inventory_source}schedules`; - $scope.canAdd = false; - rbacUiControlService.canAdd(scheduleEndpoint) - .then(function(params) { - $scope.canAdd = params.canAdd; - }); - } - - /* - * Keep processSchedulerEndDt method on the $scope - * because angular-scheduler references it - */ - $scope.processSchedulerEndDt = function(){ - // set the schedulerEndDt to be equal to schedulerStartDt + 1 day @ midnight - var dt = new Date($scope.schedulerUTCTime); - // increment date by 1 day - dt.setDate(dt.getDate() + 1); - var month = $filter('schZeroPad')(dt.getMonth() + 1, 2), - day = $filter('schZeroPad')(dt.getDate(), 2); - $scope.$parent.schedulerEndDt = month + '/' + day + '/' + dt.getFullYear(); - }; - - $scope.preventCredsWithPasswords = true; - $scope.strings = SchedulerStrings; - - /* - * This is a workaround for the angular-scheduler library inserting `ll` into fields after an - * invalid entry and never unsetting them. Presumably null is being truncated down to 2 chars - * in that case. - * - * Because this same problem exists in the edit mode and because there's no inheritence, this - * block of code is duplicated in both add/edit controllers pending a much needed broader - * refactoring effort. - */ - $scope.timeChange = () => { - if (!Number($scope.schedulerStartHour)) { - $scope.schedulerStartHour = '00'; - } - - if (!Number($scope.schedulerStartMinute)) { - $scope.schedulerStartMinute = '00'; - } - - if (!Number($scope.schedulerStartSecond)) { - $scope.schedulerStartSecond = '00'; - } - - $scope.scheduleTimeChange(); - }; - - $scope.saveSchedule = function() { - SchedulePost({ - scope: $scope, - url: schedule_url, - scheduler: scheduler, - promptData: $scope.promptData, - mode: 'add' - }).then(() => { - Wait('stop'); - $state.go("^", null, {reload: true}); - }); - }; - - $scope.prompt = () => { - $scope.promptData.triggerModalOpen = true; - }; - - $scope.formCancel = function() { - $state.go("^"); - }; - - // initial end @ midnight values - $scope.schedulerEndHour = "00"; - $scope.schedulerEndMinute = "00"; - $scope.schedulerEndSecond = "00"; - $scope.parentObject = ParentObject; - - $scope.hideForm = true; - - // extra_data field is not manifested in the UI when scheduling a Management Job - if ($state.current.name === 'templates.editJobTemplate.schedules.add'){ - $scope.parseType = 'yaml'; - - let jobTemplate = new JobTemplate(); - - $q.all([jobTemplate.optionsLaunch(ParentObject.id), jobTemplate.getLaunch(ParentObject.id)]) - .then((responses) => { - let launchConf = responses[1].data; - - if (launchConf.passwords_needed_to_start && - launchConf.passwords_needed_to_start.length > 0 && - !launchConf.ask_credential_on_launch - ) { - Alert(SchedulerStrings.get('form.WARNING'), SchedulerStrings.get('form.CREDENTIAL_REQUIRES_PASSWORD_WARNING'), 'alert-info'); - $state.go('^', { reload: true }); - } - - let watchForPromptChanges = () => { - let promptValuesToWatch = [ - 'promptData.prompts.inventory.value', - 'promptData.prompts.jobType.value', - 'promptData.prompts.verbosity.value', - 'missingSurveyValue' - ]; - - $scope.$watchGroup(promptValuesToWatch, function() { - let missingPromptValue = false; - if ($scope.missingSurveyValue) { - missingPromptValue = true; - } else if (!$scope.promptData.prompts.inventory.value || !$scope.promptData.prompts.inventory.value.id) { - missingPromptValue = true; - } - $scope.promptModalMissingReqFields = missingPromptValue; - }); - }; - - if (launchConf.ask_variables_on_launch) { - $scope.extraVars = ParentObject.extra_vars === '' ? '---' : ParentObject.extra_vars; - - ParseTypeChange({ - scope: $scope, - variable: 'extraVars', - parse_variable: 'parseType', - field_id: 'SchedulerForm-extraVars' - }); - } else { - $scope.noVars = true; - } - - if (!shouldShowPromptButton(launchConf)) { - $scope.showPromptButton = false; - } else { - $scope.showPromptButton = true; - - // Ignore the fact that variables might be promptable on launch - // Promptable variables will happen in the schedule form - launchConf.ignore_ask_variables = true; - - if (launchConf.ask_inventory_on_launch && !_.has(launchConf, 'defaults.inventory')) { - $scope.promptModalMissingReqFields = true; - } - - if (launchConf.survey_enabled) { - // go out and get the survey questions - jobTemplate.getSurveyQuestions(ParentObject.id) - .then((surveyQuestionRes) => { - - let processed = PromptService.processSurveyQuestions({ - surveyQuestions: surveyQuestionRes.data.spec - }); - - $scope.missingSurveyValue = processed.missingSurveyValue; - - $scope.promptData = { - launchConf: responses[1].data, - launchOptions: responses[0].data, - surveyQuestions: processed.surveyQuestions, - template: ParentObject.id, - prompts: PromptService.processPromptValues({ - launchConf: responses[1].data, - launchOptions: responses[0].data - }), - }; - - $scope.$watch('promptData.surveyQuestions', () => { - let missingSurveyValue = false; - _.each($scope.promptData.surveyQuestions, (question) => { - if (question.required && (Empty(question.model) || question.model === [])) { - missingSurveyValue = true; - } - }); - $scope.missingSurveyValue = missingSurveyValue; - }, true); - - watchForPromptChanges(); - }); - } else { - $scope.promptData = { - launchConf: responses[1].data, - launchOptions: responses[0].data, - template: ParentObject.id, - prompts: PromptService.processPromptValues({ - launchConf: responses[1].data, - launchOptions: responses[0].data - }), - }; - - watchForPromptChanges(); - } - } - }); - } else if ($state.current.name === 'templates.editWorkflowJobTemplate.schedules.add'){ - $scope.parseType = 'yaml'; - let workflowJobTemplate = new WorkflowJobTemplate(); - - $q.all([workflowJobTemplate.optionsLaunch(ParentObject.id), workflowJobTemplate.getLaunch(ParentObject.id)]) - .then((responses) => { - let launchConf = responses[1].data; - - let watchForPromptChanges = () => { - $scope.$watch('missingSurveyValue', function() { - $scope.promptModalMissingReqFields = $scope.missingSurveyValue ? true : false; - }); - }; - - if (launchConf.ask_variables_on_launch) { - $scope.noVars = false; - $scope.extraVars = ParentObject.extra_vars === '' ? '---' : ParentObject.extra_vars; - - ParseTypeChange({ - scope: $scope, - variable: 'extraVars', - parse_variable: 'parseType', - field_id: 'SchedulerForm-extraVars' - }); - } else { - $scope.noVars = true; - } - - if (!shouldShowPromptButton(launchConf)) { - $scope.showPromptButton = false; - } else { - $scope.showPromptButton = true; - - if(launchConf.survey_enabled) { - // go out and get the survey questions - workflowJobTemplate.getSurveyQuestions(ParentObject.id) - .then((surveyQuestionRes) => { - - let processed = PromptService.processSurveyQuestions({ - surveyQuestions: surveyQuestionRes.data.spec - }); - - $scope.missingSurveyValue = processed.missingSurveyValue; - - $scope.promptData = { - launchConf: responses[1].data, - launchOptions: responses[0].data, - surveyQuestions: processed.surveyQuestions, - templateType: ParentObject.type, - template: ParentObject.id, - prompts: PromptService.processPromptValues({ - launchConf: responses[1].data, - launchOptions: responses[0].data - }), - }; - - $scope.$watch('promptData.surveyQuestions', () => { - let missingSurveyValue = false; - _.each($scope.promptData.surveyQuestions, (question) => { - if(question.required && (Empty(question.model) || question.model === [])) { - missingSurveyValue = true; - } - }); - $scope.missingSurveyValue = missingSurveyValue; - }, true); - - watchForPromptChanges(); - }); - } - else { - $scope.promptData = { - launchConf: responses[1].data, - launchOptions: responses[0].data, - templateType: ParentObject.type, - template: ParentObject.id, - prompts: PromptService.processPromptValues({ - launchConf: responses[1].data, - launchOptions: responses[0].data - }), - }; - - watchForPromptChanges(); - } - } - }); - } - - if ($state.current.name === 'templates.editWorkflowJobTemplate.schedules.add' || - $state.current.name === 'projects.edit.schedules.add' || - $state.current.name === 'inventories.edit.inventory_sources.edit.schedules.add' - ){ - $scope.noVars = true; - } - - job_type = $scope.parentObject.job_type; - if (!Empty($stateParams.id) && base !== 'system_job_templates' && base !== 'inventories' && !schedule_url) { - schedule_url = GetBasePath(base) + $stateParams.id + '/schedules/'; - } else if (base === "inventories"){ - if (!schedule_url){ - Rest.setUrl(GetBasePath('groups') + $stateParams.id + '/'); - Rest.get() - .then(function (data) { - schedule_url = data.data.related.inventory_source + 'schedules/'; - }).catch(function (response) { - ProcessErrors(null, response.data, response.status, null, { - hdr: 'Error!', - msg: 'Failed to get inventory group info. GET returned status: ' + - response.status - }); - }); - } - } else if (base === 'system_job_templates') { - schedule_url = GetBasePath(base) + $stateParams.id + '/schedules/'; - let parentJobType = _.get($scope.parentObject, 'job_type'); - if (parentJobType !== 'cleanup_tokens' && parentJobType !== 'cleanup_sessions') { - $scope.askDaysToKeep = true; - } - } - - Wait('start'); - $('#form-container').empty(); - scheduler = SchedulerInit({ scope: $scope, requireFutureStartTime: false }); - let timeZonesAPI = () => { - return $http.get(`/api/v2/schedules/zoneinfo/`); - }; - // set API timezones to scheduler object - timeZonesAPI().then(({data}) => { - scheduler.scope.timeZones = data; - scheduler.scope.schedulerTimeZone = _.find(data, (zone) => { - return zone.name === scheduler.scope.current_timezone.name; - }); - $scope.scheduleTimeChange(); - }); - if ($scope.schedulerUTCTime) { - // The UTC time is already set - $scope.processSchedulerEndDt(); - } else { - // We need to wait for it to be set by angular-scheduler because the following function depends - // on it - var schedulerUTCTimeWatcher = $scope.$watch('schedulerUTCTime', function(newVal) { - if (newVal) { - // Remove the watcher - schedulerUTCTimeWatcher(); - $scope.processSchedulerEndDt(); - } - }); - } - - let previewList = _.debounce(function(req) { - $http.post('/api/v2/schedules/preview/', {'rrule': req}) - .then(({data}) => { - $scope.preview_list = data; - let parsePreviewList = (tz) => { - return data[tz].map(function(date) { - date = date.replace(/Z/, ''); - return moment.parseZone(date).format("MM-DD-YYYY HH:mm:ss"); - }); - }; - for (let tz in data) { - $scope.preview_list.isEmpty = data[tz].length === 0; - $scope.preview_list[tz] = parsePreviewList(tz); - } - }); - }, 300); - - $scope.$on("setPreviewPane", (event) => { - let rrule = event.currentScope.rrule.toString(); - let req = RRuleToAPI(rrule, $scope); - previewList(req); - }); - - scheduler.inject('form-container', false); - scheduler.injectDetail('occurrences', false); - scheduler.clear(); - $scope.$on("htmlDetailReady", function() { - $scope.hideForm = false; - scheduler.scope.schedulerStartDt = moment(new Date()).add(1,'days'); - - $scope.$watchGroup(["schedulerName", - "schedulerStartDt", - "schedulerStartHour", - "schedulerStartMinute", - "schedulerStartSecond", - "schedulerTimeZone", - "schedulerFrequency", - "schedulerInterval", - "monthlyRepeatOption", - "monthDay", - "monthlyOccurrence", - "monthlyWeekDay", - "yearlyRepeatOption", - "yearlyMonth", - "yearlyMonthDay", - "yearlyOccurrence", - "yearlyWeekDay", - "yearlyOtherMonth", - "schedulerEnd", - "schedulerOccurrenceCount", - "schedulerEndDt", - "schedulerEndHour", - "schedulerEndMinute", - "schedularEndSecond" - ], function() { - $rootScope.$broadcast("loadSchedulerDetailPane"); - }, true); - - $scope.$watch("weekDays", function() { - $rootScope.$broadcast("loadSchedulerDetailPane"); - }, true); - - Wait('stop'); - }); - $scope.showRRuleDetail = false; - - $('#scheduler-tabs li a').on('shown.bs.tab', function(e) { - if ($(e.target).text() === 'Details') { - if (!scheduler.isValid()) { - $('#scheduler-tabs a:first').tab('show'); - } - } - }); - - var callSelect2 = function() { - CreateSelect2({ - element: '.MakeSelect2', - multiple: false - }); - $("#schedulerTimeZone").select2({ - width:'100%', - containerCssClass: 'Form-dropDown', - placeholder: 'SEARCH' - }); - }; - - $scope.$on("updateSchedulerSelects", function() { - callSelect2(); - }); - callSelect2(); -}]; diff --git a/awx/ui/client/src/scheduler/schedulerDatePicker.block.less b/awx/ui/client/src/scheduler/schedulerDatePicker.block.less deleted file mode 100644 index 8e630e9f9d8a..000000000000 --- a/awx/ui/client/src/scheduler/schedulerDatePicker.block.less +++ /dev/null @@ -1,37 +0,0 @@ -.DatePicker { - flex: 1 0 auto; - display: flex; - - &-icon { - flex: initial; - padding: 6px 12px; - font-size: 14px; - border-radius: 4px 0 0 4px; - border: 1px solid @b7grey; - border-right: 0; - background-color: @default-bg; - } - - &-icon:hover { - background-color: @f6grey; - } - - &-icon:focus, - &-icon:active { - background-color: @f6grey; - } - - &-input { - flex: 1 0 auto; - border-radius: 0 4px 4px 0; - border: 1px solid @b7grey; - padding: 6px 12px; - background-color: @fcgrey; - } - - &-input:focus, - &-input:active { - outline-offset: 0; - outline: 0; - } -} \ No newline at end of file diff --git a/awx/ui/client/src/scheduler/schedulerDatePicker.directive.js b/awx/ui/client/src/scheduler/schedulerDatePicker.directive.js deleted file mode 100644 index 310a78909198..000000000000 --- a/awx/ui/client/src/scheduler/schedulerDatePicker.directive.js +++ /dev/null @@ -1,100 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/* jshint unused: vars */ - -export default - [ 'moment', - 'templateUrl', - function(moment, templateUrl) { - require('bootstrap-datepicker'); - return { - restrict: 'E', - scope: { - date: '=', - minDate: '=', - autoUpdate: '=?', - inputClass: '&', - disabled: '=?' - }, - templateUrl: templateUrl('scheduler/schedulerDatePicker'), - link: function(scope, element, attrs) { - var lang = window.navigator.languages ? - window.navigator.languages[0] : - (window.navigator.language || - window.navigator.userLanguage); - moment.locale(lang); - // We need to make sure this _never_ recurses, which sometimes happens - // with two-way binding. - var mustUpdateValue = true; - scope.autoUpdate = scope.autoUpdate === false ? false : true; - - // convert the passed current date into the correct moment locale - scope.$watch('date', function(newValue) { - if (newValue) { - mustUpdateValue = false; - scope.dateValueMoment = moment(newValue, ['MM/DD/YYYY'], moment.locale()); - scope.dateValue = scope.dateValueMoment.format('L'); - element.find(".DatePicker").datepicker('update', scope.dateValue); - } - }, true); - - // as the date picker value changes, convert back to - // the english date to pass back into the scheduler lib - scope.$watch('dateValue', function(newValue) { - scope.dateValueMoment = moment(newValue, ['L'], moment.locale()); - scope.date = scope.dateValueMoment.locale('en').format('L'); - mustUpdateValue = true; - }); - - var localeData = - moment.localeData(); - var dateFormat = momentFormatToDPFormat(localeData._longDateFormat.L); - var localeKey = momentLocaleToDPLocale(moment.locale()); - - element.find(".DatePicker").addClass("input-prepend date"); - element.find(".DatePicker").find(".DatePicker-icon").addClass("add-on"); - element.find(".DatePicker").datepicker({ - autoclose: true, - language: localeKey, - format: dateFormat - }); - - function momentLocaleToDPLocale(localeKey) { - var parts = localeKey.split('-'); - - if (parts.length === 2) { - var lastPart = parts[1].toUpperCase(); - return [parts[0], lastPart].join('-'); - } else { - return localeKey; - } - - } - - function momentFormatToDPFormat(momentFormat) { - var resultFormat = momentFormat; - - function lowerCase(str) { - return str.toLowerCase(); - } - - resultFormat = resultFormat.replace(/D{1,2}/, lowerCase); - - if (/MMM/.test(resultFormat)) { - resultFormat = resultFormat.replace('MMM', 'M'); - } else { - resultFormat = resultFormat.replace(/M{1,2}/, 'm'); - } - - resultFormat = resultFormat.replace(/Y{2,4}/, lowerCase); - - return resultFormat; - } - } - }; - } - ]; diff --git a/awx/ui/client/src/scheduler/schedulerDatePicker.partial.html b/awx/ui/client/src/scheduler/schedulerDatePicker.partial.html deleted file mode 100644 index 728285096f47..000000000000 --- a/awx/ui/client/src/scheduler/schedulerDatePicker.partial.html +++ /dev/null @@ -1,10 +0,0 @@ -
- - -
diff --git a/awx/ui/client/src/scheduler/schedulerEdit.controller.js b/awx/ui/client/src/scheduler/schedulerEdit.controller.js deleted file mode 100644 index d0cd7e109030..000000000000 --- a/awx/ui/client/src/scheduler/schedulerEdit.controller.js +++ /dev/null @@ -1,507 +0,0 @@ -export default ['$filter', '$state', '$stateParams', 'Wait', '$scope', 'moment', -'$rootScope', '$http', 'CreateSelect2', 'ParseTypeChange', 'ParentObject', 'ProcessErrors', 'Rest', -'GetBasePath', 'SchedulerInit', 'SchedulePost', 'JobTemplateModel', '$q', 'Empty', 'PromptService', 'RRuleToAPI', -'WorkflowJobTemplateModel', 'SchedulerStrings', 'scheduleResolve', 'timezonesResolve', 'Alert', -function($filter, $state, $stateParams, Wait, $scope, moment, - $rootScope, $http, CreateSelect2, ParseTypeChange, ParentObject, ProcessErrors, Rest, - GetBasePath, SchedulerInit, SchedulePost, JobTemplate, $q, Empty, PromptService, RRuleToAPI, - WorkflowJobTemplate, SchedulerStrings, scheduleResolve, timezonesResolve, Alert -) { - - let schedule, scheduler, scheduleCredentials = []; - - /* - * Normally if "ask_*" checkboxes are checked in a job template settings, - * shouldShowPromptButton() returns True to show the "PROMPT" button. - * However, extra_vars("ask_variables_on_launch") does not use this and - * displays a separate text area within the add/edit page for input. - * We exclude "ask_variables_on_launch" from shouldShowPromptButton() here. - */ - const shouldShowPromptButton = (launchConf) => launchConf.survey_enabled || - launchConf.ask_inventory_on_launch || - launchConf.ask_credential_on_launch || - launchConf.ask_verbosity_on_launch || - launchConf.ask_job_type_on_launch || - launchConf.ask_limit_on_launch || - launchConf.ask_tags_on_launch || - launchConf.ask_skip_tags_on_launch || - launchConf.ask_diff_mode_on_launch || - launchConf.credential_needed_to_start || - launchConf.ask_scm_branch_on_launch || - launchConf.passwords_needed_to_start.length !== 0 || - launchConf.variables_needed_to_start.length !== 0; - - $scope.preventCredsWithPasswords = true; - - // initial end @ midnight values - $scope.schedulerEndHour = "00"; - $scope.schedulerEndMinute = "00"; - $scope.schedulerEndSecond = "00"; - $scope.parentObject = ParentObject; - $scope.isEdit = true; - $scope.hideForm = true; - $scope.parseType = 'yaml'; - - $scope.strings = SchedulerStrings; - - /* - * Keep processSchedulerEndDt method on the $scope - * because angular-scheduler references it - */ - $scope.processSchedulerEndDt = function(){ - // set the schedulerEndDt to be equal to schedulerStartDt + 1 day @ midnight - var dt = new Date($scope.schedulerUTCTime); - // increment date by 1 day - dt.setDate(dt.getDate() + 1); - var month = $filter('schZeroPad')(dt.getMonth() + 1, 2), - day = $filter('schZeroPad')(dt.getDate(), 2); - $scope.$parent.schedulerEndDt = month + '/' + day + '/' + dt.getFullYear(); - }; - - $scope.formCancel = function() { - $state.go("^"); - }; - - /* - * This is a workaround for the angular-scheduler library inserting `ll` into fields after an - * invalid entry and never unsetting them. Presumably null is being truncated down to 2 chars - * in that case. - * - * Because this same problem exists in the edit mode and because there's no inheritence, this - * block of code is duplicated in both add/edit controllers pending a much needed broader - * refactoring effort. - */ - $scope.timeChange = () => { - if (!Number($scope.schedulerStartHour)) { - $scope.schedulerStartHour = '00'; - } - - if (!Number($scope.schedulerStartMinute)) { - $scope.schedulerStartMinute = '00'; - } - - if (!Number($scope.schedulerStartSecond)) { - $scope.schedulerStartSecond = '00'; - } - - $scope.scheduleTimeChange(); - }; - - $scope.saveSchedule = function() { - schedule.extra_data = $scope.extraVars; - SchedulePost({ - scope: $scope, - url: GetBasePath('schedules') + parseInt($stateParams.schedule_id) + '/', - scheduler: scheduler, - mode: 'edit', - schedule: schedule, - promptData: $scope.promptData, - priorCredentials: scheduleCredentials - }).then(() => { - Wait('stop'); - $state.go("^", null, {reload: true}); - }); - }; - - $scope.prompt = () => { - $scope.promptData.triggerModalOpen = true; - }; - - var callSelect2 = function() { - CreateSelect2({ - element: '.MakeSelect2', - multiple: false - }); - $("#schedulerTimeZone").select2({ - width:'100%', - containerCssClass: 'Form-dropDown', - placeholder: 'SEARCH' - }); - }; - - $scope.$on("updateSchedulerSelects", function() { - callSelect2(); - }); - - let previewList = _.debounce(function(req) { - $http.post('/api/v2/schedules/preview/', {'rrule': req}) - .then(({data}) => { - $scope.preview_list = data; - let parsePreviewList = (tz) => { - return data[tz].map(function(date) { - date = date.replace(/Z/, ''); - return moment.parseZone(date).format("MM-DD-YYYY HH:mm:ss"); - }); - }; - for (let tz in data) { - $scope.preview_list.isEmpty = data[tz].length === 0; - $scope.preview_list[tz] = parsePreviewList(tz); - } - }); - }, 300); - - $scope.$on("setPreviewPane", (event) => { - let rrule = event.currentScope.rrule.toString(); - let req = RRuleToAPI(rrule, $scope); - previewList(req); - }); - - Wait('start'); - - //sets the timezone dropdown to the timezone specified by the API - function setTimezone () { - $scope.schedulerTimeZone = _.find($scope.timeZones, function(x) { - return x.name === scheduleResolve.timezone; - }); - } - - // sets the UNTIL portion of the schedule form after the angular-scheduler - // sets it, but this function reads the 'until' key/value pair directly - // from the schedule GET response. - function setUntil (scheduler) { - let { until } = scheduleResolve; - if(until !== ''){ - const date = moment(until); - const endDt = moment.parseZone(date).format("MM/DD/YYYY"); - const endHour = date.format('HH'); - const endMinute = date.format('mm'); - const endSecond = date.format('ss'); - scheduler.scope.schedulerEndDt = endDt; - scheduler.scope.schedulerEndHour = endHour; - scheduler.scope.schedulerEndMinute = endMinute; - scheduler.scope.schedulerEndSecond = endSecond; - } - } - - function init() { - schedule = scheduleResolve; - - try { - schedule.extra_data = JSON.parse(schedule.extra_data); - } catch(e) { - // do nothing - } - - $scope.extraVars = (scheduleResolve.extra_data === '' || _.isEmpty(scheduleResolve.extra_data)) ? '---' : '---\n' + jsyaml.safeDump(scheduleResolve.extra_data); - - if (_.has(schedule, 'summary_fields.unified_job_template.unified_job_type') && - schedule.summary_fields.unified_job_template.unified_job_type === 'system_job'){ - let scheduleJobType = _.get(schedule.summary_fields.unified_job_template, 'job_type'); - if (scheduleJobType !== 'cleanup_tokens' && scheduleJobType !== 'cleanup_sessions') { - $scope.askDaysToKeep = true; - } - } - - $scope.schedule_obj = scheduleResolve; - - $('#form-container').empty(); - scheduler = SchedulerInit({ scope: $scope, requireFutureStartTime: false }); - - scheduler.scope.timeZones = timezonesResolve; - setTimezone(); - - scheduler.inject('form-container', false); - scheduler.injectDetail('occurrences', false); - - if (!/DTSTART/.test(schedule.rrule)) { - schedule.rrule += ";DTSTART=" + schedule.dtstart.replace(/\.\d+Z$/,'Z'); - } - schedule.rrule = schedule.rrule.replace(/ RRULE:/,';'); - schedule.rrule = schedule.rrule.replace(/DTSTART:/,'DTSTART='); - $scope.$on("htmlDetailReady", function() { - scheduler.setRRule(schedule.rrule); - scheduler.setName(schedule.name); - setTimezone(); - setUntil(scheduler); - $scope.hideForm = false; - - $scope.$watchGroup(["schedulerName", - "schedulerStartDt", - "schedulerStartHour", - "schedulerStartMinute", - "schedulerStartSecond", - "schedulerTimeZone", - "schedulerFrequency", - "schedulerInterval", - "monthlyRepeatOption", - "monthDay", - "monthlyOccurrence", - "monthlyWeekDay", - "yearlyRepeatOption", - "yearlyMonth", - "yearlyMonthDay", - "yearlyOccurrence", - "yearlyWeekDay", - "yearlyOtherMonth", - "schedulerEnd", - "schedulerOccurrenceCount", - "schedulerEndDt", - "schedulerEndHour", - "schedulerEndMinute", - "schedulerEndSecond" - ], function() { - $rootScope.$broadcast("loadSchedulerDetailPane"); - }, true); - - $scope.$watch("weekDays", function() { - $rootScope.$broadcast("loadSchedulerDetailPane"); - }, true); - - $rootScope.$broadcast("loadSchedulerDetailPane"); - Wait('stop'); - }); - - $scope.showRRuleDetail = false; - scheduler.setRRule(schedule.rrule); - scheduler.setName(schedule.name); - $rootScope.breadcrumb.schedule_name = $scope.schedulerName; - $rootScope.breadcrumb[`${$scope.parentObject.type}_name`] = $scope.parentObject.name; - $scope.noVars = true; - scheduler.scope.timeZones = timezonesResolve; - scheduler.scope.schedulerTimeZone = scheduleResolve.timezone; - if ($scope.askDaysToKeep){ - $scope.schedulerPurgeDays = Number(schedule.extra_data.days); - } - - const codeMirrorExtraVars = () => { - ParseTypeChange({ - scope: $scope, - variable: 'extraVars', - parse_variable: 'parseType', - field_id: 'SchedulerForm-extraVars' - }); - }; - - if ($state.current.name === 'templates.editJobTemplate.schedules.edit' || $scope.parentObject.type === 'job_template'){ - - let jobTemplate = new JobTemplate(); - - Rest.setUrl(scheduleResolve.related.credentials); - $q.all([jobTemplate.optionsLaunch(ParentObject.id), jobTemplate.getLaunch(ParentObject.id), Rest.get()]) - .then((responses) => { - let launchOptions = responses[0].data, - launchConf = responses[1].data; - scheduleCredentials = responses[2].data.results; - - if (launchConf.passwords_needed_to_start && - launchConf.passwords_needed_to_start.length > 0 && - !launchConf.ask_credential_on_launch - ) { - Alert(SchedulerStrings.get('form.WARNING'), SchedulerStrings.get('form.CREDENTIAL_REQUIRES_PASSWORD_WARNING'), 'alert-info'); - $scope.credentialRequiresPassword = true; - } - - let watchForPromptChanges = () => { - let promptValuesToWatch = [ - 'promptData.prompts.inventory.value', - 'promptData.prompts.jobType.value', - 'promptData.prompts.verbosity.value', - 'missingSurveyValue' - ]; - - $scope.$watchGroup(promptValuesToWatch, function() { - let missingPromptValue = false; - if ($scope.missingSurveyValue) { - missingPromptValue = true; - } else if (!$scope.promptData.prompts.inventory.value || !$scope.promptData.prompts.inventory.value.id) { - missingPromptValue = true; - } - $scope.promptModalMissingReqFields = missingPromptValue; - }); - }; - - let prompts = PromptService.processPromptValues({ - launchConf: responses[1].data, - launchOptions: responses[0].data, - currentValues: scheduleResolve - }); - - let defaultCredsWithoutOverrides = []; - - const credentialHasScheduleOverride = (templateDefaultCred) => { - let credentialHasOverride = false; - scheduleCredentials.forEach((scheduleCred) => { - if (templateDefaultCred.credential_type === scheduleCred.credential_type) { - if ( - (!templateDefaultCred.vault_id && !scheduleCred.inputs.vault_id) || - (templateDefaultCred.vault_id && scheduleCred.inputs.vault_id && templateDefaultCred.vault_id === scheduleCred.inputs.vault_id) - ) { - credentialHasOverride = true; - } - } - }); - - return credentialHasOverride; - }; - - if (_.has(launchConf, 'defaults.credentials')) { - launchConf.defaults.credentials.forEach((defaultCred) => { - if (!credentialHasScheduleOverride(defaultCred)) { - defaultCredsWithoutOverrides.push(defaultCred); - } - }); - } - - prompts.credentials.value = defaultCredsWithoutOverrides.concat(scheduleCredentials); - - // the extra vars codemirror is ONLY shown if the - // ask_variables_on_launch = true - if (launchConf.ask_variables_on_launch) { - $scope.noVars = false; - } else { - $scope.noVars = true; - } - - if (!shouldShowPromptButton(launchConf)) { - $scope.showPromptButton = false; - - if (launchConf.ask_variables_on_launch) { - codeMirrorExtraVars(); - } - } else { - $scope.showPromptButton = true; - - // Ignore the fact that variables might be promptable on launch - // Promptable variables will happen in the schedule form - launchConf.ignore_ask_variables = true; - - if (launchConf.ask_inventory_on_launch && !_.has(launchConf, 'defaults.inventory') && !_.has(scheduleResolve, 'summary_fields.inventory')) { - $scope.promptModalMissingReqFields = true; - } - - if (responses[1].data.survey_enabled) { - // go out and get the survey questions - jobTemplate.getSurveyQuestions(ParentObject.id) - .then((surveyQuestionRes) => { - - let processed = PromptService.processSurveyQuestions({ - surveyQuestions: surveyQuestionRes.data.spec, - extra_data: _.cloneDeep(scheduleResolve.extra_data) - }); - - $scope.missingSurveyValue = processed.missingSurveyValue; - - $scope.extraVars = (processed.extra_data === '' || _.isEmpty(processed.extra_data)) ? '---' : '---\n' + jsyaml.safeDump(processed.extra_data); - - codeMirrorExtraVars(); - - $scope.promptData = { - launchConf: launchConf, - launchOptions: launchOptions, - prompts: prompts, - surveyQuestions: surveyQuestionRes.data.spec, - template: ParentObject.id - }; - - $scope.$watch('promptData.surveyQuestions', () => { - let missingSurveyValue = false; - _.each($scope.promptData.surveyQuestions, (question) => { - if (question.required && (Empty(question.model) || question.model === [])) { - missingSurveyValue = true; - } - }); - $scope.missingSurveyValue = missingSurveyValue; - }, true); - - watchForPromptChanges(); - }); - } else { - codeMirrorExtraVars(); - $scope.promptData = { - launchConf: launchConf, - launchOptions: launchOptions, - prompts: prompts, - template: ParentObject.id - }; - watchForPromptChanges(); - } - } - }); - } else if ($state.current.name === 'templates.editWorkflowJobTemplate.schedules.edit' || $scope.parentObject.type === 'workflow_job_template') { - let workflowJobTemplate = new WorkflowJobTemplate(); - - $q.all([workflowJobTemplate.optionsLaunch(ParentObject.id), workflowJobTemplate.getLaunch(ParentObject.id)]) - .then((responses) => { - let launchOptions = responses[0].data, - launchConf = responses[1].data; - - let watchForPromptChanges = () => { - $scope.$watch('missingSurveyValue', function() { - $scope.promptModalMissingReqFields = $scope.missingSurveyValue ? true : false; - }); - }; - - let prompts = PromptService.processPromptValues({ - launchConf: responses[1].data, - launchOptions: responses[0].data, - currentValues: scheduleResolve - }); - - // the extra vars codemirror is ONLY shown if the - // ask_variables_on_launch = true - if (launchConf.ask_variables_on_launch) { - $scope.noVars = false; - codeMirrorExtraVars(); - } else { - $scope.noVars = true; - } - - if (!shouldShowPromptButton(launchConf)) { - $scope.showPromptButton = false; - } else { - $scope.showPromptButton = true; - - if(responses[1].data.survey_enabled) { - // go out and get the survey questions - workflowJobTemplate.getSurveyQuestions(ParentObject.id) - .then((surveyQuestionRes) => { - - let processed = PromptService.processSurveyQuestions({ - surveyQuestions: surveyQuestionRes.data.spec, - extra_data: _.cloneDeep(scheduleResolve.extra_data) - }); - - $scope.missingSurveyValue = processed.missingSurveyValue; - - $scope.promptData = { - launchConf: launchConf, - launchOptions: launchOptions, - prompts: prompts, - surveyQuestions: surveyQuestionRes.data.spec, - templateType: ParentObject.type, - template: ParentObject.id - }; - - $scope.$watch('promptData.surveyQuestions', () => { - let missingSurveyValue = false; - _.each($scope.promptData.surveyQuestions, (question) => { - if(question.required && (Empty(question.model) || question.model === [])) { - missingSurveyValue = true; - } - }); - $scope.missingSurveyValue = missingSurveyValue; - }, true); - - watchForPromptChanges(); - }); - } - else { - $scope.promptData = { - launchConf: launchConf, - launchOptions: launchOptions, - prompts: prompts, - templateType: ParentObject.type, - template: ParentObject.id - }; - watchForPromptChanges(); - } - } - }); - - } - } - - init(); - - callSelect2(); -}]; diff --git a/awx/ui/client/src/scheduler/schedulerForm.block.less b/awx/ui/client/src/scheduler/schedulerForm.block.less deleted file mode 100644 index ea1244e12013..000000000000 --- a/awx/ui/client/src/scheduler/schedulerForm.block.less +++ /dev/null @@ -1,72 +0,0 @@ -/** @define SchedulerForm */ - -.SchedulerForm-formGroup { - padding-right: 0px; -} - -.SchedulerForm-inputGroup--date { - width: 100%; -} - -#SchedulerFormTarget .DatePicker { - max-height: 31px; -} - -#SchedulerFormTarget .DatePicker-icon { - padding-top: 4px; -} - -input.DatePicker-input[disabled] { - background: @ebgrey; -} - -@media (min-width: 901px) { - .SchedulerForm-formGroup { - flex: 0 0 auto; - margin-bottom: 25px; - width: ~"calc(33% - 32px)"; - margin-right: 50px; - } - - .SchedulerForm-formGroup:nth-child(3n+3) { - flex: 0 0 auto; - margin-bottom: 25px; - margin-right: 0px; - } -} - -@media (min-width: 651px) and (max-width: 900px) { - .SchedulerForm-formGroup { - flex: 0 0 auto; - margin-bottom: 25px; - width: ~"calc(50% - 25px)"; - margin-right: 50px; - } - - .SchedulerForm-formGroup:nth-child(2n+2) { - flex: 0 0 auto; - margin-bottom: 25px; - margin-right: 0px; - } -} - -@media (max-width: 650px) { - .SchedulerForm-formGroup { - flex: 0 0 auto; - margin-bottom: 25px; - width: 100%; - margin-right: 0px; - } -} - -.SchedulerForm-promptSaveTooltip { - position: absolute; - height: 100%; - display: block; - margin-left: 20px; - width: ~"calc(100% - 20px)"; -} - -.SchedulerForm-promptSave { - position: relative; -} diff --git a/awx/ui/client/src/scheduler/schedulerForm.partial.html b/awx/ui/client/src/scheduler/schedulerForm.partial.html deleted file mode 100644 index 900b24f94141..000000000000 --- a/awx/ui/client/src/scheduler/schedulerForm.partial.html +++ /dev/null @@ -1,689 +0,0 @@ -
-
-
{{ schedulerName || strings.get('state.CREATE_SCHEDULE') }}
-
{{ schedulerName || strings.get('state.EDIT_SCHEDULE') }}
-
-
- -
-
-
-
- -
- - -
- {{ strings.get('form.NAME_REQUIRED_MESSAGE') }} -
-
-
- - - -
-
-
-
- -
- - - : - - - - : - - -
-
- {{ strings.get('form.START_TIME_ERROR_MESSAGE') }} -
-
-
- - - -
-
- - -
-
-
-
- {{ strings.get('form.FREQUENCY_DETAILS') }}
-
-
- - - -
- {{ strings.get('form.REPEAT_FREQUENCY_ERROR_MESSAGE') }} -
-
-
-
- -
- -
- {{ strings.get('form.MONTH_DAY_ERROR_MESSAGE') }} -
-
-
-
- -
-
- - -
-
-
-
- * - -
-
- - -
-
- {{ strings.get('form.MONTH_DAY_ERROR_MESSAGE') }} -
-
-
-
- -
-
- - - -
-
-
- -
-
- - - - - - - -
-
-
- {{ strings.get('form.WEEK_DAY_ERROR_MESSAGE') }} -
-
-
- -
- -
-
-
- - -
- {{ strings.get('form.REPEAT_FREQUENCY_ERROR_MESSAGE') }} -
-
-
- - - -
- {{ strings.get('form.PROVIDE_VALID_DATE') }} -
-
-
- -
- - - : - - - - : - - -
-
- {{ strings.get('form.START_TIME_ERROR_MESSAGE') }} -
-
-
-
-
-
-
-

- {{ strings.get('form.SCHEDULER_OPTIONS_ARE_INVALID') }} -

-
-
- -
- {{ rrule_nlp_description }} -
-
- -
- - - -
-
-
    -
  • - {{ occurrence }} UTC -
  • -
-
    -
  • - {{ occurrence }} -
  • -
- -
-
- -
- -
-
-
-
- - - -
- -
- -
-
- -
\ No newline at end of file diff --git a/awx/ui/client/src/scheduler/schedulerFormDetail.block.less b/awx/ui/client/src/scheduler/schedulerFormDetail.block.less deleted file mode 100644 index bb1d0b5993c5..000000000000 --- a/awx/ui/client/src/scheduler/schedulerFormDetail.block.less +++ /dev/null @@ -1,77 +0,0 @@ -/** @define SchedulerFormDetail */ - -.SchedulerFormDetail-container { - padding: 15px; - border: 1px solid @b7grey; - border-radius: 5px; - margin-bottom: 20px; -} - -.SchedulerFormDetail-container--error { - border-color: @default-err; -} - -.SchedulerFormDetail-errorText { - text-align: center; - margin-bottom: 0px; - color: @default-err; -} - -.SchedulerFormDetail-label { - text-transform: uppercase; - color: @default-interface-txt; - margin-bottom: 15px; -} - -.SchedulerFormDetail-nlp { - margin-bottom: 10px; -} - -.SchedulerFormDetail-occurrenceHeader { - display: flex; - flex-wrap: wrap; -} - -.SchedulerFormDetail-labelOccurrence { - font-size: 13px; - font-weight: normal; - margin-bottom: 10px; - margin-right: 20px; -} - -.SchedulerFormDetail-labelDetail { - font-size: 12px; - text-transform: none; -} - -.SchedulerFormDetail-dateFormats { - text-transform: uppercase; - font-size: 13px; - color: @default-interface-txt; -} - -.SchedulerFormDetail-dateFormatsLabel { - font-weight: normal; -} - -.SchedulerFormDetail-radioLabel { - margin-top: -3px !important; - color: @default-data-txt !important; - font-weight: normal; - margin-left: 5px; -} - -.SchedulerFormDetail-dateFormats label:last-of-type { - margin-left: 10px; -} - -.SchedulerFormDetail-radioButton { - margin-top: 2px !important; - color: @default-interface-txt !important; -} - -.SchedulerFormDetail-occurrenceList { - margin-top: 10px; - padding-left: 0px; - list-style-type: none; -} diff --git a/awx/ui/client/src/scheduler/schedulerList.controller.js b/awx/ui/client/src/scheduler/schedulerList.controller.js deleted file mode 100644 index f301c25cce44..000000000000 --- a/awx/ui/client/src/scheduler/schedulerList.controller.js +++ /dev/null @@ -1,211 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name controllers.function:Schedules - * @description This controller's for schedules - */ - - -export default [ - '$filter', '$scope', '$location', '$stateParams', 'ScheduleList', 'Rest', - 'rbacUiControlService', 'JobTemplateModel', 'ToggleSchedule', 'DeleteSchedule', - '$q', '$state', 'Dataset', 'ParentObject', 'UnifiedJobsOptions', 'i18n', 'SchedulerStrings', - function($filter, $scope, $location, $stateParams, ScheduleList, Rest, - rbacUiControlService, JobTemplate, ToggleSchedule, DeleteSchedule, - $q, $state, Dataset, ParentObject, UnifiedJobsOptions, i18n, strings - ) { - - var base, scheduleEndpoint, - list = ScheduleList; - - init(); - - function init() { - if (ParentObject){ - $scope.parentObject = ParentObject; - scheduleEndpoint = ParentObject.endpoint|| ParentObject.related.schedules || `${ParentObject.related.inventory_source}schedules`; - $scope.canAdd = false; - rbacUiControlService.canAdd(scheduleEndpoint) - .then(function(params) { - $scope.canAdd = params.canAdd; - }); - if (_.has(ParentObject, 'type') && ParentObject.type === 'job_template') { - const jobTemplate = new JobTemplate(); - jobTemplate.getLaunch(ParentObject.id) - .then(({data}) => { - if (data.passwords_needed_to_start && - data.passwords_needed_to_start.length > 0 && - !ParentObject.ask_credential_on_launch - ) { - $scope.credentialRequiresPassword = true; - $scope.addTooltip = i18n._("Using a credential that requires a password on launch is prohibited when creating a Job Template schedule"); - } - }); - } - } - - // search init - $scope.list = list; - $scope[`${list.iterator}_dataset`] = Dataset.data; - $scope[list.name] = $scope[`${list.iterator}_dataset`].results; - $scope.unified_job_options = UnifiedJobsOptions.actions.GET; - - // _.forEach($scope[list.name], buildTooltips); - } - - $scope.isValid = (schedule) => { - let type = schedule.summary_fields.unified_job_template.unified_job_type; - switch(type){ - case 'job': - return _.every(['project', 'inventory'], _.partial(_.has, schedule.related)); - case 'project_update': - return _.has(schedule, 'related.project'); - case 'inventory_update': - return _.has(schedule, 'related.inventory'); - default: - return true; - } - }; - - $scope.$on(`${list.iterator}_options`, function(event, data){ - $scope.options = data.data.actions.GET; - optionsRequestDataProcessing(); - }); - - $scope.$watchCollection(`${$scope.list.name}`, function() { - optionsRequestDataProcessing(); - } - ); - - // iterate over the list and add fields like type label, after the - // OPTIONS request returns, or the list is sorted/paginated/searched - function optionsRequestDataProcessing(){ - $scope[list.name].forEach(function(item, item_idx) { - var itm = $scope[list.name][item_idx]; - - // Set the item type label - if (list.fields.type && $scope.unified_job_options && - $scope.unified_job_options.hasOwnProperty('type')) { - $scope.unified_job_options.type.choices.every(function(choice) { - if (choice[0] === itm.summary_fields.unified_job_template.unified_job_type) { - itm.type_label = choice[1]; - return false; - } - return true; - }); - } - buildTooltips(itm); - - let stateParams = { schedule_id: item.id }; - let route = ''; - if (item.summary_fields.unified_job_template) { - if (item.summary_fields.unified_job_template.unified_job_type === 'job') { - route = 'templates.editJobTemplate.schedules.edit'; - stateParams.job_template_id = item.summary_fields.unified_job_template.id; - } else if (item.summary_fields.unified_job_template.unified_job_type === 'project_update') { - route = 'projects.edit.schedules.edit'; - stateParams.project_id = item.summary_fields.unified_job_template.id; - } else if (item.summary_fields.unified_job_template.unified_job_type === 'workflow_job') { - route = 'templates.editWorkflowJobTemplate.schedules.edit'; - stateParams.workflow_job_template_id = item.summary_fields.unified_job_template.id; - } else if (item.summary_fields.unified_job_template.unified_job_type === 'inventory_update') { - route = 'inventories.edit.inventory_sources.edit.schedules.edit'; - stateParams.inventory_id = item.summary_fields.inventory.id; - stateParams.inventory_source_id = item.summary_fields.unified_job_template.id; - } else if (item.summary_fields.unified_job_template.unified_job_type === 'system_job') { - route = 'managementJobsList.schedule.edit'; - stateParams.id = item.summary_fields.unified_job_template.id; - } - } - itm.route = route; - itm.stateParams = stateParams; - itm.linkToDetails = `${route}(${JSON.stringify(stateParams)})`; - }); - } - - function buildTooltips(schedule) { - var job = schedule.summary_fields.unified_job_template; - if (schedule.enabled) { - const tip = (schedule.summary_fields.user_capabilities.edit || $scope.credentialRequiresPassword) ? strings.get('list.SCHEDULE_IS_ACTIVE') : strings.get('list.SCHEDULE_IS_ACTIVE_CLICK_TO_STOP'); - schedule.play_tip = tip; - schedule.status = 'active'; - schedule.status_tip = tip; - } else { - const tip = (schedule.summary_fields.user_capabilities.edit || $scope.credentialRequiresPassword) ? strings.get('list.SCHEDULE_IS_STOPPED') : strings.get('list.SCHEDULE_IS_STOPPED_CLICK_TO_STOP');//i18n._('Schedule is stopped.') : i18n._('Schedule is stopped. Click to activate.'); - schedule.play_tip = tip; - schedule.status = 'stopped'; - schedule.status_tip = tip; - } - - schedule.nameTip = $filter('sanitize')(schedule.name); - // include the word schedule if the schedule name does not include the word schedule - if (schedule.name.indexOf("schedule") === -1 && schedule.name.indexOf("Schedule") === -1) { - schedule.nameTip += " schedule"; - } - schedule.nameTip += " for "; - if (job.name.indexOf("job") === -1 && job.name.indexOf("Job") === -1) { - schedule.nameTip += "job "; - } - schedule.nameTip += $filter('sanitize')(job.name); - schedule.nameTip += `. ${strings.get('list.CLICK_TO_EDIT')}`; - } - - $scope.refreshSchedules = function() { - $state.go('.', null, { reload: true }); - }; - - $scope.addSchedule = function() { - if($state.current.name.endsWith('.edit')) { - $state.go('^.add'); - } - if(!$state.current.name.endsWith('.add')) { - $state.go('.add'); - } - }; - - $scope.editSchedule = function(schedule) { - $state.go(schedule.route, schedule.stateParams); - }; - - $scope.toggleSchedule = function(event, id) { - try { - $(event.target).tooltip('hide'); - } catch (e) { - // ignore - } - ToggleSchedule({ - scope: $scope, - id: id - }); - }; - - $scope.deleteSchedule = function(id) { - DeleteSchedule({ - scope: $scope, - id: id - }); - }; - - base = $location.path().replace(/^\//, '').split('/')[0]; - - if (base === 'management_jobs') { - $scope.base = base = 'system_job_templates'; - } - if ($stateParams.job_type) { - $scope.job_type = $stateParams.job_type; - } - - $scope.refreshJobs = function() { - $state.go('.', null, { reload: true }); - }; - - $scope.formCancel = function() { - $state.go('^', null, { reload: true }); - }; - } -]; diff --git a/awx/ui/client/src/scheduler/schedulertime.block.less b/awx/ui/client/src/scheduler/schedulertime.block.less deleted file mode 100644 index 64a87a46e736..000000000000 --- a/awx/ui/client/src/scheduler/schedulertime.block.less +++ /dev/null @@ -1,36 +0,0 @@ -/** @define SchedulerTime */ - -.SchedulerTime { - display: flex; - flex-wrap: wrap; - align-items: flex-start; -} - -span.ui-spinner.ui-widget.ui-widget-content.ui-corner-all { - flex: 1; -} - -.SchedulerTime-separator { - margin-left: 3px; - margin-right: 3px; - margin-top: 3px; -} - -.SchedulerTime-utc { - flex: 1; - display: flex; - flex-direction: column; - margin-left: 20px; - margin-top: -2px; - font-size: 12px; -} - -.SchedulerTime-utcLabel { - flex: 1; - font-weight: normal; - margin-bottom: 0; -} - -.SchedulerTime-utcTime { - flex: 1; -} diff --git a/awx/ui/client/src/scheduler/schedules.list.js b/awx/ui/client/src/scheduler/schedules.list.js deleted file mode 100644 index 25676225c1dd..000000000000 --- a/awx/ui/client/src/scheduler/schedules.list.js +++ /dev/null @@ -1,116 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['i18n', function(i18n) { - return { - - name: 'schedules', - iterator: 'schedule', - selectTitle: '', - editTitle: 'SCHEDULES', - listTitle: '{{parentObject | sanitize}} || SCHEDULES', - index: false, - hover: true, - layoutClass: 'List-staticColumnLayout--schedules', - staticColumns: [ - { - field: 'invalid', - content: { - label: '', - type: 'invalid', - nosort: true, - awToolTip: i18n._("Resources are missing from this template."), - dataPlacement: 'right', - ngShow: '!isValid(schedule)' - } - }, - { - field: 'toggleSchedule', - content: { - ngDisabled: "!schedule.summary_fields.user_capabilities.edit || credentialRequiresPassword", - label: '', - type: "toggle", - ngClick: "toggleSchedule($event, schedule.id)", - awToolTip: "{{ schedule.play_tip }}", - dataTipWatch: "schedule.play_tip", - dataPlacement: "right", - nosort: true, - } - } - ], - - fields: { - name: { - key: true, - label: i18n._('Name'), - uiSref: "{{schedule.linkToDetails}}", - columnClass: "col-sm-3 col-xs-6" - }, - dtstart: { - label: i18n._('First Run'), - filter: "longDate", - columnClass: "d-none d-sm-flex col-sm-2" - }, - next_run: { - label: i18n._('Next Run'), - filter: "longDate", - columnClass: "d-none d-sm-flex col-sm-2" - }, - dtend: { - label: i18n._('Final Run'), - filter: "longDate", - columnClass: "d-none d-sm-flex col-sm-2" - }, - }, - - actions: { - refresh: { - mode: 'all', - awToolTip: i18n._("Refresh the page"), - ngClick: "refreshSchedules()", - actionClass: 'btn List-buttonDefault', - ngShow: "socketStatus == 'error'", - buttonContent: i18n._('REFRESH') - }, - add: { - mode: 'all', - ngClick: 'credentialRequiresPassword || addSchedule()', - awToolTip: i18n._('Add a new schedule'), - dataTipWatch: 'addTooltip', - actionClass: 'at-Button--add', - actionId: 'button-add', - ngShow: 'canAdd', - ngClass: "{ 'Form-tab--disabled': credentialRequiresPassword }" - } - }, - - fieldActions: { - columnClass: 'col-sm-3 col-xs-6', - edit: { - label: i18n._('Edit'), - ngClick: "editSchedule(schedule)", - icon: 'icon-edit', - awToolTip: i18n._('Edit schedule'), - dataPlacement: 'top', - ngShow: 'schedule.summary_fields.user_capabilities.edit && !credentialRequiresPassword' - }, - view: { - label: i18n._('View'), - ngClick: "editSchedule(schedule)", - awToolTip: i18n._('View schedule'), - dataPlacement: 'top', - ngShow: '!schedule.summary_fields.user_capabilities.edit || credentialRequiresPassword' - }, - "delete": { - label: i18n._('Delete'), - ngClick: "deleteSchedule(schedule.id)", - icon: 'icon-trash', - awToolTip: i18n._('Delete schedule'), - dataPlacement: 'top', - ngShow: 'schedule.summary_fields.user_capabilities.delete' - } - } - };}]; diff --git a/awx/ui/client/src/scheduler/schedules.route.js b/awx/ui/client/src/scheduler/schedules.route.js deleted file mode 100644 index 5126807c4b84..000000000000 --- a/awx/ui/client/src/scheduler/schedules.route.js +++ /dev/null @@ -1,383 +0,0 @@ -import { N_ } from '../i18n'; -import {templateUrl} from '../shared/template-url/template-url.factory'; -import editScheduleResolve from './editSchedule.resolve'; - -const jobTemplatesSchedulesListRoute = { - searchPrefix: 'schedule', - name: 'templates.editJobTemplate.schedules', - route: '/schedules', - data: { - activityStream: true, - activityStreamTarget: 'job_template', - }, - ncyBreadcrumb: { - label: N_('SCHEDULES') - }, - resolve: { - Dataset: ['ScheduleList', 'QuerySet', '$stateParams', 'GetBasePath', - function(list, qs, $stateParams, GetBasePath) { - let path = `${GetBasePath('job_templates')}${$stateParams.job_template_id}/schedules`; - return qs.search(path, $stateParams[`${list.iterator}_search`]); - } - ], - ParentObject: ['$stateParams', 'Rest', 'GetBasePath', function($stateParams, Rest, GetBasePath){ - let path = `${GetBasePath('job_templates')}${$stateParams.job_template_id}`; - Rest.setUrl(path); - return Rest.get(path).then(response => response.data); - }], - UnifiedJobsOptions: ['Rest', 'GetBasePath', '$q', - function(Rest, GetBasePath, $q) { - Rest.setUrl(GetBasePath('unified_jobs')); - var val = $q.defer(); - Rest.options() - .then(function(data) { - val.resolve(data.data); - }, function(data) { - val.reject(data); - }); - return val.promise; - }], - ScheduleList: ['SchedulesList', 'GetBasePath', '$stateParams', - (SchedulesList, GetBasePath, $stateParams) => { - let list = _.cloneDeep(SchedulesList); - list.basePath = GetBasePath('job_templates') + $stateParams.job_template_id + '/schedules/'; - return list; - } - ] - }, - views: { - related: { - templateProvider: function(ScheduleList, generateList){ - ScheduleList.title = false; - let html = generateList.build({ - list: ScheduleList, - mode: 'edit' - }); - return html; - }, - controller: 'schedulerListController' - } - } -}; - -const jobTemplatesSchedulesAddRoute = { - name: 'templates.editJobTemplate.schedules.add', - route: '/add', - views: { - 'scheduler@templates': { - controller: 'schedulerAddController', - templateUrl: templateUrl("scheduler/schedulerForm"), - } - }, - ncyBreadcrumb: { - label: N_('CREATE SCHEDULE') - } -}; - -const jobTemplatesSchedulesEditRoute = { - name: 'templates.editJobTemplate.schedules.edit', - route: '/:schedule_id', - views: { - 'scheduler@templates': { - controller: 'schedulerEditController', - templateUrl: templateUrl("scheduler/schedulerForm"), - } - }, - ncyBreadcrumb: { - label: "{{breadcrumb.schedule_name}}" - }, - resolve: editScheduleResolve() -}; - -// workflows -const workflowSchedulesRoute = { - searchPrefix: 'schedule', - name: 'templates.editWorkflowJobTemplate.schedules', - route: '/schedules', - data: { - activityStream: true, - activityStreamTarget: 'workflow_job_template', - }, - ncyBreadcrumb: { - label: N_('SCHEDULES') - }, - resolve: { - Dataset: ['ScheduleList', 'QuerySet', '$stateParams', 'GetBasePath', - function(list, qs, $stateParams, GetBasePath) { - let path = `${GetBasePath('workflow_job_templates')}${$stateParams.workflow_job_template_id}/schedules`; - return qs.search(path, $stateParams[`${list.iterator}_search`]); - } - ], - ParentObject: ['$stateParams', 'Rest', 'GetBasePath', function($stateParams, Rest, GetBasePath){ - let path = `${GetBasePath('workflow_job_templates')}${$stateParams.workflow_job_template_id}`; - Rest.setUrl(path); - return Rest.get(path).then(response => response.data); - }], - UnifiedJobsOptions: ['Rest', 'GetBasePath', '$stateParams', '$q', - function(Rest, GetBasePath, $stateParams, $q) { - Rest.setUrl(GetBasePath('unified_jobs')); - var val = $q.defer(); - Rest.options() - .then(function(data) { - val.resolve(data.data); - }, function(data) { - val.reject(data); - }); - return val.promise; - }], - ScheduleList: ['SchedulesList', 'GetBasePath', '$stateParams', - (SchedulesList, GetBasePath, $stateParams) => { - let list = _.cloneDeep(SchedulesList); - list.basePath = GetBasePath('workflow_job_templates') + $stateParams.workflow_job_template_id + '/schedules/'; - return list; - } - ] - }, - views: { - related: { - templateProvider: function(ScheduleList, generateList){ - ScheduleList.title = false; - let html = generateList.build({ - list: ScheduleList, - mode: 'edit' - }); - return html; - }, - controller: 'schedulerListController' - } - } -}; - -const workflowSchedulesAddRoute = { - name: 'templates.editWorkflowJobTemplate.schedules.add', - route: '/add', - views: { - 'scheduler@templates': { - controller: 'schedulerAddController', - templateUrl: templateUrl("scheduler/schedulerForm"), - } - }, - ncyBreadcrumb: { - label: N_('CREATE SCHEDULE') - } -}; - -const workflowSchedulesEditRoute = { - name: 'templates.editWorkflowJobTemplate.schedules.edit', - route: '/:schedule_id', - views: { - 'scheduler@templates': { - controller: 'schedulerEditController', - templateUrl: templateUrl("scheduler/schedulerForm"), - } - }, - ncyBreadcrumb: { - label: '{{breadcrumb.schedule_name}}' - }, - resolve: editScheduleResolve() -}; - -const projectsSchedulesListRoute = { - searchPrefix: 'schedule', - name: 'projects.edit.schedules', - route: '/schedules', - data: { - activityStream: true, - activityStreamTarget: 'project', - }, - ncyBreadcrumb: { - label: N_('SCHEDULES') - }, - resolve: { - Dataset: ['ScheduleList', 'QuerySet', '$stateParams', 'GetBasePath', - function(list, qs, $stateParams, GetBasePath) { - let path = `${GetBasePath('projects')}${$stateParams.project_id}/schedules`; - return qs.search(path, $stateParams[`${list.iterator}_search`]); - } - ], - ParentObject: ['$stateParams', 'Rest', 'GetBasePath', function($stateParams, Rest, GetBasePath){ - let path = `${GetBasePath('projects')}${$stateParams.project_id}`; - Rest.setUrl(path); - return Rest.get(path).then(response => response.data); - }], - UnifiedJobsOptions: ['Rest', 'GetBasePath', '$stateParams', '$q', - function(Rest, GetBasePath, $stateParams, $q) { - Rest.setUrl(GetBasePath('unified_jobs')); - var val = $q.defer(); - Rest.options() - .then(function(data) { - val.resolve(data.data); - }, function(data) { - val.reject(data); - }); - return val.promise; - }], - ScheduleList: ['SchedulesList', 'GetBasePath', '$stateParams', - (SchedulesList, GetBasePath, $stateParams) => { - let list = _.cloneDeep(SchedulesList); - list.basePath = GetBasePath('projects') + $stateParams.project_id + '/schedules/'; - return list; - } - ] - }, - views: { - related: { - templateProvider: function(ScheduleList, generateList){ - ScheduleList.title = false; - let html = generateList.build({ - list: ScheduleList, - mode: 'edit' - }); - return html; - }, - controller: 'schedulerListController' - } - } -}; - -const projectsSchedulesAddRoute = { - name: 'projects.edit.schedules.add', - route: '/add', - ncyBreadcrumb: { - label: N_('CREATE SCHEDULE') - }, - views: { - 'scheduler@projects': { - controller: 'schedulerAddController', - templateUrl: templateUrl("scheduler/schedulerForm"), - } - } -}; - -const projectsSchedulesEditRoute = { - name: 'projects.edit.schedules.edit', - route: '/:schedule_id', - ncyBreadcrumb: { - label: "{{breadcrumb.schedule_name}}" - }, - views: { - 'scheduler@projects': { - controller: 'schedulerEditController', - templateUrl: templateUrl("scheduler/schedulerForm"), - } - }, - resolve: editScheduleResolve() -}; - -const jobsSchedulesRoute = { - searchPrefix: 'schedule', - name: 'schedules', - route: '/schedules', - params: { - schedule_search: { - value: { - order_by: 'unified_job_template__polymorphic_ctype__model' - }, - dynamic: true - } - }, - data: { - activityStream: false, - }, - ncyBreadcrumb: { - label: N_('SCHEDULES') - }, - resolve: { - ScheduleList: ['ScheduledJobsList', function(list){ - return list; - }], - Dataset: ['ScheduleList', 'QuerySet', '$stateParams', 'GetBasePath', - function(list, qs, $stateParams, GetBasePath) { - let path = GetBasePath('schedules'); - return qs.search(path, $stateParams[`${list.iterator}_search`]); - } - ], - ParentObject: ['GetBasePath', (GetBasePath) =>{return {endpoint:GetBasePath('schedules')}; }], - UnifiedJobsOptions: ['Rest', 'GetBasePath', '$stateParams', '$q', - function(Rest, GetBasePath, $stateParams, $q) { - Rest.setUrl(GetBasePath('unified_jobs')); - var val = $q.defer(); - Rest.options() - .then(function(data) { - val.resolve(data.data); - }, function(data) { - val.reject(data); - }); - return val.promise; - }] - }, - views: { - '@': { - templateProvider: function(ScheduleList, generateList){ - - let html = generateList.build({ - list: ScheduleList, - mode: 'edit' - }); - html = generateList.wrapPanel(html); - let formPlaceholder = generateList.insertFormView(); - html = formPlaceholder + html; - return html; - }, - controller: 'schedulerListController' - } - } -}; - -// the /#/jobs/schedules/:schedule_id state needs to know about the type of -// resource is being scheduled. -const parentResolve = { - ParentObject: ['$stateParams', 'Rest', 'GetBasePath', 'scheduleResolve', - function($stateParams, Rest, GetBasePath, scheduleResolve){ - let path = scheduleResolve.related.unified_job_template; - Rest.setUrl(path); - return Rest.get(path).then(response => response.data); - } - ] -}; - -const jobsSchedulesEditRoute = { - name: 'schedules.edit', - route: '/:schedule_id', - ncyBreadcrumb: { - parent: 'schedules', - label: "{{breadcrumb.schedule_name}}" - }, - views: { - 'form':{ - templateProvider: function(ParentObject, $http){ - let path; - if(ParentObject.type === 'system_job_template'){ - path = templateUrl('management-jobs/scheduler/schedulerForm'); - } - else { - path = templateUrl('scheduler/schedulerForm'); - } - return $http.get(path).then(response => response.data); - }, - controllerProvider: function(ParentObject){ - if (ParentObject.type === 'system_job_template') { - return 'managementJobEditController'; - } - else { - return 'schedulerEditController'; - } - } - } - }, - resolve: _.merge(editScheduleResolve(), parentResolve) -}; - -export { - jobTemplatesSchedulesListRoute, - jobTemplatesSchedulesAddRoute, - jobTemplatesSchedulesEditRoute, - workflowSchedulesRoute, - workflowSchedulesAddRoute, - workflowSchedulesEditRoute, - projectsSchedulesListRoute, - projectsSchedulesAddRoute, - projectsSchedulesEditRoute, - jobsSchedulesRoute, - jobsSchedulesEditRoute -}; diff --git a/awx/ui/client/src/scheduler/spinnerInput.block.less b/awx/ui/client/src/scheduler/spinnerInput.block.less deleted file mode 100644 index 1f9a44437c51..000000000000 --- a/awx/ui/client/src/scheduler/spinnerInput.block.less +++ /dev/null @@ -1,5 +0,0 @@ -/** @define SpinnerInput */ - -.SpinnerInput { - width: ~"calc(100% - 26px)"; -} diff --git a/awx/ui/client/src/shared/Modal.js b/awx/ui/client/src/shared/Modal.js deleted file mode 100644 index 6e7c73ae8ce7..000000000000 --- a/awx/ui/client/src/shared/Modal.js +++ /dev/null @@ -1,250 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name shared.function:Modal - * @description - * Modal.js - * - * Create a draggable, resizable modal dialog using jQueryUI. - * - * - */ - - -export default -angular.module('ModalDialog', ['Utilities']) - - /** - * @ngdoc method - * @name shared.function:Modal#CreateDialog - * @methodOf shared.function:Modal - * @description - * CreateDialog({ - * id: - id attribute value of the target DOM element - * scope: - Required, $scope associated with the #id DOM element - * buttons: - Required, Array of button objects. See example below. - * width: - Desired width of modal dialog on open. Defaults to 500. - * height: - Desired height of modal on open. Defaults to 600. - * minWidth: - Minimum width that must be maintained regardless of reize attempts. Defaults to 400. - * title: - Modal window title, optional - * onResizeStop: - Function to call when user stops resizing the dialog, optional - * onClose: - Function to call after window closes, optional - * onOpen: - Function to call after window opens, optional - * beforeDestroy: - Function to call during onClose and prior to destroying the window - * callback: - String to pass to scope.$emit() after dialog is created, optional - * }) - * - * Note that the dialog will be created but not opened. It's up to the caller to open it. Use callback - * option to respond to dialog created event. - */ - .factory('CreateDialog', ['Empty', function(Empty) { - - return function(params) { - - let scope = params.scope, - buttonSet = params.buttons, - width = params.width || 500, - height = params.height || 600, - minWidth = params.minWidth || 300, - title = params.title || '', - onResizeStop = params.onResizeStop, - onClose = params.onClose, - onOpen = params.onOpen, - _allowInteraction = params._allowInteraction, - callback = params.callback, - beforeDestroy = params.beforeDestroy, - closeOnEscape = (params.closeOnEscape === undefined) ? false : params.closeOnEscape, - resizable = (params.resizable === undefined) ? false : params.resizable, - draggable = (params.draggable === undefined) ? true : params.draggable, - dialogClass = params.dialogClass, - forms = _.chain([params.form]).flatten().compact().value(), - buttons, - id = params.id, - position = (params.position === undefined) ? { my: "center", at: "center", of: window } : params.position, - x, y, wh, ww; - - function updateButtonStatus(isValid) { - $('.ui-dialog[aria-describedby="' + id + '"]').find('.btn-primary').prop('disabled', !isValid); - } - - if (Empty(buttonSet)) { - // Default button object - buttonSet = [{ - label: "OK", - onClick: function() { - scope.modalOK(); - }, - icon: "", - "class": "btn btn-primary", - "id": "dialog-ok-button" - }]; - } - - buttons = {}; - buttonSet.forEach( function(btn) { - buttons[btn.label] = btn.onClick; - }); - - // Set modal dimensions based on viewport width - ww = $(document).width(); - wh = $(document).height(); - x = (width > ww) ? ww - 10 : width; - y = height === "auto" ? "auto" : ((height > wh) ? wh - 10 : height); - - // Create the modal - $('#' + id).dialog({ - buttons: buttons, - modal: true, - width: x, - height: y, - autoOpen: false, - minWidth: minWidth, - title: title, - closeOnEscape: closeOnEscape, - resizable: resizable, - draggable: draggable, - dialogClass: dialogClass, - position: position, - create: function () { - // Fix the close button - $('.ui-dialog[aria-describedby="' + id + '"]').find('.ui-dialog-titlebar button').empty().attr({'class': 'close'}).html(''); - - setTimeout(function() { - // Make buttons bootstrapy - $('.ui-dialog[aria-describedby="' + id + '"]').find('.ui-dialog-buttonset button').each(function () { - var txt = $(this).text(), self = $(this); - buttonSet.forEach(function(btn) { - if (txt === btn.label) { - self.attr({ "class": btn['class'], "id": btn.id }); - if (btn.icon) { - self.empty().html(' ' + btn.label); - } - } - }); - }); - }, 300); - - if (forms.length > 0) { - forms.map(function(form_ctrl) { - scope.$watch(form_ctrl.$name + '.$valid', updateButtonStatus); - }); - } - - setTimeout(function() { - scope.$apply(function() { - scope.$emit(callback); - }); - }, 300); - }, - resizeStop: function () { - // for some reason, after resizing dialog the form and fields (the content) doesn't expand to 100% - var dialog = $('.ui-dialog[aria-describedby="' + id + '"]'), - titleHeight = dialog.find('.ui-dialog-titlebar').outerHeight(), - buttonHeight = dialog.find('.ui-dialog-buttonpane').outerHeight(), - content = dialog.find('#' + id); - content.width(dialog.width() - 28); - content.css({ height: (dialog.height() - titleHeight - buttonHeight - 10) }); - if (onResizeStop) { - onResizeStop(); - } - }, - close: function () { - // Destroy on close - $('.tooltip').each(function () { - // Remove any lingering tooltip
elements - $(this).remove(); - }); - $('.popover').each(function () { - // remove lingering popover
elements - $(this).remove(); - }); - if (beforeDestroy) { - beforeDestroy(); - } - $('#' + id).dialog('destroy'); - $('#' + id).hide(); - if (onClose) { - onClose(); - } - }, - open: function () { - $('.tooltip').each(function () { - // Remove any lingering tooltip
elements - $(this).remove(); - }); - $('.popover').each(function () { - // remove lingering popover
elements - $(this).remove(); - }); - if (onOpen) { - onOpen(); - } - }, - _allowInteraction: _allowInteraction - }); - }; - }]) - - /** - * TextareaResize({ - * scope: - $scope associated with the textarea element - * textareaId: - id attribute value of the textarea - * modalId: - id attribute of the
element used to create the modal - * formId: - id attribute of the textarea's parent form - * parse: - if true, call ParseTypeChange and replace textarea with codemirror editor - * fld: - optional, form field name - * bottom_margin: - optional, integer value for additional margin to leave below the textarea - * onChange; - optional, function to call when the textarea value changes - * }) - * - * Use to resize a textarea field contained on a modal. Has only been tested where the - * form contains 1 textarea and the the textarea is at the bottom of the form/modal. - * - **/ - .factory('TextareaResize', ['ParseTypeChange', 'Wait', function(ParseTypeChange, Wait){ - return function(params) { - - var scope = params.scope, - textareaId = params.textareaId, - modalId = params.modalId, - formId = params.formId, - fld = params.fld, - parse = (params.parse === undefined) ? true : params.parse, - bottom_margin = (params.bottom_margin) ? params.bottom_margin : 0, - onChange = params.onChange, - textarea, - formHeight, model, windowHeight, offset, rows; - - function waitStop() { - Wait('stop'); - } - - // Attempt to create the largest textarea field that will fit on the window. Minimum - // height is 6 rows, so on short windows you will see vertical scrolling - textarea = $('#' + textareaId); - if (scope.codeMirror) { - model = textarea.attr('ng-model'); - scope[model] = scope.codeMirror.getValue(); - scope.codeMirror.destroy(); - } - textarea.attr('rows', 1); - formHeight = $('#' + formId).height(); - windowHeight = $('#' + modalId).height() - 20; //leave a margin of 20px - offset = Math.floor(windowHeight - formHeight - bottom_margin); - rows = Math.floor(offset / 20); - rows = (rows < 6) ? 6 : rows; - textarea.attr('rows', rows); - while(rows > 6 && ($('#' + formId).height() > $('#' + modalId).height() + bottom_margin)) { - rows--; - textarea.attr('rows', rows); - } - if (parse) { - ParseTypeChange({ scope: scope, field_id: textareaId, onReady: waitStop, variable: fld, onChange: onChange }); - } - }; - }]); diff --git a/awx/ui/client/src/shared/Utilities.js b/awx/ui/client/src/shared/Utilities.js deleted file mode 100644 index 931553dac689..000000000000 --- a/awx/ui/client/src/shared/Utilities.js +++ /dev/null @@ -1,974 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - -/** - * @ngdoc function - * @name shared.function:Utilities - * @description - * Utility functions - * - */ - -/* jshint devel:true */ - - - -export default -angular.module('Utilities', ['RestServices', 'Utilities']) - -/** - * @ngdoc method - * @name shared.function:Utilities#Empty - * @methodOf shared.function:Utilities - * @description Empty() - * - * Test if a value is 'empty'. Returns true if val is null | '' | undefined. - * Only works on non-Ojbect types. - * - */ -.factory('Empty', [ - function() { - return function(val) { - return (val === null || val === undefined || val === '') ? true : false; - }; - } -]) - -/** - * @ngdoc method - * @name shared.function:Utilities#ToggleClass - * @methodOf shared.function:Utilities - * @description - */ -.factory('ToggleClass', function() { - return function(selector, cssClass) { - // Toggles the existance of a css class on a given element - if ($(selector) && $(selector).hasClass(cssClass)) { - $(selector).removeClass(cssClass); - } else if ($(selector)) { - $(selector).addClass(cssClass); - } - }; -}) - - -/** - * @ngdoc method - * @name shared.function:Utilities#Alert - * @methodOf shared.function:Utilities - * @description Pass in the header and message you want displayed on TB modal dialog found in index.html. - * Assumes an #id of 'alert-modal'. Pass in an optional TB alert class (i.e. alert-danger, alert-success, - * alert-info...). Pass an optional function(){}, if you want a specific action to occur when user - * clicks 'OK' button. Set secondAlert to true, when a second dialog is needed. - */ -.factory('Alert', ['$rootScope', '$filter', function($rootScope, $filter) { - return function(hdr, msg, cls, action, secondAlert, disableButtons, backdrop, customStyle) { - var scope = $rootScope.$new(), - alertClass, local_backdrop; - if (customStyle !== true) { - msg = $filter('sanitize')(msg); - } - if (secondAlert) { - - $('#alertHeader2').html(hdr); - $('#alert2-modal-msg').html(msg); - - alertClass = (cls) ? cls : 'alert-danger'; //default alert class is alert-danger - local_backdrop = (backdrop === undefined || backdrop === null) ? "static" : backdrop; - - $('#alert2-modal-msg').attr({ "class": "alert " + alertClass }); - $('#alert-modal2').modal({ - show: true, - keyboard: true, - backdrop: local_backdrop - }); - scope.disableButtons2 = (disableButtons) ? true : false; - - $('#alert-modal2').on('hidden.bs.modal', function() { - $(document).unbind('keydown'); - if (action) { - action(); - } - $('#alert-modal2').off(); - }); - $('#alert-modal2').on('shown.bs.modal', function() { - $('#alert2_ok_btn').focus(); - }); - $(document).bind('keydown', function(e) { - if (e.keyCode === 27 || e.keyCode === 13) { - e.preventDefault(); - $('#alert-modal2').modal('hide'); - } - }); - } else { - - $('#alertHeader').html(hdr); - $('#alert-modal-msg').html(msg); - alertClass = (cls) ? cls : 'alert-danger'; //default alert class is alert-danger - local_backdrop = (backdrop === undefined || backdrop === null) ? "static" : backdrop; - - $('#alert-modal-msg').attr({ "class": "alert " + alertClass }); - $('#alert-modal').modal({ - show: true, - keyboard: true, - backdrop: local_backdrop - }); - - $('#alert-modal').on('hidden.bs.modal', function() { - $(document).unbind('keydown'); - if (action) { - action(); - } - $('.modal-backdrop').remove(); - $('#alert-modal').off(); - }); - $('#alert-modal').on('shown.bs.modal', function() { - $('#alert_ok_btn').focus(); - }); - $(document).bind('keydown', function(e) { - if (e.keyCode === 27 || e.keyCode === 13) { - e.preventDefault(); - $('#alert-modal').modal('hide'); - } - }); - - scope.disableButtons = (disableButtons) ? true : false; - } - }; -}]) - -/** - * @ngdoc method - * @name shared.function:Utilities#ProcessErrors - * @methodOf shared.function:Utilities - * @description For handling errors that are returned from the API - */ -.factory('ProcessErrors', ['$rootScope', '$cookies', '$log', '$location', 'Alert', 'Wait', - function($rootScope, $cookies, $log, $location, Alert, Wait) { - return function(scope, data, status, form, defaultMsg) { - var field, fieldErrors, msg, keys; - Wait('stop'); - $log.debug('Debug status: ' + status); - $log.debug('Debug data: '); - $log.debug(data); - if (defaultMsg.msg) { - $log.debug('Debug: ' + defaultMsg.msg); - } - if (status === 403) { - if (data && data.detail) { - msg = data.detail; - } else { - msg = 'The API responded with a 403 Access Denied error. Please contact your system administrator.'; - } - Alert(defaultMsg.hdr, msg); - } else if (status === 409) { - Alert('Conflict', data.conflict || "Resource currently in use."); - } else if (status === 410) { - Alert('Deleted Object', 'The requested object was previously deleted and can no longer be accessed.'); - } else if ((status === 'Session is expired') || (status === 401)) { - if ($rootScope.sessionTimer) { - $rootScope.sessionTimer.expireSession('idle'); - } - $location.url('/login'); - } else if (data && data.non_field_errors) { - Alert('Error!', data.non_field_errors); - } else if (data && data.detail) { - Alert(defaultMsg.hdr, defaultMsg.msg + ' ' + data.detail); - } else if (data && data.__all__) { - if (typeof data.__all__ === 'object' && Array.isArray(data.__all__)) { - Alert('Error!', data.__all__[0]); - } else { - Alert('Error!', data.__all__); - } - } else if (form) { //if no error code is detected it begins to loop through to see where the api threw an error - fieldErrors = false; - - const addApiErrors = (field, fld) => { - if (data[fld] && field.tab) { - // If the form is part of a tab group, activate the tab - $('#' + form.name + "_tabs a[href=\"#" + field.tab + '"]').tab('show'); - } - if (field.realName) { - if (field.realName) { - scope[fld + '_api_error'] = data[field.realName][0]; - //scope[form.name + '_form'][form.fields[field].realName].$setValidity('apiError', false); - $('[name="' + field.realName + '"]').addClass('ng-invalid'); - $('html, body').animate({scrollTop: $('[name="' + field.realName + '"]').offset().top}, 0); - fieldErrors = true; - } - } - if (field.sourceModel) { - if (data[fld]) { - scope[field.sourceModel + '_' + field.sourceField + '_api_error'] = - data[fld][0]; - //scope[form.name + '_form'][form.fields[field].sourceModel + '_' + form.fields[field].sourceField].$setValidity('apiError', false); - $('[name="' + field.sourceModel + '_' + field.sourceField + '"]').addClass('ng-invalid'); - $('[name="' + field.sourceModel + '_' + field.sourceField + '"]').ScrollTo({ "onlyIfOutside": true, "offsetTop": 100 }); - fieldErrors = true; - } - } else { - if (data[fld]) { - scope[fld + '_api_error'] = data[fld][0]; - $('[name="' + fld + '"]').addClass('ng-invalid'); - $('label[for="' + fld + '"] span').addClass('error-color'); - $('html, body').animate({scrollTop: $('[name="' + fld + '"]').offset().top}, 0); - fieldErrors = true; - if(field.codeMirror){ - $(`#cm-${fld}-container .CodeMirror`).addClass('error-border'); - } - } - } - }; - - for (field in form.fields) { - if (form.fields[field].type === "checkbox_group") { - form.fields[field].fields.forEach(fld => { - addApiErrors(fld, fld.name); - }); - } else { - addApiErrors(form.fields[field], field); - } - } - if (defaultMsg) { - Alert(defaultMsg.hdr, defaultMsg.msg); - } - } else if (typeof data === 'object' && data !== null) { - if (Object.keys(data).length > 0) { - keys = Object.keys(data); - msg = ""; - _.forOwn(data, function(value, key) { - if (Array.isArray(data[key])) { - msg += `${key.toUpperCase()}: ${data[key][0]}`; - } else { - msg += `${key.toUpperCase()}: ${value} `; - } - }); - Alert(defaultMsg.hdr, msg); - } else { - Alert(defaultMsg.hdr, defaultMsg.msg); - } - } else { - Alert(defaultMsg.hdr, defaultMsg.msg); - } - }; - } -]) - -/** - * @ngdoc method - * @name shared.function:Utilities#HelpDialog - * @methodOf shared.function:Utilities - * @description Display a help dialog - * - * HelpDialog({ defn: }) - * discuss difference b/t this and other modal windows/dialogs - */ -.factory('HelpDialog', ['$rootScope', '$compile', '$location', 'Store', - function($rootScope, $compile, $location, Store) { - return function(params) { - - var defn = params.defn, - current_step = params.step, - autoShow = params.autoShow || false, - scope = (params.scope) ? params.scope : $rootScope.$new(); - - function setButtonMargin() { - var width = ($('.ui-dialog[aria-describedby="help-modal-dialog"] .ui-dialog-buttonpane').innerWidth() / 2) - $('#help-next-button').outerWidth() - 93; - $('#help-next-button').css({ 'margin-right': width + 'px' }); - } - - function showHelp(step) { - - var e, btns, ww, width, height, isOpen = false; - current_step = step; - - function buildHtml(step) { - var html = ''; - html += "

" + step.intro + "

\n"; - if (step.img) { - html += "
\n"; - html += "\n" : ""; - html += "" + step.box + "
"; - html += (autoShow && step.autoOffNotice) ? "
\n" : ""; - return html; - } - - width = (defn.story.width) ? defn.story.width : 510; - height = (defn.story.height) ? defn.story.height : 600; - - // Limit modal width to width of viewport - ww = $(document).width(); - width = (width > ww) ? ww : width; - - try { - isOpen = $('#help-modal-dialog').dialog('isOpen'); - } catch (err) { - // ignore - } - - e = angular.element(document.getElementById('help-modal-dialog')); - e.empty().html(buildHtml(defn.story.steps[current_step])); - setTimeout(function() { scope.$apply(function() { $compile(e)(scope); }); }); - - if (!isOpen) { - // Define buttons based on story length - btns = []; - if (defn.story.steps.length > 1) { - btns.push({ - text: "Prev", - click: function() { - if (current_step - 1 === 0) { - $('#help-prev-button').prop('disabled', true); - } - if (current_step - 1 < defn.story.steps.length - 1) { - $('#help-next-button').prop('disabled', false); - } - showHelp(current_step - 1); - }, - disabled: true - }); - btns.push({ - text: "Next", - click: function() { - if (current_step + 1 > 0) { - $('#help-prev-button').prop('disabled', false); - } - if (current_step + 1 >= defn.story.steps.length - 1) { - $('#help-next-button').prop('disabled', true); - } - showHelp(current_step + 1); - } - }); - } - btns.push({ - text: "Close", - click: function() { - $('#help-modal-dialog').dialog('close'); - } - }); - - $('.overlay').css({ - width: $(document).width(), - height: $(document).height() - }).fadeIn(); - - // Show the dialog - $('#help-modal-dialog').dialog({ - position: { - my: "center top", - at: "center top+150", - of: 'body' - }, - title: defn.story.hdr, - width: width, - height: height, - buttons: btns, - closeOnEscape: true, - show: 500, - hide: 500, - resizable: false, - close: function() { - $('.overlay').hide(); - $('#help-modal-dialog').empty(); - } - }); - - // Make the buttons look like TB and add FA icons - $('.ui-dialog-buttonset button').each(function() { - var c, h, i, l; - l = $(this).text(); - if (l === 'Close') { - h = "fa-times"; - c = "btn btn-default"; - i = "help-close-button"; - $(this).attr({ - 'class': c, - 'id': i - }).html(" Close"); - } else if (l === 'Prev') { - h = "fa-chevron-left"; - c = "btn btn-primary"; - i = "help-prev-button"; - $(this).attr({ - 'class': c, - 'id': i - }).html(" Prev"); - } else { - h = "fa-chevron-right"; - c = "btn btn-primary"; - i = "help-next-button"; - $(this).attr({ - 'class': c, - 'id': i - }).html("Next ").css({ - 'margin-right': '20px' - }); - } - }); - - $('.ui-dialog[aria-describedby="help-modal-dialog"]').find('.ui-dialog-titlebar button') - .empty().attr({ - 'class': 'close' - }).text('x'); - - // If user clicks the checkbox, update local storage - $('#auto-off-checkbox').click(function() { - if ($('input[name="auto-off-checkbox"]:checked').length) { - Store('inventoryAutoHelp', 'off'); - } else { - Store('inventoryAutoHelp', 'on'); - } - }); - - setButtonMargin(); - } - } - - showHelp(0); - }; - } -]) - - -/** - * @ngdoc method - * @name shared.function:Utilities#ReturnToCaller - * @methodOf shared.function:Utilities - * @description - * Split the current path by '/' and use the array elements from 0 up to and - * including idx as the new path. If no idx value supplied, use 0 to length - 1. - * - */ -.factory('ReturnToCaller', ['$location', 'Empty', - function($location, Empty) { - return function(idx) { - var paths = $location.path().replace(/^\//, '').split('/'), - newpath = '', - i; - idx = (Empty(idx)) ? paths.length - 1 : idx + 1; - for (i = 0; i < idx; i++) { - newpath += '/' + paths[i]; - } - $location.path(newpath); - }; - } -]) - - -/** - * @ngdoc method - * @name shared.function:Utilities#FormatDate - * @methodOf shared.function:Utilities - * @description - * Wrapper for data filter- an attempt to insure all dates display in - * the same format. Pass in date object or string. See: http://docs.angularjs.org/api/ng.filter:date - */ -.factory('FormatDate', ['$filter', - function($filter) { - return function(dt) { - return $filter('longDate')(dt); - }; - } -]) - -/** - * @ngdoc method - * @name shared.function:Utilities#Wait - * @methodOf shared.function:Utilities - * @description - * Display a spinning icon in the center of the screen to freeze the - * UI while waiting on async things to complete (i.e. API calls). - * Wait('start' | 'stop'); - * - */ -.factory('Wait', ['$rootScope', - function($rootScope) { - - return function(directive) { - var docw, doch, spinnyw, spinnyh; - if (directive === 'start' && !$rootScope.waiting) { - $rootScope.waiting = true; - docw = $(window).width(); - doch = $(window).height(); - spinnyw = $('.spinny').width(); - spinnyh = $('.spinny').height(); - $('.overlay').css({ - width: $(document).width(), - height: $(document).height() - }).fadeIn(); - $('.spinny').css({ - bottom: 15, - right: 15 - }).fadeIn(400); - } else if (directive === 'stop' && $rootScope.waiting) { - $('.spinny, .overlay').fadeOut(400, function() { - $rootScope.waiting = false; - }); - } - }; - } -]) - -.factory('HideElement', [ - function() { - return function(selector, action) { - // Fade-in a cloack or vail or a specific element - var target = $(selector), - width = target.css('width'), - height = target.css('height'), - position = target.position(), - parent = target.parent(), - borderRadius = target.css('border-radius'), - backgroundColor = target.css('background-color'), - margin = target.css('margin'), - padding = target.css('padding'); - - parent.append("
"); - $('#curtain-div').show(0, action); - }; - } -]) - -.factory('ShowElement', [ - function() { - return function() { - // And Fade-out the cloack revealing the element - $('#curtain-div').fadeOut(500, function() { - $(this).remove(); - }); - }; - } -]) - -/** - * @ngdoc method - * @name shared.function:Utilities#CreateSelect2 - * @methodOf shared.function:Utilities - * @description Make a regular select drop down a select2 dropdown - * To make a `` - -
- -
- ${uploadedText} - Current logo -
- -
`, - - link: function(scope) { - var fieldKey = scope.key; - var filePickerText = angular.element(document.getElementById('filePickerText')); - var filePickerError = angular.element(document.getElementById('filePickerError')); - var filePickerButton = angular.element(document.getElementById('filePickerButton')); - var filePicker = angular.element(document.getElementById('filePicker')); - - scope.imagePresent = global.$AnsibleConfig.custom_logo || false; - scope.imageData = $rootScope.custom_logo; - - scope.$on('loginUpdated', function() { - scope.imagePresent = global.$AnsibleConfig.custom_logo; - scope.imageData = $rootScope.custom_logo; - }); - - scope.$watch('imagePresent', (val) => { - if(val){ - filePickerButton.html(removeText); - } - else{ - filePickerButton.html(browseText); - } - }); - - scope.$on(fieldKey+'_reverted', function(e) { - scope.update(e, true); - }); - - scope.update = function(e, flag) { - if(scope.$parent[fieldKey] || flag ) { - e.preventDefault(); - scope.$parent[fieldKey] = ''; - filePickerButton.html(browseText); - filePickerText.val(''); - filePicker.context.value = ""; - scope.imagePresent = false; - } - else { - // Nothing exists so open file picker - } - }; - - scope.fileChange = function(file) { - filePickerError.html(''); - - SettingsUtils.imageProcess(file[0]) - .then(function(result) { - scope.$parent[fieldKey] = result; - filePickerText.val(file[0].name); - filePickerButton.html(removeText); - }).catch(function(error) { - filePickerText.html(file[0].name); - filePickerError.text(error); - }).finally(function() { - - }); - }; - - } - }; -}]) - - -.directive('surveyCheckboxes', function() { - return { - restrict: 'E', - require: 'ngModel', - scope: { ngModel: '=ngModel' }, - template: '
' + - '' + - '
', - link: function(scope, element, attrs, ctrl) { - scope.cbModel = {}; - ctrl.$setValidity('reqCheck', true); - angular.forEach(scope.ngModel.value, function(value) { - scope.cbModel[value] = true; - - }); - - if (scope.ngModel.required === true && scope.ngModel.value.length === 0) { - ctrl.$setValidity('reqCheck', false); - } - - ctrl.$parsers.unshift(function(viewValue) { - for (var c in scope.cbModel) { - if (scope.cbModel[c]) { - ctrl.$setValidity('checkbox', true); - } - } - ctrl.$setValidity('checkbox', false); - - return viewValue; - }); - - scope.update = function() { - var val = []; - angular.forEach(scope.cbModel, function(v, k) { - if (v) { - val.push(k); - } - }); - if (val.length > 0) { - scope.ngModel.value = val; - scope.$parent[scope.ngModel.name] = val; - ctrl.$setValidity('checkbox', true); - ctrl.$setValidity('reqCheck', true); - } else if (scope.ngModel.required === true) { - ctrl.$setValidity('checkbox', false); - } - }; - } - }; -}) - -// the disableRow directive disables table row click events -.directive('disableRow', function() { - return { - restrict: 'A', - link: function(scope, element, attrs) { - element.bind('click', function(event) { - if (scope.$eval(attrs.disableRow)) { - event.preventDefault(); - } - return; - }); - } - }; -}) - - -.directive('awSurveyQuestion', function() { - return { - require: 'ngModel', - link: function(scope, elm, attrs, ctrl) { - ctrl.$parsers.unshift(function(viewValue) { - var values = viewValue.split(" "), - result = "", - i; - result += values[0].charAt(0).toUpperCase() + values[0].substr(1) + ' '; - for (i = 1; i < values.length; i++) { - result += values[i] + ' '; - } - result = result.trim(); - if (result !== viewValue) { - ctrl.$setViewValue(result); - ctrl.$render(); - } - return result; - }); - } - }; -}) - -.directive('awMin', ['Empty', function(Empty) { - return { - restrict: 'A', - require: 'ngModel', - link: function(scope, elem, attr, ctrl) { - ctrl.$parsers.unshift(function(viewValue) { - var min = (attr.awMin) ? scope.$eval(attr.awMin) : -Infinity; - if (!Empty(min) && !Empty(viewValue) && Number(viewValue) < min) { - ctrl.$setValidity('awMin', false); - return viewValue; - } else { - ctrl.$setValidity('awMin', true); - return viewValue; - } - }); - } - }; -}]) - -.directive('awMax', ['Empty', function(Empty) { - return { - restrict: 'A', - require: 'ngModel', - link: function(scope, elem, attr, ctrl) { - ctrl.$parsers.unshift(function(viewValue) { - var max = (attr.awMax) ? scope.$eval(attr.awMax) : Infinity; - if (!Empty(max) && !Empty(viewValue) && Number(viewValue) > max) { - ctrl.$setValidity('awMax', false); - return viewValue; - } else { - ctrl.$setValidity('awMax', true); - return viewValue; - } - }); - } - }; -}]) - -.directive('awRange', ['Empty', function(Empty) { - return { - restrict: 'A', - require: 'ngModel', - link: function(scope, elem, attr, ctrl) { - - let checkRange = function(viewValue){ - ctrl.$setValidity('awRangeMin', true); - ctrl.$setValidity('awRangeMax', true); - var max = (attr.rangeMax) ? scope.$eval(attr.rangeMax) : Infinity; - var min = (attr.rangeMin) ? scope.$eval(attr.rangeMin) : -Infinity; - if (!Empty(max) && !Empty(viewValue) && Number(viewValue) > max) { - ctrl.$setValidity('awRangeMax', false); - } - else if(!Empty(min) && !Empty(viewValue) && Number(viewValue) < min) { - ctrl.$setValidity('awRangeMin', false); - } - return viewValue; - }; - - scope.$watch(attr.rangeMin, function () { - checkRange(scope.$eval(attr.ngModel)); - }); - - scope.$watch(attr.rangeMax, function () { - checkRange(scope.$eval(attr.ngModel)); - }); - - ctrl.$parsers.unshift(function(viewValue) { - return checkRange(viewValue); - }); - } - }; -}]) - -.directive('smartFloat', function() { - var FLOAT_REGEXP = /(^\-?\d+)?((\.|\,)\d+)?$/; - return { - require: 'ngModel', - link: function(scope, elm, attrs, ctrl) { - ctrl.$parsers.unshift(function(viewValue) { - if (viewValue === '' || FLOAT_REGEXP.test(viewValue)) { - ctrl.$setValidity('float', true); - return parseFloat(viewValue.replace(',', '.')); - } else { - ctrl.$setValidity('float', false); - return undefined; - } - }); - } - }; -}) - -// integer Validate that input is of type integer. Taken from Angular developer -// guide, form examples. Add min and max directives, and this will check -// entered values is within the range. -// -// Use input type of 'text'. Use of 'number' casuses browser validation to -// override/interfere with this directive. -.directive('integer', function() { - return { - restrict: 'A', - require: 'ngModel', - link: function(scope, elm, attrs, ctrl) { - ctrl.$parsers.unshift(function(viewValue) { - ctrl.$setValidity('min', true); - ctrl.$setValidity('max', true); - if (/^\-?\d*$/.test(viewValue)) { - // it is valid - ctrl.$setValidity('integer', true); - if (viewValue === '-' || viewValue === '-0' || viewValue === null) { - ctrl.$setValidity('integer', false); - return viewValue; - } - if (elm.attr('min') && - parseInt(viewValue, 10) < parseInt(elm.attr('min'), 10)) { - ctrl.$setValidity('min', false); - return viewValue; - } - if (elm.attr('max') && (parseInt(viewValue, 10) > parseInt(elm.attr('max'), 10))) { - ctrl.$setValidity('max', false); - return viewValue; - } - return viewValue; - } - // Invalid, return undefined (no model update) - ctrl.$setValidity('integer', false); - return viewValue; - }); - } - }; -}) - -//the awSurveyVariableName directive checks if the field contains any spaces. -// this could be elaborated in the future for other things we want to check this field against -.directive('awSurveyVariableName', function() { - var FLOAT_REGEXP = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/; - return { - restrict: 'A', - require: 'ngModel', - link: function(scope, elm, attrs, ctrl) { - ctrl.$setValidity('required', true); // we only want the error message for incorrect characters to be displayed - ctrl.$parsers.unshift(function(viewValue) { - if (viewValue.length !== 0) { - if (FLOAT_REGEXP.test(viewValue) && viewValue.indexOf(' ') === -1) { //check for a spaces - ctrl.$setValidity('variable', true); - return viewValue; - } else { - ctrl.$setValidity('variable', false); // spaces found, therefore throw error. - return viewValue; - } - } else { - ctrl.$setValidity('variable', true); // spaces found, therefore throw error. - return viewValue; - } - }); - } - }; -}) - -// -// awRequiredWhen: { reqExpression: "", init: "true|false" } -// -// Make a field required conditionally using an expression. If the expression evaluates to true, the -// field will be required. Otherwise, the required attribute will be removed. -// -.directive('awRequiredWhen', function() { - return { - require: 'ngModel', - compile: function(tElem) { - return { - pre: function preLink() { - let label = $(tElem).closest('.form-group').find('label').first(); - $(label).prepend('*'); - }, - post: function postLink( scope, elm, attrs, ctrl ) { - function updateRequired() { - var isRequired = scope.$eval(attrs.awRequiredWhen); - - var viewValue = elm.val(), - label, validity = true; - label = $(elm).closest('.form-group').find('label').first(); - - if (isRequired && (elm.attr('required') === null || elm.attr('required') === undefined)) { - $(elm).attr('required', 'required'); - if(!$(label).find('span.Form-requiredAsterisk').length){ - $(label).prepend('*'); - } - } else if (!isRequired) { - elm.removeAttr('required'); - if (!attrs.awrequiredAlwaysShowAsterisk) { - $(label).find('span.Form-requiredAsterisk').remove(); - } - } - if (isRequired && (viewValue === undefined || viewValue === null || viewValue === '')) { - validity = false; - } - ctrl.$setValidity('required', validity); - } - - scope.$watchGroup([attrs.awRequiredWhen, $(elm).attr('name')], function() { - // watch for the aw-required-when expression to change value - updateRequired(); - }); - - if (attrs.awrequiredInit !== undefined && attrs.awrequiredInit !== null) { - // We already set a watcher on the attribute above so no need to call updateRequired() in here - scope[attrs.awRequiredWhen] = attrs.awrequiredInit; - } - } - }; - } - }; -}) - -// awPlaceholder: Dynamic placeholder set to a scope variable you want watched. -// Value will be place in field placeholder attribute. -.directive('awPlaceholder', [function() { - return { - require: 'ngModel', - link: function(scope, elm, attrs) { - $(elm).attr('placeholder', scope[attrs.awPlaceholder]); - scope.$watch(attrs.awPlaceholder, function(newVal) { - $(elm).attr('placeholder', newVal); - }); - } - }; -}]) - -// lookup Validate lookup value against API -.directive('awlookup', ['Rest', 'GetBasePath', '$q', '$state', function(Rest, GetBasePath, $q, $state) { - return { - require: 'ngModel', - link: function(scope, elm, attrs, fieldCtrl) { - let query, - basePath, - defer = $q.defer(), - autopopulateLookup, - modelKey = attrs.ngModel, - modelName = attrs.source, - watcher = attrs.awRequiredWhen || undefined, - watchBasePath, - awLookupWhen = attrs.awLookupWhen; - - if (attrs.autopopulatelookup !== undefined) { - autopopulateLookup = JSON.parse(attrs.autopopulatelookup); - } else { - autopopulateLookup = true; - } - - // The following block of code is for instances where the - // lookup field is reused by varying sub-forms. Example: The groups - // form will change it's credential lookup based on the - // source type. The basepath the lookup should utilize is dynamic - // in this case. You'd configure the "watchBasePath" key on the - // field's configuration in the form configuration field. - if (attrs.watchbasepath !== undefined) { - watchBasePath = attrs.watchbasepath; - scope.$watch(watchBasePath, (newValue) => { - if(newValue !== undefined && fieldIsAutopopulatable()){ - _doAutoPopulate(); - } - }); - } - - function _doAutoPopulate() { - let query = '?role_level=use_role'; - - if (attrs.watchbasepath !== undefined && scope[attrs.watchbasepath] !== undefined) { - basePath = scope[attrs.watchbasepath]; - if (attrs.watchbasepath !== "projectBasePath") { - query = '&role_level=use_role'; - } else { - query = ''; - } - } - else { - basePath = GetBasePath(elm.attr('data-basePath')) || elm.attr('data-basePath'); - let switchType = attrs.awlookuptype ? attrs.awlookuptype : modelName; - - switch(switchType) { - case 'credential': - query = '?credential_type__kind=ssh&role_level=use_role'; - break; - case 'scm_credential': - query = '?credential_type__kind=scm&role_level=use_role'; - break; - case 'network_credential': - query = '?credential_type__kind=net&role_level=use_role'; - break; - case 'insights_credential': - query = '?credential_type__kind=insights&role_level=use_role'; - break; - case 'organization': - query = '?role_level=admin_role'; - break; - case 'inventory_script': - query = '?role_level=admin_role&organization=' + scope.$resolve.inventoryData.summary_fields.organization.id; - break; - } - - } - - Rest.setUrl(`${basePath}` + query); - Rest.get() - .then(({data}) => { - if (data.count === 1) { - scope[modelKey] = data.results[0].name; - scope[modelName] = data.results[0].id; - } - }); - } - - if (fieldIsAutopopulatable()) { - _doAutoPopulate(); - } - - // This checks to see if the field meets the criteria to - // autopopulate: - // Population rules: - // - add form only - // - lookup is required - // - lookup is not promptable - // - user must only have access to 1 item the lookup is for - function fieldIsAutopopulatable() { - if (autopopulateLookup === false) { - return false; - } - if (scope.mode === "add") { - if(watcher){ - scope.$watch(watcher, () => { - if(Boolean(scope.$eval(watcher)) === true){ - - // if we get here then the field is required - // by way of awRequiredWhen - // and is a candidate for autopopulation - - _doAutoPopulate(); - } - }); - } - else if (attrs.required === true) { - return true; - } - else { - return false; - } - } - else { - return false; - } - } - - // query the API to see if field value corresponds to a valid resource - // .ng-pending will be applied to the directive element while the request is outstanding - // form.$pending will contain object reference to any ngModelControllers with outstanding requests - fieldCtrl.$asyncValidators.validResource = function(modelValue, viewValue) { - - if(awLookupWhen === undefined || (awLookupWhen !== undefined && Boolean(scope.$eval(awLookupWhen)) === true)) { - applyValidationStrategy(viewValue, fieldCtrl); - } - else { - defer.resolve(); - } - - return defer.promise; - }; - - function applyValidationStrategy(viewValue, ctrl) { - - // use supplied data attributes to build an endpoint, query, resolve outstanding promise - function applyValidation(viewValue) { - basePath = GetBasePath(elm.attr('data-basePath')) || elm.attr('data-basePath'); - query = elm.attr('data-query'); - query = query.replace(/\:value/, encodeURIComponent(viewValue)); - - let base = attrs.awlookuptype ? attrs.awlookuptype : ctrl.$name.split('_name')[0]; - if (attrs.watchbasepath !== undefined && scope[attrs.watchbasepath] !== undefined) { - basePath = scope[attrs.watchbasepath]; - query += '&role_level=use_role'; - query = query.replace('?', '&'); - } - else { - switch(base) { - case 'credential': - query += '&credential_type__namespace=ssh&role_level=use_role'; - break; - case 'scm_credential': - query += '&redential_type__namespace=scm&role_level=use_role'; - break; - case 'network_credential': - query += '&redential_type__namespace=net&role_level=use_role'; - break; - case 'cloud_credential': - query += '&cloud=true&role_level=use_role'; - break; - case 'organization': - if ($state.current.name.includes('inventories')) { - query += '&role_level=inventory_admin_role'; - } else if ($state.current.name.includes('templates.editWorkflowJobTemplate')) { - query += '&role_level=workflow_admin_role'; - } else if ($state.current.name.includes('projects')) { - query += '&role_level=project_admin_role'; - } else if ($state.current.name.includes('notifications')) { - query += '&role_level=notification_admin_role'; - } else { - query += '&role_level=admin_role'; - } - break; - case 'inventory_script': - query += '&role_level=admin_role&organization=' + scope.$resolve.inventoryData.summary_fields.organization.id; - break; - default: - query += '&role_level=use_role'; - } - } - - Rest.setUrl(`${basePath}${query}`); - // https://github.com/ansible/ansible-tower/issues/3549 - // capturing both success/failure conditions in .then() promise - // when #3549 is resolved, this will need to be partitioned into success/error or then/catch blocks - return Rest.get() - .then((res) => { - if (res.data.results.length > 0) { - scope[elm.attr('data-source')] = res.data.results[0].id; - return setValidity(ctrl, true); - } else { - scope[elm.attr('data-source')] = null; - return setValidity(ctrl, false); - } - }); - } - - function setValidity(ctrl, validity){ - var isRequired; - if (attrs.required) { - isRequired = true; - } else { - isRequired = false; - } - if (attrs.awRequiredWhen) { - if (attrs.awRequiredWhen.charAt(0) === "!") { - isRequired = !scope[attrs.awRequiredWhen.slice(1, attrs.awRequiredWhen.length)]; - } else { - isRequired = scope[attrs.awRequiredWhen]; - } - } - if (!isRequired && (viewValue === undefined || viewValue === undefined || viewValue === "")) { - validity = true; - } - ctrl.$setValidity('awlookup', validity); - return defer.resolve(validity); - } - - // Three common cases for clarity: - - // 1) Field is not required & pristine. Pass validation & skip async $pending state - // 2) Field is required. Always validate & use async $pending state - // 3) Field is not required, but is not $pristine. Always validate & use async $pending state - - // case 1 - if (!ctrl.$validators.required && ctrl.$pristine) { - return setValidity(ctrl, true); - } - // case 2 & 3 - else { - return applyValidation(viewValue); - } - } - } - }; -}]) - -// -// awValidUrl -// -.directive('awValidUrl', [function() { - return { - require: 'ngModel', - link: function(scope, elm, attrs, ctrl) { - ctrl.$parsers.unshift(function(viewValue) { - var validity = true, - rgx, rgx2; - if (viewValue !== '') { - ctrl.$setValidity('required', true); - rgx = /^(https|http|ssh)\:\/\//; - rgx2 = /\@/g; - if (!rgx.test(viewValue) || rgx2.test(viewValue)) { - validity = false; - } - } - ctrl.$setValidity('awvalidurl', validity); - - return viewValue; - }); - } - }; -}]) - -/* - * Enable TB tooltips. To add a tooltip to an element, include the following directive in - * the element's attributes: - * - * aw-tool-tip="<< tooltip text here >>" - * - * Include the standard TB data-XXX attributes to controll a tooltip's appearance. We will - * default placement to the left and delay to the config setting. - */ -.directive('awToolTip', ['$transitions', function($transitions) { - return { - link: function(scope, element, attrs) { - var delay = { show: 200, hide: 0 }, - placement, - container, - stateChangeWatcher; - if (attrs.awTipPlacement) { - placement = attrs.awTipPlacement; - } else { - placement = (attrs.placement !== undefined && attrs.placement !== null) ? attrs.placement : 'left'; - } - - container = attrs.container ? attrs.container : 'body'; - - var template; - - let tooltipInnerClass = (attrs.tooltipInnerClass || attrs.tooltipinnerclass) ? (attrs.tooltipInnerClass || attrs.tooltipinnerclass) : ''; - let tooltipOuterClass = attrs.tooltipOuterClass ? attrs.tooltipOuterClass : ''; - - template = ''; - - // This block helps clean up tooltips that may get orphaned by a click event - $(element).on('mouseenter', function(event) { - - var elem = $(event.target).parent(); - if (elem[0].nodeName === "SOURCE-SUMMARY-POPOVER") { - $('.popover').popover('hide'); - } - - if (stateChangeWatcher) { - // Un-bind - we don't want a bunch of listeners firing - stateChangeWatcher(); - } - - stateChangeWatcher = $transitions.onStart({}, function() { - // Go ahead and force the tooltip setTimeout to expire (if it hasn't already fired) - $(element).tooltip('hide'); - // Clean up any existing tooltips including this one - $('.tooltip').each(function() { - $(this).remove(); - }); - }); - }); - - $(element).on('hidden.bs.tooltip', function() { - // TB3RC1 is leaving behind tooltip
elements. This will remove them - // after a tooltip fades away. If not, they lay overtop of other elements and - // honk up the page. - $('.tooltip').each(function() { - $(this).remove(); - }); - }); - - $(element).tooltip({ - placement: placement, - delay: delay, - html: true, - title: attrs.awToolTip, - container: container, - trigger: 'hover', - template: template, - boundary: 'window' - }); - - if (attrs.tipWatch) { - // Add dataTipWatch: 'variable_name' - scope.$watch(attrs.tipWatch, function(newVal) { - // Where did fixTitle come from?: - // http://stackoverflow.com/questions/9501921/change-twitter-bootstrap-tooltip-content-on-click - $(element).tooltip('hide').attr('data-original-title', newVal).tooltip('_fixTitle'); - }); - } - } - }; -}]) - -/* - * Enable TB pop-overs. To add a pop-over to an element, include the following directive in - * the element's attributes: - * - * aw-pop-over="<< pop-over html here >>" - * - * Include the standard TB data-XXX attributes to controll the pop-over's appearance. We will - * default placement to the left, delay to 0 seconds, content type to HTML, and title to 'Help'. - */ -.directive('awPopOver', ['$compile', function($compile) { - return function(scope, element, attrs) { - var placement = (attrs.placement !== undefined && attrs.placement !== null) ? attrs.placement : 'left', - title = (attrs.overTitle) ? attrs.overTitle : (attrs.popoverTitle) ? attrs.popoverTitle : 'Help', - container = (attrs.container !== undefined) ? attrs.container : false, - trigger = (attrs.trigger !== undefined) ? attrs.trigger : 'manual', - template = '', - id_to_close = ""; - - if (element[0].id) { - template = ''; - } - - scope.triggerPopover = function(e) { - showPopover(e); - }; - - if (attrs.awPopOverWatch) { - $(element).popover({ - placement: placement, - delay: 0, - title: title, - content: function() { - return _.get(scope, attrs.awPopOverWatch); - }, - trigger: trigger, - html: true, - container: container, - template: template - }); - } else { - $(element).popover({ - placement: placement, - delay: 0, - title: title, - content: attrs.awPopOver, - trigger: trigger, - html: true, - container: container, - template: template - }); - } - $(element).attr('tabindex', -1); - - $(element).one('click', showPopover); - - function bindPopoverDismiss() { - $('body').one('click.popover' + id_to_close, function(e) { - if ($(e.target).parents(id_to_close).length === 0) { - // case: you clicked to open the popover and then you - // clicked outside of it...hide it. - $(element).popover('hide'); - } else { - // case: you clicked to open the popover and then you - // clicked inside the popover - bindPopoverDismiss(); - } - }); - } - - $(element).on('shown.bs.popover', function() { - bindPopoverDismiss(); - $(document).on('keydown.popover', dismissOnEsc); - }); - - $(element).on('hidden.bs.popover', function() { - $(element).off('click', dismissPopover); - $(element).off('click', showPopover); - $('body').off('click.popover.' + id_to_close); - $(element).one('click', showPopover); - $(document).off('keydown.popover', dismissOnEsc); - }); - - function showPopover(e) { - e.stopPropagation(); - - var self = $(element); - - // remove tool-tip - try { - element.tooltip('hide'); - } catch (ex) { - // ignore - } - - // this is called on the help-link (over and over again) - $('.help-link, .help-link-white').each(function() { - if (self.attr('id') !== $(this).attr('id')) { - try { - // not sure what this does different than the method above - $(this).popover('hide'); - } catch (e) { - // ignore - } - } - }); - - $('.popover').each(function() { - // remove lingering popover
. Seems to be a bug in TB3 RC1 - $(this).remove(); - }); - $('.tooltip').each(function() { - // close any lingering tool tips - $(this).hide(); - }); - - // set id_to_close of the actual open element - id_to_close = "#" + $(element).attr('id') + "_popover_container"; - - // $(element).one('click', dismissPopover); - - $(element).popover('toggle'); - - $('.popover').each(function() { - $compile($(this))(scope); //make nested directives work! - }); - } - - function dismissPopover(e) { - e.stopPropagation(); - $(element).popover('hide'); - } - - function dismissOnEsc(e) { - if (e.keyCode === 27) { - $(element).popover('hide'); - $('.popover').each(function() { - // remove lingering popover
. Seems to be a bug in TB3 RC1 - // $(this).remove(); - }); - } - } - - }; -}]) - -// -// Enable jqueryui slider widget on a numeric input field -// -// -// -.directive('awSlider', [function() { - return { - require: 'ngModel', - link: function(scope, elm, attrs, ctrl) { - var name = elm.attr('name'); - $('#' + name + '-slider').slider({ - value: 0, - step: 1, - min: elm.attr('min'), - max: elm.attr('max'), - disabled: (elm.attr('readonly')) ? true : false, - slide: function(e, u) { - ctrl.$setViewValue(u.value); - ctrl.$setValidity('required', true); - ctrl.$setValidity('min', true); - ctrl.$setValidity('max', true); - ctrl.$dirty = true; - ctrl.$render(); - if (!scope.$$phase) { - scope.$digest(); - } - } - }); - - $('#' + name + '-number').change(function() { - $('#' + name + '-slider').slider('value', parseInt($(this).val(), 10)); - }); - - } - }; -}]) - -// -// Enable jqueryui spinner widget on a numeric input field -// -// -// -.directive('awSpinner', [function() { - return { - require: 'ngModel', - link: function(scope, elm, attrs, ctrl) { - var disabled, opts; - disabled = elm.attr('data-disabled'); - opts = { - value: 0, - step: 1, - min: elm.attr('min'), - max: elm.attr('max'), - numberFormat: "d", - disabled: (elm.attr('readonly')) ? true : false, - icons: { - down: "Form-numberInputButton fa fa-angle-down", - up: "Form-numberInputButton fa fa-angle-up" - }, - spin: function(e, u) { - if (e.originalEvent && e.originalEvent.type === 'mousewheel') { - e.preventDefault(); - } else { - ctrl.$setViewValue(u.value); - ctrl.$setValidity('required', true); - ctrl.$setValidity('min', true); - ctrl.$setValidity('max', true); - ctrl.$dirty = true; - ctrl.$render(); - if (scope.job_template_form) { - // need a way to find the parent form and mark it dirty - scope.job_template_form.$dirty = true; - } - if (!scope.$$phase) { - scope.$digest(); - } - } - } - }; - - // hack to get ngDisabled to work - if (attrs.ngDisabled) { - scope.$watch(attrs.ngDisabled, function(val) { - opts.disabled = (val === true) ? true : false; - $(elm).spinner(opts); - }); - } - - if (disabled) { - opts.disabled = true; - } - $(elm).spinner(opts); - $('.ui-icon').text(''); - $(".ui-icon").removeClass('ui-icon ui-icon-triangle-1-n ui-icon-triangle-1-s'); - $(elm).on("click", function() { - $(elm).select(); - }); - } - }; -}]) - -/* - * Make an element draggable. Used on inventory groups tree. - * - * awDraggable: boolean || {{ expression }} - * - */ -.directive('awDraggable', [function() { - return function(scope, element, attrs) { - - if (attrs.awDraggable === "true") { - var containment = attrs.containment; //provide dataContainment:"#id" - $(element).draggable({ - containment: containment, - scroll: true, - revert: "invalid", - helper: "clone", - start: function(e, ui) { - ui.helper.addClass('draggable-clone'); - }, - zIndex: 100, - cursorAt: { left: -1 } - }); - } - }; -}]) - -// Toggle switch inspired by http://www.bootply.com/92189 -.directive('awToggleButton', [function() { - return function(scope, element) { - $(element).click(function() { - var next, choice; - $(this).find('.btn').toggleClass('active'); - if ($(this).find('.btn-primary').size() > 0) { - $(this).find('.btn').toggleClass('btn-primary'); - } - if ($(this).find('.btn-danger').size() > 0) { - $(this).find('.btn').toggleClass('btn-danger'); - } - if ($(this).find('.btn-success').size() > 0) { - $(this).find('.btn').toggleClass('btn-success'); - } - if ($(this).find('.btn-info').size() > 0) { - $(this).find('.btn').toggleClass('btn-info'); - } - $(this).find('.btn').toggleClass('btn-default'); - - // Add data-after-toggle="functionName" to the btn-group, and we'll - // execute here. The newly active choice is passed as a parameter. - if ($(this).attr('data-after-toggle')) { - next = $(this).attr('data-after-toggle'); - choice = $(this).find('.active').text(); - setTimeout(function() { - scope.$apply(function() { - scope[next](choice); - }); - }); - } - - }); - }; -}]) - -// -// Support dropping files on an element. Used on credentials page for SSH/RSA private keys -// Inspired by https://developer.mozilla.org/en-US/docs/Using_files_from_web_applications -// -.directive('awDropFile', ['Alert', function(Alert) { - return { - require: 'ngModel', - link: function(scope, element, attrs, ctrl) { - $(element).on('dragenter', function(e) { - e.stopPropagation(); - e.preventDefault(); - }); - $(element).on('dragover', function(e) { - e.stopPropagation(); - e.preventDefault(); - }); - $(element).on('drop', function(e) { - var dt, files, reader; - e.stopPropagation(); - e.preventDefault(); - dt = e.originalEvent.dataTransfer; - files = dt.files; - reader = new FileReader(); - reader.onload = function() { - ctrl.$setViewValue(reader.result); - ctrl.$render(); - ctrl.$setValidity('required', true); - ctrl.$dirty = true; - if (!scope.$$phase) { - scope.$digest(); - } - }; - reader.onerror = function() { - Alert('Error', 'There was an error reading the selected file.'); - }; - if (files[0].size < 10000) { - reader.readAsText(files[0]); - } else { - Alert('Error', 'There was an error reading the selected file.'); - } - }); - } - }; -}]) - -.directive('awPasswordToggle', [function() { - return { - restrict: 'A', - link: function(scope, element) { - $(element).click(function() { - var buttonInnerHTML = $(element).html(); - if (buttonInnerHTML.indexOf("Show") > -1) { - $(element).html("Hide"); - $(element).closest('.input-group').find('input').first().attr("type", "text"); - } else { - $(element).html("Show"); - $(element).closest('.input-group').find('input').first().attr("type", "password"); - } - }); - } - }; -}]) - -.directive('awEnterKey', [function() { - return { - restrict: 'A', - link: function(scope, element, attrs) { - element.bind("keydown keypress", function(event) { - var keyCode = event.which || event.keyCode; - if (keyCode === 13) { - scope.$apply(function() { - scope.$eval(attrs.awEnterKey); - }); - event.preventDefault(); - } - }); - } - }; -}]) - -.directive('awTruncateBreadcrumb', ['BreadCrumbService', function(BreadCrumbService) { - return { - restrict: 'A', - scope: { - breadcrumbStep: '=' - }, - link: function(scope) { - scope.$watch('breadcrumbStep.ncyBreadcrumbLabel', function(){ - BreadCrumbService.truncateCrumbs(); - }); - } - }; -}]) - -.directive('awRequireMultiple', ['Empty', function(Empty) { - return { - require: 'ngModel', - link: function postLink(scope, element, attrs, ngModel) { - // Watch for changes to the required attribute - attrs.$observe('required', function() { - ngModel.$validate(); - }); - - ngModel.$validators.multipleSelect = function (modelValue) { - if(attrs.required) { - if(angular.isArray(modelValue)) { - // Checks to make sure at least one value in the array - return _.some(modelValue, function(arrayVal) { - return !Empty(arrayVal); - }); - } else { - return !Empty(modelValue); - } - } else { - return true; - } - }; - } - }; -}]); diff --git a/awx/ui/client/src/shared/download-standard-out.block.less b/awx/ui/client/src/shared/download-standard-out.block.less deleted file mode 100644 index 0efdb3001af3..000000000000 --- a/awx/ui/client/src/shared/download-standard-out.block.less +++ /dev/null @@ -1,28 +0,0 @@ -/** @define DownloadStandardOut */ - -.DownloadStandardOut { - color: @default-bg !important; -} - -.DownloadStandardOut--onStandardOutPage { - margin-top: -3px; - margin-right: -9px; - float: right; -} - -.DownloadStandardOut--onModal { - margin-bottom: 10px; - float: right; -} - -.DownloadStandardOut-icon { - color: @default-bg; -} - -.DownloadStandardOut-icon--withText { - margin-right: 5px; -} - -.DownloadStandardOut-pre { - width: 100%; -} diff --git a/awx/ui/client/src/shared/filters/append.filter.js b/awx/ui/client/src/shared/filters/append.filter.js deleted file mode 100644 index 1493cf59972e..000000000000 --- a/awx/ui/client/src/shared/filters/append.filter.js +++ /dev/null @@ -1,15 +0,0 @@ -export default function() { - return function(string, append) { - if (string) { - if (append) { - return string + append; - } - else { - return string; - } - } - else { - return ""; - } - }; -} diff --git a/awx/ui/client/src/shared/filters/capitalize.filter.js b/awx/ui/client/src/shared/filters/capitalize.filter.js deleted file mode 100644 index 859dd367e16a..000000000000 --- a/awx/ui/client/src/shared/filters/capitalize.filter.js +++ /dev/null @@ -1,12 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - export default [function() { - return function(input) { - input = input.charAt(0).toUpperCase() + input.substr(1).toLowerCase(); - return input; - }; - }]; diff --git a/awx/ui/client/src/shared/filters/format-epoch.filter.js b/awx/ui/client/src/shared/filters/format-epoch.filter.js deleted file mode 100644 index 9d8fee351f53..000000000000 --- a/awx/ui/client/src/shared/filters/format-epoch.filter.js +++ /dev/null @@ -1,14 +0,0 @@ -export default -[ 'moment', - function(moment) { - return function(seconds, formatStr) { - if (!formatStr) { - formatStr = 'll LT'; - } - - var millis = seconds * 1000; - - return moment(millis).format(formatStr); - }; - } -]; diff --git a/awx/ui/client/src/shared/filters/is-empty.filter.js b/awx/ui/client/src/shared/filters/is-empty.filter.js deleted file mode 100644 index a0ece767f1b6..000000000000 --- a/awx/ui/client/src/shared/filters/is-empty.filter.js +++ /dev/null @@ -1,17 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - export default function() { - return function (obj) { - var key; - for (key in obj) { - if (obj.hasOwnProperty(key)) { - return false; - } - } - return true; - }; - } diff --git a/awx/ui/client/src/shared/filters/long-date.filter.js b/awx/ui/client/src/shared/filters/long-date.filter.js deleted file mode 100644 index f2d058e3b24d..000000000000 --- a/awx/ui/client/src/shared/filters/long-date.filter.js +++ /dev/null @@ -1,17 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - export default ['moment', function(moment) { - return function(input) { - var date; - if(input === null || input === undefined){ - return ""; - }else { - date = moment(input); - return date.format('l LTS'); - } - }; - }]; diff --git a/awx/ui/client/src/shared/filters/main.js b/awx/ui/client/src/shared/filters/main.js deleted file mode 100644 index 2298bb0723b4..000000000000 --- a/awx/ui/client/src/shared/filters/main.js +++ /dev/null @@ -1,17 +0,0 @@ -import prepend from './prepend.filter'; -import append from './append.filter'; -import isEmpty from './is-empty.filter'; -import capitalize from './capitalize.filter'; -import longDate from './long-date.filter'; -import sanitize from './xss-sanitizer.filter'; -import formatEpoch from './format-epoch.filter'; - -export default - angular.module('stringFilters', []) - .filter('prepend', prepend) - .filter('append', append) - .filter('isEmpty', isEmpty) - .filter('capitalize', capitalize) - .filter('longDate', longDate) - .filter('sanitize', sanitize) - .filter('formatEpoch', formatEpoch); diff --git a/awx/ui/client/src/shared/filters/prepend.filter.js b/awx/ui/client/src/shared/filters/prepend.filter.js deleted file mode 100644 index 93bad2889d1a..000000000000 --- a/awx/ui/client/src/shared/filters/prepend.filter.js +++ /dev/null @@ -1,15 +0,0 @@ -export default function() { - return function(string, prepend) { - if (string) { - if(prepend) { - return prepend + string; - } - else { - return string; - } - } - else { - return ""; - } - }; -} diff --git a/awx/ui/client/src/shared/filters/xss-sanitizer.filter.js b/awx/ui/client/src/shared/filters/xss-sanitizer.filter.js deleted file mode 100644 index ef1c16d32d03..000000000000 --- a/awx/ui/client/src/shared/filters/xss-sanitizer.filter.js +++ /dev/null @@ -1,12 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - export default [function() { - return function(input) { - input = $("").text(input)[0].innerHTML; - return input; - }; - }]; diff --git a/awx/ui/client/src/shared/form-generator.js b/awx/ui/client/src/shared/form-generator.js deleted file mode 100644 index 18c5bf2eee26..000000000000 --- a/awx/ui/client/src/shared/form-generator.js +++ /dev/null @@ -1,2010 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - - /** - * @ngdoc function - * @name shared.function:form-generator - * @description - * - * Generate form HTML from a form object. Form objects are found in /forms. - * - * #Generate and Inject Form - * - * To generate a form and inject it into the DOM the default method call is: - * - * ``` - * GenerateForm.inject(form, { mode: 'edit', related: true, scope: $scope}); - * ``` - * Expects 2 parameters where the first is a reference to a form object, and the second is an object of key/value parameter pairs. Returns the $scope object associated with the generated HTML. - * - * Parameters that can be passed: - * - * | Parameter | Required | Description | - * | --------- | -------- | ----------- | - * | html | | String of HTML to be injected. Overrides HTML that would otherwise be generated using the form object. (Not sure if this is actually used anywhere.) | - * | id | | The ID attribute value of the DOM elment that will receive the generated HTML. If provided, form generator will inject the HTML it genertates into the DOM element identified by the string value provided. Do not preceed the value with '#' | - * | mode | Y | 'add', 'edit' or 'modal'. Use add when creating new data - creating a new orgranization, for example. Use edit when modifying existing data. Modal is deprecated. Use the 'id' option to inject a form into a modal dialog. | - * | scope | | Reference to $scope object. Will be passed to $compile and associated with any angular directives contained within the generated HTML. | - * | showButtons | | true or false. If false, buttons defined in the buttons object will not be included in the generated HTML. | - * - * #Generate HTML Only - * - * To generate the HTML only and not inject it into the DOM use the buildHTML() method: - * - * ``` - * GenerateForm.buildHTML(JobVarsPromptForm, { mode: 'edit', modal: true, scope: scope }); - * ``` - * - * Pass the same parameters as above. Returns a string containing the generated HTML. - * - * #Reset Form - * - * Call GenerateFrom.reset() to clear user input, remove error messages and return the angular form object back to a pristine state. This is should be called when the user clicks the Reset button. - * - * #Form definitions - * - * See forms/*.js for examples. - * - * The form object can have the following attributes: - * - * | Attribute | Description | - * | --------- | ----------- | - * | collapse | true or false. If true, places the form inside a jQueryUI accordion | - * | collapseMode | 'add' or 'edit'. If the value of the mode parameter passed into .inject() or .buildHTML() matches collapseMode, the <form> will be placed in an accordion. | - * | collapseOpen | true or false. If true, the accordion will be open the first time the user views the form, or if no state information is found in local storage for the accordion. Subsequent views will depend on accordion state found in local storage. Each time user opens/closes an accordion the state is saved in local storage. | - * | collapseOpenFirst | true or false. If true, the collapse will be open everytime the accordion is viewed, regardless of state data found in local storage. | - * | collapseTitle | Text to use in the <h3> element of the accordion. Typically this will be 'Properties' | - * | name | Name to give the form object. Used to create the id and name attribute values in the
element. | - * | showActions | true or false. By default actions found in the actions object will be displayed at the top of the page. If set to false, actions will not be displayed. | - * | twoColumns | true or false. By default fields are placed in a single vertical column following the Basic Example in the [Bootstrap form documentation](http://getbootstrap.com/css/#forms). Set to true for a 2 column layout as seen on the Job Templates detail page.| - * | well | true or false. If true, wraps the with <div class="aw-form-well"></div> | - * - * The form object will contain a fields object to hold the definiation of each field in the form. Attributes on a field object determine the HTML generated for the actual <input> or <textarea> element. Fields can have the following attributes: - * - * | Attribute | Description | - * | --------- | ----------- | - * | awPopOver | Adds aw-pop-over directive. Set to a string containing the text or html to be evaluated by the directive. | - * | awPopOverWatch | Causes the awPopOver directive to add a $scope.$watch on the specified scop variable. When the value of the variable changes the popover text will be updated with the change. | - * | awRequiredWhen | Adds aw-required-when directive. Set to an object to be evaluated by the directive. | - * | capitalize | true or false. If true, apply the 'capitalize' filter to the field. | - * | class | String cotaining one or more CSS class values. | - * | column | If the twoColumn option is being used, supply an integer value of 1 or 2 representing the column in which to place the field. 1 places the field in the left column, and 2 places it on the right. | - * | dataContainer | Used with awPopOver. String providing the containment parameter. | - * | dataPlacement | Used with awPopOver and awToolTip. String providing the placement parameter (i.e. left, right, top, bottom, etc.). | - * | dataTitle | Used with awPopOver. String value for the title of the popover. | - * | default | Default value to place in the field when the form is in 'add' mode. | - * | defaultText | Default value to put into a select input. | - * | falseValue | For radio buttons and checkboxes. Value to set the model to when the checkbox or radio button is not selected. | - * | genHash | true or false. If true, places the field in an input group with a button that when clicked replaces the field contents with a hash as key. Used with host_config_key on the job templates detail page. | - * | integer | Adds the integer directive to validate that the value entered is of type integer. Add min and max to supply lower and upper range bounds to the entered value. | - * | label | Text to use as <label> element for the field | - * | ngChange | Adds ng-change directive. Set to the JS expression to be evaluated by ng-change. | - * | ngClick | Adds ng-click directive. Set to the JS expression to be evaluated by ng-click. | - * | ngHide | Adds ng-hide directive. Set to the JS expression to be evaluated by ng-hide. | - * | ngShow | Adds ng-show directive. Set to the JS expression to be evaluated by ng-show. | - - * | readonly | Defaults to false. When true the readonly attribute is set, disallowing changes to field content. | - * | required | boolean. Adds required flag to form field | - * | rows | Integer value used to set the row attribute for a textarea. | - * | sourceModel | Used in conjunction with sourceField when the data for the field is part of the summary_fields object returned by the API. Set to the name of the summary_fields object that contains the field. For example, the job_templates object returned by the API contains summary_fields.inventory. | - * | sourceField | String containing the summary_field.object.field name from the API summary_field object. For example, if a fields should be associated to the summary_fields.inventory.name, set the sourceModel to 'inventory' and the sourceField to 'name'. | - * | spinner | true or false. If true, adds aw-spinner directive. Optionally add min and max attributes to control the range of allowed values. | - * | type | String containing one of the following types defined in buildField: alertblock, hidden, text, password, email, textarea, select, number, checkbox, checkbox_group, radio, radio_group, lookup, custom. | - * | trueValue | For radio buttons and checkboxes. Value to set the model to when the checkbox or radio button is selected. | - * | hasShowInputButton (sensitive type only) | This creates a button next to the input that toggles the input as text and password types. | - * The form object contains a buttons object for defining any buttons to be included in the generated HTML. Generally all forms will have a Reset and a Submit button. If no buttons should be generated define buttons as an empty object, or set the showButtons option to false. - * - * The icon used for the button is determined by SelectIcon() found in js/shared/generator-helpers.js. - * - * | Attribute | Description | - * | --------- | ----------- | - * | class | If the name of the button is reset or save, the class is automatically set to the correct bootstrap btn class for the color. Otherwise, provide a string with any classes to be added to the <button> element. | - * | label | For reset and save buttons the label is automatically set. For other types of buttons set label to the text string that should appear in the button. | - * | ngClick | Adds the ng-click directive to the button. Set to the JS expression for the ng-click directive to evaluate. | - * | ngDisabled | Only partially implemented at this point. For buttons other than reset, the ng-disabled directive is always added. The button will be disabled when the form is in an invalid state. | - * - * The form object may contain an actions object. The action object can contain one or more button definitions for buttons to appear in the top-right corner of the form. This may include activity stream, refresh, properties, etc. Each button object defined in actions may have the following attributes: - * - * | Attribute | Description | - * | --------- | ----------- | - * | awToolTip | Text or html to display in the button tooltip. Adds the aw-tool-tip directive. | - * | class | Optional classes to add to the <button> element. | - * | dataPlacement | Set the placement attribute of the tooltip - left, right, top, bottom, etc. | - * | ngClick | Set to the JS expression to be evaluated by the ng-click directive. | - * | mode | Set to edit or add, depending on which mode the button | - * | - * - * The form object may contain a related object. The related object contains one or more list objects defining sublists to display in accordions. For example, the Organization form contains a related users list and admins list. - * - * As originally conceived sublists were stored inside the form definition without regard to any list definitions found in the lists folder. In other words, lists/Users.js is completely different from the related.users object found in forms/Organizations.js. In reality they - * are very similar and lists/Users.js should be used to generate the users sublist on the organizations detail page. - * - * One approach to making this work and using list definintion inside a from was implemented in forms/JobTemplates.js. In controllers/JobTemplates.js within JobTemplatesEdit() the form object is created by calling the JobTemplateForm() method found in forms/JobTemplates.js. The - * method injects the SchedulesList and CompletedJobsList into the form object as related sets. Going forward this approach or similar should be used whenever a sublist needs to be added to a form. - * - * #Variable editing - * - * If the field type is textarea and the name is one of variables, extra_vars, inventory_variables or source_vars, then the parse type radio button group is added. This is the radio button group allowing the user to switch between JSON and YAML. - * - * Applying CodeMirror to the text area is handled by ParseTypeChange() found in helpers/Parse.js. Within the controller will be a call to ParseTypeChange that creates the CodeMirror object and sets up the required $scope methods for handles getting, setting and type conversion. - */ - -import GeneratorHelpers from './generator-helpers'; -import listGenerator from './list-generator/main'; - -export default -angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerator.name]) - -.factory('GenerateForm', ['$rootScope', '$compile', 'generateList', - 'Attr', 'Icon', 'Column', - 'NavigationLink', 'Empty', 'SelectIcon', - 'ActionButton', 'MessageBar', '$log', 'i18n', - function ($rootScope, $compile, GenerateList, - Attr, Icon, Column, NavigationLink, - Empty, SelectIcon, ActionButton, MessageBar, $log, i18n) { - return { - - setForm: function (form) { this.form = form; }, - - attr: Attr, - - icon: Icon, - - accordion_count: 0, - - scope: null, - - has: function (key) { - return (this.form[key] && this.form[key] !== null && this.form[key] !== undefined) ? true : false; - }, - // Not a very good way to do this - // Form sub-states expect to target ui-views related@stateName & modal@stateName - // Also wraps mess of generated HTML in a .card and at-Panel - wrapPanel(html, ignorePanel){ - if(ignorePanel) { - return ` -
- ${html} -
-
-
`; - } - else { - return ` - ${MessageBar(this.form)} -
- ${html} -
-
-
`; - } - }, - - inject: function (form, options) { - // - // Use to inject the form as html into the view. View MUST have an ng-bind for 'htmlTemplate'. - // Returns scope of form. - // - - var element, fld, set, show, self = this; - - if (options.modal) { - if (options.modal_body_id) { - element = angular.element(document.getElementById(options.modal_body_id)); - } else { - // use default dialog - element = angular.element(document.getElementById('form-modal-body')); - } - } else { - if (options.id) { - element = angular.element(document.getElementById(options.id)); - } else { - element = angular.element(document.getElementById('htmlTemplate')); - } - } - - this.mode = options.mode; - this.modal = (options.modal) ? true : false; - this.setForm(form); - - if (options.html) { - element.html(options.html); - } else { - element.html(this.build(options)); - } - - if (options.scope) { - this.scope = options.scope; - } else { - this.scope = element.scope(); - } - - if (options.mode) { - this.scope.mode = options.mode; - } - - if(options.mode === 'edit' && this.form.related && - !_.isEmpty(this.form.related)){ - var tabs = [this.form.name], that = this; - tabs.push(Object.keys(this.form.related)); - tabs = _.flatten(tabs); - _.map(tabs, function(itm){ - that.scope.$parent[itm+"Selected"] = false; - }); - this.scope.$parent[this.form.name+"Selected"] = true; - - - this.scope.$parent.toggleFormTabs = function($event){ - _.map(tabs, function(itm){ - that.scope.$parent[itm+"Selected"] = false; - }); - that.scope.$parent[$event.target.id.split('_tab')[0]+"Selected"] = true; - }; - - } - - for (fld in form.fields) { - this.scope[fld + '_field'] = form.fields[fld]; - this.scope[fld + '_field'].name = fld; - } - - for (fld in form.headerFields){ - this.scope[fld + '_field'] = form.headerFields[fld]; - this.scope[fld + '_field'].name = fld; - } - - $compile(element)(this.scope); - - if (!options.html) { - // Reset the scope to prevent displaying old data from our last visit to this form - for (fld in form.fields) { - this.scope[fld] = null; - } - for (set in form.related) { - this.scope[set] = null; - } - // if (((!options.modal) && options.related) || this.form.forceListeners) { - // this.addListeners(); - // } - if (options.mode === 'add') { - this.applyDefaults(form, this.scope); - } - } - - // Remove any lingering tooltip and popover
elements - $('.tooltip').each(function () { - $(this).remove(); - }); - - $('.popover').each(function () { - // remove lingering popover
. Seems to be a bug in TB3 RC1 - $(this).remove(); - }); - - try { - $('#help-modal').empty().dialog('destroy'); - } catch (e) { - //ignore any errors should the dialog not be initialized - } - - if (options.modal) { - $rootScope.flashMessage = null; - this.scope.formModalActionDisabled = false; - this.scope.formModalInfo = false; //Disable info button for default modal - if (form) { - if (options.modal_title_id) { - this.scope[options.modal_title_id] = (options.mode === 'add') ? form.addTitle : form.editTitle; - } else { - this.scope.formModalHeader = (options.mode === 'add') ? form.addTitle : form.editTitle; //Default title for default modal - } - } - if (options.modal_selector) { - $(options.modal_selector).modal({ - show: true, - backdrop: 'static', - keyboard: true - }); - $(options.modal_selector).on('shown.bs.modal', function () { - $(options.modal_select + ' input:first').focus(); - }); - $(options.modal_selector).on('hidden.bs.modal', function () { - $('.tooltip').each(function () { - // Remove any lingering tooltip and popover
elements - $(this).remove(); - }); - - $('.popover').each(function () { - // remove lingering popover
. Seems to be a bug in TB3 RC1 - $(this).remove(); - }); - }); - } else { - show = (options.show_modal === false) ? false : true; - $('#form-modal').modal({ - show: show, - backdrop: 'static', - keyboard: true - }); - $('#form-modal').on('shown.bs.modal', function () { - $('#form-modal input:first').focus(); - }); - $('#form-modal').on('hidden.bs.modal', function () { - $('.tooltip').each(function () { - // Remove any lingering tooltip and popover
elements - $(this).remove(); - }); - - $('.popover').each(function () { - // remove lingering popover
. Seems to be a bug in TB3 RC1 - $(this).remove(); - }); - }); - } - $(document).bind('keydown', function (e) { - if (e.keyCode === 27) { - if (options.modal_selector) { - $(options.modal_selector).modal('hide'); - } - $('#prompt-modal').modal('hide'); - $('#form-modal').modal('hide'); - } - }); - } - - if (self.scope && !self.scope.$$phase) { - setTimeout(function() { - if (self.scope) { - self.scope.$digest(); - } - }, 100); - } - - return self.scope; - - }, - - buildHTML: function(form, options) { - // Get HTML without actually injecting into DOM. Caller is responsible for any injection. - // Example: - // html = GenerateForm.buildHTML(JobVarsPromptForm, { mode: 'edit', modal: true, scope: scope }); - - this.mode = options.mode; - //this.modal = (options.modal) ? true : false; - this.setForm(form); - return this.build(options); - }, - - applyDefaults: function (form, scope, ignoreMode) { - if(!ignoreMode) { - // Note: This is a hack. Ideally, mode should be set in each -.controller.js - // The mode is needed by the awlookup directive to auto-populate form fields when there is a - // single related resource. - scope.mode = this.mode; - } - - for (var fld in form.fields) { - if (form.fields[fld]['default'] || form.fields[fld]['default'] === 0) { - if (form.fields[fld].type === 'select' && scope[fld + '_options']) { - scope[fld] = scope[fld + '_options'][form.fields[fld]['default']]; - } else { - scope[fld] = form.fields[fld]['default']; - } - } - } - }, - - reset: function () { - // The form field values cannot be reset with jQuery. Each field is tied to a model, so to clear the field - // value, you have to clear the model. - - var fld, scope = this.scope, - form = this.form; - - if (scope[form.name + '_form']) { - scope[form.name + '_form'].$setPristine(); - } - - function resetField(f, fld) { - // f is the field object, fld is the key - - if (f.type === 'checkbox_group') { - for (var i = 0; i < f.fields.length; i++) { - scope[f.fields[i].name] = ''; - scope[f.fields[i].name + '_api_error'] = ''; - scope[form.name + '_form'][f.fields[i].name].$setValidity('apiError', true); - } - } else { - scope[fld] = ''; - scope[fld + '_api_error'] = ''; - } - if (f.sourceModel) { - scope[f.sourceModel + '_' + f.sourceField] = ''; - scope[f.sourceModel + '_' + f.sourceField + '_api_error'] = ''; - if (scope[form.name + '_form'][f.sourceModel + '_' + f.sourceField]) { - scope[form.name + '_form'][f.sourceModel + '_' + f.sourceField].$setValidity('apiError', true); - } - } - if (f.type === 'lookup' && scope[form.name + '_form'][f.sourceModel + '_' + f.sourceField]) { - scope[form.name + '_form'][f.sourceModel + '_' + f.sourceField].$setPristine(); - scope[form.name + '_form'][f.sourceModel + '_' + f.sourceField].$setValidity('apiError', true); - } - if (scope[form.name + '_form'][fld]) { - scope[form.name + '_form'][fld].$setPristine(); - scope[form.name + '_form'][fld].$setValidity('apiError', true); - } - if (f.awPassMatch && scope[form.name + '_form'][fld]) { - scope[form.name + '_form'][fld].$setValidity('awpassmatch', true); - } - if (f.subCheckbox) { - scope[f.subCheckbox.variable] = false; - } - } - - for (fld in form.fields) { - resetField(form.fields[fld], fld); - } - if (form.statusFields) { - for (fld in form.statusFields) { - resetField(form.statusFields[fld], fld); - } - } - if (this.mode === 'add') { - this.applyDefaults(form, scope); - } - }, - - checkAutoFill: function(params) { - var fld, model, newVal, type, - scope = (params && params.scope) ? params.scope : this.scope; - for (fld in this.form.fields) { - if (this.form.fields[fld].type === 'text' || this.form.fields[fld].type === 'textarea') { - type = (this.form.fields[fld].type === 'text') ? 'input' : 'textarea'; - model = scope[this.form.name + '_form'][fld]; - newVal = $(type + '[name="' + fld + '"]').val(); - if (newVal && model && model.$viewValue !== newVal) { - model.$setViewValue(newVal); - } - } - } - }, - - genID: function () { - var id = new Date(); - return id.getTime(); - }, - - headerField: function (fld, field) { - var html = ''; - if (field.label) { - html += "\n"; - } - html += " - -
- `; - } else if (field.type === 'html') { - html += field.html; - } - return html; - }, - - - buildField: function (fld, field, options, form) { - var i, fldWidth, offset, html = '', error_message, - horizontal = (this.form.horizontal) ? true : false; - - function getFieldWidth() { - var x; - if (form.formFieldSize) { - x = form.formFieldSize; - } else if (field.xtraWide) { - x = "col-lg-10"; - } else if (field.column) { - x = "col-lg-8"; - } else if (!form.formFieldSize && options.modal) { - x = "col-lg-10"; - } else { - x = "col-lg-6"; - } - return x; - } - - function getLabelWidth() { - var x; - if (form.formLabelSize) { - x = form.formLabelSize; - } else if (field.column) { - x = "col-lg-4"; - } else if (!form.formLabelSize && options.modal) { - x = "col-lg-2"; - } else { - x = "col-lg-2"; - } - return x; - } - - function buildId(field, fld, form) { - var html = ''; - if (field.id) { - html += Attr(field, 'id'); - } else { - html += "id=\"" + form.name + "_" + fld + "\" "; - } - return html; - } - - function buildCheckbox(form, field, fld, idx) { - var html = ''; - - html += "
"; - html += "${field.label}`; - html += (field.awPopOver) ? Attr(field, 'awPopOver', fld) : ""; - html += `
`; - - return html; - } - - function label(options) { - var html = ''; - if (field.label || field.labelBind) { - html += "\n"; - } - - return html; - } - - if (field.type === 'toggle'){ - html += ` - - - - `; - } - - if (field.type === 'alertblock') { - html += "
\n"; - html += "
\n"; - html += "
×\n" : ""; - html += field.alertTxt; - html += "
\n"; - html += "
\n"; - html += "
\n"; - } - - if ((!field.readonly) || (field.readonly && options.mode === 'edit')) { - - if((field.excludeMode === undefined || field.excludeMode !== options.mode) && field.type !== 'alertblock' && field.type !== 'workflow-chart') { - - html += `
${definedInFileMessage}` : ``; - - // toggle switches - if(field.type === 'toggleSwitch') { - let labelOptions = {}; - - if (field.subCheckbox) { - labelOptions.checkbox = { - id: `${this.form.name}_${fld}_ask_chbox`, - ngModel: field.subCheckbox.variable, - ngShow: field.subCheckbox.ngShow, - ngChange: field.subCheckbox.ngChange, - ngDisabled: field.subCheckbox.ngDisabled || field.ngDisabled, - text: field.subCheckbox.text || '' - }; - } - - html += label(labelOptions); - - html += ` -
- -
- `; - } - - //text fields - if (field.type === 'text' || field.type === 'password' || field.type === 'email') { - let labelOptions = {}; - - if (field.subCheckbox) { - labelOptions.checkbox = { - id: `${this.form.name}_${fld}_ask_chbox`, - ngShow: field.subCheckbox.ngShow, - ngChange: field.subCheckbox.ngChange, - ngModel: field.subCheckbox.variable, - ngDisabled: field.subCheckbox.ngDisabled || field.ngDisabled, - text: field.subCheckbox.text || '' - }; - } - - html += label(labelOptions); - - html += "
\n" : ""; - - if (field.control === null || field.control === undefined || field.control) { - html += "\n"; - html += "\n
\n"; - } - - if (field.genHash) { - const defaultGenHashButtonTemplate = ` - - - `; - const genHashButtonTemplate = _.get(field, 'genHashButtonTemplate', defaultGenHashButtonTemplate); - html += `${genHashButtonTemplate}\n
\n`; - } - - // Add error messages - if (field.required || field.awRequiredWhen) { - error_message = i18n._('Please enter a value.'); - html += "
" + (field.requiredErrorMsg ? field.requiredErrorMsg : error_message) + "
\n"; - } - if (field.type === "email") { - error_message = i18n._('Please enter a valid email address.'); - html += "
${error_message}
`; - } - if (field.awPassMatch) { - error_message = i18n._('This value does not match the password you entered previously. Please confirm that password.'); - html += "
${error_message}
`; - } - if (field.awValidUrl) { - error_message = i18n._("Please enter a URL that begins with ssh, http or https. The URL may not contain the '@' character."); - html += "
${error_message}
`; - } - - html += "
\n"; - - html += "
\n"; - } - - //fields with sensitive data that needs to be obfuscated from view - if (field.type === 'sensitive') { - field.showInputInnerHTML = i18n._("Show"); - field.inputType = "password"; - - html += "\t" + label(); - if (field.hasShowInputButton) { - var tooltip = i18n._("Toggle the display of plaintext."); - html += "\
\n"; - // TODO: make it so that the button won't show up if the mode is edit, hasShowInputButton !== true, and there are no contents in the field. - html += "\n"; - html += "
\n"; - } - if (field.type === "email") { - error_message = i18n._("Please enter a valid email address."); - html += "
${error_message}
`; - } - if (field.awPassMatch) { - error_message = i18n._("This value does not match the password you entered previously. Please confirm that password."); - html += "
${error_message}
`; - } - if (field.awValidUrl) { - error_message = i18n._("Please enter a URL that begins with ssh, http or https. The URL may not contain the '@' character."); - html += "
${error_message}
`; - } - - html += "
\n
\n"; - } - - //textarea fields - if (field.type === 'textarea') { - let labelOptions = {}; - - if (field.subCheckbox) { - labelOptions.checkbox = { - id: `${this.form.name}_${fld}_ask_chbox`, - ngModel: field.subCheckbox.variable, - ngShow: field.subCheckbox.ngShow, - ngChange: field.subCheckbox.ngChange, - ngDisabled: field.subCheckbox.ngDisabled || field.ngDisabled, - text: field.subCheckbox.text || '' - }; - } - - html += label(labelOptions); - - html += "
" + (field.requiredErrorMsg ? field.requiredErrorMsg : i18n._("Please enter a value.")) + "
\n"; - } - html += "
\n"; - html += "
\n"; - } - - //select field - if (field.type === 'select') { - let labelOptions = {}; - - if (field.subCheckbox) { - labelOptions.checkbox = { - id: `${this.form.name}_${fld}_ask_chbox`, - ngShow: field.subCheckbox.ngShow, - ngModel: field.subCheckbox.variable, - ngChange: field.subCheckbox.ngChange, - ngDisabled: field.subCheckbox.ngDisabled || field.ngDisabled, - text: field.subCheckbox.text - }; - } - - if (field.onError) { - labelOptions.onError = { - id: `${this.form.name}_${fld}_error_text`, - ngShow: field.onError.ngShow, - ngModel: field.onError.variable, - text: field.onError.text - }; - } - - html += label(labelOptions); - - html += "
\n"; - html += "\n"; - html += "
\n"; - - // Add error messages - if (field.required || field.awRequiredWhen) { - html += "
" + (field.requiredErrorMsg ? field.requiredErrorMsg : i18n._("Please select a value.")); - if (field.includePlaybookNotFoundError) { - html += " Playbook {{ job_template_obj.playbook }} not found for project.\n"; - } - html += "
\n"; - } - if (field.label === "Labels") { - html += `
${field.onError.text}
`; - } - html += "
\n"; - - - html += "
\n"; - } - - //number field - if (field.type === 'number') { - let labelOptions = {}; - - if (field.subCheckbox) { - labelOptions.checkbox = { - id: `${this.form.name}_${fld}_ask_chbox`, - ngShow: field.subCheckbox.ngShow, - ngChange: field.subCheckbox.ngChange, - ngModel: field.subCheckbox.variable, - ngDisabled: field.subCheckbox.ngDisabled || field.ngDisabled, - text: field.subCheckbox.text || '' - }; - } - - html += label(labelOptions); - - html += "
" + (field.requiredErrorMsg ? field.requiredErrorMsg : i18n._("Please select a value.")) + "
\n"; - } - if (field.integer) { - html += "
" + i18n._("Please enter a number.") + "
\n"; - } - if (field.min !== undefined || field.max !== undefined) { - html += "
"; - if (field.max !== undefined) { - html += i18n.sprintf(i18n._("Please enter a number greater than %d and less than %d."), field.min, field.max); - } else { - html += i18n.sprintf(i18n._("Please enter a number greater than %d."), field.min); - } - html += "
\n"; - } - html += "
\n"; - html += "
\n"; - } - - //checkbox group - if (field.type === 'checkbox_group') { - - html += label(); - - html += "
" + (field.requiredErrorMsg ? field.requiredErrorMsg : i18n._("Please select at least one value.")) + "
\n"; - } - if (field.integer) { - html += "
" + i18n._("Please select a number.") + "
\n"; - } - if (field.min || field.max) { - html += "
" + i18n._("Please select a number between ") + field.min + i18n._(" and ") + - field.max + "
\n"; - } - html += "
\n"; - for (i = 0; i < field.fields.length; i++) { - html += "
\n"; - } - html += "
\n"; - html += "
\n"; - } - - //checkbox - if (field.type === 'checkbox') { - - if (horizontal) { - fldWidth = getFieldWidth(); - offset = 12 - parseInt(fldWidth.replace(/[A-Z,a-z,-]/g, ''),10); - html += "
\n"; - } - - html += "
\n"; - html += buildCheckbox(this.form, field, fld, undefined, false); - html += (field.icon) ? Icon(field.icon) : ""; - html += "
\n"; - html += "
\n"; - - if (horizontal) { - html += "
\n"; - } - } - - //radio group - if (field.type === 'radio_group') { - - html += label(); - - html += "
" + i18n._("Please select a value.") + "
\n"; - } - html += "
\n"; - - html += "
\n"; - } - - // radio button - if (field.type === 'radio') { - - if (horizontal) { - fldWidth = getFieldWidth(); - offset = 12 - parseInt(fldWidth.replace(/[A-Z,a-z,-]/g, ''),10); - html += "
\n"; - } - - html += "
\n"; - html += "
\n"; - html += "
\n"; - - if (horizontal) { - html += "
\n"; - } - } - - //lookup type fields - if (field.type === 'lookup') { - let defaultLookupNgClick = `$state.go($state.current.name + '.${field.sourceModel}', {selected: ${field.sourceModel}})`; - let labelOptions = {}; - - if (field.subCheckbox) { - labelOptions.checkbox = { - id: `${this.form.name}_${fld}_ask_chbox`, - ngShow: field.subCheckbox.ngShow, - ngChange: field.subCheckbox.ngChange, - ngModel: field.subCheckbox.variable, - ngDisabled: field.subCheckbox.ngDisabled || field.ngDisabled, - text: field.subCheckbox.text || '' - }; - } - - html += label(labelOptions); - - html += "
`; - html += "\n"; - html += ``; - html += "\n"; - html += "" + (field.requiredErrorMsg ? field.requiredErrorMsg : i18n._("Please select a value.")) + "
\n"; - html += "
" + i18n._("That value was not found. Please enter or select a valid value.") + "
\n"; - } else { - html += "
" + i18n._("That value was not found. Please enter or select a valid value.") + "
\n"; - } - html += "
\n"; - html += "
\n"; - } - - if (field.type === 'code_mirror') { - html += '"; - html += "
"; - html += (options.mode === 'edit') ? this.form.editTitle : this.form.addTitle; - if(this.form.name === "user"){ - html+= "" + i18n._("Admin") + ""; - html+= "" + i18n._("Auditor") + ""; - html+= "LDAP"; - html+= "{{external_account}}"; - html+= "" + i18n._("Last logged in: ") + "{{ last_login | longDate }}"; - } - if(this.form.name === "smartinventory"){ - html+= "" + i18n._("Smart Inventory") + ""; - } - html += "
\n"; - html += "
"; - if(this.form.headerFields){ - var that = this; - _.forEach(this.form.headerFields, function(value, key){ - html += that.buildHeaderField(key, value, options, that.form); - }); - html += "
\n"; - } - else{ html += "
\n"; } - if(this.form.cancelButton !== undefined && this.form.cancelButton === false) { - html += "
"; - html += "
"; - } else { - html += "
"; - html += "
\n"; - } - html += "
"; //end of Form-header - } - - if (!_.isEmpty(this.form.related) || !_.isEmpty(this.form.relatedButtons)) { - var collection, details = i18n._('Details'); - html += "
"; - - if(this.mode === "edit"){ - html += `
` + - `${details}
`; - - for (itm in this.form.related) { - collection = this.form.related[itm]; - html += `
${(collection.title || collection.editTitle)}
`; - } - - for (itm in this.form.relatedButtons) { - button = this.form.relatedButtons[itm]; - - // Build button HTML - html += "
";//tabHolder - } - - if(!_.isEmpty(this.form.related) && this.mode === "edit"){ - html += `
`; - } - - html += "\n"; - html += "
{{ flashMessage }}
\n"; - - var currentSubForm; - var hasSubFormField; - // original, single-column form - section = ''; - group = ''; - for (fld in this.form.fields) { - field = this.form.fields[fld]; - if (!(options.modal && field.excludeModal)) { - if (field.group && field.group !== group) { - if (group !== '') { - html += "
\n"; - } - html += "
\n"; - html += "
" + field.group + "
\n"; - group = field.group; - } - if (field.section && field.section !== section) { - if (section !== '') { - html += "
\n"; - } else { - html += "\n"; - html += "
\n"; - } - sectionShow = (this.form[field.section + 'Show']) ? " ng-show=\"" + this.form[field.section + 'Show'] + "\"" : ""; - html += "" + field.section + "\n"; - html += "\n"; - section = field.section; - } - - // To hide/show the subform when the value changes on parent - if (field.hasSubForm === true) { - hasSubFormField = fld; - } - - // Add a subform container - if(field.subForm && currentSubForm === undefined) { - currentSubForm = field.subForm; - var subFormTitle = this.form.subFormTitles[field.subForm]; - - html += '
'; - html += ''+ subFormTitle +''; - } - else if (!field.subForm && currentSubForm !== undefined) { - currentSubForm = undefined; - html += '
'; - } - - html += this.buildField(fld, field, options, this.form); - // console.log('*********') - // console.log(html) - - } - } - if (currentSubForm) { - currentSubForm = undefined; - html += '
'; - } - if (section !== '') { - html += "\n\n"; - } - if (group !== '') { - html += "\n"; - } - - html += "\n"; - - //buttons - if ((options.showButtons === undefined || options.showButtons === true) && !this.modal) { - if (this.has('buttons')) { - - html += "
\n"; - } - - for (btn in this.form.buttons) { - if (typeof this.form.buttons[btn] === 'object') { - button = this.form.buttons[btn]; - - if (button.component === 'at-launch-template') { - html += ``; - } else { - // Set default color and label for Save and Reset - if (btn === 'save') { - button.label = i18n._('Save'); - button['class'] = 'Form-saveButton'; - } - if (btn === 'select') { - button.label = i18n._('Select'); - button['class'] = 'Form-saveButton'; - } - if (btn === 'cancel') { - button.label = i18n._('Cancel'); - button['class'] = 'Form-cancelButton'; - } - if (btn === 'close') { - button.label = i18n._('Close'); - button['class'] = 'Form-cancelButton'; - } - if (btn === 'launch') { - button.label = i18n._('Launch'); - button['class'] = 'Form-launchButton'; - } - if (btn === 'add_survey') { - button.label = i18n._('Add Survey'); - button['class'] = 'Form-surveyButton'; - } - if (btn === 'edit_survey') { - button.label = i18n._('Edit Survey'); - button['class'] = 'Form-surveyButton'; - } - if (btn === 'view_survey') { - button.label = i18n._('View Survey'); - button['class'] = 'Form-surveyButton'; - } - if (btn === 'workflow_visualizer') { - button.label = i18n._('Workflow Visualizer'); - button['class'] = 'Form-primaryButton'; - } - - // Build button HTML - html += "
\n"; - - if (this.form.horizontal) { - html += "\n"; - } - } - } - - if(!_.isEmpty(this.form.related) && this.mode === "edit"){ - html += ``; - } - - if (this.form.include){ - _.forEach(this.form.include, (template) =>{ - html += `
`; - }); - } - // console.log(html); - return this.wrapPanel(html, options.noPanel); - }, - - buildCollection: function (params) { - // Currently, there are two ways we reference a list definition in a form - // Permissions lists are defined with boilerplate JSON in model.related - // this.GenerateCollection() is shaped around supporting this definition - // Notifications lists contain a reference to the NotificationList object, which contains the list's JSON definition - // However, Notification Lists contain fields that are only rendered by with generateList.build's chain - // @extendme rip out remaining HTML-concat silliness and directivize ¯\_(ツ)_/¯ - this.form = params.form; - var html = '', - collection = this.form.related[params.related]; - - if (collection.generateList) { - html += GenerateList.build({ mode: params.mode, list: collection}); - } - else { - html += this.GenerateCollection({ form: this.form, related: params.related }, {mode: params.mode}); - } - - return html; - }, - - GenerateCollection: function(params, options) { - var html = '', - form = params.form, - itm = params.related, - collection = form.related[itm], - act, fld, cnt, base, fAction, width; - - if (collection.instructions) { - html += "
\n"; - html += "\n"; - html += "Hint: " + collection.instructions + "\n"; - html += "
\n"; - } - - var actionButtons = ""; - Object.keys(collection.actions || {}) - .forEach(act => { - actionButtons += ActionButton(collection - .actions[act]); - }); - var hideOnSuperuser = (hideOnSuperuser === true) ? true : false; - if(actionButtons.length === 0 ){ - // The search bar should be full width if there are no - // action buttons - width = "col-lg-12 col-md-12 col-sm-12 col-xs-12"; - } - else { - width = "col-lg-8 col-md-8 col-sm-8 col-xs-12"; - } - - if(actionButtons.length>0){ - html += `
- ${actionButtons} -
`; - } - - // smart-search directive - html += ` -
- - -
- `; - - - //html += ""; - - // Message for when a search returns no results. This should only get shown after a search is executed with no results. - html += ` -
-
`; - html += i18n._('No records matched your search.'); - html += `
-
- `; - - // Show the "no items" box when loading is done and the user isn't actively searching and there are no results - var emptyListText = (collection.emptyListText) ? collection.emptyListText : i18n._("PLEASE ADD ITEMS TO THIS LIST"); - html += `
`; - html += `
${emptyListText}
`; - html += '
'; - - html += ` -
- ${i18n._('System Administrators have access to all ' + collection.iterator + 's')} -
- `; - - // Start the list - html += ` -
-
-
- `; - for (fld in collection.fields) { - html += `
- ${collection.fields[fld].label} -
`; - } - if (collection.fieldActions) { - html += `
${i18n._('Actions')}
`; - } - html += "
"; - - html += "
\n"; - if (collection.index === undefined || collection.index !== false) { - html += "
{{ $index + ((" + collection.iterator + "_page - 1) * " + - collection.iterator + "_page_size) + 1 }}.
\n"; - } - cnt = 1; - base = (collection.base) ? collection.base : itm; - base = base.replace(/^\//, ''); - for (fld in collection.fields) { - if (!collection.fields[fld].searchOnly) { - cnt++; - html += Column({ - list: collection, - fld: fld, - options: options, - base: base - }); - } - } - - // Row level actions - if (collection.fieldActions) { - html += `
`; - for (act in collection.fieldActions) { - if (act !== 'columnClass') { - fAction = collection.fieldActions[act]; - html += ""; - } - } - html += "
"; - html += "
\n"; - } - - // Message for loading - html += "
\n"; - html += "
" + i18n._("Loading...") + "
\n"; - html += "
\n"; - - // End List - html += "
\n"; - //html += "
\n"; // close well - html += "\n"; // close list-wrapper div - - html += ``; - return html; - }, - }; - - function createCheckbox (options) { - let ngChange = options.ngChange ? `ng-change="${options.ngChange}"` : ''; - let ngDisabled = options.ngDisabled ? `ng-disabled="${options.ngDisabled}"` : ''; - let ngModel = options.ngModel ? `ng-model="${options.ngModel}"` : ''; - let ngShow = options.ngShow ? `ng-show="${options.ngShow}"` : ''; - - return ` -
- -
`; - } - - } -]); diff --git a/awx/ui/client/src/shared/generator-helpers.js b/awx/ui/client/src/shared/generator-helpers.js deleted file mode 100644 index c0489bd9418c..000000000000 --- a/awx/ui/client/src/shared/generator-helpers.js +++ /dev/null @@ -1,735 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name shared.function:generator-helpers - * @description - * GeneratorHelpers - * - * Functions shared between FormGenerator and ListGenerator - * - */ - import systemStatus from '../smart-status/main'; - - -export default -angular.module('GeneratorHelpers', [systemStatus.name]) - -.factory('Attr', function () { - return function (obj, key, fld) { - var i, s, result, - value = (typeof obj[key] === "string") ? obj[key].replace(/[\"]/g, '"').replace(/[\']/g, ''') : obj[key]; - - if (/^ng/.test(key)) { - result = 'ng-' + key.replace(/^ng/, '').toLowerCase() + "=\"" + value + "\" "; - } else if (/^data|^aw/.test(key) && key !== 'awPopOver') { - s = ''; - for (i = 0; i < key.length; i++) { - if (/[A-Z]/.test(key.charAt(i))) { - s += '-' + key.charAt(i).toLowerCase(); - } else { - s += key.charAt(i); - } - } - result = s + "=\"" + value + "\" "; - } else { - switch (key) { - // In the cases where we specify trueValue and falseValue, - // the boolean value from the API is getting converted to - // a string. After upgrading to angular 1.4, we need to quote - // the ng-true-value and ng-false-value values so we compare - // them appropriately. - // - case 'trueValue': - result = "ng-true-value=\"'" + value + "'\" "; - break; - case 'falseValue': - result = "ng-false-value=\"'" + value + "'\" "; - break; - case 'awPopOver': - // construct the entire help link - result = "
"; - break; - case 'columnShow': - result = "ng-show=\"" + value + "\" "; - break; - case 'iconName': - result = "icon-name=\"" + value + "\" "; - break; - case 'iconSize': - result = "icon-size=\"" + value + "\" "; - break; - case 'icon': - // new method of constructing icon tag. Replaces Icon method. - result = ""; - break; - case 'autocomplete': - result = "autocomplete=\""; - result += (value) ? 'true' : 'false'; - result += "\" "; - break; - case 'columnClass': - result = 'class="'; - result += value; - result += '"'; - break; - case 'awLookupWhen': - result = "ng-attr-awlookup=\"" + value + "\" "; - break; - default: - result = key + "=\"" + value + "\" "; - } - } - - return result; - - }; -}) - -.factory('Icon', function () { - return function (icon) { - return " "; - }; -}) - -.factory('SelectIcon', ['Icon', - function (Icon) { - return function (params) { - // Common point for matching any type of action to the appropriate - // icon. The intention is to maintain consistent meaning and presentation - // for every icon used in the application. - var icon, - action = params.action, - size = params.size; - switch (action) { - case 'system_tracking': - icon = "fa-crosshairs"; - break; - case 'help': - icon = "fa-question-circle"; - break; - case 'add': - case 'create': - icon = "fa-plus"; - break; - case 'edit': - icon = "fa-pencil"; - break; - case 'delete': - icon = "fa-trash-o"; - break; - case 'group_update': - case 'source_update': - icon = 'fa-refresh'; - break; - case 'inventory_update': - icon = 'fa-refresh'; - break; - case 'scm_update': - icon = 'fa-refresh'; - break; - case 'run': - case 'rerun': - case 'submit': - icon = 'icon-launch'; - break; - case 'launch': - icon = 'icon-launch'; - break; - case 'stream': - icon = 'fa-clock-o'; - break; - case 'socket': - icon = 'fa-power-off'; - break; - case 'refresh': - icon = 'fa-refresh'; - break; - case 'close': - icon = 'fa-arrow-left'; - break; - case 'form_submit': - icon = 'fa-check-square-o'; - break; - case 'properties': - icon = "fa-pencil"; - break; - case 'reset': - icon = "fa-undo"; - break; - case 'view': - icon = "fa-search-plus"; - break; - case 'sync_status': - icon = "fa-cloud"; - break; - case 'schedule': - icon = "fa-calendar"; - break; - case 'question_cancel': - icon = 'fa-times'; - break; - case 'job_details': - icon = 'fa-list-ul'; - break; - case 'test': - icon = 'fa-bell-o'; - break; - case 'copy': - icon = "fa-copy"; - break; - case 'insights': - icon = "fa-info"; - break; - case 'network': - icon = "fa-sitemap"; - break; - case 'cancel': - icon = "fa-minus-circle"; - break; - } - icon += (size) ? " " + size : ""; - return Icon(icon); - }; - } -]) - - -.factory('NavigationLink', ['Attr', 'Icon', - function (Attr, Icon) { - return function (link) { - var html = "\n"; - } else { - html = ''; - } - - html += "
\n"; - html += "\n"; - html += "\n"; - html += "
\n"; - html += (params.td === undefined || params.td !== false) ? "\n" : ""; - - return html; - - }; - } -]) - -.factory('BadgeCount', [ - function () { - return function (params) { - // Adds a badge count with optional tooltip - - var list = params.list, - fld = params.fld, - field = list.fields[fld], - html = ''; - html = "\n"; - if (!field.noLink){ - html += ""; - html += "{{ " + list.iterator + '.' + fld + " }}"; - html += ""; - html += (field.badgeLabel) ? " " + field.badgeLabel : ""; - html += "\n"; - html += "\n"; - return html; - }; - } -]) - -.factory('Badge', [ - function () { - return function (field) { - - // Adds an icon(s) with optional tooltip - - var i, html = ''; - - if (field.badges) { - for (i = 0; i < field.badges.length; i++) { - if (field.badges[i].toolTip) { - html += "
"; - if (field.badges[i].toolTip) { - html += ""; - } - html += "\n"; - } - } - else if(field.badgeCustom === true){ - html += field.badgeIcon; - } - else { - if (field.badgeToolTip) { - html += ""; - if (field.badgeToolTip) { - html += ""; - } - html += "\n"; - } - return html; - }; - } -]) - -// List field with multiple icons -.factory('BuildLink', ['Attr', 'Icon', function(Attr, Icon){ - return function(params) { - var html = '', - field = params.field, - list = params.list, - base = params.base, - fld = params.fld; - - if (field.linkTo) { - html += " "; - } else if (field.icon) { - html += Icon(field.icon) + " "; - } - - // Add data binds - if (!field.ngBindHtml && !field.iconOnly && !field.ngEllipsis && (field.showValue === undefined || field.showValue === true)) { - if (field.ngBind) { - html += "{{ " + field.ngBind; - } else { - html += "{{" + list.iterator + "." + fld; - } - if (field.filter) { - html += " | " + field.filter + " }}"; - } - else { - html += " }}"; - } - } - - // Add additional text: - if (field.text) { - html += field.text; - } - html += ""; - if (field.alt_text) { - html += "  " + field.alt_text; - } - return html; - }; -}]) - -.factory('Template', ['Attr', function(Attr) { - return function(field) { - var ngClass = (field.ngClass) ? Attr(field, 'ngClass') : null; - var classList = (field.columnClass) ? Attr(field, 'columnClass') : null; - var ngInclude = (field.ngInclude) ? Attr(field, 'ngInclude') : null; - var attrs = _.compact([ngClass, classList, ngInclude]); - - return ''; - }; -}]) - -.factory('Column', ['i18n', 'Attr', 'Icon', 'DropDown', 'Badge', 'BadgeCount', 'BuildLink', 'Template', - function (i18n, Attr, Icon, DropDown, Badge, BadgeCount, BuildLink, Template) { - return function (params) { - var list = params.list, - fld = params.fld, - options = params.options, - base = params.base, - field = params.field ? params.field : list.fields[fld], - html = ''; - - field.columnClass = field.columnClass ? "List-tableCell " + field.columnClass : "List-tableCell"; - const classList = Attr(field, 'columnClass'); - - if (field.type !== undefined && field.type === 'DropDown') { - html = DropDown(params); - } else if (field.type === 'role') { - html += ` -
- - -
- `; - } else if (field.type === 'team_roles') { - html += ` -
- - -
- `; - } else if (field.type === 'labels') { - let showDelete = field.showDelete === undefined ? true : field.showDelete; - html += ` -
- - -
- `; - } else if (field.type === 'related_groups') { - let showDelete = field.showDelete === undefined ? true : field.showDelete; - html += ` -
- - -
- `; - } else if (field.type === 'owners') { - html += ` -
- -
- `; - } else if (field.type === 'revision') { - html += ` -
- -
`; - } else if (field.type === 'badgeCount') { - html = BadgeCount(params); - } else if (field.type === 'badgeOnly') { - html = Badge(field); - } else if (field.type === 'template') { - html = Template(field); - } else if (field.type === 'toggle') { - const ngIf = field.ngIf ? `ng-if="${field.ngIf}"` : ''; - html += ` -
- -
- `; - } else if (field.type === 'invalid') { - html += `
`; - html += ""; - html += "
"; - } else { - html += "
" : ""; - - //Add ngHide - //html += (field.ngHide) ? "" : ""; - - // Badge - if (options.mode !== 'lookup' && (field.badges || (field.badgeIcon && field.badgePlacement && field.badgePlacement === 'left'))) { - html += Badge(field); - } - - // Add collapse/expand icon --used on job_events page - if (list.hasChildren && field.hasChildren) { - html += ""; - } - - if (list.name === 'groups') { - html += "
"; - } - if (list.name === 'hosts') { - html += "
"; - } - - // Start the Link - if ((field.key || field.link || field.linkTo || field.ngClick || field.ngHref || field.uiSref || field.awToolTip || field.awPopOver) && - options.mode !== 'lookup' && options.mode !== 'select' && !field.noLink && !field.ngBindHtml) { - if(field.noLink === true){ - // provide an override here in case we want key=true for sorting purposes but don't want links -- see: portal mode, - } - else if (field.icons) { - field.icons.forEach(function(icon, idx) { - var key, i = field.icons[idx]; - for (key in i) { - field[key] = i[key]; - } - html += BuildLink({ - list: list, - field: field, - fld: fld, - base: field.linkBase || base - }) + ' '; - }); - } - else if(field.smartStatus){ - html += ''; - } - else { - html += BuildLink({ - list: list, - field: field, - fld: fld, - base: field.linkBase || base - }); - } - } - else { - if(field.simpleTip) { - html += ``; - } - // Add icon: - if (field.ngShowIcon) { - html += " "; - } else if (field.icon) { - html += Icon(field.icon) + " "; - } - // Add data binds - if (!field.ngBindHtml && !field.iconOnly && (field.showValue === undefined || field.showValue === true)) { - if (field.ngBind) { - html += "{{ " + field.ngBind; - } else { - html += "{{ " + list.iterator + "." + fld; - } - if (field.filter) { - html += " | " + field.filter + " }}"; - } - else { - html += " }}"; - } - } - // Add additional text: - if (field.text) { - html += field.text; - } - if(field.simpleTip) { - html += ``; - } - } - - if (list.name === 'hosts' || list.name === 'groups') { - html += "
"; - } - - // close ngShow - html += (field.ngShow) ? "" : ""; - - //close ngHide - //html += (field.ngHide) ? "" : ""; - - // Specific to Job Events page -showing event detail/results - html += (field.appendHTML) ? "
\n" : ""; - - // Badge - if (options.mode !== 'lookup' && field.badgeIcon && field.badgePlacement && field.badgePlacement !== 'left') { - html += Badge(field); - } - - // Field Tag - if (field.tag) { - html += ` - ${field.tag} - `; - } - } - html += "
"; - } - return html; - }; - } -]) - -.factory('ActionButton', function () { - return function (options) { - - var html = ''; - html += '
`; - } - return html; - }; -}); diff --git a/awx/ui/client/src/shared/icon/icon.block.less b/awx/ui/client/src/shared/icon/icon.block.less deleted file mode 100644 index 5cff28b209a1..000000000000 --- a/awx/ui/client/src/shared/icon/icon.block.less +++ /dev/null @@ -1,7 +0,0 @@ -/** @define Icon */ - -.Icon { - path, g { - fill: #242424; - } -} diff --git a/awx/ui/client/src/shared/icon/icon.directive.js b/awx/ui/client/src/shared/icon/icon.directive.js deleted file mode 100644 index c38abc7698c0..000000000000 --- a/awx/ui/client/src/shared/icon/icon.directive.js +++ /dev/null @@ -1,46 +0,0 @@ -export default - [ 'templateUrl', - '$rootScope', - function(templateUrl, $rootScope) { - return { - restrict: 'E', - templateUrl: templateUrl('shared/icon/icon'), - scope: { - }, - link: function(scope, element, attrs) { - - function buildSvgs() { - var svg = $('svg', element); - var iconPath = '#' + attrs.name; - - if ($(iconPath).length === 0) { - return; - } - - // Make a copy of the tag to insert its contents into this - // element's svg tag - var content = $(iconPath).clone(); - - // Copy classes & viewBox off the so that we preserve any styling - // when we copy the item inline - var classes = $(iconPath).attr('class'); - - // viewBox needs to be access via native - // javascript's setAttribute function - var viewBox = $(iconPath)[0].getAttribute('viewBox'); - - svg[0].setAttribute('viewBox', viewBox); - svg.attr('class', classes) - .html(content.contents()); - } - - $rootScope.$on('include-svg.svg-ready', function() { - buildSvgs(); - }); - - buildSvgs(); - - } - }; - } - ]; diff --git a/awx/ui/client/src/shared/icon/icon.partial.html b/awx/ui/client/src/shared/icon/icon.partial.html deleted file mode 100644 index 7640e25707ab..000000000000 --- a/awx/ui/client/src/shared/icon/icon.partial.html +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/awx/ui/client/src/shared/icon/include-svg.directive.js b/awx/ui/client/src/shared/icon/include-svg.directive.js deleted file mode 100644 index dcfa55f0456d..000000000000 --- a/awx/ui/client/src/shared/icon/include-svg.directive.js +++ /dev/null @@ -1,13 +0,0 @@ -export default ['$http', '$rootScope', function($http, $rootScope) { - return { - restrict: 'E', - link: function(scope, element, attrs) { - var path = attrs.href; - - $http.get(path).then(function(response) { - element.append(response.data); - $rootScope.$emit('include-svg.svg-ready'); - }); - } - }; -}]; diff --git a/awx/ui/client/src/shared/icon/main.js b/awx/ui/client/src/shared/icon/main.js deleted file mode 100644 index 87c8a0d474b7..000000000000 --- a/awx/ui/client/src/shared/icon/main.js +++ /dev/null @@ -1,7 +0,0 @@ -import icon from './icon.directive'; -//import includeSvg from './include-svg.directive'; - -export default - angular.module('awIcon', []) - .directive('awIcon', icon); - //.directive('includeSvg', includeSvg); diff --git a/awx/ui/client/src/shared/instance-groups-multiselect/instance-groups-modal/instance-groups-modal.directive.js b/awx/ui/client/src/shared/instance-groups-multiselect/instance-groups-modal/instance-groups-modal.directive.js deleted file mode 100644 index eebd9e68324a..000000000000 --- a/awx/ui/client/src/shared/instance-groups-multiselect/instance-groups-modal/instance-groups-modal.directive.js +++ /dev/null @@ -1,121 +0,0 @@ -export default ['templateUrl', '$window', function(templateUrl, $window) { - return { - restrict: 'E', - scope: { - instanceGroups: '=' - }, - templateUrl: templateUrl('shared/instance-groups-multiselect/instance-groups-modal/instance-groups-modal'), - - link: function(scope, element) { - - $('#instance-groups-modal').on('hidden.bs.modal', function () { - $('#instance-groups-modal').off('hidden.bs.modal'); - $(element).remove(); - }); - - scope.showModal = function() { - $('#instance-groups-modal').modal('show'); - }; - - scope.destroyModal = function() { - $('#instance-groups-modal').modal('hide'); - }; - }, - - controller: ['$scope', '$compile', 'QuerySet', 'GetBasePath','generateList', 'InstanceGroupList', function($scope, $compile, qs, GetBasePath, GenerateList, InstanceGroupList) { - - function init() { - - $scope.instance_group_queryset = { - order_by: 'name', - page_size: 5 - }; - - $scope.instance_group_default_params = { - order_by: 'name', - page_size: 5 - }; - - qs.search(GetBasePath('instance_groups'), $scope.instance_group_queryset) - .then(res => { - $scope.instance_group_dataset = res.data; - $scope.instance_groups = $scope.instance_group_dataset.results; - - let instanceGroupList = _.cloneDeep(InstanceGroupList); - - instanceGroupList.listTitle = false; - instanceGroupList.well = false; - instanceGroupList.multiSelect = true; - instanceGroupList.multiSelectPreview = { - selectedRows: 'igTags', - availableRows: 'instance_groups' - }; - instanceGroupList.fields.name.ngClick = "linkoutInstanceGroup(instance_group)"; - instanceGroupList.fields.name.columnClass = 'col-md-11 col-sm-11 col-xs-11'; - delete instanceGroupList.fields.consumed_capacity; - delete instanceGroupList.fields.jobs_running; - - let html = `${GenerateList.build({ - list: instanceGroupList, - input_type: 'instance-groups-modal-body', - hideViewPerPage: true, - mode: 'lookup' - })}`; - - $scope.list = instanceGroupList; - $('#instance-groups-modal-body').append($compile(html)($scope)); - - if ($scope.instanceGroups) { - $scope.instanceGroups = $scope.instanceGroups.map( (item) => { - item.isSelected = true; - if (!$scope.igTags) { - $scope.igTags = []; - } - $scope.igTags.push(item); - return item; - }); - } - - $scope.showModal(); - }); - - $scope.$watch('instance_groups', function(){ - angular.forEach($scope.instance_groups, function(instanceGroupRow) { - angular.forEach($scope.igTags, function(selectedInstanceGroup){ - if(selectedInstanceGroup.id === instanceGroupRow.id) { - instanceGroupRow.isSelected = true; - } - }); - }); - }); - } - - init(); - - $scope.$on("selectedOrDeselected", function(e, value) { - let item = value.value; - if (value.isSelected) { - if(!$scope.igTags) { - $scope.igTags = []; - } - $scope.igTags.push(item); - } else { - _.remove($scope.igTags, { id: item.id }); - } - }); - - $scope.linkoutInstanceGroup = function(instanceGroup) { - $window.open('/#/instance_groups/' + instanceGroup.id + '/instances','_blank'); - }; - - $scope.cancelForm = function() { - $scope.destroyModal(); - }; - - $scope.saveForm = function() { - $scope.instanceGroups = $scope.igTags; - $scope.destroyModal(); - }; - }] - }; -}]; diff --git a/awx/ui/client/src/shared/instance-groups-multiselect/instance-groups-modal/instance-groups-modal.partial.html b/awx/ui/client/src/shared/instance-groups-multiselect/instance-groups-modal/instance-groups-modal.partial.html deleted file mode 100644 index 0e3336534a98..000000000000 --- a/awx/ui/client/src/shared/instance-groups-multiselect/instance-groups-modal/instance-groups-modal.partial.html +++ /dev/null @@ -1,22 +0,0 @@ - diff --git a/awx/ui/client/src/shared/instance-groups-multiselect/instance-groups-multiselect.controller.js b/awx/ui/client/src/shared/instance-groups-multiselect/instance-groups-multiselect.controller.js deleted file mode 100644 index 91b7795a1fc1..000000000000 --- a/awx/ui/client/src/shared/instance-groups-multiselect/instance-groups-multiselect.controller.js +++ /dev/null @@ -1,14 +0,0 @@ -export default ['$scope', - function($scope) { - - $scope.instanceGroupsTags = []; - - $scope.$watch('instanceGroups', function() { - $scope.instanceGroupsTags = $scope.instanceGroups; - }, true); - - $scope.deleteTag = function(tag){ - _.remove($scope.instanceGroups, {id: tag.id}); - }; - } -]; \ No newline at end of file diff --git a/awx/ui/client/src/shared/instance-groups-multiselect/instance-groups.block.less b/awx/ui/client/src/shared/instance-groups-multiselect/instance-groups.block.less deleted file mode 100644 index bbfef9de99ee..000000000000 --- a/awx/ui/client/src/shared/instance-groups-multiselect/instance-groups.block.less +++ /dev/null @@ -1,15 +0,0 @@ -#instance-groups-panel { - table { - overflow: hidden; - } - .List-header { - margin-bottom: 20px; - } - .isActive { - border-left: 10px solid @list-row-select-bord; - } - .instances-list, - .instance-jobs-list { - margin-top: 20px; - } -} diff --git a/awx/ui/client/src/shared/instance-groups-multiselect/instance-groups.directive.js b/awx/ui/client/src/shared/instance-groups-multiselect/instance-groups.directive.js deleted file mode 100644 index cdb781b1d9c1..000000000000 --- a/awx/ui/client/src/shared/instance-groups-multiselect/instance-groups.directive.js +++ /dev/null @@ -1,19 +0,0 @@ -import instanceGroupsMultiselectController from './instance-groups-multiselect.controller'; -export default ['templateUrl', '$compile', - function(templateUrl, $compile) { - return { - scope: { - instanceGroups: '=', - fieldIsDisabled: '=' - }, - restrict: 'E', - templateUrl: templateUrl('shared/instance-groups-multiselect/instance-groups'), - controller: instanceGroupsMultiselectController, - link: function(scope) { - scope.openInstanceGroupsModal = function() { - $('#content-container').append($compile('')(scope)); - }; - } - }; - } -]; diff --git a/awx/ui/client/src/shared/instance-groups-multiselect/instance-groups.partial.html b/awx/ui/client/src/shared/instance-groups-multiselect/instance-groups.partial.html deleted file mode 100644 index d26c726ed314..000000000000 --- a/awx/ui/client/src/shared/instance-groups-multiselect/instance-groups.partial.html +++ /dev/null @@ -1,18 +0,0 @@ -
- - - - -
- -
-
- {{tag.name | sanitize}} -
-
-
diff --git a/awx/ui/client/src/shared/layouts/one-plus-one.less b/awx/ui/client/src/shared/layouts/one-plus-one.less deleted file mode 100644 index 4414be8758e4..000000000000 --- a/awx/ui/client/src/shared/layouts/one-plus-one.less +++ /dev/null @@ -1,48 +0,0 @@ -/* -* Large resolution 1/2 + 1/2 width panels -* Small resolution: 100% width panels, stacked -* Options: static height, custom breakpoint -*/ - -.OnePlusOne-container(@height: 100%; @breakpoint: 900px){ - height: ~"calc(100vh - 150px)"; - display: flex; - flex-direction: row; - @media screen and(max-width: @breakpoint){ - flex-direction: column; - height: 100%; - } -} - -.OnePlusOne-panel--left(@height: 100%; @breakpoint: 900px){ - flex: 1 1; - height: @height; - width: 100%; - max-width: 50%; - margin-right: 20px; - @media screen and (max-width: @breakpoint){ - max-width: 100%; - margin-right: 0px; - height: inherit; - } -} - -.OnePlusOne-panel--right(@height: 100%; @breakpoint: 900px){ - flex: 1 1; - height: @height; - width: 100%; - max-width: 50%; - margin-right: 0px; - @media screen and (max-width: @breakpoint) { - max-width: 100%; - height: inherit; - } -} - -.OnePlusOne-panelHeader{ - color: @default-interface-txt; - font-size: 14px; - font-weight: bold; - margin-right: 10px; - display: flex; -} diff --git a/awx/ui/client/src/shared/layouts/one-plus-two.less b/awx/ui/client/src/shared/layouts/one-plus-two.less deleted file mode 100644 index c9514daae5c7..000000000000 --- a/awx/ui/client/src/shared/layouts/one-plus-two.less +++ /dev/null @@ -1,68 +0,0 @@ -/* -* Large resolution: 1/3 + 2/3 width panels -* Small resolution: 100% width panels, stacked -* Options: static height, custom breakpoint -* -* Style conventions -* .ModuleName-component--subComponent -*/ - -.OnePlusTwo-container(@height: 100%; @breakpoint: 900px){ - height: @height; - display: flex; - flex-direction: row; - @media screen and (max-width: @breakpoint){ - flex-direction: column; - } -} - -.OnePlusTwo-left--panel(@height: 100%; @breakpoint: 900px) { - flex: 1 0; - height: @height; - width: 100%; - margin-right: 20px; - @media screen and (max-width: @breakpoint){ - height: inherit; - margin-right: 0px; - max-width: none; - } -} - -.OnePlusTwo-right--panel(@height: 100%; @breakpoint: 900px) { - height: @height; - flex: 2 0; - @media screen and (max-width: @breakpoint){ - flex-direction: column; - } -} - -.OnePlusTwo-panelHeader { - color: @default-interface-txt; - font-size: 14px; - font-weight: bold; - margin-right: 10px; - display: flex; -} - -.OnePlusTwo-left--details { - margin-top: 25px; -} - -.OnePlusTwo-left--detailsRow { - display: flex; -} - -.OnePlusTwo-left--detailsLabel { - word-wrap: break-word; - width: 170px; - display: inline-block; - color: @default-interface-txt; - text-transform: uppercase; - font-weight: 400; -} - -.OnePlusTwo-left--detailsContent { - display: inline-block; - width: 220px; - word-wrap: break-word; -} diff --git a/awx/ui/client/src/shared/limit-panels/limit-panels.directive.js b/awx/ui/client/src/shared/limit-panels/limit-panels.directive.js deleted file mode 100644 index 04505785bfd8..000000000000 --- a/awx/ui/client/src/shared/limit-panels/limit-panels.directive.js +++ /dev/null @@ -1,34 +0,0 @@ -export default [function() { - return { - restrict: 'E', - scope: { - maxPanels: '@', - panelContainer: '@' - }, - link: function(scope) { - - const maxPanels = parseInt(scope.maxPanels); - - scope.$watch( - () => angular.element('#' + scope.panelContainer).find('.at-Panel').length, - () => { - const panels = angular.element('#' + scope.panelContainer).find('.at-Panel'); - if(panels.length > maxPanels) { - // hide the excess panels - $(panels).each(function( index ) { - if(index+1 > maxPanels) { - $(this).addClass('d-none'); - } - else { - $(this).removeClass('d-none'); - } - }); - } else { - // show all the panels - $(panels).removeClass('d-none'); - } - } - ); - } - }; -}]; diff --git a/awx/ui/client/src/shared/limit-panels/main.js b/awx/ui/client/src/shared/limit-panels/main.js deleted file mode 100644 index 407cb09a95eb..000000000000 --- a/awx/ui/client/src/shared/limit-panels/main.js +++ /dev/null @@ -1,5 +0,0 @@ -import directive from './limit-panels.directive'; - -export default - angular.module('LimitPanelsModule', []) - .directive('awLimitPanels', directive); diff --git a/awx/ui/client/src/shared/list-generator/list-actions.partial.html b/awx/ui/client/src/shared/list-generator/list-actions.partial.html deleted file mode 100644 index f1dbb6f2d67e..000000000000 --- a/awx/ui/client/src/shared/list-generator/list-actions.partial.html +++ /dev/null @@ -1,70 +0,0 @@ - -
- -
-
-
-
- - -
- - - -
- -
- - -
- - - -
- -
- diff --git a/awx/ui/client/src/shared/list-generator/list-generator.factory.js b/awx/ui/client/src/shared/list-generator/list-generator.factory.js deleted file mode 100644 index 3b0b15e32ca4..000000000000 --- a/awx/ui/client/src/shared/list-generator/list-generator.factory.js +++ /dev/null @@ -1,596 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name shared.function:list-generator - * @description - * #ListGenerator - * - * Use GenerateList.inject(list_object, { key:value }) to generate HTML from a list object and inject it into the DOM. Returns the $scope of the new list. - * - * Pass in a list object and a JSON object of key:value parameters. List objects are found in lists/*.js. Possible parameters include: - * - * | Parameter | Required | Description | - * | --------- | -------- | ----------- | - * | activityStream | | Used in widgets/stream.js to create the list contained within the activity stream widget. | - * | hdr | | Deprecated. Was used when list generator created the lookup dialog. This was moved to helpers/Lookup.js. | - * | id | | DOM element ID attribute value. Use to inject the list into a custom DOM element. Otherwise, the HTML for a list will be injected into the DOM element with an ID attribute of 'htmlTemplate'. | - * | listSize | | Bootstrap size class to apply to the grid column containing the action buttons, which generally appears to the right of the search widget. Defaults to 'col-lg-8 col-md-6 col-sm-4 col-xs-3'. | - * | mode | Yes | One of 'edit', 'lookup', 'select', or 'summary'. Generally this will be 'edit'. helpers/Lookup.js uses 'lookup' to generate the lookup dialog. The 'select' option is used in certain controllers when multiple objects are being added to a parent object. For example, building a select list of Users that can be added to an Oranization. 'summary' is no longer used. | - * | scope | | If the HTML will be injected into the DOM by list generator, pass in an optional $scope to be used in conjuction with $compile. The list will be associated with the scope value. Otherwise, the scope of the DOM element will be fetched passed to $compile. | - * | showSearch | | true or false. Set to false, if the search widget should not be included in the generated HTML. | - * - * #HTML only - * - * Use the buildHTML() method to get a string containing the generated HTML for a list object. buldHTML() expects the same parameters as the inject method. For example: - * ``` - * var html = GenerateList.buildHTML({ - * mode: 'edit', - * showSearch: false - * }); - * ``` - * - * #List Objects - * - * List objects are found in lists/*.js. Any API endpoint that returns a collection or array is represented with a list object. Examples inlcude Organizations, Credentials, Inventories, etc. - * A list can have the following attributes: - * - * | Attribute | Description | - * | --------- | ----------- | - * | index | true or false. If false, the index column, which adds a sequential number to each table row starting with 1, will not be added to the table. | - * | iterator | String containing a descriptive name of a single row in the collection - inventory, organization, credential, etc. Used to generate name and ID attributes in the list HTML. | - * | name | Name of the collection. Generally matches the endpoint name - inventories, organizations, etc. Will match the $scope variable containing the array of rows comprising the collection. | - * | selectTitle | Descriptive title used when mode is 'select'. | - * | selectInstructions | Text and HTML used to create popover for help button when mode is 'select'. | - * | editTitle | Descriptive title used when mode is 'edit'. | - * - * ##Fields - * - * A list contains a fields object. Each column in the list is defined as a separate object within the fields object. A field definition may contain the following attributes: - * - * | Attribute | Description | - * | --------- | ----------- | - * | columnClass | String of CSS class names to add to the <td> elemnts of the table column. | - * | columnClick | Adds an ng-click directive to the <td> element. | - * | excludeModal | true or false. If false, the field will not be included in the generated HTML when the mode is 'lookup' | - * | key | true or false. If set to true, helpers/search.js will use the field name as the default sort order when generating the API request. | - * | noLink | true or false. If set to true this will override any 'key', 'linkTo', 'ngClick', or other option that would cause the field to be a link. Used in portal mode and custom inv. script. | - * | label | Text string used as the column header text. | - * | linkTo | Wraps the field value with an <a> element. Set to the value of the href attribute. | - * | ngClick | Wraps the field value with an <a> and adds the ng-click directive. Set to the JS expression that ng-click will evaluate. | - * | nosort | true or false. Setting to false removes the ability to sort the table by the column. | - * | searchOnly | true or false. Set to true if the field should be included in the search widget but not included as a column in the generated HTML <table>. | - * | searchOptions | Array of { name: 'Descriptive Name', value: 'api_value' } objects used to generate <options> for the <select> when searchType is 'select'. | - * | searchType | One of the available search types defined in helpers/search.js. | - * | sourceField | Name of the attribute within summary_fields. that the field maps to in the API response object. Used in conjunction with sourceModel. | - * | sourceModel | Name of the summary_fields object that the field maps to in the API response object. | - * - * ##Field Actions - * - * A list contains a fieldActions object. Each icon found in the Actions column is defined as an object within the fieldActions object. fieldActions can have a columnClass attribute, - * which may contain a string of CSS class names to add to the action <td> element. It may also contain a label attribute, which can be set to false to suppress the Actions column header. - * - * Field action items can have the following attributes: - * - * | Attribute | Description | - * | --------- | ----------- | - * | actionclass | Set to a string containing any CSS classes to add to the button. | - * | awToolTip | Adds the aw-tool-tip directive. Set to the value of the HTML or text to dislay in the tooltip. | - * | buttonContent | String containing button content. HTML is accepted in this string. | - * | dataPlacement | Set to the Bootstrip tooltip placement - right, left, top, bottom, etc. | - * | dataTipWatch | Set to the $scope variable that contains the text and HTML to display in the tooltip. A $scope.$watch will be added to the variable so that anytime its value changes the tooltip will change. | - * | mode | One of 'all' or 'edit'. Will generally be 'all'. Note that field actions are not displayed when the list is in 'lookup' mode. | - * | ngClass | Adds the ng-class directive. Set to the JS expression that ng-class will evaluate. | - * | ngShow | Adds the ng-show directive. Set to the JS expression that ng-show will evaluate. | - * - * ##Actions - * - * A list can contain an actions object. The actions object contains an object for each action button displayed in the top-right corner of the list container. An action can have the same - * attributes as an action defined in fieldAction. Both are actions. Clicking on an action evaluates the JS found in the ngClick attribute. In both cases icon is generated automatically by the SelectIcon() method in js/shared/generator-helpers.js. - * The real difference is that an <a> element is used to generate fieldAction items while a <button> element is used for action items. - */ - -import { templateUrl } from '../../shared/template-url/template-url.factory'; - -export default ['$compile', 'Attr', 'Icon', - 'Column', 'DropDown', 'SelectIcon', 'ActionButton', 'i18n', - function($compile, Attr, Icon, Column, DropDown, - SelectIcon, ActionButton, i18n) { - return { - - setList: function(list) { - this.list = list; - }, - - setOptions: function(options) { - this.options = options; - }, - - attr: Attr, - - icon: Icon, - - has: function(key) { - return (this.form[key] && this.form[key] !== null && this.form[key] !== undefined) ? true : false; - }, - - buildHTML: function(list, options) { - this.setList(list); - return this.build(options); - }, - - build: function(options) { - this.list = options.list; - this.options = options; - - var html = '', - list = this.list, - base, action, fld, field_action, fAction, itm; - - if (options.mode !== 'lookup') { - // Don't display an empty
if there is no listTitle - if ((options.title !== false && list.title !== false) && list.listTitle !== undefined) { - html += "
"; - html += "
"; - if (list.listTitle && options.listTitle !== false) { - html += "
" + list.listTitle + "
"; - // We want to show the list title badge by default and only hide it when the list config specifically passes a false flag - list.listTitleBadge = (typeof list.listTitleBadge === 'boolean' && list.listTitleBadge === false) ? false : true; - if (list.listTitleBadge) { - html += `{{ ${list.iterator}_dataset.count }}`; - } - } - html += "
"; - if (options.cancelButton === true) { - html += "
"; - html += "
\n"; - } - html += "
"; - } - } - - if (options.mode === 'edit' && list.editInstructions) { - html += "
\n"; - html += "\n"; - html += "Hint: " + list.editInstructions + "\n"; - html += "
\n"; - } - - if (list.multiSelectPreview) { - html += ""; - } - - if (options.instructions) { - html += "
" + options.instructions + "
\n"; - } else if (list.instructions) { - html += "
" + list.instructions + "
\n"; - } - - if (options.mode !== 'lookup' && (list.well === undefined || list.well)) { - html += `
`; - html += (!list.wellOverride) ? "List-well" : ""; - html += `">`; - // List actions - html += "
"; - html += "
"; - html += `
`; - - for (action in list.actions) { - list.actions[action] = _.defaults(list.actions[action], { dataPlacement: "top" }); - } - - html += "
"; - if (list.toolbarAuxAction) { - html += `
${list.toolbarAuxAction}
`; - } - html += "\n
"; - html += "
"; - // End list actions - } - - html += (list.searchRowActions) ? "
" : ""; - if (options.showSearch === undefined || options.showSearch === true) { - let singleSearchParam = list.singleSearchParam && list.singleSearchParam.param ? `single-search-param="${list.singleSearchParam.param}"` : ''; - html += ` -
- - -
- `; - } - if (list.searchRowActions) { - html += "
"; - - var actionButtons = ""; - Object.keys(list.searchRowActions || {}) - .forEach(act => { - actionButtons += ActionButton(list.searchRowActions[act]); - }); - html += ` -
- ${actionButtons} -
- `; - html += "
"; - } - - if (options.showSearch !== false) { - // Message for when a search returns no results. This should only get shown after a search is executed with no results. - html +=` -
-
No records matched your search.
-
- `; - } - - // Show the "no items" box when loading is done and the user isn't actively searching and there are no results - if (options.showEmptyPanel === undefined || options.showEmptyPanel === true){ - html += `
`; - html += (list.emptyListText) ? list.emptyListText : i18n._("PLEASE ADD ITEMS TO THIS LIST"); - html += "
"; - } - - // Add a title and optionally a close button (used on Inventory->Groups) - if (options.mode !== 'lookup' && list.showTitle) { - html += "
"; - html += (options.mode === 'edit' || options.mode === 'summary') ? list.editTitle : list.addTitle; - html += "
\n"; - } - - // table header row - html += "
0\""; - html += (list.awCustomScroll) ? " aw-custom-scroll " : ""; - html += ">\n"; - - function buildTable() { - var extraClasses = list['class']; - var multiSelect = list.multiSelect ? 'multi-select-list' : null; - var multiSelectExtended = list.multiSelectExtended ? 'true' : 'false'; - - if (options.mode === 'summary') { - extraClasses += ' table-summary'; - } - - return $('
') - .attr('id', list.name + '_table') - .addClass('List-table') - .addClass(extraClasses) - .attr('multi-select-list', multiSelect) - .attr('is-extended', multiSelectExtended); - } - - var table = buildTable(); - var innerTable = ''; - - if (!options.skipTableHead) { - innerTable += this.buildHeader(options); - } - - const layoutClass = options.mode === 'lookup' ? "List-lookupLayout" : (_.has(list, 'layoutClass') ? list.layoutClass : "List-defaultLayout"); - - // table body - // gotcha: transcluded elements require custom scope linking - binding to $parent models assumes a very rigid DOM hierarchy - // see: lookup-modal.directive.js for example - innerTable += options.mode === 'lookup' ? `
` : `
`; - - innerTable += "
"; - - let handleEditStateParams = function(stateParams){ - let matchingConditions = []; - - angular.forEach(stateParams, function(stateParam) { - matchingConditions.push(`$stateParams['` + stateParam + `'] == ${list.iterator}.id`); - }); - return matchingConditions; - }; - - if (options.mode !== 'lookup') { - let activeRowClass; - if(list && list.fieldActions && list.fieldActions.edit && list.fieldActions.edit.editStateParams) { - let matchingConditions = handleEditStateParams(list.fieldActions.edit.editStateParams); - activeRowClass = `ng-class="{'List-tableRow--selected' : ${matchingConditions.join(' || ')}}"`; - } - else if (list.iterator === 'inventory') { - activeRowClass = `ng-class="{'List-tableRow--selected': ($stateParams['${list.iterator}_id'] == ${list.iterator}.id) || ($stateParams['smartinventory_id'] == ${list.iterator}.id)}"`; - } - else { - activeRowClass = `ng-class="{'List-tableRow--selected' : $stateParams['${list.iterator}_id'] == ${list.iterator}.id}"`; - } - innerTable += `
`; - if (list.multiSelect) { - innerTable += - `
- -
`; - } - if(list.staticColumns) { - angular.forEach(list.staticColumns, function(staticColumn) { - innerTable += `
`; - innerTable += Column({ - list: list, - fld: staticColumn.field, - field: staticColumn.content, - options: options, - base: base - }); - innerTable += `
`; - }); - } - } else { - innerTable += `
`; - if (list.multiSelect) { - innerTable += ''; - } else { - if (options.input_type === "radio") { //added by JT so that lookup forms can be either radio inputs or check box inputs - innerTable += ``; - } - else { // its assumed that options.input_type = checkbox - innerTable += ""; - } - } - innerTable += `
`; - } - - innerTable += `
`; - - if (list.index) { - innerTable += "
{{ $index + ((" + list.iterator + "_page - 1) * " + list.iterator + "_page_size) + 1 }}.
\n"; - } - - base = (list.base) ? list.base : list.name; - base = base.replace(/^\//, ''); - for (fld in list.fields) { - if ((list.fields[fld].searchOnly === undefined || list.fields[fld].searchOnly === false) && - !(options.mode === 'lookup' && list.fields[fld].excludeModal === true)) { - innerTable += Column({ - list: list, - fld: fld, - options: options, - base: base - }); - } - } - - if (options.mode === 'select') { - if (options.input_type === "radio") { //added by JT so that lookup forms can be either radio inputs or check box inputs - innerTable += "
"; - } else { // its assumed that options.input_type = checkbox - innerTable += "
"; - } - } else if ((options.mode === 'edit' || options.mode === 'summary') && list.fieldActions) { - - // Row level actions - - innerTable += `
`; - - for (field_action in list.fieldActions) { - if (field_action !== 'columnClass') { - if (list.fieldActions[field_action].type && list.fieldActions[field_action].type === 'DropDown') { - innerTable += DropDown({ - list: list, - fld: field_action, - options: options, - base: base, - type: 'fieldActions', - td: false - }); - } - if (field_action === 'pending_deletion') { - innerTable += `${i18n._('Pending Delete')}`; - } else if (field_action === 'submit') { - innerTable += ``; - } else { - // Plug in Dropdown Component - if (field_action === 'submit' && list.fieldActions[field_action].relaunch === true) { - innerTable += ``; - } else if (field_action === 'submit' && list.fieldActions[field_action].launch === true) { - innerTable += ``; - } else { - fAction = list.fieldActions[field_action]; - innerTable += ""; - } - } - } - } - innerTable += "
\n"; - } - - // might need to wrap this in lookup logic - // innerTable += "
\n"; - // End List - innerTable += "
\n"; - - table.html(innerTable); - html += table.prop('outerHTML'); - - html += "
\n"; - - if (options.mode === 'select' && (options.selectButton === undefined || options.selectButton)) { - html += "
\n"; - html += " \n"; - html += "
\n"; - } - - if (options.paginate === undefined || options.paginate === true) { - let hide_view_per_page = (options.mode === "lookup" || options.hideViewPerPage) ? true : false; - html += `
`; - } - - if (options.mode === 'lookup' && options.lookupMessage) { - html = `
${options.lookupMessage}
` + html; - } - - return html; - }, - - buildHeader: function(options) { - const list = this.list; - let fld; - let html = ''; - - if (options === undefined) { - options = this.options; - } - - // will probably need some different classes for handling different layouts here - const layoutClass = options.mode === 'lookup' ? "List-lookupLayout" : (_.has(list, 'layoutClass') ? list.layoutClass : "List-defaultLayout"); - html += `
`; - - html += `
`; - - if (list.multiSelect) { - html += `
`; - } - - if (options.mode !== 'lookup' && list.staticColumns) { - angular.forEach(list.staticColumns, function() { - html += `
`; - }); - } - - html += `
`; - - for (fld in list.fields) { - if (options.mode !== 'lookup' || (options.mode === 'lookup' && (fld === 'name' || _.has(list.fields[fld], 'includeModal')))){ - let customClass = list.fields[fld].columnClass || ''; - const ngIf = list.fields[fld].ngIf ? `ng-if="${list.fields[fld].ngIf}"` : ''; - html += `
-
`; - } - } - - if(options.mode === 'edit' && list.fieldActions) { - html += `
"; - html += (list.fieldActions.label === undefined || list.fieldActions.label) ? i18n._("Actions") : ""; - html += "
"; - } - - html += "
"; - - html += `
`; - - return html; - }, - - wrapPanel: function(html){ - return ` -
${html}
`; - }, - - insertFormView: function(){ - return `
`; - }, - - insertSchedulerView: function(){ - return `
`; - } - }; - } -]; diff --git a/awx/ui/client/src/shared/list-generator/main.js b/awx/ui/client/src/shared/list-generator/main.js deleted file mode 100644 index 488179a1bfb8..000000000000 --- a/awx/ui/client/src/shared/list-generator/main.js +++ /dev/null @@ -1,15 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import generateList from './list-generator.factory'; -import toolbarButton from './toolbar-button.directive'; -import generatorHelpers from '../generator-helpers'; -import multiSelectList from '../multi-select-list/main'; - -export default - angular.module('listGenerator', [generatorHelpers.name, multiSelectList.name]) - .factory('generateList', generateList) - .directive('toolbarButton', toolbarButton); diff --git a/awx/ui/client/src/shared/list-generator/toolbar-button.directive.js b/awx/ui/client/src/shared/list-generator/toolbar-button.directive.js deleted file mode 100644 index fb9402212c96..000000000000 --- a/awx/ui/client/src/shared/list-generator/toolbar-button.directive.js +++ /dev/null @@ -1,66 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['SelectIcon', function(SelectIcon) { - return { - restrict: 'A', - scope: {}, - link: function(scope, element, attrs) { - - var toolbar = attrs.toolbar; - - if (toolbar) { - //if this is a toolbar button, set some defaults - attrs.class = 'btn-xs btn-primary'; - attrs.iconSize = 'fa-lg'; - } - - element.addClass('btn'); - - // If no class specified, default - // to btn-sm - if (_.isEmpty(attrs.class)) { - element.addClass("btn-sm"); - } else { - element.addClass(attrs.class); - } - - if (attrs.awPopOver) { - element.addClass("help-link-white"); - } - - if (attrs.iconName && _.isEmpty(attrs.id)) { - element.attr("id",attrs.iconName + "_btn"); - } - - if (!_.isEmpty(attrs.img)) { - $("") - .attr("src", $basePath + "assets/" + attrs.img) - .css({ width: "12px", height: "12px" }) - .appendTo(element); - } - - if (!_.isEmpty(attrs.iconClass)) { - $("") - .addClass(attrs.iconClass) - .appendTo(element); - } - else { - var icon = SelectIcon({ - action: attrs.iconName, - size: attrs.iconSize - }); - - $(icon).appendTo(element); - } - - if (!toolbar && !_.isEmpty(attrs.label)) { - element.text(attrs.label); - } - - } - }; -}]; diff --git a/awx/ui/client/src/shared/load-config/load-config.factory.js b/awx/ui/client/src/shared/load-config/load-config.factory.js deleted file mode 100644 index 56916cd7a409..000000000000 --- a/awx/ui/client/src/shared/load-config/load-config.factory.js +++ /dev/null @@ -1,40 +0,0 @@ -export default - function LoadConfig($rootScope, Store) { - return function() { - - var configSettings = {}; - - if(global.$ConfigResponse.custom_logo) { - configSettings.custom_logo = true; - $rootScope.custom_logo = global.$ConfigResponse.custom_logo; - } else { - configSettings.custom_logo = false; - } - - if(global.$ConfigResponse.custom_login_info) { - configSettings.custom_login_info = global.$ConfigResponse.custom_login_info; - $rootScope.custom_login_info = global.$ConfigResponse.custom_login_info; - } else { - configSettings.custom_login_info = false; - } - - if (global.$ConfigResponse.login_redirect_override) { - configSettings.login_redirect_override = global.$ConfigResponse.login_redirect_override; - } - - // Auto-resolving what used to be found when attempting to load local_setting.json - if ($rootScope.loginConfig) { - $rootScope.loginConfig.resolve('config loaded'); - } - global.$AnsibleConfig = configSettings; - Store('AnsibleConfig', global.$AnsibleConfig); - $rootScope.$emit('ConfigReady'); - - // Load new hardcoded settings from above - $rootScope.$emit('LoadConfig'); - - }; - } - -LoadConfig.$inject = - [ '$rootScope', 'Store' ]; diff --git a/awx/ui/client/src/shared/load-config/main.js b/awx/ui/client/src/shared/load-config/main.js deleted file mode 100644 index 3accf42ffaa5..000000000000 --- a/awx/ui/client/src/shared/load-config/main.js +++ /dev/null @@ -1,5 +0,0 @@ -import LoadConfig from './load-config.factory'; - -export default - angular.module('loadconfig', []) - .factory('LoadConfig', LoadConfig); diff --git a/awx/ui/client/src/shared/lookup/lookup-modal.block.less b/awx/ui/client/src/shared/lookup/lookup-modal.block.less deleted file mode 100644 index e5d7993f719e..000000000000 --- a/awx/ui/client/src/shared/lookup/lookup-modal.block.less +++ /dev/null @@ -1,3 +0,0 @@ -.Lookup .modal-body{ - padding-top: 0px; -} diff --git a/awx/ui/client/src/shared/lookup/lookup-modal.directive.js b/awx/ui/client/src/shared/lookup/lookup-modal.directive.js deleted file mode 100644 index cee16d2e1c7f..000000000000 --- a/awx/ui/client/src/shared/lookup/lookup-modal.directive.js +++ /dev/null @@ -1,119 +0,0 @@ -export default ['templateUrl', 'i18n', function(templateUrl, i18n) { - return { - restrict: 'E', - replace: true, - transclude: true, - scope: false, - templateUrl: templateUrl('shared/lookup/lookup-modal'), - link: function(scope, element, attrs, controller, transcludefn) { - - transcludefn(scope, (clone, linked_scope) => { - // scope.$resolve is a reference to resolvables in stateDefinition.resolve block - // https://ui-router.github.io/docs/latest/interfaces/state.statedeclaration.html#resolve - let list = linked_scope.$resolve.ListDefinition, - Dataset = linked_scope.$resolve.Dataset; - // search init - linked_scope.list = list; - linked_scope[`${list.iterator}_dataset`] = Dataset.data; - linked_scope[list.name] = linked_scope[`${list.iterator}_dataset`].results; - - element.find('.modal-body').append(clone); - - scope.init(element); - - }); - $('#form-modal').modal('show'); - }, - controller: ['$scope', '$state', 'EventService', function($scope, $state, eventService) { - let listeners, modal; - - function clickIsOutsideModal(e) { - const m = modal.getBoundingClientRect(); - const cx = e.clientX; - const cy = e.clientY; - - if (cx < m.left || cx > m.right || cy > m.bottom || cy < m.top) { - return true; - } - - return false; - } - - function clickToHide(event) { - if (clickIsOutsideModal(event)) { - $scope.cancelForm(); - } - } - - $scope.init = function(el) { - let list = $scope.list; - [modal] = el.find('.modal-dialog'); - if($state.params.selected) { - let selection = $scope[list.name].find(({id}) => id === parseInt($state.params.selected)); - $scope.currentSelection = _.pick(selection, 'id', 'name'); - } - $scope.$watch(list.name, function(){ - selectRowIfPresent(); - }); - let resource = list.iterator.replace(/_/g, ' '); - $scope.modalTitle = i18n._('Select') + ' ' + i18n._(resource); - - listeners = eventService.addListeners([ - [window, 'click', clickToHide] - ]); - }; - - function selectRowIfPresent(){ - let list = $scope.list; - if($scope.currentSelection && $scope.currentSelection.id) { - $scope[list.name].forEach(function(row) { - if (row.id === $scope.currentSelection.id) { - row.checked = 1; - } - }); - } - } - - $scope.saveForm = function () { - eventService.remove(listeners); - let list = $scope.list; - if($scope.currentSelection.name !== null) { - $scope.$parent[`${list.iterator}_name`] = $scope.currentSelection.name; - } - $scope.$parent[list.iterator] = $scope.currentSelection.id; - $state.go('^'); - }; - - $scope.cancelForm = function() { - eventService.remove(listeners); - $state.go('^'); - }; - - $scope.toggle_row = function (selectedRow) { - let list = $scope.list; - let count = 0; - $scope[list.name].forEach(function(row) { - if (row.id === selectedRow.id) { - if (row.checked) { - row.success_class = 'success'; - } else { - row.checked = 1; - row.success_class = ''; - } - $scope.currentSelection = { - name: row.name, - id: row.id - }; - } else { - row.checked = 0; - row.success_class = ''; - } - if (row.checked) { - count++; - } - }); - }; - - }] - }; -}]; diff --git a/awx/ui/client/src/shared/lookup/lookup-modal.partial.html b/awx/ui/client/src/shared/lookup/lookup-modal.partial.html deleted file mode 100644 index a33662d58d0c..000000000000 --- a/awx/ui/client/src/shared/lookup/lookup-modal.partial.html +++ /dev/null @@ -1,24 +0,0 @@ - diff --git a/awx/ui/client/src/shared/lookup/main.js b/awx/ui/client/src/shared/lookup/main.js deleted file mode 100644 index df069649ab35..000000000000 --- a/awx/ui/client/src/shared/lookup/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import directive from './lookup-modal.directive'; - -export default - angular.module('LookupModalModule', []) - .directive('lookupModal', directive); diff --git a/awx/ui/client/src/shared/main.js b/awx/ui/client/src/shared/main.js deleted file mode 100644 index bb3b3942ffe9..000000000000 --- a/awx/ui/client/src/shared/main.js +++ /dev/null @@ -1,72 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import listGenerator from './list-generator/main'; -import formGenerator from './form-generator'; -import lookupModal from './lookup/main'; -import smartSearch from './smart-search/main'; -import paginate from './paginate/main'; -import columnSort from './column-sort/main'; -import filters from './filters/main'; -import truncatedText from './truncated-text.directive'; -import stateExtender from './stateExtender.provider'; -import rbacUiControl from './rbacUiControl'; -import socket from './socket/main'; -import templateUrl from './template-url/main'; -import RestServices from '../rest/main'; -import stateDefinitions from './stateDefinitions.factory'; -import apiLoader from './api-loader'; -import variables from './variables/main'; -import parse from './parse/main'; -import loadconfig from './load-config/main'; -import nextpage from './next-page/main'; -import Modal from './Modal'; -import moment from './moment/main'; -import config from './config/main'; -import PromptDialog from './prompt-dialog'; -import directives from './directives'; -import orgAdminLookup from './org-admin-lookup/main'; -import limitPanels from './limit-panels/main'; -import multiSelectPreview from './multi-select-preview/main'; -import credentialTypesLookup from './credentialTypesLookup.factory'; -import utilities from './Utilities'; - -export default -angular.module('shared', [ - listGenerator.name, - formGenerator.name, - lookupModal.name, - smartSearch.name, - paginate.name, - columnSort.name, - filters.name, - 'ui.router', - rbacUiControl.name, - socket.name, - templateUrl.name, - RestServices.name, - apiLoader.name, - variables.name, - parse.name, - loadconfig.name, - nextpage.name, - Modal.name, - moment.name, - config.name, - PromptDialog.name, - directives.name, - filters.name, - orgAdminLookup.name, - limitPanels.name, - multiSelectPreview.name, - utilities.name, - 'ngCookies', - 'angular-duration-format' - ]) - .factory('stateDefinitions', stateDefinitions) - .factory('credentialTypesLookup', credentialTypesLookup) - .directive('truncatedText', truncatedText) - .provider('$stateExtender', stateExtender); diff --git a/awx/ui/client/src/shared/media-object.block.less b/awx/ui/client/src/shared/media-object.block.less deleted file mode 100644 index 085999154fdd..000000000000 --- a/awx/ui/client/src/shared/media-object.block.less +++ /dev/null @@ -1,14 +0,0 @@ -/** @define Media */ - -.Media { - display: flex; - align-items: flex-start; - - &-figure { - margin-right: 1.4rem; - } - - &-body { - flex: 1; - } -} diff --git a/awx/ui/client/src/shared/modal/modal.less b/awx/ui/client/src/shared/modal/modal.less deleted file mode 100644 index 5b09e72b92d8..000000000000 --- a/awx/ui/client/src/shared/modal/modal.less +++ /dev/null @@ -1,133 +0,0 @@ -.Modal-content { - display:flex; - flex-wrap:wrap; - flex-direction: row; - padding: 15px 20px; - -webkit-box-shadow: 5px 5px 5px 0px rgba(0,0,0,0.25); - -moz-box-shadow: 5px 5px 5px 0px rgba(0,0,0,0.25); - box-shadow: 5px 5px 5px 0px rgba(0,0,0,0.25); -} - -.Modal-header { - display: flex; - width: 100%; - margin-bottom: 20px; -} - -.Modal-title { - flex: 1 0 auto; - text-transform: uppercase; - color: @list-header-txt; - font-size: 14px; - font-weight: bold; - width: calc(100%-18px); - word-break: break-word; -} - -.Modal-exitHolder{ - justify-content: flex-end; - display:flex; -} - -.Modal-exit{ - cursor:pointer; - padding:0px; - border: none; - height:20px; - font-size: 20px; - background-color:@default-bg; - color:@default-icon-hov; - line-height:1; - opacity: 1; -} - -.Modal-exit:hover{ - color: @default-icon; - opacity: 1; -} - -.Modal-body { - width: 100%; -} - -.Modal-bodyQuery { - margin-bottom: 20px; - color: @default-interface-txt; -} - -.Modal-bodyTarget { - color: @default-data-txt; -} - -.Modal-footer { - display: flex; - justify-content: flex-end; - margin-top: 20px; - width: 100%; -} - -.Modal-defaultButton { - background-color: @default-bg; - color: @btn-txt; - text-transform: uppercase; - border-radius: 5px; - border: 1px solid @btn-bord; - transition: background-color 0.2s; - padding-left:15px; - padding-right: 15px; -} - -.Modal-defaultButton:hover { - background-color: @btn-bg-hov; - color: @btn-txt; -} - -.Modal-errorButton { - background-color: @default-err; - color: @btn-txt-sel; - text-transform: uppercase; - border-radius: 5px; - transition: background-color 0.2s; - padding-left:15px; - padding-right: 15px; -} - -.Modal-errorButton:hover { - background-color: @default-err-hov; - color: @btn-txt-sel; -} - -.Modal-errorButton--sourcesDelete:hover{ - cursor: not-allowed; -} - -.Modal-primaryButton { - background-color: @default-link; - color: @default-bg; - text-transform: uppercase; - border-radius: 5px; - transition: background-color 0.2s; - padding-left:15px; - padding-right: 15px; -} - -.Modal-primaryButton:hover { - background-color: @default-link-hov; - color: @default-bg; -} - -.Modal-errorButton:focus { - color: @btn-txt-sel; -} - -.Modal-footerButton { - padding: 4px 8px; -} - -.Modal-footerButton + .Modal-footerButton { - margin-left: 20px; -} - -.Modal-titleResourceName { - color: @default-err; -} diff --git a/awx/ui/client/src/shared/moment/main.js b/awx/ui/client/src/shared/moment/main.js deleted file mode 100644 index f1c70d88c0ad..000000000000 --- a/awx/ui/client/src/shared/moment/main.js +++ /dev/null @@ -1,9 +0,0 @@ -import newMoment from './moment'; - -export default - angular.module('moment', ['angularMoment']) - .config(function() { - // Remove the global variable for moment - delete window.moment; - }) - .constant('moment', newMoment); diff --git a/awx/ui/client/src/shared/moment/moment.js b/awx/ui/client/src/shared/moment/moment.js deleted file mode 100644 index bf03763b50fc..000000000000 --- a/awx/ui/client/src/shared/moment/moment.js +++ /dev/null @@ -1,21 +0,0 @@ -var originalMoment = require('moment'); -import {merge} from 'lodash'; - -function moment() { - - // navigator.language is available in all modern browsers. - // however navigator.languages is a new technology that - // lists the user's preferred languages, the first in the array - // being the user's top choice. navigator.languages is currently - // comptabile with chrome>v32, ffox>32, but not IE/Safari - var lang = window.navigator.languages ? - window.navigator.languages[0] : - (window.navigator.language || window.navigator.userLanguage); - - originalMoment.locale(lang); - return originalMoment.apply(this, arguments); -} - -merge(moment, originalMoment); - -export default moment; diff --git a/awx/ui/client/src/shared/multi-select-list/main.js b/awx/ui/client/src/shared/multi-select-list/main.js deleted file mode 100644 index 0c1bb7fcbadb..000000000000 --- a/awx/ui/client/src/shared/multi-select-list/main.js +++ /dev/null @@ -1,16 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import multiSelect from './multi-select-list.directive'; -//import selectAll from './select-all.directive'; -import selectListItem from './select-list-item.directive'; -import templateUrl from '../template-url/main'; - -export default - angular.module('multiSelectList', [templateUrl.name]) - .directive('multiSelectList', multiSelect) - //.directive('selectAll', selectAll) - .directive('selectListItem', selectListItem); diff --git a/awx/ui/client/src/shared/multi-select-list/multi-select-list.controller.js b/awx/ui/client/src/shared/multi-select-list/multi-select-list.controller.js deleted file mode 100644 index 9cd47667694b..000000000000 --- a/awx/ui/client/src/shared/multi-select-list/multi-select-list.controller.js +++ /dev/null @@ -1,206 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc object - * @name multiSelectList.controller:multiSelectList - * - * @description - * - * `multiSelectList` controller provides the API for the {@link multiSelectList.directive:multiSelectList `multiSelectList`} directive. The controller contains methods for selecting/deselecting items, controlling the extended selection, registering items to be selectable and emitting an event on the directive's `$scope`. - * - */ -export default ['$scope', - function ($scope) { - $scope.items = []; - $scope.selection = { - isExtended: false, - selectedItems: [], - deselectedItems: [] - }; - - // Makes $scope.selection.length an alias for $scope.selectedItems.length - Object.defineProperty($scope.selection, - 'length', - { get: function() { - return this.selectedItems.length; - } - }); - - function rebuildSelections() { - var _items = _($scope.items).chain(); - - $scope.selection.selectedItems = - _items.filter(function(item) { - return item.isSelected; - }).map('value').value(); - - $scope.selection.deselectedItems = - _items.map('value').difference($scope.selection.selectedItems) - .value(); - - /** - * - * @ngdoc event - * @name multiSelectList.selectionChanged - * @eventOf multiSelectList.directive:multiSelectList - * - */ - $scope.$emit('multiSelectList.selectionChanged', $scope.selection); - } - - /** - * @ngdoc method - * @name multiSelectList.controller:multiSelectList#registerItem - * @methodOf multiSelectList.controller:multiSelectList - * - * @description - * Prepares an object to be tracked in the select list. Returns the - * decorated item created by - * {@link multiSelectList.controller:multiSelectList#decorateItem `decorateItem`} - */ - this.registerItem = function(item) { - var foundItem = _.find($scope.items, function(existingItem) { return existingItem.id === item.id; }); - - if(foundItem) { - return foundItem; - } - else { - var decoratedItem = this.decorateItem(item); - $scope.items = $scope.items.concat(decoratedItem); - return decoratedItem; - } - }; - - /** - * @ngdoc method - * @name multiSelectList.controller:multiSelectList#deregisterItem - * @methodOf multiSelectList.controller:multiSelectList - * - * @description - * Removes an item from the list; called if the item is removed from the display - * so that it is no longer tracked as a selectable item. - */ - this.deregisterItem = function(leavingItem) { - $scope.items = $scope.items.filter(function(item) { - return leavingItem !== item; - }); - rebuildSelections(); - }; - - /** - * @ngdoc method - * @name multiSelectList.controller:multiSelectList#decorateItem - * @methodOf multiSelectList.controller:multiSelectList - * - * @description - * - * This decorates an item with an object that has an `isSelected` property. - * This value is used to determine the lists of selected and non-selected - * items to emit with the `multiSelectList.selectionChanged` - * event. - */ - this.decorateItem = function(item) { - return { - isSelected: false, - id: item.id, - value: item - }; - }; - - /** - * @ngdoc method - * @name multiSelectList.controller:multiSelectList#selectAll - * @methodOf multiSelectList.controller:multiSelectList - * - * @description - * Marks all items in the list as selected. - * Triggers {@link multiSelectList.selectionChanged `multiSelectList.selectionChanged`} - */ - this.selectAll = function() { - $scope.items.forEach(function(item) { - item.isSelected = true; - }); - rebuildSelections(); - }; - - /** - * @ngdoc method - * @name multiSelectList.controller:multiSelectList#deselectAll - * @methodOf multiSelectList.controller:multiSelectList - * - * @description - * Marks all items in the list as not selected. - * Triggers {@link multiSelectList.selectionChanged `multiSelectList.selectionChanged`} - */ - this.deselectAll = function() { - $scope.items.forEach(function(item) { - item.isSelected = false; - }); - $scope.selection.isExtended = false; - rebuildSelections(); - }; - - - /** - * @ngdoc method - * @name multiSelectList.controller:multiSelectList#deselectAllExtended - * @methodOf multiSelectList.controller:multiSelectList - * - * @description - * Disables extended selection. - * Triggers {@link multiSelectList.selectionChanged `multiSelectList.selectionChanged`} - */ - this.deselectAllExtended = function() { - $scope.selection.isExtended = false; - rebuildSelections(); - }; - - /** - * @ngdoc method - * @name multiSelectList.controller:multiSelectList#selectAllExtended - * @methodOf multiSelectList.controller:multiSelectList - * - * @description - * Enables extended selection. - * Triggers {@link multiSelectList.selectionChanged `multiSelectList.selectionChanged`} - */ - this.selectAllExtended = function() { - $scope.selection.isExtended = true; - rebuildSelections(); - }; - - /** - * @ngdoc method - * @name multiSelectList.controller:multiSelectList#selectItem - * @methodOf multiSelectList.controller:multiSelectList - * - * @description - * Marks an item as selected. - * Triggers {@link multiSelectList.selectionChanged `multiSelectList.selectionChanged`} - * - */ - this.selectItem = function(item) { - item.isSelected = true; - rebuildSelections(); - }; - - /** - * @ngdoc method - * @name multiSelectList.controller:multiSelectList#deregisterItem - * @methodOf multiSelectList.controller:multiSelectList - * - * @description - * Marks an item as not selected. - * Triggers {@link multiSelectList.selectionChanged `multiSelectList.selectionChanged`} - * - */ - this.deselectItem = function(item) { - item.isSelected = false; - rebuildSelections(); - }; - - }]; diff --git a/awx/ui/client/src/shared/multi-select-list/multi-select-list.directive.js b/awx/ui/client/src/shared/multi-select-list/multi-select-list.directive.js deleted file mode 100644 index 71424d3cbea5..000000000000 --- a/awx/ui/client/src/shared/multi-select-list/multi-select-list.directive.js +++ /dev/null @@ -1,146 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc overview - * @name multiSelectList - * @scope - * @description Does some stuff - * - * @ngdoc directive - * @name multiSelectList.directive:multiSelectList - * @description - * The `multiSelectList` directive works in conjunction with the - * `selectListItem` and (optionally) the `selectAll` directives to - * render checkboxes with list items and tracking the selected state - * of each item. The `selectListItem` directive renders a checkbox, - * and the `multiSelectList` directive tracks the selected state - * of list items. The `selectAll` directive renders a checkbox that - * will select/deselect all items in the list. - * - * - * This directive exposes a special object on its local scope called - * `selection` that is used to access the current selection state. - * The following properties on `selection` are available: - * - * | Property | Type | Details | - * |-------------------|-----------------|-------------------------------------------------------------| - * | `selectedItems` | {@type array} | The items that are currently selected | - * | `deselectedItem` | {@type array} | The items that are currently _not_ selected | - * | `isExtended` | {@type boolean} | Indicates that the user has requested an extended selection | - * | `length` | {@type number} | The length of the selected items array | - * - * Use the `multi-select-list` directive to indicate that you want - * to allow users to select items in a list. To display a checkbox - * next to each item, use the {@link multiSelectList.directive:selectListItem `select-list-item`} directive. - * - * # Rendering a basic multi-select list - * - * @example - * - * This example creates a list of names and then - * uses `multiSelectList` to make the names - * selectable: - * - - -
-
    -
  • - - {{item.name}} -
  • -
-
-
- -
- - # Making other elements respond to the state of the selction - - In this example, we'll see how to make a button that enables/disables - itself based on some property of the selection. - - - - angular.module('stateTrackingExample', ['multiSelectList']) - .controller('namesController', ['$scope', function($scope) { - $scope.names = - [ { name: 'John' - }, - { name: 'Jared' - }, - { name: 'Joe' - }, - { name: 'James' - }, - { name: 'Matt' - }, - { name: 'Luke' - }, - { name: 'Chris' - } - ]; - - // Initial logic for buttons - $scope.noSelections = true; - $scope.atLeastOneSelection = false; - $scope.exactlyTwoSelections = false; - - var cleanup = $scope.$on('multiSelectList.selectionChanged', function(e, selection) { - // Recalculate logic for buttons whenever selections change - $scope.noSelections = selection.length == 0; - $scope.atLeastOneSelection = selection.length >= 1; - $scope.exactlyTwoSelections = selection.length == 2; - }); - }]); - - -
- - - -
    -
  • - - {{item.name}} -
  • -
-
-
- -
- - * -*/ -import controller from './multi-select-list.controller'; - -export default -[ function() { - return { - restrict: 'A', - scope: { - }, - controller: controller - }; - }]; diff --git a/awx/ui/client/src/shared/multi-select-list/select-all.directive.js b/awx/ui/client/src/shared/multi-select-list/select-all.directive.js deleted file mode 100644 index 4470f05e79fa..000000000000 --- a/awx/ui/client/src/shared/multi-select-list/select-all.directive.js +++ /dev/null @@ -1,193 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc directive - * @name multiSelectList.directive:selectAll - * @scope - * @restrict E - * - * @param {string} label The text that will appear next to the checkbox - * @param {number} itemsLength The number of displayed items in the list - * @param {number} extendedItemsLength The total number of items in the list used for extended mode (see below) - * @param {string_expression} extendedLabel A custom label to display when prompting the user to extend the selection; this is an expression so strings must be in single quotes ('), but you can use scope varibles here to display the count of items with the `extendedItemsLength` property - * @param {boolean_expression} selectionsEmpty An expression that evaluates to a truthy value used to disable - * the select all checkbox when the displayed list is empty - * - * @description - * - * Use the `select-all` directive as a child of a `multi-select-list` - * to present the user with a checkbox that, when checked, checks all - * `select-list-item` children, and when unchecked it unchecks them. - * - * - -
-
    -
  • - -
  • -
  • - - {{item.name}} -
  • -
-
-
- - *
- * - * ## Extended Selections - * - * In some cases the list items you are displaying are only a subset of - * a larger list (eg. using pagination or infinite scroll to seperate - * items). In these cases, when a user checks "select all", it may be - * useful to give them the option to also select all the remaining - * items in the list. - * - * This behavior is controlled by the `extendedItemsLength` property - * of this directive. Set it to the total length of items in the list. - * For example, if you have a list of 100 items, displayed 10 per page, - * then `itemsLength` would be 10 and `extendedItemsLength` would be 100. - * When the user checks "select all" in the above example, it will show - * a button prompting them to "Select all 100 items". When the user selects - * this option, the `select-all` directive tells the `multiSelectList` - * controller that the selection is "extended" to all the items in the list. - * Listeners to the `multiSelectList.selectionChanged` event can then use this - * flag to respond differently when all items are selected. - * - * - * - - angular.module('extendedSelectionExample', ['multiSelectList']) - .controller('namesController', ['$scope', function($scope) { - - var cleanup = $scope.$on('multiSelectList.selectionChanged', function(e, selection) { - $scope.isSelectionExtended = selection.isExtended; - }); - - $scope.$on('$destroy', cleanup); - - $scope.allNames = - [ { name: 'John' - }, - { name: 'Jared' - }, - { name: 'Joe' - }, - { name: 'James' - }, - { name: 'Matt' - }, - { name: 'Luke' - }, - { name: 'Chris' - } - ]; - - $scope.firstPageOfNames = - $scope.allNames.slice(0,3); - }]); - - -
-

Extended Selection

-
    -
  • - -
  • -
  • - - {{item.name}} -
  • -
-
-
- *
- */ -// TODO: Extract to its own helper -// Example: -// template('shared/multi-select-list/select-all') -// // => -// '/static/js/shared/multi-select-list/select-all.html -// - -export default - [ 'templateUrl', - function(templateUrl) { - return { - require: '^multiSelectList', - restrict: 'E', - scope: { - label: '@', - itemsLength: '=', - extendedItemsLength: '=', - extendedLabel: '&', - isSelectionEmpty: '=selectionsEmpty' - }, - templateUrl: templateUrl('shared/multi-select-list/select-all'), - link: function(scope, element, attrs, controller) { - - scope.label = scope.label || 'All'; - scope.selectExtendedLabel = scope.extendedLabel() || 'Select all ' + scope.extendedItemsLength + ' items'; - scope.deselectExtendedLabel = scope.deselectExtendedLabel || 'Deselect extra items'; - - scope.doSelectAll = function() { - if (scope.isSelected) { - controller.selectAll(); - - if (scope.supportsExtendedItems) { - scope.showExtendedMessage = scope.itemsLength !== scope.extendedItemsLength; - } - } else { - controller.deselectAll(); - - if (scope.isSelectionExtended) { - scope.deselectAllExtended(); - } - - scope.showExtendedMessage = false; - } - }; - - scope.$watch('extendedItemsLength', function(value) { - scope.supportsExtendedItems = _.isNumber(value); - }); - - scope.$watch('isSelectionEmpty', function(value) { - if (value) { - scope.isSelected = false; - } - }); - - scope.selectAllExtended = function() { - controller.selectAllExtended(scope.extendedItemsLength); - scope.isSelectionExtended = true; - }; - - scope.deselectAllExtended = function() { - controller.deselectAllExtended(scope.extendedItemsLength); - scope.isSelectionExtended = false; - }; - - } - }; - }]; diff --git a/awx/ui/client/src/shared/multi-select-list/select-all.partial.html b/awx/ui/client/src/shared/multi-select-list/select-all.partial.html deleted file mode 100644 index e16d664345f2..000000000000 --- a/awx/ui/client/src/shared/multi-select-list/select-all.partial.html +++ /dev/null @@ -1,19 +0,0 @@ - - - diff --git a/awx/ui/client/src/shared/multi-select-list/select-list-item.directive.js b/awx/ui/client/src/shared/multi-select-list/select-list-item.directive.js deleted file mode 100644 index 31d64431f09a..000000000000 --- a/awx/ui/client/src/shared/multi-select-list/select-list-item.directive.js +++ /dev/null @@ -1,50 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc directive - * @name multiSelectList.directive:selectListItem - * @restrict E - * @scope - * @description - * - The `select-list-item` directive renders a checkbox for tracking - the state of a given item in a list. When the user checks the - checkbox it tells the `multi-select-list` controller to select - the item; when the user unchecks the checkbox it tells the controller - to deselect the item. - - @example - - For examples of using this directive, see {@link multiSelectList.directive:multiSelectList multiSelectList}. - - */ -export default - [ function() { - return { - restrict: 'E', - scope: { - item: '=item', - disabled: '=' - }, - require: '^multiSelectList', - template: '', - link: function(scope, element, attrs, multiSelectList) { - - scope.decoratedItem = multiSelectList.registerItem(scope.item); - - scope.userInteractionSelect = function(item) { - if (item.isSelected === true) { - multiSelectList.selectItem(scope.decoratedItem); - } else if (item.isSelected === false) { - multiSelectList.deselectItem(scope.decoratedItem); - } - scope.$emit("selectedOrDeselected", scope.decoratedItem); - }; - - } - }; - }]; diff --git a/awx/ui/client/src/shared/multi-select-preview/main.js b/awx/ui/client/src/shared/multi-select-preview/main.js deleted file mode 100644 index 1f50c494c0ff..000000000000 --- a/awx/ui/client/src/shared/multi-select-preview/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import multiSelectPreview from './multi-select-preview.directive'; - -export default - angular.module('multiSelectPreview', []) - .directive('multiSelectPreview', multiSelectPreview); diff --git a/awx/ui/client/src/shared/multi-select-preview/multi-select-preview.block.less b/awx/ui/client/src/shared/multi-select-preview/multi-select-preview.block.less deleted file mode 100644 index 2a21ab3f2e2e..000000000000 --- a/awx/ui/client/src/shared/multi-select-preview/multi-select-preview.block.less +++ /dev/null @@ -1,97 +0,0 @@ -.MultiSelectPreview { - display: flex; - flex: 1 0 auto; - margin-bottom: 15px; - align-items: baseline; -} - -.MultiSelectPreview-selectedItems { - display: flex; - flex: 0 0 100%; - background-color: @default-no-items-bord; - border: 1px solid @default-border; - padding: 10px; - border-radius: 5px; -} - -.MultiSelectPreview-selectedItemsLabel, .MultiSelectPreview-label { - color: @default-interface-txt; - margin-right: 10px; -} - -.MultiSelectPreview-selectedItemsLabel { - flex: 0 0 80px; - line-height: 29px; -} - -.MultiSelectPreview-previewTags--outer { - flex: 1 0 auto; - max-width: ~"calc(100% - 140px)"; -} - -.MultiSelectPreview-previewTags--inner { - display: flex; - flex-wrap: wrap; - align-items: flex-start; -} - -.MultiSelectPreview-previewTagContainer { - display: flex; -} - -.MultiSelectPreview-previewTagRevert { - flex: 0 0 60px; - line-height: 29px; -} - -.MultiSelectPreview-revertLink { - font-size: 12px; -} - -.MultiSelectPreview-previewTagContainerDelete { - background-color: @default-link; - border-top-left-radius: 5px; - border-bottom-left-radius: 5px; - color: @default-bg; - padding: 0 5px; - margin: 4px 0px; - align-items: center; - display: flex; - cursor: pointer; -} - -.MultiSelectPreview-previewTagContainerDelete:hover { - border-color: @default-err; - background-color: @default-err; -} - -.MultiSelectPreview-previewTagContainerDelete:hover > .MultiSelectPreview-previewTagContainerTagDelete { - color: @default-bg; -} - -.MultiSelectPreview-previewTag { - border-radius: 5px; - padding: 2px 10px; - margin: 4px 0px; - font-size: 12px; - color: @default-interface-txt; - background-color: @default-list-header-bg; - margin-right: 5px; - max-width: 100%; - display: inline-block; -} - -.MultiSelectPreview-previewTagLabel { - color: @default-list-header-bg; -} - -.MultiSelectPreview-previewTag--deletable { - color: @default-bg; - background-color: @default-link; - margin-right: 0px; - border-top-left-radius: 0px; - border-bottom-left-radius: 0px; - border-right: 0; - max-width: ~"calc(100% - 23px)"; - margin-right: 5px; -} diff --git a/awx/ui/client/src/shared/multi-select-preview/multi-select-preview.controller.js b/awx/ui/client/src/shared/multi-select-preview/multi-select-preview.controller.js deleted file mode 100644 index be166234c733..000000000000 --- a/awx/ui/client/src/shared/multi-select-preview/multi-select-preview.controller.js +++ /dev/null @@ -1,21 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['$scope', - function ($scope) { - $scope.unselectSelectedRow = function(index) { - - angular.forEach($scope.availableRows, function(value) { - if(value.id === $scope.selectedRows[index].id) { - value.isSelected = false; - } - }); - - $scope.selectedRows.splice(index, 1); - - }; - } -]; diff --git a/awx/ui/client/src/shared/multi-select-preview/multi-select-preview.directive.js b/awx/ui/client/src/shared/multi-select-preview/multi-select-preview.directive.js deleted file mode 100644 index 6805840f193a..000000000000 --- a/awx/ui/client/src/shared/multi-select-preview/multi-select-preview.directive.js +++ /dev/null @@ -1,20 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - import MultiSelectPreviewController from './multi-select-preview.controller'; - - export default ['templateUrl', function(templateUrl) { - return { - restrict: 'E', - replace: true, - scope: { - selectedRows: '=', - availableRows: '=' - }, - controller: MultiSelectPreviewController, - templateUrl: templateUrl('shared/multi-select-preview/multi-select-preview') - }; - }]; diff --git a/awx/ui/client/src/shared/multi-select-preview/multi-select-preview.partial.html b/awx/ui/client/src/shared/multi-select-preview/multi-select-preview.partial.html deleted file mode 100644 index 891026dd8aa4..000000000000 --- a/awx/ui/client/src/shared/multi-select-preview/multi-select-preview.partial.html +++ /dev/null @@ -1,15 +0,0 @@ -
-
-
- SELECTED: -
-
-
-
- - -
-
-
-
-
diff --git a/awx/ui/client/src/shared/next-page/main.js b/awx/ui/client/src/shared/next-page/main.js deleted file mode 100644 index 8d9e83f28a63..000000000000 --- a/awx/ui/client/src/shared/next-page/main.js +++ /dev/null @@ -1,5 +0,0 @@ -import NextPage from './next-page.factory'; - -export default - angular.module('nextpage', []) - .factory('NextPage', NextPage); diff --git a/awx/ui/client/src/shared/next-page/next-page.factory.js b/awx/ui/client/src/shared/next-page/next-page.factory.js deleted file mode 100644 index fa5bce3dc76b..000000000000 --- a/awx/ui/client/src/shared/next-page/next-page.factory.js +++ /dev/null @@ -1,28 +0,0 @@ -export default - function NextPage(Rest, $q) { - return function(params) { - - let getNext = function(getNextParams){ - Rest.setUrl(getNextParams.url); - return Rest.get() - .then(function (res) { - if (res.data.next) { - return getNext({ - url: res.data.next, - arrayOfValues: getNextParams.arrayOfValues.concat(res.data.results) - }); - } else { - return $q.resolve(getNextParams.arrayOfValues.concat(res.data.results)); - } - }) - .catch(function(response) { - return $q.reject( response ); - }); - }; - - return getNext(params); - - }; - } - -NextPage.$inject = ['Rest', '$q']; diff --git a/awx/ui/client/src/shared/org-admin-lookup/main.js b/awx/ui/client/src/shared/org-admin-lookup/main.js deleted file mode 100644 index 3d4f162f6265..000000000000 --- a/awx/ui/client/src/shared/org-admin-lookup/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import OrgAdminLookupFactory from './org-admin-lookup.factory'; - -export default - angular.module('orgAdminLookup', []) - .service('OrgAdminLookup', OrgAdminLookupFactory); diff --git a/awx/ui/client/src/shared/org-admin-lookup/org-admin-lookup.factory.js b/awx/ui/client/src/shared/org-admin-lookup/org-admin-lookup.factory.js deleted file mode 100644 index 3e86cd866c97..000000000000 --- a/awx/ui/client/src/shared/org-admin-lookup/org-admin-lookup.factory.js +++ /dev/null @@ -1,65 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default - ['Rest', 'Authorization', 'GetBasePath', 'ProcessErrors', '$rootScope', '$q', - function(Rest, Authorization, GetBasePath, ProcessErrors, $rootScope, $q){ - return { - checkForAdminAccess: function(params) { - // params.organization - id of the organization in question - var deferred = $q.defer(); - if(Authorization.getUserInfo('is_superuser') !== true) { - Rest.setUrl(GetBasePath('users') + $rootScope.current_user.id + '/admin_of_organizations'); - Rest.get({ params: { id: params.organization } }) - .then(({data}) => { - if(data.count && data.count > 0) { - deferred.resolve(true); - } - else { - deferred.resolve(false); - } - }); - } - else { - deferred.resolve(true); - } - - return deferred.promise; - }, - - checkForRoleLevelAdminAccess: function(organization_id, role_level) { - let deferred = $q.defer(); - let params = { - role_level, - id: organization_id - }; - - if(Authorization.getUserInfo('is_superuser') !== true) { - Rest.setUrl(GetBasePath('organizations')); - Rest.get({ params: params }) - .then(({data}) => { - if(data.count && data.count > 0) { - deferred.resolve(true); - } - else { - deferred.resolve(false); - } - }) - .catch(({data, status}) => { - ProcessErrors(null, data, status, null, { - hdr: 'Error!', - msg: 'Failed to get organization data based on role_level. Return status: ' + status - }); - }); - } - else { - deferred.resolve(true); - } - return deferred.promise; - } - }; - } - ]; diff --git a/awx/ui/client/src/shared/paginate/main.js b/awx/ui/client/src/shared/paginate/main.js deleted file mode 100644 index 394f9b90342b..000000000000 --- a/awx/ui/client/src/shared/paginate/main.js +++ /dev/null @@ -1,13 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import directive from './paginate.directive'; -import controller from './paginate.controller'; - -export default - angular.module('PaginateModule', []) - .directive('paginate', directive) - .controller('PaginateController', controller); diff --git a/awx/ui/client/src/shared/paginate/paginate.block.less b/awx/ui/client/src/shared/paginate/paginate.block.less deleted file mode 100644 index 0947c4d4e224..000000000000 --- a/awx/ui/client/src/shared/paginate/paginate.block.less +++ /dev/null @@ -1,80 +0,0 @@ - // @todo cleanup these messy overrides for styles in ansible-ui.min.css - -.Paginate-controls--first a, -.Paginate-controls--previous a{ - border-radius: 4px 0 0 4px; - } -.Paginate-controls--last a, -.Paginate-controls--next a{ - border-radius: 0px 4px 4px 0; -} - - .Paginate-controls--item a { - font-size: 12px; - padding: 3px 6px !important; - border-color: @list-pagin-bord; - } - -.Paginate { - margin-top: 20px; - font-size: 10px !important; - color: @list-pagin-text; - text-transform: uppercase; - display: flex; - justify-content: flex-end; -} - -.Paginate-pager--pageof { - line-height: 25px; - margin-left: 10px; -} - -.Paginate-wrapper { - display: flex; - flex: 1 0 auto; -} - -.Paginate-controls { - margin-bottom: 0px; -} - -.Paginate-controls--active { - color: #fff !important; - border-color: @default-icon-hov !important; - background-color: @default-icon-hov !important; -} - -.Paginate-total { - display: flex; - align-items: flex-end; - margin: 0 0 -2px 10px; -} - -.Paginate-filterLabel{ - padding: 0px 10px 0px 10px; -} - -.Paginate-filteringDropdowns{ - display: flex; - align-items: center; - line-height: 25px; -} - -.Paginate-dropdown{ - z-index: 1041; - margin-bottom: 10px; -} - -.Paginate-filteringDropdowns { - & > .DashboardGraphs-periodDropdown { - min-width: 50px; - - & > ul { - min-width: 50px; - } - } -} - -.Paginate-itemsOf { - line-height: 25px; -} diff --git a/awx/ui/client/src/shared/paginate/paginate.controller.js b/awx/ui/client/src/shared/paginate/paginate.controller.js deleted file mode 100644 index 10a30bcaef0b..000000000000 --- a/awx/ui/client/src/shared/paginate/paginate.controller.js +++ /dev/null @@ -1,132 +0,0 @@ -export default ['$scope', '$stateParams', '$state', 'GetBasePath', 'QuerySet', '$interpolate', - function($scope, $stateParams, $state, GetBasePath, qs, $interpolate) { - - let pageSize = $scope.querySet ? $scope.querySet.page_size || 20 : $stateParams[`${$scope.iterator}_search`].page_size || 20, - queryset, path; - - $scope.pageSize = pageSize; - $scope.basePageSize = parseInt(pageSize) === 5 ? 5 : 20; - $scope.maxVisiblePages = $scope.maxVisiblePages ? parseInt($scope.maxVisiblePages) : 10; - - $scope.filter = function(id){ - let pageSize = Number(id); - $('#period-dropdown') - .replaceWith(""+id+ - "\n"); - - if ($scope.querySet){ - $scope.querySet = _.merge($scope.querySet, { page_size: `${pageSize}`}); - } else { - $scope.querySet = _.merge($stateParams[`${$scope.iterator}_search`], { page_size: `${pageSize}`}); - } - $scope.toPage(1); - }; - - $scope.toPage = function(page) { - if (page === 0 || page > $scope.last) { - return; - } - if (GetBasePath($scope.basePath) || $scope.basePath) { - path = GetBasePath($scope.basePath) || $scope.basePath; - } else { - let interpolator = $interpolate($scope.basePath); - path = interpolator({ $stateParams: $stateParams }); - } - if ($scope.querySet) { - // merging $scope.querySet seems to destroy our initial reference which - // kills the two-way binding here. To fix that, clone the queryset first - // and merge with that object. - let origQuerySet = _.cloneDeep($scope.querySet); - queryset = _.merge(origQuerySet, { page: page }); - - } else { - let origStateParams = _.cloneDeep($stateParams[`${$scope.iterator}_search`]); - queryset = _.merge(origStateParams, { page: page }); - } - if (!$scope.querySet) { - $state.go('.', { - [$scope.iterator + '_search']: queryset - }, {notify: false}); - } - qs.search(path, queryset).then((res) => { - if ($scope.querySet) { - // Update the query set - $scope.querySet = queryset; - } - $scope.dataset = res.data; - $scope.collection = res.data.results; - $scope.$emit('updateDataset', res.data, queryset); - }); - $('html, body').animate({scrollTop: 0}, 0); - }; - - function calcLast() { - return Math.ceil($scope.dataset.count / $scope.pageSize); - } - - function calcCurrent() { - if ($scope.querySet) { - return parseInt($scope.querySet.page || '1'); - } else { - return parseInt($stateParams[`${$scope.iterator}_search`].page || '1'); - } - } - - function calcPageRange(current, last) { - let result = [], - maxVisiblePages = parseInt($scope.maxVisiblePages), - pagesLeft, - pagesRight; - if (maxVisiblePages % 2) { - // It's an odd number - pagesLeft = (maxVisiblePages - 1) / 2; - pagesRight = ((maxVisiblePages - 1) / 2) + 1; - } else { - // Its an even number - pagesLeft = pagesRight = maxVisiblePages / 2; - } - if (last < maxVisiblePages) { - // Don't have enough pages to exceed the max range - just show all of them - result = _.range(1, last + 1); - } else if (current === last) { - result = _.range(last + 1 - maxVisiblePages, last + 1); - } else { - let topOfRange = current + pagesRight > maxVisiblePages + 1 ? (current + pagesRight < last + 1 ? current + pagesRight : last + 1) : maxVisiblePages + 1; - let bottomOfRange = (topOfRange === last + 1) ? last + 1 - maxVisiblePages : (current - pagesLeft > 0 ? current - pagesLeft : 1); - result = _.range(bottomOfRange, topOfRange); - } - return result; - } - - function calcDataRange() { - if ($scope.current === 1 && $scope.dataset.count < parseInt($scope.pageSize)) { - return `1 - ${$scope.dataset.count}`; - } else if ($scope.current === 1) { - return `1 - ${$scope.pageSize}`; - } else { - let floor = (($scope.current - 1) * parseInt($scope.pageSize)) + 1; - let ceil = floor + parseInt($scope.pageSize) - 1 < $scope.dataset.count ? floor + parseInt($scope.pageSize) - 1 : $scope.dataset.count; - return `${floor} - ${ceil}`; - } - } - - function calcPageSize(){ - let pageSize = $scope.querySet ? $scope.querySet.page_size || 20 : $stateParams[`${$scope.iterator}_search`].page_size || 20; - return Number(pageSize) ; - } - - let updatePaginationVariables = function() { - $scope.pageSize = calcPageSize(); - $scope.current = calcCurrent(); - $scope.last = calcLast(); - $scope.pageRange = calcPageRange($scope.current, $scope.last); - $scope.dataRange = calcDataRange(); - }; - - updatePaginationVariables(); - - $scope.$watch('collection', function(){ - updatePaginationVariables(); - }); - } -]; diff --git a/awx/ui/client/src/shared/paginate/paginate.directive.js b/awx/ui/client/src/shared/paginate/paginate.directive.js deleted file mode 100644 index b07ad12212cc..000000000000 --- a/awx/ui/client/src/shared/paginate/paginate.directive.js +++ /dev/null @@ -1,19 +0,0 @@ -export default ['templateUrl', - function(templateUrl) { - return { - restrict: 'E', - replace: false, - scope: { - collection: '=', - dataset: '=', - iterator: '@', - basePath: '@', - querySet: '=?', - maxVisiblePages: '@', - hideViewPerPage: '=' - }, - controller: 'PaginateController', - templateUrl: templateUrl('shared/paginate/paginate') - }; - } -]; diff --git a/awx/ui/client/src/shared/paginate/paginate.partial.html b/awx/ui/client/src/shared/paginate/paginate.partial.html deleted file mode 100644 index e5516753794d..000000000000 --- a/awx/ui/client/src/shared/paginate/paginate.partial.html +++ /dev/null @@ -1,72 +0,0 @@ -
-
- - {{ 'Page' | translate }} - {{current}} {{ 'of' | translate }} - {{last}} - -
-
- - {{ 'ITEMS' | translate }}  - {{dataRange}} - {{ 'of' | translate }} {{dataset.count | number}} - -
-
VIEW PER PAGE
-
- - {{pageSize}} - - -
-
-
-
diff --git a/awx/ui/client/src/shared/parse/main.js b/awx/ui/client/src/shared/parse/main.js deleted file mode 100644 index 962bf14bcf46..000000000000 --- a/awx/ui/client/src/shared/parse/main.js +++ /dev/null @@ -1,5 +0,0 @@ -import ParseTypeChange from './parse-type-change.factory'; - -export default - angular.module('parse', []) - .factory('ParseTypeChange', ParseTypeChange); diff --git a/awx/ui/client/src/shared/parse/parse-type-change.factory.js b/awx/ui/client/src/shared/parse/parse-type-change.factory.js deleted file mode 100644 index eaa3ff0c452f..000000000000 --- a/awx/ui/client/src/shared/parse/parse-type-change.factory.js +++ /dev/null @@ -1,124 +0,0 @@ -import 'codemirror/lib/codemirror.js'; -import 'codemirror/mode/javascript/javascript.js'; -import 'codemirror/mode/yaml/yaml.js'; -import 'codemirror/mode/jinja2/jinja2.js'; -import 'codemirror/addon/lint/lint.js'; -import 'angular-codemirror/lib/yaml-lint.js'; -import 'codemirror/addon/edit/closebrackets.js'; -import 'codemirror/addon/edit/matchbrackets.js'; -import 'codemirror/addon/selection/active-line.js'; - -export default - function ParseTypeChange(Alert, AngularCodeMirror) { - return function(params) { - var scope = params.scope, - field_id = params.field_id, - fld = (params.variable) ? params.variable : 'variables', - pfld = (params.parse_variable) ? params.parse_variable : 'parseType', - onReady = params.onReady, - onChange = params.onChange, - readOnly = params.readOnly; - - function removeField(fld) { - //set our model to the last change in CodeMirror and then destroy CodeMirror - scope[fld] = scope[fld + 'codeMirror'].getValue(); - $('#cm-' + fld + '-container > .CodeMirror').empty().remove(); - } - - function createField(onChange, onReady, fld) { - //hide the textarea and show a fresh CodeMirror with the current mode (json or yaml) - let variableEditModes = { - yaml: { - mode: 'text/x-yaml', - matchBrackets: true, - autoCloseBrackets: true, - styleActiveLine: true, - lineNumbers: true, - gutters: ['CodeMirror-lint-markers'], - lint: true, - scrollbarStyle: null - }, - json: { - mode: 'application/json', - styleActiveLine: true, - matchBrackets: true, - autoCloseBrackets: true, - lineNumbers: true, - gutters: ['CodeMirror-lint-markers'], - lint: true, - scrollbarStyle: null - } - }; - scope[fld + 'codeMirror'] = AngularCodeMirror(readOnly); - scope[fld + 'codeMirror'].addModes(variableEditModes); - scope[fld + 'codeMirror'].showTextArea({ - scope: scope, - model: fld, - element: field_id, - lineNumbers: true, - mode: scope[pfld], - onReady: onReady, - onChange: onChange - }); - } - // Hide the textarea and show a CodeMirror editor - createField(onChange, onReady, fld); - - - // Toggle displayed variable string between JSON and YAML - scope.parseTypeChange = function(model, fld) { - var json_obj; - if (scope[model] === 'json') { - // converting yaml to json - try { - removeField(fld); - - json_obj = jsyaml.load(scope[fld]); - - if ($.isEmptyObject(json_obj)) { - if (Array.isArray(json_obj)) { - scope[fld] = "[]"; - } else { - scope[fld] = "{}"; - } - } else { - scope[fld] = JSON.stringify(json_obj, null, " "); - } - createField(onReady, onChange, fld); - } - catch (e) { - Alert('Parse Error', 'Failed to parse valid YAML. ' + e.message); - setTimeout( function() { scope.$apply( function() { scope[model] = 'yaml'; createField(onReady, onChange, fld); }); }, 500); - } - } - else { - // convert json to yaml - try { - removeField(fld); - let jsonString = scope[fld]; - if (jsonString.trim() === '') { - jsonString = '{}'; - } - - json_obj = JSON.parse(jsonString); - if ($.isEmptyObject(json_obj)) { - scope[fld] = '---'; - } - else { - scope[fld] = jsyaml.safeDump(json_obj); - } - createField(onReady, onChange, fld); - } - catch (e) { - Alert('Parse Error', 'Failed to parse valid JSON. ' + e.message); - setTimeout( function() { scope.$apply( function() { scope[model] = 'json'; createField(onReady, onChange, fld); }); }, 500 ); - } - } - }; - }; - } - -ParseTypeChange.$inject = - [ 'Alert', - 'AngularCodeMirror' - ]; diff --git a/awx/ui/client/src/shared/prompt-dialog.js b/awx/ui/client/src/shared/prompt-dialog.js deleted file mode 100644 index cfcc9e0e2eb8..000000000000 --- a/awx/ui/client/src/shared/prompt-dialog.js +++ /dev/null @@ -1,133 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name shared.function:prompt-dialog - * @description - * PromptDialog - * Prompt the user with a Yes/No dialog to confirm an action such - * as Delete. Assumes a hidden dialog already exists in $scope. - * See example at bottom. If user responds with Yes, execute action - * parameter. - * - * params: { hdr: 'header msg', - * body: 'body text/html', - * class: 'btn-class for Yes button', --defaults to btn-danger - * action: function() {} --action to take, if use clicks Yes - * } - */ - - -/** -* @ngdoc method -* @name shared.function:prompt-dialog#PromptDialog -* @methodOf shared.function:prompt-dialog -* @description discuss difference b/t this and other modals -*/ - -export default -angular.module('PromptDialog', ['Utilities']) - .factory('Prompt', [ 'AppStrings', - function (strings) { - return function (params) { - - var dialog = angular.element(document.getElementById('prompt-modal')), - scope = dialog.scope(), cls, local_backdrop; - - scope.promptHeader = params.hdr; - scope.promptResourceName = params.resourceName; - scope.promptBody = params.body; - scope.promptAction = params.action; - scope.promptActionText = (params.actionText === null || params.actionText === undefined || params.actionText === '') ? strings.get('YES') : params.actionText; - scope.cancelActionText = (params.cancelText === null || params.cancelText === undefined || params.cancelText === '') ? strings.get('CANCEL') : params.cancelText; - scope.hideActionButton = params.hideActionButton ? true : false; - - local_backdrop = (params.backdrop === undefined) ? "static" : params.backdrop; - - cls = (params['class'] === null || params['class'] === undefined) ? 'Modal-errorButton' : params['class']; - - $('#prompt_action_btn').removeClass('Modal-errorButton Modal-primaryButton').addClass(cls); - - // bootstrap modal's have an open defect with disallowing tab index's of the background of the modal - // This will keep the tab indexing on the modal's focus. This is to fix an issue with tabbing working when - // the user is attempting to delete something. Might need to be checked for other occurances of the bootstrap - // modal other than deleting - function disableTabModalShown() { - - $('.modal').on('shown.bs.modal', function() { - - var modal = $(this), - focusableChildren = modal.find('a[href], a[data-dismiss], area[href], input, select, textarea, button, iframe, object, embed, *[tabindex], *[contenteditable]'), - numElements = focusableChildren.length, - currentIndex = 0, - focus, - focusPrevious, - focusNext; - - $(document.activeElement).blur(); - - focus = function() { - var focusableElement = focusableChildren[currentIndex]; - if (focusableElement) { - focusableElement.focus(); - } - }; - - focusPrevious = function () { - currentIndex--; - if (currentIndex < 0) { - currentIndex = numElements - 1; - } - - focus(); - - return false; - }; - - focusNext = function () { - currentIndex++; - if (currentIndex >= numElements) { - currentIndex = 0; - } - - focus(); - - return false; - }; - - $(document).on('keydown', function (e) { - - if (e.keyCode === 9 && e.shiftKey) { - e.preventDefault(); - focusPrevious(); - } - else if (e.keyCode === 9) { - e.preventDefault(); - focusNext(); - } - }); - - $(this).focus(); - }); - - $('.modal').on('hidden.bs.modal', function() { - $(document).unbind('keydown'); - }); - } - - - $('#prompt-modal').off('hidden.bs.modal'); - $('#prompt-modal').modal({ - backdrop: 'static', - keyboard: true, - show: true - }); - disableTabModalShown(); - - }; - } - ]); diff --git a/awx/ui/client/src/shared/prompt/prompt.less b/awx/ui/client/src/shared/prompt/prompt.less deleted file mode 100644 index b2ad0108031a..000000000000 --- a/awx/ui/client/src/shared/prompt/prompt.less +++ /dev/null @@ -1,29 +0,0 @@ -.Prompt-bodyQuery { - margin-bottom: 20px; - color: @default-interface-txt; - word-break: break-word; -} - -.Prompt-bodyTarget { - color: @default-data-txt; - word-break: break-word; -} - -.Prompt-bodyNote { - margin: 20px 0; - color: @default-interface-txt; -} - -.Prompt-bodyNote--emphasis { - text-transform: uppercase; - color: @default-err; -} - -.Prompt-emphasis { - font-weight: bold; - text-transform: uppercase; -} - -.Prompt-warningResourceTitle { - margin-right: 10px; -} diff --git a/awx/ui/client/src/shared/rbacUiControl.js b/awx/ui/client/src/shared/rbacUiControl.js deleted file mode 100644 index b65c6c57bdb9..000000000000 --- a/awx/ui/client/src/shared/rbacUiControl.js +++ /dev/null @@ -1,32 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default - angular.module('rbacUiControl', []) - .service('rbacUiControlService', ['$q', 'GetBasePath', 'Rest', 'Wait', function($q, GetBasePath, Rest, Wait){ - this.canAdd = function(apiPath) { - var canAddVal = $q.defer(); - - if (/api\/v[0-9]+\//.test(apiPath)) { - Rest.setUrl(apiPath); - } else { - Rest.setUrl(GetBasePath(apiPath)); - } - - Wait("start"); - Rest.options() - .then(({data}) => { - if (data.actions.POST) { - canAddVal.resolve({canAdd: true, options: data}); - } else { - canAddVal.resolve({canAdd: false}); - } - Wait("stop"); - }); - - return canAddVal.promise; - }; - }]); diff --git a/awx/ui/client/src/shared/smart-search/django-search-model.class.js b/awx/ui/client/src/shared/smart-search/django-search-model.class.js deleted file mode 100644 index 3809e47a316a..000000000000 --- a/awx/ui/client/src/shared/smart-search/django-search-model.class.js +++ /dev/null @@ -1,50 +0,0 @@ -export default -class DjangoSearchModel { - /* - @property name - supplied model name - @property base { - field: { - type: 'string' // string, bool, field, choice, datetime, - label: 'Label', // Capitalized - help_text: 'Some helpful descriptive text' - } - } - @@property related ['field' ...] - */ - constructor(name, baseFields, relatedSearchFields) { - function trimRelated(relatedSearchField){ - return relatedSearchField.replace(/\__search$/, ""); - } - this.name = name; - this.searchExamples = []; - this.related = _.uniq(_.map(relatedSearchFields, trimRelated)); - // Remove "object" type fields from this list - for (var key in baseFields) { - if (baseFields.hasOwnProperty(key)) { - if (baseFields[key].type === 'object'){ - delete baseFields[key]; - } - } - } - delete baseFields.url; - this.base = baseFields; - if(baseFields.id) { - this.searchExamples.push("id:>10"); - } - // Loop across the base fields and try find one of type = string and one of type = datetime - let stringFound = false, - dateTimeFound = false; - - _.forEach(baseFields, (value, key) => { - if(!stringFound && value.type === 'string') { - this.searchExamples.push(key + ":foobar"); - stringFound = true; - } - if(!dateTimeFound && value.type === 'datetime') { - this.searchExamples.push(key + ":>=2000-01-01T00:00:00Z"); - this.searchExamples.push(key + ":<2000-01-01"); - dateTimeFound = true; - } - }); - } -} diff --git a/awx/ui/client/src/shared/smart-search/main.js b/awx/ui/client/src/shared/smart-search/main.js deleted file mode 100644 index e7aaf825a72c..000000000000 --- a/awx/ui/client/src/shared/smart-search/main.js +++ /dev/null @@ -1,13 +0,0 @@ -import directive from './smart-search.directive'; -import controller from './smart-search.controller'; -import service from './queryset.service'; -import DjangoSearchModel from './django-search-model.class'; -import smartSearchService from './smart-search.service'; - -export default -angular.module('SmartSearchModule', []) - .directive('smartSearch', directive) - .controller('SmartSearchController', controller) - .service('QuerySet', service) - .service('SmartSearchService', smartSearchService) - .constant('DjangoSearchModel', DjangoSearchModel); diff --git a/awx/ui/client/src/shared/smart-search/queryset.service.js b/awx/ui/client/src/shared/smart-search/queryset.service.js deleted file mode 100644 index 216045eee392..000000000000 --- a/awx/ui/client/src/shared/smart-search/queryset.service.js +++ /dev/null @@ -1,524 +0,0 @@ -function searchWithoutKey (term, singleSearchParam = null) { - if (singleSearchParam) { - return { [singleSearchParam]: `search=${encodeURIComponent(term)}` }; - } - return { search: encodeURIComponent(term) }; -} - -function QuerysetService ($q, Rest, ProcessErrors, $rootScope, Wait, DjangoSearchModel, SmartSearchService) { - return { - // kick off building a model for a specific endpoint - // this is usually a list's basePath - // unified_jobs is the exception, where we need to fetch many subclass OPTIONS and summary_fields - initFieldset(path, name) { - let defer = $q.defer(); - defer.resolve(this.getCommonModelOptions(path, name)); - return defer.promise; - }, - getCommonModelOptions(path, name) { - let base, - defer = $q.defer(); - - this.url = path; - this.options(path) - .then((res) => { - base = res.data.actions.GET; - let relatedSearchFields = res.data.related_search_fields; - defer.resolve({ - models: { - [name]: new DjangoSearchModel(name, base, relatedSearchFields) - }, - options: res - }); - }); - return defer.promise; - }, - replaceDefaultFlags (value) { - if (value) { - value = value.toString().replace(/__icontains_DEFAULT/g, "__icontains"); - value = value.toString().replace(/__search_DEFAULT/g, "__search"); - } - - return value; - }, - replaceEncodedTokens(value) { - return decodeURIComponent(value).replace(/"|'/g, ""); - }, - encodeTerms(value, key, singleSearchParam){ - key = this.replaceDefaultFlags(key); - value = this.replaceDefaultFlags(value); - var that = this; - if (Array.isArray(value)){ - value = _.uniq(_.flattenDeep(value)); - let concated = ''; - angular.forEach(value, function(item){ - if(item && typeof item === 'string' && !singleSearchParam) { - item = that.replaceEncodedTokens(item); - } - concated += `${key}=${item}&`; - }); - - return concated; - } - else { - if(value && typeof value === 'string' && !singleSearchParam) { - value = this.replaceEncodedTokens(value); - } - - return `${key}=${value}&`; - } - }, - // encodes ui-router params from {operand__key__comparator: value} pairs to API-consumable URL - encodeQueryset(params, singleSearchParam) { - let queryset; - queryset = _.reduce(params, (result, value, key) => { - return result + this.encodeTerms(value, key, singleSearchParam); - }, ''); - queryset = queryset.substring(0, queryset.length - 1); - return angular.isObject(params) ? `?${queryset}` : ''; - - }, - // like encodeQueryset, but return an actual unstringified API-consumable http param object - encodeQuerysetObject(params) { - return _.reduce(params, (obj, value, key) => { - const encodedKey = this.replaceDefaultFlags(key); - const values = Array.isArray(value) ? value : [value]; - - obj[encodedKey] = values - .map(value => this.replaceDefaultFlags(value)) - .map(value => this.replaceEncodedTokens(value)) - .join(','); - - return obj; - }, {}); - }, - // encodes a ui smart-search param to a django-friendly param - // operand:key:comparator:value => {operand__key__comparator: value} - encodeParam({ term, relatedSearchTerm, searchTerm, singleSearchParam }){ - // Assumption here is that we have a key and a value so the length - // of the paramParts array will be 2. [0] is the key and [1] the value - let paramParts = SmartSearchService.splitTermIntoParts(term); - let keySplit = paramParts[0].split('.'); - let exclude = false; - let lessThanGreaterThan = paramParts[1].match(/^(>|<).*$/) ? true : false; - if(keySplit[0].match(/^-/g)) { - exclude = true; - keySplit[0] = keySplit[0].replace(/^-/, ''); - } - let paramString = exclude ? "not__" : ""; - let valueString = paramParts[1]; - - if(keySplit.length === 1) { - if(searchTerm && !lessThanGreaterThan) { - if(singleSearchParam) { - paramString += keySplit[0] + '__icontains'; - } - else { - paramString += keySplit[0] + '__icontains_DEFAULT'; - } - } - else if(relatedSearchTerm) { - if(singleSearchParam) { - paramString += keySplit[0]; - } - else { - paramString += keySplit[0] + '__search_DEFAULT'; - } - } - else { - paramString += keySplit[0]; - } - } - else { - paramString += keySplit.join('__'); - } - - if(lessThanGreaterThan) { - if(paramParts[1].match(/^>=.*$/)) { - paramString += '__gte'; - valueString = valueString.replace(/^(>=)/,""); - } - else if(paramParts[1].match(/^<=.*$/)) { - paramString += '__lte'; - valueString = valueString.replace(/^(<=)/,""); - } - else if(paramParts[1].match(/^<.*$/)) { - paramString += '__lt'; - valueString = valueString.replace(/^(<)/,""); - } - else if(paramParts[1].match(/^>.*$/)) { - paramString += '__gt'; - valueString = valueString.replace(/^(>)/,""); - } - } - - if(singleSearchParam) { - return {[singleSearchParam]: paramString + "=" + valueString}; - } - else { - return {[paramString] : encodeURIComponent(valueString)}; - } - }, - // decodes a django queryset param into a ui smart-search tag or set of tags - decodeParam(value, key){ - - let decodeParamString = function(searchString) { - if(key === 'search') { - // Don't include 'search:' in the search tag - return decodeURIComponent(`${searchString}`); - } - else { - key = key.toString().replace(/__icontains_DEFAULT/g, ""); - key = key.toString().replace(/__search_DEFAULT/g, ""); - let split = key.split('__'); - let decodedParam = searchString; - let exclude = false; - if(key.startsWith('not__')) { - exclude = true; - split = split.splice(1, split.length); - } - if(key.endsWith('__gt')) { - decodedParam = '>' + decodedParam; - split = split.splice(0, split.length-1); - } - else if(key.endsWith('__lt')) { - decodedParam = '<' + decodedParam; - split = split.splice(0, split.length-1); - } - else if(key.endsWith('__gte')) { - decodedParam = '>=' + decodedParam; - split = split.splice(0, split.length-1); - } - else if(key.endsWith('__lte')) { - decodedParam = '<=' + decodedParam; - split = split.splice(0, split.length-1); - } - - let uriDecodedParam = decodeURIComponent(decodedParam); - - return exclude ? `-${split.join('.')}:${uriDecodedParam}` : `${split.join('.')}:${uriDecodedParam}`; - } - }; - - if (Array.isArray(value)){ - value = _.uniq(_.flattenDeep(value)); - return _.map(value, (item) => { - return decodeParamString(item); - }); - } - else { - return decodeParamString(value); - } - }, - // encodes a django queryset for ui-router's URLMatcherFactory - // {operand__key__comparator: value, } => 'operand:key:comparator:value;...' - // value.isArray expands to: - // {operand__key__comparator: [value1, value2], } => 'operand:key:comparator:value1;operand:key:comparator:value1...' - encodeArr(params) { - let url; - url = _.reduce(params, (result, value, key) => { - return result.concat(encodeUrlString(value, key)); - }, []); - - return url.join(';'); - - // {key:'value'} => 'key:value' - // {key: [value1, value2, ...]} => ['key:value1', 'key:value2'] - function encodeUrlString(value, key){ - if (Array.isArray(value)){ - value = _.uniq(_.flattenDeep(value)); - return _.map(value, (item) => { - return `${key}:${item}`; - }); - } - else { - return `${key}:${value}`; - } - } - }, - // decodes a django queryset for ui-router's URLMatcherFactory - // 'operand:key:comparator:value,...' => {operand__key__comparator: value, } - decodeArr(arr) { - let params = {}; - - if (!arr) { - return params; - } - - _.forEach(arr.split(';'), (item) => { - let key = item.split(':')[0], - value = item.split(':')[1]; - if(!params[key]){ - params[key] = value; - } - else if (Array.isArray(params[key])){ - params[key] = _.uniq(_.flattenDeep(params[key])); - params[key].push(value); - } - else { - params[key] = [params[key], value]; - } - }); - return params; - }, - // REST utilities - options(endpoint) { - Rest.setUrl(endpoint); - return Rest.options(endpoint); - }, - search(endpoint, params, singleSearchParam) { - Wait('start'); - this.url = `${endpoint}${this.encodeQueryset(params, singleSearchParam)}`; - Rest.setUrl(this.url); - - return Rest.get() - .then(function(response) { - Wait('stop'); - - if (response - .headers('X-UI-Max-Events') !== null) { - response.data.maxEvents = response. - headers('X-UI-Max-Events'); - } - - return response; - }) - .catch(function(response) { - Wait('stop'); - - this.error(response.data, response.status); - - throw response; - }.bind(this)); - }, - error(data, status) { - if(data && data.detail){ - let error = typeof data.detail === "string" ? data.detail : JSON.parse(data.detail); - - if(_.isArray(error)){ - data.detail = error[0]; - } - } - ProcessErrors($rootScope, data, status, null, { - hdr: 'Error!', - msg: `Invalid search term entered. GET returned: ${status}` - }); - }, - // Removes state definition defaults and pagination terms - stripDefaultParams(params, defaultParams) { - if (!params) { - return []; - } - if(defaultParams) { - let stripped =_.pickBy(params, (value, key) => { - // setting the default value of a term to null in a state definition is a very explicit way to ensure it will NEVER generate a search tag, even with a non-default value - return defaultParams[key] !== value && key !== 'order_by' && key !== 'page' && key !== 'page_size' && defaultParams[key] !== null; - }); - let strippedCopy = _.cloneDeep(stripped); - if(_.keys(_.pickBy(defaultParams, _.keys(strippedCopy))).length > 0){ - for (var key in strippedCopy) { - if (strippedCopy.hasOwnProperty(key)) { - let value = strippedCopy[key]; - if(_.isArray(value)){ - let index = _.indexOf(value, defaultParams[key]); - value = value.splice(index, 1)[0]; - } - } - } - stripped = strippedCopy; - } - return _(strippedCopy).map(this.decodeParam).flatten().value(); - } - else { - return _(params).map(this.decodeParam).flatten().value(); - } - }, - mergeQueryset (queryset, additional, singleSearchParam) { - const space = '%20and%20'; - - const merged = _.mergeWith({}, queryset, additional, (objectValue, sourceValue, key, object) => { - if (!(object[key] && object[key] !== sourceValue)) { - // // https://lodash.com/docs/3.10.1#each - // If this returns undefined merging is handled by default _.merge algorithm - return undefined; - } - - if (_.isArray(object[key])) { - object[key].push(sourceValue); - return object[key]; - } - - if (singleSearchParam) { - if (!object[key]) { - return sourceValue; - } - - const singleSearchParamKeys = object[key].split(space); - - if (_.includes(singleSearchParamKeys, sourceValue)) { - return object[key]; - } - - return `${object[key]}${space}${sourceValue}`; - } - - // Start the array of keys - return [object[key], sourceValue]; - }); - - return merged; - }, - getSearchInputQueryset (searchInput, isFilterableBaseField = null, isRelatedField = null, isAnsibleFactField = null, singleSearchParam = null) { - // XXX Should find a better approach than passing in the two 'is...Field' callbacks XXX - const encodedAnd = '%20and%20'; - const encodedOr = '%20or%20'; - let params = {}; - - // Remove leading/trailing whitespace if there is any - const terms = (searchInput) ? searchInput.trim() : ''; - - if (!(terms && terms !== '')) { - return; - } - - let splitTerms; - - if (singleSearchParam === 'host_filter') { - splitTerms = SmartSearchService.splitFilterIntoTerms(terms); - } else { - splitTerms = SmartSearchService.splitSearchIntoTerms(terms); - } - - const combineSameSearches = (a, b) => { - if (!a) { - return undefined; - } - - if (_.isArray(a)) { - return a.concat(b); - } - - if (singleSearchParam) { - if (b === 'or') { - return `${a}${encodedOr}`; - } else if (a.match(/%20or%20$/g)) { - return `${a}${b}`; - } else { - return `${a}${encodedAnd}${b}`; - } - } - - return [a, b]; - }; - - _.each(splitTerms, term => { - const termParts = SmartSearchService.splitTermIntoParts(term); - let termParams; - - if (termParts.length === 1) { - if (singleSearchParam && termParts[0].toLowerCase() === "or") { - termParams = { [singleSearchParam]: "or" }; - } else { - termParams = searchWithoutKey(term, singleSearchParam); - } - } else if ((isAnsibleFactField && isAnsibleFactField(termParts)) || (isFilterableBaseField && isFilterableBaseField(termParts))) { - termParams = this.encodeParam({ term, singleSearchParam, searchTerm: true }); - } else if (isRelatedField && isRelatedField(termParts)) { - termParams = this.encodeParam({ term, singleSearchParam, relatedSearchTerm: true }); - } else { - termParams = searchWithoutKey(term, singleSearchParam); - } - - params = _.mergeWith(params, termParams, combineSameSearches); - }); - - return params; - }, - removeTermsFromQueryset(queryset, term, isFilterableBaseField, isRelatedField = null, isAnsibleFactField = null, singleSearchParam = null) { - const modifiedQueryset = _.cloneDeep(queryset); - - const removeSingleTermFromQueryset = (value, key) => { - const space = '%20and%20'; - - if (Array.isArray(modifiedQueryset[key])) { - modifiedQueryset[key] = modifiedQueryset[key].filter(item => item !== value); - if (modifiedQueryset[key].length < 1) { - delete modifiedQueryset[key]; - } - } else if (singleSearchParam && _.get(modifiedQueryset, singleSearchParam, []).includes(space)) { - const searchParamParts = modifiedQueryset[singleSearchParam].split(space); - // The value side of each paramPart might have been encoded in - // SmartSearch.splitFilterIntoTerms - _.each(searchParamParts, (paramPart, paramPartIndex) => { - searchParamParts[paramPartIndex] = decodeURIComponent(paramPart); - }); - - const paramPartIndex = searchParamParts.indexOf(decodeURIComponent(value)); - - if (paramPartIndex !== -1) { - searchParamParts.splice(paramPartIndex, 1); - } - - modifiedQueryset[singleSearchParam] = searchParamParts.join(space); - - } else { - delete modifiedQueryset[key]; - } - }; - - const termParts = SmartSearchService.splitTermIntoParts(term); - - let removed; - - if (termParts.length === 1) { - removed = searchWithoutKey(term, singleSearchParam); - } else if ((isAnsibleFactField && isAnsibleFactField(termParts)) || (isFilterableBaseField && isFilterableBaseField(termParts))) { - removed = this.encodeParam({ term, singleSearchParam, searchTerm: true }); - } else if (isRelatedField && isRelatedField(termParts)) { - removed = this.encodeParam({ term, singleSearchParam, relatedSearchTerm: true }); - } else { - removed = searchWithoutKey(term, singleSearchParam); - } - - if (!removed) { - removed = searchWithoutKey(termParts[termParts.length - 1], singleSearchParam); - } - - _.each(removed, removeSingleTermFromQueryset); - - return modifiedQueryset; - }, - createSearchTagsFromQueryset(queryset, defaultParams = null, singleSearchParam = null) { - const space = '%20and%20'; - const modifiedQueryset = angular.copy(queryset); - - let searchTags = []; - - if (singleSearchParam && modifiedQueryset[singleSearchParam]) { - const searchParam = modifiedQueryset[singleSearchParam].split(space); - delete modifiedQueryset[singleSearchParam]; - - $.each(searchParam, (index, param) => { - const paramParts = decodeURIComponent(param).split(/=(.+)/); - const reconstructedSearchString = this.decodeParam(paramParts[1], paramParts[0]); - - searchTags.push(reconstructedSearchString); - }); - } - - return searchTags.concat(this.stripDefaultParams(modifiedQueryset, defaultParams)); - } - }; -} - -QuerysetService.$inject = [ - '$q', - 'Rest', - 'ProcessErrors', - '$rootScope', - 'Wait', - 'DjangoSearchModel', - 'SmartSearchService', -]; - -export default QuerysetService; diff --git a/awx/ui/client/src/shared/smart-search/smart-search.block.less b/awx/ui/client/src/shared/smart-search/smart-search.block.less deleted file mode 100644 index c5e0f2b3eae0..000000000000 --- a/awx/ui/client/src/shared/smart-search/smart-search.block.less +++ /dev/null @@ -1,256 +0,0 @@ -@import "../branding/colors.default.less"; -.SmartSearch { - padding-left: 15px; - padding-right: 15px; - display: block; -} - -.SmartSearch-form { - width: 100%; -} - -.SmartSearch-bar { - display: flex; - padding: 0; - font-size: 12px; - align-items: stretch; - line-height: 20px; - width: 50%; -} - -.SmartSearch-bar--fullWidth { - width: 100%; -} - -.SmartSearch-tags{ - padding-left: 0px; -} - -.SmartSearch-bar i { - font-size: 16px; - color: @default-icon; -} - -.SmartSearch-searchTermContainer { - flex: initial; - flex-grow: 1; - border: 1px solid @b7grey; - border-radius: 4px; - display: flex; - background-color: @default-bg; - position: relative; - height: 34px; -} - -.SmartSearch-searchTermContainer.is-open { - border-bottom-right-radius: 0; -} - -.SmartSearch-input { - flex: 1 0 auto; - margin: 0 10px; - border: none; - font-size: 14px; - height: 100%; - width: 100%; -} - -.SmartSearch-input:focus, -.SmartSearch-input:active { - outline: 0; -} - -.SmartSearch-searchTermContainer input:placeholder-shown { - color: @default-icon !important; - text-transform: uppercase; -} - -.SmartSearch-searchButton { - flex: initial; - margin-left: auto; - padding: 8px 10px; - border-left: 1px solid @b7grey; - background-color: @default-bg; - cursor: pointer; - border-top-right-radius: 5px; - border-bottom-right-radius: 5px; - z-index: 1; -} - -.SmartSearch-searchButton:not(.SmartSearch-searchButton--disabled):hover { - background-color: @default-tertiary-bg; -} - -.SmartSearch-searchButton--disabled { - cursor: not-allowed; - opacity: 0.65; -} - -.SmartSearch-flexContainer { - display: flex; - width: 100%; - flex-wrap: wrap; -} - -.SmartSearch-tagContainer { - display: flex; - max-width: 100%; - margin-top: 10px; -} - -.SmartSearch-tag { - border-radius: 5px; - padding: 2px 10px; - margin: 0px; - font-size: 12px; - color: @default-interface-txt; - background-color: @default-bg; - margin-right: 10px; - max-width: 100%; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - height: 20px; -} - -.SmartSearch-tag--deletable { - margin-right: 0px; - border-top-left-radius: 0px; - border-bottom-left-radius: 0px; - border-right: 0; - max-width: ~"calc(100% - 23px)"; - background-color: @default-link; - color: @default-bg; - margin-right: 10px; -} - -.SmartSearch-deleteContainer { - background-color: @default-link!important; - color: white; - background-color: @default-bg; - border-top-left-radius: 5px; - border-bottom-left-radius: 5px; - padding: 0 5px; - margin: 0px; - align-items: center; - display: flex; - cursor: pointer; - height: 20px; -} - -.SmartSearch-tagDelete { - font-size: 13px; -} - -.SmartSearch-name { - flex: initial; - max-width: 100%; -} - -.SmartSearch-tag--deletable > .SmartSearch-name { - max-width: ~"calc(100% - 23px)"; -} - -.SmartSearch-deleteContainer:hover, -{ - border-color: @default-err; - background-color: @default-err!important; -} - -.SmartSearch-deleteContainer:hover > .SmartSearch-tagDelete { - color: @default-bg; -} -.SmartSearch-clearAll-container{ - .at-mixin-VerticallyCenter(); -} -.SmartSearch-clearAll{ - font-size: 10px; - padding-top: 14px; -} -.SmartSearch-keyToggle { - margin-right: auto; - margin-left: 20px; - text-transform: uppercase; - background-color: @default-bg; - border-radius: 5px; - color: @default-interface-txt; - border: 1px solid @b7grey; - cursor: pointer; - min-width: 70px; - height: 34px; - line-height: 20px; -} - -.SmartSearch-keyToggle:hover { - background-color: @default-tertiary-bg; -} - -.SmartSearch-keyToggle.is-active { - background-color: @default-link; - border-color: @default-link; - color: @default-bg; - &:hover{ - background-color: @default-link-hov; - } -} - -.SmartSearch-keyPane { - max-height: 215px; - overflow: auto; - margin: 10px 0 0 0; - font-size: 12px; - width: 100%; - padding: 20px; - border-radius: 4px; - border: 1px solid @d7grey; - background-color: @login-notice-bg; - color: @login-notice-text; - position: relative; -} - -.SmartSearch-keyRow { - margin-bottom: 15px; -} - -.SmartSearch-keyRow:last-child { - margin-bottom: 0; -} - -.SmartSearch-keyName { - flex: 1 0 auto; - text-transform: uppercase; - font-weight: bold; - padding-bottom: 3px; -} - -.SmartSearch-keyComparators { - flex: 1 0 auto; -} - -.SmartSearch-keyPane--exit { - background-color: @login-notice-bg; -} - -.SmartSearch-examples { - display: flex; - flex-wrap: wrap; -} - -.SmartSearch-examples--title { - margin-right: 5px; -} - -.SmartSearch-examples--search { - color: @default-err; - background-color: @default-bg; - border: 1px solid @default-border; - border-radius: 5px; - padding: 0px 5px; - margin-right: 5px; -} - -@media (max-width: 700px) { - .SmartSearch-bar { - width: 100%; - } -} diff --git a/awx/ui/client/src/shared/smart-search/smart-search.controller.js b/awx/ui/client/src/shared/smart-search/smart-search.controller.js deleted file mode 100644 index 80aabc22e930..000000000000 --- a/awx/ui/client/src/shared/smart-search/smart-search.controller.js +++ /dev/null @@ -1,297 +0,0 @@ -function SmartSearchController ( - $scope, - $rootScope, - $state, - $stateParams, - $transitions, - configService, - GetBasePath, - i18n, - qs -) { - const searchKey = `${$scope.iterator}_search`; - const optionsKey = `${$scope.list.iterator}_options`; - - let path; - let defaults; - let queryset; - let transitionSuccessListener; - - const compareParams = (a, b) => { - for (let key in a) { - if (!(key in b) || a[key].toString() !== b[key].toString()) { - return false; - } - } - for (let key in b) { - if (!(key in a)) { - return false; - } - } - return true; - }; - - const generateSearchTags = () => { - const { singleSearchParam } = $scope; - $scope.searchTags = qs.createSearchTagsFromQueryset(queryset, defaults, singleSearchParam); - }; - - const listenForTransitionSuccess = () => { - transitionSuccessListener = $transitions.onSuccess({}, trans => { - // State has changed - check to see if this is a param change - if (trans.from().name === trans.to().name) { - if (!compareParams(trans.params('from')[searchKey], trans.params('to')[searchKey])) { - // Params are not the same - we need to update the search. This should only - // happen when the user hits the forward/back browser navigation buttons. - queryset = trans.params('to')[searchKey]; - qs.search(path, queryset).then((res) => { - $scope.dataset = res.data; - $scope.collection = res.data.results; - $scope.$emit('updateDataset', res.data, queryset); - }); - - $scope.searchTerm = null; - generateSearchTags(); - } - } - }); - }; - - const isAnsibleFactField = (termParts) => { - const rootField = termParts[0].split('.')[0].replace(/^-/, ''); - return rootField === 'ansible_facts'; - }; - - const revertSearch = (queryToBeRestored) => { - queryset = queryToBeRestored; - // https://ui-router.github.io/docs/latest/interfaces/params.paramdeclaration.html#dynamic - // This transition will not reload controllers/resolves/views - // but will register new $stateParams[$scope.iterator + '_search'] terms - if (!$scope.querySet) { - transitionSuccessListener(); - $state.go('.', { [searchKey]: queryset }) - .then(() => listenForTransitionSuccess()); - } - qs.search(path, queryset).then((res) => { - if ($scope.querySet) { - $scope.querySet = queryset; - } - $scope.dataset = res.data; - $scope.collection = res.data.results; - $scope.$emit('updateDataset', res.data, queryset); - }); - - $scope.searchTerm = null; - - generateSearchTags(); - }; - - const isFilterableBaseField = (termParts) => { - const rootField = termParts[0].split('.')[0].replace(/^-/, ''); - const listName = $scope.list.name; - const baseFieldPath = `models.${listName}.base.${rootField}`; - const isBaseField = _.has($scope, `${baseFieldPath}`); - - const isFilterable = _.get($scope, `${baseFieldPath}.filterable`); - const isBaseModelRelatedSearchTermField = (_.get($scope, `${baseFieldPath}.type`) === 'field'); - - return isBaseField && !isBaseModelRelatedSearchTermField && isFilterable; - }; - - const isRelatedField = (termParts) => { - const rootField = termParts[0].split('.')[0].replace(/^-/, ''); - const listName = $scope.list.name; - const baseRelatedTypePath = `models.${listName}.base.${rootField}.type`; - const relatedTypePath = `models.${listName}.related`; - - const isRelatedSearchTermField = (_.includes(_.get($scope, relatedTypePath), rootField)); - const isBaseModelRelatedSearchTermField = (_.get($scope, baseRelatedTypePath) === 'field'); - - return (isRelatedSearchTermField || isBaseModelRelatedSearchTermField); - }; - - configService.getConfig() - .then(config => { - let version; - if ($rootScope.BRAND_NAME === 'Tower') { - try { - [version] = config.version.split('-'); - } catch (err) { - version = 'latest'; - } - } else { - version = 'latest'; - } - - $scope.documentationLink = `http://docs.ansible.com/ansible-tower/${version}/html/userguide/search_sort.html`; - $scope.searchPlaceholder = i18n._('Search'); - - if ($scope.defaultParams) { - defaults = $scope.defaultParams; - } else { - // steps through the current tree of $state configurations, grabs default search params - const stateConfig = _.find($state.$current.path, step => _.has(step, `params.${searchKey}`)); - defaults = stateConfig.params[searchKey].config.value; - } - - if ($scope.querySet) { - queryset = $scope.querySet; - } else { - queryset = $state.params[searchKey]; - } - - path = GetBasePath($scope.basePath) || $scope.basePath; - generateSearchTags(); - - qs.initFieldset(path, $scope.djangoModel) - .then((data) => { - $scope.models = data.models; - $scope.options = data.options.data; - $scope.keyFields = _.reduce(data.models[$scope.djangoModel].base, (result, value, key) => { - if (value.filterable) { - result.push(key); - } - return result; - }, []); - if ($scope.list) { - $scope.$emit(optionsKey, data.options); - } - }); - $scope.$on('$destroy', () => { - if (transitionSuccessListener) { - transitionSuccessListener(); - } - }); - $scope.$watch('disableSearch', disableSearch => { - if (disableSearch) { - $scope.searchPlaceholder = i18n._('Cannot search running job'); - } else { - $scope.searchPlaceholder = i18n._('Search'); - } - }); - listenForTransitionSuccess(); - }); - - - $scope.toggleKeyPane = () => { - $scope.showKeyPane = !$scope.showKeyPane; - }; - - $scope.addTerms = terms => { - if (terms && terms !== "") { - const { singleSearchParam } = $scope; - const unmodifiedQueryset = _.clone(queryset); - - const searchInputQueryset = qs.getSearchInputQueryset(terms, isFilterableBaseField, isRelatedField, isAnsibleFactField, singleSearchParam); - queryset = qs.mergeQueryset(queryset, searchInputQueryset, singleSearchParam); - - // Go back to the first page after a new search - delete queryset.page; - - // https://ui-router.github.io/docs/latest/interfaces/params.paramdeclaration.html#dynamic - // This transition will not reload controllers/resolves/views but will register new - // $stateParams[searchKey] terms. - if (!$scope.querySet) { - transitionSuccessListener(); - $state.go('.', { [searchKey]: queryset }) - .then(() => { - // same as above in $scope.remove. For some reason deleting the page - // from the queryset works for all lists except lists in modals. - delete $stateParams[searchKey].page; - listenForTransitionSuccess(); - }); - } - - qs.search(path, queryset, singleSearchParam) - .then(({ data }) => { - if ($scope.querySet) { - $scope.querySet = queryset; - } - $scope.dataset = data; - $scope.collection = data.results; - $scope.$emit('updateDataset', data, queryset); - }) - .catch(() => revertSearch(unmodifiedQueryset)); - - $scope.searchTerm = null; - - generateSearchTags(); - } - }; - // remove tag, merge new queryset, $state.go - $scope.removeTerm = index => { - const { singleSearchParam } = $scope; - const [term] = $scope.searchTags.splice(index, 1); - - queryset = qs.removeTermsFromQueryset(queryset, term, isFilterableBaseField, isRelatedField, isAnsibleFactField, singleSearchParam); - - if (!$scope.querySet) { - transitionSuccessListener(); - $state.go('.', { [searchKey]: queryset }) - .then(() => { - // for some reason deleting a tag from a list in a modal does not - // remove the param from $stateParams. Here we'll manually check to make sure - // that that happened and remove it if it didn't. - const clearedParams = qs.removeTermsFromQueryset($stateParams[searchKey], term, isFilterableBaseField, isRelatedField, isAnsibleFactField, singleSearchParam); - $stateParams[searchKey] = clearedParams; - listenForTransitionSuccess(); - }); - } - - qs.search(path, queryset) - .then(({ data }) => { - if ($scope.querySet) { - $scope.querySet = queryset; - } - $scope.dataset = data; - $scope.collection = data.results; - $scope.$emit('updateDataset', data, queryset); - }); - - generateSearchTags(); - }; - - $scope.clearAllTerms = () => { - _.forOwn(defaults, (value, key) => { - // preserve the `credential_type` queryset param if it exists - if (key === 'credential_type') { - defaults[key] = queryset[key]; - } - }); - const cleared = _(defaults).omitBy(_.isNull).value(); - delete cleared.page; - queryset = cleared; - - if (!$scope.querySet) { - transitionSuccessListener(); - $state.go('.', { [searchKey]: queryset }) - .then(() => listenForTransitionSuccess()); - } - - qs.search(path, queryset) - .then(({ data }) => { - if ($scope.querySet) { - $scope.querySet = queryset; - } - $scope.dataset = data; - $scope.collection = data.results; - $scope.$emit('updateDataset', data, queryset); - }); - - $scope.searchTags = qs.stripDefaultParams(queryset, defaults); - }; -} - -SmartSearchController.$inject = [ - '$scope', - '$rootScope', - '$state', - '$stateParams', - '$transitions', - 'ConfigService', - 'GetBasePath', - 'i18n', - 'QuerySet', -]; - -export default SmartSearchController; diff --git a/awx/ui/client/src/shared/smart-search/smart-search.directive.js b/awx/ui/client/src/shared/smart-search/smart-search.directive.js deleted file mode 100644 index 6ae367a0c8cd..000000000000 --- a/awx/ui/client/src/shared/smart-search/smart-search.directive.js +++ /dev/null @@ -1,27 +0,0 @@ -export default ['templateUrl', - function(templateUrl) { - return { - restrict: 'E', - replace: false, - transclude: { - actions: '?div' // preferably would transclude an actions directive here - }, - scope: { - djangoModel: '@', - basePath: '@', - iterator: '@', - list: '=', - dataset: '=', - collection: '=', - searchTags: '=', - disableSearch: '=', - defaultParams: '=', - querySet: '=', - singleSearchParam: '@', - searchBarFullWidth: '=' - }, - controller: 'SmartSearchController', - templateUrl: templateUrl('shared/smart-search/smart-search') - }; - } -]; diff --git a/awx/ui/client/src/shared/smart-search/smart-search.partial.html b/awx/ui/client/src/shared/smart-search/smart-search.partial.html deleted file mode 100644 index 40c70b2de76b..000000000000 --- a/awx/ui/client/src/shared/smart-search/smart-search.partial.html +++ /dev/null @@ -1,53 +0,0 @@ -
- -
-
- -
- -
-
- -
-
-
- Key -
-
- -
-
-
-
- -
-
- CLEAR ALL -
-
-
-
- -
-
-
-
- EXAMPLES: -
- -
-
-
- FIELDS: {{ field }}, -
-
- RELATED FIELDS: {{ relation }}, -
-
- {{ 'ADDITIONAL INFORMATION' | translate }}: - {{ 'For additional information on advanced search syntax please see the Ansible Tower' | translate }} - {{ 'documentation' | translate }}. -
-
-
diff --git a/awx/ui/client/src/shared/smart-search/smart-search.service.js b/awx/ui/client/src/shared/smart-search/smart-search.service.js deleted file mode 100644 index fc540c357310..000000000000 --- a/awx/ui/client/src/shared/smart-search/smart-search.service.js +++ /dev/null @@ -1,63 +0,0 @@ -export default [function() { - return { - /** - * For the Smart Host Filter, values with spaces are wrapped with double quotes on input. - * To avoid having these quoted values split up and treated as terms themselves, some - * work is done to encode quotes in quoted values and the spaces within those quoted - * values before calling to `splitSearchIntoTerms`. - */ - splitFilterIntoTerms (searchString) { - if (!searchString) { - return null; - } - - let groups = []; - let quoted; - - // This split _may_ split search terms down the middle - // ex) searchString=ansible_facts.some_other_thing:"foo foobar" ansible_facts.some_thing:"foobar" - // would result in 3 different substring's but only two search terms - // This logic handles that scenario with the `quoted` variable - searchString.split(' ').forEach(substring => { - if (/:"/g.test(substring)) { - if (/"$/.test(substring)) { - groups.push(this.encode(substring)); - } else { - quoted = substring; - } - } else if (quoted) { - quoted += ` ${substring}`; - - if (/"/g.test(substring)) { - groups.push(this.encode(quoted)); - quoted = undefined; - } - } else { - groups.push(substring); - } - }); - - return this.splitSearchIntoTerms(groups.join(' ')); - }, - encode (string) { - string = string.replace(/'/g, '%27'); - - return string.replace(/("| )/g, match => encodeURIComponent(match)); - }, - splitSearchIntoTerms(searchString) { - return searchString.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g); - }, - splitTermIntoParts(searchTerm) { - let breakOnColon = searchTerm.match(/(?:[^:"]+|"[^"]*")+/g); - - if(breakOnColon.length > 2) { - // concat all the strings after the first one together - let stringsToJoin = breakOnColon.slice(1,breakOnColon.length); - return [breakOnColon[0], stringsToJoin.join(':')]; - } - else { - return breakOnColon; - } - } - }; -}]; diff --git a/awx/ui/client/src/shared/socket/main.js b/awx/ui/client/src/shared/socket/main.js deleted file mode 100644 index 0b512aa8c178..000000000000 --- a/awx/ui/client/src/shared/socket/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import socketService from './socket.service'; - -export default - angular.module('socket', []) - .service('SocketService', socketService); diff --git a/awx/ui/client/src/shared/socket/socket.service.js b/awx/ui/client/src/shared/socket/socket.service.js deleted file mode 100644 index 24c7ec0234c4..000000000000 --- a/awx/ui/client/src/shared/socket/socket.service.js +++ /dev/null @@ -1,286 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ -import ReconnectingWebSocket from 'reconnectingwebsocket'; -export default -['$rootScope', '$location', '$log','$state', '$q', 'i18n', 'GetBasePath', 'Rest', '$cookies', - function ($rootScope, $location, $log, $state, $q, i18n, GetBasePath, Rest, $cookies) { - var needsResubscribing = false, - socketPromise = $q.defer(), - needsRefreshAfterBlur; - return { - init: function() { - var self = this, - host = window.location.host, - protocol, - url; - - if($location.protocol() === 'http'){ - protocol = 'ws'; - } - if($location.protocol() === 'https'){ - protocol = 'wss'; - } - url = `${protocol}://${host}/websocket/`; - - // only toggle background tabbed sockets if the - // UI_LIVE_UPDATES_ENABLED flag is true in the settings file - if(window.liveUpdates){ - document.addEventListener('visibilitychange', function() { - $log.debug(document.visibilityState); - if(document.visibilityState === 'hidden'){ - window.liveUpdates = false; - } - else if(document.visibilityState === 'visible'){ - window.liveUpdates = true; - if(needsRefreshAfterBlur){ - $state.go('.', null, {reload: true}); - needsRefreshAfterBlur = false; - } - - } - }); - } - - - if (!$rootScope.sessionTimer || ($rootScope.sessionTimer && !$rootScope.sessionTimer.isExpired())) { - - $log.debug('Socket connecting to: ' + url); - self.socket = new ReconnectingWebSocket(url, null, { - timeoutInterval: 3000, - maxReconnectAttempts: 10 }); - - self.socket.onopen = function () { - $log.debug("Websocket connection opened. Socket readyState: " + self.socket.readyState); - socketPromise.resolve(); - self.checkStatus(); - if(needsResubscribing){ - self.subscribe(self.getLast()); - needsResubscribing = false; - } - - }; - - self.socket.onerror = function (error) { - self.checkStatus(); - $log.debug('Websocket Error Logged: ' + error); //log errors - }; - - self.socket.onconnecting = function () { - self.checkStatus(); - $log.debug('Websocket reconnecting'); - needsResubscribing = true; - }; - - self.socket.onclose = function () { - self.checkStatus(); - $log.debug(`Websocket disconnected`); - }; - - self.socket.onmessage = this.onMessage; - - return self.socket; - } - else { - // encountered expired token, redirect to login page - $rootScope.sessionTimer.expireSession('idle'); - $location.url('/login'); - } - }, - onMessage: function(e){ - // Function called when messages are received on by the UI from - // the API over the websocket. This will route each message to - // the appropriate controller for the current $state. - $log.debug('Received From Server: ' + e.data); - - var data = JSON.parse(e.data), str = ""; - - if (data.group_name === 'jobs' && - 'type' in data && - data.type === 'workflow_approval' - ) { - $rootScope.$broadcast('ws-approval'); - } - - if(!window.liveUpdates && data.group_name !== "control" && $state.current.name !== "output"){ - $log.debug('Message from server dropped: ' + e.data); - needsRefreshAfterBlur = true; - return; - } - - if(data.group_name==="jobs" && !('status' in data)){ - // we know that this must have been a - // summary complete message b/c status is missing. - // A an object w/ group_name === "jobs" AND a 'status' key - // means it was for the event: status_changed. - $log.debug('Job summary_complete ' + data.unified_job_id); - $rootScope.$broadcast('ws-jobs-summary', data); - return; - } - else if(data.group_name==="job_events"){ - // The naming scheme is "ws" then a - // dash (-) and the group_name, then the job ID - // ex: 'ws-jobs-' - str = `ws-${data.group_name}-${data.job}`; - } - else if(data.group_name==="project_update_events"){ - str = `ws-${data.group_name}-${data.project_update}`; - } - else if(data.group_name==="ad_hoc_command_events"){ - str = `ws-${data.group_name}-${data.ad_hoc_command}`; - } - else if(data.group_name==="system_job_events"){ - str = `ws-${data.group_name}-${data.system_job}`; - } - else if(data.group_name==="inventory_update_events"){ - str = `ws-${data.group_name}-${data.inventory_update}`; - } - else if(data.group_name === "control" && data.reason === "limit_reached"){ - // If we got a `limit_reached_` message, determine - // if the current session is still valid (it may have been - // invalidated) - // If so, log the user out and show a meaningful error - $log.debug(data.reason); - let url = GetBasePath('me'); - Rest.get(url) - .catch(function(resp) { - if (resp.status === 401) { - $rootScope.sessionTimer.expireSession('session_limit'); - $state.go('signOut'); - } - }); - } - else { - // The naming scheme is "ws" then a - // dash (-) and the group_name. - // ex: 'ws-jobs' - str = `ws-${data.group_name}`; - } - $rootScope.$broadcast(str, data); - }, - disconnect: function(){ - if(this.socket){ - this.socket.close(); - delete this.socket; - $log.debug("Socket deleted: "+this.socket); - } - }, - subscribe: function(state){ - // Subscribe is used to tell the API that the UI wants to - // listen for specific messages. A subscription object could - // look like {"groups":{"jobs": ["status_changed", "summary"]}. - // This is used by all socket-enabled $states - state.data.socket.groups.control = ['limit_reached_' + $rootScope.current_user.id]; - state.data.socket.xrftoken = $cookies.get('csrftoken'); - this.emit(JSON.stringify(state.data.socket)); - this.setLast(state); - }, - unsubscribe: function(state){ - // Unsubscribing tells the API that the user is no longer on - // on a socket-enabled page, and sends an empty groups object - // to the API: {"groups": {}}. - // This is used for all pages that are socket-disabled - state.data.socket.xrftoken = $cookies.get('csrftoken'); - if(this.requiresNewSubscribe(state)){ - this.emit(JSON.stringify(state.data.socket) || JSON.stringify({"groups": {}})); - } - this.setLast(state); - }, - setLast: function(state){ - this.last = state; - }, - getLast: function(){ - return this.last; - }, - requiresNewSubscribe(state){ - // This function is used for unsubscribing. If the last $state - // required an "unsubscribe", then we don't need to unsubscribe - // again, b/c the UI is already unsubscribed from all groups - if (this.getLast() !== undefined){ - if( _.isEmpty(state.data.socket.groups) && _.isEmpty(this.getLast().data.socket.groups)){ - return false; - } - else { - return true; - } - } - else { - return true; - } - }, - checkStatus: function() { - // Function for changing the socket indicator icon in the nav bar - var self = this; - if(self){ - if(self.socket){ - if (self.socket.readyState === 0 ) { - $rootScope.socketStatus = 'connecting'; - $rootScope.socketTip = i18n._(`Live events: attempting to connect to the ${$rootScope.BRAND_NAME} server.`); - } - else if (self.socket.readyState === 1){ - $rootScope.socketStatus = 'ok'; - $rootScope.socketTip = i18n._("Live events: connected. Pages containing job status information will automatically update in real-time."); - } - else if (self.socket.readyState === 2 || self.socket.readyState === 3 ){ - $rootScope.socketStatus = 'error'; - $rootScope.socketTip = i18n._(`Live events: error connecting to the ${$rootScope.BRAND_NAME} server.`); - } - return; - } - } - - }, - emit: function(data, callback) { - // Function used for sending objects to the API over the - // websocket. - var self = this; - socketPromise.promise.then(function(){ - if(self.socket.readyState === 0){ - $log.debug('Unable to send message, waiting 500ms to resend. Socket readyState: ' + self.socket.readyState); - setTimeout(function(){ - self.subscribe(self.getLast()); - }, 500); - } - else if(self.socket.readyState === 1){ - self.socket.send(data, function () { - var args = arguments; - self.scope.$apply(function () { - if (callback) { - callback.apply(self.socket, args); - } - }); - }); - $log.debug('Sent to Websocket Server: ' + data); - } - }); - }, - addStateResolve: function(state, id){ - // This function is used for add a state resolve to all states, - // socket-enabled AND socket-disabled, and whether the $state - // requires a subscribe or an unsubscribe - var self = this; - return socketPromise.promise.then(function(){ - if (_.get(state, 'data.socket.groups.jobs')) { - if (!state.data.socket.groups.jobs.includes("status_changed")) { - state.data.socket.groups.jobs.push("status_changed"); - } - } - else if(!state.data || !state.data.socket){ - _.merge(state.data, {socket: {groups: {jobs: ["status_changed"]}}}); - } - ["job_events", "ad_hoc_command_events", "workflow_events", - "project_update_events", "inventory_update_events", - "system_job_events" - ].forEach(function(group) { - if(state.data && state.data.socket && state.data.socket.groups.hasOwnProperty(group)){ - state.data.socket.groups[group] = [id]; - } - }); - self.subscribe(state); - return true; - }); - } - }; - }]; diff --git a/awx/ui/client/src/shared/stateDefinitions.factory.js b/awx/ui/client/src/shared/stateDefinitions.factory.js deleted file mode 100644 index e72df8e2f99d..000000000000 --- a/awx/ui/client/src/shared/stateDefinitions.factory.js +++ /dev/null @@ -1,942 +0,0 @@ -/** - * @ngdoc interface - * @name stateDefinitions - * @description An API for generating a standard set of state definitions - * generateTree - builds a full list/form tree - * generateListNode - builds a single list node e.g. {name: 'projects', ...} - * generateFormNode - builds a form node definition e.g. {name: 'projects.add', ...} - * generateFormListDefinitions - builds form list definitions attached to a form node e.g. {name: 'projects.edit.permissions', ...} - * generateLookupNodes - Attaches to a form node. Builds an abstract '*.lookup' node with field-specific 'lookup.*' children e.g. {name: 'projects.add.lookup.organizations', ...} - */ - -export default ['$injector', '$stateExtender', '$log', 'i18n', -function($injector, $stateExtender, $log, i18n) { - return { - /** - * @ngdoc method - * @name stateDefinitions.generateTree - * @description intended for consumption by $stateProvider.state.lazyLoad in a placeholder node - * @param {object} params - { - parent: 'stateName', // the name of the top-most node of this tree - modes: ['add', 'edit'], // form modes to include in this state tree - list: 'InjectableListDefinition', - form: 'InjectableFormDefinition', - controllers: { - list: 'Injectable' || Object, - add: 'Injectable' || Object, - edit: 'Injectable' || Object, - } - * @returns {object} Promise which resolves to an object.state containing array of all state definitions in this tree - * e.g. {state: [{...}, {...}, ...]} - */ - generateTree: function(params) { - let form, list, formStates, listState, - states = []; - //return defer.promise; - return new Promise((resolve) => { - // returns array of the following states: - // resource.add, resource.edit - // resource.add.lookup, resource.add.lookup.* => [field in form.fields if field.type == 'lookup'] - // resource.edit.lookup, resource.edit.lookup.* => [field in form.fields if field.type == 'lookup'] - // resource.edit.* => [relationship in form.related] - if (params.list) { - list = $injector.get(params.list); - - listState = this.generateListNode(list, params); - states.push(listState); - } - if (params.form) { - // handle inconsistent typing of form definitions - // can be either an object or fn - form = $injector.get(params.form); - form = typeof(form) === 'function' ? form() : form; - - formStates = _.map(params.modes, (mode) => this.generateFormNode(mode, form, params)); - states = states.concat(_.flatten(formStates)); - } - - $log.debug('*** Generated State Tree', states); - resolve({ states: states }); - }); - }, - - /** - * @ngdoc method - * @name stateDefinitions.generateListNode - * @description builds single list node - * @params {object} list - list definition/configuration object - * @params {object} params - * @returns {object} a list state definition - */ - generateListNode: function(list, params) { - let state, - url = params.urls && params.urls.list ? params.urls.list : (params.url ? params.url : `/${list.name}`); - - // allows passed-in params to specify a custom templateUrl - // otherwise, use html returned by generateList.build() to fulfill templateProvider fn - function generateTemplateBlock() { - if (params.templates && params.templates.list) { - return params.templates.list; - } else { - return function(ListDefinition, generateList) { - let html = generateList.build({ - list: ListDefinition, - mode: 'edit' - }); - html = generateList.wrapPanel(html); - // generateList.formView() inserts a ui-view="form" inside the list view's hierarchy - html = generateList.insertFormView() + html; - if(params.generateSchedulerView){ - html = generateList.insertSchedulerView() + html; - } - return html; - }; - } - } - - let views = params.views ? params.views : { - '@': { - // resolves to a variable property name: - // 'templateUrl' OR 'templateProvider' - [params.templates && params.templates.list ? 'templateUrl' : 'templateProvider']: generateTemplateBlock(), - controller: params.controllers.list, - } - }; - - state = $stateExtender.buildDefinition({ - searchPrefix: list.iterator, - name: params.parent, - url: url, - data: params.data, - ncyBreadcrumb: { - label: list.title - }, - resolve: { - Dataset: [params.list, 'QuerySet', '$stateParams', 'GetBasePath', - function(list, qs, $stateParams, GetBasePath) { - let path = GetBasePath(list.basePath) || GetBasePath(list.name); - return qs.search(path, $stateParams[`${list.iterator}_search`]); - } - ], - ListDefinition: () => list - }, - views: views - }); - // allow passed-in params to override default resolve block - if (params.resolve && params.resolve.list) { - state.resolve = _.merge(state.resolve, params.resolve.list); - } - // allow passed-in params to override default ncyBreadcrumb property - if (params.ncyBreadcrumb) { - state.ncyBreadcrumb = params.ncyBreadcrumb; - } - if (list.search) { - state.params[`${list.iterator}_search`].value = _.merge(state.params[`${list.iterator}_search`].value, list.search); - } - return state; - }, - /** - * @ngdoc method - * @name stateDefinitions.generateFormNode - * @description builds a node of form states, e.g. resource.edit.** or resource.add.** - * @param {string} mode - 'add' || 'edit' - the form mode of this node - * @param {object} form - form definition/configuration object - * @returns {array} Array of state definitions required by form mode [{...}, {...}, ...] - */ - generateFormNode: function(mode, form, params) { - let formNode, - states = [], - url; - switch (mode) { - case 'add': - url = params.urls && params.urls.add ? params.urls.add : (params.url ? params.url : '/add'); - // breadcrumbName necessary for resources that are more than one word like - // job templates. form.name can't have spaces in it or it busts form gen - formNode = $stateExtender.buildDefinition({ - name: params.name || `${params.parent}.add`, - url: url, - ncyBreadcrumb: { - [params.parent ? 'parent' : null]: `${params.parent}`, - label: i18n.sprintf(i18n._("CREATE %s"), i18n._(`${form.breadcrumbName || form.name.toUpperCase()}`)) - }, - views: { - 'form': { - templateProvider: function(FormDefinition, GenerateForm) { - let form = typeof(FormDefinition) === 'function' ? - FormDefinition() : FormDefinition; - return GenerateForm.buildHTML(form, { - mode: 'add', - related: false - }); - }, - controller: params.controllers.add - } - }, - resolve: { - 'FormDefinition': [params.form, function(definition) { - return definition; - }] - } - }); - if (params.resolve && params.resolve.add) { - formNode.resolve = _.merge(formNode.resolve, params.resolve.add); - } - break; - case 'edit': - url = params.urls && params.urls.edit ? params.urls.edit : (params.url ? params.url : `/:${form.name}_id`); - let breadcrumbLabel = params.breadcrumbs && params.breadcrumbs.edit ? params.breadcrumbs.edit : '{{parentObject.name || name}}'; - let formNodeState = { - name: params.name || `${params.parent}.edit`, - url: url, - ncyBreadcrumb: { - [params.parent ? 'parent' : null]: `${params.parent}`, - label: breadcrumbLabel - }, - views: { - 'form': { - templateProvider: function(FormDefinition, GenerateForm) { - let form = typeof(FormDefinition) === 'function' ? - FormDefinition() : FormDefinition; - return GenerateForm.buildHTML(form, { - mode: 'edit' - }); - }, - controller: params.controllers.edit - } - }, - resolve: { - FormDefinition: [params.form, function(definition) { - return definition; - }], - resourceData: ['FormDefinition', 'Rest', '$stateParams', 'GetBasePath', '$q', 'ProcessErrors', - function(FormDefinition, Rest, $stateParams, GetBasePath, $q, ProcessErrors) { - let form, path; - let deferred = $q.defer(); - form = typeof(FormDefinition) === 'function' ? - FormDefinition() : FormDefinition; - if (GetBasePath(form.basePath) === undefined && GetBasePath(form.stateTree) === undefined ){ - throw { name: 'NotImplementedError', message: `${form.name} form definition is missing basePath or stateTree property.` }; - } - else{ - path = (GetBasePath(form.basePath) || GetBasePath(form.stateTree) || form.basePath) + $stateParams[`${form.name}_id`]; - } - Rest.setUrl(path); - Rest.get() - .then((response) => deferred.resolve(response)) - .catch(({ data, status }) => { - ProcessErrors(null, data, status, null, - { - hdr: i18n._('Error!'), - msg: i18n._('Unable to get resource: ') + status - } - ); - deferred.reject(); - }); - return deferred.promise; - } - ] - }, - }; - if (params.data && params.data.activityStreamTarget) { - formNodeState.data = {}; - formNodeState.data.activityStreamId = params.data.activityStreamId ? params.data.activityStreamId : params.data.activityStreamTarget + '_id'; - formNodeState.data.activityStreamTarget = params.data.activityStreamTarget; - } - formNode = $stateExtender.buildDefinition(formNodeState); - - if (params.resolve && params.resolve.edit) { - formNode.resolve = _.merge(formNode.resolve, params.resolve.edit); - } - break; - } - states.push(formNode); - states = states.concat(this.generateLookupNodes(form, formNode)).concat(this.generateFormListDefinitions(form, formNode, params)); - return states; - }, - /** - * @ngdoc method - * @name stateDefinitions.generateFormListDefinitions - * @description builds state definitions for a form's related lists, like notifications/permissions - * @param {object} form - form definition/configuration object - * @params {object} formStateDefinition - the parent form node - * @returns {array} Array of state definitions [{...}, {...}, ...] - */ - generateFormListDefinitions: function(form, formStateDefinition, params) { - function buildRbacUserTeamDirective(){ - let states = []; - - states.push($stateExtender.buildDefinition({ - name: `${formStateDefinition.name}.permissions.add`, - squashSearchUrl: true, - url: '/add-permissions', - params: { - project_search: { - value: {order_by: 'name', page_size: '5', role_level: 'admin_role'}, - dynamic: true - }, - job_template_search: { - value: {order_by: 'name', page_size: '5', role_level: 'admin_role'}, - dynamic: true - }, - workflow_template_search: { - value: {order_by: 'name', page_size: '5', role_level: 'admin_role'}, - dynamic: true - }, - inventory_search: { - value: {order_by: 'name', page_size: '5', role_level: 'admin_role'}, - dynamic: true - }, - credential_search: { - value: {order_by: 'name', page_size: '5', role_level: 'admin_role'}, - dynamic: true - }, - organization_search: { - value: {order_by: 'name', page_size: '5', role_level: 'admin_role'}, - dynamic: true - } - }, - ncyBreadcrumb:{ - skip:true - }, - views: { - [`modal@${formStateDefinition.name}`]: { - template: `` - } - }, - resolve: { - jobTemplatesDataset: ['QuerySet', '$stateParams', 'GetBasePath', - function(qs, $stateParams, GetBasePath) { - let path = GetBasePath('job_templates'); - return qs.search(path, $stateParams.job_template_search); - } - ], - workflowTemplatesDataset: ['QuerySet', '$stateParams', 'GetBasePath', - function(qs, $stateParams, GetBasePath) { - let path = GetBasePath('workflow_job_templates'); - return qs.search(path, $stateParams.workflow_template_search); - } - ], - projectsDataset: ['ProjectList', 'QuerySet', '$stateParams', 'GetBasePath', - function(list, qs, $stateParams, GetBasePath) { - let path = GetBasePath(list.basePath) || GetBasePath(list.name); - return qs.search(path, $stateParams[`${list.iterator}_search`]); - } - ], - inventoriesDataset: ['InventoryList', 'QuerySet', '$stateParams', 'GetBasePath', - function(list, qs, $stateParams, GetBasePath) { - let path = GetBasePath(list.basePath) || GetBasePath(list.name); - return qs.search(path, $stateParams[`${list.iterator}_search`]); - } - ], - credentialsDataset: ['CredentialList', 'QuerySet', '$stateParams', 'GetBasePath', 'resourceData', 'Rest', '$q', - function(list, qs, $stateParams, GetBasePath, resourceData, Rest, $q) { - let path = GetBasePath(list.basePath) || GetBasePath(list.name); - - if(resourceData.data.type === "team") { - $stateParams[`${list.iterator}_search`].organization = resourceData.data.organization; - } - - if(resourceData.data.type === "user") { - - let resolve = $q.defer(); - - let getMoreOrgs = function(data, arr) { - Rest.setUrl(data.next); - Rest.get() - .then(function (resData) { - if (data.next) { - getMoreOrgs(resData.data, arr.concat(resData.data.results)); - } else { - resolve.resolve(arr.concat(resData.data.results)); - } - }); - }; - - Rest.setUrl(GetBasePath('users') + `${resourceData.data.id}/organizations?page_size=200`); - Rest.get() - .then(function(resData) { - if (resData.data.next) { - getMoreOrgs(resData.data, resData.data.results); - } else { - resolve.resolve(resData.data.results); - } - }); - - return resolve.promise.then(function (organizations) { - if(organizations && organizations.length > 0) { - let orgIds = _.map(organizations, function(organization){ - return organization.id; - }); - - $stateParams[`${list.iterator}_search`].or__organization = 'null'; - $stateParams[`${list.iterator}_search`].or__organization__in = orgIds.join(); - - } - else { - $stateParams[`${list.iterator}_search`].organization = 'null'; - } - - return qs.search(path, $stateParams[`${list.iterator}_search`]); - }); - - } - else { - return qs.search(path, $stateParams[`${list.iterator}_search`]); - } - } - ], - organizationsDataset: ['OrganizationList', 'QuerySet', '$stateParams', 'GetBasePath', - function(list, qs, $stateParams, GetBasePath) { - let path = GetBasePath(list.basePath) || GetBasePath(list.name); - return qs.search(path, $stateParams[`${list.iterator}_search`]); - } - ], - }, - onExit: function($state) { - if ($state.transition) { - $('#add-permissions-modal').modal('hide'); - $('.modal-backdrop').remove(); - $('body').removeClass('modal-open'); - } - }, - })); - return states; - } - - function buildRbacResourceDirective() { - let states = []; - - states.push($stateExtender.buildDefinition({ - name: `${formStateDefinition.name}.permissions.add`, - squashSearchUrl: true, - url: '/add-permissions', - params: { - user_search: { - value: { order_by: 'username', page_size: '5', is_superuser: false }, - dynamic: true, - }, - team_search: { - value: { order_by: 'name', page_size: '5' }, - dynamic: true - } - }, - views: { - [`modal@${formStateDefinition.name}`]: { - template: `` - } - }, - ncyBreadcrumb:{ - skip:true - }, - resolve: { - usersDataset: ['addPermissionsUsersList', 'QuerySet', '$stateParams', 'GetBasePath', - function(list, qs, $stateParams, GetBasePath) { - let path = GetBasePath(list.basePath) || GetBasePath(list.name); - return qs.search(path, $stateParams.user_search); - - } - ], - teamsDataset: ['addPermissionsTeamsList', 'QuerySet', '$stateParams', 'GetBasePath', - function(list, qs, $stateParams, GetBasePath) { - let path = GetBasePath(list.basePath) || GetBasePath(list.name); - return qs.search(path, $stateParams.team_search); - } - ] - }, - onExit: function($state) { - if ($state.transition) { - $('#add-permissions-modal').modal('hide'); - $('.modal-backdrop').remove(); - $('body').removeClass('modal-open'); - } - }, - })); - return states; - } - - function buildNotificationState(field) { - let state, - list = field.include ? $injector.get(field.include) : field, - breadcrumbLabel = (field.iterator.replace('_', ' ') + 's').toUpperCase(); - state = $stateExtender.buildDefinition({ - searchPrefix: `${list.iterator}`, - name: `${formStateDefinition.name}.${list.iterator}s`, - url: `/${list.iterator}s`, - ncyBreadcrumb: { - parent: `${formStateDefinition.name}`, - label: `${breadcrumbLabel}` - }, - params: { - [list.iterator + '_search']: { - value: { order_by: field.order_by ? field.order_by : 'name' } - } - }, - views: { - 'related': { - templateProvider: function(FormDefinition, GenerateForm, $stateParams, SourcesFormDefinition) { - var form, html; - if($stateParams && $stateParams.inventory_source_id){ - form = SourcesFormDefinition; - } - else { - form = typeof(FormDefinition) === 'function' ? - FormDefinition() : FormDefinition; - } - html = GenerateForm.buildCollection({ - mode: 'edit', - related: `${list.iterator}s`, - form: form - }); - return html; - }, - controller: ['$scope', 'ListDefinition', 'Dataset', 'ToggleNotification', 'NotificationsListInit', 'GetBasePath', '$stateParams', - function($scope, list, Dataset, ToggleNotification, NotificationsListInit, GetBasePath, $stateParams) { - var url , params = $stateParams, id; - if(params.hasOwnProperty('project_id')){ - id = params.project_id; - url = GetBasePath('projects'); - } - if(params.hasOwnProperty('job_template_id')){ - id = params.job_template_id; - url = GetBasePath('job_templates'); - } - if(params.hasOwnProperty('workflow_job_template_id')){ - id = params.workflow_job_template_id; - url = GetBasePath('workflow_job_templates'); - } - if(params.hasOwnProperty('inventory_source_id')){ - id = params.inventory_source_id; - url = GetBasePath('inventory_sources'); - } - if(params.hasOwnProperty('organization_id')){ - id = params.organization_id; - url = GetBasePath('organizations'); - } - function init() { - $scope.list = list; - $scope[`${list.iterator}_dataset`] = Dataset.data; - $scope[list.name] = $scope[`${list.iterator}_dataset`].results; - - - NotificationsListInit({ - scope: $scope, - url: url, - id: id - }); - - $scope.$watch(`${list.iterator}_dataset`, function() { - // The list data has changed and we need to update which notifications are on/off - $scope.$emit('relatednotifications'); - }); - } - - $scope.toggleNotification = function(event, notifier_id, column) { - var notifier = this.notification; - try { - $(event.target).tooltip('hide'); - } - catch(e) { - // ignore - } - ToggleNotification({ - scope: $scope, - url: url + id, - notifier: notifier, - column: column, - callback: 'NotificationRefresh' - }); - }; - - init(); - - } - ] - } - }, - resolve: { - ListDefinition: () => { - return list; - }, - Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath', '$interpolate', '$rootScope', - (list, qs, $stateParams, GetBasePath, $interpolate, $rootScope) => { - // allow related list definitions to use interpolated $rootScope / $stateParams in basePath field - let path, interpolator; - if (GetBasePath(list.basePath)) { - path = GetBasePath(list.basePath); - } else { - interpolator = $interpolate(list.basePath); - path = interpolator({ $rootScope: $rootScope, $stateParams: $stateParams }); - } - return qs.search(path, $stateParams[`${list.iterator}_search`]); - } - ] - } - }); - return state; - } - - function buildRbacUserDirective() { - let states = []; - - states.push($stateExtender.buildDefinition({ - name: `${formStateDefinition.name}.users.add`, - url: '/add-user', - searchPrefix: 'add_user', - params: { - add_user_search: { - value: { order_by: 'username', page_size: '5' }, - dynamic: true - } - }, - views: { - [`modal@${formStateDefinition.name}`]: { - template: `` - } - }, - ncyBreadcrumb:{ - skip:true - }, - resolve: { - roleToExclude: ['$stateParams', 'Rest', 'GetBasePath', 'i18n', function($stateParams, Rest, GetBasePath, i18n) { - const basePath = ($stateParams.team_id) ? GetBasePath('teams') + `${$stateParams.team_id}/object_roles` : - GetBasePath('organizations') + `${$stateParams.organization_id}/object_roles`; - Rest.setUrl(basePath); - return Rest.get().then(({data}) => { - return data.results - .filter(({name}) => name === i18n._('Member')) - .map(({id}) => id)[0]; - }); - }], - usersDataset: ['addPermissionsUsersList', 'QuerySet', '$stateParams', 'GetBasePath', 'roleToExclude', - function(list, qs, $stateParams, GetBasePath, roleToExclude) { - let path = GetBasePath(list.basePath) || GetBasePath(list.name); - if (roleToExclude) { - $stateParams.add_user_search.not__roles = roleToExclude; - } - return qs.search(path, $stateParams.add_user_search); - } - ], - defaultParams: ['$stateParams', 'usersDataset', function($stateParams) { - return $stateParams.add_user_search; - }] - }, - onExit: function($state) { - if ($state.transition) { - $('#add-permissions-modal').modal('hide'); - $('.modal-backdrop').remove(); - $('body').removeClass('modal-open'); - } - }, - })); - return states; - } - - function buildListNodes(field) { - let states = []; - if(!field.skipGenerator) { - if(field.iterator === 'notification'){ - states.push(buildNotificationState(field)); - states = _.flatten(states); - } - else{ - states.push(buildListDefinition(field)); - if (field.iterator === 'permission' && field.actions && field.actions.add) { - if (form.name === 'user' || form.name === 'team'){ - states.push(buildRbacUserTeamDirective()); - } - else { - states.push(buildRbacResourceDirective()); - } - } - else if (field.iterator === 'user' && field.actions && field.actions.add) { - if(form.name === 'team' || form.name === 'organization') { - states.push(buildRbacUserDirective()); - } - } - } - } - - states = _.flatten(states); - return states; - } - - function buildListDefinition(field) { - let state, - list = field.include ? $injector.get(field.include) : field, - // Added this line specifically for Completed Jobs but should be OK - // for all the rest of the related tabs - breadcrumbLabel = (field.iterator.replace('_', ' ') + 's').toUpperCase(), - stateConfig = { - searchPrefix: `${list.iterator}`, - name: `${formStateDefinition.name}.${list.iterator}s`, - url: `/${list.iterator}s`, - ncyBreadcrumb: { - parent: `${formStateDefinition.name}`, - label: `${breadcrumbLabel}` - }, - params: { - [list.iterator + '_search']: { - value: { order_by: field.order_by ? field.order_by : 'name' } - }, - }, - views: { - 'related': { - templateProvider: function(FormDefinition, GenerateForm) { - let html = GenerateForm.buildCollection({ - mode: 'edit', - related: `${list.iterator}s`, - form: typeof(FormDefinition) === 'function' ? - FormDefinition() : FormDefinition - }); - return html; - } - } - }, - resolve: { - ListDefinition: () => { - return list; - }, - Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath', '$interpolate', '$rootScope', - (list, qs, $stateParams, GetBasePath, $interpolate, $rootScope) => { - // allow related list definitions to use interpolated $rootScope / $stateParams in basePath field - let path, interpolator; - if (GetBasePath(list.basePath)) { - path = GetBasePath(list.basePath); - } else { - interpolator = $interpolate(list.basePath); - path = interpolator({ $rootScope: $rootScope, $stateParams: $stateParams }); - } - return qs.search(path, $stateParams[`${list.iterator}_search`]); - } - ] - } - }; - - if(params.controllers && params.controllers.related && params.controllers.related[field.name]) { - stateConfig.views.related.controller = params.controllers.related[field.name]; - } - else if(field.name === 'permissions') { - stateConfig.views.related.controller = 'PermissionsList'; - } - else { - // Generic controller - stateConfig.views.related.controller = ['$scope', 'ListDefinition', 'Dataset', - function($scope, list, Dataset) { - $scope.list = list; - $scope[`${list.iterator}_dataset`] = Dataset.data; - $scope[`${list.iterator}s`] = $scope[`${list.iterator}_dataset`].results; - } - ]; - } - - state = $stateExtender.buildDefinition(stateConfig); - // appy any default search parameters in form definition - if (field.search) { - state.params[`${field.iterator}_search`].value = _.merge(state.params[`${field.iterator}_search`].value, field.search); - } - - return state; - } - return _(form.related).map(buildListNodes).flatten().value(); - }, - /** - * @ngdoc method - * @name stateDefinitions.generateLookupNode - * @description builds a node of child states for each lookup field in a form - * @param {object} form - form definition/configuration object - * @params {object} formStateDefinition - the parent form node - * @returns {array} Array of state definitions [{...}, {...}, ...] - */ - generateLookupNodes: function(form, formStateDefinition) { - - function buildFieldDefinition(field) { - - // Some lookup modals require some additional default params, - // namely organization and inventory_script, and insights - // credentials. If these params - // aren't set as default params out of the gate, then smart - // search will think they need to be set as search tags. - var params; - if(field.sourceModel === "organization"){ - if (form.name === "notification_template") { - // Users with admin_role role level should also have - // notification_admin_role so this should handle regular admin - // users as well as notification admin users - params = { - page_size: '5', - role_level: 'notification_admin_role' - }; - } else { - params = { - page_size: '5', - role_level: 'admin_role' - }; - } - } - else if(field.sourceModel === "inventory_script"){ - params = { - page_size: '5', - role_level: 'admin_role', - organization: null - }; - } - else if(field.sourceModel === "insights_credential"){ - params = { - page_size: '5', - role_level: 'admin_role', - credential_type: null - }; - } - else if(field.sourceModel === 'host') { - params = { - page_size: '5' - }; - } - else { - params = { - page_size: '5', - role_level: 'use_role' - }; - } - - let state = $stateExtender.buildDefinition({ - searchPrefix: field.sourceModel, - //squashSearchUrl: true, @issue enable - name: `${formStateDefinition.name}.${field.sourceModel}`, - url: `/${field.sourceModel}?selected`, - // a lookup field's basePath takes precedence over generic list definition's basePath, if supplied - data: { - basePath: field.basePath || null, - formChildState: true - }, - params: { - [field.sourceModel + '_search']: { - value: params - } - }, - ncyBreadcrumb: { - skip: true - }, - views: { - 'modal': { - templateProvider: function(ListDefinition, generateList) { - const listConfig = { - mode: 'lookup', - list: ListDefinition, - input_type: 'radio' - }; - - if (field.lookupMessage) { - listConfig.lookupMessage = field.lookupMessage; - } - - let list_html = generateList.build(listConfig); - - return `${list_html}`; - } - } - }, - resolve: { - ListDefinition: [field.list, function(list) { - let listClone = _.cloneDeep(list); - listClone.iterator = field.sourceModel; - return listClone; - }], - OrganizationId: ['ListDefinition', 'InventoriesService', '$stateParams', '$rootScope', - function(list, InventoriesService, $stateParams, $rootScope){ - if(list.iterator === 'inventory_script'){ - if($rootScope.$$childTail && - $rootScope.$$childTail.$resolve && - $rootScope.$$childTail.$resolve.hasOwnProperty('inventoryData')){ - return $rootScope.$$childTail.$resolve.inventoryData.summary_fields.organization.id; - } - else { - return InventoriesService.getInventory($stateParams.inventory_id).then(res => res.data.summary_fields.organization.id); - } - - } - else { - return; - } - }], - InsightsCredTypePK: ['ListDefinition', 'Rest', 'GetBasePath', 'ProcessErrors', - function(list, Rest, GetBasePath,ProcessErrors) { - if(list.iterator === 'insights_credential'){ - Rest.setUrl(GetBasePath('credential_types') + '?name=Insights'); - return Rest.get() - .then(({data}) => { - return data.results[0].id; - }) - .catch(({data, status}) => { - ProcessErrors(null, data, status, null, { - hdr: 'Error!', - msg: 'Failed to get credential type data: ' + status - }); - }); - } - }], - Dataset: ['ListDefinition', 'QuerySet', '$stateParams', 'GetBasePath', '$interpolate', '$rootScope', '$state', 'OrganizationId', 'InsightsCredTypePK', - (list, qs, $stateParams, GetBasePath, $interpolate, $rootScope, $state, OrganizationId, InsightsCredTypePK) => { - // allow lookup field definitions to use interpolated $stateParams / $rootScope in basePath field - // the basePath on a form's lookup field will take precedence over the general model list's basepath - let path, interpolator; - if ($state.transition._targetState._definition.data && GetBasePath($state.transition._targetState._definition.data.basePath)) { - path = GetBasePath($state.transition._targetState._definition.data.basePath); - } else if ($state.transition._targetState._definition.data && $state.transition._targetState._definition.data.basePath) { - interpolator = $interpolate($state.transition._targetState._definition.data.basePath); - path = interpolator({ $rootScope: $rootScope, $stateParams: $stateParams }); - } else if (GetBasePath(list.basePath)) { - path = GetBasePath(list.basePath); - } else { - interpolator = $interpolate(list.basePath); - path = interpolator({ $rootScope: $rootScope, $stateParams: $stateParams }); - } - // Need to change the role_level here b/c organizations and inventory scripts - // don't have a "use_role", only "admin_role" and "read_role" - if(list.iterator === "organization"){ - if ($state.current.name.includes('inventories')) { - $stateParams[`${list.iterator}_search`].role_level = "inventory_admin_role"; - } else if ($state.current.name.includes('projects')) { - $stateParams[`${list.iterator}_search`].role_level = "project_admin_role"; - } else if ($state.current.name.includes('templates.addWorkflowJobTemplate') || $state.current.name.includes('templates.editWorkflowJobTemplate')) { - $stateParams[`${list.iterator}_search`].role_level = "workflow_admin_role"; - } - } - if(list.iterator === "inventory_script"){ - $stateParams[`${list.iterator}_search`].role_level = "admin_role"; - $stateParams[`${list.iterator}_search`].organization = OrganizationId; - } - if(list.iterator === "insights_credential"){ - $stateParams[`${list.iterator}_search`].role_level = "admin_role"; - $stateParams[`${list.iterator}_search`].credential_type = InsightsCredTypePK.toString() ; - } - if(list.iterator === 'credential') { - if($state.current.name.includes('projects.edit') || $state.current.name.includes('projects.add')) { - state.params[`${list.iterator}_search`].value = _.merge(state.params[`${list.iterator}_search`].value, $stateParams[`${list.iterator}_search`]); - } - } - - return qs.search(path, $stateParams[`${list.iterator}_search`]); - } - ] - }, - onExit: function($state) { - if ($state.transition) { - $('#form-modal').modal('hide'); - $('.modal-backdrop').remove(); - $('body').removeClass('modal-open'); - } - }, - }); - if (field.search) { - state.params[`${field.sourceModel}_search`].value = _.merge(state.params[`${field.sourceModel}_search`].value, field.search); - } - return state; - } - return _(form.fields).filter({ type: 'lookup' }).map(buildFieldDefinition).value(); - } - - }; - -}]; diff --git a/awx/ui/client/src/shared/stateExtender.provider.js b/awx/ui/client/src/shared/stateExtender.provider.js deleted file mode 100644 index 2ca49c3a76e0..000000000000 --- a/awx/ui/client/src/shared/stateExtender.provider.js +++ /dev/null @@ -1,75 +0,0 @@ -export default function($stateProvider) { - this.$get = function() { - return { - // attaches socket as resolvable if specified in state definition - addSocket: function(state){ - // The login route has a 'null' socket because it should - // neither subscribe or unsubscribe - if(state.data && state.data.socket!==null){ - if(!state.resolve){ - state.resolve = {}; - } - state.resolve.socket = ['SocketService', '$stateParams', - function(SocketService, $stateParams) { - SocketService.addStateResolve(state, $stateParams.id); - } - ]; - } - }, - // builds a state definition with default slaw - buildDefinition: function(state) { - let params, defaults, definition, - searchPrefix = state.searchPrefix ? `${state.searchPrefix}_search` : null, - route = state.route || state.url; - - if (searchPrefix) { - defaults = { - params: { - [searchPrefix]: { - value: { - page_size: "20", - order_by: "name" - }, - dynamic: true, - squash: false - } - } - }; - route = !state.squashSearchUrl ? `${route}?{${searchPrefix}:queryset}` : route; - params = state.params === undefined ? defaults.params : _.merge(defaults.params, state.params); - } else { - params = state.params; - } - - definition = { - name: state.name, - url: route, - abstract: state.abstract, - controller: state.controller, - templateUrl: state.templateUrl, - templateProvider: state.templateProvider, - resolve: state.resolve, - params: params, - data: state.data, - ncyBreadcrumb: state.ncyBreadcrumb, - onEnter: state.onEnter, - onExit: state.onExit, - template: state.template, - controllerAs: state.controllerAs, - views: state.views, - parent: state.parent, - redirectTo: state.redirectTo, - // new in uiRouter 1.0 - lazyLoad: state.lazyLoad, - }; - this.addSocket(definition); - return definition; - }, - // registers a state definition with $stateProvider service - addState: function(state) { - let definition = this.buildDefinition(state); - $stateProvider.state(state.name, definition); - } - }; - }; -} diff --git a/awx/ui/client/src/shared/template-url/main.js b/awx/ui/client/src/shared/template-url/main.js deleted file mode 100644 index 8c1c64185e54..000000000000 --- a/awx/ui/client/src/shared/template-url/main.js +++ /dev/null @@ -1,6 +0,0 @@ -import templateUrl from './template-url.factory'; - -export default - angular.module('templateUrl', []) - .factory('templateUrl', templateUrl); - diff --git a/awx/ui/client/src/shared/template-url/template-url.factory.js b/awx/ui/client/src/shared/template-url/template-url.factory.js deleted file mode 100644 index bcdd1d5e87b9..000000000000 --- a/awx/ui/client/src/shared/template-url/template-url.factory.js +++ /dev/null @@ -1,26 +0,0 @@ -// This function is accessible outside of angular -// -export function templateUrl(path) { - return _templateUrl(null, path); -} - -function _templateUrl($sce, path, isTrusted) { - isTrusted = isTrusted !== false; // defaults to true, can be passed in as false - var parts = ['', 'static/partials']; - parts.push(path); - - var url = parts.join('/') + '.partial.html'; - - if (isTrusted && $sce) { - url = $sce.trustAsResourceUrl(url); - } - - return url; -} - -export default - [ '$sce', - function($sce) { - return _.partial(_templateUrl, $sce); - } - ]; diff --git a/awx/ui/client/src/shared/text-label.less b/awx/ui/client/src/shared/text-label.less deleted file mode 100644 index 91ff0f1a656d..000000000000 --- a/awx/ui/client/src/shared/text-label.less +++ /dev/null @@ -1,18 +0,0 @@ -/* oops */ - -.include-text-label(@background-color; @color; @content) { - display: inline-block; - content: @content; - - border-radius: 3px; - background-color: @background-color; - color: @color; - text-transform: uppercase; - font-size: .7em; - font-weight: bold; - font-style: normal; - margin-left: 0.5em; - padding: 0.35em; - padding-bottom: 0.2em; - line-height: 1.1; -} diff --git a/awx/ui/client/src/shared/title.directive.js b/awx/ui/client/src/shared/title.directive.js deleted file mode 100644 index 717b0912b78d..000000000000 --- a/awx/ui/client/src/shared/title.directive.js +++ /dev/null @@ -1,17 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/* jshint unused: vars */ - -export default function() { - return function(scope, element, attrs) { - if (attrs.awToolTip) { - return; - } - - element.tooltip(); - }; -} diff --git a/awx/ui/client/src/shared/truncated-text.directive.js b/awx/ui/client/src/shared/truncated-text.directive.js deleted file mode 100644 index bf3d1fd8cc62..000000000000 --- a/awx/ui/client/src/shared/truncated-text.directive.js +++ /dev/null @@ -1,84 +0,0 @@ -/* jshint unused: vars */ - -function link($compile, scope, element, attrs) { - - // If the element is a DOM comment, that means - // it's been hidden with `ng-if` so don't try - // to process it or we get an error! - if (element[0].nodeType === 8) { - element = element.next(); - - // Element was removed due to `ng-if`, so don't - // worry about it - if (element.length === 0) { - return; - } - } - - - function elementTextWillWrap(element) { - - if (element[0].nodeType === 8) { - return false; - } - - var fullTextWidth = element[0].scrollWidth; - var elementWidth = element.outerWidth(); - - // HACK: For some reason many of my divs - // are ending up with a 1px size diff. - // Guessing this is because of 3 cols - // at 33% flex-basis, with 8px padding. - // Perhaps the padding is pushing it over - // in JavaScript but not visually? Using - // threshold to filter those out. - var threshold = 5; - - if(fullTextWidth > elementWidth && - fullTextWidth - elementWidth > threshold) { - return true; - } - - return false; - } - - function getText() { - return element.text(); - } - - function addTitleIfWrapping(text) { - - if (elementTextWillWrap(element)) { - element - .addClass('u-truncatedText') - .removeAttr('truncated-text') - .attr('title', text); - - $compile(element)(scope); - } - } - - scope.$watch(getText, addTitleIfWrapping); - - // HACK: This handles truncating _after_ other elements - // are added that affect the size of this element. I - // wanted to leave this as a regular event binding, but - // was running into issues with the resized element getting - // removed from the DOM after truncating! No idea why yet. - element.resize(function() { - addTitleIfWrapping(getText()); - }); - - scope.$watch('$destroy', function() { - element.off('resize'); - }); -} - -export default - ['$compile', - function($compile) { - return { - link: _.partial(link, $compile) - }; - } - ]; diff --git a/awx/ui/client/src/shared/upgrade/upgrade.block.less b/awx/ui/client/src/shared/upgrade/upgrade.block.less deleted file mode 100644 index 4d0203fa3295..000000000000 --- a/awx/ui/client/src/shared/upgrade/upgrade.block.less +++ /dev/null @@ -1,57 +0,0 @@ -.at-Upgrade--panel { - align-items: center; - background-color: @at-color-body-background-light; - border-radius: 10px; - color: @at-color-body-text; - display: flex; - flex-direction: column; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: @at-font-size-jumbotron-text; - height: ~"calc(100vh - 40px)"; - justify-content: center; - margin: @at-space-4x; - padding: @at-space-10x; -} - - .at-Upgrade--header { - display: flex; - font-size: @at-font-size-jumbotron-heading; - margin-top: @at-space-2x; - } - - .at-Upgrade--text { - align-items: center; - display: flex; - flex-flow: column; - } - - .at-Upgrade--brand { - margin-right: .4em; - } - - .at-Upgrade--icon { - color: @at-gray-b7; - } - - .at-Upgrade--loading:after { - content: "\2026"; - display: inline-block; - overflow: hidden; - position: absolute; - vertical-align: bottom; - width: 0px; - animation: ellipsis steps(4, end) 1500ms infinite; - -webkit-animation: ellipsis steps(4, end) 1500ms infinite; - } - - @keyframes ellipsis { - to { - width: 30px; - } - } - - @-webkit-keyframes ellipsis { - to { - width: 30px; - } - } diff --git a/awx/ui/client/src/shared/utilities/alerts.less b/awx/ui/client/src/shared/utilities/alerts.less deleted file mode 100644 index 1aaed209983d..000000000000 --- a/awx/ui/client/src/shared/utilities/alerts.less +++ /dev/null @@ -1,8 +0,0 @@ -.u-input-info-alert { - border-color: #31708f !important; -} - -.u-info-alert { - color: #31708f !important; -} - diff --git a/awx/ui/client/src/shared/utilities/hidden.less b/awx/ui/client/src/shared/utilities/hidden.less deleted file mode 100644 index 36942122d40a..000000000000 --- a/awx/ui/client/src/shared/utilities/hidden.less +++ /dev/null @@ -1,4 +0,0 @@ -.u-hiddenVisually { - visibility: hidden; -} - diff --git a/awx/ui/client/src/shared/utilities/icons.less b/awx/ui/client/src/shared/utilities/icons.less deleted file mode 100644 index 654f4f08005c..000000000000 --- a/awx/ui/client/src/shared/utilities/icons.less +++ /dev/null @@ -1,14 +0,0 @@ -/* not bem */ - -.icon(@icon-var) { - display: flex; - align-self: center; - font-family: FontAwesome; - font-style: normal; - font-weight: normal; - line-height: 1; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - - content: @icon-var; -} diff --git a/awx/ui/client/src/shared/utilities/layer.less b/awx/ui/client/src/shared/utilities/layer.less deleted file mode 100644 index e5af025d5b42..000000000000 --- a/awx/ui/client/src/shared/utilities/layer.less +++ /dev/null @@ -1,4 +0,0 @@ -.u-layer { - position: relative; - z-index: 10000; -} diff --git a/awx/ui/client/src/shared/utilities/truncated-text.less b/awx/ui/client/src/shared/utilities/truncated-text.less deleted file mode 100644 index 42be763ab9e8..000000000000 --- a/awx/ui/client/src/shared/utilities/truncated-text.less +++ /dev/null @@ -1,9 +0,0 @@ -.u-truncatedText { - overflow: hidden; - text-overflow: ellipsis; -} - -.u-wrappedText { - white-space: normal; - word-wrap: break-word; -} diff --git a/awx/ui/client/src/shared/utilities/unbold.less b/awx/ui/client/src/shared/utilities/unbold.less deleted file mode 100644 index 35654737e03a..000000000000 --- a/awx/ui/client/src/shared/utilities/unbold.less +++ /dev/null @@ -1,3 +0,0 @@ -.u-unbold { - font-weight: normal; -} diff --git a/awx/ui/client/src/shared/utilities/wordwrap.less b/awx/ui/client/src/shared/utilities/wordwrap.less deleted file mode 100644 index 7ca289d4da27..000000000000 --- a/awx/ui/client/src/shared/utilities/wordwrap.less +++ /dev/null @@ -1,10 +0,0 @@ -.u-wordwrap { - white-space: -moz-pre-wrap !important; - white-space: -webkit-pre-wrap; - white-space: -pre-wrap; - white-space: -o-pre-wrap; - white-space: pre-wrap; - word-wrap: break-word; - word-break: break-all; - white-space: normal; -} \ No newline at end of file diff --git a/awx/ui/client/src/shared/variables/main.js b/awx/ui/client/src/shared/variables/main.js deleted file mode 100644 index 29942a7ff9d6..000000000000 --- a/awx/ui/client/src/shared/variables/main.js +++ /dev/null @@ -1,9 +0,0 @@ -import ParseVariableString from './parse-variable-string.factory'; -import SortVariables from './sort-variables.factory'; -import ToJSON from './to-json.factory'; - -export default - angular.module('variables', []) - .factory('ParseVariableString', ParseVariableString) - .factory('SortVariables', SortVariables) - .factory('ToJSON', ToJSON); diff --git a/awx/ui/client/src/shared/variables/parse-variable-string.factory.js b/awx/ui/client/src/shared/variables/parse-variable-string.factory.js deleted file mode 100644 index 71e145897f9f..000000000000 --- a/awx/ui/client/src/shared/variables/parse-variable-string.factory.js +++ /dev/null @@ -1,55 +0,0 @@ -export default - function ParseVariableString($log, ProcessErrors, SortVariables) { - return function (variables) { - var result = "---", json_obj; - if (typeof variables === 'string') { - if (variables === "{}" || variables === "null" || variables === "" || variables === "\"\"") { - // String is empty, return --- - } else { - try { - json_obj = JSON.parse(variables); - json_obj = SortVariables(json_obj); - result = jsyaml.safeDump(json_obj); - - } - catch (e) { - $log.debug('Attempt to parse extra_vars as JSON failed. Check that the variables parse as yaml. Set the raw string as the result.'); - try { - // do safeLoad, which well error if not valid yaml - json_obj = jsyaml.safeLoad(variables); - // but just send the variables - result = variables; - } - catch(e2) { - ProcessErrors(null, variables, e2.message, null, { hdr: 'Error!', - msg: 'Attempts to parse variables as JSON and YAML failed. Last attempt returned: ' + e2.message }); - } - } - } - } - else { - if ($.isEmptyObject(variables) || variables === null) { - // Empty object, return --- - } - else { - // convert object to yaml - try { - json_obj = SortVariables(variables); - result = jsyaml.safeDump(json_obj); - // result = variables; - } - catch(e3) { - ProcessErrors(null, variables, e3.message, null, { hdr: 'Error!', - msg: 'Attempt to convert JSON object to YAML document failed: ' + e3.message }); - } - } - } - return result; - }; - } - -ParseVariableString.$inject = - [ '$log', - 'ProcessErrors', - 'SortVariables' - ]; diff --git a/awx/ui/client/src/shared/variables/sort-variables.factory.js b/awx/ui/client/src/shared/variables/sort-variables.factory.js deleted file mode 100644 index a9cb3a363b20..000000000000 --- a/awx/ui/client/src/shared/variables/sort-variables.factory.js +++ /dev/null @@ -1,23 +0,0 @@ -export default - function SortVariables() { - return function(variableObj) { - var newObj; - function sortIt(objToSort) { - var i, - keys = Object.keys(objToSort), - newObj = {}; - keys = keys.sort(); - for (i=0; i < keys.length; i++) { - if (typeof objToSort[keys[i]] === 'object' && objToSort[keys[i]] !== null && !Array.isArray(objToSort[keys[i]])) { - newObj[keys[i]] = sortIt(objToSort[keys[i]]); - } - else { - newObj[keys[i]] = objToSort[keys[i]]; - } - } - return newObj; - } - newObj = sortIt(variableObj); - return newObj; - }; - } diff --git a/awx/ui/client/src/shared/variables/to-json.factory.js b/awx/ui/client/src/shared/variables/to-json.factory.js deleted file mode 100644 index 70996d489ed2..000000000000 --- a/awx/ui/client/src/shared/variables/to-json.factory.js +++ /dev/null @@ -1,80 +0,0 @@ -export default - function ToJSON($log, ProcessErrors) { - return function(parseType, variables, stringify, reviver) { - var json_data, - result, - tmp; - // bracketVar, - // key, - // lines, i, newVars = []; - if (parseType === 'json') { - try { - // perform a check to see if the user cleared the field completly - if(variables.trim() === "" || variables.trim() === "{" || variables.trim() === "}" ){ - variables = "{}"; - } - //parse a JSON string - if (reviver) { - json_data = JSON.parse(variables, reviver); - } - else { - json_data = JSON.parse(variables); - } - } - catch(e) { - json_data = {}; - $log.error('Failed to parse JSON string. Parser returned: ' + e.message); - ProcessErrors(null, variables, e.message, null, { hdr: 'Error!', - msg: 'Failed to parse JSON string. Parser returned: ' + e.message }); - throw 'Parse error. Failed to parse variables.'; - } - } else { - try { - if(variables.trim() === "" || variables.trim() === "-" || variables.trim() === "--"){ - variables = '---'; - } - json_data = jsyaml.safeLoad(variables); - if(json_data!==null){ - // unparsing just to make sure no weird characters are included. - tmp = jsyaml.dump(json_data); - if(tmp.indexOf('[object Object]')!==-1){ - throw "Failed to parse YAML string. Parser returned' + key + ' : ' +value + '.' "; - } - } - } - catch(e) { - json_data = undefined; // {}; - // $log.error('Failed to parse YAML string. Parser returned undefined'); - ProcessErrors(null, variables, e.message, null, { hdr: 'Error!', - msg: 'Failed to parse YAML string. Parser returned undefined'}); - } - } - // Make sure our JSON is actually an object - if (typeof json_data !== 'object') { - ProcessErrors(null, variables, null, null, { hdr: 'Error!', - msg: 'Failed to parse variables. Attempted to parse ' + parseType + '. Parser did not return an object.' }); - // setTimeout( function() { - throw 'Parse error. Failed to parse variables.'; - // }, 1000); - } - result = json_data; - if (stringify) { - if(json_data === undefined){ - result = undefined; - } - else if ($.isEmptyObject(json_data)) { - result = ""; - } else { - // utilize the parsing to get here - // but send the raw variable string - result = variables; - } - } - return result; - }; - } - -ToJSON.$inject = - [ '$log', - 'ProcessErrors' - ]; diff --git a/awx/ui/client/src/smart-status/main.js b/awx/ui/client/src/smart-status/main.js deleted file mode 100644 index dfa2b35f0fab..000000000000 --- a/awx/ui/client/src/smart-status/main.js +++ /dev/null @@ -1,10 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import smartStatusDirective from './smart-status.directive'; -export default - angular.module('systemStatus', []) - .directive('awSmartStatus', smartStatusDirective); diff --git a/awx/ui/client/src/smart-status/smart-status.block.less b/awx/ui/client/src/smart-status/smart-status.block.less deleted file mode 100644 index 7e908741663c..000000000000 --- a/awx/ui/client/src/smart-status/smart-status.block.less +++ /dev/null @@ -1,106 +0,0 @@ -/** @define SmartStatus */ - -.SmartStatus-container{ - display:flex; -} - -.SmartStatus-iconContainer{ - padding: 2px; - flex: 0 1 auto; -} - -.SmartStatus-icon { - width: 14px; - height: 14px; -} - -.SmartStatus-iconDirectionPlaceholder { - width: 14px; - height: 7px; - border: 1px solid @d7grey; - background: #f2f2f2; -} - -.SmartStatus-iconDirectionPlaceholder--bottom { - border-bottom: 0; -} - -.SmartStatus-iconDirectionPlaceholder--top { - border-top: 0; -} - -.SmartStatus-iconIndicator { - width: 14px; - height: 7px; -} - -.SmartStatus-iconIndicator--success { - background: #5cb85c; -} - -.SmartStatus-iconIndicator--failed { - background: #d9534f; -} - -.SmartStatus-iconPlaceholder { - height: 14px; - width: 14px; - border: 1px solid @d7grey; - background: #f2f2f2; -} - -.SmartStatus-tooltip--successful, -.SmartStatus-tooltip--success{ - color: @default-succ; - padding-right: 0px; - text-shadow: - -1px -1px 0 @default-bg, - 1px -1px 0 @default-bg, - -1px 1px 0 @default-bg, - 1px 1px 0 @default-bg; - -} - -.SmartStatus-tooltip--error, .SmartStatus-tooltip--failed{ - color: @default-err; - padding-right: 0px; - text-shadow: - -1px -1px 0 @default-bg, - 1px -1px 0 @default-bg, - -1px 1px 0 @default-bg, - 1px 1px 0 @default-bg; -} - -.SmartStatus-tooltip--running{ - color: @default-data-txt; - padding-right: 0px; - text-shadow: - -1px -1px 0 @default-bg, - 1px -1px 0 @default-bg, - -1px 1px 0 @default-bg, - 1px 1px 0 @default-bg; - .pulsate(); -} - -.SmartStatus-waiting { - width: 14px; - height: 14px; - border: 1px solid @d7grey; -} - -@keyframes pulse_animation { - 0% { transform: scale(1); } - 50% { transform: scale(0); } - 100% { transform: scale(1); } -} - -.SmartStatus-running { - height: 14px; - width: 14px; - background-color: @default-succ; - animation-name: pulse_animation; - animation-duration: 5000ms; - transform-origin:70% 70%; - animation-iteration-count: infinite; - animation-timing-function: linear; -} diff --git a/awx/ui/client/src/smart-status/smart-status.controller.js b/awx/ui/client/src/smart-status/smart-status.controller.js deleted file mode 100644 index a974e31ee30f..000000000000 --- a/awx/ui/client/src/smart-status/smart-status.controller.js +++ /dev/null @@ -1,112 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['$scope', '$filter', 'i18n', 'JobsStrings', - function ($scope, $filter, i18n, JobsStrings) { - - const strings = JobsStrings; - - function isFailureState(status) { - return status === 'failed' || status === 'error' || status === 'canceled'; - } - - function getTranslatedStatusString(status) { - switch (status) { - case 'new': - return strings.get('list.NEW'); - case 'pending': - return strings.get('list.PENDING'); - case 'waiting': - return strings.get('list.WAITING'); - case 'running': - return strings.get('list.RUNNING'); - case 'successful': - return strings.get('list.SUCCESSFUL'); - case 'failed': - return strings.get('list.FAILED'); - case 'error': - return strings.get('list.ERROR'); - case 'canceled': - return strings.get('list.CANCELED'); - default: - return status; - } - } - - function init(){ - var singleJobStatus = true; - var firstJobStatus; - var recentJobs = $scope.jobs; - var detailsBaseUrl; - - if(!recentJobs){ - return; - } - - var sparkData = - _.sortBy(recentJobs.map(function(job) { - const finished = $filter('longDate')(job.finished) || job.status+""; - - // We now get the job type of recent jobs associated with a JT - if (job.type === 'workflow_job') { - detailsBaseUrl = '/#/workflows/'; - } else if (job.type === 'job') { - detailsBaseUrl = '/#/jobs/playbook/'; - } - - const data = { - status: job.status, - jobId: job.id, - sortDate: job.finished || "running" + job.id, - finished: finished, - status_tip: `${i18n._('JOB ID')}: ${job.id}
${i18n._('STATUS')}: ${getTranslatedStatusString(job.status).toUpperCase()}
${i18n._('FINISHED')}: ${finished}`, - detailsUrl: detailsBaseUrl + job.id - }; - - // If we've already determined that there are both failed and successful jobs OR if the current job in the loop is - // pending/waiting/running then we don't worry about checking for a single job status - if(singleJobStatus && (isFailureState(job.status) || job.status === "successful")) { - if(firstJobStatus) { - // We've already been through at least once and have a first job status - if(!(isFailureState(firstJobStatus) && isFailureState(job.status) || firstJobStatus === job.status)) { - // We have a different status in the array - singleJobStatus = false; - } - } - else { - // We haven't set a first job status yet so go ahead set it - firstJobStatus = job.status; - } - } - - return data; - }), "sortDate").reverse(); - - $scope.singleJobStatus = singleJobStatus; - - $scope.sparkArray = sparkData; - $scope.placeholders = new Array(10 - sparkData.length); - } - $scope.$watchCollection('jobs', function(){ - init(); - }); - -}]; - -// -// -// JOB_STATUS_CHOICES = [ -// ('new', _('New')), # Job has been created, but not started. -// ('pending', _('Pending')), # Job has been queued, but is not yet running. -// ('waiting', _('Waiting')), # Job is waiting on an update/dependency. -// ('running', _('Running')), # Job is currently running. -// ('successful', _('Successful')), # Job completed successfully. -// ('failed', _('Failed')), # Job completed, but with failures. -// ('error', _('Error')), # The job was unable to run. -// ('canceled', _('Canceled')), # The job was canceled before completion. -// final states only***** -// ] -// diff --git a/awx/ui/client/src/smart-status/smart-status.directive.js b/awx/ui/client/src/smart-status/smart-status.directive.js deleted file mode 100644 index a4486e125ec7..000000000000 --- a/awx/ui/client/src/smart-status/smart-status.directive.js +++ /dev/null @@ -1,19 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import smartStatusController from './smart-status.controller'; -export default [ 'templateUrl', - function(templateUrl) { - return { - scope: { - jobs: '=', - templateType: '=?', - }, - templateUrl: templateUrl('smart-status/smart-status'), - restrict: 'E', - controller: smartStatusController - }; -}]; diff --git a/awx/ui/client/src/smart-status/smart-status.partial.html b/awx/ui/client/src/smart-status/smart-status.partial.html deleted file mode 100644 index ee3a157eaac5..000000000000 --- a/awx/ui/client/src/smart-status/smart-status.partial.html +++ /dev/null @@ -1,35 +0,0 @@ - diff --git a/awx/ui/client/src/standard-out/standard-out-factories/delete-job.factory.js b/awx/ui/client/src/standard-out/standard-out-factories/delete-job.factory.js deleted file mode 100644 index 6e28362f3a20..000000000000 --- a/awx/ui/client/src/standard-out/standard-out-factories/delete-job.factory.js +++ /dev/null @@ -1,145 +0,0 @@ -export default -function DeleteJob($state, Find, Rest, Wait, ProcessErrors, Prompt, Alert, - $filter, i18n) { - return function(params) { - var scope = params.scope, - id = params.id, - job = params.job, - callback = params.callback, - action, jobs, url, action_label, hdr; - - if (!job) { - if (scope.completed_jobs) { - jobs = scope.completed_jobs; - } - else if (scope.running_jobs) { - jobs = scope.running_jobs; - } - else if (scope.queued_jobs) { - jobs = scope.queued_jobs; - } - else if (scope.all_jobs) { - jobs = scope.all_jobs; - } - else if (scope.jobs) { - jobs = scope.jobs; - } - job = Find({list: jobs, key: 'id', val: id }); - } - - if (job.status === 'pending' || job.status === 'running' || job.status === 'waiting') { - url = job.related.cancel; - action_label = 'cancel'; - hdr = i18n._('Cancel'); - } else { - url = job.url; - action_label = 'delete'; - hdr = i18n._('Delete'); - } - - action = function () { - Wait('start'); - Rest.setUrl(url); - if (action_label === 'cancel') { - Rest.post() - .then(() => { - $('#prompt-modal').modal('hide'); - if (callback) { - scope.$emit(callback, action_label); - } - else { - $state.reload(); - Wait('stop'); - } - }) - .catch(({obj, status}) => { - Wait('stop'); - $('#prompt-modal').modal('hide'); - if (status === 403) { - Alert('Error', obj.detail); - } - // Ignore the error. The job most likely already finished. - // ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url + - // ' failed. POST returned status: ' + status }); - }); - } else { - Rest.destroy() - .then(() => { - $('#prompt-modal').modal('hide'); - if (callback) { - scope.$emit(callback, action_label); - } - else { - let reloadListStateParams = null; - - if(scope.jobs.length === 1 && $state.params.job_search && !_.isEmpty($state.params.job_search.page) && $state.params.job_search.page !== '1') { - reloadListStateParams = _.cloneDeep($state.params); - reloadListStateParams.job_search.page = (parseInt(reloadListStateParams.job_search.page)-1).toString(); - } - - $state.go('.', reloadListStateParams, {reload: true}); - Wait('stop'); - } - }) - .catch(({obj, status}) => { - Wait('stop'); - $('#prompt-modal').modal('hide'); - if (status === 403) { - Alert('Error', obj.detail); - } - // Ignore the error. The job most likely already finished. - //ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url + - // ' failed. DELETE returned status: ' + status }); - }); - } - }; - - if (scope.removeCancelNotAllowed) { - scope.removeCancelNotAllowed(); - } - scope.removeCancelNotAllowed = scope.$on('CancelNotAllowed', function() { - Wait('stop'); - Alert('Job Completed', 'The request to cancel the job could not be submitted. The job already completed.', 'alert-info'); - }); - - if (scope.removeCancelJob) { - scope.removeCancelJob(); - } - scope.removeCancelJob = scope.$on('CancelJob', function() { - var cancelBody = "
" + i18n._("Are you sure you want to submit the request to cancel this job?") + "
"; - var deleteBody = "
" + i18n._("Are you sure you want to delete this job?") + "
"; - Prompt({ - hdr: hdr, - resourceName: `#${job.id} ` + $filter('sanitize')(job.name), - body: (action_label === 'cancel' || job.status === 'new') ? cancelBody : deleteBody, - action: action, - actionText: (action_label === 'cancel' || job.status === 'new') ? i18n._("OK") : i18n._("DELETE") - }); - }); - - if (action_label === 'cancel') { - Rest.setUrl(url); - Rest.get() - .then(({data}) => { - if (data.can_cancel) { - scope.$emit('CancelJob'); - } - else { - scope.$emit('CancelNotAllowed'); - } - }) - .catch(({data, status}) => { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url + - ' failed. GET returned: ' + status }); - }); - } - else { - scope.$emit('CancelJob'); - } - }; -} - -DeleteJob.$inject = -[ '$state', 'Find', 'Rest', 'Wait', - 'ProcessErrors', 'Prompt', 'Alert', '$filter', 'i18n' -]; diff --git a/awx/ui/client/src/standard-out/standard-out-factories/main.js b/awx/ui/client/src/standard-out/standard-out-factories/main.js deleted file mode 100644 index 935c8dca375c..000000000000 --- a/awx/ui/client/src/standard-out/standard-out-factories/main.js +++ /dev/null @@ -1,13 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import lookUpName from './lookup-name.factory'; -import DeleteJob from './delete-job.factory'; - -export default - angular.module('StandardOutHelper', []) - .factory('LookUpName', lookUpName) - .factory('DeleteJob', DeleteJob); diff --git a/awx/ui/client/src/teams/add/teams-add.controller.js b/awx/ui/client/src/teams/add/teams-add.controller.js deleted file mode 100644 index e33c918a63b6..000000000000 --- a/awx/ui/client/src/teams/add/teams-add.controller.js +++ /dev/null @@ -1,66 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['$scope', '$rootScope', 'TeamForm', 'GenerateForm', 'Rest', - 'Alert', 'ProcessErrors', 'GetBasePath', 'Wait', '$state', - function($scope, $rootScope, TeamForm, GenerateForm, Rest, Alert, - ProcessErrors, GetBasePath, Wait, $state) { - - Rest.setUrl(GetBasePath('teams')); - Rest.options() - .then(({data}) => { - if (!data.actions.POST) { - $state.go("^"); - Alert('Permission Error', 'You do not have permission to add a team.', 'alert-info'); - } - }); - - // Inject dynamic view - var defaultUrl = GetBasePath('teams'), - form = TeamForm; - - init(); - - function init() { - $scope.canEditOrg = true; - // apply form definition's default field values - GenerateForm.applyDefaults(form, $scope); - - $rootScope.flashMessage = null; - } - - // Save - $scope.formSave = function() { - var fld, data; - GenerateForm.clearApiErrors($scope); - Wait('start'); - Rest.setUrl(defaultUrl); - data = {}; - for (fld in form.fields) { - data[fld] = $scope[fld]; - } - Rest.post(data) - .then(({data}) => { - Wait('stop'); - $rootScope.flashMessage = "New team successfully created!"; - $rootScope.$broadcast("EditIndicatorChange", "users", data.id); - $state.go('teams.edit', { team_id: data.id }, { reload: true }); - }) - .catch(({data, status}) => { - Wait('stop'); - ProcessErrors($scope, data, status, form, { - hdr: 'Error!', - msg: 'Failed to add new team. Post returned status: ' + - status - }); - }); - }; - - $scope.formCancel = function() { - $state.go('teams'); - }; - } -]; diff --git a/awx/ui/client/src/teams/edit/teams-edit.controller.js b/awx/ui/client/src/teams/edit/teams-edit.controller.js deleted file mode 100644 index db6eab8bee32..000000000000 --- a/awx/ui/client/src/teams/edit/teams-edit.controller.js +++ /dev/null @@ -1,101 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['$scope', '$rootScope', '$stateParams', 'TeamForm', 'Rest', - 'ProcessErrors', 'GetBasePath', 'Wait', '$state', 'OrgAdminLookup', 'resolvedModels', 'resourceData', - function($scope, $rootScope, $stateParams, TeamForm, Rest, ProcessErrors, - GetBasePath, Wait, $state, OrgAdminLookup, models, Dataset) { - - const { me } = models; - const { data } = Dataset; - const id = $stateParams.team_id; - const defaultUrl = GetBasePath('teams') + id; - let form = TeamForm; - - init(); - - function init() { - $scope.canEdit = me.get('summary_fields.user_capabilities.edit'); - $scope.isOrgAdmin = me.get('related.admin_of_organizations.count') > 0; - $scope.team_id = id; - _.forEach(form.fields, (value, key) => { - $scope[key] = data[key]; - }); - $scope.organization_name = data.summary_fields.organization.name; - - OrgAdminLookup.checkForAdminAccess({organization: data.organization}) - .then(function(canEditOrg){ - $scope.canEditOrg = canEditOrg; - }); - - $scope.team_obj = data; - - $scope.$watch('team_obj.summary_fields.user_capabilities.edit', function(val) { - $scope.canAdd = (val === false) ? false : true; - }); - } - - // prepares a data payload for a PUT request to the API - function processNewData(fields) { - var data = {}; - _.forEach(fields, function(value, key) { - if ($scope[key] !== '' && $scope[key] !== null && $scope[key] !== undefined) { - data[key] = $scope[key]; - } - }); - return data; - } - - $scope.formCancel = function() { - $state.go('teams', null, { reload: true }); - }; - - $scope.formSave = function() { - $rootScope.flashMessage = null; - if ($scope[form.name + '_form'].$valid) { - var data = processNewData(form.fields); - Rest.setUrl(defaultUrl); - Rest.put(data).then(() => { - $state.go($state.current, null, { reload: true }); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, null, { - hdr: 'Error!', - msg: 'Failed to retrieve user: ' + - $stateParams.id + '. GET status: ' + status - }); - }); - } - }; - - init(); - - $scope.redirectToResource = function(resource) { - let type = resource.summary_fields.resource_type.replace(/ /g , "_"); - var id = resource.related[type].split("/")[4]; - switch (type) { - case 'organization': - $state.go('organizations.edit', { "organization_id": id }, { reload: true }); - break; - case 'credential': - $state.go('credentials.edit', { "credential_id": id }, { reload: true }); - break; - case 'project': - $state.go('projects.edit', { "project_id": id }, { reload: true }); - break; - case 'inventory': - $state.go('inventories.edit', { "inventory_id": id }, { reload: true }); - break; - case 'job_template': - $state.go('templates.editJobTemplate', { "job_template_id": id }, { reload: true }); - break; - case 'workflow_job_template': - $state.go('templates.editWorkflowJobTemplate', { "workflow_job_template_id": id }, { reload: true }); - break; - } - }; - } -]; diff --git a/awx/ui/client/src/teams/list/teams-list.controller.js b/awx/ui/client/src/teams/list/teams-list.controller.js deleted file mode 100644 index 25826495a279..000000000000 --- a/awx/ui/client/src/teams/list/teams-list.controller.js +++ /dev/null @@ -1,86 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['$scope', 'Rest', 'TeamList', 'Prompt', - 'ProcessErrors', 'GetBasePath', 'Wait', '$state', '$filter', - 'rbacUiControlService', 'Dataset', 'resolvedModels', 'i18n', - function($scope, Rest, TeamList, Prompt, ProcessErrors, - GetBasePath, Wait, $state, $filter, rbacUiControlService, Dataset, models, i18n) { - - const { me } = models; - var list = TeamList, - defaultUrl = GetBasePath('teams'); - - init(); - - function init() { - $scope.canEdit = me.get('summary_fields.user_capabilities.edit'); - $scope.canAdd = false; - - rbacUiControlService.canAdd('teams') - .then(function(params) { - $scope.canAdd = params.canAdd; - }); - // search init - $scope.list = list; - $scope[`${list.iterator}_dataset`] = Dataset.data; - $scope[list.name] = $scope[`${list.iterator}_dataset`].results; - - $scope.selected = []; - } - - $scope.addTeam = function() { - $state.go('teams.add'); - }; - - $scope.editTeam = function(id) { - $state.go('teams.edit', { team_id: id }); - }; - - $scope.deleteTeam = function(id, name) { - - var action = function() { - Wait('start'); - var url = defaultUrl + id + '/'; - Rest.setUrl(url); - Rest.destroy() - .then(() => { - Wait('stop'); - $('#prompt-modal').modal('hide'); - - let reloadListStateParams = null; - - if($scope.teams.length === 1 && $state.params.team_search && _.has($state, 'params.team_search.page') && $state.params.team_search.page !== '1') { - reloadListStateParams = _.cloneDeep($state.params); - reloadListStateParams.team_search.page = (parseInt(reloadListStateParams.team_search.page)-1).toString(); - } - - if (parseInt($state.params.team_id) === id) { - $state.go('^', reloadListStateParams, { reload: true }); - } else { - $state.go('.', reloadListStateParams, { reload: true }); - } - }) - .catch(({data, status}) => { - Wait('stop'); - $('#prompt-modal').modal('hide'); - ProcessErrors($scope, data, status, null, { - hdr: 'Error!', - msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status - }); - }); - }; - - Prompt({ - hdr: i18n._('Delete'), - resourceName: $filter('sanitize')(name), - body: '
' + i18n._('Are you sure you want to delete this team?') + '
', - action: action, - actionText: i18n._('DELETE') - }); - }; - } -]; diff --git a/awx/ui/client/src/teams/main.js b/awx/ui/client/src/teams/main.js deleted file mode 100644 index 7140e9aaf938..000000000000 --- a/awx/ui/client/src/teams/main.js +++ /dev/null @@ -1,70 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import TeamsList from './list/teams-list.controller'; -import TeamsAdd from './add/teams-add.controller'; -import TeamsEdit from './edit/teams-edit.controller'; -import TeamList from './teams.list'; -import TeamForm from './teams.form'; -import { N_ } from '../i18n'; - -export default -angular.module('Teams', []) - .controller('TeamsList', TeamsList) - .controller('TeamsAdd', TeamsAdd) - .controller('TeamsEdit', TeamsEdit) - .factory('TeamList', TeamList) - .factory('TeamForm', TeamForm) - .config(['$stateProvider', 'stateDefinitionsProvider', - function($stateProvider, stateDefinitionsProvider) { - let stateDefinitions = stateDefinitionsProvider.$get(); - - // lazily generate a tree of substates which will replace this node in ui-router's stateRegistry - // see: stateDefinition.factory for usage documentation - $stateProvider.state({ - name: 'teams.**', - url: '/teams', - lazyLoad: () => stateDefinitions.generateTree({ - parent: 'teams', - modes: ['add', 'edit'], - list: 'TeamList', - form: 'TeamForm', - controllers: { - list: TeamsList, - add: TeamsAdd, - edit: TeamsEdit - }, - data: { - activityStream: true, - activityStreamTarget: 'team' - }, - resolve: { - edit: { - resolvedModels: ['MeModel', '$q', function(Me, $q) { - const promises = { - me: new Me('get').then((me) => me.extend('get', 'admin_of_organizations')) - }; - - return $q.all(promises); - }] - }, - list: { - resolvedModels: ['MeModel', '$q', function(Me, $q) { - const promises = { - me: new Me('get') - }; - - return $q.all(promises); - }] - } - }, - ncyBreadcrumb: { - label: N_('TEAMS') - } - }) - }); - } - ]); diff --git a/awx/ui/client/src/teams/teams.form.js b/awx/ui/client/src/teams/teams.form.js deleted file mode 100644 index 6cd3b5bc2657..000000000000 --- a/awx/ui/client/src/teams/teams.form.js +++ /dev/null @@ -1,178 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name forms.function:Teams - * @description This form is for adding/editing teams -*/ - -export default ['i18n', function(i18n) { - return { - - addTitle: i18n._('NEW TEAM'), //Legend in add mode - editTitle: '{{ name }}', //Legend in edit mode - name: 'team', - // the top-most node of generated state tree - stateTree: 'teams', - tabs: true, - messageBar: { - ngShow: 'isOrgAdmin && !canEdit', - message: i18n._("Contact your System Administrator to grant you the appropriate permissions to add and edit Users and Teams.") - }, - fields: { - name: { - label: i18n._('Name'), - type: 'text', - ngDisabled: '!(team_obj.summary_fields.user_capabilities.edit || canAdd)', - required: true, - capitalize: false - }, - description: { - label: i18n._('Description'), - type: 'text', - ngDisabled: '!(team_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - organization: { - label: i18n._('Organization'), - type: 'lookup', - list: 'OrganizationList', - sourceModel: 'organization', - basePath: 'organizations', - sourceField: 'name', - ngDisabled: '!(team_obj.summary_fields.user_capabilities.edit || canAdd) || !canEditOrg', - awLookupWhen: '(team_obj.summary_fields.user_capabilities.edit || canAdd) && canEditOrg', - required: true, - } - }, - - buttons: { - cancel: { - ngClick: 'formCancel()', - ngShow: '(team_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - close: { - ngClick: 'formCancel()', - ngShow: '!(team_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - save: { - ngClick: 'formSave()', - ngDisabled: true, - ngShow: '(team_obj.summary_fields.user_capabilities.edit || canAdd)' - } - }, - - related: { - users: { - name: 'users', - dataPlacement: 'top', - awToolTip: i18n._('Please save before adding users.'), - basePath: 'api/v2/teams/{{$stateParams.team_id}}/access_list/', - search: { - order_by: 'username' - }, - type: 'collection', - title: i18n._('Users'), - iterator: 'user', - index: false, - open: false, - actions: { - add: { - ngClick: "$state.go('.add')", - label: i18n._('Add'), - awToolTip: i18n._('Add User'), - actionClass: 'at-Button--add', - actionId: 'button-add', - ngShow: '(team_obj.summary_fields.user_capabilities.edit || canAdd)' - } - }, - - fields: { - username: { - key: true, - label: i18n._('User'), - linkBase: 'users', - columnClass: 'col-sm-3' - }, - first_name: { - label: i18n._('First Name'), - columnClass: 'col-sm-3' - }, - last_name: { - label: i18n._('Last Name'), - columnClass: 'col-sm-3' - }, - role: { - label: i18n._('Role'), - type: 'role', - nosort: true, - columnClass: 'col-sm-3' - } - } - }, - permissions: { - name: 'permissions', - basePath: 'api/v2/teams/{{$stateParams.team_id}}/roles/', - search: { - page_size: '10', - // @todo ask about name field / serializer on this endpoint - order_by: 'id' - }, - awToolTip: i18n._('Please save before assigning permissions.'), - dataPlacement: 'top', - hideSearchAndActions: true, - type: 'collection', - title: i18n._('Permissions'), - iterator: 'permission', - open: false, - index: false, - emptyListText: i18n._('No permissions have been granted'), - fields: { - name: { - label: i18n._('Name'), - ngBind: 'permission.summary_fields.resource_name', - ngClick: "redirectToResource(permission)", - nosort: true, - columnClass: 'col-sm-4' - }, - type: { - label: i18n._('Type'), - ngBind: 'permission.summary_fields.resource_type_display_name', - nosort: true, - columnClass: 'col-sm-3' - }, - role: { - label: i18n._('Role'), - ngBind: 'permission.name', - nosort: true, - columnClass: 'col-sm-3' - } - }, - fieldActions: { - columnClass: 'col-sm-2', - "delete": { - label: i18n._('Remove'), - ngClick: 'deletePermissionFromTeam(team_id, team_obj.name, permission.name, permission.summary_fields.resource_name, permission.related.teams)', - 'class': "List-actionButton--delete", - iconClass: 'fa fa-times', - awToolTip: i18n._('Dissassociate permission from team'), - dataPlacement: 'top', - ngShow: 'permission.summary_fields.user_capabilities.unattach' - } - }, - actions: { - add: { - ngClick: "$state.go('.add')", - label: i18n._('Add'), - awToolTip: i18n._('Grant Permission'), - actionClass: 'at-Button--add', - actionId: 'button-add', - ngShow: '(team_obj.summary_fields.user_capabilities.edit || canEditOrg)' - } - } - } - }, - };}]; diff --git a/awx/ui/client/src/teams/teams.list.js b/awx/ui/client/src/teams/teams.list.js deleted file mode 100644 index 11d0d9e74917..000000000000 --- a/awx/ui/client/src/teams/teams.list.js +++ /dev/null @@ -1,81 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - -export default ['i18n', function(i18n) { - return { - - name: 'teams', - iterator: 'team', - selectTitle: i18n._('Add Team'), - editTitle: i18n._('TEAMS'), - listTitle: i18n._('TEAMS'), - selectInstructions: i18n.sprintf(i18n._("Click on a row to select it, and click Finished when done. Click the %s button to create a new team."), " "), - index: false, - hover: true, - - fields: { - name: { - key: true, - label: i18n._('Name'), - columnClass: 'col-md-4 col-sm-9 col-xs-9', - modalColumnClass: 'col-md-8', - awToolTip: '{{team.description | sanitize}}', - dataPlacement: 'top' - }, - organization: { - label: i18n._('Organization'), - ngBind: 'team.summary_fields.organization.name', - sourceModel: 'organization', - sourceField: 'name', - columnClass: 'd-none d-md-flex col-md-4', - excludeModal: true - } - }, - - actions: { - add: { - mode: 'all', // One of: edit, select, all - ngClick: 'addTeam()', - awToolTip: i18n._('Create a new team'), - actionClass: 'at-Button--add', - actionId: 'button-add', - ngShow: 'canAdd && canEdit' - } - }, - - fieldActions: { - - columnClass: 'col-md-4 col-sm-3 col-xs-3', - - edit: { - label: i18n._('Edit'), - ngClick: "editTeam(team.id)", - icon: 'icon-edit', - "class": 'btn-xs btn-default', - awToolTip: i18n._('Edit team'), - dataPlacement: 'top', - ngShow: 'team.summary_fields.user_capabilities.edit' - }, - view: { - label: i18n._('View'), - ngClick: "editTeam(team.id)", - "class": 'btn-xs btn-default', - awToolTip: i18n._('View team'), - dataPlacement: 'top', - ngShow: '!team.summary_fields.user_capabilities.edit' - }, - "delete": { - label: i18n._('Delete'), - ngClick: "deleteTeam(team.id, team.name)", - icon: 'icon-trash', - "class": 'btn-xs btn-danger', - awToolTip: i18n._('Delete team'), - dataPlacement: 'top', - ngShow: 'team.summary_fields.user_capabilities.delete' - } - } - };}]; diff --git a/awx/ui/client/src/templates/inventory-sources.list.js b/awx/ui/client/src/templates/inventory-sources.list.js deleted file mode 100644 index 712a81835fa7..000000000000 --- a/awx/ui/client/src/templates/inventory-sources.list.js +++ /dev/null @@ -1,31 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - -export default { - name: 'workflow_inventory_sources', - iterator: 'inventory_source', - basePath: 'inventory_sources', - listTitle: 'INVENTORY SOURCES', - index: false, - hover: true, - searchBarFullWidth: true, - - fields: { - name: { - label: 'Name', - columnClass: 'col-md-11', - simpleTip: { - awToolTip: "Inventory: {{inventory_source.summary_fields.inventory.name}}", - dataPlacement: "top" - } - } - }, - - actions: {}, - - fieldActions: {} -}; diff --git a/awx/ui/client/src/templates/job_templates/add-job-template/job-template-add.controller.js b/awx/ui/client/src/templates/job_templates/add-job-template/job-template-add.controller.js deleted file mode 100644 index 6e855eb0a5cc..000000000000 --- a/awx/ui/client/src/templates/job_templates/add-job-template/job-template-add.controller.js +++ /dev/null @@ -1,705 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - export default - [ '$filter', '$scope', - '$stateParams', 'JobTemplateForm', 'GenerateForm', 'Rest', 'Alert', - 'ProcessErrors', 'GetBasePath', 'hashSetup', 'ParseTypeChange', 'Wait', - 'Empty', 'ToJSON', 'CallbackHelpInit', 'GetChoices', '$state', 'availableLabels', - 'CreateSelect2', '$q', 'i18n', 'Inventory', 'Project', 'InstanceGroupsService', - 'MultiCredentialService', 'ConfigData', 'resolvedModels', '$compile', - function( - $filter, $scope, - $stateParams, JobTemplateForm, GenerateForm, Rest, Alert, - ProcessErrors, GetBasePath, hashSetup, ParseTypeChange, Wait, - Empty, ToJSON, CallbackHelpInit, GetChoices, - $state, availableLabels, CreateSelect2, $q, i18n, Inventory, Project, InstanceGroupsService, - MultiCredentialService, ConfigData, resolvedModels, $compile - ) { - - // Inject dynamic view - let defaultUrl = GetBasePath('job_templates'), - form = JobTemplateForm(), - generator = GenerateForm, - master = {}, - selectPlaybook, checkSCMStatus, - callback; - - const jobTemplate = resolvedModels[0]; - - $scope.canAddJobTemplate = jobTemplate.options('actions.POST'); - $scope.disableLaunch = true; - - // apply form definition's default field values - GenerateForm.applyDefaults(form, $scope); - - $scope.can_edit = true; - $scope.allow_callbacks = false; - $scope.playbook_options = []; - $scope.webhook_service_options = []; - $scope.mode = "add"; - $scope.parseType = 'yaml'; - $scope.credentialNotPresent = false; - $scope.canGetAllRelatedResources = true; - $scope.webhook_key_help = i18n._('Webhook services can use this as a shared secret.'); - - // - // webhook credential - all handlers, dynamic state, etc. live here - // - - $scope.webhookCredential = { - id: null, - name: null, - isModalOpen: false, - isModalReady: false, - modalTitle: i18n._('Select Webhook Credential'), - modalBaseParams: { - order_by: 'name', - page_size: 5, - credential_type__namespace: null, - }, - modalSelectedId: null, - modalSelectedName: null, - }; - - $scope.handleWebhookCredentialLookupClick = () => { - $scope.webhookCredential.modalSelectedId = $scope.webhookCredential.id; - $scope.webhookCredential.isModalOpen = true; - }; - - $scope.handleWebhookCredentialTagDelete = () => { - $scope.webhookCredential.id = null; - $scope.webhookCredential.name = null; - }; - - $scope.handleWebhookCredentialModalClose = () => { - $scope.webhookCredential.isModalOpen = false; - $scope.webhookCredential.isModalReady = false; - }; - - $scope.handleWebhookCredentialModalReady = () => { - $scope.webhookCredential.isModalReady = true; - }; - - $scope.handleWebhookCredentialModalItemSelect = (item) => { - $scope.webhookCredential.modalSelectedId = item.id; - $scope.webhookCredential.modalSelectedName = item.name; - }; - - $scope.handleWebhookCredentialModalCancel = () => { - $scope.webhookCredential.isModalOpen = false; - $scope.webhookCredential.isModalReady = false; - $scope.webhookCredential.modalSelectedId = null; - $scope.webhookCredential.modalSelectedName = null; - - }; - - $scope.handleWebhookCredentialSelect = () => { - $scope.webhookCredential.isModalOpen = false; - $scope.webhookCredential.isModalReady = false; - $scope.webhookCredential.id = $scope.webhookCredential.modalSelectedId; - $scope.webhookCredential.name = $scope.webhookCredential.modalSelectedName; - $scope.webhookCredential.modalSelectedId = null; - $scope.webhookCredential.modalSelectedName = null; - }; - - $scope.handleWebhookKeyButtonClick = () => {}; - - $('#content-container').append($compile(` - - - - - ${i18n._('CANCEL')} - - - ${i18n._('SELECT')} - - - `)($scope)); - - $scope.$watch('webhook_service', (newValue, oldValue) => { - const newServiceValue = newValue && typeof newValue === 'object' ? newValue.value : newValue; - const oldServiceValue = oldValue && typeof oldValue === 'object' ? oldValue.value : oldValue; - if (newServiceValue !== oldServiceValue || newServiceValue === newValue) { - $scope.webhook_service = { value: newServiceValue }; - sync_webhook_service_select2(); - $scope.webhookCredential.modalBaseParams.credential_type__namespace = newServiceValue ? - `${newServiceValue}_token` - : null; - if (newServiceValue !== newValue || newValue === null) { - $scope.webhookCredential.id = null; - $scope.webhookCredential.name = null; - } - } - }); - - hashSetup({ - scope: $scope, - master: master, - check_field: 'allow_callbacks', - default_val: false - }); - CallbackHelpInit({ scope: $scope }); - // set initial vals for webhook checkbox - $scope.enable_webhook = false; - master.enable_webhook = false; - - $scope.surveyTooltip = i18n._('Please save before adding a survey to this job template.'); - - MultiCredentialService.getCredentialTypes() - .then(({ data }) => { - $scope.multiCredential = { - credentialTypes: data.results, - selectedCredentials: [] - }; - }); - - callback = function() { - // Make sure the form controller knows there was a change - $scope[form.name + '_form'].$setDirty(); - }; - - var selectCount = 0; - - if ($scope.removeChoicesReady) { - $scope.removeChoicesReady(); - } - $scope.removeChoicesReady = $scope.$on('choicesReadyVerbosity', function () { - ParseTypeChange({ - scope: $scope, - field_id: 'extra_vars', - variable: 'extra_vars', - onChange: callback - }); - - selectCount++; - if (selectCount === 3) { - var verbosity; - // this sets the default options for the selects as specified by the controller. - for (verbosity in $scope.verbosity_options) { - if ($scope.verbosity_options[verbosity].isDefault) { - $scope.verbosity = $scope.verbosity_options[verbosity]; - } - } - $scope.job_type = $scope.job_type_options[form.fields.job_type.default]; - const virtualEnvs = ConfigData.custom_virtualenvs || []; - $scope.custom_virtualenvs_options = virtualEnvs; - - CreateSelect2({ - element:'#job_template_job_type', - multiple: false - }); - CreateSelect2({ - element:'#job_template_labels', - multiple: true, - addNew: true - }); - CreateSelect2({ - element:'#playbook-select', - addNew: true, - multiple: false, - scope: $scope, - options: 'playbook_options', - model: 'playbook' - }); - CreateSelect2({ - element:'#job_template_verbosity', - multiple: false - }); - CreateSelect2({ - element:'#job_template_job_tags', - multiple: true, - addNew: true - }); - - CreateSelect2({ - element:'#job_template_skip_tags', - multiple: true, - addNew: true - }); - - CreateSelect2({ - element: '#job_template_custom_virtualenv', - multiple: false, - opts: $scope.custom_virtualenvs_options - }); - CreateSelect2({ - element:'#webhook-service-select', - addNew: false, - multiple: false, - scope: $scope, - options: 'webhook_service_options', - model: 'webhook_service' - }); - } - }); - - // setup verbosity options select - GetChoices({ - scope: $scope, - url: defaultUrl, - field: 'verbosity', - variable: 'verbosity_options', - callback: 'choicesReadyVerbosity' - }); - - // setup job type options select - GetChoices({ - scope: $scope, - url: defaultUrl, - field: 'job_type', - variable: 'job_type_options', - callback: 'choicesReadyVerbosity' - }); - GetChoices({ - scope: $scope, - url: defaultUrl, - field: 'webhook_service', - variable: 'webhook_service_options', - callback: 'choicesReadyVerbosity' - }); - $scope.labelOptions = availableLabels - .map((i) => ({label: i.name, value: i.id})); - $scope.$emit("choicesReadyVerbosity"); - - function sync_playbook_select2() { - CreateSelect2({ - element:'#playbook-select', - addNew: true, - multiple: false, - scope: $scope, - options: 'playbook_options', - model: 'playbook' - }); - } - - function sync_webhook_service_select2() { - CreateSelect2({ - element:'#webhook-service-select', - addNew: false, - multiple: false, - scope: $scope, - options: 'webhook_service_options', - model: 'webhook_service' - }); - } - - $scope.toggleForm = function(key) { - $scope[key] = !$scope[key]; - }; - - // Update playbook select whenever project value changes - selectPlaybook = function (oldValue, newValue) { - var url; - if (oldValue !== newValue) { - if ($scope.project) { - Wait('start'); - url = GetBasePath('projects') + $scope.project + '/playbooks/'; - Rest.setUrl(url); - Rest.get() - .then(({data}) => { - var i, opts = []; - for (i = 0; i < data.length; i++) { - opts.push(data[i]); - } - if ($scope.playbook && opts.indexOf($scope.playbook) === -1) { - opts.push($scope.playbook); - } - $scope.playbook_options = opts; - sync_playbook_select2(); - Wait('stop'); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, form, { hdr: 'Error!', - msg: 'Failed to get playbook list for ' + url + '. GET returned status: ' + status }); - }); - } - } - }; - - $scope.jobTypeChange = function() { - sync_playbook_select2(); - }; - - // Detect and alert user to potential SCM status issues - checkSCMStatus = function (oldValue, newValue) { - if ((oldValue !== newValue || (oldValue === undefined && newValue === undefined)) && !Empty($scope.project)) { - Rest.setUrl(GetBasePath('projects') + $scope.project + '/'); - Rest.get() - .then(({data}) => { - $scope.allow_branch_override = data.allow_override; - $scope.allow_playbook_selection = true; - selectPlaybook('force_load'); - - var msg; - switch (data.status) { - case 'failed': - msg = `
${i18n._('The Project selected has a status of')} \"${i18n._('failed')}\". ${i18n._('You must run a successful update before you can select a playbook. You will not be able to save this Job Template without a valid playbook.')}
`; - break; - case 'never updated': - msg = `
${i18n._('The Project selected has a status of')} \"${i18n._('never updated')}\". ${i18n._('You must run a successful update before you can select a playbook. You will not be able to save this Job Template without a valid playbook.')}
`; - break; - case 'missing': - msg = `
${i18n._('The selected project has a status of')} \"${i18n._('missing')}\". ${i18n._('Please check the server and make sure the directory exists and file permissions are set correctly.')}
`; - break; - } - if (msg) { - Alert(i18n._('Warning'), msg, 'alert-info alert-info--noTextTransform', null, null, null, null, true); - } - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, form, { hdr: 'Error!', - msg: 'Failed to get project ' + $scope.project + '. GET returned status: ' + status }); - }); - } else { - $scope.allow_playbook_selection = false; - } - }; - - if(Inventory){ - $scope.inventory = Inventory.id; - $scope.inventory_name = Inventory.name; - } - if(Project){ - $scope.project = Project.id; - $scope.project_name = Project.name; - selectPlaybook('force_load'); - checkSCMStatus(); - } - - // Register a watcher on project_name - if ($scope.selectPlaybookUnregister) { - $scope.selectPlaybookUnregister(); - } - $scope.selectPlaybookUnregister = $scope.$watch('project', function (newValue, oldValue) { - if (newValue !== oldValue) { - selectPlaybook(oldValue, newValue); - checkSCMStatus(); - } - }); - - if ($scope.removeSurveySaved) { - $scope.removeSurveySaved(); - } - $scope.removeSurveySaved = $scope.$on('SurveySaved', function() { - Wait('stop'); - $scope.survey_exists = true; - $scope.invalid_survey = false; - }); - - - function saveCompleted(id) { - $state.go('templates.editJobTemplate', {job_template_id: id}, {reload: true}); - } - - // Save - $scope.formSave = function () { - var fld, data = {}; - $scope.invalid_survey = false; - - // Can't have a survey enabled without a survey - if($scope.survey_enabled === true && - $scope.survey_exists !== true){ - $scope.survey_enabled = false; - } - - generator.clearApiErrors($scope); - - Wait('start'); - - try { - for (fld in form.fields) { - if (form.fields[fld].type === 'select' && - fld !== 'playbook' && fld !== 'custom_virtualenv' && $scope[fld]) { - data[fld] = $scope[fld].value; - } - else if(form.fields[fld].type === 'checkbox_group') { - // Loop across the checkboxes - for(var i=0; i option").filter("[data-select2-tag=true]").each(function(optionIndex, option) { - $("#job_template_labels").siblings(".select2").first().find(".select2-selection__choice").each(function(labelIndex, label) { - if($(option).text() === $(label).attr('title')) { - // Mark that the option has a label present so that we can filter by that down below - $(option).attr('data-label-is-present', true); - } - }); - }); - - $scope.newLabels = $("#job_template_labels > option") - .filter("[data-select2-tag=true]") - .filter("[data-label-is-present=true]") - .map((i, val) => ({name: $(val).text()})); - - $scope.job_tags = _.map($scope.job_tags, function(i){return i.value;}); - $("#job_template_job_tags").siblings(".select2").first().find(".select2-selection__choice").each(function(optionIndex, option){ - $scope.job_tags.push(option.title); - }); - - $scope.skip_tags = _.map($scope.skip_tags, function(i){return i.value;}); - $("#job_template_skip_tags").siblings(".select2").first().find(".select2-selection__choice").each(function(optionIndex, option){ - $scope.skip_tags.push(option.title); - }); - - data.job_tags = (Array.isArray($scope.job_tags)) ? _.uniq($scope.job_tags).join() : ""; - data.skip_tags = (Array.isArray($scope.skip_tags)) ? _.uniq($scope.skip_tags).join() : ""; - - Rest.setUrl(defaultUrl); - Rest.post(data) - .then(({data}) => { - if (data.related && data.related.callback) { - Alert('Callback URL', - `Host callbacks are enabled for this template. The callback URL is: -

- - ${$scope.callback_server_path} - ${data.related.callback} - -

-

The host configuration key is: - - ${$filter('sanitize')(data.host_config_key)} - -

`, - 'alert-danger', saveCompleted, null, null, - null, true); - } - - var orgDefer = $q.defer(); - var associationDefer = $q.defer(); - Rest.setUrl(data.related.labels); - - var currentLabels = Rest.get() - .then(function(data) { - return data.data.results - .map(val => val.id); - }); - - currentLabels.then(function (current) { - var labelsToAdd = ($scope.labels || []) - .map(val => val.value); - var labelsToDisassociate = current - .filter(val => labelsToAdd - .indexOf(val) === -1) - .map(val => ({id: val, disassociate: true})); - var labelsToAssociate = labelsToAdd - .filter(val => current - .indexOf(val) === -1) - .map(val => ({id: val, associate: true})); - var pass = labelsToDisassociate - .concat(labelsToAssociate); - associationDefer.resolve(pass); - }); - - Rest.setUrl(GetBasePath("organizations")); - Rest.get() - .then(({data}) => { - orgDefer.resolve(data.results[0].id); - }); - - orgDefer.promise.then(function(orgId) { - var toPost = []; - $scope.newLabels = $scope.newLabels - .map(function(i, val) { - val.organization = orgId; - return val; - }); - - $scope.newLabels.each(function(i, val) { - toPost.push(val); - }); - - associationDefer.promise.then(function(arr) { - toPost = toPost - .concat(arr); - - Rest.setUrl(data.related.labels); - - var defers = []; - for (var i = 0; i < toPost.length; i++) { - defers.push(Rest.post(toPost[i])); - } - $q.all(defers) - .then(function() { - $scope.addedItem = data.id; - - if($scope.survey_questions && - $scope.survey_questions.length > 0){ - //once the job template information - // is saved we submit the survey - // info to the correct endpoint - var url = data.url+ 'survey_spec/'; - Rest.setUrl(url); - Rest.post({ name: $scope.survey_name, - description: $scope.survey_description, - spec: $scope.survey_questions }) - .then(() => { - Wait('stop'); - }) - .error(function (data, - status) { - ProcessErrors( - $scope, - data, - status, - form, - { - hdr: 'Error!', - msg: 'Failed to add new ' + - 'survey. Post returned ' + - 'status: ' + - status - }); - }); - } - - MultiCredentialService - .saveRelated(data, $scope.multiCredential.selectedCredentials) - .then(() => saveCompleted(data.id)); - }); - }); - }); - - const instance_group_url = data.related.instance_groups; - InstanceGroupsService.addInstanceGroups(instance_group_url, $scope.instance_groups) - .then(() => { - Wait('stop'); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, form, { - hdr: 'Error!', - msg: 'Failed to post instance groups. POST returned ' + - 'status: ' + status - }); - }); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, form, { - hdr: 'Error!', - msg: 'Failed to add new job ' + - 'template. POST returned status: ' + status - }); - }); - } catch (err) { - Wait('stop'); - Alert("Error", "Error parsing extra variables. " + - "Parser returned: " + err); - } - }; - - $scope.formCancel = function () { - $state.transitionTo('templates'); - }; - - let handleLabelCount = () => { - /** - * This block of code specifically handles the client-side validation of the `labels` field. - * Due to it's detached nature in relation to the other job template fields, we must - * validate this field client-side in order to avoid the edge case where a user can make a - * successful POST to the `job_templates` endpoint but however encounter a 200 error from - * the `labels` endpoint due to a character limit. - * - * We leverage two of select2's available events, `select` and `unselect`, to detect when the user - * has either added or removed a label. From there, we set a flag and do simple string length - * checks to make sure a label's chacacter count remains under 512. Otherwise, we disable the "Save" button - * by invalidating the field and inform the user of the error. - */ - - - $scope.job_template_labels_isValid = true; - const maxCount = 512; - const jt_label_id = 'job_template_labels'; - - // Detect when a new label is added - $(`#${jt_label_id}`).on('select2:select', (e) => { - const { text } = e.params.data; - - // If the character count of an added label is greater than 512, we set `labels` field as invalid - if (text.length > maxCount) { - $scope.job_template_form.labels.$setValidity(`${jt_label_id}`, false); - $scope.job_template_labels_isValid = false; - } - }); - - // Detect when a label is removed - $(`#${jt_label_id}`).on('select2:unselect', (e) => { - const { text } = e.params.data; - - /* If the character count of a removed label is greater than 512 AND the field is currently marked - as invalid, we set it back to valid */ - if (text.length > maxCount && $scope.job_template_form.labels.$error) { - $scope.job_template_form.labels.$setValidity(`${jt_label_id}`, true); - $scope.job_template_labels_isValid = true; - } - }); - }; - - handleLabelCount(); - } - ]; diff --git a/awx/ui/client/src/templates/job_templates/add-job-template/main.js b/awx/ui/client/src/templates/job_templates/add-job-template/main.js deleted file mode 100644 index fedbed489819..000000000000 --- a/awx/ui/client/src/templates/job_templates/add-job-template/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import controller from './job-template-add.controller'; - -export default - angular.module('jobTemplateAdd', []) - .controller('JobTemplateAdd', controller); diff --git a/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js b/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js deleted file mode 100644 index d27282d16282..000000000000 --- a/awx/ui/client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js +++ /dev/null @@ -1,987 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name controllers.function:JobTemplatesEdit - * @description This controller's for Job Template Edit -*/ - -export default - [ '$filter', '$scope', - '$stateParams', 'JobTemplateForm', 'GenerateForm', - 'Rest', 'Alert', 'ProcessErrors', 'GetBasePath', 'hashSetup', - 'ParseTypeChange', 'Wait', 'selectedLabels', 'i18n', - 'Empty', 'ToJSON', 'GetChoices', 'CallbackHelpInit', - 'initSurvey', '$state', 'CreateSelect2', 'isNotificationAdmin', - 'ToggleNotification','$q', 'InstanceGroupsService', 'InstanceGroupsData', - 'MultiCredentialService', 'availableLabels', 'projectGetPermissionDenied', - 'inventoryGetPermissionDenied', 'jobTemplateData', 'ParseVariableString', 'ConfigData', '$compile', 'webhookKey', - function( - $filter, $scope, - $stateParams, JobTemplateForm, GenerateForm, Rest, Alert, - ProcessErrors, GetBasePath, hashSetup, - ParseTypeChange, Wait, selectedLabels, i18n, - Empty, ToJSON, GetChoices, CallbackHelpInit, - SurveyControllerInit, $state, CreateSelect2, isNotificationAdmin, - ToggleNotification, $q, InstanceGroupsService, InstanceGroupsData, - MultiCredentialService, availableLabels, projectGetPermissionDenied, - inventoryGetPermissionDenied, jobTemplateData, ParseVariableString, ConfigData, $compile, webhookKey - ) { - - $scope.$watch('job_template_obj.summary_fields.user_capabilities.edit', function(val) { - if (val === false) { - $scope.canAddJobTemplate = false; - } - }); - - let defaultUrl = GetBasePath('job_templates'), - generator = GenerateForm, - form = JobTemplateForm(), - master = {}, - id = $stateParams.job_template_id, - callback, - choicesCount = 0, - instance_group_url = defaultUrl + id + '/instance_groups', - select2LoadDefer = [], - launchHasBeenEnabled = false; - - init(); - function init() { - - CallbackHelpInit({ scope: $scope }); - - // To toggle notifications a user needs to have a read role on the JT - // _and_ have at least a notification template admin role on an org. - // If the user has gotten this far it's safe to say they have - // at least read access to the JT - $scope.sufficientRoleForNotifToggle = isNotificationAdmin; - $scope.sufficientRoleForNotif = isNotificationAdmin || $scope.user_is_system_auditor; - $scope.playbook_options = null; - $scope.webhook_service_options = null; - $scope.playbook = null; - $scope.webhook_service = jobTemplateData.webhook_service; - $scope.webhook_url = ''; - $scope.mode = 'edit'; - $scope.parseType = 'yaml'; - $scope.showJobType = false; - $scope.instance_groups = InstanceGroupsData; - $scope.credentialNotPresent = false; - $scope.surveyTooltip = i18n._('Surveys allow users to be prompted at job launch with a series of questions related to the job. This allows for variables to be defined that affect the playbook run at time of launch.'); - $scope.job_tag_options = []; - $scope.skip_tag_options = []; - const virtualEnvs = ConfigData.custom_virtualenvs || []; - $scope.custom_virtualenvs_options = virtualEnvs; - $scope.webhook_url_help = i18n._('Webhook services can launch jobs with this job template by making a POST request to this URL.'); - $scope.webhook_key_help = i18n._('Webhook services can use this as a shared secret.'); - - $scope.currentlySavedWebhookKey = webhookKey; - $scope.webhook_key = webhookKey; - - // - // webhook credential - all handlers, dynamic state, etc. live here - // - - $scope.webhookCredential = { - id: _.get(jobTemplateData, ['summary_fields', 'webhook_credential', 'id']), - name: _.get(jobTemplateData, ['summary_fields', 'webhook_credential', 'name']), - isModalOpen: false, - isModalReady: false, - modalSelectedId: null, - modalSelectedName: null, - modalBaseParams: { - order_by: 'name', - page_size: 5, - credential_type__namespace: `${jobTemplateData.webhook_service}_token`, - }, - modalTitle: i18n._('Select Webhook Credential'), - }; - - $scope.handleWebhookCredentialLookupClick = () => { - $scope.webhookCredential.modalSelectedId = $scope.webhookCredential.id; - $scope.webhookCredential.isModalOpen = true; - }; - - $scope.handleWebhookCredentialTagDelete = () => { - $scope.webhookCredential.id = null; - $scope.webhookCredential.name = null; - }; - - $scope.handleWebhookCredentialModalClose = () => { - $scope.webhookCredential.isModalOpen = false; - $scope.webhookCredential.isModalReady = false; - }; - - $scope.handleWebhookCredentialModalReady = () => { - $scope.webhookCredential.isModalReady = true; - }; - - $scope.handleWebhookCredentialModalItemSelect = (item) => { - $scope.webhookCredential.modalSelectedId = item.id; - $scope.webhookCredential.modalSelectedName = item.name; - }; - - $scope.handleWebhookCredentialModalCancel = () => { - $scope.webhookCredential.isModalOpen = false; - $scope.webhookCredential.isModalReady = false; - $scope.webhookCredential.modalSelectedId = null; - $scope.webhookCredential.modalSelectedName = null; - }; - - $scope.handleWebhookCredentialSelect = () => { - $scope.webhookCredential.isModalOpen = false; - $scope.webhookCredential.isModalReady = false; - $scope.webhookCredential.id = $scope.webhookCredential.modalSelectedId; - $scope.webhookCredential.name = $scope.webhookCredential.modalSelectedName; - $scope.webhookCredential.modalSelectedId = null; - $scope.webhookCredential.modalSelectedName = null; - }; - - $scope.handleWebhookKeyButtonClick = () => { - Rest.setUrl(jobTemplateData.related.webhook_key); - Wait('start'); - Rest.post({}) - .then(({ data }) => { - $scope.currentlySavedWebhookKey = data.webhook_key; - $scope.webhook_key = data.webhook_key; - }) - .catch(({ data }) => { - const errorMsg = `Failed to generate new webhook key. POST returned status: ${status}`; - ProcessErrors($scope, data, status, form, { hdr: 'Error!', msg: errorMsg }); - }) - .finally(() => { - Wait('stop'); - }); - }; - - $('#content-container').append($compile(` - - - - - ${i18n._('CANCEL')} - - - ${i18n._('SELECT')} - - - `)($scope)); - - $scope.$watch('webhook_service', (newValue, oldValue) => { - const newServiceValue = newValue && typeof newValue === 'object' ? newValue.value : newValue; - const oldServiceValue = oldValue && typeof oldValue === 'object' ? oldValue.value : oldValue; - if (newServiceValue) { - $scope.webhook_url = `${$scope.callback_server_path}${jobTemplateData.url}${newServiceValue}/`; - } else { - $scope.webhook_url = ''; - $scope.webhook_key = ''; - } - if (newServiceValue !== oldServiceValue || newServiceValue === newValue) { - $scope.webhook_service = { value: newServiceValue }; - sync_webhook_service_select2(); - $scope.webhookCredential.modalBaseParams.credential_type__namespace = newServiceValue ? - `${newServiceValue}_token` : null; - if (newServiceValue !== newValue || newValue === null) { - $scope.webhookCredential.id = null; - $scope.webhookCredential.name = null; - } - if (newServiceValue !== newValue) { - if (newServiceValue === jobTemplateData.webhook_service) { - $scope.webhook_key = $scope.currentlySavedWebhookKey; - } else { - $scope.webhook_key = i18n._('A NEW WEBHOOK KEY WILL BE GENERATED ON SAVE'); - } - } - } - }); - - $scope.$watch('verbosity', sync_verbosity_select2); - - SurveyControllerInit({ - scope: $scope, - parent_scope: $scope, - id: id, - templateType: 'job_template' - }); - - $scope.$watch('project', function (newValue, oldValue) { - if (newValue !== oldValue) { - if (oldValue) { - $scope.scm_branch = null; - $scope.ask_scm_branch_on_launch = false; - } - - var url; - if ($scope.playbook) { - $scope.playbook_options = [$scope.playbook]; - } - - if (!Empty($scope.project) && $scope.job_template_obj.summary_fields.user_capabilities.edit) { - let promises = []; - url = GetBasePath('projects') + $scope.project + '/playbooks/'; - Wait('start'); - Rest.setUrl(url); - promises.push(Rest.get() - .then(({data}) => { - $scope.disablePlaybookBecausePermissionDenied = false; - $scope.playbook_options = []; - var playbookNotFound = true; - for (var i = 0; i < data.length; i++) { - $scope.playbook_options.push(data[i]); - if (data[i] === $scope.playbook) { - $scope.job_template_form.playbook.$setValidity('required', true); - playbookNotFound = false; - } - } - if ($scope.playbook && $scope.playbook_options.indexOf($scope.playbook) === -1) { - $scope.playbook_options.push($scope.playbook); - } - $scope.playbookNotFound = playbookNotFound; - $scope.allow_playbook_selection = true; - sync_playbook_select2(); - if ($scope.playbook) { - jobTemplateLoadFinished(); - } - }) - .catch( (error) => { - if (error.status === 403) { - /* user doesn't have access to see the project, no big deal. */ - $scope.disablePlaybookBecausePermissionDenied = true; - } else { - Alert('Missing Playbooks', 'Unable to retrieve the list of playbooks for this project. Choose a different ' + - ' project or make the playbooks available on the file system.', 'alert-info'); - } - Wait('stop'); - })); - - - Rest.setUrl(GetBasePath('projects') + $scope.project + '/'); - promises.push(Rest.get() - .then(({data}) => { - $scope.allow_branch_override = data.allow_override; - var msg; - switch (data.status) { - case 'failed': - msg = "
The Project selected has a status of \"failed\". You must run a successful update before you can select a playbook. You will not be able to save this Job Template without a valid playbook."; - break; - case 'never updated': - msg = "
The Project selected has a status of \"never updated\". You must run a successful update before you can select a playbook. You will not be able to save this Job Template without a valid playbook."; - break; - case 'missing': - msg = '
The selected project has a status of \"missing\". Please check the server and make sure ' + - ' the directory exists and file permissions are set correctly.
'; - break; - } - if (msg) { - Alert('Warning', msg, 'alert-info alert-info--noTextTransform', null, null, null, null, true); - } - }) - .catch(({data, status}) => { - if (status === 403) { - /* User doesn't have read access to the project, no problem. */ - } else { - ProcessErrors($scope, data, status, form, { hdr: 'Error!', msg: 'Failed to get project ' + $scope.project + - '. GET returned status: ' + status }); - } - })); - - $q.all(promises) - .then(function(){ - Wait('stop'); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, null, { - hdr: 'Error!', - msg: 'Call failed. Returned status: ' + status - }); - }); - } - } - }); - } - - callback = function() { - // Make sure the form controller knows there was a change - $scope[form.name + '_form'].$setDirty(); - }; - - function sync_playbook_select2() { - select2LoadDefer.push(CreateSelect2({ - element:'#playbook-select', - addNew: true, - multiple: false, - scope: $scope, - options: 'playbook_options', - model: 'playbook' - })); - } - - function sync_verbosity_select2() { - select2LoadDefer.push(CreateSelect2({ - element:'#job_template_verbosity', - multiple: false - })); - } - - function sync_webhook_service_select2() { - select2LoadDefer.push(CreateSelect2({ - element:'#webhook-service-select', - addNew: false, - multiple: false, - scope: $scope, - options: 'webhook_service_options', - model: 'webhook_service' - })); - } - - function jobTemplateLoadFinished(){ - select2LoadDefer.push(CreateSelect2({ - element:'#job_template_job_type', - multiple: false - })); - - select2LoadDefer.push(CreateSelect2({ - element:'#job_template_job_tags', - multiple: true, - addNew: true - })); - - select2LoadDefer.push(CreateSelect2({ - element:'#job_template_skip_tags', - multiple: true, - addNew: true - })); - - select2LoadDefer.push(CreateSelect2({ - element: '#job_template_custom_virtualenv', - multiple: false, - opts: $scope.custom_virtualenvs_options - })); - select2LoadDefer.push(CreateSelect2({ - element:'#webhook-service-select', - addNew: false, - multiple: false, - scope: $scope, - options: 'webhook_service_options', - model: 'webhook_service' - })); - - if (!launchHasBeenEnabled) { - $q.all(select2LoadDefer).then(() => { - // updates based on lookups will initially set the form as dirty. - // we need to set it as pristine when it contains the values given by the api - // so that we can enable launching when the two are the same - $scope.job_template_form.$setPristine(); - // this is used to set the overall form as dirty for the values - // that don't actually set this internally (lookups, toggles and code mirrors). - $scope.$watchCollection('multiCredential.selectedCredentials', (val, prevVal) => { - if (!_.isEqual(val, prevVal)) { - $scope.job_template_form.$setDirty(); - } - }); - $scope.$watchGroup([ - 'inventory', - 'project', - 'extra_vars', - 'diff_mode', - 'instance_groups' - ], (val, prevVal) => { - if (!_.isEqual(val, prevVal)) { - $scope.job_template_form.$setDirty(); - } - }); - }); - } - } - - $scope.toggleForm = function(key) { - $scope[key] = !$scope[key]; - }; - - $scope.jobTypeChange = function() { - sync_playbook_select2(); - }; - - $scope.toggleNotification = function(event, notifier_id, column) { - var notifier = this.notification; - try { - $(event.target).tooltip('hide'); - } - catch(e) { - // ignore - } - ToggleNotification({ - scope: $scope, - url: defaultUrl + id, - notifier: notifier, - column: column, - callback: 'NotificationRefresh' - }); - }; - - // Retrieve each related set and populate the playbook list - if ($scope.jobTemplateLoadedRemove) { - $scope.jobTemplateLoadedRemove(); - } - $scope.jobTemplateLoadedRemove = $scope.$on('jobTemplateLoaded', function (e, masterObject) { - var dft; - - master = masterObject; - - dft = ($scope.host_config_key === "" || $scope.host_config_key === null) ? false : true; - hashSetup({ - scope: $scope, - master: master, - check_field: 'allow_callbacks', - default_val: dft - }); - - // set initial vals for webhook checkbox - if (jobTemplateData.webhook_service) { - $scope.enable_webhook = true; - master.enable_webhook = true; - } else { - $scope.enable_webhook = false; - master.enable_webhook = false; - } - - ParseTypeChange({ - scope: $scope, - field_id: 'extra_vars', - variable: 'extra_vars', - onChange: callback, - readOnly: !$scope.job_template_obj.summary_fields.user_capabilities.edit - }); - jobTemplateLoadFinished(); - launchHasBeenEnabled = true; - }); - - Wait('start'); - - if ($scope.removeSurveySaved) { - $scope.removeSurveySaved(); - } - $scope.removeSurveySaved = $scope.$on('SurveySaved', function() { - Wait('stop'); - $scope.survey_exists = true; - $scope.invalid_survey = false; - }); - - if ($scope.removeLoadJobs) { - $scope.rmoveLoadJobs(); - } - $scope.removeLoadJobs = $scope.$on('LoadJobs', function() { - $scope.job_template_obj = jobTemplateData; - $scope.name = jobTemplateData.name; - $scope.breadcrumb.job_template_name = jobTemplateData.name; - var fld, i; - for (fld in form.fields) { - if (fld !== 'extra_vars' && fld !== 'survey' && jobTemplateData[fld] !== null && jobTemplateData[fld] !== undefined) { - if (form.fields[fld].type === 'select') { - if ($scope[fld + '_options'] && $scope[fld + '_options'].length > 0) { - for (i = 0; i < $scope[fld + '_options'].length; i++) { - if (jobTemplateData[fld] === $scope[fld + '_options'][i].value) { - $scope[fld] = $scope[fld + '_options'][i]; - } - } - } else { - $scope[fld] = jobTemplateData[fld]; - } - } else { - $scope[fld] = jobTemplateData[fld]; - if(!Empty(jobTemplateData.summary_fields.survey)) { - $scope.survey_exists = true; - } - } - master[fld] = $scope[fld]; - } - if (fld === 'extra_vars') { - // Parse extra_vars, converting to YAML. - $scope.extra_vars = ParseVariableString(jobTemplateData.extra_vars); - master.extra_vars = $scope.extra_vars; - } - if (form.fields[fld].type === 'lookup' && jobTemplateData.summary_fields[form.fields[fld].sourceModel]) { - $scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] = - jobTemplateData.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField]; - master[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] = - $scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField]; - } - if (form.fields[fld].type === 'checkbox_group') { - for(var j=0; j ({name: i, label: i, value: i})) : []; - $scope.job_tags = $scope.job_tag_options; - master.job_tags = $scope.job_tags; - - $scope.skip_tag_options = (jobTemplateData.skip_tags) ? jobTemplateData.skip_tags.split(',') - .map((i) => ({name: i, label: i, value: i})) : []; - $scope.skip_tags = $scope.skip_tag_options; - master.skip_tags = $scope.skip_tags; - - $scope.ask_job_type_on_launch = (jobTemplateData.ask_job_type_on_launch) ? true : false; - master.ask_job_type_on_launch = $scope.ask_job_type_on_launch; - - $scope.ask_inventory_on_launch = (jobTemplateData.ask_inventory_on_launch) ? true : false; - master.ask_inventory_on_launch = $scope.ask_inventory_on_launch; - - $scope.ask_credential_on_launch = (jobTemplateData.ask_credential_on_launch) ? true : false; - master.ask_credential_on_launch = $scope.ask_credential_on_launch; - - if (jobTemplateData.host_config_key) { - $scope.example_config_key = jobTemplateData.host_config_key; - } - $scope.example_template_id = id; - $scope.setCallbackHelp(); - - $scope.callback_url = $scope.callback_server_path + ((jobTemplateData.related.callback) ? jobTemplateData.related.callback : - GetBasePath('job_templates') + id + '/callback/'); - master.callback_url = $scope.callback_url; - - $scope.can_edit = jobTemplateData.summary_fields.user_capabilities.edit; - - const multiCredential = {}; - const credentialTypesPromise = MultiCredentialService.getCredentialTypes() - .then(({ data }) => { - multiCredential.credentialTypes = data.results; - }); - const multiCredentialPromises = [credentialTypesPromise]; - - if ($scope.can_edit) { - const selectedCredentialsPromise = MultiCredentialService - .getRelated(jobTemplateData, { permitted: [403] }) - .then(({ data, status }) => { - if (status === 403) { - $scope.canGetAllRelatedResources = false; - multiCredential.selectedCredentials = _.get(jobTemplateData, 'summary_fields.credentials'); - } else { - $scope.canGetAllRelatedResources = !projectGetPermissionDenied && !inventoryGetPermissionDenied; - multiCredential.selectedCredentials = data.results; - } - }); - - multiCredentialPromises.push(selectedCredentialsPromise); - } else { - $scope.canGetAllRelatedResources = false; - multiCredential.selectedCredentials = _.get(jobTemplateData, 'summary_fields.credentials'); - } - - $q.all(multiCredentialPromises) - .then(() => { - $scope.multiCredential = multiCredential; - $scope.$emit('jobTemplateLoaded', master); - }); - }); - - if ($scope.removeChoicesReady) { - $scope.removeChoicesReady(); - } - $scope.removeChoicesReady = $scope.$on('choicesReady', function() { - choicesCount++; - if (choicesCount === 5) { - $scope.$emit('LoadJobs'); - } - }); - - GetChoices({ - scope: $scope, - url: GetBasePath('unified_jobs'), - field: 'status', - variable: 'status_choices', - callback: 'choicesReady' - }); - - GetChoices({ - scope: $scope, - url: GetBasePath('unified_jobs'), - field: 'type', - variable: 'type_choices', - callback: 'choicesReady' - }); - - // setup verbosity options lookup - GetChoices({ - scope: $scope, - url: defaultUrl, - field: 'verbosity', - variable: 'verbosity_options', - callback: 'choicesReady' - }); - sync_verbosity_select2(); - - // setup job type options lookup - GetChoices({ - scope: $scope, - url: defaultUrl, - field: 'job_type', - variable: 'job_type_options', - callback: 'choicesReady' - }); - - GetChoices({ - scope: $scope, - url: defaultUrl, - field: 'webhook_service', - variable: 'webhook_service_options', - callback: 'choicesReady' - }); - - $scope.labelOptions = availableLabels - .map((i) => ({label: i.name, value: i.id})); - - var opts = selectedLabels - .map(i => ({id: i.id + "", - test: i.name})); - - select2LoadDefer.push(CreateSelect2({ - element:'#job_template_labels', - multiple: true, - addNew: true, - opts: opts - })); - - $scope.$emit("choicesReady"); - - function saveCompleted() { - $state.go($state.current, {}, {reload: true}); - } - - if ($scope.removeTemplateSaveSuccess) { - $scope.removeTemplateSaveSuccess(); - } - $scope.removeTemplateSaveSuccess = $scope.$on('templateSaveSuccess', function(e, data) { - - if (data.related && - data.related.callback) { - Alert('Callback URL', -`Host callbacks are enabled for this template. The callback URL is: -

- - ${$scope.callback_server_path}${data.related.callback} - -

-

The host configuration key is: - - ${$filter('sanitize')(data.host_config_key)} - -

-`, - 'alert-danger', saveCompleted, null, null, - null, true); - } - - var credDefer = MultiCredentialService - .saveRelated(jobTemplateData, $scope.multiCredential.selectedCredentials); - - const instanceGroupDefer = InstanceGroupsService.editInstanceGroups(instance_group_url, $scope.instance_groups) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, form, { - hdr: 'Error!', - msg: 'Failed to update instance groups. POST returned status: ' + status - }); - }); - - var orgDefer = $q.defer(); - var associationDefer = $q.defer(); - var associatedLabelsDefer = $q.defer(); - - var getNext = function(data, arr, resolve) { - Rest.setUrl(data.next); - Rest.get() - .then(({data}) => { - if (data.next) { - getNext(data, arr.concat(data.results), resolve); - } else { - resolve.resolve(arr.concat(data.results)); - } - }); - }; - - Rest.setUrl(data.related.labels); - - Rest.get() - .then(({data}) => { - if (data.next) { - getNext(data, data.results, associatedLabelsDefer); - } else { - associatedLabelsDefer.resolve(data.results); - } - }); - - associatedLabelsDefer.promise.then(function (current) { - current = current.map(data => data.id); - var labelsToAdd = $scope.labels - .map(val => val.value); - var labelsToDisassociate = current - .filter(val => labelsToAdd - .indexOf(val) === -1) - .map(val => ({id: val, disassociate: true})); - var labelsToAssociate = labelsToAdd - .filter(val => current - .indexOf(val) === -1) - .map(val => ({id: val, associate: true})); - var pass = labelsToDisassociate - .concat(labelsToAssociate); - associationDefer.resolve(pass); - }); - - Rest.setUrl(GetBasePath("organizations")); - Rest.get() - .then(({data}) => { - orgDefer.resolve(data.results[0].id); - }); - - orgDefer.promise.then(function(orgId) { - var toPost = []; - $scope.newLabels = $scope.newLabels - .map(function(i, val) { - val.organization = orgId; - return val; - }); - - $scope.newLabels.each(function(i, val) { - toPost.push(val); - }); - - associationDefer.promise.then(function(arr) { - toPost = toPost - .concat(arr); - - Rest.setUrl(data.related.labels); - - var defers = [credDefer, instanceGroupDefer]; - for (var i = 0; i < toPost.length; i++) { - defers.push(Rest.post(toPost[i])); - } - $q.all(defers) - .then(function() { - Wait('stop'); - saveCompleted(); - }); - }); - }); - }); - - - - // Save changes to the parent - // Save - $scope.formSave = function () { - var fld, data = {}; - $scope.invalid_survey = false; - - // Can't have a survey enabled without a survey - if($scope.survey_enabled === true && - $scope.survey_exists!==true){ - $scope.survey_enabled = false; - } - - generator.clearApiErrors($scope); - - Wait('start'); - - try { - for (fld in form.fields) { - if (form.fields[fld].type === 'select' && - fld !== 'playbook' && fld !== 'custom_virtualenv' && $scope[fld]) { - data[fld] = $scope[fld].value; - } - else if(form.fields[fld].type === 'checkbox_group') { - // Loop across the checkboxes - for(var i=0; i option").filter("[data-select2-tag=true]").each(function(optionIndex, option) { - $("#job_template_labels").siblings(".select2").first().find(".select2-selection__choice").each(function(labelIndex, label) { - if($(option).text() === $(label).attr('title')) { - // Mark that the option has a label present so that we can filter by that down below - $(option).attr('data-label-is-present', true); - } - }); - }); - - $scope.newLabels = $("#job_template_labels > option") - .filter("[data-select2-tag=true]") - .filter("[data-label-is-present=true]") - .map((i, val) => ({name: $(val).text()})); - - $scope.job_tags = _.map($scope.job_tags, function(i){return i.value;}); - $("#job_template_job_tags").siblings(".select2").first().find(".select2-selection__choice").each(function(optionIndex, option){ - $scope.job_tags.push(option.title); - }); - - $scope.skip_tags = _.map($scope.skip_tags, function(i){return i.value;}); - $("#job_template_skip_tags").siblings(".select2").first().find(".select2-selection__choice").each(function(optionIndex, option){ - $scope.skip_tags.push(option.title); - }); - - data.job_tags = (Array.isArray($scope.job_tags)) ? _.uniq($scope.job_tags).join() : ""; - data.skip_tags = (Array.isArray($scope.skip_tags)) ? _.uniq($scope.skip_tags).join() : ""; - - delete data.webhook_url; - delete data.webhook_key; - delete data.enable_webhook; - data.webhook_credential = $scope.webhookCredential.id; - - if (!data.webhook_service) { - data.webhook_credential = null; - } - - if (!$scope.enable_webhook) { - data.webhook_service = ''; - data.webhook_credential = null; - } - - Rest.setUrl(defaultUrl + $state.params.job_template_id); - Rest.patch(data) - .then(({data}) => { - $scope.$emit('templateSaveSuccess', data); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, form, { hdr: 'Error!', - msg: 'Failed to update job template. PATCH returned status: ' + status }); - }); - } catch (err) { - Wait('stop'); - Alert("Error", "Error saving job template. " + - "Error: " + err); - } - }; - - $scope.formCancel = function () { - $state.go('templates'); - }; - - let handleLabelCount = () => { - /** - * This block of code specifically handles the client-side validation of the `labels` field. - * Due to it's detached nature in relation to the other job template fields, we must - * validate this field client-side in order to avoid the edge case where a user can make a - * successful POST to the `job_templates` endpoint but however encounter a 200 error from - * the `labels` endpoint due to a character limit. - * - * We leverage two of select2's available events, `select` and `unselect`, to detect when the user - * has either added or removed a label. From there, we set a flag and do simple string length - * checks to make sure a label's chacacter count remains under 512. Otherwise, we disable the "Save" button - * by invalidating the field and inform the user of the error. - */ - - $scope.job_template_labels_isValid = true; - const maxCount = 512; - const jt_label_id = 'job_template_labels'; - - // Detect when a new label is added - $(`#${jt_label_id}`).on('select2:select', (e) => { - const { text } = e.params.data; - - // If the character count of an added label is greater than 512, we set `labels` field as invalid - if (text.length > maxCount) { - $scope.job_template_form.labels.$setValidity(`${jt_label_id}`, false); - $scope.job_template_labels_isValid = false; - } - }); - - // Detect when a label is removed - $(`#${jt_label_id}`).on('select2:unselect', (e) => { - const { text } = e.params.data; - - /* If the character count of a removed label is greater than 512 AND the field is currently marked - as invalid, we set it back to valid */ - if (text.length > maxCount && $scope.job_template_form.labels.$error) { - $scope.job_template_form.labels.$setValidity(`${jt_label_id}`, true); - $scope.job_template_labels_isValid = true; - } - }); - }; - - handleLabelCount(); - } - ]; diff --git a/awx/ui/client/src/templates/job_templates/edit-job-template/main.js b/awx/ui/client/src/templates/job_templates/edit-job-template/main.js deleted file mode 100644 index ede92fbe00d9..000000000000 --- a/awx/ui/client/src/templates/job_templates/edit-job-template/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import controller from './job-template-edit.controller'; - -export default - angular.module('jobTemplateEdit', []) - .controller('JobTemplateEdit', controller); diff --git a/awx/ui/client/src/templates/job_templates/factories/callback-help-init.factory.js b/awx/ui/client/src/templates/job_templates/factories/callback-help-init.factory.js deleted file mode 100644 index 2a3b0382e532..000000000000 --- a/awx/ui/client/src/templates/job_templates/factories/callback-help-init.factory.js +++ /dev/null @@ -1,47 +0,0 @@ -export default - function CallbackHelpInit($q, $location, GetBasePath, Rest, JobTemplateForm, GenerateForm, $stateParams, ProcessErrors, - ParseVariableString, Empty, Wait, MultiCredentialService, $rootScope) { - return function(params) { - var scope = params.scope; - // checkSCMStatus, getPlaybooks, callback, - // choicesCount = 0; - - // The form uses awPopOverWatch directive to 'watch' scope.callback_help for changes. Each time the - // popover is activated, a function checks the value of scope.callback_help before constructing the content. - scope.setCallbackHelp = function() { - scope.callback_help = "

With a provisioning callback URL and a host config key a host can contact " + $rootScope.BRAND_NAME + " and request a configuration update using this job " + - "template. The request from the host must be a POST. Here is an example using curl:

\n" + - "
curl --data \"host_config_key=" + scope.example_config_key + "\" " +
-                    scope.callback_server_path + GetBasePath('job_templates') + scope.example_template_id + "/callback/
\n" + - "

Note the requesting host must be defined in the inventory associated with the job template. If " + $rootScope.BRAND_NAME + " fails to " + - "locate the host, the request will be denied.

" + - "

Successful requests create an entry on the Jobs page, where results and history can be viewed.

"; - }; - - // The hash helper emits NewHashGenerated whenever a new key is available - if (scope.removeNewHashGenerated) { - scope.removeNewHashGenerated(); - } - scope.removeNewHashGenerated = scope.$on('NewHashGenerated', function() { - scope.configKeyChange(); - }); - - // Fired when user enters a key value - scope.configKeyChange = function() { - scope.example_config_key = scope.host_config_key; - scope.setCallbackHelp(); - }; - - // Set initial values and construct help text - scope.callback_server_path = $location.protocol() + '://' + $location.host() + (($location.port()) ? ':' + $location.port() : ''); - scope.example_config_key = '5a8ec154832b780b9bdef1061764ae5a'; - scope.example_template_id = 'N'; - scope.setCallbackHelp(); - }; - } - -CallbackHelpInit.$inject = - [ '$q', '$location', 'GetBasePath', 'Rest', 'JobTemplateForm', 'GenerateForm', - '$stateParams', 'ProcessErrors', 'ParseVariableString', - 'Empty', 'Wait', 'MultiCredentialService', '$rootScope' - ]; diff --git a/awx/ui/client/src/templates/job_templates/factories/hash-setup.factory.js b/awx/ui/client/src/templates/job_templates/factories/hash-setup.factory.js deleted file mode 100644 index b565e06820bf..000000000000 --- a/awx/ui/client/src/templates/job_templates/factories/hash-setup.factory.js +++ /dev/null @@ -1,30 +0,0 @@ -export default - function hashSetup() { - return function(params) { - var scope = params.scope, - master = params.master, - check_field = params.check_field, - default_val = params.default_val; - - scope[check_field] = default_val; - master[check_field] = default_val; - - // Original gist here: https://gist.github.com/jed/982883 - scope.genHash = function (fld) { - scope[fld] = ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c => - (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) - ); - scope.$emit('NewHashGenerated'); - }; - - scope.toggleCallback = function (fld) { - if (scope.allow_callbacks === false) { - scope[fld] = ''; - } - }; - - scope.selectAll = function (fld) { - $('input[name="' + fld + '"]').focus().select(); - }; - }; - } diff --git a/awx/ui/client/src/templates/job_templates/job-template.form.js b/awx/ui/client/src/templates/job_templates/job-template.form.js deleted file mode 100644 index 6f1f8cf025a3..000000000000 --- a/awx/ui/client/src/templates/job_templates/job-template.form.js +++ /dev/null @@ -1,620 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name forms.function:JobTemplate - * @description This form is for adding/editing a Job Template -*/ - - -export default ['NotificationsList', 'i18n', -function(NotificationsList, i18n) { - return function() { - var JobTemplateFormObject = { - - addTitle: i18n._('NEW JOB TEMPLATE'), - editTitle: '{{ name }}', - name: 'job_template', - breadcrumbName: i18n._('JOB TEMPLATE'), - basePath: 'job_templates', - // the top-most node of generated state tree - stateTree: 'templates', - tabs: true, - activeEditState: 'templates.editJobTemplate', - // (optional) array of supporting templates to ng-include inside generated html - include: ['/static/partials/survey-maker-modal.html'], - detailsClick: "$state.go('templates.editJobTemplate')", - - fields: { - name: { - label: i18n._('Name'), - type: 'text', - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)', - required: true, - column: 1 - }, - description: { - label: i18n._('Description'), - type: 'text', - column: 1, - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)' - }, - job_type: { - label: i18n._('Job Type'), - type: 'select', - ngOptions: 'type.label for type in job_type_options track by type.value', - ngChange: 'jobTypeChange()', - "default": 0, - required: true, - column: 1, - awPopOver: i18n._('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.'), - dataTitle: i18n._('Job Type'), - dataPlacement: 'right', - dataContainer: "body", - subCheckbox: { - variable: 'ask_job_type_on_launch', - text: i18n._('Prompt on launch'), - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)' - }, - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)' - }, - inventory: { - label: i18n._('Inventory'), - type: 'lookup', - basePath: 'inventory', - list: 'InventoryList', - sourceModel: 'inventory', - sourceField: 'name', - autopopulateLookup: false, - awRequiredWhen: { - reqExpression: '!ask_inventory_on_launch', - alwaysShowAsterisk: true - }, - requiredErrorMsg: i18n._("Please select an Inventory or check the Prompt on launch option."), - column: 1, - awPopOver: "

" + i18n._("Select the inventory containing the hosts you want this job to manage.") + "

", - dataTitle: i18n._('Inventory'), - dataPlacement: 'right', - dataContainer: "body", - subCheckbox: { - variable: 'ask_inventory_on_launch', - ngChange: 'job_template_form.inventory_name.$validate()', - text: i18n._('Prompt on launch') - }, - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate) || !canGetAllRelatedResources' - }, - project: { - label: i18n._('Project'), - type: 'lookup', - list: 'ProjectList', - basePath: 'projects', - sourceModel: 'project', - sourceField: 'name', - required: true, - column: 1, - awPopOver: "

" + i18n._("Select the project containing the playbook you want this job to execute.") + "

", - dataTitle: i18n._('Project'), - dataPlacement: 'right', - dataContainer: "body", - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate) || !canGetAllRelatedResources', - awLookupWhen: 'canGetAllRelatedResources' - }, - scm_branch: { - label: i18n._('SCM Branch'), - type: 'text', - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)', - ngShow: 'allow_branch_override', - column: 1, - awPopOver: "

" + i18n._("Branch to use in job run. Project default used if blank.") + "

", - dataTitle: i18n._('Project'), - subCheckbox: { - variable: 'ask_scm_branch_on_launch', - text: i18n._('Prompt on launch'), - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)' - }, - dataPlacement: 'right', - dataContainer: "body" - }, - playbook: { - label: i18n._('Playbook'), - type:'select', - defaultText: i18n._('Choose a playbook'), - ngOptions: 'book for book in playbook_options track by book', - ngShow: 'allow_playbook_selection', - ngDisabled: "!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate) || !canGetAllRelatedResources", - id: 'playbook-select', - required: true, - column: 1, - awPopOver: "

" + i18n._("Select the playbook to be executed by this job." + - "You can select from the dropdown or enter a file within the input.") + "

", - dataTitle: i18n._('Playbook'), - dataPlacement: 'right', - dataContainer: "body", - includePlaybookNotFoundError: true - }, - credential: { - label: i18n._('Credentials'), - type: 'custom', - control: ` - - `, - awPopOver: i18n._('Select credentials that allow Tower to access 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.'), - dataTitle: i18n._('Credentials'), - dataPlacement: 'right', - dataContainer: "body", - subCheckbox: { - variable: 'ask_credential_on_launch', - text: i18n._('Prompt on launch'), - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)' - } - }, - forks: { - label: i18n._('Forks'), - type: 'number', - integer: true, - min: 0, - default: 0, - spinner: true, - dataTitle: i18n._('Forks'), - dataPlacement: 'right', - dataContainer: 'body', - awPopOver: "

" + i18n._("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. " + i18n._("Refer to the Ansible documentation for details about the configuration file.") + "

", - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)' - }, - limit: { - label: i18n._('Limit'), - type: 'text', - column: 1, - awPopOver: i18n._('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.'), - dataTitle: i18n._('Limit'), - dataPlacement: 'right', - dataContainer: "body", - subCheckbox: { - variable: 'ask_limit_on_launch', - text: i18n._('Prompt on launch') - }, - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)' - }, - verbosity: { - label: i18n._('Verbosity'), - type: 'select', - ngOptions: 'v.label for v in verbosity_options track by v.value', - "default": 1, - required: true, - column: 1, - awPopOver: "

" + i18n._("Control the level of output ansible will produce as the playbook executes.") + "

", - dataTitle: i18n._('Verbosity'), - dataPlacement: 'right', - dataContainer: "body", - subCheckbox: { - variable: 'ask_verbosity_on_launch', - text: i18n._('Prompt on launch') - }, - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)', - }, - job_tags: { - label: i18n._('Job Tags'), - type: 'select', - multiSelect: true, - 'elementClass': 'Form-textInput', - ngOptions: 'tag.label for tag in job_tag_options track by tag.value', - column: 2, - awPopOver: i18n._('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 Ansible Tower documentation for details on the usage of tags.'), - dataTitle: i18n._("Job Tags"), - dataPlacement: "right", - dataContainer: "body", - subCheckbox: { - variable: 'ask_tags_on_launch', - text: i18n._('Prompt on launch') - }, - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)' - }, - skip_tags: { - label: i18n._('Skip Tags'), - type: 'select', - multiSelect: true, - 'elementClass': 'Form-textInput', - ngOptions: 'tag.label for tag in skip_tag_options track by tag.value', - column: 2, - awPopOver: i18n._('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 Ansible Tower documentation for details on the usage of tags.'), - dataTitle: i18n._("Skip Tags"), - dataPlacement: "right", - dataContainer: "body", - subCheckbox: { - variable: 'ask_skip_tags_on_launch', - text: i18n._('Prompt on launch') - }, - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)' - }, - labels: { - label: i18n._('Labels'), - type: 'select', - ngOptions: 'label.label for label in labelOptions track by label.value', - multiSelect: true, - dataTitle: i18n._('Labels'), - dataPlacement: 'right', - awPopOver: "

" + i18n._("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.") + "

", - dataContainer: 'body', - onError: { - ngShow: 'job_template_labels_isValid !== true', - text: i18n._('Max 512 characters per label.'), - }, - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)' - }, - custom_virtualenv: { - label: i18n._('Ansible Environment'), - type: 'select', - defaultText: i18n._('Use Default Environment'), - ngOptions: 'venv for venv in custom_virtualenvs_options track by venv', - - awPopOver: "

" + i18n._("Select the custom Python virtual environment for this job template to run on.") + "

", - dataTitle: i18n._('Ansible Environment'), - dataContainer: 'body', - dataPlacement: 'right', - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)', - ngShow: 'custom_virtualenvs_options.length > 1' - }, - instance_groups: { - label: i18n._('Instance Groups'), - type: 'custom', - awPopOver: "

" + i18n._("Select the Instance Groups for this Job Template to run on.") + "

", - dataTitle: i18n._('Instance Groups'), - dataContainer: 'body', - dataPlacement: 'right', - control: '', - }, - job_slice_count: { - label: i18n._('Job Slicing'), - type: 'number', - integer: true, - min: 1, - default: 1, - spinner: true, - dataTitle: i18n._('Slice Job Count'), - dataPlacement: 'right', - dataContainer: 'body', - awPopOver: "

" + i18n._("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.") + "

", - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)' - }, - timeout: { - label: i18n._('Timeout'), - type: 'number', - integer: true, - min: 0, - default: 0, - spinner: true, - dataTitle: i18n._('Timeout'), - dataPlacement: 'right', - dataContainer: 'body', - awPopOver: "

" + i18n._("The amount of time (in seconds) to run before the task is canceled. Defaults to 0 for no job timeout.") + "

", - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)' - }, - diff_mode: { - label: i18n._('Show Changes'), - type: 'toggleSwitch', - toggleSource: 'diff_mode', - dataTitle: i18n._('Show Changes'), - dataPlacement: 'right', - dataContainer: 'body', - awPopOver: "

" + i18n._("If enabled, show the changes made by Ansible tasks, where supported. This is equivalent to Ansible’s --diff mode.") + "

", - subCheckbox: { - variable: 'ask_diff_mode_on_launch', - text: i18n._('Prompt on launch') - }, - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)' - }, - checkbox_group: { - label: i18n._('Options'), - type: 'checkbox_group', - fields: [{ - name: 'become_enabled', - label: i18n._('Enable Privilege Escalation'), - type: 'checkbox', - column: 2, - awPopOver: i18n._('If enabled, run this playbook as an administrator.'), - dataPlacement: 'right', - dataTitle: i18n._('Enable Privilege Escalation'), - dataContainer: "body", - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)' - }, { - name: 'allow_callbacks', - label: i18n._('Enable Provisioning Callbacks'), - type: 'checkbox', - ngChange: "toggleCallback('host_config_key')", - column: 2, - awPopOver: "

" + i18n._("Enables creation of a provisioning callback URL. Using the URL a host can contact {{BRAND_NAME}} and request a configuration update " + - "using this job template.") + "

", - dataPlacement: 'right', - dataTitle: i18n._('Enable Provisioning Callbacks'), - dataContainer: "body", - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)' - }, { - name: 'enable_webhook', - label: i18n._('Enable Webhook'), - type: 'checkbox', - column: 2, - awPopOver: "

" + i18n._("Enable webhook for this job template.") + "

", - dataPlacement: 'right', - dataTitle: i18n._('Enable Webhook'), - dataContainer: "body", - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)' - }, { - name: 'allow_simultaneous', - label: i18n._('Enable Concurrent Jobs'), - type: 'checkbox', - column: 2, - awPopOver: "

" + i18n._("If enabled, simultaneous runs of this job template will be allowed.") + "

", - dataPlacement: 'right', - dataTitle: i18n._('Enable Concurrent Jobs'), - dataContainer: "body", - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)' - }, { - name: 'use_fact_cache', - label: i18n._('Enable Fact Cache'), - type: 'checkbox', - column: 2, - awPopOver: "

" + i18n._("If enabled, use cached facts if available and store discovered facts in the cache.") + "

", - dataPlacement: 'right', - dataTitle: i18n._('Enable Fact Cache'), - dataContainer: "body", - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)' - }] - }, - callback_url: { - label: i18n._('Provisioning Callback URL'), - type: 'text', - readonly: true, - ngShow: "allow_callbacks && allow_callbacks !== 'false'", - column: 2, - awPopOver: "callback_help", - awPopOverWatch: "callback_help", - dataPlacement: 'top', - dataTitle: i18n._('Provisioning Callback URL'), - dataContainer: "body", - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)' - }, - host_config_key: { - label: i18n._('Host Config Key'), - type: 'text', - ngShow: "allow_callbacks && allow_callbacks !== 'false'", - ngChange: "configKeyChange()", - genHash: true, - column: 2, - awPopOver: "callback_help", - awPopOverWatch: "callback_help", - dataPlacement: 'right', - dataTitle: i18n._("Host Config Key"), - dataContainer: "body", - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)', - awRequiredWhen: { - reqExpression: 'allow_callbacks', - alwaysShowAsterisk: true - } - }, - webhook_service: { - label: i18n._('Webhook Service'), - type:'select', - defaultText: i18n._('Choose a Webhook Service'), - ngOptions: 'svc.label for svc in webhook_service_options track by svc.value', - ngShow: "enable_webhook && enable_webhook !== 'false'", - ngDisabled: "!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate) || !canGetAllRelatedResources", - id: 'webhook-service-select', - column: 1, - awPopOver: "

" + i18n._("Select a webhook service.") + "

", - dataTitle: i18n._('Webhook Service'), - dataPlacement: 'right', - dataContainer: "body", - }, - webhook_url: { - label: i18n._('Webhook URL'), - type: 'text', - ngShow: "job_template_obj && enable_webhook && enable_webhook !== 'false'", - awPopOver: "webhook_url_help", - awPopOverWatch: "webhook_url_help", - dataPlacement: 'top', - dataTitle: i18n._('Webhook URL'), - dataContainer: "body", - readonly: true - }, - webhook_key: { - label: i18n._('Webhook Key'), - type: 'text', - ngShow: "enable_webhook && enable_webhook !== 'false'", - genHash: true, - genHashButtonTemplate: ` - - - - `, - genHashButtonClickHandlerName: "handleWebhookKeyButtonClick", - awPopOver: "webhook_key_help", - awPopOverWatch: "webhook_key_help", - dataPlacement: 'right', - dataTitle: i18n._("Webhook Key"), - dataContainer: "body", - readonly: true, - required: false, - }, - webhook_credential: { - label: i18n._('Webhook Credential'), - type: 'custom', - ngShow: "enable_webhook && enable_webhook !== 'false'", - control: ` - `, - awPopOver: "

" + i18n._("Optionally, select the credential to use to send status updates back to the webhook service") + "

", - dataTitle: i18n._('Webhook Credential'), - dataPlacement: 'right', - dataContainer: "body", - ngDisabled: '!(webhook_service || webhook_service.value)', - required: false, - }, - extra_vars: { - label: i18n._('Extra Variables'), - type: 'textarea', - class: 'Form-textAreaLabel Form-formGroup--fullWidth', - rows: 6, - "default": "---", - column: 2, - awPopOver: i18n._('Pass extra command line variables to the playbook. Provide key/value pairs using either YAML or JSON. Refer to the Ansible Tower documentation for example syntax.'), - dataTitle: i18n._('Extra Variables'), - dataPlacement: 'right', - dataContainer: "body", - id: 'extra_vars', - subCheckbox: { - variable: 'ask_variables_on_launch', - text: i18n._('Prompt on launch') - }, - ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)' // TODO: get working - } - }, - - buttons: { //for now always generates -
-
- - -
-
-
diff --git a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.block.less b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.block.less deleted file mode 100644 index 887cdb09617b..000000000000 --- a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.block.less +++ /dev/null @@ -1,165 +0,0 @@ -.MultiCredential-selectedBar { - display: flex; - padding: 5px 10px; - background: @default-no-items-bord; - margin-bottom: 20px; - border: 1px solid @default-icon-hov; - border-radius: 5px; - max-height: 120px; - overflow-y: auto; -} - -.MultiCredential-selectedBarLabel { - align-self: center; - margin-right: 20px; - font-size: 12px; - color: @default-icon; -} - -.MultiCredential-tags { - padding-left: 0px; -} - -.MultiCredential-bar i { - font-size: 16px; - color: @default-icon; -} - -.MultiCredential-flexContainer { - display: flex; - width: 100%; - flex-wrap: wrap; -} - -.MultiCredential-tagContainer { - display: flex; - max-width: 100%; - background-color: @default-link; - color: @default-bg; - border-radius: 5px; - padding: 0px 0px 0px 10px; - margin: 3px 10px 3px 0px; -} - -.MultiCredential-tagContainer--disabled { - background-color: @default-icon; -} - -.MultiCredential-tag { - font-size: 12px; - margin-right: 10px; - max-width: 100%; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - padding: 2px 0px 2px 15px; -} - -.MultiCredential-tag--disabled { - border-top-left-radius: 0px; - border-bottom-left-radius: 0px; - padding-left: 10px; -} - -.MultiCredential-tag--deletable { - margin-right: 0px; - border-top-right-radius: 0px; - border-bottom-right-radius: 0px; - border-right: 0; - max-width: ~"calc(100% - 23px)"; - padding-left: 10px; -} - -.MultiCredential-deleteContainer { - border-top-right-radius: 5px; - border-bottom-right-radius: 5px; - padding: 2px 5px; - align-items: center; - display: flex; - cursor: pointer; -} - -.MultiCredential-tagDelete { - font-size: 11px; -} - -.MultiCredential-iconContainer { - border-top-left-radius: 0px; - border-bottom-left-radius: 0px; - padding: 0px 5px; - margin: 3px 0px; - margin-left: -3px; - align-items: center; - display: flex; -} - -.MultiCredential-iconContainer--disabled { - border-top-left-radius: 5px; - border-bottom-left-radius: 5px; - padding: 0 5px; - padding-left: 10px; - margin: 3px 0px; - align-items: center; - display: flex; -} - - -.MultiCredential-tagIcon { - margin: 0px 0px; - font-size: 12px; -} - -.MultiCredential-name { - flex: initial; - font-size: 12px; - max-width: 100%; -} - -.MultiCredential-name--label { - color: @default-list-header-bg; - font-size: 12px; - margin-left: -8px; - margin-right: 5px; -} - -.MultiCredential-tag--deletable > .MultiCredential-name { - max-width: ~"calc(100% - 23px)"; -} - -.MultiCredential-deleteContainer:hover { - border-color: @default-err; - background-color: @default-err!important; -} - -.MultiCredential-deleteContainer:hover > .MultiCredential-tagDelete { - color: @default-bg; -} - -.MultiCredential-credentialSubSection { - display: flex; - justify-content: flex-end; - align-items: center; - margin-bottom: 15px; -} - -.MultiCredential-credentialSubSection .select2 { - width: 50% !important; -} - -.MultiCredential-selectLabel { - color: @default-interface-txt; - margin-right: 10px; - line-height: 24px; - text-transform: uppercase; -} - -.MultiCredential-previewTagRevert { - flex: 0 0 60px; - line-height: 29px; - margin-left: auto; - text-align: right; -} - -.MultiCredential-revertLink { - font-size: 12px; -} diff --git a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.directive.js b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.directive.js deleted file mode 100644 index bca3e1e29219..000000000000 --- a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.directive.js +++ /dev/null @@ -1,70 +0,0 @@ -/************************************************* - * Copyright (c) 2018 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ -const templatePath = 'templates/job_templates/multi-credential/multi-credential'; - -function MultiCredential ($compile, templateUrl) { - return { - templateUrl: templateUrl(templatePath), - require: ['multiCredential'], - restrict: 'E', - controllerAs: 'vm', - scope: { - selectedCredentials: '=', - prompt: '=', - credentialNotPresent: '=', - fieldIsDisabled: '=', - credentialTypes: '=', - }, - link: (scope, element, attrs, controllers) => { - const [controller] = controllers; - - scope.openModal = () => { - const containerElement = $('#content-container'); - const templateFunction = $compile(` - - `); - containerElement.append(templateFunction(scope)); - }; - - controller.init(scope); - }, - controller: multiCredentialController, - }; -} - -function multiCredentialController (MultiCredentialService) { - const vm = this; - const { createTag } = MultiCredentialService; - - let scope; - - vm.init = _scope_ => { - scope = _scope_; - scope.$watchCollection('selectedCredentials', onSelectedCredentialsChanged); - }; - - function onSelectedCredentialsChanged (oldValues, newValues) { - if (oldValues !== newValues) { - scope.fieldDirty = (scope.prompt && scope.selectedCredentials.length > 0); - scope.tags = scope.selectedCredentials.map(c => createTag(c, scope.credentialTypes)); - } - } - - vm.deselectCredential = ({ id }) => { - const index = scope.selectedCredentials.map(c => c.id).indexOf(id); - - if (index > -1) { - scope.selectedCredentials.splice(index, 1); - } - }; -} - -MultiCredential.$inject = ['$compile', 'templateUrl']; -multiCredentialController.$inject = ['MultiCredentialService']; - -export default MultiCredential; diff --git a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.partial.html b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.partial.html deleted file mode 100644 index a4b8512b0f48..000000000000 --- a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.partial.html +++ /dev/null @@ -1,56 +0,0 @@ -
- - - - -
-
-
-
-
- - - - - - -
-
- - - - - - -
-
- - {{ tag.name }} - - - {{ tag.name }} | {{ tag.info }} - -
-
- -
-
-
-
-
-
-
diff --git a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.service.js b/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.service.js deleted file mode 100644 index e687834645d5..000000000000 --- a/awx/ui/client/src/templates/job_templates/multi-credential/multi-credential.service.js +++ /dev/null @@ -1,91 +0,0 @@ -function MultiCredentialService (Rest, ProcessErrors, $q, GetBasePath) { - - const handleError = (method, resource, permitted = []) => { - return ({ data, status }) => { - if (permitted.indexOf(status) > -1) { - return { data, status }; - } - const hdr = 'Error!'; - const msg = `${resource} request failed. ${method} returned status: ${status}`; - return ProcessErrors(null, data, status, null, { hdr, msg }); - }; - }; - - const associate = ({ related }, id) => { - Rest.setUrl(related.credentials); - return Rest - .post({ id }) - .then(({ data }) => data.results) - .catch(handleError('POST', 'credential association')); - }; - - const disassociate = ({ related }, id) => { - Rest.setUrl(related.credentials); - return Rest - .post({ id, disassociate: true }) - .catch(handleError('POST', 'credential disassociation')); - }; - - this.saveRelated = ({ related }, credentials) => { - Rest.setUrl(related.credentials); - return Rest - .get() - .then(({ data }) => { - const currentlyAssociated = data.results.map(c => c.id); - const selected = credentials.map(c => c.id); - - const disassociationPromises = currentlyAssociated - .filter(id => selected.indexOf(id) < 0) - .map(id => disassociate({ related }, id)); - - return $q.all(disassociationPromises).then(() => { - _.each(selected.filter(id => currentlyAssociated.indexOf(id) < 0), (id) => { - return associate({related}, id); - }); - }); - }); - }; - - this.getRelated = ({ related }, params = { permitted: [] }) => { - Rest.setUrl(related.credentials); - return Rest - .get() - .catch(handleError('GET', 'related credentials', params.permitted)); - }; - - this.getCredentials = () => { - Rest.setUrl(GetBasePath('credentials')); - return Rest - .get() - .catch(handleError('GET', 'related credentials')); - }; - - this.getCredentialTypes = () => { - Rest.setUrl(GetBasePath('credential_types')); - return Rest - .get({ params: { page_size: 200 }}) - .catch(handleError('GET', 'credential types')); - }; - - this.createTag = (credential, credential_types) => { - const credentialTypeId = credential.credential_type || credential.credential_type_id; - const credentialType = credential_types.find(t => t.id === credentialTypeId); - - return { - id: credential.id, - name: credential.name, - kind: _.get(credentialType, 'kind'), - typeName: _.get(credentialType, 'name'), - info: _.get(credential, 'inputs.vault_id') - }; - }; -} - -MultiCredentialService.$inject = [ - 'Rest', - 'ProcessErrors', - '$q', - 'GetBasePath', -]; - -export default MultiCredentialService; diff --git a/awx/ui/client/src/templates/job_templates/webhook-credential/index.js b/awx/ui/client/src/templates/job_templates/webhook-credential/index.js deleted file mode 100644 index 97b634463a43..000000000000 --- a/awx/ui/client/src/templates/job_templates/webhook-credential/index.js +++ /dev/null @@ -1,4 +0,0 @@ -import webhookCredentialInput from './webhook-credential-input.component'; - -export default angular.module('webhookCredential', []) - .component('webhookCredentialInput', webhookCredentialInput); diff --git a/awx/ui/client/src/templates/job_templates/webhook-credential/main.js b/awx/ui/client/src/templates/job_templates/webhook-credential/main.js deleted file mode 100644 index a91d79dbc63a..000000000000 --- a/awx/ui/client/src/templates/job_templates/webhook-credential/main.js +++ /dev/null @@ -1,9 +0,0 @@ -import webhookCredential from './webhook-credential.directive'; -import webhookCredentialModal from './webhook-credential-modal.directive'; -import webhookCredentialService from './webhook-credential.service'; - -export default - angular.module('webhookCredential', []) - .directive('webhookCredential', webhookCredential) - .directive('webhookCredentialModal', webhookCredentialModal) - .service('WebhookCredentialService', webhookCredentialService); diff --git a/awx/ui/client/src/templates/job_templates/webhook-credential/webhook-credential-input.component.js b/awx/ui/client/src/templates/job_templates/webhook-credential/webhook-credential-input.component.js deleted file mode 100644 index 8f5d7529486e..000000000000 --- a/awx/ui/client/src/templates/job_templates/webhook-credential/webhook-credential-input.component.js +++ /dev/null @@ -1,11 +0,0 @@ -const templateUrl = require('~src/templates/job_templates/webhook-credential/webhook-credential-input.partial.html'); -export default { - templateUrl, - controllerAs: 'vm', - bindings: { - isFieldDisabled: '<', - tagName: '<', - onLookupClick: '<', - onTagDelete: '<', - }, -}; diff --git a/awx/ui/client/src/templates/job_templates/webhook-credential/webhook-credential-input.partial.html b/awx/ui/client/src/templates/job_templates/webhook-credential/webhook-credential-input.partial.html deleted file mode 100644 index f34c70b8e735..000000000000 --- a/awx/ui/client/src/templates/job_templates/webhook-credential/webhook-credential-input.partial.html +++ /dev/null @@ -1,43 +0,0 @@ -
- - - - -
-
-
-
-
- -
-
- -
-
- - {{ vm.tagName }} - -
-
- -
-
-
-
-
-
-
diff --git a/awx/ui/client/src/templates/job_templates/webhook-credential/webhook-credential.block.less b/awx/ui/client/src/templates/job_templates/webhook-credential/webhook-credential.block.less deleted file mode 100644 index 24467c7d0fb6..000000000000 --- a/awx/ui/client/src/templates/job_templates/webhook-credential/webhook-credential.block.less +++ /dev/null @@ -1,113 +0,0 @@ -.WebhookCredential-tags { - padding-left: 0px; -} - -.WebhookCredential-flexContainer { - display: flex; - width: 100%; - flex-wrap: wrap; -} - -.WebhookCredential-tagContainer { - display: flex; - max-width: 100%; - background-color: @default-link; - color: @default-bg; - border-radius: 5px; - padding: 0px 0px 0px 10px; - margin: 3px 10px 3px 0px; -} - -.WebhookCredential-tagContainer--disabled { - background-color: @default-icon; -} - -.WebhookCredential-tag { - font-size: 12px; - margin-right: 10px; - max-width: 100%; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - padding: 2px 0px 2px 15px; -} - -.WebhookCredential-tag--disabled { - border-top-left-radius: 0px; - border-bottom-left-radius: 0px; - padding-left: 10px; -} - -.WebhookCredential-tag--deletable { - margin-right: 0px; - border-top-right-radius: 0px; - border-bottom-right-radius: 0px; - border-right: 0; - max-width: ~"calc(100% - 23px)"; - padding-left: 10px; -} - -.WebhookCredential-deleteContainer { - border-top-right-radius: 5px; - border-bottom-right-radius: 5px; - padding: 2px 5px; - align-items: center; - display: flex; - cursor: pointer; -} - -.WebhookCredential-tagDelete { - font-size: 11px; -} - -.WebhookCredential-iconContainer { - border-top-left-radius: 0px; - border-bottom-left-radius: 0px; - padding: 0px 5px; - margin: 3px 0px; - margin-left: -3px; - align-items: center; - display: flex; -} - -.WebhookCredential-iconContainer--disabled { - border-top-left-radius: 5px; - border-bottom-left-radius: 5px; - padding: 0 5px; - padding-left: 10px; - margin: 3px 0px; - align-items: center; - display: flex; -} - - -.WebhookCredential-tagIcon { - margin: 0px 0px; - font-size: 12px; -} - -.WebhookCredential-name { - flex: initial; - font-size: 12px; - max-width: 100%; -} - -.WebhookCredential-name--label { - color: @default-list-header-bg; - font-size: 12px; - margin-left: -8px; - margin-right: 5px; -} - -.WebhookCredential-tag--deletable > .WebhookCredential-name { - max-width: ~"calc(100% - 23px)"; -} - -.WebhookCredential-deleteContainer:hover { - border-color: @default-err; - background-color: @default-err!important; -} - -.WebhookCredential-deleteContainer:hover > .WebhookCredential-tagDelete { - color: @default-bg; -} diff --git a/awx/ui/client/src/templates/labels/labelsList.block.less b/awx/ui/client/src/templates/labels/labelsList.block.less deleted file mode 100644 index 661a0545fb76..000000000000 --- a/awx/ui/client/src/templates/labels/labelsList.block.less +++ /dev/null @@ -1,110 +0,0 @@ -/** @define LabelList */ - -.LabelList { - display: flex; - flex-wrap: wrap; - align-items: flex-start; -} - -.LabelList-tagContainer { - display: flex; - padding-bottom: 5px; -} - -.LabelList-tagContainer { - max-width: 200px; -} - -.LabelList-tag, .JobSubmission-previewTag { - background-color: @default-list-header-bg; - border-radius: 5px; - color: @default-interface-txt; - display: inline-block; - font-size: 12px; - margin-right: 5px; - max-width: 100%; - padding: 2px 10px; -} - -.LabelList-seeMoreLess { - color: @default-link; - margin: 4px 0px; - text-transform: uppercase; - padding: 2px 0px; - cursor: pointer; - border-radius: 5px; - font-size: 11px; - display: inherit; -} - -.LabelList-seeMoreLess:hover { - color: @default-link-hov; -} - -.LabelList-tag--deletable, .JobSubmission-previewTag--deletable, .Prompt-previewTag--deletable { - color: @default-bg; - background-color: @default-link; - margin-right: 0px; - border-top-right-radius: 0px; - border-bottom-right-radius: 0px; - border-right: 0; - max-width: ~"calc(100% - 23px)"; -} - -.LabelList-deleteContainer, .JobSubmission-previewTagContainerDelete, .Prompt-previewTagContainerDelete { - background-color: @default-link; - border-top-right-radius: 5px; - border-bottom-right-radius: 5px; - color: @default-bg; - padding: 0 5px; - align-items: center; - display: flex; - cursor: pointer; - margin-right: 5px; -} - -.LabelList-tagDelete { - font-size: 13px; - color: @default-bg; -} - -.LabelList-name { - flex: initial; - max-width: 100%; -} - -.LabelList-tag--deletable > .LabelList-name { - max-width: ~"calc(100% - 23px)"; -} - -.LabelList-deleteContainer:hover, .JobSubmission-previewTagContainerDelete:hover, .Prompt-previewTagContainerDelete:hover { - border-color: @default-err; - background-color: @default-err; -} - -.LabelList-deleteContainer:hover > .LabelList-tagDelete, .JobSubmission-previewTagContainerDelete:hover > .JobSubmission-previewTagContainerTagDelete, .Prompt-previewTagContainerDelete:hover > .Prompt-previewTagContainerTagDelete { - color: @default-bg; -} - -.LabelList-lookupTags { - display: flex; - padding: 0 12px; -} - -.LabelList-lookupTags--disabled { - cursor: not-allowed; - background-color: @default-list-header-bg; - .LabelList-tag { - background-color: @default-icon; - color: @default-bg; - margin: 4px 5px 5px 5px; - } -} - -.JobSubmission-previewTagContainer--vault{ - flex: 1 0 auto; -} - -.JobSubmission-previewTag--vault{ - border-radius: 5px; -} diff --git a/awx/ui/client/src/templates/labels/labelsList.directive.js b/awx/ui/client/src/templates/labels/labelsList.directive.js deleted file mode 100644 index 158343babd3a..000000000000 --- a/awx/ui/client/src/templates/labels/labelsList.directive.js +++ /dev/null @@ -1,121 +0,0 @@ -/* jshint unused: vars */ -export default - [ 'templateUrl', - 'Wait', - 'Rest', - 'GetBasePath', - 'ProcessErrors', - 'Prompt', - '$q', - '$filter', - '$state', - 'i18n', - function(templateUrl, Wait, Rest, GetBasePath, ProcessErrors, Prompt, $q, $filter, $state, i18n) { - return { - restrict: 'E', - scope: { - state: '=' - }, - templateUrl: templateUrl('templates/labels/labelsList'), - link: function(scope, element, attrs) { - scope.showDelete = attrs.showDelete === 'true'; - scope.isRowItem = attrs.isRowItem === 'true'; - scope.seeMoreInactive = true; - - var getNext = function(data, arr, resolve) { - Rest.setUrl(data.next); - Rest.get() - .then(({data}) => { - if (data.next) { - getNext(data, arr.concat(data.results), resolve); - } else { - resolve.resolve(arr.concat(data.results)); - } - }); - }; - - scope.seeMore = function () { - var seeMoreResolve = $q.defer(); - if (scope.state) { - Rest.setUrl(scope.state.related.labels); - } - Rest.get() - .then(({data}) => { - if (data.next) { - getNext(data, data.results, seeMoreResolve); - } else { - seeMoreResolve.resolve(data.results); - } - }); - - seeMoreResolve.promise.then(function (labels) { - scope.labels = labels; - scope.seeMoreInactive = false; - }); - }; - - scope.seeLess = function() { - // Trim the labels array back down to 10 items - scope.labels = scope.labels.slice(0, 5); - // Re-set the seeMoreInteractive flag so that the "See More" will be displayed - scope.seeMoreInactive = true; - }; - - scope.deleteLabel = function(template, label) { - var action = function () { - $('#prompt-modal').modal('hide'); - scope.seeMoreInactive = true; - Wait('start'); - let url; - if(template.type === 'job_template') { - url = GetBasePath("job_templates") + template.id + "/labels/"; - } - else if(template.type === 'workflow_job_template') { - url = GetBasePath("workflow_job_templates") + template.id + "/labels/"; - } - - if(url) { - Rest.setUrl(url); - Rest.post({"disassociate": true, "id": label.id}) - .then(() => { - Wait('stop'); - $state.go('.', null, {reload: true}); - }) - .catch(({data, status}) => { - Wait('stop'); - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Could not disassociate label from JT. Call to ' + url + ' failed. DELETE returned status: ' + status }); - }); - } - }; - - Prompt({ - hdr: i18n._('Remove Label from ') + template.name, - body: '
Confirm the removal of the ' + $filter('sanitize')(label.name) + ' label.
', - action: action, - actionText: i18n._('REMOVE') - }); - }; - - if (_.has(scope.state, 'summary_fields.labels.results')) { - scope.labels = scope.state.summary_fields.labels.results.slice(0, 5); - scope.count = scope.state.summary_fields.labels.count; - } else { - scope.$watchCollection(scope.state, function() { - // To keep the array of labels fresh, we need to set up a watcher - otherwise, the - // array will get set initially and then never be updated as labels are removed - if (scope.state.summary_fields.labels){ - scope.labels = scope.state.summary_fields.labels.results.slice(0, 5); - scope.count = scope.state.summary_fields.labels.count; - } - else{ - scope.labels = null; - scope.count = null; - } - }); - } - - } - }; - } - ]; diff --git a/awx/ui/client/src/templates/labels/labelsList.partial.html b/awx/ui/client/src/templates/labels/labelsList.partial.html deleted file mode 100644 index fce351f8006d..000000000000 --- a/awx/ui/client/src/templates/labels/labelsList.partial.html +++ /dev/null @@ -1,35 +0,0 @@ -
-
- -
-
- {{ label.name }} -
-
-
View More
-
View Less
-
-
- Labels -
-
-
-
- {{ label.name }} -
-
- -
-
-
View More
-
View Less
-
-
diff --git a/awx/ui/client/src/templates/labels/main.js b/awx/ui/client/src/templates/labels/main.js deleted file mode 100644 index 6d325ff42c1d..000000000000 --- a/awx/ui/client/src/templates/labels/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import labelsList from './labelsList.directive'; - -export default - angular.module('labels', []) - .directive('labelsList', labelsList); diff --git a/awx/ui/client/src/templates/main.js b/awx/ui/client/src/templates/main.js deleted file mode 100644 index fb4dab309aec..000000000000 --- a/awx/ui/client/src/templates/main.js +++ /dev/null @@ -1,575 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import templatesService from './templates.service'; -import surveyMaker from './survey-maker/main'; -import jobTemplates from './job_templates/main'; -import workflowAdd from './workflows/add-workflow/main'; -import workflowEdit from './workflows/edit-workflow/main'; -import labels from './labels/main'; -import prompt from './prompt/main'; -import workflowChart from './workflows/workflow-chart/main'; -import workflowKey from './workflows/workflow-key/main'; -import workflowMaker from './workflows/workflow-maker/main'; -import workflowControls from './workflows/workflow-controls/main'; -import WorkflowForm from './workflows.form'; -import InventorySourcesList from './inventory-sources.list'; -import TemplateList from './templates.list'; -import listRoute from '~features/templates/routes/templatesList.route.js'; -import templateCompletedJobsRoute from '~features/jobs/routes/templateCompletedJobs.route.js'; -import workflowJobTemplateCompletedJobsRoute from '~features/jobs/routes/workflowJobTemplateCompletedJobs.route.js'; -import { - jobTemplatesSchedulesListRoute, - jobTemplatesSchedulesAddRoute, - jobTemplatesSchedulesEditRoute, - workflowSchedulesRoute, - workflowSchedulesAddRoute, - workflowSchedulesEditRoute -} from '../scheduler/schedules.route'; - -export default -angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, prompt.name, workflowAdd.name, workflowEdit.name, - workflowChart.name, workflowKey.name, workflowMaker.name, workflowControls.name - ]) - .service('TemplatesService', templatesService) - .factory('WorkflowForm', WorkflowForm) - // TODO: currently being kept arround for rbac selection, templates within projects and orgs, etc. - .factory('TemplateList', TemplateList) - .value('InventorySourcesList', InventorySourcesList) - .config(['$stateProvider', 'stateDefinitionsProvider', '$stateExtenderProvider', - function($stateProvider, stateDefinitionsProvider, $stateExtenderProvider) { - let stateTree, addJobTemplate, editJobTemplate, addWorkflow, editWorkflow, - workflowMaker, - stateDefinitions = stateDefinitionsProvider.$get(), - stateExtender = $stateExtenderProvider.$get(); - - function generateStateTree() { - - addJobTemplate = stateDefinitions.generateTree({ - name: 'templates.addJobTemplate', - url: '/add_job_template?inventory_id&credential_id&project_id', - modes: ['add'], - form: 'JobTemplateForm', - controllers: { - add: 'JobTemplateAdd' - }, - resolve: { - add: { - Inventory: ['$stateParams', 'Rest', 'GetBasePath', 'ProcessErrors', 'i18n', - function($stateParams, Rest, GetBasePath, ProcessErrors, i18n){ - if($stateParams.inventory_id){ - let path = `${GetBasePath('inventory')}${$stateParams.inventory_id}`; - Rest.setUrl(path); - return Rest.get(). - then(function(data){ - return data.data; - }).catch(function(response) { - ProcessErrors(null, response.data, response.status, null, { - hdr: i18n._('Error!'), - msg: i18n._('Failed to get inventory info. GET returned status: ') + - response.status - }); - }); - } - }], - Project: ['Rest', 'GetBasePath', 'ProcessErrors', '$transition$', 'i18n', - function(Rest, GetBasePath, ProcessErrors, $transition$, i18n){ - if($transition$.params().credential_id){ - let path = `${GetBasePath('projects')}?credential__id=${Number($transition$.params().credential_id)}`; - Rest.setUrl(path); - return Rest.get(). - then(function(data){ - return data.data.results[0]; - }).catch(function(response) { - ProcessErrors(null, response.data, response.status, null, { - hdr: i18n._('Error!'), - msg: i18n._('Failed to get project info. GET returned status: ') + - response.status - }); - }); - } - else if($transition$.params().project_id){ - let path = `${GetBasePath('projects')}${$transition$.params().project_id}`; - Rest.setUrl(path); - return Rest.get(). - then(function(data){ - return data.data; - }).catch(function(response) { - ProcessErrors(null, response.data, response.status, null, { - hdr: i18n._('Error!'), - msg: i18n._('Failed to get project info. GET returned status: ') + - response.status - }); - }); - } - }], - availableLabels: ['ProcessErrors', 'TemplatesService', 'i18n', - function(ProcessErrors, TemplatesService, i18n) { - return TemplatesService.getAllLabelOptions() - .then(function(labels){ - return labels; - }).catch(function(response){ - ProcessErrors(null, response.data, response.status, null, { - hdr: i18n._('Error!'), - msg: i18n._('Failed to get labels. GET returned status: ') + - response.status - }); - }); - }], - checkPermissions: ['TemplatesService', 'Alert', 'ProcessErrors', '$state', 'i18n', - function(TemplatesService, Alert, ProcessErrors, $state, i18n) { - return TemplatesService.getJobTemplateOptions() - .then(function(data) { - if (!data.actions.POST) { - $state.go("^"); - Alert(i18n._('Permission Error'), i18n._('You do not have permission to add a job template, or there are no projects available.'), 'alert-info'); - } - }).catch(function(response){ - ProcessErrors(null, response.data, response.status, null, { - hdr: i18n._('Error!'), - msg: i18n._('Failed to get job template options. OPTIONS returned status: ') + - response.status - }); - }); - }], - ConfigData: ['ConfigService', 'ProcessErrors', 'i18n', (ConfigService, ProcessErrors, i18n) => { - return ConfigService.getConfig() - .then(response => response) - .catch(({data, status}) => { - ProcessErrors(null, data, status, null, { - hdr: i18n._('Error!'), - msg: i18n._('Failed to get config. GET returned status: ') + status - }); - }); - }] - } - } - }); - - editJobTemplate = stateDefinitions.generateTree({ - name: 'templates.editJobTemplate', - url: '/job_template/:job_template_id', - modes: ['edit'], - form: 'JobTemplateForm', - controllers: { - edit: 'JobTemplateEdit' - }, - data: { - activityStream: true, - activityStreamTarget: 'job_template', - activityStreamId: 'job_template_id' - }, - breadcrumbs: { - edit: '{{breadcrumb.job_template_name}}' - }, - resolve: { - edit: { - jobTemplateData: ['$stateParams', 'TemplatesService', 'ProcessErrors', 'i18n', - function($stateParams, TemplatesService, ProcessErrors, i18n) { - return TemplatesService.getJobTemplate($stateParams.job_template_id) - .then(function(res) { - return res.data; - }).catch(function(response){ - ProcessErrors(null, response.data, response.status, null, { - hdr: i18n._('Error!'), - msg: i18n._('Failed to get job template. GET returned status: ') + - response.status - }); - }); - }], - projectGetPermissionDenied: ['Rest', 'ProcessErrors', 'jobTemplateData', 'i18n', - function(Rest, ProcessErrors, jobTemplateData, i18n) { - if(jobTemplateData.related.project) { - Rest.setUrl(jobTemplateData.related.project); - return Rest.get() - .then(() => { - return false; - }) - .catch(({data, status}) => { - if (status !== 403) { - ProcessErrors(null, data, status, null, { - hdr: i18n._('Error!'), - msg: i18n._('Failed to get project. GET returned status: ') + status - }); - return false; - } - else { - return true; - } - }); - } - else { - return false; - } - }], - inventoryGetPermissionDenied: ['Rest', 'ProcessErrors', 'jobTemplateData', 'i18n', - function(Rest, ProcessErrors, jobTemplateData, i18n) { - if(jobTemplateData.related.inventory) { - Rest.setUrl(jobTemplateData.related.inventory); - return Rest.get() - .then(() => { - return false; - }) - .catch(({data, status}) => { - if (status !== 403) { - ProcessErrors(null, data, status, null, { - hdr: i18n._('Error!'), - msg: i18n._('Failed to get project. GET returned status: ') + status - }); - return false; - } - else { - return true; - } - }); - } - else { - return false; - } - }], - InstanceGroupsData: ['$stateParams', 'Rest', 'GetBasePath', 'ProcessErrors', 'i18n', - function($stateParams, Rest, GetBasePath, ProcessErrors, i18n){ - let path = `${GetBasePath('job_templates')}${$stateParams.job_template_id}/instance_groups/`; - Rest.setUrl(path); - return Rest.get() - .then(({data}) => { - if (data.results.length > 0) { - return data.results; - } - }) - .catch(({data, status}) => { - ProcessErrors(null, data, status, null, { - hdr: i18n._('Error!'), - msg: i18n._('Failed to get instance groups. GET returned status: ') + status - }); - }); - }], - availableLabels: ['ProcessErrors', 'TemplatesService', 'i18n', - function(ProcessErrors, TemplatesService, i18n) { - return TemplatesService.getAllLabelOptions() - .then(function(labels){ - return labels; - }).catch(function(response){ - ProcessErrors(null, response.data, response.status, null, { - hdr: i18n._('Error!'), - msg: i18n._('Failed to get labels. GET returned status: ') + - response.status - }); - }); - }], - selectedLabels: ['$stateParams', 'TemplatesService', 'ProcessErrors', 'i18n', - function($stateParams, TemplatesService, ProcessErrors, i18n) { - return TemplatesService.getAllJobTemplateLabels($stateParams.job_template_id) - .then(function(labels){ - return labels; - }).catch(function(response){ - ProcessErrors(null, response.data, response.status, null, { - hdr: i18n._('Error!'), - msg: i18n._('Failed to get workflow job template labels. GET returned status: ') + - response.status - }); - }); - }], - ConfigData: ['ConfigService', 'ProcessErrors', 'i18n', (ConfigService, ProcessErrors, i18n) => { - return ConfigService.getConfig() - .then(response => response) - .catch(({data, status}) => { - ProcessErrors(null, data, status, null, { - hdr: i18n._('Error!'), - msg: i18n._('Failed to get config. GET returned status: ') + status - }); - }); - }], - isNotificationAdmin: ['Rest', 'ProcessErrors', 'GetBasePath', 'i18n', - function(Rest, ProcessErrors, GetBasePath, i18n) { - Rest.setUrl(`${GetBasePath('organizations')}?role_level=notification_admin_role&page_size=1`); - return Rest.get() - .then(({data}) => { - return data.count > 0; - }) - .catch(({data, status}) => { - ProcessErrors(null, data, status, null, { - hdr: i18n._('Error!'), - msg: i18n._('Failed to get organizations for which this user is a notification administrator. GET returned ') + status - }); - }); - }], - webhookKey: ['Rest', 'ProcessErrors', 'jobTemplateData', 'i18n', - function(Rest, ProcessErrors, jobTemplateData, i18n) { - Rest.setUrl(jobTemplateData.related.webhook_key); - return Rest.get() - .then(({ data = {} }) => { - return data.webhook_key || ''; - }) - .catch(({data, status}) => { - if (status === 403) { - return; - } - ProcessErrors(null, data, status, null, { - hdr: i18n._('Error!'), - msg: i18n._('Failed to get webhook key GET returned ') + status - }); - }); - }], - } - } - }); - - addWorkflow = stateDefinitions.generateTree({ - name: 'templates.addWorkflowJobTemplate', - url: '/add_workflow_job_template', - modes: ['add'], - form: 'WorkflowForm', - controllers: { - add: 'WorkflowAdd' - }, - resolve: { - add: { - Inventory: ['$stateParams', 'Rest', 'GetBasePath', 'ProcessErrors', 'i18n', - function($stateParams, Rest, GetBasePath, ProcessErrors, i18n){ - if($stateParams.inventory_id){ - let path = `${GetBasePath('inventory')}${$stateParams.inventory_id}`; - Rest.setUrl(path); - return Rest.get(). - then(function(data){ - return data.data; - }).catch(function(response) { - ProcessErrors(null, response.data, response.status, null, { - hdr: i18n._('Error!'), - msg: i18n._('Failed to get inventory info. GET returned status: ') + - response.status - }); - }); - } - }], - availableLabels: ['ProcessErrors', 'TemplatesService', 'i18n', - function(ProcessErrors, TemplatesService, i18n) { - return TemplatesService.getAllLabelOptions() - .then(function(labels){ - return labels; - }).catch(function(response){ - ProcessErrors(null, response.data, response.status, null, { - hdr: i18n._('Error!'), - msg: i18n._('Failed to get labels. GET returned status: ') + - response.status - }); - }); - }], - checkPermissions: ['TemplatesService', 'Alert', 'ProcessErrors', '$state', 'i18n', - function(TemplatesService, Alert, ProcessErrors, $state, i18n) { - return TemplatesService.getWorkflowJobTemplateOptions() - .then(function(data) { - if (!data.actions.POST) { - $state.go("^"); - Alert(i18n._('Permission Error'), i18n._('You do not have permission to add a workflow job template.'), 'alert-info'); - } - }).catch(function(response){ - ProcessErrors(null, response.data, response.status, null, { - hdr: i18n._('Error!'), - msg: i18n._('Failed to get workflow job template options. OPTIONS returned status: ') + - response.status - }); - }); - }] - } - } - }); - - editWorkflow = stateDefinitions.generateTree({ - name: 'templates.editWorkflowJobTemplate', - url: '/workflow_job_template/:workflow_job_template_id', - modes: ['edit'], - form: 'WorkflowForm', - controllers: { - edit: 'WorkflowEdit' - }, - data: { - activityStream: true, - activityStreamTarget: 'workflow_job_template', - activityStreamId: 'workflow_job_template_id' - }, - breadcrumbs: { - edit: '{{breadcrumb.workflow_job_template_name}}' - }, - resolve: { - edit: { - Inventory: ['$stateParams', 'Rest', 'GetBasePath', 'ProcessErrors', 'i18n', - function($stateParams, Rest, GetBasePath, ProcessErrors, i18n){ - if($stateParams.inventory_id){ - let path = `${GetBasePath('inventory')}${$stateParams.inventory_id}`; - Rest.setUrl(path); - return Rest.get(). - then(function(data){ - return data.data; - }).catch(function(response) { - ProcessErrors(null, response.data, response.status, null, { - hdr: i18n._('Error!'), - msg: i18n._('Failed to get inventory info. GET returned status: ') + - response.status - }); - }); - } - }], - availableLabels: ['ProcessErrors', 'TemplatesService', 'i18n', - function(ProcessErrors, TemplatesService, i18n) { - return TemplatesService.getAllLabelOptions() - .then(function(labels){ - return labels; - }).catch(function(response){ - ProcessErrors(null, response.data, response.status, null, { - hdr: i18n._('Error!'), - msg: i18n._('Failed to get labels. GET returned status: ') + - response.status - }); - }); - }], - selectedLabels: ['$stateParams', 'TemplatesService', 'ProcessErrors', 'i18n', - function($stateParams, TemplatesService, ProcessErrors, i18n) { - return TemplatesService.getAllWorkflowJobTemplateLabels($stateParams.workflow_job_template_id) - .then(function(labels){ - return labels; - }).catch(function(response){ - ProcessErrors(null, response.data, response.status, null, { - hdr: i18n._('Error!'), - msg: i18n._('Failed to get workflow job template labels. GET returned status: ') + - response.status - }); - }); - }], - workflowJobTemplateData: ['$stateParams', 'TemplatesService', 'ProcessErrors', 'i18n', - function($stateParams, TemplatesService, ProcessErrors, i18n) { - return TemplatesService.getWorkflowJobTemplate($stateParams.workflow_job_template_id) - .then(function(res) { - return res.data; - }).catch(function(response){ - ProcessErrors(null, response.data, response.status, null, { - hdr: i18n._('Error!'), - msg: i18n._('Failed to get workflow job template. GET returned status: ') + - response.status - }); - }); - }], - workflowLaunch: ['$stateParams', 'WorkflowJobTemplateModel', - function($stateParams, WorkflowJobTemplate) { - let workflowJobTemplate = new WorkflowJobTemplate(); - - return workflowJobTemplate.getLaunch($stateParams.workflow_job_template_id) - .then(({data}) => { - return data; - }); - }], - isNotificationAdmin: ['Rest', 'ProcessErrors', 'GetBasePath', 'i18n', - function(Rest, ProcessErrors, GetBasePath, i18n) { - Rest.setUrl(`${GetBasePath('organizations')}?role_level=notification_admin_role&page_size=1`); - return Rest.get() - .then(({data}) => { - return data.count > 0; - }) - .catch(({data, status}) => { - ProcessErrors(null, data, status, null, { - hdr: i18n._('Error!'), - msg: i18n._('Failed to get organizations for which this user is a notification administrator. GET returned ') + status - }); - }); - }], - webhookKey: ['Rest', 'ProcessErrors', 'workflowJobTemplateData', 'i18n', - function(Rest, ProcessErrors, workflowJobTemplateData, i18n) { - Rest.setUrl(workflowJobTemplateData.related.webhook_key); - return Rest.get() - .then(({ data = {} }) => { - return data.webhook_key || ''; - }) - .catch(({data, status}) => { - if (status === 403) { - return; - } - ProcessErrors(null, data, status, null, { - hdr: i18n._('Error!'), - msg: i18n._('Failed to get webhook key GET returned ') + status - }); - }); - }], - } - } - }); - - workflowMaker = { - name: 'templates.editWorkflowJobTemplate.workflowMaker', - url: '/workflow-maker', - data: { - formChildState: true - }, - params: { - wf_maker_template_search: { - value: { - order_by: 'name', - page_size: '10', - role_level: 'execute_role', - type: 'workflow_job_template,job_template' - }, - squash: false, - dynamic: true - }, - wf_maker_project_search: { - value: { - order_by: 'name', - page_size: '10' - }, - squash: true, - dynamic: true - }, - wf_maker_inventory_source_search: { - value: { - not__source: '', - order_by: 'name', - page_size: '10' - }, - squash: true, - dynamic: true - } - }, - views: { - 'modal': { - template: `` - } - } - }; - - return Promise.all([ - addJobTemplate, - editJobTemplate, - addWorkflow, - editWorkflow - ]).then((generated) => { - return { - states: _.reduce(generated, (result, definition) => { - return result.concat(definition.states); - }, [ - stateExtender.buildDefinition(listRoute), - stateExtender.buildDefinition(templateCompletedJobsRoute), - stateExtender.buildDefinition(workflowJobTemplateCompletedJobsRoute), - stateExtender.buildDefinition(workflowMaker), - stateExtender.buildDefinition(jobTemplatesSchedulesListRoute), - stateExtender.buildDefinition(jobTemplatesSchedulesAddRoute), - stateExtender.buildDefinition(jobTemplatesSchedulesEditRoute), - stateExtender.buildDefinition(workflowSchedulesRoute), - stateExtender.buildDefinition(workflowSchedulesAddRoute), - stateExtender.buildDefinition(workflowSchedulesEditRoute) - ]) - }; - }); - } - - stateTree = { - name: 'templates.**', - url: '/templates', - lazyLoad: () => generateStateTree() - }; - - $stateProvider.state(stateTree); - - } - ]); \ No newline at end of file diff --git a/awx/ui/client/src/templates/prompt/main.js b/awx/ui/client/src/templates/prompt/main.js deleted file mode 100644 index dd97c78cf129..000000000000 --- a/awx/ui/client/src/templates/prompt/main.js +++ /dev/null @@ -1,17 +0,0 @@ -import promptDirective from './prompt.directive'; -import promptInventory from './steps/inventory/prompt-inventory.directive'; -import promptCredential from './steps/credential/prompt-credential.directive'; -import promptOtherPrompts from './steps/other-prompts/prompt-other-prompts.directive'; -import promptSurvey from './steps/survey/prompt-survey.directive'; -import promptPreview from './steps/preview/prompt-preview.directive'; -import promptService from './prompt.service'; - -export default - angular.module('prompt', []) - .directive('prompt', promptDirective) - .directive('promptInventory', promptInventory) - .directive('promptCredential', promptCredential) - .directive('promptOtherPrompts', promptOtherPrompts) - .directive('promptSurvey', promptSurvey) - .directive('promptPreview', promptPreview) - .service('PromptService', promptService); diff --git a/awx/ui/client/src/templates/prompt/prompt.block.less b/awx/ui/client/src/templates/prompt/prompt.block.less deleted file mode 100644 index 6dcef1de705a..000000000000 --- a/awx/ui/client/src/templates/prompt/prompt.block.less +++ /dev/null @@ -1,185 +0,0 @@ -.Prompt .modal-dialog { - max-width: calc(100vw - 10px); - width: 700px -} -.Prompt-step { - margin-top: 20px; -} -.Prompt-footer { - display: flex; - flex: 0 0 auto; - flex-wrap: wrap; - margin-top: 15px; - justify-content: flex-end; - align-items: flex-end; -} -.Prompt-actionButton { - background-color: @submit-button-bg; - border: 1px solid @submit-button-bg; - color: @submit-button-text; - text-transform: uppercase; - border-radius: 5px; - height: 30px; - padding-left:15px; - padding-right: 15px; - margin: 10px 0; - min-width: 85px; - margin-left: 20px; -} -.Prompt-actionButton:disabled { - background-color: @d7grey; - border-color: @d7grey; - opacity: 0.65; -} -.Prompt-actionButton:enabled:hover, -.Prompt-actionButton:enabled:focus { - background-color: @submit-button-bg-hov; - border: 1px solid @submit-button-bg-hov; -} -.Prompt-defaultButton{ - background-color: @default-bg; - color: @btn-txt; - text-transform: uppercase; - border-radius: 5px; - border: 1px solid @btn-bord; - padding-left:15px; - padding-right: 15px; - margin: 10px 0; - height: 30px; - min-width: 85px; -} -.Prompt-defaultButton:hover{ - background-color: @btn-bg-hov; - color: @btn-txt; -} -.Prompt-revertLink { - font-size: 12px; -} - -.Prompt-selectedItem { - display: flex; - flex: 1 0 auto; - align-items: baseline; -} -.Prompt-selectedItemInfo { - display: flex; - flex: 0 0 100%; - background-color: @default-no-items-bord; - border: 1px solid @default-border; - padding: 10px; - border-radius: 5px; - margin-bottom: 20px; -} -.Prompt-selectedItemRevert { - display: flex; - flex: 0 0 auto; -} -.Prompt-credentialSubSection { - display: flex; - justify-content: flex-end; - align-items: center; - margin-bottom: 15px; -} -.Prompt-selectedItemLabel, .Prompt-label { - color: @default-interface-txt; - margin-right: 10px; -} -.Prompt-label { - line-height: 24px; -} -.Prompt-selectedItemNone { - color: @default-icon; -} -.Prompt-selectedItemContainer { - display: block; - width: 100%; -} -.Prompt-instructions { - color: @default-interface-txt; - margin-top: 25px; - margin-bottom: 15px; -} -.Prompt-passwordButton { - padding: 4px 13px; -} -.Prompt .List-noItems { - margin-top: auto; -} -.Prompt-selectedItemLabel { - flex: 0 0 80px; - line-height: 29px; -} -.Prompt-previewTags--outer { - display: flex; - flex: 1 0 auto; - width: ~"calc(100% - 140px)"; -} -.Prompt-previewTags--inner { - display: flex; - flex-wrap: wrap; - align-items: flex-start; -} -.Prompt-previewTagLabel { - color: @default-interface-txt; -} -.Prompt-previewTagLabel--deletable{ - color: @default-list-header-bg; -} -.Prompt-previewTagRevert { - display: flex; - align-items: center; - justify-content: center; -} -.Prompt-previewTagContainer { - display: flex; - flex-flow: row wrap; -} -.Prompt-previewRow--flex { - display: flex; - margin-bottom: 10px; -} -.Prompt-previewRow--noflex { - margin-bottom: 10px; -} -.Prompt-previewRowTitle { - width: 150px; - color: @default-interface-txt; - text-transform: uppercase; -} -.Prompt-previewRowValue { - max-width: 508px; - display: flex; - flex-wrap: wrap; - align-items: flex-start; -} -.Prompt-noSelectedItem { - height: 30px; - line-height: 30px; - font-style: italic; - color: @default-interface-txt; -} -.Prompt-previewTag { - border-radius: 5px; - padding: 2px 10px; - margin: 3px 0px; - font-size: 12px; - color: @default-bg; - background-color: @default-link; - margin-right: 5px; - max-width: 100%; - display: inline-block; -} -.Prompt-credentialSubSection .select2 { - width: 50% !important; -} -.Prompt-credentialTypeMissing { - margin-bottom: 20px; - color: @default-err; -} -.Prompt-credsWarning { - margin-bottom: 5px; - color: @default-err; -} -.Prompt-credsWarningList { - margin-bottom: 20px; -} diff --git a/awx/ui/client/src/templates/prompt/prompt.controller.js b/awx/ui/client/src/templates/prompt/prompt.controller.js deleted file mode 100644 index 53a8370a0cd0..000000000000 --- a/awx/ui/client/src/templates/prompt/prompt.controller.js +++ /dev/null @@ -1,361 +0,0 @@ -export default [ 'ProcessErrors', 'CredentialTypeModel', 'TemplatesStrings', '$filter', - function (ProcessErrors, CredentialType, strings, $filter) { - - const vm = this || {}; - - vm.strings = strings; - - let scope; - let modal; - let activeTab; - - vm.init = (_scope_) => { - scope = _scope_; - ({ modal } = scope[scope.ns]); - - scope.$watch('vm.promptData.triggerModalOpen', () => { - vm.actionButtonClicked = false; - if(vm.promptData && vm.promptData.triggerModalOpen) { - scope.$emit('launchModalOpen', true); - vm.promptDataClone = _.cloneDeep(vm.promptData); - - vm.steps = { - inventory: { - includeStep: false - }, - credential: { - includeStep: false - }, - other_prompts: { - includeStep: false - }, - survey: { - includeStep: false - }, - preview: { - includeStep: true, - tab: { - _active: false, - _disabled: true - } - } - }; - - let order = 1; - - vm.actionText = vm.actionText ? vm.actionText : strings.get('prompt.LAUNCH'); - - vm.forms = {}; - - let credentialType = new CredentialType(); - - credentialType.http.get({ params: { page_size: 200 }}) - .then( (response) => { - vm.promptDataClone.prompts.credentials.credentialTypes = {}; - vm.promptDataClone.prompts.credentials.credentialTypeOptions = []; - response.data.results.forEach((credentialTypeRow => { - vm.promptDataClone.prompts.credentials.credentialTypes[credentialTypeRow.id] = credentialTypeRow.kind; - if(credentialTypeRow.kind.match(/^(cloud|net|ssh|vault)$/)) { - if(credentialTypeRow.kind === 'ssh') { - vm.promptDataClone.prompts.credentials.credentialKind = credentialTypeRow.id.toString(); - } - vm.promptDataClone.prompts.credentials.credentialTypeOptions.push({ - name: credentialTypeRow.name, - value: credentialTypeRow.id - }); - } - })); - - vm.promptDataClone.prompts.credentials.passwords = {}; - - vm.promptDataClone.prompts.credentials.value.forEach((credential) => { - if(credential.inputs) { - if(credential.inputs.password && credential.inputs.password === "ASK") { - vm.promptDataClone.prompts.credentials.passwords.ssh_password = { - id: credential.id, - name: credential.name - }; - } - if(credential.inputs.become_password && credential.inputs.become_password === "ASK") { - vm.promptDataClone.prompts.credentials.passwords.become_password = { - id: credential.id, - name: credential.name - }; - } - if(credential.inputs.ssh_key_unlock && credential.inputs.ssh_key_unlock === "ASK") { - vm.promptDataClone.prompts.credentials.passwords.ssh_key_unlock = { - id: credential.id, - name: credential.name - }; - } - if(credential.inputs.vault_password && credential.inputs.vault_password === "ASK") { - if(!vm.promptDataClone.prompts.credentials.passwords.vault) { - vm.promptDataClone.prompts.credentials.passwords.vault = []; - } - vm.promptDataClone.prompts.credentials.passwords.vault.push({ - id: credential.id, - name: credential.name, - vault_id: credential.inputs.vault_id - }); - } - } else if(credential.passwords_needed && credential.passwords_needed.length > 0) { - credential.passwords_needed.forEach((passwordNeeded) => { - if (passwordNeeded === "ssh_password" || - passwordNeeded === "become_password" || - passwordNeeded === "ssh_key_unlock" - ) { - vm.promptDataClone.prompts.credentials.passwords[passwordNeeded] = { - id: credential.id, - name: credential.name - }; - } else if (passwordNeeded.startsWith("vault_password")) { - let vault_id = null; - if (passwordNeeded.includes('.')) { - vault_id = passwordNeeded.split(/\.(.+)/)[1]; - } - - if (!vm.promptDataClone.prompts.credentials.passwords.vault) { - vm.promptDataClone.prompts.credentials.passwords.vault = []; - } - - vm.promptDataClone.prompts.credentials.passwords.vault.push({ - id: credential.id, - name: credential.name, - vault_id: vault_id - }); - } - }); - } - }); - - vm.promptDataClone.credentialTypeMissing = []; - - vm.promptDataClone.prompts.variables.ignore = vm.promptDataClone.launchConf.ignore_ask_variables; - - if(vm.promptDataClone.launchConf.ask_inventory_on_launch) { - vm.steps.inventory.includeStep = true; - vm.steps.inventory.tab = { - _active: true, - order: order - }; - activeTab = activeTab || vm.steps.inventory.tab; - order++; - } - if (vm.promptDataClone.launchConf.ask_credential_on_launch || - (_.has(vm, 'promptDataClone.prompts.credentials.passwords.vault') && - vm.promptDataClone.prompts.credentials.passwords.vault.length > 0) || - _.has(vm, 'promptDataClone.prompts.credentials.passwords.ssh_key_unlock') || - _.has(vm, 'promptDataClone.prompts.credentials.passwords.become_password') || - _.has(vm, 'promptDataClone.prompts.credentials.passwords.ssh_password') - ) { - vm.steps.credential.includeStep = true; - vm.steps.credential.tab = { - _active: order === 1 ? true : false, - _disabled: (order === 1 || vm.readOnlyPrompts) ? false : true, - order: order - }; - activeTab = activeTab || vm.steps.credential.tab; - order++; - } - if(vm.promptDataClone.launchConf.ask_verbosity_on_launch || vm.promptDataClone.launchConf.ask_job_type_on_launch || vm.promptDataClone.launchConf.ask_limit_on_launch || vm.promptDataClone.launchConf.ask_tags_on_launch || vm.promptDataClone.launchConf.ask_skip_tags_on_launch || (vm.promptDataClone.launchConf.ask_variables_on_launch && !vm.promptDataClone.launchConf.ignore_ask_variables) || vm.promptDataClone.launchConf.ask_diff_mode_on_launch || vm.promptDataClone.launchConf.ask_scm_branch_on_launch) { - vm.steps.other_prompts.includeStep = true; - vm.steps.other_prompts.tab = { - _active: order === 1 ? true : false, - _disabled: (order === 1 || vm.readOnlyPrompts) ? false : true, - order: order - }; - activeTab = activeTab || vm.steps.other_prompts.tab; - order++; - - let codemirror = () => { - return { - validate:{} - }; - }; - vm.codeMirror = new codemirror(); - } - if(vm.promptDataClone.launchConf.survey_enabled) { - vm.steps.survey.includeStep = true; - vm.steps.survey.tab = { - _active: order === 1 ? true : false, - _disabled: (order === 1 || vm.readOnlyPrompts) ? false : true, - order: order - }; - activeTab = activeTab || vm.steps.survey.tab; - order++; - } - vm.steps.preview.tab.order = order; - vm.steps.preview.tab._disabled = vm.readOnlyPrompts ? false : true; - modal.show($filter('sanitize')(vm.promptDataClone.templateName)); - vm.promptData.triggerModalOpen = false; - - vm._savedPromptData = { - 1: _.cloneDeep(vm.promptDataClone) - }; - Object.keys(vm.steps).forEach(step => { - if (!vm.steps[step].tab) { - return; - } - vm.steps[step].tab._onClickActivate = () => { - if (vm._savedPromptData[vm.steps[step].tab.order]) { - vm.promptDataClone = vm._savedPromptData[vm.steps[step].tab.order]; - } - Object.keys(vm.steps).forEach(tabStep => { - if (!vm.steps[tabStep].tab) { - return; - } - if (vm.steps[tabStep].tab.order < vm.steps[step].tab.order) { - vm.steps[tabStep].tab._disabled = false; - vm.steps[tabStep].tab._active = false; - } else if (vm.steps[tabStep].tab.order === vm.steps[step].tab.order) { - vm.steps[tabStep].tab._disabled = false; - vm.steps[tabStep].tab._active = true; - } else { - vm.steps[tabStep].tab._disabled = true; - vm.steps[tabStep].tab._active = false; - } - }); - scope.$broadcast('promptTabChange', { step }); - }; - }); - - modal.onClose = () => { - scope.$emit('launchModalOpen', false); - }; - }) - .catch(({data, status}) => { - ProcessErrors(scope, data, status, null, { - hdr: 'Error!', - msg: 'Failed to get credential types. GET status: ' + status - }); - }); - } - }, true); - }; - - function getSelectedTags(tagId) { - const selectedTags = []; - const choiceElements = $(tagId).siblings(".select2").first() - .find(".select2-selection__choice"); - choiceElements.each((index, option) => { - selectedTags.push({ - value: option.title, - name: option.title, - label: option.title - }); - }); - return selectedTags; - } - - function consolidateTags (tags, otherTags) { - const seen = []; - const consolidated = []; - tags.forEach(tag => { - if (!seen.includes(tag.value)) { - seen.push(tag.value); - consolidated.push(tag); - } - }); - otherTags.forEach(tag => { - if (!seen.includes(tag.value)) { - seen.push(tag.value); - consolidated.push(tag); - } - }); - return consolidated; - } - - vm.next = (currentTab) => { - if(_.has(vm, 'steps.other_prompts.tab._active') && vm.steps.other_prompts.tab._active === true){ - try { - if (vm.codeMirror.validate) { - vm.codeMirror.validate(); - } - } catch (err) { - event.preventDefault(); - return; - } - - // The current tag input state lives somewhere in the associated select2 - // widgetry and isn't directly tied to the vm, so extract the tag values - // and update the vm to keep it in sync. - if (vm.promptDataClone.launchConf.ask_tags_on_launch) { - vm.promptDataClone.prompts.tags.value = consolidateTags( - angular.copy(vm.promptDataClone.prompts.tags.value), - getSelectedTags("#job_launch_job_tags") - ); - } - if (vm.promptDataClone.launchConf.ask_skip_tags_on_launch) { - vm.promptDataClone.prompts.skipTags.value = consolidateTags( - angular.copy(vm.promptDataClone.prompts.skipTags.value), - getSelectedTags("#job_launch_skip_tags") - ); - } - } - - let nextStep; - Object.keys(vm.steps).forEach(step => { - if (!vm.steps[step].tab) { - return; - } - if (vm.steps[step].tab.order === currentTab.order + 1) { - nextStep = step; - } - }); - - if (!nextStep) { - return; - } - - // Save the current promptData state in case we need to revert - vm._savedPromptData[currentTab.order] = _.cloneDeep(vm.promptDataClone); - Object.keys(vm.steps).forEach(tabStep => { - if (!vm.steps[tabStep].tab) { - return; - } - if (vm.steps[tabStep].tab.order < vm.steps[nextStep].tab.order) { - vm.steps[tabStep].tab._disabled = false; - vm.steps[tabStep].tab._active = false; - } else if (vm.steps[tabStep].tab.order === vm.steps[nextStep].tab.order) { - vm.steps[tabStep].tab._disabled = false; - vm.steps[tabStep].tab._active = true; - } else { - vm.steps[tabStep].tab._disabled = true; - vm.steps[tabStep].tab._active = false; - } - }); - scope.$broadcast('promptTabChange', { step: nextStep }); - }; - - vm.keypress = (event) => { - if (vm.steps.survey.tab && vm.steps.survey.tab._active && !vm.readOnlyPrompts && !vm.forms.survey.$valid) { - return; - } - if (document.activeElement.type === 'textarea') { - return; - } - if (event.key === 'Enter') { - vm.next(activeTab); - } - }; - - vm.finish = () => { - // Disable the action button to prevent double clicking - vm.actionButtonClicked = true; - - _.forEach(vm.promptDataClone, (value, key) => { - vm.promptData[key] = value; - }); - - vm.promptData.triggerModalOpen = false; - if(vm.onFinish) { - vm.onFinish(); - } - modal.hide(); - }; - - vm.cancel = () => { - vm.promptData.triggerModalOpen = false; - modal.hide(); - }; -}]; diff --git a/awx/ui/client/src/templates/prompt/prompt.directive.js b/awx/ui/client/src/templates/prompt/prompt.directive.js deleted file mode 100644 index dcc25fb78475..000000000000 --- a/awx/ui/client/src/templates/prompt/prompt.directive.js +++ /dev/null @@ -1,26 +0,0 @@ -import promptController from './prompt.controller'; -export default [ 'templateUrl', - function(templateUrl) { - return { - scope: { - promptData: '=', - onFinish: '&', - actionText: '@', - preventCredsWithPasswords: '<', - readOnlyPrompts: '=' - }, - templateUrl: templateUrl('templates/prompt/prompt'), - replace: true, - transclude: true, - restrict: 'E', - controller: promptController, - controllerAs: 'vm', - bindToController: true, - link: function(scope, el, attrs, promptController) { - scope.ns = 'launch'; - scope[scope.ns] = { modal: {} }; - - promptController.init(scope); - } - }; -}]; diff --git a/awx/ui/client/src/templates/prompt/prompt.partial.html b/awx/ui/client/src/templates/prompt/prompt.partial.html deleted file mode 100644 index 217ddddf9483..000000000000 --- a/awx/ui/client/src/templates/prompt/prompt.partial.html +++ /dev/null @@ -1,61 +0,0 @@ -
- - - {{:: vm.strings.get('prompt.INVENTORY') }} - {{:: vm.strings.get('prompt.CREDENTIAL') }} - {{:: vm.strings.get('prompt.OTHER_PROMPTS') }} - {{:: vm.strings.get('prompt.SURVEY') }} - {{:: vm.strings.get('prompt.PREVIEW') }} - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- -
-
- -
-
diff --git a/awx/ui/client/src/templates/prompt/prompt.service.js b/awx/ui/client/src/templates/prompt/prompt.service.js deleted file mode 100644 index d5e801a57390..000000000000 --- a/awx/ui/client/src/templates/prompt/prompt.service.js +++ /dev/null @@ -1,310 +0,0 @@ -function PromptService (Empty, $filter) { - - this.processPromptValues = (params) => { - const prompts = { - credentials: {}, - inventory: {}, - variables: {}, - verbosity: {}, - jobType: {}, - limit: {}, - tags: {}, - skipTags: {}, - diffMode: {}, - scmBranch: {} - }; - - prompts.credentials.value = _.has(params, 'launchConf.defaults.credentials') ? _.cloneDeep(params.launchConf.defaults.credentials) : []; - prompts.inventory.value = _.has(params, 'currentValues.summary_fields.inventory') ? params.currentValues.summary_fields.inventory : (_.has(params, 'launchConf.defaults.inventory') ? params.launchConf.defaults.inventory : null); - - const skipTags = _.has(params, 'currentValues.skip_tags') && params.currentValues.skip_tags ? params.currentValues.skip_tags : (_.has(params, 'launchConf.defaults.skip_tags') ? params.launchConf.defaults.skip_tags : ""); - const jobTags = _.has(params, 'currentValues.job_tags') && params.currentValues.job_tags ? params.currentValues.job_tags : (_.has(params, 'launchConf.defaults.job_tags') ? params.launchConf.defaults.job_tags : ""); - - let extraVars = ''; - - const hasCurrentExtraVars = _.get(params, 'currentValues.extra_data'), - hasDefaultExtraVars = _.get(params, 'launchConf.defaults.extra_vars'); - - if(hasCurrentExtraVars && hasDefaultExtraVars) { - let currentExtraVars = {}; - let defaultExtraVars = {}; - if (typeof params.currentValues.extra_data === 'object') { - currentExtraVars = params.currentValues.extra_data; - } else if (typeof params.currentValues.extra_data === 'string') { - currentExtraVars = jsyaml.safeDump(params.currentValues.extra_data); - } - - if (typeof params.launchConf.defaults.extra_vars === 'object') { - defaultExtraVars = params.launchConf.defaults.extra_vars; - } else if (typeof params.launchConf.defaults.extra_vars === 'string') { - defaultExtraVars = jsyaml.safeLoad(params.launchConf.defaults.extra_vars); - } - extraVars = '---\n' + jsyaml.safeDump(_.merge(defaultExtraVars, currentExtraVars)); - } else if(hasCurrentExtraVars) { - if (typeof params.currentValues.extra_data === 'object') { - extraVars = '---\n' + jsyaml.safeDump(params.currentValues.extra_data); - } else if (typeof params.currentValues.extra_data === 'string') { - extraVars = params.currentValues.extra_data; - } - } else if(hasDefaultExtraVars) { - extraVars = params.launchConf.defaults.extra_vars; - } - - prompts.variables.value = extraVars && extraVars !== '' ? extraVars : '---\n'; - prompts.verbosity.choices = _.get(params, 'launchOptions.actions.POST.verbosity.choices', []).map(c => ({label: c[1], value: c[0]})); - prompts.verbosity.value = _.has(params, 'currentValues.verbosity') && params.currentValues.verbosity ? _.find(prompts.verbosity.choices, item => item.value === params.currentValues.verbosity) : _.find(prompts.verbosity.choices, item => item.value === params.launchConf.defaults.verbosity); - prompts.jobType.choices = _.get(params, 'launchOptions.actions.POST.job_type.choices', []).map(c => ({label: c[1], value: c[0]})); - prompts.jobType.value = _.has(params, 'currentValues.job_type') && params.currentValues.job_type ? _.find(prompts.jobType.choices, item => item.value === params.currentValues.job_type) : _.find(prompts.jobType.choices, item => item.value === params.launchConf.defaults.job_type); - prompts.limit.value = _.has(params, 'currentValues.limit') && params.currentValues.limit ? params.currentValues.limit : (_.has(params, 'launchConf.defaults.limit') ? params.launchConf.defaults.limit : ""); - prompts.tags.value = (jobTags && jobTags !== "") ? jobTags.split(',').map((i) => ({name: i, label: i, value: i})) : []; - prompts.skipTags.value = (skipTags && skipTags !== "") ? skipTags.split(',').map((i) => ({name: i, label: i, value: i})) : []; - prompts.diffMode.value = _.has(params, 'currentValues.diff_mode') && typeof params.currentValues.diff_mode === 'boolean' ? params.currentValues.diff_mode : (_.has(params, 'launchConf.defaults.diff_mode') ? params.launchConf.defaults.diff_mode : null); - prompts.scmBranch.value = _.has(params, 'currentValues.scm_branch') && params.currentValues.scm_branch ? params.currentValues.scm_branch : (_.has(params, 'launchConf.defaults.scm_branch') ? params.launchConf.defaults.scm_branch : ""); - return prompts; - }; - - this.processSurveyQuestions = (params) => { - - let missingSurveyValue = false; - - for(let i=0; i { - const launchData = { - extra_vars: promptData.extraVars - }; - - if (promptData.launchConf.ask_tags_on_launch){ - launchData.job_tags = promptData.prompts.tags.value.map(a => a.value).join(); - } - if (promptData.launchConf.ask_skip_tags_on_launch){ - launchData.skip_tags = promptData.prompts.skipTags.value.map(a => a.value).join(); - } - if (promptData.launchConf.ask_limit_on_launch && _.has(promptData, 'prompts.limit.value')){ - launchData.limit = promptData.prompts.limit.value; - } - if (promptData.launchConf.ask_job_type_on_launch && _.has(promptData, 'prompts.jobType.value.value')) { - launchData.job_type = promptData.prompts.jobType.value.value; - } - if (promptData.launchConf.ask_verbosity_on_launch && _.has(promptData, 'prompts.verbosity.value.value')) { - launchData.verbosity = promptData.prompts.verbosity.value.value; - } - if (promptData.launchConf.ask_inventory_on_launch && _.has(promptData, 'prompts.inventory.value.id')) { - launchData.inventory_id = promptData.prompts.inventory.value.id; - } - if (promptData.launchConf.ask_credential_on_launch){ - launchData.credentials = []; - promptData.prompts.credentials.value.forEach((credential) => { - launchData.credentials.push(credential.id); - }); - } - if (promptData.launchConf.ask_diff_mode_on_launch && _.has(promptData, 'prompts.diffMode.value')) { - launchData.diff_mode = promptData.prompts.diffMode.value; - } - if (promptData.launchConf.ask_scm_branch_on_launch && _.has(promptData, 'prompts.scmBranch.value')) { - launchData.scm_branch = promptData.prompts.scmBranch.value; - } - if (promptData.prompts.credentials.passwords) { - _.forOwn(promptData.prompts.credentials.passwords, (val, key) => { - if (!launchData.credential_passwords) { - launchData.credential_passwords = {}; - } - if (key === "ssh_key_unlock") { - launchData.credential_passwords.ssh_key_unlock = val.value; - } else if (key !== "vault") { - launchData.credential_passwords[`${key}`] = val.value; - } else { - _.each(val, (vaultCred) => { - launchData.credential_passwords[vaultCred.vault_id ? `${key}_password.${vaultCred.vault_id}` : `${key}_password`] = vaultCred.value; - }); - } - }); - } - - if (_.get(promptData, 'templateType') === 'workflow_job_template') { - if (_.get(launchData, 'inventory_id', null) === null) { - // It's possible to get here on a workflow job template with an inventory prompt and no - // default value by selecting an inventory, removing it, selecting a different inventory, - // and then reverting. A null inventory_id may be accepted by the API for prompted workflow - // inventories in the future, but for now they will 400. As such, we intercept that case here - // and remove it from the request data prior to launching. - delete launchData.inventory_id; - } - } - - return launchData; - }; - - this.bundlePromptDataForRelaunch = (promptData) => { - const launchData = {}; - - if(promptData.relaunchHostType) { - launchData.hosts = promptData.relaunchHostType; - } - - if (promptData.prompts.credentials.passwords) { - _.forOwn(promptData.prompts.credentials.passwords, (val, key) => { - if (!launchData.credential_passwords) { - launchData.credential_passwords = {}; - } - if (key === "ssh_key_unlock") { - launchData.credential_passwords.ssh_key_unlock = val.value; - } else if (key !== "vault") { - launchData.credential_passwords[`${key}`] = val.value; - } else { - _.each(val, (vaultCred) => { - launchData.credential_passwords[vaultCred.vault_id ? `${key}_password.${vaultCred.vault_id}` : `${key}_password`] = vaultCred.value; - }); - } - }); - } - - return launchData; - }; - - this.bundlePromptDataForSaving = (params) => { - const promptDataToSave = params.dataToSave ? params.dataToSave : {}; - - if(params.promptData.launchConf.survey_enabled){ - if(!promptDataToSave.extra_data) { - promptDataToSave.extra_data = {}; - } - for (var i=0; i < params.promptData.surveyQuestions.length; i++){ - var fld = params.promptData.surveyQuestions[i].variable; - // grab all survey questions that have answers - if(params.promptData.surveyQuestions[i].required || (params.promptData.surveyQuestions[i].required === false && params.promptData.surveyQuestions[i].model.toString()!=="")) { - promptDataToSave.extra_data[fld] = params.promptData.surveyQuestions[i].model; - } - - if(params.promptData.surveyQuestions[i].required === false && _.isEmpty(params.promptData.surveyQuestions[i].model)) { - switch (params.promptData.surveyQuestions[i].type) { - // for optional text and text-areas, submit a blank string if min length is 0 - // -- this is confusing, for an explanation see: - // http://docs.ansible.com/ansible-tower/latest/html/userguide/job_templates.html#optional-survey-questions - // - case "text": - case "textarea": - if (params.promptData.surveyQuestions[i].min === 0) { - promptDataToSave.extra_data[fld] = ""; - } - break; - } - } - } - } - - const launchConfDefaults = _.get(params, ['promptData', 'launchConf', 'defaults'], {}); - - if(_.has(params, 'promptData.prompts.jobType.value.value') && _.get(params, 'promptData.launchConf.ask_job_type_on_launch')) { - promptDataToSave.job_type = launchConfDefaults.job_type && launchConfDefaults.job_type === params.promptData.prompts.jobType.value.value ? null : params.promptData.prompts.jobType.value.value; - } - if(_.has(params, 'promptData.prompts.tags.value') && _.get(params, 'promptData.launchConf.ask_tags_on_launch')){ - const templateDefaultJobTags = launchConfDefaults.job_tags.split(','); - promptDataToSave.job_tags = (_.isEqual(templateDefaultJobTags.sort(), params.promptData.prompts.tags.value.map(a => a.value).sort())) ? null : params.promptData.prompts.tags.value.map(a => a.value).join(); - } - if(_.has(params, 'promptData.prompts.skipTags.value') && _.get(params, 'promptData.launchConf.ask_skip_tags_on_launch')){ - const templateDefaultSkipTags = launchConfDefaults.skip_tags.split(','); - promptDataToSave.skip_tags = (_.isEqual(templateDefaultSkipTags.sort(), params.promptData.prompts.skipTags.value.map(a => a.value).sort())) ? null : params.promptData.prompts.skipTags.value.map(a => a.value).join(); - } - if(_.has(params, 'promptData.prompts.limit.value') && _.get(params, 'promptData.launchConf.ask_limit_on_launch')){ - promptDataToSave.limit = launchConfDefaults.limit && launchConfDefaults.limit === params.promptData.prompts.limit.value ? null : params.promptData.prompts.limit.value; - } - if(_.has(params, 'promptData.prompts.verbosity.value.value') && _.get(params, 'promptData.launchConf.ask_verbosity_on_launch')){ - promptDataToSave.verbosity = launchConfDefaults.verbosity && launchConfDefaults.verbosity === params.promptData.prompts.verbosity.value.value ? null : params.promptData.prompts.verbosity.value.value; - } - if(_.has(params, 'promptData.prompts.inventory.value') && _.get(params, 'promptData.launchConf.ask_inventory_on_launch')){ - promptDataToSave.inventory = launchConfDefaults.inventory && launchConfDefaults.inventory.id === params.promptData.prompts.inventory.value.id ? null : params.promptData.prompts.inventory.value.id; - } - if(_.has(params, 'promptData.prompts.diffMode.value') && _.get(params, 'promptData.launchConf.ask_diff_mode_on_launch')){ - promptDataToSave.diff_mode = launchConfDefaults.diff_mode && launchConfDefaults.diff_mode === params.promptData.prompts.diffMode.value ? null : params.promptData.prompts.diffMode.value; - } - if(_.has(params, 'promptData.prompts.scmBranch.value') && _.get(params, 'promptData.launchConf.ask_scm_branch_on_launch')){ - promptDataToSave.scm_branch = launchConfDefaults.scm_branch && launchConfDefaults.scm_branch === params.promptData.prompts.scmBranch.value ? null : params.promptData.prompts.scmBranch.value; - } - return promptDataToSave; - }; -} - -PromptService.$inject = ['Empty', '$filter']; - -export default PromptService; diff --git a/awx/ui/client/src/templates/prompt/steps/credential/prompt-credential.controller.js b/awx/ui/client/src/templates/prompt/steps/credential/prompt-credential.controller.js deleted file mode 100644 index 9258ecdbec43..000000000000 --- a/awx/ui/client/src/templates/prompt/steps/credential/prompt-credential.controller.js +++ /dev/null @@ -1,352 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default - [ 'CredentialList', 'QuerySet', 'GetBasePath', 'CreateSelect2', 'TemplatesStrings', - function(CredentialList, qs, GetBasePath, CreateSelect2, strings) { - const vm = this; - - vm.strings = strings; - - let scope; - let launch; - - let updateSelectedRow = () => { - if(scope.credentials && scope.credentials.length > 0) { - scope.credentials.forEach((credential, i) => { - scope.credentials[i].checked = 0; - - }); - scope.promptData.prompts.credentials.value.forEach((selectedCredential) => { - if (_.get(selectedCredential, 'inputs.vault_id') || _.get(selectedCredential, 'vault_id')) { - const vaultId = selectedCredential.vault_id ? selectedCredential.vault_id : _.get(selectedCredential, 'inputs.vault_id'); - selectedCredential.tag = `${selectedCredential.name } | ${vaultId}`; - } else { - selectedCredential.tag = selectedCredential.name; - } - if (selectedCredential.credential_type === parseInt(scope.promptData.prompts.credentials.credentialKind)) { - scope.credentials.forEach((credential, i) => { - if(scope.credentials[i].id === selectedCredential.id) { - scope.credentials[i].checked = 1; - } - }); - } - }); - } - }; - - let wipePasswords = (cred) => { - if(cred.passwords_needed) { - cred.passwords_needed.forEach((passwordNeeded => { - if(passwordNeeded === 'ssh_password') { - delete scope.promptData.prompts.credentials.passwords.ssh_password; - } else if(passwordNeeded === 'become_password') { - delete scope.promptData.prompts.credentials.passwords.become_password; - } else if(passwordNeeded === 'ssh_key_unlock') { - delete scope.promptData.prompts.credentials.passwords.ssh_key_unlock; - } else if(passwordNeeded.startsWith("vault_password")) { - for (let i = scope.promptData.prompts.credentials.passwords.vault.length - 1; i >= 0; i--) { - if(cred.id === scope.promptData.prompts.credentials.passwords.vault[i].id) { - scope.promptData.prompts.credentials.passwords.vault.splice(i, 1); - } - } - } - })); - } else if(cred.inputs && !_.isEmpty(cred.inputs)) { - if(cred.inputs.password && cred.inputs.password === "ASK") { - delete scope.promptData.prompts.credentials.passwords.ssh_password; - } - if(cred.inputs.become_password && cred.inputs.become_password === "ASK") { - delete scope.promptData.prompts.credentials.passwords.become_password; - } - if(cred.inputs.ssh_key_unlock && cred.inputs.ssh_key_unlock === "ASK") { - delete scope.promptData.prompts.credentials.passwords.ssh_key_unlock; - } - if(cred.inputs.vault_password && cred.inputs.vault_password === "ASK") { - for (let i = scope.promptData.prompts.credentials.passwords.vault.length - 1; i >= 0; i--) { - if(cred.id === scope.promptData.prompts.credentials.passwords.vault[i].id) { - scope.promptData.prompts.credentials.passwords.vault.splice(i, 1); - } - } - } - } - - }; - - let updateNeededPasswords = (cred) => { - if(cred.inputs) { - if(cred.inputs.password && cred.inputs.password === "ASK") { - scope.promptData.prompts.credentials.passwords.ssh_password = { - id: cred.id, - name: cred.name - }; - } - if(cred.inputs.become_password && cred.inputs.become_password === "ASK") { - scope.promptData.prompts.credentials.passwords.become_password = { - id: cred.id, - name: cred.name - }; - } - if(cred.inputs.ssh_key_unlock && cred.inputs.ssh_key_unlock === "ASK") { - scope.promptData.prompts.credentials.passwords.ssh_key_unlock = { - id: cred.id, - name: cred.name - }; - } - if(cred.inputs.vault_password && cred.inputs.vault_password === "ASK") { - if(!scope.promptData.prompts.credentials.passwords.vault) { - scope.promptData.prompts.credentials.passwords.vault = []; - } - scope.promptData.prompts.credentials.passwords.vault.push({ - id: cred.id, - name: cred.name, - vault_id: cred.inputs.vault_id - }); - } - } - }; - - let checkMissingCredType = (cred) => { - scope.promptData.launchConf.defaults.credentials.forEach((defaultCred) => { - if(cred.credential_type === defaultCred.credential_type) { - let credTypeLabel = ""; - scope.promptData.prompts.credentials.credentialTypeOptions.forEach((credTypeOption) => { - if(credTypeOption.value === defaultCred.credential_type) { - credTypeLabel = credTypeOption.name; - } - }); - - if(scope.promptData.prompts.credentials.credentialTypes[cred.credential_type] === "vault") { - if((_.get(cred, 'inputs.vault_id') ? _.get(cred, 'inputs.vault_id') : _.get(cred, 'vault_id')) === _.get(defaultCred, 'vault_id')) { - scope.promptData.credentialTypeMissing.push({ - credential_type: defaultCred.credential_type, - vault_id: defaultCred.vault_id, - label: defaultCred.vault_id ? `${credTypeLabel} (id: ${defaultCred.vault_id})` : credTypeLabel - }); - } - } else { - scope.promptData.credentialTypeMissing.push({ - credential_type: defaultCred.credential_type, - label: credTypeLabel - }); - } - } - }); - }; - - vm.init = (_scope_, _launch_) => { - scope = _scope_; - launch = _launch_; - - scope.toggle_row = (selectedRow) => { - if (!scope.readOnlyPrompts) { - let selectedCred = _.cloneDeep(selectedRow); - - for (let i = scope.promptData.prompts.credentials.value.length - 1; i >= 0; i--) { - if(scope.promptData.prompts.credentials.value[i].credential_type === parseInt(scope.promptData.prompts.credentials.credentialKind)) { - wipePasswords(scope.promptData.prompts.credentials.value[i]); - scope.promptData.prompts.credentials.value.splice(i, 1); - } - } - - scope.promptData.prompts.credentials.value.push(selectedCred); - updateNeededPasswords(selectedRow); - - for (let i = scope.promptData.credentialTypeMissing.length - 1; i >= 0; i--) { - if(scope.promptData.credentialTypeMissing[i].credential_type === selectedRow.credential_type) { - scope.promptData.credentialTypeMissing.splice(i,1); - i = -1; - } - } - } - }; - - scope.toggle_credential = (cred) => { - if (!scope.readOnlyPrompts) { - // This is a checkbox click. At the time of writing this the only - // multi-select credentials on launch are vault credentials so this - // logic should only get executed when a vault credential checkbox - // is clicked. - - let uncheck = false; - - let removeCredential = (credentialToRemove, index) => { - wipePasswords(credentialToRemove); - scope.promptData.prompts.credentials.value.splice(index, 1); - }; - - // Only one vault credential per vault_id is allowed so we need to check - // to see if one has already been selected and if so replace it. - for (let i = scope.promptData.prompts.credentials.value.length - 1; i >= 0; i--) { - if(cred.credential_type === scope.promptData.prompts.credentials.value[i].credential_type) { - if(scope.promptData.prompts.credentials.value[i].id === cred.id) { - removeCredential(scope.promptData.prompts.credentials.value[i], i); - i = -1; - uncheck = true; - } - else if(scope.promptData.prompts.credentials.value[i].inputs) { - if(cred.inputs.vault_id === scope.promptData.prompts.credentials.value[i].inputs.vault_id) { - removeCredential(scope.promptData.prompts.credentials.value[i], i); - } - } else if(scope.promptData.prompts.credentials.value[i].vault_id) { - if(cred.inputs.vault_id === scope.promptData.prompts.credentials.value[i].vault_id) { - removeCredential(scope.promptData.prompts.credentials.value[i], i); - } - } else { - // The currently selected vault credential does not have a vault_id - if(!cred.inputs.vault_id || cred.inputs.vault_id === "") { - removeCredential(scope.promptData.prompts.credentials.value[i], i); - } - } - } - } - - if(!uncheck) { - scope.promptData.prompts.credentials.value.push(cred); - updateNeededPasswords(cred); - - _.remove(scope.promptData.credentialTypeMissing, (missingCredType) => { - return ( - missingCredType.credential_type === cred.credential_type && - _.get(cred, 'inputs.vault_id') === _.get(missingCredType, 'vault_id') - ); - }); - } else { - if(scope.promptData.launchConf.defaults.credentials && scope.promptData.launchConf.defaults.credentials.length > 0) { - checkMissingCredType(cred); - } - } - } - }; - - scope.credential_dataset = []; - scope.credentials = []; - - let credList = _.cloneDeep(CredentialList); - credList.emptyListText = strings.get('prompt.NO_CREDS_MATCHING_TYPE'); - scope.list = credList; - scope.generateCredentialList(scope.promptData.prompts.credentials.credentialKind); - - scope.credential_default_params = { - order_by: 'name', - page_size: 5 - }; - - scope.credential_queryset = { - order_by: 'name', - page_size: 5 - }; - - scope.$watch('promptData.prompts.credentials.credentialKind', (oldKind, newKind) => { - if (scope.promptData.prompts.credentials && scope.promptData.prompts.credentials.credentialKind) { - if(scope.promptData.prompts.credentials.credentialTypes[oldKind] === "vault" || scope.promptData.prompts.credentials.credentialTypes[newKind] === "vault") { - scope.generateCredentialList(scope.promptData.prompts.credentials.credentialKind); - } - scope.credential_queryset.page = 1; - scope.credential_default_params.credential_type = scope.credential_queryset.credential_type = parseInt(scope.promptData.prompts.credentials.credentialKind); - - qs.search(GetBasePath('credentials'), scope.credential_default_params) - .then(res => { - scope.credential_dataset = res.data; - scope.credentials = scope.credential_dataset.results; - }); - } - }); - - scope.$watchCollection('promptData.prompts.credentials.value', () => { - updateSelectedRow(); - }); - - scope.$watchCollection('credentials', () => { - updateSelectedRow(); - }); - - CreateSelect2({ - element: '#launch-kind-select', - multiple: false - }); - }; - - vm.deleteSelectedCredential = (index) => { - const credentialToDelete = scope.promptData.prompts.credentials.value[index]; - for (let i = scope.promptData.prompts.credentials.value.length - 1; i >= 0; i--) { - if(scope.promptData.prompts.credentials.value[i].id === credentialToDelete.id) { - if(scope.promptData.launchConf.defaults.credentials && scope.promptData.launchConf.defaults.credentials.length > 0) { - checkMissingCredType(credentialToDelete); - } - wipePasswords(credentialToDelete); - scope.promptData.prompts.credentials.value.splice(i, 1); - } - } - - scope.credentials.forEach((credential, i) => { - if(credential.id === credentialToDelete.id) { - scope.credentials[i].checked = 0; - } - }); - }; - - vm.revert = () => { - scope.promptData.prompts.credentials.value = _.has(scope, 'promptData.launchConf.defaults.credentials') ? _.cloneDeep(scope.promptData.launchConf.defaults.credentials) : []; - scope.promptData.prompts.credentials.passwords = { - vault: [] - }; - scope.promptData.prompts.credentials.value.forEach((credential) => { - if (credential.passwords_needed && credential.passwords_needed.length > 0) { - credential.passwords_needed.forEach(passwordNeeded => { - let credPassObj = { - id: credential.id, - name: credential.name - }; - - if(passwordNeeded === "ssh_password") { - scope.promptData.prompts.credentials.passwords.ssh_password = credPassObj; - } - if(passwordNeeded === "become_password") { - scope.promptData.prompts.credentials.passwords.become_password = credPassObj; - } - if(passwordNeeded === "ssh_key_unlock") { - scope.promptData.prompts.credentials.passwords.ssh_key_unlock = credPassObj; - } - if(passwordNeeded.startsWith("vault_password")) { - credPassObj.vault_id = credential.vault_id; - scope.promptData.prompts.credentials.passwords.vault.push(credPassObj); - } - }); - - } - }); - - scope.promptData.credentialTypeMissing = []; - }; - - vm.showRevertCredentials = () => { - if(!scope.readOnlyPrompts && scope.promptData.launchConf.ask_credential_on_launch) { - if(scope.promptData.prompts.credentials.value && _.has(scope, 'promptData.launchConf.defaults.credentials') && (scope.promptData.prompts.credentials.value.length === scope.promptData.launchConf.defaults.credentials.length)) { - let selectedIds = scope.promptData.prompts.credentials.value.map((x) => { return x.id; }).sort(); - let defaultIds = _.has(scope, 'promptData.launchConf.defaults.credentials') ? scope.promptData.launchConf.defaults.credentials.map((x) => { return x.id; }).sort() : []; - return !selectedIds.every((e, i) => { return defaultIds.indexOf(e) === i; }); - } else { - return true; - } - } else { - return false; - } - }; - - vm.togglePassword = (id) => { - var buttonId = id + "_show_input_button", - inputId = id; - if ($(inputId).attr("type") === "password") { - $(buttonId).html(strings.get('HIDE')); - $(inputId).attr("type", "text"); - } else { - $(buttonId).html(strings.get('SHOW')); - $(inputId).attr("type", "password"); - } - }; - } - ]; diff --git a/awx/ui/client/src/templates/prompt/steps/credential/prompt-credential.directive.js b/awx/ui/client/src/templates/prompt/steps/credential/prompt-credential.directive.js deleted file mode 100644 index d629d4a9845e..000000000000 --- a/awx/ui/client/src/templates/prompt/steps/credential/prompt-credential.directive.js +++ /dev/null @@ -1,61 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import promptCredentialController from './prompt-credential.controller'; - -export default [ 'templateUrl', '$compile', 'generateList', 'i18n', - (templateUrl, $compile, GenerateList, i18n) => { - return { - scope: { - promptData: '=', - credentialPasswordsForm: '=', - preventCredsWithPasswords: '<', - readOnlyPrompts: '<' - }, - templateUrl: templateUrl('templates/prompt/steps/credential/prompt-credential'), - controller: promptCredentialController, - controllerAs: 'vm', - require: ['^^prompt', 'promptCredential'], - restrict: 'E', - replace: true, - transclude: true, - link: (scope, el, attrs, controllers) => { - - const launchController = controllers[0]; - const promptCredentialController = controllers[1]; - - scope.generateCredentialList = (credKind) => { - let inputType = (credKind && scope.promptData.prompts.credentials.credentialTypes[credKind] === "vault") ? null : 'radio'; - let list = _.cloneDeep(scope.list); - - if(credKind && scope.promptData.prompts.credentials.credentialTypes[credKind] === "vault") { - list.fields.name.modalColumnClass = 'col-md-6'; - list.fields.info = { - label: i18n._('Vault ID'), - ngBind: 'credential.inputs.vault_id', - key: false, - nosort: true, - modalColumnClass: 'col-md-6', - infoHeaderClass: '', - dataPlacement: 'top', - }; - } - - list.disableRow = "{{ readOnlyPrompts }}"; - list.disableRowValue = "readOnlyPrompts"; - - let html = GenerateList.build({ - list: list, - input_type: inputType, - mode: 'lookup' - }); - $('#prompt-credential').empty().append($compile(html)(scope)); - }; - - promptCredentialController.init(scope, launchController); - } - }; -}]; diff --git a/awx/ui/client/src/templates/prompt/steps/credential/prompt-credential.partial.html b/awx/ui/client/src/templates/prompt/steps/credential/prompt-credential.partial.html deleted file mode 100644 index 4986cda86834..000000000000 --- a/awx/ui/client/src/templates/prompt/steps/credential/prompt-credential.partial.html +++ /dev/null @@ -1,118 +0,0 @@ -
-
-
-
- {{:: vm.strings.get('prompt.SELECTED') }} -
-
-
{{:: vm.strings.get('prompt.NO_CREDENTIALS_SELECTED') }}
-
- - - - - - -
-
- -
-
-
-
-  {{:: vm.strings.get('prompt.CREDENTIAL_TYPE_MISSING', missingCred.label) }} -
-
- -
-
- - {{ vm.strings.get('prompt.CREDENTIAL_PASSWORD_WARNING')}} -
-
-
{{promptData.prompts.credentials.passwords.ssh_password.name || promptData.prompts.credentials.passwords.become_password.name || promptData.prompts.credentials.passwords.ssh_key_unlock.name}}
-
{{vaultCred.name}}
-
-
-
- -
- {{:: vm.strings.get('prompt.CREDENTIAL_TYPE') }}: - -
-
-
-
-
{{:: vm.strings.get('prompt.PASSWORDS_REQUIRED_HELP') }}
-
-
- -
- - - - -
-
{{:: vm.strings.get('prompt.PLEASE_ENTER_PASSWORD') }}
-
-
-
- -
- - - - -
-
{{:: vm.strings.get('prompt.PLEASE_ENTER_PASSWORD') }}
-
-
-
- -
- - - - -
-
{{:: vm.strings.get('prompt.PLEASE_ENTER_PASSWORD') }}
-
-
- -
- -
- - - - -
-
{{:: vm.strings.get('prompt.PLEASE_ENTER_PASSWORD') }}
-
-
- -
-
diff --git a/awx/ui/client/src/templates/prompt/steps/inventory/prompt-inventory.controller.js b/awx/ui/client/src/templates/prompt/steps/inventory/prompt-inventory.controller.js deleted file mode 100644 index 658626906fdf..000000000000 --- a/awx/ui/client/src/templates/prompt/steps/inventory/prompt-inventory.controller.js +++ /dev/null @@ -1,52 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default - [ 'TemplatesStrings', function(strings) { - const vm = this; - - vm.strings = strings; - - let scope; - let launch; - - vm.init = (_scope_, _launch_) => { - scope = _scope_; - launch = _launch_; - - scope.toggle_row = (row) => { - if (!scope.readOnlyPrompts) { - scope.promptData.prompts.inventory.value = row; - } - }; - - scope.$watchCollection('inventories', () => { - if(scope.inventories && scope.inventories.length > 0) { - scope.inventories.forEach((credential, i) => { - if (_.has(scope, 'promptData.prompts.inventory.value.id') && scope.promptData.prompts.inventory.value.id === scope.inventories[i].id) { - scope.inventories[i].checked = 1; - } else { - scope.inventories[i].checked = 0; - } - - }); - } - }); - }; - - vm.deleteSelectedInventory = () => { - scope.promptData.prompts.inventory.value = null; - - scope.inventories.forEach((inventory) => { - inventory.checked = 0; - }); - }; - - vm.revert = () => { - scope.promptData.prompts.inventory.value = scope.promptData.launchConf.defaults.inventory; - }; - } - ]; diff --git a/awx/ui/client/src/templates/prompt/steps/inventory/prompt-inventory.directive.js b/awx/ui/client/src/templates/prompt/steps/inventory/prompt-inventory.directive.js deleted file mode 100644 index ab633cd6d786..000000000000 --- a/awx/ui/client/src/templates/prompt/steps/inventory/prompt-inventory.directive.js +++ /dev/null @@ -1,98 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import promptInventoryController from './prompt-inventory.controller'; - -export default [ 'templateUrl', 'QuerySet', 'GetBasePath', 'generateList', '$compile', 'InventoryList', 'i18n', - (templateUrl, qs, GetBasePath, GenerateList, $compile, InventoryList, i18n) => { - return { - scope: { - promptData: '=', - readOnlyPrompts: '<' - }, - templateUrl: templateUrl('templates/prompt/steps/inventory/prompt-inventory'), - controller: promptInventoryController, - controllerAs: 'vm', - require: ['^^prompt', 'promptInventory'], - restrict: 'E', - replace: true, - transclude: true, - link: (scope, el, attrs, controllers) => { - - const launchController = controllers[0]; - const promptInventoryController = controllers[1]; - - promptInventoryController.init(scope, launchController); - - scope.inventory_default_params = { - order_by: 'name', - page_size: 5 - }; - - scope.inventory_queryset = { - order_by: 'name', - page_size: 5 - }; - - // Fire off the initial search - qs.search(GetBasePath('inventory'), scope.inventory_default_params) - .then(res => { - scope.inventory_dataset = res.data; - scope.inventories = scope.inventory_dataset.results; - - let invList = _.cloneDeep(InventoryList); - invList.disableRow = "{{ readOnlyPrompts }}"; - invList.disableRowValue = "readOnlyPrompts"; - - const defaultWarning = i18n._("This inventory is applied to all job template nodes that prompt for an inventory."); - const missingWarning = i18n._("This workflow job template has a default inventory which must be included or replaced before proceeding."); - - const updateInventoryWarning = () => { - scope.inventoryWarning = null; - if (scope.promptData.templateType === "workflow_job_template") { - scope.inventoryWarning = defaultWarning; - - const isPrompted = _.get(scope.promptData, 'launchConf.ask_inventory_on_launch'); - const isDefault = _.get(scope.promptData, 'launchConf.defaults.inventory.id'); - const isSelected = _.get(scope.promptData, 'prompts.inventory.value.id', null) !== null; - - if (isPrompted && isDefault && !isSelected) { - scope.inventoryWarning = missingWarning; - } - } - }; - - updateInventoryWarning(); - - let html = GenerateList.build({ - list: invList, - input_type: 'radio', - mode: 'lookup', - }); - - scope.list = invList; - - $('#prompt-inventory').append($compile(html)(scope)); - - scope.$watch('promptData.prompts.inventory.value', () => { - scope.inventories.forEach((row, i) => { - if ( - _.has(scope, 'promptData.prompts.inventory.value.id') && - row.id === scope.promptData.prompts.inventory.value.id - ) { - scope.inventories[i].checked = 1; - } - else { - scope.inventories[i].checked = 0; - } - - updateInventoryWarning(); - }); - }); - }); - } - }; -}]; diff --git a/awx/ui/client/src/templates/prompt/steps/inventory/prompt-inventory.partial.html b/awx/ui/client/src/templates/prompt/steps/inventory/prompt-inventory.partial.html deleted file mode 100644 index b4c1d88e39ec..000000000000 --- a/awx/ui/client/src/templates/prompt/steps/inventory/prompt-inventory.partial.html +++ /dev/null @@ -1,21 +0,0 @@ -
-
-
-
- {{:: vm.strings.get('prompt.SELECTED') }} -
-
-
{{:: vm.strings.get('prompt.NO_INVENTORY_SELECTED') }}
- - -
- -
-
-
-   {{ inventoryWarning }} -
-
-
diff --git a/awx/ui/client/src/templates/prompt/steps/other-prompts/prompt-other-prompts.controller.js b/awx/ui/client/src/templates/prompt/steps/other-prompts/prompt-other-prompts.controller.js deleted file mode 100644 index 35083e0dbe8f..000000000000 --- a/awx/ui/client/src/templates/prompt/steps/other-prompts/prompt-other-prompts.controller.js +++ /dev/null @@ -1,136 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default - ['ParseTypeChange', 'CreateSelect2', 'TemplatesStrings', '$timeout', 'ToJSON', function(ParseTypeChange, CreateSelect2, strings, $timeout, ToJSON) { - const vm = this; - - vm.strings = strings; - - let scope; - - vm.init = (_scope_, controller, el) => { - scope = _scope_; - - scope.parseType = 'yaml'; - - // Can't pass otherPrompts.variables.value into ParseTypeChange - // due to the fact that Angular CodeMirror uses scope[string] - // notation. - scope.extraVariables = scope.promptData.prompts.variables.value; - - scope.$watch('extraVariables', () => { - scope.promptData.prompts.variables.value = scope.extraVariables; - }); - - let codemirrorExtraVars = () => { - if(scope.promptData.launchConf.ask_variables_on_launch && !scope.promptData.prompts.variables.ignore) { - $timeout(() => { - ParseTypeChange({ - scope: scope, - variable: 'extraVariables', - field_id: 'job_launch_variables' - }); - }); - } - }; - - if(scope.promptData.launchConf.ask_job_type_on_launch) { - CreateSelect2({ - element: '#job_launch_job_type', - multiple: false - }); - } - - if(scope.promptData.launchConf.ask_verbosity_on_launch) { - CreateSelect2({ - element: '#job_launch_verbosity', - multiple: false - }); - } - - if(scope.promptData.launchConf.ask_tags_on_launch) { - // Ensure that the options match the currently selected tags. These two things - // might get out of sync if the user re-opens the prompts before saving the - // schedule/wf node - scope.promptData.prompts.tags.options = _.map(scope.promptData.prompts.tags.value, function(tag){ - return { - value: tag.value, - name: tag.name, - label: tag.label - }; - }); - CreateSelect2({ - element: '#job_launch_job_tags', - multiple: true, - addNew: true - }); - } - - if(scope.promptData.launchConf.ask_skip_tags_on_launch) { - // Ensure that the options match the currently selected tags. These two things - // might get out of sync if the user re-opens the prompts before saving the - // schedule/wf node - scope.promptData.prompts.skipTags.options = _.map(scope.promptData.prompts.skipTags.value, function(tag){ - return { - value: tag.value, - name: tag.name, - label: tag.label - }; - }); - CreateSelect2({ - element: '#job_launch_skip_tags', - multiple: true, - addNew: true - }); - } - - if(scope.isActiveStep) { - codemirrorExtraVars(); - } - - scope.$watch('isActiveStep', () => { - if(scope.isActiveStep) { - codemirrorExtraVars(); - } - }); - - function validate () { - return ToJSON(scope.parseType, scope.extraVariables, true); - } - scope.validate = validate; - - function focusFirstInput () { - const inputs = el.find('input[type=text], select, textarea:visible, .CodeMirror textarea'); - if (inputs.length) { - inputs.get(0).focus(); - } - } - - angular.element(el).ready(() => { - focusFirstInput(); - }); - - scope.$on('promptTabChange', (event, args) => { - if (args.step === 'other_prompts') { - angular.element(el).ready(() => { - focusFirstInput(); - }); - } - }); - }; - - vm.toggleDiff = () => { - scope.promptData.prompts.diffMode.value = !scope.promptData.prompts.diffMode.value; - }; - - vm.updateParseType = (parseType) => { - scope.parseType = parseType; - // This function gets added to scope by the ParseTypeChange factory - scope.parseTypeChange('parseType', 'extraVariables'); - }; - } - ]; diff --git a/awx/ui/client/src/templates/prompt/steps/other-prompts/prompt-other-prompts.directive.js b/awx/ui/client/src/templates/prompt/steps/other-prompts/prompt-other-prompts.directive.js deleted file mode 100644 index 38ac48ef6be2..000000000000 --- a/awx/ui/client/src/templates/prompt/steps/other-prompts/prompt-other-prompts.directive.js +++ /dev/null @@ -1,34 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import promptOtherPrompts from './prompt-other-prompts.controller'; - -export default [ 'templateUrl', - (templateUrl) => { - return { - scope: { - promptData: '=', - otherPromptsForm: '=', - isActiveStep: '=', - validate: '=', - readOnlyPrompts: '<' - }, - templateUrl: templateUrl('templates/prompt/steps/other-prompts/prompt-other-prompts'), - controller: promptOtherPrompts, - controllerAs: 'vm', - require: ['^^prompt', 'promptOtherPrompts'], - restrict: 'E', - replace: true, - transclude: true, - link: (scope, el, attrs, controllers) => { - - const launchController = controllers[0]; - const promptOtherPromptsController = controllers[1]; - - promptOtherPromptsController.init(scope, launchController, el); - } - }; -}]; diff --git a/awx/ui/client/src/templates/prompt/steps/other-prompts/prompt-other-prompts.partial.html b/awx/ui/client/src/templates/prompt/steps/other-prompts/prompt-other-prompts.partial.html deleted file mode 100644 index 941582ee7eb5..000000000000 --- a/awx/ui/client/src/templates/prompt/steps/other-prompts/prompt-other-prompts.partial.html +++ /dev/null @@ -1,150 +0,0 @@ -
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
diff --git a/awx/ui/client/src/templates/prompt/steps/preview/prompt-preview.controller.js b/awx/ui/client/src/templates/prompt/steps/preview/prompt-preview.controller.js deleted file mode 100644 index 94c5dd8b9fce..000000000000 --- a/awx/ui/client/src/templates/prompt/steps/preview/prompt-preview.controller.js +++ /dev/null @@ -1,75 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default - [ 'ParseTypeChange', 'ToJSON', 'TemplatesStrings', function(ParseTypeChange, ToJSON, strings) { - const vm = this; - - vm.strings = strings; - - let scope; - - vm.init = (_scope_) => { - scope = _scope_; - - vm.showJobTags = true; - vm.showSkipTags = true; - - scope.parseType = 'yaml'; - - const surveyPasswords = {}; - - if (scope.promptData.launchConf.survey_enabled){ - scope.promptData.extraVars = ToJSON(scope.parseType, scope.promptData.prompts.variables.value, false); - scope.promptData.surveyQuestions.forEach(surveyQuestion => { - if (!scope.promptData.extraVars) { - scope.promptData.extraVars = {}; - } - // grab all survey questions that have answers - if (surveyQuestion.required || (surveyQuestion.required === false && surveyQuestion.model.toString()!=="")) { - scope.promptData.extraVars[surveyQuestion.variable] = surveyQuestion.model; - } - - if (surveyQuestion.required === false && _.isEmpty(surveyQuestion.model)) { - switch (surveyQuestion.type) { - // for optional text and text-areas, submit a blank string if min length is 0 - // -- this is confusing, for an explanation see: - // http://docs.ansible.com/ansible-tower/latest/html/userguide/job_templates.html#optional-survey-questions - // - case "text": - case "textarea": - if (surveyQuestion.min === 0) { - scope.promptData.extraVars[surveyQuestion.variable] = ""; - } - break; - } - } - - if (surveyQuestion.type === 'password' && surveyQuestion.model.toString()!=="") { - surveyPasswords[surveyQuestion.variable] = '$encrypted$'; - } - }); - // We don't want to modify the extra vars when we merge them with the survey - // password $encrypted$ strings so we clone it - const extraVarsClone = _.cloneDeep(scope.promptData.extraVars); - // Replace the survey passwords with $encrypted$ to display to the user - const cleansedExtraVars = extraVarsClone ? Object.assign(extraVarsClone, surveyPasswords) : {}; - - scope.promptExtraVars = $.isEmptyObject(scope.promptData.extraVars) ? '---' : '---\n' + jsyaml.safeDump(cleansedExtraVars); - } else { - scope.promptData.extraVars = scope.promptData.prompts.variables.value; - scope.promptExtraVars = scope.promptData.prompts.variables.value && scope.promptData.prompts.variables.value !== '' ? scope.promptData.prompts.variables.value : '---\n'; - } - - ParseTypeChange({ - scope: scope, - variable: 'promptExtraVars', - field_id: 'job_launch_preview_variables', - readOnly: true - }); - }; - } - ]; diff --git a/awx/ui/client/src/templates/prompt/steps/preview/prompt-preview.directive.js b/awx/ui/client/src/templates/prompt/steps/preview/prompt-preview.directive.js deleted file mode 100644 index 034236cc4355..000000000000 --- a/awx/ui/client/src/templates/prompt/steps/preview/prompt-preview.directive.js +++ /dev/null @@ -1,30 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import promptPreview from './prompt-preview.controller'; - -export default [ 'templateUrl', - (templateUrl) => { - return { - scope: { - promptData: '=' - }, - templateUrl: templateUrl('templates/prompt/steps/preview/prompt-preview'), - controller: promptPreview, - controllerAs: 'vm', - require: ['^^prompt', 'promptPreview'], - restrict: 'E', - replace: true, - transclude: true, - link: (scope, el, attrs, controllers) => { - - const launchController = controllers[0]; - const promptPreviewController = controllers[1]; - - promptPreviewController.init(scope, launchController); - } - }; -}]; diff --git a/awx/ui/client/src/templates/prompt/steps/preview/prompt-preview.partial.html b/awx/ui/client/src/templates/prompt/steps/preview/prompt-preview.partial.html deleted file mode 100644 index dea1de635123..000000000000 --- a/awx/ui/client/src/templates/prompt/steps/preview/prompt-preview.partial.html +++ /dev/null @@ -1,80 +0,0 @@ -
-
-
{{:: vm.strings.get('prompt.JOB_TYPE') }}
-
- {{:: vm.strings.get('prompt.PLAYBOOK_RUN') }} - {{:: vm.strings.get('prompt.CHECK') }} -
-
-
-
{{:: vm.strings.get('prompt.CREDENTIAL') }}
-
- - -
-
-
-
{{:: vm.strings.get('prompt.INVENTORY') }}
-
-
-
-
{{:: vm.strings.get('prompt.SCM_BRANCH') }}
-
-
-
-
{{:: vm.strings.get('prompt.LIMIT') }}
-
-
-
-
{{:: vm.strings.get('prompt.VERBOSITY') }}
-
-
-
-
- {{:: vm.strings.get('prompt.JOB_TAGS') }}  - - - - -
-
-
-
- {{tag.name}} -
-
-
-
-
-
- {{:: vm.strings.get('prompt.SKIP_TAGS') }}  - - - - -
-
-
-
- {{tag.name}} -
-
-
-
-
-
{{:: vm.strings.get('prompt.SHOW_CHANGES') }}
-
- {{:: vm.strings.get('ON') }} - {{:: vm.strings.get('OFF') }} -
-
-
-
{{:: vm.strings.get('prompt.EXTRA_VARIABLES') }}
-
- -
-
-
diff --git a/awx/ui/client/src/templates/prompt/steps/survey/prompt-survey.controller.js b/awx/ui/client/src/templates/prompt/steps/survey/prompt-survey.controller.js deleted file mode 100644 index e37afa49fb22..000000000000 --- a/awx/ui/client/src/templates/prompt/steps/survey/prompt-survey.controller.js +++ /dev/null @@ -1,35 +0,0 @@ -/************************************************* - * Copyright (c) 2017 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default - [ 'TemplatesStrings', function(strings) { - const vm = this; - - vm.strings = strings; - - let scope; - let launch; - - vm.init = (_scope_, _launch_) => { - scope = _scope_; - launch = _launch_; - }; - - // This function is used to hide/show the contents of a password - // within a form - vm.togglePassword = (id) => { - var buttonId = id + "_show_input_button", - inputId = id; - if ($(inputId).attr("type") === "password") { - $(buttonId).html(strings.get('HIDE')); - $(inputId).attr("type", "text"); - } else { - $(buttonId).html(strings.get('SHOW')); - $(inputId).attr("type", "password"); - } - }; - } - ]; diff --git a/awx/ui/client/src/templates/prompt/steps/survey/prompt-survey.directive.js b/awx/ui/client/src/templates/prompt/steps/survey/prompt-survey.directive.js deleted file mode 100644 index eb1ae7169fcc..000000000000 --- a/awx/ui/client/src/templates/prompt/steps/survey/prompt-survey.directive.js +++ /dev/null @@ -1,32 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import promptSurvey from './prompt-survey.controller'; - -export default [ 'templateUrl', - (templateUrl) => { - return { - scope: { - promptData: '=', - surveyForm: '=', - readOnlyPrompts: '<' - }, - templateUrl: templateUrl('templates/prompt/steps/survey/prompt-survey'), - controller: promptSurvey, - controllerAs: 'vm', - require: ['^^prompt', 'promptSurvey'], - restrict: 'E', - replace: true, - transclude: true, - link: (scope, el, attrs, controllers) => { - - const launchController = controllers[0]; - const promptSurveyController = controllers[1]; - - promptSurveyController.init(scope, launchController); - } - }; -}]; diff --git a/awx/ui/client/src/templates/prompt/steps/survey/prompt-survey.partial.html b/awx/ui/client/src/templates/prompt/steps/survey/prompt-survey.partial.html deleted file mode 100644 index 63ccea473c45..000000000000 --- a/awx/ui/client/src/templates/prompt/steps/survey/prompt-survey.partial.html +++ /dev/null @@ -1,71 +0,0 @@ -
-
- - -
- -
-
- -
{{:: vm.strings.get('prompt.PLEASE_ENTER_ANSWER') }}
-
Please enter an answer between {{question.minlength}} to {{question.maxlength}} characters long.
-
-
- -
{{:: vm.strings.get('prompt.PLEASE_ENTER_ANSWER') }}
-
Please enter an answer between {{question.minlength}} to {{question.maxlength}} characters long.
-
-
-
- - - - - -
-
{{:: vm.strings.get('prompt.PLEASE_ENTER_ANSWER') }}
-
Please enter an answer between {{question.minlength}} to {{question.maxlength}} characters long.
-
-
- -
{{:: vm.strings.get('prompt.PLEASE_ENTER_ANSWER') }}
-
{{:: vm.strings.get('prompt.VALID_INTEGER') }}
-
Please enter an answer between {{question.minValue}} and {{question.maxValue}}.
-
-
- -
{{:: vm.strings.get('prompt.PLEASE_ENTER_ANSWER') }}
-
{{:: vm.strings.get('prompt.VALID_DECIMAL') }}
-
Please enter an answer between {{question.minValue}} and {{question.maxValue}}.
-
-
-
- - -
-
{{:: vm.strings.get('prompt.PLEASE_SELECT_VALUE') }}
-
-
- - -
{{:: vm.strings.get('prompt.PLEASE_SELECT_VALUE') }}
-
-
-
diff --git a/awx/ui/client/src/templates/survey-maker/main.js b/awx/ui/client/src/templates/survey-maker/main.js deleted file mode 100644 index 1d41e63aef5e..000000000000 --- a/awx/ui/client/src/templates/survey-maker/main.js +++ /dev/null @@ -1,14 +0,0 @@ -import listGenerator from '../../shared/list-generator/main'; -import questions from './questions/main'; -import surveys from './surveys/main'; -import render from './render/main'; -import shared from './shared/main'; - -export default - angular.module('templates.surveyMaker', - [ listGenerator.name, - questions.name, - surveys.name, - render.name, - shared.name - ]); diff --git a/awx/ui/client/src/templates/survey-maker/questions/edit.factory.js b/awx/ui/client/src/templates/survey-maker/questions/edit.factory.js deleted file mode 100644 index 4aec1a5c6f18..000000000000 --- a/awx/ui/client/src/templates/survey-maker/questions/edit.factory.js +++ /dev/null @@ -1,100 +0,0 @@ -export default - function EditQuestion(GenerateForm, CreateSelect2, SurveyQuestionForm) { - return function(params) { - - var scope = params.scope, - index = params.index, - tmpVar, - i, - question = params.question, - form = SurveyQuestionForm; - - // Update the index so that we know which question is being edited. - scope.editQuestionIndex = index; - - scope.text_min = null; - scope.text_max = null; - scope.int_min = null; - scope.int_max = null; - scope.float_min = null; - scope.float_max = null; - scope.password_min = null; - scope.password_max = null; - scope.pwcheckbox = false; - - if (scope.removeFillQuestionForm) { - scope.removeFillQuestionForm(); - } - scope.removeFillQuestionForm = scope.$on('FillQuestionForm', function() { - for( var fld in form.fields){ - scope[fld] = question[fld]; - if(form.fields[fld].type === 'select'){ - for (i = 0; i < scope.answer_types.length; i++) { - if (question[fld] === scope.answer_types[i].type) { - scope[fld] = scope.answer_types[i]; - } - } - } - } - if( question.type === 'text'){ - scope.text_min = question.min; - scope.text_max = question.max; - scope.default_text = question.default; - } - if( question.type === 'textarea'){ - scope.textarea_min = question.min; - scope.textarea_max = question.max; - scope.default_textarea= question.default; - } - if(question.type === 'password'){ - scope.password_min = question.min; - scope.password_max = question.max; - scope.default_password = question.default; - } - if( question.type === 'integer'){ - scope.int_min = question.min; - scope.int_max = question.max; - scope.default_int = question.default; - } - else if( question.type === 'float' ) { - scope.float_min = question.min; - scope.float_max = question.max; - scope.default_float = question.default; - - } - else if ( question.type === 'multiselect'){ - scope.default_multiselect = question.default; - } - - // After we populate the form with data, need to call CreateSelect2 again - // to get the dropdown to show the selected item. - CreateSelect2({ - element:'#survey_question_type', - multiple: false - }); - - // Set the form to dirty. This lets the cancel button know that it should become enabled. - scope.survey_question_form.$setDirty(); - }); - - if (scope.removeGenerateForm) { - scope.removeGenerateForm(); - } - scope.removeGenerateForm = scope.$on('GenerateForm', function() { - tmpVar = scope.mode; - GenerateForm.inject(form, { id: 'survey_maker_question_form', mode: 'edit', related: false, scope:scope, noPanel: true }); - scope.mode = tmpVar; - scope.$emit('FillQuestionForm'); - }); - - - scope.$emit('GenerateForm'); - - }; - } - -EditQuestion.$inject = - [ 'GenerateForm', - 'CreateSelect2', - 'questionDefinitionForm' - ]; diff --git a/awx/ui/client/src/templates/survey-maker/questions/main.js b/awx/ui/client/src/templates/survey-maker/questions/main.js deleted file mode 100644 index 957c06638687..000000000000 --- a/awx/ui/client/src/templates/survey-maker/questions/main.js +++ /dev/null @@ -1,7 +0,0 @@ -import questionScope from './question-scope.factory'; -import edit from './edit.factory'; - -export default - angular.module('jobTemplates.surveyMaker.questions', []) - .factory('questionScope', questionScope) - .factory('editQuestion', edit); diff --git a/awx/ui/client/src/templates/survey-maker/questions/question-scope.factory.js b/awx/ui/client/src/templates/survey-maker/questions/question-scope.factory.js deleted file mode 100644 index 31af0902a50a..000000000000 --- a/awx/ui/client/src/templates/survey-maker/questions/question-scope.factory.js +++ /dev/null @@ -1,25 +0,0 @@ -var typesSupportingIsolatedScope = - [ 'multiselect', - 'multiplechoice' - ]; - -function typeSupportsIsolatedScope(type) { - return _.include(typesSupportingIsolatedScope, type); -} - -function getIsolatedScope(question, oldScope) { - var newScope = oldScope.$new(); - newScope.question = question; - return newScope; -} - -export default - function() { - return function(question, oldScope) { - if (typeSupportsIsolatedScope(question.type)) { - return getIsolatedScope(question, oldScope); - } else { - return oldScope; - } - }; - } diff --git a/awx/ui/client/src/templates/survey-maker/render/main.js b/awx/ui/client/src/templates/survey-maker/render/main.js deleted file mode 100644 index 90390c74e1a4..000000000000 --- a/awx/ui/client/src/templates/survey-maker/render/main.js +++ /dev/null @@ -1,9 +0,0 @@ -import surveyQuestion from './survey-question.directive'; -import multipleChoice from './multiple-choice.directive'; -import multiSelect from './multiselect.directive'; - -export default - angular.module('jobTemplates.surveyMaker.render', []) - .directive('surveyQuestion', surveyQuestion) - .directive('multipleChoice', multipleChoice) - .directive('multiSelect', multiSelect); diff --git a/awx/ui/client/src/templates/survey-maker/render/multiple-choice.directive.js b/awx/ui/client/src/templates/survey-maker/render/multiple-choice.directive.js deleted file mode 100644 index c55231f24ebc..000000000000 --- a/awx/ui/client/src/templates/survey-maker/render/multiple-choice.directive.js +++ /dev/null @@ -1,47 +0,0 @@ -/* jshint unused: vars */ -import {templateUrl} from '../../../shared/template-url/template-url.factory'; - -function link($timeout, CreateSelect2, scope, element, attrs, ngModel) { - $timeout(function() { - - // select2-ify the dropdown. If the preview flag is passed here - // and it's true then we don't want to use a custom dropdown adapter. - // The reason for this is that the custom dropdown adapter breaks - // the draggability of this element. We're able to get away with this - // in preview mode (survey create/edit) because the element is disabled - // and we don't actually need the dropdown portion. Note that the custom - // dropdown adapter is used to get the dropdown contents to show up in - // a modal. - - CreateSelect2({ - element: element.find('select'), - multiple: scope.isMultipleSelect(), - minimumResultsForSearch: scope.isMultipleSelect() ? Infinity : 10, - customDropdownAdapter: scope.preview ? false : true - }); - }); - -} - -export default - [ '$timeout', 'CreateSelect2', - function($timeout, CreateSelect2) { - var directive = - { restrict: 'E', - require: 'ngModel', - scope: { - isMultipleSelect: '&multiSelect', - choices: '=', - question: '=', - isRequired: '=ngRequired', - selectedValue: '=ngModel', - isDisabled: '=ngDisabled', - preview: '=', - formElementName: '@' - }, - templateUrl: templateUrl('templates/survey-maker/render/multiple-choice'), - link: _.partial(link, $timeout, CreateSelect2) - }; - return directive; - } - ]; diff --git a/awx/ui/client/src/templates/survey-maker/render/multiple-choice.partial.html b/awx/ui/client/src/templates/survey-maker/render/multiple-choice.partial.html deleted file mode 100644 index a29362e7ecfc..000000000000 --- a/awx/ui/client/src/templates/survey-maker/render/multiple-choice.partial.html +++ /dev/null @@ -1,5 +0,0 @@ -
- -
diff --git a/awx/ui/client/src/templates/survey-maker/render/multiselect.directive.js b/awx/ui/client/src/templates/survey-maker/render/multiselect.directive.js deleted file mode 100644 index 82a39bbd547f..000000000000 --- a/awx/ui/client/src/templates/survey-maker/render/multiselect.directive.js +++ /dev/null @@ -1,49 +0,0 @@ -/* jshint unused: vars */ - -/** - * @ngdoc directive - * - * @name jobTemplates.surveyMaker.render.multiSelect - * @description - * Angular provides no method of binding to "multiple" for - * select lists. This is because under normal circumstances, - * the structure of `ng-model` changes based on whether `multiple` - * is true or false. We're not needing to "bind" to "multiple", - * but we do need to pass in the value dynamically. This allows - * us to do that. - */ - -var directive = - { require: 'ngModel', - controller: function($scope) { - $('select').on('select2:unselecting', (event) => { - if (_.has($scope.$parent, 'preview')) { - event.preventDefault(); - } - }); - }, - compile: function() { - return { - pre: function(scope, element, attrs, ngModel) { - if (_.isUndefined(scope.isMultipleSelect)) { - return; - } - - if (!scope.isMultipleSelect()) { - return; - } - - element.attr('multiple', true); - attrs.multiple = true; - - - } - }; - }, - priority: 1000 - }; - -export default - function() { - return directive; - } diff --git a/awx/ui/client/src/templates/survey-maker/render/survey-question.directive.js b/awx/ui/client/src/templates/survey-maker/render/survey-question.directive.js deleted file mode 100644 index 97df8d1872bb..000000000000 --- a/awx/ui/client/src/templates/survey-maker/render/survey-question.directive.js +++ /dev/null @@ -1,128 +0,0 @@ -/* jshint unused: vars */ -import {templateUrl} from '../../../shared/template-url/template-url.factory'; - -/** - * @ngdoc directive - * @name jobTemplates.surveyMaker.render.surveyQuestion - * @description - * Directive that will eventually hold all logic - * for rendering different form controls based on - * the question type for a survey. - */ - -// Since we're generating HTML for the entire survey, and _then_ -// calling $compile, this directive never actually gets compiled -// with the question object we need. Therefore, we give it the index -// of the question as an attribute (not scope) and then look it up -// in the `survey_questions` by that index when it the directive gets -// compiled. -// -function findQuestionByIndex(questions, index) { - return _.find(questions, function(question) { - return question.index === index; - }); -} - -function link($sce, $filter, Empty, scope, element, attrs) { - - function serialize(expression) { - return $sce.getTrustedHtml(expression); - } - - function sanitizeDefault() { - - var defaultValue = "", - min, - max; - - if(scope.question.type === 'text'|| scope.question.type === "password" ){ - defaultValue = (scope.question.default) ? scope.question.default : ""; - defaultValue = $filter('sanitize')(defaultValue); - defaultValue = serialize(defaultValue); - } - - if(scope.question.type === "textarea"){ - defaultValue = (scope.question.default) ? scope.question.default : (scope.question.default_textarea) ? scope.question.default_textarea: "" ; - defaultValue = $filter('sanitize')(defaultValue); - defaultValue = serialize(defaultValue); - } - - if(scope.question.type === 'multiplechoice' || scope.question.type === "multiselect"){ - - scope.question.default = scope.question.default_multiselect || scope.question.default; - - if (scope.question.default) { - if (scope.question.type === 'multiselect' && typeof scope.question.default.split === 'function') { - defaultValue = scope.question.default.split('\n'); - } else if (scope.question.type !== 'multiselect') { - defaultValue = scope.question.default; - } - } else { - defaultValue = ''; - } - } - - if(scope.question.type === 'integer'){ - min = (!Empty(scope.question.min)) ? scope.question.min : ""; - max = (!Empty(scope.question.max)) ? scope.question.max : "" ; - defaultValue = (!Empty(scope.question.default)) ? scope.question.default : (!Empty(scope.question.default_int)) ? scope.question.default_int : "" ; - - } - if(scope.question.type === "float"){ - min = (!Empty(scope.question.min)) ? scope.question.min : ""; - max = (!Empty(scope.question.max)) ? scope.question.max : "" ; - defaultValue = (!Empty(scope.question.default)) ? scope.question.default : (!Empty(scope.question.default_float)) ? scope.question.default_float : "" ; - - } - - scope.defaultValue = defaultValue; - - } - - //for toggling the input on password inputs - scope.toggleInput = function(id) { - var buttonId = id + "_show_input_button", - inputId = id, - buttonInnerHTML = $(buttonId).html(); - if (buttonInnerHTML.indexOf("SHOW") > -1) { - $(buttonId).html("HIDE"); - $(inputId).attr("type", "text"); - } else { - $(buttonId).html("SHOW"); - $(inputId).attr("type", "password"); - } - }; - - if (!scope.question) { - scope.question = findQuestionByIndex(scope.surveyQuestions, Number(attrs.index)); - } - - // Split out choices to be consumed by the multiple-choice directive - if (!_.isUndefined(scope.question.choices)) { - scope.choices = typeof scope.question.choices.split === 'function' ? scope.question.choices.split('\n') : scope.question.choices; - } - - sanitizeDefault(); - -} - -export default - [ - '$sce', '$filter', 'Empty', - function($sce, $filter, Empty) { - var directive = - { restrict: 'E', - scope: - { question: '=', - surveyQuestions: '=', - isRequired: '@ngRequired', - isDisabled: '@ngDisabled', - preview: '=' - }, - templateUrl: templateUrl('templates/survey-maker/render/survey-question'), - link: _.partial(link, $sce, $filter, Empty) - }; - - return directive; - } - ]; diff --git a/awx/ui/client/src/templates/survey-maker/render/survey-question.partial.html b/awx/ui/client/src/templates/survey-maker/render/survey-question.partial.html deleted file mode 100644 index b90631898c1d..000000000000 --- a/awx/ui/client/src/templates/survey-maker/render/survey-question.partial.html +++ /dev/null @@ -1,30 +0,0 @@ -
- -
-
- -
-
- - -
-
- - - - -
- -
- -
-
- -
diff --git a/awx/ui/client/src/templates/survey-maker/shared/main.js b/awx/ui/client/src/templates/survey-maker/shared/main.js deleted file mode 100644 index 4b92e376b6c9..000000000000 --- a/awx/ui/client/src/templates/survey-maker/shared/main.js +++ /dev/null @@ -1,5 +0,0 @@ -import form from './question-definition.form'; - -export default - angular.module('jobTemplates.surveyMaker.shared', []) - .factory('questionDefinitionForm', form); diff --git a/awx/ui/client/src/templates/survey-maker/shared/question-definition.form.js b/awx/ui/client/src/templates/survey-maker/shared/question-definition.form.js deleted file mode 100644 index 2cfc932c71ea..000000000000 --- a/awx/ui/client/src/templates/survey-maker/shared/question-definition.form.js +++ /dev/null @@ -1,312 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name forms.function:Questions - * @description This form is for adding a question -*/ - -export default ['i18n', function(i18n){ - return { - - addTitle: i18n._('ADD SURVEY PROMPT'), - editTitle: i18n._('EDIT SURVEY PROMPT'), - titleClass: 'Form-secondaryTitle', - base: 'survey_question', - name: 'survey_question', - well: true, - cancelButton: false, - - fields: { - question_name: { - realName: 'question_text', - label: i18n._('Prompt'), - type: 'text', - required: true, - column: 1, - awSurveyQuestion: true, - class: 'Form-formGroup--singleColumn' - }, - question_description: { - realName: 'question_description', - label: i18n._('Description'), - type: 'text', - column: 1, - class: 'Form-formGroup--singleColumn' - }, - variable: { - realName: 'variable', - type: 'custom', - label: i18n._('Answer Variable Name'), - control: - '
'+ - '
' + i18n._('Please enter an answer variable name.') + '
'+ - '
' + i18n._('Please remove the illegal character from the survey question variable name.') + '
'+ - '
' + i18n._('This question variable is already in use. Please enter a different variable name.') + '
' + - '
'+ - '
', - awPopOver: i18n._("The suggested format for variable names is lowercase and underscore-separated (for example, foo_bar, user_id, host_name, etc.). Variable names with spaces are not allowed."), - dataTitle: i18n._('Answer Variable Name'), - dataPlacement: 'right', - dataContainer: "body", - required: true, - column: 1, - class: 'Form-formGroup--singleColumn' - }, - type: { - realName: 'answer_type', - label: i18n._('Answer Type'), - type: 'select', - defaultText: i18n._('Choose an answer type'), - ngOptions: 'answer_types.name for answer_types in answer_types track by answer_types.type', - required: true, - awPopOver: i18n._('Choose an answer type or format you want as the prompt for the user. Refer to the Ansible Tower Documentation for more additional information about each option.'), - dataTitle: i18n._('Answer Type'), - dataPlacement: 'right', - dataContainer: "body", - column: 2, - ngChange: 'typeChange()', - class: 'Form-formGroup--singleColumn' - }, - choices: { - realName: 'answer_options', - label: i18n._('Multiple Choice Options'), - type: 'textarea', - rows: 3, - required: true, - - ngRequired: "type.type=== 'multiselect' || type.type=== 'multiplechoice' " , - ngShow: 'type.type=== "multiselect" || type.type=== "multiplechoice" ', - column: 2, - class: 'Form-formGroup--singleColumn' - }, - text_options: { - realName: 'answer_options', - type: 'custom', - control:'
'+ - '
'+ - ''+ - '
' + i18n._('The minimum length you entered is not a valid number. Please enter a whole number.') + '
'+ - '
' + i18n._('The minimium length is too high. Please enter a lower number.') + '
'+ - '
' + i18n._('The minimum length is too low. Please enter a positive number.') + '
'+ - '
'+ - '
'+ - ''+ - '
' + i18n._('The maximum length you entered is not a valid number. Please enter a whole number.') + '
'+ - '
' + i18n._('The maximum length is too low. Please enter a number larger than the minimum length you set.') + '
'+ - '
'+ - '
', - ngShow: 'type.type==="text" ', - required: true, - column: 2, - class: 'Form-formGroup--singleColumn' - }, - textarea_options: { - realName: 'answer_options', - type: 'custom', - control:'
'+ - '
'+ - ''+ - '
' + i18n._('The minimum length you entered is not a valid number. Please enter a whole number.') + '
'+ - '
' + i18n._('The minimium length is too high. Please enter a lower number.') + '
'+ - '
' + i18n._('The minimum length is too low. Please enter a positive number.') + '
'+ - '
'+ - '
'+ - ''+ - '
' + i18n._('The maximum length you entered is not a valid number. Please enter a whole number.') + '
'+ - '
' + i18n._('The maximum length is too low. Please enter a number larger than the minimum length you set.') + '
'+ - '
'+ - '
', - ngShow: 'type.type==="textarea" ', - required: true, - column: 2, - class: 'Form-formGroup--singleColumn' - }, - password_options: { - realName: 'answer_options', - type: 'custom', - control:'
'+ - '
'+ - ''+ - '
' + i18n._('The minimum length you entered is not a valid number. Please enter a whole number.') + '
'+ - '
' + i18n._('The minimium length is too high. Please enter a lower number.') + '
'+ - '
' + i18n._('The minimum length is too low. Please enter a positive number.') + '
'+ - '
'+ - '
'+ - ''+ - '
' + i18n._('The maximum length you entered is not a valid number. Please enter a whole number.') + '
'+ - '
' + i18n._('The maximum length is too low. Please enter a number larger than the minimum length you set.') + '
'+ - '
'+ - '
', - ngShow: 'type.type==="password" ', - required: true, - - column: 2, - class: 'Form-formGroup--singleColumn' - }, - int_options: { - realName: 'answer_options', - type: 'custom', - control:'
'+ - '
'+ - ''+ - '
' + i18n._('Please enter a valid integer.') + '
'+ - '
' + i18n._('Please enter a smaller integer.') + '
'+ - '
'+ - '
'+ - ''+ - '
' + i18n._('Please enter a valid integer.') + '
'+ - '
' + i18n._('Please enter a larger integer.') + '
'+ - '
'+ - '
', - ngShow: 'type.type==="integer" ', - required: true, - column: 2, - class: 'Form-formGroup--singleColumn' - }, - float_options: { - realName: 'answer_options', - type: 'custom', - control: '
'+ - '
'+ - ''+ - '
' + i18n._('Please enter a valid float.') + '
'+ - '
' + i18n._('Please enter a smaller float.') + '
'+ - '
'+ - '
'+ - ''+ - '
' + i18n._('Please enter a valid float.') + '
'+ - '
' + i18n._('Please enter a larger float.') + '
'+ - - '
'+ - '
', - ngShow: 'type.type==="float" ', - required: true, - column: 2, - class: 'Form-formGroup--singleColumn' - }, - default:{ - realName: 'default_answer', - type: 'custom' , - control: '
'+ - ''+ - '
'+ - ''+ - '
' + i18n._('Please enter an answer from the choices listed.') + '
' + - '
' + i18n._('The answer is shorter than the minimium length. Please make the answer longer.') + '
' + - '
' + i18n._('The answer is longer than the maximum length. Please make the answer shorter.') + '
' + - '
'+ - '
'+ - '
', - column: 2, - ngShow: 'type.type === "text" || type.type === "multiplechoice" ', - class: 'Form-formGroup--singleColumn' - }, - default_multiselect: { - realName: 'default_answer' , - type: 'custom', - control: '
'+ - ''+ - '
'+ - ''+ - '
' + i18n._('Please enter an answer/answers from the choices listed.') + '
' + - '
'+ - '
'+ - '
', - column: 2, - ngShow: 'type.type==="multiselect" ', - class: 'Form-formGroup--singleColumn' - }, - default_int: { - realName: 'default_answer', - type: 'custom', - control: '
'+ - ''+ - ''+ - '
' + i18n._('Please enter a valid integer.') + '
'+ - '
' + i18n._('Please enter a minimum default of {{int_min}}.') + '
'+ - '
' + i18n._('Please enter a maximum default of {{int_max}}.') + '
'+ - '
', - column: 2, - ngShow: 'type.type === "integer" ', - class: 'Form-formGroup--singleColumn' - }, - default_float: { - realName: 'default_answer', - type: 'custom', - control: '
'+ - ''+ - ''+ - '
' + i18n._('Please enter a valid float.') + '
'+ - '
' + i18n._('Please enter a minimum default of {{float_min}}.') + '
'+ - '
' + i18n._('Please enter a maximum default of {{float_max}}.') + '
'+ - '
', - column: 2, - ngShow: 'type.type=== "float" ', - class: 'Form-formGroup--singleColumn' - }, - default_textarea: { - realName: "default_answer" , - type: 'custom', - control: '
'+ - ''+ - '
'+ - ''+ - '
' + i18n._('The answer is shorter than the minimium length. Please make the answer longer.') + '
' + - '
' + i18n._('The answer is longer than the maximum length. Please make the answer shorter.') + '
' + - '
'+ - '
'+ - '
', - column : 2, - ngShow: 'type.type === "textarea" ', - class: 'Form-formGroup--singleColumn' - }, - default_password: { - realName: 'default_answer' , - type: 'custom' , - control: '
'+ - ''+ - '
'+ - '
'+ - ''+ - ''+ - ''+ - ''+ - '
'+ - '
' + i18n._('The answer is shorter than the minimium length. Please make the answer longer.') + '
' + - '
' + i18n._('The answer is longer than the maximum length. Please make the answer shorter.') + '
' + - '
'+ - '
'+ - '
', - column: 2, - ngShow: 'type.type === "password" ', - class: 'Form-formGroup--singleColumn' - }, - required: { - realName: 'required_answer', - label: i18n._('Required'), - type: 'checkbox', - column: 2, - class: 'Form-formGroup--singleColumn' - } - }, - buttons: { - question_cancel : { - label: i18n._('Clear'), - 'class' : 'btn btn-default Form-cancelButton', - ngClick: 'generateAddQuestionForm()', - ngDisabled: 'survey_question_form.$pristine' - }, - submit_question: { - ngClick: 'submitQuestion($event)', - ngDisabled: true, - 'class': 'btn btn-sm Form-saveButton', - label: '{{editQuestionIndex === null ? "+ ADD" : "UPDATE"}}' - } - } -}; -}]; diff --git a/awx/ui/client/src/templates/survey-maker/shared/survey-controls.block.less b/awx/ui/client/src/templates/survey-maker/shared/survey-controls.block.less deleted file mode 100644 index 74b3af496733..000000000000 --- a/awx/ui/client/src/templates/survey-maker/shared/survey-controls.block.less +++ /dev/null @@ -1,11 +0,0 @@ -/** @define SurveyControls */ - -.SurveyControls { - &-selectWrapper { - margin-left: 15px; - } - &--dropdown { - z-index: 10000; - opacity: 1; - } -} diff --git a/awx/ui/client/src/templates/survey-maker/survey-maker.block.less b/awx/ui/client/src/templates/survey-maker/survey-maker.block.less deleted file mode 100644 index 8f5e036be3bd..000000000000 --- a/awx/ui/client/src/templates/survey-maker/survey-maker.block.less +++ /dev/null @@ -1,258 +0,0 @@ -.position-center { - left: 0; - right: 0; - margin: auto; -} - -.SurveyMaker-dialog { - max-width: 1200px; - padding: 0px; - .position-center; - - .ui-dialog-buttonpane, .ui-dialog-titlebar { - display:none; - } -} -.SurveyMaker-header { - display: flex; -} -.SurveyMaker-title { - align-items: center; - flex: 1 0 auto; - display: flex; - word-wrap: break-word; - word-break: break-all; - max-width: 98%; -} -.SurveyMaker-titleText { - color: @list-title-txt; - font-size: 14px; - font-weight: bold; - margin-right: 10px; - max-width: 75%; -} -.SurveyMaker-titleLockup { - margin-left: 4px; - margin-right: 6px; - display: inline-block; - margin-top: 0px; - padding-bottom: 2px; - vertical-align: bottom; -} -.SurveyMaker-titleLockup:before { - content: "\007C"; - color: @default-icon-hov; - display: block; - font-size: 13px; -} -.SurveyMaker-close { - justify-content: flex-end; - display: flex; -} -.SurveyMaker-exit{ - cursor:pointer; - padding:0px; - border: none; - height:20px; - font-size: 20px; - background-color:@default-bg; - color:@d7grey; - transition: color 0.2s; - line-height:1; -} -.SurveyMaker-exit:hover{ - color:@default-icon; -} -.SurveyMaker-content { - display: flex; - margin-top: 25px; - min-height: 560px; - flex-flow: row nowrap; - justify-content: space-around; -} -.SurveyMaker-questionPanel, -.SurveyMaker-previewPanel { - display: flex; - flex: 0 0 90%; - max-width: 500px; -} -.SurveyMaker-separatorPanel { - display: flex; - flex: 0 0 51px; -} -.SurveyMaker-contentSeparator { - width: 1px; - background-color: @default-list-header-bg; - margin: 0px 25px; -} -.SurveyMaker-panelHeader { - color: @default-icon; - padding-bottom: 20px; - min-height: 40px; - flex: 0 0 auto; - text-transform: uppercase; - font-size: 14px; - font-weight: bold; - white-space: nowrap; -} -.SurveyMaker-panelBody { - flex: 1 0 auto; - padding-bottom: 20px; -} -.SurveyMaker-panelFooter { - flex: 0 0 auto; -} -.SurveyMaker-noQuestions { - color: @default-icon; -} -.SurveyMaker-deleteButton { - color: @default-bg; - background-color: @default-err; - text-transform: uppercase; - padding-left:15px; - padding-right: 15px; - margin-right: 20px; -} -.SurveyMaker-deleteButton:hover { - background-color: @default-err-hov; - color: @default-bg; -} -.SurveyMaker-previewLabel { - text-transform: uppercase; - color: @default-interface-txt; - font-weight: normal; - font-size: small; - width: 100%; - word-break: break-word; -} -.SurveyMaker-deleteOverlay { - height: 100%; - width: 100%; - position: absolute; - top: 0; - left: 0; - background: rgba(0,0,0,0.3); - z-index: 3; - display: flex; - align-items: center; - justify-content: center; - border-radius: 4px; - - .Modal-content { - position: fixed; - top: ~"calc(50% - 100px)"; - width: 600px; - .position-center; - } -} -.SurveyMaker-previewInputRow { - display: flex; - margin-bottom: 20px; -} -.SurveyMaker-previewInput { - width: 100%; -} -.SurveyMaker-previewActions { - display: flex; - align-items: center; - margin-left: 20px; -} -.SurveyMaker-previewActions--selected { - background-color: @default-link !important; - color: @default-bg; -} -.SurveyMaker-previewRows { - position: relative; - min-height: 42px; - padding-left: 0px; - margin-bottom: 0px; - padding-bottom: 10px; - - /* These classes are dynamically added via the angular-drag-and-drop-lists directive */ - .dndPlaceholder { - display: block; - background-color: @default-tertiary-bg; - border: 1px solid @d7grey; - padding: 10px 15px; - min-height: 42px; - margin-bottom: 20px; - border-radius: 4px; - color: @default-interface-txt; - } - - .dndDraggingSource { - display: none; - } -} -.SurveyMaker-previewRow { - position: relative; - display: block; - - /* Disable text selection if item is not draggable */ - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} -.SurveyMaker-reorderButton { - color: @default-icon; - margin-right: 10px; - display: flex; - align-items: center; - cursor: -moz-grab; - cursor: -webkit-grab; - cursor: grab; -} -.SurveyMaker-reorderButton:hover { - color: @default-interface-txt; -} -.SurveyMaker-previewPasswordButton { - padding: 7px 15px!important; -} -.SurveyMaker-previewInput { - .select2-container--disabled { - opacity: inherit!important; - } -} -.SurveyMaker-previewMultiSelect { - background-color: #EEEEEE!important; - cursor: not-allowed!important; -} -.SurveyMaker-previewDescription { - margin-bottom: 5px; -} - -.SurveyMaker-questionPanel { - #survey_question_default_password { - button { - height: 30px; /* show/hide button should match our input height */ - } - } -} - -@media screen and (max-width: 1200px) { - .SurveyMaker-content { - flex-wrap: wrap; - justify-content: center; - } - .SurveyMaker-separatorPanel { - flex: 0 0 90%; - .SurveyMaker-contentSeparator { - width: 100%; - margin: 25px 0; - height: 1px; - } - } -} - -@media screen and (max-width: 600px) { - .SurveyMaker-dialog { - max-width: 100vw; - - .Modal-content { - max-width: ~"calc(100vw - 50px)"; - } - } -} diff --git a/awx/ui/client/src/templates/survey-maker/surveys/add.factory.js b/awx/ui/client/src/templates/survey-maker/surveys/add.factory.js deleted file mode 100644 index eef26fa5d67d..000000000000 --- a/awx/ui/client/src/templates/survey-maker/surveys/add.factory.js +++ /dev/null @@ -1,27 +0,0 @@ -export default - function AddFactory(ShowSurveyModal, Wait) { - return function(params) { - var scope = params.scope; - - // This variable controls the survey on/off toggle beside the create survey - // modal title. We want this toggle to be on by default - scope.survey_enabled = true; - - scope.isEditSurvey = false; - - if (scope.removeDialogReady) { - scope.removeDialogReady(); - } - scope.removeDialogReady = scope.$on('DialogReady', function() { - $('#survey-modal-dialog').dialog('open'); - scope.generateAddQuestionForm(); - }); - Wait('start'); - ShowSurveyModal({ scope: scope, callback: 'DialogReady' }); - }; - } - -AddFactory.$inject = - [ 'showSurvey', - 'Wait' - ]; diff --git a/awx/ui/client/src/templates/survey-maker/surveys/delete.factory.js b/awx/ui/client/src/templates/survey-maker/surveys/delete.factory.js deleted file mode 100644 index a1ee5d36608e..000000000000 --- a/awx/ui/client/src/templates/survey-maker/surveys/delete.factory.js +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Delete a survey. Prompts user to confirm delete - * - * DeleteSurvey({ - * scope: $scope containing list of survey form fields - * id: id of job template that survey is attached to - * }) - * - */ -export default - function DeleteSurvey(GetBasePath, Rest, Wait, ProcessErrors) { - return function(params) { - - var scope = params.scope, - id = params.id, - templateType = params.templateType, - url; - - - if (scope.removeSurveyDeleted) { - scope.removeSurveyDeleted(); - } - scope.$on('SurveyDeleted', function(){ - scope.survey_name = ""; - scope.survey_description = ""; - scope.survey_questions = []; - scope.closeSurvey('survey-modal-dialog'); - Wait('stop'); - scope.survey_exists = false; - }); - - - Wait('start'); - - if(scope.mode==="add"){ - scope.$emit("SurveyDeleted"); - - } else { - let basePath = templateType === 'workflow_job_template' ? GetBasePath('workflow_job_templates') : GetBasePath('job_templates'); - url = basePath + id + '/survey_spec/'; - - Rest.setUrl(url); - Rest.destroy() - .then(() => { - scope.$emit("SurveyDeleted"); - - }) - .catch(({data, status}) => { - ProcessErrors(scope, data, status, { hdr: 'Error!', - msg: 'Failed to delete survey. DELETE returned status: ' + status }); - }); - } - }; - } - -DeleteSurvey.$inject = - [ 'GetBasePath', - 'Rest', - 'Wait', - 'ProcessErrors' - ]; diff --git a/awx/ui/client/src/templates/survey-maker/surveys/edit.factory.js b/awx/ui/client/src/templates/survey-maker/surveys/edit.factory.js deleted file mode 100644 index 90d2c734cef5..000000000000 --- a/awx/ui/client/src/templates/survey-maker/surveys/edit.factory.js +++ /dev/null @@ -1,67 +0,0 @@ -export default - function EditFactory(ShowSurveyModal, Wait, Rest, ProcessErrors, GetBasePath, Empty, AddSurvey) { - return function(params) { - var scope = params.scope, - id = params.id, - templateType = params.templateType, - url; - - if(templateType === 'job_template'){ - url = GetBasePath('job_templates') + id + '/survey_spec/'; - } - else if(templateType === 'workflow_job_template') { - url = GetBasePath('workflow_job_templates') + id + '/survey_spec/'; - } - - if (scope.removeDialogReady) { - scope.removeDialogReady(); - } - scope.removeDialogReady = scope.$on('DialogReady', function() { - $('#survey-modal-dialog').dialog('open'); - scope.generateAddQuestionForm(); - }); - - Wait('start'); - //for adding a job template: - if(scope.mode === 'add'){ - ShowSurveyModal({ title: "Edit Survey", scope: scope, callback: 'DialogReady' }); - } - //editing an existing job template: - else{ - // Get the existing record - Rest.setUrl(url); - Rest.get() - .then(({data}) => { - if(!Empty(data)){ - ShowSurveyModal({ title: "Edit Survey", scope: scope, callback: 'DialogReady' }); - scope.survey_name = data.name; - scope.survey_description = data.description; - scope.survey_questions = data.spec; - scope.isEditSurvey = true; - Wait('stop'); - } else { - AddSurvey({ - scope: scope - }); - } - - }) - .catch(({data, status}) => { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to retrieve survey. GET returned status: ' + status }); - }); - - } - }; - } - - -EditFactory.$inject = - [ 'showSurvey', - 'Wait', - 'Rest', - 'ProcessErrors', - 'GetBasePath', - 'Empty', - 'addSurvey' - ]; diff --git a/awx/ui/client/src/templates/survey-maker/surveys/init.factory.js b/awx/ui/client/src/templates/survey-maker/surveys/init.factory.js deleted file mode 100644 index d47be4a5c002..000000000000 --- a/awx/ui/client/src/templates/survey-maker/surveys/init.factory.js +++ /dev/null @@ -1,554 +0,0 @@ -export default - function Init(DeleteSurvey, EditSurvey, AddSurvey, GenerateForm, SurveyQuestionForm, Wait, Alert, - GetBasePath, Rest, ProcessErrors, EditQuestion, CreateSelect2, i18n) { - return function(params) { - var scope = params.scope, - id = params.id, - form = SurveyQuestionForm, - sce = params.sce, - templateType = params.templateType; - scope.sce = sce; - scope.survey_questions = []; - scope.answer_types=[ - {name: i18n._('Text'), type: 'text'}, - {name: i18n._('Textarea'), type: 'textarea'}, - {name: i18n._('Password'), type: 'password'}, - {name: i18n._('Multiple Choice (single select)'), type: 'multiplechoice'}, - {name: i18n._('Multiple Choice (multiple select)'), type: 'multiselect'}, - {name: i18n._('Integer'), type: 'integer'}, - {name: i18n._('Float'), type: 'float'} - ]; - scope.disableSurveyTooltip = i18n._('Disable Survey'); - scope.editQuestionTooltip = i18n._('Edit Question'); - scope.deleteQuestionTooltip = i18n._('Delete Question'); - scope.dragQuestionTooltip = i18n._('Drag to reorder question'); - - /* SURVEY RELATED FUNCTIONS */ - - // Called when a job template does not have a saved survey. This simply sets some - // default variables and fills the add question form via form generator. - scope.addSurvey = function() { - AddSurvey({ - scope: scope - }); - }; - - // Called when a job template (new or existing) already has a "saved" survey - // In the case where a job template has not yet been created but a survey has - // been the data is just pulled out of the scope rather than from the server. - // (this is dictated by scope.mode) - scope.editSurvey = function() { - // Goes out and fetches the existing survey and populates the preview - EditSurvey({ - scope: scope, - id: id, - templateType: templateType - }); - }; - - // This gets called after a user confirms survey deletion - scope.deleteSurvey = function() { - // Hide the delete overlay - scope.hideDeleteOverlay(); - // Show the loading spinner - Wait('start'); - // Call the delete survey factory which handles making the rest call - // and closing the modal after success - DeleteSurvey({ - scope: scope, - id: id, - templateType: templateType - }); - }; - - // Called when the user hits cancel/close on the survey modal. This function - // goes out and cleans up the survey_questions on scope before destroying - // the modal. - scope.closeSurvey = function(id) { - // Clear out the whole array, this data gets pulled in each time the modal is opened - scope.survey_questions = []; - - $('#' + id).dialog('destroy'); - }; - - scope.saveSurvey = function() { - Wait('start'); - - scope.survey_name = ""; - scope.survey_description = ""; - - var updateSurveyQuestions = function() { - if(templateType === 'job_template') { - Rest.setUrl(GetBasePath('job_templates') + id + '/survey_spec/'); - } - else if(templateType === 'workflow_job_template') { - Rest.setUrl(GetBasePath('workflow_job_templates') + id + '/survey_spec/'); - } - return Rest.post({name: scope.survey_name, description: scope.survey_description, spec: scope.survey_questions }) - .then(() => { - - }) - .catch(({data, status}) => { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to add new survey. POST returned status: ' + status }); - }); - }; - - var updateSurveyEnabled = function() { - if(templateType === 'job_template') { - Rest.setUrl(GetBasePath('job_templates') + id+ '/'); - } - else if(templateType === 'workflow_job_template') { - Rest.setUrl(GetBasePath('workflow_job_templates') + id+ '/'); - } - return Rest.patch({"survey_enabled": scope.survey_enabled}) - .then(() => { - - }) - .catch(({data, status}) => { - ProcessErrors(scope, data, status, form, { - hdr: 'Error!', - msg: 'Failed to save survey_enabled: GET status: ' + status - }); - }); - }; - - if (!scope.survey_questions || scope.survey_questions.length === 0) { - scope.deleteSurvey(); - } else { - updateSurveyQuestions() - .then(function() { - return updateSurveyEnabled(); - }) - .then(function() { - scope.closeSurvey('survey-modal-dialog'); - scope.$emit('SurveySaved'); - }); - } - }; - - // Gets called when the user clicks the on/off toggle beside the survey modal title. - scope.toggleSurveyEnabled = function() { - scope.survey_enabled = !scope.survey_enabled; - }; - - /* END SURVEY RELATED FUNCTIONS */ - - /* QUESTION RELATED FUNCTIONS */ - - // This injects the Add Question form into survey_maker_question_form - scope.generateAddQuestionForm = function(){ - // This tmpMode logic is necessary because form generator seems to set scope.mode to match the mode that you pass it. - // So if a user is editing a job template (scope.mode='edit') but the JT doesn't have a survey then when we open the - // modal we need to make sure that scope.mode is still 'edit' after the Add Question form is injected. - // To avoid having to do this we'd need to track the job template mode in a variable other than scope.mode. - var tmpMode = scope.mode; - GenerateForm.inject(form, { id:'survey_maker_question_form', mode: 'add' , scope: scope, related: false, noPanel: true}); - scope.mode = tmpMode; - scope.clearQuestion(); - }; - - // This gets called when a users clicks the pencil icon beside a question preview in order to edit it. - scope.editQuestion = function(index){ - scope.duplicate = false; - // The edit question factory injects the edit form and fills the form with the question data from memory. - EditQuestion({ - index: index, - scope: scope, - question: scope.survey_questions[index] - }); - }; - - // Gets called when a user clicks the delete icon on a question in the survey preview - scope.showDeleteQuestion = function(deleteIndex) { - // Keep track of the question to be deleted on scope - scope.questionToBeDeleted = deleteIndex; - // Show the delete overlay with mode='question' - scope.showDeleteOverlay('question'); - }; - - // Called after a user confirms question deletion (hitting the DELETE button on the delete question overlay). - scope.deleteQuestion = function(index){ - // Move the edit question index down by one if this question came before the - // one being edited in the array. This makes sure that our pointer to the question - // currently being edited gets updated independently from a deleted question. - if(GenerateForm.mode === 'edit' && !isNaN(scope.editQuestionIndex)){ - if(scope.editQuestionIndex === index) { - // The user is deleting the question being edited - need to roll back to Add Question mode - scope.editQuestionIndex = null; - scope.generateAddQuestionForm(); - } - else if(scope.editQuestionIndex > index) { - scope.editQuestionIndex--; - } - } - // Remove the question from the array - scope.survey_questions.splice(index, 1); - // Hide the delete overlay - scope.hideDeleteOverlay(); - }; - - function clearTypeSpecificFields() { - scope.minTextError = false; - scope.maxTextError = false; - scope.default = ""; - scope.default_multiselect = ""; - scope.default_float = ""; - scope.default_int = ""; - scope.default_textarea = ""; - scope.default_password = "" ; - scope.choices = ""; - scope.text_min = 0; - scope.text_max = 1024 ; - scope.textarea_min = 0; - scope.textarea_max = 4096; - scope.password_min = 0; - scope.password_max = 32; - scope.int_min = 0; - scope.int_max = 100; - scope.float_min = 0.0; - scope.float_max = 100.0; - } - - // Sets all of our scope variables used for adding/editing a question back to a clean state - scope.clearQuestion = function(){ - clearTypeSpecificFields(); - scope.editQuestionIndex = null; - scope.question_name = null; - scope.question_description = null; - scope.variable = null; - scope.required = true; //set the required checkbox to true via the ngmodel attached to scope.required. - scope.duplicate = false; - scope.invalidChoice = false; - scope.type = ""; - - // Make sure that the select2 dropdown for question type is clean - CreateSelect2({ - element:'#survey_question_type', - multiple: false - }); - - // Set the whole form to pristine - scope.survey_question_form.$setPristine(); - }; - - // Gets called when the "type" dropdown value changes. In that case, we want to clear out - // all the "type" specific fields/errors and start fresh. - scope.typeChange = function() { - clearTypeSpecificFields(); - scope.survey_question_form.default.$setPristine(); - scope.survey_question_form.default_multiselect.$setPristine(); - scope.survey_question_form.default_float.$setPristine(); - scope.survey_question_form.default_int.$setPristine(); - scope.survey_question_form.default_textarea.$setPristine(); - scope.survey_question_form.default_password.$setPristine(); - scope.survey_question_form.choices.$setPristine(); - scope.survey_question_form.int_min.$setPristine(); - scope.survey_question_form.int_max.$setPristine(); - }; - - // Function that gets called when a user hits ADD/UPDATE on the survey question form. This - // function handles some validation as well as eventually adding the question to the - // scope.survey_questions array. - scope.submitQuestion = function(){ - var data = {}, - fld, i, - choiceArray, - answerArray; - scope.invalidChoice = false; - scope.duplicate = false; - scope.minTextError = false; - scope.maxTextError = false; - - if(scope.type.type==="text"){ - if(scope.default && scope.default.trim() !== ""){ - if(scope.default.trim().length < scope.text_min && - scope.text_min !== "" && - scope.text_min !== null ){ - scope.minTextError = true; - } - if(scope.text_max < scope.default.trim().length && - scope.text_max !== "" && - scope.text_max !== null ){ - scope.maxTextError = true; - } - } - } - - if(scope.type.type==="textarea"){ - if(scope.default_textarea && scope.default_textarea.trim() !== ""){ - if(scope.default_textarea.trim().length < scope.textarea_min && - scope.textarea_min !== "" && - scope.textarea_min !== null ){ - scope.minTextError = true; - } - if(scope.textarea_max < scope.default_textarea.trim().length && - scope.textarea_max !== "" && - scope.textarea_max !== null ){ - scope.maxTextError = true; - } - } - } - - if(scope.type.type==="password"){ - if(scope.default_password && scope.default_password.trim() !== ""){ - if(scope.default_password.trim().length < scope.password_min && - scope.password_min !== "" && - scope.password_min !== null ){ - scope.minTextError = true; - } - if(scope.password_max < scope.default_password.trim().length && - scope.password_max !== "" && - scope.password_max !== null ){ - scope.maxTextError = true; - } - } - } - - if(scope.type.type==="multiselect" && scope.default_multiselect && scope.default_multiselect.trim() !== ""){ - choiceArray = scope.choices.split(/\n/); - answerArray = scope.default_multiselect.split(/\n/); - - if(answerArray.length>0){ - for(i=0; i i && dropIndex >= scope.editQuestionIndex) { - // An element that was ahead of the edit question is now behind it - scope.editQuestionIndex--; - } - } - } - - // Break out of the for loop - break; - } - - } - - // return true here signals that the drop is allowed, but that we've already taken care of inserting the element - return true; - }; - - // Gets called when a user is creating/editing a question that has a password - // field. The password field in the form has a SHOW/HIDE button that calls this. - scope.toggleInput = function(id) { - // Note that the id string passed into this function will have a "#" prepended - var buttonId = id + "_show_input_button", - inputId = id, - buttonInnerHTML = $(buttonId).html(); - if (buttonInnerHTML.indexOf("SHOW") > -1) { - $(buttonId).html(i18n._("HIDE")); - $(inputId).attr("type", "text"); - } else { - $(buttonId).html(i18n._("SHOW")); - $(inputId).attr("type", "password"); - } - }; - - /* END QUESTION RELATED FUNCTIONS */ - - /* DELETE OVERLAY RELATED FUNCTIONS */ - - // This handles setting the delete mode and flipping the boolean used to show the delete overlay - scope.showDeleteOverlay = function(mode) { - // Set the delete mode (question or survey) so that the overlay knows - // how to phrase the prompt - scope.deleteMode = mode; - // Flip the deleteOverlayVisible flag so that the overlay becomes visible via ng-show - scope.deleteOverlayVisible = true; - }; - - // Called by the cancel/close buttons on the delete overlay. Also called after deletion has been confirmed. - scope.hideDeleteOverlay = function() { - // Clear out the delete mode for next time - scope.deleteMode = null; - // Clear out the index variable for next time - scope.questionToBeDeleted = null; - // Hide the delete overlay - scope.deleteOverlayVisible = false; - }; - - /* END DELETE OVERLAY RELATED FUNCTIONS */ - - // Watcher that updates the survey enabled/disabled tooltip based on scope.survey_enabled - scope.$watch('survey_enabled', function(newVal) { - scope.surveyEnabledTooltip = (newVal) ? i18n._("Disable survey") : i18n._("Enable survey"); - }); - - }; - } - -Init.$inject = - [ 'deleteSurvey', - 'editSurvey', - 'addSurvey', - 'GenerateForm', - 'questionDefinitionForm', - 'Wait', - 'Alert', - 'GetBasePath', - 'Rest', - 'ProcessErrors', - 'editQuestion', - 'CreateSelect2', - 'i18n' - ]; diff --git a/awx/ui/client/src/templates/survey-maker/surveys/main.js b/awx/ui/client/src/templates/survey-maker/surveys/main.js deleted file mode 100644 index a16c10073cdb..000000000000 --- a/awx/ui/client/src/templates/survey-maker/surveys/main.js +++ /dev/null @@ -1,13 +0,0 @@ -import add from './add.factory'; -import edit from './edit.factory'; -import _delete from './delete.factory'; -import init from './init.factory'; -import show from './show.factory'; - -export default - angular.module('jobTemplates.surveyMaker.surveys', []) - .factory('showSurvey', show) - .factory('addSurvey', add) - .factory('editSurvey', edit) - .factory('deleteSurvey', _delete) - .factory('initSurvey', init); diff --git a/awx/ui/client/src/templates/survey-maker/surveys/show.factory.js b/awx/ui/client/src/templates/survey-maker/surveys/show.factory.js deleted file mode 100644 index 12faa2f401fb..000000000000 --- a/awx/ui/client/src/templates/survey-maker/surveys/show.factory.js +++ /dev/null @@ -1,53 +0,0 @@ -export default - function ShowFactory(Wait, CreateDialog, $compile) { - return function(params) { - // Set modal dimensions based on viewport width - - let scope = params.scope, - callback = params.callback, - mode = (params.mode) ? params.mode : "survey-maker", - title = params.title, - element, - target = (mode==='survey-taker') ? 'password-modal' : "survey-modal-dialog", - width = params.scope.can_edit ? 'calc(100vw - 50px)' : 600; - - - CreateDialog({ - id: target, - title: title, - scope: scope, - width: width, - minWidth: 400, - draggable: false, - dialogClass: 'SurveyMaker-dialog', - onClose: function() { - $('#'+target).empty(); - }, - onOpen: function() { - Wait('stop'); - - // Let the modal height be variable based on the content - // and set a uniform padding - $('#'+target).css({'height': 'auto', 'padding': '20px'}); - - if(mode==="survey-taker"){ - $('#survey-save-button').attr('ng-disabled', "survey_taker_form.$invalid"); - element = angular.element(document.getElementById('survey-save-button')); - $compile(element)(scope); - - } - - }, - _allowInteraction: function(e) { - return !!$(e.target).is('.select2-input') || this._super(e); - }, - callback: callback - }); - }; - } - -ShowFactory.$inject = - [ 'Wait', - 'CreateDialog', - '$compile' - ]; diff --git a/awx/ui/client/src/templates/templates.list.js b/awx/ui/client/src/templates/templates.list.js deleted file mode 100644 index 13c82763df21..000000000000 --- a/awx/ui/client/src/templates/templates.list.js +++ /dev/null @@ -1,117 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - -export default ['i18n', function(i18n) { - return { - - name: 'templates', - iterator: 'template', - basePath: 'unified_job_templates', - selectTitle: i18n._('Template'), - editTitle: i18n._('TEMPLATES'), - listTitle: i18n._('TEMPLATES'), - selectInstructions: i18n.sprintf(i18n._("Click on a row to select it, and click Finished when done. Use the %s button to create a new job template."), " "), - index: false, - hover: true, - - fields: { - name: { - key: true, - label: i18n._('Name'), - columnClass: 'col-lg-2 col-md-2 col-sm-4 col-xs-9', - ngHref: '#/templates/{{template.type}}/{{template.id}}', - awToolTip: '{{template.description | sanitize}}', - dataPlacement: 'top' - }, - type: { - label: i18n._('Type'), - ngBind: 'template.type_label', - columnClass: 'd-none d-sm-flex col-lg-2 col-md-2 col-sm-4' - }, - smart_status: { - label: i18n._('Activity'), - columnClass: 'd-none d-md-flex List-tableCell col-lg-2 col-md-3', - nosort: true, - ngInclude: "'/static/partials/job-template-smart-status.html'", - type: 'template' - }, - labels: { - label: i18n._('Labels'), - type: 'labels', - nosort: true, - showDelete: true, - columnClass: 'd-none d-md-flex List-tableCell col-lg-2 col-md-3' - } - }, - - actions: { - add: { - mode: 'all', // One of: edit, select, all - type: 'buttonDropdown', - basePaths: ['templates'], - awToolTip: i18n._('Create a new template'), - actionClass: 'at-Button--add', - actionId: 'button-add', - options: [ - { - optionContent: i18n._('Job Template'), - optionSref: 'templates.addJobTemplate', - ngShow: 'canAddJobTemplate' - }, - { - optionContent: i18n._('Workflow Template'), - optionSref: 'templates.addWorkflowJobTemplate', - ngShow: 'canAddWorkflowJobTemplate' - } - ], - ngShow: 'canAddJobTemplate || canAddWorkflowJobTemplate' - } - }, - - fieldActions: { - - columnClass: 'col-lg-2 col-md-3 col-sm-4 col-xs-3', - - edit: { - label: i18n._('Edit'), - ngClick: "editJobTemplate(template)", - awToolTip: i18n._('Edit template'), - "class": 'btn-default btn-xs', - dataPlacement: 'top', - ngShow: 'template.summary_fields.user_capabilities.edit', - editStateParams: ['job_template_id', 'workflow_job_template_id'] - }, - submit: { - // The submit key lets the list generator know that we want to use the - // at-launch-template directive - }, - copy: { - label: i18n._('Copy'), - ngClick: 'copyTemplate(template)', - "class": 'btn-danger btn-xs', - awToolTip: i18n._('Copy template'), - dataPlacement: 'top', - ngShow: 'template.summary_fields.user_capabilities.copy' - }, - view: { - label: i18n._('View'), - ngClick: "editJobTemplate(template)", - awToolTip: i18n._('View template'), - "class": 'btn-default btn-xs', - dataPlacement: 'top', - ngShow: '!template.summary_fields.user_capabilities.edit' - }, - "delete": { - label: i18n._('Delete'), - ngClick: "deleteJobTemplate(template)", - "class": 'btn-danger btn-xs', - awToolTip: i18n._('Delete template'), - dataPlacement: 'top', - ngShow: 'template.summary_fields.user_capabilities.delete' - } - } - };}]; diff --git a/awx/ui/client/src/templates/templates.service.js b/awx/ui/client/src/templates/templates.service.js deleted file mode 100644 index e293d03ebe83..000000000000 --- a/awx/ui/client/src/templates/templates.service.js +++ /dev/null @@ -1,295 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['Rest', 'GetBasePath', '$q', 'NextPage', function(Rest, GetBasePath, $q, NextPage){ - return { - deleteJobTemplate: function(id){ - var url = GetBasePath('job_templates'); - - url = url + id; - - Rest.setUrl(url); - return Rest.destroy(); - }, - deleteWorkflowJobTemplate: function(id) { - var url = GetBasePath('workflow_job_templates'); - - url = url + id; - - Rest.setUrl(url); - return Rest.destroy(); - }, - createJobTemplate: function(data){ - var url = GetBasePath('job_templates'); - - Rest.setUrl(url); - return Rest.post(data); - }, - createWorkflowJobTemplate: function(data) { - var url = GetBasePath('workflow_job_templates'); - - Rest.setUrl(url); - return Rest.post(data); - }, - getAllLabelOptions: function() { - Rest.setUrl(GetBasePath('labels') + '?page_size=200'); - return Rest.get() - .then(function(res) { - if (res.data.next) { - return NextPage({ - url: res.data.next, - arrayOfValues: res.data.results - }).then(function(labels) { - return labels; - }).catch(function(response){ - return $q.reject( response ); - }); - } - else { - return $q.resolve( res.data.results ); - } - }).catch(function(response){ - return $q.reject( response ); - }); - }, - getAllJobTemplateLabels: function(id) { - Rest.setUrl(GetBasePath('job_templates') + id + "/labels?page_size=20"); - return Rest.get() - .then(function(res) { - if (res.data.next) { - return NextPage({ - url: res.data.next, - arrayOfValues: res.data.results - }).then(function(labels) { - return labels; - }).catch(function(response){ - return $q.reject( response ); - }); - } - else { - return $q.resolve( res.data.results ); - } - }).catch(function(response){ - return $q.reject( response ); - }); - }, - - getAllWorkflowJobTemplateLabels: function(id) { - Rest.setUrl(GetBasePath('workflow_job_templates') + id + "/labels?page_size=200"); - return Rest.get() - .then(function(res) { - if (res.data.next) { - return NextPage({ - url: res.data.next, - arrayOfValues: res.data.results - }).then(function(labels) { - return labels; - }).catch(function(response){ - return $q.reject( response ); - }); - } - else { - return $q.resolve( res.data.results ); - } - }).catch(function(response){ - return $q.reject( response ); - }); - }, - getJobTemplate: function(id) { - var url = GetBasePath('job_templates'); - - url = url + id; - - Rest.setUrl(url); - return Rest.get(); - }, - addWorkflowNode: function(params) { - // params.url - // params.data - - Rest.setUrl(params.url); - return Rest.post(params.data); - }, - editWorkflowNode: function(params) { - // params.id - // params.data - - var url = GetBasePath('workflow_job_template_nodes') + params.id; - - Rest.setUrl(url); - return Rest.put(params.data); - }, - getJobTemplateLaunchInfo: function(id) { - var url = GetBasePath('job_templates'); - - url = url + id + '/launch'; - - Rest.setUrl(url); - return Rest.get(); - }, - getWorkflowJobTemplateNodes: function(id, page) { - var url = GetBasePath('workflow_job_templates'); - - url = url + id + '/workflow_nodes?page_size=200'; - - if(page) { - url += '&page=' + page; - } - - Rest.setUrl(url); - return Rest.get(); - }, - updateWorkflowJobTemplate: function(params) { - // params.id - // params.data - - var url = GetBasePath('workflow_job_templates'); - - url = url + params.id; - - Rest.setUrl(url); - return Rest.patch(params.data); - }, - getWorkflowJobTemplate: function(id) { - var url = GetBasePath('workflow_job_templates'); - - url = url + id; - - Rest.setUrl(url); - return Rest.get(); - }, - deleteWorkflowJobTemplateNode: function(id) { - var url = GetBasePath('workflow_job_template_nodes') + id; - - Rest.setUrl(url); - return Rest.destroy(); - }, - disassociateWorkflowNode: function(params) { - //params.parentId - //params.nodeId - //params.edge - - var url = GetBasePath('workflow_job_template_nodes') + params.parentId; - - if(params.edge === 'success') { - url = url + '/success_nodes'; - } - else if(params.edge === 'failure') { - url = url + '/failure_nodes'; - } - else if(params.edge === 'always') { - url = url + '/always_nodes'; - } - - Rest.setUrl(url); - return Rest.post({ - "id": params.nodeId, - "disassociate": true - }); - }, - associateWorkflowNode: function(params) { - //params.parentId - //params.nodeId - //params.edge - - var url = GetBasePath('workflow_job_template_nodes') + params.parentId; - - if(params.edge === 'success') { - url = url + '/success_nodes'; - } - else if(params.edge === 'failure') { - url = url + '/failure_nodes'; - } - else if(params.edge === 'always') { - url = url + '/always_nodes'; - } - - Rest.setUrl(url); - return Rest.post({ - id: params.nodeId - }); - }, - getUnifiedJobTemplate: function(id) { - var url = GetBasePath('unified_job_templates'); - - url = url + "?id=" + id; - - Rest.setUrl(url); - return Rest.get(); - }, - getCredential: function(id) { - var url = GetBasePath('credentials'); - - url = url + id; - - Rest.setUrl(url); - return Rest.get(); - }, - getInventory: function(id) { - var url = GetBasePath('inventory'); - - url = url + id; - - Rest.setUrl(url); - return Rest.get(); - }, - getWorkflowCopy: function(id) { - let url = GetBasePath('workflow_job_templates'); - - url = url + id + '/copy'; - - Rest.setUrl(url); - return Rest.get(); - }, - copyWorkflow: function(id) { - let url = GetBasePath('workflow_job_templates'); - - url = url + id + '/copy'; - - Rest.setUrl(url); - return Rest.post(); - }, - getWorkflowJobTemplateOptions: function() { - var deferred = $q.defer(); - - let url = GetBasePath('workflow_job_templates'); - - Rest.setUrl(url); - Rest.options() - .then(({data}) => { - deferred.resolve(data); - }).catch(({msg, code}) => { - deferred.reject(msg, code); - }); - - return deferred.promise; - }, - getJobTemplateOptions: function() { - var deferred = $q.defer(); - - let url = GetBasePath('job_templates'); - - Rest.setUrl(url); - Rest.options() - .then(({data}) => { - deferred.resolve(data); - }).catch(({msg, code}) => { - deferred.reject(msg, code); - }); - - return deferred.promise; - }, - postWorkflowNodeCredential: function(params) { - // params.id - // params.data - - var url = GetBasePath('workflow_job_template_nodes') + params.id + '/credentials'; - - Rest.setUrl(url); - return Rest.post(params.data); - } - }; -}]; diff --git a/awx/ui/client/src/templates/workflows.form.js b/awx/ui/client/src/templates/workflows.form.js deleted file mode 100644 index 989b3a1800f7..000000000000 --- a/awx/ui/client/src/templates/workflows.form.js +++ /dev/null @@ -1,384 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -/** - * @ngdoc function - * @name forms.function:Workflow - * @description This form is for adding/editing a Workflow -*/ - -export default ['NotificationsList', 'i18n', function(NotificationsList, i18n) { - return function() { - var WorkflowFormObject = { - - addTitle: i18n._('NEW WORKFLOW JOB TEMPLATE'), - editTitle: '{{ name }}', - name: 'workflow_job_template', - breadcrumbName: i18n._('WORKFLOW'), - base: 'workflow', - basePath: 'workflow_job_templates', - // the top-most node of generated state tree - stateTree: 'templates', - activeEditState: 'templates.editWorkflowJobTemplate', - tabs: true, - detailsClick: "$state.go('templates.editWorkflowJobTemplate')", - include: ['/static/partials/survey-maker-modal.html'], - - headerFields: { - missingTemplates: { - type: 'html', - html: `
- ` + - i18n._("Missing Job Templates found in the Workflow Editor") + - `
` - } - }, - - fields: { - name: { - label: i18n._('Name'), - type: 'text', - required: true, - ngDisabled: '!(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddOrEdit)', - column: 1 - }, - description: { - label: i18n._('Description'), - type: 'text', - column: 1, - ngDisabled: '!(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddOrEdit)' - }, - organization: { - label: i18n._('Organization'), - type: 'lookup', - sourceModel: 'organization', - basePath: 'organizations', - list: 'OrganizationList', - sourceField: 'name', - dataTitle: i18n._('Organization'), - dataContainer: 'body', - dataPlacement: 'right', - awRequiredWhen: { - reqExpression: '!current_user.is_superuser' - }, - column: 1, - ngDisabled: '!(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddOrEdit) || !canEditOrg', - awLookupWhen: '(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddOrEdit) && canEditOrg' - }, - inventory: { - label: i18n._('Inventory'), - type: 'lookup', - lookupMessage: i18n._("This inventory is applied to all job template nodes that prompt for an inventory."), - basePath: 'inventory', - list: 'InventoryList', - sourceModel: 'inventory', - sourceField: 'name', - autopopulateLookup: false, - column: 1, - awPopOver: "

" + i18n._("Select an inventory for the workflow. This inventory is applied to all job template nodes that prompt for an inventory.") + "

", - dataTitle: i18n._('Inventory'), - dataPlacement: 'right', - dataContainer: "body", - subCheckbox: { - variable: 'ask_inventory_on_launch', - ngChange: 'workflow_job_template_form.inventory_name.$validate()', - text: i18n._('Prompt on launch') - }, - ngDisabled: '!(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddOrEdit) || !canEditInventory', - }, - limit: { - label: i18n._('Limit'), - type: 'text', - column: 1, - awPopOver: "

" + i18n._("Provide a host pattern to further constrain the list of hosts that will be managed or affected by the workflow. This limit is applied to all job template nodes that prompt for a limit. Refer to Ansible documentation for more information and examples on patterns.") + "

", - dataTitle: i18n._('Limit'), - dataPlacement: 'right', - dataContainer: "body", - subCheckbox: { - variable: 'ask_limit_on_launch', - text: i18n._('Prompt on launch') - }, - ngDisabled: '!(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddOrEdit) || !canEditInventory', - }, - scm_branch: { - label: i18n._('SCM Branch'), - type: 'text', - column: 1, - awPopOver: "

" + i18n._("Select a branch for the workflow. This branch is applied to all job template nodes that prompt for a branch.") + "

", - dataTitle: i18n._('SCM Branch'), - dataPlacement: 'right', - dataContainer: "body", - subCheckbox: { - variable: 'ask_scm_branch_on_launch', - text: i18n._('Prompt on launch') - }, - ngDisabled: '!(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddOrEdit)', - }, - labels: { - label: i18n._('Labels'), - type: 'select', - ngOptions: 'label.label for label in labelOptions track by label.value', - multiSelect: true, - dataTitle: i18n._('Labels'), - dataPlacement: 'right', - awPopOver: "

" + i18n._("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.") + "

", - dataContainer: 'body', - onError: { - ngShow: 'workflow_job_template_labels_isValid !== true', - text: i18n._('Max 512 characters per label.'), - }, - ngDisabled: '!(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddOrEdit)' - }, - variables: { - label: i18n._('Extra Variables'), - type: 'textarea', - class: 'Form-textAreaLabel Form-formGroup--fullWidth', - rows: 6, - "default": "---", - column: 2, - awPopOver:i18n._('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 Tower documentation for example syntax.'), - dataTitle: i18n._('Extra Variables'), - dataPlacement: 'right', - dataContainer: "body", - subCheckbox: { - variable: 'ask_variables_on_launch', - text: i18n._('Prompt on launch') - }, - ngDisabled: '!(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddOrEdit)' // TODO: get working - }, - checkbox_group: { - label: i18n._('Options'), - type: 'checkbox_group', - fields: [{ - name: 'allow_simultaneous', - label: i18n._('Enable Concurrent Jobs'), - type: 'checkbox', - column: 2, - awPopOver: "

" + i18n._("If enabled, simultaneous runs of this workflow job template will be allowed.") + "

", - dataPlacement: 'right', - dataTitle: i18n._('Enable Concurrent Jobs'), - dataContainer: "body", - ngDisabled: '!(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddOrEdit)' - }, { - name: 'enable_webhook', - label: i18n._('Enable Webhook'), - type: 'checkbox', - column: 2, - awPopOver: "

" + i18n._("Enable webhook for this workflow job template.") + "

", - dataPlacement: 'right', - dataTitle: i18n._('Enable Webhook'), - dataContainer: "body", - ngDisabled: '!(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddOrEdit)' - }] - }, - webhook_service: { - label: i18n._('Webhook Service'), - type:'select', - defaultText: i18n._('Choose a Webhook Service'), - ngOptions: 'svc.label for svc in webhook_service_options track by svc.value', - ngShow: "enable_webhook && enable_webhook !== 'false'", - ngDisabled: "!(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddOrEdit)", - id: 'webhook-service-select', - column: 1, - awPopOver: "

" + i18n._("Select a webhook service.") + "

", - dataTitle: i18n._('Webhook Service'), - dataPlacement: 'right', - dataContainer: "body", - }, - webhook_url: { - label: i18n._('Webhook URL'), - type: 'text', - ngShow: "workflow_job_template_obj && enable_webhook && enable_webhook !== 'false'", - awPopOver: "webhook_url_help", - awPopOverWatch: "webhook_url_help", - dataPlacement: 'top', - dataTitle: i18n._('Webhook URL'), - dataContainer: "body", - readonly: true - }, - webhook_key: { - label: i18n._('Webhook Key'), - type: 'text', - ngShow: "enable_webhook && enable_webhook !== 'false'", - genHash: true, - genHashButtonTemplate: ` - - - - `, - genHashButtonClickHandlerName: "handleWebhookKeyButtonClick", - awPopOver: "webhook_key_help", - awPopOverWatch: "webhook_key_help", - dataPlacement: 'right', - dataTitle: i18n._("Webhook Key"), - dataContainer: "body", - readonly: true, - required: false, - }, - webhook_credential: { - label: i18n._('Webhook Credential'), - type: 'custom', - ngShow: "enable_webhook && enable_webhook !== 'false'", - control: ` - `, - awPopOver: "

" + i18n._("Optionally, select the credential to use to send status updates back to the webhook service.") + "

", - dataTitle: i18n._('Webhook Credential'), - dataPlacement: 'right', - dataContainer: "body", - ngDisabled: '!webhook_service.value', - required: false, - }, - }, - - buttons: { //for now always generates - - - -
- diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.controller.js b/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.controller.js deleted file mode 100644 index 136b2e6cdfbd..000000000000 --- a/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.controller.js +++ /dev/null @@ -1,800 +0,0 @@ -/************************************************* - * Copyright (c) 2018 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['$scope', 'TemplatesService', 'JobTemplateModel', 'PromptService', 'Rest', '$q', - 'TemplatesStrings', 'CreateSelect2', 'Empty', 'QuerySet', '$filter', - 'GetBasePath', 'WorkflowNodeFormService', 'ProcessErrors', - 'i18n', 'ParseTypeChange', 'WorkflowJobTemplateModel', - function($scope, TemplatesService, JobTemplate, PromptService, Rest, $q, - TemplatesStrings, CreateSelect2, Empty, qs, $filter, - GetBasePath, WorkflowNodeFormService, ProcessErrors, - i18n, ParseTypeChange, WorkflowJobTemplate - ) { - - let promptWatcher, credentialsWatcher, surveyQuestionWatcher, listPromises = []; - - const shouldShowPromptButton = (launchConf) => launchConf.survey_enabled || - launchConf.ask_inventory_on_launch || - launchConf.ask_credential_on_launch || - launchConf.ask_verbosity_on_launch || - launchConf.ask_job_type_on_launch || - launchConf.ask_limit_on_launch || - launchConf.ask_tags_on_launch || - launchConf.ask_skip_tags_on_launch || - launchConf.ask_diff_mode_on_launch || - launchConf.credential_needed_to_start || - launchConf.ask_variables_on_launch || - launchConf.ask_scm_branch_on_launch || - launchConf.variables_needed_to_start.length !== 0; - - $scope.strings = TemplatesStrings; - $scope.editNodeHelpMessage = null; - - $scope.templateList = WorkflowNodeFormService.templateListDefinition(); - $scope.inventorySourceList = WorkflowNodeFormService.inventorySourceListDefinition(); - $scope.projectList = WorkflowNodeFormService.projectListDefinition(); - - const checkCredentialsForRequiredPasswords = () => { - let credentialRequiresPassword = false; - $scope.jobNodeState.promptData.prompts.credentials.value.forEach((credential) => { - if ((credential.passwords_needed && - credential.passwords_needed.length > 0) || - (_.has(credential, 'inputs.vault_password') && - credential.inputs.vault_password === "ASK") - ) { - credentialRequiresPassword = true; - } - }); - - $scope.jobNodeState.credentialRequiresPassword = credentialRequiresPassword; - }; - - const watchForPromptChanges = () => { - let promptDataToWatch = [ - 'jobNodeState.promptData.prompts.inventory.value', - 'jobNodeState.promptData.prompts.verbosity.value', - 'jobNodeState.missingSurveyValue' - ]; - - promptWatcher = $scope.$watchGroup(promptDataToWatch, () => { - const templateType = _.get($scope, 'jobNodeState.promptData.templateType'); - let missingPromptValue = false; - - if ($scope.jobNodeState.missingSurveyValue) { - missingPromptValue = true; - } - - if (templateType !== "workflow_job_template") { - if (!$scope.jobNodeState.promptData.prompts.inventory.value || !$scope.jobNodeState.promptData.prompts.inventory.value.id) { - missingPromptValue = true; - } - } - - $scope.jobNodeState.promptModalMissingReqFields = missingPromptValue; - }); - - if ($scope.jobNodeState.promptData.launchConf.ask_credential_on_launch && $scope.jobNodeState.credentialRequiresPassword) { - credentialsWatcher = $scope.$watch('jobNodeState.promptData.prompts.credentials', () => { - checkCredentialsForRequiredPasswords(); - }); - } - }; - - const clearWatchers = () => { - if (promptWatcher) { - promptWatcher(); - } - - if (surveyQuestionWatcher) { - surveyQuestionWatcher(); - } - - if (credentialsWatcher) { - credentialsWatcher(); - } - }; - - const select2ifyDropdowns = () => { - CreateSelect2({ - element: '#workflow-node-types', - multiple: false - }); - CreateSelect2({ - element: '#workflow_node_edge', - multiple: false - }); - }; - - const formatPopOverDetails = (model) => { - const popOverDetails = {}; - popOverDetails.playbook = model.playbook || i18n._('NONE SELECTED'); - Object.keys(model.summary_fields).forEach(field => { - if (field === 'project') { - popOverDetails.project = model.summary_fields[field].name || i18n._('NONE SELECTED'); - } - if (field === 'inventory') { - popOverDetails.inventory = model.summary_fields[field].name || i18n._('NONE SELECTED'); - } - if (field === 'credentials') { - if (model.summary_fields[field].length <= 0) { - popOverDetails.credentials = i18n._('NONE SELECTED'); - } - else { - const credentialNames = model.summary_fields[field].map(({name}) => name); - popOverDetails.credentials = credentialNames.join('
'); - } - } - }); - model.popOver = ` - - - - - - - - - - - - - - - - - -
${i18n._('INVENTORY')} ${$filter('sanitize')(popOverDetails.inventory)}
${i18n._('PROJECT')} ${$filter('sanitize')(popOverDetails.project)}
${i18n._('PLAYBOOK')} ${$filter('sanitize')(popOverDetails.playbook)}
${i18n._('CREDENTIAL')} ${$filter('sanitize')(popOverDetails.credentials)}
- `; - }; - - const updateSelectedRow = () => { - let unifiedJobTemplateId; - switch($scope.activeTab) { - case 'templates': - unifiedJobTemplateId = _.get($scope, 'jobNodeState.selectedTemplate.id') || null; - $scope.wf_maker_templates.forEach((row, i) => { - if (row.type === 'job_template') { - formatPopOverDetails(row); - } - $scope.wf_maker_templates[i].checked = (row.id === unifiedJobTemplateId) ? 1 : 0; - }); - break; - case 'project_syncs': - unifiedJobTemplateId = _.get($scope, 'projectNodeState.selectedTemplate.id') || null; - $scope.wf_maker_projects.forEach((row, i) => { - $scope.wf_maker_projects[i].checked = (row.id === unifiedJobTemplateId) ? 1 : 0; - }); - break; - case 'inventory_syncs': - unifiedJobTemplateId = _.get($scope, 'inventoryNodeState.selectedTemplate.id') || null; - $scope.wf_maker_inventory_sources.forEach((row, i) => { - $scope.wf_maker_inventory_sources[i].checked = (row.id === unifiedJobTemplateId) ? 1 : 0; - }); - break; - } - }; - - const getEditNodeHelpMessage = (selectedTemplate, workflowJobTemplateObj) => { - if (selectedTemplate) { - if (selectedTemplate.type === "workflow_job_template") { - if (workflowJobTemplateObj.inventory && selectedTemplate.ask_inventory_on_launch) { - return $scope.strings.get('workflow_maker.INVENTORY_WILL_OVERRIDE'); - } - - if (workflowJobTemplateObj.ask_inventory_on_launch && selectedTemplate.ask_inventory_on_launch) { - return $scope.strings.get('workflow_maker.INVENTORY_PROMPT_WILL_OVERRIDE'); - } - } - - if (selectedTemplate.type === "job_template") { - if (workflowJobTemplateObj.inventory) { - if (selectedTemplate.ask_inventory_on_launch) { - return $scope.strings.get('workflow_maker.INVENTORY_WILL_OVERRIDE'); - } - - return $scope.strings.get('workflow_maker.INVENTORY_WILL_NOT_OVERRIDE'); - } - - if (workflowJobTemplateObj.ask_inventory_on_launch) { - if (selectedTemplate.ask_inventory_on_launch) { - return $scope.strings.get('workflow_maker.INVENTORY_PROMPT_WILL_OVERRIDE'); - } - - return $scope.strings.get('workflow_maker.INVENTORY_PROMPT_WILL_NOT_OVERRIDE'); - } - } - } - - return null; - }; - - const finishConfiguringEdit = () => { - const ujt = _.get($scope, 'nodeConfig.node.fullUnifiedJobTemplateObject'); - const templateType = _.get(ujt, 'type'); - - $scope.editNodeHelpMessage = getEditNodeHelpMessage(ujt, $scope.workflowJobTemplateObj); - - if (!$scope.readOnly) { - let jobTemplate = templateType === "workflow_job_template" ? new WorkflowJobTemplate() : new JobTemplate(); - - if (_.get($scope, 'nodeConfig.node.promptData') && !_.isEmpty($scope.nodeConfig.node.promptData)) { - $scope.jobNodeState.promptData = _.cloneDeep($scope.nodeConfig.node.promptData); - const launchConf = $scope.jobNodeState.promptData.launchConf; - - if (!shouldShowPromptButton(launchConf)) { - $scope.jobNodeState.showPromptButton = false; - $scope.jobNodeState.promptModalMissingReqFields = false; - } else { - $scope.jobNodeState.showPromptButton = true; - - if (templateType !== "workflow_job_template" && launchConf.ask_inventory_on_launch && !_.has(launchConf, 'defaults.inventory') && !_.has($scope, 'nodeConfig.node.originalNodeObject.summary_fields.inventory')) { - $scope.jobNodeState.promptModalMissingReqFields = true; - } else { - $scope.jobNodeState.promptModalMissingReqFields = false; - } - } - watchForPromptChanges(); - $scope.nodeFormDataLoaded = true; - } else if ( - _.get($scope, 'nodeConfig.node.fullUnifiedJobTemplateObject.unified_job_type') === 'job_template' || - _.get($scope, 'nodeConfig.node.fullUnifiedJobTemplateObject.type') === 'job_template' || - _.get($scope, 'nodeConfig.node.fullUnifiedJobTemplateObject.type') === 'workflow_job_template' - ) { - let promises = [jobTemplate.optionsLaunch($scope.nodeConfig.node.fullUnifiedJobTemplateObject.id), jobTemplate.getLaunch($scope.nodeConfig.node.fullUnifiedJobTemplateObject.id)]; - - if (_.has($scope, 'nodeConfig.node.originalNodeObject.related.credentials')) { - Rest.setUrl($scope.nodeConfig.node.originalNodeObject.related.credentials); - promises.push(Rest.get()); - } - - $q.all(promises) - .then((responses) => { - let launchOptions = responses[0].data, - launchConf = responses[1].data, - workflowNodeCredentials = responses[2] ? responses[2].data.results : []; - - let prompts = PromptService.processPromptValues({ - launchConf: responses[1].data, - launchOptions: responses[0].data, - currentValues: $scope.nodeConfig.node.originalNodeObject - }); - - let defaultCredsWithoutOverrides = []; - - prompts.credentials.previousOverrides = _.cloneDeep(workflowNodeCredentials); - - const credentialHasScheduleOverride = (templateDefaultCred) => { - let credentialHasOverride = false; - workflowNodeCredentials.forEach((scheduleCred) => { - if (templateDefaultCred.credential_type === scheduleCred.credential_type) { - if ( - (!templateDefaultCred.vault_id && !scheduleCred.inputs.vault_id) || - (templateDefaultCred.vault_id && scheduleCred.inputs.vault_id && templateDefaultCred.vault_id === scheduleCred.inputs.vault_id) - ) { - credentialHasOverride = true; - } - } - }); - - return credentialHasOverride; - }; - - if (_.has(launchConf, 'defaults.credentials')) { - launchConf.defaults.credentials.forEach((defaultCred) => { - if (!credentialHasScheduleOverride(defaultCred)) { - defaultCredsWithoutOverrides.push(defaultCred); - } - }); - } - - prompts.credentials.value = workflowNodeCredentials.concat(defaultCredsWithoutOverrides); - - if ( - $scope.nodeConfig.node.fullUnifiedJobTemplateObject.type === "job_template" && - ((!$scope.nodeConfig.node.fullUnifiedJobTemplateObject.inventory && !launchConf.ask_inventory_on_launch) || - !$scope.nodeConfig.node.fullUnifiedJobTemplateObject.project) - ) { - $scope.jobNodeState.selectedTemplateInvalid = true; - } else { - $scope.jobNodeState.selectedTemplateInvalid = false; - } - - let credentialRequiresPassword = false; - - prompts.credentials.value.forEach((credential) => { - if(credential.inputs) { - if ((credential.inputs.password && credential.inputs.password === "ASK") || - (credential.inputs.become_password && credential.inputs.become_password === "ASK") || - (credential.inputs.ssh_key_unlock && credential.inputs.ssh_key_unlock === "ASK") || - (credential.inputs.vault_password && credential.inputs.vault_password === "ASK") - ) { - credentialRequiresPassword = true; - } - } else if (credential.passwords_needed && credential.passwords_needed.length > 0) { - credentialRequiresPassword = true; - } - }); - - $scope.jobNodeState.credentialRequiresPassword = credentialRequiresPassword; - - if (!shouldShowPromptButton(launchConf)) { - $scope.jobNodeState.showPromptButton = false; - $scope.jobNodeState.promptModalMissingReqFields = false; - $scope.nodeFormDataLoaded = true; - } else { - $scope.jobNodeState.showPromptButton = true; - - if (launchConf.ask_inventory_on_launch && !_.has(launchConf, 'defaults.inventory') && !_.has($scope, 'nodeConfig.node.originalNodeObject.summary_fields.inventory')) { - $scope.jobNodeState.promptModalMissingReqFields = true; - } else { - $scope.jobNodeState.promptModalMissingReqFields = false; - } - - if (responses[1].data.survey_enabled) { - // go out and get the survey questions - jobTemplate.getSurveyQuestions($scope.nodeConfig.node.fullUnifiedJobTemplateObject.id) - .then((surveyQuestionRes) => { - - let processed = PromptService.processSurveyQuestions({ - surveyQuestions: surveyQuestionRes.data.spec, - extra_data: jsyaml.safeLoad(prompts.variables.value) - }); - - $scope.jobNodeState.missingSurveyValue = processed.missingSurveyValue; - - $scope.extraVars = (processed.extra_data === '' || _.isEmpty(processed.extra_data)) ? '---' : '---\n' + jsyaml.safeDump(processed.extra_data); - - // PromptService.processSurveyQuestions will strip the survey answers out of the extra - // vars so we should update the prompt value - prompts.variables = { - value: $scope.extraVars - }; - - $scope.nodeConfig.node.promptData = $scope.jobNodeState.promptData = { - launchConf: launchConf, - launchOptions: launchOptions, - prompts: prompts, - surveyQuestions: surveyQuestionRes.data.spec, - templateType: $scope.nodeConfig.node.fullUnifiedJobTemplateObject.type, - template: $scope.nodeConfig.node.fullUnifiedJobTemplateObject.id - }; - - surveyQuestionWatcher = $scope.$watch('jobNodeState.promptData.surveyQuestions', () => { - let missingSurveyValue = false; - _.each($scope.jobNodeState.promptData.surveyQuestions, (question) => { - if (question.required && (Empty(question.model) || question.model === [])) { - missingSurveyValue = true; - } - }); - $scope.jobNodeState.missingSurveyValue = missingSurveyValue; - }, true); - - checkCredentialsForRequiredPasswords(); - - watchForPromptChanges(); - - $scope.nodeFormDataLoaded = true; - }); - } else { - $scope.nodeConfig.node.promptData = $scope.jobNodeState.promptData = { - launchConf: launchConf, - launchOptions: launchOptions, - prompts: prompts, - templateType: $scope.nodeConfig.node.fullUnifiedJobTemplateObject.type, - template: $scope.nodeConfig.node.fullUnifiedJobTemplateObject.id - }; - - checkCredentialsForRequiredPasswords(); - - watchForPromptChanges(); - - $scope.nodeFormDataLoaded = true; - } - } - }); - } else { - $scope.nodeFormDataLoaded = true; - } - - if (_.get($scope, 'nodeConfig.node.fullUnifiedJobTemplateObject')) { - const selectedTemplate = $scope.nodeConfig.node.fullUnifiedJobTemplateObject; - - if (selectedTemplate.unified_job_type) { - switch (selectedTemplate.unified_job_type) { - case "job": - $scope.activeTab = "templates"; - $scope.jobNodeState.selectedTemplate = selectedTemplate; - break; - case "project_update": - $scope.activeTab = "project_syncs"; - $scope.projectNodeState.selectedTemplate = selectedTemplate; - break; - case "inventory_update": - $scope.activeTab = "inventory_syncs"; - $scope.inventoryNodeState.selectedTemplate = selectedTemplate; - break; - } - } else if (selectedTemplate.type) { - switch (selectedTemplate.type) { - case "job_template": - case "workflow_job_template": - $scope.activeTab = "templates"; - $scope.jobNodeState.selectedTemplate = selectedTemplate; - break; - case "project": - $scope.activeTab = "project_syncs"; - $scope.projectNodeState.selectedTemplate = selectedTemplate; - break; - case "inventory_source": - $scope.activeTab = "inventory_syncs"; - $scope.inventoryNodeState.selectedTemplate = selectedTemplate; - break; - } - } - updateSelectedRow(); - } else { - $scope.activeTab = "templates"; - } - - select2ifyDropdowns(); - } else { - $scope.jobTags = $scope.nodeConfig.node.originalNodeObject.job_tags ? $scope.nodeConfig.node.originalNodeObject.job_tags.split(',').map((tag) => (tag)) : []; - $scope.skipTags = $scope.nodeConfig.node.originalNodeObject.skip_tags ? $scope.nodeConfig.node.originalNodeObject.skip_tags.split(',').map((tag) => (tag)) : []; - $scope.showJobTags = true; - $scope.showSkipTags = true; - - if (!$.isEmptyObject($scope.nodeConfig.node.originalNodeObject.extra_data)) { - $scope.extraVars = '---\n' + jsyaml.safeDump($scope.nodeConfig.node.originalNodeObject.extra_data); - $scope.showExtraVars = true; - $scope.parseType = 'yaml'; - - ParseTypeChange({ - scope: $scope, - variable: 'extraVars', - field_id: 'workflow_node_form_extra_vars', - readOnly: true - }); - } else { - $scope.extraVars = null; - $scope.showExtraVars = false; - } - - $scope.nodeFormDataLoaded = true; - } - - }; - - const setupNodeForm = () => { - $scope.jobNodeState = { - credentialRequiresPassword: false, - missingSurveyValue: false, - promptData: null, - promptModalMissingReqFields: false, - searchTags: [], - selectedTemplate: null, - selectedTemplateInvalid: false, - showPromptButton: false - }; - $scope.projectNodeState = { - searchTags: [], - selectedTemplate: null - }; - $scope.inventoryNodeState = { - searchTags: [], - selectedTemplate: null, - }; - $scope.approvalNodeState = { - name: null, - description: null, - timeoutMinutes: 0, - timeoutSeconds: 0 - }; - $scope.nodeFormDataLoaded = false; - $scope.wf_maker_template_queryset = { - page_size: '10', - order_by: 'name', - role_level: 'execute_role', - type: 'workflow_job_template,job_template' - }; - - $scope.wf_maker_templates = []; - $scope.wf_maker_template_dataset = {}; - - // Go out and GET the list contents for each of the tabs - - listPromises.push( - qs.search(GetBasePath('unified_job_templates'), $scope.wf_maker_template_queryset) - .then((res) => { - $scope.wf_maker_template_dataset = res.data; - $scope.wf_maker_templates = $scope.wf_maker_template_dataset.results; - }) - ); - - $scope.wf_maker_project_queryset = { - page_size: '10', - order_by: 'name' - }; - - $scope.wf_maker_projects = []; - $scope.wf_maker_project_dataset = {}; - - listPromises.push( - qs.search(GetBasePath('projects'), $scope.wf_maker_project_queryset) - .then((res) => { - $scope.wf_maker_project_dataset = res.data; - $scope.wf_maker_projects = $scope.wf_maker_project_dataset.results; - }) - ); - - $scope.wf_maker_inventory_source_dataset = { - page_size: '10', - order_by: 'name', - not__source: '' - }; - - $scope.wf_maker_inventory_sources = []; - $scope.wf_maker_inventory_source_dataset = {}; - - listPromises.push( - qs.search(GetBasePath('inventory_sources'), $scope.wf_maker_inventory_source_dataset) - .then((res) => { - $scope.wf_maker_inventory_source_dataset = res.data; - $scope.wf_maker_inventory_sources = $scope.wf_maker_inventory_source_dataset.results; - }) - ); - - $q.all(listPromises) - .then(() => { - if ($scope.nodeConfig.mode === "edit") { - if ($scope.nodeConfig.node.unifiedJobTemplate && $scope.nodeConfig.node.unifiedJobTemplate.unified_job_type === "workflow_approval") { - $scope.activeTab = "approval"; - select2ifyDropdowns(); - - const timeoutMinutes = Math.floor($scope.nodeConfig.node.unifiedJobTemplate.timeout / 60); - const timeoutSeconds = $scope.nodeConfig.node.unifiedJobTemplate.timeout - timeoutMinutes * 60; - - $scope.approvalNodeState = { - name: $scope.nodeConfig.node.unifiedJobTemplate.name, - description: $scope.nodeConfig.node.unifiedJobTemplate.description, - timeoutMinutes, - timeoutSeconds - }; - - $scope.nodeFormDataLoaded = true; - } else { - // Make sure that we have the full unified job template object - if (!$scope.nodeConfig.node.fullUnifiedJobTemplateObject && _.has($scope, 'nodeConfig.node.originalNodeObject.summary_fields.unified_job_template')) { - // This is a node that we got back from the api with an incomplete - // unified job template so we're going to pull down the whole object - TemplatesService.getUnifiedJobTemplate($scope.nodeConfig.node.originalNodeObject.summary_fields.unified_job_template.id) - .then(({data}) => { - $scope.nodeConfig.node.fullUnifiedJobTemplateObject = data.results[0]; - finishConfiguringEdit(); - }, (error) => { - ProcessErrors($scope, error.data, error.status, null, { - hdr: 'Error!', - msg: 'Failed to get unified job template. GET returned ' + - 'status: ' + error.status - }); - }); - } else { - finishConfiguringEdit(); - } - } - } else { - $scope.activeTab = "templates"; - const alwaysOption = { - label: $scope.strings.get('workflow_maker.ALWAYS'), - value: 'always' - }; - const successOption = { - label: $scope.strings.get('workflow_maker.ON_SUCCESS'), - value: 'success' - }; - const failureOption = { - label: $scope.strings.get('workflow_maker.ON_FAILURE'), - value: 'failure' - }; - $scope.edgeTypeOptions = [alwaysOption]; - switch($scope.nodeConfig.newNodeIsRoot) { - case true: - $scope.edgeType = alwaysOption; - break; - case false: - $scope.edgeType = successOption; - $scope.edgeTypeOptions.push(successOption, failureOption); - break; - } - select2ifyDropdowns(); - - $scope.nodeFormDataLoaded = true; - } - }); - }; - - $scope.confirmNodeForm = () => { - const nodeFormData = { - edgeType: $scope.edgeType - }; - - if ($scope.activeTab === "approval") { - const timeout = $scope.approvalNodeState.timeoutMinutes * 60 + $scope.approvalNodeState.timeoutSeconds; - - nodeFormData.selectedTemplate = { - name: $scope.approvalNodeState.name, - description: $scope.approvalNodeState.description, - timeout, - unified_job_type: "workflow_approval" - }; - } else if($scope.activeTab === "templates") { - nodeFormData.selectedTemplate = $scope.jobNodeState.selectedTemplate; - nodeFormData.promptData = $scope.jobNodeState.promptData; - } else if($scope.activeTab === "project_syncs") { - nodeFormData.selectedTemplate = $scope.projectNodeState.selectedTemplate; - } else if($scope.activeTab === "inventory_syncs") { - nodeFormData.selectedTemplate = $scope.inventoryNodeState.selectedTemplate; - } - - $scope.select({ nodeFormData }); - }; - - $scope.openPromptModal = () => { - $scope.jobNodeState.promptData.triggerModalOpen = true; - }; - - $scope.selectIsDisabled = () => { - if($scope.activeTab === "templates") { - return !($scope.jobNodeState.selectedTemplate) || - $scope.jobNodeState.promptModalMissingReqFields || - $scope.jobNodeState.credentialRequiresPassword || - $scope.jobNodeState.selectedTemplateInvalid; - } else if($scope.activeTab === "project_syncs") { - return !$scope.projectNodeState.selectedTemplate; - } else if($scope.activeTab === "inventory_syncs") { - return !$scope.inventoryNodeState.selectedTemplate; - } else if ($scope.activeTab === "approval") { - return !($scope.approvalNodeState.name && $scope.approvalNodeState.name !== "") || $scope.workflow_approval.pauseTimeoutMinutes.$error.min || $scope.workflow_approval.pauseTimeoutSeconds.$error.min; - } - }; - - $scope.selectTemplate = (selectedTemplate) => { - if (!$scope.readOnly) { - clearWatchers(); - - $scope.approvalNodeState = { - name: null, - description: null, - timeoutMinutes: 0, - timeoutSeconds: 0 - }; - $scope.editNodeHelpMessage = getEditNodeHelpMessage(selectedTemplate, $scope.workflowJobTemplateObj); - - if (selectedTemplate.type === "job_template" || selectedTemplate.type === "workflow_job_template") { - let jobTemplate = selectedTemplate.type === "workflow_job_template" ? new WorkflowJobTemplate() : new JobTemplate(); - $scope.jobNodeState.promptData = null; - - $q.all([jobTemplate.optionsLaunch(selectedTemplate.id), jobTemplate.getLaunch(selectedTemplate.id)]) - .then((responses) => { - let launchConf = responses[1].data; - - let credentialRequiresPassword = false; - let selectedTemplateInvalid = false; - - if (selectedTemplate.type !== "workflow_job_template") { - if ((!selectedTemplate.inventory && !launchConf.ask_inventory_on_launch) || !selectedTemplate.project) { - selectedTemplateInvalid = true; - } - - if (launchConf.passwords_needed_to_start && launchConf.passwords_needed_to_start.length > 0) { - credentialRequiresPassword = true; - } - } - - $scope.jobNodeState.credentialRequiresPassword = credentialRequiresPassword; - $scope.jobNodeState.selectedTemplateInvalid = selectedTemplateInvalid; - $scope.jobNodeState.selectedTemplate = angular.copy(selectedTemplate); - updateSelectedRow(); - - if (!launchConf.survey_enabled && - !launchConf.ask_inventory_on_launch && - !launchConf.ask_credential_on_launch && - !launchConf.ask_verbosity_on_launch && - !launchConf.ask_job_type_on_launch && - !launchConf.ask_limit_on_launch && - !launchConf.ask_tags_on_launch && - !launchConf.ask_skip_tags_on_launch && - !launchConf.ask_diff_mode_on_launch && - !launchConf.credential_needed_to_start && - !launchConf.ask_variables_on_launch && - launchConf.variables_needed_to_start.length === 0) { - $scope.jobNodeState.showPromptButton = false; - $scope.jobNodeState.promptModalMissingReqFields = false; - } else { - $scope.jobNodeState.showPromptButton = true; - $scope.jobNodeState.promptModalMissingReqFields = false; - - if (selectedTemplate.type !== "workflow_job_template") { - if (launchConf.ask_inventory_on_launch && !_.has(launchConf, 'defaults.inventory')) { - $scope.jobNodeState.promptModalMissingReqFields = true; - } - } - - if (launchConf.survey_enabled) { - // go out and get the survey questions - jobTemplate.getSurveyQuestions(selectedTemplate.id) - .then((surveyQuestionRes) => { - - let processed = PromptService.processSurveyQuestions({ - surveyQuestions: surveyQuestionRes.data.spec - }); - - $scope.jobNodeState.missingSurveyValue = processed.missingSurveyValue; - - $scope.jobNodeState.promptData = { - launchConf: responses[1].data, - launchOptions: responses[0].data, - surveyQuestions: processed.surveyQuestions, - template: selectedTemplate.id, - templateType: selectedTemplate.type, - prompts: PromptService.processPromptValues({ - launchConf: responses[1].data, - launchOptions: responses[0].data - }), - }; - - surveyQuestionWatcher = $scope.$watch('jobNodeState.promptData.surveyQuestions', () => { - let missingSurveyValue = false; - _.each($scope.jobNodeState.promptData.surveyQuestions, (question) => { - if (question.required && (Empty(question.model) || question.model === [])) { - missingSurveyValue = true; - } - }); - $scope.jobNodeState.missingSurveyValue = missingSurveyValue; - }, true); - - watchForPromptChanges(); - }); - } else { - $scope.jobNodeState.promptData = { - launchConf: responses[1].data, - launchOptions: responses[0].data, - template: selectedTemplate.id, - templateType: selectedTemplate.type, - prompts: PromptService.processPromptValues({ - launchConf: responses[1].data, - launchOptions: responses[0].data - }), - }; - - watchForPromptChanges(); - } - } - }); - } else { - if (selectedTemplate.type === "project") { - $scope.projectNodeState.selectedTemplate = angular.copy(selectedTemplate); - } else if (selectedTemplate.type === "inventory_source") { - $scope.inventoryNodeState.selectedTemplate = angular.copy(selectedTemplate); - } - updateSelectedRow(); - } - } - }; - - $scope.$watch('nodeConfig.nodeId', (newNodeId, oldNodeId) => { - if (newNodeId !== oldNodeId) { - clearWatchers(); - setupNodeForm(); - } - }); - - $scope.$watchGroup(['wf_maker_templates', 'wf_maker_projects', 'wf_maker_inventory_sources', 'activeTab'], () => { - updateSelectedRow(); - }); - - setupNodeForm(); - } -]; diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.directive.js b/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.directive.js deleted file mode 100644 index ff16c0b2cc7e..000000000000 --- a/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.directive.js +++ /dev/null @@ -1,24 +0,0 @@ -/************************************************* - * Copyright (c) 2018 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import workflowNodeFormController from './workflow-node-form.controller'; - -export default ['templateUrl', - function(templateUrl) { - return { - scope: { - nodeConfig: '<', - workflowJobTemplateObj: '<', - cancel: '&', - select: '&', - readOnly: '<' - }, - restrict: 'E', - templateUrl: templateUrl('templates/workflows/workflow-maker/forms/workflow-node-form'), - controller: workflowNodeFormController - }; - } -]; diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.partial.html b/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.partial.html deleted file mode 100644 index b79ca9e79b74..000000000000 --- a/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.partial.html +++ /dev/null @@ -1,291 +0,0 @@ -
-
{{nodeConfig.mode === 'edit' ? nodeConfig.node.fullUnifiedJobTemplateObject.name || nodeConfig.node.unifiedJobTemplate.name : strings.get('workflow_maker.ADD_A_NODE')}}
-
- -
-
-
-
- - -
- -
-
No records matched your search.
-
-
PLEASE ADD ITEMS TO THIS LIST
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
- {{wf_maker_template.name}} - - {{:: strings.get('workflow_maker.WORKFLOW') }} - -
-
-
-
-
-
- -
-
-
- - -
- -
-
No records matched your search.
-
-
No Projects Have Been Created
-
-
-
-
-
-
-
-
-
-
- -
-
-
{{ wf_maker_project.name }}
-
-
-
-
- -
-
-
- - -
- -
-
No records matched your search.
-
-
PLEASE ADD ITEMS TO THIS LIST
-
-
-
-
-
-
-
-
-
-
- -
-
-
{{ wf_maker_inventory_source.name }}
-
-
-
-
- -
-
-
- -
- -
- -
-
- -
- -
- -
-
- -
- - - - min - - - - sec -
Please enter a number greater than or equal to 0.
-
-
-
-
-
-
- - {{:: strings.get('workflows.INVALID_JOB_TEMPLATE') }} -
-
-
-
- - {{:: strings.get('workflows.CREDENTIAL_WITH_PASS') }} -
-
-
- -
- -
-
-
-
- {{:: strings.get('workflow_maker.READ_ONLY_PROMPT_VALUES')}} -
-
- {{:: strings.get('workflow_maker.READ_ONLY_NO_PROMPT_VALUES')}} -
-
-
{{:: strings.get('prompt.JOB_TYPE') }}
-
- {{:: strings.get('prompt.PLAYBOOK_RUN') }} - {{:: strings.get('prompt.CHECK') }} -
-
-
-
{{:: strings.get('prompt.INVENTORY') }}
-
-
-
-
{{:: strings.get('prompt.LIMIT') }}
-
-
-
-
{{:: strings.get('prompt.VERBOSITY') }}
-
-
-
-
- {{:: strings.get('prompt.JOB_TAGS') }}  - - - - -
-
-
-
- {{tag}} -
-
-
-
-
-
- {{:: strings.get('prompt.SKIP_TAGS') }}  - - - - -
-
-
-
- {{tag}} -
-
-
-
-
-
{{:: strings.get('prompt.SHOW_CHANGES') }}
-
- {{:: strings.get('ON') }} - {{:: strings.get('OFF') }} -
-
-
-
{{:: strings.get('prompt.SCM_BRANCH') }}
-
-
-
-
{{:: strings.get('prompt.EXTRA_VARIABLES') }}
-
- -
-
-
-
-
-
- - - - -
- -
\ No newline at end of file diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.service.js b/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.service.js deleted file mode 100644 index 460f6ccfa0ee..000000000000 --- a/awx/ui/client/src/templates/workflows/workflow-maker/forms/workflow-node-form.service.js +++ /dev/null @@ -1,67 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['TemplateList', 'ProjectList', 'InventorySourcesList', 'i18n', - function(TemplateList, ProjectList, InventorySourcesList, i18n){ - return { - inventorySourceListDefinition: function() { - const inventorySourceList = _.cloneDeep(InventorySourcesList); - inventorySourceList.name = 'wf_maker_inventory_sources'; - inventorySourceList.iterator = 'wf_maker_inventory_source'; - inventorySourceList.maxVisiblePages = 5; - inventorySourceList.searchBarFullWidth = true; - inventorySourceList.disableRow = "{{ readOnly }}"; - inventorySourceList.disableRowValue = 'readOnly'; - - return inventorySourceList; - }, - projectListDefinition: function(){ - const projectList = _.cloneDeep(ProjectList); - delete projectList.fields.status; - delete projectList.fields.scm_type; - delete projectList.fields.last_updated; - projectList.name = 'wf_maker_projects'; - projectList.iterator = 'wf_maker_project'; - projectList.fields.name.columnClass = "col-md-11"; - projectList.maxVisiblePages = 5; - projectList.searchBarFullWidth = true; - projectList.disableRow = "{{ readOnly }}"; - projectList.disableRowValue = 'readOnly'; - - return projectList; - }, - templateListDefinition: function(){ - const templateList = _.cloneDeep(TemplateList); - delete templateList.actions; - delete templateList.fields.type; - delete templateList.fields.description; - delete templateList.fields.smart_status; - delete templateList.fields.labels; - delete templateList.fieldActions; - templateList.name = 'wf_maker_templates'; - templateList.iterator = 'wf_maker_template'; - templateList.fields.name.columnClass = "col-md-8"; - templateList.fields.name.tag = i18n._('WORKFLOW'); - templateList.fields.name.showTag = "{{wf_maker_template.type === 'workflow_job_template'}}"; - templateList.disableRow = "{{ readOnly }}"; - templateList.disableRowValue = 'readOnly'; - templateList.basePath = 'unified_job_templates'; - templateList.fields.info = { - ngInclude: "'/static/partials/job-template-details.html'", - type: 'template', - columnClass: 'col-md-3', - infoHeaderClass: 'col-md-3', - label: '', - nosort: true - }; - templateList.maxVisiblePages = 5; - templateList.searchBarFullWidth = true; - - return templateList; - } - }; - } -]; diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/main.js b/awx/ui/client/src/templates/workflows/workflow-maker/main.js deleted file mode 100644 index f93a952b822f..000000000000 --- a/awx/ui/client/src/templates/workflows/workflow-maker/main.js +++ /dev/null @@ -1,10 +0,0 @@ -import workflowMaker from './workflow-maker.directive'; -import WorkflowMakerController from './workflow-maker.controller'; -import workflowMakerForms from './forms/main'; - -export default - angular.module('templates.workflowMaker', [workflowMakerForms.name]) - // In order to test this controller I had to expose it at the module level - // like so. Is this correct? Is there a better pattern for doing this? - .controller('WorkflowMakerController', WorkflowMakerController) - .directive('workflowMaker', workflowMaker); diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less deleted file mode 100644 index bb17089dc9f7..000000000000 --- a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.block.less +++ /dev/null @@ -1,414 +0,0 @@ -.WorkflowMaker-dialog { - padding: 0px; - margin-bottom: 20px; - width: 100vw !important; - height: 100vh !important; - overflow: scroll; - .ui-dialog-buttonpane, .ui-dialog-titlebar { - display:none; - } - - input[type="number"]::-webkit-outer-spin-button, input[type="number"]::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; - } - - input[type="number"] { - -moz-appearance: textfield; - } -} - -.WorkflowMaker-header { - display: flex; - height: 34px; -} - -.WorkflowMaker-title { - align-items: center; - flex: 1 0 auto; - display: flex; - height: 34px; -} - -.WorkflowMaker-titleText { - color: @list-title-txt; - font-size: 14px; - font-weight: bold; - margin-right: 10px; -} - -.WorkflowMaker-exitHolder { - justify-content: flex-end; - display: flex; -} - -.WorkflowMaker-exit{ - cursor:pointer; - padding:0px; - border: none; - height:20px; - font-size: 20px; - background-color:@default-bg; - color:@d7grey; - transition: color 0.2s; - line-height:1; -} - -.WorkflowMaker-exit:hover{ - color:@default-icon; -} - -.WorkflowMaker-contentHolder { - display: flex; - border: 1px solid @b7grey; - border-radius: 5px; - height: ~"calc(100% - 85px)"; - overflow: hidden; -} - -.WorkflowMaker-contentLeft { - flex: 1; - flex-direction: column; - height: 100%; -} - -.WorkflowMaker-contentRight { - flex: 0 0 400px; - border-left: 1px solid @b7grey; - background: @default-bg; - padding: 20px; - height: 100%; - overflow-y: scroll; -} - -.WorkflowMaker-buttonHolder { - height: 30px; - display: flex; - justify-content: flex-end; - margin-top: 20px; -} - -.WorkflowMaker-saveButton{ - background-color: @submit-button-bg; - color: @submit-button-text; - text-transform: uppercase; - transition: background-color 0.2s; - padding-left:15px; - padding-right: 15px; - margin-left: 20px; -} - -.WorkflowMaker-saveButton:disabled{ - background-color: @submit-button-bg-dis; -} - -.WorkflowMaker-saveButton:hover{ - background-color: @submit-button-bg-hov; - color: @submit-button-text; -} - -.WorkflowMaker-cancelButton{ - background-color: @default-bg; - color: @btn-txt; - text-transform: uppercase; - border-radius: 5px; - border: 1px solid @b7grey; - transition: background-color 0.2s; - padding-left:15px; - padding-right: 15px; -} - -.WorkflowMaker-cancelButton:hover{ - background-color: @btn-bg-hov; - color: @btn-txt; -} - -.WorkflowMaker-deleteOverlay, -.WorkflowMaker-unsavedChangesOverlay { - height: 100%; - width: 100%; - position: absolute; - top: 0; - left: 0; - background: rgba(0,0,0,0.3); - z-index: 3; - display: flex; - align-items: center; - justify-content: center; - border-radius: 4px; -} - -.WorkflowMaker-deleteModal { - height: 200px; - width: 600px; - background-color: @default-bg; - border-radius: 5px; -} - -.WorkflowMaker-formTitle { - color: @list-title-txt; - font-size: 14px; - font-weight: bold; - margin-bottom: 20px; -} - -.WorkflowMaker-formHelp { - color: @default-interface-txt; -} - -.WorkflowMaker-formLists { - margin-bottom: 20px; - .SmartSearch-searchTermContainer { - width: 100%; - } -} - -.WorkflowMaker-formTitle { - display: flex; - color: @default-interface-txt; - margin-right: 10px; -} - -.WorkflowMaker-formLabel { - font-weight: normal; -} - -.WorkflowMaker-formElement { - margin-bottom: 10px; -} - -.WorkflowMaker-chart { - display: flex; - width: 100%; -} - -.WorkflowMaker-totalJobs { - margin-right: 5px; -} - -.WorkflowLegend-maker { - display: flex; - height: 40px; - line-height: 40px; - color: @default-interface-txt; - background: @default-bg; - border-bottom: 1px solid @b7grey; -} - -.WorkflowLegend-maker--left { - flex: 1 0 auto; -} - -.WorkflowLegend-maker--right { - flex: 0 0 217px; - text-align: right; - padding-right: 20px; - position: relative; -} - -.WorkflowLegend-onSuccessLegend { - height: 4px; - width: 20px; - background-color: @submit-button-bg; - margin: 18px 5px 18px 0px; -} - -.WorkflowLegend-onFailLegend { - height: 4px; - width: 20px; - background-color: @default-err; - margin: 18px 5px 18px 0px; -} - -.WorkflowLegend-alwaysLegend { - height: 4px; - width: 20px; - background-color: @default-link; - margin: 18px 5px 18px 0px; -} - -.WorkflowLegend-letterCircle{ - border-radius: 50%; - width: 20px; - height: 20px; - background: @default-icon; - color: @default-bg; - text-align: center; - margin: 10px 5px 10px 0px; - line-height: 20px; -} - -.WorkflowLegend-details { - align-items: center; - display: flex; - height: 40px; - line-height: 40px; - margin-top:10px; - border: 1px solid @d7grey; - border-top-left-radius: 5px; - border-top-right-radius: 5px; -} -.WorkflowLegend-details--left { - display: block; - flex: 1 0 auto; -} - -.WorkflowLegend-details--right { - flex: 0 0 44px; - text-align: right; - padding-right: 20px; - position:relative; -} - -.Key-menuIcon, -.WorkflowMaker-manualControlsIcon { - color: @default-icon; - vertical-align: middle; - font-size: 1.2em; - margin-left: 15px; -} - -.Key-menuIcon:hover, -.WorkflowMaker-manualControlsIcon:hover { - color: @default-link-hov; - cursor: pointer; -} - -.Key-menuIcon--active, -.WorkflowMaker-manualControlsIcon--active { - color: @default-link-hov; -} - -.WorkflowMaker-manualControls { - position: absolute; - left: -106px; - height: 60px; - width: 322px; - background-color: @default-bg; - display: flex; - border: 1px solid @b7grey; - border-top: 0px; - border-bottom-left-radius: 5px; - margin-left: -1px; - border-right: 0; -} - -.WorkflowLegend-manualControls { - position: absolute; - left: -272px; - top: 38px; - height: 60px; - width: 322px; - background-color: @default-bg; - display: flex; - border: 1px solid @d7grey; - border-bottom-left-radius: 5px; -} - -.WorkflowMaker-formTypeDropdown { - margin-bottom: 20px; -} - -.WorkflowMaker-preventBodyScrolling { - height: 100%; - overflow: hidden; -} - -.WorkflowMaker-invalidJobTemplateWarning { - margin-bottom: 5px; - color: @default-err; -} - -.WorkflowMaker-readOnlyPromptText { - margin-bottom: 20px; -} - -.WorkflowMaker-timeoutInput { - .ui-spinner { - width: 100px; - } -} - -.WorkflowMaker-timeoutSeconds { - margin-left: 10px; -} - -.WorkflowMaker-timeoutLabel { - margin-left: 3px; -} - -.Key-list { - margin: 0; - padding: 20px; - position: absolute; - background-color: @default-bg; - border: 1px solid @default-list-header-bg; -} - -.Key-listItem { - display: flex; - padding: 0; - margin: 5px 0 0 0; -} - -.Key-listItemContent { - margin: 0; - line-height: 20px; -} - -.Key-listItemContent--circle { - line-height: 28px; -} - -.Key-icon { - height: 3px; - width: 20px; - margin: 9px 5px 9px 0px; - outline: none; -} - -.Key-heading { - font-weight: 700; - margin: 0 0 10px; - line-height: 0; - padding: 0; -} - -.Key-icon--success { - background-color: @submit-button-bg; -} - -.Key-icon--fail { - background-color: @default-err; -} - -.Key-icon--always { - background-color: @default-link; -} - -.Key-icon--warning { - background: @default-warning; -} - -.Key-icon--default { - background: @default-icon; -} - -.Key-icon--circle { - border-radius: 50%; - width: 24px; - height: 24px; - color: @default-bg; - text-align: center; - line-height: 24px; - margin: 4px 5px 5px 0px; -} - -.Key-details { - display: flex; - height: 40px; - line-height: 40px; - padding-left: 20px; - border: 1px solid @default-no-items-bord; - margin-top:10px; -} diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.controller.js b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.controller.js deleted file mode 100644 index 323f13824cca..000000000000 --- a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.controller.js +++ /dev/null @@ -1,1096 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default ['$scope', 'TemplatesService', - 'ProcessErrors', '$q', 'Rest', - 'PromptService', 'TemplatesStrings', 'WorkflowChartService', - 'Wait', '$state', - function ($scope, TemplatesService, - ProcessErrors, $q, Rest, - PromptService, TemplatesStrings, WorkflowChartService, - Wait, $state - ) { - - let deletedNodeIds = []; - let workflowMakerNodeIdCounter; - let nodeIdToChartNodeIdMapping = {}; - let nodeRef = {}; - let allNodes = []; - let page = 1; - - $scope.strings = TemplatesStrings; - $scope.preventCredsWithPasswords = true; - $scope.showKey = false; - $scope.toggleKey = () => $scope.showKey = !$scope.showKey; - $scope.keyClassList = `{ 'Key-menuIcon--active': showKey }`; - $scope.readOnly = !_.get($scope, 'workflowJobTemplateObj.summary_fields.user_capabilities.edit'); - $scope.formState = { - 'showNodeForm': false, - 'showLinkForm': false - }; - - $scope.workflowChangesUnsaved = false; - $scope.workflowChangesStarted = false; - - $scope.cancelUnsavedChanges = () => { - $scope.unsavedChangesVisible = false; - }; - - let getNodes = () => { - Wait('start'); - TemplatesService.getWorkflowJobTemplateNodes($scope.workflowJobTemplateObj.id, page) - .then(({data}) => { - for (let i = 0; i < data.results.length; i++) { - allNodes.push(data.results[i]); - } - if (data.next) { - // Get the next page - page++; - getNodes(); - } else { - let arrayOfLinksForChart = []; - let arrayOfNodesForChart = []; - - ({arrayOfNodesForChart, arrayOfLinksForChart, nodeIdToChartNodeIdMapping, nodeRef, workflowMakerNodeIdCounter} = WorkflowChartService.generateArraysOfNodesAndLinks(allNodes)); - - $scope.graphState = { arrayOfNodesForChart, arrayOfLinksForChart }; - - Wait('stop'); - } - }, ({ data, status, config }) => { - Wait('stop'); - ProcessErrors($scope, data, status, null, { - hdr: $scope.strings.get('error.HEADER'), - msg: $scope.strings.get('error.CALL', { - path: `${config.url}`, - action: `${config.method}`, - status - }) - }); - }); - }; - - getNodes(); - - $scope.saveWorkflowMaker = () => { - - Wait('start'); - - $scope.unsavedChangesVisible = false; - - let buildSendableNodeData = (node) => { - // Create the node - let sendableNodeData = { - extra_data: {}, - inventory: null, - job_type: null, - job_tags: null, - skip_tags: null, - limit: null, - diff_mode: null, - verbosity: null, - credential: null - }; - - if (_.has(node, 'fullUnifiedJobTemplateObject')) { - sendableNodeData.unified_job_template = node.fullUnifiedJobTemplateObject.id; - } - - if (_.has(node, 'promptData.extraVars')) { - const formVars = node.promptData.extraVars; - const formVarsJSON = typeof formVars === 'string' ? jsyaml.safeLoad(formVars) : formVars; - if (_.get(node, 'promptData.launchConf.defaults.extra_vars')) { - const defaultVars = node.promptData.launchConf.defaults.extra_vars; - const defaultVarsJSON = typeof defaultVars === 'string' ? jsyaml.safeLoad(defaultVars) : defaultVars; - - // Only include extra vars that differ from the template default vars - _.forOwn(formVarsJSON, (value, key) => { - if (!defaultVarsJSON[key] || defaultVarsJSON[key] !== value) { - sendableNodeData.extra_data[key] = value; - } - }); - if (_.isEmpty(sendableNodeData.extra_data)) { - delete sendableNodeData.extra_data; - } - } else { - if (_.has(node, 'promptData.extraVars') && !_.isEmpty(formVarsJSON)) { - sendableNodeData.extra_data = formVarsJSON; - } - } - } - - // Check to see if the user has provided any prompt values that are different - // from the defaults in the job template - - if (_.has(node, 'fullUnifiedJobTemplateObject') && - (node.fullUnifiedJobTemplateObject.type === "workflow_job_template" || - node.fullUnifiedJobTemplateObject.type === "job_template") && - node.promptData - ) { - sendableNodeData = PromptService.bundlePromptDataForSaving({ - promptData: node.promptData, - dataToSave: sendableNodeData - }); - } - - return sendableNodeData; - }; - - if ($scope.graphState.arrayOfNodesForChart.length > 1) { - let approvalTemplatePromises = []; - let addPromises = []; - let editPromises = []; - let credentialRequests = []; - - Object.keys(nodeRef).map((workflowMakerNodeId) => { - const node = nodeRef[workflowMakerNodeId]; - if (node.isNew) { - if (node.unifiedJobTemplate && node.unifiedJobTemplate.unified_job_type === "workflow_approval") { - addPromises.push(TemplatesService.addWorkflowNode({ - url: $scope.workflowJobTemplateObj.related.workflow_nodes, - data: {} - }).then(({data: newNodeData}) => { - Rest.setUrl(newNodeData.related.create_approval_template); - approvalTemplatePromises.push(Rest.post({ - name: node.unifiedJobTemplate.name, - description: node.unifiedJobTemplate.description, - timeout: node.unifiedJobTemplate.timeout - }).then(() => { - node.originalNodeObject = newNodeData; - nodeIdToChartNodeIdMapping[newNodeData.id] = parseInt(workflowMakerNodeId); - }).catch(({ data, status }) => { - Wait('stop'); - ProcessErrors($scope, data, status, null, { - hdr: $scope.strings.get('error.HEADER') - }); - })); - }).catch(({ data, status }) => { - Wait('stop'); - ProcessErrors($scope, data, status, null, { - hdr: $scope.strings.get('error.HEADER') - }); - })); - } else { - addPromises.push(TemplatesService.addWorkflowNode({ - url: $scope.workflowJobTemplateObj.related.workflow_nodes, - data: buildSendableNodeData(node) - }).then(({data: newNodeData}) => { - node.originalNodeObject = newNodeData; - nodeIdToChartNodeIdMapping[newNodeData.id] = parseInt(workflowMakerNodeId); - if (_.get(node, 'promptData.launchConf.ask_credential_on_launch')) { - // This finds the credentials that were selected in the prompt but don't occur - // in the template defaults - let credentialIdsToPost = node.promptData.prompts.credentials.value.filter((credFromPrompt) => { - let defaultCreds = _.get(node, 'promptData.launchConf.defaults.credentials', []); - return !defaultCreds.some((defaultCred) => { - return credFromPrompt.id === defaultCred.id; - }); - }); - - credentialIdsToPost.forEach((credentialToPost) => { - credentialRequests.push({ - id: newNodeData.id, - data: { - id: credentialToPost.id - } - }); - }); - } - }).catch(({ data, status }) => { - Wait('stop'); - ProcessErrors($scope, data, status, null, { - hdr: $scope.strings.get('error.HEADER') - }); - })); - } - } else if (node.isEdited) { - if (node.unifiedJobTemplate && node.unifiedJobTemplate.unified_job_type === "workflow_approval") { - if (node.originalNodeObject.summary_fields.unified_job_template.unified_job_type === "workflow_approval") { - Rest.setUrl(node.originalNodeObject.related.unified_job_template); - approvalTemplatePromises.push(Rest.patch({ - name: node.unifiedJobTemplate.name, - description: node.unifiedJobTemplate.description, - timeout: node.unifiedJobTemplate.timeout - }).catch(({ data, status }) => { - Wait('stop'); - ProcessErrors($scope, data, status, null, { - hdr: $scope.strings.get('error.HEADER') - }); - })); - } else { - Rest.setUrl(node.originalNodeObject.related.create_approval_template); - approvalTemplatePromises.push(Rest.post({ - name: node.unifiedJobTemplate.name, - description: node.unifiedJobTemplate.description, - timeout: node.unifiedJobTemplate.timeout - }).catch(({ data, status }) => { - Wait('stop'); - ProcessErrors($scope, data, status, null, { - hdr: $scope.strings.get('error.HEADER') - }); - })); - } - } else { - editPromises.push(TemplatesService.editWorkflowNode({ - id: node.originalNodeObject.id, - data: buildSendableNodeData(node) - })); - } - - if (_.get(node, 'promptData.launchConf.ask_credential_on_launch')) { - let credentialsNotInPriorCredentials = node.promptData.prompts.credentials.value.filter((credFromPrompt) => { - let defaultCreds = _.get(node, 'promptData.launchConf.defaults.credentials', []); - return !defaultCreds.some((defaultCred) => { - return credFromPrompt.id === defaultCred.id; - }); - }); - - let credentialsToAdd = credentialsNotInPriorCredentials.filter((credNotInPrior) => { - let previousOverrides = _.get(node, 'promptData.prompts.credentials.previousOverrides', []); - return !previousOverrides.some((priorCred) => { - return credNotInPrior.id === priorCred.id; - }); - }); - - let credentialsToRemove = []; - - if (_.has(node, 'promptData.prompts.credentials.previousOverrides')) { - credentialsToRemove = node.promptData.prompts.credentials.previousOverrides.filter((priorCred) => { - return !credentialsNotInPriorCredentials.some((credNotInPrior) => { - return priorCred.id === credNotInPrior.id; - }); - }); - } - - credentialsToAdd.forEach((credentialToAdd) => { - credentialRequests.push({ - id: node.originalNodeObject.id, - data: { - id: credentialToAdd.id - } - }); - }); - - credentialsToRemove.forEach((credentialToRemove) => { - credentialRequests.push({ - id: node.originalNodeObject.id, - data: { - id: credentialToRemove.id, - disassociate: true - } - }); - }); - } - } - - }); - - let deletePromises = deletedNodeIds.map((nodeId) => { - return TemplatesService.deleteWorkflowJobTemplateNode(nodeId); - }); - - $q.all(addPromises.concat(editPromises, deletePromises)) - .then(() => { - $q.all(approvalTemplatePromises) - .then(() => { - let disassociatePromises = []; - let linkMap = {}; - - // Build a link map for easy access - $scope.graphState.arrayOfLinksForChart.forEach(link => { - // link.source.id of 1 is our artificial start node - if (link.source.id !== 1) { - const sourceNodeId = nodeRef[link.source.id].originalNodeObject.id; - const targetNodeId = nodeRef[link.target.id].originalNodeObject.id; - if (!linkMap[sourceNodeId]) { - linkMap[sourceNodeId] = {}; - } - - linkMap[sourceNodeId][targetNodeId] = link.edgeType; - } - }); - - Object.keys(nodeRef).map((workflowNodeId) => { - let nodeId = nodeRef[workflowNodeId].originalNodeObject.id; - if (nodeRef[workflowNodeId].originalNodeObject.success_nodes) { - nodeRef[workflowNodeId].originalNodeObject.success_nodes.forEach((successNodeId) => { - if ( - !deletedNodeIds.includes(successNodeId) && - (!linkMap[nodeId] || - !linkMap[nodeId][successNodeId] || - linkMap[nodeId][successNodeId] !== "success") - ) { - disassociatePromises.push( - TemplatesService.disassociateWorkflowNode({ - parentId: nodeId, - nodeId: successNodeId, - edge: "success" - }) - ); - } - }); - } - if (nodeRef[workflowNodeId].originalNodeObject.failure_nodes) { - nodeRef[workflowNodeId].originalNodeObject.failure_nodes.forEach((failureNodeId) => { - if ( - !deletedNodeIds.includes(failureNodeId) && - (!linkMap[nodeId] || - !linkMap[nodeId][failureNodeId] || - linkMap[nodeId][failureNodeId] !== "failure") - ) { - disassociatePromises.push( - TemplatesService.disassociateWorkflowNode({ - parentId: nodeId, - nodeId: failureNodeId, - edge: "failure" - }) - ); - } - }); - } - if (nodeRef[workflowNodeId].originalNodeObject.always_nodes) { - nodeRef[workflowNodeId].originalNodeObject.always_nodes.forEach((alwaysNodeId) => { - if ( - !deletedNodeIds.includes(alwaysNodeId) && - (!linkMap[nodeId] || - !linkMap[nodeId][alwaysNodeId] || - linkMap[nodeId][alwaysNodeId] !== "always") - ) { - disassociatePromises.push( - TemplatesService.disassociateWorkflowNode({ - parentId: nodeId, - nodeId: alwaysNodeId, - edge: "always" - }) - ); - } - }); - } - }); - - $q.all(disassociatePromises) - .then(() => { - let associatePromises = []; - Object.keys(linkMap).map((sourceNodeId) => { - Object.keys(linkMap[sourceNodeId]).map((targetNodeId) => { - const sourceChartNodeId = nodeIdToChartNodeIdMapping[sourceNodeId]; - const targetChartNodeId = nodeIdToChartNodeIdMapping[targetNodeId]; - switch(linkMap[sourceNodeId][targetNodeId]) { - case "success": - if ( - !nodeRef[sourceChartNodeId].originalNodeObject.success_nodes || - !nodeRef[sourceChartNodeId].originalNodeObject.success_nodes.includes(nodeRef[targetChartNodeId].originalNodeObject.id) - ) { - associatePromises.push( - TemplatesService.associateWorkflowNode({ - parentId: parseInt(sourceNodeId), - nodeId: parseInt(targetNodeId), - edge: "success" - }) - ); - } - break; - case "failure": - if ( - !nodeRef[sourceChartNodeId].originalNodeObject.failure_nodes || - !nodeRef[sourceChartNodeId].originalNodeObject.failure_nodes.includes(nodeRef[targetChartNodeId].originalNodeObject.id) - ) { - associatePromises.push( - TemplatesService.associateWorkflowNode({ - parentId: parseInt(sourceNodeId), - nodeId: parseInt(targetNodeId), - edge: "failure" - }) - ); - } - break; - case "always": - if ( - !nodeRef[sourceChartNodeId].originalNodeObject.always_nodes || - !nodeRef[sourceChartNodeId].originalNodeObject.always_nodes.includes(nodeRef[targetChartNodeId].originalNodeObject.id) - ) { - associatePromises.push( - TemplatesService.associateWorkflowNode({ - parentId: parseInt(sourceNodeId), - nodeId: parseInt(targetNodeId), - edge: "always" - }) - ); - } - break; - } - }); - }); - - let credentialPromises = credentialRequests.map((request) => { - return TemplatesService.postWorkflowNodeCredential({ - id: request.id, - data: request.data - }); - }); - - return $q.all(associatePromises.concat(credentialPromises)) - .then(() => { - Wait('stop'); - $scope.workflowChangesUnsaved = false; - $scope.workflowChangesStarted = false; - $scope.closeDialog(); - }).catch(({ data, status }) => { - Wait('stop'); - ProcessErrors($scope, data, status, null, { - hdr: $scope.strings.get('error.HEADER') - }); - }); - }).catch(({ - data, - status - }) => { - Wait('stop'); - ProcessErrors($scope, data, status, null, { - hdr: $scope.strings.get('error.HEADER') - }); - }); - }); - }).catch(({ data, status }) => { - Wait('stop'); - ProcessErrors($scope, data, status, null, { - hdr: $scope.strings.get('error.HEADER') - }); - }); - } else { - - let deletePromises = deletedNodeIds.map((nodeId) => { - return TemplatesService.deleteWorkflowJobTemplateNode(nodeId); - }); - - $q.all(deletePromises) - .then(() => { - Wait('stop'); - $scope.workflowChangesUnsaved = false; - $scope.workflowChangesStarted = false; - $scope.closeDialog(); - $state.transitionTo('templates'); - }).catch(({ data, status }) => { - Wait('stop'); - ProcessErrors($scope, data, status, null, { - hdr: $scope.strings.get('error.HEADER') - }); - }); - } - }; - - /* ADD NODE FUNCTIONS */ - - $scope.startAddNodeWithoutChild = (parent) => { - $scope.workflowChangesStarted = true; - if ($scope.nodeConfig) { - $scope.cancelNodeForm(); - } - - if ($scope.linkConfig) { - $scope.cancelLinkForm(); - } - - $scope.graphState.arrayOfNodesForChart.push({ - id: workflowMakerNodeIdCounter, - unifiedJobTemplate: null - }); - - $scope.graphState.nodeBeingAdded = workflowMakerNodeIdCounter; - - $scope.graphState.arrayOfLinksForChart.push({ - source: { - id: parent.id, - unifiedJobTemplate: parent.unifiedJobTemplate - }, - target: {id: workflowMakerNodeIdCounter}, - edgeType: "placeholder" - }); - - $scope.nodeConfig = { - mode: "add", - nodeId: workflowMakerNodeIdCounter, - newNodeIsRoot: parent.id === 1 - }; - - workflowMakerNodeIdCounter++; - - $scope.$broadcast("refreshWorkflowChart"); - - $scope.formState.showNodeForm = true; - }; - - $scope.startAddNodeWithChild = (link) => { - $scope.workflowChangesStarted = true; - if ($scope.nodeConfig) { - $scope.cancelNodeForm(); - } - - if ($scope.linkConfig) { - $scope.cancelLinkForm(); - } - - $scope.graphState.arrayOfNodesForChart.push({ - id: workflowMakerNodeIdCounter, - unifiedJobTemplate: null - }); - - $scope.graphState.nodeBeingAdded = workflowMakerNodeIdCounter; - - $scope.graphState.arrayOfLinksForChart.push({ - source: { - id: link.source.id, - unifiedJobTemplate: link.source.unifiedJobTemplate - }, - target: {id: workflowMakerNodeIdCounter}, - edgeType: "placeholder" - }); - - $scope.nodeConfig = { - mode: "add", - nodeId: workflowMakerNodeIdCounter, - newNodeIsRoot: link.source.id === 1 - }; - - // Search for the link that used to exist between source and target and shift it to - // go from our new node to the target - $scope.graphState.arrayOfLinksForChart.forEach((linkToCompare) => { - if (linkToCompare.source.id === link.source.id && linkToCompare.target.id === link.target.id) { - linkToCompare.source = {id: workflowMakerNodeIdCounter}; - } - }); - - workflowMakerNodeIdCounter++; - - $scope.$broadcast("refreshWorkflowChart"); - - $scope.formState.showNodeForm = true; - }; - - $scope.confirmNodeForm = (nodeFormData) => { - const { edgeType, selectedTemplate, promptData } = nodeFormData; - const isPauseNode = selectedTemplate.type === "workflow_approval" || - selectedTemplate.unified_job_type === "workflow_approval"; - // edgeType, selectedTemplate, promptData - // can determine pause node by looking at the type (?) or maybe unified_job_type - $scope.workflowChangesUnsaved = true; - const nodeId = $scope.nodeConfig.nodeId; - if ($scope.nodeConfig.mode === "add") { - if (edgeType && edgeType.value && selectedTemplate) { - if (isPauseNode) { - nodeRef[$scope.nodeConfig.nodeId] = { - unifiedJobTemplate: { - name: selectedTemplate.name, - description: selectedTemplate.description, - timeout: selectedTemplate.timeout, - unified_job_type: "workflow_approval" - }, - isNew: true - }; - } else { - nodeRef[$scope.nodeConfig.nodeId] = { - fullUnifiedJobTemplateObject: selectedTemplate, - promptData, - isNew: true - }; - } - $scope.graphState.nodeBeingAdded = null; - - $scope.graphState.arrayOfLinksForChart.map( (link) => { - if (link.target.id === nodeId) { - link.edgeType = edgeType.value; - link.target.unifiedJobTemplate = selectedTemplate; - } - }); - } - } else if ($scope.nodeConfig.mode === "edit") { - if (selectedTemplate) { - if (isPauseNode) { - // If it's a _new_ pause node then we'll want to create the new ujt - // If it's an existing pause node then we'll want to update the ujt - nodeRef[$scope.nodeConfig.nodeId].unifiedJobTemplate = { - name: selectedTemplate.name, - description: selectedTemplate.description, - timeout: selectedTemplate.timeout, - unified_job_type: "workflow_approval" - }; - nodeRef[$scope.nodeConfig.nodeId].isEdited = true; - } else { - nodeRef[$scope.nodeConfig.nodeId].fullUnifiedJobTemplateObject = selectedTemplate; - nodeRef[$scope.nodeConfig.nodeId].unifiedJobTemplate = selectedTemplate; - nodeRef[$scope.nodeConfig.nodeId].promptData = _.cloneDeep(promptData); - nodeRef[$scope.nodeConfig.nodeId].isEdited = true; - } - - $scope.graphState.nodeBeingEdited = null; - - $scope.graphState.arrayOfLinksForChart.map( (link) => { - if (link.target.id === nodeId) { - link.target.unifiedJobTemplate = selectedTemplate; - } - if (link.source.id === nodeId) { - link.source.unifiedJobTemplate = selectedTemplate; - } - }); - } - } - - $scope.graphState.arrayOfNodesForChart.map( (node) => { - if (node.id === nodeId) { - if (isPauseNode) { - node.unifiedJobTemplate = { - unified_job_type: 'workflow_approval', - name: selectedTemplate.name, - description: selectedTemplate.description, - timeout: selectedTemplate.timeout, - }; - } else { - node.unifiedJobTemplate = selectedTemplate; - } - - } - }); - - $scope.formState.showNodeForm = false; - $scope.nodeConfig = null; - - $scope.$broadcast("refreshWorkflowChart"); - }; - - $scope.cancelNodeForm = () => { - $scope.workflowChangesStarted = false; - const nodeId = $scope.nodeConfig.nodeId; - if ($scope.nodeConfig.mode === "add") { - // Remove the placeholder node from the array - for( let i = $scope.graphState.arrayOfNodesForChart.length; i--; ){ - if ($scope.graphState.arrayOfNodesForChart[i].id === nodeId) { - $scope.graphState.arrayOfNodesForChart.splice(i, 1); - i = 0; - } - } - - // Update the links - let parents = []; - let children = []; - - // Remove any links that reference this node - for( let i = $scope.graphState.arrayOfLinksForChart.length; i--; ){ - const link = $scope.graphState.arrayOfLinksForChart[i]; - - if (link.source.id === nodeId || link.target.id === nodeId) { - if (link.source.id === nodeId) { - children.push({id: link.target.id, edgeType: link.edgeType}); - } else if (link.target.id === nodeId) { - parents.push(link.source.id); - } - $scope.graphState.arrayOfLinksForChart.splice(i, 1); - } - } - - // Add the new links - parents.forEach((parentId) => { - children.forEach((child) => { - let source = { - id: parentId - }; - if (parentId === 1) { - child.edgeType = "always"; - } - $scope.graphState.arrayOfLinksForChart.push({ - source, - target: {id: child.id}, - edgeType: child.edgeType - }); - }); - }); - - } else if ($scope.nodeConfig.mode === "edit") { - $scope.graphState.nodeBeingEdited = null; - } - $scope.formState.showNodeForm = false; - $scope.nodeConfig = null; - $scope.$broadcast("refreshWorkflowChart"); - }; - - /* EDIT NODE FUNCTIONS */ - - $scope.startEditNode = (nodeToEdit) => { - $scope.workflowChangesStarted = true; - if ($scope.linkConfig) { - $scope.cancelLinkForm(); - } - - if (!$scope.nodeConfig || ($scope.nodeConfig && $scope.nodeConfig.nodeId !== nodeToEdit.id)) { - if ($scope.nodeConfig) { - $scope.cancelNodeForm(); - } - - $scope.nodeConfig = { - mode: "edit", - nodeId: nodeToEdit.id, - node: nodeRef[nodeToEdit.id] - }; - - $scope.graphState.nodeBeingEdited = nodeToEdit.id; - - $scope.formState.showNodeForm = true; - } - - $scope.$broadcast("refreshWorkflowChart"); - }; - - /* LINK FUNCTIONS */ - - $scope.startEditLink = (linkToEdit) => { - $scope.workflowChangesStarted = true; - const setupLinkEdit = () => { - - // Determine whether or not this link can be removed - let numberOfParents = 0; - $scope.graphState.arrayOfLinksForChart.forEach((link) => { - if (link.target.id === linkToEdit.target.id) { - numberOfParents++; - } - }); - - $scope.graphState.linkBeingEdited = { - source: linkToEdit.source.id, - target: linkToEdit.target.id - }; - - $scope.linkConfig = { - mode: "edit", - source: { - id: linkToEdit.source.id, - name: _.get(linkToEdit, 'source.unifiedJobTemplate.name') || "" - }, - target: { - id: linkToEdit.target.id, - name: _.get(linkToEdit, 'target.unifiedJobTemplate.name') || "" - }, - edgeType: linkToEdit.edgeType, - canUnlink: numberOfParents > 1 - }; - $scope.formState.showLinkForm = true; - - $scope.$broadcast("refreshWorkflowChart"); - }; - - if ($scope.nodeConfig) { - $scope.cancelNodeForm(); - } - - if ($scope.linkConfig) { - if ($scope.linkConfig.source.id !== linkToEdit.source.id || $scope.linkConfig.target.id !== linkToEdit.target.id) { - // User is going from editing one link to editing another - if ($scope.linkConfig.mode === "add") { - $scope.graphState.arrayOfLinksForChart.splice($scope.graphState.arrayOfLinksForChart.length-1, 1); - } - setupLinkEdit(); - } - } else { - setupLinkEdit(); - } - - }; - - $scope.selectNodeForLinking = (node) => { - $scope.workflowChangesStarted = true; - if ($scope.nodeConfig) { - $scope.cancelNodeForm(); - } - // User was add/editing a link and then hit the link icon - if ($scope.linkConfig && $scope.linkConfig.target) { - $scope.cancelLinkForm(); - } - if ($scope.linkConfig) { - // This is the second node selected - $scope.linkConfig.target = { - id: node.id, - name: node.unifiedJobTemplate.name - }; - $scope.linkConfig.edgeType = "success"; - - $scope.graphState.arrayOfNodesForChart.forEach((nodeToUpdate) => { - nodeToUpdate.isInvalidLinkTarget = false; - }); - - $scope.graphState.arrayOfLinksForChart.push({ - source: {id: $scope.linkConfig.source.id}, - target: {id: node.id}, - edgeType: "placeholder" - }); - - $scope.graphState.linkBeingEdited = { - source: {id: $scope.linkConfig.source.id}, - target: {id: node.id} - }; - - $scope.graphState.arrayOfLinksForChart.forEach((link, index) => { - if (link.source.id === 1 && link.target.id === node.id) { - $scope.graphState.arrayOfLinksForChart.splice(index, 1); - } - }); - - $scope.graphState.isLinkMode = false; - } else { - // This is the first node selected - $scope.graphState.addLinkSource = node.id; - $scope.linkConfig = { - mode: "add", - source: { - id: node.id, - name: node.unifiedJobTemplate.name - } - }; - - let parentMap = {}; - let invalidLinkTargetIds = []; - - // Find and mark any ancestors as disabled to prevent cycles - $scope.graphState.arrayOfLinksForChart.forEach((link) => { - // id=1 is our artificial root node so we don't care about that - if (link.source.id !== 1) { - if (link.source.id === node.id) { - // Disables direct children from the add link process - invalidLinkTargetIds.push(link.target.id); - } - if (!parentMap[link.target.id]) { - parentMap[link.target.id] = []; - } - parentMap[link.target.id].push(link.source.id); - } - }); - - let getAncestors = (id) => { - if (parentMap[id]) { - parentMap[id].forEach((parentId) => { - invalidLinkTargetIds.push(parentId); - getAncestors(parentId); - }); - } - }; - - getAncestors(node.id); - - // Filter out the duplicates - invalidLinkTargetIds.filter((element, index, array) => index === array.indexOf(element)).forEach((ancestorId) => { - $scope.graphState.arrayOfNodesForChart.forEach((node) => { - if (node.id === ancestorId) { - node.isInvalidLinkTarget = true; - } - }); - }); - - $scope.graphState.isLinkMode = true; - - $scope.formState.showLinkForm = true; - } - - $scope.$broadcast("refreshWorkflowChart"); - }; - - $scope.confirmLinkForm = (newEdgeType) => { - $scope.workflowChangesUnsaved = true; - $scope.graphState.arrayOfLinksForChart.forEach((link) => { - if (link.source.id === $scope.linkConfig.source.id && link.target.id === $scope.linkConfig.target.id) { - link.edgeType = newEdgeType; - } - }); - - if ($scope.linkConfig.mode === "add") { - $scope.graphState.arrayOfNodesForChart.forEach((node) => { - node.isInvalidLinkTarget = false; - }); - } - - $scope.graphState.linkBeingEdited = null; - $scope.graphState.addLinkSource = null; - $scope.formState.showLinkForm = false; - $scope.linkConfig = null; - $scope.$broadcast("refreshWorkflowChart"); - }; - - $scope.unlink = () => { - $scope.workflowChangesUnsaved = true; - // Remove the link - for( let i = $scope.graphState.arrayOfLinksForChart.length; i--; ){ - const link = $scope.graphState.arrayOfLinksForChart[i]; - - if (link.source.id === $scope.linkConfig.source.id && link.target.id === $scope.linkConfig.target.id) { - $scope.graphState.arrayOfLinksForChart.splice(i, 1); - } - } - - $scope.formState.showLinkForm = false; - $scope.linkConfig = null; - $scope.$broadcast("refreshWorkflowChart"); - }; - - $scope.cancelLinkForm = () => { - $scope.workflowChangesStarted = false; - if ($scope.linkConfig.mode === "add" && $scope.linkConfig.target) { - $scope.graphState.arrayOfLinksForChart.splice($scope.graphState.arrayOfLinksForChart.length-1, 1); - let targetIsOrphaned = true; - $scope.graphState.arrayOfLinksForChart.forEach((link) => { - if (link.target.id === $scope.linkConfig.target.id) { - targetIsOrphaned = false; - } - }); - if (targetIsOrphaned) { - // Link it to the start node - $scope.graphState.arrayOfLinksForChart.push({ - source: {id: 1}, - target: {id: $scope.linkConfig.target.id}, - edgeType: "always" - }); - } - } - $scope.graphState.linkBeingEdited = null; - $scope.graphState.addLinkSource = null; - $scope.graphState.isLinkMode = false; - $scope.graphState.arrayOfNodesForChart.forEach((node) => { - node.isInvalidLinkTarget = false; - }); - $scope.formState.showLinkForm = false; - $scope.linkConfig = null; - $scope.$broadcast("refreshWorkflowChart"); - }; - - /* DELETE NODE FUNCTIONS */ - - $scope.startDeleteNode = (nodeToDelete) => { - $scope.workflowChangesStarted = true; - $scope.nodeToBeDeleted = nodeToDelete; - $scope.deleteOverlayVisible = true; - }; - - $scope.cancelDeleteNode = () => { - $scope.workflowChangesStarted = false; - $scope.nodeToBeDeleted = null; - $scope.deleteOverlayVisible = false; - }; - - $scope.confirmDeleteNode = () => { - $scope.workflowChangesUnsaved = true; - if ($scope.nodeToBeDeleted) { - const nodeId = $scope.nodeToBeDeleted.id; - - if ($scope.linkConfig) { - $scope.cancelLinkForm(); - } - - if ($scope.nodeConfig && $scope.nodeConfig.nodeId === nodeId) { - $scope.cancelNodeForm(); - } - - // Remove the node from the array - for( let i = $scope.graphState.arrayOfNodesForChart.length; i--; ){ - if ($scope.graphState.arrayOfNodesForChart[i].id === nodeId) { - $scope.graphState.arrayOfNodesForChart.splice(i, 1); - i = 0; - } - } - - // Update the links - let parents = []; - let children = []; - let linkParentMapping = {}; - - // Remove any links that reference this node - for( let i = $scope.graphState.arrayOfLinksForChart.length; i--; ){ - const link = $scope.graphState.arrayOfLinksForChart[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, edgeType: link.edgeType}); - } else if (link.target.id === nodeId) { - parents.push(link.source.id); - } - $scope.graphState.arrayOfLinksForChart.splice(i, 1); - } - } - - // Add the new links - 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) { - $scope.graphState.arrayOfLinksForChart.push({ - source: {id: parentId}, - target: {id: child.id}, - edgeType: "always" - }); - } - } else { - // We don't want to add a link that already exists - if (!linkParentMapping[child.id].includes(parentId)) { - $scope.graphState.arrayOfLinksForChart.push({ - source: {id: parentId}, - target: {id: child.id}, - edgeType: child.edgeType - }); - } - } - - }); - }); - - if (nodeRef[$scope.nodeToBeDeleted.id].isNew !== true) { - deletedNodeIds.push(nodeRef[$scope.nodeToBeDeleted.id].originalNodeObject.id); - } - - delete nodeRef[$scope.nodeToBeDeleted.id]; - - $scope.deleteOverlayVisible = false; - - $scope.nodeToBeDeleted = null; - $scope.deleteOverlayVisible = false; - - $scope.$broadcast("refreshWorkflowChart"); - } - - }; - - $scope.toggleManualControls = () => { - $scope.showManualControls = !$scope.showManualControls; - }; - - $scope.panChart = (direction) => { - $scope.$broadcast('panWorkflowChart', { - direction: direction - }); - }; - - $scope.zoomChart = (zoom) => { - $scope.$broadcast('zoomWorkflowChart', { - zoom: zoom - }); - }; - - $scope.resetChart = () => { - $scope.$broadcast('resetWorkflowChart'); - }; - - $scope.workflowZoomed = (zoom) => { - $scope.$broadcast('workflowZoomed', { - zoom: zoom - }); - }; - - $scope.zoomToFitChart = () => { - $scope.$broadcast('zoomToFitChart'); - }; - } -]; diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.directive.js b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.directive.js deleted file mode 100644 index 79bb9f9429a3..000000000000 --- a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.directive.js +++ /dev/null @@ -1,99 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import workflowMakerController from './workflow-maker.controller'; - -export default ['templateUrl', 'CreateDialog', 'Wait', '$state', '$window', - function(templateUrl, CreateDialog, Wait, $state, $window) { - return { - scope: { - workflowJobTemplateObj: '=', - canAddOrEdit: '=' - }, - restrict: 'E', - templateUrl: templateUrl('templates/workflows/workflow-maker/workflow-maker'), - controller: workflowMakerController, - link: function(scope) { - - let availableHeight = $(window).height(), - availableWidth = $(window).width(), - minimumWidth = 682, - minimumHeight = 400; - - CreateDialog({ - id: 'workflow-modal-dialog', - scope: scope, - width: availableWidth > minimumWidth ? availableWidth : minimumWidth, - height: availableHeight > minimumHeight ? availableHeight : minimumHeight, - draggable: false, - dialogClass: 'WorkflowMaker-dialog', - position: ['center', 20], - onClose: function() { - $('#workflow-modal-dialog').empty(); - $('body').removeClass('WorkflowMaker-preventBodyScrolling'); - }, - onOpen: function() { - Wait('stop'); - $('body').addClass('WorkflowMaker-preventBodyScrolling'); - - // Let the modal height be variable based on the content - // and set a uniform padding - $('#workflow-modal-dialog').css({ 'padding': '20px' }); - $('#workflow-modal-dialog').outerHeight(availableHeight > minimumHeight ? availableHeight : minimumHeight); - $('#workflow-modal-dialog').outerWidth(availableWidth > minimumWidth ? availableWidth : minimumWidth); - - }, - _allowInteraction: function(e) { - return !!$(e.target).is('.select2-input') || this._super(e); - }, - callback: 'WorkflowDialogReady' - }); - if (scope.removeWorkflowDialogReady) { - scope.removeWorkflowDialogReady(); - } - scope.removeWorkflowDialogReady = scope.$on('WorkflowDialogReady', function() { - $('#workflow-modal-dialog').dialog('open'); - - scope.modalOpen = true; - - scope.$broadcast("refreshWorkflowChart"); - }); - - scope.closeDialog = function(exitWithUnsavedChanges) { - if ( - !scope.canAddOrEdit || - exitWithUnsavedChanges || - !(scope.workflowChangesUnsaved || scope.workflowChangesStarted) - ) { - scope.unsavedChangesVisible = false; - $('#workflow-modal-dialog').dialog('destroy'); - $('body').removeClass('WorkflowMaker-preventBodyScrolling'); - - $state.go('^'); - } else { - scope.unsavedChangesVisible = true; - } - }; - - function onResize(){ - availableHeight = $(window).height(); - availableWidth = $(window).width(); - $('#workflow-modal-dialog').outerHeight(availableHeight > minimumHeight ? availableHeight : minimumHeight); - $('#workflow-modal-dialog').outerWidth(availableWidth > minimumWidth ? availableWidth : minimumWidth); - - scope.$broadcast('workflowMakerModalResized'); - } - - function cleanUpResize() { - angular.element($window).off('resize', onResize); - } - - angular.element($window).on('resize', onResize); - scope.$on('$destroy', cleanUpResize); - } - }; - } -]; diff --git a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.partial.html b/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.partial.html deleted file mode 100644 index 92e904aa3d1f..000000000000 --- a/awx/ui/client/src/templates/workflows/workflow-maker/workflow-maker.partial.html +++ /dev/null @@ -1,110 +0,0 @@ - diff --git a/awx/ui/client/src/templates/workflows/workflow.block.less b/awx/ui/client/src/templates/workflows/workflow.block.less deleted file mode 100644 index 66f3f76ec4b2..000000000000 --- a/awx/ui/client/src/templates/workflows/workflow.block.less +++ /dev/null @@ -1,13 +0,0 @@ -.Workflow-warning { - float: right; - margin-right: 20px; - color: @default-interface-txt; -} -.Workflow-warningIcon { - color: @default-warning; - margin-right: 5px; -} -.Workflow-warningLink { - color: @default-link; - cursor: pointer; -} diff --git a/awx/ui/client/src/tooltip/tooltip.block.less b/awx/ui/client/src/tooltip/tooltip.block.less deleted file mode 100644 index bdcae890a278..000000000000 --- a/awx/ui/client/src/tooltip/tooltip.block.less +++ /dev/null @@ -1,78 +0,0 @@ -/** @define Tooltip */ -.tooltip.bs-tooltip-bottom .tooltip-arrow { - border-bottom-color: @default-interface-txt; -} - -.tooltip.bs-tooltip-top .tooltip-arrow { - border-top-color: @default-interface-txt; -} - -.tooltip.bs-tooltip-left .tooltip-arrow { - border-left-color: @default-interface-txt; -} - -.tooltip.bs-tooltip-right .tooltip-arrow { - border-right-color: @default-interface-txt; -} - -.tooltip.Tooltip.fade.bottom.in { - opacity: 1; - padding-top: 4px; -} - -.tooltip-inner { - background-color: @default-interface-txt; -} - -.tooltip-inner--logOut { - margin-left: -15px !important; -} - -.tooltip { - z-index: 2050; - opacity: 1.0; -} - -.tooltip-inner { - padding: 10px; - text-align:left; - max-width: 150px; -} - -.Tooltip-inner { - white-space: pre-wrap; - word-break: break-word; -} -.Tooltip-wide { - max-width: 300px!important; -} - -.Tooltip-secondary { - .Tooltip-inner { - background-color: @default-icon; - } -} - -.Tooltip-secondary.bs-tooltip-top { - .Tooltip-arrow { - border-top-color: @default-icon; - } -} - -.Tooltip-secondary.bs-tooltip-bottom { - .Tooltip-arrow { - border-bottom-color: @default-icon; - } -} - -.Tooltip-secondary.bs-tooltip-left { - .Tooltip-arrow { - border-left-color: @default-icon; - } -} - -.Tooltip-secondary.bs-tooltip-right { - .Tooltip-arrow { - border-right-color: @default-icon; - } -} diff --git a/awx/ui/client/src/users/add/users-add.controller.js b/awx/ui/client/src/users/add/users-add.controller.js deleted file mode 100644 index b6b1b3d035ed..000000000000 --- a/awx/ui/client/src/users/add/users-add.controller.js +++ /dev/null @@ -1,121 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import { N_ } from "../../i18n"; - -const user_type_options = [ - { type: 'normal', label: N_('Normal User') }, - { type: 'system_auditor', label: N_('System Auditor') }, - { type: 'system_administrator', label: N_('System Administrator') }, -]; - -export default ['$scope', '$rootScope', 'UserForm', 'GenerateForm', 'Rest', - 'Alert', 'ProcessErrors', 'ReturnToCaller', 'GetBasePath', - 'Wait', 'CreateSelect2', '$state', '$location', 'i18n', 'canAdd', - function($scope, $rootScope, UserForm, GenerateForm, Rest, Alert, - ProcessErrors, ReturnToCaller, GetBasePath, Wait, CreateSelect2, - $state, $location, i18n, canAdd) { - - var defaultUrl = GetBasePath('organizations'), - form = UserForm; - - init(); - - function init() { - // apply form definition's default field values - GenerateForm.applyDefaults(form, $scope); - - $scope.canAdd = canAdd; - $scope.isAddForm = true; - $scope.ldap_user = false; - $scope.not_ldap_user = !$scope.ldap_user; - $scope.ldap_dn = null; - $scope.socialAuthUser = false; - $scope.external_account = null; - $scope.last_login = null; - - Rest.setUrl(GetBasePath('users')); - Rest.options() - .then(({data}) => { - if (!data.actions.POST) { - $state.go("^"); - Alert(i18n._('Permission Error'), i18n._('You do not have permission to add a user.'), 'alert-info'); - } - }); - - $scope.user_type_options = user_type_options; - $scope.user_type = user_type_options[0]; - $scope.$watch('user_type', user_type_sync($scope)); - CreateSelect2({ - element: '#user_user_type', - multiple: false - }); - } - - function user_type_sync($scope) { - return (type_option) => { - $scope.is_superuser = false; - $scope.is_system_auditor = false; - switch (type_option.type) { - case 'system_administrator': - $scope.is_superuser = true; - break; - case 'system_auditor': - $scope.is_system_auditor = true; - break; - } - }; - } - - // Save - $scope.formSave = function() { - var fld, data = {}; - if ($scope[form.name + '_form'].$valid) { - if ($scope.organization !== undefined && $scope.organization !== null && $scope.organization !== '') { - Rest.setUrl(defaultUrl + $scope.organization + '/users/'); - for (fld in form.fields) { - if (form.fields[fld].realName) { - data[form.fields[fld].realName] = $scope[fld]; - } else { - data[fld] = $scope[fld]; - } - } - data.is_superuser = $scope.is_superuser; - data.is_system_auditor = $scope.is_system_auditor; - Wait('start'); - Rest.post(data) - .then(({data}) => { - var base = $location.path().replace(/^\//, '').split('/')[0]; - if (base === 'users') { - $rootScope.flashMessage = i18n._('New user successfully created!'); - $rootScope.$broadcast("EditIndicatorChange", "users", data.id); - $state.go('users.edit', { user_id: data.id }, { reload: true }); - } else { - ReturnToCaller(1); - } - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, form, { hdr: i18n._('Error!'), msg: i18n._('Failed to add new user. POST returned status: ') + status }); - }); - } else { - $scope.organization_name_api_error = i18n._('A value is required'); - } - } - }; - - $scope.formCancel = function() { - $state.go('users'); - }; - - // Password change - $scope.clearPWConfirm = function() { - // If password value changes, make sure password_confirm must be re-entered - $scope.password_confirm = ''; - let passValidity = (!$scope.password || $scope.password === '') ? true : false; - $scope[form.name + '_form'].password_confirm.$setValidity('awpassmatch', passValidity); - }; - } -]; diff --git a/awx/ui/client/src/users/edit/users-edit.controller.js b/awx/ui/client/src/users/edit/users-edit.controller.js deleted file mode 100644 index 73682dafa78e..000000000000 --- a/awx/ui/client/src/users/edit/users-edit.controller.js +++ /dev/null @@ -1,186 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import { N_ } from "../../i18n"; - -const user_type_options = [ - { type: 'normal', label: N_('Normal User') }, - { type: 'system_auditor', label: N_('System Auditor') }, - { type: 'system_administrator', label: N_('System Administrator') }, -]; - -export default ['$scope', '$rootScope', '$stateParams', 'UserForm', 'Rest', - 'ProcessErrors', 'GetBasePath', 'Wait', 'CreateSelect2', - '$state', 'i18n', 'resolvedModels', 'resourceData', - function($scope, $rootScope, $stateParams, UserForm, Rest, ProcessErrors, - GetBasePath, Wait, CreateSelect2, $state, i18n, models, resourceData) { - - for (var i = 0; i < user_type_options.length; i++) { - user_type_options[i].label = i18n._(user_type_options[i].label); - } - - const { me } = models; - var form = UserForm, - master = {}, - id = $stateParams.user_id, - defaultUrl = GetBasePath('users') + id, - user_obj = resourceData.data; - - $scope.breadcrumb.user_name = user_obj.username; - - init(); - - function init() { - _.forEach(form.fields, (value, key) => { - $scope[key] = user_obj[key]; - }); - - $scope.canEdit = me.get('summary_fields.user_capabilities.edit'); - $scope.isOrgAdmin = me.get('related.admin_of_organizations.count') > 0; - $scope.isCurrentlyLoggedInUser = (parseInt(id) === $rootScope.current_user.id); - $scope.hidePagination = false; - $scope.hideSmartSearch = false; - $scope.user_type_options = user_type_options; - $scope.user_type = user_type_options[0]; - $scope.$watch('user_type', user_type_sync($scope)); - $scope.$watch('is_superuser', hidePermissionsTabSmartSearchAndPaginationIfSuperUser($scope)); - $scope.user_id = id; - $scope.ldap_user = (user_obj.ldap_dn !== null && user_obj.ldap_dn !== undefined && user_obj.ldap_dn !== '') ? true : false; - $scope.not_ldap_user = !$scope.ldap_user; - master.ldap_user = $scope.ldap_user; - $scope.socialAuthUser = (user_obj.auth.length > 0) ? true : false; - $scope.last_login = user_obj.last_login; - $scope.external_account = user_obj.external_account; - - $scope.user_type = $scope.user_type_options[0]; - $scope.is_system_auditor = false; - $scope.is_superuser = false; - if (user_obj.is_system_auditor) { - $scope.user_type = $scope.user_type_options[1]; - $scope.is_system_auditor = true; - } - if (user_obj.is_superuser) { - $scope.user_type = $scope.user_type_options[2]; - $scope.is_superuser = true; - } - - $scope.user_obj = user_obj; - $scope.name = user_obj.username; - - CreateSelect2({ - element: '#user_user_type', - multiple: false - }); - - $scope.$watch('user_obj.summary_fields.user_capabilities.edit', function(val) { - $scope.canAdd = (val === false) ? false : true; - }); - } - - function user_type_sync($scope) { - return (type_option) => { - $scope.is_superuser = false; - $scope.is_system_auditor = false; - switch (type_option.type) { - case 'system_administrator': - $scope.is_superuser = true; - break; - case 'system_auditor': - $scope.is_system_auditor = true; - break; - } - }; - } - - // Organizations and Teams tab pagination is hidden through other mechanism - function hidePermissionsTabSmartSearchAndPaginationIfSuperUser(scope) { - return function(isSuperuserNewValue) { - let shouldHide = isSuperuserNewValue; - if (shouldHide === true) { - scope.hidePagination = true; - scope.hideSmartSearch = true; - } else if (shouldHide === false) { - scope.hidePagination = false; - scope.hideSmartSearch = false; - } - }; - } - - $scope.redirectToResource = function(resource) { - let type = resource.summary_fields.resource_type.replace(/ /g , "_"); - var id = resource.related[type].split("/")[4]; - switch (type) { - case 'organization': - $state.go('organizations.edit', { "organization_id": id }, { reload: true }); - break; - case 'team': - $state.go('teams.edit', { "team_id": id }, { reload: true }); - break; - case 'credential': - $state.go('credentials.edit', { "credential_id": id }, { reload: true }); - break; - case 'project': - $state.go('projects.edit', { "project_id": id }, { reload: true }); - break; - case 'inventory': - $state.go('inventories.edit', { "inventory_id": id }, { reload: true }); - break; - case 'job_template': - $state.go('templates.editJobTemplate', { "job_template_id": id }, { reload: true }); - break; - case 'workflow_job_template': - $state.go('templates.editWorkflowJobTemplate', { "workflow_job_template_id": id }, { reload: true }); - break; - } - }; - - // prepares a data payload for a PUT request to the API - var processNewData = function(fields) { - var data = {}; - _.forEach(fields, function(value, key) { - if (value.type === 'sensitive') { - if ($scope[key] !== '' && $scope[key] !== null && $scope[key] !== undefined) { - data[key] = $scope[key]; - } - } else { - data[key] = $scope[key]; - } - }); - data.is_superuser = $scope.is_superuser; - data.is_system_auditor = $scope.is_system_auditor; - return data; - }; - - $scope.formCancel = function() { - $state.go('users', null, { reload: true }); - }; - - $scope.formSave = function() { - $rootScope.flashMessage = null; - if ($scope[form.name + '_form'].$valid) { - Rest.setUrl(defaultUrl + '/'); - var data = processNewData(form.fields); - Rest.put(data).then(() => { - $state.go($state.current, null, { reload: true }); - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, null, { - hdr: i18n._('Error!'), - msg: i18n.sprintf(i18n._('Failed to retrieve user: %s. GET status: '), $stateParams.id) + status - }); - }); - } - }; - - $scope.clearPWConfirm = function() { - // If password value changes, make sure password_confirm must be re-entered - $scope.password_confirm = ''; - let passValidity = (!$scope.password || $scope.password === '') ? true : false; - $scope[form.name + '_form'].password_confirm.$setValidity('awpassmatch', passValidity); - $rootScope.flashMessage = null; - }; - } -]; diff --git a/awx/ui/client/src/users/list/users-list.controller.js b/awx/ui/client/src/users/list/users-list.controller.js deleted file mode 100644 index 96d01ebf04dd..000000000000 --- a/awx/ui/client/src/users/list/users-list.controller.js +++ /dev/null @@ -1,99 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import { N_ } from "../../i18n"; - -const user_type_options = [ - { type: 'normal', label: N_('Normal User') }, - { type: 'system_auditor', label: N_('System Auditor') }, - { type: 'system_administrator', label: N_('System Administrator') }, -]; - -export default ['$scope', '$rootScope', 'Rest', 'UserList', 'Prompt', - 'ProcessErrors', 'GetBasePath', 'Wait', '$state', '$filter', - 'rbacUiControlService', 'Dataset', 'i18n', 'resolvedModels', - function($scope, $rootScope, Rest, UserList, Prompt, - ProcessErrors, GetBasePath, Wait, $state, $filter, rbacUiControlService, - Dataset, i18n, models) { - - for (var i = 0; i < user_type_options.length; i++) { - user_type_options[i].label = i18n._(user_type_options[i].label); - } - - const { me } = models; - var list = UserList, - defaultUrl = GetBasePath('users'); - - init(); - - function init() { - $scope.canEdit = me.get('summary_fields.user_capabilities.edit'); - $scope.canAdd = false; - - rbacUiControlService.canAdd('users') - .then(function(params) { - $scope.canAdd = params.canAdd; - }); - - // search init - $scope.list = list; - $scope[`${list.iterator}_dataset`] = Dataset.data; - $scope[list.name] = $scope[`${list.iterator}_dataset`].results; - - - $rootScope.flashMessage = null; - $scope.selected = []; - } - - $scope.addUser = function() { - $state.go('users.add'); - }; - - $scope.editUser = function(id) { - $state.go('users.edit', { user_id: id }); - }; - - $scope.deleteUser = function(id, name) { - - var action = function() { - $('#prompt-modal').modal('hide'); - Wait('start'); - var url = defaultUrl + id + '/'; - Rest.setUrl(url); - Rest.destroy() - .then(() => { - - let reloadListStateParams = null; - - if($scope.users.length === 1 && $state.params.user_search && _.has($state, 'params.user_search.page') && $state.params.user_search.page !== '1') { - reloadListStateParams = _.cloneDeep($state.params); - reloadListStateParams.user_search.page = (parseInt(reloadListStateParams.user_search.page)-1).toString(); - } - - if (parseInt($state.params.user_id) === id) { - $state.go('^', null, { reload: true }); - } else { - $state.go('.', null, { reload: true }); - } - }) - .catch(({data, status}) => { - ProcessErrors($scope, data, status, null, { - hdr: i18n._('Error!'), - msg: i18n.sprintf(i18n._('Call to %s failed. DELETE returned status: '), url) + status - }); - }); - }; - - Prompt({ - hdr: i18n._('Delete'), - resourceName: $filter('sanitize')(name), - body: '
' + i18n._('Are you sure you want to delete this user?') + '
', - action: action, - actionText: i18n._('DELETE') - }); - }; - } -]; diff --git a/awx/ui/client/src/users/main.js b/awx/ui/client/src/users/main.js deleted file mode 100644 index 64222e39770d..000000000000 --- a/awx/ui/client/src/users/main.js +++ /dev/null @@ -1,114 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import UsersList from './list/users-list.controller'; -import UsersAdd from './add/users-add.controller'; -import UsersEdit from './edit/users-edit.controller'; -import UserForm from './users.form'; -import UserList from './users.list'; - -import userListRoute from './users.route'; -import UserTokensListRoute from '../../features/users/tokens/users-tokens-list.route'; -import UserTokensAddRoute from '../../features/users/tokens/users-tokens-add.route'; -import UserTokensAddApplicationRoute from '../../features/users/tokens/users-tokens-add-application.route'; - -export default -angular.module('Users', []) - .controller('UsersList', UsersList) - .controller('UsersAdd', UsersAdd) - .controller('UsersEdit', UsersEdit) - .factory('UserForm', UserForm) - .factory('UserList', UserList) - .config(['$stateProvider', 'stateDefinitionsProvider', '$stateExtenderProvider', - function($stateProvider, stateDefinitionsProvider, $stateExtenderProvider) { - let stateDefinitions = stateDefinitionsProvider.$get(); - let stateExtender = $stateExtenderProvider.$get(); - - function generateStateTree() { - let userAdd = stateDefinitions.generateTree({ - name: 'users.add', - url: '/add', - modes: ['add'], - form: 'UserForm', - controllers: { - add: 'UsersAdd' - }, - resolve: { - add: { - canAdd: ['rbacUiControlService', '$state', function(rbacUiControlService, $state) { - return rbacUiControlService.canAdd('users') - .then(function(res) { - return res.canAdd; - }) - .catch(function() { - $state.go('users'); - }); - }], - resolvedModels: ['MeModel', '$q', function(Me, $q) { - const promises= { - me: new Me('get').then((me) => me.extend('get', 'admin_of_organizations')) - }; - - return $q.all(promises); - }] - } - } - }); - - let userEdit = stateDefinitions.generateTree({ - name: 'users.edit', - url: '/:user_id', - modes: ['edit'], - form: 'UserForm', - parent: 'users', - controllers: { - edit: 'UsersEdit' - }, - data: { - activityStream: true, - activityStreamTarget: 'user' - }, - breadcrumbs: { - edit: "{{breadcrumb.user_name}}" - }, - resolve: { - edit: { - resolvedModels: ['MeModel', '$q', function(Me, $q) { - const promises= { - me: new Me('get').then((me) => me.extend('get', 'admin_of_organizations')) - }; - - return $q.all(promises); - }] - } - }, - }); - - return Promise.all([ - userAdd, - userEdit - ]).then((generated) => { - return { - states: _.reduce(generated, (result, definition) => { - return result.concat(definition.states); - }, [ - stateExtender.buildDefinition(userListRoute), - stateExtender.buildDefinition(UserTokensListRoute), - stateExtender.buildDefinition(UserTokensAddRoute), - stateExtender.buildDefinition(UserTokensAddApplicationRoute) - ]) - }; - }); - } - - $stateProvider.state({ - name: 'users.**', - url: '/users', - lazyLoad: () => generateStateTree() - }); - - } - ]); diff --git a/awx/ui/client/src/users/users.form.js b/awx/ui/client/src/users/users.form.js deleted file mode 100644 index 92d0442e456d..000000000000 --- a/awx/ui/client/src/users/users.form.js +++ /dev/null @@ -1,247 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - /** - * @ngdoc function - * @name forms.function:Users - * @description This form is for adding/editing users -*/ - -export default ['i18n', function(i18n) { - return { - - addTitle: i18n._('NEW USER'), - editTitle: '{{ username }}', - name: 'user', - // the top-most node of generated state tree - stateTree: 'users', - forceListeners: true, - tabs: true, - messageBar: { - ngShow: 'isOrgAdmin && !canEdit', - message: i18n._("Contact your System Administrator to grant you the appropriate permissions to add and edit Users and Teams.") - }, - fields: { - first_name: { - label: i18n._('First Name'), - type: 'text', - ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)', - required: false, - }, - last_name: { - label: i18n._('Last Name'), - type: 'text', - ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)', - required: false, - }, - organization: { - label: i18n._('Organization'), - type: 'lookup', - list: 'OrganizationList', - basePath: 'organizations', - sourceModel: 'organization', - sourceField: 'name', - required: true, - excludeMode: 'edit', - ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - email: { - label: i18n._('Email'), - type: 'email', - ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)', - required: true, - autocomplete: false - }, - username: { - label: i18n._('Username'), - type: 'text', - awRequiredWhen: { - reqExpression: "not_ldap_user && external_account === null", - init: true - }, - autocomplete: false, - ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - password: { - label: i18n._('Password'), - type: 'sensitive', - hasShowInputButton: true, - ngShow: 'ldap_user == false && socialAuthUser === false && external_account === null', - awRequiredWhen: { - reqExpression: "isAddForm", - init: false - }, - ngChange: "clearPWConfirm()", - autocomplete: false, - ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - password_confirm: { - label: i18n._('Confirm Password'), - type: 'sensitive', - hasShowInputButton: true, - ngShow: 'ldap_user == false && socialAuthUser === false && external_account === null', - awRequiredWhen: { - reqExpression: "isAddForm", - init: false - }, - awPassMatch: true, - associated: 'password', - autocomplete: false, - ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - user_type: { - label: i18n._('User Type'), - type: 'select', - ngOptions: 'item as item.label for item in user_type_options track by item.type', - disableChooseOption: true, - ngModel: 'user_type', - ngShow: 'current_user["is_superuser"]', - ngDisabled: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - }, - - buttons: { - cancel: { - ngClick: 'formCancel()', - ngShow: '(user_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - close: { - ngClick: 'formCancel()', - ngShow: '!(user_obj.summary_fields.user_capabilities.edit || canAdd)' - }, - save: { - ngClick: 'formSave()', - ngDisabled: true, - ngShow: '(user_obj.summary_fields.user_capabilities.edit || canAdd)' - } - }, - - related: { - organizations: { - name: 'organizations', - awToolTip: i18n._('Please save before assigning to organizations.'), - basePath: 'api/v2/users/{{$stateParams.user_id}}/organizations', - emptyListText: i18n._('Please add user to an Organization.'), - search: { - page_size: '10' - }, - dataPlacement: 'top', - type: 'collection', - title: i18n._('Organizations'), - iterator: 'organization', - index: false, - open: false, - - actions: {}, - - fields: { - name: { - key: true, - label: i18n._('Name'), - columnClass: "col-sm-6" - }, - description: { - label: i18n._('Description'), - columnClass: "col-sm-6" - } - }, - //hideOnSuperuser: true // RBAC defunct - }, - teams: { - name: 'teams', - awToolTip: i18n._('Please save before assigning to teams.'), - basePath: 'api/v2/users/{{$stateParams.user_id}}/teams', - search: { - page_size: '10' - }, - dataPlacement: 'top', - type: 'collection', - title: i18n._('Teams'), - iterator: 'team', - open: false, - index: false, - actions: {}, - emptyListText: i18n._('This user is not a member of any teams'), - fields: { - name: { - key: true, - label: i18n._('Name'), - columnClass: "col-sm-6" - }, - description: { - label: i18n._('Description'), - columnClass: "col-sm-6" - } - }, - //hideOnSuperuser: true // RBAC defunct - }, - permissions: { - name: 'permissions', - basePath: 'api/v2/users/{{$stateParams.user_id}}/roles/', - search: { - page_size: '10', - order_by: 'id' - }, - awToolTip: i18n._('Please save before assigning to organizations.'), - dataPlacement: 'top', - hideSearchAndActions: true, - type: 'collection', - title: i18n._('Permissions'), - iterator: 'permission', - open: false, - index: false, - emptyListText: i18n._('No permissions have been granted'), - fields: { - name: { - label: i18n._('Name'), - ngBind: 'permission.summary_fields.resource_name', - ngClick: "redirectToResource(permission)", - nosort: true, - columnClass: "col-sm-4" - }, - type: { - label: i18n._('Type'), - ngBind: 'permission.summary_fields.resource_type_display_name', - nosort: true, - columnClass: "col-sm-3" - }, - role: { - label: i18n._('Role'), - ngBind: 'permission.name', - nosort: true, - columnClass: "col-sm-3" - }, - }, - actions: { - add: { - ngClick: "$state.go('.add')", - label: i18n._('Add'), - awToolTip: i18n._('Grant Permission'), - actionClass: 'at-Button--add', - actionId: 'button-add', - ngShow: '(!is_superuser && (user_obj.summary_fields.user_capabilities.edit || canAdd))' - } - }, - fieldActions: { - columnClass: 'col-sm-2', - "delete": { - label: i18n._('Remove'), - ngClick: 'deletePermissionFromUser(user_id, username, permission.name, permission.summary_fields.resource_name, permission.related.users)', - iconClass: 'fa fa-times', - awToolTip: i18n._('Dissassociate permission from user'), - ngShow: 'permission.summary_fields.user_capabilities.unattach' - } - }, - //hideOnSuperuser: true // RBAC defunct - }, - tokens: { - ngIf: 'isCurrentlyLoggedInUser', - title: i18n._('Tokens'), - skipGenerator: true, - } - } - - };}]; diff --git a/awx/ui/client/src/users/users.list.js b/awx/ui/client/src/users/users.list.js deleted file mode 100644 index 47e4e1f8d7e8..000000000000 --- a/awx/ui/client/src/users/users.list.js +++ /dev/null @@ -1,88 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - - -export default ['i18n', function(i18n) { - return { - - name: 'users', - stateTree: 'users', - search: { - order_by: 'username' - }, - iterator: 'user', - selectTitle: i18n._('Add Users'), - editTitle: i18n._('USERS'), - listTitle: i18n._('USERS'), - selectInstructions: '

Select existing users by clicking each user or checking the related checkbox. When finished, click the blue ' + - 'Select button, located bottom right.

When available, a brand new user can be created by clicking the ' + - ' button.

', - index: false, - hover: true, - - fields: { - username: { - key: true, - label: i18n._('Username'), - columnClass: 'col-md-3 col-sm-3 col-xs-9' - }, - first_name: { - label: i18n._('First Name'), - columnClass: 'd-none d-sm-flex col-sm-3' - }, - last_name: { - label: i18n._('Last Name'), - columnClass: 'd-none d-sm-flex col-sm-3' - } - }, - - actions: { - add: { - label: i18n._('Create New'), - mode: 'all', // One of: edit, select, all - ngClick: 'addUser()', - basePaths: ['organizations', 'users'], // base path must be in list, or action not available - awToolTip: i18n._('Create a new user'), - actionClass: 'at-Button--add', - actionId: 'button-add', - ngShow: 'canAdd && canEdit' - } - }, - - fieldActions: { - - columnClass: 'col-md-3 col-sm-3 col-xs-3', - - edit: { - label: i18n._('Edit'), - ngClick: "editUser(user.id)", - icon: 'icon-edit', - "class": 'btn-xs btn-default', - awToolTip: i18n._('Edit user'), - dataPlacement: 'top', - ngShow: 'user.summary_fields.user_capabilities.edit' - }, - - view: { - label: i18n._('View'), - ngClick: "editUser(user.id)", - "class": 'btn-xs btn-default', - awToolTip: i18n._('View user'), - dataPlacement: 'top', - ngShow: '!user.summary_fields.user_capabilities.edit' - }, - - "delete": { - label: i18n._('Delete'), - ngClick: "deleteUser(user.id, user.username)", - icon: 'icon-trash', - "class": 'btn-xs btn-danger', - awToolTip: i18n._('Delete user'), - dataPlacement: 'top', - ngShow: 'user.summary_fields.user_capabilities.delete' - } - } - };}]; diff --git a/awx/ui/client/src/users/users.partial.html b/awx/ui/client/src/users/users.partial.html deleted file mode 100644 index 41b78528d46f..000000000000 --- a/awx/ui/client/src/users/users.partial.html +++ /dev/null @@ -1,6 +0,0 @@ -
- -
-
-
-
diff --git a/awx/ui/client/src/users/users.route.js b/awx/ui/client/src/users/users.route.js deleted file mode 100644 index 80f581a93b2c..000000000000 --- a/awx/ui/client/src/users/users.route.js +++ /dev/null @@ -1,55 +0,0 @@ -import {templateUrl} from '../shared/template-url/template-url.factory'; -import { N_ } from '../i18n'; - -export default { - name: 'users', - route: '/users', - ncyBreadcrumb: { - label: N_('USERS') - }, - data: { - activityStream: true, - activityStreamTarget: 'user' - }, - params: { - user_search: { - value: { - page_size: 20, - order_by: 'username' - } - } - }, - views: { - '@': { - templateUrl: templateUrl('users/users') - }, - 'list@users': { - templateProvider: function(UserList, generateList) { - let html = generateList.build({ - list: UserList, - mode: 'edit' - }); - html = generateList.wrapPanel(html); - return html; - }, - controller: 'UsersList' - } - }, - searchPrefix: 'user', - resolve: { - Dataset: ['UserList', 'QuerySet', '$stateParams', 'GetBasePath', - function(list, qs, $stateParams, GetBasePath) { - let path = GetBasePath(list.basePath) || GetBasePath(list.name); - return qs.search(path, $stateParams[`${list.iterator}_search`]); - } - ], - resolvedModels: ['MeModel', '$q', function(Me, $q) { - const promises= { - me: new Me('get') - }; - - return $q.all(promises); - }] - - } -}; diff --git a/awx/ui/client/src/vendor.js b/awx/ui/client/src/vendor.js deleted file mode 100644 index b9129b3f7005..000000000000 --- a/awx/ui/client/src/vendor.js +++ /dev/null @@ -1,75 +0,0 @@ -// Theme -require('~assets/custom-theme/jquery-ui-1.10.3.custom.min.css'); -require('~node_modules/bootstrap/dist/css/bootstrap.min.css'); -require('~assets/fontcustom/fontcustom.css'); -require('~node_modules/components-font-awesome/css/font-awesome.min.css'); -require('~node_modules/select2/dist/css/select2.css'); -require('~node_modules/codemirror/lib/codemirror.css'); -require('~node_modules/codemirror/theme/elegant.css'); -require('~node_modules/codemirror/addon/lint/lint.css'); -require('~node_modules/nvd3/build/nv.d3.css'); -require('~node_modules/ng-toast/dist/ngToast.min.css'); - -// jQuery + extensions -global.jQuery = require('jquery'); - -global.jquery = global.jQuery; -global.$ = global.jQuery; - -require('jquery-resize'); -require('jquery-ui'); -require('jquery-ui/ui/widgets/button'); -require('jquery-ui/ui/widgets/dialog'); -require('jquery-ui/ui/widgets/slider'); -require('jquery-ui/ui/widgets/spinner'); -require('bootstrap'); -require('popper.js'); -require('bootstrap-datepicker'); - -// jquery-ui and bootstrap both define $.fn.button -// the code below resolves that namespace clash -const btn = $.fn.button.noConflict(); -$.fn.btn = btn; - -// Whitelist table elements so they can be used in popovers -$.fn.popover.Constructor.Default.whiteList.table = []; -$.fn.popover.Constructor.Default.whiteList.th = []; -$.fn.popover.Constructor.Default.whiteList.tr = []; -$.fn.popover.Constructor.Default.whiteList.td = []; -$.fn.popover.Constructor.Default.whiteList.tbody = []; -$.fn.popover.Constructor.Default.whiteList.thead = []; - -require('select2'); - -// Standalone libs -global._ = require('lodash'); -require('moment'); -require('rrule'); -require('sprintf-js'); -require('reconnectingwebsocket'); -global.dagre = require('dagre'); - -// D3 + extensions -require('d3'); -require('nvd3'); - -// Angular -require('angular'); -require('angular-cookies'); -require('angular-sanitize'); -require('angular-breadcrumb'); -require('angular-codemirror'); -require('angular-drag-and-drop-lists'); -require('angular-duration-format'); -require('angular-gettext'); -require('angular-moment'); -require('angular-scheduler'); -require('angular-tz-extensions'); -require('@uirouter/angularjs'); -require('ng-toast-provider'); -require('ng-toast-directives'); -require('ng-toast'); -require('lr-infinite-scroll'); -require('codemirror/mode/yaml/yaml'); -require('codemirror/mode/javascript/javascript'); -require('codemirror/mode/jinja2/jinja2'); diff --git a/awx/ui/client/src/workflow-results/main.js b/awx/ui/client/src/workflow-results/main.js deleted file mode 100644 index f01c60ccafc1..000000000000 --- a/awx/ui/client/src/workflow-results/main.js +++ /dev/null @@ -1,18 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import workflowStatusBar from './workflow-status-bar/main'; -import route from './workflow-results.route.js'; -import workflowResultsService from './workflow-results.service'; -import controller from './workflow-results.controller'; - -export default - angular.module('workflowResults', [workflowStatusBar.name]) - .run(['$stateExtender', function($stateExtender) { - $stateExtender.addState(route); - }]) - .controller('workflowResultsController', controller) - .service('workflowResultsService', workflowResultsService); diff --git a/awx/ui/client/src/workflow-results/standard-out.block.less b/awx/ui/client/src/workflow-results/standard-out.block.less deleted file mode 100644 index aab04ed73b85..000000000000 --- a/awx/ui/client/src/workflow-results/standard-out.block.less +++ /dev/null @@ -1,47 +0,0 @@ -/** @define StandardOut */ - -@breakpoint-md: 1180px; - -.StandardOut-panelHeader { - .OnePlusOne-panelHeader -} - -.StandardOut-panelHeaderText { - align-items: center; - flex: 1 0 auto; - display: flex; -} - -.StandardOut-panelHeaderActions { - justify-content: flex-end; - margin-left: 10px; - font-size: 12px; -} - -.StandardOut-actionButton { - font-size: 16px; - height: 30px; - min-width: 30px; - color: @list-action-icon; - background-color: inherit; - border: none; - border-radius: 5px; -} - -.StandardOut-actionButton:hover { - background-color: @list-actn-bg-hov; - color: @list-actn-icn-hov; -} - -.StandardOut-actionButton--active { - background-color: @list-actn-bg-hov; - color: @list-actn-icn-hov; - - &:hover { - background-color: @default-link-hov; - } -} - -.StandardOut-actionButton + a { - margin-left: 15px; -} diff --git a/awx/ui/client/src/workflow-results/workflow-results.block.less b/awx/ui/client/src/workflow-results/workflow-results.block.less deleted file mode 100644 index 93b968c7d5c4..000000000000 --- a/awx/ui/client/src/workflow-results/workflow-results.block.less +++ /dev/null @@ -1,175 +0,0 @@ -@breakpoint-md: 1200px; -@breakpoint-sm: 700px; - -.WorkflowResults { - .OnePlusTwo-container(100%, @breakpoint-md); - - &.fullscreen { - .WorkflowResults-rightSide { - max-width: 100%; - } - } - - @media screen and (max-width: @breakpoint-md) { - display: block; - min-width: 400px; - } -} - -.WorkflowResults-leftSide { - max-width: 33%; - - .OnePlusTwo-left--panel(100%, @breakpoint-md); - height: ~"calc(100vh - 120px)"; - min-height: 350px; - display:flex; - .card { - width: 100%; - overflow: scroll; - } - @media screen and (width: @breakpoint-md - 1px){ - width: 100%; - } -} - -.WorkflowResults-rightSide { - .OnePlusTwo-right--panel(100%, @breakpoint-md); - height: ~"calc(100vh - 120px)"; - min-height: 350px; - min-width: 0; - display:flex; - .card { - width:100% - } - - @media (max-width: @breakpoint-md - 1px) { - padding-right: 15px; - } -} - -@media (max-width: @breakpoint-md) { - .WorkflowResults-rightSide { - height: inherit; - } -} - -.WorkflowResults-stdoutActionButton--active { - display: none; - visibility: hidden; - flex:none; - width:0px; - padding-right: 0px; -} - -.WorkflowResults-panelHeader { - display: flex; - height: 30px; -} - -.WorkflowResults-panelHeaderText { - color: @default-interface-txt; - flex: 1 0 auto; - font-size: 14px; - font-weight: bold; - margin-right: 10px; - text-transform: uppercase; -} - -.WorkflowResults-resultRow { - width: 100%; - display: flex; - padding-bottom: 10px; - flex-wrap: wrap; -} - -.WorkflowResults-resultRow--variables { - flex-direction: column; -} - -.WorkflowResults-resultRowLabel { - text-transform: uppercase; - color: @default-interface-txt; - font-size: 12px; - font-weight: normal!important; - width: 30%; - margin-right: 20px; - - @media screen and (max-width: @breakpoint-md) { - flex: 2.5 0 auto; - } -} - -.WorkflowResults-resultRowLabel--fullWidth { - width: 100%; - margin-right: 0px; -} - -.WorkflowResults-resultRowText { - width: ~"calc(70% - 20px)"; - flex: 1 0 auto; - text-transform: none; - word-wrap: break-word; -} - -.WorkflowResults-resultRowText--fullWidth { - width: 100%; -} - -.WorkflowResults-statusResultIcon { - padding-left: 0px; - padding-right: 10px; -} - -.WorkflowResults-panelRightTitle{ - flex-wrap: wrap; -} - -.WorkflowResults-panelRightTitleText{ - word-wrap: break-word; - word-break: break-all; - max-width: 100%; -} - -.WorkflowResults-badgeAndActionRow{ - display:flex; - flex: 1 0 auto; - justify-content: flex-end; - flex-wrap: wrap; - max-width: 100%; -} - -.WorkflowResults-badgeRow { - display: flex; - align-items: center; - margin-right: 5px; -} - -.WorkflowResults-badgeTitle{ - color: @default-interface-txt; - font-size: 14px; - margin-right: 10px; - font-weight: normal; - text-transform: uppercase; - margin-left: 20px; -} - -.WorkflowResults-extraVarsLabel { - font-size:14px!important; -} - -.WorkflowResults-seeMoreLess { - color: #337AB7; - margin: 4px 0px; - text-transform: uppercase; - padding: 2px 0px; - cursor: pointer; - border-radius: 5px; - font-size: 11px; -} - -.WorkflowResults-rightSide .WorkflowChart-svg { - background-color: @f6grey; - border: 1px solid @d7grey; - border-top: 0px; - border-bottom-right-radius: 5px; -} diff --git a/awx/ui/client/src/workflow-results/workflow-results.controller.js b/awx/ui/client/src/workflow-results/workflow-results.controller.js deleted file mode 100644 index d18f90f02f26..000000000000 --- a/awx/ui/client/src/workflow-results/workflow-results.controller.js +++ /dev/null @@ -1,311 +0,0 @@ -export default ['workflowData', 'workflowResultsService', 'workflowDataOptions', - 'jobLabels', 'workflowNodes', '$scope', - 'ParseVariableString', 'count', '$state', 'i18n', 'WorkflowChartService', '$filter', - 'moment', function(workflowData, workflowResultsService, - workflowDataOptions, jobLabels, workflowNodes, $scope, - ParseVariableString, count, $state, i18n, WorkflowChartService, $filter, - moment) { - let nodeRef; - var runTimeElapsedTimer = null; - - $scope.toggleKey = () => $scope.showKey = !$scope.showKey; - $scope.keyClassList = `{ 'Key-menuIcon--active': showKey }`; - - var getLinks = function() { - var getLink = function(key) { - if(key === 'schedule') { - if($scope.workflow.related.schedule) { - return '/#/templates/workflow_job_template/' + $scope.workflow.workflow_job_template + '/schedules' + $scope.workflow.related.schedule.split(/api\/v\d+\/schedules/)[1]; - } - else { - return null; - } - } - else { - if ($scope.workflow.related[key]) { - return '/#/' + $scope.workflow.related[key] - .split(/api\/v\d+\//)[1]; - } else { - return null; - } - } - }; - - $scope.workflow_template_link = '/#/templates/workflow_job_template/'+$scope.workflow.workflow_job_template; - $scope.created_by_link = getLink('created_by'); - $scope.scheduled_by_link = getLink('schedule'); - $scope.cloud_credential_link = getLink('cloud_credential'); - $scope.network_credential_link = getLink('network_credential'); - - $scope.launched_by_webhook_link = null; - if ($scope.workflow.launch_type === 'webhook') { - $scope.launched_by_webhook_link = $scope.workflow_template_link; - } - - if ($scope.workflow.summary_fields.inventory) { - if ($scope.workflow.summary_fields.inventory.kind === 'smart') { - $scope.inventory_link = '/#/inventories/smart/' + $scope.workflow.inventory; - } else { - $scope.inventory_link = '/#/inventories/inventory/' + $scope.workflow.inventory; - } - } - - $scope.strings = { - tooltips: { - RELAUNCH: i18n._('Relaunch using the same parameters'), - CANCEL: i18n._('Cancel'), - DELETE: i18n._('Delete'), - EDIT_USER: i18n._('Edit the user'), - EDIT_WORKFLOW: i18n._('Edit the workflow job template'), - EDIT_SLICE_TEMPLATE: i18n._('Edit the slice job template'), - EDIT_SCHEDULE: i18n._('Edit the schedule'), - EDIT_INVENTORY: i18n._('Edit the inventory'), - SOURCE_WORKFLOW_JOB: i18n._('View the source Workflow Job'), - TOGGLE_STDOUT_FULLSCREEN: i18n._('Expand Output'), - WEBHOOK_WORKFLOW_JOB_TEMPLATE: i18n._('View the webhook configuration on the workflow job template.'), - STATUS: '' // re-assigned elsewhere - }, - labels: { - TEMPLATE: i18n._('Template'), - LAUNCHED_BY: i18n._('Launched By'), - STARTED: i18n._('Started'), - FINISHED: i18n._('Finished'), - LABELS: i18n._('Labels'), - STATUS: i18n._('Status'), - SLICE_TEMPLATE: i18n._('Slice Job Template'), - JOB_EXPLANATION: i18n._('Explanation'), - SOURCE_WORKFLOW_JOB: i18n._('Source Workflow'), - INVENTORY: i18n._('Inventory'), - LIMIT: i18n._('Inventory Limit'), - SCM_BRANCH: i18n._('SCM Branch') - }, - details: { - HEADER: i18n._('DETAILS'), - NOT_FINISHED: i18n._('Not Finished'), - NOT_STARTED: i18n._('Not Started'), - SHOW_LESS: i18n._('Show Less'), - SHOW_MORE: i18n._('Show More'), - WEBHOOK: i18n._('Webhook'), - }, - results: { - TOTAL_NODES: i18n._('Total Nodes'), - ELAPSED: i18n._('Elapsed'), - }, - legend: { - ON_SUCCESS: i18n._('On Success'), - ON_FAILURE: i18n._('On Failure'), - ALWAYS: i18n._('Always'), - PROJECT_SYNC: i18n._('Project Sync'), - INVENTORY_SYNC: i18n._('Inventory Sync'), - WORKFLOW: i18n._('Workflow'), - KEY: i18n._('KEY'), - } - }; - }; - - var getLabelsAndTooltips = function() { - var getLabel = function(key) { - if ($scope.workflowOptions && $scope.workflowOptions[key]) { - return $scope.workflowOptions[key].choices - .filter(val => val[0] === $scope.workflow[key]) - .map(val => val[1])[0]; - } else { - return null; - } - }; - - $scope.workflow.statusLabel = i18n._(getLabel('status')); - $scope.strings.tooltips.STATUS = `${i18n._('Job')} ${$scope.workflow.statusLabel}`; - }; - - var updateWorkflowJobElapsedTimer = function(time) { - $scope.workflow.elapsed = time; - }; - - function init() { - // put initially resolved request data on scope - $scope.workflow = workflowData; - $scope.workflow_nodes = workflowNodes; - $scope.workflowOptions = workflowDataOptions.actions.GET; - $scope.labels = jobLabels; - $scope.showManualControls = false; - $scope.readOnly = true; - $scope.count = count.val; - - // Start elapsed time updater for job known to be running - if ($scope.workflow.started !== null && $scope.workflow.status === 'running') { - runTimeElapsedTimer = workflowResultsService.createOneSecondTimer($scope.workflow.started, updateWorkflowJobElapsedTimer); - } - - if(workflowData.summary_fields && workflowData.summary_fields.workflow_job_template && - workflowData.summary_fields.workflow_job_template.id){ - $scope.workflow_job_template_link = `/#/templates/workflow_job_template/${$scope.workflow.summary_fields.workflow_job_template.id}`; - } - - if(workflowData.summary_fields && workflowData.summary_fields.job_template && - workflowData.summary_fields.job_template.id){ - $scope.slice_job_template_link = `/#/templates/job_template/${$scope.workflow.summary_fields.job_template.id}`; - } - - if (_.get(workflowData, 'summary_fields.source_workflow_job.id')) { - $scope.source_workflow_job_link = `/#/workflows/${workflowData.summary_fields.source_workflow_job.id}`; - } - - if (workflowData.job_explanation) { - const limit = 150; - const more = workflowData.job_explanation; - const less = $filter('limitTo')(more, limit); - const showMore = false; - const hasMoreToShow = more.length > limit; - - const job_explanation = { - more: more, - less: less, - showMore: showMore, - hasMoreToShow: hasMoreToShow - }; - - $scope.job_explanation = job_explanation; - } - - // turn related api browser routes into front end routes - getLinks(); - - // use options labels to manipulate display of details - getLabelsAndTooltips(); - - // set up a read only code mirror for extra vars - $scope.variables = ParseVariableString($scope.workflow.extra_vars); - $scope.varsTooltip= i18n._('Read only view of extra variables added to the workflow.'); - $scope.varsLabel = i18n._('Extra Variables'); - $scope.varsName = 'extra_vars'; - - // Click binding for the expand/collapse button on the standard out log - $scope.stdoutFullScreen = false; - - let arrayOfLinksForChart = []; - let arrayOfNodesForChart = []; - - ({arrayOfNodesForChart, arrayOfLinksForChart, nodeRef} = WorkflowChartService.generateArraysOfNodesAndLinks(workflowNodes)); - - $scope.graphState = { arrayOfNodesForChart, arrayOfLinksForChart }; - } - - $scope.toggleStdoutFullscreen = function() { - - $scope.$broadcast('workflowDetailsResized'); - - $scope.stdoutFullScreen = !$scope.stdoutFullScreen; - - if ($scope.stdoutFullScreen === true) { - $scope.toggleStdoutFullscreenTooltip = i18n._("Collapse Output"); - } else if ($scope.stdoutFullScreen === false) { - $scope.toggleStdoutFullscreenTooltip = i18n._("Expand Output"); - } - }; - - $scope.deleteJob = function() { - workflowResultsService.deleteJob($scope.workflow); - }; - - $scope.cancelJob = function() { - workflowResultsService.cancelJob($scope.workflow); - }; - - $scope.relaunchJob = function() { - workflowResultsService.relaunchJob($scope); - }; - - $scope.toggleManualControls = function() { - $scope.showManualControls = !$scope.showManualControls; - }; - - $scope.lessLabels = false; - $scope.toggleLessLabels = function() { - if (!$scope.lessLabels) { - $('#workflow-results-labels').slideUp(200); - $scope.lessLabels = true; - } - else { - $('#workflow-results-labels').slideDown(200); - $scope.lessLabels = false; - } - }; - - $scope.panChart = function(direction) { - $scope.$broadcast('panWorkflowChart', { - direction: direction - }); - }; - - $scope.zoomChart = function(zoom) { - $scope.$broadcast('zoomWorkflowChart', { - zoom: zoom - }); - }; - - $scope.resetChart = function() { - $scope.$broadcast('resetWorkflowChart'); - }; - - $scope.zoomToFitChart = function() { - $scope.$broadcast('zoomToFitChart'); - }; - - $scope.workflowZoomed = function(zoom) { - $scope.$broadcast('workflowZoomed', { - zoom: zoom - }); - }; - - init(); - - // Processing of job-status messages from the websocket - $scope.$on(`ws-jobs`, function(e, data) { - // Update the workflow job's unified job: - if (parseInt(data.unified_job_id, 10) === parseInt($scope.workflow.id,10)) { - $scope.workflow.status = data.status; - // start internval counter for job that transitioned to running - if ($scope.workflow.status === 'running') { - runTimeElapsedTimer = workflowResultsService.createOneSecondTimer(moment(), updateWorkflowJobElapsedTimer); - } - - if(data.status === "successful" || data.status === "failed" || data.status === "canceled" || data.status === "error"){ - $state.go('.', null, { reload: true }); - } - } - // Update the jobs spawned by the workflow: - if(data.hasOwnProperty('workflow_job_id') && - parseInt(data.workflow_job_id, 10) === parseInt($scope.workflow.id,10)){ - - // This check ensures that the workflow status icon doesn't get stuck in - // the waiting state due to the UI missing the initial socket message. This - // can happen if the GET request on the workflow job returns "waiting" and - // the sockets aren't established yet so we miss the event that indicates - // the workflow job has moved into a running state. - if (!_.includes(['running', 'successful', 'failed', 'error', 'canceled'], $scope.workflow.status)){ - $scope.workflow.status = 'running'; - runTimeElapsedTimer = workflowResultsService.createOneSecondTimer(moment(), updateWorkflowJobElapsedTimer); - } - - $scope.graphState.arrayOfNodesForChart.forEach((node) => { - if (nodeRef[node.id] && nodeRef[node.id].originalNodeObject.id === data.workflow_node_id) { - node.job = { - id: data.unified_job_id, - status: data.status - }; - - $scope.$broadcast("refreshWorkflowChart"); - } - }); - - $scope.count = workflowResultsService - .getCounts($scope.graphState.arrayOfNodesForChart); - } - getLabelsAndTooltips(); - }); - - $scope.$on('$destroy', function() { - workflowResultsService.destroyTimer(runTimeElapsedTimer); - }); -}]; diff --git a/awx/ui/client/src/workflow-results/workflow-results.partial.html b/awx/ui/client/src/workflow-results/workflow-results.partial.html deleted file mode 100644 index d2c87893c189..000000000000 --- a/awx/ui/client/src/workflow-results/workflow-results.partial.html +++ /dev/null @@ -1,387 +0,0 @@ -
-
-
- - -
-
- - -
-
- {{ strings.details.HEADER }} -
- - -
- - - - - - - - - -
-
- - -
- - -
- -
- - {{ workflow.statusLabel }} -
-
- - -
- - -
- {{ job_explanation.less }} - ... - - {{ strings.details.SHOW_MORE }} - -
- -
- {{ job_explanation.more }} - - {{ strings.details.SHOW_LESS }} - -
-
- - -
- -
- {{ (workflow.started | longDate) || strings.details.NOT_STARTED }} -
-
- - -
- -
- {{ (workflow.finished | - longDate) || strings.details.NOT_FINISHED }} -
-
- - -
- - -
- - -
- -
- {{ workflow.limit }} -
-
- - -
- -
- {{ workflow.scm_branch }} -
-
- - -
- - -
- - -
- - -
- - -
- - -
- - - -
- - -
- - -
- - -
- - -
- - -
- - - - - - - - - -
- -
-
- - -
-
- - -
-
- - - {{ workflow.name }} -
-
- -
- -
- {{ strings.results.TOTAL_NODES }} -
- - {{ workflow_nodes.length || 0}} - - - -
- {{ strings.results.ELAPSED }} -
- - {{ workflow.elapsed * 1000 | duration: "hh:mm:ss"}} - -
- - -
- - - - -
-
-
- -
-
- - -
-
- -
- -
-
-
- - -
- -
-
-
diff --git a/awx/ui/client/src/workflow-results/workflow-results.route.js b/awx/ui/client/src/workflow-results/workflow-results.route.js deleted file mode 100644 index 149a2111290d..000000000000 --- a/awx/ui/client/src/workflow-results/workflow-results.route.js +++ /dev/null @@ -1,138 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import {templateUrl} from '../shared/template-url/template-url.factory'; - -import workflowResultsController from './workflow-results.controller'; - -export default { - name: 'workflowResults', - url: '/workflows/:id', - ncyBreadcrumb: { - parent: 'jobs', - label: '{{ workflow.id }} - {{ workflow.name }}' - }, - templateUrl: templateUrl('workflow-results/workflow-results'), - controller: workflowResultsController, - resolve: { - // the GET for the particular workflow - workflowData: ['Rest', 'GetBasePath', '$stateParams', '$q', '$state', 'Alert', 'i18n', function(Rest, GetBasePath, $stateParams, $q, $state, Alert, i18n) { - Rest.setUrl(GetBasePath('workflow_jobs') + $stateParams.id); - var defer = $q.defer(); - Rest.get() - .then(function(data) { - defer.resolve(data.data); - }, function(data) { - defer.reject(data); - - if (data.status === 404) { - Alert(i18n._('Job Not Found'), i18n._('Cannot find job.'), 'alert-info'); - } else if (data.status === 403) { - Alert(i18n._('Insufficient Permissions'), i18n._('You do not have permission to view this job.'), 'alert-info'); - } - - $state.go('jobs'); - }); - return defer.promise; - }], - // after the GET for the job, this helps us keep the status bar from - // flashing as rest data comes in. Provides the list of workflow nodes - workflowNodes: ['workflowData', 'Rest', '$q', function(workflowData, Rest, $q) { - var defer = $q.defer(); - Rest.setUrl(workflowData.related.workflow_nodes + '?order_by=id&page_size=200'); - Rest.get() - .then(({data}) => { - if(data.next) { - let allNodes = data.results; - let getNodes = function(nextUrl){ - // Get the workflow nodes - Rest.setUrl(nextUrl); - Rest.get() - .then(function(nextData) { - for(var i=0; i { - // TODO: handle this - //defer.resolve(data); - }); - return defer.promise; - }], - // after the GET for the workflow & it's nodes, this helps us keep the - // status bar from flashing as rest data comes in. If the workflow - // is finished and there's a playbook_on_stats event, go ahead and - // resolve the count so you don't get that flashing! - count: ['workflowData', 'workflowNodes', 'workflowResultsService', 'Rest', '$q', function(workflowData, workflowNodes, workflowResultsService, Rest, $q) { - var defer = $q.defer(); - defer.resolve({ - val: workflowResultsService - .getCounts(workflowNodes), - countFinished: true}); - return defer.promise; - }], - // GET for the particular jobs labels to be displayed in the - // left-hand pane - jobLabels: ['Rest', 'GetBasePath', '$stateParams', '$q', function(Rest, GetBasePath, $stateParams, $q) { - var getNext = function(data, arr, resolve) { - Rest.setUrl(data.next); - Rest.get() - .then(({data}) => { - if (data.next) { - getNext(data, arr.concat(data.results), resolve); - } else { - resolve.resolve(arr.concat(data.results) - .map(val => val.name)); - } - }); - }; - - var seeMoreResolve = $q.defer(); - - Rest.setUrl(GetBasePath('workflow_jobs') + $stateParams.id + '/labels/'); - Rest.get() - .then(({data}) => { - if (data.next) { - getNext(data, data.results, seeMoreResolve); - } else { - seeMoreResolve.resolve(data.results - .map(val => val.name)); - } - }); - - return seeMoreResolve.promise; - }], - // OPTIONS request for the workflow. Used to make things like the - // verbosity data in the left-hand pane prettier than just an - // integer - workflowDataOptions: ['Rest', 'GetBasePath', '$stateParams', '$q', function(Rest, GetBasePath, $stateParams, $q) { - Rest.setUrl(GetBasePath('workflow_jobs') + $stateParams.id); - var defer = $q.defer(); - Rest.options() - .then(function(data) { - defer.resolve(data.data); - }, function(data) { - defer.reject(data); - }); - return defer.promise; - }] - } - -}; diff --git a/awx/ui/client/src/workflow-results/workflow-results.service.js b/awx/ui/client/src/workflow-results/workflow-results.service.js deleted file mode 100644 index ea180d34b889..000000000000 --- a/awx/ui/client/src/workflow-results/workflow-results.service.js +++ /dev/null @@ -1,137 +0,0 @@ -/************************************************* -* Copyright (c) 2016 Ansible, Inc. -* -* All Rights Reserved -*************************************************/ - - -export default ['i18n', 'Prompt', '$filter', 'Wait', 'Rest', '$state', 'ProcessErrors', 'WorkflowJobModel', '$interval', 'moment', 'ComponentsStrings', function (i18n, Prompt, $filter, Wait, Rest, $state, ProcessErrors, WorkflowJob, $interval, moment, strings) { - var val = { - getCounts: function(workflowNodes){ - var nodeArr = []; - workflowNodes.forEach(node => { - if(node && node.summary_fields && node.summary_fields.job && node.summary_fields.job.status){ - nodeArr.push(node.summary_fields.job.status); - } else if (_.has(node, 'job.status')) { - nodeArr.push(node.job.status); - } - }); - // use the workflow nodes data populate above to get the count - var count = { - successful : _.filter(nodeArr, function(o){ - return o === "successful"; - }), - failed : _.filter(nodeArr, function(o){ - return o === "failed" || o === "error" || o === "canceled"; - }) - }; - - // turn the count into an actual count, rather than a list of - // statuses - Object.keys(count).forEach(key => { - count[key] = count[key].length; - }); - - return count; - }, - deleteJob: function(workflow) { - Prompt({ - hdr: i18n._('Delete Job'), - resourceName: `#${workflow.id} ` + $filter('sanitize')(workflow.name), - body: `
- ${i18n._('Are you sure you want to delete this workflow?')} -
`, - action: function() { - Wait('start'); - Rest.setUrl(workflow.url); - Rest.destroy() - .then(() => { - Wait('stop'); - $('#prompt-modal').modal('hide'); - $state.go('jobs'); - }) - .catch(({obj, status}) => { - Wait('stop'); - $('#prompt-modal').modal('hide'); - ProcessErrors(null, obj, status, null, { - hdr: i18n._('Error!'), - msg: `${i18n._('Could not delete job. Returned status: ' + status)}` - }); - }); - }, - actionText: i18n._('DELETE') - }); - }, - cancelJob: function(workflow) { - var doCancel = function() { - Rest.setUrl(workflow.url + 'cancel'); - Rest.post({}) - .then(() => { - Wait('stop'); - $('#prompt-modal').modal('hide'); - }) - .catch(({obj, status}) => { - Wait('stop'); - $('#prompt-modal').modal('hide'); - ProcessErrors(null, obj, status, null, { - hdr: i18n._('Error!'), - msg: `${i18n._('Could not cancel workflow. Returned status: ' + status)}` - }); - }); - }; - - Prompt({ - hdr: i18n._('Cancel Workflow'), - resourceName: `#${workflow.id} ${$filter('sanitize')(workflow.name)}`, - body: `
- ${i18n._('Are you sure you want to cancel this workflow job?')} -
`, - action: function() { - Wait('start'); - Rest.setUrl(workflow.url + 'cancel'); - Rest.get() - .then(({data}) => { - if (data.can_cancel === true) { - doCancel(); - } else { - $('#prompt-modal').modal('hide'); - ProcessErrors(null, data, null, null, { - hdr: i18n._('Error!'), - msg: `${i18n._('Job has completed. Unable to be canceled.')}` - }); - } - }); - }, - actionText: i18n._('PROCEED') - }); - }, - relaunchJob: function(scope) { - const workflowJob = new WorkflowJob(); - - workflowJob.postRelaunch({ - id: scope.workflow.id - }).then((launchRes) => { - $state.go('workflowResults', { id: launchRes.data.id }, { reload: true }); - }).catch(({ data, status, config }) => { - ProcessErrors(scope, data, status, null, { - hdr: strings.get('error.HEADER'), - msg: strings.get('error.CALL', { path: `${config.url}`, status }) - }); - }); - }, - createOneSecondTimer: function(startTime, fn) { - return $interval(function(){ - fn(moment().diff(moment(startTime), 'seconds')); - }, 1000); - }, - destroyTimer: function(timer) { - if (timer !== null) { - $interval.cancel(timer); - timer = null; - return true; - } - return false; - } - }; - return val; -}]; diff --git a/awx/ui/client/src/workflow-results/workflow-status-bar/main.js b/awx/ui/client/src/workflow-results/workflow-status-bar/main.js deleted file mode 100644 index 251258fc70f7..000000000000 --- a/awx/ui/client/src/workflow-results/workflow-status-bar/main.js +++ /dev/null @@ -1,11 +0,0 @@ -/************************************************* - * Copyright (c) 2015 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -import workflowStatusBar from './workflow-status-bar.directive'; - -export default - angular.module('workflowStatusBarDirective', []) - .directive('workflowStatusBar', workflowStatusBar); diff --git a/awx/ui/client/src/workflow-results/workflow-status-bar/workflow-status-bar.block.less b/awx/ui/client/src/workflow-results/workflow-status-bar/workflow-status-bar.block.less deleted file mode 100644 index b5a14a8605db..000000000000 --- a/awx/ui/client/src/workflow-results/workflow-status-bar/workflow-status-bar.block.less +++ /dev/null @@ -1,58 +0,0 @@ -.WorkflowStatusBar { - display: flex; - flex: 0 0 auto; - width: 100%; - margin-top: 10px; - margin-bottom: 15px; -} - -.WorkflowStatusBar-successful, -.WorkflowStatusBar-failed, -.WorkflowStatusBar-pending, -.WorkflowStatusBar-noData { - height: 15px; - border-top: 5px solid @default-bg; - border-bottom: 5px solid @default-bg; -} - -.WorkflowStatusBar-successful { - background-color: @default-succ; - display: flex; - flex: 0 0 auto; -} - -.WorkflowStatusBar-failed { - background-color: @default-err; - flex: 0 0 auto; -} - -.WorkflowStatusBar-pending { - background-color: @b7grey; - flex: 0 0 auto; -} - -.WorkflowStatusBar-noData { - background-color: @default-icon-hov; - flex: 1 0 auto; -} - -.WorkflowStatusBar-tooltipLabel { - text-transform: uppercase; - margin-right: 15px; -} - -.WorkflowStatusBar-tooltipBadge { - border-radius: 5px; -} - -.WorkflowStatusBar-tooltipBadge--successful { - background-color: @default-succ; -} - -.WorkflowStatusBar-tooltipBadge--failed { - background-color: @default-err; -} - -.WorkflowStatusBar-tooltipBadge--pending { - background-color: @b7grey; -} diff --git a/awx/ui/client/src/workflow-results/workflow-status-bar/workflow-status-bar.directive.js b/awx/ui/client/src/workflow-results/workflow-status-bar/workflow-status-bar.directive.js deleted file mode 100644 index 8ffe84beae65..000000000000 --- a/awx/ui/client/src/workflow-results/workflow-status-bar/workflow-status-bar.directive.js +++ /dev/null @@ -1,53 +0,0 @@ -/************************************************* - * Copyright (c) 2016 Ansible, Inc. - * - * All Rights Reserved - *************************************************/ - -export default [ 'templateUrl', 'OutputStrings', - function(templateUrl, OutputStrings) { - return { - scope: true, - templateUrl: templateUrl('workflow-results/workflow-status-bar/workflow-status-bar'), - restrict: 'E', - link: function(scope) { - scope.strings = OutputStrings; - // as count is changed by jobs coming in, - // update the workflow status bar - scope.$watch('count', function(val) { - if (val) { - Object.keys(val).forEach(key => { - // reposition the workflow status bar by setting - // the various flex values to the count of - // those jobs - $(`.WorkflowStatusBar-${key}`) - .css('flex', `${val[key]} 0 auto`); - - let tooltipLabel = key; - - switch(key) { - case 'successful': - tooltipLabel = scope.strings.get('workflow_status.SUCCESSFUL'); - break; - case 'failed': - tooltipLabel = scope.strings.get('workflow_status.FAILED'); - break; - } - - // set the tooltip to give how many jobs of - // each type - if (val[key] > 0) { - scope[`${key}CountTip`] = `${tooltipLabel}${val[key]}`; - } - }); - - // if there are any hosts that have finished, don't - // show default grey bar - scope.hostsFinished = (Object - .keys(val) - .filter(key => (val[key] > 0)).length > 0); - } - }); - } - }; -}]; diff --git a/awx/ui/client/src/workflow-results/workflow-status-bar/workflow-status-bar.partial.html b/awx/ui/client/src/workflow-results/workflow-status-bar/workflow-status-bar.partial.html deleted file mode 100644 index 0a7c2f07f3af..000000000000 --- a/awx/ui/client/src/workflow-results/workflow-status-bar/workflow-status-bar.partial.html +++ /dev/null @@ -1,18 +0,0 @@ -
-
-
-
-
-
-
-
\ No newline at end of file diff --git a/awx/ui/conf.py b/awx/ui/conf.py index c8f6d3491b2b..3148aec6ee7f 100644 --- a/awx/ui/conf.py +++ b/awx/ui/conf.py @@ -31,8 +31,8 @@ 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, as ' - 'custom HTML or other markup languages are not supported.'), + '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', ) @@ -71,3 +71,4 @@ category=_('UI'), category_slug='ui', ) + diff --git a/awx/ui/context_processors.py b/awx/ui/context_processors.py index 38976a6eaac1..87c071c2850a 100644 --- a/awx/ui/context_processors.py +++ b/awx/ui/context_processors.py @@ -1,26 +1,8 @@ -# Copyright (c) 2015 Ansible, Inc. -# All Rights Reserved. +import base64 +import os -# Django -from django.conf import settings as django_settings -# Ansible Tower -from awx.main.utils import get_awx_version - -def settings(request): - return { - 'settings': django_settings, - } - -def version(request): - context = getattr(request, 'parser_context', {}) +def csp(request): return { - 'version': get_awx_version(), - 'tower_version': get_awx_version(), - 'short_tower_version': get_awx_version().split('-')[0], - 'deprecated': getattr( - context.get('view'), - 'deprecated', - False - ) + 'csp_nonce': base64.encodebytes(os.urandom(32)).decode().rstrip(), } diff --git a/awx/ui/fields.py b/awx/ui/fields.py index 32e08a12c159..4d96165d4d8b 100644 --- a/awx/ui/fields.py +++ b/awx/ui/fields.py @@ -42,3 +42,4 @@ def to_internal_value(self, data): except (TypeError, binascii.Error): self.fail('invalid_data') return data + diff --git a/awx/ui/grunt-tasks/jshint.js b/awx/ui/grunt-tasks/jshint.js deleted file mode 100644 index 0ed7193e6eab..000000000000 --- a/awx/ui/grunt-tasks/jshint.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = { - source: { - src: ['client/src/**/*.js', '*conf.js', '*.config.js', 'Gruntfile.js'], - options: { - reporter: require('jshint-stylish'), - jshintrc: true - } - }, - -}; diff --git a/awx/ui/grunt-tasks/nggettext_compile.js b/awx/ui/grunt-tasks/nggettext_compile.js deleted file mode 100644 index 1fb250a6d438..000000000000 --- a/awx/ui/grunt-tasks/nggettext_compile.js +++ /dev/null @@ -1,16 +0,0 @@ -module.exports = { - all: { - options: { - format: 'json' - }, - files: [{ - expand: true, - dot: true, - dest: 'client/languages', - cwd: 'po', - ext: '.json', - src: ['*.po'] - }] - } -}; - diff --git a/awx/ui/grunt-tasks/nggettext_extract.js b/awx/ui/grunt-tasks/nggettext_extract.js deleted file mode 100644 index 8b07d3669323..000000000000 --- a/awx/ui/grunt-tasks/nggettext_extract.js +++ /dev/null @@ -1,23 +0,0 @@ -let source = [ - 'client/features/**/*.js', - 'client/features/**/*.html', - 'client/lib/**/*.js', - 'client/lib/**/*.html', - 'client/src/**/*.js', - 'client/src/**/*.html', - 'client/*.ejs' -]; - -module.exports = { - all: { - options: { - markerNames: ['_', 'N_'], - moduleName: 't', - moduleMethodString: 's', - moduleMethodPlural: 'p' - }, - files: { - 'po/ansible-tower-ui.pot': source - } - } -}; diff --git a/awx/ui/models.py b/awx/ui/models.py deleted file mode 100644 index 33cc349cf4aa..000000000000 --- a/awx/ui/models.py +++ /dev/null @@ -1,4 +0,0 @@ -# Copyright (c) 2015 Ansible, Inc. -# All Rights Reserved. - -# Empty models file. diff --git a/awx/ui/package-lock.json b/awx/ui/package-lock.json deleted file mode 100644 index 6719e512e403..000000000000 --- a/awx/ui/package-lock.json +++ /dev/null @@ -1,14682 +0,0 @@ -{ - "name": "awx", - "version": "1.0.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@uirouter/angularjs": { - "version": "1.0.18", - "resolved": "https://registry.npmjs.org/@uirouter/angularjs/-/angularjs-1.0.18.tgz", - "integrity": "sha512-tswhwMMBDnbGOZnnCVpnA0pbd7dXkBck1HO0WY7fw8GO3dKbWAAc/rL0479dLypR89UDDGym5leTvZCLW4cJnA==", - "requires": { - "@uirouter/core": "5.0.19" - } - }, - "@uirouter/core": { - "version": "5.0.19", - "resolved": "https://registry.npmjs.org/@uirouter/core/-/core-5.0.19.tgz", - "integrity": "sha512-wow+CKRThUAQkiTLNQCBsKQIU3NbH8GGH/w/TrcjKdvkZQA2jQB9QSqmmZxj7XNoZXY7QVcSSc4DWmxuSeAWmQ==" - }, - "a-sync-waterfall": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.0.tgz", - "integrity": "sha1-OOgxnXk3niRiiEW1O5ZyKyng5Hw=", - "dev": true - }, - "abbrev": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", - "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", - "dev": true - }, - "accepts": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", - "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=", - "dev": true, - "requires": { - "mime-types": "~2.1.11", - "negotiator": "0.6.1" - } - }, - "acorn": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.6.2.tgz", - "integrity": "sha512-zUzo1E5dI2Ey8+82egfnttyMlMZ2y0D8xOCO3PNPPlYXpl8NZvF6Qk9L9BEtJs+43FqEmfBViDqc5d1ckRDguw==", - "dev": true - }, - "acorn-dynamic-import": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz", - "integrity": "sha1-x1K9IQvvZ5UBtsbLf8hPj0cVjMQ=", - "dev": true, - "requires": { - "acorn": "^4.0.3" - }, - "dependencies": { - "acorn": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", - "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", - "dev": true - } - } - }, - "acorn-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", - "dev": true, - "requires": { - "acorn": "^3.0.4" - }, - "dependencies": { - "acorn": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", - "dev": true - } - } - }, - "after": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", - "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", - "dev": true - }, - "agent-base": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-2.1.1.tgz", - "integrity": "sha1-1t4Q1a9hMtW9aSQn1G/FOFOQlMc=", - "dev": true, - "requires": { - "extend": "~3.0.0", - "semver": "~5.0.1" - }, - "dependencies": { - "semver": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.0.3.tgz", - "integrity": "sha1-d0Zt5YnNXTyV8TiqeLxWmjy10no=", - "dev": true - } - } - }, - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "dev": true, - "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - } - }, - "ajv-keywords": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", - "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", - "dev": true - }, - "align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "dev": true, - "requires": { - "kind-of": "^3.0.2", - "longest": "^1.0.1", - "repeat-string": "^1.5.2" - } - }, - "almond": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/almond/-/almond-0.3.3.tgz", - "integrity": "sha1-oOfJWsdiTWQXtElLHmi/9pMWiiA=" - }, - "alphanum-sort": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", - "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", - "dev": true - }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" - }, - "angular": { - "version": "1.7.9", - "resolved": "https://registry.npmjs.org/angular/-/angular-1.7.9.tgz", - "integrity": "sha512-5se7ZpcOtu0MBFlzGv5dsM1quQDoDeUTwZrWjGtTNA7O88cD8TEk5IEKCTDa3uECV9XnvKREVUr7du1ACiWGFQ==" - }, - "angular-breadcrumb": { - "version": "git+https://git@github.com/ansible/angular-breadcrumb.git#6c2b1ad45ad5fbe7adf39af1ef3b294ca8e207a9", - "from": "git+https://git@github.com/ansible/angular-breadcrumb.git#0.4.1" - }, - "angular-codemirror": { - "version": "git+https://git@github.com/ansible/angular-codemirror.git#447f071eff8f6fde7b5ec769c57c7dc98a014fdf", - "from": "git+https://git@github.com/ansible/angular-codemirror.git#v1.1.2", - "requires": { - "angular": "~1.6.6", - "codemirror": "^5.17.0", - "jquery": "^3.2.1" - }, - "dependencies": { - "angular": { - "version": "1.6.10", - "resolved": "https://registry.npmjs.org/angular/-/angular-1.6.10.tgz", - "integrity": "sha512-PCZ5/hVdvPQiYyH0VwsPjrErPHRcITnaXxhksceOXgtJeesKHLA7KDu4X/yvcAi+1zdGgGF+9pDxkJvghXI9Wg==" - }, - "jquery": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz", - "integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==" - } - } - }, - "angular-cookies": { - "version": "1.7.9", - "resolved": "https://registry.npmjs.org/angular-cookies/-/angular-cookies-1.7.9.tgz", - "integrity": "sha512-3eRq/aPrtCZKDWQnc3nW3sFoMbLiHkCkyDF2O9u7VXnqvVsUPaipk5R1ZqahgcSQHQrN/F5IU4T4nrz52qAZmA==" - }, - "angular-drag-and-drop-lists": { - "version": "git+https://git@github.com/ansible/angular-drag-and-drop-lists.git#cceda38b836402ed4ce77fc287c23c8d02e950f6", - "from": "git+https://git@github.com/ansible/angular-drag-and-drop-lists.git#v1.4.1" - }, - "angular-duration-format": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/angular-duration-format/-/angular-duration-format-1.0.1.tgz", - "integrity": "sha1-scbu5u7/3ljrQmRM8WU+VXTP3xw=" - }, - "angular-filters": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/angular-filters/-/angular-filters-1.1.2.tgz", - "integrity": "sha1-btYFgIEsmQmnzHR2dyfR2ZOkVjM=" - }, - "angular-gettext": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/angular-gettext/-/angular-gettext-2.3.11.tgz", - "integrity": "sha512-CcoK8IXQLjdu5DzU2rJ38CNNfBPIrEZoPC6pgGf/q//upt6O+WpiL0DawEzy7gw5i3Em7ANh+BrXRoR1tgrFEQ==" - }, - "angular-gettext-tools": { - "version": "2.3.18", - "resolved": "https://registry.npmjs.org/angular-gettext-tools/-/angular-gettext-tools-2.3.18.tgz", - "integrity": "sha512-FR3w2dY09bEwm7REzIHPDytg0TuL2ps94/f/hIjGQXCkTmp+hIDnVAuFEX55neQ1xaDZynBs+jG9vxutKLkkiQ==", - "dev": true, - "requires": { - "babylon": "^6.11.4", - "binary-search": "^1.2.0", - "cheerio": "~0.19.0", - "lodash": "^4.17.5", - "pofile": "~1.0.0", - "typescript": "~2.3.2", - "typescript-eslint-parser": "^3.0.0" - } - }, - "angular-mocks": { - "version": "1.7.9", - "resolved": "https://registry.npmjs.org/angular-mocks/-/angular-mocks-1.7.9.tgz", - "integrity": "sha512-LQRqqiV3sZ7NTHBnNmLT0bXtE5e81t97+hkJ56oU0k3dqKv1s6F+nBWRlOVzqHWPGFOiPS8ZJVdrS8DFzHyNIA==", - "dev": true - }, - "angular-moment": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/angular-moment/-/angular-moment-1.3.0.tgz", - "integrity": "sha512-KG8rvO9MoaBLwtGnxTeUveSyNtrL+RNgGl1zqWN36+HDCCVGk2DGWOzqKWB6o+eTTbO3Opn4hupWKIElc8XETA==", - "requires": { - "moment": ">=2.8.0 <3.0.0" - } - }, - "angular-mousewheel": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/angular-mousewheel/-/angular-mousewheel-1.0.5.tgz", - "integrity": "sha1-/fA45SnT2K91wO6IFfu717gAdUo=", - "requires": { - "angular": "^1.0.6", - "hamsterjs": "^1.0.2" - } - }, - "angular-sanitize": { - "version": "1.7.9", - "resolved": "https://registry.npmjs.org/angular-sanitize/-/angular-sanitize-1.7.9.tgz", - "integrity": "sha512-nB/xe7JQWF9nLvhHommAICQ3eWrfRETo0EVGFESi952CDzDa+GAJ/2BFBNw44QqQPxj1Xua/uYKrbLsOGWZdbQ==" - }, - "angular-scheduler": { - "version": "git+https://git@github.com/ansible/angular-scheduler.git#a519c52312cb4430a59a8d58e01d3eac3fe5018a", - "from": "git+https://git@github.com/ansible/angular-scheduler.git#v0.4.1", - "requires": { - "angular": "~1.7.2", - "angular-tz-extensions": "github:ansible/angular-tz-extensions#fc60660f43ee9ff84da94ca71ab27ef0c20fd77d", - "jquery": "*", - "jquery-ui": "*", - "lodash": "~3.8.0", - "moment": "^2.10.2", - "rrule": "github:jkbrzt/rrule#4ff63b2f8524fd6d5ba6e80db770953b5cd08a0c" - }, - "dependencies": { - "angular-tz-extensions": { - "version": "github:ansible/angular-tz-extensions#fc60660f43ee9ff84da94ca71ab27ef0c20fd77d", - "from": "github:ansible/angular-tz-extensions", - "requires": { - "angular": "~1.7.2", - "angular-filters": "^1.1.2", - "jquery": "^3.1.0", - "jstimezonedetect": "1.0.5", - "timezone-js": "github:ansible/timezone-js#6937de14ce0c193961538bb5b3b12b7ef62a358f" - } - }, - "lodash": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.8.0.tgz", - "integrity": "sha1-N265i9zZOCqTZcM8TLglDeEyW5E=" - }, - "rrule": { - "version": "github:jkbrzt/rrule#4ff63b2f8524fd6d5ba6e80db770953b5cd08a0c", - "from": "github:jkbrzt/rrule#4ff63b2f8524fd6d5ba6e80db770953b5cd08a0c" - } - } - }, - "angular-tz-extensions": { - "version": "git+https://git@github.com/ansible/angular-tz-extensions.git#9cabb05d58079092bfb29ccae721b35b46f28af6", - "from": "git+https://git@github.com/ansible/angular-tz-extensions.git#v0.5.2", - "requires": { - "angular": "~1.6.6", - "angular-filters": "^1.1.2", - "jquery": "^3.1.0", - "jstimezonedetect": "1.0.5", - "timezone-js": "github:ansible/timezone-js#6937de14ce0c193961538bb5b3b12b7ef62a358f" - }, - "dependencies": { - "angular": { - "version": "1.6.10", - "resolved": "https://registry.npmjs.org/angular/-/angular-1.6.10.tgz", - "integrity": "sha512-PCZ5/hVdvPQiYyH0VwsPjrErPHRcITnaXxhksceOXgtJeesKHLA7KDu4X/yvcAi+1zdGgGF+9pDxkJvghXI9Wg==" - }, - "jquery": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz", - "integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==" - }, - "timezone-js": { - "version": "github:ansible/timezone-js#6937de14ce0c193961538bb5b3b12b7ef62a358f", - "from": "github:ansible/timezone-js#6937de14ce0c193961538bb5b3b12b7ef62a358f" - } - } - }, - "angular-xeditable": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/angular-xeditable/-/angular-xeditable-0.8.1.tgz", - "integrity": "sha512-L4AkM18NDKL1M/vumZxSfMm+IGO/tWDIDjy0tk9qo5TO4zGH4R8Z1g+IelewUPR3KvLPhkXUcZsIY+wjMnOTaw==", - "requires": { - "angular": "~1.x" - } - }, - "ansi-escapes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", - "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", - "dev": true - }, - "ansi-html": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", - "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", - "dev": true - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "ansi-to-html": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/ansi-to-html/-/ansi-to-html-0.6.4.tgz", - "integrity": "sha512-XuUGfj3zOAg3/NCU7Oyf9PaCyFuDVj8dzMqezMycPxo5U52atXt+R4L/zW7ETNA2GTjyj/KGBVEFI8sgPWUu2w==", - "requires": { - "entities": "^1.1.1" - } - }, - "anymatch": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", - "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", - "dev": true, - "requires": { - "micromatch": "^2.1.5", - "normalize-path": "^2.0.0" - }, - "dependencies": { - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1" - } - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" - } - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, - "requires": { - "is-posix-bracket": "^0.1.0" - } - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" - } - } - } - }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true - }, - "archiver": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-2.1.1.tgz", - "integrity": "sha1-/2YrSnggFJSj7lRNOjP+dJZQnrw=", - "dev": true, - "requires": { - "archiver-utils": "^1.3.0", - "async": "^2.0.0", - "buffer-crc32": "^0.2.1", - "glob": "^7.0.0", - "lodash": "^4.8.0", - "readable-stream": "^2.0.0", - "tar-stream": "^1.5.0", - "zip-stream": "^1.2.0" - }, - "dependencies": { - "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", - "dev": true - } - } - }, - "archiver-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-1.3.0.tgz", - "integrity": "sha1-5QtMCccL89aA4y/xt5lOn52JUXQ=", - "dev": true, - "requires": { - "glob": "^7.0.0", - "graceful-fs": "^4.1.0", - "lazystream": "^1.0.0", - "lodash": "^4.8.0", - "normalize-path": "^2.0.0", - "readable-stream": "^2.0.0" - }, - "dependencies": { - "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", - "dev": true - } - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "requires": { - "sprintf-js": "~1.0.2" - }, - "dependencies": { - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" - } - } - }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true - }, - "array-differ": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", - "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", - "dev": true - }, - "array-find": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-find/-/array-find-1.0.0.tgz", - "integrity": "sha1-bI4obRHtdoMn+OYuzuhzU8o+eLg=", - "dev": true - }, - "array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", - "dev": true - }, - "array-flatten": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.1.tgz", - "integrity": "sha1-Qmu52oQJDBg42BLIFQryCoMx4pY=", - "dev": true - }, - "array-includes": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", - "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.7.0" - } - }, - "array-slice": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", - "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", - "dev": true - }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true, - "requires": { - "array-uniq": "^1.0.1" - } - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "arraybuffer.slice": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz", - "integrity": "sha1-8zshWfBTKj8xB6JywMz70a0peco=", - "dev": true - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true - }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", - "dev": true - }, - "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", - "dev": true - }, - "asn1.js": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", - "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", - "dev": true, - "requires": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "assert": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", - "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", - "dev": true, - "requires": { - "util": "0.10.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "dev": true - }, - "util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "dev": true, - "requires": { - "inherits": "2.0.1" - } - } - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - }, - "assertion-error": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.0.tgz", - "integrity": "sha1-x/hUOP3UZrx8oWq5DIFRN5el0js=", - "dev": true - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true - }, - "ast-types": { - "version": "0.9.6", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.9.6.tgz", - "integrity": "sha1-ECyenpAF0+fjgpvwxPok7oYu6bk=", - "dev": true - }, - "async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", - "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", - "dev": true, - "requires": { - "lodash": "^4.17.10" - }, - "dependencies": { - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - } - } - }, - "async-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", - "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", - "dev": true - }, - "async-limiter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "atob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.1.tgz", - "integrity": "sha1-ri1acpR38onWDdf5amMUoi3Wwio=", - "dev": true - }, - "autoprefixer": { - "version": "6.7.7", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-6.7.7.tgz", - "integrity": "sha1-Hb0cg1ZY41zj+ZhAmdsAWFx4IBQ=", - "dev": true, - "requires": { - "browserslist": "^1.7.6", - "caniuse-db": "^1.0.30000634", - "normalize-range": "^0.1.2", - "num2fraction": "^1.2.2", - "postcss": "^5.2.16", - "postcss-value-parser": "^3.2.3" - }, - "dependencies": { - "browserslist": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", - "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", - "dev": true, - "requires": { - "caniuse-db": "^1.0.30000639", - "electron-to-chromium": "^1.2.7" - } - } - } - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true - }, - "aws4": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", - "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==", - "dev": true - }, - "axios": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.16.2.tgz", - "integrity": "sha1-uk+S8XFn37q0CYN4VFS5rBScPG0=", - "dev": true, - "requires": { - "follow-redirects": "^1.2.3", - "is-buffer": "^1.1.5" - } - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - } - }, - "babel-core": { - "version": "6.26.3", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", - "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-generator": "^6.26.0", - "babel-helpers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-register": "^6.26.0", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "convert-source-map": "^1.5.1", - "debug": "^2.6.9", - "json5": "^0.5.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.4", - "path-is-absolute": "^1.0.1", - "private": "^0.1.8", - "slash": "^1.0.0", - "source-map": "^0.5.7" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "babel-generator": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", - "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", - "dev": true, - "requires": { - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "detect-indent": "^4.0.0", - "jsesc": "^1.3.0", - "lodash": "^4.17.4", - "source-map": "^0.5.7", - "trim-right": "^1.0.1" - }, - "dependencies": { - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "babel-helper-builder-binary-assignment-operator-visitor": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz", - "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=", - "dev": true, - "requires": { - "babel-helper-explode-assignable-expression": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-helper-call-delegate": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", - "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", - "dev": true, - "requires": { - "babel-helper-hoist-variables": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-helper-define-map": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz", - "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=", - "dev": true, - "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "lodash": "^4.17.4" - }, - "dependencies": { - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - } - } - }, - "babel-helper-explode-assignable-expression": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz", - "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-helper-function-name": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", - "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", - "dev": true, - "requires": { - "babel-helper-get-function-arity": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-helper-get-function-arity": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", - "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-helper-hoist-variables": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", - "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-helper-optimise-call-expression": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", - "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-helper-regex": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz", - "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "lodash": "^4.17.4" - }, - "dependencies": { - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - } - } - }, - "babel-helper-remap-async-to-generator": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz", - "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=", - "dev": true, - "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-helper-replace-supers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", - "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", - "dev": true, - "requires": { - "babel-helper-optimise-call-expression": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-helpers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", - "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "babel-istanbul": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/babel-istanbul/-/babel-istanbul-0.12.2.tgz", - "integrity": "sha1-5yPwfJokMtiAVVILwi519cI5Fhw=", - "dev": true, - "requires": { - "abbrev": "1.0.x", - "async": "1.x", - "escodegen": "1.8.x", - "esprima": "2.7.x", - "handlebars": "^4.0.1", - "js-yaml": "3.x", - "mkdirp": "0.5.x", - "multi-glob": "^1.0.1", - "nopt": "3.x", - "object-assign": "^4.0.1", - "once": "1.x", - "resolve": "^1.1.0", - "source-map": "0.4.x", - "supports-color": "3.1.x", - "which": "1.2.x", - "wordwrap": "1.0.x" - }, - "dependencies": { - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - }, - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "source-map": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", - "dev": true, - "requires": { - "amdefine": ">=0.0.4" - } - }, - "supports-color": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", - "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - } - } - }, - "babel-loader": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-7.1.4.tgz", - "integrity": "sha512-/hbyEvPzBJuGpk9o80R0ZyTej6heEOr59GoEUtn8qFKbnx4cJm9FWES6J/iv644sYgrtVw9JJQkjaLW/bqb5gw==", - "dev": true, - "requires": { - "find-cache-dir": "^1.0.0", - "loader-utils": "^1.0.2", - "mkdirp": "^0.5.1" - }, - "dependencies": { - "loader-utils": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", - "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", - "dev": true, - "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0" - } - } - } - }, - "babel-messages": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-check-es2015-constants": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", - "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-istanbul": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz", - "integrity": "sha512-PWP9FQ1AhZhS01T/4qLSKoHGY/xvkZdVBGlKM/HuxxS3+sC66HhTNR7+MpbO/so/cz/wY94MeSWJuP1hXIPfwQ==", - "dev": true, - "requires": { - "babel-plugin-syntax-object-rest-spread": "^6.13.0", - "find-up": "^2.1.0", - "istanbul-lib-instrument": "^1.10.1", - "test-exclude": "^4.2.1" - } - }, - "babel-plugin-syntax-async-functions": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", - "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=", - "dev": true - }, - "babel-plugin-syntax-exponentiation-operator": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", - "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=", - "dev": true - }, - "babel-plugin-syntax-object-rest-spread": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", - "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=", - "dev": true - }, - "babel-plugin-syntax-trailing-function-commas": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz", - "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=", - "dev": true - }, - "babel-plugin-transform-async-to-generator": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz", - "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=", - "dev": true, - "requires": { - "babel-helper-remap-async-to-generator": "^6.24.1", - "babel-plugin-syntax-async-functions": "^6.8.0", - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-arrow-functions": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", - "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-block-scoped-functions": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", - "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-block-scoping": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz", - "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "lodash": "^4.17.4" - }, - "dependencies": { - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - } - } - }, - "babel-plugin-transform-es2015-classes": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", - "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", - "dev": true, - "requires": { - "babel-helper-define-map": "^6.24.1", - "babel-helper-function-name": "^6.24.1", - "babel-helper-optimise-call-expression": "^6.24.1", - "babel-helper-replace-supers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-computed-properties": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", - "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-destructuring": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", - "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-duplicate-keys": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz", - "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-for-of": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", - "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-function-name": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", - "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", - "dev": true, - "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-literals": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", - "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-modules-amd": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz", - "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=", - "dev": true, - "requires": { - "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-modules-commonjs": { - "version": "6.26.2", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz", - "integrity": "sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==", - "dev": true, - "requires": { - "babel-plugin-transform-strict-mode": "^6.24.1", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-types": "^6.26.0" - } - }, - "babel-plugin-transform-es2015-modules-systemjs": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz", - "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", - "dev": true, - "requires": { - "babel-helper-hoist-variables": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-modules-umd": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz", - "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=", - "dev": true, - "requires": { - "babel-plugin-transform-es2015-modules-amd": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-object-super": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", - "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", - "dev": true, - "requires": { - "babel-helper-replace-supers": "^6.24.1", - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-parameters": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", - "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", - "dev": true, - "requires": { - "babel-helper-call-delegate": "^6.24.1", - "babel-helper-get-function-arity": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-shorthand-properties": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", - "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-spread": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", - "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-sticky-regex": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", - "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", - "dev": true, - "requires": { - "babel-helper-regex": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-template-literals": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", - "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-typeof-symbol": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz", - "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-unicode-regex": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", - "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", - "dev": true, - "requires": { - "babel-helper-regex": "^6.24.1", - "babel-runtime": "^6.22.0", - "regexpu-core": "^2.0.0" - } - }, - "babel-plugin-transform-exponentiation-operator": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz", - "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=", - "dev": true, - "requires": { - "babel-helper-builder-binary-assignment-operator-visitor": "^6.24.1", - "babel-plugin-syntax-exponentiation-operator": "^6.8.0", - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-regenerator": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz", - "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=", - "dev": true, - "requires": { - "regenerator-transform": "^0.10.0" - } - }, - "babel-plugin-transform-strict-mode": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", - "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-polyfill": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", - "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", - "requires": { - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "regenerator-runtime": "^0.10.5" - } - }, - "babel-preset-env": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.7.0.tgz", - "integrity": "sha512-9OR2afuKDneX2/q2EurSftUYM0xGu4O2D9adAhVfADDhrYDaxXV0rBbevVYoY9n6nyX1PmQW/0jtpJvUNr9CHg==", - "dev": true, - "requires": { - "babel-plugin-check-es2015-constants": "^6.22.0", - "babel-plugin-syntax-trailing-function-commas": "^6.22.0", - "babel-plugin-transform-async-to-generator": "^6.22.0", - "babel-plugin-transform-es2015-arrow-functions": "^6.22.0", - "babel-plugin-transform-es2015-block-scoped-functions": "^6.22.0", - "babel-plugin-transform-es2015-block-scoping": "^6.23.0", - "babel-plugin-transform-es2015-classes": "^6.23.0", - "babel-plugin-transform-es2015-computed-properties": "^6.22.0", - "babel-plugin-transform-es2015-destructuring": "^6.23.0", - "babel-plugin-transform-es2015-duplicate-keys": "^6.22.0", - "babel-plugin-transform-es2015-for-of": "^6.23.0", - "babel-plugin-transform-es2015-function-name": "^6.22.0", - "babel-plugin-transform-es2015-literals": "^6.22.0", - "babel-plugin-transform-es2015-modules-amd": "^6.22.0", - "babel-plugin-transform-es2015-modules-commonjs": "^6.23.0", - "babel-plugin-transform-es2015-modules-systemjs": "^6.23.0", - "babel-plugin-transform-es2015-modules-umd": "^6.23.0", - "babel-plugin-transform-es2015-object-super": "^6.22.0", - "babel-plugin-transform-es2015-parameters": "^6.23.0", - "babel-plugin-transform-es2015-shorthand-properties": "^6.22.0", - "babel-plugin-transform-es2015-spread": "^6.22.0", - "babel-plugin-transform-es2015-sticky-regex": "^6.22.0", - "babel-plugin-transform-es2015-template-literals": "^6.22.0", - "babel-plugin-transform-es2015-typeof-symbol": "^6.23.0", - "babel-plugin-transform-es2015-unicode-regex": "^6.22.0", - "babel-plugin-transform-exponentiation-operator": "^6.22.0", - "babel-plugin-transform-regenerator": "^6.22.0", - "browserslist": "^3.2.6", - "invariant": "^2.2.2", - "semver": "^5.3.0" - } - }, - "babel-register": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", - "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", - "dev": true, - "requires": { - "babel-core": "^6.26.0", - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "home-or-tmp": "^2.0.0", - "lodash": "^4.17.4", - "mkdirp": "^0.5.1", - "source-map-support": "^0.4.15" - }, - "dependencies": { - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - } - } - }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - }, - "dependencies": { - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" - } - } - }, - "babel-template": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", - "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "lodash": "^4.17.4" - }, - "dependencies": { - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - } - } - }, - "babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "debug": "^2.6.8", - "globals": "^9.18.0", - "invariant": "^2.2.2", - "lodash": "^4.17.4" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - } - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - }, - "dependencies": { - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - } - } - }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true - }, - "backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", - "dev": true - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "base64-arraybuffer": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", - "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=", - "dev": true - }, - "base64-js": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", - "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", - "dev": true - }, - "base64id": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz", - "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=", - "dev": true - }, - "batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", - "dev": true, - "optional": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "beeper": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", - "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=", - "dev": true - }, - "better-assert": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", - "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", - "dev": true, - "requires": { - "callsite": "1.0.0" - } - }, - "big.js": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", - "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==" - }, - "binary-extensions": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", - "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", - "dev": true - }, - "binary-search": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/binary-search/-/binary-search-1.3.3.tgz", - "integrity": "sha512-T/jKp3vw1JI+6KQgsyT5R6CcRhMtxlHojeKrA5gX5WG50BQaoujRfoJJKMkuokNuZ0w2S+1wHufEWzw6Qhj30Q==", - "dev": true - }, - "bl": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", - "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", - "dev": true, - "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - } - }, - "blob": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz", - "integrity": "sha1-vPEwUspURj8w+fx+lbmkdjCpSSE=", - "dev": true - }, - "bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", - "dev": true - }, - "bn.js": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", - "dev": true - }, - "body-parser": { - "version": "1.18.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", - "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", - "dev": true, - "requires": { - "bytes": "3.0.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "~1.6.3", - "iconv-lite": "0.4.23", - "on-finished": "~2.3.0", - "qs": "6.5.2", - "raw-body": "2.3.3", - "type-is": "~1.6.16" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "bonjour": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", - "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", - "dev": true, - "requires": { - "array-flatten": "^2.1.0", - "deep-equal": "^1.0.1", - "dns-equal": "^1.0.0", - "dns-txt": "^2.0.2", - "multicast-dns": "^6.0.1", - "multicast-dns-service-types": "^1.1.0" - } - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", - "dev": true - }, - "boom": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", - "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", - "dev": true, - "requires": { - "hoek": "2.x.x" - } - }, - "bootstrap": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.3.1.tgz", - "integrity": "sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag==" - }, - "bootstrap-datepicker": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/bootstrap-datepicker/-/bootstrap-datepicker-1.8.0.tgz", - "integrity": "sha512-213St/G8KT3mjs4qu4qwww74KWysMaIeqgq5OhrboZjIjemIpyuxlSo9FNNI5+KzpkkxkRRba+oewiRGV42B1A==", - "requires": { - "jquery": ">=1.7.1 <4.0.0" - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "dev": true - }, - "browser-stdout": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", - "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", - "dev": true - }, - "browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, - "requires": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "dev": true, - "requires": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" - } - }, - "browserify-des": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.1.tgz", - "integrity": "sha512-zy0Cobe3hhgpiOM32Tj7KQ3Vl91m0njwsjzZQK1L+JDf11dzP9qIvjreVinsvXrgfjhStXwUWAEpB9D7Gwmayw==", - "dev": true, - "requires": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "browserify-rsa": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", - "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "randombytes": "^2.0.1" - } - }, - "browserify-sign": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", - "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", - "dev": true, - "requires": { - "bn.js": "^4.1.1", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.2", - "elliptic": "^6.0.0", - "inherits": "^2.0.1", - "parse-asn1": "^5.0.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dev": true, - "requires": { - "pako": "~1.0.5" - } - }, - "browserslist": { - "version": "3.2.8", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-3.2.8.tgz", - "integrity": "sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30000844", - "electron-to-chromium": "^1.3.47" - } - }, - "buffer": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", - "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", - "dev": true, - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" - } - }, - "buffer-alloc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", - "dev": true, - "requires": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" - } - }, - "buffer-alloc-unsafe": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", - "dev": true - }, - "buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", - "dev": true - }, - "buffer-fill": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", - "dev": true - }, - "buffer-from": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.0.tgz", - "integrity": "sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ==", - "dev": true - }, - "buffer-indexof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", - "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", - "dev": true - }, - "buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", - "dev": true - }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true - }, - "builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", - "dev": true - }, - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", - "dev": true - }, - "cacache": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz", - "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==", - "dev": true, - "requires": { - "bluebird": "^3.5.1", - "chownr": "^1.0.1", - "glob": "^7.1.2", - "graceful-fs": "^4.1.11", - "lru-cache": "^4.1.1", - "mississippi": "^2.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.2", - "ssri": "^5.2.4", - "unique-filename": "^1.1.0", - "y18n": "^4.0.0" - } - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } - }, - "caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", - "dev": true, - "requires": { - "callsites": "^0.2.0" - } - }, - "callsite": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", - "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", - "dev": true - }, - "callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", - "dev": true - }, - "camel-case": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", - "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", - "dev": true, - "requires": { - "no-case": "^2.2.0", - "upper-case": "^1.1.1" - } - }, - "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", - "dev": true - }, - "camelcase-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", - "dev": true, - "requires": { - "camelcase": "^2.0.0", - "map-obj": "^1.0.0" - }, - "dependencies": { - "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", - "dev": true - } - } - }, - "caniuse-api": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-1.6.1.tgz", - "integrity": "sha1-tTTnxzTE+B7F++isoq0kNUuWLGw=", - "dev": true, - "requires": { - "browserslist": "^1.3.6", - "caniuse-db": "^1.0.30000529", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - }, - "dependencies": { - "browserslist": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", - "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", - "dev": true, - "requires": { - "caniuse-db": "^1.0.30000639", - "electron-to-chromium": "^1.2.7" - } - } - } - }, - "caniuse-db": { - "version": "1.0.30000851", - "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000851.tgz", - "integrity": "sha1-ig08pN3nIGhWCsyYus91o1no0+M=", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30000851", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000851.tgz", - "integrity": "sha512-Y1ecA1cL9wg0vni8t33nBw/poX8ypm+2c3fbwAESj8cm4ufK9CBFQ1+nUK8Dp5dtFo5Fc3JzkI5DKmQbuIo6hQ==", - "dev": true - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, - "center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", - "dev": true, - "requires": { - "align-text": "^0.1.3", - "lazy-cache": "^1.0.3" - } - }, - "chai-nightwatch": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/chai-nightwatch/-/chai-nightwatch-0.1.1.tgz", - "integrity": "sha1-HKVt52jTwIaP5/wvTTLC/olOa+k=", - "dev": true, - "requires": { - "assertion-error": "1.0.0", - "deep-eql": "0.1.3" - } - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", - "dev": true - }, - "cheerio": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.19.0.tgz", - "integrity": "sha1-dy5wFfLuKZZQltcepBdbdas1SSU=", - "dev": true, - "requires": { - "css-select": "~1.0.0", - "dom-serializer": "~0.1.0", - "entities": "~1.1.1", - "htmlparser2": "~3.8.1", - "lodash": "^3.2.0" - }, - "dependencies": { - "lodash": { - "version": "3.8.0", - "resolved": "http://registry.npmjs.org/lodash/-/lodash-3.8.0.tgz", - "integrity": "sha1-N265i9zZOCqTZcM8TLglDeEyW5E=", - "dev": true - } - } - }, - "chokidar": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", - "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", - "dev": true, - "requires": { - "anymatch": "^1.3.0", - "async-each": "^1.0.0", - "fsevents": "^1.0.0", - "glob-parent": "^2.0.0", - "inherits": "^2.0.1", - "is-binary-path": "^1.0.0", - "is-glob": "^2.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.0.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - } - } - }, - "chownr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", - "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=", - "dev": true - }, - "chromedriver": { - "version": "2.40.0", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-2.40.0.tgz", - "integrity": "sha512-ewvRQ1HMk0vpFSWYCk5hKDoEz5QMPplx5w3C6/Me+03y1imr67l3Hxl9U0jn3mu2N7+c7BoC7JtNW6HzbRAwDQ==", - "dev": true, - "requires": { - "del": "^3.0.0", - "extract-zip": "^1.6.7", - "kew": "^0.7.0", - "mkdirp": "^0.5.1", - "request": "^2.87.0" - } - }, - "cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", - "dev": true - }, - "clap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/clap/-/clap-1.2.3.tgz", - "integrity": "sha512-4CoL/A3hf90V3VIEjeuhSvlGFEHKzOz+Wfc2IVZc+FaUgU0ZQafJTP49fvnULipOPcAfqhyI2duwQyns6xqjYA==", - "dev": true, - "requires": { - "chalk": "^1.1.3" - } - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "clean-css": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.1.11.tgz", - "integrity": "sha1-Ls3xRaujj1R0DybO/Q/z4D4SXWo=", - "dev": true, - "requires": { - "source-map": "0.5.x" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "clean-webpack-plugin": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-0.1.19.tgz", - "integrity": "sha512-M1Li5yLHECcN2MahoreuODul5LkjohJGFxLPTjl3j1ttKrF5rgjZET1SJduuqxLAuT1gAPOdkhg03qcaaU1KeA==", - "dev": true, - "requires": { - "rimraf": "^2.6.1" - } - }, - "cli": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz", - "integrity": "sha1-IoF1NPJL+klQw01TLUjsvGIbjBQ=", - "dev": true, - "requires": { - "exit": "0.1.2", - "glob": "^7.1.1" - } - }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", - "dev": true, - "requires": { - "restore-cursor": "^2.0.0" - } - }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", - "dev": true - }, - "cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", - "dev": true, - "requires": { - "center-align": "^0.1.1", - "right-align": "^0.1.1", - "wordwrap": "0.0.2" - }, - "dependencies": { - "wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", - "dev": true - } - } - }, - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "coa": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/coa/-/coa-1.0.4.tgz", - "integrity": "sha1-qe8VNmDWqGqL3sAomlxoTSF0Mv0=", - "dev": true, - "requires": { - "q": "^1.1.2" - } - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, - "codemirror": { - "version": "5.38.0", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.38.0.tgz", - "integrity": "sha512-PEPnDg8U3DTGFB/Dn2T/INiRNC9CB5k2vLAQJidYCsHvAgtXbklqnuidEwx7yGrMrdGhl0L0P3iNKW9I07J6tQ==" - }, - "coffeescript": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-1.10.0.tgz", - "integrity": "sha1-56qDAZF+9iGzXYo580jc3R234z4=", - "dev": true - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "color": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/color/-/color-0.11.4.tgz", - "integrity": "sha1-bXtcdPtl6EHNSHkq0e1eB7kE12Q=", - "dev": true, - "requires": { - "clone": "^1.0.2", - "color-convert": "^1.3.0", - "color-string": "^0.3.0" - } - }, - "color-convert": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", - "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", - "dev": true, - "requires": { - "color-name": "^1.1.1" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "color-string": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz", - "integrity": "sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE=", - "dev": true, - "requires": { - "color-name": "^1.0.0" - } - }, - "colormin": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colormin/-/colormin-1.1.2.tgz", - "integrity": "sha1-6i90IKcrlogaOKrlnsEkpvcpgTM=", - "dev": true, - "requires": { - "color": "^0.11.0", - "css-color-names": "0.0.4", - "has": "^1.0.1" - } - }, - "colors": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", - "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", - "dev": true - }, - "combine-lists": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/combine-lists/-/combine-lists-1.0.1.tgz", - "integrity": "sha1-RYwH4J4NkA/Ci3Cj/sLazR0st/Y=", - "dev": true, - "requires": { - "lodash": "^4.5.0" - }, - "dependencies": { - "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", - "dev": true - } - } - }, - "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, - "complex.js": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.0.4.tgz", - "integrity": "sha512-Syl95HpxUTS0QjwNxencZsKukgh1zdS9uXeXX2Us0pHaqBR6kiZZi0AkZ9VpZFwHJyVIUVzI4EumjWdXP3fy6w==" - }, - "component-bind": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", - "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", - "dev": true - }, - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true - }, - "component-inherit": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", - "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", - "dev": true - }, - "components-font-awesome": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/components-font-awesome/-/components-font-awesome-4.7.0.tgz", - "integrity": "sha1-p1UAlLbiy1zX3OScQFdxxTPpz+E=" - }, - "compress-commons": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-1.2.2.tgz", - "integrity": "sha1-UkqfEJA/OoEzibAiXSfEi7dRiQ8=", - "dev": true, - "requires": { - "buffer-crc32": "^0.2.1", - "crc32-stream": "^2.0.0", - "normalize-path": "^2.0.0", - "readable-stream": "^2.0.0" - } - }, - "compressible": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.14.tgz", - "integrity": "sha1-MmxfUH+7BV9UEWeCuWmoG2einac=", - "dev": true, - "requires": { - "mime-db": ">= 1.34.0 < 2" - }, - "dependencies": { - "mime-db": { - "version": "1.34.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.34.0.tgz", - "integrity": "sha1-RS0Oz/XDA0am3B5kseruDTcZ/5o=", - "dev": true - } - } - }, - "compression": { - "version": "1.7.2", - "resolved": "http://registry.npmjs.org/compression/-/compression-1.7.2.tgz", - "integrity": "sha1-qv+81qr4VLROuygDU9WtFlH1mmk=", - "dev": true, - "requires": { - "accepts": "~1.3.4", - "bytes": "3.0.0", - "compressible": "~2.0.13", - "debug": "2.6.9", - "on-headers": "~1.0.1", - "safe-buffer": "5.1.1", - "vary": "~1.1.2" - }, - "dependencies": { - "accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", - "dev": true, - "requires": { - "mime-types": "~2.1.18", - "negotiator": "0.6.1" - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", - "dev": true - } - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "connect": { - "version": "3.6.6", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.6.6.tgz", - "integrity": "sha1-Ce/2xVr3I24TcTWnJXSFi2eG9SQ=", - "dev": true, - "requires": { - "debug": "2.6.9", - "finalhandler": "1.1.0", - "parseurl": "~1.3.2", - "utils-merge": "1.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "connect-history-api-fallback": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz", - "integrity": "sha1-sGhzk0vF40T+9hGhlqb6rgruAVo=", - "dev": true - }, - "console-browserify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", - "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", - "dev": true, - "requires": { - "date-now": "^0.1.4" - } - }, - "constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", - "dev": true - }, - "contains-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", - "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", - "dev": true - }, - "content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", - "dev": true - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "dev": true - }, - "convert-source-map": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", - "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", - "dev": true - }, - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", - "dev": true - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", - "dev": true - }, - "copy-concurrently": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", - "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", - "dev": true, - "requires": { - "aproba": "^1.1.1", - "fs-write-stream-atomic": "^1.0.8", - "iferr": "^0.1.5", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.0" - } - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, - "copy-webpack-plugin": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-4.5.1.tgz", - "integrity": "sha512-OlTo6DYg0XfTKOF8eLf79wcHm4Ut10xU2cRBRPMW/NA5F9VMjZGTfRHWDIYC3s+1kObGYrBLshXWU1K0hILkNQ==", - "dev": true, - "requires": { - "cacache": "^10.0.4", - "find-cache-dir": "^1.0.0", - "globby": "^7.1.1", - "is-glob": "^4.0.0", - "loader-utils": "^1.1.0", - "minimatch": "^3.0.4", - "p-limit": "^1.0.0", - "serialize-javascript": "^1.4.0" - }, - "dependencies": { - "globby": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", - "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "dir-glob": "^2.0.0", - "glob": "^7.1.2", - "ignore": "^3.3.5", - "pify": "^3.0.0", - "slash": "^1.0.0" - } - }, - "loader-utils": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", - "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", - "dev": true, - "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0" - } - } - } - }, - "core-js": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", - "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==" - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "crc": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/crc/-/crc-3.5.0.tgz", - "integrity": "sha1-mLi6fUiWZbo5efWbITgTdBAaGWQ=", - "dev": true - }, - "crc32-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-2.0.0.tgz", - "integrity": "sha1-483TtN8xaN10494/u8t7KX/pCPQ=", - "dev": true, - "requires": { - "crc": "^3.4.4", - "readable-stream": "^2.0.0" - } - }, - "create-ecdh": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", - "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "elliptic": "^6.0.0" - } - }, - "create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, - "requires": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, - "requires": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "cryptiles": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", - "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", - "dev": true, - "optional": true, - "requires": { - "boom": "2.x.x" - } - }, - "crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "dev": true, - "requires": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "cson-parser": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/cson-parser/-/cson-parser-1.3.5.tgz", - "integrity": "sha1-fsZ14DkUVTO/KmqFYHPxWZ2cLSQ=", - "dev": true, - "requires": { - "coffee-script": "^1.10.0" - }, - "dependencies": { - "coffee-script": { - "version": "1.12.7", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.7.tgz", - "integrity": "sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==", - "dev": true - } - } - }, - "css-color-names": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", - "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=", - "dev": true - }, - "css-loader": { - "version": "0.28.11", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-0.28.11.tgz", - "integrity": "sha512-wovHgjAx8ZIMGSL8pTys7edA1ClmzxHeY6n/d97gg5odgsxEgKjULPR0viqyC+FWMCL9sfqoC/QCUBo62tLvPg==", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "css-selector-tokenizer": "^0.7.0", - "cssnano": "^3.10.0", - "icss-utils": "^2.1.0", - "loader-utils": "^1.0.2", - "lodash.camelcase": "^4.3.0", - "object-assign": "^4.1.1", - "postcss": "^5.0.6", - "postcss-modules-extract-imports": "^1.2.0", - "postcss-modules-local-by-default": "^1.2.0", - "postcss-modules-scope": "^1.1.0", - "postcss-modules-values": "^1.3.0", - "postcss-value-parser": "^3.3.0", - "source-list-map": "^2.0.0" - }, - "dependencies": { - "loader-utils": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", - "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", - "dev": true, - "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0" - } - } - } - }, - "css-select": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.0.0.tgz", - "integrity": "sha1-sRIcpRhI3SZOIkTQWM7iVN7rRLA=", - "dev": true, - "requires": { - "boolbase": "~1.0.0", - "css-what": "1.0", - "domutils": "1.4", - "nth-check": "~1.0.0" - } - }, - "css-selector-tokenizer": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz", - "integrity": "sha1-5piEdK6MlTR3v15+/s/OzNnPTIY=", - "dev": true, - "requires": { - "cssesc": "^0.1.0", - "fastparse": "^1.1.1", - "regexpu-core": "^1.0.0" - }, - "dependencies": { - "regexpu-core": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", - "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", - "dev": true, - "requires": { - "regenerate": "^1.2.1", - "regjsgen": "^0.2.0", - "regjsparser": "^0.1.4" - } - } - } - }, - "css-what": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-1.0.0.tgz", - "integrity": "sha1-18wt9FGAZm+Z0rFEYmOUaeAPc2w=", - "dev": true - }, - "cssesc": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", - "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=", - "dev": true - }, - "cssnano": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-3.10.0.tgz", - "integrity": "sha1-Tzj2zqK5sX+gFJDyPx3GjqZcHDg=", - "dev": true, - "requires": { - "autoprefixer": "^6.3.1", - "decamelize": "^1.1.2", - "defined": "^1.0.0", - "has": "^1.0.1", - "object-assign": "^4.0.1", - "postcss": "^5.0.14", - "postcss-calc": "^5.2.0", - "postcss-colormin": "^2.1.8", - "postcss-convert-values": "^2.3.4", - "postcss-discard-comments": "^2.0.4", - "postcss-discard-duplicates": "^2.0.1", - "postcss-discard-empty": "^2.0.1", - "postcss-discard-overridden": "^0.1.1", - "postcss-discard-unused": "^2.2.1", - "postcss-filter-plugins": "^2.0.0", - "postcss-merge-idents": "^2.1.5", - "postcss-merge-longhand": "^2.0.1", - "postcss-merge-rules": "^2.0.3", - "postcss-minify-font-values": "^1.0.2", - "postcss-minify-gradients": "^1.0.1", - "postcss-minify-params": "^1.0.4", - "postcss-minify-selectors": "^2.0.4", - "postcss-normalize-charset": "^1.1.0", - "postcss-normalize-url": "^3.0.7", - "postcss-ordered-values": "^2.1.0", - "postcss-reduce-idents": "^2.2.2", - "postcss-reduce-initial": "^1.0.0", - "postcss-reduce-transforms": "^1.0.3", - "postcss-svgo": "^2.1.1", - "postcss-unique-selectors": "^2.0.2", - "postcss-value-parser": "^3.2.3", - "postcss-zindex": "^2.0.1" - } - }, - "csso": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/csso/-/csso-2.3.2.tgz", - "integrity": "sha1-3dUsWHAz9J6Utx/FVWnyUuj/X4U=", - "dev": true, - "requires": { - "clap": "^1.0.9", - "source-map": "^0.5.3" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", - "dev": true, - "requires": { - "array-find-index": "^1.0.1" - } - }, - "custom-event": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", - "dev": true - }, - "cyclist": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", - "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=", - "dev": true - }, - "d": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", - "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", - "dev": true, - "requires": { - "es5-ext": "^0.10.9" - } - }, - "d3": { - "version": "3.5.17", - "resolved": "https://registry.npmjs.org/d3/-/d3-3.5.17.tgz", - "integrity": "sha1-vEZ0gAQ3iyGjYMn8fPUjF5B2L7g=" - }, - "dagre": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.2.tgz", - "integrity": "sha512-TEOOGZOkCOgCG7AoUIq64sJ3d21SMv8tyoqteLpX+UsUsS9Qw8iap4hhogXY4oB3r0bbZuAjO0atAilgCmsE0Q==", - "requires": { - "graphlib": "^2.1.5", - "lodash": "^4.17.4" - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "data-uri-to-buffer": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz", - "integrity": "sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ==", - "dev": true - }, - "date-now": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", - "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", - "dev": true - }, - "date-time": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/date-time/-/date-time-1.1.0.tgz", - "integrity": "sha1-GIdtC9pMGf5w3Tv0sDTygbEqQLY=", - "dev": true, - "requires": { - "time-zone": "^0.1.0" - } - }, - "dateformat": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", - "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", - "dev": true, - "requires": { - "get-stdin": "^4.0.1", - "meow": "^3.3.0" - } - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "decimal.js": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-9.0.1.tgz", - "integrity": "sha512-2h0iKbJwnImBk4TGk7CG1xadoA0g3LDPlQhQzbZ221zvG0p2YVUedbKIPsOZXKZGx6YmZMJKYOalpCMxSdDqTQ==" - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true - }, - "deep-eql": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", - "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", - "dev": true, - "requires": { - "type-detect": "0.1.1" - } - }, - "deep-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", - "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", - "dev": true - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "define-properties": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", - "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", - "dev": true, - "requires": { - "foreach": "^2.0.5", - "object-keys": "^1.0.8" - } - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", - "dev": true - }, - "degenerator": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-1.0.4.tgz", - "integrity": "sha1-/PSQo37OJmRk2cxDGrmMWBnO0JU=", - "dev": true, - "requires": { - "ast-types": "0.x.x", - "escodegen": "1.x.x", - "esprima": "3.x.x" - }, - "dependencies": { - "esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", - "dev": true - } - } - }, - "del": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", - "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", - "dev": true, - "requires": { - "globby": "^6.1.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "p-map": "^1.1.1", - "pify": "^3.0.0", - "rimraf": "^2.2.8" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "dev": true - }, - "des.js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", - "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", - "dev": true - }, - "detect-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", - "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } - }, - "detect-node": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.3.tgz", - "integrity": "sha1-ogM8CcyOFY03dI+951B4Mr1s4Sc=", - "dev": true - }, - "di": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", - "dev": true - }, - "diff": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", - "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", - "dev": true - }, - "diffie-hellman": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - } - }, - "dir-glob": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", - "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "path-type": "^3.0.0" - }, - "dependencies": { - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "requires": { - "pify": "^3.0.0" - } - } - } - }, - "dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", - "dev": true - }, - "dns-packet": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", - "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", - "dev": true, - "requires": { - "ip": "^1.1.0", - "safe-buffer": "^5.0.1" - }, - "dependencies": { - "ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", - "dev": true - } - } - }, - "dns-txt": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", - "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", - "dev": true, - "requires": { - "buffer-indexof": "^1.0.0" - } - }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "dom-converter": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.1.4.tgz", - "integrity": "sha1-pF71cnuJDJv/5tfIduexnLDhfzs=", - "dev": true, - "requires": { - "utila": "~0.3" - }, - "dependencies": { - "utila": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.3.3.tgz", - "integrity": "sha1-1+jn1+MJEHCSsF+NloiCTWM6QiY=", - "dev": true - } - } - }, - "dom-serialize": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", - "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", - "dev": true, - "requires": { - "custom-event": "~1.0.0", - "ent": "~2.2.0", - "extend": "^3.0.0", - "void-elements": "^2.0.0" - } - }, - "dom-serializer": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", - "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", - "dev": true, - "requires": { - "domelementtype": "~1.1.1", - "entities": "~1.1.1" - }, - "dependencies": { - "domelementtype": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", - "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", - "dev": true - } - } - }, - "domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", - "dev": true - }, - "domelementtype": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", - "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=", - "dev": true - }, - "domhandler": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", - "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=", - "dev": true, - "requires": { - "domelementtype": "1" - } - }, - "domutils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.4.3.tgz", - "integrity": "sha1-CGVRN5bGswYDGFDhdVFrr4C3Km8=", - "dev": true, - "requires": { - "domelementtype": "1" - } - }, - "duplexify": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.0.tgz", - "integrity": "sha512-fO3Di4tBKJpYTFHAxTU00BcfWMY9w24r/x21a6rZRbsD/ToUgGxsMbiGRmB7uVAXeGKXD9MwiLZa5E97EVgIRQ==", - "dev": true, - "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "ecc-jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", - "dev": true, - "optional": true, - "requires": { - "jsbn": "~0.1.0" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", - "dev": true - }, - "ejs": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.5.7.tgz", - "integrity": "sha1-zIcsFoiArjxxiXYv1f/ACJbJUYo=", - "dev": true - }, - "electron-to-chromium": { - "version": "1.3.48", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.48.tgz", - "integrity": "sha1-07DYWTgUBE4JLs4hCPw6ya6kuQA=", - "dev": true - }, - "elliptic": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", - "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", - "dev": true, - "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "emojis-list": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", - "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=" - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "dev": true - }, - "end-of-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "engine.io": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-1.8.3.tgz", - "integrity": "sha1-jef5eJXSDTm4X4ju7nd7K9QrE9Q=", - "dev": true, - "requires": { - "accepts": "1.3.3", - "base64id": "1.0.0", - "cookie": "0.3.1", - "debug": "2.3.3", - "engine.io-parser": "1.3.2", - "ws": "1.1.2" - }, - "dependencies": { - "debug": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", - "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", - "dev": true, - "requires": { - "ms": "0.7.2" - } - }, - "ms": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", - "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", - "dev": true - } - } - }, - "engine.io-client": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-1.8.3.tgz", - "integrity": "sha1-F5jtk0USRkU9TG9jXXogH+lA1as=", - "dev": true, - "requires": { - "component-emitter": "1.2.1", - "component-inherit": "0.0.3", - "debug": "2.3.3", - "engine.io-parser": "1.3.2", - "has-cors": "1.1.0", - "indexof": "0.0.1", - "parsejson": "0.0.3", - "parseqs": "0.0.5", - "parseuri": "0.0.5", - "ws": "1.1.2", - "xmlhttprequest-ssl": "1.5.3", - "yeast": "0.1.2" - }, - "dependencies": { - "debug": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", - "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", - "dev": true, - "requires": { - "ms": "0.7.2" - } - }, - "ms": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", - "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", - "dev": true - } - } - }, - "engine.io-parser": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-1.3.2.tgz", - "integrity": "sha1-k3sHnwAH0Ik+xW1GyyILjLQ1Igo=", - "dev": true, - "requires": { - "after": "0.8.2", - "arraybuffer.slice": "0.0.6", - "base64-arraybuffer": "0.1.5", - "blob": "0.0.4", - "has-binary": "0.1.7", - "wtf-8": "1.0.0" - } - }, - "enhanced-resolve": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz", - "integrity": "sha1-TW5omzcl+GCQknzMhs2fFjW4ni4=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.2.0", - "tapable": "^0.1.8" - } - }, - "ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", - "dev": true - }, - "entities": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", - "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=" - }, - "errno": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", - "dev": true, - "requires": { - "prr": "~1.0.1" - } - }, - "error-ex": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", - "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", - "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", - "dev": true, - "requires": { - "es-to-primitive": "^1.1.1", - "function-bind": "^1.1.1", - "has": "^1.0.1", - "is-callable": "^1.1.3", - "is-regex": "^1.0.4" - } - }, - "es-to-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", - "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", - "dev": true, - "requires": { - "is-callable": "^1.1.1", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.1" - } - }, - "es5-ext": { - "version": "0.10.45", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.45.tgz", - "integrity": "sha512-FkfM6Vxxfmztilbxxz5UKSD4ICMf5tSpRFtDNtkAhOxZ0EKtX6qwmXNyH/sFyIbX2P/nU5AMiA9jilWsUGJzCQ==", - "dev": true, - "requires": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.1", - "next-tick": "1" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "es6-map": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", - "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-set": "~0.1.5", - "es6-symbol": "~3.1.1", - "event-emitter": "~0.3.5" - } - }, - "es6-promise": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.6.tgz", - "integrity": "sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q==", - "dev": true - }, - "es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", - "dev": true, - "requires": { - "es6-promise": "^4.0.3" - } - }, - "es6-set": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", - "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-symbol": "3.1.1", - "event-emitter": "~0.3.5" - } - }, - "es6-symbol": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", - "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "es6-templates": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/es6-templates/-/es6-templates-0.2.3.tgz", - "integrity": "sha1-XLmsn7He1usSOTQrgdeSu7QHjuQ=", - "dev": true, - "requires": { - "recast": "~0.11.12", - "through": "~2.3.6" - } - }, - "es6-weak-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", - "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.14", - "es6-iterator": "^2.0.1", - "es6-symbol": "^3.1.1" - } - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", - "dev": true - }, - "escape-latex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.1.0.tgz", - "integrity": "sha512-7k372jNDrL8uW7P/Sw8IkF+QcaeGoyjzrLx4pJj/CSIe02CvxL1wUJ+qMVVHsna/jNZ6PD6aCo7iEeRnXTzvdw==" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "escodegen": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", - "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", - "dev": true, - "requires": { - "esprima": "^2.7.1", - "estraverse": "^1.9.1", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.2.0" - }, - "dependencies": { - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "source-map": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", - "dev": true, - "optional": true, - "requires": { - "amdefine": ">=0.0.4" - } - } - } - }, - "escope": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", - "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", - "dev": true, - "requires": { - "es6-map": "^0.1.3", - "es6-weak-map": "^2.0.1", - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - }, - "dependencies": { - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - } - } - }, - "eslint": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", - "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", - "dev": true, - "requires": { - "ajv": "^5.3.0", - "babel-code-frame": "^6.22.0", - "chalk": "^2.1.0", - "concat-stream": "^1.6.0", - "cross-spawn": "^5.1.0", - "debug": "^3.1.0", - "doctrine": "^2.1.0", - "eslint-scope": "^3.7.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^3.5.4", - "esquery": "^1.0.0", - "esutils": "^2.0.2", - "file-entry-cache": "^2.0.0", - "functional-red-black-tree": "^1.0.1", - "glob": "^7.1.2", - "globals": "^11.0.1", - "ignore": "^3.3.3", - "imurmurhash": "^0.1.4", - "inquirer": "^3.0.6", - "is-resolvable": "^1.0.0", - "js-yaml": "^3.9.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.4", - "minimatch": "^3.0.2", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.2", - "pluralize": "^7.0.0", - "progress": "^2.0.0", - "regexpp": "^1.0.1", - "require-uncached": "^1.0.3", - "semver": "^5.3.0", - "strip-ansi": "^4.0.0", - "strip-json-comments": "~2.0.1", - "table": "4.0.2", - "text-table": "~0.2.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "globals": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.5.0.tgz", - "integrity": "sha512-hYyf+kI8dm3nORsiiXUQigOU62hDLfJ9G01uyGMxhc6BKsircrUhC4uJPQPUSuq2GrTmiiEt7ewxlMdBewfmKQ==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", - "dev": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "eslint-config-airbnb-base": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-12.1.0.tgz", - "integrity": "sha512-/vjm0Px5ZCpmJqnjIzcFb9TKZrKWz0gnuG/7Gfkt0Db1ELJR51xkZth+t14rYdqWgX836XbuxtArbIHlVhbLBA==", - "dev": true, - "requires": { - "eslint-restricted-globals": "^0.1.1" - } - }, - "eslint-import-resolver-node": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", - "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", - "dev": true, - "requires": { - "debug": "^2.6.9", - "resolve": "^1.5.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "eslint-import-resolver-webpack": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-webpack/-/eslint-import-resolver-webpack-0.8.4.tgz", - "integrity": "sha512-b6JxR57ruiMxq2tIu4T/SrYED5RKJfeBEs8u3+JWF+O2RxDmFpUH84c5uS1T5qiP0K4r0SL7CXhvd41hXdDlAg==", - "dev": true, - "requires": { - "array-find": "^1.0.0", - "debug": "^2.6.8", - "enhanced-resolve": "~0.9.0", - "find-root": "^0.1.1", - "has": "^1.0.1", - "interpret": "^1.0.0", - "is-absolute": "^0.2.3", - "lodash.get": "^3.7.0", - "node-libs-browser": "^1.0.0 || ^2.0.0", - "resolve": "^1.2.0", - "semver": "^5.3.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "eslint-loader": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/eslint-loader/-/eslint-loader-1.9.0.tgz", - "integrity": "sha512-40aN976qSNPyb9ejTqjEthZITpls1SVKtwguahmH1dzGCwQU/vySE+xX33VZmD8csU0ahVNCtFlsPgKqRBiqgg==", - "dev": true, - "requires": { - "loader-fs-cache": "^1.0.0", - "loader-utils": "^1.0.2", - "object-assign": "^4.0.1", - "object-hash": "^1.1.4", - "rimraf": "^2.6.1" - }, - "dependencies": { - "loader-utils": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", - "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", - "dev": true, - "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0" - } - } - } - }, - "eslint-module-utils": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.2.0.tgz", - "integrity": "sha1-snA2LNiLGkitMIl2zn+lTphBF0Y=", - "dev": true, - "requires": { - "debug": "^2.6.8", - "pkg-dir": "^1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "requires": { - "pinkie-promise": "^2.0.0" - } - }, - "pkg-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", - "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", - "dev": true, - "requires": { - "find-up": "^1.0.0" - } - } - } - }, - "eslint-plugin-disable": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-disable/-/eslint-plugin-disable-0.3.0.tgz", - "integrity": "sha1-xfQRz0AE5I55roLPw7KLldyQtzc=", - "dev": true, - "requires": { - "multimatch": "^2.1.0", - "resolve": "^1.1.6" - } - }, - "eslint-plugin-import": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.12.0.tgz", - "integrity": "sha1-2tMXgSktZmSyUxf9BJ0uKy8CIF0=", - "dev": true, - "requires": { - "contains-path": "^0.1.0", - "debug": "^2.6.8", - "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.1", - "eslint-module-utils": "^2.2.0", - "has": "^1.0.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.3", - "read-pkg-up": "^2.0.0", - "resolve": "^1.6.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "doctrine": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", - "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" - } - }, - "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "dev": true, - "requires": { - "pify": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "dev": true, - "requires": { - "load-json-file": "^2.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" - } - }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "dev": true, - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - } - } - }, - "eslint-restricted-globals": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz", - "integrity": "sha1-NfDVy8ZMLj7WLpO0saevBbp+1Nc=", - "dev": true - }, - "eslint-scope": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", - "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - }, - "dependencies": { - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - } - } - }, - "eslint-visitor-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", - "dev": true - }, - "espree": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", - "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", - "dev": true, - "requires": { - "acorn": "^5.5.0", - "acorn-jsx": "^3.0.0" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - }, - "esquery": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", - "dev": true, - "requires": { - "estraverse": "^4.0.0" - }, - "dependencies": { - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", - "dev": true, - "requires": { - "estraverse": "^4.1.0" - }, - "dependencies": { - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - } - } - }, - "estraverse": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", - "dev": true - }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", - "dev": true - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", - "dev": true - }, - "event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "eventemitter2": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", - "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=", - "dev": true - }, - "eventemitter3": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", - "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==", - "dev": true - }, - "events": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", - "dev": true - }, - "eventsource": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-0.1.6.tgz", - "integrity": "sha1-Cs7ehJ7X3RzMMsgRuxG5RNTykjI=", - "dev": true, - "requires": { - "original": ">=0.0.5" - } - }, - "evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, - "requires": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "dev": true, - "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true - }, - "expand-braces": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/expand-braces/-/expand-braces-0.1.2.tgz", - "integrity": "sha1-SIsdHSRRyz06axks/AMPRMWFX+o=", - "dev": true, - "requires": { - "array-slice": "^0.2.3", - "array-unique": "^0.2.1", - "braces": "^0.1.2" - }, - "dependencies": { - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true - }, - "braces": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-0.1.5.tgz", - "integrity": "sha1-wIVxEIUpHYt1/ddOqw+FlygHEeY=", - "dev": true, - "requires": { - "expand-range": "^0.1.0" - } - }, - "expand-range": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-0.1.1.tgz", - "integrity": "sha1-TLjtoJk8pW+k9B/ELzy7TMrf8EQ=", - "dev": true, - "requires": { - "is-number": "^0.1.1", - "repeat-string": "^0.2.2" - } - }, - "is-number": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-0.1.1.tgz", - "integrity": "sha1-aaevEWlj1HIG7JvZtIoUIW8eOAY=", - "dev": true - }, - "repeat-string": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-0.2.2.tgz", - "integrity": "sha1-x6jTI2BoNiBZp+RlH8aITosftK4=", - "dev": true - } - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "expand-range": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", - "dev": true, - "requires": { - "fill-range": "^2.1.0" - }, - "dependencies": { - "fill-range": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", - "dev": true, - "requires": { - "is-number": "^2.1.0", - "isobject": "^2.0.0", - "randomatic": "^3.0.0", - "repeat-element": "^1.1.2", - "repeat-string": "^1.5.2" - } - }, - "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "express": { - "version": "4.16.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz", - "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", - "dev": true, - "requires": { - "accepts": "~1.3.5", - "array-flatten": "1.1.1", - "body-parser": "1.18.2", - "content-disposition": "0.5.2", - "content-type": "~1.0.4", - "cookie": "0.3.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.1.1", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.3", - "qs": "6.5.1", - "range-parser": "~1.2.0", - "safe-buffer": "5.1.1", - "send": "0.16.2", - "serve-static": "1.13.2", - "setprototypeof": "1.1.0", - "statuses": "~1.4.0", - "type-is": "~1.6.16", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", - "dev": true, - "requires": { - "mime-types": "~2.1.18", - "negotiator": "0.6.1" - } - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", - "dev": true - }, - "body-parser": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", - "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", - "dev": true, - "requires": { - "bytes": "3.0.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.1", - "http-errors": "~1.6.2", - "iconv-lite": "0.4.19", - "on-finished": "~2.3.0", - "qs": "6.5.1", - "raw-body": "2.3.2", - "type-is": "~1.6.15" - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "finalhandler": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", - "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", - "dev": true, - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "statuses": "~1.4.0", - "unpipe": "~1.0.0" - } - }, - "iconv-lite": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", - "dev": true - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", - "dev": true - }, - "raw-body": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", - "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", - "dev": true, - "requires": { - "bytes": "3.0.0", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", - "unpipe": "1.0.0" - }, - "dependencies": { - "depd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=", - "dev": true - }, - "http-errors": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", - "dev": true, - "requires": { - "depd": "1.1.1", - "inherits": "2.0.3", - "setprototypeof": "1.0.3", - "statuses": ">= 1.3.1 < 2" - } - }, - "setprototypeof": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=", - "dev": true - } - } - }, - "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", - "dev": true - }, - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", - "dev": true - } - } - }, - "extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", - "dev": true - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "external-editor": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", - "dev": true, - "requires": { - "chardet": "^0.4.0", - "iconv-lite": "^0.4.17", - "tmp": "^0.0.33" - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "extract-text-webpack-plugin": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extract-text-webpack-plugin/-/extract-text-webpack-plugin-3.0.2.tgz", - "integrity": "sha512-bt/LZ4m5Rqt/Crl2HiKuAl/oqg0psx1tsTLkvWbJen1CtD+fftkZhMaQ9HOtY2gWsl2Wq+sABmMVi9z3DhKWQQ==", - "dev": true, - "requires": { - "async": "^2.4.1", - "loader-utils": "^1.1.0", - "schema-utils": "^0.3.0", - "webpack-sources": "^1.0.1" - }, - "dependencies": { - "loader-utils": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", - "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", - "dev": true, - "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0" - } - } - } - }, - "extract-zip": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", - "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=", - "dev": true, - "requires": { - "concat-stream": "1.6.2", - "debug": "2.6.9", - "mkdirp": "0.5.1", - "yauzl": "2.4.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true - }, - "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "fastparse": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz", - "integrity": "sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg=", - "dev": true - }, - "faye-websocket": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", - "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", - "dev": true, - "requires": { - "websocket-driver": ">=0.5.1" - } - }, - "fd-slicer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", - "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", - "dev": true, - "requires": { - "pend": "~1.2.0" - } - }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", - "dev": true, - "requires": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" - } - }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true - }, - "filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", - "dev": true - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "finalhandler": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", - "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", - "dev": true, - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.1", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "statuses": "~1.3.1", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "statuses": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", - "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=", - "dev": true - } - } - }, - "find-cache-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", - "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^1.0.0", - "pkg-dir": "^2.0.0" - } - }, - "find-root": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/find-root/-/find-root-0.1.2.tgz", - "integrity": "sha1-mNImfP8ZFsyvJ0OzoO6oHXnX3NE=", - "dev": true - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "findup-sync": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.3.0.tgz", - "integrity": "sha1-N5MKpdgWt3fANEXhlmzGeQpMCxY=", - "dev": true, - "requires": { - "glob": "~5.0.0" - }, - "dependencies": { - "glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", - "dev": true, - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "flat-cache": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", - "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", - "dev": true, - "requires": { - "circular-json": "^0.3.1", - "del": "^2.0.2", - "graceful-fs": "^4.1.2", - "write": "^0.2.1" - }, - "dependencies": { - "del": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", - "dev": true, - "requires": { - "globby": "^5.0.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "rimraf": "^2.2.8" - } - }, - "globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, - "flatten": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz", - "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=", - "dev": true - }, - "flush-write-stream": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", - "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.4" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "follow-redirects": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.0.tgz", - "integrity": "sha512-fdrt472/9qQ6Kgjvb935ig6vJCuofpBUD14f9Vb+SLlm7xIe4Qva5gey8EKtv8lp7ahE1wilg3xL1znpVGtZIA==", - "dev": true, - "requires": { - "debug": "^3.1.0" - } - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true - }, - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } - }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", - "dev": true - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true - }, - "form-data": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "1.0.6", - "mime-types": "^2.1.12" - } - }, - "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", - "dev": true - }, - "fraction.js": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.0.4.tgz", - "integrity": "sha512-aK/oGatyYLTtXRHjfEsytX5fieeR5H4s8sLorzcT12taFS+dbMZejnvm9gRa8mZAPwci24ucjq9epDyaq5u8Iw==" - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "requires": { - "map-cache": "^0.2.2" - } - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", - "dev": true - }, - "from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "fs-access": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", - "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=", - "dev": true, - "requires": { - "null-check": "^1.0.0" - } - }, - "fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true - }, - "fs-minipass": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.6.tgz", - "integrity": "sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ==", - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "fs-write-stream-atomic": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", - "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "iferr": "^0.1.5", - "imurmurhash": "^0.1.4", - "readable-stream": "1 || 2" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", - "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", - "dev": true, - "optional": true, - "requires": { - "nan": "^2.9.2", - "node-pre-gyp": "^0.10.0" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", - "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", - "dev": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true, - "optional": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "deep-extend": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.1.tgz", - "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==", - "dev": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "dev": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", - "dev": true, - "optional": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "dev": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", - "dev": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.21", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.21.tgz", - "integrity": "sha512-En5V9za5mBt2oUA03WGD3TwDv0MKAruqsuxstbMUZaj9W9k/m1CV/9py3l0L5kw9Bln8fdHQmzHSYtvpvTLpKw==", - "dev": true, - "optional": true, - "requires": { - "safer-buffer": "^2.1.0" - } - }, - "ignore-walk": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", - "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", - "dev": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true, - "optional": true - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "optional": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true, - "optional": true - }, - "needle": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.2.0.tgz", - "integrity": "sha512-eFagy6c+TYayorXw/qtAdSvaUpEbBsDwDyxYFgLZ0lTojfH7K+OdBqAF7TAFwDokJaGpubpSGG0wO3iC0XPi8w==", - "dev": true, - "optional": true, - "requires": { - "debug": "^2.1.2", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.10.0.tgz", - "integrity": "sha512-G7kEonQLRbcA/mOoFoxvlMrw6Q6dPf92+t/l0DFSMuSlDoWaI9JWIyPwK0jyE1bph//CUEL65/Fz1m2vJbmjQQ==", - "dev": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.0", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.1.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" - } - }, - "nopt": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", - "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", - "dev": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.3.tgz", - "integrity": "sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow==", - "dev": true, - "optional": true - }, - "npm-packlist": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.10.tgz", - "integrity": "sha512-AQC0Dyhzn4EiYEfIUjCdMl0JJ61I2ER9ukf/sLxJUcZHfo+VyEfz2rMJgLZSS1v30OxPQe1cN0LZA1xbcaVfWA==", - "dev": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "dev": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true, - "optional": true - }, - "rc": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.7.tgz", - "integrity": "sha512-LdLD8xD4zzLsAT5xyushXDNscEjB7+2ulnl8+r1pnESlYtlJtVSoCMBGr30eDRJ3+2Gq89jK9P9e4tCEH1+ywA==", - "dev": true, - "optional": true, - "requires": { - "deep-extend": "^0.5.1", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "dev": true, - "optional": true, - "requires": { - "glob": "^7.0.5" - } - }, - "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true, - "optional": true - }, - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true, - "optional": true - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true, - "optional": true - }, - "wide-align": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", - "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", - "dev": true, - "optional": true, - "requires": { - "string-width": "^1.0.2" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - } - } - }, - "ftp": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz", - "integrity": "sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0=", - "dev": true, - "requires": { - "readable-stream": "1.1.x", - "xregexp": "2.0.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - } - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "get-caller-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", - "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", - "dev": true - }, - "get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", - "dev": true - }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true - }, - "get-uri": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-2.0.2.tgz", - "integrity": "sha512-ZD325dMZOgerGqF/rF6vZXyFGTAay62svjQIT+X/oU2PtxYpFxvSkbsdi+oxIrsNxlZVd4y8wUDqkaExWTI/Cw==", - "dev": true, - "requires": { - "data-uri-to-buffer": "1", - "debug": "2", - "extend": "3", - "file-uri-to-path": "1", - "ftp": "~0.3.10", - "readable-stream": "2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true - }, - "getobject": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/getobject/-/getobject-0.1.0.tgz", - "integrity": "sha1-BHpEl4n6Fg0Bj1SG7ZEyC27HiFw=", - "dev": true - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", - "dev": true, - "requires": { - "glob-parent": "^2.0.0", - "is-glob": "^2.0.0" - }, - "dependencies": { - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - } - } - }, - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "dev": true, - "requires": { - "is-glob": "^2.0.0" - }, - "dependencies": { - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - } - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true - }, - "graceful-readlink": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", - "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", - "dev": true - }, - "graphlib": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.5.tgz", - "integrity": "sha512-XvtbqCcw+EM5SqQrIetIKKD+uZVNQtDPD1goIg7K73RuRZtVI5rYMdcCVSHm/AS1sCBZ7vt0p5WgXouucHQaOA==", - "requires": { - "lodash": "^4.11.1" - } - }, - "growl": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", - "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", - "dev": true - }, - "grunt": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.0.4.tgz", - "integrity": "sha512-PYsMOrOC+MsdGEkFVwMaMyc6Ob7pKmq+deg1Sjr+vvMWp35sztfwKE7qoN51V+UEtHsyNuMcGdgMLFkBHvMxHQ==", - "dev": true, - "requires": { - "coffeescript": "~1.10.0", - "dateformat": "~1.0.12", - "eventemitter2": "~0.4.13", - "exit": "~0.1.1", - "findup-sync": "~0.3.0", - "glob": "~7.0.0", - "grunt-cli": "~1.2.0", - "grunt-known-options": "~1.1.0", - "grunt-legacy-log": "~2.0.0", - "grunt-legacy-util": "~1.1.1", - "iconv-lite": "~0.4.13", - "js-yaml": "~3.13.0", - "minimatch": "~3.0.2", - "mkdirp": "~0.5.1", - "nopt": "~3.0.6", - "path-is-absolute": "~1.0.0", - "rimraf": "~2.6.2" - }, - "dependencies": { - "glob": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", - "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.2", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "grunt-angular-gettext": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/grunt-angular-gettext/-/grunt-angular-gettext-2.3.9.tgz", - "integrity": "sha512-gOB0qBwADWXiKhpkyzeTHq2WyIFZKCzdn7HIRRyUn7xURxyBXgRjBTXEtUhWhg5bnY8z4lW28eCuki2RoYnt7Q==", - "dev": true, - "requires": { - "angular-gettext-tools": "~2.3.9" - } - }, - "grunt-cli": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.2.0.tgz", - "integrity": "sha1-VisRnrsGndtGSs4oRVAb6Xs1tqg=", - "dev": true, - "requires": { - "findup-sync": "~0.3.0", - "grunt-known-options": "~1.1.0", - "nopt": "~3.0.6", - "resolve": "~1.1.0" - }, - "dependencies": { - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true - } - } - }, - "grunt-concurrent": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/grunt-concurrent/-/grunt-concurrent-2.3.1.tgz", - "integrity": "sha1-Hj2zjM71o9oRleYdYx/n4yE0TSM=", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "async": "^1.2.1", - "indent-string": "^2.0.0", - "pad-stream": "^1.0.0" - }, - "dependencies": { - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - } - } - }, - "grunt-contrib-jshint": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-jshint/-/grunt-contrib-jshint-1.1.0.tgz", - "integrity": "sha1-Np2QmyWTxA6L55lAshNAhQx5Oaw=", - "dev": true, - "requires": { - "chalk": "^1.1.1", - "hooker": "^0.2.3", - "jshint": "~2.9.4" - }, - "dependencies": { - "jshint": { - "version": "2.9.7", - "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.9.7.tgz", - "integrity": "sha512-Q8XN38hGsVQhdlM+4gd1Xl7OB1VieSuCJf+fEJjpo59JH99bVJhXRXAh26qQ15wfdd1VPMuDWNeSWoNl53T4YA==", - "dev": true, - "requires": { - "cli": "~1.0.0", - "console-browserify": "1.1.x", - "exit": "0.1.x", - "htmlparser2": "3.8.x", - "lodash": "~4.17.10", - "minimatch": "~3.0.2", - "shelljs": "0.3.x", - "strip-json-comments": "1.0.x" - } - }, - "strip-json-comments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", - "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", - "dev": true - } - } - }, - "grunt-known-options": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-1.1.0.tgz", - "integrity": "sha1-pCdO6zL6dl2lp6OxcSYXzjsUQUk=", - "dev": true - }, - "grunt-legacy-log": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-2.0.0.tgz", - "integrity": "sha512-1m3+5QvDYfR1ltr8hjiaiNjddxGdQWcH0rw1iKKiQnF0+xtgTazirSTGu68RchPyh1OBng1bBUjLmX8q9NpoCw==", - "dev": true, - "requires": { - "colors": "~1.1.2", - "grunt-legacy-log-utils": "~2.0.0", - "hooker": "~0.2.3", - "lodash": "~4.17.5" - } - }, - "grunt-legacy-log-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.0.1.tgz", - "integrity": "sha512-o7uHyO/J+i2tXG8r2bZNlVk20vlIFJ9IEYyHMCQGfWYru8Jv3wTqKZzvV30YW9rWEjq0eP3cflQ1qWojIe9VFA==", - "dev": true, - "requires": { - "chalk": "~2.4.1", - "lodash": "~4.17.10" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "grunt-legacy-util": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-1.1.1.tgz", - "integrity": "sha512-9zyA29w/fBe6BIfjGENndwoe1Uy31BIXxTH3s8mga0Z5Bz2Sp4UCjkeyv2tI449ymkx3x26B+46FV4fXEddl5A==", - "dev": true, - "requires": { - "async": "~1.5.2", - "exit": "~0.1.1", - "getobject": "~0.1.0", - "hooker": "~0.2.3", - "lodash": "~4.17.10", - "underscore.string": "~3.3.4", - "which": "~1.3.0" - }, - "dependencies": { - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "grunt-newer": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/grunt-newer/-/grunt-newer-1.3.0.tgz", - "integrity": "sha1-g8y3od2ny9irI7BZAk6+YUrS80I=", - "dev": true, - "requires": { - "async": "^1.5.2", - "rimraf": "^2.5.2" - }, - "dependencies": { - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - } - } - }, - "hamsterjs": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/hamsterjs/-/hamsterjs-1.1.3.tgz", - "integrity": "sha512-q4XBr7hnxx1WyZA8mpVDuZVa1YXaR0WZaFSBxnj8hUXltuqXJOt5yuWYkAbMXsj+q0REDUO990+/TuxEadXFyg==" - }, - "handle-thing": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-1.2.5.tgz", - "integrity": "sha1-/Xqtcmvxpf0W38KbL3pmAdJxOcQ=", - "dev": true - }, - "handlebars": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz", - "integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==", - "dev": true, - "requires": { - "neo-async": "^2.6.0", - "optimist": "^0.6.1", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4" - }, - "dependencies": { - "commander": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", - "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", - "dev": true, - "optional": true - }, - "neo-async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", - "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "uglify-js": { - "version": "3.5.14", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.14.tgz", - "integrity": "sha512-dgyjIw8KFK6AyVl5vm2tEqPewv5TKGEiiVFLI1LbF+oHua/Njd8tZk3lIbF1AWU1rNdEg7scaceADb4zqCcWXg==", - "dev": true, - "optional": true, - "requires": { - "commander": "~2.20.0", - "source-map": "~0.6.1" - } - } - } - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true - }, - "har-validator": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", - "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", - "dev": true, - "requires": { - "ajv": "^5.1.0", - "har-schema": "^2.0.0" - } - }, - "hard-source-webpack-plugin": { - "version": "0.5.18", - "resolved": "https://registry.npmjs.org/hard-source-webpack-plugin/-/hard-source-webpack-plugin-0.5.18.tgz", - "integrity": "sha512-qo1kuq0J4aII77JnkS36/mkhM+Mk7MNURKKmVVgY+m+2Pm3iu/hOUVkJS4sqRAKLcRRtDMdr+SrlPgJZCmdcPQ==", - "dev": true, - "requires": { - "lodash": "^4.15.0", - "mkdirp": "^0.5.1", - "node-object-hash": "^1.2.0", - "rimraf": "^2.6.2", - "source-list-map": "^0.1.6", - "source-map": "^0.5.6", - "webpack-core": "~0.6.0", - "webpack-sources": "^1.0.1", - "write-json-file": "^2.3.0" - }, - "dependencies": { - "source-list-map": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-0.1.8.tgz", - "integrity": "sha1-xVCyq1Qn9rPyH1r+rYjE9Vh7IQY=", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "has-binary": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/has-binary/-/has-binary-0.1.7.tgz", - "integrity": "sha1-aOYesWIQyVRaClzOBqhzkS/h5ow=", - "dev": true, - "requires": { - "isarray": "0.0.1" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - } - } - }, - "has-cors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", - "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", - "dev": true - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "hash-base": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", - "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "hash.js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", - "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "hawk": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", - "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", - "dev": true, - "optional": true, - "requires": { - "boom": "2.x.x", - "cryptiles": "2.x.x", - "hoek": "2.x.x", - "sntp": "1.x.x" - } - }, - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", - "dev": true - }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dev": true, - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "hoek": { - "version": "2.16.3", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", - "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", - "dev": true - }, - "home-or-tmp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", - "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", - "dev": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.1" - } - }, - "hooker": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", - "integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk=", - "dev": true - }, - "hosted-git-info": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", - "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==", - "dev": true - }, - "hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "html-comment-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.1.tgz", - "integrity": "sha1-ZouTd26q5V696POtRkswekljYl4=", - "dev": true - }, - "html-entities": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", - "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=" - }, - "html-loader": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/html-loader/-/html-loader-0.5.5.tgz", - "integrity": "sha512-7hIW7YinOYUpo//kSYcPB6dCKoceKLmOwjEMmhIobHuWGDVl0Nwe4l68mdG/Ru0wcUxQjVMEoZpkalZ/SE7zog==", - "dev": true, - "requires": { - "es6-templates": "^0.2.3", - "fastparse": "^1.1.1", - "html-minifier": "^3.5.8", - "loader-utils": "^1.1.0", - "object-assign": "^4.1.1" - }, - "dependencies": { - "loader-utils": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", - "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", - "dev": true, - "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0" - } - } - } - }, - "html-minifier": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.16.tgz", - "integrity": "sha512-zP5EfLSpiLRp0aAgud4CQXPQZm9kXwWjR/cF0PfdOj+jjWnOaCgeZcll4kYXSvIBPeUMmyaSc7mM4IDtA+kboA==", - "dev": true, - "requires": { - "camel-case": "3.0.x", - "clean-css": "4.1.x", - "commander": "2.15.x", - "he": "1.1.x", - "param-case": "2.1.x", - "relateurl": "0.2.x", - "uglify-js": "3.3.x" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "uglify-js": { - "version": "3.3.28", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.3.28.tgz", - "integrity": "sha512-68Rc/aA6cswiaQ5SrE979UJcXX+ADA1z33/ZsPd+fbAiVdjZ16OXdbtGO+rJUUBgK6qdf3SOPhQf3K/ybF5Miw==", - "dev": true, - "requires": { - "commander": "~2.15.0", - "source-map": "~0.6.1" - } - } - } - }, - "html-webpack-harddisk-plugin": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/html-webpack-harddisk-plugin/-/html-webpack-harddisk-plugin-0.1.0.tgz", - "integrity": "sha1-QyAklhohrGaPorXf4kYpxgucWNc=", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } - }, - "html-webpack-plugin": { - "version": "2.30.1", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-2.30.1.tgz", - "integrity": "sha1-f5xCG36pHsRg9WUn1430hO51N9U=", - "dev": true, - "requires": { - "bluebird": "^3.4.7", - "html-minifier": "^3.2.3", - "loader-utils": "^0.2.16", - "lodash": "^4.17.3", - "pretty-error": "^2.0.2", - "toposort": "^1.0.0" - }, - "dependencies": { - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - } - } - }, - "htmlparser2": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", - "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", - "dev": true, - "requires": { - "domelementtype": "1", - "domhandler": "2.3", - "domutils": "1.5", - "entities": "1.0", - "readable-stream": "1.1" - }, - "dependencies": { - "domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", - "dev": true, - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "entities": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", - "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=", - "dev": true - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - } - } - }, - "http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", - "dev": true - }, - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "http-parser-js": { - "version": "0.4.13", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.13.tgz", - "integrity": "sha1-O9bW/ebjFyyTNMOzO2wZPYD+ETc=", - "dev": true - }, - "http-proxy": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", - "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", - "dev": true, - "requires": { - "eventemitter3": "^3.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - } - }, - "http-proxy-agent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-1.0.0.tgz", - "integrity": "sha1-zBzjjkU7+YSg93AtLdWcc9CBKEo=", - "dev": true, - "requires": { - "agent-base": "2", - "debug": "2", - "extend": "3" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "http-proxy-middleware": { - "version": "0.17.4", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.17.4.tgz", - "integrity": "sha1-ZC6ISIUdZvCdTxJJEoRtuutBuDM=", - "dev": true, - "requires": { - "http-proxy": "^1.16.2", - "is-glob": "^3.1.0", - "lodash": "^4.17.2", - "micromatch": "^2.3.11" - }, - "dependencies": { - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1" - } - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" - } - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, - "requires": { - "is-posix-bracket": "^0.1.0" - } - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - }, - "dependencies": { - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - } - } - }, - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - }, - "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", - "dev": true - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" - }, - "dependencies": { - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - } - } - } - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", - "dev": true - }, - "https-proxy-agent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz", - "integrity": "sha1-NffabEjOTdv6JkiRrFk+5f+GceY=", - "dev": true, - "requires": { - "agent-base": "2", - "debug": "2", - "extend": "3" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "iconv-lite": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "icss-replace-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", - "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=", - "dev": true - }, - "icss-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-2.1.0.tgz", - "integrity": "sha1-g/Cg7DeL8yRheLbCrZE28TWxyWI=", - "dev": true, - "requires": { - "postcss": "^6.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "postcss": { - "version": "6.0.22", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.22.tgz", - "integrity": "sha512-Toc9lLoUASwGqxBSJGTVcOQiDqjK+Z2XlWBg+IgYwQMY9vA2f7iMpXVc1GpPcfTSyM5lkxNo0oDwDRO+wm7XHA==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "ieee754": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz", - "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==", - "dev": true - }, - "iferr": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", - "dev": true - }, - "ignore": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.8.tgz", - "integrity": "sha512-pUh+xUQQhQzevjRHHFqqcTy0/dP/kS9I8HSrUydhihjuD09W6ldVWFtIrwhXdUJHis3i2rZNqEHpZH/cbinFbg==", - "dev": true - }, - "image-size": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", - "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", - "dev": true, - "optional": true - }, - "import-local": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz", - "integrity": "sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==", - "dev": true, - "requires": { - "pkg-dir": "^2.0.0", - "resolve-cwd": "^2.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } - }, - "indexes-of": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", - "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", - "dev": true - }, - "indexof": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", - "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=" - }, - "inquirer": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", - "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", - "dev": true, - "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^2.0.4", - "figures": "^2.0.0", - "lodash": "^4.3.0", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rx-lite": "^4.0.8", - "rx-lite-aggregates": "^4.0.8", - "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", - "dev": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "internal-ip": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-1.2.0.tgz", - "integrity": "sha1-rp+/k7mEh4eF1QqN4bNWlWBYz1w=", - "dev": true, - "requires": { - "meow": "^3.3.0" - } - }, - "interpret": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", - "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", - "dev": true - }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "requires": { - "loose-envify": "^1.0.0" - } - }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true - }, - "ip": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.0.1.tgz", - "integrity": "sha1-x+NWzeoiWucbNtcPLnGpK6TkJZA=", - "dev": true - }, - "ipaddr.js": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", - "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=", - "dev": true - }, - "irregular-plurals": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-1.4.0.tgz", - "integrity": "sha1-LKmwM2UREYVUEvFr5dd8YqRYp2Y=", - "dev": true - }, - "is-absolute": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.2.6.tgz", - "integrity": "sha1-IN5p89uULvLYe5wto28XIjWxtes=", - "dev": true, - "requires": { - "is-relative": "^0.2.1", - "is-windows": "^0.2.0" - }, - "dependencies": { - "is-windows": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", - "integrity": "sha1-3hqm1j6indJIc3tp8f+LgALSEIw=", - "dev": true - } - } - }, - "is-absolute-url": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", - "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=", - "dev": true - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, - "requires": { - "binary-extensions": "^1.0.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-builtin-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "dev": true, - "requires": { - "builtin-modules": "^1.0.0" - } - }, - "is-callable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz", - "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=", - "dev": true - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-date-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", - "dev": true - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "is-dotfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", - "dev": true - }, - "is-equal-shallow": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", - "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", - "dev": true, - "requires": { - "is-primitive": "^2.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-finite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", - "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "is-glob": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", - "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-odd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-2.0.0.tgz", - "integrity": "sha512-OTiixgpZAT1M4NHgS5IguFp/Vz2VI3U7Goh4/HA1adtwyLtSBrxYlcSYkhpAE07s4fKEcjrFxyvtQBND4vFQyQ==", - "dev": true, - "requires": { - "is-number": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - } - } - }, - "is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", - "dev": true - }, - "is-path-in-cwd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", - "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", - "dev": true, - "requires": { - "is-path-inside": "^1.0.0" - } - }, - "is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", - "dev": true, - "requires": { - "path-is-inside": "^1.0.1" - } - }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "is-posix-bracket": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", - "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", - "dev": true - }, - "is-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", - "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", - "dev": true - }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", - "dev": true - }, - "is-regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", - "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", - "dev": true, - "requires": { - "has": "^1.0.1" - } - }, - "is-relative": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.2.1.tgz", - "integrity": "sha1-0n9MfVFtF1+2ENuEu+7yPDvJeqU=", - "dev": true, - "requires": { - "is-unc-path": "^0.1.1" - } - }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", - "dev": true - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - }, - "is-svg": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-2.1.0.tgz", - "integrity": "sha1-z2EJDaDZ77yrhyLeum8DIgjbsOk=", - "dev": true, - "requires": { - "html-comment-regex": "^1.1.0" - } - }, - "is-symbol": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", - "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", - "dev": true - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "is-unc-path": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-0.1.2.tgz", - "integrity": "sha1-arBTpyVzwQJQ/0FqOBTDUXivObk=", - "dev": true, - "requires": { - "unc-path-regex": "^0.1.0" - } - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isbinaryfile": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.2.tgz", - "integrity": "sha1-Sj6XTsDLqQBNP8bN5yCeppNopiE=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, - "istanbul": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", - "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", - "dev": true, - "requires": { - "abbrev": "1.0.x", - "async": "1.x", - "escodegen": "1.8.x", - "esprima": "2.7.x", - "glob": "^5.0.15", - "handlebars": "^4.0.1", - "js-yaml": "3.x", - "mkdirp": "0.5.x", - "nopt": "3.x", - "once": "1.x", - "resolve": "1.1.x", - "supports-color": "^3.1.0", - "which": "^1.1.1", - "wordwrap": "^1.0.0" - }, - "dependencies": { - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - }, - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", - "dev": true, - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - } - } - }, - "istanbul-instrumenter-loader": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-instrumenter-loader/-/istanbul-instrumenter-loader-3.0.1.tgz", - "integrity": "sha512-a5SPObZgS0jB/ixaKSMdn6n/gXSrK2S6q/UfRJBT3e6gQmVjwZROTODQsYW5ZNwOu78hG62Y3fWlebaVOL0C+w==", - "dev": true, - "requires": { - "convert-source-map": "^1.5.0", - "istanbul-lib-instrument": "^1.7.3", - "loader-utils": "^1.1.0", - "schema-utils": "^0.3.0" - }, - "dependencies": { - "loader-utils": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", - "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", - "dev": true, - "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0" - } - } - } - }, - "istanbul-lib-instrument": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz", - "integrity": "sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==", - "dev": true, - "requires": { - "babel-generator": "^6.18.0", - "babel-template": "^6.16.0", - "babel-traverse": "^6.18.0", - "babel-types": "^6.18.0", - "babylon": "^6.18.0", - "istanbul-lib-coverage": "^1.2.1", - "semver": "^5.3.0" - }, - "dependencies": { - "istanbul-lib-coverage": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", - "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", - "dev": true - } - } - }, - "jasmine-core": { - "version": "2.99.1", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.99.1.tgz", - "integrity": "sha1-5kAN8ea1bhMLYcS80JPap/boyhU=", - "dev": true - }, - "javascript-detect-element-resize": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/javascript-detect-element-resize/-/javascript-detect-element-resize-0.5.3.tgz", - "integrity": "sha1-GnHNUd/lZZB/KZAS/nOilBBAJd4=" - }, - "javascript-natural-sort": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", - "integrity": "sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k=" - }, - "jquery": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.4.1.tgz", - "integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==" - }, - "jquery-mousewheel": { - "version": "3.1.13", - "resolved": "https://registry.npmjs.org/jquery-mousewheel/-/jquery-mousewheel-3.1.13.tgz", - "integrity": "sha1-BvAzXxbjU6aV5yBr9QUDy1I6buU=" - }, - "jquery-ui": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/jquery-ui/-/jquery-ui-1.12.1.tgz", - "integrity": "sha1-vLQEXI3QU5wTS8FIjN0+dop6nlE=" - }, - "js-base64": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.5.tgz", - "integrity": "sha512-aUnNwqMOXw3yvErjMPSQu6qIIzUmT1e5KcU1OZxRDU1g/am6mzBvcrmLAYwzmB59BHPrh5/tKaiF4OPhqRWESQ==", - "dev": true - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true, - "optional": true - }, - "jsesc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", - "dev": true - }, - "jshint": { - "version": "2.10.2", - "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.10.2.tgz", - "integrity": "sha512-e7KZgCSXMJxznE/4WULzybCMNXNAd/bf5TSrvVEq78Q/K8ZwFpmBqQeDtNiHc3l49nV4E/+YeHU/JZjSUIrLAA==", - "dev": true, - "requires": { - "cli": "~1.0.0", - "console-browserify": "1.1.x", - "exit": "0.1.x", - "htmlparser2": "3.8.x", - "lodash": "~4.17.11", - "minimatch": "~3.0.2", - "shelljs": "0.3.x", - "strip-json-comments": "1.0.x" - }, - "dependencies": { - "strip-json-comments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", - "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", - "dev": true - } - } - }, - "jshint-stylish": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/jshint-stylish/-/jshint-stylish-2.2.1.tgz", - "integrity": "sha1-JCCCosA1rgP9gQROBXDMQgjPbmE=", - "dev": true, - "requires": { - "beeper": "^1.1.0", - "chalk": "^1.0.0", - "log-symbols": "^1.0.0", - "plur": "^2.1.0", - "string-length": "^1.0.0", - "text-table": "^0.2.0" - } - }, - "json-loader": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz", - "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==", - "dev": true - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", - "dev": true - }, - "json-stable-stringify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "dev": true, - "optional": true, - "requires": { - "jsonify": "~0.0.0" - } - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "json3": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", - "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", - "dev": true - }, - "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=" - }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", - "dev": true, - "optional": true - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "jstimezonedetect": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/jstimezonedetect/-/jstimezonedetect-1.0.5.tgz", - "integrity": "sha1-k9A1zSDox9ZOsTdc9ap6EKAkRmo=" - }, - "karma": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/karma/-/karma-1.7.1.tgz", - "integrity": "sha512-k5pBjHDhmkdaUccnC7gE3mBzZjcxyxYsYVaqiL2G5AqlfLyBO5nw2VdNK+O16cveEPd/gIOWULH7gkiYYwVNHg==", - "dev": true, - "requires": { - "bluebird": "^3.3.0", - "body-parser": "^1.16.1", - "chokidar": "^1.4.1", - "colors": "^1.1.0", - "combine-lists": "^1.0.0", - "connect": "^3.6.0", - "core-js": "^2.2.0", - "di": "^0.0.1", - "dom-serialize": "^2.2.0", - "expand-braces": "^0.1.1", - "glob": "^7.1.1", - "graceful-fs": "^4.1.2", - "http-proxy": "^1.13.0", - "isbinaryfile": "^3.0.0", - "lodash": "^3.8.0", - "log4js": "^0.6.31", - "mime": "^1.3.4", - "minimatch": "^3.0.2", - "optimist": "^0.6.1", - "qjobs": "^1.1.4", - "range-parser": "^1.2.0", - "rimraf": "^2.6.0", - "safe-buffer": "^5.0.1", - "socket.io": "1.7.3", - "source-map": "^0.5.3", - "tmp": "0.0.31", - "useragent": "^2.1.12" - }, - "dependencies": { - "lodash": { - "version": "3.8.0", - "resolved": "http://registry.npmjs.org/lodash/-/lodash-3.8.0.tgz", - "integrity": "sha1-N265i9zZOCqTZcM8TLglDeEyW5E=", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "tmp": { - "version": "0.0.31", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.31.tgz", - "integrity": "sha1-jzirlDjhcxXl29izZX6L+yd65Kc=", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.1" - } - } - } - }, - "karma-chrome-launcher": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz", - "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==", - "dev": true, - "requires": { - "fs-access": "^1.0.0", - "which": "^1.2.1" - } - }, - "karma-coverage": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-1.1.2.tgz", - "integrity": "sha512-eQawj4Cl3z/CjxslYy9ariU4uDh7cCNFZHNWXWRpl0pNeblY/4wHR7M7boTYXWrn9bY0z2pZmr11eKje/S/hIw==", - "dev": true, - "requires": { - "dateformat": "^1.0.6", - "istanbul": "^0.4.0", - "lodash": "^4.17.0", - "minimatch": "^3.0.0", - "source-map": "^0.5.1" - }, - "dependencies": { - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "karma-firefox-launcher": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-1.1.0.tgz", - "integrity": "sha512-LbZ5/XlIXLeQ3cqnCbYLn+rOVhuMIK9aZwlP6eOLGzWdo1UVp7t6CN3DP4SafiRLjexKwHeKHDm0c38Mtd3VxA==", - "dev": true - }, - "karma-html2js-preprocessor": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/karma-html2js-preprocessor/-/karma-html2js-preprocessor-1.1.0.tgz", - "integrity": "sha1-/Ant8Eu+K7bu6boZaPgmtziAIL0=", - "dev": true - }, - "karma-jasmine": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-1.1.2.tgz", - "integrity": "sha1-OU8rJf+0pkS5rabyLUQ+L9CIhsM=", - "dev": true - }, - "karma-junit-reporter": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/karma-junit-reporter/-/karma-junit-reporter-1.2.0.tgz", - "integrity": "sha1-T5xAzt+xo5X4rvh2q/lhiZF8Y5Y=", - "dev": true, - "requires": { - "path-is-absolute": "^1.0.0", - "xmlbuilder": "8.2.2" - } - }, - "karma-sourcemap-loader": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/karma-sourcemap-loader/-/karma-sourcemap-loader-0.3.7.tgz", - "integrity": "sha1-kTIsd/jxPUb+0GKwQuEAnUxFBdg=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2" - } - }, - "karma-webpack": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/karma-webpack/-/karma-webpack-2.0.13.tgz", - "integrity": "sha512-2cyII34jfrAabbI2+4Rk4j95Nazl98FvZQhgSiqKUDarT317rxfv/EdzZ60CyATN4PQxJdO5ucR5bOOXkEVrXw==", - "dev": true, - "requires": { - "async": "^2.0.0", - "babel-runtime": "^6.0.0", - "loader-utils": "^1.0.0", - "lodash": "^4.0.0", - "source-map": "^0.5.6", - "webpack-dev-middleware": "^1.12.0" - }, - "dependencies": { - "loader-utils": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", - "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", - "dev": true, - "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0" - } - }, - "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "kew": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz", - "integrity": "sha1-edk9LTM2PW/dKXCzNdkUGtWR15s=", - "dev": true - }, - "killable": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.0.tgz", - "integrity": "sha1-2ouEvUfeU5WHj5XWTQLyRJ/gXms=", - "dev": true - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - }, - "lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", - "dev": true - }, - "lazystream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", - "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", - "dev": true, - "requires": { - "readable-stream": "^2.0.5" - } - }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true, - "requires": { - "invert-kv": "^1.0.0" - } - }, - "legacy-loader": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/legacy-loader/-/legacy-loader-0.0.2.tgz", - "integrity": "sha1-iEVZAGIV6uqlBVjXCbSmbmcw3E4=", - "requires": { - "loader-utils": "^0.2.6", - "source-map": "^0.3.0" - } - }, - "less": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/less/-/less-2.7.3.tgz", - "integrity": "sha512-KPdIJKWcEAb02TuJtaLrhue0krtRLoRoo7x6BNJIBelO00t/CCdJQUnHW5V34OnHMWzIktSalJxRO+FvytQlCQ==", - "dev": true, - "requires": { - "errno": "^0.1.1", - "graceful-fs": "^4.1.2", - "image-size": "~0.5.0", - "mime": "^1.2.11", - "mkdirp": "^0.5.0", - "promise": "^7.1.1", - "request": "2.81.0", - "source-map": "^0.5.3" - }, - "dependencies": { - "ajv": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", - "dev": true, - "optional": true, - "requires": { - "co": "^4.6.0", - "json-stable-stringify": "^1.0.1" - } - }, - "assert-plus": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", - "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", - "dev": true, - "optional": true - }, - "aws-sign2": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", - "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", - "dev": true, - "optional": true - }, - "form-data": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", - "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", - "dev": true, - "optional": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.5", - "mime-types": "^2.1.12" - } - }, - "har-schema": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", - "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=", - "dev": true, - "optional": true - }, - "har-validator": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", - "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", - "dev": true, - "optional": true, - "requires": { - "ajv": "^4.9.1", - "har-schema": "^1.0.5" - } - }, - "http-signature": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", - "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", - "dev": true, - "optional": true, - "requires": { - "assert-plus": "^0.2.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "performance-now": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", - "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=", - "dev": true, - "optional": true - }, - "qs": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", - "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=", - "dev": true, - "optional": true - }, - "request": { - "version": "2.81.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", - "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", - "dev": true, - "optional": true, - "requires": { - "aws-sign2": "~0.6.0", - "aws4": "^1.2.1", - "caseless": "~0.12.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.0", - "forever-agent": "~0.6.1", - "form-data": "~2.1.1", - "har-validator": "~4.2.1", - "hawk": "~3.1.3", - "http-signature": "~1.1.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.7", - "oauth-sign": "~0.8.1", - "performance-now": "^0.2.0", - "qs": "~6.4.0", - "safe-buffer": "^5.0.1", - "stringstream": "~0.0.4", - "tough-cookie": "~2.3.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.0.0" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "optional": true - } - } - }, - "less-loader": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-4.1.0.tgz", - "integrity": "sha512-KNTsgCE9tMOM70+ddxp9yyt9iHqgmSs0yTZc5XH5Wo+g80RWRIYNqE58QJKm/yMud5wZEvz50ugRDuzVIkyahg==", - "dev": true, - "requires": { - "clone": "^2.1.1", - "loader-utils": "^1.1.0", - "pify": "^3.0.0" - }, - "dependencies": { - "clone": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", - "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", - "dev": true - }, - "loader-utils": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", - "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", - "dev": true, - "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0" - } - } - } - }, - "less-plugin-autoprefix": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/less-plugin-autoprefix/-/less-plugin-autoprefix-1.5.1.tgz", - "integrity": "sha1-vKTlsuSMrGlloXgxQuOzLDwAzgc=", - "dev": true, - "requires": { - "autoprefixer": "^6.0.0", - "postcss": "^5.0.0" - } - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "load-grunt-configs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/load-grunt-configs/-/load-grunt-configs-1.0.0.tgz", - "integrity": "sha1-vbn67BSrcg1TiNZ8LqvdUQdxeBk=", - "dev": true, - "requires": { - "cson-parser": "^1.0.9", - "inquirer": "^0.8.2", - "js-yaml": "^3.2.7", - "lodash": "^3.6.0", - "rimraf": "^2.3.2" - }, - "dependencies": { - "ansi-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz", - "integrity": "sha1-QchHGUZGN15qGl0Qw8oFTvn8mA0=", - "dev": true - }, - "cli-width": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-1.1.1.tgz", - "integrity": "sha1-pNKT72frt7iNSk1CwMzwDE0eNm0=", - "dev": true - }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - } - }, - "inquirer": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.8.5.tgz", - "integrity": "sha1-29dAz2yjtzEpamPOb22WGFHzNt8=", - "dev": true, - "requires": { - "ansi-regex": "^1.1.1", - "chalk": "^1.0.0", - "cli-width": "^1.0.1", - "figures": "^1.3.5", - "lodash": "^3.3.1", - "readline2": "^0.1.1", - "rx": "^2.4.3", - "through": "^2.3.6" - } - }, - "lodash": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", - "dev": true - } - } - }, - "load-grunt-tasks": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/load-grunt-tasks/-/load-grunt-tasks-3.5.2.tgz", - "integrity": "sha1-ByhWEYD9IP+KaSdQWFL8WKrqDIg=", - "dev": true, - "requires": { - "arrify": "^1.0.0", - "multimatch": "^2.0.0", - "pkg-up": "^1.0.0", - "resolve-pkg": "^0.1.0" - } - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, - "loader-fs-cache": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/loader-fs-cache/-/loader-fs-cache-1.0.1.tgz", - "integrity": "sha1-VuC/CL2XCLJqdltoUJhAyN7J/bw=", - "dev": true, - "requires": { - "find-cache-dir": "^0.1.1", - "mkdirp": "0.5.1" - }, - "dependencies": { - "find-cache-dir": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-0.1.1.tgz", - "integrity": "sha1-yN765XyKUqinhPnjHFfHQumToLk=", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "mkdirp": "^0.5.1", - "pkg-dir": "^1.0.0" - } - }, - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "requires": { - "pinkie-promise": "^2.0.0" - } - }, - "pkg-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", - "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", - "dev": true, - "requires": { - "find-up": "^1.0.0" - } - } - } - }, - "loader-runner": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.0.tgz", - "integrity": "sha1-9IKuqC1UPgeSFwDVpG7yb9rGuKI=", - "dev": true - }, - "loader-utils": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", - "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", - "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0", - "object-assign": "^4.0.1" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" - }, - "lodash._arraycopy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz", - "integrity": "sha1-due3wfH7klRzdIeKVi7Qaj5Q9uE=", - "dev": true - }, - "lodash._arrayeach": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz", - "integrity": "sha1-urFWsqkNPxu9XGU0AzSeXlkz754=", - "dev": true - }, - "lodash._baseassign": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", - "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", - "dev": true, - "requires": { - "lodash._basecopy": "^3.0.0", - "lodash.keys": "^3.0.0" - } - }, - "lodash._baseclone": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lodash._baseclone/-/lodash._baseclone-3.3.0.tgz", - "integrity": "sha1-MDUZv2OT/n5C802LYw73eU41Qrc=", - "dev": true, - "requires": { - "lodash._arraycopy": "^3.0.0", - "lodash._arrayeach": "^3.0.0", - "lodash._baseassign": "^3.0.0", - "lodash._basefor": "^3.0.0", - "lodash.isarray": "^3.0.0", - "lodash.keys": "^3.0.0" - } - }, - "lodash._basecopy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", - "dev": true - }, - "lodash._basecreate": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", - "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", - "dev": true - }, - "lodash._basefor": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash._basefor/-/lodash._basefor-3.0.3.tgz", - "integrity": "sha1-dVC06SGO8J+tJDQ7YSAhx5tMIMI=", - "dev": true - }, - "lodash._baseget": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/lodash._baseget/-/lodash._baseget-3.7.2.tgz", - "integrity": "sha1-G2rh1frPPCVTI1ChPBGXy4u2dPQ=", - "dev": true - }, - "lodash._bindcallback": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", - "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=", - "dev": true - }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", - "dev": true - }, - "lodash._isiterateecall": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", - "dev": true - }, - "lodash._stack": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/lodash._stack/-/lodash._stack-4.1.3.tgz", - "integrity": "sha1-dRqnbBuWSwR+dtFPxyoJP8teLdA=", - "dev": true - }, - "lodash._topath": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/lodash._topath/-/lodash._topath-3.8.1.tgz", - "integrity": "sha1-PsXiYGAU9MuX91X+aRTt2L/ADqw=", - "dev": true, - "requires": { - "lodash.isarray": "^3.0.0" - } - }, - "lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", - "dev": true - }, - "lodash.clone": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-3.0.3.tgz", - "integrity": "sha1-hGiMc9MrWpDKJWFpY/GJJSqZcEM=", - "dev": true, - "requires": { - "lodash._baseclone": "^3.0.0", - "lodash._bindcallback": "^3.0.0", - "lodash._isiterateecall": "^3.0.0" - } - }, - "lodash.create": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", - "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", - "dev": true, - "requires": { - "lodash._baseassign": "^3.0.0", - "lodash._basecreate": "^3.0.0", - "lodash._isiterateecall": "^3.0.0" - } - }, - "lodash.defaultsdeep": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.3.2.tgz", - "integrity": "sha1-bBpYbmxWR7DmTi15gUG4g2FYvoo=", - "dev": true, - "requires": { - "lodash._baseclone": "^4.0.0", - "lodash._stack": "^4.0.0", - "lodash.isplainobject": "^4.0.0", - "lodash.keysin": "^4.0.0", - "lodash.mergewith": "^4.0.0", - "lodash.rest": "^4.0.0" - }, - "dependencies": { - "lodash._baseclone": { - "version": "4.5.7", - "resolved": "https://registry.npmjs.org/lodash._baseclone/-/lodash._baseclone-4.5.7.tgz", - "integrity": "sha1-zkKt4IOE711i+nfDD2GkbmhvhDQ=", - "dev": true - } - } - }, - "lodash.get": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-3.7.0.tgz", - "integrity": "sha1-POaK4skWg7KBzFOUEoMDy/deaR8=", - "dev": true, - "requires": { - "lodash._baseget": "^3.0.0", - "lodash._topath": "^3.0.0" - } - }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", - "dev": true - }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", - "dev": true - }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", - "dev": true - }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "dev": true, - "requires": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, - "lodash.keysin": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.keysin/-/lodash.keysin-4.2.0.tgz", - "integrity": "sha1-jMP7NcLZSsxEOhhj4C+kB5nqbyg=", - "dev": true - }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", - "dev": true - }, - "lodash.mergewith": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz", - "integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==", - "dev": true - }, - "lodash.rest": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/lodash.rest/-/lodash.rest-4.0.5.tgz", - "integrity": "sha1-lU73UEkmIDjJbR/Jiyj9r58Hcqo=", - "dev": true - }, - "lodash.unescape": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz", - "integrity": "sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=", - "dev": true - }, - "lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", - "dev": true - }, - "log-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", - "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", - "dev": true, - "requires": { - "chalk": "^1.0.0" - } - }, - "log4js": { - "version": "0.6.38", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-0.6.38.tgz", - "integrity": "sha1-LElBFmldb7JUgJQ9P8hy5mKlIv0=", - "dev": true, - "requires": { - "readable-stream": "~1.0.2", - "semver": "~4.3.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "semver": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", - "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=", - "dev": true - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - } - } - }, - "loglevel": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz", - "integrity": "sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=", - "dev": true - }, - "longest": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", - "dev": true - }, - "loose-envify": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", - "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", - "dev": true, - "requires": { - "js-tokens": "^3.0.0" - } - }, - "loud-rejection": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", - "dev": true, - "requires": { - "currently-unhandled": "^0.4.1", - "signal-exit": "^3.0.0" - } - }, - "lower-case": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", - "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", - "dev": true - }, - "lr-infinite-scroll": { - "version": "git+https://git@github.com/lorenzofox3/lrInfiniteScroll.git#59d348bc5c18f164438d2a30f1fd79b333c3b649", - "from": "git+https://git@github.com/lorenzofox3/lrInfiniteScroll.git" - }, - "lru-cache": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", - "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "make-dir": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", - "dev": true, - "requires": { - "pify": "^3.0.0" - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true - }, - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "requires": { - "object-visit": "^1.0.0" - } - }, - "math-expression-evaluator": { - "version": "1.2.17", - "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz", - "integrity": "sha1-3oGf282E3M2PrlnGrreWFbnSZqw=", - "dev": true - }, - "math-random": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz", - "integrity": "sha1-izqsWIuKZuSXXjzepn97sylgH6w=", - "dev": true - }, - "mathjs": { - "version": "3.20.2", - "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-3.20.2.tgz", - "integrity": "sha512-3f6/+uf1cUtIz1rYFz775wekl/UEDSQ3mU6xdxW7qzpvvhc2v28i3UtLsGTRB+u8OqDWoSX6Dz8gehaGFs6tCA==", - "requires": { - "complex.js": "2.0.4", - "decimal.js": "9.0.1", - "escape-latex": "^1.0.0", - "fraction.js": "4.0.4", - "javascript-natural-sort": "0.7.1", - "seed-random": "2.2.0", - "tiny-emitter": "2.0.2", - "typed-function": "0.10.7" - } - }, - "md5.js": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", - "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=", - "dev": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "dev": true - }, - "mem": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "memory-fs": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.2.0.tgz", - "integrity": "sha1-8rslNovBIeORwlIN6Slpyu4KApA=", - "dev": true - }, - "meow": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", - "dev": true, - "requires": { - "camelcase-keys": "^2.0.0", - "decamelize": "^1.1.2", - "loud-rejection": "^1.0.0", - "map-obj": "^1.0.1", - "minimist": "^1.1.3", - "normalize-package-data": "^2.3.4", - "object-assign": "^4.0.1", - "read-pkg-up": "^1.0.1", - "redent": "^1.0.0", - "trim-newlines": "^1.0.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } - } - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", - "dev": true - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dev": true, - "requires": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - } - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true - }, - "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", - "dev": true - }, - "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", - "dev": true, - "requires": { - "mime-db": "~1.33.0" - } - }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true - }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "minipass": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", - "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - }, - "dependencies": { - "yallist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", - "dev": true - } - } - }, - "minizlib": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", - "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "mississippi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-2.0.0.tgz", - "integrity": "sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==", - "dev": true, - "requires": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^2.0.1", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" - } - }, - "mixin-deep": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", - "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", - "dev": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "mkpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mkpath/-/mkpath-1.0.0.tgz", - "integrity": "sha1-67Opd+evHGg65v2hK1Raa6bFhT0=", - "dev": true - }, - "mocha-nightwatch": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/mocha-nightwatch/-/mocha-nightwatch-3.2.2.tgz", - "integrity": "sha1-kby5s73gV912d8eBJeSR5Y1mZHw=", - "dev": true, - "requires": { - "browser-stdout": "1.3.0", - "commander": "2.9.0", - "debug": "2.2.0", - "diff": "1.4.0", - "escape-string-regexp": "1.0.5", - "glob": "7.0.5", - "growl": "1.9.2", - "json3": "3.3.2", - "lodash.create": "3.1.1", - "mkdirp": "0.5.1", - "supports-color": "3.1.2" - }, - "dependencies": { - "commander": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", - "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", - "dev": true, - "requires": { - "graceful-readlink": ">= 1.0.0" - } - }, - "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", - "dev": true, - "requires": { - "ms": "0.7.1" - } - }, - "glob": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.5.tgz", - "integrity": "sha1-tCAqaQmbu00pKnwblbZoK2fr3JU=", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.2", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", - "dev": true - }, - "supports-color": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", - "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - } - } - }, - "moment": { - "version": "2.22.2", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", - "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=" - }, - "move-concurrently": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", - "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", - "dev": true, - "requires": { - "aproba": "^1.1.1", - "copy-concurrently": "^1.0.0", - "fs-write-stream-atomic": "^1.0.8", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.3" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "multi-glob": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/multi-glob/-/multi-glob-1.0.1.tgz", - "integrity": "sha1-5n0qtEKdJ2BubrTbNQlK/JF4h1A=", - "dev": true, - "requires": { - "async": "1.x", - "glob": "5.x", - "lodash": "3.x" - }, - "dependencies": { - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - }, - "glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", - "dev": true, - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "lodash": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", - "dev": true - } - } - }, - "multicast-dns": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", - "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", - "dev": true, - "requires": { - "dns-packet": "^1.3.1", - "thunky": "^1.0.2" - } - }, - "multicast-dns-service-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", - "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", - "dev": true - }, - "multimatch": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz", - "integrity": "sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis=", - "dev": true, - "requires": { - "array-differ": "^1.0.0", - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "minimatch": "^3.0.0" - } - }, - "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", - "dev": true - }, - "nan": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", - "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", - "dev": true, - "optional": true - }, - "nanomatch": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz", - "integrity": "sha512-n8R9bS8yQ6eSXaV6jHUpKzD8gLsin02w1HSFiegwrs9E098Ylhw5jdyKPaYqvHknHaSCKTPp7C8dGCQ0q9koXA==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-odd": "^2.0.0", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "negotiator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", - "dev": true - }, - "neo-async": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.5.1.tgz", - "integrity": "sha512-3KL3fvuRkZ7s4IFOMfztb7zJp3QaVWnBeGoJlgB38XnCRPj/0tLzzLG5IB8NYOHbJ8g8UGrgZv44GLDk6CxTxA==", - "dev": true - }, - "netmask": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-1.0.6.tgz", - "integrity": "sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU=", - "dev": true - }, - "next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", - "dev": true - }, - "ng-toast": { - "version": "git+https://git@github.com/ansible/ngToast.git#2c2038381d5cfcab26fdefe98e0408a52e71daa5", - "from": "git+https://git@github.com/ansible/ngToast.git#v2.1.1", - "requires": { - "angular": "~1.6.6", - "angular-sanitize": "~1.6.6" - }, - "dependencies": { - "angular": { - "version": "1.6.10", - "resolved": "https://registry.npmjs.org/angular/-/angular-1.6.10.tgz", - "integrity": "sha512-PCZ5/hVdvPQiYyH0VwsPjrErPHRcITnaXxhksceOXgtJeesKHLA7KDu4X/yvcAi+1zdGgGF+9pDxkJvghXI9Wg==" - }, - "angular-sanitize": { - "version": "1.6.10", - "resolved": "https://registry.npmjs.org/angular-sanitize/-/angular-sanitize-1.6.10.tgz", - "integrity": "sha512-01i1Xoq9ykUrsoYQMSB6dWZmPp9Df5hfCqMAGGzJBWZ7L2WY0OtUphdI0YvR8ZF9lAsWtGNtsEFilObjq5nTgQ==" - } - } - }, - "ngtemplate-loader": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ngtemplate-loader/-/ngtemplate-loader-2.0.1.tgz", - "integrity": "sha1-nX7S6KI2NSOte2TXSqxALY2v8/M=", - "dev": true, - "requires": { - "jsesc": "^0.5.0", - "loader-utils": "^1.0.2" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true - }, - "loader-utils": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", - "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", - "dev": true, - "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0" - } - } - } - }, - "nightwatch": { - "version": "0.9.21", - "resolved": "https://registry.npmjs.org/nightwatch/-/nightwatch-0.9.21.tgz", - "integrity": "sha1-nnlKdRS0/V9GYC02jlBRUjKrnpA=", - "dev": true, - "requires": { - "chai-nightwatch": "~0.1.x", - "ejs": "2.5.7", - "lodash.clone": "3.0.3", - "lodash.defaultsdeep": "4.3.2", - "minimatch": "3.0.3", - "mkpath": "1.0.0", - "mocha-nightwatch": "3.2.2", - "optimist": "0.6.1", - "proxy-agent": "2.0.0", - "q": "1.4.1" - }, - "dependencies": { - "minimatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha1-Kk5AkLlrLbBqnX3wEFWmKnfJt3Q=", - "dev": true, - "requires": { - "brace-expansion": "^1.0.0" - } - }, - "q": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", - "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=", - "dev": true - } - } - }, - "no-case": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", - "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", - "dev": true, - "requires": { - "lower-case": "^1.1.1" - } - }, - "node-forge": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz", - "integrity": "sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ==", - "dev": true - }, - "node-libs-browser": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.1.0.tgz", - "integrity": "sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==", - "dev": true, - "requires": { - "assert": "^1.1.1", - "browserify-zlib": "^0.2.0", - "buffer": "^4.3.0", - "console-browserify": "^1.1.0", - "constants-browserify": "^1.0.0", - "crypto-browserify": "^3.11.0", - "domain-browser": "^1.1.1", - "events": "^1.0.0", - "https-browserify": "^1.0.0", - "os-browserify": "^0.3.0", - "path-browserify": "0.0.0", - "process": "^0.11.10", - "punycode": "^1.2.4", - "querystring-es3": "^0.2.0", - "readable-stream": "^2.3.3", - "stream-browserify": "^2.0.1", - "stream-http": "^2.7.2", - "string_decoder": "^1.0.0", - "timers-browserify": "^2.0.4", - "tty-browserify": "0.0.0", - "url": "^0.11.0", - "util": "^0.10.3", - "vm-browserify": "0.0.4" - } - }, - "node-object-hash": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/node-object-hash/-/node-object-hash-1.4.1.tgz", - "integrity": "sha512-JQVqSM5/mOaUoUhCYR0t1vgm8RFo7qpJtPvnoFCLeqQh1xrfmr3BCD3nGBnACzpIEF7F7EVgqGD3O4lao/BY/A==", - "dev": true - }, - "nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", - "dev": true, - "requires": { - "abbrev": "1" - } - }, - "normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "is-builtin-module": "^1.0.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, - "normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", - "dev": true - }, - "normalize-url": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", - "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", - "dev": true, - "requires": { - "object-assign": "^4.0.1", - "prepend-http": "^1.0.0", - "query-string": "^4.1.0", - "sort-keys": "^1.0.0" - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "nth-check": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", - "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", - "dev": true, - "requires": { - "boolbase": "~1.0.0" - } - }, - "null-check": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz", - "integrity": "sha1-l33/1xdgErnsMNKjnbXPcqBDnt0=", - "dev": true - }, - "num2fraction": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", - "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", - "dev": true - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, - "nunjucks": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.1.3.tgz", - "integrity": "sha512-UtlKKAzg9vdtvURdNy9DjGhiB7qYf2R7Ez+hsucOQG5gYJexSggXSSZ+9IpSDyKOlWu/4rMVPH2oVoANOSqNKA==", - "dev": true, - "requires": { - "a-sync-waterfall": "^1.0.0", - "asap": "^2.0.3", - "chokidar": "^2.0.0", - "postinstall-build": "^5.0.1", - "yargs": "^3.32.0" - }, - "dependencies": { - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "optional": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", - "dev": true - }, - "chokidar": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.3.tgz", - "integrity": "sha512-zW8iXYZtXMx4kux/nuZVXjkLP+CyIK5Al5FHnj1OgTKGZfp4Oy6/ymtMSKFv3GD8DviEmUPmJg9eFdJ/JzudMg==", - "dev": true, - "optional": true, - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.0", - "braces": "^2.3.0", - "fsevents": "^1.1.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.1", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^2.1.1", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.0.0", - "upath": "^1.0.0" - } - }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dev": true, - "optional": true, - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "optional": true, - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "window-size": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz", - "integrity": "sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY=", - "dev": true - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true - }, - "yargs": { - "version": "3.32.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", - "integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=", - "dev": true, - "requires": { - "camelcase": "^2.0.1", - "cliui": "^3.0.3", - "decamelize": "^1.1.1", - "os-locale": "^1.4.0", - "string-width": "^1.0.1", - "window-size": "^0.1.4", - "y18n": "^3.2.0" - } - } - } - }, - "nvd3": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/nvd3/-/nvd3-1.8.6.tgz", - "integrity": "sha1-LT66dL8zNjtRAevx0JPFmlOuc8Q=" - }, - "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "object-component": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", - "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=", - "dev": true - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "object-hash": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.0.tgz", - "integrity": "sha512-05KzQ70lSeGSrZJQXE5wNDiTkBJDlUT/myi6RX9dVIvz7a7Qh4oH93BQdiPMn27nldYvVQCKMUaM83AfizZlsQ==", - "dev": true - }, - "object-keys": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", - "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=", - "dev": true - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "requires": { - "isobject": "^3.0.0" - } - }, - "object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", - "dev": true, - "requires": { - "for-own": "^0.1.4", - "is-extendable": "^0.1.1" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", - "dev": true - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "on-headers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", - "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "opn": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz", - "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==", - "dev": true, - "requires": { - "is-wsl": "^1.1.0" - } - }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - }, - "dependencies": { - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true - } - } - }, - "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "wordwrap": "~1.0.0" - } - }, - "options": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", - "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=", - "dev": true - }, - "original": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/original/-/original-1.0.1.tgz", - "integrity": "sha512-IEvtB5vM5ULvwnqMxWBLxkS13JIEXbakizMSo3yoPNPCIWzg8TG3Usn/UhXoZFM/m+FuEA20KdzPSFq/0rS+UA==", - "dev": true, - "requires": { - "url-parse": "~1.4.0" - } - }, - "os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", - "dev": true - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true - }, - "os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", - "dev": true, - "requires": { - "lcid": "^1.0.0" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-map": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", - "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", - "dev": true - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "pac-proxy-agent": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-1.1.0.tgz", - "integrity": "sha512-QBELCWyLYPgE2Gj+4wUEiMscHrQ8nRPBzYItQNOHWavwBt25ohZHQC4qnd5IszdVVrFbLsQ+dPkm6eqdjJAmwQ==", - "dev": true, - "requires": { - "agent-base": "2", - "debug": "2", - "extend": "3", - "get-uri": "2", - "http-proxy-agent": "1", - "https-proxy-agent": "1", - "pac-resolver": "~2.0.0", - "raw-body": "2", - "socks-proxy-agent": "2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "pac-resolver": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-2.0.0.tgz", - "integrity": "sha1-mbiNLxk/ve78HJpSnB8yYKtSd80=", - "dev": true, - "requires": { - "co": "~3.0.6", - "degenerator": "~1.0.2", - "ip": "1.0.1", - "netmask": "~1.0.4", - "thunkify": "~2.1.1" - }, - "dependencies": { - "co": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/co/-/co-3.0.6.tgz", - "integrity": "sha1-FEXyJsXrlWE45oyawwFn6n0ua9o=", - "dev": true - } - } - }, - "pad-stream": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pad-stream/-/pad-stream-1.2.0.tgz", - "integrity": "sha1-Yx3Mn3mBC3BZZeid7eps/w/B38k=", - "dev": true, - "requires": { - "meow": "^3.0.0", - "pumpify": "^1.3.3", - "repeating": "^2.0.0", - "split2": "^1.0.0", - "through2": "^2.0.0" - } - }, - "pako": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", - "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==", - "dev": true - }, - "parallel-transform": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz", - "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", - "dev": true, - "requires": { - "cyclist": "~0.2.2", - "inherits": "^2.0.3", - "readable-stream": "^2.1.5" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "param-case": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", - "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", - "dev": true, - "requires": { - "no-case": "^2.2.0" - } - }, - "parse-asn1": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", - "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", - "dev": true, - "requires": { - "asn1.js": "^4.0.0", - "browserify-aes": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3" - } - }, - "parse-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", - "dev": true, - "requires": { - "glob-base": "^0.3.0", - "is-dotfile": "^1.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.0" - }, - "dependencies": { - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - } - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "requires": { - "error-ex": "^1.2.0" - } - }, - "parse-ms": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-1.0.1.tgz", - "integrity": "sha1-VjRtR0nXjyNDDKDHE4UK75GqNh0=", - "dev": true - }, - "parsejson": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/parsejson/-/parsejson-0.0.3.tgz", - "integrity": "sha1-q343WfIJ7OmUN5c/fQ8fZK4OZKs=", - "dev": true, - "requires": { - "better-assert": "~1.0.0" - } - }, - "parseqs": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", - "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", - "dev": true, - "requires": { - "better-assert": "~1.0.0" - } - }, - "parseuri": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", - "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", - "dev": true, - "requires": { - "better-assert": "~1.0.0" - } - }, - "parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", - "dev": true - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true - }, - "path-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", - "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", - "dev": true - }, - "path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "path-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", - "dev": true - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", - "dev": true - }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, - "pbkdf2": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.16.tgz", - "integrity": "sha512-y4CXP3thSxqf7c0qmOF+9UeOTrifiVTIM+u7NWlq+PRsHbr7r7dpCmvzrZxa96JJUNi0Y5w9VqG5ZNeCVMoDcA==", - "dev": true, - "requires": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", - "dev": true - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, - "requires": { - "pinkie": "^2.0.0" - } - }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "dev": true, - "requires": { - "find-up": "^2.1.0" - } - }, - "pkg-up": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-1.0.0.tgz", - "integrity": "sha1-Pgj7RhUlxEIWJKM7n35tCvWwWiY=", - "dev": true, - "requires": { - "find-up": "^1.0.0" - }, - "dependencies": { - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "requires": { - "pinkie-promise": "^2.0.0" - } - } - } - }, - "plur": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/plur/-/plur-2.1.2.tgz", - "integrity": "sha1-dIJFLBoPUI4+NE6uwxLJHCncZVo=", - "dev": true, - "requires": { - "irregular-plurals": "^1.0.0" - } - }, - "pluralize": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", - "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", - "dev": true - }, - "pofile": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pofile/-/pofile-1.0.11.tgz", - "integrity": "sha512-Vy9eH1dRD9wHjYt/QqXcTz+RnX/zg53xK+KljFSX30PvdDMb2z+c6uDUeblUGqqJgz3QFsdlA0IJvHziPmWtQg==", - "dev": true - }, - "popper.js": { - "version": "1.14.6", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.6.tgz", - "integrity": "sha512-AGwHGQBKumlk/MDfrSOf0JHhJCImdDMcGNoqKmKkU+68GFazv3CQ6q9r7Ja1sKDZmYWTckY/uLyEznheTDycnA==" - }, - "portfinder": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.13.tgz", - "integrity": "sha1-uzLs2HwnEErm7kS1o8y/Drsa7ek=", - "dev": true, - "requires": { - "async": "^1.5.2", - "debug": "^2.2.0", - "mkdirp": "0.5.x" - }, - "dependencies": { - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - } - } - }, - "postcss-calc": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-5.3.1.tgz", - "integrity": "sha1-d7rnypKK2FcW4v2kLyYb98HWW14=", - "dev": true, - "requires": { - "postcss": "^5.0.2", - "postcss-message-helpers": "^2.0.0", - "reduce-css-calc": "^1.2.6" - } - }, - "postcss-colormin": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-2.2.2.tgz", - "integrity": "sha1-ZjFBfV8OkJo9fsJrJMio0eT5bks=", - "dev": true, - "requires": { - "colormin": "^1.0.5", - "postcss": "^5.0.13", - "postcss-value-parser": "^3.2.3" - } - }, - "postcss-convert-values": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-2.6.1.tgz", - "integrity": "sha1-u9hZPFwf0uPRwyK7kl3K6Nrk1i0=", - "dev": true, - "requires": { - "postcss": "^5.0.11", - "postcss-value-parser": "^3.1.2" - } - }, - "postcss-discard-comments": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz", - "integrity": "sha1-vv6J+v1bPazlzM5Rt2uBUUvgDj0=", - "dev": true, - "requires": { - "postcss": "^5.0.14" - } - }, - "postcss-discard-duplicates": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-2.1.0.tgz", - "integrity": "sha1-uavye4isGIFYpesSq8riAmO5GTI=", - "dev": true, - "requires": { - "postcss": "^5.0.4" - } - }, - "postcss-discard-empty": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz", - "integrity": "sha1-0rS9nVztXr2Nyt52QMfXzX9PkrU=", - "dev": true, - "requires": { - "postcss": "^5.0.14" - } - }, - "postcss-discard-overridden": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz", - "integrity": "sha1-ix6vVU9ob7KIzYdMVWZ7CqNmjVg=", - "dev": true, - "requires": { - "postcss": "^5.0.16" - } - }, - "postcss-discard-unused": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz", - "integrity": "sha1-vOMLLMWR/8Y0Mitfs0ZLbZNPRDM=", - "dev": true, - "requires": { - "postcss": "^5.0.14", - "uniqs": "^2.0.0" - } - }, - "postcss-filter-plugins": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/postcss-filter-plugins/-/postcss-filter-plugins-2.0.3.tgz", - "integrity": "sha512-T53GVFsdinJhgwm7rg1BzbeBRomOg9y5MBVhGcsV0CxurUdVj1UlPdKtn7aqYA/c/QVkzKMjq2bSV5dKG5+AwQ==", - "dev": true, - "requires": { - "postcss": "^5.0.4" - } - }, - "postcss-merge-idents": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz", - "integrity": "sha1-TFUwMTwI4dWzu/PSu8dH4njuonA=", - "dev": true, - "requires": { - "has": "^1.0.1", - "postcss": "^5.0.10", - "postcss-value-parser": "^3.1.1" - } - }, - "postcss-merge-longhand": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-2.0.2.tgz", - "integrity": "sha1-I9kM0Sewp3mUkVMyc5A0oaTz1lg=", - "dev": true, - "requires": { - "postcss": "^5.0.4" - } - }, - "postcss-merge-rules": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-2.1.2.tgz", - "integrity": "sha1-0d9d+qexrMO+VT8OnhDofGG19yE=", - "dev": true, - "requires": { - "browserslist": "^1.5.2", - "caniuse-api": "^1.5.2", - "postcss": "^5.0.4", - "postcss-selector-parser": "^2.2.2", - "vendors": "^1.0.0" - }, - "dependencies": { - "browserslist": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", - "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", - "dev": true, - "requires": { - "caniuse-db": "^1.0.30000639", - "electron-to-chromium": "^1.2.7" - } - } - } - }, - "postcss-message-helpers": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz", - "integrity": "sha1-pPL0+rbk/gAvCu0ABHjN9S+bpg4=", - "dev": true - }, - "postcss-minify-font-values": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz", - "integrity": "sha1-S1jttWZB66fIR0qzUmyv17vey2k=", - "dev": true, - "requires": { - "object-assign": "^4.0.1", - "postcss": "^5.0.4", - "postcss-value-parser": "^3.0.2" - } - }, - "postcss-minify-gradients": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz", - "integrity": "sha1-Xb2hE3NwP4PPtKPqOIHY11/15uE=", - "dev": true, - "requires": { - "postcss": "^5.0.12", - "postcss-value-parser": "^3.3.0" - } - }, - "postcss-minify-params": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz", - "integrity": "sha1-rSzgcTc7lDs9kwo/pZo1jCjW8fM=", - "dev": true, - "requires": { - "alphanum-sort": "^1.0.1", - "postcss": "^5.0.2", - "postcss-value-parser": "^3.0.2", - "uniqs": "^2.0.0" - } - }, - "postcss-minify-selectors": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz", - "integrity": "sha1-ssapjAByz5G5MtGkllCBFDEXNb8=", - "dev": true, - "requires": { - "alphanum-sort": "^1.0.2", - "has": "^1.0.1", - "postcss": "^5.0.14", - "postcss-selector-parser": "^2.0.0" - } - }, - "postcss-modules-extract-imports": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.0.tgz", - "integrity": "sha1-ZhQOzs447wa/DT41XWm/WdFB6oU=", - "dev": true, - "requires": { - "postcss": "^6.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "postcss": { - "version": "6.0.22", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.22.tgz", - "integrity": "sha512-Toc9lLoUASwGqxBSJGTVcOQiDqjK+Z2XlWBg+IgYwQMY9vA2f7iMpXVc1GpPcfTSyM5lkxNo0oDwDRO+wm7XHA==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-modules-local-by-default": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz", - "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=", - "dev": true, - "requires": { - "css-selector-tokenizer": "^0.7.0", - "postcss": "^6.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "postcss": { - "version": "6.0.22", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.22.tgz", - "integrity": "sha512-Toc9lLoUASwGqxBSJGTVcOQiDqjK+Z2XlWBg+IgYwQMY9vA2f7iMpXVc1GpPcfTSyM5lkxNo0oDwDRO+wm7XHA==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-modules-scope": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz", - "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=", - "dev": true, - "requires": { - "css-selector-tokenizer": "^0.7.0", - "postcss": "^6.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "postcss": { - "version": "6.0.22", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.22.tgz", - "integrity": "sha512-Toc9lLoUASwGqxBSJGTVcOQiDqjK+Z2XlWBg+IgYwQMY9vA2f7iMpXVc1GpPcfTSyM5lkxNo0oDwDRO+wm7XHA==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-modules-values": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz", - "integrity": "sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA=", - "dev": true, - "requires": { - "icss-replace-symbols": "^1.1.0", - "postcss": "^6.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "postcss": { - "version": "6.0.22", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.22.tgz", - "integrity": "sha512-Toc9lLoUASwGqxBSJGTVcOQiDqjK+Z2XlWBg+IgYwQMY9vA2f7iMpXVc1GpPcfTSyM5lkxNo0oDwDRO+wm7XHA==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-normalize-charset": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz", - "integrity": "sha1-757nEhLX/nWceO0WL2HtYrXLk/E=", - "dev": true, - "requires": { - "postcss": "^5.0.5" - } - }, - "postcss-normalize-url": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz", - "integrity": "sha1-EI90s/L82viRov+j6kWSJ5/HgiI=", - "dev": true, - "requires": { - "is-absolute-url": "^2.0.0", - "normalize-url": "^1.4.0", - "postcss": "^5.0.14", - "postcss-value-parser": "^3.2.3" - } - }, - "postcss-ordered-values": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-2.2.3.tgz", - "integrity": "sha1-7sbCpntsQSqNsgQud/6NpD+VwR0=", - "dev": true, - "requires": { - "postcss": "^5.0.4", - "postcss-value-parser": "^3.0.1" - } - }, - "postcss-reduce-idents": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz", - "integrity": "sha1-wsbSDMlYKE9qv75j92Cb9AkFmtM=", - "dev": true, - "requires": { - "postcss": "^5.0.4", - "postcss-value-parser": "^3.0.2" - } - }, - "postcss-reduce-initial": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz", - "integrity": "sha1-aPgGlfBF0IJjqHmtJA343WT2ROo=", - "dev": true, - "requires": { - "postcss": "^5.0.4" - } - }, - "postcss-reduce-transforms": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz", - "integrity": "sha1-/3b02CEkN7McKYpC0uFEQCV3GuE=", - "dev": true, - "requires": { - "has": "^1.0.1", - "postcss": "^5.0.8", - "postcss-value-parser": "^3.0.1" - } - }, - "postcss-selector-parser": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz", - "integrity": "sha1-+UN3iGBsPJrO4W/+jYsWKX8nu5A=", - "dev": true, - "requires": { - "flatten": "^1.0.2", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - }, - "postcss-svgo": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-2.1.6.tgz", - "integrity": "sha1-tt8YqmE7Zm4TPwittSGcJoSsEI0=", - "dev": true, - "requires": { - "is-svg": "^2.0.0", - "postcss": "^5.0.14", - "postcss-value-parser": "^3.2.3", - "svgo": "^0.7.0" - } - }, - "postcss-unique-selectors": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz", - "integrity": "sha1-mB1X0p3csz57Hf4f1DuGSfkzyh0=", - "dev": true, - "requires": { - "alphanum-sort": "^1.0.1", - "postcss": "^5.0.4", - "uniqs": "^2.0.0" - } - }, - "postcss-value-parser": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz", - "integrity": "sha1-h/OPnxj3dKSrTIojL1xc6IcqnRU=", - "dev": true - }, - "postcss-zindex": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-2.2.0.tgz", - "integrity": "sha1-0hCd3AVbka9n/EyzsCWUZjnSryI=", - "dev": true, - "requires": { - "has": "^1.0.1", - "postcss": "^5.0.4", - "uniqs": "^2.0.0" - } - }, - "postinstall-build": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postinstall-build/-/postinstall-build-5.0.1.tgz", - "integrity": "sha1-uRepB5smF42aJK9aXNjLSpkdEbk=", - "dev": true - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", - "dev": true - }, - "preserve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", - "dev": true - }, - "pretty-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", - "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=", - "dev": true, - "requires": { - "renderkid": "^2.0.1", - "utila": "~0.4" - } - }, - "pretty-ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-2.1.0.tgz", - "integrity": "sha1-QlfCVt8/sLRR1q/6qwIYhBJpgdw=", - "dev": true, - "requires": { - "is-finite": "^1.0.1", - "parse-ms": "^1.0.0", - "plur": "^1.0.0" - }, - "dependencies": { - "plur": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/plur/-/plur-1.0.0.tgz", - "integrity": "sha1-24XGgU9eXlo7Se/CjWBP7GKXUVY=", - "dev": true - } - } - }, - "private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", - "dev": true - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true - }, - "progress": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", - "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", - "dev": true - }, - "promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", - "dev": true, - "optional": true, - "requires": { - "asap": "~2.0.3" - } - }, - "promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", - "dev": true - }, - "proxy-addr": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", - "integrity": "sha512-jQTChiCJteusULxjBp8+jftSQE5Obdl3k4cnmLA6WXtK6XFuWRnvVL7aCiBqaLPM8c4ph0S4tKna8XvmIwEnXQ==", - "dev": true, - "requires": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.6.0" - } - }, - "proxy-agent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-2.0.0.tgz", - "integrity": "sha1-V+tTR6qAXXTsaByyVknbo5yTNJk=", - "dev": true, - "requires": { - "agent-base": "2", - "debug": "2", - "extend": "3", - "http-proxy-agent": "1", - "https-proxy-agent": "1", - "lru-cache": "~2.6.5", - "pac-proxy-agent": "1", - "socks-proxy-agent": "2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "lru-cache": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.6.5.tgz", - "integrity": "sha1-5W1jVBSO3o13B7WNFDIg/QjfD9U=", - "dev": true - } - } - }, - "proxy-from-env": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", - "integrity": "sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4=", - "dev": true - }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "dev": true - }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, - "public-encrypt": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.2.tgz", - "integrity": "sha512-4kJ5Esocg8X3h8YgJsKAuoesBgB7mqH3eowiDzMUPKiRDDE7E/BqqZD1hnTByIaAFiwAw246YEltSq7tdrOH0Q==", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1" - } - }, - "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "dev": true, - "requires": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - }, - "puppeteer": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-1.8.0.tgz", - "integrity": "sha512-wJ7Fxs03l4dy/ZXQACUKBBobIuJaS4NHq44q7/QinpAXFMwJMJFEIPjzoksVzUhZxQe+RXnjXH69mg13yMh0BA==", - "dev": true, - "requires": { - "debug": "^3.1.0", - "extract-zip": "^1.6.6", - "https-proxy-agent": "^2.2.1", - "mime": "^2.0.3", - "progress": "^2.0.0", - "proxy-from-env": "^1.0.0", - "rimraf": "^2.6.1", - "ws": "^5.1.1" - }, - "dependencies": { - "agent-base": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", - "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", - "dev": true, - "requires": { - "es6-promisify": "^5.0.0" - } - }, - "https-proxy-agent": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", - "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", - "dev": true, - "requires": { - "agent-base": "^4.1.0", - "debug": "^3.1.0" - } - }, - "mime": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.3.tgz", - "integrity": "sha512-QgrPRJfE+riq5TPZMcHZOtm8c6K/yYrMbKIoRfapfiGLxS8OTeIfRhUGW5LU7MlRa52KOAGCfUNruqLrIBvWZw==", - "dev": true - }, - "ws": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", - "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0" - } - } - } - }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", - "dev": true - }, - "qjobs": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", - "dev": true - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true - }, - "query-string": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", - "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", - "dev": true, - "requires": { - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" - } - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true - }, - "querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", - "dev": true - }, - "querystringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.0.0.tgz", - "integrity": "sha512-eTPo5t/4bgaMNZxyjWx6N2a6AuE0mq51KWvpc7nU/MAqixcI6v6KrGUKES0HaomdnolQBBXU/++X6/QQ9KL4tw==", - "dev": true - }, - "randomatic": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.0.0.tgz", - "integrity": "sha512-VdxFOIEY3mNO5PtSRkkle/hPJDHvQhK21oa73K4yAc9qmp6N429gAyF1gZMOTMeS0/AYzaV/2Trcef+NaIonSA==", - "dev": true, - "requires": { - "is-number": "^4.0.0", - "kind-of": "^6.0.0", - "math-random": "^1.0.1" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "randombytes": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz", - "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dev": true, - "requires": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" - } - }, - "range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", - "dev": true - }, - "raw-body": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", - "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", - "dev": true, - "requires": { - "bytes": "3.0.0", - "http-errors": "1.6.3", - "iconv-lite": "0.4.23", - "unpipe": "1.0.0" - } - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "dev": true, - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "dev": true, - "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - }, - "dependencies": { - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "requires": { - "pinkie-promise": "^2.0.0" - } - } - } - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "readdirp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", - "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "minimatch": "^3.0.2", - "readable-stream": "^2.0.2", - "set-immediate-shim": "^1.0.1" - } - }, - "readline2": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/readline2/-/readline2-0.1.1.tgz", - "integrity": "sha1-mUQ7pug7gw7zBRv9fcJBqCco1Wg=", - "dev": true, - "requires": { - "mute-stream": "0.0.4", - "strip-ansi": "^2.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz", - "integrity": "sha1-QchHGUZGN15qGl0Qw8oFTvn8mA0=", - "dev": true - }, - "mute-stream": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.4.tgz", - "integrity": "sha1-qSGZYKbV1dBGWXruUSUsZlX3F34=", - "dev": true - }, - "strip-ansi": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-2.0.1.tgz", - "integrity": "sha1-32LBqpTtLxFOHQ8h/R1QSCt5pg4=", - "dev": true, - "requires": { - "ansi-regex": "^1.0.0" - } - } - } - }, - "recast": { - "version": "0.11.23", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.11.23.tgz", - "integrity": "sha1-RR/TAEqx5N+bTktmN2sqIZEkYtM=", - "dev": true, - "requires": { - "ast-types": "0.9.6", - "esprima": "~3.1.0", - "private": "~0.1.5", - "source-map": "~0.5.0" - }, - "dependencies": { - "esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "reconnectingwebsocket": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/reconnectingwebsocket/-/reconnectingwebsocket-1.0.0.tgz", - "integrity": "sha1-C4Jbq7N7ZwRFxlqn0+2XgwAgVEQ=" - }, - "redent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", - "dev": true, - "requires": { - "indent-string": "^2.1.0", - "strip-indent": "^1.0.1" - } - }, - "reduce-css-calc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", - "integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=", - "dev": true, - "requires": { - "balanced-match": "^0.4.2", - "math-expression-evaluator": "^1.2.14", - "reduce-function-call": "^1.0.1" - }, - "dependencies": { - "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", - "dev": true - } - } - }, - "reduce-function-call": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.2.tgz", - "integrity": "sha1-WiAL+S4ON3UXUv5FsKszD9S2vpk=", - "dev": true, - "requires": { - "balanced-match": "^0.4.2" - }, - "dependencies": { - "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", - "dev": true - } - } - }, - "regenerate": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", - "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", - "dev": true - }, - "regenerator-runtime": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", - "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=" - }, - "regenerator-transform": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", - "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", - "dev": true, - "requires": { - "babel-runtime": "^6.18.0", - "babel-types": "^6.19.0", - "private": "^0.1.6" - } - }, - "regex-cache": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", - "dev": true, - "requires": { - "is-equal-shallow": "^0.1.3" - } - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, - "regexpp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", - "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==", - "dev": true - }, - "regexpu-core": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", - "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", - "dev": true, - "requires": { - "regenerate": "^1.2.1", - "regjsgen": "^0.2.0", - "regjsparser": "^0.1.4" - } - }, - "regjsgen": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", - "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", - "dev": true - }, - "regjsparser": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", - "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", - "dev": true, - "requires": { - "jsesc": "~0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true - } - } - }, - "relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", - "dev": true - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "renderkid": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.1.tgz", - "integrity": "sha1-iYyr/Ivt5Le5ETWj/9Mj5YwNsxk=", - "dev": true, - "requires": { - "css-select": "^1.1.0", - "dom-converter": "~0.1", - "htmlparser2": "~3.3.0", - "strip-ansi": "^3.0.0", - "utila": "~0.3" - }, - "dependencies": { - "css-select": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", - "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", - "dev": true, - "requires": { - "boolbase": "~1.0.0", - "css-what": "2.1", - "domutils": "1.5.1", - "nth-check": "~1.0.1" - } - }, - "css-what": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz", - "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=", - "dev": true - }, - "domhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.1.0.tgz", - "integrity": "sha1-0mRvXlf2w7qxHPbLBdPArPdBJZQ=", - "dev": true, - "requires": { - "domelementtype": "1" - } - }, - "domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", - "dev": true, - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "htmlparser2": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz", - "integrity": "sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4=", - "dev": true, - "requires": { - "domelementtype": "1", - "domhandler": "2.1", - "domutils": "1.1", - "readable-stream": "1.0" - }, - "dependencies": { - "domutils": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.1.6.tgz", - "integrity": "sha1-vdw94Jm5ou+sxRxiPyj0FuzFdIU=", - "dev": true, - "requires": { - "domelementtype": "1" - } - } - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - }, - "utila": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.3.3.tgz", - "integrity": "sha1-1+jn1+MJEHCSsF+NloiCTWM6QiY=", - "dev": true - } - } - }, - "repeat-element": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", - "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "dev": true, - "requires": { - "is-finite": "^1.0.0" - } - }, - "request": { - "version": "2.87.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", - "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.6.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.1", - "forever-agent": "~0.6.1", - "form-data": "~2.3.1", - "har-validator": "~5.0.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.17", - "oauth-sign": "~0.8.2", - "performance-now": "^2.1.0", - "qs": "~6.5.1", - "safe-buffer": "^5.1.1", - "tough-cookie": "~2.3.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.1.0" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true - }, - "require-uncached": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", - "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", - "dev": true, - "requires": { - "caller-path": "^0.1.0", - "resolve-from": "^1.0.0" - } - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", - "dev": true - }, - "resolve": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz", - "integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==", - "dev": true, - "requires": { - "path-parse": "^1.0.5" - } - }, - "resolve-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", - "dev": true, - "requires": { - "resolve-from": "^3.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "dev": true - } - } - }, - "resolve-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", - "dev": true - }, - "resolve-pkg": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/resolve-pkg/-/resolve-pkg-0.1.0.tgz", - "integrity": "sha1-AsyZNBDik2livZcWahsHfalyVTE=", - "dev": true, - "requires": { - "resolve-from": "^2.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", - "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=", - "dev": true - } - } - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", - "dev": true, - "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - } - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true - }, - "right-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", - "dev": true, - "requires": { - "align-text": "^0.1.1" - } - }, - "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "dev": true, - "requires": { - "glob": "^7.0.5" - } - }, - "ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "rrule": { - "version": "git+https://git@github.com/jkbrzt/rrule.git#4ff63b2f8524fd6d5ba6e80db770953b5cd08a0c", - "from": "rrule@git+https://git@github.com/jkbrzt/rrule.git#4ff63b2f8524fd6d5ba6e80db770953b5cd08a0c" - }, - "run-async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", - "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", - "dev": true, - "requires": { - "is-promise": "^2.1.0" - } - }, - "run-queue": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", - "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", - "dev": true, - "requires": { - "aproba": "^1.1.1" - } - }, - "rx": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/rx/-/rx-2.5.3.tgz", - "integrity": "sha1-Ia3H2A8CACr1Da6X/Z2/JIdV9WY=", - "dev": true - }, - "rx-lite": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", - "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", - "dev": true - }, - "rx-lite-aggregates": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", - "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", - "dev": true, - "requires": { - "rx-lite": "*" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "requires": { - "ret": "~0.1.10" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true - }, - "schema-utils": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.3.0.tgz", - "integrity": "sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8=", - "dev": true, - "requires": { - "ajv": "^5.0.0" - } - }, - "seed-random": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/seed-random/-/seed-random-2.2.0.tgz", - "integrity": "sha1-KpsZ4lCoFwmSMaW5mk2vgLf77VQ=" - }, - "select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", - "dev": true - }, - "select2": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/select2/-/select2-4.0.5.tgz", - "integrity": "sha1-eqxQaSVhmFs007guxV4ib4lg1Ao=", - "requires": { - "almond": "~0.3.1", - "jquery-mousewheel": "~3.1.13" - } - }, - "selfsigned": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.3.tgz", - "integrity": "sha512-vmZenZ+8Al3NLHkWnhBQ0x6BkML1eCP2xEi3JE+f3D9wW9fipD9NNJHYtE9XJM4TsPaHGZJIamrSI6MTg1dU2Q==", - "dev": true, - "requires": { - "node-forge": "0.7.5" - } - }, - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", - "dev": true - }, - "send": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", - "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", - "dev": true, - "requires": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", - "on-finished": "~2.3.0", - "range-parser": "~1.2.0", - "statuses": "~1.4.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", - "dev": true - }, - "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", - "dev": true - } - } - }, - "serialize-javascript": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.5.0.tgz", - "integrity": "sha512-Ga8c8NjAAp46Br4+0oZ2WxJCwIzwP60Gq1YPgU+39PiTVxyed/iKE/zyZI6+UlVYH5Q4PaQdHhcegIFPZTUfoQ==", - "dev": true - }, - "serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", - "dev": true, - "requires": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "dependencies": { - "accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", - "dev": true, - "requires": { - "mime-types": "~2.1.18", - "negotiator": "0.6.1" - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "serve-static": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", - "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", - "dev": true, - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.2", - "send": "0.16.2" - } - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "dev": true - }, - "set-value": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", - "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", - "dev": true - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - }, - "sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "shelljs": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", - "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=", - "dev": true - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true - }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", - "dev": true - }, - "slice-ansi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", - "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0" - } - }, - "smart-buffer": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-1.1.15.tgz", - "integrity": "sha1-fxFLW2X6s+KjWqd1uxLw0cZJvxY=", - "dev": true - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "requires": { - "kind-of": "^3.2.0" - } - }, - "sntp": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", - "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", - "dev": true, - "optional": true, - "requires": { - "hoek": "2.x.x" - } - }, - "socket.io": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-1.7.3.tgz", - "integrity": "sha1-uK+cq6AJSeVo42nxMn6pvp6iRhs=", - "dev": true, - "requires": { - "debug": "2.3.3", - "engine.io": "1.8.3", - "has-binary": "0.1.7", - "object-assign": "4.1.0", - "socket.io-adapter": "0.5.0", - "socket.io-client": "1.7.3", - "socket.io-parser": "2.3.1" - }, - "dependencies": { - "debug": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", - "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", - "dev": true, - "requires": { - "ms": "0.7.2" - } - }, - "ms": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", - "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", - "dev": true - }, - "object-assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz", - "integrity": "sha1-ejs9DpgGPUP0wD8uiubNUahog6A=", - "dev": true - } - } - }, - "socket.io-adapter": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-0.5.0.tgz", - "integrity": "sha1-y21LuL7IHhB4uZZ3+c7QBGBmu4s=", - "dev": true, - "requires": { - "debug": "2.3.3", - "socket.io-parser": "2.3.1" - }, - "dependencies": { - "debug": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", - "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", - "dev": true, - "requires": { - "ms": "0.7.2" - } - }, - "ms": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", - "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", - "dev": true - } - } - }, - "socket.io-client": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-1.7.3.tgz", - "integrity": "sha1-sw6GqhDV7zVGYBwJzeR2Xjgdo3c=", - "dev": true, - "requires": { - "backo2": "1.0.2", - "component-bind": "1.0.0", - "component-emitter": "1.2.1", - "debug": "2.3.3", - "engine.io-client": "1.8.3", - "has-binary": "0.1.7", - "indexof": "0.0.1", - "object-component": "0.0.3", - "parseuri": "0.0.5", - "socket.io-parser": "2.3.1", - "to-array": "0.1.4" - }, - "dependencies": { - "debug": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", - "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", - "dev": true, - "requires": { - "ms": "0.7.2" - } - }, - "ms": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", - "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", - "dev": true - } - } - }, - "socket.io-parser": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-2.3.1.tgz", - "integrity": "sha1-3VMgJRA85Clpcya+/WQAX8/ltKA=", - "dev": true, - "requires": { - "component-emitter": "1.1.2", - "debug": "2.2.0", - "isarray": "0.0.1", - "json3": "3.3.2" - }, - "dependencies": { - "component-emitter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.1.2.tgz", - "integrity": "sha1-KWWU8nU9qmOZbSrwjRWpURbJrsM=", - "dev": true - }, - "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", - "dev": true, - "requires": { - "ms": "0.7.1" - } - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", - "dev": true - } - } - }, - "sockjs": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", - "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", - "dev": true, - "requires": { - "faye-websocket": "^0.10.0", - "uuid": "^3.0.1" - } - }, - "sockjs-client": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.1.4.tgz", - "integrity": "sha1-W6vjhrd15M8U51IJEUUmVAFsixI=", - "dev": true, - "requires": { - "debug": "^2.6.6", - "eventsource": "0.1.6", - "faye-websocket": "~0.11.0", - "inherits": "^2.0.1", - "json3": "^3.3.2", - "url-parse": "^1.1.8" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "faye-websocket": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.1.tgz", - "integrity": "sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg=", - "dev": true, - "requires": { - "websocket-driver": ">=0.5.1" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "socks": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/socks/-/socks-1.1.10.tgz", - "integrity": "sha1-W4t/x8jzQcU+0FbpKbe/Tei6e1o=", - "dev": true, - "requires": { - "ip": "^1.1.4", - "smart-buffer": "^1.0.13" - }, - "dependencies": { - "ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", - "dev": true - } - } - }, - "socks-proxy-agent": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-2.1.1.tgz", - "integrity": "sha512-sFtmYqdUK5dAMh85H0LEVFUCO7OhJJe1/z2x/Z6mxp3s7/QPf1RkZmpZy+BpuU0bEjcV9npqKjq9Y3kwFUjnxw==", - "dev": true, - "requires": { - "agent-base": "2", - "extend": "3", - "socks": "~1.1.5" - } - }, - "sort-keys": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", - "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", - "dev": true, - "requires": { - "is-plain-obj": "^1.0.0" - } - }, - "source-list-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.0.tgz", - "integrity": "sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A==", - "dev": true - }, - "source-map": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.3.0.tgz", - "integrity": "sha1-hYb7mloAXltQHiHNGLbyG0V60fk=", - "requires": { - "amdefine": ">=0.0.4" - } - }, - "source-map-resolve": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", - "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", - "dev": true, - "requires": { - "atob": "^2.1.1", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", - "dev": true, - "requires": { - "source-map": "^0.5.6" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true - }, - "spdx-correct": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", - "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", - "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", - "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", - "dev": true - }, - "spdy": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-3.4.7.tgz", - "integrity": "sha1-Qv9B7OXMD5mjpsKKq7c/XDsDrLw=", - "dev": true, - "requires": { - "debug": "^2.6.8", - "handle-thing": "^1.2.5", - "http-deceiver": "^1.2.7", - "safe-buffer": "^5.0.1", - "select-hose": "^2.0.0", - "spdy-transport": "^2.0.18" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "spdy-transport": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-2.1.0.tgz", - "integrity": "sha512-bpUeGpZcmZ692rrTiqf9/2EUakI6/kXX1Rpe0ib/DyOzbiexVfXkw6GnvI9hVGvIwVaUhkaBojjCZwLNRGQg1g==", - "dev": true, - "requires": { - "debug": "^2.6.8", - "detect-node": "^2.0.3", - "hpack.js": "^2.1.6", - "obuf": "^1.1.1", - "readable-stream": "^2.2.9", - "safe-buffer": "^5.0.1", - "wbuf": "^1.7.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.0" - } - }, - "split2": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/split2/-/split2-1.1.1.tgz", - "integrity": "sha1-Fi2bGIZfAqsvKtlYVSLbm1TEgfk=", - "dev": true, - "requires": { - "through2": "~2.0.0" - } - }, - "sprintf-js": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.1.tgz", - "integrity": "sha1-Nr54Mgr+WAH2zqPueLblqrlA6gw=" - }, - "sshpk": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", - "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "ssri": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-5.3.0.tgz", - "integrity": "sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.1" - } - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "dev": true - }, - "stream-browserify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", - "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", - "dev": true, - "requires": { - "inherits": "~2.0.1", - "readable-stream": "^2.0.2" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "stream-each": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.2.tgz", - "integrity": "sha512-mc1dbFhGBxvTM3bIWmAAINbqiuAk9TATcfIQC8P+/+HJefgaiTlMn2dHvkX8qlI12KeYKSQ1Ua9RrIqrn1VPoA==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "stream-shift": "^1.0.0" - } - }, - "stream-http": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", - "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", - "dev": true, - "requires": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.3.6", - "to-arraybuffer": "^1.0.0", - "xtend": "^4.0.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "stream-shift": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", - "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", - "dev": true - }, - "strict-uri-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", - "dev": true - }, - "string-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz", - "integrity": "sha1-VpcPscOFWOnnC3KL894mmsRa36w=", - "dev": true, - "requires": { - "strip-ansi": "^3.0.0" - } - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "stringstream": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", - "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==", - "dev": true, - "optional": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "^0.2.0" - } - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true - }, - "strip-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", - "dev": true, - "requires": { - "get-stdin": "^4.0.1" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - }, - "svgo": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-0.7.2.tgz", - "integrity": "sha1-n1dyQTlSE1xv779Ar+ak+qiLS7U=", - "dev": true, - "requires": { - "coa": "~1.0.1", - "colors": "~1.1.2", - "csso": "~2.3.1", - "js-yaml": "~3.7.0", - "mkdirp": "~0.5.1", - "sax": "~1.2.1", - "whet.extend": "~0.9.9" - }, - "dependencies": { - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "js-yaml": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.7.0.tgz", - "integrity": "sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A=", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^2.6.0" - } - } - } - }, - "table": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", - "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", - "dev": true, - "requires": { - "ajv": "^5.2.3", - "ajv-keywords": "^2.1.0", - "chalk": "^2.1.0", - "lodash": "^4.17.4", - "slice-ansi": "1.0.0", - "string-width": "^2.1.1" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", - "dev": true - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "tapable": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.1.10.tgz", - "integrity": "sha1-KcNXB8K3DlDQdIK10gLo7URtr9Q=", - "dev": true - }, - "tar": { - "version": "4.4.8", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", - "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", - "dev": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.3.4", - "minizlib": "^1.1.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" - }, - "dependencies": { - "chownr": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", - "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", - "dev": true, - "optional": true - }, - "yallist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", - "dev": true, - "optional": true - } - } - }, - "tar-stream": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.1.tgz", - "integrity": "sha512-IFLM5wp3QrJODQFPm6/to3LJZrONdBY/otxcvDIQzu217zKye6yVR3hhi9lAjrC2Z+m/j5oDxMPb1qcd8cIvpA==", - "dev": true, - "requires": { - "bl": "^1.0.0", - "buffer-alloc": "^1.1.0", - "end-of-stream": "^1.0.0", - "fs-constants": "^1.0.0", - "readable-stream": "^2.3.0", - "to-buffer": "^1.1.0", - "xtend": "^4.0.0" - } - }, - "test-exclude": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-4.2.1.tgz", - "integrity": "sha512-qpqlP/8Zl+sosLxBcVKl9vYy26T9NPalxSzzCP/OY6K7j938ui2oKgo+kRZYfxAeIpLqpbVnsHq1tyV70E4lWQ==", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "micromatch": "^3.1.8", - "object-assign": "^4.1.0", - "read-pkg-up": "^1.0.1", - "require-main-filename": "^1.0.1" - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "through2": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", - "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", - "dev": true, - "requires": { - "readable-stream": "^2.1.5", - "xtend": "~4.0.1" - } - }, - "thunkify": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/thunkify/-/thunkify-2.1.2.tgz", - "integrity": "sha1-+qDp0jDFGsyVyhOjYawFyn4EVT0=", - "dev": true - }, - "thunky": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.0.2.tgz", - "integrity": "sha1-qGLgGOP7HqLsP85dVWBc9X8kc3E=", - "dev": true - }, - "time-grunt": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/time-grunt/-/time-grunt-1.4.0.tgz", - "integrity": "sha1-BiIT5mDJB+hvRAVWwB6mWXtxJCA=", - "dev": true, - "requires": { - "chalk": "^1.0.0", - "date-time": "^1.1.0", - "figures": "^1.0.0", - "hooker": "^0.2.3", - "number-is-nan": "^1.0.0", - "pretty-ms": "^2.1.0", - "text-table": "^0.2.0" - }, - "dependencies": { - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - } - } - } - }, - "time-stamp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-2.0.0.tgz", - "integrity": "sha1-lcakRTDhW6jW9KPsuMOj+sRto1c=", - "dev": true - }, - "time-zone": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/time-zone/-/time-zone-0.1.0.tgz", - "integrity": "sha1-Sncotqwo2w4Aj1FAQ/1VW9VXO0Y=", - "dev": true - }, - "timers-browserify": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", - "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==", - "dev": true, - "requires": { - "setimmediate": "^1.0.4" - } - }, - "timezone-js": { - "version": "github:ansible/timezone-js#6937de14ce0c193961538bb5b3b12b7ef62a358f", - "from": "github:ansible/timezone-js#0.4.14" - }, - "tiny-emitter": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.0.2.tgz", - "integrity": "sha512-2NM0auVBGft5tee/OxP4PI3d8WItkDM+fPnaRAVo6xTDI2knbz9eC5ArWGqtGlYqiH3RU5yMpdyTTO7MguC4ow==" - }, - "titlecase": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/titlecase/-/titlecase-1.1.2.tgz", - "integrity": "sha1-eBE9EQgIa4MmMxoyR96o9aSeqFM=" - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - }, - "to-array": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", - "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", - "dev": true - }, - "to-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", - "dev": true - }, - "to-buffer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", - "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", - "dev": true - }, - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", - "dev": true - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - }, - "toposort": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.7.tgz", - "integrity": "sha1-LmhELZ9k7HILjMieZEOsbKqVACk=", - "dev": true - }, - "tough-cookie": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", - "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", - "dev": true, - "requires": { - "punycode": "^1.4.1" - } - }, - "trim-newlines": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", - "dev": true - }, - "trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", - "dev": true - }, - "tty-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", - "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", - "dev": true - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true, - "optional": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "type-detect": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", - "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", - "dev": true - }, - "type-is": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", - "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", - "dev": true, - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.18" - } - }, - "typed-function": { - "version": "0.10.7", - "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-0.10.7.tgz", - "integrity": "sha512-3mlZ5AwRMbLvUKkc8a1TI4RUJUS2H27pmD5q0lHRObgsoWzhDAX01yg82kwSP1FUw922/4Y9ZliIEh0qJZcz+g==" - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "typescript": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.3.4.tgz", - "integrity": "sha1-PTgyGCgjHkNPKHUUlZw3qCtin0I=", - "dev": true - }, - "typescript-eslint-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/typescript-eslint-parser/-/typescript-eslint-parser-3.0.0.tgz", - "integrity": "sha1-3QQ1swOryEFGTALQAYTXs5vUiLU=", - "dev": true, - "requires": { - "lodash.unescape": "4.0.1", - "semver": "5.3.0" - }, - "dependencies": { - "semver": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", - "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", - "dev": true - } - } - }, - "uglify-js": { - "version": "2.8.29", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", - "dev": true, - "requires": { - "source-map": "~0.5.1", - "uglify-to-browserify": "~1.0.0", - "yargs": "~3.10.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "uglify-to-browserify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", - "dev": true, - "optional": true - }, - "uglifyjs-webpack-plugin": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz", - "integrity": "sha1-uVH0q7a9YX5m9j64kUmOORdj4wk=", - "dev": true, - "requires": { - "source-map": "^0.5.6", - "uglify-js": "^2.8.29", - "webpack-sources": "^1.0.1" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "ultron": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz", - "integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=", - "dev": true - }, - "unc-path-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", - "dev": true - }, - "underscore.string": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.5.tgz", - "integrity": "sha512-g+dpmgn+XBneLmXXo+sGlW5xQEt4ErkS3mgeN2GFbremYeMBSJKr9Wf2KJplQVaiPY/f7FN6atosWYNm9ovrYg==", - "dev": true, - "requires": { - "sprintf-js": "^1.0.3", - "util-deprecate": "^1.0.2" - } - }, - "union-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", - "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^0.4.3" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "set-value": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", - "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.1", - "to-object-path": "^0.3.0" - } - } - } - }, - "uniq": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", - "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", - "dev": true - }, - "uniqs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", - "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=", - "dev": true - }, - "unique-filename": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.0.tgz", - "integrity": "sha1-0F8v5AMlYIcfMOk8vnNe6iAVFPM=", - "dev": true, - "requires": { - "unique-slug": "^2.0.0" - } - }, - "unique-slug": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.0.tgz", - "integrity": "sha1-22Z258fMBimHj/GWCXx4hVrp9Ks=", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4" - } - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "dev": true - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true - } - } - }, - "upath": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", - "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", - "dev": true - }, - "upper-case": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", - "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", - "dev": true - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - }, - "dependencies": { - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - } - } - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, - "url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dev": true, - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - }, - "dependencies": { - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true - } - } - }, - "url-parse": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.1.tgz", - "integrity": "sha512-x95Td74QcvICAA0+qERaVkRpTGKyBHHYdwL2LXZm5t/gBtCB9KQSO/0zQgSTYEV1p0WcvSg79TLNPSvd5IDJMQ==", - "dev": true, - "requires": { - "querystringify": "^2.0.0", - "requires-port": "^1.0.0" - } - }, - "use": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.0.tgz", - "integrity": "sha512-6UJEQM/L+mzC3ZJNM56Q4DFGLX/evKGRg15UJHGB9X5j5Z3AFbgZvjUh2yq/UJUY4U5dh7Fal++XbNg1uzpRAw==", - "dev": true, - "requires": { - "kind-of": "^6.0.2" - }, - "dependencies": { - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "useragent": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz", - "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==", - "dev": true, - "requires": { - "lru-cache": "4.1.x", - "tmp": "0.0.x" - } - }, - "util": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", - "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", - "dev": true, - "requires": { - "inherits": "2.0.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "utila": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", - "dev": true - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "dev": true - }, - "uuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==", - "dev": true - }, - "validate-npm-package-license": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", - "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "dev": true - }, - "vendors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.2.tgz", - "integrity": "sha512-w/hry/368nO21AN9QljsaIhb9ZiZtZARoVH5f3CsFbawdLdayCgKRPup7CggujvySMxx0I91NOyxdVENohprLQ==", - "dev": true - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "vm-browserify": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", - "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", - "dev": true, - "requires": { - "indexof": "0.0.1" - } - }, - "void-elements": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", - "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", - "dev": true - }, - "watchpack": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", - "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==", - "dev": true, - "requires": { - "chokidar": "^2.0.2", - "graceful-fs": "^4.1.2", - "neo-async": "^2.5.0" - }, - "dependencies": { - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "chokidar": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.3.tgz", - "integrity": "sha512-zW8iXYZtXMx4kux/nuZVXjkLP+CyIK5Al5FHnj1OgTKGZfp4Oy6/ymtMSKFv3GD8DviEmUPmJg9eFdJ/JzudMg==", - "dev": true, - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.0", - "braces": "^2.3.0", - "fsevents": "^1.1.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.1", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^2.1.1", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.0.0", - "upath": "^1.0.0" - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dev": true, - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "dev": true, - "requires": { - "minimalistic-assert": "^1.0.0" - } - }, - "webpack": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-3.12.0.tgz", - "integrity": "sha512-Sw7MdIIOv/nkzPzee4o0EdvCuPmxT98+vVpIvwtcwcF1Q4SDSNp92vwcKc4REe7NItH9f1S4ra9FuQ7yuYZ8bQ==", - "dev": true, - "requires": { - "acorn": "^5.0.0", - "acorn-dynamic-import": "^2.0.0", - "ajv": "^6.1.0", - "ajv-keywords": "^3.1.0", - "async": "^2.1.2", - "enhanced-resolve": "^3.4.0", - "escope": "^3.6.0", - "interpret": "^1.0.0", - "json-loader": "^0.5.4", - "json5": "^0.5.1", - "loader-runner": "^2.3.0", - "loader-utils": "^1.1.0", - "memory-fs": "~0.4.1", - "mkdirp": "~0.5.0", - "node-libs-browser": "^2.0.0", - "source-map": "^0.5.3", - "supports-color": "^4.2.1", - "tapable": "^0.2.7", - "uglifyjs-webpack-plugin": "^0.4.6", - "watchpack": "^1.4.0", - "webpack-sources": "^1.0.1", - "yargs": "^8.0.2" - }, - "dependencies": { - "ajv": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.1.tgz", - "integrity": "sha512-pgZos1vgOHDiC7gKNbZW8eKvCnNXARv2oqrGQT7Hzbq5Azp7aZG6DJzADnkuSq7RH6qkXp4J/m68yPX/2uBHyQ==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.1" - } - }, - "ajv-keywords": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", - "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=", - "dev": true - }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" - }, - "dependencies": { - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - } - } - }, - "enhanced-resolve": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz", - "integrity": "sha1-BCHjOf1xQZs9oT0Smzl5BAIwR24=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.4.0", - "object-assign": "^4.0.1", - "tapable": "^0.2.7" - } - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" - } - }, - "loader-utils": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", - "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", - "dev": true, - "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0" - } - }, - "memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", - "dev": true, - "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - }, - "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", - "dev": true, - "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" - } - }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "dev": true, - "requires": { - "pify": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "dev": true, - "requires": { - "load-json-file": "^2.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" - } - }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "dev": true, - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, - "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", - "dev": true, - "requires": { - "has-flag": "^2.0.0" - } - }, - "tapable": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.8.tgz", - "integrity": "sha1-mTcqXJmb8t8WCvwNdL7U9HlIzSI=", - "dev": true - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true - }, - "yargs": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-8.0.2.tgz", - "integrity": "sha1-YpmpBVsc78lp/355wdkY3Osiw2A=", - "dev": true, - "requires": { - "camelcase": "^4.1.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", - "read-pkg-up": "^2.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^7.0.0" - } - } - } - }, - "webpack-core": { - "version": "0.6.9", - "resolved": "https://registry.npmjs.org/webpack-core/-/webpack-core-0.6.9.tgz", - "integrity": "sha1-/FcViMhVjad76e+23r3Fo7FyvcI=", - "dev": true, - "requires": { - "source-list-map": "~0.1.7", - "source-map": "~0.4.1" - }, - "dependencies": { - "source-list-map": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-0.1.8.tgz", - "integrity": "sha1-xVCyq1Qn9rPyH1r+rYjE9Vh7IQY=", - "dev": true - }, - "source-map": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", - "dev": true, - "requires": { - "amdefine": ">=0.0.4" - } - } - } - }, - "webpack-dev-middleware": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-1.12.2.tgz", - "integrity": "sha512-FCrqPy1yy/sN6U/SaEZcHKRXGlqU0DUaEBL45jkUYoB8foVb6wCnbIJ1HKIx+qUFTW+3JpVcCJCxZ8VATL4e+A==", - "dev": true, - "requires": { - "memory-fs": "~0.4.1", - "mime": "^1.5.0", - "path-is-absolute": "^1.0.0", - "range-parser": "^1.0.3", - "time-stamp": "^2.0.0" - }, - "dependencies": { - "memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", - "dev": true, - "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - } - } - }, - "webpack-dev-server": { - "version": "2.11.2", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-2.11.2.tgz", - "integrity": "sha512-zrPoX97bx47vZiAXfDrkw8pe9QjJ+lunQl3dypojyWwWr1M5I2h0VSrMPfTjopHQPRNn+NqfjcMmhoLcUJe2gA==", - "dev": true, - "requires": { - "ansi-html": "0.0.7", - "array-includes": "^3.0.3", - "bonjour": "^3.5.0", - "chokidar": "^2.0.0", - "compression": "^1.5.2", - "connect-history-api-fallback": "^1.3.0", - "debug": "^3.1.0", - "del": "^3.0.0", - "express": "^4.16.2", - "html-entities": "^1.2.0", - "http-proxy-middleware": "~0.17.4", - "import-local": "^1.0.0", - "internal-ip": "1.2.0", - "ip": "^1.1.5", - "killable": "^1.0.0", - "loglevel": "^1.4.1", - "opn": "^5.1.0", - "portfinder": "^1.0.9", - "selfsigned": "^1.9.1", - "serve-index": "^1.7.2", - "sockjs": "0.3.19", - "sockjs-client": "1.1.4", - "spdy": "^3.4.1", - "strip-ansi": "^3.0.0", - "supports-color": "^5.1.0", - "webpack-dev-middleware": "1.12.2", - "yargs": "6.6.0" - }, - "dependencies": { - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", - "dev": true - }, - "chokidar": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.3.tgz", - "integrity": "sha512-zW8iXYZtXMx4kux/nuZVXjkLP+CyIK5Al5FHnj1OgTKGZfp4Oy6/ymtMSKFv3GD8DviEmUPmJg9eFdJ/JzudMg==", - "dev": true, - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.0", - "braces": "^2.3.0", - "fsevents": "^1.1.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.1", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^2.1.1", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.0.0", - "upath": "^1.0.0" - } - }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dev": true, - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", - "dev": true - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true - }, - "yargs": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz", - "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=", - "dev": true, - "requires": { - "camelcase": "^3.0.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^1.4.0", - "read-pkg-up": "^1.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^1.0.2", - "which-module": "^1.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^4.2.0" - } - }, - "yargs-parser": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz", - "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=", - "dev": true, - "requires": { - "camelcase": "^3.0.0" - } - } - } - }, - "webpack-merge": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.1.2.tgz", - "integrity": "sha512-/0QYwW/H1N/CdXYA2PNPVbsxO3u2Fpz34vs72xm03SRfg6bMNGfMJIQEpQjKRvkG2JvT6oRJFpDtSrwbX8Jzvw==", - "dev": true, - "requires": { - "lodash": "^4.17.5" - }, - "dependencies": { - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - } - } - }, - "webpack-sources": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.1.0.tgz", - "integrity": "sha512-aqYp18kPphgoO5c/+NaUvEeACtZjMESmDChuD3NBciVpah3XpMEU9VAAtIaB1BsfJWWTSdv8Vv1m3T0aRk2dUw==", - "dev": true, - "requires": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "websocket-driver": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.0.tgz", - "integrity": "sha1-DK+dLXVdk67gSdS90NP+LMoqJOs=", - "dev": true, - "requires": { - "http-parser-js": ">=0.4.0", - "websocket-extensions": ">=0.1.1" - } - }, - "websocket-extensions": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", - "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", - "dev": true - }, - "whet.extend": { - "version": "0.9.9", - "resolved": "https://registry.npmjs.org/whet.extend/-/whet.extend-0.9.9.tgz", - "integrity": "sha1-+HfVv2SMl+WqVC+twW1qJZucEaE=", - "dev": true - }, - "which": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", - "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "window-size": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", - "dev": true - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } - }, - "write-file-atomic": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", - "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - }, - "write-json-file": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/write-json-file/-/write-json-file-2.3.0.tgz", - "integrity": "sha1-K2TIozAE1UuGmMdtWFp3zrYdoy8=", - "dev": true, - "requires": { - "detect-indent": "^5.0.0", - "graceful-fs": "^4.1.2", - "make-dir": "^1.0.0", - "pify": "^3.0.0", - "sort-keys": "^2.0.0", - "write-file-atomic": "^2.0.0" - }, - "dependencies": { - "detect-indent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", - "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=", - "dev": true - }, - "sort-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", - "integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=", - "dev": true, - "requires": { - "is-plain-obj": "^1.0.0" - } - } - } - }, - "ws": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.2.tgz", - "integrity": "sha1-iiRPoFJAHgjJiGz0SoUYnh/UBn8=", - "dev": true, - "requires": { - "options": ">=0.0.5", - "ultron": "1.0.x" - } - }, - "wtf-8": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wtf-8/-/wtf-8-1.0.0.tgz", - "integrity": "sha1-OS2LotDxw00e4tYw8V0O+2jhBIo=", - "dev": true - }, - "xmlbuilder": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz", - "integrity": "sha1-aSSGc0ELS6QuGmE2VR0pIjNap3M=", - "dev": true - }, - "xmlhttprequest-ssl": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz", - "integrity": "sha1-GFqIjATspGw+QHDZn3tJ3jUomS0=", - "dev": true - }, - "xregexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz", - "integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=", - "dev": true - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", - "dev": true - }, - "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true - }, - "yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", - "dev": true, - "requires": { - "camelcase": "^1.0.2", - "cliui": "^2.1.0", - "decamelize": "^1.0.0", - "window-size": "0.1.0" - } - }, - "yargs-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", - "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", - "dev": true, - "requires": { - "camelcase": "^4.1.0" - }, - "dependencies": { - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - } - } - }, - "yauzl": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", - "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", - "dev": true, - "requires": { - "fd-slicer": "~1.0.1" - } - }, - "yeast": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", - "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", - "dev": true - }, - "zip-stream": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-1.2.0.tgz", - "integrity": "sha1-qLxF9MG0lpnGuQGYuqyqzbzUugQ=", - "dev": true, - "requires": { - "archiver-utils": "^1.3.0", - "compress-commons": "^1.2.0", - "lodash": "^4.8.0", - "readable-stream": "^2.0.0" - }, - "dependencies": { - "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", - "dev": true - } - } - } - } -} diff --git a/awx/ui/package.json b/awx/ui/package.json deleted file mode 100644 index 60e79f0cd108..000000000000 --- a/awx/ui/package.json +++ /dev/null @@ -1,143 +0,0 @@ -{ - "name": "awx", - "version": "1.0.0", - "repository": { - "type": "git", - "url": "https://github.com/ansible/awx" - }, - "config": { - "django_port": "8043", - "django_host": "localhost" - }, - "engines": { - "node": "^8.11.2", - "npm": "^6.4.1" - }, - "scripts": { - "ui-docker-machine": "ip=$(docker-machine ip $DOCKER_MACHINE_NAME); npm set ansible-tower:django_host ${ip}; grunt dev;", - "ui-docker": "npm run watch;", - "build-devel": "npm run dev", - "pot": "grunt nggettext_extract", - "languages": "grunt nggettext_compile", - "build-release": "npm run production", - "pretest": "", - "test": "karma start test/spec/karma.spec.js", - "jshint": "grunt jshint:source --no-color", - "test:ci": "npm run test -- --single-run --reporter junit,dots --browsers=chromeHeadless", - "e2e": "./test/e2e/runner.js --config ./test/e2e/nightwatch.conf.js", - "headless-e2e": "./test/e2e/runner.js --config ./test/e2e/nightwatch.conf.js --env headless --suiteRetries=2", - "unit": "karma start test/unit/karma.unit.js", - "lint": "eslint .", - "dev": "webpack --config build/webpack.development.js --progress", - "watch": "webpack-dev-server --config build/webpack.watch.js --progress --https", - "production": "webpack --config build/webpack.production.js", - "grab-licenses": "./utils/get_licenses.js", - "pre-check": "npm run lint && npm run jshint && npm run unit && npm run test" - }, - "devDependencies": { - "angular-mocks": "^1.7.9", - "archiver": "^2.1.1", - "axios": "^0.16.2", - "babel-core": "^6.26.0", - "babel-istanbul": "^0.12.2", - "babel-loader": "^7.1.2", - "babel-plugin-istanbul": "^4.1.5", - "babel-preset-env": "^1.6.0", - "chromedriver": "^2.35.0", - "clean-webpack-plugin": "^0.1.16", - "copy-webpack-plugin": "^4.0.1", - "css-loader": "^0.28.5", - "eslint": "^4.6.1", - "eslint-config-airbnb-base": "^12.0.0", - "eslint-import-resolver-webpack": "^0.8.3", - "eslint-loader": "^1.9.0", - "eslint-plugin-disable": "^0.3.0", - "eslint-plugin-import": "^2.7.0", - "extract-text-webpack-plugin": "^3.0.0", - "grunt": "^1.0.4", - "grunt-angular-gettext": "^2.2.3", - "grunt-cli": "^1.2.0", - "grunt-concurrent": "^2.3.0", - "grunt-contrib-jshint": "^1.0.0", - "grunt-newer": "^1.2.0", - "hard-source-webpack-plugin": "^0.5.8", - "html-loader": "^0.5.1", - "html-webpack-harddisk-plugin": "^0.1.0", - "html-webpack-plugin": "^2.30.1", - "istanbul-instrumenter-loader": "^3.0.0", - "jasmine-core": "^2.5.2", - "jshint": "^2.10.2", - "jshint-stylish": "^2.2.0", - "json-loader": "^0.5.4", - "karma": "^1.4.1", - "karma-chrome-launcher": "^2.2.0", - "karma-coverage": "^1.1.1", - "karma-firefox-launcher": "^1.0.0", - "karma-html2js-preprocessor": "^1.0.0", - "karma-jasmine": "^1.1.0", - "karma-junit-reporter": "^1.2.0", - "karma-sourcemap-loader": "^0.3.7", - "karma-webpack": "^2.0.4", - "less": "^2.7.2", - "less-loader": "^4.0.5", - "less-plugin-autoprefix": "^1.4.2", - "load-grunt-configs": "^1.0.0", - "load-grunt-tasks": "^3.5.0", - "ngtemplate-loader": "^2.0.1", - "nightwatch": "^0.9.19", - "node-object-hash": "^1.3.0", - "nunjucks": "^3.1.2", - "puppeteer": "^1.8.0", - "time-grunt": "^1.4.0", - "uglifyjs-webpack-plugin": "^0.4.6", - "uuid": "^3.1.0", - "webpack": "^3.0.0", - "webpack-dev-server": "^2.7.1", - "webpack-merge": "^4.1.0" - }, - "dependencies": { - "@uirouter/angularjs": "1.0.18", - "angular": "^1.7.9", - "angular-breadcrumb": "git+https://git@github.com/ansible/angular-breadcrumb#0.4.1", - "angular-codemirror": "git+https://git@github.com/ansible/angular-codemirror#v1.1.2", - "angular-cookies": "^1.7.9", - "angular-drag-and-drop-lists": "git+https://git@github.com/ansible/angular-drag-and-drop-lists#v1.4.1", - "angular-duration-format": "^1.0.1", - "angular-gettext": "^2.3.5", - "angular-moment": "^1.3.0", - "angular-mousewheel": "^1.0.5", - "angular-sanitize": "^1.7.9", - "angular-scheduler": "git+https://git@github.com/ansible/angular-scheduler#v0.4.1", - "angular-tz-extensions": "git+https://git@github.com/ansible/angular-tz-extensions#v0.5.2", - "angular-xeditable": "~0.8.0", - "ansi-to-html": "^0.6.3", - "babel-polyfill": "^6.26.0", - "bootstrap": "^4.3.1", - "bootstrap-datepicker": "^1.8.0", - "codemirror": "^5.17.0", - "components-font-awesome": "^4.6.1", - "d3": "^3.5.4", - "dagre": "^0.8.2", - "hamsterjs": "^1.1.2", - "html-entities": "^1.2.1", - "inherits": "^1.0.2", - "javascript-detect-element-resize": "^0.5.3", - "jquery": "^3.4.1", - "jquery-ui": "^1.12.1", - "js-yaml": "^3.13.1", - "legacy-loader": "0.0.2", - "lodash": "^4.17.15", - "lr-infinite-scroll": "git+https://git@github.com/lorenzofox3/lrInfiniteScroll", - "mathjs": "^3.15.0", - "moment": "^2.19.4", - "ng-toast": "git+https://git@github.com/ansible/ngToast#v2.1.1", - "nvd3": "^1.8.6", - "popper.js": "~1.14.4", - "reconnectingwebsocket": "^1.0.0", - "rrule": "git+https://git@github.com/jkbrzt/rrule#4ff63b2f8524fd6d5ba6e80db770953b5cd08a0c", - "select2": "^4.0.2", - "sprintf-js": "^1.0.3", - "timezone-js": "github:ansible/timezone-js#0.4.14", - "titlecase": "^1.1.2" - } -} diff --git a/awx/ui/po/ansible-tower-ui.pot b/awx/ui/po/ansible-tower-ui.pot deleted file mode 100644 index 4f2a7be8d1c2..000000000000 --- a/awx/ui/po/ansible-tower-ui.pot +++ /dev/null @@ -1,6370 +0,0 @@ -msgid "" -msgstr "" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Project-Id-Version: \n" - -#: client/src/projects/add/projects-add.controller.js:165 -#: client/src/projects/edit/projects-edit.controller.js:297 -msgid "%sNote:%s Mercurial does not support password authentication for SSH. Do not put the username and key in the URL. If using Bitbucket and SSH, do not supply your Bitbucket username." -msgstr "" - -#: client/src/projects/add/projects-add.controller.js:144 -#: client/src/projects/edit/projects-edit.controller.js:276 -msgid "%sNote:%s 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." -msgstr "" - -#: client/src/credentials/credentials.form.js:287 -msgid "(defaults to %s)" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:402 -msgid "(seconds)" -msgstr "" - -#: client/src/shared/paginate/paginate.partial.html:66 -msgid "100" -msgstr "" - -#: client/src/shared/paginate/paginate.partial.html:60 -msgid "20" -msgstr "" - -#: client/src/shared/paginate/paginate.partial.html:63 -msgid "50" -msgstr "" - -#: client/lib/components/code-mirror/code-mirror.strings.js:17 -msgid "" -"

\n" -" Enter inventory variables using either JSON or YAML\n" -" syntax. Use the radio button to toggle between the two.\n" -"

\n" -" JSON:\n" -"
\n" -"
\n" -" {\n" -"
\"somevar\": \"somevalue\",\n" -"
\"password\": \"magic\"\n" -"
\n" -" }\n" -"
\n" -" YAML:\n" -"
\n" -"
\n" -" ---\n" -"
somevar: somevalue\n" -"
password: magic\n" -"
\n" -"
\n" -"

\n" -" View JSON examples at\n" -" www.json.org\n" -"

\n" -"

\n" -" View YAML examples at\n" -" \n" -" docs.ansible.com\n" -"

" -msgstr "" - -#: client/features/templates/templates.strings.js:56 -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.

JSON:
{
"somevar": "somevalue",
"password": "magic"
}
YAML:
---
somevar: somevalue
password: magic
" -msgstr "" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:33 -#: client/src/scheduler/scheduler.strings.js:22 -msgid "A schedule name is required." -msgstr "" - -#: client/src/users/add/users-add.controller.js:103 -msgid "A value is required" -msgstr "" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:167 -msgid "A value is required." -msgstr "" - -#: client/src/about/about.route.js:10 -msgid "ABOUT" -msgstr "" - -#: client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html:16 -msgid "ACTION" -msgstr "" - -#: client/src/activity-stream/activity-detail.form.js:23 -msgid "ACTIVITY DETAIL" -msgstr "" - -#: client/src/activity-stream/activitystream.route.js:28 -#: client/src/activity-stream/streams.list.js:14 -#: client/src/activity-stream/streams.list.js:15 -msgid "ACTIVITY STREAM" -msgstr "" - -#: client/src/organizations/linkout/addUsers/addUsers.partial.html:8 -msgid "ADD" -msgstr "" - -#: client/src/notifications/notification-templates-list/add-notifications-action.partial.html:3 -msgid "ADD A NEW TEMPLATE" -msgstr "" - -#: client/features/templates/templates.strings.js:109 -msgid "ADD A TEMPLATE" -msgstr "" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:16 -msgid "ADD SURVEY PROMPT" -msgstr "" - -#: client/src/shared/smart-search/smart-search.partial.html:48 -msgid "ADDITIONAL INFORMATION" -msgstr "" - -#: client/features/output/output.strings.js:84 -msgid "ADDITIONAL_INFORMATION" -msgstr "" - -#: client/src/organizations/linkout/organizations-linkout.route.js:258 -#: client/src/organizations/list/organizations-list.controller.js:85 -msgid "ADMINS" -msgstr "" - -#: client/src/activity-stream/get-target-title.factory.js:4 -msgid "ALL ACTIVITY" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:239 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:254 -msgid "ANY" -msgstr "" - -#: client/src/credentials/credentials.form.js:198 -msgid "API Key" -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:243 -msgid "API Service/Integration Key" -msgstr "" - -#: client/src/notifications/shared/type-change.service.js:60 -msgid "API Token" -msgstr "" - -#: client/features/users/tokens/tokens.strings.js:40 -msgid "APPLICATION" -msgstr "" - -#: client/features/applications/applications.strings.js:27 -msgid "APPLICATION INFORMATION" -msgstr "" - -#: client/features/applications/applications.strings.js:32 -#: client/features/applications/applications.strings.js:8 -#: client/src/activity-stream/get-target-title.factory.js:47 -msgid "APPLICATIONS" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.route.js:19 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.route.js:19 -msgid "ASSOCIATED GROUPS" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.route.js:19 -msgid "ASSOCIATED HOSTS" -msgstr "" - -#: client/lib/components/components.strings.js:87 -msgid "About" -msgstr "" - -#: client/lib/components/components.strings.js:91 -msgid "Access" -msgstr "" - -#: client/src/credentials/credentials.form.js:91 -msgid "Access Key" -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:221 -msgid "Account SID" -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:180 -msgid "Account Token" -msgstr "" - -#: client/src/activity-stream/activity-detail.form.js:36 -msgid "Action" -msgstr "" - -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:20 -#: client/src/inventories-hosts/hosts/hosts.partial.html:47 -#: client/src/shared/list-generator/list-generator.factory.js:595 -msgid "Actions" -msgstr "" - -#: client/features/templates/templates.strings.js:16 -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:17 -#: client/src/templates/templates.list.js:36 -msgid "Activity" -msgstr "" - -#: client/src/configuration/forms/system-form/configuration-system.controller.js:84 -msgid "Activity Stream" -msgstr "" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:113 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:115 -#: client/src/organizations/organizations.form.js:93 -#: client/src/teams/teams.form.js:85 -#: client/src/templates/workflows.form.js:172 -msgid "Add" -msgstr "" - -#: client/src/credentials/credentials.list.js:14 -msgid "Add Credentials" -msgstr "" - -#: client/src/inventories-hosts/inventories/inventory.list.js:13 -msgid "Add Inventories" -msgstr "" - -#: client/src/shared/stateDefinitions.factory.js:304 -msgid "Add Permissions" -msgstr "" - -#: client/src/projects/projects.list.js:13 -msgid "Add Project" -msgstr "" - -#: client/src/shared/form-generator.js:1731 -#: client/src/templates/job_templates/job-template.form.js:481 -#: client/src/templates/workflows.form.js:230 -msgid "Add Survey" -msgstr "" - -#: client/src/teams/teams.list.js:13 -msgid "Add Team" -msgstr "" - -#: client/src/teams/teams.form.js:86 -msgid "Add User" -msgstr "" - -#: client/src/shared/stateDefinitions.factory.js:426 -#: client/src/shared/stateDefinitions.factory.js:594 -#: client/src/users/users.list.js:17 -msgid "Add Users" -msgstr "" - -#: client/src/organizations/organizations.form.js:94 -msgid "Add Users to this organization." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:69 -msgid "Add a group" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:118 -msgid "Add a host" -msgstr "" - -#: client/src/scheduler/schedules.list.js:74 -msgid "Add a new schedule" -msgstr "" - -#: client/features/credentials/legacy.credentials.js:71 -#: client/src/credentials/credentials.form.js:448 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:115 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:117 -#: client/src/organizations/organizations.form.js:134 -#: client/src/projects/projects.form.js:255 -#: client/src/templates/job_templates/job-template.form.js:424 -#: client/src/templates/workflows.form.js:173 -msgid "Add a permission" -msgstr "" - -#: client/src/shared/form-generator.js:1466 -msgid "Admin" -msgstr "" - -#: client/lib/components/components.strings.js:92 -msgid "Administration" -msgstr "" - -#: client/src/organizations/linkout/organizations-linkout.route.js:281 -msgid "Admins" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:391 -msgid "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." -msgstr "" - -#: client/lib/components/components.strings.js:99 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:37 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:43 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:65 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:74 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:139 -msgid "All" -msgstr "" - -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:23 -msgid "All Activity" -msgstr "" - -#: client/features/portalMode/index.view.html:33 -msgid "All Jobs" -msgstr "" - -#: client/src/templates/job_templates/job-template.form.js:303 -#: client/src/templates/job_templates/job-template.form.js:310 -msgid "Allow Provisioning Callbacks" -msgstr "" - -#: client/features/templates/templates.strings.js:103 -#: client/src/workflow-results/workflow-results.controller.js:82 -msgid "Always" -msgstr "" - -#: client/features/projects/projects.strings.js:23 -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:277 -msgid "An SCM update does not appear to be running for project:" -msgstr "" - -#: client/src/organizations/organizations.form.js:47 -#: client/src/organizations/organizations.form.js:52 -#: client/src/projects/projects.form.js:207 -#: client/src/projects/projects.form.js:212 -#: client/src/templates/job_templates/job-template.form.js:239 -#: client/src/templates/job_templates/job-template.form.js:245 -msgid "Ansible Environment" -msgstr "" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:62 -#: client/src/templates/survey-maker/shared/question-definition.form.js:68 -msgid "Answer Type" -msgstr "" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:44 -#: client/src/templates/survey-maker/shared/question-definition.form.js:53 -msgid "Answer Variable Name" -msgstr "" - -#: client/lib/components/components.strings.js:85 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:24 -msgid "Applications" -msgstr "" - -#: client/src/standard-out/standard-out-factories/delete-job.factory.js:110 -msgid "Are you sure you want to delete this job?" -msgstr "" - -#: client/src/notifications/notification-templates-list/list.controller.js:228 -msgid "Are you sure you want to delete this notification template?" -msgstr "" - -#: client/src/teams/list/teams-list.controller.js:80 -msgid "Are you sure you want to delete this team?" -msgstr "" - -#: client/src/users/list/users-list.controller.js:93 -msgid "Are you sure you want to delete this user?" -msgstr "" - -#: client/features/templates/templates.strings.js:99 -msgid "Are you sure you want to delete this workflow node?" -msgstr "" - -#: client/lib/services/base-string.service.js:82 -msgid "Are you sure you want to delete this {{ resourceType }}?" -msgstr "" - -#: client/src/partials/survey-maker-modal.html:13 -msgid "Are you sure you want to delete this {{deleteMode}}?" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-disassociate.partial.html:25 -msgid "Are you sure you want to disassociate the group below from" -msgstr "" - -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.partial.html:23 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-disassociate.partial.html:25 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-disassociate.partial.html:26 -msgid "Are you sure you want to disassociate the host below from" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:47 -msgid "Are you sure you want to permanently delete the group below from the inventory?" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/hosts/list/host-list.controller.js:104 -msgid "Are you sure you want to permanently delete the host below from the inventory?" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:69 -msgid "Are you sure you want to permanently delete the inventory source below from the inventory?" -msgstr "" - -#: client/src/projects/edit/projects-edit.controller.js:253 -msgid "Are you sure you want to remove the %s below from %s?" -msgstr "" - -#: client/lib/services/base-string.service.js:87 -#: client/src/standard-out/standard-out-factories/delete-job.factory.js:109 -msgid "Are you sure you want to submit the request to cancel this job?" -msgstr "" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:39 -msgid "Arguments" -msgstr "" - -#: client/src/credentials/credentials.form.js:232 -#: client/src/credentials/credentials.form.js:271 -#: client/src/credentials/credentials.form.js:311 -#: client/src/credentials/credentials.form.js:397 -msgid "Ask at runtime?" -msgstr "" - -#: client/src/instance-groups/instance-groups.strings.js:31 -msgid "Associate an existing Instance" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:69 -msgid "Associate an existing group" -msgstr "" - -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.list.js:51 -msgid "Associate this host with a new group" -msgstr "" - -#: client/src/shared/form-generator.js:1468 -msgid "Auditor" -msgstr "" - -#: client/src/credentials/credentials.form.js:72 -msgid "Authentication for network device access. This can include SSH keys, usernames, passwords, and authorize information. Network credentials are used when submitting jobs to run playbooks against network devices." -msgstr "" - -#: client/src/credentials/credentials.form.js:68 -msgid "Authentication for remote machine access. This can include SSH keys, usernames, passwords, and sudo information. Machine credentials are used when submitting jobs to run playbooks against remote hosts." -msgstr "" - -#: client/src/credentials/credentials.form.js:343 -msgid "Authorize" -msgstr "" - -#: client/src/credentials/credentials.form.js:351 -msgid "Authorize Password" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:226 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:240 -msgid "Availability Zone:" -msgstr "" - -#: client/src/configuration/forms/auth-form/configuration-auth.controller.js:91 -msgid "Azure AD" -msgstr "" - -#: client/src/shared/directives.js:91 -msgid "BROWSE" -msgstr "" - -#: client/features/output/output.strings.js:105 -msgid "Back to Top" -msgstr "" - -#: client/src/projects/projects.form.js:81 -msgid "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." -msgstr "" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:129 -msgid "Become Privilege Escalation" -msgstr "" - -#: client/src/license/license.partial.html:107 -msgid "Browse" -msgstr "" - -#: client/src/license/license.partial.html:129 -msgid "" -"By default, Tower collects and transmits analytics data on Tower usage to Red Hat. This data is used to enhance future releases of the Tower Software and help streamline customer experience and success. For more information, see\n" -"\t\t\t\t\t\t\t\t\t\t\n" -"\t\t\t\t\t\t\t\t\t\t\t\tthis Tower documentation page\n" -"\t\t\t\t\t\t\t\t\t\t. Uncheck this box to disable this feature." -msgstr "" - -#: client/lib/services/base-string.service.js:61 -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.partial.html:28 -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:51 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-disassociate.partial.html:29 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-disassociate.partial.html:29 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-disassociate.partial.html:30 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:73 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal.partial.html:16 -#: client/src/inventories-hosts/shared/associate-groups/associate-groups.partial.html:16 -#: client/src/inventories-hosts/shared/associate-hosts/associate-hosts.partial.html:16 -#: client/src/job-submission/job-submission.partial.html:370 -#: client/src/partials/survey-maker-modal.html:17 -#: client/src/partials/survey-maker-modal.html:85 -#: client/src/shared/instance-groups-multiselect/instance-groups-modal/instance-groups-modal.partial.html:17 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html:68 -msgid "CANCEL" -msgstr "" - -#: client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html:28 -msgid "CHANGES" -msgstr "" - -#: client/features/templates/templates.strings.js:115 -msgid "CHECK" -msgstr "" - -#: client/lib/components/components.strings.js:20 -msgid "CHOOSE A FILE" -msgstr "" - -#: client/features/output/output.strings.js:86 -#: client/src/shared/smart-search/smart-search.partial.html:26 -msgid "CLEAR ALL" -msgstr "" - -#: client/features/applications/applications.strings.js:25 -msgid "CLIENT ID" -msgstr "" - -#: client/features/applications/applications.strings.js:26 -msgid "CLIENT SECRET" -msgstr "" - -#: client/lib/services/base-string.service.js:62 -#: client/lib/services/base-string.service.js:75 -#: client/src/partials/survey-maker-modal.html:86 -msgid "CLOSE" -msgstr "" - -#: client/features/jobs/routes/hostCompletedJobs.route.js:20 -#: client/features/jobs/routes/templateCompletedJobs.route.js:21 -#: client/features/jobs/routes/workflowJobTemplateCompletedJobs.route.js:21 -msgid "COMPLETED JOBS" -msgstr "" - -#: client/features/templates/templates.strings.js:32 -#: client/src/scheduler/scheduler.strings.js:63 -msgid "CONFIRM" -msgstr "" - -#: client/lib/services/base-string.service.js:73 -msgid "COPY" -msgstr "" - -#: client/features/users/tokens/tokens.strings.js:25 -msgid "COULD NOT CREATE TOKEN" -msgstr "" - -#: client/src/instance-groups/instance-groups.strings.js:46 -msgid "CPU" -msgstr "" - -#: client/src/shared/stateDefinitions.factory.js:161 -msgid "CREATE %s" -msgstr "" - -#: client/features/applications/applications.strings.js:9 -msgid "CREATE APPLICATION" -msgstr "" - -#: client/features/credentials/credentials.strings.js:8 -#: client/src/credentials/credentials.form.js:16 -msgid "CREATE CREDENTIAL" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/groups/add/groups-add.route.js:8 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:16 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-add.route.js:8 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js:16 -msgid "CREATE GROUP" -msgstr "" - -#: client/src/inventories-hosts/hosts/host.form.js:17 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-add.route.js:8 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:17 -#: client/src/inventories-hosts/inventories/related/hosts/add/host-add.route.js:8 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:17 -msgid "CREATE HOST" -msgstr "" - -#: client/src/instance-groups/instance-groups.strings.js:10 -msgid "CREATE INSTANCE GROUP" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.route.js:8 -msgid "CREATE INVENTORY SOURCE" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/list/schedule/sources-schedule-add.route.js:9 -#: client/src/scheduler/scheduler.strings.js:8 -#: client/src/scheduler/schedules.route.js:161 -#: client/src/scheduler/schedules.route.js:242 -#: client/src/scheduler/schedules.route.js:73 -msgid "CREATE SCHEDULE" -msgstr "" - -#: client/src/management-jobs/scheduler/main.js:83 -msgid "CREATE SCHEDULED JOB" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:32 -msgid "CREATE SOURCE" -msgstr "" - -#: client/features/users/tokens/tokens.strings.js:18 -#: client/features/users/tokens/tokens.strings.js:9 -#: client/features/users/tokens/users-tokens-add.route.js:49 -msgid "CREATE TOKEN" -msgstr "" - -#: client/features/output/output.strings.js:109 -msgid "CREATED" -msgstr "" - -#: client/src/job-submission/job-submission.partial.html:351 -#: client/src/partials/job-template-details.html:2 -msgid "CREDENTIAL" -msgstr "" - -#: client/src/credential-types/credential-types.form.js:21 -msgid "CREDENTIAL TYPE" -msgstr "" - -#: client/src/job-submission/job-submission.partial.html:92 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html:60 -msgid "CREDENTIAL TYPE:" -msgstr "" - -#: client/src/activity-stream/get-target-title.factory.js:11 -#: client/src/credential-types/credential-types.list.js:12 -#: client/src/credential-types/main.js:44 -msgid "CREDENTIAL TYPES" -msgstr "" - -#: client/features/credentials/legacy.credentials.js:11 -#: client/src/activity-stream/get-target-title.factory.js:17 -#: client/src/credentials/credentials.list.js:15 -#: client/src/credentials/credentials.list.js:16 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html:5 -msgid "CREDENTIALS" -msgstr "" - -#: client/features/credentials/credentials.strings.js:30 -msgid "CREDENTIALS PERMISSIONS" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:402 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:414 -#: client/src/projects/projects.form.js:200 -msgid "Cache Timeout" -msgstr "" - -#: client/src/projects/projects.form.js:189 -msgid "Cache Timeout%s (seconds)%s" -msgstr "" - -#: client/src/users/list/users-list.controller.js:85 -msgid "Call to %s failed. DELETE returned status:" -msgstr "" - -#: client/src/projects/edit/projects-edit.controller.js:247 -msgid "Call to %s failed. POST returned status:" -msgstr "" - -#: client/src/management-jobs/card/card.controller.js:29 -msgid "Call to %s failed. Return status: %d" -msgstr "" - -#: client/lib/services/base-string.service.js:94 -msgid "Call to {{ path }} failed. {{ action }} returned status: {{ status }}." -msgstr "" - -#: client/features/output/output.strings.js:17 -#: client/lib/services/base-string.service.js:86 -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:105 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:187 -#: client/src/configuration/forms/settings-form.controller.js:429 -#: client/src/scheduler/scheduler.strings.js:56 -#: client/src/shared/form-generator.js:1719 -#: client/src/shared/lookup/lookup-modal.partial.html:19 -#: client/src/standard-out/standard-out-factories/delete-job.factory.js:33 -#: client/src/workflow-results/workflow-results.controller.js:46 -msgid "Cancel" -msgstr "" - -#: client/lib/services/base-string.service.js:88 -msgid "Cancel Job" -msgstr "" - -#: client/features/projects/projects.strings.js:29 -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:251 -msgid "Cancel Not Allowed" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:100 -msgid "Cancel sync process" -msgstr "" - -#: client/src/projects/projects.list.js:122 -msgid "Cancel the SCM update" -msgstr "" - -#: client/lib/services/base-string.service.js:100 -msgid "Cancel the {{resourceType}}" -msgstr "" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:32 -msgid "Canceled. Click for details" -msgstr "" - -#: client/src/shared/smart-search/smart-search.controller.js:162 -msgid "Cannot search running job" -msgstr "" - -#: client/src/instance-groups/instance-groups.list.js:22 -msgid "Capacity" -msgstr "" - -#: client/src/projects/projects.form.js:83 -msgid "Change %s when deploying {{BRAND_NAME}} to change this location." -msgstr "" - -#: client/src/activity-stream/activity-detail.form.js:41 -msgid "Changes" -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:355 -msgid "Channel" -msgstr "" - -#: client/features/templates/templates.strings.js:62 -msgid "Check" -msgstr "" - -#: client/src/shared/form-generator.js:1087 -msgid "Choose a %s" -msgstr "" - -#: client/features/templates/templates.strings.js:53 -msgid "Choose a job type" -msgstr "" - -#: client/features/templates/templates.strings.js:54 -msgid "Choose a verbosity" -msgstr "" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:64 -msgid "Choose an answer type" -msgstr "" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:67 -msgid "Choose an answer type or format you want as the prompt for the user. Refer to the Ansible Tower Documentation for more additional information about each option." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:112 -msgid "Choose an inventory file" -msgstr "" - -#: client/src/shared/directives.js:92 -msgid "Choose file" -msgstr "" - -#: client/src/license/license.partial.html:97 -msgid "Choose your license file, agree to the End User License Agreement, and click submit." -msgstr "" - -#: client/src/projects/projects.form.js:157 -msgid "Clean" -msgstr "" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:299 -msgid "Clear" -msgstr "" - -#: client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.directive.js:64 -msgid "Click for details" -msgstr "" - -#: client/features/templates/templates.strings.js:15 -msgid "Click here to open the workflow visualizer" -msgstr "" - -#: client/src/templates/workflows/edit-workflow/workflow-edit.controller.js:269 -msgid "Click here to open the workflow visualizer." -msgstr "" - -#: client/src/inventories-hosts/inventories/inventory.list.js:16 -msgid "Click on a row to select it, and click Finished when done. Click the %s button to create a new inventory." -msgstr "" - -#: client/src/teams/teams.list.js:16 -msgid "Click on a row to select it, and click Finished when done. Click the %s button to create a new team." -msgstr "" - -#: client/src/templates/templates.list.js:17 -msgid "Click on a row to select it, and click Finished when done. Use the %s button to create a new job template." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:138 -msgid "Click on the regions field to see a list of regions for your cloud provider. You can select multiple regions, or choose" -msgstr "" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:277 -msgid "Click the" -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:13 -msgid "Click to edit schedule." -msgstr "" - -#: client/src/credentials/credentials.form.js:321 -msgid "Client ID" -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:254 -msgid "Client Identifier" -msgstr "" - -#: client/src/credentials/credentials.form.js:330 -msgid "Client Secret" -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:55 -#: client/src/shared/form-generator.js:1723 -msgid "Close" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:26 -msgid "Cloud source not configured." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:25 -msgid "Cloud source not configured. Click" -msgstr "" - -#: client/src/credentials/factories/become-method-change.factory.js:80 -#: client/src/credentials/factories/kind-change.factory.js:137 -msgid "CloudForms URL" -msgstr "" - -#: client/features/output/output.strings.js:18 -#: client/src/workflow-results/workflow-results.controller.js:198 -msgid "Collapse Output" -msgstr "" - -#: client/src/inventories-hosts/hosts/host.form.js:129 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:128 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:155 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:172 -#: client/src/templates/job_templates/job-template.form.js:456 -#: client/src/templates/workflows.form.js:205 -msgid "Completed Jobs" -msgstr "" - -#: client/src/management-jobs/card/card.partial.html:34 -msgid "Configure Notifications" -msgstr "" - -#: client/src/users/users.form.js:83 -msgid "Confirm Password" -msgstr "" - -#: client/src/configuration/forms/settings-form.controller.js:436 -msgid "Confirm Reset" -msgstr "" - -#: client/src/configuration/forms/settings-form.controller.js:445 -msgid "Confirm factory reset" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.directive.js:82 -msgid "Confirm the removal of the" -msgstr "" - -#: client/src/teams/teams.form.js:24 -#: client/src/users/users.form.js:25 -msgid "Contact your System Administrator to grant you the appropriate permissions to add and edit Users and Teams." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/groups/factories/get-hosts-status-msg.factory.js:18 -msgid "Contains 0 hosts." -msgstr "" - -#: client/src/templates/job_templates/job-template.form.js:179 -msgid "Control the level of output ansible will produce as the playbook executes." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:337 -msgid "Control the level of output ansible will produce for inventory source update jobs." -msgstr "" - -#: client/lib/components/components.strings.js:52 -msgid "Copied to clipboard." -msgstr "" - -#: client/src/credentials/credentials.list.js:73 -#: client/src/inventories-hosts/inventories/inventory.list.js:105 -#: client/src/inventory-scripts/inventory-scripts.list.js:61 -#: client/src/notifications/notificationTemplates.list.js:82 -#: client/src/projects/projects.list.js:100 -#: client/src/templates/templates.list.js:93 -msgid "Copy" -msgstr "" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:56 -msgid "Copy Inventory" -msgstr "" - -#: client/src/credentials/credentials.list.js:76 -msgid "Copy credential" -msgstr "" - -#: client/lib/components/components.strings.js:51 -msgid "Copy full revision to clipboard." -msgstr "" - -#: client/src/inventory-scripts/inventory-scripts.list.js:64 -msgid "Copy inventory script" -msgstr "" - -#: client/src/notifications/notificationTemplates.list.js:85 -msgid "Copy notification" -msgstr "" - -#: client/src/projects/projects.list.js:103 -msgid "Copy project" -msgstr "" - -#: client/src/templates/templates.list.js:96 -msgid "Copy template" -msgstr "" - -#: client/lib/services/base-string.service.js:98 -msgid "Copy {{resourceType}}" -msgstr "" - -#: client/src/about/about.partial.html:27 -msgid "" -"Copyright © 2018 Red Hat, Inc.
\n" -" Visit Ansible.com for more information.
" -msgstr "" - -#: client/lib/components/components.strings.js:88 -msgid "Copyright © 2018 Red Hat, Inc." -msgstr "" - -#: client/src/users/users.list.js:44 -msgid "Create New" -msgstr "" - -#: client/features/applications/applications.strings.js:20 -msgid "Create a new Application" -msgstr "" - -#: client/src/instance-groups/instance-groups.strings.js:30 -msgid "Create a new Instance Group" -msgstr "" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:47 -msgid "Create a new Smart Inventory from search results.

Note: changing the organization of the Smart Inventory could change the hosts included in the Smart Inventory." -msgstr "" - -#: client/src/credentials/credentials.list.js:52 -msgid "Create a new credential" -msgstr "" - -#: client/src/credential-types/credential-types.list.js:42 -msgid "Create a new credential type" -msgstr "" - -#: client/src/inventory-scripts/inventory-scripts.list.js:40 -msgid "Create a new custom inventory" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:69 -msgid "Create a new group" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:123 -msgid "Create a new host" -msgstr "" - -#: client/src/inventories-hosts/inventories/inventory.list.js:76 -msgid "Create a new inventory" -msgstr "" - -#: client/src/notifications/notificationTemplates.list.js:52 -msgid "Create a new notification template" -msgstr "" - -#: client/src/organizations/list/organizations-list.partial.html:21 -msgid "Create a new organization" -msgstr "" - -#: client/src/projects/projects.list.js:75 -msgid "Create a new project" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:68 -msgid "Create a new source" -msgstr "" - -#: client/src/teams/teams.list.js:43 -msgid "Create a new team" -msgstr "" - -#: client/src/templates/templates.list.js:56 -msgid "Create a new template" -msgstr "" - -#: client/src/users/users.list.js:48 -msgid "Create a new user" -msgstr "" - -#: client/features/output/output.strings.js:50 -#: client/features/templates/templates.strings.js:26 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:73 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:74 -#: client/src/job-submission/job-submission.partial.html:18 -#: client/src/projects/add/projects-add.controller.js:139 -#: client/src/projects/add/projects-add.controller.js:152 -#: client/src/projects/add/projects-add.controller.js:161 -#: client/src/projects/add/projects-add.controller.js:180 -#: client/src/projects/edit/projects-edit.controller.js:273 -#: client/src/projects/edit/projects-edit.controller.js:284 -#: client/src/projects/edit/projects-edit.controller.js:293 -#: client/src/projects/edit/projects-edit.controller.js:312 -#: client/src/templates/job_templates/job-template.form.js:121 -msgid "Credential" -msgstr "" - -#: client/features/templates/templates.strings.js:37 -msgid "Credential Type" -msgstr "" - -#: client/lib/components/components.strings.js:74 -#: client/lib/models/models.strings.js:12 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:34 -msgid "Credential Types" -msgstr "" - -#: client/features/jobs/jobs.strings.js:16 -#: client/features/templates/templates.strings.js:19 -#: client/lib/components/components.strings.js:73 -#: client/lib/models/models.strings.js:8 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:128 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:58 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:26 -#: client/src/templates/job_templates/job-template.form.js:133 -msgid "Credentials" -msgstr "" - -#: client/features/templates/templates.strings.js:38 -msgid "Credentials that require passwords on launch are not permitted for template schedules and workflow nodes. The following credentials must be removed or replaced to proceed:" -msgstr "" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:17 -msgid "Critical" -msgstr "" - -#: client/src/shared/directives.js:93 -msgid "Current Image:" -msgstr "" - -#: client/features/output/output.strings.js:36 -msgid "Currently following output as it arrives. Click to unfollow" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:171 -msgid "Custom Inventory Script" -msgstr "" - -#: client/src/inventory-scripts/inventory-scripts.form.js:50 -#: client/src/inventory-scripts/inventory-scripts.form.js:60 -msgid "Custom Script" -msgstr "" - -#: client/src/home/home.route.js:21 -msgid "DASHBOARD" -msgstr "" - -#: client/features/users/tokens/tokens.strings.js:28 -#: client/lib/services/base-string.service.js:72 -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:52 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:74 -#: client/src/notifications/notification-templates-list/list.controller.js:230 -#: client/src/organizations/list/organizations-list.controller.js:196 -#: client/src/partials/survey-maker-modal.html:18 -#: client/src/projects/edit/projects-edit.controller.js:255 -#: client/src/standard-out/standard-out-factories/delete-job.factory.js:116 -#: client/src/users/list/users-list.controller.js:95 -msgid "DELETE" -msgstr "" - -#: client/src/partials/survey-maker-modal.html:84 -msgid "DELETE SURVEY" -msgstr "" - -#: client/features/templates/templates.strings.js:117 -msgid "DELETED" -msgstr "" - -#: client/features/users/tokens/tokens.strings.js:36 -msgid "DESCRIPTION" -msgstr "" - -#: client/features/templates/templates.strings.js:119 -#: client/src/instance-groups/instance-groups.strings.js:24 -#: client/src/workflow-results/workflow-results.controller.js:69 -msgid "DETAILS" -msgstr "" - -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.partial.html:29 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-disassociate.partial.html:30 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-disassociate.partial.html:30 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-disassociate.partial.html:31 -msgid "DISASSOCIATE" -msgstr "" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal.partial.html:5 -msgid "DYNAMIC HOSTS" -msgstr "" - -#: client/lib/components/components.strings.js:68 -msgid "Dashboard" -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:52 -msgid "Date format" -msgstr "" - -#: client/src/configuration/forms/auth-form/configuration-auth.controller.js:101 -msgid "Default" -msgstr "" - -#: client/features/output/output.strings.js:19 -#: client/lib/services/base-string.service.js:79 -#: client/src/credential-types/credential-types.list.js:73 -#: client/src/credential-types/list/list.controller.js:106 -#: client/src/credentials/credentials.list.js:92 -#: client/src/credentials/list/credentials-list.controller.js:176 -#: client/src/inventories-hosts/inventories/inventory.list.js:121 -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:157 -#: client/src/inventory-scripts/inventory-scripts.list.js:79 -#: client/src/inventory-scripts/list/list.controller.js:126 -#: client/src/notifications/notification-templates-list/list.controller.js:226 -#: client/src/notifications/notificationTemplates.list.js:100 -#: client/src/organizations/list/organizations-list.controller.js:192 -#: client/src/projects/edit/projects-edit.controller.js:252 -#: client/src/scheduler/schedules.list.js:100 -#: client/src/standard-out/standard-out-factories/delete-job.factory.js:37 -#: client/src/teams/teams.list.js:72 -#: client/src/templates/templates.list.js:109 -#: client/src/users/list/users-list.controller.js:91 -#: client/src/users/users.list.js:79 -#: client/src/workflow-results/workflow-results.controller.js:47 -msgid "Delete" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:6 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:6 -msgid "Delete Group" -msgstr "" - -#: client/src/templates/survey-maker/surveys/init.factory.js:23 -msgid "Delete Question" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:194 -msgid "Delete Source" -msgstr "" - -#: client/src/credentials/credentials.list.js:94 -msgid "Delete credential" -msgstr "" - -#: client/src/credential-types/credential-types.list.js:75 -msgid "Delete credential type" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:101 -#: client/src/inventories-hosts/inventory-hosts.strings.js:19 -msgid "Delete group" -msgid_plural "Delete groups" -msgstr[0] "" -msgstr[1] "" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:48 -msgid "Delete groups" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:37 -msgid "Delete groups and hosts" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:100 -#: client/src/inventories-hosts/inventory-hosts.strings.js:21 -msgid "Delete host" -msgid_plural "Delete hosts" -msgstr[0] "" -msgstr[1] "" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:59 -msgid "Delete hosts" -msgstr "" - -#: client/src/inventories-hosts/inventories/inventory.list.js:123 -msgid "Delete inventory" -msgstr "" - -#: client/src/inventory-scripts/inventory-scripts.list.js:81 -msgid "Delete inventory script" -msgstr "" - -#: client/src/notifications/notificationTemplates.list.js:102 -msgid "Delete notification" -msgstr "" - -#: client/src/projects/projects.form.js:167 -msgid "Delete on Update" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:27 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:27 -msgid "Delete or promote the group's children?" -msgstr "" - -#: client/src/scheduler/schedules.list.js:103 -msgid "Delete schedule" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:117 -msgid "Delete source" -msgstr "" - -#: client/src/teams/teams.list.js:76 -msgid "Delete team" -msgstr "" - -#: client/src/templates/templates.list.js:112 -msgid "Delete template" -msgstr "" - -#: client/src/projects/projects.form.js:169 -msgid "Delete the local repository in its entirety prior to performing an update." -msgstr "" - -#: client/src/projects/projects.list.js:116 -msgid "Delete the project" -msgstr "" - -#: client/src/scheduler/scheduled-jobs.list.js:81 -msgid "Delete the schedule" -msgstr "" - -#: client/lib/services/base-string.service.js:99 -msgid "Delete the {{resourceType}}" -msgstr "" - -#: client/src/users/users.list.js:83 -msgid "Delete user" -msgstr "" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:14 -msgid "Delete {{ group }} and {{ host }}" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:23 -msgid "Deleting group" -msgstr "" - -#: client/lib/services/base-string.service.js:81 -msgid "Deleting this {{ resourceType }} will make the following resources unavailable." -msgstr "" - -#: client/src/projects/projects.form.js:169 -msgid "Depending on the size of the repository this may significantly increase the amount of time required to complete an update." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:246 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:261 -msgid "Describe Instances documentation" -msgstr "" - -#: client/src/credential-types/credential-types.form.js:34 -#: client/src/credentials/credentials.form.js:39 -#: client/src/inventories-hosts/hosts/host.form.js:63 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:39 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js:40 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:62 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:62 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:58 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:28 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:36 -#: client/src/inventory-scripts/inventory-scripts.form.js:35 -#: client/src/notifications/notificationTemplates.form.js:39 -#: client/src/organizations/organizations.form.js:33 -#: client/src/projects/projects.form.js:37 -#: client/src/teams/teams.form.js:35 -#: client/src/templates/job_templates/job-template.form.js:41 -#: client/src/templates/survey-maker/shared/question-definition.form.js:36 -#: client/src/templates/workflows.form.js:49 -#: client/src/users/users.form.js:147 -#: client/src/users/users.form.js:173 -msgid "Description" -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:136 -#: client/src/notifications/notificationTemplates.form.js:140 -#: client/src/notifications/notificationTemplates.form.js:152 -#: client/src/notifications/notificationTemplates.form.js:156 -msgid "Destination Channels" -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:430 -#: client/src/notifications/notificationTemplates.form.js:434 -msgid "Destination Channels or Users" -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:205 -#: client/src/notifications/notificationTemplates.form.js:206 -msgid "Destination SMS Number" -msgstr "" - -#: client/features/applications/applications.strings.js:15 -#: client/features/credentials/credentials.strings.js:13 -#: client/features/output/output.strings.js:40 -#: client/features/users/tokens/tokens.strings.js:14 -#: client/src/license/license.partial.html:5 -#: client/src/shared/form-generator.js:1501 -msgid "Details" -msgstr "" - -#: client/src/job-submission/job-submission.partial.html:263 -msgid "Diff Mode" -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:369 -#: client/src/notifications/notificationTemplates.form.js:401 -msgid "Disable SSL Verification" -msgstr "" - -#: client/src/templates/survey-maker/surveys/init.factory.js:518 -msgid "Disable survey" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-disassociate.partial.html:6 -msgid "Disassociate Group From Group" -msgstr "" - -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.partial.html:6 -msgid "Disassociate Host" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-disassociate.partial.html:6 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-disassociate.partial.html:6 -msgid "Disassociate Host From Group" -msgstr "" - -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.list.js:65 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:110 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:98 -msgid "Disassociate group" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:87 -msgid "Disassociate host" -msgstr "" - -#: client/src/templates/survey-maker/surveys/init.factory.js:21 -msgid "Disable Survey" -msgstr "" - -#: client/src/configuration/forms/auth-form/configuration-auth.controller.js:54 -#: client/src/configuration/forms/settings-form.controller.js:402 -#: client/src/configuration/forms/system-form/configuration-system.controller.js:52 -msgid "Discard changes" -msgstr "" - -#: client/src/teams/teams.form.js:149 -msgid "Dissassociate permission from team" -msgstr "" - -#: client/src/users/users.form.js:227 -msgid "Dissassociate permission from user" -msgstr "" - -#: client/src/templates/job_templates/job-template.form.js:270 -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 "" - -#: client/src/credentials/credentials.form.js:384 -#: client/src/credentials/factories/become-method-change.factory.js:54 -#: client/src/credentials/factories/kind-change.factory.js:111 -msgid "Domain Name" -msgstr "" - -#: client/features/output/output.strings.js:20 -msgid "Download Output" -msgstr "" - -#: client/src/inventory-scripts/inventory-scripts.form.js:59 -msgid "Drag and drop your custom inventory script file here or create one in the field to import your custom inventory. Refer to the Ansible Tower documentation for example syntax." -msgstr "" - -#: client/src/templates/survey-maker/surveys/init.factory.js:24 -msgid "Drag to reorder question" -msgstr "" - -#: client/src/partials/survey-maker-modal.html:77 -msgid "Drop question here to reorder" -msgstr "" - -#: client/features/applications/applications.strings.js:10 -msgid "EDIT APPLICATION" -msgstr "" - -#: client/features/credentials/credentials.strings.js:9 -msgid "EDIT CREDENTIAL" -msgstr "" - -#: client/src/instance-groups/instance-groups.strings.js:11 -msgid "EDIT INSTANCE GROUP" -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:9 -msgid "EDIT SCHEDULE" -msgstr "" - -#: client/src/management-jobs/scheduler/main.js:97 -msgid "EDIT SCHEDULED JOB" -msgstr "" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:17 -msgid "EDIT SURVEY PROMPT" -msgstr "" - -#: client/features/templates/templates.strings.js:110 -msgid "EDIT TEMPLATE" -msgstr "" - -#: client/lib/components/components.strings.js:9 -msgid "ENCRYPTED" -msgstr "" - -#: client/features/output/output.strings.js:88 -msgid "EXAMPLES" -msgstr "" - -#: client/src/shared/smart-search/smart-search.partial.html:36 -msgid "EXAMPLES:" -msgstr "" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:15 -msgid "EXECUTE COMMAND" -msgstr "" - -#: client/lib/components/code-mirror/code-mirror.strings.js:10 -msgid "EXPAND" -msgstr "" - -#: client/features/applications/applications.strings.js:33 -#: client/features/users/tokens/tokens.strings.js:37 -msgid "EXPIRATION" -msgstr "" - -#: client/features/users/tokens/tokens.strings.js:24 -msgid "EXPIRES" -msgstr "" - -#: client/lib/components/code-mirror/code-mirror.strings.js:48 -#: client/lib/components/code-mirror/code-mirror.strings.js:8 -msgid "EXTRA VARIABLES" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:379 -msgid "Each time a job runs using this inventory, refresh the inventory from the selected source before executing job tasks." -msgstr "" - -#: client/src/projects/projects.form.js:180 -msgid "Each time a job runs using this project, update the revision of the project prior to starting the job." -msgstr "" - -#: client/src/credential-types/credential-types.list.js:56 -#: client/src/credentials/credentials.list.js:66 -#: client/src/inventories-hosts/inventories/inventory.list.js:98 -#: client/src/inventory-scripts/inventory-scripts.list.js:54 -#: client/src/notifications/notificationTemplates.list.js:66 -#: client/src/notifications/notificationTemplates.list.js:75 -#: client/src/scheduler/schedules.list.js:85 -#: client/src/teams/teams.list.js:55 -#: client/src/templates/templates.list.js:80 -#: client/src/users/users.list.js:60 -msgid "Edit" -msgstr "" - -#: client/src/templates/survey-maker/surveys/init.factory.js:22 -msgid "Edit Question" -msgstr "" - -#: client/src/shared/form-generator.js:1735 -#: client/src/templates/job_templates/job-template.form.js:488 -#: client/src/templates/workflows.form.js:237 -msgid "Edit Survey" -msgstr "" - -#: client/src/credential-types/credential-types.list.js:58 -msgid "Edit credential type" -msgstr "" - -#: client/src/credentials/credentials.list.js:68 -msgid "Edit credential" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:85 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:96 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:84 -msgid "Edit group" -msgstr "" - -#: client/src/inventories-hosts/hosts/host.list.js:83 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:73 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:79 -#: client/src/inventories-hosts/inventory-hosts.strings.js:25 -msgid "Edit host" -msgstr "" - -#: client/src/inventories-hosts/inventories/inventory.list.js:100 -msgid "Edit inventory" -msgstr "" - -#: client/src/inventory-scripts/inventory-scripts.list.js:56 -msgid "Edit inventory script" -msgstr "" - -#: client/src/notifications/notificationTemplates.list.js:68 -msgid "Edit notification" -msgstr "" - -#: client/src/scheduler/schedules.list.js:88 -msgid "Edit schedule" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:83 -msgid "Edit source" -msgstr "" - -#: client/src/teams/teams.list.js:59 -msgid "Edit team" -msgstr "" - -#: client/src/templates/templates.list.js:82 -msgid "Edit template" -msgstr "" - -#: client/src/projects/projects.list.js:87 -msgid "Edit the project" -msgstr "" - -#: client/src/scheduler/scheduled-jobs.list.js:67 -#: client/src/workflow-results/workflow-results.controller.js:51 -msgid "Edit the schedule" -msgstr "" - -#: client/src/workflow-results/workflow-results.controller.js:50 -msgid "Edit the slice job template" -msgstr "" - -#: client/src/workflow-results/workflow-results.controller.js:48 -msgid "Edit the user" -msgstr "" - -#: client/src/workflow-results/workflow-results.controller.js:49 -msgid "Edit the workflow job template" -msgstr "" - -#: client/src/users/users.list.js:64 -msgid "Edit user" -msgstr "" - -#: client/features/projects/projects.strings.js:22 -msgid "Either you do not have access or the SCM update process completed" -msgstr "" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:251 -msgid "Either you do not have access or the SCM update process completed. Click the" -msgstr "" - -#: client/features/output/output.strings.js:98 -#: client/src/workflow-results/workflow-results.controller.js:77 -msgid "Elapsed" -msgstr "" - -#: client/src/credentials/credentials.form.js:191 -#: client/src/users/users.form.js:53 -msgid "Email" -msgstr "" - -#: client/src/templates/job_templates/job-template.form.js:316 -#: client/src/templates/job_templates/job-template.form.js:321 -#: client/src/templates/workflows.form.js:125 -#: client/src/templates/workflows.form.js:130 -msgid "Enable Concurrent Jobs" -msgstr "" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:124 -#: client/src/templates/job_templates/job-template.form.js:292 -#: client/src/templates/job_templates/job-template.form.js:297 -msgid "Enable Privilege Escalation" -msgstr "" - -#: client/src/templates/survey-maker/surveys/init.factory.js:518 -msgid "Enable survey" -msgstr "" - -#: client/src/templates/job_templates/job-template.form.js:307 -msgid "Enables creation of a provisioning callback URL. Using the URL a host can contact {{BRAND_NAME}} and request a configuration update using this job template." -msgstr "" - -#: client/src/credentials/factories/credential-form-save.factory.js:73 -msgid "Encrypted credentials are not supported." -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:44 -msgid "End" -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:46 -msgid "End Date" -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:48 -msgid "End Time" -msgstr "" - -#: client/src/license/license.partial.html:113 -msgid "End User License Agreement" -msgstr "" - -#: client/src/inventories-hosts/hosts/host.form.js:73 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:72 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:72 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:68 -msgid "Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two." -msgstr "" - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:76 -msgid "Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax." -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:155 -msgid "Enter one HipChat channel per line. The pound symbol (#) is not required." -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:433 -msgid "Enter one IRC channel or username per line. The pound symbol (#) for channels, and the at (@) symbol for users, are not required." -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:139 -msgid "Enter one Slack channel per line. The pound symbol (#) is not required." -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:97 -msgid "Enter one email address per line to create a recipient list for this type of notification." -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:209 -msgid "Enter one phone number per line to specify where to route SMS messages." -msgstr "" - -#: client/src/credentials/factories/become-method-change.factory.js:81 -#: client/src/credentials/factories/kind-change.factory.js:138 -msgid "Enter the URL for the virtual machine which %scorresponds to your CloudForms instance. %sFor example, %s" -msgstr "" - -#: client/src/credentials/factories/become-method-change.factory.js:71 -#: client/src/credentials/factories/kind-change.factory.js:128 -msgid "Enter the URL which corresponds to your %sRed Hat Satellite 6 server. %sFor example, %s" -msgstr "" - -#: client/src/credentials/factories/become-method-change.factory.js:49 -#: client/src/credentials/factories/kind-change.factory.js:106 -msgid "Enter the hostname or IP address which corresponds to your VMware vCenter." -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:195 -msgid "Enter the number associated with the \"Messaging Service\" in Twilio in the format +18005550199." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:197 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:221 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:245 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:320 -msgid "Enter variables using either JSON or YAML syntax. Use the radio button to toggle between the two." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:187 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:194 -msgid "Environment Variables" -msgstr "" - -#: client/src/configuration/forms/settings-form.controller.js:150 -msgid "Error" -msgstr "" - -#: client/features/output/output.strings.js:72 -msgid "Error Details" -msgstr "" - -#: client/lib/services/base-string.service.js:93 -#: client/src/configuration/forms/settings-form.controller.js:277 -#: client/src/configuration/forms/settings-form.controller.js:385 -#: client/src/configuration/forms/settings-form.controller.js:507 -#: client/src/configuration/forms/settings-form.controller.js:555 -#: client/src/configuration/forms/system-form/configuration-system.controller.js:216 -#: client/src/credentials/factories/credential-form-save.factory.js:77 -#: client/src/credentials/factories/credential-form-save.factory.js:93 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:130 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:140 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:167 -#: client/src/job-submission/job-submission-factories/launchjob.factory.js:198 -#: client/src/job-submission/job-submission-factories/launchjob.factory.js:217 -#: client/src/management-jobs/card/card.controller.js:102 -#: client/src/management-jobs/card/card.controller.js:28 -#: client/src/projects/add/projects-add.controller.js:120 -#: client/src/projects/edit/projects-edit.controller.js:165 -#: client/src/projects/edit/projects-edit.controller.js:231 -#: client/src/projects/edit/projects-edit.controller.js:247 -#: client/src/shared/stateDefinitions.factory.js:230 -#: client/src/users/add/users-add.controller.js:100 -#: client/src/users/edit/users-edit.controller.js:170 -#: client/src/users/list/users-list.controller.js:84 -msgid "Error!" -msgstr "" - -#: client/src/activity-stream/streams.list.js:40 -msgid "Event" -msgstr "" - -#: client/src/activity-stream/factories/build-description.factory.js:120 -msgid "Event summary not available" -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:29 -msgid "Every" -msgstr "" - -#: client/src/projects/add/projects-add.controller.js:141 -#: client/src/projects/edit/projects-edit.controller.js:274 -msgid "Example URLs for GIT SCM include:" -msgstr "" - -#: client/src/projects/add/projects-add.controller.js:162 -#: client/src/projects/edit/projects-edit.controller.js:294 -msgid "Example URLs for Mercurial SCM include:" -msgstr "" - -#: client/src/projects/add/projects-add.controller.js:153 -#: client/src/projects/edit/projects-edit.controller.js:285 -msgid "Example URLs for Subversion SCM include:" -msgstr "" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:47 -msgid "Example: ansible_facts.ansible_distribution:\"RedHat\"" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:76 -msgid "Existing Group" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:125 -msgid "Existing Host" -msgstr "" - -#: client/features/output/output.strings.js:22 -#: client/src/workflow-results/workflow-results.controller.js:200 -#: client/src/workflow-results/workflow-results.controller.js:53 -msgid "Expand Output" -msgstr "" - -#: client/src/license/license.partial.html:39 -msgid "Expires On" -msgstr "" - -#: client/features/output/output.strings.js:56 -#: client/src/workflow-results/workflow-results.controller.js:64 -msgid "Explanation" -msgstr "" - -#: client/features/output/output.strings.js:51 -#: client/features/templates/templates.strings.js:55 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:133 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:145 -#: client/src/job-submission/job-submission.partial.html:165 -#: client/src/partials/logviewer.html:8 -#: client/src/scheduler/scheduler.strings.js:53 -#: client/src/templates/job_templates/job-template.form.js:370 -#: client/src/templates/job_templates/job-template.form.js:377 -#: client/src/templates/workflows.form.js:108 -#: client/src/templates/workflows.form.js:115 -#: client/src/workflow-results/workflow-results.controller.js:168 -msgid "Extra Variables" -msgstr "" - -#: client/src/inventories-hosts/shared/ansible-facts/ansible-facts.partial.html:4 -#: client/src/inventories-hosts/shared/ansible-facts/ansible-facts.route.js:7 -msgid "FACTS" -msgstr "" - -#: client/src/home/dashboard/graphs/job-status/job-status-graph.directive.js:65 -msgid "FAILED" -msgstr "" - -#: client/features/output/output.strings.js:89 -msgid "FIELDS" -msgstr "" - -#: client/src/shared/smart-search/smart-search.partial.html:42 -msgid "FIELDS:" -msgstr "" - -#: client/src/smart-status/smart-status.controller.js:43 -msgid "FINISHED" -msgstr "" - -#: client/src/inventories-hosts/hosts/host.form.js:107 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:106 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:107 -msgid "Facts" -msgstr "" - -#: client/lib/components/components.strings.js:100 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:80 -msgid "Failed" -msgstr "" - -#: client/src/home/dashboard/counts/dashboard-counts.directive.js:44 -msgid "Failed Hosts" -msgstr "" - -#: client/src/users/add/users-add.controller.js:100 -msgid "Failed to add new user. POST returned status:" -msgstr "" - -#: client/src/credentials/factories/credential-form-save.factory.js:78 -msgid "Failed to create new Credential. POST status:" -msgstr "" - -#: client/src/projects/add/projects-add.controller.js:121 -msgid "Failed to create new project. POST returned status:" -msgstr "" - -#: client/src/job-submission/job-submission-factories/launchjob.factory.js:218 -msgid "Failed to retrieve job template extra variables." -msgstr "" - -#: client/src/projects/edit/projects-edit.controller.js:166 -msgid "Failed to retrieve project: %s. GET status:" -msgstr "" - -#: client/src/users/edit/users-edit.controller.js:171 -msgid "Failed to retrieve user: %s. GET status:" -msgstr "" - -#: client/src/configuration/forms/settings-form.controller.js:386 -msgid "Failed to save settings. Returned status:" -msgstr "" - -#: client/src/configuration/forms/settings-form.controller.js:508 -msgid "Failed to save toggle settings. Returned status:" -msgstr "" - -#: client/src/credentials/factories/credential-form-save.factory.js:94 -msgid "Failed to update Credential. PUT status:" -msgstr "" - -#: client/src/projects/edit/projects-edit.controller.js:231 -msgid "Failed to update project: %s. PUT status:" -msgstr "" - -#: client/features/output/output.strings.js:93 -msgid "Failed to update search results." -msgstr "" - -#: client/src/job-submission/job-submission-factories/launchjob.factory.js:199 -#: client/src/management-jobs/card/card.controller.js:103 -msgid "Failed updating job %s with variables. POST returned: %d" -msgstr "" - -#: client/src/notifications/notifications.list.js:50 -msgid "Failure" -msgstr "" - -#: client/src/scheduler/schedules.list.js:56 -msgid "Final Run" -msgstr "" - -#: client/features/jobs/jobs.strings.js:10 -#: client/features/output/output.strings.js:46 -#: client/features/output/output.strings.js:52 -#: client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.directive.js:54 -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:44 -#: client/src/workflow-results/workflow-results.controller.js:60 -msgid "Finished" -msgstr "" - -#: client/src/access/rbac-multiselect/permissionsUsers.list.js:27 -#: client/src/users/users.form.js:29 -#: client/src/users/users.list.js:33 -msgid "First Name" -msgstr "" - -#: client/src/scheduler/schedules.list.js:46 -msgid "First Run" -msgstr "" - -#: client/src/templates/survey-maker/surveys/init.factory.js:19 -msgid "Float" -msgstr "" - -#: client/features/output/output.strings.js:85 -#: client/src/shared/smart-search/smart-search.partial.html:49 -msgid "For additional information on advanced search syntax please see the Ansible Tower" -msgstr "" - -#: client/src/credentials/factories/become-method-change.factory.js:63 -#: client/src/credentials/factories/kind-change.factory.js:120 -msgid "For example, %s" -msgstr "" - -#: client/src/inventories-hosts/hosts/host.form.js:36 -#: client/src/inventories-hosts/hosts/host.list.js:36 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:35 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:32 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:35 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:31 -#: client/src/inventories-hosts/inventory-hosts.strings.js:33 -msgid "For hosts that are part of an external inventory, this flag cannot be changed. It will be set by the inventory sync process." -msgstr "" - -#: client/src/templates/job_templates/job-template.form.js:54 -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 "" - -#: client/features/output/output.strings.js:53 -#: client/src/instance-groups/instance-groups.strings.js:48 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:110 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:97 -#: client/src/templates/job_templates/job-template.form.js:143 -#: client/src/templates/job_templates/job-template.form.js:153 -msgid "Forks" -msgstr "" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:173 -#: client/src/scheduler/scheduler.strings.js:28 -msgid "Frequency Details" -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:41 -msgid "Fri" -msgstr "" - -#: client/src/notifications/notification-templates-list/add-notifications-action.partial.html:2 -msgid "GO TO NOTIFICATIONS TO" -msgstr "" - -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.route.js:45 -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.route.js:46 -msgid "GROUPS" -msgstr "" - -#: client/features/projects/projects.strings.js:16 -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:28 -#: client/src/projects/edit/projects-edit.controller.js:136 -msgid "Get latest SCM revision" -msgstr "" - -#: client/features/output/output.strings.js:33 -msgid "Get next page" -msgstr "" - -#: client/features/output/output.strings.js:34 -msgid "Get previous page" -msgstr "" - -#: client/src/credential-types/add/add.controller.js:41 -msgid "Getting Started with Credential Types" -msgstr "" - -#: client/src/configuration/forms/auth-form/configuration-auth.controller.js:92 -msgid "GitHub" -msgstr "" - -#: client/src/configuration/forms/auth-form/configuration-auth.controller.js:110 -msgid "GitHub (Default)" -msgstr "" - -#: client/src/configuration/forms/auth-form/configuration-auth.partial.html:31 -msgid "GitHub Category" -msgstr "" - -#: client/src/configuration/forms/auth-form/configuration-auth.controller.js:111 -msgid "GitHub Org" -msgstr "" - -#: client/src/configuration/forms/auth-form/configuration-auth.controller.js:112 -msgid "GitHub Team" -msgstr "" - -#: client/features/output/output.strings.js:32 -msgid "Go to first page" -msgstr "" - -#: client/features/output/output.strings.js:35 -msgid "Go to last page of available output" -msgstr "" - -#: client/src/configuration/forms/auth-form/configuration-auth.controller.js:93 -msgid "Google OAuth2" -msgstr "" - -#: client/src/teams/teams.form.js:158 -#: client/src/users/users.form.js:216 -msgid "Grant Permission" -msgstr "" - -#: client/src/notifications/add/add.controller.js:79 -#: client/src/notifications/edit/edit.controller.js:126 -msgid "Gray" -msgstr "" - -#: client/src/notifications/add/add.controller.js:80 -#: client/src/notifications/edit/edit.controller.js:127 -msgid "Green" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:51 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js:52 -msgid "Group Variables" -msgstr "" - -#: client/src/inventories-hosts/hosts/host.form.js:115 -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.list.js:31 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:89 -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:32 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js:88 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:32 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:114 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:115 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:32 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:150 -msgid "Groups" -msgstr "" - -#: client/lib/components/components.strings.js:12 -#: client/lib/services/base-string.service.js:67 -#: client/src/templates/survey-maker/surveys/init.factory.js:483 -msgid "HIDE" -msgstr "" - -#: client/lib/components/components.strings.js:43 -msgid "HINT: Drag and drop an SSH private key file on the field below." -msgstr "" - -#: client/src/activity-stream/get-target-title.factory.js:41 -#: client/src/inventories-hosts/hosts/hosts.partial.html:9 -#: client/src/inventories-hosts/hosts/main.js:81 -#: client/src/inventories-hosts/inventories/inventories.partial.html:15 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.route.js:18 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-hosts.route.js:17 -msgid "HOSTS" -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:320 -#: client/src/notifications/notificationTemplates.form.js:321 -msgid "HTTP Headers" -msgstr "" - -#: client/src/bread-crumb/bread-crumb.directive.js:41 -msgid "Hide Activity Stream" -msgstr "" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:23 -msgid "High" -msgstr "" - -#: client/src/credentials/credentials.form.js:139 -#: client/src/notifications/notificationTemplates.form.js:83 -msgid "Host" -msgstr "" - -#: client/src/credentials/factories/become-method-change.factory.js:52 -#: client/src/credentials/factories/kind-change.factory.js:109 -msgid "Host (Authentication URL)" -msgstr "" - -#: client/src/templates/job_templates/job-template.form.js:352 -#: client/src/templates/job_templates/job-template.form.js:361 -msgid "Host Config Key" -msgstr "" - -#: client/src/inventories-hosts/hosts/host.form.js:40 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:39 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:39 -msgid "Host Enabled" -msgstr "" - -#: client/src/inventories-hosts/hosts/host.form.js:46 -#: client/src/inventories-hosts/hosts/host.form.js:57 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:45 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:56 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:45 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:56 -msgid "Host Name" -msgstr "" - -#: client/src/inventories-hosts/hosts/host.form.js:80 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:79 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:79 -msgid "Host Variables" -msgstr "" - -#: client/src/inventories-hosts/shared/factories/set-enabled-msg.factory.js:6 -msgid "Host is available" -msgstr "" - -#: client/src/inventories-hosts/shared/factories/set-enabled-msg.factory.js:10 -msgid "Host is available. Click to toggle." -msgstr "" - -#: client/src/inventories-hosts/shared/factories/set-enabled-msg.factory.js:6 -msgid "Host is not available" -msgstr "" - -#: client/src/inventories-hosts/shared/factories/set-enabled-msg.factory.js:10 -msgid "Host is not available. Click to toggle." -msgstr "" - -#: client/features/output/output.strings.js:13 -msgid "Host status information for this job is unavailable." -msgstr "" - -#: client/features/output/output.strings.js:101 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:27 -#: client/src/home/dashboard/counts/dashboard-counts.directive.js:39 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:98 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:57 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:56 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:149 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:159 -msgid "Hosts" -msgstr "" - -#: client/src/license/license.partial.html:52 -msgid "Hosts Available" -msgstr "" - -#: client/src/license/license.partial.html:64 -msgid "Hosts Remaining" -msgstr "" - -#: client/src/license/license.partial.html:58 -msgid "Hosts Used" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:239 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:254 -msgid "Hosts are imported to" -msgstr "" - -#: client/src/license/license.partial.html:121 -msgid "I agree to the End User License Agreement" -msgstr "" - -#: client/features/output/output.strings.js:110 -msgid "ID" -msgstr "" - -#: client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html:12 -msgid "INITIATED BY" -msgstr "" - -#: client/src/inventories-hosts/inventories/insights/insights.route.js:7 -msgid "INSIGHTS" -msgstr "" - -#: client/src/instance-groups/instance-groups.list.js:6 -#: client/src/instance-groups/instance-groups.list.js:7 -#: client/src/instance-groups/instance-groups.strings.js:16 -#: client/src/instance-groups/instance-groups.strings.js:8 -msgid "INSTANCE GROUPS" -msgstr "" - -#: client/src/instance-groups/instance-groups.strings.js:25 -#: client/src/instance-groups/instance-groups.strings.js:9 -msgid "INSTANCES" -msgstr "" - -#: client/src/activity-stream/get-target-title.factory.js:14 -#: client/src/inventories-hosts/hosts/hosts.partial.html:8 -#: client/src/inventories-hosts/inventories/inventories.partial.html:14 -#: client/src/inventories-hosts/inventories/inventories.route.js:8 -#: client/src/inventories-hosts/inventories/inventory.list.js:14 -#: client/src/inventories-hosts/inventories/inventory.list.js:15 -#: client/src/organizations/linkout/organizations-linkout.route.js:144 -#: client/src/organizations/list/organizations-list.controller.js:67 -msgid "INVENTORIES" -msgstr "" - -#: client/src/job-submission/job-submission.partial.html:346 -#: client/src/partials/job-template-details.html:2 -msgid "INVENTORY" -msgstr "" - -#: client/src/inventory-scripts/inventory-scripts.form.js:23 -msgid "INVENTORY SCRIPT" -msgstr "" - -#: client/src/activity-stream/get-target-title.factory.js:35 -#: client/src/inventory-scripts/inventory-scripts.list.js:12 -#: client/src/inventory-scripts/main.js:65 -msgid "INVENTORY SCRIPTS" -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:419 -msgid "IRC Nick" -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:408 -msgid "IRC Server Address" -msgstr "" - -#: client/src/notifications/shared/type-change.service.js:66 -msgid "IRC Server Password" -msgstr "" - -#: client/src/notifications/shared/type-change.service.js:65 -msgid "IRC Server Port" -msgstr "" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:79 -msgid "ISSUE: {{report.rule.description}}" -msgstr "" - -#: client/src/shared/paginate/paginate.partial.html:43 -msgid "ITEMS" -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:362 -#: client/src/notifications/notificationTemplates.form.js:394 -msgid "Icon URL" -msgstr "" - -#: client/src/login/authenticationServices/timer.factory.js:157 -msgid "Idle Session" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:236 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:250 -msgid "If blank, all groups above are created except" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:367 -msgid "If checked, all variables for child groups and hosts will be removed and replaced by those found on the external source." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:355 -msgid "If checked, any hosts and groups that were previously present on the external source but are now removed will be removed from the Tower 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." -msgstr "" - -#: client/src/templates/job_templates/job-template.form.js:295 -msgid "If enabled, run this playbook as an administrator." -msgstr "" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:121 -msgid "If enabled, show the changes made by Ansible tasks, where supported. This is equivalent to Ansible’s --diff mode." -msgstr "" - -#: client/src/templates/job_templates/job-template.form.js:280 -msgid "If enabled, show the changes made by Ansible tasks, where supported. This is equivalent to Ansible’s --diff mode." -msgstr "" - -#: client/src/templates/job_templates/job-template.form.js:319 -msgid "If enabled, simultaneous runs of this job template will be allowed." -msgstr "" - -#: client/src/templates/workflows.form.js:128 -msgid "If enabled, simultaneous runs of this workflow job template will be allowed." -msgstr "" - -#: client/src/templates/job_templates/job-template.form.js:330 -msgid "If enabled, use cached facts if available and store discovered facts in the cache." -msgstr "" - -#: client/src/credentials/credentials.form.js:52 -msgid "If no organization is given, the credential can only be used by the user that creates the credential. Organization admins and system administrators can assign an organization so that roles for the credential can be assigned to users and teams in that organization." -msgstr "" - -#: client/src/license/license.partial.html:70 -msgid "If you are ready to upgrade, please contact us by clicking the button below" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:227 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:241 -msgid "Image ID:" -msgstr "" - -#: client/src/inventories-hosts/hosts/host.form.js:34 -#: client/src/inventories-hosts/hosts/host.list.js:34 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:33 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:30 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:33 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:29 -#: client/src/inventories-hosts/inventory-hosts.strings.js:32 -msgid "Indicates if a host is available and should be included in running jobs." -msgstr "" - -#: client/src/activity-stream/activity-detail.form.js:31 -#: client/src/activity-stream/streams.list.js:33 -msgid "Initiated by" -msgstr "" - -#: client/src/credential-types/credential-types.form.js:53 -#: client/src/credential-types/credential-types.form.js:61 -msgid "Injector Configuration" -msgstr "" - -#: client/src/credential-types/credential-types.form.js:39 -#: client/src/credential-types/credential-types.form.js:47 -msgid "Input Configuration" -msgstr "" - -#: client/src/inventories-hosts/hosts/host.form.js:123 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:122 -msgid "Insights" -msgstr "" - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:52 -msgid "Insights Credential" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:145 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:148 -msgid "Instance Filters" -msgstr "" - -#: client/features/output/output.strings.js:54 -msgid "Instance Group" -msgstr "" - -#: client/src/instance-groups/instance-groups.strings.js:63 -msgid "Instance Group parameter is missing." -msgstr "" - -#: client/lib/components/components.strings.js:84 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:54 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:57 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:61 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:64 -#: client/src/organizations/organizations.form.js:38 -#: client/src/organizations/organizations.form.js:41 -#: client/src/templates/job_templates/job-template.form.js:252 -#: client/src/templates/job_templates/job-template.form.js:255 -msgid "Instance Groups" -msgstr "" - -#: client/src/instance-groups/instance-groups.strings.js:32 -msgid "Instance Groups Help" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:236 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:250 -msgid "Instance ID" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:228 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:242 -msgid "Instance ID:" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:229 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:243 -msgid "Instance Type:" -msgstr "" - -#: client/lib/components/components.strings.js:83 -#: client/src/instance-groups/instance-groups.strings.js:17 -msgid "Instances" -msgstr "" - -#: client/src/templates/survey-maker/surveys/init.factory.js:18 -msgid "Integer" -msgstr "" - -#: client/src/license/license.partial.html:11 -msgid "Invalid License" -msgstr "" - -#: client/src/license/license.controller.js:74 -#: client/src/license/license.controller.js:82 -msgid "Invalid file format. Please upload valid JSON." -msgstr "" - -#: client/lib/components/components.strings.js:16 -msgid "Invalid input for this type." -msgstr "" - -#: client/features/output/output.strings.js:94 -msgid "Invalid search filter provided." -msgstr "" - -#: client/src/login/loginModal/loginModal.partial.html:40 -msgid "Invalid username and/or password. Please try again." -msgstr "" - -#: client/lib/components/components.strings.js:75 -#: client/lib/models/models.strings.js:16 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:121 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:52 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:28 -#: client/src/home/dashboard/counts/dashboard-counts.directive.js:50 -#: client/src/organizations/linkout/organizations-linkout.route.js:156 -#: client/src/organizations/linkout/organizations-linkout.route.js:158 -msgid "Inventories" -msgstr "" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:44 -msgid "Inventories with sources cannot be copied" -msgstr "" - -#: client/features/jobs/jobs.strings.js:14 -#: client/features/output/output.strings.js:55 -#: client/features/templates/templates.strings.js:17 -#: client/features/templates/templates.strings.js:25 -#: client/src/inventories-hosts/hosts/host.list.js:69 -#: client/src/inventories-hosts/inventories/inventory.list.js:81 -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:76 -#: client/src/job-submission/job-submission.partial.html:17 -#: client/src/organizations/linkout/controllers/organizations-inventories.controller.js:70 -#: client/src/templates/job_templates/job-template.form.js:66 -#: client/src/templates/job_templates/job-template.form.js:80 -#: client/src/templates/workflows.form.js:72 -#: client/src/templates/workflows.form.js:82 -#: client/src/workflow-results/workflow-results.controller.js:66 -msgid "Inventory" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:110 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:124 -msgid "Inventory File" -msgstr "" - -#: client/lib/components/components.strings.js:80 -#: client/lib/models/models.strings.js:20 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:29 -msgid "Inventory Scripts" -msgstr "" - -#: client/lib/models/models.strings.js:25 -msgid "Inventory Sources" -msgstr "" - -#: client/features/templates/templates.strings.js:105 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:46 -#: client/src/workflow-results/workflow-results.controller.js:84 -msgid "Inventory Sync" -msgstr "" - -#: client/src/home/dashboard/counts/dashboard-counts.directive.js:55 -msgid "Inventory Sync Failures" -msgstr "" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:75 -msgid "Inventory Variables" -msgstr "" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:73 -msgid "Inventory contains 0 hosts." -msgstr "" - -#: client/features/output/output.strings.js:41 -msgid "Isolated" -msgstr "" - -#: client/src/smart-status/smart-status.controller.js:43 -msgid "JOB ID" -msgstr "" - -#: client/features/output/output.strings.js:92 -msgid "JOB IS STILL RUNNING" -msgstr "" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:4 -msgid "JOB STATUS" -msgstr "" - -#: client/src/templates/job_templates/job-template.form.js:22 -msgid "JOB TEMPLATE" -msgstr "" - -#: client/features/portalMode/portalMode.strings.js:8 -#: client/features/templates/routes/organizationsTemplatesList.route.js:29 -#: client/features/templates/routes/projectsTemplatesList.route.js:18 -#: client/src/organizations/list/organizations-list.controller.js:79 -msgid "JOB TEMPLATES" -msgstr "" - -#: client/features/jobs/jobs.strings.js:8 -#: client/features/jobs/routes/instanceGroupJobs.route.js:13 -#: client/features/jobs/routes/instanceJobs.route.js:13 -#: client/features/jobs/routes/inventoryCompletedJobs.route.js:22 -#: client/features/jobs/routes/jobs.route.js:12 -#: client/features/portalMode/portalMode.strings.js:9 -#: client/features/templates/templates.strings.js:111 -#: client/src/activity-stream/get-target-title.factory.js:32 -#: client/src/home/dashboard/graphs/job-status/job-status-graph.directive.js:116 -#: client/src/instance-groups/instance-groups.strings.js:26 -msgid "JOBS" -msgstr "" - -#: client/lib/components/code-mirror/code-mirror.strings.js:12 -#: client/lib/services/base-string.service.js:71 -#: client/src/job-submission/job-submission.partial.html:173 -msgid "JSON" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:198 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:222 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:246 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:321 -msgid "JSON:" -msgstr "" - -#: client/features/jobs/jobs.strings.js:18 -#: client/src/workflow-results/workflow-results.controller.js:103 -msgid "Job" -msgstr "" - -#: client/src/templates/job_templates/job-template.form.js:261 -msgid "Job Slicing" -msgstr "" - -#: client/features/output/output.strings.js:57 -#: client/features/templates/templates.strings.js:49 -#: client/src/job-submission/job-submission.partial.html:228 -#: client/src/templates/job_templates/job-template.form.js:190 -#: client/src/templates/job_templates/job-template.form.js:197 -msgid "Job Tags" -msgstr "" - -#: client/features/jobs/jobs.strings.js:13 -#: client/features/output/output.strings.js:58 -#: client/features/templates/templates.strings.js:13 -#: client/src/templates/templates.list.js:61 -msgid "Job Template" -msgstr "" - -#: client/lib/models/models.strings.js:30 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:102 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:36 -#: client/src/projects/projects.form.js:303 -msgid "Job Templates" -msgstr "" - -#: client/features/output/output.strings.js:60 -#: client/features/templates/templates.strings.js:51 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:32 -#: client/src/job-submission/job-submission.partial.html:202 -#: client/src/templates/job_templates/job-template.form.js:47 -#: client/src/templates/job_templates/job-template.form.js:55 -msgid "Job Type" -msgstr "" - -#: client/features/output/output.strings.js:26 -msgid "Job is one of several from a JT that slices on inventory" -msgstr "" - -#: client/features/jobs/jobs.strings.js:19 -msgid "Job {{status}}. Click for details." -msgstr "" - -#: client/lib/components/components.strings.js:69 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:30 -#: client/src/instance-groups/instance-groups.strings.js:52 -msgid "Jobs" -msgstr "" - -#: client/features/output/output.strings.js:90 -#: client/features/templates/templates.strings.js:100 -#: client/src/workflow-results/workflow-results.controller.js:86 -msgid "KEY" -msgstr "" - -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:61 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:153 -#: client/src/shared/smart-search/smart-search.partial.html:14 -msgid "Key" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:230 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:244 -msgid "Key Name:" -msgstr "" - -#: client/src/credential-types/credential-types.list.js:31 -#: client/src/credentials/credentials.list.js:33 -msgid "Kind" -msgstr "" - -#: client/features/applications/applications.strings.js:35 -#: client/features/projects/projects.strings.js:11 -msgid "LAST MODIFIED" -msgstr "" - -#: client/features/projects/projects.strings.js:12 -#: client/features/users/tokens/tokens.strings.js:38 -msgid "LAST USED" -msgstr "" - -#: client/features/templates/templates.strings.js:31 -msgid "LAUNCH" -msgstr "" - -#: client/src/job-submission/job-submission.partial.html:6 -msgid "LAUNCH JOB" -msgstr "" - -#: client/src/configuration/forms/auth-form/configuration-auth.controller.js:94 -msgid "LDAP" -msgstr "" - -#: client/src/configuration/forms/auth-form/configuration-auth.controller.js:102 -msgid "LDAP 1 (Optional)" -msgstr "" - -#: client/src/configuration/forms/auth-form/configuration-auth.controller.js:103 -msgid "LDAP 2 (Optional)" -msgstr "" - -#: client/src/configuration/forms/auth-form/configuration-auth.controller.js:104 -msgid "LDAP 3 (Optional)" -msgstr "" - -#: client/src/configuration/forms/auth-form/configuration-auth.controller.js:105 -msgid "LDAP 4 (Optional)" -msgstr "" - -#: client/src/configuration/forms/auth-form/configuration-auth.controller.js:106 -msgid "LDAP 5 (Optional)" -msgstr "" - -#: client/src/configuration/forms/auth-form/configuration-auth.partial.html:18 -msgid "LDAP Server" -msgstr "" - -#: client/src/license/license.route.js:18 -msgid "LICENSE" -msgstr "" - -#: client/features/output/output.strings.js:61 -#: client/src/templates/job_templates/job-template.form.js:224 -#: client/src/templates/job_templates/job-template.form.js:228 -#: client/src/templates/templates.list.js:43 -#: client/src/templates/workflows.form.js:93 -#: client/src/templates/workflows.form.js:97 -#: client/src/workflow-results/workflow-results.controller.js:61 -msgid "Labels" -msgstr "" - -#: client/features/templates/templates.strings.js:20 -msgid "Last Modified" -msgstr "" - -#: client/src/access/rbac-multiselect/permissionsUsers.list.js:31 -#: client/src/users/users.form.js:36 -#: client/src/users/users.list.js:37 -msgid "Last Name" -msgstr "" - -#: client/features/templates/templates.strings.js:21 -msgid "Last Ran" -msgstr "" - -#: client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.directive.js:56 -msgid "Last Sync" -msgstr "" - -#: client/src/projects/projects.list.js:56 -msgid "Last Updated" -msgstr "" - -#: client/src/shared/form-generator.js:1727 -msgid "Launch" -msgstr "" - -#: client/src/management-jobs/card/card.partial.html:23 -msgid "Launch Management Job" -msgstr "" - -#: client/features/jobs/jobs.strings.js:12 -#: client/features/output/output.strings.js:62 -#: client/src/workflow-results/workflow-results.controller.js:58 -msgid "Launched By" -msgstr "" - -#: client/features/templates/templates.strings.js:39 -#: client/src/job-submission/job-submission.partial.html:99 -msgid "Launching this job requires the passwords listed below. Enter and confirm each password before continuing." -msgstr "" - -#: client/features/users/tokens/tokens.strings.js:32 -msgid "Leaving this field blank will result in the creation of a Personal Access Token which is not linked to an Application." -msgstr "" - -#: client/features/credentials/legacy.credentials.js:350 -msgid "Legacy state configuration for does not exist" -msgstr "" - -#: client/src/license/license.controller.js:44 -#: client/src/license/license.partial.html:8 -msgid "License" -msgstr "" - -#: client/features/output/output.strings.js:63 -msgid "License Error" -msgstr "" - -#: client/src/license/license.partial.html:104 -msgid "License File" -msgstr "" - -#: client/src/license/license.partial.html:33 -msgid "License Key" -msgstr "" - -#: client/src/license/license.controller.js:46 -msgid "License Management" -msgstr "" - -#: client/src/license/license.partial.html:21 -msgid "License Type" -msgstr "" - -#: client/features/output/output.strings.js:64 -#: client/features/templates/templates.strings.js:50 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:45 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:55 -#: client/src/job-submission/job-submission.partial.html:220 -#: client/src/templates/job_templates/job-template.form.js:159 -#: client/src/templates/job_templates/job-template.form.js:163 -msgid "Limit" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:240 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:255 -msgid "Limit to hosts having a tag:" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:242 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:257 -msgid "Limit to hosts using either key pair:" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:244 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:259 -msgid "Limit to hosts where the Name tag begins with" -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:51 -msgid "Limited to first 10" -msgstr "" - -#: client/src/shared/socket/socket.service.js:213 -msgid "Live events: attempting to connect to the server." -msgstr "" - -#: client/src/shared/socket/socket.service.js:217 -msgid "Live events: connected. Pages containing job status information will automatically update in real-time." -msgstr "" - -#: client/src/shared/socket/socket.service.js:221 -msgid "Live events: error connecting to the server." -msgstr "" - -#: client/src/shared/form-generator.js:2005 -msgid "Loading..." -msgstr "" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:133 -#: client/src/scheduler/scheduler.strings.js:26 -msgid "Local Time Zone" -msgstr "" - -#: client/src/configuration/forms/system-form/configuration-system.controller.js:210 -msgid "Log aggregator test failed.
Detail:" -msgstr "" - -#: client/src/configuration/forms/system-form/configuration-system.controller.js:203 -msgid "Log aggregator test successful." -msgstr "" - -#: client/lib/components/components.strings.js:65 -msgid "Logged in as" -msgstr "" - -#: client/src/configuration/forms/system-form/configuration-system.controller.js:85 -msgid "Logging" -msgstr "" - -#: client/lib/components/components.strings.js:67 -msgid "Logout" -msgstr "" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:35 -msgid "Low" -msgstr "" - -#: client/src/management-jobs/card/card.partial.html:6 -#: client/src/management-jobs/card/card.route.js:20 -msgid "MANAGEMENT JOBS" -msgstr "" - -#: client/src/instance-groups/instance-groups.strings.js:15 -msgid "MANUAL" -msgstr "" - -#: client/features/output/output.strings.js:113 -msgid "MODULE" -msgstr "" - -#: client/features/portalMode/routes/portalModeTemplatesList.route.js:13 -msgid "MY VIEW" -msgstr "" - -#: client/src/credentials/credentials.form.js:67 -#: client/src/job-submission/job-submission.partial.html:356 -msgid "Machine" -msgstr "" - -#: client/features/output/output.strings.js:65 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:60 -msgid "Machine Credential" -msgstr "" - -#: client/lib/components/components.strings.js:82 -msgid "Management Jobs" -msgstr "" - -#: client/features/projects/projects.strings.js:18 -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:45 -#: client/src/projects/edit/projects-edit.controller.js:143 -msgid "Manual projects do not require an SCM update" -msgstr "" - -#: client/src/templates/job_templates/job-template.form.js:234 -#: client/src/templates/workflows.form.js:103 -msgid "Max 512 characters per label." -msgstr "" - -#: client/src/login/loginModal/loginModal.partial.html:34 -msgid "Maximum per-user sessions reached. Please sign in." -msgstr "" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:29 -msgid "Medium" -msgstr "" - -#: client/src/configuration/forms/system-form/configuration-system.controller.js:83 -msgid "Misc. System" -msgstr "" - -#: client/src/templates/workflows.form.js:35 -msgid "Missing Job Templates found in the Workflow Editor" -msgstr "" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:22 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:30 -msgid "Module" -msgstr "" - -#: client/features/output/output.strings.js:66 -msgid "Module Args" -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:37 -msgid "Mon" -msgstr "" - -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:25 -msgid "Most recent job failed. Click to view jobs." -msgstr "" - -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:29 -msgid "Most recent job successful. Click to view jobs." -msgstr "" - -#: client/src/templates/survey-maker/surveys/init.factory.js:17 -msgid "Multiple Choice (multiple select)" -msgstr "" - -#: client/src/templates/survey-maker/surveys/init.factory.js:16 -msgid "Multiple Choice (single select)" -msgstr "" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:77 -msgid "Multiple Choice Options" -msgstr "" - -#: client/features/portalMode/index.view.html:26 -msgid "My Jobs" -msgstr "" - -#: client/lib/components/components.strings.js:71 -msgid "My View" -msgstr "" - -#: client/features/applications/applications.strings.js:28 -msgid "NAME" -msgstr "" - -#: client/features/applications/applications.strings.js:24 -msgid "NEW APPLICATION" -msgstr "" - -#: client/features/credentials/credentials.strings.js:26 -msgid "NEW CREDENTIAL" -msgstr "" - -#: client/src/credential-types/credential-types.form.js:16 -msgid "NEW CREDENTIAL TYPE" -msgstr "" - -#: client/src/inventory-scripts/inventory-scripts.form.js:16 -msgid "NEW CUSTOM INVENTORY" -msgstr "" - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:17 -msgid "NEW INVENTORY" -msgstr "" - -#: client/src/templates/job_templates/job-template.form.js:19 -msgid "NEW JOB TEMPLATE" -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:16 -msgid "NEW NOTIFICATION TEMPLATE" -msgstr "" - -#: client/src/organizations/organizations.form.js:18 -msgid "NEW ORGANIZATION" -msgstr "" - -#: client/src/projects/projects.form.js:17 -msgid "NEW PROJECT" -msgstr "" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:10 -msgid "NEW SMART INVENTORY" -msgstr "" - -#: client/src/teams/teams.form.js:16 -msgid "NEW TEAM" -msgstr "" - -#: client/src/users/users.form.js:16 -msgid "NEW USER" -msgstr "" - -#: client/src/templates/workflows.form.js:17 -msgid "NEW WORKFLOW JOB TEMPLATE" -msgstr "" - -#: client/lib/services/base-string.service.js:65 -msgid "NEXT" -msgstr "" - -#: client/src/inventories-hosts/hosts/hosts.partial.html:38 -msgid "NO HOSTS HAVE BEEN CREATED" -msgstr "" - -#: client/lib/components/components.strings.js:39 -msgid "NO OPTIONS AVAILABLE" -msgstr "" - -#: client/src/login/loginModal/loginModal.partial.html:95 -msgid "NOTICE" -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:21 -msgid "NOTIFICATION TEMPLATE" -msgstr "" - -#: client/src/activity-stream/get-target-title.factory.js:26 -#: client/src/notifications/notificationTemplates.list.js:14 -msgid "NOTIFICATION TEMPLATES" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-notifications.route.js:9 -#: client/src/management-jobs/notifications/notification.route.js:46 -#: client/src/notifications/main.js:42 -#: client/src/notifications/main.js:89 -#: client/src/notifications/notification-templates-list/add-notifications-action.partial.html:2 -msgid "NOTIFICATIONS" -msgstr "" - -#: client/features/output/output.strings.js:67 -#: client/src/credential-types/credential-types.form.js:27 -#: client/src/credential-types/credential-types.list.js:24 -#: client/src/credentials/credentials.form.js:32 -#: client/src/credentials/credentials.list.js:26 -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:14 -#: client/src/home/dashboard/lists/jobs/jobs-list.partial.html:13 -#: client/src/instance-groups/instance-groups.list.js:15 -#: client/src/inventories-hosts/hosts/host.list.js:61 -#: client/src/inventories-hosts/inventories/inventory.list.js:48 -#: client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.directive.js:55 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:32 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js:33 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:51 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:21 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:28 -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:45 -#: client/src/inventory-scripts/inventory-scripts.form.js:28 -#: client/src/inventory-scripts/inventory-scripts.list.js:20 -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:21 -#: client/src/notifications/notificationTemplates.form.js:32 -#: client/src/notifications/notificationTemplates.list.js:32 -#: client/src/notifications/notifications.list.js:27 -#: client/src/organizations/organizations.form.js:26 -#: client/src/projects/projects.form.js:30 -#: client/src/projects/projects.list.js:37 -#: client/src/scheduler/scheduled-jobs.list.js:31 -#: client/src/scheduler/scheduler.strings.js:21 -#: client/src/scheduler/schedules.list.js:41 -#: client/src/teams/teams.form.js:127 -#: client/src/teams/teams.form.js:28 -#: client/src/teams/teams.list.js:23 -#: client/src/templates/job_templates/job-template.form.js:34 -#: client/src/templates/templates.list.js:24 -#: client/src/templates/workflows.form.js:42 -#: client/src/users/users.form.js:144 -#: client/src/users/users.form.js:170 -#: client/src/users/users.form.js:196 -msgid "Name" -msgstr "" - -#: client/src/credentials/credentials.form.js:71 -msgid "Network" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:81 -msgid "New Group" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:130 -msgid "New Host" -msgstr "" - -#: client/src/users/add/users-add.controller.js:92 -msgid "New user successfully created!" -msgstr "" - -#: client/src/scheduler/scheduled-jobs.list.js:51 -#: client/src/scheduler/schedules.list.js:51 -msgid "Next Run" -msgstr "" - -#: client/src/credentials/credentials.list.js:21 -msgid "No Credentials Have Been Created" -msgstr "" - -#: client/features/templates/templates.strings.js:63 -#: client/src/job-submission/lists/credential/job-sub-cred-list.controller.js:44 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js:15 -msgid "No Credentials Matching This Type Have Been Created" -msgstr "" - -#: client/features/output/host-event/host-event-codemirror.partial.html:3 -msgid "No JSON data returned by the module" -msgstr "" - -#: client/src/projects/projects.list.js:20 -msgid "No Projects Have Been Created" -msgstr "" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:50 -msgid "No Remediation Playbook Available" -msgstr "" - -#: client/features/projects/projects.strings.js:30 -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:206 -msgid "No SCM Configuration" -msgstr "" - -#: client/features/projects/projects.strings.js:36 -#: client/src/projects/factories/get-project-tool-tip.factory.js:9 -msgid "No SCM updates have run for this project" -msgstr "" - -#: client/src/access/rbac-multiselect/permissionsTeams.list.js:17 -msgid "No Teams exist" -msgstr "" - -#: client/features/projects/projects.strings.js:27 -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:198 -msgid "No Updates Available" -msgstr "" - -#: client/src/access/rbac-multiselect/permissionsUsers.list.js:18 -msgid "No Users exist" -msgstr "" - -#: client/features/templates/templates.strings.js:34 -msgid "No credentials selected" -msgstr "" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:63 -msgid "No data is available. There are no issues to report." -msgstr "" - -#: client/src/license/license.controller.js:41 -msgid "No file selected." -msgstr "" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:69 -msgid "No hosts with failures. Click for details." -msgstr "" - -#: client/features/templates/templates.strings.js:35 -msgid "No inventory selected" -msgstr "" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:52 -msgid "No inventory sync failures. Click for details." -msgstr "" - -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:16 -msgid "No job data" -msgstr "" - -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:75 -msgid "No job data available." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/groups/factories/get-hosts-status-msg.factory.js:22 -msgid "No job failures" -msgstr "" - -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:51 -msgid "No job templates were recently used." -msgstr "" - -#: client/src/home/dashboard/lists/jobs/jobs-list.partial.html:44 -msgid "No jobs were recently run." -msgstr "" - -#: client/src/teams/teams.form.js:124 -#: client/src/users/users.form.js:193 -msgid "No permissions have been granted" -msgstr "" - -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:17 -msgid "No recent job data available for this host." -msgstr "" - -#: client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.directive.js:75 -msgid "No recent job data available for this inventory." -msgstr "" - -#: client/src/notifications/notification-templates-list/list.controller.js:86 -msgid "No recent notifications." -msgstr "" - -#: client/src/inventories-hosts/hosts/hosts.partial.html:36 -#: client/src/shared/form-generator.js:1899 -#: client/src/shared/list-generator/list-generator.factory.js:240 -msgid "No records matched your search." -msgstr "" - -#: client/features/output/output.strings.js:114 -msgid "No result found" -msgstr "" - -#: client/src/scheduler/scheduled-jobs.list.js:16 -msgid "No schedules exist" -msgstr "" - -#: client/src/job-submission/job-submission.partial.html:348 -#: client/src/job-submission/job-submission.partial.html:353 -msgid "None selected" -msgstr "" - -#: client/src/users/add/users-add.controller.js:10 -#: client/src/users/edit/users-edit.controller.js:10 -#: client/src/users/list/users-list.controller.js:10 -msgid "Normal User" -msgstr "" - -#: client/features/output/output.strings.js:42 -#: client/src/workflow-results/workflow-results.controller.js:70 -msgid "Not Finished" -msgstr "" - -#: client/features/output/output.strings.js:43 -#: client/src/workflow-results/workflow-results.controller.js:71 -msgid "Not Started" -msgstr "" - -#: client/features/projects/projects.strings.js:35 -msgid "Not configured for SCM" -msgstr "" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:59 -msgid "Not configured for inventory sync." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-disassociate.partial.html:25 -msgid "Note that only hosts directly in this group can be disassociated. Hosts in sub-groups must be disassociated directly from the sub-group level that they belong." -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:288 -#: client/src/notifications/notificationTemplates.form.js:289 -#: client/src/notifications/notificationTemplates.form.js:472 -#: client/src/notifications/notificationTemplates.form.js:473 -msgid "Notification Color" -msgstr "" - -#: client/src/notifications/notification-templates-list/list.controller.js:140 -msgid "Notification Failed." -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:277 -msgid "Notification Label" -msgstr "" - -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:31 -msgid "Notification Templates" -msgstr "" - -#: client/lib/components/components.strings.js:81 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:20 -#: client/src/management-jobs/notifications/notification.route.js:21 -#: client/src/notifications/notifications.list.js:17 -msgid "Notifications" -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:302 -msgid "Notify Channel" -msgstr "" - -#: client/lib/services/base-string.service.js:69 -#: client/src/inventories-hosts/hosts/hosts.partial.html:55 -#: client/src/job-submission/job-submission.partial.html:269 -#: client/src/partials/survey-maker-modal.html:27 -#: client/src/shared/form-generator.js:545 -#: client/src/shared/form-generator.js:780 -#: client/src/shared/generator-helpers.js:554 -msgid "OFF" -msgstr "" - -#: client/lib/services/base-string.service.js:64 -#: client/src/standard-out/standard-out-factories/delete-job.factory.js:116 -msgid "OK" -msgstr "" - -#: client/lib/services/base-string.service.js:68 -#: client/src/inventories-hosts/hosts/hosts.partial.html:54 -#: client/src/job-submission/job-submission.partial.html:267 -#: client/src/partials/survey-maker-modal.html:26 -#: client/src/shared/form-generator.js:541 -#: client/src/shared/form-generator.js:778 -#: client/src/shared/generator-helpers.js:550 -msgid "ON" -msgstr "" - -#: client/lib/components/components.strings.js:10 -msgid "OPTIONS" -msgstr "" - -#: client/features/applications/applications.strings.js:34 -msgid "ORG" -msgstr "" - -#: client/features/projects/projects.strings.js:10 -msgid "ORGANIZATION" -msgstr "" - -#: client/src/activity-stream/get-target-title.factory.js:29 -#: client/src/organizations/list/organizations-list.partial.html:6 -#: client/src/organizations/main.js:51 -msgid "ORGANIZATIONS" -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:45 -msgid "Occurrences" -msgstr "" - -#: client/features/templates/templates.strings.js:102 -#: client/src/workflow-results/workflow-results.controller.js:81 -msgid "On Failure" -msgstr "" - -#: client/features/templates/templates.strings.js:101 -#: client/src/workflow-results/workflow-results.controller.js:80 -msgid "On Success" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:157 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:162 -msgid "Only Group By" -msgstr "" - -#: client/src/credentials/credentials.form.js:379 -msgid "OpenStack domains define administrative boundaries. It is only needed for Keystone v3 authentication URLs. Common scenarios include:" -msgstr "" - -#: client/src/templates/job_templates/job-template.form.js:230 -#: client/src/templates/workflows.form.js:99 -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 "" - -#: client/src/notifications/notificationTemplates.form.js:453 -#: client/src/partials/logviewer.html:7 -#: client/src/templates/job_templates/job-template.form.js:288 -#: client/src/templates/workflows.form.js:121 -msgid "Options" -msgstr "" - -#: client/src/credentials/credentials.form.js:46 -#: client/src/credentials/credentials.form.js:53 -#: client/src/inventories-hosts/inventories/inventory.list.js:61 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:33 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:41 -#: client/src/inventory-scripts/inventory-scripts.form.js:40 -#: client/src/inventory-scripts/inventory-scripts.list.js:27 -#: client/src/notifications/notificationTemplates.form.js:44 -#: client/src/projects/projects.form.js:42 -#: client/src/projects/projects.form.js:48 -#: client/src/teams/teams.form.js:40 -#: client/src/teams/teams.list.js:30 -#: client/src/templates/workflows.form.js:55 -#: client/src/templates/workflows.form.js:61 -#: client/src/users/users.form.js:42 -msgid "Organization" -msgstr "" - -#: client/lib/components/components.strings.js:77 -#: client/lib/models/models.strings.js:35 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:135 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:64 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:32 -#: client/src/users/users.form.js:134 -msgid "Organizations" -msgstr "" - -#: client/features/templates/templates.strings.js:28 -#: client/src/job-submission/job-submission.partial.html:19 -msgid "Other Prompts" -msgstr "" - -#: client/src/credentials/credentials.form.js:79 -msgid "Others (Cloud Providers)" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:317 -msgid "Override variables found in azure_rm.ini and used by the inventory update script. For a detailed description of these variables" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:283 -msgid "" -"Override variables found in cloudforms.ini and used by the inventory update script. For an example variable configuration\n" -" \n" -" view cloudforms.ini in the Ansible github repo. Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:218 -msgid "Override variables found in ec2.ini and used by the inventory update script. For a detailed description of these variables" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:300 -msgid "" -"Override variables found in foreman.ini and used by the inventory update script. For an example variable configuration\n" -" \n" -" view foreman.ini in the Ansible github repo. Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:266 -msgid "" -"Override variables found in openstack.yml and used by the inventory update script. For an example variable configuration\n" -" \n" -" view openstack.yml in the Ansible github repo. Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:242 -msgid "Override variables found in vmware.ini and used by the inventory update script. For a detailed description of these variables" -msgstr "" - -#: client/features/output/output.strings.js:68 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:352 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:357 -msgid "Overwrite" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:364 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:369 -msgid "Overwrite Variables" -msgstr "" - -#: client/features/output/output.strings.js:69 -msgid "Overwrite Vars" -msgstr "" - -#: client/src/credentials/credentials.list.js:40 -msgid "Owners" -msgstr "" - -#: client/src/login/loginModal/loginModal.partial.html:74 -msgid "PASSWORD" -msgstr "" - -#: client/features/credentials/legacy.credentials.js:117 -msgid "PERMISSIONS" -msgstr "" - -#: client/features/output/output.strings.js:111 -msgid "PLAY" -msgstr "" - -#: client/src/partials/job-template-details.html:2 -msgid "PLAYBOOK" -msgstr "" - -#: client/src/partials/survey-maker-modal.html:45 -msgid "PLEASE ADD A SURVEY PROMPT." -msgstr "" - -#: client/src/organizations/list/organizations-list.partial.html:37 -#: client/src/shared/form-generator.js:1905 -#: client/src/shared/list-generator/list-generator.factory.js:248 -msgid "PLEASE ADD ITEMS TO THIS LIST" -msgstr "" - -#: client/src/partials/survey-maker-modal.html:43 -msgid "PREVIEW" -msgstr "" - -#: client/src/partials/job-template-details.html:2 -msgid "PROJECT" -msgstr "" - -#: client/features/projects/projects.strings.js:8 -#: client/features/projects/routes/projectsList.route.js:13 -#: client/src/activity-stream/get-target-title.factory.js:8 -#: client/src/organizations/linkout/organizations-linkout.route.js:196 -#: client/src/organizations/list/organizations-list.controller.js:73 -#: client/src/projects/projects.list.js:14 -#: client/src/projects/projects.list.js:15 -msgid "PROJECTS" -msgstr "" - -#: client/features/templates/templates.strings.js:27 -msgid "PROMPT" -msgstr "" - -#: client/src/shared/paginate/paginate.partial.html:33 -msgid "Page" -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:232 -msgid "Pagerduty subdomain" -msgstr "" - -#: client/src/templates/job_templates/job-template.form.js:376 -msgid "Pass extra command line variables to the playbook. Provide key/value pairs using either YAML or JSON. Refer to the Ansible Tower documentation for example syntax." -msgstr "" - -#: client/src/templates/workflows.form.js:114 -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 Tower documentation for example syntax." -msgstr "" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:139 -msgid "Pass extra command line variables. This is the %s or %s command line parameter for %s. Provide key/value pairs using either YAML or JSON." -msgstr "" - -#: client/src/credentials/credentials.form.js:226 -#: client/src/credentials/factories/become-method-change.factory.js:21 -#: client/src/credentials/factories/become-method-change.factory.js:40 -#: client/src/credentials/factories/become-method-change.factory.js:48 -#: client/src/credentials/factories/become-method-change.factory.js:68 -#: client/src/credentials/factories/become-method-change.factory.js:78 -#: client/src/credentials/factories/become-method-change.factory.js:88 -#: client/src/credentials/factories/kind-change.factory.js:105 -#: client/src/credentials/factories/kind-change.factory.js:125 -#: client/src/credentials/factories/kind-change.factory.js:135 -#: client/src/credentials/factories/kind-change.factory.js:145 -#: client/src/credentials/factories/kind-change.factory.js:30 -#: client/src/credentials/factories/kind-change.factory.js:78 -#: client/src/credentials/factories/kind-change.factory.js:97 -#: client/src/job-submission/job-submission.partial.html:104 -#: client/src/notifications/shared/type-change.service.js:30 -#: client/src/templates/survey-maker/surveys/init.factory.js:15 -#: client/src/users/users.form.js:70 -msgid "Password" -msgstr "" - -#: client/src/credentials/factories/kind-change.factory.js:58 -msgid "Password (API Key)" -msgstr "" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:20 -msgid "Past 24 Hours" -msgstr "" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:15 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:26 -msgid "Past Month" -msgstr "" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:23 -msgid "Past Week" -msgstr "" - -#: client/src/credentials/factories/become-method-change.factory.js:29 -#: client/src/credentials/factories/kind-change.factory.js:86 -msgid "Paste the contents of the PEM file associated with the service account email." -msgstr "" - -#: client/src/credentials/factories/kind-change.factory.js:51 -msgid "Paste the contents of the SSH private key file." -msgstr "" - -#: client/src/credentials/factories/kind-change.factory.js:26 -msgid "Paste the contents of the SSH private key file.%s or click to close%s" -msgstr "" - -#: client/src/inventories-hosts/inventories/inventory.list.js:129 -msgid "Pending Delete" -msgstr "" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:8 -msgid "Period" -msgstr "" - -#: client/src/projects/add/projects-add.controller.js:35 -#: client/src/users/add/users-add.controller.js:44 -msgid "Permission Error" -msgstr "" - -#: client/features/credentials/credentials.strings.js:14 -#: client/features/credentials/legacy.credentials.js:63 -#: client/src/credentials/credentials.form.js:439 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:104 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:106 -#: client/src/organizations/organizations.form.js:126 -#: client/src/projects/projects.form.js:247 -#: client/src/teams/teams.form.js:120 -#: client/src/templates/job_templates/job-template.form.js:415 -#: client/src/templates/workflows.form.js:164 -#: client/src/users/users.form.js:189 -msgid "Permissions" -msgstr "" - -#: client/features/users/tokens/tokens.strings.js:41 -msgid "Personal Access Token" -msgstr "" - -#: client/features/output/output.strings.js:70 -#: client/src/shared/form-generator.js:1085 -#: client/src/templates/job_templates/job-template.form.js:107 -#: client/src/templates/job_templates/job-template.form.js:115 -msgid "Playbook" -msgstr "" - -#: client/src/projects/projects.form.js:90 -msgid "Playbook Directory" -msgstr "" - -#: client/features/templates/templates.strings.js:61 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:52 -msgid "Playbook Run" -msgstr "" - -#: client/features/output/output.strings.js:99 -msgid "Plays" -msgstr "" - -#: client/lib/components/components.strings.js:108 -msgid "Please add items to this list." -msgstr "" - -#: client/src/users/users.form.js:128 -msgid "Please add user to an Organization." -msgstr "" - -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:99 -msgid "Please assign roles to the selected resources" -msgstr "" - -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:60 -msgid "Please assign roles to the selected users/teams" -msgstr "" - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:210 -msgid "Please check the server and make sure the directory exists and file permissions are set correctly." -msgstr "" - -#: client/src/license/license.partial.html:84 -msgid "Please click the button below to visit Ansible's website to get a Tower license key." -msgstr "" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:40 -msgid "Please click the icon to edit the host filter." -msgstr "" - -#: client/features/templates/templates.strings.js:112 -msgid "Please click the start button to build your workflow." -msgstr "" - -#: client/src/shared/form-generator.js:868 -#: client/src/shared/form-generator.js:963 -msgid "Please enter a URL that begins with ssh, http or https. The URL may not contain the '@' character." -msgstr "" - -#: client/src/shared/form-generator.js:1178 -msgid "Please enter a number greater than %d and less than %d." -msgstr "" - -#: client/src/shared/form-generator.js:1180 -msgid "Please enter a number greater than %d." -msgstr "" - -#: client/src/shared/form-generator.js:1172 -msgid "Please enter a number." -msgstr "" - -#: client/features/templates/templates.strings.js:40 -#: client/src/job-submission/job-submission.partial.html:112 -#: client/src/job-submission/job-submission.partial.html:126 -#: client/src/job-submission/job-submission.partial.html:140 -#: client/src/job-submission/job-submission.partial.html:154 -#: client/src/login/loginModal/loginModal.partial.html:84 -msgid "Please enter a password." -msgstr "" - -#: client/src/login/loginModal/loginModal.partial.html:64 -msgid "Please enter a username." -msgstr "" - -#: client/src/shared/form-generator.js:858 -#: client/src/shared/form-generator.js:953 -msgid "Please enter a valid email address." -msgstr "" - -#: client/lib/components/components.strings.js:15 -#: client/src/shared/form-generator.js:1023 -#: client/src/shared/form-generator.js:853 -#: client/src/shared/form-generator.js:948 -msgid "Please enter a value." -msgstr "" - -#: client/src/job-submission/job-submission.partial.html:289 -#: client/src/job-submission/job-submission.partial.html:294 -#: client/src/job-submission/job-submission.partial.html:305 -#: client/src/job-submission/job-submission.partial.html:311 -#: client/src/job-submission/job-submission.partial.html:317 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:14 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:19 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:30 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:36 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:42 -msgid "Please enter an answer between" -msgstr "" - -#: client/features/templates/templates.strings.js:60 -#: client/src/job-submission/job-submission.partial.html:316 -msgid "Please enter an answer that is a decimal number." -msgstr "" - -#: client/features/templates/templates.strings.js:59 -#: client/src/job-submission/job-submission.partial.html:310 -msgid "Please enter an answer that is a valid integer." -msgstr "" - -#: client/features/templates/templates.strings.js:57 -#: client/src/job-submission/job-submission.partial.html:288 -#: client/src/job-submission/job-submission.partial.html:293 -#: client/src/job-submission/job-submission.partial.html:304 -#: client/src/job-submission/job-submission.partial.html:309 -#: client/src/job-submission/job-submission.partial.html:315 -msgid "Please enter an answer." -msgstr "" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:46 -msgid "Please enter at least one search term to create a new Smart Inventory." -msgstr "" - -#: client/features/templates/templates.strings.js:113 -msgid "Please hover over a template for additional options." -msgstr "" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:169 -msgid "Please input a number greater than 1." -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:47 -msgid "Please provide a valid date." -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:30 -msgid "Please provide a value between 1 and 999." -msgstr "" - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:54 -msgid "Please save before adding a survey to this job template." -msgstr "" - -#: client/src/templates/workflows/add-workflow/workflow-add.controller.js:52 -msgid "Please save before adding a survey to this workflow." -msgstr "" - -#: client/src/notifications/notifications.list.js:15 -msgid "Please save before adding notifications." -msgstr "" - -#: client/src/organizations/organizations.form.js:80 -#: client/src/teams/teams.form.js:72 -msgid "Please save before adding users." -msgstr "" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:100 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:102 -#: client/src/organizations/organizations.form.js:118 -#: client/src/projects/projects.form.js:239 -#: client/src/teams/teams.form.js:116 -#: client/src/templates/job_templates/job-template.form.js:408 -#: client/src/templates/workflows.form.js:157 -msgid "Please save before assigning permissions." -msgstr "" - -#: client/src/users/users.form.js:126 -#: client/src/users/users.form.js:185 -msgid "Please save before assigning to organizations." -msgstr "" - -#: client/src/users/users.form.js:154 -msgid "Please save before assigning to teams." -msgstr "" - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:147 -msgid "Please save before creating groups." -msgstr "" - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:156 -msgid "Please save before creating hosts." -msgstr "" - -#: client/src/inventories-hosts/hosts/host.form.js:112 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:86 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:111 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:112 -msgid "Please save before defining groups." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:94 -msgid "Please save before defining hosts." -msgstr "" - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:165 -msgid "Please save before defining inventory sources." -msgstr "" - -#: client/src/templates/workflows/add-workflow/workflow-add.controller.js:51 -msgid "Please save before defining the workflow graph." -msgstr "" - -#: client/src/inventories-hosts/hosts/host.form.js:121 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:120 -msgid "Please save before viewing Insights." -msgstr "" - -#: client/src/inventories-hosts/hosts/host.form.js:105 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:104 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:105 -msgid "Please save before viewing facts." -msgstr "" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:146 -msgid "Please save before viewing hosts." -msgstr "" - -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:26 -msgid "Please select Users / Teams from the lists below." -msgstr "" - -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:29 -msgid "Please select Users from the list below." -msgstr "" - -#: client/src/shared/form-generator.js:1213 -msgid "Please select a number between" -msgstr "" - -#: client/src/shared/form-generator.js:1209 -msgid "Please select a number." -msgstr "" - -#: client/features/templates/templates.strings.js:58 -msgid "Please select a value" -msgstr "" - -#: client/src/shared/form-generator.js:1097 -#: client/src/shared/form-generator.js:1169 -#: client/src/shared/form-generator.js:1290 -#: client/src/shared/form-generator.js:1398 -msgid "Please select a value." -msgstr "" - -#: client/src/templates/job_templates/job-template.form.js:77 -msgid "Please select an Inventory or check the Prompt on launch option." -msgstr "" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:39 -msgid "Please select an organization before editing the host filter." -msgstr "" - -#: client/src/shared/form-generator.js:1206 -msgid "Please select at least one value." -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:43 -msgid "Please select one or more days." -msgstr "" - -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:30 -msgid "Please select resources from the lists below." -msgstr "" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:47 -msgid "Populate the hosts for this inventory by using a search filter." -msgstr "" - -#: client/src/notifications/shared/type-change.service.js:29 -msgid "Port" -msgstr "" - -#: client/features/templates/templates.strings.js:30 -msgid "Preview" -msgstr "" - -#: client/src/credentials/credentials.form.js:257 -#: client/src/credentials/factories/kind-change.factory.js:21 -#: client/src/credentials/factories/kind-change.factory.js:45 -msgid "Private Key" -msgstr "" - -#: client/features/templates/templates.strings.js:43 -#: client/src/credentials/credentials.form.js:264 -#: client/src/job-submission/job-submission.partial.html:118 -msgid "Private Key Passphrase" -msgstr "" - -#: client/src/credentials/credentials.form.js:279 -#: client/src/credentials/credentials.form.js:283 -msgid "Privilege Escalation" -msgstr "" - -#: client/features/templates/templates.strings.js:44 -#: client/src/credentials/credentials.form.js:305 -#: client/src/job-submission/job-submission.partial.html:132 -msgid "Privilege Escalation Password" -msgstr "" - -#: client/src/credentials/credentials.form.js:295 -msgid "Privilege Escalation Username" -msgstr "" - -#: client/features/jobs/jobs.strings.js:15 -#: client/features/output/output.strings.js:71 -#: client/features/templates/templates.strings.js:18 -#: client/src/credentials/factories/become-method-change.factory.js:30 -#: client/src/credentials/factories/kind-change.factory.js:87 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:93 -#: client/src/templates/job_templates/job-template.form.js:100 -#: client/src/templates/job_templates/job-template.form.js:91 -msgid "Project" -msgstr "" - -#: client/src/credentials/factories/become-method-change.factory.js:53 -#: client/src/credentials/factories/kind-change.factory.js:110 -msgid "Project (Tenant Name)" -msgstr "" - -#: client/src/projects/projects.form.js:76 -#: client/src/projects/projects.form.js:84 -msgid "Project Base Path" -msgstr "" - -#: client/src/credentials/credentials.form.js:365 -msgid "Project Name" -msgstr "" - -#: client/src/projects/projects.form.js:101 -msgid "Project Path" -msgstr "" - -#: client/features/templates/templates.strings.js:104 -#: client/src/workflow-results/workflow-results.controller.js:83 -msgid "Project Sync" -msgstr "" - -#: client/src/home/dashboard/counts/dashboard-counts.directive.js:66 -msgid "Project Sync Failures" -msgstr "" - -#: client/lib/components/components.strings.js:72 -#: client/lib/models/models.strings.js:40 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:114 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:47 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:33 -#: client/src/home/dashboard/counts/dashboard-counts.directive.js:61 -#: client/src/organizations/linkout/organizations-linkout.route.js:207 -#: client/src/organizations/linkout/organizations-linkout.route.js:209 -msgid "Projects" -msgstr "" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:18 -msgid "Promote group" -msgid_plural "Promote groups" -msgstr[0] "" -msgstr[1] "" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:43 -msgid "Promote groups" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:32 -msgid "Promote groups and hosts" -msgstr "" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:20 -msgid "Promote host" -msgid_plural "Promote hosts" -msgstr[0] "" -msgstr[1] "" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:54 -msgid "Promote hosts" -msgstr "" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:10 -msgid "Promote {{ group }} and {{ host }}" -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:54 -#: client/src/templates/survey-maker/shared/question-definition.form.js:27 -msgid "Prompt" -msgstr "" - -#: client/lib/components/components.strings.js:34 -#: client/src/templates/job_templates/job-template.form.js:138 -#: client/src/templates/job_templates/job-template.form.js:168 -#: client/src/templates/job_templates/job-template.form.js:185 -#: client/src/templates/job_templates/job-template.form.js:202 -#: client/src/templates/job_templates/job-template.form.js:219 -#: client/src/templates/job_templates/job-template.form.js:283 -#: client/src/templates/job_templates/job-template.form.js:383 -#: client/src/templates/job_templates/job-template.form.js:60 -#: client/src/templates/job_templates/job-template.form.js:86 -#: client/src/templates/workflows.form.js:88 -msgid "Prompt on launch" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:238 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:253 -msgid "Provide a comma-separated list of filter expressions." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:254 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:271 -msgid "Provide a comma-separated list of filter expressions. Hosts are imported when all of the filters match. Refer to Ansible Tower documentation for more detail." -msgstr "" - -#: client/src/inventories-hosts/hosts/host.form.js:50 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:49 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:49 -msgid "Provide a host name, ip address, or ip address:port. Examples include:" -msgstr "" - -#: client/src/templates/job_templates/job-template.form.js:162 -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 "" - -#: client/features/credentials/credentials.strings.js:22 -msgid "Provide account information using Google Compute Engine JSON credentials file." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:196 -msgid "Provide environment variables to pass to the custom inventory script." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:257 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:274 -msgid "Provide the named URL encoded name or id of the remote Tower inventory to be imported." -msgstr "" - -#: client/src/templates/job_templates/job-template.form.js:339 -#: client/src/templates/job_templates/job-template.form.js:347 -msgid "Provisioning Callback URL" -msgstr "" - -#: client/src/notifications/add/add.controller.js:81 -#: client/src/notifications/edit/edit.controller.js:128 -msgid "Purple" -msgstr "" - -#: client/src/configuration/forms/auth-form/configuration-auth.controller.js:95 -msgid "RADIUS" -msgstr "" - -#: client/src/instance-groups/instance-groups.strings.js:47 -msgid "RAM" -msgstr "" - -#: client/lib/components/code-mirror/code-mirror.strings.js:13 -msgid "READ ONLY" -msgstr "" - -#: client/src/home/dashboard/lists/jobs/jobs-list.partial.html:4 -msgid "RECENT JOB RUNS" -msgstr "" - -#: client/src/home/dashboard/lists/jobs/jobs-list.partial.html:40 -msgid "RECENTLY RUN JOBS" -msgstr "" - -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:47 -msgid "RECENTLY USED JOB TEMPLATES" -msgstr "" - -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:4 -msgid "RECENTLY USED TEMPLATES" -msgstr "" - -#: client/src/activity-stream/streams.list.js:54 -#: client/src/inventories-hosts/hosts/host.list.js:102 -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.list.js:46 -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:47 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:47 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:113 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:47 -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:54 -#: client/src/projects/projects.list.js:70 -#: client/src/scheduler/schedules.list.js:69 -msgid "REFRESH" -msgstr "" - -#: client/features/users/tokens/tokens.strings.js:23 -msgid "REFRESH TOKEN" -msgstr "" - -#: client/src/shared/smart-search/smart-search.partial.html:45 -msgid "RELATED FIELDS:" -msgstr "" - -#: client/src/shared/directives.js:94 -msgid "REMOVE" -msgstr "" - -#: client/lib/components/components.strings.js:7 -msgid "REPLACE" -msgstr "" - -#: client/features/output/output.strings.js:8 -msgid "RESULTS" -msgstr "" - -#: client/features/templates/templates.strings.js:36 -#: client/lib/components/components.strings.js:8 -#: client/src/job-submission/job-submission.partial.html:44 -#: client/src/job-submission/job-submission.partial.html:87 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html:50 -msgid "REVERT" -msgstr "" - -#: client/features/projects/projects.strings.js:9 -msgid "REVISION" -msgstr "" - -#: client/src/credentials/factories/become-method-change.factory.js:25 -#: client/src/credentials/factories/kind-change.factory.js:82 -msgid "RSA Private Key" -msgstr "" - -#: client/features/templates/templates.strings.js:114 -msgid "RUN" -msgstr "" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.route.js:45 -msgid "RUN COMMAND" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:56 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:56 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:101 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:114 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:56 -msgid "RUN COMMANDS" -msgstr "" - -#: client/src/notifications/add/add.controller.js:84 -#: client/src/notifications/edit/edit.controller.js:131 -msgid "Random" -msgstr "" - -#: client/features/users/tokens/tokens.strings.js:30 -msgid "Read" -msgstr "" - -#: client/src/workflow-results/workflow-results.controller.js:167 -msgid "Read only view of extra variables added to the workflow." -msgstr "" - -#: client/features/output/output.strings.js:23 -msgid "Read-only view of extra variables added to the job template" -msgstr "" - -#: client/lib/components/code-mirror/code-mirror.strings.js:49 -msgid "Read-only view of extra variables added to the job template." -msgstr "" - -#: client/src/notifications/notificationTemplates.list.js:26 -msgid "Recent Notifications" -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:94 -#: client/src/notifications/notificationTemplates.form.js:98 -msgid "Recipient List" -msgstr "" - -#: client/src/notifications/add/add.controller.js:82 -#: client/src/notifications/edit/edit.controller.js:129 -msgid "Red" -msgstr "" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:47 -msgid "Refer to the Ansible Tower documentation for further syntax and examples." -msgstr "" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:252 -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:277 -msgid "Refresh" -msgstr "" - -#: client/src/activity-stream/streams.list.js:51 -#: client/src/bread-crumb/bread-crumb.partial.html:6 -#: client/src/inventories-hosts/hosts/host.list.js:98 -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.list.js:42 -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:43 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:43 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:109 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:43 -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:50 -#: client/src/projects/projects.list.js:66 -#: client/src/scheduler/schedules.list.js:65 -msgid "Refresh the page" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:231 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:245 -msgid "Region:" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:131 -msgid "Regions" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:65 -msgid "Related Groups" -msgstr "" - -#: client/lib/components/components.strings.js:98 -msgid "Relaunch On" -msgstr "" - -#: client/lib/components/components.strings.js:97 -msgid "Relaunch using host parameters" -msgstr "" - -#: client/lib/components/components.strings.js:96 -#: client/src/workflow-results/workflow-results.controller.js:45 -msgid "Relaunch using the same parameters" -msgstr "" - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:180 -msgid "Remediate Inventory" -msgstr "" - -#: client/src/access/add-rbac-user-team/rbac-selected-list.directive.js:102 -#: client/src/access/add-rbac-user-team/rbac-selected-list.directive.js:103 -#: client/src/teams/teams.form.js:145 -#: client/src/users/users.form.js:224 -msgid "Remove" -msgstr "" - -#: client/src/projects/projects.form.js:159 -msgid "Remove any local modifications prior to performing an update." -msgstr "" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:149 -#: client/src/scheduler/scheduler.strings.js:27 -msgid "Repeat frequency" -msgstr "" - -#: client/src/license/license.partial.html:89 -msgid "Request License" -msgstr "" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:291 -msgid "Required" -msgstr "" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:154 -msgid "Reset" -msgstr "" - -#: client/lib/components/components.strings.js:90 -msgid "Resources" -msgstr "" - -#: client/features/templates/templates.strings.js:86 -#: client/src/scheduler/schedules.list.js:24 -msgid "Resources are missing from this template." -msgstr "" - -#: client/lib/services/base-string.service.js:89 -msgid "Return" -msgstr "" - -#: client/features/users/tokens/tokens.strings.js:26 -msgid "Returned status:" -msgstr "" - -#: client/src/shared/form-generator.js:697 -msgid "Revert" -msgstr "" - -#: client/src/configuration/forms/auth-form/sub-forms/auth-azure.form.js:47 -#: client/src/configuration/forms/auth-form/sub-forms/auth-github-org.form.js:51 -#: client/src/configuration/forms/auth-form/sub-forms/auth-github-team.form.js:51 -#: client/src/configuration/forms/auth-form/sub-forms/auth-github.form.js:47 -#: client/src/configuration/forms/auth-form/sub-forms/auth-google-oauth2.form.js:59 -#: client/src/configuration/forms/auth-form/sub-forms/auth-ldap.form.js:102 -#: client/src/configuration/forms/auth-form/sub-forms/auth-ldap1.form.js:102 -#: client/src/configuration/forms/auth-form/sub-forms/auth-ldap2.form.js:102 -#: client/src/configuration/forms/auth-form/sub-forms/auth-ldap3.form.js:102 -#: client/src/configuration/forms/auth-form/sub-forms/auth-ldap4.form.js:102 -#: client/src/configuration/forms/auth-form/sub-forms/auth-ldap5.form.js:102 -#: client/src/configuration/forms/auth-form/sub-forms/auth-radius.form.js:34 -#: client/src/configuration/forms/auth-form/sub-forms/auth-saml.form.js:121 -#: client/src/configuration/forms/auth-form/sub-forms/auth-tacacs.form.js:47 -#: client/src/configuration/forms/jobs-form/configuration-jobs.form.js:76 -#: client/src/configuration/forms/system-form/sub-forms/system-activity-stream.form.js:26 -#: client/src/configuration/forms/system-form/sub-forms/system-logging.form.js:74 -#: client/src/configuration/forms/system-form/sub-forms/system-misc.form.js:56 -#: client/src/configuration/forms/ui-form/configuration-ui.form.js:36 -msgid "Revert all to default" -msgstr "" - -#: client/features/output/output.strings.js:73 -#: client/src/projects/projects.list.js:50 -msgid "Revision" -msgstr "" - -#: client/src/projects/add/projects-add.controller.js:158 -#: client/src/projects/edit/projects-edit.controller.js:290 -msgid "Revision #" -msgstr "" - -#: client/features/credentials/legacy.credentials.js:85 -#: client/src/credentials/credentials.form.js:462 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:130 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:132 -#: client/src/organizations/organizations.form.js:109 -#: client/src/organizations/organizations.form.js:148 -#: client/src/projects/projects.form.js:270 -#: client/src/teams/teams.form.js:101 -#: client/src/teams/teams.form.js:138 -#: client/src/templates/workflows.form.js:188 -#: client/src/users/users.form.js:207 -msgid "Role" -msgstr "" - -#: client/src/instance-groups/instance-groups.list.js:26 -#: client/src/instance-groups/instance-groups.strings.js:18 -#: client/src/instance-groups/instance-groups.strings.js:53 -msgid "Running Jobs" -msgstr "" - -#: client/src/configuration/forms/auth-form/configuration-auth.controller.js:96 -msgid "SAML" -msgstr "" - -#: client/lib/services/base-string.service.js:63 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal.partial.html:17 -#: client/src/inventories-hosts/shared/associate-groups/associate-groups.partial.html:17 -#: client/src/inventories-hosts/shared/associate-hosts/associate-hosts.partial.html:17 -#: client/src/partials/survey-maker-modal.html:87 -#: client/src/shared/instance-groups-multiselect/instance-groups-modal/instance-groups-modal.partial.html:18 -msgid "SAVE" -msgstr "" - -#: client/src/scheduler/scheduled-jobs.list.js:13 -#: client/src/scheduler/scheduled-jobs.list.js:14 -msgid "SCHEDULED JOBS" -msgstr "" - -#: client/src/activity-stream/get-target-title.factory.js:38 -#: client/src/inventories-hosts/inventories/related/sources/list/schedule/sources-schedule.route.js:9 -#: client/src/management-jobs/scheduler/main.js:28 -#: client/src/management-jobs/scheduler/main.js:34 -#: client/src/scheduler/schedules.route.js:102 -#: client/src/scheduler/schedules.route.js:14 -#: client/src/scheduler/schedules.route.js:189 -#: client/src/scheduler/schedules.route.js:284 -msgid "SCHEDULES" -msgstr "" - -#: client/src/projects/add/projects-add.controller.js:130 -#: client/src/projects/edit/projects-edit.controller.js:263 -msgid "SCM Branch" -msgstr "" - -#: client/src/projects/add/projects-add.controller.js:149 -#: client/src/projects/edit/projects-edit.controller.js:281 -msgid "SCM Branch/Tag/Commit" -msgstr "" - -#: client/src/projects/add/projects-add.controller.js:170 -#: client/src/projects/edit/projects-edit.controller.js:302 -msgid "SCM Branch/Tag/Revision" -msgstr "" - -#: client/src/projects/projects.form.js:160 -msgid "SCM Clean" -msgstr "" - -#: client/src/projects/projects.form.js:171 -msgid "SCM Delete" -msgstr "" - -#: client/src/credentials/factories/become-method-change.factory.js:20 -#: client/src/credentials/factories/kind-change.factory.js:77 -msgid "SCM Private Key" -msgstr "" - -#: client/src/projects/projects.form.js:56 -msgid "SCM Type" -msgstr "" - -#: client/src/projects/projects.form.js:107 -msgid "SCM URL" -msgstr "" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:49 -#: client/src/projects/projects.form.js:181 -msgid "SCM Update" -msgstr "" - -#: client/features/projects/projects.strings.js:28 -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:231 -msgid "SCM Update Cancel" -msgstr "" - -#: client/src/projects/projects.form.js:151 -msgid "SCM Update Options" -msgstr "" - -#: client/features/projects/projects.strings.js:17 -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:126 -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:36 -#: client/src/projects/edit/projects-edit.controller.js:139 -msgid "SCM update currently running" -msgstr "" - -#: client/features/users/tokens/tokens.strings.js:39 -msgid "SCOPE" -msgstr "" - -#: client/features/output/output.strings.js:91 -msgid "SEARCH" -msgstr "" - -#: client/features/templates/templates.strings.js:116 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html:74 -msgid "SELECT" -msgstr "" - -#: client/features/credentials/credentials.strings.js:20 -msgid "SELECT A CREDENTIAL TYPE" -msgstr "" - -#: client/features/users/tokens/tokens.strings.js:19 -msgid "SELECT AN APPLICATION" -msgstr "" - -#: client/features/applications/applications.strings.js:39 -#: client/features/credentials/credentials.strings.js:19 -msgid "SELECT AN ORGANIZATION" -msgstr "" - -#: client/src/inventories-hosts/shared/associate-groups/associate-groups.partial.html:6 -msgid "SELECT GROUPS" -msgstr "" - -#: client/src/inventories-hosts/shared/associate-hosts/associate-hosts.partial.html:6 -msgid "SELECT HOSTS" -msgstr "" - -#: client/src/instance-groups/instance-groups.strings.js:36 -msgid "SELECT INSTANCE" -msgstr "" - -#: client/features/templates/templates.strings.js:33 -msgid "SELECTED" -msgstr "" - -#: client/src/job-submission/job-submission.partial.html:29 -#: client/src/job-submission/job-submission.partial.html:56 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html:18 -msgid "SELECTED:" -msgstr "" - -#: client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html:20 -msgid "SETTING CATEGORY" -msgstr "" - -#: client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html:24 -msgid "SETTING NAME" -msgstr "" - -#: client/src/configuration/settings.route.js:10 -msgid "SETTINGS" -msgstr "" - -#: client/lib/components/components.strings.js:11 -#: client/lib/services/base-string.service.js:66 -#: client/src/templates/survey-maker/surveys/init.factory.js:486 -msgid "SHOW" -msgstr "" - -#: client/src/login/loginModal/loginModal.partial.html:103 -msgid "SIGN IN" -msgstr "" - -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.partial.html:2 -msgid "SIGN IN WITH" -msgstr "" - -#: client/src/inventories-hosts/hosts/host.list.js:110 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:14 -msgid "SMART INVENTORY" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.route.js:27 -msgid "SOURCES" -msgstr "" - -#: client/src/credentials/factories/become-method-change.factory.js:89 -#: client/src/credentials/factories/kind-change.factory.js:146 -msgid "SSH Key" -msgstr "" - -#: client/features/templates/templates.strings.js:42 -msgid "SSH Password" -msgstr "" - -#: client/src/credentials/credentials.form.js:255 -msgid "SSH key description" -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:446 -msgid "SSL Connection" -msgstr "" - -#: client/features/templates/templates.strings.js:118 -msgid "START" -msgstr "" - -#: client/src/smart-status/smart-status.controller.js:43 -msgid "STATUS" -msgstr "" - -#: client/src/credentials/credentials.form.js:119 -#: client/src/credentials/credentials.form.js:127 -msgid "STS Token" -msgstr "" - -#: client/src/home/dashboard/graphs/job-status/job-status-graph.directive.js:62 -msgid "SUCCESSFUL" -msgstr "" - -#: client/src/partials/survey-maker-modal.html:24 -msgid "SURVEY" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:62 -msgid "SYNC ALL" -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:42 -msgid "Sat" -msgstr "" - -#: client/src/credentials/factories/become-method-change.factory.js:70 -#: client/src/credentials/factories/kind-change.factory.js:127 -msgid "Satellite 6 URL" -msgstr "" - -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:110 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:192 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:158 -#: client/src/scheduler/scheduler.strings.js:57 -#: client/src/shared/form-generator.js:1711 -msgid "Save" -msgstr "" - -#: client/src/configuration/forms/settings-form.controller.js:378 -msgid "Save Complete" -msgstr "" - -#: client/src/configuration/forms/auth-form/configuration-auth.controller.js:66 -#: client/src/configuration/forms/settings-form.controller.js:410 -#: client/src/configuration/forms/system-form/configuration-system.controller.js:63 -msgid "Save changes" -msgstr "" - -#: client/src/license/license.partial.html:140 -msgid "Save successful!" -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:50 -msgid "Schedule Description" -msgstr "" - -#: client/src/management-jobs/card/card.partial.html:28 -msgid "Schedule Management Job" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:7 -msgid "Schedule inventory syncs" -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:14 -msgid "Schedule is active." -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:15 -msgid "Schedule is active. Click to stop." -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:16 -msgid "Schedule is stopped." -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:17 -msgid "Schedule is stopped. Click to activate." -msgstr "" - -#: client/lib/components/components.strings.js:70 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:35 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:440 -#: client/src/projects/projects.form.js:290 -#: client/src/templates/job_templates/job-template.form.js:461 -#: client/src/templates/workflows.form.js:210 -msgid "Schedules" -msgstr "" - -#: client/src/shared/smart-search/smart-search.controller.js:122 -#: client/src/shared/smart-search/smart-search.controller.js:164 -msgid "Search" -msgstr "" - -#: client/src/credentials/credentials.form.js:104 -msgid "Secret Key" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:232 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:246 -msgid "Security Group:" -msgstr "" - -#: client/src/credentials/credentials.form.js:124 -msgid "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." -msgstr "" - -#: client/src/shared/form-generator.js:1715 -#: client/src/shared/lookup/lookup-modal.directive.js:59 -#: client/src/shared/lookup/lookup-modal.partial.html:20 -msgid "Select" -msgstr "" - -#: client/src/shared/instance-groups-multiselect/instance-groups-modal/instance-groups-modal.partial.html:5 -msgid "Select Instance Groups" -msgstr "" - -#: client/src/job-submission/job-submission.directive.js:65 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js:73 -msgid "Select a credential" -msgstr "" - -#: client/src/access/add-rbac-user-team/rbac-user-team.controller.js:68 -msgid "Select a role" -msgstr "" - -#: client/features/users/tokens/tokens.strings.js:29 -msgid "Select a scope" -msgstr "" - -#: client/src/templates/workflows.form.js:81 -msgid "Select an inventory for the workflow. This inventory is applied to all job template nodes that prompt for an inventory." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:53 -msgid "Select an inventory source by clicking the check box beside it. The inventory source can be a single group or a selection of multiple groups." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:53 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:98 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:53 -msgid "Select an inventory source by clicking the check box beside it. The inventory source can be a single group or host, a selection of multiple hosts, or a selection of multiple groups." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:111 -msgid "Select an inventory source by clicking the check box beside it. The inventory source can be a single host or a selection of multiple hosts." -msgstr "" - -#: client/src/configuration/forms/jobs-form/configuration-jobs.controller.js:107 -#: client/src/configuration/forms/jobs-form/configuration-jobs.controller.js:132 -#: client/src/configuration/forms/ui-form/configuration-ui.controller.js:94 -msgid "Select commands" -msgstr "" - -#: client/src/templates/job_templates/job-template.form.js:132 -msgid "Select credentials that allow Tower to access 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 "" - -#: client/src/projects/projects.form.js:99 -msgid "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." -msgstr "" - -#: client/src/configuration/forms/auth-form/configuration-auth.controller.js:317 -#: client/src/configuration/forms/auth-form/configuration-auth.controller.js:336 -msgid "Select group types" -msgstr "" - -#: client/src/access/rbac-multiselect/rbac-multiselect-role.directive.js:24 -msgid "Select roles" -msgstr "" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:56 -msgid "Select the Instance Groups for this Inventory to run on." -msgstr "" - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:63 -msgid "Select the Instance Groups for this Inventory to run on. Refer to the Ansible Tower documentation for more detail." -msgstr "" - -#: client/src/templates/job_templates/job-template.form.js:254 -msgid "Select the Instance Groups for this Job Template to run on." -msgstr "" - -#: client/src/organizations/organizations.form.js:40 -msgid "Select the Instance Groups for this Organization to run on." -msgstr "" - -#: client/src/templates/job_templates/job-template.form.js:244 -msgid "Select the custom Python virtual environment for this job template to run on." -msgstr "" - -#: client/src/organizations/organizations.form.js:51 -msgid "Select the custom Python virtual environment for this organization to run on." -msgstr "" - -#: client/src/projects/projects.form.js:211 -msgid "Select the custom Python virtual environment for this project to run on." -msgstr "" - -#: client/src/templates/job_templates/job-template.form.js:79 -msgid "Select the inventory containing the hosts you want this job to manage." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:122 -msgid "Select the inventory file to be synced by this source. You can select from the dropdown or enter a file within the input." -msgstr "" - -#: client/src/templates/job_templates/job-template.form.js:114 -msgid "Select the playbook to be executed by this job." -msgstr "" - -#: client/src/templates/job_templates/job-template.form.js:99 -msgid "Select the project containing the playbook you want this job to execute." -msgstr "" - -#: client/src/configuration/forms/system-form/configuration-system.controller.js:188 -msgid "Select types" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:224 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:238 -msgid "Select which groups to create automatically." -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:110 -msgid "Sender Email" -msgstr "" - -#: client/src/credentials/factories/become-method-change.factory.js:24 -#: client/src/credentials/factories/kind-change.factory.js:81 -msgid "Service Account Email Address" -msgstr "" - -#: client/features/credentials/credentials.strings.js:21 -msgid "Service Account JSON File" -msgstr "" - -#: client/lib/components/components.strings.js:86 -msgid "Settings" -msgstr "" - -#: client/src/job-submission/job-submission.partial.html:108 -#: client/src/job-submission/job-submission.partial.html:122 -#: client/src/job-submission/job-submission.partial.html:136 -#: client/src/job-submission/job-submission.partial.html:150 -#: client/src/job-submission/job-submission.partial.html:299 -#: client/src/shared/form-generator.js:883 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:24 -msgid "Show" -msgstr "" - -#: client/features/templates/templates.strings.js:47 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:115 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:118 -#: client/src/templates/job_templates/job-template.form.js:274 -#: client/src/templates/job_templates/job-template.form.js:277 -msgid "Show Changes" -msgstr "" - -#: client/features/output/output.strings.js:44 -#: client/src/workflow-results/workflow-results.controller.js:72 -msgid "Show Less" -msgstr "" - -#: client/features/output/output.strings.js:45 -#: client/src/workflow-results/workflow-results.controller.js:73 -msgid "Show More" -msgstr "" - -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:33 -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:44 -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:55 -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:76 -msgid "Sign in with %s" -msgstr "" - -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:63 -msgid "Sign in with %s Organizations" -msgstr "" - -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:61 -msgid "Sign in with %s Teams" -msgstr "" - -#: client/features/output/output.strings.js:74 -#: client/features/templates/templates.strings.js:48 -#: client/src/job-submission/job-submission.partial.html:245 -#: client/src/templates/job_templates/job-template.form.js:207 -#: client/src/templates/job_templates/job-template.form.js:214 -msgid "Skip Tags" -msgstr "" - -#: client/src/templates/job_templates/job-template.form.js:213 -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 Ansible Tower documentation for details on the usage of tags." -msgstr "" - -#: client/features/output/output.strings.js:59 -msgid "Slice Job" -msgstr "" - -#: client/src/templates/job_templates/job-template.form.js:267 -msgid "Slice Job Count" -msgstr "" - -#: client/src/workflow-results/workflow-results.controller.js:63 -msgid "Slice Job Template" -msgstr "" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:44 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:48 -msgid "Smart Host Filter" -msgstr "" - -#: client/src/inventories-hosts/inventories/inventory.list.js:86 -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:76 -#: client/src/organizations/linkout/controllers/organizations-inventories.controller.js:70 -#: client/src/shared/form-generator.js:1476 -msgid "Smart Inventory" -msgstr "" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:44 -msgid "Solvable With Playbook" -msgstr "" - -#: client/features/output/output.strings.js:75 -#: client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.directive.js:57 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:64 -msgid "Source" -msgstr "" - -#: client/src/credentials/credentials.form.js:75 -msgid "Source Control" -msgstr "" - -#: client/features/output/output.strings.js:76 -msgid "Source Credential" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:47 -#: client/src/projects/projects.form.js:26 -msgid "Source Details" -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:192 -#: client/src/notifications/notificationTemplates.form.js:193 -msgid "Source Phone Number" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:136 -msgid "Source Regions" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:209 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:216 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:233 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:240 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:257 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:264 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:274 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:281 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:291 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:298 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:308 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:315 -msgid "Source Variables" -msgstr "" - -#: client/src/partials/logviewer.html:9 -msgid "Source Vars" -msgstr "" - -#: client/features/output/output.strings.js:77 -#: client/src/workflow-results/workflow-results.controller.js:65 -msgid "Source Workflow" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:34 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:167 -msgid "Sources" -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:330 -msgid "Specify HTTP Headers in JSON format. Refer to the Ansible Tower documentation for example syntax." -msgstr "" - -#: client/src/credentials/credentials.form.js:285 -msgid "Specify a method for %s operations. This is equivalent to specifying the %s parameter, where %s could be %s" -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:478 -msgid "Specify a notification color. Acceptable colors are hex color code (example: #3af or #789abc) ." -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:292 -msgid "Specify a notification color. Acceptable colors are: yellow, green, red purple, gray or random." -msgstr "" - -#: client/features/users/tokens/tokens.strings.js:20 -msgid "Specify a scope for the token's access" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:253 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:270 -msgid "Specify which groups to create automatically. Group names will be created similar to the options selected. If blank, all groups above are created. Refer to Ansible Tower documentation for more detail." -msgstr "" - -#: client/features/output/output.strings.js:116 -msgid "Standard Error" -msgstr "" - -#: client/features/output/output.strings.js:115 -#: client/src/partials/logviewer.html:5 -msgid "Standard Out" -msgstr "" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:41 -#: client/src/scheduler/scheduler.strings.js:23 -msgid "Start Date" -msgstr "" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:56 -#: client/src/scheduler/scheduler.strings.js:24 -msgid "Start Time" -msgstr "" - -#: client/lib/components/components.strings.js:104 -msgid "Start a job using this template" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:6 -msgid "Start sync process" -msgstr "" - -#: client/features/jobs/jobs.strings.js:9 -#: client/features/output/output.strings.js:78 -#: client/src/workflow-results/workflow-results.controller.js:59 -msgid "Started" -msgstr "" - -#: client/features/output/output.strings.js:79 -#: client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.directive.js:53 -#: client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.directive.js:55 -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:43 -#: client/src/notifications/notification-templates-list/list.controller.js:71 -#: client/src/partials/logviewer.html:4 -#: client/src/workflow-results/workflow-results.controller.js:62 -msgid "Status" -msgstr "" - -#: client/src/license/license.partial.html:139 -msgid "Submit" -msgstr "" - -#: client/src/license/license.partial.html:27 -msgid "Subscription" -msgstr "" - -#: client/src/credentials/credentials.form.js:151 -#: client/src/credentials/credentials.form.js:162 -msgid "Subscription ID" -msgstr "" - -#: client/src/credentials/credentials.form.js:161 -msgid "Subscription ID is an Azure construct, which is mapped to a username." -msgstr "" - -#: client/src/notifications/notifications.list.js:39 -msgid "Success" -msgstr "" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:77 -msgid "Successful" -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:36 -msgid "Sun" -msgstr "" - -#: client/features/templates/templates.strings.js:29 -#: client/src/job-submission/job-submission.partial.html:20 -msgid "Survey" -msgstr "" - -#: client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js:62 -#: client/src/templates/workflows/edit-workflow/workflow-edit.controller.js:270 -msgid "Surveys allow users to be prompted at job launch with a series of questions related to the job. This allows for variables to be defined that affect the playbook run at time of launch." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:58 -msgid "Sync all inventory sources" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:29 -msgid "Sync canceled. Click to view log." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:35 -msgid "Sync completed. Click to view log." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:32 -msgid "Sync failed. Click to view log." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:17 -msgid "Sync not performed. Click" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:38 -msgid "Sync pending." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:45 -msgid "Sync running" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:46 -msgid "Sync running. Click to view log." -msgstr "" - -#: client/src/users/add/users-add.controller.js:12 -#: client/src/users/edit/users-edit.controller.js:12 -#: client/src/users/list/users-list.controller.js:12 -msgid "System Administrator" -msgstr "" - -#: client/src/users/add/users-add.controller.js:11 -#: client/src/users/edit/users-edit.controller.js:11 -#: client/src/users/list/users-list.controller.js:11 -msgid "System Auditor" -msgstr "" - -#: client/src/configuration/forms/settings-form.partial.html:3 -msgid "System auditors have read-only permissions in this section." -msgstr "" - -#: client/src/configuration/forms/auth-form/configuration-auth.controller.js:97 -msgid "TACACS+" -msgstr "" - -#: client/features/output/output.strings.js:112 -msgid "TASK" -msgstr "" - -#: client/src/activity-stream/get-target-title.factory.js:23 -#: client/src/organizations/linkout/organizations-linkout.route.js:98 -#: client/src/organizations/list/organizations-list.controller.js:61 -#: client/src/teams/main.js:65 -#: client/src/teams/teams.list.js:14 -#: client/src/teams/teams.list.js:15 -msgid "TEAMS" -msgstr "" - -#: client/features/templates/routes/templatesList.route.js:12 -#: client/features/templates/templates.strings.js:12 -#: client/features/templates/templates.strings.js:8 -#: client/src/activity-stream/get-target-title.factory.js:44 -#: client/src/templates/templates.list.js:15 -#: client/src/templates/templates.list.js:16 -msgid "TEMPLATES" -msgstr "" - -#: client/src/instance-groups/instance-groups.list.js:8 -msgid "THERE ARE CURRENTLY NO INSTANCE GROUPS DEFINED" -msgstr "" - -#: client/src/home/dashboard/graphs/job-status/job-status-graph.directive.js:104 -msgid "TIME" -msgstr "" - -#: client/features/users/tokens/tokens.strings.js:22 -msgid "TOKEN" -msgstr "" - -#: client/features/users/tokens/tokens.strings.js:21 -msgid "TOKEN INFORMATION" -msgstr "" - -#: client/features/applications/applications.strings.js:11 -#: client/features/users/tokens/tokens.strings.js:10 -#: client/features/users/tokens/tokens.strings.js:8 -#: client/features/users/tokens/users-tokens-list.route.js:24 -#: client/src/activity-stream/get-target-title.factory.js:50 -msgid "TOKENS" -msgstr "" - -#: client/features/templates/templates.strings.js:108 -msgid "TOTAL TEMPLATES" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:235 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:249 -msgid "Tag None:" -msgstr "" - -#: client/src/templates/job_templates/job-template.form.js:196 -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 Ansible Tower documentation for details on the usage of tags." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:233 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:247 -msgid "Tags:" -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:309 -#: client/src/notifications/notificationTemplates.form.js:337 -#: client/src/notifications/notificationTemplates.form.js:376 -msgid "Target URL" -msgstr "" - -#: client/features/output/output.strings.js:100 -msgid "Tasks" -msgstr "" - -#: client/features/credentials/legacy.credentials.js:91 -#: client/src/credentials/credentials.form.js:468 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:136 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:138 -#: client/src/organizations/organizations.form.js:154 -#: client/src/projects/projects.form.js:276 -#: client/src/templates/workflows.form.js:194 -msgid "Team Roles" -msgstr "" - -#: client/lib/components/components.strings.js:79 -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:40 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:36 -#: client/src/organizations/linkout/organizations-linkout.route.js:109 -#: client/src/organizations/linkout/organizations-linkout.route.js:111 -#: client/src/shared/stateDefinitions.factory.js:426 -#: client/src/users/users.form.js:161 -msgid "Teams" -msgstr "" - -#: client/src/templates/templates.list.js:14 -#: client/src/workflow-results/workflow-results.controller.js:57 -msgid "Template" -msgstr "" - -#: client/features/templates/templates.strings.js:68 -msgid "Template parameter is missing." -msgstr "" - -#: client/lib/components/components.strings.js:76 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:37 -msgid "Templates" -msgstr "" - -#: client/src/credentials/credentials.form.js:337 -msgid "Tenant ID" -msgstr "" - -#: client/src/configuration/forms/system-form/sub-forms/system-logging.form.js:79 -msgid "Test" -msgstr "" - -#: client/src/notifications/notificationTemplates.list.js:77 -msgid "Test notification" -msgstr "" - -#: client/src/templates/survey-maker/surveys/init.factory.js:13 -msgid "Text" -msgstr "" - -#: client/src/templates/survey-maker/surveys/init.factory.js:14 -msgid "Textarea" -msgstr "" - -#: client/src/shared/form-generator.js:1406 -#: client/src/shared/form-generator.js:1412 -msgid "That value was not found. Please enter or select a valid value." -msgstr "" - -#: client/lib/components/components.strings.js:47 -msgid "That value was not found. Please enter or select a valid value." -msgstr "" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:66 -msgid "The Insights Credential for {{inventory.name}} was not found." -msgstr "" - -#: client/src/credentials/factories/become-method-change.factory.js:32 -#: client/src/credentials/factories/kind-change.factory.js:89 -msgid "The Project ID is the GCE assigned identification. It is constructed as two words followed by a three digit number. Such as:" -msgstr "" - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:204 -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:207 -msgid "The Project selected has a status of" -msgstr "" - -#: client/src/projects/edit/projects-edit.controller.js:338 -msgid "The SCM update process is running." -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:32 -msgid "The day must be between 1 and 31." -msgstr "" - -#: client/src/credentials/credentials.form.js:190 -msgid "The email address assigned to the Google Compute Engine %sservice account." -msgstr "" - -#: client/features/output/output.strings.js:12 -msgid "The host status bar will update when the job is complete." -msgstr "" - -#: client/src/credentials/factories/become-method-change.factory.js:62 -#: client/src/credentials/factories/kind-change.factory.js:119 -msgid "The host to authenticate with." -msgstr "" - -#: client/src/credentials/factories/kind-change.factory.js:60 -msgid "The host value" -msgstr "" - -#: client/features/templates/templates.strings.js:121 -msgid "The inventory of this node will be overridden by the parent workflow inventory." -msgstr "" - -#: client/features/templates/templates.strings.js:123 -msgid "The inventory of this node will be overridden if a parent workflow inventory is provided at launch." -msgstr "" - -#: client/features/templates/templates.strings.js:122 -msgid "The inventory of this node will not be overridden by the parent workflow inventory." -msgstr "" - -#: client/features/templates/templates.strings.js:124 -msgid "The inventory of this node will not be overridden if a parent workflow inventory is provided at launch." -msgstr "" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:154 -msgid "The inventory will be in a pending status until the final delete is processed." -msgstr "" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:105 -msgid "The number of parallel or simultaneous processes to use while executing the playbook. Inputting no value will use the default value from the %sansible configuration file%s." -msgstr "" - -#: client/src/templates/job_templates/job-template.form.js:151 -msgid "The number of parallel or simultaneous processes to use while executing the playbook. Value defaults to 0. Refer to the Ansible documentation for details about the configuration file." -msgstr "" - -#: client/src/credentials/factories/kind-change.factory.js:59 -msgid "The project value" -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:49 -msgid "The scheduler options are invalid, incomplete, or a date is in the past." -msgstr "" - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:210 -msgid "The selected project has a status of" -msgstr "" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:206 -msgid "The selected project is not configured for SCM. To configure for SCM, edit the project and provide SCM settings and then run an update." -msgstr "" - -#: client/features/projects/projects.strings.js:21 -msgid "The selected project is not configured for SCM. To configure for SCM, edit the project and provide SCM settings, and then run an update." -msgstr "" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:52 -msgid "The suggested format for variable names is lowercase and underscore-separated (for example, foo_bar, user_id, host_name, etc.). Variable names with spaces are not allowed." -msgstr "" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:124 -#: client/src/scheduler/scheduler.strings.js:25 -msgid "The time must be in HH24:MM:SS format." -msgstr "" - -#: client/lib/services/base-string.service.js:80 -msgid "The {{ resourceType }} is currently being used by other resources." -msgstr "" - -#: client/src/activity-stream/streams.list.js:17 -msgid "There are no events to display at this time" -msgstr "" - -#: client/features/jobs/jobs.strings.js:17 -msgid "There are no running jobs." -msgstr "" - -#: client/features/projects/projects.strings.js:20 -msgid "There is no SCM update information available for this project. An update has not yet been completed. If you have not already done so, start an update for this project." -msgstr "" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:198 -msgid "There is no SCM update information available for this project. An update has not yet been completed. If you have not already done so, start an update for this project." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:141 -msgid "There was an error deleting inventory source groups. Returned status:" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:131 -msgid "There was an error deleting inventory source hosts. Returned status:" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:168 -msgid "There was an error deleting inventory source. Returned status:" -msgstr "" - -#: client/src/configuration/forms/settings-form.controller.js:151 -msgid "There was an error getting config values:" -msgstr "" - -#: client/src/configuration/forms/settings-form.controller.js:278 -msgid "There was an error resetting value. Returned status:" -msgstr "" - -#: client/src/configuration/forms/settings-form.controller.js:556 -msgid "There was an error resetting values. Returned status:" -msgstr "" - -#: client/src/configuration/forms/system-form/configuration-system.controller.js:217 -msgid "There was an error testing the log aggregator. Returned status:" -msgstr "" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:29 -msgid "These are the modules that {{BRAND_NAME}} supports running commands against." -msgstr "" - -#: client/features/templates/templates.strings.js:95 -msgid "This Job Template has a credential that requires a password. Credentials requiring passwords on launch are not permitted on workflow nodes." -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:59 -msgid "This Job Template has a default credential that requires a password before launch. Adding or editing schedules is prohibited while this credential is selected. To add or edit a schedule, credentials that require a password must be removed from the Job Template." -msgstr "" - -#: client/features/templates/templates.strings.js:94 -msgid "This Job Template is missing a default inventory or project. This must be addressed in the Job Template form before this node can be saved." -msgstr "" - -#: client/src/credential-types/credential-types.strings.js:8 -msgid "This credential type is currently being used by one or more credentials. Credentials that use this credential type must be deleted before the credential type can be deleted." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:24 -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:25 -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:26 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:24 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:25 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:26 -msgid "This group contains" -msgstr "" - -#: client/src/templates/prompt/steps/inventory/prompt-inventory.directive.js:50 -#: client/src/templates/workflows.form.js:74 -msgid "This inventory is applied to all job template nodes that prompt for an inventory." -msgstr "" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:168 -msgid "This is not a valid number." -msgstr "" - -#: client/src/credentials/factories/become-method-change.factory.js:59 -#: client/src/credentials/factories/kind-change.factory.js:116 -msgid "This is the tenant name. This value is usually the same as the username." -msgstr "" - -#: client/features/templates/templates.strings.js:64 -msgid "This job template has a default {{typeLabel}} credential which must be included or replaced before proceeding." -msgstr "" - -#: client/src/organizations/linkout/organizations-linkout.route.js:158 -msgid "This list is populated by inventories added from the" -msgstr "" - -#: client/src/notifications/notifications.list.js:21 -msgid "This list is populated by notification templates added from the %sNotifications%s section" -msgstr "" - -#: client/src/organizations/linkout/organizations-linkout.route.js:209 -msgid "This list is populated by projects added from the" -msgstr "" - -#: client/src/organizations/linkout/organizations-linkout.route.js:111 -msgid "This list is populated by teams added from the" -msgstr "" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:4 -msgid "This machine has not checked in with Insights in {{last_check_in}} hours" -msgstr "" - -#: client/src/shared/form-generator.js:753 -msgid "This setting has been set manually in a settings file and is now disabled." -msgstr "" - -#: client/src/users/users.form.js:166 -msgid "This user is not a member of any teams" -msgstr "" - -#: client/src/shared/form-generator.js:863 -#: client/src/shared/form-generator.js:958 -msgid "This value does not match the password you entered previously. Please confirm that password." -msgstr "" - -#: client/src/configuration/forms/settings-form.controller.js:444 -msgid "This will reset all configuration values to their factory defaults. Are you sure you want to proceed?" -msgstr "" - -#: client/src/templates/prompt/steps/inventory/prompt-inventory.directive.js:51 -msgid "This workflow job template has a default inventory which must be included or replaced before proceeding." -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:40 -msgid "Thu" -msgstr "" - -#: client/src/activity-stream/streams.list.js:25 -#: client/src/home/dashboard/lists/jobs/jobs-list.partial.html:14 -#: client/src/notifications/notification-templates-list/list.controller.js:72 -msgid "Time" -msgstr "" - -#: client/src/license/license.partial.html:45 -msgid "Time Remaining" -msgstr "" - -#: client/src/projects/projects.form.js:197 -msgid "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." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:411 -msgid "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." -msgstr "" - -#: client/src/credentials/credentials.form.js:125 -msgid "To learn more about the IAM STS Token, refer to the %sAmazon documentation%s." -msgstr "" - -#: client/src/shared/form-generator.js:888 -msgid "Toggle the display of plaintext." -msgstr "" - -#: client/src/notifications/shared/type-change.service.js:36 -#: client/src/notifications/shared/type-change.service.js:42 -msgid "Token" -msgstr "" - -#: client/features/applications/applications.strings.js:16 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:25 -#: client/src/users/users.form.js:235 -msgid "Tokens" -msgstr "" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:10 -msgid "Total Issues" -msgstr "" - -#: client/src/instance-groups/instance-groups.strings.js:19 -#: client/src/workflow-results/workflow-results.controller.js:76 -msgid "Total Jobs" -msgstr "" - -#: client/src/partials/logviewer.html:6 -msgid "Traceback" -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:38 -msgid "Tue" -msgstr "" - -#: client/src/credentials/credentials.form.js:60 -#: client/src/credentials/credentials.form.js:84 -#: client/src/inventories-hosts/inventories/inventory.list.js:56 -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:41 -#: client/src/notifications/notificationTemplates.form.js:54 -#: client/src/notifications/notificationTemplates.list.js:39 -#: client/src/notifications/notifications.list.js:32 -#: client/src/projects/projects.list.js:44 -#: client/src/scheduler/scheduled-jobs.list.js:42 -#: client/src/teams/teams.form.js:133 -#: client/src/templates/templates.list.js:31 -#: client/src/users/users.form.js:202 -msgid "Type" -msgstr "" - -#: client/features/credentials/credentials.strings.js:18 -#: client/src/credentials/credentials.form.js:23 -#: client/src/notifications/notificationTemplates.form.js:26 -msgid "Type Details" -msgstr "" - -#: client/src/projects/add/projects-add.controller.js:181 -#: client/src/projects/edit/projects-edit.controller.js:313 -msgid "URL popover text" -msgstr "" - -#: client/src/login/loginModal/loginModal.partial.html:55 -msgid "USERNAME" -msgstr "" - -#: client/src/activity-stream/get-target-title.factory.js:20 -#: client/src/organizations/linkout/organizations-linkout.route.js:43 -#: client/src/organizations/list/organizations-list.controller.js:55 -#: client/src/users/users.list.js:18 -#: client/src/users/users.list.js:19 -#: client/src/users/users.route.js:8 -msgid "USERS" -msgstr "" - -#: client/lib/components/components.strings.js:24 -msgid "Unable to Submit" -msgstr "" - -#: client/features/templates/templates.strings.js:85 -msgid "Unable to copy template." -msgstr "" - -#: client/src/instance-groups/instance-groups.strings.js:59 -msgid "Unable to delete instance group." -msgstr "" - -#: client/features/templates/templates.strings.js:81 -msgid "Unable to delete template." -msgstr "" - -#: client/features/templates/templates.strings.js:83 -msgid "Unable to determine template type." -msgstr "" - -#: client/features/templates/templates.strings.js:70 -msgid "Unable to determine this template's type while copying." -msgstr "" - -#: client/features/templates/templates.strings.js:71 -msgid "Unable to determine this template's type while deleting." -msgstr "" - -#: client/features/templates/templates.strings.js:72 -msgid "Unable to determine this template's type while editing." -msgstr "" - -#: client/features/templates/templates.strings.js:73 -msgid "Unable to determine this template's type while launching." -msgstr "" - -#: client/features/templates/templates.strings.js:74 -msgid "Unable to determine this template's type while scheduling." -msgstr "" - -#: client/features/templates/templates.strings.js:80 -msgid "Unable to edit template." -msgstr "" - -#: client/src/shared/stateDefinitions.factory.js:231 -msgid "Unable to get resource:" -msgstr "" - -#: client/features/templates/templates.strings.js:82 -msgid "Unable to launch template." -msgstr "" - -#: client/features/templates/templates.strings.js:84 -msgid "Unable to schedule job." -msgstr "" - -#: client/src/instance-groups/instance-groups.strings.js:41 -msgid "Unavailable" -msgstr "" - -#: client/src/instance-groups/instance-groups.strings.js:40 -msgid "Unavailable to run jobs." -msgstr "" - -#: client/lib/components/components.strings.js:26 -msgid "Unexpected Error" -msgstr "" - -#: client/lib/components/components.strings.js:25 -msgid "Unexpected server error. View the console for more information" -msgstr "" - -#: client/lib/components/components.strings.js:38 -msgid "Unsupported display model type" -msgstr "" - -#: client/lib/components/components.strings.js:30 -msgid "Unsupported input type" -msgstr "" - -#: client/features/projects/projects.strings.js:31 -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:277 -msgid "Update Not Found" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:345 -msgid "Update Options" -msgstr "" - -#: client/src/projects/projects.form.js:178 -msgid "Update Revision on Launch" -msgstr "" - -#: client/features/projects/projects.strings.js:42 -#: client/src/projects/factories/get-project-tool-tip.factory.js:30 -msgid "Update canceled. Click for details" -msgstr "" - -#: client/features/projects/projects.strings.js:40 -#: client/src/projects/factories/get-project-tool-tip.factory.js:24 -msgid "Update failed. Click for details" -msgstr "" - -#: client/src/projects/edit/projects-edit.controller.js:338 -msgid "Update in Progress" -msgstr "" - -#: client/features/projects/projects.strings.js:41 -#: client/src/projects/factories/get-project-tool-tip.factory.js:27 -msgid "Update missing. Click for details" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:376 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:381 -msgid "Update on Launch" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:394 -msgid "Update on Project Update" -msgstr "" - -#: client/features/projects/projects.strings.js:37 -#: client/src/projects/factories/get-project-tool-tip.factory.js:14 -msgid "Update queued. Click for details" -msgstr "" - -#: client/features/projects/projects.strings.js:38 -#: client/src/projects/factories/get-project-tool-tip.factory.js:18 -msgid "Update running. Click for details" -msgstr "" - -#: client/features/projects/projects.strings.js:39 -#: client/src/projects/factories/get-project-tool-tip.factory.js:21 -msgid "Update succeeded. Click for details" -msgstr "" - -#: client/src/license/license.partial.html:71 -msgid "Upgrade" -msgstr "" - -#: client/src/organizations/organizations.form.js:48 -#: client/src/projects/projects.form.js:209 -#: client/src/templates/job_templates/job-template.form.js:241 -msgid "Use Default Environment" -msgstr "" - -#: client/src/templates/job_templates/job-template.form.js:327 -#: client/src/templates/job_templates/job-template.form.js:332 -msgid "Use Fact Cache" -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:466 -msgid "Use SSL" -msgstr "" - -#: client/src/notifications/notificationTemplates.form.js:461 -msgid "Use TLS" -msgstr "" - -#: client/src/instance-groups/instance-groups.strings.js:20 -#: client/src/instance-groups/instance-groups.strings.js:42 -msgid "Used Capacity" -msgstr "" - -#: client/src/credentials/credentials.form.js:76 -msgid "Used to check out and synchronize playbook repositories with a remote source control management system such as Git, Subversion (svn), or Mercurial (hg). These credentials are used by Projects." -msgstr "" - -#: client/features/credentials/legacy.credentials.js:80 -#: client/src/credentials/credentials.form.js:457 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:125 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:127 -#: client/src/organizations/organizations.form.js:104 -#: client/src/organizations/organizations.form.js:143 -#: client/src/projects/projects.form.js:265 -#: client/src/teams/teams.form.js:96 -#: client/src/templates/workflows.form.js:183 -msgid "User" -msgstr "" - -#: client/src/users/users.form.js:97 -msgid "User Type" -msgstr "" - -#: client/src/access/rbac-multiselect/permissionsUsers.list.js:36 -#: client/src/credentials/factories/become-method-change.factory.js:17 -#: client/src/credentials/factories/become-method-change.factory.js:38 -#: client/src/credentials/factories/kind-change.factory.js:17 -#: client/src/credentials/factories/kind-change.factory.js:41 -#: client/src/credentials/factories/kind-change.factory.js:74 -#: client/src/credentials/factories/kind-change.factory.js:95 -#: client/src/notifications/notificationTemplates.form.js:348 -#: client/src/notifications/notificationTemplates.form.js:387 -#: client/src/notifications/notificationTemplates.form.js:64 -#: client/src/users/users.form.js:60 -#: client/src/users/users.list.js:29 -msgid "Username" -msgstr "" - -#: client/src/credentials/credentials.form.js:80 -msgid "Usernames, passwords, and access keys for authenticating to the specified cloud or infrastructure provider. These are used for smart inventory sources and for cloud provisioning and deployment in playbook runs." -msgstr "" - -#: client/lib/components/components.strings.js:78 -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:35 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:38 -#: client/src/organizations/organizations.form.js:86 -#: client/src/teams/teams.form.js:78 -msgid "Users" -msgstr "" - -#: client/src/scheduler/schedulerList.controller.js:46 -msgid "Using a credential that requires a password on launch is prohibited when creating a Job Template schedule" -msgstr "" - -#: client/lib/components/code-mirror/code-mirror.strings.js:9 -msgid "VARIABLES" -msgstr "" - -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:7 -#: client/src/home/dashboard/lists/jobs/jobs-list.partial.html:7 -msgid "VIEW ALL" -msgstr "" - -#: client/lib/components/components.strings.js:57 -msgid "VIEW LESS" -msgstr "" - -#: client/lib/components/components.strings.js:56 -msgid "VIEW MORE" -msgstr "" - -#: client/src/shared/paginate/paginate.partial.html:48 -msgid "VIEW PER PAGE" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:234 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:248 -msgid "VPC ID:" -msgstr "" - -#: client/src/license/license.partial.html:10 -msgid "Valid License" -msgstr "" - -#: client/src/inventories-hosts/hosts/host.form.js:68 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:46 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js:47 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:67 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:67 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:63 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:71 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:77 -msgid "Variables" -msgstr "" - -#: client/src/job-submission/job-submission.partial.html:364 -msgid "Vault" -msgstr "" - -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js:25 -msgid "Vault ID" -msgstr "" - -#: client/features/templates/templates.strings.js:45 -#: client/src/credentials/credentials.form.js:391 -#: client/src/job-submission/job-submission.partial.html:146 -msgid "Vault Password" -msgstr "" - -#: client/features/output/output.strings.js:80 -#: client/features/templates/templates.strings.js:52 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:82 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:91 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:331 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:338 -#: client/src/job-submission/job-submission.partial.html:183 -#: client/src/templates/job_templates/job-template.form.js:173 -#: client/src/templates/job_templates/job-template.form.js:180 -msgid "Verbosity" -msgstr "" - -#: client/src/license/license.partial.html:15 -msgid "Version" -msgstr "" - -#: client/src/activity-stream/streams.list.js:63 -#: client/src/credential-types/credential-types.list.js:64 -#: client/src/credentials/credentials.list.js:82 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:58 -#: client/src/inventories-hosts/inventories/inventory.list.js:114 -#: client/src/inventory-scripts/inventory-scripts.list.js:70 -#: client/src/notifications/notificationTemplates.list.js:91 -#: client/src/scheduler/schedules.list.js:93 -#: client/src/teams/teams.list.js:64 -#: client/src/templates/templates.list.js:101 -#: client/src/users/users.list.js:70 -msgid "View" -msgstr "" - -#: client/src/bread-crumb/bread-crumb.directive.js:41 -msgid "View Activity Stream" -msgstr "" - -#: client/lib/components/components.strings.js:66 -msgid "View Documentation" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:86 -#: client/src/inventories-hosts/inventory-hosts.strings.js:27 -msgid "View Insights Data" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:202 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:226 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:250 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:325 -msgid "View JSON examples at" -msgstr "" - -#: client/src/inventories-hosts/hosts/host.form.js:78 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:77 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:77 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:73 -msgid "View JSON examples at %s" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.partial.html:13 -msgid "View Less" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.partial.html:11 -msgid "View More" -msgstr "" - -#: client/features/output/output.strings.js:28 -msgid "View Project checkout results" -msgstr "" - -#: client/src/shared/form-generator.js:1739 -#: client/src/templates/job_templates/job-template.form.js:472 -#: client/src/templates/workflows.form.js:221 -msgid "View Survey" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:203 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:227 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:251 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:326 -msgid "View YAML examples at" -msgstr "" - -#: client/src/inventories-hosts/hosts/host.form.js:79 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:78 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:78 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:74 -msgid "View YAML examples at %s" -msgstr "" - -#: client/src/credentials/credentials.list.js:84 -msgid "View credential" -msgstr "" - -#: client/src/credential-types/credential-types.list.js:66 -msgid "View credential type" -msgstr "" - -#: client/src/activity-stream/streams.list.js:67 -msgid "View event details" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:93 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:103 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:91 -msgid "View group" -msgstr "" - -#: client/src/inventories-hosts/hosts/host.list.js:89 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:79 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:93 -#: client/src/inventories-hosts/inventory-hosts.strings.js:26 -msgid "View host" -msgstr "" - -#: client/src/inventories-hosts/inventories/inventory.list.js:116 -msgid "View inventory" -msgstr "" - -#: client/src/inventory-scripts/inventory-scripts.list.js:72 -msgid "View inventory script" -msgstr "" - -#: client/src/notifications/notificationTemplates.list.js:93 -msgid "View notification" -msgstr "" - -#: client/src/scheduler/schedules.list.js:95 -msgid "View schedule" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:110 -msgid "View source" -msgstr "" - -#: client/src/teams/teams.list.js:67 -msgid "View team" -msgstr "" - -#: client/src/templates/templates.list.js:103 -msgid "View template" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:246 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:261 -msgid "View the" -msgstr "" - -#: client/features/output/output.strings.js:21 -#: client/lib/components/components.strings.js:61 -msgid "View the Credential" -msgstr "" - -#: client/features/output/output.strings.js:24 -msgid "View the Inventory" -msgstr "" - -#: client/features/output/output.strings.js:25 -msgid "View the Job Template" -msgstr "" - -#: client/features/output/output.strings.js:27 -msgid "View the Project" -msgstr "" - -#: client/features/output/output.strings.js:29 -msgid "View the Schedule" -msgstr "" - -#: client/features/output/output.strings.js:31 -msgid "View the User" -msgstr "" - -#: client/src/projects/projects.list.js:109 -msgid "View the project" -msgstr "" - -#: client/src/scheduler/scheduled-jobs.list.js:74 -msgid "View the schedule" -msgstr "" - -#: client/features/output/output.strings.js:30 -#: client/src/workflow-results/workflow-results.controller.js:52 -msgid "View the source Workflow Job" -msgstr "" - -#: client/src/users/users.list.js:73 -msgid "View user" -msgstr "" - -#: client/lib/components/components.strings.js:89 -msgid "Views" -msgstr "" - -#: client/src/templates/main.js:746 -#: client/src/templates/workflows.form.js:20 -msgid "WORKFLOW" -msgstr "" - -#: client/features/templates/templates.strings.js:120 -msgid "WORKFLOW VISUALIZER" -msgstr "" - -#: client/features/templates/templates.strings.js:107 -#: client/src/scheduler/scheduler.strings.js:58 -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:214 -msgid "Warning" -msgstr "" - -#: client/src/configuration/forms/auth-form/configuration-auth.controller.js:52 -#: client/src/configuration/forms/settings-form.controller.js:400 -#: client/src/configuration/forms/system-form/configuration-system.controller.js:50 -msgid "Warning: Unsaved Changes" -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:39 -msgid "Wed" -msgstr "" - -#: client/src/license/license.partial.html:78 -msgid "Welcome to Ansible Tower! Please complete the steps below to acquire a license." -msgstr "" - -#: client/src/login/loginModal/loginModal.partial.html:17 -msgid "Welcome to Ansible {{BRAND_NAME}}!  Please sign in." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:368 -msgid "When not checked, a merge will be performed, combining local variables with those found on the external source." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:356 -msgid "When not checked, local child hosts and groups not found on the external source will remain untouched by the inventory update process." -msgstr "" - -#: client/features/templates/templates.strings.js:106 -#: client/src/workflow-results/workflow-results.controller.js:85 -msgid "Workflow" -msgstr "" - -#: client/features/jobs/jobs.strings.js:11 -msgid "Workflow Job" -msgstr "" - -#: client/lib/models/models.strings.js:49 -msgid "Workflow Job Template Nodes" -msgstr "" - -#: client/lib/models/models.strings.js:45 -msgid "Workflow Job Templates" -msgstr "" - -#: client/features/templates/templates.strings.js:14 -#: client/src/templates/templates.list.js:66 -msgid "Workflow Template" -msgstr "" - -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:108 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:41 -msgid "Workflow Templates" -msgstr "" - -#: client/src/shared/form-generator.js:1743 -#: client/src/templates/workflows.form.js:247 -msgid "Workflow Visualizer" -msgstr "" - -#: client/features/users/tokens/tokens.strings.js:31 -msgid "Write" -msgstr "" - -#: client/lib/components/code-mirror/code-mirror.strings.js:11 -#: client/lib/services/base-string.service.js:70 -#: client/src/job-submission/job-submission.partial.html:171 -msgid "YAML" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:200 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:224 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:248 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:323 -msgid "YAML:" -msgstr "" - -#: client/lib/services/base-string.service.js:74 -msgid "YES" -msgstr "" - -#: client/src/notifications/add/add.controller.js:83 -#: client/src/notifications/edit/edit.controller.js:130 -msgid "Yellow" -msgstr "" - -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:53 -msgid "You can create a job template here." -msgstr "" - -#: client/features/templates/templates.strings.js:90 -msgid "You do not have access to all resources used by this workflow. Resources that you don't have access to will not be copied and will result in an incomplete workflow." -msgstr "" - -#: client/src/projects/edit/projects-edit.controller.js:64 -msgid "You do not have access to view this property" -msgstr "" - -#: client/src/projects/add/projects-add.controller.js:35 -msgid "You do not have permission to add a project." -msgstr "" - -#: client/src/users/add/users-add.controller.js:44 -msgid "You do not have permission to add a user." -msgstr "" - -#: client/src/access/rbac-multiselect/rbac-multiselect-list.directive.js:175 -msgid "You do not have permission to manage this user" -msgstr "" - -#: client/features/templates/templates.strings.js:69 -msgid "You do not have permission to perform this action." -msgstr "" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:41 -msgid "You do not have sufficient permissions to edit the host filter." -msgstr "" - -#: client/src/login/loginModal/loginModal.partial.html:28 -msgid "You have been logged out. Please sign in." -msgstr "" - -#: client/src/configuration/forms/auth-form/configuration-auth.controller.js:51 -#: client/src/configuration/forms/settings-form.controller.js:399 -#: client/src/configuration/forms/system-form/configuration-system.controller.js:49 -msgid "You have unsaved changes. Would you like to proceed without saving?" -msgstr "" - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:204 -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:207 -msgid "You must run a successful update before you can select a playbook. You will not be able to save this Job Template without a valid playbook." -msgstr "" - -#: client/features/projects/projects.strings.js:19 -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:231 -msgid "Your request to cancel the update was submitted to the task manager." -msgstr "" - -#: client/src/login/loginModal/loginModal.partial.html:22 -msgid "Your session timed out due to inactivity. Please sign in." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:24 -#: client/src/job-submission/job-submission.partial.html:317 -#: client/src/shared/form-generator.js:1213 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:42 -msgid "and" -msgstr "" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:252 -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:277 -msgid "button to view the latest status." -msgstr "" - -#: client/features/users/tokens/tokens.strings.js:27 -msgid "by" -msgstr "" - -#: client/src/job-submission/job-submission.partial.html:289 -#: client/src/job-submission/job-submission.partial.html:294 -#: client/src/job-submission/job-submission.partial.html:305 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:14 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:19 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:30 -msgid "characters long." -msgstr "" - -#: client/features/output/output.strings.js:87 -#: client/src/shared/smart-search/smart-search.partial.html:50 -msgid "documentation" -msgstr "" - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:204 -msgid "failed" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:247 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:262 -msgid "for a complete list of supported filters." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.directive.js:82 -msgid "from the" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.directive.js:82 -#: client/src/inventories-hosts/inventory-hosts.strings.js:8 -msgid "group" -msgid_plural "groups" -msgstr[0] "" -msgstr[1] "" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:26 -msgid "groups" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:24 -msgid "groups and" -msgstr "" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:9 -msgid "host" -msgid_plural "hosts" -msgstr[0] "" -msgstr[1] "" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:24 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:25 -msgid "hosts" -msgstr "" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:65 -msgid "hosts with failures. Click for details." -msgstr "" - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:210 -msgid "missing" -msgstr "" - -#: client/src/access/rbac-multiselect/permissionsTeams.list.js:21 -msgid "name" -msgstr "" - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:207 -msgid "never updated" -msgstr "" - -#: client/src/shared/paginate/paginate.partial.html:34 -msgid "of" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:239 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:254 -msgid "of the filters match." -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:34 -msgid "on" -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:31 -msgid "on day" -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:35 -msgid "on days" -msgstr "" - -#: client/src/scheduler/scheduler.strings.js:33 -msgid "on the" -msgstr "" - -#: client/src/access/rbac-multiselect/permissionsTeams.list.js:24 -msgid "organization" -msgstr "" - -#: client/src/shared/form-generator.js:1085 -msgid "playbook" -msgstr "" - -#: client/src/organizations/linkout/organizations-linkout.route.js:111 -#: client/src/organizations/linkout/organizations-linkout.route.js:158 -#: client/src/organizations/linkout/organizations-linkout.route.js:209 -msgid "section" -msgstr "" - -#: client/src/credentials/credentials.form.js:138 -#: client/src/credentials/credentials.form.js:364 -msgid "set in helpers/credentials" -msgstr "" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:48 -msgid "sources with sync failures. Click for details" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:244 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:259 -msgid "test" -msgstr "" - -#: client/src/job-submission/job-submission.partial.html:289 -#: client/src/job-submission/job-submission.partial.html:294 -#: client/src/job-submission/job-submission.partial.html:305 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:14 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:19 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:30 -msgid "to" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:139 -msgid "to include all regions. Only Hosts associated with the selected regions will be updated." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:17 -msgid "to start it now." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:25 -msgid "to update." -msgstr "" - -#: client/src/credentials/credentials.form.js:381 -msgid "v2 URLs%s - leave blank" -msgstr "" - -#: client/src/credentials/credentials.form.js:382 -msgid "v3 default%s - set to 'default'" -msgstr "" - -#: client/src/credentials/credentials.form.js:383 -msgid "v3 multi-domain%s - your domain name" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:319 -msgid "view azure_rm.ini in the Ansible github repo." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:220 -msgid "view ec2.ini in the Ansible github repo." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:244 -msgid "view vmware_inventory.ini in the Ansible github repo." -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:239 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:254 -msgid "when" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:225 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:239 -msgid "will create group names similar to the following examples based on the options selected:" -msgstr "" - -#: client/src/inventories-hosts/inventories/related/groups/factories/get-hosts-status-msg.factory.js:11 -msgid "with failed jobs." -msgstr "" - -#: client/features/users/tokens/tokens.strings.js:42 -msgid "{{ appName }} Token" -msgstr "" - -#: client/lib/services/base-string.service.js:103 -msgid "{{ header }} {{ body }}" -msgstr "" - -#: client/lib/services/base-string.service.js:76 -msgid "{{ resource }} successfully created" -msgstr "" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:31 -msgid "{{ str1 }}

{{ str2 }}

" -msgstr "" - -#: client/lib/components/cards/card.partial.html:2 -msgid "{{ title }}" -msgstr "" - -#: client/src/configuration/forms/settings-form.partial.html:10 -#: client/src/configuration/forms/settings-form.route.js:15 -msgid "{{ vm.getCurrentFormTitle() }}" -msgstr "" - -#: client/src/templates/prompt/steps/other-prompts/prompt-other-prompts.partial.html:5 -msgid "{{:: vm.strings.get('prompt.JOB_TYPE') }}" -msgstr "" - -#: client/lib/components/input/label.partial.html:5 -msgid "{{::state._hint}}" -msgstr "" - -#: client/src/configuration/forms/auth-form/configuration-auth.partial.html:6 -#: client/src/configuration/forms/system-form/configuration-system.partial.html:5 -msgid "{{opt.label}}" -msgstr "" - -#: client/src/shared/paginate/paginate.partial.html:55 -msgid "{{pageSize}}" -msgstr "" diff --git a/awx/ui/po/es.po b/awx/ui/po/es.po deleted file mode 100644 index f4a4d06aea48..000000000000 --- a/awx/ui/po/es.po +++ /dev/null @@ -1,7021 +0,0 @@ -# Froebel Flores , 2017. #zanata -# edrh01 , 2017. #zanata -# mkim , 2017. #zanata -# plocatelli , 2017. #zanata -# trh01 , 2017. #zanata -# trrh02 , 2017. #zanata -# edrh01 , 2018. #zanata -# trh01 , 2018. #zanata -msgid "" -msgstr "" -"Project-Id-Version: \n" -"PO-Revision-Date: 2018-08-16 04:29+0000\n" -"Last-Translator: trh01 \n" -"Language-Team: \n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: es\n" -"Plural-Forms: nplurals=2; plural=(n != 1)\n" -"X-Generator: Zanata 4.6.0\n" - -#: client/src/projects/add/projects-add.controller.js:162 -#: client/src/projects/edit/projects-edit.controller.js:297 -msgid "" -"%sNote:%s Mercurial does not support password authentication for SSH. Do not" -" put the username and key in the URL. If using Bitbucket and SSH, do not " -"supply your Bitbucket username." -msgstr "" -"%sNota%s: Mercurial no soporta autenticación utilizando contraseña. No " -"introduzca el usuario y la clave en la dirección URL. Si utiliza Bitbucket y" -" SSH, no indique su usuario de Bitbucket." - -#: client/src/projects/add/projects-add.controller.js:141 -#: client/src/projects/edit/projects-edit.controller.js:276 -msgid "" -"%sNote:%s 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." -msgstr "" -"%sNota%s: Si usted utiliza el protocolo SSH para GitHub o Bitbucker, " -"introduzca sólo la clave de SSH, no introduzca el usuario (otro distinto a " -"git). Además, GitHub y Bitbucket no soporta autenticación utilizando " -"contraseña cuando se utiliza SSH. El protocolo de sólo lectura GIT (git://) " -"no utiliza usuario y contraseña." - -#: client/src/credentials/credentials.form.js:287 -msgid "(defaults to %s)" -msgstr "(valor por defecto a %s)" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:402 -msgid "(seconds)" -msgstr "(segundos)" - -#: client/src/shared/paginate/paginate.partial.html:66 -msgid "100" -msgstr "100" - -#: client/src/shared/paginate/paginate.partial.html:60 -msgid "20" -msgstr "20" - -#: client/src/shared/paginate/paginate.partial.html:63 -msgid "50" -msgstr "50" - -#: client/lib/components/code-mirror/code-mirror.strings.js:17 -msgid "" -"

\n" -" Enter inventory variables using either JSON or YAML\n" -" syntax. Use the radio button to toggle between the two.\n" -"

\n" -" JSON:\n" -"
\n" -"
\n" -" {\n" -"
\"somevar\": \"somevalue\",\n" -"
\"password\": \"magic\"\n" -"
\n" -" }\n" -"
\n" -" YAML:\n" -"
\n" -"
\n" -" ---\n" -"
somevar: somevalue\n" -"
password: magic\n" -"
\n" -"
\n" -"

\n" -" View JSON examples at\n" -" www.json.org\n" -"

\n" -"

\n" -" View YAML examples at\n" -" \n" -" docs.ansible.com\n" -"

" -msgstr "" -"

\n" -" Ingrese las variables del inventario usando la sintaxis JSON o YAML. Utilice el botón de radio para alternar entre los dos.\n" -"

\n" -" JSON:\n" -"
\n" -"
\n" -" {\n" -"
\"somevar\": \"somevalue\",\n" -"
\"password\": \"magic\"\n" -"
\n" -" }\n" -"
\n" -" YAML:\n" -"
\n" -"
\n" -" ---\n" -"
somevar: somevalue\n" -"
password: magic\n" -"
\n" -"
\n" -"

\n" -" Vea los ejemplos de JSON en\n" -" www.json.org\n" -"

\n" -"

\n" -" Vea los ejemplos de YAML en\n" -" \n" -" docs.ansible.com\n" -"

" - -#: client/features/templates/templates.strings.js:55 -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.

JSON:
{
"somevar": "somevalue",
"password": " -""magic"
}
YAML:
---
somevar: somevalue
password: magic
" -msgstr "" -"

Traslade variables de línea de comando adicionales al manual. Este es el " -"parámetro de línea de comando -e o --extra vars para el manual de Ansible. " -"Proporcione claves/pares de valores utilizando YAML o JSON. Consulte la " -"documentación de Ansible Tower para acceder a ejemplos de " -"sintaxis.

JSON:
{
"somevar": " -""somevalue",
"password": "magic"
" -"}
YAML:
---
somevar: somevalue
password: magic
" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:33 -#: client/src/scheduler/scheduler.strings.js:22 -msgid "A schedule name is required." -msgstr "Un nombre para el planificador es necesario." - -#: client/src/users/add/users-add.controller.js:103 -msgid "A value is required" -msgstr "Un valor es necesario" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:167 -msgid "A value is required." -msgstr "Un valor es necesario." - -#: client/src/about/about.route.js:10 -msgid "ABOUT" -msgstr "ACERCA DE" - -#: client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html:16 -msgid "ACTION" -msgstr "ACCIÓN" - -#: client/src/activity-stream/activity-detail.form.js:23 -msgid "ACTIVITY DETAIL" -msgstr "DETALLES DE ACTIVIDAD" - -#: client/src/activity-stream/activitystream.route.js:28 -#: client/src/activity-stream/streams.list.js:14 -#: client/src/activity-stream/streams.list.js:15 -msgid "ACTIVITY STREAM" -msgstr "FLUJO DE ACTIVIDAD" - -#: client/src/organizations/linkout/addUsers/addUsers.partial.html:8 -msgid "ADD" -msgstr "AÑADIR" - -#: client/src/notifications/notification-templates-list/add-notifications-action.partial.html:3 -msgid "ADD A NEW TEMPLATE" -msgstr "AÑADIR UNA NUEVA PLANTILLA" - -#: client/features/templates/templates.strings.js:107 -msgid "ADD A TEMPLATE" -msgstr "AÑADIR UNA PLANTILLA" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:16 -msgid "ADD SURVEY PROMPT" -msgstr "AGREGAR AVISO DE ENCUESTA" - -#: client/src/shared/smart-search/smart-search.partial.html:48 -msgid "ADDITIONAL INFORMATION" -msgstr "INFORMACIÓN ADICIONAL" - -#: client/features/output/output.strings.js:76 -msgid "ADDITIONAL_INFORMATION" -msgstr "ADDITIONAL_INFORMATION" - -#: client/src/organizations/linkout/organizations-linkout.route.js:258 -#: client/src/organizations/list/organizations-list.controller.js:85 -msgid "ADMINS" -msgstr "ADMINS" - -#: client/src/activity-stream/get-target-title.factory.js:4 -msgid "ALL ACTIVITY" -msgstr "TODA LA ACTIVIDAD" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:239 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:254 -msgid "ANY" -msgstr "CUALQUIERA" - -#: client/src/credentials/credentials.form.js:198 -msgid "API Key" -msgstr "Clave API" - -#: client/src/notifications/notificationTemplates.form.js:243 -msgid "API Service/Integration Key" -msgstr "Servicio API/Clave de integración" - -#: client/src/notifications/shared/type-change.service.js:60 -msgid "API Token" -msgstr "Token API" - -#: client/features/users/tokens/tokens.strings.js:40 -msgid "APPLICATION" -msgstr "APLICACIÓN" - -#: client/features/applications/applications.strings.js:28 -#: client/features/applications/applications.strings.js:8 -#: client/src/activity-stream/get-target-title.factory.js:47 -msgid "APPLICATIONS" -msgstr "APLICACIONES" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.route.js:19 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.route.js:19 -msgid "ASSOCIATED GROUPS" -msgstr "GRUPOS ASOCIADOS" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.route.js:19 -msgid "ASSOCIATED HOSTS" -msgstr "HOSTS ASOCIADOS" - -#: client/lib/components/components.strings.js:87 -msgid "About" -msgstr "Acerca de" - -#: client/lib/components/components.strings.js:91 -msgid "Access" -msgstr "Acceso" - -#: client/src/credentials/credentials.form.js:91 -msgid "Access Key" -msgstr "Clave de acceso" - -#: client/src/notifications/notificationTemplates.form.js:221 -msgid "Account SID" -msgstr "Cuenta SID" - -#: client/src/notifications/notificationTemplates.form.js:180 -msgid "Account Token" -msgstr "Cuenta Token" - -#: client/src/activity-stream/activity-detail.form.js:36 -msgid "Action" -msgstr "Acción" - -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:20 -#: client/src/inventories-hosts/hosts/hosts.partial.html:47 -#: client/src/shared/list-generator/list-generator.factory.js:591 -msgid "Actions" -msgstr "Acciones" - -#: client/features/templates/templates.strings.js:15 -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:17 -#: client/src/templates/templates.list.js:36 -msgid "Activity" -msgstr "Actividad" - -#: client/src/configuration/system-form/configuration-system.controller.js:88 -msgid "Activity Stream" -msgstr "Flujo de actividad" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:113 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:115 -#: client/src/organizations/organizations.form.js:93 -#: client/src/teams/teams.form.js:85 -#: client/src/templates/workflows.form.js:147 -msgid "Add" -msgstr "Añadir" - -#: client/src/credentials/credentials.list.js:14 -msgid "Add Credentials" -msgstr "Añadir credencial" - -#: client/src/inventories-hosts/inventories/inventory.list.js:13 -msgid "Add Inventories" -msgstr "Añadir inventarios" - -#: client/src/shared/stateDefinitions.factory.js:304 -msgid "Add Permissions" -msgstr "Añadir permisos" - -#: client/src/projects/projects.list.js:13 -msgid "Add Project" -msgstr "Añadir proyecto" - -#: client/src/shared/form-generator.js:1731 -#: client/src/templates/job_templates/job-template.form.js:468 -#: client/src/templates/workflows.form.js:205 -msgid "Add Survey" -msgstr "Añadir encuesta" - -#: client/src/teams/teams.list.js:13 -msgid "Add Team" -msgstr "Añadir equipo" - -#: client/src/teams/teams.form.js:86 -msgid "Add User" -msgstr "Añadir usuario" - -#: client/src/shared/stateDefinitions.factory.js:426 -#: client/src/shared/stateDefinitions.factory.js:594 -#: client/src/users/users.list.js:17 -msgid "Add Users" -msgstr "Añadir usuarios" - -#: client/src/organizations/organizations.form.js:94 -msgid "Add Users to this organization." -msgstr "Añadir usuarios a la organización." - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:69 -msgid "Add a group" -msgstr "Agregar un grupo" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:118 -msgid "Add a host" -msgstr "Agregar un host" - -#: client/src/scheduler/schedules.list.js:74 -msgid "Add a new schedule" -msgstr "Añadir un nuevo planificador" - -#: client/features/credentials/legacy.credentials.js:71 -#: client/src/credentials/credentials.form.js:448 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:115 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:117 -#: client/src/projects/projects.form.js:255 -#: client/src/templates/job_templates/job-template.form.js:411 -#: client/src/templates/workflows.form.js:148 -msgid "Add a permission" -msgstr "Añadir un permiso" - -#: client/src/shared/form-generator.js:1466 -msgid "Admin" -msgstr "Administrador" - -#: client/lib/components/components.strings.js:92 -msgid "Administration" -msgstr "Administración" - -#: client/src/organizations/linkout/organizations-linkout.route.js:281 -msgid "Admins" -msgstr "Administradores" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:391 -msgid "" -"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." -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 de trabajo. Esto está orientado a contenido estático, " -"como el formato de archivo .ini del inventario Ansible." - -#: client/lib/components/components.strings.js:99 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:37 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:43 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:65 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:74 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:139 -msgid "All" -msgstr "Todo" - -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:23 -msgid "All Activity" -msgstr "Todas las actividades" - -#: client/features/portalMode/index.view.html:33 -msgid "All Jobs" -msgstr "Todos los trabajos" - -#: client/src/templates/job_templates/job-template.form.js:290 -#: client/src/templates/job_templates/job-template.form.js:297 -msgid "Allow Provisioning Callbacks" -msgstr "Permitir la ejecución utilizando Callbacks" - -#: client/features/templates/templates.strings.js:102 -#: client/src/workflow-results/workflow-results.controller.js:66 -msgid "Always" -msgstr "Siempre" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:270 -msgid "An SCM update does not appear to be running for project:" -msgstr "" -"Parece que una actualización de SCM no se está ejecutando para el proyecto:" - -#: client/src/projects/list/projects-list.controller.js:311 -msgid "" -"An SCM update does not appear to be running for project: %s. Click the " -"%sRefresh%s button to view the latest status." -msgstr "" -"Una actualización de SCM no aparece estar siendo ejecutada para los " -"proyectos: %s. Pulse sobre el botón %sActualizar%s para ver el estado más " -"reciente." - -#: client/src/organizations/organizations.form.js:47 -#: client/src/organizations/organizations.form.js:52 -#: client/src/projects/projects.form.js:207 -#: client/src/projects/projects.form.js:212 -#: client/src/templates/job_templates/job-template.form.js:239 -#: client/src/templates/job_templates/job-template.form.js:245 -msgid "Ansible Environment" -msgstr "Entorno de Ansible" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:62 -#: client/src/templates/survey-maker/shared/question-definition.form.js:68 -msgid "Answer Type" -msgstr "Tipo de respuesta" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:44 -#: client/src/templates/survey-maker/shared/question-definition.form.js:53 -msgid "Answer Variable Name" -msgstr "Nombre de la variable de respuesta" - -#: client/lib/components/components.strings.js:85 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:24 -msgid "Applications" -msgstr "Aplicaciones" - -#: client/src/notifications/notification-templates-list/list.controller.js:228 -msgid "Are you sure you want to delete this notification template?" -msgstr "¿Está seguro de que quiere eliminar esta plantilla de notificación?" - -#: client/src/teams/list/teams-list.controller.js:80 -msgid "Are you sure you want to delete this team?" -msgstr "¿Está seguro de que desea eliminar este equipo?" - -#: client/src/users/list/users-list.controller.js:93 -msgid "Are you sure you want to delete this user?" -msgstr "¿Está seguro de que desea eliminar este usuario?" - -#: client/features/templates/templates.strings.js:98 -msgid "Are you sure you want to delete this workflow node?" -msgstr "¿Está seguro de que desea eliminar este nodo de flujo de trabajo?" - -#: client/lib/services/base-string.service.js:81 -msgid "Are you sure you want to delete this {{ resourceType }}?" -msgstr "¿Está seguro de que desea eliminar este {{ resourceType }}?" - -#: client/src/partials/survey-maker-modal.html:13 -msgid "Are you sure you want to delete this {{deleteMode}}?" -msgstr "¿Esta seguro de que desea eliminar este {{deleteMode}}?" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-disassociate.partial.html:25 -msgid "Are you sure you want to disassociate the group below from" -msgstr "¿Está seguro de que quiere desasociar el grupo indicado de" - -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.partial.html:23 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-disassociate.partial.html:25 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-disassociate.partial.html:26 -msgid "Are you sure you want to disassociate the host below from" -msgstr "¿Está seguro de que quiere desasociar el host indicado de" - -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:47 -msgid "" -"Are you sure you want to permanently delete the group below from the " -"inventory?" -msgstr "" -"¿Está seguro de que quiere eliminar permanentemente el grupo indicado del " -"inventario?" - -#: client/src/inventories-hosts/inventories/related/hosts/list/host-list.controller.js:106 -msgid "" -"Are you sure you want to permanently delete the host below from the " -"inventory?" -msgstr "" -"¿Está seguro de que quiere eliminar permanentemente el host indicado del " -"inventario?" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:69 -msgid "" -"Are you sure you want to permanently delete the inventory source below from " -"the inventory?" -msgstr "" -"¿Está seguro de que quiere eliminar permanentemente la fuente del inventario" -" del inventario?" - -#: client/src/projects/edit/projects-edit.controller.js:253 -msgid "Are you sure you want to remove the %s below from %s?" -msgstr "¿Está seguro de que quiere eliminar el %s indicado de %s?" - -#: client/lib/services/base-string.service.js:86 -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?" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:39 -msgid "Arguments" -msgstr "Argumentos" - -#: client/src/credentials/credentials.form.js:232 -#: client/src/credentials/credentials.form.js:271 -#: client/src/credentials/credentials.form.js:311 -#: client/src/credentials/credentials.form.js:397 -msgid "Ask at runtime?" -msgstr "¿Preguntar durante la ejecución?" - -#: client/src/instance-groups/instance-groups.strings.js:31 -msgid "Associate an existing Instance" -msgstr "Asociar una instancia existente" - -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:69 -msgid "Associate an existing group" -msgstr "Asociar un grupo existente" - -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.list.js:51 -msgid "Associate this host with a new group" -msgstr "Asociar este host a un nuevo grupo" - -#: client/src/shared/form-generator.js:1468 -msgid "Auditor" -msgstr "Auditor" - -#: client/src/configuration/configuration.partial.html:15 -msgid "Authentication" -msgstr "Autenticación " - -#: client/src/credentials/credentials.form.js:72 -msgid "" -"Authentication for network device access. This can include SSH keys, " -"usernames, passwords, and authorize information. Network credentials are " -"used when submitting jobs to run playbooks against network devices." -msgstr "" -"Autenticación para el acceso a dispositivos de red. Puede incluir claves " -"SSH, usuarios, contraseñas, e información de autorización. Las credenciales " -"de Red son utilizadas al lanzar trabajos que ejecutan playbooks sobre " -"dispositivos de red." - -#: client/src/credentials/credentials.form.js:68 -msgid "" -"Authentication for remote machine access. This can include SSH keys, " -"usernames, passwords, and sudo information. Machine credentials are used " -"when submitting jobs to run playbooks against remote hosts." -msgstr "" -"Autenticación para máquinas remotas. Puede incluir claves SSH, usuarios, " -"contraseñas e información de sudo. Las credenciales de máquina son " -"utilizadas al lanzar trabajos que ejecutan playbooks contra servidores " -"remotos." - -#: client/src/credentials/credentials.form.js:343 -msgid "Authorize" -msgstr "Autorizar" - -#: client/src/credentials/credentials.form.js:351 -msgid "Authorize Password" -msgstr "Contraseña de autorización" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:226 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:240 -msgid "Availability Zone:" -msgstr "Zona de disponibilidad:" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:152 -msgid "Azure AD" -msgstr "Azure AD" - -#: client/src/shared/directives.js:91 -msgid "BROWSE" -msgstr "NAVEGAR" - -#: client/features/output/output.strings.js:97 -msgid "Back to Top" -msgstr "Volver a la parte superior" - -#: client/src/projects/projects.form.js:81 -msgid "" -"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." -msgstr "" -"Directorio base utilizado para encontrar playbooks. Los directorios " -"encontrados dentro de este directorio serán listados en el desplegable " -"correspondiente a la lista de playbook. Junto a la ruta base y el el " -"directorio del playbook seleccionado se creará la ruta completa para " -"encontrar los playbooks." - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:129 -msgid "Become Privilege Escalation" -msgstr "Activar la escalada de privilegios" - -#: client/src/license/license.partial.html:107 -msgid "Browse" -msgstr "Navegar" - -#: client/src/license/license.partial.html:129 -msgid "" -"By default, Tower collects and transmits analytics data on Tower usage to Red Hat. This data is used to enhance future releases of the Tower Software and help streamline customer experience and success. For more information, see\n" -"\t\t\t\t\t\t\t\t\t\t\n" -"\t\t\t\t\t\t\t\t\t\t\t\tthis Tower documentation page\n" -"\t\t\t\t\t\t\t\t\t\t. Uncheck this box to disable this feature." -msgstr "" -"De manera predeterminada, Tower recopila y transmite datos analíticos sobre el uso de Tower a Red Hat. Estos datos se utilizan para perfeccionar futuras versiones del Tower Software y ayudar a optimizar la experiencia y el éxito del cliente. Para obtener más información, consulte\n" -"\t\t\t\t\t\t\t\t\t\t\n" -"\t\t\t\t\t\t\t\t\t\t\t\testa página de documentación Tower\n" -"\t\t\t\t\t\t\t\t\t\t. Desmarque esta casilla para deshabilitar esta función." - -#: client/lib/services/base-string.service.js:60 -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.partial.html:28 -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:51 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-disassociate.partial.html:29 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-disassociate.partial.html:29 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-disassociate.partial.html:30 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:73 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal.partial.html:16 -#: client/src/inventories-hosts/shared/associate-groups/associate-groups.partial.html:16 -#: client/src/inventories-hosts/shared/associate-hosts/associate-hosts.partial.html:16 -#: client/src/job-submission/job-submission.partial.html:370 -#: client/src/partials/survey-maker-modal.html:17 -#: client/src/partials/survey-maker-modal.html:85 -#: client/src/shared/instance-groups-multiselect/instance-groups-modal/instance-groups-modal.partial.html:17 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html:68 -msgid "CANCEL" -msgstr "CANCELAR" - -#: client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html:28 -msgid "CHANGES" -msgstr "MODIFICACIONES" - -#: client/features/templates/templates.strings.js:113 -msgid "CHECK" -msgstr "COMPROBAR" - -#: client/lib/components/components.strings.js:20 -msgid "CHOOSE A FILE" -msgstr "ELEGIR UN ARCHIVO" - -#: client/features/output/output.strings.js:78 -#: client/src/shared/smart-search/smart-search.partial.html:26 -msgid "CLEAR ALL" -msgstr "LIMPIAR TODO" - -#: client/lib/services/base-string.service.js:61 -#: client/lib/services/base-string.service.js:74 -#: client/src/partials/survey-maker-modal.html:86 -msgid "CLOSE" -msgstr "CERRAR" - -#: client/features/jobs/routes/hostCompletedJobs.route.js:20 -#: client/features/jobs/routes/templateCompletedJobs.route.js:21 -#: client/features/jobs/routes/workflowJobTemplateCompletedJobs.route.js:21 -msgid "COMPLETED JOBS" -msgstr "TRABAJOS COMPLETADOS" - -#: client/src/configuration/configuration.partial.html:10 -msgid "CONFIGURE {{ BRAND_NAME }}" -msgstr "CONFIGURAR {{ BRAND_NAME }}" - -#: client/features/templates/templates.strings.js:31 -#: client/src/scheduler/scheduler.strings.js:63 -msgid "CONFIRM" -msgstr "CONFIRMAR" - -#: client/lib/services/base-string.service.js:72 -msgid "COPY" -msgstr "COPIAR" - -#: client/features/users/tokens/tokens.strings.js:25 -msgid "COULD NOT CREATE TOKEN" -msgstr "NO SE PUDO CREAR EL TOKEN" - -#: client/src/instance-groups/instance-groups.strings.js:46 -msgid "CPU" -msgstr "CPU" - -#: client/src/shared/stateDefinitions.factory.js:161 -msgid "CREATE %s" -msgstr "CREAR %s" - -#: client/features/applications/applications.strings.js:9 -msgid "CREATE APPLICATION" -msgstr "CREAR APLICACIÓN" - -#: client/features/credentials/credentials.strings.js:8 -#: client/src/credentials/credentials.form.js:16 -msgid "CREATE CREDENTIAL" -msgstr "CREAR CREDENCIAL" - -#: client/src/inventories-hosts/inventories/related/groups/add/groups-add.route.js:8 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:16 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-add.route.js:8 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js:16 -msgid "CREATE GROUP" -msgstr "CREAR GRUPO" - -#: client/src/inventories-hosts/hosts/host.form.js:17 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-add.route.js:8 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:17 -#: client/src/inventories-hosts/inventories/related/hosts/add/host-add.route.js:8 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:17 -msgid "CREATE HOST" -msgstr "CREAR HOST" - -#: client/src/instance-groups/instance-groups.strings.js:10 -msgid "CREATE INSTANCE GROUP" -msgstr "CREAR GRUPO DE INSTANCIA" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.route.js:8 -msgid "CREATE INVENTORY SOURCE" -msgstr "CREAR FUENTE DE INVENTARIO" - -#: client/src/inventories-hosts/inventories/related/sources/list/schedule/sources-schedule-add.route.js:9 -#: client/src/scheduler/scheduler.strings.js:8 -#: client/src/scheduler/schedules.route.js:161 -#: client/src/scheduler/schedules.route.js:242 -#: client/src/scheduler/schedules.route.js:73 -msgid "CREATE SCHEDULE" -msgstr "CREAR PROGRAMACIÓN" - -#: client/src/management-jobs/scheduler/main.js:83 -msgid "CREATE SCHEDULED JOB" -msgstr "CREAR TRABAJO PROGRAMADO" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:32 -msgid "CREATE SOURCE" -msgstr "CREAR FUENTE" - -#: client/features/users/tokens/tokens.strings.js:18 -#: client/features/users/tokens/tokens.strings.js:9 -#: client/features/users/tokens/users-tokens-add.route.js:49 -msgid "CREATE TOKEN" -msgstr "CREAR TOKEN" - -#: client/features/output/output.strings.js:101 -msgid "CREATED" -msgstr "CREADO" - -#: client/src/job-submission/job-submission.partial.html:351 -#: client/src/partials/job-template-details.html:2 -msgid "CREDENTIAL" -msgstr "CREDENCIAL" - -#: client/src/credential-types/credential-types.form.js:21 -msgid "CREDENTIAL TYPE" -msgstr "TIPO DE CREDENCIAL" - -#: client/src/job-submission/job-submission.partial.html:92 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html:60 -msgid "CREDENTIAL TYPE:" -msgstr "TIPO DE CREDENCIAL:" - -#: client/src/activity-stream/get-target-title.factory.js:11 -#: client/src/credential-types/credential-types.list.js:12 -#: client/src/credential-types/main.js:44 -msgid "CREDENTIAL TYPES" -msgstr "TIPOS DE CREDENCIAL" - -#: client/features/credentials/legacy.credentials.js:11 -#: client/src/activity-stream/get-target-title.factory.js:17 -#: client/src/credentials/credentials.list.js:15 -#: client/src/credentials/credentials.list.js:16 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html:5 -msgid "CREDENTIALS" -msgstr "CREDENCIALES" - -#: client/features/credentials/credentials.strings.js:30 -msgid "CREDENTIALS PERMISSIONS" -msgstr "PERMISOS DE CREDENCIAL" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:402 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:414 -#: client/src/projects/projects.form.js:200 -msgid "Cache Timeout" -msgstr "Tiempo de espera para la expiración de la caché" - -#: client/src/projects/projects.form.js:189 -msgid "Cache Timeout%s (seconds)%s" -msgstr "Tiempo de espera para la expiración de la caché%s (segundos)%s" - -#: client/src/projects/list/projects-list.controller.js:224 -#: client/src/users/list/users-list.controller.js:85 -msgid "Call to %s failed. DELETE returned status:" -msgstr "Fallo en la llamada a %s. DELETE ha devuelto el estado:" - -#: client/src/projects/list/projects-list.controller.js:291 -#: client/src/projects/list/projects-list.controller.js:308 -msgid "Call to %s failed. GET status:" -msgstr "Fallo en la llamada a %s. Estado GET:" - -#: client/src/projects/edit/projects-edit.controller.js:247 -msgid "Call to %s failed. POST returned status:" -msgstr "Fallo en la llamada a %s. POST ha devuelto el estado:" - -#: client/src/projects/list/projects-list.controller.js:270 -msgid "Call to %s failed. POST status:" -msgstr "Fallo en la llamada a %s. Estado POST :" - -#: client/src/management-jobs/card/card.controller.js:29 -msgid "Call to %s failed. Return status: %d" -msgstr "Fallo en la llamada a %s. Ha devuelto el estado: %d" - -#: client/src/projects/list/projects-list.controller.js:317 -msgid "Call to get project failed. GET status:" -msgstr "Fallo en la obtención del proyecto. Estado GET:" - -#: client/lib/services/base-string.service.js:93 -msgid "Call to {{ path }} failed. {{ action }} returned status: {{ status }}." -msgstr "" -"Llamada a {{ path }} fallida. {{ action }} arrojó el estado: {{ status }}." - -#: client/features/output/output.strings.js:17 -#: client/lib/services/base-string.service.js:85 -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:105 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:188 -#: client/src/configuration/configuration.controller.js:617 -#: client/src/scheduler/scheduler.strings.js:56 -#: client/src/shared/form-generator.js:1719 -#: client/src/shared/lookup/lookup-modal.partial.html:19 -#: client/src/workflow-results/workflow-results.controller.js:38 -msgid "Cancel" -msgstr "Cancelar" - -#: client/lib/services/base-string.service.js:87 -msgid "Cancel Job" -msgstr "Cancelar tarea" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:244 -#: client/src/projects/list/projects-list.controller.js:286 -msgid "Cancel Not Allowed" -msgstr "Cancelación no permitida." - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:100 -msgid "Cancel sync process" -msgstr "Cancelar proceso de sincronización" - -#: client/src/projects/projects.list.js:122 -msgid "Cancel the SCM update" -msgstr "Cancelar la actualización de SCM" - -#: client/lib/services/base-string.service.js:99 -msgid "Cancel the {{resourceType}}" -msgstr "Cancelar el {{resourceType}}" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:42 -#: client/src/projects/list/projects-list.controller.js:80 -msgid "Canceled. Click for details" -msgstr "Cancelado. Pulse para mostrar más detalles." - -#: client/src/shared/smart-search/smart-search.controller.js:162 -msgid "Cannot search running job" -msgstr "Imposible buscar en trabajos en ejecución." - -#: client/src/instance-groups/instance-groups.list.js:22 -msgid "Capacity" -msgstr "Capacidad" - -#: client/src/projects/projects.form.js:83 -msgid "Change %s under \"Configure {{BRAND_NAME}}\" to change this location." -msgstr "" -"Modificar %s dentro de \"Configurar {{BRAND_NAME}}\" para cambiar esta " -"ubicación." - -#: client/src/activity-stream/activity-detail.form.js:41 -msgid "Changes" -msgstr "Modificaciones" - -#: client/src/notifications/notificationTemplates.form.js:355 -msgid "Channel" -msgstr "Canal" - -#: client/features/templates/templates.strings.js:61 -msgid "Check" -msgstr "Comprobar" - -#: client/src/shared/form-generator.js:1087 -msgid "Choose a %s" -msgstr "Escoja un %s" - -#: client/features/templates/templates.strings.js:52 -msgid "Choose a job type" -msgstr "Seleccionar un tipo de tarea" - -#: client/features/templates/templates.strings.js:53 -msgid "Choose a verbosity" -msgstr "Elegir un nivel de detalle" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:64 -msgid "Choose an answer type" -msgstr "Elegir un tipo de respuesta" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:67 -msgid "" -"Choose an answer type or format you want as the prompt for the user. Refer " -"to the Ansible Tower Documentation for more additional information about " -"each option." -msgstr "" -"Especifique el tipo de respuesta o el formato 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." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:112 -msgid "Choose an inventory file" -msgstr "Escoja un archivo del inventario." - -#: client/src/shared/directives.js:92 -msgid "Choose file" -msgstr "Escoja fichero" - -#: client/src/license/license.partial.html:97 -msgid "" -"Choose your license file, agree to the End User License Agreement, and click" -" submit." -msgstr "" -"Escoja su fichero de licencia, aceptando el Acuerdo de licencia de usuario " -"final, y pulse sobre Enviar." - -#: client/src/projects/projects.form.js:157 -msgid "Clean" -msgstr "Limpiar" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:299 -msgid "Clear" -msgstr "Borrar" - -#: client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.directive.js:64 -msgid "Click for details" -msgstr "Hacer clic para obtener más información" - -#: client/src/templates/workflows/edit-workflow/workflow-edit.controller.js:261 -msgid "Click here to open the workflow visualizer." -msgstr "Haga clic aquí para abrir el visualizador de flujos de trabajo." - -#: client/src/inventories-hosts/inventories/inventory.list.js:16 -msgid "" -"Click on a row to select it, and click Finished when done. Click the %s " -"button to create a new inventory." -msgstr "" -"Pulse sobre una fila para seleccionarla, y pulse Finalizado una vez " -"terminado. Pulse sobre el botón %s para crear un nuevo inventario." - -#: client/src/teams/teams.list.js:16 -msgid "" -"Click on a row to select it, and click Finished when done. Click the %s " -"button to create a new team." -msgstr "" -"Pulse sobre una fila para seleccionarla, y pulse Finalizado una vez " -"terminado. Pulse sobre el botón %s para crear un nuevo equipo." - -#: client/src/templates/templates.list.js:17 -msgid "" -"Click on a row to select it, and click Finished when done. Use the %s button" -" to create a new job template." -msgstr "" -"Pulse sobre una fila para seleccionarla, y pulse Finalizado una vez " -"terminado. Pulse sobre el botón %s para crear una nueva plantilla de " -"trabajo." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:138 -msgid "" -"Click on the regions field to see a list of regions for your cloud provider." -" You can select multiple regions, or choose" -msgstr "" -"Haga clic en el campo de regiones para ver una lista de regiones para su " -"proveedor de nube. Puede seleccionar varias regiones o elegir" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:270 -msgid "Click the" -msgstr "Haga clic en" - -#: client/src/scheduler/scheduler.strings.js:13 -msgid "Click to edit schedule." -msgstr "Haga clic para editar el cronograma." - -#: client/src/credentials/credentials.form.js:321 -msgid "Client ID" -msgstr "ID del cliente" - -#: client/src/notifications/notificationTemplates.form.js:254 -msgid "Client Identifier" -msgstr "Identificador del cliente" - -#: client/src/credentials/credentials.form.js:330 -msgid "Client Secret" -msgstr "Pregunta secreta del cliente" - -#: client/src/scheduler/scheduler.strings.js:55 -#: client/src/shared/form-generator.js:1723 -msgid "Close" -msgstr "Cerrar" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:26 -msgid "Cloud source not configured." -msgstr "Fuente de nube no configurada." - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:25 -msgid "Cloud source not configured. Click" -msgstr "Fuente de nube no configurada. Haga clic en" - -#: client/src/credentials/factories/become-method-change.factory.js:80 -#: client/src/credentials/factories/kind-change.factory.js:137 -msgid "CloudForms URL" -msgstr "URL CloudForms" - -#: client/features/output/output.strings.js:18 -#: client/src/workflow-results/workflow-results.controller.js:152 -msgid "Collapse Output" -msgstr "Colapsar salida" - -#: client/src/inventories-hosts/hosts/host.form.js:129 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:128 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:155 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:172 -#: client/src/templates/job_templates/job-template.form.js:443 -#: client/src/templates/workflows.form.js:180 -msgid "Completed Jobs" -msgstr "Tareas completadas" - -#: client/src/management-jobs/card/card.partial.html:34 -msgid "Configure Notifications" -msgstr "Configurar las notificaciones" - -#: client/src/users/users.form.js:83 -msgid "Confirm Password" -msgstr "Confirmar la contraseña" - -#: client/src/configuration/configuration.controller.js:624 -msgid "Confirm Reset" -msgstr "Confirmar la reinicialización" - -#: client/src/configuration/configuration.controller.js:633 -msgid "Confirm factory reset" -msgstr "Confirmar la reinicialización a valores de fábrica" - -#: client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.directive.js:82 -msgid "Confirm the removal of the" -msgstr "Confirmar la eliminación de" - -#: client/src/teams/teams.form.js:24 client/src/users/users.form.js:25 -msgid "" -"Contact your System Administrator to grant you the appropriate permissions " -"to add and edit Users and Teams." -msgstr "" -"Entre en contacto con su Administrador de sistema para que le otorgue los " -"permisos adecuados para añadir y editar Usuarios y Equipos." - -#: client/src/inventories-hosts/inventories/related/groups/factories/get-hosts-status-msg.factory.js:18 -msgid "Contains 0 hosts." -msgstr "Contiene 0 hosts." - -#: client/src/templates/job_templates/job-template.form.js:179 -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." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:337 -msgid "" -"Control the level of output ansible will produce for inventory source update" -" jobs." -msgstr "" -"Controle el nivel de salida que Ansible producirá para las tareas de " -"actualización de fuentes de inventario." - -#: client/lib/components/components.strings.js:52 -msgid "Copied to clipboard." -msgstr "Copiado al portapapeles." - -#: client/src/credentials/credentials.list.js:73 -#: client/src/inventories-hosts/inventories/inventory.list.js:105 -#: client/src/inventory-scripts/inventory-scripts.list.js:61 -#: client/src/notifications/notificationTemplates.list.js:82 -#: client/src/projects/projects.list.js:100 -#: client/src/templates/templates.list.js:93 -msgid "Copy" -msgstr "Copiar" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:56 -msgid "Copy Inventory" -msgstr "Copiar inventario" - -#: client/src/credentials/credentials.list.js:76 -msgid "Copy credential" -msgstr "Copiar credencial" - -#: client/lib/components/components.strings.js:51 -msgid "Copy full revision to clipboard." -msgstr "Copie la revisión completa al portapapeles." - -#: client/src/inventory-scripts/inventory-scripts.list.js:64 -msgid "Copy inventory script" -msgstr "Copiar script de inventario" - -#: client/src/notifications/notificationTemplates.list.js:85 -msgid "Copy notification" -msgstr "Copiar notificación" - -#: client/src/projects/projects.list.js:103 -msgid "Copy project" -msgstr "Copiar proyecto" - -#: client/src/templates/templates.list.js:96 -msgid "Copy template" -msgstr "Copiar plantilla" - -#: client/lib/services/base-string.service.js:97 -msgid "Copy {{resourceType}}" -msgstr "Copiar {{resourceType}}" - -#: client/src/about/about.partial.html:27 -msgid "" -"Copyright © 2018 Red Hat, Inc.
\n" -" Visit Ansible.com for more information.
" -msgstr "" -"Copyright © 2018 Red Hat, Inc.
\n" -" Visite Ansible.com para obtener más información.
" - -#: client/lib/components/components.strings.js:88 -msgid "Copyright © 2018 Red Hat, Inc." -msgstr "Copyright © 2018 Red Hat, Inc." - -#: client/src/users/users.list.js:44 -msgid "Create New" -msgstr "Crear nuevo" - -#: client/features/applications/applications.strings.js:20 -msgid "Create a new Application" -msgstr "Crear una nueva aplicación" - -#: client/src/instance-groups/instance-groups.strings.js:30 -msgid "Create a new Instance Group" -msgstr "Crear un nuevo Grupo de instancia" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:47 -msgid "" -"Create a new Smart Inventory from search results.

Note: changing " -"the organization of the Smart Inventory could change the hosts included in " -"the Smart Inventory." -msgstr "" -"Cree un nuevo inventario inteligente de los resultados de búsqueda.
Nota: Cambiar la organización del inventario inteligente podría cambiar " -"los hosts que están incluidos en el inventario inteligente." - -#: client/src/credentials/credentials.list.js:52 -msgid "Create a new credential" -msgstr "Crear una nueva credencial" - -#: client/src/credential-types/credential-types.list.js:42 -msgid "Create a new credential type" -msgstr "Crear un nuevo tipo de credencial" - -#: client/src/inventory-scripts/inventory-scripts.list.js:40 -msgid "Create a new custom inventory" -msgstr "Crear un nuevo inventario personalizado" - -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:69 -msgid "Create a new group" -msgstr "Crear un nuevo grupo" - -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:123 -msgid "Create a new host" -msgstr "Crear un nuevo host" - -#: client/src/inventories-hosts/inventories/inventory.list.js:76 -msgid "Create a new inventory" -msgstr "Crear un nuevo inventario" - -#: client/src/notifications/notificationTemplates.list.js:52 -msgid "Create a new notification template" -msgstr "Crear una nueva plantilla de notificación" - -#: client/src/organizations/list/organizations-list.partial.html:21 -msgid "Create a new organization" -msgstr "Crear una nueva organización" - -#: client/src/projects/projects.list.js:75 -msgid "Create a new project" -msgstr "Crear un nuevo proyecto" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:68 -msgid "Create a new source" -msgstr "Crear una nueva fuente" - -#: client/src/teams/teams.list.js:43 -msgid "Create a new team" -msgstr "Crear un nuevo equipo" - -#: client/src/templates/templates.list.js:56 -msgid "Create a new template" -msgstr "Crear una nueva plantilla" - -#: client/src/users/users.list.js:48 -msgid "Create a new user" -msgstr "Crear un nuevo usuario" - -#: client/features/output/output.strings.js:44 -#: client/features/templates/templates.strings.js:25 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:73 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:74 -#: client/src/job-submission/job-submission.partial.html:18 -#: client/src/templates/job_templates/job-template.form.js:121 -msgid "Credential" -msgstr "Credencial" - -#: client/features/templates/templates.strings.js:36 -msgid "Credential Type" -msgstr "Tipo de credencial" - -#: client/lib/components/components.strings.js:74 -#: client/lib/models/models.strings.js:12 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:34 -msgid "Credential Types" -msgstr "Tipos de credencial" - -#: client/features/jobs/jobs.strings.js:16 -#: client/features/templates/templates.strings.js:18 -#: client/lib/components/components.strings.js:73 -#: client/lib/models/models.strings.js:8 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:129 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:58 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:26 -#: client/src/templates/job_templates/job-template.form.js:133 -msgid "Credentials" -msgstr "Credenciales" - -#: client/features/templates/templates.strings.js:37 -msgid "" -"Credentials that require passwords on launch are not permitted for template " -"schedules and workflow nodes. The following credentials must be removed or " -"replaced to proceed:" -msgstr "" -"No se permiten las credenciales que requieran contraseñas durante el " -"lanzamiento para programaciones de plantillas y nodos de flujo de trabajo. " -"Para continuar, se deben eliminar o sustituir las siguientes credenciales:" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:17 -msgid "Critical" -msgstr "Crítico" - -#: client/src/shared/directives.js:93 -msgid "Current Image:" -msgstr "Imagen actual:" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:171 -msgid "Custom Inventory Script" -msgstr "Script de inventario personalizado" - -#: client/src/inventory-scripts/inventory-scripts.form.js:50 -#: client/src/inventory-scripts/inventory-scripts.form.js:60 -msgid "Custom Script" -msgstr "Script personalizado" - -#: client/src/home/home.route.js:21 -msgid "DASHBOARD" -msgstr "PANEL DE CONTROL" - -#: client/features/users/tokens/tokens.strings.js:28 -#: client/lib/services/base-string.service.js:71 -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:52 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:74 -#: client/src/notifications/notification-templates-list/list.controller.js:230 -#: client/src/organizations/list/organizations-list.controller.js:196 -#: client/src/partials/survey-maker-modal.html:18 -#: client/src/projects/edit/projects-edit.controller.js:255 -#: client/src/projects/list/projects-list.controller.js:254 -#: client/src/users/list/users-list.controller.js:95 -msgid "DELETE" -msgstr "ELIMINAR" - -#: client/src/partials/survey-maker-modal.html:84 -msgid "DELETE SURVEY" -msgstr "BORRAR ENCUESTA" - -#: client/features/templates/templates.strings.js:116 -msgid "DELETED" -msgstr "ELIMINADO" - -#: client/features/users/tokens/tokens.strings.js:36 -msgid "DESCRIPTION" -msgstr "DESCRIPCIÓN" - -#: client/features/templates/templates.strings.js:118 -#: client/src/instance-groups/instance-groups.strings.js:24 -#: client/src/workflow-results/workflow-results.controller.js:55 -msgid "DETAILS" -msgstr "DETALLES" - -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.partial.html:29 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-disassociate.partial.html:30 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-disassociate.partial.html:30 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-disassociate.partial.html:31 -msgid "DISASSOCIATE" -msgstr "DISOCIAR" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal.partial.html:5 -msgid "DYNAMIC HOSTS" -msgstr "HOSTS DINÁMICOS" - -#: client/lib/components/components.strings.js:68 -msgid "Dashboard" -msgstr "Tablero" - -#: client/src/scheduler/scheduler.strings.js:52 -msgid "Date format" -msgstr "Formato de fecha" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:164 -msgid "Default" -msgstr "Predeterminado" - -#: client/features/output/output.strings.js:19 -#: client/lib/services/base-string.service.js:78 -#: client/src/credential-types/credential-types.list.js:73 -#: client/src/credential-types/list/list.controller.js:106 -#: client/src/credentials/credentials.list.js:92 -#: client/src/credentials/list/credentials-list.controller.js:176 -#: client/src/inventories-hosts/inventories/inventory.list.js:121 -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:157 -#: client/src/inventory-scripts/inventory-scripts.list.js:79 -#: client/src/inventory-scripts/list/list.controller.js:126 -#: client/src/notifications/notification-templates-list/list.controller.js:226 -#: client/src/notifications/notificationTemplates.list.js:100 -#: client/src/organizations/list/organizations-list.controller.js:192 -#: client/src/projects/edit/projects-edit.controller.js:252 -#: client/src/projects/list/projects-list.controller.js:250 -#: client/src/scheduler/schedules.list.js:100 -#: client/src/teams/teams.list.js:72 -#: client/src/templates/templates.list.js:109 -#: client/src/users/list/users-list.controller.js:91 -#: client/src/users/users.list.js:79 -#: client/src/workflow-results/workflow-results.controller.js:39 -msgid "Delete" -msgstr "ELIMINAR" - -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:6 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:6 -msgid "Delete Group" -msgstr "Eliminar grupo" - -#: client/src/templates/survey-maker/surveys/init.factory.js:23 -msgid "Delete Question" -msgstr "Eliminar pregunta" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:194 -msgid "Delete Source" -msgstr "Eliminar fuente" - -#: client/src/credentials/credentials.list.js:94 -msgid "Delete credential" -msgstr "Eliminar la credencial." - -#: client/src/credential-types/credential-types.list.js:75 -msgid "Delete credential type" -msgstr "Eliminar tipo de credencial" - -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:101 -#: client/src/inventories-hosts/inventory-hosts.strings.js:19 -msgid "Delete group" -msgid_plural "Delete groups" -msgstr[0] "Eliminar grupo" -msgstr[1] "Eliminar grupos" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:48 -msgid "Delete groups" -msgstr "Eliminar grupos" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:37 -msgid "Delete groups and hosts" -msgstr "Eliminar grupos y hosts" - -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:100 -#: client/src/inventories-hosts/inventory-hosts.strings.js:21 -msgid "Delete host" -msgid_plural "Delete hosts" -msgstr[0] "Eliminar host" -msgstr[1] "Eliminar hosts" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:59 -msgid "Delete hosts" -msgstr "Eliminar hosts" - -#: client/src/inventories-hosts/inventories/inventory.list.js:123 -msgid "Delete inventory" -msgstr "Eliminar el inventario" - -#: client/src/inventory-scripts/inventory-scripts.list.js:81 -msgid "Delete inventory script" -msgstr "Eliminar el script de inventario." - -#: client/src/notifications/notificationTemplates.list.js:102 -msgid "Delete notification" -msgstr "Eliminar la notificación" - -#: client/src/projects/projects.form.js:167 -msgid "Delete on Update" -msgstr "Eliminar la actualización" - -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:27 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:27 -msgid "Delete or promote the group's children?" -msgstr "¿Desea eliminar o promover los elementos secundarios del grupo?" - -#: client/src/scheduler/schedules.list.js:103 -msgid "Delete schedule" -msgstr "Eliminar planificación" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:117 -msgid "Delete source" -msgstr "Eliminar fuente" - -#: client/src/teams/teams.list.js:76 -msgid "Delete team" -msgstr "Eliminar el equipo" - -#: client/src/templates/templates.list.js:112 -msgid "Delete template" -msgstr "Eliminar la plantilla" - -#: client/src/projects/projects.form.js:169 -msgid "" -"Delete the local repository in its entirety prior to performing an update." -msgstr "" -"Eliminar el repositorio local en su totalidad antes de realizar la " -"actualización." - -#: client/src/projects/projects.list.js:116 -msgid "Delete the project" -msgstr "Eliminar el proyecto" - -#: client/src/scheduler/scheduled-jobs.list.js:81 -msgid "Delete the schedule" -msgstr "Eliminar la planificación" - -#: client/lib/services/base-string.service.js:98 -msgid "Delete the {{resourceType}}" -msgstr "Eliminar el {{resourceType}}" - -#: client/src/users/users.list.js:83 -msgid "Delete user" -msgstr "Eliminar el usuario" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:14 -msgid "Delete {{ group }} and {{ host }}" -msgstr "Eliminar {{ group }} y {{ host }}" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:23 -msgid "Deleting group" -msgstr "Eliminando grupo" - -#: client/lib/services/base-string.service.js:80 -msgid "" -"Deleting this {{ resourceType }} will make the following resources " -"unavailable." -msgstr "" -"Al eliminar este {{ resourceType }}, los siguientes recursos dejarán de " -"estar disponibles." - -#: client/src/projects/projects.form.js:169 -msgid "" -"Depending on the size of the repository this may significantly increase the " -"amount of time required to complete an update." -msgstr "" -"Dependiendo del tamaño del repositorio esto podría incrementar " -"significativamente el tiempo necesario para completar una actualización." - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:246 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:261 -msgid "Describe Instances documentation" -msgstr "Documentación de descripción de las instancias" - -#: client/src/credential-types/credential-types.form.js:34 -#: client/src/credentials/credentials.form.js:39 -#: client/src/inventories-hosts/hosts/host.form.js:63 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:39 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js:40 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:62 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:62 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:58 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:28 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:36 -#: client/src/inventory-scripts/inventory-scripts.form.js:35 -#: client/src/notifications/notificationTemplates.form.js:39 -#: client/src/organizations/organizations.form.js:33 -#: client/src/projects/projects.form.js:37 client/src/teams/teams.form.js:35 -#: client/src/templates/job_templates/job-template.form.js:41 -#: client/src/templates/survey-maker/shared/question-definition.form.js:36 -#: client/src/templates/workflows.form.js:49 -#: client/src/users/users.form.js:147 client/src/users/users.form.js:173 -msgid "Description" -msgstr "Descripción" - -#: client/src/notifications/notificationTemplates.form.js:136 -#: client/src/notifications/notificationTemplates.form.js:140 -#: client/src/notifications/notificationTemplates.form.js:152 -#: client/src/notifications/notificationTemplates.form.js:156 -msgid "Destination Channels" -msgstr "Canales destinatarios" - -#: client/src/notifications/notificationTemplates.form.js:430 -#: client/src/notifications/notificationTemplates.form.js:434 -msgid "Destination Channels or Users" -msgstr "Canales destinatarios o usuarios" - -#: client/src/notifications/notificationTemplates.form.js:205 -#: client/src/notifications/notificationTemplates.form.js:206 -msgid "Destination SMS Number" -msgstr "Número SMS del destinatario" - -#: client/features/applications/applications.strings.js:15 -#: client/features/credentials/credentials.strings.js:13 -#: client/features/output/output.strings.js:34 -#: client/features/users/tokens/tokens.strings.js:14 -#: client/src/license/license.partial.html:5 -#: client/src/shared/form-generator.js:1501 -msgid "Details" -msgstr "Detalles" - -#: client/src/job-submission/job-submission.partial.html:263 -msgid "Diff Mode" -msgstr "Modo diff" - -#: client/src/notifications/notificationTemplates.form.js:369 -#: client/src/notifications/notificationTemplates.form.js:401 -msgid "Disable SSL Verification" -msgstr "Deshabilite la verificación de SSL" - -#: client/src/templates/survey-maker/surveys/init.factory.js:518 -msgid "Disable survey" -msgstr "Desactivar la encuesta" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-disassociate.partial.html:6 -msgid "Disassociate Group From Group" -msgstr "Desasociar un grupo de otro grupo" - -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.partial.html:6 -msgid "Disassociate Host" -msgstr "Disociar host" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-disassociate.partial.html:6 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-disassociate.partial.html:6 -msgid "Disassociate Host From Group" -msgstr "Desasociar un host de un grupo" - -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.list.js:65 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:110 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:98 -msgid "Disassociate group" -msgstr "Disociar grupo" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:87 -msgid "Disassociate host" -msgstr "Disociar host" - -#: client/src/templates/survey-maker/surveys/init.factory.js:21 -msgid "Disable Survey" -msgstr "Desactivar la encuesta" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:94 -#: client/src/configuration/configuration.controller.js:231 -#: client/src/configuration/configuration.controller.js:316 -#: client/src/configuration/system-form/configuration-system.controller.js:57 -msgid "Discard changes" -msgstr "Descartar cambios" - -#: client/src/teams/teams.form.js:149 -msgid "Dissassociate permission from team" -msgstr "Desasociar permiso de un equipo." - -#: client/src/users/users.form.js:227 -msgid "Dissassociate permission from user" -msgstr "Desasociar permiso de un usuario" - -#: client/src/credentials/credentials.form.js:384 -#: client/src/credentials/factories/become-method-change.factory.js:54 -#: client/src/credentials/factories/kind-change.factory.js:111 -msgid "Domain Name" -msgstr "Nombre de dominio" - -#: client/features/output/output.strings.js:20 -msgid "Download Output" -msgstr "Descargar salida" - -#: client/src/inventory-scripts/inventory-scripts.form.js:59 -msgid "" -"Drag and drop your custom inventory script file here or create one in the " -"field to import your custom inventory. Refer to the Ansible Tower " -"documentation for example syntax." -msgstr "" -"Arrastre y suelte el archivo personalizado del script de inventario aquí o " -"cree uno en el campo para importar el inventario personalizado. Consulte la " -"documentación de Ansible Tower para acceder a ejemplos de sintaxis." - -#: client/src/templates/survey-maker/surveys/init.factory.js:24 -msgid "Drag to reorder question" -msgstr "Arrastrar hasta reordenar pregunta" - -#: client/src/partials/survey-maker-modal.html:77 -msgid "Drop question here to reorder" -msgstr "Arrastre y suelte una pregunta aquí para reordenar" - -#: client/features/templates/templates.strings.js:115 -msgid "EDGE CONFLICT" -msgstr "CONFLICTO DE PERÍMETRO" - -#: client/features/applications/applications.strings.js:10 -msgid "EDIT APPLICATION" -msgstr "EDITAR APLICACIÓN" - -#: client/src/configuration/configuration.route.js:28 -msgid "EDIT CONFIGURATION" -msgstr "EDITAR CONFIGURACION" - -#: client/features/credentials/credentials.strings.js:9 -msgid "EDIT CREDENTIAL" -msgstr "EDITAR CREDENCIAL" - -#: client/src/instance-groups/instance-groups.strings.js:11 -msgid "EDIT INSTANCE GROUP" -msgstr "EDITAR GRUPO DE INSTANCIA" - -#: client/src/scheduler/scheduler.strings.js:9 -msgid "EDIT SCHEDULE" -msgstr "EDITAR PROGRAMA" - -#: client/src/management-jobs/scheduler/main.js:97 -msgid "EDIT SCHEDULED JOB" -msgstr "MODIFICAR UN TRABAJO PLANIFICADO" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:17 -msgid "EDIT SURVEY PROMPT" -msgstr "EDITAR AVISO DE ENCUESTA" - -#: client/features/templates/templates.strings.js:108 -msgid "EDIT TEMPLATE" -msgstr "EDITAR PLANTILLA" - -#: client/lib/components/components.strings.js:9 -msgid "ENCRYPTED" -msgstr "CIFRADO" - -#: client/features/output/output.strings.js:80 -msgid "EXAMPLES" -msgstr "EJEMPLOS" - -#: client/src/shared/smart-search/smart-search.partial.html:36 -msgid "EXAMPLES:" -msgstr "EJEMPLOS:" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:15 -msgid "EXECUTE COMMAND" -msgstr "EJECUTAR COMANDO" - -#: client/lib/components/code-mirror/code-mirror.strings.js:10 -msgid "EXPAND" -msgstr "EXPANDIR" - -#: client/features/applications/applications.strings.js:29 -#: client/features/users/tokens/tokens.strings.js:37 -msgid "EXPIRATION" -msgstr "EXPIRACIÓN" - -#: client/features/users/tokens/tokens.strings.js:24 -msgid "EXPIRES" -msgstr "EXPIRA" - -#: client/lib/components/code-mirror/code-mirror.strings.js:48 -#: client/lib/components/code-mirror/code-mirror.strings.js:8 -msgid "EXTRA VARIABLES" -msgstr "VARIABLES ADICIONALES" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:379 -msgid "" -"Each time a job runs using this inventory, refresh the inventory from the " -"selected source before executing job tasks." -msgstr "" -"Cada vez que se ejecuta un trabajo con este inventario, actualice el " -"inventario de la fuente seleccionada antes de ejecutar tareas de trabajo." - -#: client/src/projects/projects.form.js:180 -msgid "" -"Each time a job runs using this project, update the revision of the project " -"prior to starting the job." -msgstr "" -"Cada vez que una tarea se ejecuta usando este proyecto, deberá actualizar la" -" revisión del proyecto antes de iniciar dicha tarea." - -#: client/src/credential-types/credential-types.list.js:56 -#: client/src/credentials/credentials.list.js:66 -#: client/src/inventories-hosts/inventories/inventory.list.js:98 -#: client/src/inventory-scripts/inventory-scripts.list.js:54 -#: client/src/notifications/notificationTemplates.list.js:66 -#: client/src/notifications/notificationTemplates.list.js:75 -#: client/src/scheduler/schedules.list.js:85 client/src/teams/teams.list.js:55 -#: client/src/templates/templates.list.js:80 client/src/users/users.list.js:60 -msgid "Edit" -msgstr "Editar" - -#: client/src/templates/survey-maker/surveys/init.factory.js:22 -msgid "Edit Question" -msgstr "Editar pregunta" - -#: client/src/shared/form-generator.js:1735 -#: client/src/templates/job_templates/job-template.form.js:475 -#: client/src/templates/workflows.form.js:212 -msgid "Edit Survey" -msgstr "Editar la encuesta" - -#: client/src/credential-types/credential-types.list.js:58 -msgid "Edit credential type" -msgstr "Editar tipo de credencial" - -#: client/src/credentials/credentials.list.js:68 -msgid "Edit credential" -msgstr "Editar credenciales" - -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:85 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:96 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:84 -msgid "Edit group" -msgstr "Editar grupo" - -#: client/src/inventories-hosts/hosts/host.list.js:83 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:73 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:79 -#: client/src/inventories-hosts/inventory-hosts.strings.js:25 -msgid "Edit host" -msgstr "Editar el servidor" - -#: client/src/inventories-hosts/inventories/inventory.list.js:100 -msgid "Edit inventory" -msgstr "Editar el inventario" - -#: client/src/inventory-scripts/inventory-scripts.list.js:56 -msgid "Edit inventory script" -msgstr "Editar el script de inventario" - -#: client/src/notifications/notificationTemplates.list.js:68 -msgid "Edit notification" -msgstr "Editar la notificación" - -#: client/src/scheduler/schedules.list.js:88 -msgid "Edit schedule" -msgstr "Editar la programación" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:83 -msgid "Edit source" -msgstr "Editar fuente" - -#: client/src/teams/teams.list.js:59 -msgid "Edit team" -msgstr "Editar el equipo" - -#: client/src/templates/templates.list.js:82 -msgid "Edit template" -msgstr "Editar la plantilla" - -#: client/src/projects/projects.list.js:87 -msgid "Edit the project" -msgstr "Editar el proyecto" - -#: client/src/scheduler/scheduled-jobs.list.js:67 -#: client/src/workflow-results/workflow-results.controller.js:42 -msgid "Edit the schedule" -msgstr "Editar la planificación" - -#: client/src/workflow-results/workflow-results.controller.js:40 -msgid "Edit the user" -msgstr "Edite el usuario" - -#: client/src/workflow-results/workflow-results.controller.js:41 -msgid "Edit the workflow job template" -msgstr "Edite la plantilla de tareas para el flujo de trabajo" - -#: client/src/users/users.list.js:64 -msgid "Edit user" -msgstr "Editar el usuario" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:244 -msgid "" -"Either you do not have access or the SCM update process completed. Click the" -msgstr "" -"No tiene acceso o el proceso de actualización de SCM ha finalizado. Haga " -"clic en" - -#: client/src/projects/list/projects-list.controller.js:286 -msgid "" -"Either you do not have access or the SCM update process completed. Click the" -" %sRefresh%s button to view the latest status." -msgstr "" -"Usted no tiene acceso o el proceso de actualización de SCM ha sido " -"completado. Pulse sobre el botón %sActualizar%s para ver el estado más " -"reciente." - -#: client/features/output/output.strings.js:90 -#: client/src/workflow-results/workflow-results.controller.js:61 -msgid "Elapsed" -msgstr "Tiempo transcurrido" - -#: client/src/credentials/credentials.form.js:191 -#: client/src/users/users.form.js:53 -msgid "Email" -msgstr "Correo" - -#: client/src/templates/job_templates/job-template.form.js:303 -#: client/src/templates/job_templates/job-template.form.js:308 -#: client/src/templates/workflows.form.js:100 -#: client/src/templates/workflows.form.js:105 -msgid "Enable Concurrent Jobs" -msgstr "Activar los trabajos concurrentes" - -#: client/src/configuration/system-form/configuration-system.partial.html:30 -msgid "Enable External Logging" -msgstr "Habilitar registro externo" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:124 -#: client/src/templates/job_templates/job-template.form.js:279 -#: client/src/templates/job_templates/job-template.form.js:284 -msgid "Enable Privilege Escalation" -msgstr "Activar la elevación de privilegios" - -#: client/src/templates/survey-maker/surveys/init.factory.js:518 -msgid "Enable survey" -msgstr "Habilitar encuesta" - -#: client/src/templates/job_templates/job-template.form.js:294 -msgid "" -"Enables creation of a provisioning callback URL. Using the URL a host can " -"contact {{BRAND_NAME}} and request a configuration update using this job " -"template." -msgstr "" -"Habilita la creación de una dirección URL para el uso de retorno de " -"llamadas. Mediante esta URL, un host puede contactar a {{BRAND_NAME}} y " -"solicitar la actualización de la configuración utilizando esta plantilla de " -"trabajo." - -#: client/src/credentials/factories/credential-form-save.factory.js:73 -msgid "Encrypted credentials are not supported." -msgstr "Credenciales cifrados no están soportados." - -#: client/src/scheduler/scheduler.strings.js:44 -msgid "End" -msgstr "Fin" - -#: client/src/scheduler/scheduler.strings.js:46 -msgid "End Date" -msgstr "Fecha de terminación" - -#: client/src/scheduler/scheduler.strings.js:48 -msgid "End Time" -msgstr "Hora de terminación" - -#: client/src/license/license.partial.html:113 -msgid "End User License Agreement" -msgstr "Acuerdo de licencia de usuario final" - -#: client/src/inventories-hosts/hosts/host.form.js:73 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:72 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:72 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:68 -msgid "" -"Enter inventory variables using either JSON or YAML syntax. Use the radio " -"button to toggle between the two." -msgstr "" -"Introduzca variables del inventario usando sintaxis JSON o YAML. Utilice el " -"botón de selección para elegir entre los dos." - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:76 -msgid "" -"Enter inventory variables using either JSON or YAML syntax. Use the radio " -"button to toggle between the two. Refer to the Ansible Tower documentation " -"for example syntax." -msgstr "" -"Ingrese variables de inventario por medio del uso de sintaxis JSON o YAML. " -"Utilice el botón de selección para alternar entre los dos. Consulte la " -"documentación de Ansible Tower para acceder a ejemplos de sintaxis." - -#: client/src/notifications/notificationTemplates.form.js:155 -msgid "" -"Enter one HipChat channel per line. The pound symbol (#) is not required." -msgstr "" -"Ingrese un canal de HipChat por línea. El símbolo numeral (#) no es " -"necesario." - -#: client/src/notifications/notificationTemplates.form.js:433 -msgid "" -"Enter one IRC channel or username per line. The pound symbol (#) for " -"channels, and the at (@) symbol for users, are not required." -msgstr "" -"Ingrese un canal de IRC o nombre de usuario por línea. El símbolo numeral " -"(#) para canales y el símbolo arroba (@) para usuarios no son necesarios." - -#: client/src/notifications/notificationTemplates.form.js:139 -msgid "" -"Enter one Slack channel per line. The pound symbol (#) is not required." -msgstr "" -"Ingrese un canal de Slack por línea. El símbolo numeral (#) no es necesario." - -#: client/src/notifications/notificationTemplates.form.js:97 -msgid "" -"Enter 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 para crear una lista " -"de destinatarios para este tipo de notificación." - -#: client/src/notifications/notificationTemplates.form.js:209 -msgid "" -"Enter one phone number per line to specify where to route SMS messages." -msgstr "" -"Ingrese un número de teléfono por línea para indicar adónde enviar los " -"mensajes de SMS." - -#: client/src/credentials/factories/become-method-change.factory.js:81 -#: client/src/credentials/factories/kind-change.factory.js:138 -msgid "" -"Enter the URL for the virtual machine which %scorresponds to your CloudForms " -"instance. %sFor example, %s" -msgstr "" -"Introduzca la URL para la máquina virtual la cual %scorresponda a su " -"instancia CloudForm. %sPor ejemplo, %s" - -#: client/src/credentials/factories/become-method-change.factory.js:71 -#: client/src/credentials/factories/kind-change.factory.js:128 -msgid "" -"Enter the URL which corresponds to your %sRed Hat Satellite 6 server. %sFor " -"example, %s" -msgstr "" -"Introduzca la URL que corresponda a su servidor %sRed Hat Satellite 6. %sPor" -" ejemplo, %s" - -#: client/src/credentials/factories/become-method-change.factory.js:49 -#: client/src/credentials/factories/kind-change.factory.js:106 -msgid "" -"Enter the hostname or IP address which corresponds to your VMware vCenter." -msgstr "" -"Introduzca el nombre de servidor o dirección IP que corresponda a su VMWare " -"vCenter." - -#: client/src/notifications/notificationTemplates.form.js:195 -msgid "" -"Enter the number associated with the \"Messaging Service\" in Twilio in the " -"format +18005550199." -msgstr "" -"Ingrese el número asociado con el \"Servicio de mensajería\" en Twilio con " -"el formato +18005550199." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:197 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:221 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:245 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:320 -msgid "" -"Enter variables using either 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." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:187 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:194 -msgid "Environment Variables" -msgstr "Variables del entorno" - -#: client/src/configuration/configuration.controller.js:141 -msgid "Error" -msgstr "Error" - -#: client/features/output/output.strings.js:65 -msgid "Error Details" -msgstr "Detalles del error" - -#: client/lib/services/base-string.service.js:92 -#: client/src/configuration/configuration.controller.js:414 -#: client/src/configuration/configuration.controller.js:523 -#: client/src/configuration/configuration.controller.js:558 -#: client/src/configuration/configuration.controller.js:606 -#: client/src/configuration/system-form/configuration-system.controller.js:231 -#: client/src/credentials/factories/credential-form-save.factory.js:77 -#: client/src/credentials/factories/credential-form-save.factory.js:93 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:130 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:140 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:167 -#: client/src/job-submission/job-submission-factories/launchjob.factory.js:198 -#: client/src/job-submission/job-submission-factories/launchjob.factory.js:217 -#: client/src/management-jobs/card/card.controller.js:102 -#: client/src/management-jobs/card/card.controller.js:28 -#: client/src/projects/add/projects-add.controller.js:117 -#: client/src/projects/edit/projects-edit.controller.js:165 -#: client/src/projects/edit/projects-edit.controller.js:231 -#: client/src/projects/edit/projects-edit.controller.js:247 -#: client/src/projects/list/projects-list.controller.js:196 -#: client/src/projects/list/projects-list.controller.js:223 -#: client/src/projects/list/projects-list.controller.js:270 -#: client/src/projects/list/projects-list.controller.js:291 -#: client/src/projects/list/projects-list.controller.js:307 -#: client/src/projects/list/projects-list.controller.js:316 -#: client/src/shared/stateDefinitions.factory.js:230 -#: client/src/users/add/users-add.controller.js:100 -#: client/src/users/edit/users-edit.controller.js:178 -#: client/src/users/list/users-list.controller.js:84 -msgid "Error!" -msgstr "¡Error!" - -#: client/src/activity-stream/streams.list.js:40 -msgid "Event" -msgstr "Evento" - -#: client/src/activity-stream/factories/build-description.factory.js:120 -msgid "Event summary not available" -msgstr "Resumen del evento no disponible." - -#: client/src/scheduler/scheduler.strings.js:29 -msgid "Every" -msgstr "Cada" - -#: client/src/projects/add/projects-add.controller.js:138 -#: client/src/projects/edit/projects-edit.controller.js:274 -msgid "Example URLs for GIT SCM include:" -msgstr "Ejemplos de URLs para SCM GIT :" - -#: client/src/projects/add/projects-add.controller.js:159 -#: client/src/projects/edit/projects-edit.controller.js:294 -msgid "Example URLs for Mercurial SCM include:" -msgstr "Ejemplos de URLs para SCM Mercurial :" - -#: client/src/projects/add/projects-add.controller.js:150 -#: client/src/projects/edit/projects-edit.controller.js:285 -msgid "Example URLs for Subversion SCM include:" -msgstr "Ejemplos de URLs para SCM Subversion :" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:47 -msgid "Example: ansible_facts.ansible_distribution:\"RedHat\"" -msgstr "Ejemplo: ansible_facts.ansible_distribution:\"RedHat\"" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:76 -msgid "Existing Group" -msgstr "Grupo existente" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:125 -msgid "Existing Host" -msgstr "Host existente" - -#: client/features/output/output.strings.js:22 -#: client/src/workflow-results/workflow-results.controller.js:154 -#: client/src/workflow-results/workflow-results.controller.js:43 -msgid "Expand Output" -msgstr "Extender salida" - -#: client/src/license/license.partial.html:39 -msgid "Expires On" -msgstr "Fecha de expiración el" - -#: client/features/output/output.strings.js:50 -msgid "Explanation" -msgstr "Explicación" - -#: client/features/output/output.strings.js:45 -#: client/features/templates/templates.strings.js:54 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:133 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:145 -#: client/src/job-submission/job-submission.partial.html:165 -#: client/src/partials/logviewer.html:8 -#: client/src/scheduler/scheduler.strings.js:53 -#: client/src/templates/job_templates/job-template.form.js:357 -#: client/src/templates/job_templates/job-template.form.js:364 -#: client/src/templates/workflows.form.js:83 -#: client/src/templates/workflows.form.js:90 -#: client/src/workflow-results/workflow-results.controller.js:122 -msgid "Extra Variables" -msgstr "Variables adicionales" - -#: client/src/inventories-hosts/shared/ansible-facts/ansible-facts.partial.html:4 -#: client/src/inventories-hosts/shared/ansible-facts/ansible-facts.route.js:7 -msgid "FACTS" -msgstr "EVENTOS" - -#: client/src/home/dashboard/graphs/job-status/job-status-graph.directive.js:65 -msgid "FAILED" -msgstr "FALLIDO" - -#: client/features/output/output.strings.js:81 -msgid "FIELDS" -msgstr "CAMPOS" - -#: client/src/shared/smart-search/smart-search.partial.html:42 -msgid "FIELDS:" -msgstr "CAMPOS:" - -#: client/src/smart-status/smart-status.controller.js:43 -msgid "FINISHED" -msgstr "FINALIZADO" - -#: client/src/inventories-hosts/hosts/host.form.js:107 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:106 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:107 -msgid "Facts" -msgstr "Eventos" - -#: client/lib/components/components.strings.js:100 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:80 -msgid "Failed" -msgstr "Fallido" - -#: client/src/home/dashboard/counts/dashboard-counts.directive.js:44 -msgid "Failed Hosts" -msgstr "Servidores fallidos" - -#: client/src/users/add/users-add.controller.js:100 -msgid "Failed to add new user. POST returned status:" -msgstr "Ha fallado la creación de nuevo usuario. POST ha devuelto el estado:" - -#: client/src/credentials/factories/credential-form-save.factory.js:78 -msgid "Failed to create new Credential. POST status:" -msgstr "Ha fallado la creación de un nuevo Credencial. Estado POST :" - -#: client/src/projects/add/projects-add.controller.js:118 -msgid "Failed to create new project. POST returned status:" -msgstr "" -"Ha fallado la creación de un nuevo proyecto. POST ha devuelto el estado:" - -#: client/src/job-submission/job-submission-factories/launchjob.factory.js:218 -msgid "Failed to retrieve job template extra variables." -msgstr "" -"Ha fallado la obtención de variables adicionales para la plantilla de tarea." - -#: client/src/projects/edit/projects-edit.controller.js:166 -msgid "Failed to retrieve project: %s. GET status:" -msgstr "Ha fallado la obtención del proyecto: %s. Estado GET :" - -#: client/src/users/edit/users-edit.controller.js:179 -msgid "Failed to retrieve user: %s. GET status:" -msgstr "Ha fallado la obtención del usuario: %s. Estado GET :" - -#: client/src/configuration/configuration.controller.js:524 -msgid "Failed to save settings. Returned status:" -msgstr "Ha fallado guardar los ajustes. Estado devuelto:" - -#: client/src/configuration/configuration.controller.js:559 -msgid "Failed to save toggle settings. Returned status:" -msgstr "Ha fallado el guardado de los ajustes cambiados. Estado devuelto:" - -#: client/src/credentials/factories/credential-form-save.factory.js:94 -msgid "Failed to update Credential. PUT status:" -msgstr "Ha fallado la actualización de Credencial. Estado PUT :" - -#: client/src/projects/edit/projects-edit.controller.js:231 -msgid "Failed to update project: %s. PUT status:" -msgstr "Ha fallado la actualización del proyecto: %s. Estado PUT :" - -#: client/features/output/output.strings.js:85 -msgid "Failed to update search results." -msgstr "No se pudieron actualizar los resultados de búsqueda." - -#: client/src/job-submission/job-submission-factories/launchjob.factory.js:199 -#: client/src/management-jobs/card/card.controller.js:103 -msgid "Failed updating job %s with variables. POST returned: %d" -msgstr "" -"Ha fallado la actualización del trabajo %s con variables. POST ha devuelto: " -"%d" - -#: client/src/notifications/notifications.list.js:49 -msgid "Failure" -msgstr "Fallo" - -#: client/src/scheduler/schedules.list.js:56 -msgid "Final Run" -msgstr "Última ejecución" - -#: client/features/jobs/jobs.strings.js:10 -#: client/features/output/output.strings.js:40 -#: client/features/output/output.strings.js:46 -#: client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.directive.js:54 -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:44 -#: client/src/workflow-results/workflow-results.controller.js:50 -msgid "Finished" -msgstr "Finalizado" - -#: client/src/access/rbac-multiselect/permissionsUsers.list.js:27 -#: client/src/users/users.form.js:29 client/src/users/users.list.js:33 -msgid "First Name" -msgstr "Nombre" - -#: client/src/scheduler/schedules.list.js:46 -msgid "First Run" -msgstr "Primera ejecución" - -#: client/src/templates/survey-maker/surveys/init.factory.js:19 -msgid "Float" -msgstr "Decimal corto" - -#: client/features/output/output.strings.js:77 -#: client/src/shared/smart-search/smart-search.partial.html:49 -msgid "" -"For additional information on advanced search syntax please see the Ansible " -"Tower" -msgstr "" -"Para obtener información adicional sobre la sintaxis de búsqueda avanzada, " -"consulte Ansible Tower" - -#: client/src/credentials/factories/become-method-change.factory.js:63 -#: client/src/credentials/factories/kind-change.factory.js:120 -msgid "For example, %s" -msgstr "Por ejemplo, %s" - -#: client/src/inventories-hosts/hosts/host.form.js:36 -#: client/src/inventories-hosts/hosts/host.list.js:36 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:35 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:32 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:35 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:31 -#: client/src/inventories-hosts/inventory-hosts.strings.js:33 -msgid "" -"For hosts that are part of an external inventory, this flag cannot be " -"changed. It will be set by the inventory sync process." -msgstr "" -"Para servidores que son parte de un inventario externo, este indicador de " -"estado no puede ser cambiado. Será establecido en el proceso de " -"sincronización del inventario." - -#: client/src/templates/job_templates/job-template.form.js:54 -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." - -#: client/features/output/output.strings.js:47 -#: client/src/instance-groups/instance-groups.strings.js:48 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:110 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:97 -#: client/src/templates/job_templates/job-template.form.js:143 -#: client/src/templates/job_templates/job-template.form.js:153 -msgid "Forks" -msgstr "Forks" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:173 -#: client/src/scheduler/scheduler.strings.js:28 -msgid "Frequency Details" -msgstr "Información sobre la frecuencia" - -#: client/src/scheduler/scheduler.strings.js:41 -msgid "Fri" -msgstr "Vie" - -#: client/src/notifications/notification-templates-list/add-notifications-action.partial.html:2 -msgid "GO TO NOTIFICATIONS TO" -msgstr "IR A NOTIFICACIONES PARA" - -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.route.js:45 -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.route.js:46 -msgid "GROUPS" -msgstr "GRUPOS" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:38 -#: client/src/projects/edit/projects-edit.controller.js:136 -#: client/src/projects/list/projects-list.controller.js:76 -msgid "Get latest SCM revision" -msgstr "Utilice la revisión SCM más reciente" - -#: client/src/credential-types/add/add.controller.js:41 -msgid "Getting Started with Credential Types" -msgstr "Inicio con tipos de credenciales" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:153 -msgid "GitHub" -msgstr "GitHub" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:154 -msgid "GitHub Org" -msgstr "GitHub Org" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:155 -msgid "GitHub Team" -msgstr "Equipo GitHub" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:156 -msgid "Google OAuth2" -msgstr "Google OAuth2" - -#: client/src/teams/teams.form.js:158 client/src/users/users.form.js:216 -msgid "Grant Permission" -msgstr "Conceder permiso" - -#: client/src/notifications/add/add.controller.js:79 -#: client/src/notifications/edit/edit.controller.js:126 -msgid "Gray" -msgstr "Gris" - -#: client/src/notifications/add/add.controller.js:80 -#: client/src/notifications/edit/edit.controller.js:127 -msgid "Green" -msgstr "Verde" - -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:51 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js:52 -msgid "Group Variables" -msgstr "Variables de grupo" - -#: client/src/inventories-hosts/hosts/host.form.js:115 -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.list.js:31 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:89 -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:32 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js:88 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:32 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:114 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:115 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:32 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:150 -msgid "Groups" -msgstr "Grupos" - -#: client/lib/components/components.strings.js:12 -#: client/lib/services/base-string.service.js:66 -#: client/src/templates/survey-maker/surveys/init.factory.js:483 -msgid "HIDE" -msgstr "OCULTAR" - -#: client/lib/components/components.strings.js:43 -msgid "HINT: Drag and drop an SSH private key file on the field below." -msgstr "" -"SUGERENCIA: Arrastre y suelte el archivo de clave privada SSH en el " -"siguiente campo." - -#: client/src/activity-stream/get-target-title.factory.js:41 -#: client/src/inventories-hosts/hosts/hosts.partial.html:9 -#: client/src/inventories-hosts/hosts/main.js:81 -#: client/src/inventories-hosts/inventories/inventories.partial.html:15 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.route.js:18 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-hosts.route.js:17 -msgid "HOSTS" -msgstr "SERVIDORES" - -#: client/src/notifications/notificationTemplates.form.js:320 -#: client/src/notifications/notificationTemplates.form.js:321 -msgid "HTTP Headers" -msgstr "Cabeceras HTTP" - -#: client/src/bread-crumb/bread-crumb.directive.js:41 -msgid "Hide Activity Stream" -msgstr "Ocultar flujo de actividad" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:23 -msgid "High" -msgstr "Alto" - -#: client/src/credentials/credentials.form.js:139 -#: client/src/notifications/notificationTemplates.form.js:83 -msgid "Host" -msgstr "Servidor" - -#: client/src/credentials/factories/become-method-change.factory.js:52 -#: client/src/credentials/factories/kind-change.factory.js:109 -msgid "Host (Authentication URL)" -msgstr "Servidor (URL de autenticación)" - -#: client/src/templates/job_templates/job-template.form.js:339 -#: client/src/templates/job_templates/job-template.form.js:348 -msgid "Host Config Key" -msgstr "Clave de configuración del servidor" - -#: client/src/inventories-hosts/hosts/host.form.js:40 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:39 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:39 -msgid "Host Enabled" -msgstr "Servidor habilitado" - -#: client/src/inventories-hosts/hosts/host.form.js:46 -#: client/src/inventories-hosts/hosts/host.form.js:57 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:45 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:56 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:45 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:56 -msgid "Host Name" -msgstr "Nombre de Host" - -#: client/src/inventories-hosts/hosts/host.form.js:80 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:79 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:79 -msgid "Host Variables" -msgstr "Variables de Host" - -#: client/src/inventories-hosts/shared/factories/set-enabled-msg.factory.js:6 -msgid "Host is available" -msgstr "El host está disponible" - -#: client/src/inventories-hosts/shared/factories/set-enabled-msg.factory.js:10 -msgid "Host is available. Click to toggle." -msgstr "El host está disponible. Haga clic para alternar." - -#: client/src/inventories-hosts/shared/factories/set-enabled-msg.factory.js:6 -msgid "Host is not available" -msgstr "El host no está disponible" - -#: client/src/inventories-hosts/shared/factories/set-enabled-msg.factory.js:10 -msgid "Host is not available. Click to toggle." -msgstr "El host no está disponible. Haga clic para alternar." - -#: client/features/output/output.strings.js:13 -msgid "Host status information for this job is unavailable." -msgstr "" -"La información de estado del host para esta tarea no se encuentra " -"disponible." - -#: client/features/output/output.strings.js:93 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:27 -#: client/src/home/dashboard/counts/dashboard-counts.directive.js:39 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:98 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:57 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:56 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:149 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:159 -msgid "Hosts" -msgstr "Servidores" - -#: client/src/license/license.partial.html:52 -msgid "Hosts Available" -msgstr "Servidores disponibles" - -#: client/src/license/license.partial.html:64 -msgid "Hosts Remaining" -msgstr "Servidores restantes" - -#: client/src/license/license.partial.html:58 -msgid "Hosts Used" -msgstr "Servidores utilizados" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:239 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:254 -msgid "Hosts are imported to" -msgstr "Los hosts se importan a" - -#: client/src/license/license.partial.html:121 -msgid "I agree to the End User License Agreement" -msgstr "Yo acepto el Acuerdo de licencia de usuario final." - -#: client/features/output/output.strings.js:102 -msgid "ID" -msgstr "ID" - -#: client/src/partials/job-template-details.html:2 -msgid "INFO" -msgstr "INFORMACIÓN" - -#: client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html:12 -msgid "INITIATED BY" -msgstr "INICIALIZADO POR" - -#: client/src/inventories-hosts/inventories/insights/insights.route.js:7 -msgid "INSIGHTS" -msgstr "OBSERVACIONES" - -#: client/src/instance-groups/instance-groups.list.js:6 -#: client/src/instance-groups/instance-groups.list.js:7 -#: client/src/instance-groups/instance-groups.strings.js:16 -#: client/src/instance-groups/instance-groups.strings.js:8 -msgid "INSTANCE GROUPS" -msgstr "GRUPOS DE INSTANCIA" - -#: client/src/instance-groups/instance-groups.strings.js:25 -#: client/src/instance-groups/instance-groups.strings.js:9 -msgid "INSTANCES" -msgstr "INSTANCIAS" - -#: client/src/activity-stream/get-target-title.factory.js:14 -#: client/src/inventories-hosts/hosts/hosts.partial.html:8 -#: client/src/inventories-hosts/inventories/inventories.partial.html:14 -#: client/src/inventories-hosts/inventories/inventories.route.js:8 -#: client/src/inventories-hosts/inventories/inventory.list.js:14 -#: client/src/inventories-hosts/inventories/inventory.list.js:15 -#: client/src/organizations/linkout/organizations-linkout.route.js:144 -#: client/src/organizations/list/organizations-list.controller.js:67 -msgid "INVENTORIES" -msgstr "INVENTARIOS" - -#: client/src/job-submission/job-submission.partial.html:346 -#: client/src/partials/job-template-details.html:2 -msgid "INVENTORY" -msgstr "INVENTARIO" - -#: client/src/inventory-scripts/inventory-scripts.form.js:23 -msgid "INVENTORY SCRIPT" -msgstr "SCRIPT DE INVENTARIO" - -#: client/src/activity-stream/get-target-title.factory.js:35 -#: client/src/inventory-scripts/inventory-scripts.list.js:12 -#: client/src/inventory-scripts/main.js:65 -msgid "INVENTORY SCRIPTS" -msgstr "SCRIPTS DE INVENTARIO" - -#: client/src/notifications/notificationTemplates.form.js:419 -msgid "IRC Nick" -msgstr "Alias en IRC" - -#: client/src/notifications/notificationTemplates.form.js:408 -msgid "IRC Server Address" -msgstr "Dirección del servidor IRC" - -#: client/src/notifications/shared/type-change.service.js:66 -msgid "IRC Server Password" -msgstr "Contraseña del servidor IRC" - -#: client/src/notifications/shared/type-change.service.js:65 -msgid "IRC Server Port" -msgstr "Puerto del servidor IRC" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:79 -msgid "ISSUE: {{report.rule.description}}" -msgstr "PROBLEMA: {{report.rule.description}}" - -#: client/src/shared/paginate/paginate.partial.html:43 -msgid "ITEMS" -msgstr "ELEMENTOS" - -#: client/src/notifications/notificationTemplates.form.js:362 -#: client/src/notifications/notificationTemplates.form.js:394 -msgid "Icon URL" -msgstr "URL de icono" - -#: client/src/login/authenticationServices/timer.factory.js:157 -msgid "Idle Session" -msgstr "Sesión inactiva" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:236 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:250 -msgid "If blank, all groups above are created except" -msgstr "Si está en blanco, se crean todos los grupos de arriba, excepto" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:367 -msgid "" -"If checked, all variables for child groups and hosts will be removed and " -"replaced by those found on the external source." -msgstr "" -"Si las opciones están marcadas, todas las variables de los hosts y grupos " -"secundarios se eliminarán y se reemplazarán con aquellas que se hallen en la" -" fuente externa." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:355 -msgid "" -"If checked, any hosts and groups that were previously present on the " -"external source but are now removed will be removed from the Tower " -"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." -msgstr "" -"Si las opciones están marcadas, cualquier grupo o host que estuvo presente " -"previamente en la fuente externa pero que ahora se eliminó, se eliminará del" -" inventario de Tower. Los hosts y grupos que no fueron administrados por la " -"fuente del inventario serán promovidos al siguiente grupo creado manualmente" -" o, si no hay un grupo creado manualmente al que puedan ser promovidos, se " -"los dejará en el grupo predeterminado \"Todo\" para el inventario." - -#: client/src/templates/job_templates/job-template.form.js:282 -msgid "If enabled, run this playbook as an administrator." -msgstr "Si se habilita esta opción, ejecute este manual como administrador." - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:121 -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, muestra los cambios realizados por las tareas de" -" Ansible, donde sea compatible. Esto es equivalente al modo --diff de " -"Ansible." - -#: client/src/templates/job_templates/job-template.form.js:267 -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, muestra los cambios realizados por las tareas de" -" Ansible, donde sea compatible. Esto es equivalente al modo --diff de " -"Ansible." - -#: client/src/templates/job_templates/job-template.form.js:306 -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." - -#: client/src/templates/workflows.form.js:103 -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." - -#: client/src/templates/job_templates/job-template.form.js:317 -msgid "" -"If enabled, use cached facts if available and store discovered facts in the " -"cache." -msgstr "" -"Si se habilita, usa los eventos almacenados en caché, si están disponibles," -" y almacena los elementos detectados en la caché." - -#: client/src/credentials/credentials.form.js:52 -msgid "" -"If no organization is given, the credential can only be used by the user " -"that creates the credential. Organization admins and system administrators " -"can assign an organization so that roles for the credential can be assigned " -"to users and teams in that organization." -msgstr "" -"Si no se especifica ninguna organización, el credencial puede ser sólo " -"utilizado por el usuario que ha creado dicho credencial. Administradores de " -"organización y administradores de sistema pueden asignar una organización " -"para que los roles puedan permitir que los credenciales puedan ser asignados" -" a usuarios y equipos en esa organización" - -#: client/src/license/license.partial.html:70 -msgid "" -"If you are ready to upgrade, please contact us by clicking the button below" -msgstr "" -"Si usted está listo para la actualización, por favor contáctenos pulsando el" -" siguiente botón" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:227 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:241 -msgid "Image ID:" -msgstr "ID de imagen:" - -#: client/src/inventories-hosts/hosts/host.form.js:34 -#: client/src/inventories-hosts/hosts/host.list.js:34 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:33 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:30 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:33 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:29 -#: client/src/inventories-hosts/inventory-hosts.strings.js:32 -msgid "" -"Indicates if a host is available and should be included in running jobs." -msgstr "" -"Indica si el servidor está disponible y debe ser incluído en trabajos en " -"ejecución." - -#: client/src/activity-stream/activity-detail.form.js:31 -#: client/src/activity-stream/streams.list.js:33 -msgid "Initiated by" -msgstr "Inicializado por" - -#: client/src/credential-types/credential-types.form.js:53 -#: client/src/credential-types/credential-types.form.js:61 -msgid "Injector Configuration" -msgstr "Configuración del inyector" - -#: client/src/credential-types/credential-types.form.js:39 -#: client/src/credential-types/credential-types.form.js:47 -msgid "Input Configuration" -msgstr "Configuración de entrada" - -#: client/src/inventories-hosts/hosts/host.form.js:123 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:122 -msgid "Insights" -msgstr "Insights" - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:52 -msgid "Insights Credential" -msgstr "Credencial de Insights" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:145 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:148 -msgid "Instance Filters" -msgstr "Filtros de instancias" - -#: client/features/output/output.strings.js:48 -msgid "Instance Group" -msgstr "Grupo de instancias" - -#: client/src/instance-groups/instance-groups.strings.js:63 -msgid "Instance Group parameter is missing." -msgstr "Falta el parámetro del Grupo de instancias." - -#: client/lib/components/components.strings.js:84 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:54 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:57 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:61 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:64 -#: client/src/organizations/organizations.form.js:38 -#: client/src/organizations/organizations.form.js:41 -#: client/src/templates/job_templates/job-template.form.js:252 -#: client/src/templates/job_templates/job-template.form.js:255 -msgid "Instance Groups" -msgstr "Grupos de instancias" - -#: client/src/instance-groups/instance-groups.strings.js:32 -msgid "Instance Groups Help" -msgstr "Ayuda para grupos de instancias" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:236 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:250 -msgid "Instance ID" -msgstr "ID de instancia" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:228 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:242 -msgid "Instance ID:" -msgstr "ID de instancia:" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:229 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:243 -msgid "Instance Type:" -msgstr "Tipo de instancia:" - -#: client/lib/components/components.strings.js:83 -#: client/src/instance-groups/instance-groups.strings.js:17 -msgid "Instances" -msgstr "Instancias" - -#: client/src/templates/survey-maker/surveys/init.factory.js:18 -msgid "Integer" -msgstr "Entero" - -#: client/src/license/license.partial.html:11 -msgid "Invalid License" -msgstr "Licencia no valida" - -#: client/src/license/license.controller.js:74 -#: client/src/license/license.controller.js:82 -msgid "Invalid file format. Please upload valid JSON." -msgstr "Formato de fichero inválido. Por favor cargue un JSON válido." - -#: client/lib/components/components.strings.js:16 -msgid "Invalid input for this type." -msgstr "Entrada no válida para este tipo." - -#: client/features/output/output.strings.js:86 -msgid "Invalid search filter provided." -msgstr "Se introdujo un filtro de búsqueda no válido." - -#: client/src/login/loginModal/loginModal.partial.html:34 -msgid "Invalid username and/or password. Please try again." -msgstr "Nombre de usuario o contraseña inválida. Por favor intente de nuevo." - -#: client/lib/components/components.strings.js:75 -#: client/lib/models/models.strings.js:16 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:122 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:52 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:28 -#: client/src/home/dashboard/counts/dashboard-counts.directive.js:50 -#: client/src/organizations/linkout/organizations-linkout.route.js:156 -#: client/src/organizations/linkout/organizations-linkout.route.js:158 -msgid "Inventories" -msgstr "Inventarios" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:44 -msgid "Inventories with sources cannot be copied" -msgstr "No se pueden copiar los inventarios con fuentes" - -#: client/features/jobs/jobs.strings.js:14 -#: client/features/output/output.strings.js:49 -#: client/features/templates/templates.strings.js:16 -#: client/features/templates/templates.strings.js:24 -#: client/src/inventories-hosts/hosts/host.list.js:69 -#: client/src/inventories-hosts/inventories/inventory.list.js:81 -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:76 -#: client/src/job-submission/job-submission.partial.html:17 -#: client/src/organizations/linkout/controllers/organizations-inventories.controller.js:70 -#: client/src/templates/job_templates/job-template.form.js:66 -#: client/src/templates/job_templates/job-template.form.js:80 -msgid "Inventory" -msgstr "Inventario" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:110 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:124 -msgid "Inventory File" -msgstr "Archivo de inventario" - -#: client/lib/components/components.strings.js:80 -#: client/lib/models/models.strings.js:20 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:29 -msgid "Inventory Scripts" -msgstr "Scripts de inventario" - -#: client/lib/models/models.strings.js:25 -msgid "Inventory Sources" -msgstr "Fuentes de inventario" - -#: client/features/templates/templates.strings.js:104 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:46 -#: client/src/workflow-results/workflow-results.controller.js:68 -msgid "Inventory Sync" -msgstr "Sincronización de inventario" - -#: client/src/home/dashboard/counts/dashboard-counts.directive.js:55 -msgid "Inventory Sync Failures" -msgstr "Errores de sincronización de inventario" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:75 -msgid "Inventory Variables" -msgstr "Variables de inventario" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:73 -msgid "Inventory contains 0 hosts." -msgstr "El inventario contiene 0 hosts." - -#: client/features/output/output.strings.js:35 -msgid "Isolated" -msgstr "Aislada" - -#: client/src/smart-status/smart-status.controller.js:43 -msgid "JOB ID" -msgstr "ID DE TAREA" - -#: client/features/output/output.strings.js:84 -msgid "JOB IS STILL RUNNING" -msgstr "EL TRABAJO AÚN SE ESTÁ EJECUTANDO" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:4 -msgid "JOB STATUS" -msgstr "ESTADO DEL TRABAJO" - -#: client/src/templates/job_templates/job-template.form.js:22 -msgid "JOB TEMPLATE" -msgstr "PLANTILLA DE TRABAJO" - -#: client/features/portalMode/portalMode.strings.js:8 -#: client/features/templates/routes/organizationsTemplatesList.route.js:20 -#: client/features/templates/routes/projectsTemplatesList.route.js:18 -#: client/src/organizations/list/organizations-list.controller.js:79 -msgid "JOB TEMPLATES" -msgstr "PLANTILLAS DE TRABAJO" - -#: client/features/jobs/jobs.strings.js:8 -#: client/features/jobs/routes/instanceGroupJobs.route.js:13 -#: client/features/jobs/routes/instanceJobs.route.js:13 -#: client/features/jobs/routes/inventoryCompletedJobs.route.js:22 -#: client/features/jobs/routes/jobs.route.js:13 -#: client/features/portalMode/portalMode.strings.js:9 -#: client/features/templates/templates.strings.js:109 -#: client/src/activity-stream/get-target-title.factory.js:32 -#: client/src/home/dashboard/graphs/job-status/job-status-graph.directive.js:119 -#: client/src/instance-groups/instance-groups.strings.js:26 -msgid "JOBS" -msgstr "TRABAJOS" - -#: client/lib/components/code-mirror/code-mirror.strings.js:12 -#: client/lib/services/base-string.service.js:70 -#: client/src/job-submission/job-submission.partial.html:173 -msgid "JSON" -msgstr "JSON" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:198 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:222 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:246 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:321 -msgid "JSON:" -msgstr "JSON:" - -#: client/features/jobs/jobs.strings.js:18 -#: client/src/workflow-results/workflow-results.controller.js:86 -msgid "Job" -msgstr "Tarea" - -#: client/features/output/output.strings.js:51 -#: client/features/templates/templates.strings.js:48 -#: client/src/job-submission/job-submission.partial.html:228 -#: client/src/templates/job_templates/job-template.form.js:190 -#: client/src/templates/job_templates/job-template.form.js:197 -msgid "Job Tags" -msgstr "Etiquetas de trabajo" - -#: client/features/jobs/jobs.strings.js:13 -#: client/features/output/output.strings.js:52 -#: client/features/templates/templates.strings.js:13 -#: client/src/templates/templates.list.js:61 -msgid "Job Template" -msgstr "Plantilla de trabajo" - -#: client/lib/models/models.strings.js:30 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:103 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:36 -#: client/src/projects/projects.form.js:303 -msgid "Job Templates" -msgstr "Plantillas de trabajo" - -#: client/features/output/output.strings.js:53 -#: client/features/templates/templates.strings.js:50 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:32 -#: client/src/job-submission/job-submission.partial.html:202 -#: client/src/templates/job_templates/job-template.form.js:47 -#: client/src/templates/job_templates/job-template.form.js:55 -msgid "Job Type" -msgstr "Tipo de trabajo" - -#: client/features/jobs/jobs.strings.js:19 -msgid "Job {{status}}. Click for details." -msgstr "Tarea {{status}}. Haga clic para obtener más información." - -#: client/lib/components/components.strings.js:69 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:30 -#: client/src/configuration/configuration.partial.html:22 -#: client/src/instance-groups/instance-groups.strings.js:52 -msgid "Jobs" -msgstr "Trabajos" - -#: client/features/output/output.strings.js:82 -#: client/features/templates/templates.strings.js:99 -#: client/src/workflow-results/workflow-results.controller.js:69 -msgid "KEY" -msgstr "CLAVE" - -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:61 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:154 -#: client/src/shared/smart-search/smart-search.partial.html:14 -msgid "Key" -msgstr "Clave" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:230 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:244 -msgid "Key Name:" -msgstr "Nombre de la clave:" - -#: client/src/credential-types/credential-types.list.js:31 -#: client/src/credentials/credentials.list.js:33 -msgid "Kind" -msgstr "Tipo" - -#: client/features/applications/applications.strings.js:31 -msgid "LAST MODIFIED" -msgstr "ÚLTIMO MODIFICADO" - -#: client/features/users/tokens/tokens.strings.js:38 -msgid "LAST USED" -msgstr "ÚLTIMO UTILIZADO" - -#: client/features/templates/templates.strings.js:30 -msgid "LAUNCH" -msgstr "LANZAMIENTO" - -#: client/src/job-submission/job-submission.partial.html:6 -msgid "LAUNCH JOB" -msgstr "EJECUTAR TAREA" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:157 -msgid "LDAP" -msgstr "LDAP" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:165 -msgid "LDAP 1 (Optional)" -msgstr "LDAP 1 (Opcional)" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:166 -msgid "LDAP 2 (Optional)" -msgstr "LDAP 2 (Opcional)" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:167 -msgid "LDAP 3 (Optional)" -msgstr "LDAP 3 (Opcional)" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:168 -msgid "LDAP 4 (Optional)" -msgstr "LDAP 4 (Opcional)" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:169 -msgid "LDAP 5 (Optional)" -msgstr "LDAP 5 (Opcional)" - -#: client/src/configuration/auth-form/configuration-auth.partial.html:17 -msgid "LDAP Server" -msgstr "Servidor LDAP" - -#: client/src/configuration/license.route.js:18 -#: client/src/license/license.route.js:18 -msgid "LICENSE" -msgstr "LICENCIA" - -#: client/features/output/output.strings.js:54 -#: client/src/templates/job_templates/job-template.form.js:224 -#: client/src/templates/job_templates/job-template.form.js:228 -#: client/src/templates/templates.list.js:43 -#: client/src/templates/workflows.form.js:72 -#: client/src/templates/workflows.form.js:76 -#: client/src/workflow-results/workflow-results.controller.js:51 -msgid "Labels" -msgstr "Etiquetas" - -#: client/features/templates/templates.strings.js:19 -msgid "Last Modified" -msgstr "Último modificado" - -#: client/src/access/rbac-multiselect/permissionsUsers.list.js:31 -#: client/src/users/users.form.js:36 client/src/users/users.list.js:37 -msgid "Last Name" -msgstr "Apellidos" - -#: client/features/templates/templates.strings.js:20 -msgid "Last Ran" -msgstr "Último ejecutado" - -#: client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.directive.js:56 -msgid "Last Sync" -msgstr "Última sincronización" - -#: client/src/projects/projects.list.js:56 -msgid "Last Updated" -msgstr "Última actualización" - -#: client/src/shared/form-generator.js:1727 -msgid "Launch" -msgstr "Ejecutar" - -#: client/src/management-jobs/card/card.partial.html:23 -msgid "Launch Management Job" -msgstr "Ejecutar trabajo de gestión" - -#: client/features/jobs/jobs.strings.js:12 -#: client/features/output/output.strings.js:55 -#: client/src/workflow-results/workflow-results.controller.js:48 -msgid "Launched By" -msgstr "Ejecutado por" - -#: client/features/templates/templates.strings.js:38 -#: client/src/job-submission/job-submission.partial.html:99 -msgid "" -"Launching this job requires the passwords listed below. Enter and confirm " -"each password before continuing." -msgstr "" -"Ejecutar esta tarea requiere las siguientes contraseñas. Ingrese y confirme " -"cada contraseña antes de continuar." - -#: client/features/users/tokens/tokens.strings.js:32 -msgid "" -"Leaving this field blank will result in the creation of a Personal Access " -"Token which is not linked to an Application." -msgstr "" -"Si deja este campo en blanco, se creará un token de acceso personal que no " -"está vinculado a una aplicación." - -#: client/features/credentials/legacy.credentials.js:350 -msgid "Legacy state configuration for does not exist" -msgstr "No existe la configuración del estado heredado de" - -#: client/src/configuration/configuration.partial.html:43 -#: client/src/license/license.controller.js:44 -#: client/src/license/license.partial.html:8 -msgid "License" -msgstr "Licencia" - -#: client/features/output/output.strings.js:56 -msgid "License Error" -msgstr "Error de licencia" - -#: client/src/license/license.partial.html:104 -msgid "License File" -msgstr "Fichero de licencia" - -#: client/src/license/license.partial.html:33 -msgid "License Key" -msgstr "Clave de licencia" - -#: client/src/license/license.controller.js:46 -msgid "License Management" -msgstr "Gestión de licencia" - -#: client/src/license/license.partial.html:21 -msgid "License Type" -msgstr "Tipo de licencia" - -#: client/features/output/output.strings.js:57 -#: client/features/templates/templates.strings.js:49 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:45 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:55 -#: client/src/job-submission/job-submission.partial.html:220 -#: client/src/templates/job_templates/job-template.form.js:159 -#: client/src/templates/job_templates/job-template.form.js:163 -msgid "Limit" -msgstr "Límite" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:240 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:255 -msgid "Limit to hosts having a tag:" -msgstr "Se limita a hosts que tengan una etiqueta:" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:242 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:257 -msgid "Limit to hosts using either key pair:" -msgstr "Se limita a hosts que utilicen un par de claves:" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:244 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:259 -msgid "Limit to hosts where the Name tag begins with" -msgstr "Se limita a hosts en los que la etiqueta Nombre comience con" - -#: client/src/scheduler/scheduler.strings.js:51 -msgid "Limited to first 10" -msgstr "Limitado a los primeros 10" - -#: client/src/shared/socket/socket.service.js:213 -msgid "Live events: attempting to connect to the server." -msgstr "Eventos en directo: intentando conectar al servidor." - -#: client/src/shared/socket/socket.service.js:217 -msgid "" -"Live events: connected. Pages containing job status information will " -"automatically update in real-time." -msgstr "" -"Eventos en directo: Páginas que contienen información del estado de un " -"trabajo serán actualizados automáticamente en tiempo real." - -#: client/src/shared/socket/socket.service.js:221 -msgid "Live events: error connecting to the server." -msgstr "Eventos en directo: error al conectar al servidor." - -#: client/src/shared/form-generator.js:2005 -msgid "Loading..." -msgstr "Cargando..." - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:133 -#: client/src/scheduler/scheduler.strings.js:26 -msgid "Local Time Zone" -msgstr "Huso horario local" - -#: client/src/configuration/system-form/configuration-system.controller.js:225 -msgid "Log aggregator test failed.
Detail:" -msgstr "Error en la prueba del agregador de registros.
Detalles:" - -#: client/src/configuration/system-form/configuration-system.controller.js:218 -msgid "Log aggregator test successful." -msgstr "Se realizó correctamente la prueba del agregador de registros." - -#: client/lib/components/components.strings.js:65 -msgid "Logged in as" -msgstr "Conectado como" - -#: client/src/configuration/system-form/configuration-system.controller.js:89 -msgid "Logging" -msgstr "Iniciando sesión" - -#: client/lib/components/components.strings.js:67 -msgid "Logout" -msgstr "Finalización de la sesión" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:35 -msgid "Low" -msgstr "Bajo" - -#: client/src/management-jobs/card/card.partial.html:6 -#: client/src/management-jobs/card/card.route.js:20 -msgid "MANAGEMENT JOBS" -msgstr "TAREAS DE GESTIÓN" - -#: client/src/instance-groups/instance-groups.strings.js:15 -msgid "MANUAL" -msgstr "MANUAL" - -#: client/features/output/output.strings.js:105 -msgid "MODULE" -msgstr "MÓDULO" - -#: client/features/portalMode/routes/portalModeTemplatesList.route.js:13 -msgid "MY VIEW" -msgstr "MI VISTA" - -#: client/src/credentials/credentials.form.js:67 -#: client/src/job-submission/job-submission.partial.html:356 -msgid "Machine" -msgstr "Máquina" - -#: client/features/output/output.strings.js:58 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:60 -msgid "Machine Credential" -msgstr "Credenciales de máquina" - -#: client/lib/components/components.strings.js:82 -msgid "Management Jobs" -msgstr "Trabajos de gestión" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:54 -#: client/src/projects/edit/projects-edit.controller.js:143 -#: client/src/projects/list/projects-list.controller.js:89 -msgid "Manual projects do not require an SCM update" -msgstr "Los proyectos manuales no necesitan una actualización del SCM" - -#: client/src/templates/job_templates/job-template.form.js:234 -msgid "Max 512 characters per label." -msgstr "Máx. 512 caracteres por etiqueta." - -#: client/src/login/loginModal/loginModal.partial.html:28 -msgid "Maximum per-user sessions reached. Please sign in." -msgstr "" -"Máximo número de sesiones por usuario alcanzado. Por favor inicie sesión." - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:29 -msgid "Medium" -msgstr "Medio" - -#: client/src/configuration/system-form/configuration-system.controller.js:90 -msgid "Misc. System" -msgstr "Miscelánea del sistema" - -#: client/src/templates/workflows.form.js:35 -msgid "" -"Missing Job Templates found in the Workflow Editor" -msgstr "" -"Plantillas de trabajo faltantes encontradas en el Editor de flujo de " -"trabajo" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:22 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:30 -msgid "Module" -msgstr "Módulo" - -#: client/features/output/output.strings.js:59 -msgid "Module Args" -msgstr "Argumentos del módulo" - -#: client/src/scheduler/scheduler.strings.js:37 -msgid "Mon" -msgstr "Lun" - -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:25 -msgid "Most recent job failed. Click to view jobs." -msgstr "Error en la tarea más reciente. Haga clic para ver las tareas." - -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:29 -msgid "Most recent job successful. Click to view jobs." -msgstr "" -"La tarea más reciente se completó correctamente. Haga clic para ver las " -"tareas." - -#: client/src/templates/survey-maker/surveys/init.factory.js:17 -msgid "Multiple Choice (multiple select)" -msgstr "Opciones de selección múltiple" - -#: client/src/templates/survey-maker/surveys/init.factory.js:16 -msgid "Multiple Choice (single select)" -msgstr "Selección múltiple" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:77 -msgid "Multiple Choice Options" -msgstr "Opciones de selección múltiple" - -#: client/features/portalMode/index.view.html:26 -msgid "My Jobs" -msgstr "Mis trabajos" - -#: client/lib/components/components.strings.js:71 -msgid "My View" -msgstr "Mi vista" - -#: client/features/applications/applications.strings.js:24 -msgid "NEW APPLICATION" -msgstr "NUEVA APLICACIÓN" - -#: client/features/credentials/credentials.strings.js:26 -msgid "NEW CREDENTIAL" -msgstr "NUEVA CREDENCIAL" - -#: client/src/credential-types/credential-types.form.js:16 -msgid "NEW CREDENTIAL TYPE" -msgstr "NUEVO TIPO DE CREDENCIAL" - -#: client/src/inventory-scripts/inventory-scripts.form.js:16 -msgid "NEW CUSTOM INVENTORY" -msgstr "NUEVO INVENTARIO PERSONALIZADO" - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:17 -msgid "NEW INVENTORY" -msgstr "NUEVO INVENTARIO" - -#: client/src/templates/job_templates/job-template.form.js:19 -msgid "NEW JOB TEMPLATE" -msgstr "NEVA PLANTILLA DE TRABAJO" - -#: client/src/notifications/notificationTemplates.form.js:16 -msgid "NEW NOTIFICATION TEMPLATE" -msgstr "NUEVA PLANTILLA DE NOTIFICACION" - -#: client/src/organizations/organizations.form.js:18 -msgid "NEW ORGANIZATION" -msgstr "NUEVA ORGANIZACION" - -#: client/src/projects/projects.form.js:17 -msgid "NEW PROJECT" -msgstr "NUEVO PROYECTO" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:10 -msgid "NEW SMART INVENTORY" -msgstr "NUEVO INVENTARIO INTELIGENTE" - -#: client/src/teams/teams.form.js:16 -msgid "NEW TEAM" -msgstr "NUEVO EQUIPO" - -#: client/src/users/users.form.js:16 -msgid "NEW USER" -msgstr "NUEVO USUARIO" - -#: client/src/templates/workflows.form.js:17 -msgid "NEW WORKFLOW JOB TEMPLATE" -msgstr "NUEVA PLANTILLA DE FLUJO DE TRABAJO" - -#: client/lib/services/base-string.service.js:64 -msgid "NEXT" -msgstr "SIGUIENTE" - -#: client/src/inventories-hosts/hosts/hosts.partial.html:38 -msgid "NO HOSTS HAVE BEEN CREATED" -msgstr "NO SE CREARON HOSTS" - -#: client/lib/components/components.strings.js:39 -msgid "NO OPTIONS AVAILABLE" -msgstr "NO HAY OPCIONES DISPONIBLES" - -#: client/src/login/loginModal/loginModal.partial.html:89 -msgid "NOTICE" -msgstr "AVISO" - -#: client/src/notifications/notificationTemplates.form.js:21 -msgid "NOTIFICATION TEMPLATE" -msgstr "PLANTILLA DE NOTIFICACIÓN" - -#: client/src/activity-stream/get-target-title.factory.js:26 -#: client/src/notifications/notificationTemplates.list.js:14 -msgid "NOTIFICATION TEMPLATES" -msgstr "PLANTILLAS DE NOTIFICACIÓN" - -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-notifications.route.js:9 -#: client/src/management-jobs/notifications/notification.route.js:46 -#: client/src/notifications/main.js:42 client/src/notifications/main.js:89 -#: client/src/notifications/notification-templates-list/add-notifications-action.partial.html:2 -msgid "NOTIFICATIONS" -msgstr "NOTIFICACIONES" - -#: client/features/output/output.strings.js:60 -#: client/src/credential-types/credential-types.form.js:27 -#: client/src/credential-types/credential-types.list.js:24 -#: client/src/credentials/credentials.form.js:32 -#: client/src/credentials/credentials.list.js:26 -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:14 -#: client/src/home/dashboard/lists/jobs/jobs-list.partial.html:13 -#: client/src/instance-groups/instance-groups.list.js:15 -#: client/src/inventories-hosts/hosts/host.list.js:61 -#: client/src/inventories-hosts/inventories/inventory.list.js:48 -#: client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.directive.js:55 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:32 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js:33 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:51 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:21 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:28 -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:45 -#: client/src/inventory-scripts/inventory-scripts.form.js:28 -#: client/src/inventory-scripts/inventory-scripts.list.js:20 -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:21 -#: client/src/notifications/notificationTemplates.form.js:32 -#: client/src/notifications/notificationTemplates.list.js:32 -#: client/src/notifications/notifications.list.js:27 -#: client/src/organizations/organizations.form.js:26 -#: client/src/projects/projects.form.js:30 -#: client/src/projects/projects.list.js:37 -#: client/src/scheduler/scheduled-jobs.list.js:31 -#: client/src/scheduler/scheduler.strings.js:21 -#: client/src/scheduler/schedules.list.js:41 -#: client/src/teams/teams.form.js:127 client/src/teams/teams.form.js:28 -#: client/src/teams/teams.list.js:23 -#: client/src/templates/job_templates/job-template.form.js:34 -#: client/src/templates/templates.list.js:24 -#: client/src/templates/workflows.form.js:42 -#: client/src/users/users.form.js:144 client/src/users/users.form.js:170 -#: client/src/users/users.form.js:196 -msgid "Name" -msgstr "Nombre" - -#: client/src/credentials/credentials.form.js:71 -msgid "Network" -msgstr "Red" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:81 -msgid "New Group" -msgstr "Nuevo grupo" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:130 -msgid "New Host" -msgstr "Nuevo host" - -#: client/src/users/add/users-add.controller.js:92 -msgid "New user successfully created!" -msgstr "¡Nuevo usuario creado correctamente!" - -#: client/src/scheduler/scheduled-jobs.list.js:51 -#: client/src/scheduler/schedules.list.js:51 -msgid "Next Run" -msgstr "Siguiente ejecución" - -#: client/src/credentials/credentials.list.js:21 -msgid "No Credentials Have Been Created" -msgstr "Ningún credencial ha sido creado" - -#: client/features/templates/templates.strings.js:62 -#: client/src/job-submission/lists/credential/job-sub-cred-list.controller.js:44 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js:15 -msgid "No Credentials Matching This Type Have Been Created" -msgstr "No se crearon credenciales que coincidan con este tipo" - -#: client/features/output/host-event/host-event-codemirror.partial.html:3 -msgid "No JSON data returned by the module" -msgstr "El módulo no arrojó datos JSON" - -#: client/src/projects/projects.list.js:20 -msgid "No Projects Have Been Created" -msgstr "No se crearon proyectos" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:50 -msgid "No Remediation Playbook Available" -msgstr "No hay un playbook de reparación disponible" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:199 -#: client/src/projects/list/projects-list.controller.js:186 -msgid "No SCM Configuration" -msgstr "Ninguna configuración SCM" - -#: client/src/projects/factories/get-project-tool-tip.factory.js:9 -msgid "No SCM updates have run for this project" -msgstr "Ninguna actualización SCM ha sido ejecutada para este proyecto" - -#: client/src/access/rbac-multiselect/permissionsTeams.list.js:17 -msgid "No Teams exist" -msgstr "No existe ningún equipo" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:191 -#: client/src/projects/list/projects-list.controller.js:150 -msgid "No Updates Available" -msgstr "No existen actualizaciones disponibles" - -#: client/src/access/rbac-multiselect/permissionsUsers.list.js:18 -msgid "No Users exist" -msgstr "No existe ningún usuario" - -#: client/features/templates/templates.strings.js:33 -msgid "No credentials selected" -msgstr "No se seleccionaron credenciales" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:63 -msgid "No data is available. There are no issues to report." -msgstr "No hay datos disponibles. No hay problemas para informar." - -#: client/src/license/license.controller.js:41 -msgid "No file selected." -msgstr "Ningún fichero seleccionado." - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:69 -msgid "No hosts with failures. Click for details." -msgstr "No hay hosts con fallos. Haga clic para obtener más información." - -#: client/features/templates/templates.strings.js:34 -msgid "No inventory selected" -msgstr "Sin inventario seleccionado" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:52 -msgid "No inventory sync failures. Click for details." -msgstr "" -"No hay fallos en la sincronización de inventario. Haga clic para obtener más" -" información." - -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:16 -msgid "No job data" -msgstr "No hay datos de tareas" - -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:75 -msgid "No job data available." -msgstr "No hay datos de tareas disponibles." - -#: client/src/inventories-hosts/inventories/related/groups/factories/get-hosts-status-msg.factory.js:22 -msgid "No job failures" -msgstr "No hay fallos en las tareas" - -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:51 -msgid "No job templates were recently used." -msgstr "Ninguna plantilla de trabajo fue recientemente utilizada." - -#: client/src/home/dashboard/lists/jobs/jobs-list.partial.html:44 -msgid "No jobs were recently run." -msgstr "Ningún trabajo ha sido recientemente ejecutado." - -#: client/src/teams/teams.form.js:124 client/src/users/users.form.js:193 -msgid "No permissions have been granted" -msgstr "Ningún permiso concedido" - -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:17 -msgid "No recent job data available for this host." -msgstr "No hay datos de tareas recientes disponibles para este host." - -#: client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.directive.js:75 -msgid "No recent job data available for this inventory." -msgstr "No hay datos de tareas recientes disponibles para este inventario." - -#: client/src/notifications/notification-templates-list/list.controller.js:86 -msgid "No recent notifications." -msgstr "No hay notificaciones recientes" - -#: client/src/inventories-hosts/hosts/hosts.partial.html:36 -#: client/src/shared/form-generator.js:1899 -#: client/src/shared/list-generator/list-generator.factory.js:240 -msgid "No records matched your search." -msgstr "No existe registros que coincidan con su búsqueda." - -#: client/features/output/output.strings.js:106 -msgid "No result found" -msgstr "No se encontraron resultados" - -#: client/src/scheduler/scheduled-jobs.list.js:16 -msgid "No schedules exist" -msgstr "No existen planificaciones" - -#: client/src/job-submission/job-submission.partial.html:348 -#: client/src/job-submission/job-submission.partial.html:353 -msgid "None selected" -msgstr "Ninguno seleccionado" - -#: client/src/users/add/users-add.controller.js:10 -#: client/src/users/edit/users-edit.controller.js:10 -#: client/src/users/list/users-list.controller.js:10 -msgid "Normal User" -msgstr "Usuario normal" - -#: client/features/output/output.strings.js:36 -#: client/src/workflow-results/workflow-results.controller.js:56 -msgid "Not Finished" -msgstr "No finalizado" - -#: client/features/output/output.strings.js:37 -#: client/src/workflow-results/workflow-results.controller.js:57 -msgid "Not Started" -msgstr "No iniciado" - -#: client/src/projects/list/projects-list.controller.js:91 -msgid "Not configured for SCM" -msgstr "No configurado para SCM" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:59 -msgid "Not configured for inventory sync." -msgstr "No configurado para la sincronización de inventario." - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-disassociate.partial.html:25 -msgid "" -"Note that only hosts directly in this group can be disassociated. Hosts in " -"sub-groups must be disassociated directly from the sub-group level that they" -" belong." -msgstr "" -"Tenga en cuenta que solo se pueden desasociar los hosts asociados " -"directamente a este grupo. Los hosts en subgrupos deben ser desasociados del" -" nivel de subgrupo al que pertenecen." - -#: client/src/notifications/notificationTemplates.form.js:288 -#: client/src/notifications/notificationTemplates.form.js:289 -#: client/src/notifications/notificationTemplates.form.js:472 -#: client/src/notifications/notificationTemplates.form.js:473 -msgid "Notification Color" -msgstr "Color de notificación" - -#: client/src/notifications/notification-templates-list/list.controller.js:140 -msgid "Notification Failed." -msgstr "Notificación fallida" - -#: client/src/notifications/notificationTemplates.form.js:277 -msgid "Notification Label" -msgstr "Etiqueta de Notificación" - -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:31 -msgid "Notification Templates" -msgstr "Plantillas de notificación" - -#: client/lib/components/components.strings.js:81 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:20 -#: client/src/management-jobs/notifications/notification.route.js:21 -#: client/src/notifications/notifications.list.js:17 -msgid "Notifications" -msgstr "Notificación" - -#: client/src/notifications/notificationTemplates.form.js:302 -msgid "Notify Channel" -msgstr "Notificar canal" - -#: client/lib/services/base-string.service.js:68 -#: client/src/inventories-hosts/hosts/hosts.partial.html:55 -#: client/src/job-submission/job-submission.partial.html:269 -#: client/src/partials/survey-maker-modal.html:27 -#: client/src/shared/form-generator.js:545 -#: client/src/shared/form-generator.js:780 -#: client/src/shared/generator-helpers.js:554 -msgid "OFF" -msgstr "OFF" - -#: client/lib/services/base-string.service.js:63 -msgid "OK" -msgstr "Aceptar" - -#: client/lib/services/base-string.service.js:67 -#: client/src/inventories-hosts/hosts/hosts.partial.html:54 -#: client/src/job-submission/job-submission.partial.html:267 -#: client/src/partials/survey-maker-modal.html:26 -#: client/src/shared/form-generator.js:541 -#: client/src/shared/form-generator.js:778 -#: client/src/shared/generator-helpers.js:550 -msgid "ON" -msgstr "ON" - -#: client/lib/components/components.strings.js:10 -msgid "OPTIONS" -msgstr "OPCIONES" - -#: client/features/applications/applications.strings.js:30 -msgid "ORG" -msgstr "ORG" - -#: client/src/activity-stream/get-target-title.factory.js:29 -#: client/src/organizations/list/organizations-list.partial.html:6 -#: client/src/organizations/main.js:51 -msgid "ORGANIZATIONS" -msgstr "ORGANIZACIONES" - -#: client/src/scheduler/scheduler.strings.js:45 -msgid "Occurrences" -msgstr "Ocurrencias" - -#: client/src/workflow-results/workflow-results.controller.js:65 -msgid "On Fail" -msgstr "En falla" - -#: client/features/templates/templates.strings.js:101 -msgid "On Failure" -msgstr "Con error" - -#: client/features/templates/templates.strings.js:100 -#: client/src/workflow-results/workflow-results.controller.js:64 -msgid "On Success" -msgstr "Con éxito" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:157 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:162 -msgid "Only Group By" -msgstr "Agrupar solo por" - -#: client/src/credentials/credentials.form.js:379 -msgid "" -"OpenStack domains define administrative boundaries. It is only needed for " -"Keystone v3 authentication URLs. Common scenarios include:" -msgstr "" -"Los dominios OpenStack definen los límites administrativos. Sólo es " -"necesario para las direcciones URLs en el uso de autenticación para KeyStone" -" v3. Los escenarios más habituales:" - -#: client/src/templates/job_templates/job-template.form.js:230 -#: client/src/templates/workflows.form.js:78 -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." - -#: client/src/notifications/notificationTemplates.form.js:453 -#: client/src/partials/logviewer.html:7 -#: client/src/templates/job_templates/job-template.form.js:275 -#: client/src/templates/workflows.form.js:96 -msgid "Options" -msgstr "Opciones" - -#: client/src/credentials/credentials.form.js:46 -#: client/src/credentials/credentials.form.js:53 -#: client/src/inventories-hosts/inventories/inventory.list.js:61 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:33 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:41 -#: client/src/inventory-scripts/inventory-scripts.form.js:40 -#: client/src/inventory-scripts/inventory-scripts.list.js:27 -#: client/src/notifications/notificationTemplates.form.js:44 -#: client/src/projects/projects.form.js:42 -#: client/src/projects/projects.form.js:48 client/src/teams/teams.form.js:40 -#: client/src/teams/teams.list.js:30 client/src/templates/workflows.form.js:55 -#: client/src/templates/workflows.form.js:61 client/src/users/users.form.js:42 -msgid "Organization" -msgstr "Organización" - -#: client/lib/components/components.strings.js:77 -#: client/lib/models/models.strings.js:35 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:136 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:64 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:32 -#: client/src/users/users.form.js:134 -msgid "Organizations" -msgstr "Organizaciones" - -#: client/features/templates/templates.strings.js:27 -#: client/src/job-submission/job-submission.partial.html:19 -msgid "Other Prompts" -msgstr "Otros avisos" - -#: client/src/credentials/credentials.form.js:79 -msgid "Others (Cloud Providers)" -msgstr "Otros (Proveedores Cloud)" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:317 -msgid "" -"Override variables found in azure_rm.ini and used by the inventory update " -"script. For a detailed description of these variables" -msgstr "" -"Variables de sobreescritura halladas en azure_rm.ini y utilizadas por el " -"script de actualización del inventario. Para acceder a una descripción " -"detallada de estas variables" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:283 -msgid "" -"Override variables found in cloudforms.ini and used by the inventory update script. For an example variable configuration\n" -" \n" -" view cloudforms.ini in the Ansible github repo. Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax." -msgstr "" -"Variables de sobreescritura halladas en cloudforms.ini y utilizadas en el script de actualización del inventario. Para acceder a un ejemplo de configuración variable, \n" -" \n" -" vea cloudforms.ini en el repositorio github de Ansible. Ingrese variables de inventario con sintaxis JSON o YAML. Utilice el botón de selección para alternar entre los dos. Consulte la documentación de Ansible Tower para acceder a ejemplos de sintaxis." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:218 -msgid "" -"Override variables found in ec2.ini and used by the inventory update script." -" For a detailed description of these variables" -msgstr "" -"Variables de sobrescritura halladas en ec2.ini y utilizadas en el script de " -"actualización del inventario. Para acceder a una descripción detallada de " -"estas variables" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:300 -msgid "" -"Override variables found in foreman.ini and used by the inventory update script. For an example variable configuration\n" -" \n" -" view foreman.ini in the Ansible github repo. Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax." -msgstr "" -"Variables de sobreescritura halladas en foreman.ini y utilizadas en el script de actualización del inventario. Para acceder a un ejemplo de configuración de variable\n" -" \n" -" vea foreman.ini en el repositorio github de Ansible. Ingrese variables de inventario con sintaxis JSON o YAML. Utilice el botón de selección para alternar entre los dos. Consulte la documentación de Ansible Tower para acceder a ejemplos de sintaxis." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:266 -msgid "" -"Override variables found in openstack.yml and used by the inventory update script. For an example variable configuration\n" -" \n" -" view openstack.yml in the Ansible github repo. Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax." -msgstr "" -"Variables de sobreescritura halladas en openstack.yml y utilizadas en el script de actualización del inventario. Para acceder a un ejemplo de configuración de variable,\n" -" Ingrese variables de inventario con la sintaxis JSON o YAML. Utilice el botón de opción para alternar entre los dos. Consulte la documentación de Ansible Tower para acceder a ejemplos de sintaxis." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:242 -msgid "" -"Override variables found in vmware.ini and used by the inventory update " -"script. For a detailed description of these variables" -msgstr "" -"Variables de sobreescritura halladas en vmware.ini y utilizadas en el script" -" de actualización del inventario. Para acceder a un ejemplo de configuración" -" de estas variables" - -#: client/features/output/output.strings.js:61 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:352 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:357 -msgid "Overwrite" -msgstr "Anular" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:364 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:369 -msgid "Overwrite Variables" -msgstr "Anular variables" - -#: client/features/output/output.strings.js:62 -msgid "Overwrite Vars" -msgstr "Sobrescribir variables" - -#: client/src/credentials/credentials.list.js:40 -msgid "Owners" -msgstr "Propietarios" - -#: client/src/login/loginModal/loginModal.partial.html:68 -msgid "PASSWORD" -msgstr "CONTRASEÑA" - -#: client/features/credentials/legacy.credentials.js:117 -msgid "PERMISSIONS" -msgstr "PERMISOS" - -#: client/features/output/output.strings.js:103 -msgid "PLAY" -msgstr "REPRODUCIR" - -#: client/src/partials/job-template-details.html:2 -msgid "PLAYBOOK" -msgstr "PLAYBOOK" - -#: client/src/partials/survey-maker-modal.html:45 -msgid "PLEASE ADD A SURVEY PROMPT." -msgstr "AGREGUE UN AVISO DE ENCUESTA." - -#: client/src/organizations/list/organizations-list.partial.html:37 -#: client/src/shared/form-generator.js:1905 -#: client/src/shared/list-generator/list-generator.factory.js:248 -msgid "PLEASE ADD ITEMS TO THIS LIST" -msgstr "Por favor añada elementos a la lista" - -#: client/src/partials/survey-maker-modal.html:43 -msgid "PREVIEW" -msgstr "VISTA PREVIA" - -#: client/src/partials/job-template-details.html:2 -msgid "PROJECT" -msgstr "PROYECTO" - -#: client/src/activity-stream/get-target-title.factory.js:8 -#: client/src/organizations/linkout/organizations-linkout.route.js:196 -#: client/src/organizations/list/organizations-list.controller.js:73 -#: client/src/projects/main.js:92 client/src/projects/projects.list.js:14 -#: client/src/projects/projects.list.js:15 -msgid "PROJECTS" -msgstr "PROYECTOS" - -#: client/features/templates/templates.strings.js:26 -msgid "PROMPT" -msgstr "AVISO" - -#: client/src/shared/paginate/paginate.partial.html:33 -msgid "Page" -msgstr "Página" - -#: client/src/notifications/notificationTemplates.form.js:232 -msgid "Pagerduty subdomain" -msgstr "Subdominio Pagerduty" - -#: client/src/templates/job_templates/job-template.form.js:363 -msgid "" -"Pass extra command line variables to the playbook. Provide key/value pairs " -"using either YAML or JSON. Refer to the Ansible Tower documentation for " -"example syntax." -msgstr "" -"Traslade variables de línea de comando adicionales al manual. Proporcione " -"claves/pares de valores utilizando YAML o JSON. Consulte la documentación de" -" Ansible Tower para acceder a ejemplos de sintaxis." - -#: client/src/templates/workflows.form.js:89 -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 Tower documentation for" -" example syntax." -msgstr "" -"Traslade variables de línea de comando adicionales al manual. Este es el " -"parámetro de línea de comando -e o --extra-vars para el manual de Ansible. " -"Proporcione claves/pares de valores utilizando YAML o JSON. Consulte la " -"documentación de Ansible Tower para acceder a ejemplos de sintaxis." - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:139 -msgid "" -"Pass extra command line variables. This is the %s or %s command line " -"parameter for %s. Provide key/value pairs using either YAML or JSON." -msgstr "" -"Transmitir variables adicionales en la línea de comandos a los playbook. " -"Este es el parámetro de línea de comandos %s o %s para %s. Introduzca pareja" -" de clave/valor utilizando la sintaxis YAML o JSON." - -#: client/src/credentials/credentials.form.js:226 -#: client/src/credentials/factories/become-method-change.factory.js:21 -#: client/src/credentials/factories/become-method-change.factory.js:40 -#: client/src/credentials/factories/become-method-change.factory.js:48 -#: client/src/credentials/factories/become-method-change.factory.js:68 -#: client/src/credentials/factories/become-method-change.factory.js:78 -#: client/src/credentials/factories/become-method-change.factory.js:88 -#: client/src/credentials/factories/kind-change.factory.js:105 -#: client/src/credentials/factories/kind-change.factory.js:125 -#: client/src/credentials/factories/kind-change.factory.js:135 -#: client/src/credentials/factories/kind-change.factory.js:145 -#: client/src/credentials/factories/kind-change.factory.js:30 -#: client/src/credentials/factories/kind-change.factory.js:78 -#: client/src/credentials/factories/kind-change.factory.js:97 -#: client/src/job-submission/job-submission.partial.html:104 -#: client/src/notifications/shared/type-change.service.js:30 -#: client/src/templates/survey-maker/surveys/init.factory.js:15 -#: client/src/users/users.form.js:70 -msgid "Password" -msgstr "Contraseña" - -#: client/src/credentials/factories/kind-change.factory.js:58 -msgid "Password (API Key)" -msgstr "Contraseña (clave API)" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:20 -msgid "Past 24 Hours" -msgstr "Últimas 24 horas" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:15 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:26 -msgid "Past Month" -msgstr "Mes pasado" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:23 -msgid "Past Week" -msgstr "Semana pasada" - -#: client/src/credentials/factories/become-method-change.factory.js:29 -#: client/src/credentials/factories/kind-change.factory.js:86 -msgid "" -"Paste the contents of the PEM file associated with the service account " -"email." -msgstr "" -"Pegue el contenido del fichero PEM asociado al correo de la cuenta de " -"servicio." - -#: client/src/credentials/factories/kind-change.factory.js:51 -msgid "Paste the contents of the SSH private key file." -msgstr "Pegue el contenido del fichero de la clave privada SSH." - -#: client/src/credentials/factories/kind-change.factory.js:26 -msgid "Paste the contents of the SSH private key file.%s or click to close%s" -msgstr "" -"Pegue el contenido del fichero de la clave privada SSH. %s o pulse cerrar%s" - -#: client/src/inventories-hosts/inventories/inventory.list.js:129 -msgid "Pending Delete" -msgstr "Eliminación pendiente" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:8 -msgid "Period" -msgstr "Periodo" - -#: client/src/projects/add/projects-add.controller.js:32 -#: client/src/users/add/users-add.controller.js:44 -msgid "Permission Error" -msgstr "Error de permiso" - -#: client/features/credentials/credentials.strings.js:14 -#: client/features/credentials/legacy.credentials.js:63 -#: client/src/credentials/credentials.form.js:439 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:104 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:106 -#: client/src/projects/projects.form.js:247 client/src/teams/teams.form.js:120 -#: client/src/templates/job_templates/job-template.form.js:402 -#: client/src/templates/workflows.form.js:139 -#: client/src/users/users.form.js:189 -msgid "Permissions" -msgstr "Permisos" - -#: client/features/users/tokens/tokens.strings.js:41 -msgid "Personal Access Token" -msgstr "Token de acceso personal" - -#: client/features/output/output.strings.js:63 -#: client/src/shared/form-generator.js:1085 -#: client/src/templates/job_templates/job-template.form.js:107 -#: client/src/templates/job_templates/job-template.form.js:115 -msgid "Playbook" -msgstr "Playbook" - -#: client/src/projects/projects.form.js:90 -msgid "Playbook Directory" -msgstr "Directorio de playbook" - -#: client/features/templates/templates.strings.js:60 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:52 -msgid "Playbook Run" -msgstr "Ejecución de playbook" - -#: client/features/output/output.strings.js:91 -msgid "Plays" -msgstr "Reproduccciones" - -#: client/lib/components/components.strings.js:108 -msgid "Please add items to this list." -msgstr "Añada elementos a esta lista." - -#: client/src/users/users.form.js:128 -msgid "Please add user to an Organization." -msgstr "Por favor añada un usuario a su organización." - -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:100 -msgid "Please assign roles to the selected resources" -msgstr "Por favor asigne funciones a los recursos seleccionados" - -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:60 -msgid "Please assign roles to the selected users/teams" -msgstr "Por favor asigne funciones a los usuarios/equipos seleccionados" - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:210 -msgid "" -"Please check the server and make sure the directory exists and file " -"permissions are set correctly." -msgstr "" -"Verifique el servidor y asegúrese de que el directorio exista y que los " -"permisos de archivos estén configurados de manera correcta." - -#: client/src/license/license.partial.html:84 -msgid "" -"Please click the button below to visit Ansible's website to get a Tower " -"license key." -msgstr "" -"Por favor pulse sobre el siguiente botón para visitar la página web de " -"Ansible para obtener una clave de licencia Tower." - -#: client/src/inventories-hosts/inventory-hosts.strings.js:40 -msgid "Please click the icon to edit the host filter." -msgstr "Haga clic en el icono para editar el filtro del host." - -#: client/features/templates/templates.strings.js:110 -msgid "Please click the start button to build your workflow." -msgstr "Haga clic en el botón de inicio para desarrollar su flujo de trabajo." - -#: client/src/shared/form-generator.js:868 -#: client/src/shared/form-generator.js:963 -msgid "" -"Please enter a URL that begins with ssh, http or https. The URL may not " -"contain the '@' character." -msgstr "" -"Por favor introduzca una URL que inicie por ssh, http o https. La URL no " -"puede contener el caracter '@'." - -#: client/src/shared/form-generator.js:1178 -msgid "Please enter a number greater than %d and less than %d." -msgstr "Por favor introduzca un número mayor que %d y menor que %d." - -#: client/src/shared/form-generator.js:1180 -msgid "Please enter a number greater than %d." -msgstr "Por favor introduzca un número mayor que %d." - -#: client/src/shared/form-generator.js:1172 -msgid "Please enter a number." -msgstr "Por favor introduzca un número." - -#: client/features/templates/templates.strings.js:39 -#: client/src/job-submission/job-submission.partial.html:112 -#: client/src/job-submission/job-submission.partial.html:126 -#: client/src/job-submission/job-submission.partial.html:140 -#: client/src/job-submission/job-submission.partial.html:154 -#: client/src/login/loginModal/loginModal.partial.html:78 -msgid "Please enter a password." -msgstr "Por favor introduzca una contraseña." - -#: client/src/login/loginModal/loginModal.partial.html:58 -msgid "Please enter a username." -msgstr "Por favor introduzca un nombre de usuario." - -#: client/src/shared/form-generator.js:858 -#: client/src/shared/form-generator.js:953 -msgid "Please enter a valid email address." -msgstr "Por favor introduzca una dirección de correo válida." - -#: client/lib/components/components.strings.js:15 -#: client/src/shared/form-generator.js:1023 -#: client/src/shared/form-generator.js:853 -#: client/src/shared/form-generator.js:948 -msgid "Please enter a value." -msgstr "Por favor introduzca un valor." - -#: client/src/job-submission/job-submission.partial.html:289 -#: client/src/job-submission/job-submission.partial.html:294 -#: client/src/job-submission/job-submission.partial.html:305 -#: client/src/job-submission/job-submission.partial.html:311 -#: client/src/job-submission/job-submission.partial.html:317 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:14 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:19 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:30 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:36 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:42 -msgid "Please enter an answer between" -msgstr "Ingrese una respuesta entre" - -#: client/features/templates/templates.strings.js:59 -#: client/src/job-submission/job-submission.partial.html:316 -msgid "Please enter an answer that is a decimal number." -msgstr "Ingrese una respuesta que sea un número decimal." - -#: client/features/templates/templates.strings.js:58 -#: client/src/job-submission/job-submission.partial.html:310 -msgid "Please enter an answer that is a valid integer." -msgstr "Ingrese una respuesta que sea un valor entero válido." - -#: client/features/templates/templates.strings.js:56 -#: client/src/job-submission/job-submission.partial.html:288 -#: client/src/job-submission/job-submission.partial.html:293 -#: client/src/job-submission/job-submission.partial.html:304 -#: client/src/job-submission/job-submission.partial.html:309 -#: client/src/job-submission/job-submission.partial.html:315 -msgid "Please enter an answer." -msgstr "Ingrese una respuesta." - -#: client/src/inventories-hosts/inventory-hosts.strings.js:46 -msgid "Please enter at least one search term to create a new Smart Inventory." -msgstr "" -"Introduzca al menos un término de búsqueda para crear un nuevo inventario " -"inteligente." - -#: client/features/templates/templates.strings.js:111 -msgid "Please hover over a template for additional options." -msgstr "Coloque el cursos sobre la plantilla para opciones adicionales." - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:169 -msgid "Please input a number greater than 1." -msgstr "Introducir un número superior a 1." - -#: client/src/scheduler/scheduler.strings.js:47 -msgid "Please provide a valid date." -msgstr "Provea una fecha válida." - -#: client/src/scheduler/scheduler.strings.js:30 -msgid "Please provide a value between 1 and 999." -msgstr "Introduzca un valor entre 1 y 9999." - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:54 -msgid "Please save before adding a survey to this job template." -msgstr "Guarde antes de agregar una encuesta a esta plantilla de trabajo." - -#: client/src/templates/workflows/add-workflow/workflow-add.controller.js:51 -msgid "Please save before adding a survey to this workflow." -msgstr "Guarde antes de agregar una encuesta a este flujo de trabajo." - -#: client/src/notifications/notifications.list.js:15 -msgid "Please save before adding notifications." -msgstr "Guarde antes de agregar notificaciones." - -#: client/src/organizations/organizations.form.js:80 -#: client/src/teams/teams.form.js:72 -msgid "Please save before adding users." -msgstr "Guarde antes de agregar usuarios." - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:100 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:102 -#: client/src/projects/projects.form.js:239 client/src/teams/teams.form.js:116 -#: client/src/templates/job_templates/job-template.form.js:395 -#: client/src/templates/workflows.form.js:132 -msgid "Please save before assigning permissions." -msgstr "Guarde antes de asignar permisos." - -#: client/src/users/users.form.js:126 client/src/users/users.form.js:185 -msgid "Please save before assigning to organizations." -msgstr "Guarde antes de asignar a organizaciones" - -#: client/src/users/users.form.js:154 -msgid "Please save before assigning to teams." -msgstr "Guarde antes de asignar a equipos." - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:147 -msgid "Please save before creating groups." -msgstr "Guarde antes de crear grupos." - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:156 -msgid "Please save before creating hosts." -msgstr "Guarde antes de crear hosts." - -#: client/src/inventories-hosts/hosts/host.form.js:112 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:86 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:111 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:112 -msgid "Please save before defining groups." -msgstr "Guarde antes de definir grupos." - -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:94 -msgid "Please save before defining hosts." -msgstr "Guarde antes de definir hosts." - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:165 -msgid "Please save before defining inventory sources." -msgstr "Guarde antes de definir fuentes de inventario." - -#: client/src/templates/workflows/add-workflow/workflow-add.controller.js:50 -msgid "Please save before defining the workflow graph." -msgstr "Guarde antes de definir un gráfico de flujo de trabajo." - -#: client/src/inventories-hosts/hosts/host.form.js:121 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:120 -msgid "Please save before viewing Insights." -msgstr "Guarde antes de ver Insights." - -#: client/src/inventories-hosts/hosts/host.form.js:105 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:104 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:105 -msgid "Please save before viewing facts." -msgstr "Guarde antes de ver eventos." - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:146 -msgid "Please save before viewing hosts." -msgstr "Guarde antes de ver los hosts." - -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:26 -msgid "Please select Users / Teams from the lists below." -msgstr "Por favor seleccione Usuarios / Equipos de la siguiente lista." - -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:29 -msgid "Please select Users from the list below." -msgstr "Por favor seleccione Usuarios de la siguiente lista.." - -#: client/src/shared/form-generator.js:1213 -msgid "Please select a number between" -msgstr "Por favor seleccione un número entre" - -#: client/src/shared/form-generator.js:1209 -msgid "Please select a number." -msgstr "Por favor seleccione un número." - -#: client/features/templates/templates.strings.js:57 -msgid "Please select a value" -msgstr "Seleccione un valor." - -#: client/src/shared/form-generator.js:1097 -#: client/src/shared/form-generator.js:1169 -#: client/src/shared/form-generator.js:1290 -#: client/src/shared/form-generator.js:1398 -msgid "Please select a value." -msgstr "Por favor seleccione un valor." - -#: client/src/templates/job_templates/job-template.form.js:77 -msgid "Please select an Inventory or check the Prompt on launch option." -msgstr "" -"Por favor seleccione un inventario o seleccione la opción Preguntar al " -"ejecutar." - -#: client/src/inventories-hosts/inventory-hosts.strings.js:39 -msgid "Please select an organization before editing the host filter." -msgstr "Seleccione una organización antes de editar el filtro del host." - -#: client/src/shared/form-generator.js:1206 -msgid "Please select at least one value." -msgstr "Por favor seleccione al menos un valor." - -#: client/src/scheduler/scheduler.strings.js:43 -msgid "Please select one or more days." -msgstr "Seleccione uno o más días." - -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:30 -msgid "Please select resources from the lists below." -msgstr "Por favor seleccione recursos de la lista siguiente." - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:47 -msgid "Populate the hosts for this inventory by using a search filter." -msgstr "" -"Complete los hosts de este inventario por medio del filtro de búsqueda." - -#: client/src/notifications/shared/type-change.service.js:29 -msgid "Port" -msgstr "Puerto" - -#: client/features/templates/templates.strings.js:29 -msgid "Preview" -msgstr "Vista previa" - -#: client/src/credentials/credentials.form.js:257 -#: client/src/credentials/factories/kind-change.factory.js:21 -#: client/src/credentials/factories/kind-change.factory.js:45 -msgid "Private Key" -msgstr "Clave privada" - -#: client/features/templates/templates.strings.js:42 -#: client/src/credentials/credentials.form.js:264 -#: client/src/job-submission/job-submission.partial.html:118 -msgid "Private Key Passphrase" -msgstr "Frase de contraseña para la clave privada" - -#: client/src/credentials/credentials.form.js:279 -#: client/src/credentials/credentials.form.js:283 -msgid "Privilege Escalation" -msgstr "Elevación de privilegios" - -#: client/features/templates/templates.strings.js:43 -#: client/src/credentials/credentials.form.js:305 -#: client/src/job-submission/job-submission.partial.html:132 -msgid "Privilege Escalation Password" -msgstr "Contraseña para la elevación de privilegios" - -#: client/src/credentials/credentials.form.js:295 -msgid "Privilege Escalation Username" -msgstr "Usuario para la elevación de privilegios" - -#: client/features/jobs/jobs.strings.js:15 -#: client/features/output/output.strings.js:64 -#: client/features/templates/templates.strings.js:17 -#: client/src/credentials/factories/become-method-change.factory.js:30 -#: client/src/credentials/factories/kind-change.factory.js:87 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:93 -#: client/src/templates/job_templates/job-template.form.js:100 -#: client/src/templates/job_templates/job-template.form.js:91 -msgid "Project" -msgstr "Proyecto" - -#: client/src/credentials/factories/become-method-change.factory.js:53 -#: client/src/credentials/factories/kind-change.factory.js:110 -msgid "Project (Tenant Name)" -msgstr "Proyecto (Nombre del inquilino [Tenant])" - -#: client/src/projects/projects.form.js:76 -#: client/src/projects/projects.form.js:84 -msgid "Project Base Path" -msgstr "Ruta base del proyecto" - -#: client/src/credentials/credentials.form.js:365 -msgid "Project Name" -msgstr "Nombre del proyecto" - -#: client/src/projects/projects.form.js:101 -msgid "Project Path" -msgstr "Ruta del proyecto" - -#: client/features/templates/templates.strings.js:103 -#: client/src/workflow-results/workflow-results.controller.js:67 -msgid "Project Sync" -msgstr "Sincronización del proyecto" - -#: client/src/home/dashboard/counts/dashboard-counts.directive.js:66 -msgid "Project Sync Failures" -msgstr "Errores de sincronización del proyecto" - -#: client/src/projects/list/projects-list.controller.js:197 -msgid "Project lookup failed. GET returned:" -msgstr "La búsqueda del proyecto ha fallado. GET ha devuelto:" - -#: client/lib/components/components.strings.js:72 -#: client/lib/models/models.strings.js:40 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:115 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:47 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:33 -#: client/src/home/dashboard/counts/dashboard-counts.directive.js:61 -#: client/src/organizations/linkout/organizations-linkout.route.js:207 -#: client/src/organizations/linkout/organizations-linkout.route.js:209 -msgid "Projects" -msgstr "Proyectos" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:18 -msgid "Promote group" -msgid_plural "Promote groups" -msgstr[0] "Promover grupo" -msgstr[1] "Promover grupos" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:43 -msgid "Promote groups" -msgstr "Promover grupos" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:32 -msgid "Promote groups and hosts" -msgstr "Promover grupos y hosts" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:20 -msgid "Promote host" -msgid_plural "Promote hosts" -msgstr[0] "Promover host" -msgstr[1] "Promover hosts" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:54 -msgid "Promote hosts" -msgstr "Promover hosts" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:10 -msgid "Promote {{ group }} and {{ host }}" -msgstr "Promover {{ group }} y {{ host }}" - -#: client/src/scheduler/scheduler.strings.js:54 -#: client/src/templates/survey-maker/shared/question-definition.form.js:27 -msgid "Prompt" -msgstr "Aviso" - -#: client/lib/components/components.strings.js:34 -#: client/src/templates/job_templates/job-template.form.js:138 -#: client/src/templates/job_templates/job-template.form.js:168 -#: client/src/templates/job_templates/job-template.form.js:185 -#: client/src/templates/job_templates/job-template.form.js:202 -#: client/src/templates/job_templates/job-template.form.js:219 -#: client/src/templates/job_templates/job-template.form.js:270 -#: client/src/templates/job_templates/job-template.form.js:370 -#: client/src/templates/job_templates/job-template.form.js:60 -#: client/src/templates/job_templates/job-template.form.js:86 -msgid "Prompt on launch" -msgstr "Preguntar al ejecutar" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:238 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:253 -msgid "Provide a comma-separated list of filter expressions." -msgstr "Proporcione una lista de expresiones de filtrado separadas por coma." - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:254 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:271 -msgid "" -"Provide a comma-separated list of filter expressions. Hosts are imported " -"when all of the filters match. Refer to Ansible Tower documentation for more" -" detail." -msgstr "" -"Proporcione una lista de expresiones de filtrado separadas por coma. Los " -"hosts se importan cuando coinciden todos los filtros. Consulte la " -"documentación de Ansible Tower para obtener más información." - -#: client/src/inventories-hosts/hosts/host.form.js:50 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:49 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:49 -msgid "Provide a host name, ip address, or ip address:port. Examples include:" -msgstr "" -"Provea un nombre de host, dirección ip, ó dirección:puerto. Por ejemplo:" - -#: client/src/templates/job_templates/job-template.form.js:162 -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 aún 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." - -#: client/features/credentials/credentials.strings.js:22 -msgid "" -"Provide account information using Google Compute Engine JSON credentials " -"file." -msgstr "" -"Introduzca información de la cuenta usando el archivo de credenciales Google" -" Compute Engine JSON." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:196 -msgid "Provide environment variables to pass to the custom inventory script." -msgstr "" -"Proporcione variables del entorno para trasladar al script del inventario " -"personalizado." - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:257 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:274 -msgid "" -"Provide the named URL encoded name or id of the remote Tower inventory to be" -" imported." -msgstr "" -"Indique la URL, el nombre cifrado o id del inventario remoto de Tower para " -"importarlos." - -#: client/src/templates/job_templates/job-template.form.js:326 -#: client/src/templates/job_templates/job-template.form.js:334 -msgid "Provisioning Callback URL" -msgstr "Dirección URL para las llamadas callback" - -#: client/src/notifications/add/add.controller.js:81 -#: client/src/notifications/edit/edit.controller.js:128 -msgid "Purple" -msgstr "Púrpura" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:158 -msgid "RADIUS" -msgstr "RADIUS" - -#: client/src/instance-groups/instance-groups.strings.js:47 -msgid "RAM" -msgstr "RAM" - -#: client/lib/components/code-mirror/code-mirror.strings.js:13 -msgid "READ ONLY" -msgstr "SOLO LECTURA" - -#: client/src/home/dashboard/lists/jobs/jobs-list.partial.html:4 -msgid "RECENT JOB RUNS" -msgstr "TRABAJOS EJECUTADOS RECIENTEMENTE" - -#: client/src/home/dashboard/lists/jobs/jobs-list.partial.html:40 -msgid "RECENTLY RUN JOBS" -msgstr "TRABAJOS EJECUTADOS RECIENTEMENTE" - -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:47 -msgid "RECENTLY USED JOB TEMPLATES" -msgstr "PLANTILLAS DE TRABAJO USADOS RECIENTEMENTE" - -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:4 -msgid "RECENTLY USED TEMPLATES" -msgstr "PLANTILLAS USADAS RECIENTEMENTE" - -#: client/src/activity-stream/streams.list.js:54 -#: client/src/inventories-hosts/hosts/host.list.js:102 -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.list.js:46 -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:47 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:47 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:113 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:47 -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:54 -#: client/src/projects/projects.list.js:70 -#: client/src/scheduler/schedules.list.js:69 -msgid "REFRESH" -msgstr "ACTUALIZAR" - -#: client/features/users/tokens/tokens.strings.js:23 -msgid "REFRESH TOKEN" -msgstr "ACTUALIZAR TOKEN" - -#: client/src/shared/smart-search/smart-search.partial.html:45 -msgid "RELATED FIELDS:" -msgstr "CAMPOS RELACIONADOS:" - -#: client/src/shared/directives.js:94 -msgid "REMOVE" -msgstr "ELIMINAR" - -#: client/lib/components/components.strings.js:7 -msgid "REPLACE" -msgstr "REEMPLAZAR" - -#: client/features/output/output.strings.js:8 -msgid "RESULTS" -msgstr "RESULTADOS" - -#: client/features/templates/templates.strings.js:35 -#: client/lib/components/components.strings.js:8 -#: client/src/job-submission/job-submission.partial.html:44 -#: client/src/job-submission/job-submission.partial.html:87 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html:50 -msgid "REVERT" -msgstr "REVERTIR" - -#: client/src/credentials/factories/become-method-change.factory.js:25 -#: client/src/credentials/factories/kind-change.factory.js:82 -msgid "RSA Private Key" -msgstr "Clave privada RSA" - -#: client/features/templates/templates.strings.js:112 -msgid "RUN" -msgstr "EJECUTAR" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.route.js:28 -msgid "RUN COMMAND" -msgstr "EJECUTAR COMANDO" - -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:56 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:56 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:101 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:114 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:56 -msgid "RUN COMMANDS" -msgstr "EJECUTAR COMANDOS" - -#: client/src/notifications/add/add.controller.js:84 -#: client/src/notifications/edit/edit.controller.js:131 -msgid "Random" -msgstr "Aleatorio" - -#: client/features/users/tokens/tokens.strings.js:30 -msgid "Read" -msgstr "Leído" - -#: client/src/workflow-results/workflow-results.controller.js:121 -msgid "Read only view of extra variables added to the workflow." -msgstr "" -"Vista de sólo lectura de las variables adicionales añadidas al flujo de " -"trabajo." - -#: client/features/output/output.strings.js:23 -#: client/lib/components/code-mirror/code-mirror.strings.js:49 -msgid "Read-only view of extra variables added to the job template." -msgstr "" -"Vista de solo lectura de las variables adicionales añadidas a la plantilla " -"de trabajo." - -#: client/src/notifications/notificationTemplates.list.js:26 -msgid "Recent Notifications" -msgstr "Notificaciones recientes" - -#: client/src/notifications/notificationTemplates.form.js:94 -#: client/src/notifications/notificationTemplates.form.js:98 -msgid "Recipient List" -msgstr "Lista de destinatarios" - -#: client/src/notifications/add/add.controller.js:82 -#: client/src/notifications/edit/edit.controller.js:129 -msgid "Red" -msgstr "Rojo" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:47 -msgid "" -"Refer to the Ansible Tower documentation for further syntax and examples." -msgstr "" -"Consulte la documentación de Ansible Tower para obtener más ejemplos sobre " -"sintaxis." - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:245 -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:270 -msgid "Refresh" -msgstr "Actualizar" - -#: client/src/activity-stream/streams.list.js:51 -#: client/src/bread-crumb/bread-crumb.partial.html:6 -#: client/src/inventories-hosts/hosts/host.list.js:98 -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.list.js:42 -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:43 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:43 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:109 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:43 -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:50 -#: client/src/projects/projects.list.js:66 -#: client/src/scheduler/schedules.list.js:65 -msgid "Refresh the page" -msgstr "Actualizar la página" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:231 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:245 -msgid "Region:" -msgstr "Región:" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:131 -msgid "Regions" -msgstr "Regiones" - -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:65 -msgid "Related Groups" -msgstr "Grupos relacionados" - -#: client/lib/components/components.strings.js:98 -msgid "Relaunch On" -msgstr "Relanzar el" - -#: client/lib/components/components.strings.js:97 -msgid "Relaunch using host parameters" -msgstr "Relanzar utilizando los parámetros de host" - -#: client/lib/components/components.strings.js:96 -#: client/src/workflow-results/workflow-results.controller.js:37 -msgid "Relaunch using the same parameters" -msgstr "Relanzar utilizando los mismos parámetros" - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:180 -msgid "Remediate Inventory" -msgstr "Reparar inventario" - -#: client/src/access/add-rbac-user-team/rbac-selected-list.directive.js:102 -#: client/src/access/add-rbac-user-team/rbac-selected-list.directive.js:103 -#: client/src/teams/teams.form.js:145 client/src/users/users.form.js:224 -msgid "Remove" -msgstr "Eliminar" - -#: client/src/projects/projects.form.js:159 -msgid "Remove any local modifications prior to performing an update." -msgstr "" -"Eliminar cualquier modificación local antes de realizar una actualización." - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:149 -#: client/src/scheduler/scheduler.strings.js:27 -msgid "Repeat frequency" -msgstr "Frecuencia de repetición" - -#: client/src/license/license.partial.html:89 -msgid "Request License" -msgstr "Solicitar una licencia" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:291 -msgid "Required" -msgstr "Obligatorio" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:154 -msgid "Reset" -msgstr "Restablecer" - -#: client/lib/components/components.strings.js:90 -msgid "Resources" -msgstr "Recursos" - -#: client/features/templates/templates.strings.js:85 -#: client/src/scheduler/schedules.list.js:24 -msgid "Resources are missing from this template." -msgstr "Faltan recursos de esta plantilla." - -#: client/lib/services/base-string.service.js:88 -msgid "Return" -msgstr "Volver" - -#: client/features/users/tokens/tokens.strings.js:26 -msgid "Returned status:" -msgstr "Estado devuelto:" - -#: client/src/shared/form-generator.js:697 -msgid "Revert" -msgstr "Revertir" - -#: client/src/configuration/auth-form/sub-forms/auth-azure.form.js:47 -#: client/src/configuration/auth-form/sub-forms/auth-github-org.form.js:51 -#: client/src/configuration/auth-form/sub-forms/auth-github-team.form.js:51 -#: client/src/configuration/auth-form/sub-forms/auth-github.form.js:47 -#: client/src/configuration/auth-form/sub-forms/auth-google-oauth2.form.js:59 -#: client/src/configuration/auth-form/sub-forms/auth-ldap.form.js:102 -#: client/src/configuration/auth-form/sub-forms/auth-ldap1.form.js:102 -#: client/src/configuration/auth-form/sub-forms/auth-ldap2.form.js:102 -#: client/src/configuration/auth-form/sub-forms/auth-ldap3.form.js:102 -#: client/src/configuration/auth-form/sub-forms/auth-ldap4.form.js:102 -#: client/src/configuration/auth-form/sub-forms/auth-ldap5.form.js:102 -#: client/src/configuration/auth-form/sub-forms/auth-radius.form.js:34 -#: client/src/configuration/auth-form/sub-forms/auth-saml.form.js:121 -#: client/src/configuration/auth-form/sub-forms/auth-tacacs.form.js:47 -#: client/src/configuration/jobs-form/configuration-jobs.form.js:73 -#: client/src/configuration/system-form/sub-forms/system-activity-stream.form.js:26 -#: client/src/configuration/system-form/sub-forms/system-logging.form.js:74 -#: client/src/configuration/system-form/sub-forms/system-misc.form.js:56 -#: client/src/configuration/ui-form/configuration-ui.form.js:36 -msgid "Revert all to default" -msgstr "Revertir todo a valores por defecto" - -#: client/features/output/output.strings.js:66 -#: client/src/projects/projects.list.js:50 -msgid "Revision" -msgstr "Revisión" - -#: client/src/projects/add/projects-add.controller.js:155 -#: client/src/projects/edit/projects-edit.controller.js:290 -msgid "Revision #" -msgstr "Revisión n°" - -#: client/features/credentials/legacy.credentials.js:85 -#: client/src/credentials/credentials.form.js:462 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:130 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:132 -#: client/src/organizations/organizations.form.js:109 -#: client/src/projects/projects.form.js:270 client/src/teams/teams.form.js:101 -#: client/src/teams/teams.form.js:138 -#: client/src/templates/workflows.form.js:163 -#: client/src/users/users.form.js:207 -msgid "Role" -msgstr "Función" - -#: client/src/instance-groups/instance-groups.list.js:26 -#: client/src/instance-groups/instance-groups.strings.js:18 -#: client/src/instance-groups/instance-groups.strings.js:53 -msgid "Running Jobs" -msgstr "Tareas en ejecución" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:159 -msgid "SAML" -msgstr "SAML" - -#: client/lib/services/base-string.service.js:62 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal.partial.html:17 -#: client/src/inventories-hosts/shared/associate-groups/associate-groups.partial.html:17 -#: client/src/inventories-hosts/shared/associate-hosts/associate-hosts.partial.html:17 -#: client/src/partials/survey-maker-modal.html:87 -#: client/src/shared/instance-groups-multiselect/instance-groups-modal/instance-groups-modal.partial.html:18 -msgid "SAVE" -msgstr "GUARDAR" - -#: client/src/scheduler/scheduled-jobs.list.js:13 -#: client/src/scheduler/scheduled-jobs.list.js:14 -msgid "SCHEDULED JOBS" -msgstr "TRABAJOS PROGRAMADOS" - -#: client/src/activity-stream/get-target-title.factory.js:38 -#: client/src/inventories-hosts/inventories/related/sources/list/schedule/sources-schedule.route.js:9 -#: client/src/management-jobs/scheduler/main.js:28 -#: client/src/management-jobs/scheduler/main.js:34 -#: client/src/scheduler/schedules.route.js:102 -#: client/src/scheduler/schedules.route.js:14 -#: client/src/scheduler/schedules.route.js:189 -#: client/src/scheduler/schedules.route.js:284 -msgid "SCHEDULES" -msgstr "PROGRAMACIONES" - -#: client/src/projects/add/projects-add.controller.js:127 -#: client/src/projects/edit/projects-edit.controller.js:263 -msgid "SCM Branch" -msgstr "Rama SCM" - -#: client/src/projects/add/projects-add.controller.js:146 -#: client/src/projects/edit/projects-edit.controller.js:281 -msgid "SCM Branch/Tag/Commit" -msgstr "Consigna/Etiqueta/Rama SCM" - -#: client/src/projects/add/projects-add.controller.js:167 -#: client/src/projects/edit/projects-edit.controller.js:302 -msgid "SCM Branch/Tag/Revision" -msgstr "Revisión/Etiqueta/Rama SCM" - -#: client/src/projects/projects.form.js:160 -msgid "SCM Clean" -msgstr "Limpiar SCM" - -#: client/src/projects/projects.form.js:171 -msgid "SCM Delete" -msgstr "Eliminar SCM" - -#: client/src/credentials/factories/become-method-change.factory.js:20 -#: client/src/credentials/factories/kind-change.factory.js:77 -msgid "SCM Private Key" -msgstr "Clave privada SCM" - -#: client/src/projects/projects.form.js:56 -msgid "SCM Type" -msgstr "Tipo SCM" - -#: client/src/projects/projects.form.js:107 -msgid "SCM URL" -msgstr "URL DE SCM" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:49 -#: client/src/projects/projects.form.js:181 -msgid "SCM Update" -msgstr "Actualización SCM" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:224 -#: client/src/projects/list/projects-list.controller.js:267 -msgid "SCM Update Cancel" -msgstr "Cancelar actualización SCM" - -#: client/src/projects/projects.form.js:151 -msgid "SCM Update Options" -msgstr "Opciones de actualización SCM" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:119 -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:46 -#: client/src/projects/edit/projects-edit.controller.js:139 -#: client/src/projects/list/projects-list.controller.js:118 -#: client/src/projects/list/projects-list.controller.js:85 -msgid "SCM update currently running" -msgstr "Actualización SCM actualmente en ejecución" - -#: client/features/users/tokens/tokens.strings.js:39 -msgid "SCOPE" -msgstr "ALCANCE" - -#: client/features/output/output.strings.js:83 -msgid "SEARCH" -msgstr "BÚSQUEDA" - -#: client/features/templates/templates.strings.js:114 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html:74 -msgid "SELECT" -msgstr "SELECCIONAR" - -#: client/features/credentials/credentials.strings.js:20 -msgid "SELECT A CREDENTIAL TYPE" -msgstr "SELECCIONAR UN TIPO DE CREDENCIAL" - -#: client/features/users/tokens/tokens.strings.js:19 -msgid "SELECT AN APPLICATION" -msgstr "SELECCIONAR UNA APLICACIÓN" - -#: client/features/applications/applications.strings.js:35 -#: client/features/credentials/credentials.strings.js:19 -msgid "SELECT AN ORGANIZATION" -msgstr "SELECCIONAR UNA ORGANIZACIÓN" - -#: client/src/inventories-hosts/shared/associate-groups/associate-groups.partial.html:6 -msgid "SELECT GROUPS" -msgstr "SELECCIONAR GRUPOS" - -#: client/src/inventories-hosts/shared/associate-hosts/associate-hosts.partial.html:6 -msgid "SELECT HOSTS" -msgstr "SELECCIONAR HOSTS" - -#: client/src/instance-groups/instance-groups.strings.js:36 -msgid "SELECT INSTANCE" -msgstr "SELECCIONAR INSTANCIA" - -#: client/features/templates/templates.strings.js:32 -msgid "SELECTED" -msgstr "SELECCIONADO" - -#: client/src/job-submission/job-submission.partial.html:29 -#: client/src/job-submission/job-submission.partial.html:56 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html:18 -msgid "SELECTED:" -msgstr "SELECCIONADO:" - -#: client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html:20 -msgid "SETTING CATEGORY" -msgstr "CATEGORÍA DE LA CONFIGURACIÓN" - -#: client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html:24 -msgid "SETTING NAME" -msgstr "NOMBRE DE LA CONFIGURACIÓN" - -#: client/lib/components/components.strings.js:11 -#: client/lib/services/base-string.service.js:65 -#: client/src/templates/survey-maker/surveys/init.factory.js:486 -msgid "SHOW" -msgstr "MOSTRAR" - -#: client/src/login/loginModal/loginModal.partial.html:97 -msgid "SIGN IN" -msgstr "INICIAR SESIÓN" - -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.partial.html:2 -msgid "SIGN IN WITH" -msgstr "INICIAR SESIÓN CON" - -#: client/src/inventories-hosts/hosts/host.list.js:110 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:14 -msgid "SMART INVENTORY" -msgstr "INVENTARIO INTELIGENTE" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.route.js:27 -msgid "SOURCES" -msgstr "FUENTES" - -#: client/src/credentials/factories/become-method-change.factory.js:89 -#: client/src/credentials/factories/kind-change.factory.js:146 -msgid "SSH Key" -msgstr "Clave SSH" - -#: client/features/templates/templates.strings.js:41 -msgid "SSH Password" -msgstr "Contraseña de SSH" - -#: client/src/credentials/credentials.form.js:255 -msgid "SSH key description" -msgstr "Descripción de la clave SSH" - -#: client/src/notifications/notificationTemplates.form.js:446 -msgid "SSL Connection" -msgstr "Conexión SSL" - -#: client/features/templates/templates.strings.js:117 -msgid "START" -msgstr "INICIAR" - -#: client/src/smart-status/smart-status.controller.js:43 -msgid "STATUS" -msgstr "ESTADO" - -#: client/src/credentials/credentials.form.js:119 -#: client/src/credentials/credentials.form.js:127 -msgid "STS Token" -msgstr "Token STS" - -#: client/src/home/dashboard/graphs/job-status/job-status-graph.directive.js:62 -msgid "SUCCESSFUL" -msgstr "CORRECTO" - -#: client/src/partials/survey-maker-modal.html:24 -msgid "SURVEY" -msgstr "ENCUESTA" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:62 -msgid "SYNC ALL" -msgstr "SINCRONIZAR TODO" - -#: client/src/system-tracking/system-tracking.route.js:18 -msgid "SYSTEM TRACKING" -msgstr "SISTEMA DE RASTREO" - -#: client/src/scheduler/scheduler.strings.js:42 -msgid "Sat" -msgstr "Sáb" - -#: client/src/credentials/factories/become-method-change.factory.js:70 -#: client/src/credentials/factories/kind-change.factory.js:127 -msgid "Satellite 6 URL" -msgstr "URL Satellite 6" - -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:110 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:193 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:158 -#: client/src/scheduler/scheduler.strings.js:57 -#: client/src/shared/form-generator.js:1711 -msgid "Save" -msgstr "Guardar" - -#: client/src/configuration/configuration.controller.js:516 -msgid "Save Complete" -msgstr "Guardado completo" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:105 -#: client/src/configuration/configuration.controller.js:242 -#: client/src/configuration/configuration.controller.js:326 -#: client/src/configuration/system-form/configuration-system.controller.js:68 -msgid "Save changes" -msgstr "Guardar los cambios" - -#: client/src/license/license.partial.html:140 -msgid "Save successful!" -msgstr "Guardado correctamente" - -#: client/src/scheduler/scheduler.strings.js:50 -msgid "Schedule Description" -msgstr "Descripción de la programación" - -#: client/src/management-jobs/card/card.partial.html:28 -msgid "Schedule Management Job" -msgstr "Planificar trabajo de gestión" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:7 -msgid "Schedule inventory syncs" -msgstr "Programar sincronizaciones de inventario" - -#: client/src/scheduler/scheduler.strings.js:14 -msgid "Schedule is active." -msgstr "La programación está activa." - -#: client/src/scheduler/scheduler.strings.js:15 -msgid "Schedule is active. Click to stop." -msgstr "La programación está activa. Haga clic para detenerla." - -#: client/src/scheduler/scheduler.strings.js:16 -msgid "Schedule is stopped." -msgstr "La programación se detuvo." - -#: client/src/scheduler/scheduler.strings.js:17 -msgid "Schedule is stopped. Click to activate." -msgstr "La programación se detuvo. Haga clic para activarla." - -#: client/lib/components/components.strings.js:70 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:35 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:440 -#: client/src/projects/projects.form.js:290 -#: client/src/templates/job_templates/job-template.form.js:448 -#: client/src/templates/workflows.form.js:185 -msgid "Schedules" -msgstr "Programaciones" - -#: client/src/shared/smart-search/smart-search.controller.js:122 -#: client/src/shared/smart-search/smart-search.controller.js:164 -msgid "Search" -msgstr "Buscar" - -#: client/src/credentials/credentials.form.js:104 -msgid "Secret Key" -msgstr "Clave secreta" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:232 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:246 -msgid "Security Group:" -msgstr "Grupo de seguridad:" - -#: client/src/credentials/credentials.form.js:124 -msgid "" -"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." -msgstr "" -"El Security Token Service (STS) es un servicio web que habilita su solicitud" -" temporalmente y con credenciales con privilegio limitado para usuarios de " -"AWS Identity y Access Management (IAM)." - -#: client/src/shared/form-generator.js:1715 -#: client/src/shared/lookup/lookup-modal.directive.js:59 -#: client/src/shared/lookup/lookup-modal.partial.html:20 -msgid "Select" -msgstr "Seleccionar" - -#: client/src/shared/instance-groups-multiselect/instance-groups-modal/instance-groups-modal.partial.html:5 -msgid "Select Instance Groups" -msgstr "Seleccionar grupos de instancias" - -#: client/src/job-submission/job-submission.directive.js:65 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js:66 -msgid "Select a credential" -msgstr "Seleccionar una credencial" - -#: client/src/access/add-rbac-user-team/rbac-user-team.controller.js:68 -msgid "Select a role" -msgstr "Seleccionar un rol" - -#: client/features/users/tokens/tokens.strings.js:29 -msgid "Select a scope" -msgstr "Seleccionar un alcance" - -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:53 -msgid "" -"Select an inventory source by clicking the check box beside it. The " -"inventory source can be a single group or a selection of multiple groups." -msgstr "" -"Seleccione una fuente de inventario al hacer clic en la casilla de " -"verificación a su lado. La fuente de inventario puede ser un único grupo o " -"una selección de varios grupos." - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:53 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:98 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:53 -msgid "" -"Select an inventory source by clicking the check box beside it. The " -"inventory source can be a single group or host, a selection of multiple " -"hosts, or a selection of multiple groups." -msgstr "" -"Seleccione una fuente de inventario al hacer clic en la casilla de " -"verificación a su lado. La fuente de inventario puede ser un único grupo o " -"host, una selección de varios hosts o una selección de varios grupos." - -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:111 -msgid "" -"Select an inventory source by clicking the check box beside it. The " -"inventory source can be a single host or a selection of multiple hosts." -msgstr "" -"Seleccione una fuente de inventario al hacer clic en la casilla de " -"verificación a su lado. La fuente de inventario puede ser un único host o " -"una selección de varios hosts." - -#: client/src/configuration/jobs-form/configuration-jobs.controller.js:109 -#: client/src/configuration/jobs-form/configuration-jobs.controller.js:134 -#: client/src/configuration/ui-form/configuration-ui.controller.js:95 -msgid "Select commands" -msgstr "Seleccionar comandos" - -#: client/src/templates/job_templates/job-template.form.js:132 -msgid "" -"Select credentials that allow Tower to access 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 que le permiten a Tower acceder a los nodos en " -"función de los cuales se ejecutará este trabajo. Solo puede seleccionar una " -"credencial de cada tipo. Para las credenciales de equipo (SSH), si marca " -"\"Preguntar al ejecutar\" sin seleccionar las credenciales, se le pedirá que" -" seleccione una credencial del equipo en el momento de la ejecución. Si " -"selecciona credenciales y marca \"Preguntar al ejecutar\", las credenciales " -"seleccionadas se convierten en las predeterminadas que pueden actualizarse " -"al momento de la ejecución." - -#: client/src/projects/projects.form.js:99 -msgid "" -"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." -msgstr "" -"Seleccione desde la lista de directorios encontrados en el directorio base " -"del proyecto. Junto al directorio base y el directorio del playbook se " -"construirá la ruta completa utilizada para encontrar playbooks." - -#: client/src/configuration/auth-form/configuration-auth.controller.js:370 -#: client/src/configuration/auth-form/configuration-auth.controller.js:389 -msgid "Select group types" -msgstr "Seleccionar un tipo de grupo" - -#: client/src/access/rbac-multiselect/rbac-multiselect-role.directive.js:24 -msgid "Select roles" -msgstr "Seleccionar roles" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:56 -msgid "Select the Instance Groups for this Inventory to run on." -msgstr "" -"Seleccione los grupos de instancias en los que se ejecutará este inventario." - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:63 -msgid "" -"Select the Instance Groups for this Inventory to run on. Refer to the " -"Ansible Tower documentation for more detail." -msgstr "" -"Seleccione los Grupos de instancias respecto de los cuales se ejecutará este" -" Inventario. Consulte la documentación de Ansible Tower para obtener más " -"información." - -#: client/src/templates/job_templates/job-template.form.js:254 -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." - -#: client/src/organizations/organizations.form.js:40 -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." - -#: client/src/templates/job_templates/job-template.form.js:244 -msgid "" -"Select the custom Python virtual environment for this job template to run " -"on." -msgstr "" -"Seleccione el entorno virtual Python personalizado en el que se ejecutará " -"esta plantilla." - -#: client/src/organizations/organizations.form.js:51 -msgid "" -"Select the custom Python virtual environment for this organization to run " -"on." -msgstr "" -"Seleccione el entorno virtual Python personalizado en el que se ejecutará " -"esta organización." - -#: client/src/projects/projects.form.js:211 -msgid "" -"Select the custom Python virtual environment for this project to run on." -msgstr "" -"Seleccione el entorno virtual Python personalizado en el que se ejecutará " -"este proyecto." - -#: client/src/templates/job_templates/job-template.form.js:79 -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." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:122 -msgid "" -"Select the inventory file to be synced by this source. You can select from " -"the dropdown or enter a file within the input." -msgstr "" -"Seleccione el archivo del inventario que sincronizará esta fuente. Puede " -"seleccionar del menú desplegable o ingresar un archivo en la entrada." - -#: client/src/templates/job_templates/job-template.form.js:114 -msgid "Select the playbook to be executed by this job." -msgstr "Seleccionar el playbook a ser ejecutado por este trabajo." - -#: client/src/templates/job_templates/job-template.form.js:99 -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." - -#: client/src/configuration/system-form/configuration-system.controller.js:197 -msgid "Select types" -msgstr "Seleccionar los tipos" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:224 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:238 -msgid "Select which groups to create automatically." -msgstr "Seleccione los grupos que se crearán de manera automática." - -#: client/src/notifications/notificationTemplates.form.js:110 -msgid "Sender Email" -msgstr "Dirección de correo del remitente" - -#: client/src/credentials/factories/become-method-change.factory.js:24 -#: client/src/credentials/factories/kind-change.factory.js:81 -msgid "Service Account Email Address" -msgstr "Dirección de correo de cuenta de servicio" - -#: client/features/credentials/credentials.strings.js:21 -msgid "Service Account JSON File" -msgstr "Archivo JSON de cuenta de servicio " - -#: client/lib/components/components.strings.js:86 -msgid "Settings" -msgstr "Ajustes" - -#: client/src/job-submission/job-submission.partial.html:108 -#: client/src/job-submission/job-submission.partial.html:122 -#: client/src/job-submission/job-submission.partial.html:136 -#: client/src/job-submission/job-submission.partial.html:150 -#: client/src/job-submission/job-submission.partial.html:299 -#: client/src/shared/form-generator.js:883 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:24 -msgid "Show" -msgstr "Mostrar" - -#: client/features/templates/templates.strings.js:46 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:115 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:118 -#: client/src/templates/job_templates/job-template.form.js:261 -#: client/src/templates/job_templates/job-template.form.js:264 -msgid "Show Changes" -msgstr "Mostrar cambios" - -#: client/features/output/output.strings.js:38 -msgid "Show Less" -msgstr "Mostrar menos" - -#: client/features/output/output.strings.js:39 -msgid "Show More" -msgstr "Mostrar más" - -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:33 -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:44 -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:55 -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:76 -msgid "Sign in with %s" -msgstr "Iniciar sesión con %s" - -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:63 -msgid "Sign in with %s Organizations" -msgstr "Iniciar sesión con las organizaciones %s" - -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:61 -msgid "Sign in with %s Teams" -msgstr "Iniciar sesión con los equipos %s" - -#: client/features/output/output.strings.js:67 -#: client/features/templates/templates.strings.js:47 -#: client/src/job-submission/job-submission.partial.html:245 -#: client/src/templates/job_templates/job-template.form.js:207 -#: client/src/templates/job_templates/job-template.form.js:214 -msgid "Skip Tags" -msgstr "Omitir etiquetas" - -#: client/src/templates/job_templates/job-template.form.js:213 -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 Ansible Tower documentation for details on the usage of tags." -msgstr "" -"La omisión de etiquetas resulta útil cuando tiene un manual de gran tamaño y" -" desea omitir partes específicas de la tarea o la reproducción. Utilice " -"comas para separar las distintas etiquetas. Consulte la documentación de " -"Ansible Tower para obtener información detallada sobre el uso de etiquetas." - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:44 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:48 -msgid "Smart Host Filter" -msgstr "Filtro de host inteligente" - -#: client/src/inventories-hosts/inventories/inventory.list.js:86 -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:76 -#: client/src/organizations/linkout/controllers/organizations-inventories.controller.js:70 -#: client/src/shared/form-generator.js:1476 -msgid "Smart Inventory" -msgstr "Inventario inteligente" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:44 -msgid "Solvable With Playbook" -msgstr "Se puede solucionar con playbook" - -#: client/features/output/output.strings.js:68 -#: client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.directive.js:57 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:64 -msgid "Source" -msgstr "Fuente" - -#: client/src/credentials/credentials.form.js:75 -msgid "Source Control" -msgstr "Fuente de control" - -#: client/features/output/output.strings.js:69 -msgid "Source Credential" -msgstr "Credencial de origen" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:47 -#: client/src/projects/projects.form.js:26 -msgid "Source Details" -msgstr "Detalles de la fuente" - -#: client/src/notifications/notificationTemplates.form.js:192 -#: client/src/notifications/notificationTemplates.form.js:193 -msgid "Source Phone Number" -msgstr "Número de teléfono de la fuente" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:136 -msgid "Source Regions" -msgstr "Regiones de fuente" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:209 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:216 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:233 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:240 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:257 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:264 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:274 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:281 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:291 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:298 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:308 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:315 -msgid "Source Variables" -msgstr "Variables de fuente" - -#: client/src/partials/logviewer.html:9 -msgid "Source Vars" -msgstr "Vars de la fuente" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:34 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:167 -msgid "Sources" -msgstr "Fuentes" - -#: client/src/notifications/notificationTemplates.form.js:330 -msgid "" -"Specify HTTP Headers in JSON format. Refer to the Ansible Tower " -"documentation for example syntax." -msgstr "" -"Especifique los encabezados HTTP en formato JSON. Consulte la documentación " -"de Ansible Tower para obtener ejemplos de sintaxis." - -#: client/src/credentials/credentials.form.js:285 -msgid "" -"Specify a method for %s operations. This is equivalent to specifying the %s " -"parameter, where %s could be %s" -msgstr "" -"Especificar un método para las operaciones %s. Esto es equivalente a " -"especificar el parámetro %s, cuando %s puede ser %s." - -#: client/src/notifications/notificationTemplates.form.js:478 -msgid "" -"Specify a notification color. Acceptable colors are hex color code (example:" -" #3af or #789abc) ." -msgstr "" -"Especifique un color de notificación. Los colores aceptables son el código " -"de color hex (ejemplo: #3af o #789abc) ." - -#: client/src/notifications/notificationTemplates.form.js:292 -msgid "" -"Specify a notification color. Acceptable colors are: yellow, green, red " -"purple, gray or random." -msgstr "" -"Especifique un color para la notificación. Se aceptan los siguientes " -"colores: amarillo, verde, rojo, púrpura, gris o al azar." - -#: client/features/users/tokens/tokens.strings.js:20 -msgid "Specify a scope for the token's access" -msgstr "Especifique un alcance para el acceso al token" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:253 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:270 -msgid "" -"Specify which groups to create automatically. Group names will be created " -"similar to the options selected. If blank, all groups above are created. " -"Refer to Ansible Tower documentation for more detail." -msgstr "" -"Especifique los grupos que se crearán de manera automática. Los nombres de " -"grupos creados serán similares a los de las opciones seleccionadas. Si la " -"opción se deja en blanco, se crearán todos los grupos de arriba. Consulte la" -" documentación de Ansible Tower para obtener información detallada." - -#: client/features/output/output.strings.js:108 -msgid "Standard Error" -msgstr "Error estándar" - -#: client/features/output/output.strings.js:107 -#: client/src/partials/logviewer.html:5 -msgid "Standard Out" -msgstr "Salida estándar" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:41 -#: client/src/scheduler/scheduler.strings.js:23 -msgid "Start Date" -msgstr "Fecha de inicio" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:56 -#: client/src/scheduler/scheduler.strings.js:24 -msgid "Start Time" -msgstr "Hora de inicio" - -#: client/lib/components/components.strings.js:104 -msgid "Start a job using this template" -msgstr "Iniciar un trabajo usando esta plantilla" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:6 -msgid "Start sync process" -msgstr "Iniciar proceso de sincronización" - -#: client/features/jobs/jobs.strings.js:9 -#: client/features/output/output.strings.js:70 -#: client/src/workflow-results/workflow-results.controller.js:49 -msgid "Started" -msgstr "Iniciado" - -#: client/features/output/output.strings.js:71 -#: client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.directive.js:53 -#: client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.directive.js:55 -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:43 -#: client/src/notifications/notification-templates-list/list.controller.js:71 -#: client/src/partials/logviewer.html:4 -#: client/src/workflow-results/workflow-results.controller.js:52 -msgid "Status" -msgstr "Estado" - -#: client/src/configuration/auth-form/configuration-auth.partial.html:4 -msgid "Sub Category" -msgstr "Subcategoría" - -#: client/src/license/license.partial.html:139 -msgid "Submit" -msgstr "Enviar" - -#: client/src/license/license.partial.html:27 -msgid "Subscription" -msgstr "Subscripción" - -#: client/src/credentials/credentials.form.js:151 -#: client/src/credentials/credentials.form.js:162 -msgid "Subscription ID" -msgstr "ID de suscripción" - -#: client/src/credentials/credentials.form.js:161 -msgid "Subscription ID is an Azure construct, which is mapped to a username." -msgstr "" -"El ID de subscripción es un elemento Azure, el cual está asociado al " -"usuario." - -#: client/src/notifications/notifications.list.js:39 -msgid "Success" -msgstr "Correcto" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:77 -msgid "Successful" -msgstr "Correctos" - -#: client/src/scheduler/scheduler.strings.js:36 -msgid "Sun" -msgstr "Dom" - -#: client/features/templates/templates.strings.js:28 -#: client/src/job-submission/job-submission.partial.html:20 -msgid "Survey" -msgstr "Encuesta" - -#: client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js:62 -#: client/src/templates/workflows/edit-workflow/workflow-edit.controller.js:262 -msgid "" -"Surveys allow users to be prompted at job launch with a series of questions " -"related to the job. This allows for variables to be defined that affect the " -"playbook run at time of launch." -msgstr "" -"Las encuestas permiten que los usuarios reciban una serie de preguntas " -"relacionadas a la tarea en el momento de su ejecución. Esto permite que se " -"definan las variables que afectan el playbook ejecutado en el lanzamiento." - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:58 -msgid "Sync all inventory sources" -msgstr "Sincronizar todas las fuentes de inventario" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:29 -msgid "Sync canceled. Click to view log." -msgstr "Sincronización cancelada. Haga clic para ver el registro." - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:35 -msgid "Sync completed. Click to view log." -msgstr "Sincronización completada. Haga clic para ver el registro." - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:32 -msgid "Sync failed. Click to view log." -msgstr "Error en la sincronización. Haga clic para ver el registro." - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:17 -msgid "Sync not performed. Click" -msgstr "Sincronización no realizada. Haga clic" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:38 -msgid "Sync pending." -msgstr "Sincronización pendiente." - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:45 -msgid "Sync running" -msgstr "Sincronización en ejecución" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:46 -msgid "Sync running. Click to view log." -msgstr "Sincronización en ejecución. Haga clic para ver el registro." - -#: client/src/configuration/configuration.partial.html:29 -msgid "System" -msgstr "Sistema" - -#: client/src/users/add/users-add.controller.js:12 -#: client/src/users/edit/users-edit.controller.js:12 -#: client/src/users/list/users-list.controller.js:12 -msgid "System Administrator" -msgstr "Administrador de sistema" - -#: client/src/users/add/users-add.controller.js:11 -#: client/src/users/edit/users-edit.controller.js:11 -#: client/src/users/list/users-list.controller.js:11 -msgid "System Auditor" -msgstr "Auditor de sistema" - -#: client/src/configuration/configuration.partial.html:3 -msgid "System auditors have read-only permissions in this section." -msgstr "" -"Los auditores de sistema tienen permisos sólo de lectura en esta sección." - -#: client/src/configuration/auth-form/configuration-auth.controller.js:160 -msgid "TACACS+" -msgstr "TACACS+" - -#: client/features/output/output.strings.js:104 -msgid "TASK" -msgstr "TAREA" - -#: client/src/activity-stream/get-target-title.factory.js:23 -#: client/src/organizations/linkout/organizations-linkout.route.js:98 -#: client/src/organizations/list/organizations-list.controller.js:61 -#: client/src/teams/main.js:65 client/src/teams/teams.list.js:14 -#: client/src/teams/teams.list.js:15 -msgid "TEAMS" -msgstr "EQUIPOS" - -#: client/features/templates/routes/templatesList.route.js:12 -#: client/features/templates/templates.strings.js:12 -#: client/features/templates/templates.strings.js:8 -#: client/src/activity-stream/get-target-title.factory.js:44 -#: client/src/templates/templates.list.js:15 -#: client/src/templates/templates.list.js:16 -msgid "TEMPLATES" -msgstr "PLANTILLAS" - -#: client/src/instance-groups/instance-groups.list.js:8 -msgid "THERE ARE CURRENTLY NO INSTANCE GROUPS DEFINED" -msgstr "NO HAY INSTANCIAS DE GRUPOS DEFINIDAS EN ESTE MOMENTO" - -#: client/src/home/dashboard/graphs/job-status/job-status-graph.directive.js:105 -msgid "TIME" -msgstr "DURACIÓN" - -#: client/features/users/tokens/tokens.strings.js:22 -msgid "TOKEN" -msgstr "TOKEN" - -#: client/features/users/tokens/tokens.strings.js:21 -msgid "TOKEN INFORMATION" -msgstr "INFORMACIÓN DE TOKEN" - -#: client/features/applications/applications.strings.js:11 -#: client/features/users/tokens/tokens.strings.js:10 -#: client/features/users/tokens/tokens.strings.js:8 -#: client/features/users/tokens/users-tokens-list.route.js:24 -#: client/src/activity-stream/get-target-title.factory.js:50 -msgid "TOKENS" -msgstr "TOKENS" - -#: client/features/templates/templates.strings.js:106 -msgid "TOTAL TEMPLATES" -msgstr "PLANTILLAS TOTALES" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:235 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:249 -msgid "Tag None:" -msgstr "Etiqueta None:" - -#: client/src/templates/job_templates/job-template.form.js:196 -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 Ansible Tower documentation for details on the usage of tags." -msgstr "" -"Las etiquetas resultan útiles cuando tiene un manual de gran tamaño y desea " -"omitir partes específicas de la tarea o la reproducción. Utilice comas para " -"separar las distintas etiquetas. Consulte la documentación de Ansible Tower " -"para obtener información detallada sobre el uso de etiquetas." - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:233 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:247 -msgid "Tags:" -msgstr "Etiquetas:" - -#: client/src/notifications/notificationTemplates.form.js:309 -#: client/src/notifications/notificationTemplates.form.js:337 -#: client/src/notifications/notificationTemplates.form.js:376 -msgid "Target URL" -msgstr "URL destino" - -#: client/features/output/output.strings.js:92 -msgid "Tasks" -msgstr "Tareas" - -#: client/features/credentials/legacy.credentials.js:91 -#: client/src/credentials/credentials.form.js:468 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:136 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:138 -#: client/src/projects/projects.form.js:276 -#: client/src/templates/workflows.form.js:169 -msgid "Team Roles" -msgstr "Funciones de equipo" - -#: client/lib/components/components.strings.js:79 -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:40 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:36 -#: client/src/organizations/linkout/organizations-linkout.route.js:109 -#: client/src/organizations/linkout/organizations-linkout.route.js:111 -#: client/src/shared/stateDefinitions.factory.js:426 -#: client/src/users/users.form.js:161 -msgid "Teams" -msgstr "Equipos" - -#: client/src/templates/templates.list.js:14 -#: client/src/workflow-results/workflow-results.controller.js:47 -msgid "Template" -msgstr "Plantilla" - -#: client/features/templates/templates.strings.js:67 -msgid "Template parameter is missing." -msgstr "Falta el parámetro de la plantilla" - -#: client/lib/components/components.strings.js:76 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:37 -msgid "Templates" -msgstr "Plantillas" - -#: client/src/credentials/credentials.form.js:337 -msgid "Tenant ID" -msgstr "ID inquilino [Tenant]" - -#: client/src/configuration/system-form/sub-forms/system-logging.form.js:79 -msgid "Test" -msgstr "Probar" - -#: client/src/notifications/notificationTemplates.list.js:77 -msgid "Test notification" -msgstr "Probar notificación" - -#: client/src/templates/survey-maker/surveys/init.factory.js:13 -msgid "Text" -msgstr "Texto" - -#: client/src/templates/survey-maker/surveys/init.factory.js:14 -msgid "Textarea" -msgstr "Area de texto" - -#: client/src/shared/form-generator.js:1406 -#: client/src/shared/form-generator.js:1412 -msgid "That value was not found. Please enter or select a valid value." -msgstr "" -"El valor no fue encontrado. Por favor introduzca o seleccione un valor " -"válido." - -#: client/lib/components/components.strings.js:47 -msgid "That value was not found. Please enter or select a valid value." -msgstr "No se encontró ese valor. Introduzca o seleccione un valor válido." - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:66 -msgid "The Insights Credential for {{inventory.name}} was not found." -msgstr "No se encontró la credencial de Insights para {{inventory.name}}." - -#: client/src/credentials/factories/become-method-change.factory.js:32 -#: client/src/credentials/factories/kind-change.factory.js:89 -msgid "" -"The Project ID is the GCE assigned identification. It is constructed as two " -"words followed by a three digit number. Such as:" -msgstr "" -"El ID del proyecto es el identificador asignado en GCE. Se construye de dos " -"palabras seguidas por tres dígitos. Ejemplo:" - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:204 -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:207 -msgid "The Project selected has a status of" -msgstr "El proyecto seleccionado tienen un estado de" - -#: client/src/projects/edit/projects-edit.controller.js:341 -msgid "The SCM update process is running." -msgstr "El proceso de actualización SCM está en ejecución." - -#: client/src/scheduler/scheduler.strings.js:32 -msgid "The day must be between 1 and 31." -msgstr "El día debe tener un valor de entre 1 y 31" - -#: client/src/credentials/credentials.form.js:190 -msgid "" -"The email address assigned to the Google Compute Engine %sservice account." -msgstr "" -"La dirección de correo asignada a la cuenta de servicio Google Compute " -"Engine %s." - -#: client/features/output/output.strings.js:12 -msgid "The host status bar will update when the job is complete." -msgstr "" -"La barra de estado del host se actualizará cuando se complete la tarea." - -#: client/src/credentials/factories/become-method-change.factory.js:62 -#: client/src/credentials/factories/kind-change.factory.js:119 -msgid "The host to authenticate with." -msgstr "El servidor al que autentificarse." - -#: client/src/credentials/factories/kind-change.factory.js:60 -msgid "The host value" -msgstr "El valor del servidor" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:154 -msgid "" -"The inventory will be in a pending status until the final delete is " -"processed." -msgstr "" -"El inventario estará en un estado pendiente hasta que se procese la " -"eliminación final." - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:105 -msgid "" -"The number of parallel or simultaneous processes to use while executing the " -"playbook. Inputting no value will use the default value from the %sansible " -"configuration file%s." -msgstr "" -"La cantidad de procesos paralelos o simultáneos que se utiliza cuando se " -"ejecuta el playbook. Si no ingresa un valor, se utilizará el valor " -"predeterminado desde el %sfichero de configuración ansible%s." - -#: client/src/templates/job_templates/job-template.form.js:151 -msgid "" -"The number of parallel or simultaneous processes to use while executing the " -"playbook. Value defaults to 0. Refer to the Ansible documentation for " -"details about the configuration file." -msgstr "" -"Cantidad de procesos paralelos o simultáneos que se utilizan al ejecutar el " -"manual. Los valores se establecen de manera predeterminada en 0. Consulte la" -" documentación de Ansible Tower para obtener información detallada sobre el " -"archivo de configuración." - -#: client/src/credentials/factories/kind-change.factory.js:59 -msgid "The project value" -msgstr "El valor del proyecto" - -#: client/src/scheduler/scheduler.strings.js:49 -msgid "" -"The scheduler options are invalid, incomplete, or a date is in the past." -msgstr "" -"Las opciones del programador no son válidas o están incompletas, o hay una " -"fecha en el pasado." - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:210 -msgid "The selected project has a status of" -msgstr "El proyecto seleccionado tiene un estado de" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:199 -msgid "" -"The selected project is not configured for SCM. To configure for SCM, edit " -"the project and provide SCM settings and then run an update." -msgstr "" -"El proyecto seleccionado no está configurado para usar SCM. Para configurar " -"el uso de SCM, edite el proyecto, proporcione los ajustes de SCM y, luego, " -"ejecute una actualización." - -#: client/src/projects/list/projects-list.controller.js:186 -msgid "" -"The selected project is not configured for SCM. To configure for SCM, edit " -"the project and provide SCM settings, and then run an update." -msgstr "" -"El proyecto seleccionado no está configurado para usar SCM. Para configurar " -"el uso SCM, edita el proyecto y establezca opciones SCM y ejecute una " -"actualización." - -#: client/src/templates/survey-maker/shared/question-definition.form.js:52 -msgid "" -"The suggested format for variable names is lowercase and underscore-" -"separated (for example, foo_bar, user_id, host_name, etc.). Variable names " -"with spaces are not allowed." -msgstr "" -"El formato sugerido para los nombres de variables es minúsculas y guiones " -"bajos (por ejemplo, foo_bar, user_id, host_name, etc.). No están permitidos " -"los nombres de variables con espacios." - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:124 -#: client/src/scheduler/scheduler.strings.js:25 -msgid "The time must be in HH24:MM:SS format." -msgstr "La hora debe ser en formato HH24:MM:SS" - -#: client/lib/services/base-string.service.js:79 -msgid "The {{ resourceType }} is currently being used by other resources." -msgstr "Otros recursos están usando actualmente el {{ resourceType }}." - -#: client/src/activity-stream/streams.list.js:17 -msgid "There are no events to display at this time" -msgstr "No hay eventos a mostrar por el momento" - -#: client/features/jobs/jobs.strings.js:17 -msgid "There are no running jobs." -msgstr "No hay tareas en ejecución." - -#: client/src/projects/list/projects-list.controller.js:150 -msgid "" -"There is no SCM update information available for this project. An update has" -" not yet been completed. If you have not already done so, start an update " -"for this project." -msgstr "" -"No hay información disponible de la actualización SCM disponible para este " -"proyecto. Una actualización no ha sido todavía completada." - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:191 -msgid "" -"There is no SCM update information available for this project. An update has" -" not yet been completed. If you have not already done so, start an update " -"for this project." -msgstr "" -"No hay información disponible sobre la actualización de SCM para este " -"proyecto. Aún no se completó una actualización. Si todavía no lo ha hecho, " -"inicie una actualización para este proyecto." - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:141 -msgid "There was an error deleting inventory source groups. Returned status:" -msgstr "" -"Se produjo un error al eliminar los grupos de la fuente del inventario. " -"Estado devuelto:" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:131 -msgid "There was an error deleting inventory source hosts. Returned status:" -msgstr "" -"Se produjo un error al eliminar los hosts de la fuente del inventario. " -"Estado devuelto:" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:168 -msgid "There was an error deleting inventory source. Returned status:" -msgstr "" -"Se produjo un error al eliminar la fuente del inventario. Estado devuelto:" - -#: client/src/configuration/configuration.controller.js:142 -msgid "There was an error getting config values:" -msgstr "Hubo un error al obtener los valores de configuración:" - -#: client/src/configuration/configuration.controller.js:415 -msgid "There was an error resetting value. Returned status:" -msgstr "Ha habido un error reiniciando el valor. Estado devuelto:" - -#: client/src/configuration/configuration.controller.js:607 -msgid "There was an error resetting values. Returned status:" -msgstr "Ha habido un error reiniciando valores. Estado devuelto:" - -#: client/src/configuration/system-form/configuration-system.controller.js:232 -msgid "There was an error testing the log aggregator. Returned status:" -msgstr "" -"Se produjo un error al probar el agregador de registros. Estado devuelto:" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:29 -msgid "" -"These are the modules that {{BRAND_NAME}} supports running commands against." -msgstr "" -"Estos son los módulos que {{BRAND_NAME}} admite para ejecutar comandos." - -#: client/features/templates/templates.strings.js:94 -msgid "" -"This Job Template has a credential that requires a password. Credentials " -"requiring passwords on launch are not permitted on workflow nodes." -msgstr "" -"Esta plantilla de tareas cuenta con una credencial que requiere contraseña. " -"Las credenciales que requieren contraseña en el lanzamiento no se permiten " -"en los nodos de flujo de trabajo." - -#: client/src/scheduler/scheduler.strings.js:59 -msgid "" -"This Job Template has a default credential that requires a password before " -"launch. Adding or editing schedules is prohibited while this credential is " -"selected. To add or edit a schedule, credentials that require a password " -"must be removed from the Job Template." -msgstr "" -"Esta plantilla de trabajo cuenta con una credencial predeterminada que " -"requiere contraseña antes del lanzamiento. Queda prohibido añadir o editar " -"programaciones mientras esta credencial está seleccionada. Para añadir o " -"editar una programación, las credenciales que requieren contraseña se deben " -"eliminar de la plantilla de trabajo." - -#: client/features/templates/templates.strings.js:93 -msgid "" -"This Job Template is missing a default inventory or project. This must be " -"addressed in the Job Template form before this node can be saved." -msgstr "" -"A esta plantilla de trabajo le falta un inventario o proyecto " -"predeterminado. Esto se debe abordar en el formulario de Plantilla del " -"trabajo para que se pueda guardar este nodo." - -#: client/src/credential-types/credential-types.strings.js:8 -msgid "" -"This credential type is currently being used by one or more credentials. " -"Credentials that use this credential type must be deleted before the " -"credential type can be deleted." -msgstr "" -"Una o más credenciales están usando este tipo de credencial actualmente. Las" -" credenciales que usan este tipo de credencial se deben eliminar para que se" -" pueda eliminar el tipo de credencial." - -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:24 -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:25 -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:26 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:24 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:25 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:26 -msgid "This group contains" -msgstr "Este grupo contiene" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:168 -msgid "This is not a valid number." -msgstr "Éste no es un número válido." - -#: client/src/credentials/factories/become-method-change.factory.js:59 -#: client/src/credentials/factories/kind-change.factory.js:116 -msgid "" -"This is the tenant name. This value is usually the same as the username." -msgstr "" -"Este es el nombre del inquilino [Tenant]. Este valor normalmente es el mismo" -" que el usuario." - -#: client/features/templates/templates.strings.js:63 -msgid "" -"This job template has a default {{typeLabel}} credential which must be " -"included or replaced before proceeding." -msgstr "" -"Esta plantilla de trabajo cuenta con una credencial predeterminada " -"{{typeLabel}} que se debe incluir o reemplazar antes de continuar." - -#: client/src/organizations/linkout/organizations-linkout.route.js:158 -msgid "This list is populated by inventories added from the" -msgstr "Esta lista se completa con los inventarios añadidos del" - -#: client/src/notifications/notifications.list.js:21 -msgid "" -"This list is populated by notification templates added from the " -"%sNotifications%s section" -msgstr "" -"La lista contiene las plantillas de notificación añadidas desde la sección " -"%sNotificaciones%s" - -#: client/src/organizations/linkout/organizations-linkout.route.js:209 -msgid "This list is populated by projects added from the" -msgstr "Esta lista se completa con los proyectos añadidos del" - -#: client/src/organizations/linkout/organizations-linkout.route.js:111 -msgid "This list is populated by teams added from the" -msgstr "Esta lista se completa con los equipos añadidos del" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:4 -msgid "" -"This machine has not checked in with Insights in {{last_check_in}} hours" -msgstr "" -"Esta máquina no se ha registrado con Insights en {{last_check_in}} horas" - -#: client/src/shared/form-generator.js:753 -msgid "" -"This setting has been set manually in a settings file and is now disabled." -msgstr "" -"Este valor ha sido establecido manualmente en el fichero de configuración y " -"ahora está inhabilitado." - -#: client/src/users/users.form.js:166 -msgid "This user is not a member of any teams" -msgstr "Este usuario no es miembro de ningún equipo." - -#: client/src/shared/form-generator.js:863 -#: client/src/shared/form-generator.js:958 -msgid "" -"This value does not match the password you entered previously. Please " -"confirm that password." -msgstr "" -"Este valor no corresponde con la contraseña introducida anteriormente. Por " -"favor confirme la contraseña." - -#: client/src/configuration/configuration.controller.js:632 -msgid "" -"This will reset all configuration values to their factory defaults. Are you " -"sure you want to proceed?" -msgstr "" -"Esta operación reiniciará todos los valores de configuración a los valores " -"por defecto de fábrica. ¿Está seguro de querer continuar?" - -#: client/src/scheduler/scheduler.strings.js:40 -msgid "Thu" -msgstr "Jue" - -#: client/src/activity-stream/streams.list.js:25 -#: client/src/home/dashboard/lists/jobs/jobs-list.partial.html:14 -#: client/src/notifications/notification-templates-list/list.controller.js:72 -msgid "Time" -msgstr "Duración" - -#: client/src/license/license.partial.html:45 -msgid "Time Remaining" -msgstr "Tiempo restante" - -#: client/src/projects/projects.form.js:197 -msgid "" -"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." -msgstr "" -"Tiempo en segundos a considerar que un proyecto es reciente. Durante la " -"ejecución del trabajo y callbacks la tarea del sistema evaluará la fecha y " -"hora de la última actualización del proyecto. Si es más antigua que el " -"tiempo de expiración de caché, se considera que no es reciente y una nueva " -"actualización del proyecto será llevada a cabo." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:411 -msgid "" -"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." -msgstr "" -"Tiempo en segundos para que la sincronización del inventario esté " -"actualizada. Durante la ejecución de trabajos y callbacks, el sistema de " -"tareas evaluará la marca de tiempo de la última sincronización. Si es " -"anterior al Tiempo de espera para la ejecución del caché, no se considera " -"actualizada y se llevará a cabo una nueva sincronización del inventario." - -#: client/src/credentials/credentials.form.js:125 -msgid "" -"To learn more about the IAM STS Token, refer to the %sAmazon " -"documentation%s." -msgstr "" -"Para aprender más sobre el token de IAM STS, acuda a la documentación de " -"%sAmazon%s." - -#: client/src/shared/form-generator.js:888 -msgid "Toggle the display of plaintext." -msgstr "Conmutar la visualización en texto plano." - -#: client/src/notifications/shared/type-change.service.js:36 -#: client/src/notifications/shared/type-change.service.js:42 -msgid "Token" -msgstr "Token" - -#: client/features/applications/applications.strings.js:16 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:25 -#: client/src/users/users.form.js:235 -msgid "Tokens" -msgstr "Tokens" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:10 -msgid "Total Issues" -msgstr "Total de problemas" - -#: client/src/instance-groups/instance-groups.strings.js:19 -#: client/src/workflow-results/workflow-results.controller.js:60 -msgid "Total Jobs" -msgstr "Tareas totales" - -#: client/src/partials/logviewer.html:6 -msgid "Traceback" -msgstr "Traceback" - -#: client/src/scheduler/scheduler.strings.js:38 -msgid "Tue" -msgstr "Mar" - -#: client/src/credentials/credentials.form.js:60 -#: client/src/credentials/credentials.form.js:84 -#: client/src/inventories-hosts/inventories/inventory.list.js:56 -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:41 -#: client/src/notifications/notificationTemplates.form.js:54 -#: client/src/notifications/notificationTemplates.list.js:39 -#: client/src/notifications/notifications.list.js:32 -#: client/src/projects/projects.list.js:44 -#: client/src/scheduler/scheduled-jobs.list.js:42 -#: client/src/teams/teams.form.js:133 -#: client/src/templates/templates.list.js:31 -#: client/src/users/users.form.js:202 -msgid "Type" -msgstr "Tipo" - -#: client/features/credentials/credentials.strings.js:18 -#: client/src/credentials/credentials.form.js:23 -#: client/src/notifications/notificationTemplates.form.js:26 -msgid "Type Details" -msgstr "Detalles del tipo" - -#: client/src/projects/add/projects-add.controller.js:178 -#: client/src/projects/edit/projects-edit.controller.js:313 -msgid "URL popover text" -msgstr "Texto 'popover' de la URL" - -#: client/src/login/loginModal/loginModal.partial.html:49 -msgid "USERNAME" -msgstr "NOMBRE DE USUARIO" - -#: client/src/activity-stream/get-target-title.factory.js:20 -#: client/src/organizations/linkout/organizations-linkout.route.js:43 -#: client/src/organizations/list/organizations-list.controller.js:55 -#: client/src/users/users.list.js:18 client/src/users/users.list.js:19 -#: client/src/users/users.route.js:8 -msgid "USERS" -msgstr "USUARIOS" - -#: client/lib/components/components.strings.js:24 -msgid "Unable to Submit" -msgstr "No se puede enviar" - -#: client/features/templates/templates.strings.js:84 -msgid "Unable to copy template." -msgstr "No es posible copiar la plantilla." - -#: client/src/instance-groups/instance-groups.strings.js:59 -msgid "Unable to delete instance group." -msgstr "No es posible eliminar el grupo de instancias." - -#: client/features/templates/templates.strings.js:80 -msgid "Unable to delete template." -msgstr "No es posible eliminar la plantilla." - -#: client/features/templates/templates.strings.js:82 -msgid "Unable to determine template type." -msgstr "No es posible determinar el tipo de plantilla." - -#: client/features/templates/templates.strings.js:69 -msgid "Unable to determine this template's type while copying." -msgstr "No es posible determinar este tipo de plantilla mientras se copia." - -#: client/features/templates/templates.strings.js:70 -msgid "Unable to determine this template's type while deleting." -msgstr "No es posible determinar este tipo de plantilla mientras se elimina." - -#: client/features/templates/templates.strings.js:71 -msgid "Unable to determine this template's type while editing." -msgstr "No es posible determinar este tipo de plantilla mientras se edita." - -#: client/features/templates/templates.strings.js:72 -msgid "Unable to determine this template's type while launching." -msgstr "" -"No es posible determinar este tipo de plantilla durante su lanzamiento." - -#: client/features/templates/templates.strings.js:73 -msgid "Unable to determine this template's type while scheduling." -msgstr "No es posible determinar este tipo de plantilla mientras se programa." - -#: client/features/templates/templates.strings.js:79 -msgid "Unable to edit template." -msgstr "No es posible editar la plantilla." - -#: client/src/shared/stateDefinitions.factory.js:231 -msgid "Unable to get resource:" -msgstr "No es posible obtener el recurso:" - -#: client/features/templates/templates.strings.js:81 -msgid "Unable to launch template." -msgstr "No es posible lanzar la plantilla." - -#: client/features/templates/templates.strings.js:83 -msgid "Unable to schedule job." -msgstr "No es posible programar la tarea." - -#: client/src/instance-groups/instance-groups.strings.js:41 -msgid "Unavailable" -msgstr "No disponible" - -#: client/src/instance-groups/instance-groups.strings.js:40 -msgid "Unavailable to run jobs." -msgstr "No disponible para ejecutar tareas." - -#: client/lib/components/components.strings.js:26 -msgid "Unexpected Error" -msgstr "Error inesperado" - -#: client/lib/components/components.strings.js:25 -msgid "Unexpected server error. View the console for more information" -msgstr "" -"Error inesperado del servidor. Consulte la consola para obtener más " -"información." - -#: client/lib/components/components.strings.js:38 -msgid "Unsupported display model type" -msgstr "Tipo de modelo de visualización no compatible" - -#: client/lib/components/components.strings.js:30 -msgid "Unsupported input type" -msgstr "Tipo de entrada no compatible" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:270 -#: client/src/projects/list/projects-list.controller.js:311 -msgid "Update Not Found" -msgstr "Actualización no encontrada" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:345 -msgid "Update Options" -msgstr "Actualizar opciones" - -#: client/src/projects/projects.form.js:178 -msgid "Update Revision on Launch" -msgstr "Revisión de actualización durante el lanzamiento" - -#: client/src/projects/factories/get-project-tool-tip.factory.js:30 -msgid "Update canceled. Click for details" -msgstr "Actualización cancelada. Haga clic para obtener más información." - -#: client/src/projects/factories/get-project-tool-tip.factory.js:24 -msgid "Update failed. Click for details" -msgstr "Actualización fallida. Haga clic para obtener más información." - -#: client/src/projects/edit/projects-edit.controller.js:341 -msgid "Update in Progress" -msgstr "Actualización en curso" - -#: client/src/projects/factories/get-project-tool-tip.factory.js:27 -msgid "Update missing. Click for details" -msgstr "Actualización faltante. Haga clic para obtener más información." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:376 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:381 -msgid "Update on Launch" -msgstr "Actualizar al ejecutar" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:394 -msgid "Update on Project Update" -msgstr "Actualizar en la actualización del proyecto" - -#: client/src/projects/factories/get-project-tool-tip.factory.js:14 -msgid "Update queued. Click for details" -msgstr "Actualización en fila. Haga clic para obtener más información." - -#: client/src/projects/factories/get-project-tool-tip.factory.js:18 -msgid "Update running. Click for details" -msgstr "Actualización en ejecución. Haga clic para obtener más información." - -#: client/src/projects/factories/get-project-tool-tip.factory.js:21 -msgid "Update succeeded. Click for details" -msgstr "Actualización exitosa. Haga clic para obtener más información." - -#: client/src/license/license.partial.html:71 -msgid "Upgrade" -msgstr "Actualizar" - -#: client/src/organizations/organizations.form.js:48 -#: client/src/projects/projects.form.js:209 -#: client/src/templates/job_templates/job-template.form.js:241 -msgid "Use Default Environment" -msgstr "Utilizar entorno predeterminado" - -#: client/src/templates/job_templates/job-template.form.js:314 -#: client/src/templates/job_templates/job-template.form.js:319 -msgid "Use Fact Cache" -msgstr "Usar caché de eventos" - -#: client/src/notifications/notificationTemplates.form.js:466 -msgid "Use SSL" -msgstr "Utilizar SSL" - -#: client/src/notifications/notificationTemplates.form.js:461 -msgid "Use TLS" -msgstr "Utilizar TLS" - -#: client/src/instance-groups/instance-groups.strings.js:20 -#: client/src/instance-groups/instance-groups.strings.js:42 -msgid "Used Capacity" -msgstr "Capacidad usada" - -#: client/src/credentials/credentials.form.js:76 -msgid "" -"Used to check out and synchronize playbook repositories with a remote source" -" control management system such as Git, Subversion (svn), or Mercurial (hg)." -" These credentials are used by Projects." -msgstr "" -"Utilizado para verificar y sincronizar los repositorios playbook con sistema" -" remoto de gestión de código fuente como Git, Subversion (svn) o Mercurial " -"(hg). Estos credenciales son utilizados por proyectos." - -#: client/features/credentials/legacy.credentials.js:80 -#: client/src/credentials/credentials.form.js:457 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:125 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:127 -#: client/src/organizations/organizations.form.js:104 -#: client/src/projects/projects.form.js:265 client/src/teams/teams.form.js:96 -#: client/src/templates/workflows.form.js:158 -msgid "User" -msgstr "Usuario" - -#: client/src/configuration/configuration.partial.html:36 -msgid "User Interface" -msgstr "Interfaz de usuario" - -#: client/src/users/users.form.js:97 -msgid "User Type" -msgstr "Tipo de usuario" - -#: client/src/access/rbac-multiselect/permissionsUsers.list.js:36 -#: client/src/credentials/factories/become-method-change.factory.js:17 -#: client/src/credentials/factories/become-method-change.factory.js:38 -#: client/src/credentials/factories/kind-change.factory.js:17 -#: client/src/credentials/factories/kind-change.factory.js:41 -#: client/src/credentials/factories/kind-change.factory.js:74 -#: client/src/credentials/factories/kind-change.factory.js:95 -#: client/src/notifications/notificationTemplates.form.js:348 -#: client/src/notifications/notificationTemplates.form.js:387 -#: client/src/notifications/notificationTemplates.form.js:64 -#: client/src/users/users.form.js:60 client/src/users/users.list.js:29 -msgid "Username" -msgstr "Usuario" - -#: client/src/credentials/credentials.form.js:80 -msgid "" -"Usernames, passwords, and access keys for authenticating to the specified " -"cloud or infrastructure provider. These are used for smart inventory sources" -" and for cloud provisioning and deployment in playbook runs." -msgstr "" -"Nombres de usuarios, contraseñas y claves de acceso para la autenticación " -"del proveedor de nube o infraestructura especificado. Estos son utilizados " -"para fuentes de inventario inteligente y para el aprovisionamiento y la " -"implementación de nube cuando se ejecuta un playbook." - -#: client/lib/components/components.strings.js:78 -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:35 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:38 -#: client/src/organizations/organizations.form.js:86 -#: client/src/teams/teams.form.js:78 -msgid "Users" -msgstr "Usuarios" - -#: client/src/scheduler/schedulerList.controller.js:46 -msgid "" -"Using a credential that requires a password on launch is prohibited when " -"creating a Job Template schedule" -msgstr "" -"El uso de una credencial que requiere una contraseña durante el lanzamiento " -"está prohibido al crear una programación de la Plantilla de trabajo" - -#: client/lib/components/code-mirror/code-mirror.strings.js:9 -msgid "VARIABLES" -msgstr "VARIABLES" - -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:7 -#: client/src/home/dashboard/lists/jobs/jobs-list.partial.html:7 -msgid "VIEW ALL" -msgstr "VER TODO" - -#: client/lib/components/components.strings.js:57 -msgid "VIEW LESS" -msgstr "VER MENOS" - -#: client/lib/components/components.strings.js:56 -msgid "VIEW MORE" -msgstr "VER MÁS" - -#: client/src/shared/paginate/paginate.partial.html:48 -msgid "VIEW PER PAGE" -msgstr "VISTA POR PÁGINA" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:234 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:248 -msgid "VPC ID:" -msgstr "VPC ID:" - -#: client/src/license/license.partial.html:10 -msgid "Valid License" -msgstr "Licencia válida" - -#: client/src/inventories-hosts/hosts/host.form.js:68 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:46 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js:47 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:67 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:67 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:63 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:71 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:77 -msgid "Variables" -msgstr "Variables" - -#: client/src/job-submission/job-submission.partial.html:364 -msgid "Vault" -msgstr "Vault" - -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js:25 -msgid "Vault ID" -msgstr "ID de Vault" - -#: client/features/templates/templates.strings.js:44 -#: client/src/credentials/credentials.form.js:391 -#: client/src/job-submission/job-submission.partial.html:146 -msgid "Vault Password" -msgstr "Contraseña Vault" - -#: client/features/output/output.strings.js:72 -#: client/features/templates/templates.strings.js:51 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:82 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:91 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:331 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:338 -#: client/src/job-submission/job-submission.partial.html:183 -#: client/src/templates/job_templates/job-template.form.js:173 -#: client/src/templates/job_templates/job-template.form.js:180 -msgid "Verbosity" -msgstr "Nivel de detalle" - -#: client/src/license/license.partial.html:15 -msgid "Version" -msgstr "Versión" - -#: client/src/activity-stream/streams.list.js:63 -#: client/src/credential-types/credential-types.list.js:64 -#: client/src/credentials/credentials.list.js:82 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:58 -#: client/src/inventories-hosts/inventories/inventory.list.js:114 -#: client/src/inventory-scripts/inventory-scripts.list.js:70 -#: client/src/notifications/notificationTemplates.list.js:91 -#: client/src/scheduler/schedules.list.js:93 client/src/teams/teams.list.js:64 -#: client/src/templates/templates.list.js:101 -#: client/src/users/users.list.js:70 -msgid "View" -msgstr "Mostrar" - -#: client/src/bread-crumb/bread-crumb.directive.js:41 -msgid "View Activity Stream" -msgstr "Mostrar el flujo de actividad" - -#: client/lib/components/components.strings.js:66 -msgid "View Documentation" -msgstr "Mostrar la documentación" - -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:86 -#: client/src/inventories-hosts/inventory-hosts.strings.js:27 -msgid "View Insights Data" -msgstr "Ver datos de Insights" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:202 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:226 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:250 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:325 -msgid "View JSON examples at" -msgstr "Ver ejemplos de JSON en" - -#: client/src/inventories-hosts/hosts/host.form.js:78 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:77 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:77 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:73 -msgid "View JSON examples at %s" -msgstr "Mostrar los ejemplos JSON en %s" - -#: client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.partial.html:13 -msgid "View Less" -msgstr "Ver menos" - -#: client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.partial.html:11 -msgid "View More" -msgstr "Ver más" - -#: client/features/output/output.strings.js:27 -msgid "View Project checkout results" -msgstr "Ver resultados de verificación del proyecto" - -#: client/src/shared/form-generator.js:1739 -#: client/src/templates/job_templates/job-template.form.js:459 -#: client/src/templates/workflows.form.js:196 -msgid "View Survey" -msgstr "Mostrar la encuesta" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:203 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:227 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:251 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:326 -msgid "View YAML examples at" -msgstr "Ver ejemplos de YAML en" - -#: client/src/inventories-hosts/hosts/host.form.js:79 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:78 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:78 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:74 -msgid "View YAML examples at %s" -msgstr "Mostrar los ejemplos YAML en %s" - -#: client/src/credentials/credentials.list.js:84 -msgid "View credential" -msgstr "Mostrar credencial" - -#: client/src/credential-types/credential-types.list.js:66 -msgid "View credential type" -msgstr "Ver tipo de credencial" - -#: client/src/activity-stream/streams.list.js:67 -msgid "View event details" -msgstr "Mostrar detalles del evento" - -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:93 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:103 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:91 -msgid "View group" -msgstr "Ver grupo" - -#: client/src/inventories-hosts/hosts/host.list.js:89 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:79 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:93 -#: client/src/inventories-hosts/inventory-hosts.strings.js:26 -msgid "View host" -msgstr "Ver host" - -#: client/src/inventories-hosts/inventories/inventory.list.js:116 -msgid "View inventory" -msgstr "Mostrar inventario" - -#: client/src/inventory-scripts/inventory-scripts.list.js:72 -msgid "View inventory script" -msgstr "Mostrar script de inventario" - -#: client/src/notifications/notificationTemplates.list.js:93 -msgid "View notification" -msgstr "Mostrar notificación" - -#: client/src/scheduler/schedules.list.js:95 -msgid "View schedule" -msgstr "Mostrar el calendario" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:110 -msgid "View source" -msgstr "Ver fuente" - -#: client/src/teams/teams.list.js:67 -msgid "View team" -msgstr "Mostrar equipo" - -#: client/src/templates/templates.list.js:103 -msgid "View template" -msgstr "Mostrar plantilla" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:246 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:261 -msgid "View the" -msgstr "Ver" - -#: client/features/output/output.strings.js:21 -#: client/lib/components/components.strings.js:61 -msgid "View the Credential" -msgstr "Ver credencial" - -#: client/features/output/output.strings.js:24 -msgid "View the Inventory" -msgstr "Ver inventario" - -#: client/features/output/output.strings.js:25 -msgid "View the Job Template" -msgstr "Ver la plantilla de tareas" - -#: client/features/output/output.strings.js:26 -msgid "View the Project" -msgstr "Ver el proyecto" - -#: client/features/output/output.strings.js:28 -msgid "View the Schedule" -msgstr "Ver la programación" - -#: client/features/output/output.strings.js:30 -msgid "View the User" -msgstr "Ver el usuario" - -#: client/src/projects/projects.list.js:109 -msgid "View the project" -msgstr "Mostrar el proyecto" - -#: client/src/scheduler/scheduled-jobs.list.js:74 -msgid "View the schedule" -msgstr "Mostrar el calendario" - -#: client/features/output/output.strings.js:29 -msgid "View the source Workflow Job" -msgstr "Ver la tarea del flujo de trabajo de origen" - -#: client/src/users/users.list.js:73 -msgid "View user" -msgstr "Mostrar usuario" - -#: client/lib/components/components.strings.js:89 -msgid "Views" -msgstr "Vistas" - -#: client/src/templates/workflows.form.js:20 -msgid "WORKFLOW" -msgstr "FLUJO DE TRABAJO" - -#: client/features/templates/templates.strings.js:119 -msgid "WORKFLOW VISUALIZER" -msgstr "VISUALIZADOR DE FLUJOS DE TRABAJO" - -#: client/features/templates/templates.strings.js:105 -#: client/src/scheduler/scheduler.strings.js:58 -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:214 -msgid "Warning" -msgstr "Advertencia" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:92 -#: client/src/configuration/configuration.controller.js:229 -#: client/src/configuration/configuration.controller.js:314 -#: client/src/configuration/system-form/configuration-system.controller.js:55 -msgid "Warning: Unsaved Changes" -msgstr "Aviso: modificaciones no guardadas" - -#: client/src/scheduler/scheduler.strings.js:39 -msgid "Wed" -msgstr "Mié" - -#: client/src/license/license.partial.html:78 -msgid "" -"Welcome to Ansible Tower! Please complete the steps below to acquire a " -"license." -msgstr "" -"¡Bienvenido a Ansible Tower! Por favor complete los siguientes pasos para " -"adquirir una licencia." - -#: client/src/login/loginModal/loginModal.partial.html:17 -msgid "Welcome to Ansible {{BRAND_NAME}}!  Please sign in." -msgstr "¡Bienvenido a Ansible {{BRAND_NAME}}!  Inicie sesión." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:368 -msgid "" -"When not checked, a merge will be performed, combining local variables with " -"those found on the external source." -msgstr "" -"Si la opción no está marcada, se llevará a cabo una fusión y se combinarán " -"las variables locales con las variables halladas en la fuente externa." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:356 -msgid "" -"When not checked, local child hosts and groups not found on the external " -"source will remain untouched by the inventory update process." -msgstr "" -"Si la opción no está marcada, los hosts hijos y los grupos locales que no se" -" encuentren en la fuente externa no se modificarán a partir del proceso de " -"actualización del inventario." - -#: client/features/jobs/jobs.strings.js:11 -msgid "Workflow Job" -msgstr "Tarea de flujo de trabajo" - -#: client/lib/models/models.strings.js:45 -msgid "Workflow Job Template Nodes" -msgstr "Nodos de la plantilla de tareas de flujo de trabajo" - -#: client/features/templates/templates.strings.js:14 -#: client/src/templates/templates.list.js:66 -msgid "Workflow Template" -msgstr "Plantilla de flujo de trabajo" - -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:109 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:41 -msgid "Workflow Templates" -msgstr "Plantillas de flujo de trabajo" - -#: client/src/shared/form-generator.js:1743 -#: client/src/templates/workflows.form.js:222 -msgid "Workflow Visualizer" -msgstr "Visualizador de flujos de trabajo" - -#: client/features/users/tokens/tokens.strings.js:31 -msgid "Write" -msgstr "Escribir" - -#: client/lib/components/code-mirror/code-mirror.strings.js:11 -#: client/lib/services/base-string.service.js:69 -#: client/src/job-submission/job-submission.partial.html:171 -msgid "YAML" -msgstr "YAML" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:200 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:224 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:248 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:323 -msgid "YAML:" -msgstr "YAML:" - -#: client/lib/services/base-string.service.js:73 -msgid "YES" -msgstr "SÍ" - -#: client/src/notifications/add/add.controller.js:83 -#: client/src/notifications/edit/edit.controller.js:130 -msgid "Yellow" -msgstr "Amarillo" - -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:53 -msgid "" -"You can create a job template here." -msgstr "" -"Usted puede crear una plantilla de trabajo aquí." - -#: client/features/templates/templates.strings.js:89 -msgid "" -"You do not have access to all resources used by this workflow. Resources " -"that you don't have access to will not be copied and will result in an " -"incomplete workflow." -msgstr "" -"Usted no tiene acceso a todos los recursos utilizados por este flujo de " -"trabajo. Los recursos a los que no tiene acceso no se copiarán y darán como " -"resultado un flujo de trabajo incompleto." - -#: client/src/projects/edit/projects-edit.controller.js:64 -msgid "You do not have access to view this property" -msgstr "Usted no tiene permiso para ver esta propiedad" - -#: client/src/projects/add/projects-add.controller.js:32 -msgid "You do not have permission to add a project." -msgstr "Usted no tiene permiso para añadir un proyecto." - -#: client/src/users/add/users-add.controller.js:44 -msgid "You do not have permission to add a user." -msgstr "Usted no tiene permiso para añadir un usuario." - -#: client/src/access/rbac-multiselect/rbac-multiselect-list.directive.js:174 -msgid "You do not have permission to manage this user" -msgstr "No tiene permiso para gestionar este usuario." - -#: client/features/templates/templates.strings.js:68 -msgid "You do not have permission to perform this action." -msgstr "No tiene permiso para realizar esta acción." - -#: client/src/inventories-hosts/inventory-hosts.strings.js:41 -msgid "You do not have sufficient permissions to edit the host filter." -msgstr "No tiene permisos suficientes para editar el filtro del host." - -#: client/src/configuration/auth-form/configuration-auth.controller.js:91 -#: client/src/configuration/configuration.controller.js:228 -#: client/src/configuration/configuration.controller.js:313 -#: client/src/configuration/system-form/configuration-system.controller.js:54 -msgid "" -"You have unsaved changes. Would you like to proceed without" -" saving?" -msgstr "" -"Usted tiene modificaciones sin guardar.¿Desea proceder sin " -"guardarlas?" - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:204 -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:207 -msgid "" -"You must run a successful update before you can select a playbook. You will " -"not be able to save this Job Template without a valid playbook." -msgstr "" -"Debe ejecutar una actualización exitosa para poder seleccionar una playbook." -" No podrá guardar esta Plantilla de trabajo sin una playbook válida." - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:224 -#: client/src/projects/list/projects-list.controller.js:267 -msgid "Your request to cancel the update was submitted to the task manager." -msgstr "" -"Su solicitud de cancelación de la actualización ha sido enviada al gestor de" -" tareas." - -#: client/src/login/loginModal/loginModal.partial.html:22 -msgid "Your session timed out due to inactivity. Please sign in." -msgstr "Su sesión ha expirado debido a inactividad. Por favor inicie sesión." - -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:24 -#: client/src/job-submission/job-submission.partial.html:317 -#: client/src/shared/form-generator.js:1213 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:42 -msgid "and" -msgstr "y" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:245 -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:270 -msgid "button to view the latest status." -msgstr "botón para ver el último estado." - -#: client/features/users/tokens/tokens.strings.js:27 -msgid "by" -msgstr "por" - -#: client/src/job-submission/job-submission.partial.html:289 -#: client/src/job-submission/job-submission.partial.html:294 -#: client/src/job-submission/job-submission.partial.html:305 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:14 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:19 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:30 -msgid "characters long." -msgstr "caracteres." - -#: client/features/output/output.strings.js:79 -#: client/src/shared/smart-search/smart-search.partial.html:50 -msgid "documentation" -msgstr "documentación" - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:204 -msgid "failed" -msgstr "falló" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:247 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:262 -msgid "for a complete list of supported filters." -msgstr "para obtener una lista completa de filtros compatibles." - -#: client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.directive.js:82 -msgid "from the" -msgstr "del" - -#: client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.directive.js:82 -#: client/src/inventories-hosts/inventory-hosts.strings.js:8 -msgid "group" -msgid_plural "groups" -msgstr[0] "grupo" -msgstr[1] "grupos" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:26 -msgid "groups" -msgstr "grupos" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:24 -msgid "groups and" -msgstr "grupos y" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:9 -msgid "host" -msgid_plural "hosts" -msgstr[0] "host" -msgstr[1] "hosts" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:24 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:25 -msgid "hosts" -msgstr "hosts" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:65 -msgid "hosts with failures. Click for details." -msgstr "hosts con fallos. Haga clic para obtener más información." - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:210 -msgid "missing" -msgstr "no encontrado" - -#: client/src/access/rbac-multiselect/permissionsTeams.list.js:21 -msgid "name" -msgstr "nombre" - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:207 -msgid "never updated" -msgstr "nunca actualizado" - -#: client/src/shared/paginate/paginate.partial.html:34 -msgid "of" -msgstr "de" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:239 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:254 -msgid "of the filters match." -msgstr "de la coincidencia con los filtros." - -#: client/src/scheduler/scheduler.strings.js:34 -msgid "on" -msgstr "en" - -#: client/src/scheduler/scheduler.strings.js:31 -msgid "on day" -msgstr "el día" - -#: client/src/scheduler/scheduler.strings.js:35 -msgid "on days" -msgstr "los días" - -#: client/src/scheduler/scheduler.strings.js:33 -msgid "on the" -msgstr "en la" - -#: client/src/access/rbac-multiselect/permissionsTeams.list.js:24 -msgid "organization" -msgstr "organización" - -#: client/src/shared/form-generator.js:1085 -msgid "playbook" -msgstr "playbook" - -#: client/src/organizations/linkout/organizations-linkout.route.js:111 -#: client/src/organizations/linkout/organizations-linkout.route.js:158 -#: client/src/organizations/linkout/organizations-linkout.route.js:209 -msgid "section" -msgstr "sección" - -#: client/src/credentials/credentials.form.js:138 -#: client/src/credentials/credentials.form.js:364 -msgid "set in helpers/credentials" -msgstr "definir en helpers/credentials" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:48 -msgid "sources with sync failures. Click for details" -msgstr "" -"fuentes con fallos en la sincronización. Haga clic para obtener más " -"información" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:244 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:259 -msgid "test" -msgstr "prueba" - -#: client/src/job-submission/job-submission.partial.html:289 -#: client/src/job-submission/job-submission.partial.html:294 -#: client/src/job-submission/job-submission.partial.html:305 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:14 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:19 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:30 -msgid "to" -msgstr "para" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:139 -msgid "" -"to include all regions. Only Hosts associated with the selected regions will" -" be updated." -msgstr "" -"para incluir todas las regiones. Solo se actualizarán los hosts asociados " -"con las regiones seleccionadas." - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:17 -msgid "to start it now." -msgstr "para comenzarlo ahora." - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:25 -msgid "to update." -msgstr "para actualizar." - -#: client/src/credentials/credentials.form.js:381 -msgid "v2 URLs%s - leave blank" -msgstr "v2 URLs%s - dejad en blanco" - -#: client/src/credentials/credentials.form.js:382 -msgid "v3 default%s - set to 'default'" -msgstr "v3 default%s - establecer a 'default'" - -#: client/src/credentials/credentials.form.js:383 -msgid "v3 multi-domain%s - your domain name" -msgstr "v3 multi-domain%s - vuestro nombre de dominio" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:319 -msgid "view azure_rm.ini in the Ansible github repo." -msgstr "vea azure_rm.ini en el repositorio github de Ansible." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:220 -msgid "view ec2.ini in the Ansible github repo." -msgstr "vea ec2.ini en el repositorio github de Ansible." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:244 -msgid "view vmware_inventory.ini in the Ansible github repo." -msgstr "vea vmware_inventory.ini en el repositorio github de Ansible." - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:239 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:254 -msgid "when" -msgstr "cuando" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:225 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:239 -msgid "" -"will create group names similar to the following examples based on the " -"options selected:" -msgstr "" -"se crearán nombres de grupos similares a los de los siguientes ejemplos en " -"función de las opciones seleccionadas:" - -#: client/src/inventories-hosts/inventories/related/groups/factories/get-hosts-status-msg.factory.js:11 -msgid "with failed jobs." -msgstr "con tareas con errores." - -#: client/features/users/tokens/tokens.strings.js:42 -msgid "{{ appName }} Token" -msgstr "Token de {{ appName }}" - -#: client/lib/services/base-string.service.js:102 -msgid "{{ header }} {{ body }}" -msgstr "{{ header }} {{ body }}" - -#: client/lib/services/base-string.service.js:75 -msgid "{{ resource }} successfully created" -msgstr "{{ resource }} creado correctamente" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:31 -msgid "{{ str1 }}

{{ str2 }}

" -msgstr "{{ str1 }}

{{ str2 }}

" - -#: client/src/templates/prompt/steps/other-prompts/prompt-other-prompts.partial.html:5 -msgid "{{:: vm.strings.get('prompt.JOB_TYPE') }}" -msgstr "{{:: vm.strings.get('prompt.JOB_TYPE') }}" - -#: client/lib/components/input/label.partial.html:5 -msgid "{{::state._hint}}" -msgstr "{{::state._hint}}" - -#: client/src/shared/paginate/paginate.partial.html:55 -msgid "{{pageSize}}" -msgstr "{{pageSize}}" diff --git a/awx/ui/po/fr.po b/awx/ui/po/fr.po deleted file mode 100644 index 361ee0485a52..000000000000 --- a/awx/ui/po/fr.po +++ /dev/null @@ -1,7033 +0,0 @@ -# aude_stoquart , 2017. #zanata -# croe , 2017. #zanata -# mkim , 2017. #zanata -# shanemcd , 2017. #zanata -# croe , 2018. #zanata -msgid "" -msgstr "" -"Project-Id-Version: \n" -"PO-Revision-Date: 2018-08-15 11:15+0000\n" -"Last-Translator: croe \n" -"Language-Team: French\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: fr\n" -"Plural-Forms: nplurals=2; plural=(n > 1)\n" -"X-Generator: Zanata 4.6.0\n" - -#: client/src/projects/add/projects-add.controller.js:162 -#: client/src/projects/edit/projects-edit.controller.js:297 -msgid "" -"%sNote:%s Mercurial does not support password authentication for SSH. Do not" -" put the username and key in the URL. If using Bitbucket and SSH, do not " -"supply your Bitbucket username." -msgstr "" -"%sRemarque%s : Mercurial ne prend pas en charge l’authentification par mot " -"de passe pour SSH. N’entrez ni le nom d’utilisateur, ni la clé dans l’URL. " -"Si vous utilisez Bitbucket et SSH, ne saisissez pas votre nom d’utilisateur " -"Bitbucket." - -#: client/src/projects/add/projects-add.controller.js:141 -#: client/src/projects/edit/projects-edit.controller.js:276 -msgid "" -"%sNote:%s 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." -msgstr "" -"%sRemarque%s : 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." - -#: client/src/credentials/credentials.form.js:287 -msgid "(defaults to %s)" -msgstr "(défini par défaut sur %s)" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:402 -msgid "(seconds)" -msgstr "(secondes)" - -#: client/src/shared/paginate/paginate.partial.html:66 -msgid "100" -msgstr "100" - -#: client/src/shared/paginate/paginate.partial.html:60 -msgid "20" -msgstr "20" - -#: client/src/shared/paginate/paginate.partial.html:63 -msgid "50" -msgstr "50" - -#: client/lib/components/code-mirror/code-mirror.strings.js:17 -msgid "" -"

\n" -" Enter inventory variables using either JSON or YAML\n" -" syntax. Use the radio button to toggle between the two.\n" -"

\n" -" JSON:\n" -"
\n" -"
\n" -" {\n" -"
\"somevar\": \"somevalue\",\n" -"
\"password\": \"magic\"\n" -"
\n" -" }\n" -"
\n" -" YAML:\n" -"
\n" -"
\n" -" ---\n" -"
somevar: somevalue\n" -"
password: magic\n" -"
\n" -"
\n" -"

\n" -" View JSON examples at\n" -" www.json.org\n" -"

\n" -"

\n" -" View YAML examples at\n" -" \n" -" docs.ansible.com\n" -"

" -msgstr "" -"

\n" -" Saisir les variables d'inventaire en utilisant la syntaxe JSON ou YAML.\n" -" Utiliser le bouton radio pour basculer d'une syntaxe à l'autre.\n" -"

\n" -" JSON:\n" -"
\n" -"
\n" -" {\n" -"
\"somevar\": \"somevalue\",\n" -"
\"password\": \"magic\"\n" -"
\n" -" }\n" -"
\n" -" YAML:\n" -"
\n" -"
\n" -" ---\n" -"
somevar: somevalue\n" -"
password: magic\n" -"
\n" -"
\n" -"

\n" -" View JSON examples at\n" -" www.json.org\n" -"

\n" -"

\n" -" Exemples YAML\n" -" \n" -" docs.ansible.com\n" -"

" - -#: client/features/templates/templates.strings.js:55 -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.

JSON:
{
"somevar": "somevalue",
"password": " -""magic"
}
YAML:
---
somevar: somevalue
password: magic
" -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. " -"

JSON:
{
"somevar": " -""somevalue",
"password": "magic"
" -"}
YAML:
---
somevar: somevalue
password: magic
" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:33 -#: client/src/scheduler/scheduler.strings.js:22 -msgid "A schedule name is required." -msgstr "Un intitulé est requis pour le planning" - -#: client/src/users/add/users-add.controller.js:103 -msgid "A value is required" -msgstr "Entrez une valeur" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:167 -msgid "A value is required." -msgstr "Entrez une valeur." - -#: client/src/about/about.route.js:10 -msgid "ABOUT" -msgstr "À PROPOS" - -#: client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html:16 -msgid "ACTION" -msgstr "ACTION" - -#: client/src/activity-stream/activity-detail.form.js:23 -msgid "ACTIVITY DETAIL" -msgstr "DÉTAILS ACTIVITÉ" - -#: client/src/activity-stream/activitystream.route.js:28 -#: client/src/activity-stream/streams.list.js:14 -#: client/src/activity-stream/streams.list.js:15 -msgid "ACTIVITY STREAM" -msgstr "FLUX D’ACTIVITÉ" - -#: client/src/organizations/linkout/addUsers/addUsers.partial.html:8 -msgid "ADD" -msgstr "AJOUTER" - -#: client/src/notifications/notification-templates-list/add-notifications-action.partial.html:3 -msgid "ADD A NEW TEMPLATE" -msgstr "AJOUTER UN NOUVEAU MODÈLE" - -#: client/features/templates/templates.strings.js:107 -msgid "ADD A TEMPLATE" -msgstr "AJOUTER UN MODÈLE" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:16 -msgid "ADD SURVEY PROMPT" -msgstr "AJOUTER UNE INVITE AU QUESTIONNAIRE" - -#: client/src/shared/smart-search/smart-search.partial.html:48 -msgid "ADDITIONAL INFORMATION" -msgstr "INFORMATIONS SUPPLÉMENTAIRES" - -#: client/features/output/output.strings.js:76 -msgid "ADDITIONAL_INFORMATION" -msgstr "INFORMATIONS_SUPPLÉMENTAIRES" - -#: client/src/organizations/linkout/organizations-linkout.route.js:258 -#: client/src/organizations/list/organizations-list.controller.js:85 -msgid "ADMINS" -msgstr "ADMINS" - -#: client/src/activity-stream/get-target-title.factory.js:4 -msgid "ALL ACTIVITY" -msgstr "ACTIVITÉS" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:239 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:254 -msgid "ANY" -msgstr "QUELCONQUE" - -#: client/src/credentials/credentials.form.js:198 -msgid "API Key" -msgstr "Clé API" - -#: client/src/notifications/notificationTemplates.form.js:243 -msgid "API Service/Integration Key" -msgstr "Service API/Clé d’intégration" - -#: client/src/notifications/shared/type-change.service.js:60 -msgid "API Token" -msgstr "Jeton API" - -#: client/features/users/tokens/tokens.strings.js:40 -msgid "APPLICATION" -msgstr "APPLICATION" - -#: client/features/applications/applications.strings.js:28 -#: client/features/applications/applications.strings.js:8 -#: client/src/activity-stream/get-target-title.factory.js:47 -msgid "APPLICATIONS" -msgstr "APPLICATIONS" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.route.js:19 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.route.js:19 -msgid "ASSOCIATED GROUPS" -msgstr "GROUPES ASSOCIÉS" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.route.js:19 -msgid "ASSOCIATED HOSTS" -msgstr "HÔTES ASSOCIÉS" - -#: client/lib/components/components.strings.js:87 -msgid "About" -msgstr "À propos de " - -#: client/lib/components/components.strings.js:91 -msgid "Access" -msgstr "Accès" - -#: client/src/credentials/credentials.form.js:91 -msgid "Access Key" -msgstr "Clé d’accès" - -#: client/src/notifications/notificationTemplates.form.js:221 -msgid "Account SID" -msgstr "SID de compte" - -#: client/src/notifications/notificationTemplates.form.js:180 -msgid "Account Token" -msgstr "Jeton de compte" - -#: client/src/activity-stream/activity-detail.form.js:36 -msgid "Action" -msgstr "Action" - -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:20 -#: client/src/inventories-hosts/hosts/hosts.partial.html:47 -#: client/src/shared/list-generator/list-generator.factory.js:591 -msgid "Actions" -msgstr "Actions" - -#: client/features/templates/templates.strings.js:15 -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:17 -#: client/src/templates/templates.list.js:36 -msgid "Activity" -msgstr "Activité" - -#: client/src/configuration/system-form/configuration-system.controller.js:88 -msgid "Activity Stream" -msgstr "Flux d’activité" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:113 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:115 -#: client/src/organizations/organizations.form.js:93 -#: client/src/teams/teams.form.js:85 -#: client/src/templates/workflows.form.js:147 -msgid "Add" -msgstr "Ajouter" - -#: client/src/credentials/credentials.list.js:14 -msgid "Add Credentials" -msgstr "Ajouter des informations d’identification" - -#: client/src/inventories-hosts/inventories/inventory.list.js:13 -msgid "Add Inventories" -msgstr "Ajouter des inventaires" - -#: client/src/shared/stateDefinitions.factory.js:304 -msgid "Add Permissions" -msgstr "Ajouter les permissions" - -#: client/src/projects/projects.list.js:13 -msgid "Add Project" -msgstr "Ajouter un projet" - -#: client/src/shared/form-generator.js:1731 -#: client/src/templates/job_templates/job-template.form.js:468 -#: client/src/templates/workflows.form.js:205 -msgid "Add Survey" -msgstr "Ajouter un questionnaire" - -#: client/src/teams/teams.list.js:13 -msgid "Add Team" -msgstr "Ajouter une équipe" - -#: client/src/teams/teams.form.js:86 -msgid "Add User" -msgstr "Ajouter un utilisateur" - -#: client/src/shared/stateDefinitions.factory.js:426 -#: client/src/shared/stateDefinitions.factory.js:594 -#: client/src/users/users.list.js:17 -msgid "Add Users" -msgstr "Ajouter des utilisateurs" - -#: client/src/organizations/organizations.form.js:94 -msgid "Add Users to this organization." -msgstr "Ajouter des utilisateurs à cette organisation." - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:69 -msgid "Add a group" -msgstr "Ajouter un groupe" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:118 -msgid "Add a host" -msgstr "Ajouter un hôte" - -#: client/src/scheduler/schedules.list.js:74 -msgid "Add a new schedule" -msgstr "Ajouter un nouveau planning" - -#: client/features/credentials/legacy.credentials.js:71 -#: client/src/credentials/credentials.form.js:448 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:115 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:117 -#: client/src/projects/projects.form.js:255 -#: client/src/templates/job_templates/job-template.form.js:411 -#: client/src/templates/workflows.form.js:148 -msgid "Add a permission" -msgstr "Ajouter une permission" - -#: client/src/shared/form-generator.js:1466 -msgid "Admin" -msgstr "Administrateur" - -#: client/lib/components/components.strings.js:92 -msgid "Administration" -msgstr "Administration" - -#: client/src/organizations/linkout/organizations-linkout.route.js:281 -msgid "Admins" -msgstr "Admins" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:391 -msgid "" -"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." -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 des " -"tâches liées à un travail. Le but est le contenu statique, comme le format " -".ini de fichier d'inventaire Ansible." - -#: client/lib/components/components.strings.js:99 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:37 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:43 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:65 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:74 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:139 -msgid "All" -msgstr "Tous" - -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:23 -msgid "All Activity" -msgstr "Activités" - -#: client/features/portalMode/index.view.html:33 -msgid "All Jobs" -msgstr "Tous les jobs" - -#: client/src/templates/job_templates/job-template.form.js:290 -#: client/src/templates/job_templates/job-template.form.js:297 -msgid "Allow Provisioning Callbacks" -msgstr "Autoriser les rappels d’exécution de Tower job_template" - -#: client/features/templates/templates.strings.js:102 -#: client/src/workflow-results/workflow-results.controller.js:66 -msgid "Always" -msgstr "Toujours" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:270 -msgid "An SCM update does not appear to be running for project:" -msgstr "Une mise à jour SCM ne semble pas s'exécuter pour le projet :" - -#: client/src/projects/list/projects-list.controller.js:311 -msgid "" -"An SCM update does not appear to be running for project: %s. Click the " -"%sRefresh%s button to view the latest status." -msgstr "" -"Une mise à jour SCM ne semble pas s’exécuter pour le projet : %s. Cliquez " -"sur le bouton %sActualiser%s pour voir l’état le plus récent." - -#: client/src/organizations/organizations.form.js:47 -#: client/src/organizations/organizations.form.js:52 -#: client/src/projects/projects.form.js:207 -#: client/src/projects/projects.form.js:212 -#: client/src/templates/job_templates/job-template.form.js:239 -#: client/src/templates/job_templates/job-template.form.js:245 -msgid "Ansible Environment" -msgstr "Environnement Ansible" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:62 -#: client/src/templates/survey-maker/shared/question-definition.form.js:68 -msgid "Answer Type" -msgstr "Type de réponse" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:44 -#: client/src/templates/survey-maker/shared/question-definition.form.js:53 -msgid "Answer Variable Name" -msgstr "Nom de variable de réponse" - -#: client/lib/components/components.strings.js:85 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:24 -msgid "Applications" -msgstr "Applications" - -#: client/src/notifications/notification-templates-list/list.controller.js:228 -msgid "Are you sure you want to delete this notification template?" -msgstr "Voulez-vous vraiment supprimer ce modèle de notification ?" - -#: client/src/teams/list/teams-list.controller.js:80 -msgid "Are you sure you want to delete this team?" -msgstr "Voulez-vous vraiment supprimer cette équipe ?" - -#: client/src/users/list/users-list.controller.js:93 -msgid "Are you sure you want to delete this user?" -msgstr "Voulez-vous vraiment supprimer cet utilisateur ?" - -#: client/features/templates/templates.strings.js:98 -msgid "Are you sure you want to delete this workflow node?" -msgstr "Voulez-vous vraiment supprimer ce node de workflow ?" - -#: client/lib/services/base-string.service.js:81 -msgid "Are you sure you want to delete this {{ resourceType }}?" -msgstr "Voulez-vous vraiment supprimer ce {{ resourceType }} ?" - -#: client/src/partials/survey-maker-modal.html:13 -msgid "Are you sure you want to delete this {{deleteMode}}?" -msgstr "Êtes-vous certain de vouloir supprimer ce {{deleteMode}}?" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-disassociate.partial.html:25 -msgid "Are you sure you want to disassociate the group below from" -msgstr "Voulez-vous vraiment supprimer le groupe ci-dessous de" - -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.partial.html:23 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-disassociate.partial.html:25 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-disassociate.partial.html:26 -msgid "Are you sure you want to disassociate the host below from" -msgstr "Voulez-vous vraiment supprimer l'hôte ci-dessous de" - -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:47 -msgid "" -"Are you sure you want to permanently delete the group below from the " -"inventory?" -msgstr "" -"Voulez-vous vraiment supprimer définitivement le groupe ci-dessous de " -"l'inventaire ?" - -#: client/src/inventories-hosts/inventories/related/hosts/list/host-list.controller.js:106 -msgid "" -"Are you sure you want to permanently delete the host below from the " -"inventory?" -msgstr "" -"Voulez-vous vraiment supprimer définitivement l'hôte ci-dessous de " -"l'inventaire ?" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:69 -msgid "" -"Are you sure you want to permanently delete the inventory source below from " -"the inventory?" -msgstr "" -"Voulez-vous vraiment supprimer définitivement la source de l'inventaire ci-" -"dessous ?" - -#: client/src/projects/edit/projects-edit.controller.js:253 -msgid "Are you sure you want to remove the %s below from %s?" -msgstr "Voulez-vous vraiment supprimer le %s ci-dessous de %s ?" - -#: client/lib/services/base-string.service.js:86 -msgid "Are you sure you want to submit the request to cancel this job?" -msgstr "Voulez-vous vraiment demander l'annulation de ce job ?" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:39 -msgid "Arguments" -msgstr "Arguments" - -#: client/src/credentials/credentials.form.js:232 -#: client/src/credentials/credentials.form.js:271 -#: client/src/credentials/credentials.form.js:311 -#: client/src/credentials/credentials.form.js:397 -msgid "Ask at runtime?" -msgstr "Demander durant l’éxecution ?" - -#: client/src/instance-groups/instance-groups.strings.js:31 -msgid "Associate an existing Instance" -msgstr "Associer une instance existante" - -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:69 -msgid "Associate an existing group" -msgstr "Associer un groupe existant" - -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.list.js:51 -msgid "Associate this host with a new group" -msgstr "Associer cet hôte à un nouveau groupe" - -#: client/src/shared/form-generator.js:1468 -msgid "Auditor" -msgstr "Auditeur" - -#: client/src/configuration/configuration.partial.html:15 -msgid "Authentication" -msgstr "Authentification" - -#: client/src/credentials/credentials.form.js:72 -msgid "" -"Authentication for network device access. This can include SSH keys, " -"usernames, passwords, and authorize information. Network credentials are " -"used when submitting jobs to run playbooks against network devices." -msgstr "" -"Authentification pour l’accès aux périphériques réseau. Il peut s’agir de " -"clés SSH, de noms d’utilisateur, de mots de passe et d’informations " -"d’autorisation. Les informations d’identification réseau sont utilisées au " -"cours de l’envoi de jobs afin d’exécuter des playbooks sur des périphériques" -" réseau." - -#: client/src/credentials/credentials.form.js:68 -msgid "" -"Authentication for remote machine access. This can include SSH keys, " -"usernames, passwords, and sudo information. Machine credentials are used " -"when submitting jobs to run playbooks against remote hosts." -msgstr "" -"Authentification pour l’accès aux machines distantes. Il peut s’agir de clés" -" SSH, de noms d’utilisateur, de mots de passe et d’informations sudo. Les " -"informations d’identification de machine sont utilisées au cours de l’envoi " -"de jobs afin d’exécuter des playbooks sur des hôtes distants." - -#: client/src/credentials/credentials.form.js:343 -msgid "Authorize" -msgstr "Autoriser" - -#: client/src/credentials/credentials.form.js:351 -msgid "Authorize Password" -msgstr "Mot de passe d’autorisation" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:226 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:240 -msgid "Availability Zone:" -msgstr "Zone de disponibilité :" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:152 -msgid "Azure AD" -msgstr "Azure AD" - -#: client/src/shared/directives.js:91 -msgid "BROWSE" -msgstr "NAVIGUER" - -#: client/features/output/output.strings.js:97 -msgid "Back to Top" -msgstr "Retour Haut de page" - -#: client/src/projects/projects.form.js:81 -msgid "" -"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." -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." - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:129 -msgid "Become Privilege Escalation" -msgstr "Activer l’élévation des privilèges" - -#: client/src/license/license.partial.html:107 -msgid "Browse" -msgstr "Parcourir" - -#: client/src/license/license.partial.html:129 -msgid "" -"By default, Tower collects and transmits analytics data on Tower usage to Red Hat. This data is used to enhance future releases of the Tower Software and help streamline customer experience and success. For more information, see\n" -"\t\t\t\t\t\t\t\t\t\t\n" -"\t\t\t\t\t\t\t\t\t\t\t\tthis Tower documentation page\n" -"\t\t\t\t\t\t\t\t\t\t. Uncheck this box to disable this feature." -msgstr "" -"Par défaut, Tower collecte et transmet des données analytiques sur l'utilisation de Tower dans Red Hat. Ces données sont utilisées pour améliorer les nouvelles versions de Tower Software et contribue à rationaliser l'expérience client.\n" -"\t\t\t\t\t\t\t\t\t\t\n" -"\t\t\t\t\t\t\t\t\t\t\t\tcette page de documentation Tower\n" -"\t\t\t\t\t\t\t\t\t\t. Décochez cette case pour désactiver cette fonctionnalité." - -#: client/lib/services/base-string.service.js:60 -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.partial.html:28 -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:51 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-disassociate.partial.html:29 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-disassociate.partial.html:29 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-disassociate.partial.html:30 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:73 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal.partial.html:16 -#: client/src/inventories-hosts/shared/associate-groups/associate-groups.partial.html:16 -#: client/src/inventories-hosts/shared/associate-hosts/associate-hosts.partial.html:16 -#: client/src/job-submission/job-submission.partial.html:370 -#: client/src/partials/survey-maker-modal.html:17 -#: client/src/partials/survey-maker-modal.html:85 -#: client/src/shared/instance-groups-multiselect/instance-groups-modal/instance-groups-modal.partial.html:17 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html:68 -msgid "CANCEL" -msgstr "ANNULER" - -#: client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html:28 -msgid "CHANGES" -msgstr "MODIFICATIONS" - -#: client/features/templates/templates.strings.js:113 -msgid "CHECK" -msgstr "VÉRIFIER" - -#: client/lib/components/components.strings.js:20 -msgid "CHOOSE A FILE" -msgstr "SÉLECTIONNER UN FICHIER" - -#: client/features/output/output.strings.js:78 -#: client/src/shared/smart-search/smart-search.partial.html:26 -msgid "CLEAR ALL" -msgstr "TOUT EFFACER" - -#: client/lib/services/base-string.service.js:61 -#: client/lib/services/base-string.service.js:74 -#: client/src/partials/survey-maker-modal.html:86 -msgid "CLOSE" -msgstr "FERMER" - -#: client/features/jobs/routes/hostCompletedJobs.route.js:20 -#: client/features/jobs/routes/templateCompletedJobs.route.js:21 -#: client/features/jobs/routes/workflowJobTemplateCompletedJobs.route.js:21 -msgid "COMPLETED JOBS" -msgstr "JOBS TERMINÉS" - -#: client/src/configuration/configuration.partial.html:10 -msgid "CONFIGURE {{ BRAND_NAME }}" -msgstr "CONFIGURER {{ BRAND_NAME }}" - -#: client/features/templates/templates.strings.js:31 -#: client/src/scheduler/scheduler.strings.js:63 -msgid "CONFIRM" -msgstr "CONFIRMER" - -#: client/lib/services/base-string.service.js:72 -msgid "COPY" -msgstr "COPIER" - -#: client/features/users/tokens/tokens.strings.js:25 -msgid "COULD NOT CREATE TOKEN" -msgstr "N'A PAS PU CRÉER DE JETON" - -#: client/src/instance-groups/instance-groups.strings.js:46 -msgid "CPU" -msgstr "CPU" - -#: client/src/shared/stateDefinitions.factory.js:161 -msgid "CREATE %s" -msgstr "CRÉER %s" - -#: client/features/applications/applications.strings.js:9 -msgid "CREATE APPLICATION" -msgstr "CRÉER UNE APPLICATION" - -#: client/features/credentials/credentials.strings.js:8 -#: client/src/credentials/credentials.form.js:16 -msgid "CREATE CREDENTIAL" -msgstr "CRÉER IDENTIFIANTS" - -#: client/src/inventories-hosts/inventories/related/groups/add/groups-add.route.js:8 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:16 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-add.route.js:8 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js:16 -msgid "CREATE GROUP" -msgstr "CRÉER UN GROUPE" - -#: client/src/inventories-hosts/hosts/host.form.js:17 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-add.route.js:8 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:17 -#: client/src/inventories-hosts/inventories/related/hosts/add/host-add.route.js:8 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:17 -msgid "CREATE HOST" -msgstr "CRÉER HOTE" - -#: client/src/instance-groups/instance-groups.strings.js:10 -msgid "CREATE INSTANCE GROUP" -msgstr "CRÉER UN GROUPE D'INSTANCES" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.route.js:8 -msgid "CREATE INVENTORY SOURCE" -msgstr "CRÉER UNE SOURCE D'INVENTAIRE" - -#: client/src/inventories-hosts/inventories/related/sources/list/schedule/sources-schedule-add.route.js:9 -#: client/src/scheduler/scheduler.strings.js:8 -#: client/src/scheduler/schedules.route.js:161 -#: client/src/scheduler/schedules.route.js:242 -#: client/src/scheduler/schedules.route.js:73 -msgid "CREATE SCHEDULE" -msgstr "CRÉER PLANNING" - -#: client/src/management-jobs/scheduler/main.js:83 -msgid "CREATE SCHEDULED JOB" -msgstr "CRÉER UN JOB DE PLANNING" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:32 -msgid "CREATE SOURCE" -msgstr "CRÉER UNE SOURCE" - -#: client/features/users/tokens/tokens.strings.js:18 -#: client/features/users/tokens/tokens.strings.js:9 -#: client/features/users/tokens/users-tokens-add.route.js:49 -msgid "CREATE TOKEN" -msgstr "CRÉER JETON" - -#: client/features/output/output.strings.js:101 -msgid "CREATED" -msgstr "CRÉÉ" - -#: client/src/job-submission/job-submission.partial.html:351 -#: client/src/partials/job-template-details.html:2 -msgid "CREDENTIAL" -msgstr "INFORMATIONS D’IDENTIFICATION" - -#: client/src/credential-types/credential-types.form.js:21 -msgid "CREDENTIAL TYPE" -msgstr "TYPE D'INFORMATIONS D'IDENTIFICATION" - -#: client/src/job-submission/job-submission.partial.html:92 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html:60 -msgid "CREDENTIAL TYPE:" -msgstr "TYPE D'INFORMATIONS D'IDENTIFICATION :" - -#: client/src/activity-stream/get-target-title.factory.js:11 -#: client/src/credential-types/credential-types.list.js:12 -#: client/src/credential-types/main.js:44 -msgid "CREDENTIAL TYPES" -msgstr "TYPES D'INFORMATIONS D'IDENTIFICATION" - -#: client/features/credentials/legacy.credentials.js:11 -#: client/src/activity-stream/get-target-title.factory.js:17 -#: client/src/credentials/credentials.list.js:15 -#: client/src/credentials/credentials.list.js:16 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html:5 -msgid "CREDENTIALS" -msgstr "INFORMATIONS D’IDENTIFICATION" - -#: client/features/credentials/credentials.strings.js:30 -msgid "CREDENTIALS PERMISSIONS" -msgstr "PERMISSIONS LIÉES AUX INFORMATIONS D'IDENTIFICATION" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:402 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:414 -#: client/src/projects/projects.form.js:200 -msgid "Cache Timeout" -msgstr "Expiration du délai d’attente du cache" - -#: client/src/projects/projects.form.js:189 -msgid "Cache Timeout%s (seconds)%s" -msgstr "Expiration du délai d’attente du cache%s (secondes)%s" - -#: client/src/projects/list/projects-list.controller.js:224 -#: client/src/users/list/users-list.controller.js:85 -msgid "Call to %s failed. DELETE returned status:" -msgstr "Échec de l’appel de %s. État DELETE renvoyé :" - -#: client/src/projects/list/projects-list.controller.js:291 -#: client/src/projects/list/projects-list.controller.js:308 -msgid "Call to %s failed. GET status:" -msgstr "Échec de l’appel de %s. État GET :" - -#: client/src/projects/edit/projects-edit.controller.js:247 -msgid "Call to %s failed. POST returned status:" -msgstr "Échec de l’appel de %s. État POST renvoyé :" - -#: client/src/projects/list/projects-list.controller.js:270 -msgid "Call to %s failed. POST status:" -msgstr "Échec de l’appel de %s. État POST :" - -#: client/src/management-jobs/card/card.controller.js:29 -msgid "Call to %s failed. Return status: %d" -msgstr "Échec de l’appel de %s. État du renvoie : %d" - -#: client/src/projects/list/projects-list.controller.js:317 -msgid "Call to get project failed. GET status:" -msgstr "Échec de l’appel du projet en GET. État GET :" - -#: client/lib/services/base-string.service.js:93 -msgid "Call to {{ path }} failed. {{ action }} returned status: {{ status }}." -msgstr "" -"La connexion à {{ path }} a échoué. {{ action }} statut renvoyé : {{ status " -"}}." - -#: client/features/output/output.strings.js:17 -#: client/lib/services/base-string.service.js:85 -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:105 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:188 -#: client/src/configuration/configuration.controller.js:617 -#: client/src/scheduler/scheduler.strings.js:56 -#: client/src/shared/form-generator.js:1719 -#: client/src/shared/lookup/lookup-modal.partial.html:19 -#: client/src/workflow-results/workflow-results.controller.js:38 -msgid "Cancel" -msgstr "Annuler" - -#: client/lib/services/base-string.service.js:87 -msgid "Cancel Job" -msgstr "Annuler Job" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:244 -#: client/src/projects/list/projects-list.controller.js:286 -msgid "Cancel Not Allowed" -msgstr "Annulation non autorisée" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:100 -msgid "Cancel sync process" -msgstr "Annuler le processus de synchronisation" - -#: client/src/projects/projects.list.js:122 -msgid "Cancel the SCM update" -msgstr "Annuler la mise à jour SCM" - -#: client/lib/services/base-string.service.js:99 -msgid "Cancel the {{resourceType}}" -msgstr "Annuler {{resourceType}}" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:42 -#: client/src/projects/list/projects-list.controller.js:80 -msgid "Canceled. Click for details" -msgstr "Annulé. Cliquez pour connaître les détails." - -#: client/src/shared/smart-search/smart-search.controller.js:162 -msgid "Cannot search running job" -msgstr "Impossible de rechercher les jobs en cours" - -#: client/src/instance-groups/instance-groups.list.js:22 -msgid "Capacity" -msgstr "Capacité" - -#: client/src/projects/projects.form.js:83 -msgid "Change %s under \"Configure {{BRAND_NAME}}\" to change this location." -msgstr "Modifiez %s sous \"Configure {{BRAND_NAME}}\" pour changer d'emplacement." - -#: client/src/activity-stream/activity-detail.form.js:41 -msgid "Changes" -msgstr "Modifications" - -#: client/src/notifications/notificationTemplates.form.js:355 -msgid "Channel" -msgstr "Canal" - -#: client/features/templates/templates.strings.js:61 -msgid "Check" -msgstr "Vérifier" - -#: client/src/shared/form-generator.js:1087 -msgid "Choose a %s" -msgstr "Choisir un %s" - -#: client/features/templates/templates.strings.js:52 -msgid "Choose a job type" -msgstr "Choisir un type de job" - -#: client/features/templates/templates.strings.js:53 -msgid "Choose a verbosity" -msgstr "Sélectionnez une verbosité" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:64 -msgid "Choose an answer type" -msgstr "Choisissez un type de réponse" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:67 -msgid "" -"Choose an answer type or format you want as the prompt for the user. Refer " -"to the Ansible Tower Documentation for more additional 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 d’Ansible Tower pour en" -" savoir plus sur chaque option." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:112 -msgid "Choose an inventory file" -msgstr "Sélectionner un fichier d'inventaire" - -#: client/src/shared/directives.js:92 -msgid "Choose file" -msgstr "Choisir un fichier" - -#: client/src/license/license.partial.html:97 -msgid "" -"Choose your license file, agree to the End User License Agreement, and click" -" submit." -msgstr "" -"Choisissez votre fichier de licence, acceptez le Contrat de licence de " -"l’utilisateur final et validez." - -#: client/src/projects/projects.form.js:157 -msgid "Clean" -msgstr "Nettoyer" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:299 -msgid "Clear" -msgstr "Effacer" - -#: client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.directive.js:64 -msgid "Click for details" -msgstr "Cliquez pour obtenir plus d’informations" - -#: client/src/templates/workflows/edit-workflow/workflow-edit.controller.js:261 -msgid "Click here to open the workflow visualizer." -msgstr "Cliquez ici pour ouvrir le visualisateur de workflow." - -#: client/src/inventories-hosts/inventories/inventory.list.js:16 -msgid "" -"Click on a row to select it, and click Finished when done. Click the %s " -"button to create a new inventory." -msgstr "" -"Cliquez sur une ligne pour la sélectionner, puis sur Terminé lorsque vous " -"avez fini. Cliquez sur le bouton %s pour créer un inventaire." - -#: client/src/teams/teams.list.js:16 -msgid "" -"Click on a row to select it, and click Finished when done. Click the %s " -"button to create a new team." -msgstr "" -"Cliquez sur une ligne pour la sélectionner, puis sur Terminé lorsque vous " -"avez fini. Cliquez sur le bouton %s pour créer une équipe." - -#: client/src/templates/templates.list.js:17 -msgid "" -"Click on a row to select it, and click Finished when done. Use the %s button" -" to create a new job template." -msgstr "" -"Cliquez sur une ligne pour la sélectionner, puis sur Terminé lorsque vous " -"avez fini. Cliquez sur le bouton %s pour créer un modèle de job." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:138 -msgid "" -"Click on the regions field to see a list of regions for your cloud provider." -" You can select multiple regions, or choose" -msgstr "" -"Cliquez sur le champ régions pour voir la liste des régions associées à " -"votre fournisseur cloud. Vous pouvez choisir plusieurs régions ou l'option" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:270 -msgid "Click the" -msgstr "Cliquer" - -#: client/src/scheduler/scheduler.strings.js:13 -msgid "Click to edit schedule." -msgstr "Cliquez pour modifier le planning." - -#: client/src/credentials/credentials.form.js:321 -msgid "Client ID" -msgstr "ID du client" - -#: client/src/notifications/notificationTemplates.form.js:254 -msgid "Client Identifier" -msgstr "Identifiant client" - -#: client/src/credentials/credentials.form.js:330 -msgid "Client Secret" -msgstr "Question secrète du client" - -#: client/src/scheduler/scheduler.strings.js:55 -#: client/src/shared/form-generator.js:1723 -msgid "Close" -msgstr "Fermer" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:26 -msgid "Cloud source not configured." -msgstr "Source cloud non configurée." - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:25 -msgid "Cloud source not configured. Click" -msgstr "Source cloud non configurée. Cliquez sur" - -#: client/src/credentials/factories/become-method-change.factory.js:80 -#: client/src/credentials/factories/kind-change.factory.js:137 -msgid "CloudForms URL" -msgstr "URL CloudForms" - -#: client/features/output/output.strings.js:18 -#: client/src/workflow-results/workflow-results.controller.js:152 -msgid "Collapse Output" -msgstr "Tout réduire" - -#: client/src/inventories-hosts/hosts/host.form.js:129 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:128 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:155 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:172 -#: client/src/templates/job_templates/job-template.form.js:443 -#: client/src/templates/workflows.form.js:180 -msgid "Completed Jobs" -msgstr "Jobs complétés" - -#: client/src/management-jobs/card/card.partial.html:34 -msgid "Configure Notifications" -msgstr "Configurer les notifications" - -#: client/src/users/users.form.js:83 -msgid "Confirm Password" -msgstr "Confirmer le mot de passe" - -#: client/src/configuration/configuration.controller.js:624 -msgid "Confirm Reset" -msgstr "Confirmer la réinitialisation" - -#: client/src/configuration/configuration.controller.js:633 -msgid "Confirm factory reset" -msgstr "Confirmer la réinitialisation usine" - -#: client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.directive.js:82 -msgid "Confirm the removal of the" -msgstr "Confirmer la suppression du" - -#: client/src/teams/teams.form.js:24 client/src/users/users.form.js:25 -msgid "" -"Contact your System Administrator to grant you the appropriate permissions " -"to add and edit Users and Teams." -msgstr "" -"Contactez votre administrateur de systèmes pour qu'il vous octroie les " -"permissions dont vous aurez besoin afin d'ajouter et de modifier les " -"Utilisateurs et les Équipes." - -#: client/src/inventories-hosts/inventories/related/groups/factories/get-hosts-status-msg.factory.js:18 -msgid "Contains 0 hosts." -msgstr "Contient 0 hôtes." - -#: client/src/templates/job_templates/job-template.form.js:179 -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." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:337 -msgid "" -"Control the level of output ansible will produce for inventory source update" -" jobs." -msgstr "" -"Contrôlez le niveau de sortie produit par Ansible pour les jobs " -"d'actualisation de source d'inventaire." - -#: client/lib/components/components.strings.js:52 -msgid "Copied to clipboard." -msgstr "Copié dans le Presse-papiers." - -#: client/src/credentials/credentials.list.js:73 -#: client/src/inventories-hosts/inventories/inventory.list.js:105 -#: client/src/inventory-scripts/inventory-scripts.list.js:61 -#: client/src/notifications/notificationTemplates.list.js:82 -#: client/src/projects/projects.list.js:100 -#: client/src/templates/templates.list.js:93 -msgid "Copy" -msgstr "Copier" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:56 -msgid "Copy Inventory" -msgstr "Copier l'inventaire" - -#: client/src/credentials/credentials.list.js:76 -msgid "Copy credential" -msgstr "Copier les identifiants" - -#: client/lib/components/components.strings.js:51 -msgid "Copy full revision to clipboard." -msgstr "Copier la révision complète dans le Presse-papiers." - -#: client/src/inventory-scripts/inventory-scripts.list.js:64 -msgid "Copy inventory script" -msgstr "Copier le script d’inventaire" - -#: client/src/notifications/notificationTemplates.list.js:85 -msgid "Copy notification" -msgstr "Copier la notification" - -#: client/src/projects/projects.list.js:103 -msgid "Copy project" -msgstr "Copier le projet" - -#: client/src/templates/templates.list.js:96 -msgid "Copy template" -msgstr "Copier le modèle" - -#: client/lib/services/base-string.service.js:97 -msgid "Copy {{resourceType}}" -msgstr "Copier {{resourceType}}" - -#: client/src/about/about.partial.html:27 -msgid "" -"Copyright © 2018 Red Hat, Inc.
\n" -" Visit Ansible.com for more information.
" -msgstr "" -"Copyright © 2018 Red Hat, Inc.
\n" -" Visit Ansible.com pour plus d'informations.
" - -#: client/lib/components/components.strings.js:88 -msgid "Copyright © 2018 Red Hat, Inc." -msgstr "Copyright © 2018 Red Hat, Inc." - -#: client/src/users/users.list.js:44 -msgid "Create New" -msgstr "Créer" - -#: client/features/applications/applications.strings.js:20 -msgid "Create a new Application" -msgstr "Créer une nouvelle application" - -#: client/src/instance-groups/instance-groups.strings.js:30 -msgid "Create a new Instance Group" -msgstr "Créer un nouveau groupe d'instances" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:47 -msgid "" -"Create a new Smart Inventory from search results.

Note: changing " -"the organization of the Smart Inventory could change the hosts included in " -"the Smart Inventory." -msgstr "" -"Créer un nouvel Inventaire Smart à partir des résultats de recherche.

À noter : changer l'organisation de l'Inventaire Smart pourrait " -"changer les hôtes inclus dans l'Inventaire Smart." - -#: client/src/credentials/credentials.list.js:52 -msgid "Create a new credential" -msgstr "Créer de nouvelles informations d’identification" - -#: client/src/credential-types/credential-types.list.js:42 -msgid "Create a new credential type" -msgstr "Créer un nouveau type d'informations d'identification." - -#: client/src/inventory-scripts/inventory-scripts.list.js:40 -msgid "Create a new custom inventory" -msgstr "Créer un inventaire personnalisé" - -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:69 -msgid "Create a new group" -msgstr "Créer un groupe" - -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:123 -msgid "Create a new host" -msgstr "Créer un hôte" - -#: client/src/inventories-hosts/inventories/inventory.list.js:76 -msgid "Create a new inventory" -msgstr "Créer un inventaire" - -#: client/src/notifications/notificationTemplates.list.js:52 -msgid "Create a new notification template" -msgstr "Créer un modèle de notification" - -#: client/src/organizations/list/organizations-list.partial.html:21 -msgid "Create a new organization" -msgstr "Créer une organisation" - -#: client/src/projects/projects.list.js:75 -msgid "Create a new project" -msgstr "Créer un projet" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:68 -msgid "Create a new source" -msgstr "Créer une source" - -#: client/src/teams/teams.list.js:43 -msgid "Create a new team" -msgstr "Créer une équipe" - -#: client/src/templates/templates.list.js:56 -msgid "Create a new template" -msgstr "Créer un modèle" - -#: client/src/users/users.list.js:48 -msgid "Create a new user" -msgstr "Créer un utilisateur" - -#: client/features/output/output.strings.js:44 -#: client/features/templates/templates.strings.js:25 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:73 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:74 -#: client/src/job-submission/job-submission.partial.html:18 -#: client/src/templates/job_templates/job-template.form.js:121 -msgid "Credential" -msgstr "Information d’identification" - -#: client/features/templates/templates.strings.js:36 -msgid "Credential Type" -msgstr "Type d'informations d’identification" - -#: client/lib/components/components.strings.js:74 -#: client/lib/models/models.strings.js:12 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:34 -msgid "Credential Types" -msgstr "Types d'informations d'identification" - -#: client/features/jobs/jobs.strings.js:16 -#: client/features/templates/templates.strings.js:18 -#: client/lib/components/components.strings.js:73 -#: client/lib/models/models.strings.js:8 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:129 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:58 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:26 -#: client/src/templates/job_templates/job-template.form.js:133 -msgid "Credentials" -msgstr "Informations d’identification" - -#: client/features/templates/templates.strings.js:37 -msgid "" -"Credentials that require passwords on launch are not permitted for template " -"schedules and workflow nodes. The following credentials must be removed or " -"replaced to proceed:" -msgstr "" -"Les identifiants nécessitant des mots de passe au lancement ne sont pas " -"autorisés pour les modèles de planning et les nodes de workflow. Les " -"identifiants suivants doivent être retirés ou remplacés pour continuer :" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:17 -msgid "Critical" -msgstr "Critique" - -#: client/src/shared/directives.js:93 -msgid "Current Image:" -msgstr "Image actuelle :" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:171 -msgid "Custom Inventory Script" -msgstr "Script d'inventaire personnel" - -#: client/src/inventory-scripts/inventory-scripts.form.js:50 -#: client/src/inventory-scripts/inventory-scripts.form.js:60 -msgid "Custom Script" -msgstr "Script personnalisé" - -#: client/src/home/home.route.js:21 -msgid "DASHBOARD" -msgstr "TABLEAU DE BORD" - -#: client/features/users/tokens/tokens.strings.js:28 -#: client/lib/services/base-string.service.js:71 -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:52 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:74 -#: client/src/notifications/notification-templates-list/list.controller.js:230 -#: client/src/organizations/list/organizations-list.controller.js:196 -#: client/src/partials/survey-maker-modal.html:18 -#: client/src/projects/edit/projects-edit.controller.js:255 -#: client/src/projects/list/projects-list.controller.js:254 -#: client/src/users/list/users-list.controller.js:95 -msgid "DELETE" -msgstr "SUPPRIMER" - -#: client/src/partials/survey-maker-modal.html:84 -msgid "DELETE SURVEY" -msgstr "SUPPRIMER QUESTIONNAIRE" - -#: client/features/templates/templates.strings.js:116 -msgid "DELETED" -msgstr "SUPPRIMÉ" - -#: client/features/users/tokens/tokens.strings.js:36 -msgid "DESCRIPTION" -msgstr "DESCRIPTION" - -#: client/features/templates/templates.strings.js:118 -#: client/src/instance-groups/instance-groups.strings.js:24 -#: client/src/workflow-results/workflow-results.controller.js:55 -msgid "DETAILS" -msgstr "DÉTAILS" - -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.partial.html:29 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-disassociate.partial.html:30 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-disassociate.partial.html:30 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-disassociate.partial.html:31 -msgid "DISASSOCIATE" -msgstr "DISSOCIER" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal.partial.html:5 -msgid "DYNAMIC HOSTS" -msgstr "HÔTES DYNAMIQUES" - -#: client/lib/components/components.strings.js:68 -msgid "Dashboard" -msgstr "Tableau de bord" - -#: client/src/scheduler/scheduler.strings.js:52 -msgid "Date format" -msgstr "Format de la date" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:164 -msgid "Default" -msgstr "Par défaut" - -#: client/features/output/output.strings.js:19 -#: client/lib/services/base-string.service.js:78 -#: client/src/credential-types/credential-types.list.js:73 -#: client/src/credential-types/list/list.controller.js:106 -#: client/src/credentials/credentials.list.js:92 -#: client/src/credentials/list/credentials-list.controller.js:176 -#: client/src/inventories-hosts/inventories/inventory.list.js:121 -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:157 -#: client/src/inventory-scripts/inventory-scripts.list.js:79 -#: client/src/inventory-scripts/list/list.controller.js:126 -#: client/src/notifications/notification-templates-list/list.controller.js:226 -#: client/src/notifications/notificationTemplates.list.js:100 -#: client/src/organizations/list/organizations-list.controller.js:192 -#: client/src/projects/edit/projects-edit.controller.js:252 -#: client/src/projects/list/projects-list.controller.js:250 -#: client/src/scheduler/schedules.list.js:100 -#: client/src/teams/teams.list.js:72 -#: client/src/templates/templates.list.js:109 -#: client/src/users/list/users-list.controller.js:91 -#: client/src/users/users.list.js:79 -#: client/src/workflow-results/workflow-results.controller.js:39 -msgid "Delete" -msgstr "Supprimer" - -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:6 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:6 -msgid "Delete Group" -msgstr "Supprimer le groupe" - -#: client/src/templates/survey-maker/surveys/init.factory.js:23 -msgid "Delete Question" -msgstr "Supprimer la question" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:194 -msgid "Delete Source" -msgstr "Supprimer la source" - -#: client/src/credentials/credentials.list.js:94 -msgid "Delete credential" -msgstr "Supprimer les informations d’identification" - -#: client/src/credential-types/credential-types.list.js:75 -msgid "Delete credential type" -msgstr "Supprimer le type d'informations d’identification" - -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:101 -#: client/src/inventories-hosts/inventory-hosts.strings.js:19 -msgid "Delete group" -msgid_plural "Delete groups" -msgstr[0] "Supprimer le groupe" -msgstr[1] "Supprimer les groupes" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:48 -msgid "Delete groups" -msgstr "Supprimer les groupes" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:37 -msgid "Delete groups and hosts" -msgstr "Supprimer les groupes et les hôtes" - -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:100 -#: client/src/inventories-hosts/inventory-hosts.strings.js:21 -msgid "Delete host" -msgid_plural "Delete hosts" -msgstr[0] "Supprimer l'hôte" -msgstr[1] "Supprimer les hôtes" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:59 -msgid "Delete hosts" -msgstr "Supprimer les hôtes" - -#: client/src/inventories-hosts/inventories/inventory.list.js:123 -msgid "Delete inventory" -msgstr "Supprimer l’inventaire" - -#: client/src/inventory-scripts/inventory-scripts.list.js:81 -msgid "Delete inventory script" -msgstr "Supprimer le script d’inventaire" - -#: client/src/notifications/notificationTemplates.list.js:102 -msgid "Delete notification" -msgstr "Supprimer la notification" - -#: client/src/projects/projects.form.js:167 -msgid "Delete on Update" -msgstr "Supprimer lors de la mise à jour" - -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:27 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:27 -msgid "Delete or promote the group's children?" -msgstr "Supprimer ou promouvoir les enfants du groupe ?" - -#: client/src/scheduler/schedules.list.js:103 -msgid "Delete schedule" -msgstr "Supprimer Planning" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:117 -msgid "Delete source" -msgstr "Supprimer la source" - -#: client/src/teams/teams.list.js:76 -msgid "Delete team" -msgstr "Supprimer l’équipe" - -#: client/src/templates/templates.list.js:112 -msgid "Delete template" -msgstr "Supprimer le modèle" - -#: client/src/projects/projects.form.js:169 -msgid "" -"Delete the local repository in its entirety prior to performing an update." -msgstr "" -"Supprimer le référentiel local dans son intégralité avant de lancer la mise " -"à jour." - -#: client/src/projects/projects.list.js:116 -msgid "Delete the project" -msgstr "Supprimer le projet" - -#: client/src/scheduler/scheduled-jobs.list.js:81 -msgid "Delete the schedule" -msgstr "Supprimer la planning" - -#: client/lib/services/base-string.service.js:98 -msgid "Delete the {{resourceType}}" -msgstr "Supprimer {{resourceType}}" - -#: client/src/users/users.list.js:83 -msgid "Delete user" -msgstr "Supprimer l’utilisateur" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:14 -msgid "Delete {{ group }} and {{ host }}" -msgstr "Supprimer {{ group }} et {{ host }}" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:23 -msgid "Deleting group" -msgstr "Suppression du groupe" - -#: client/lib/services/base-string.service.js:80 -msgid "" -"Deleting this {{ resourceType }} will make the following resources " -"unavailable." -msgstr "" -"En supprimant cette {{ resourceType }} les ressources suivantes ne seront " -"plus disponibles." - -#: client/src/projects/projects.form.js:169 -msgid "" -"Depending on the size of the repository this may significantly increase the " -"amount of time required to complete an update." -msgstr "" -"Selon la taille du référentiel, cette opération risque d’augmenter " -"considérablement le délai d’exécution de la mise à jour." - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:246 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:261 -msgid "Describe Instances documentation" -msgstr "Décrire la documentation des instances" - -#: client/src/credential-types/credential-types.form.js:34 -#: client/src/credentials/credentials.form.js:39 -#: client/src/inventories-hosts/hosts/host.form.js:63 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:39 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js:40 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:62 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:62 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:58 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:28 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:36 -#: client/src/inventory-scripts/inventory-scripts.form.js:35 -#: client/src/notifications/notificationTemplates.form.js:39 -#: client/src/organizations/organizations.form.js:33 -#: client/src/projects/projects.form.js:37 client/src/teams/teams.form.js:35 -#: client/src/templates/job_templates/job-template.form.js:41 -#: client/src/templates/survey-maker/shared/question-definition.form.js:36 -#: client/src/templates/workflows.form.js:49 -#: client/src/users/users.form.js:147 client/src/users/users.form.js:173 -msgid "Description" -msgstr "Description" - -#: client/src/notifications/notificationTemplates.form.js:136 -#: client/src/notifications/notificationTemplates.form.js:140 -#: client/src/notifications/notificationTemplates.form.js:152 -#: client/src/notifications/notificationTemplates.form.js:156 -msgid "Destination Channels" -msgstr "Canaux de destination" - -#: client/src/notifications/notificationTemplates.form.js:430 -#: client/src/notifications/notificationTemplates.form.js:434 -msgid "Destination Channels or Users" -msgstr "Canaux de destination pour les utilisateurs" - -#: client/src/notifications/notificationTemplates.form.js:205 -#: client/src/notifications/notificationTemplates.form.js:206 -msgid "Destination SMS Number" -msgstr "Numéro SMS de destination" - -#: client/features/applications/applications.strings.js:15 -#: client/features/credentials/credentials.strings.js:13 -#: client/features/output/output.strings.js:34 -#: client/features/users/tokens/tokens.strings.js:14 -#: client/src/license/license.partial.html:5 -#: client/src/shared/form-generator.js:1501 -msgid "Details" -msgstr "Détails" - -#: client/src/job-submission/job-submission.partial.html:263 -msgid "Diff Mode" -msgstr "Mode diff" - -#: client/src/notifications/notificationTemplates.form.js:369 -#: client/src/notifications/notificationTemplates.form.js:401 -msgid "Disable SSL Verification" -msgstr "Désactiver la vérification SSL" - -#: client/src/templates/survey-maker/surveys/init.factory.js:518 -msgid "Disable survey" -msgstr "Désactiver le questionnaire" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-disassociate.partial.html:6 -msgid "Disassociate Group From Group" -msgstr "Dissocier Groupe du Groupe" - -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.partial.html:6 -msgid "Disassociate Host" -msgstr "Dissocier l'hôte" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-disassociate.partial.html:6 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-disassociate.partial.html:6 -msgid "Disassociate Host From Group" -msgstr "Dissocier Hôte du Groupe" - -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.list.js:65 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:110 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:98 -msgid "Disassociate group" -msgstr "Dissocier le groupe" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:87 -msgid "Disassociate host" -msgstr "Dissocier l'hôte" - -#: client/src/templates/survey-maker/surveys/init.factory.js:21 -msgid "Disable Survey" -msgstr "Désactiver le questionnaire" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:94 -#: client/src/configuration/configuration.controller.js:231 -#: client/src/configuration/configuration.controller.js:316 -#: client/src/configuration/system-form/configuration-system.controller.js:57 -msgid "Discard changes" -msgstr "Ignorer les modifications" - -#: client/src/teams/teams.form.js:149 -msgid "Dissassociate permission from team" -msgstr "Dissocier la permission de l’équipe" - -#: client/src/users/users.form.js:227 -msgid "Dissassociate permission from user" -msgstr "Dissocier la permission de l’utilisateur" - -#: client/src/credentials/credentials.form.js:384 -#: client/src/credentials/factories/become-method-change.factory.js:54 -#: client/src/credentials/factories/kind-change.factory.js:111 -msgid "Domain Name" -msgstr "Nom de domaine" - -#: client/features/output/output.strings.js:20 -msgid "Download Output" -msgstr "Télécharger la sortie" - -#: client/src/inventory-scripts/inventory-scripts.form.js:59 -msgid "" -"Drag and drop your custom inventory script file here or create one in the " -"field to import your custom inventory. Refer to the Ansible Tower " -"documentation for example syntax." -msgstr "" -"Faites glisser votre script d’inventaire personnalisé et déposez-le ici ou " -"créez-en un dans le champ pour importer votre inventaire personnalisé. Voir " -"la documentation Ansible Tower pour obtenir des exemples de syntaxe." - -#: client/src/templates/survey-maker/surveys/init.factory.js:24 -msgid "Drag to reorder question" -msgstr "Faites glisser pour réorganiser les questions" - -#: client/src/partials/survey-maker-modal.html:77 -msgid "Drop question here to reorder" -msgstr "Mettez la question ici pour réordonnancer" - -#: client/features/templates/templates.strings.js:115 -msgid "EDGE CONFLICT" -msgstr "CONFLIT PARAMÈTRES" - -#: client/features/applications/applications.strings.js:10 -msgid "EDIT APPLICATION" -msgstr "MODIFIER APPLICATION" - -#: client/src/configuration/configuration.route.js:28 -msgid "EDIT CONFIGURATION" -msgstr "MODIFIER CONFIGURATION" - -#: client/features/credentials/credentials.strings.js:9 -msgid "EDIT CREDENTIAL" -msgstr "MODIFIER LES INFORMATIONS D'IDENTIFICATION" - -#: client/src/instance-groups/instance-groups.strings.js:11 -msgid "EDIT INSTANCE GROUP" -msgstr "MODIFIER LE GROUPE D'INSTANCES" - -#: client/src/scheduler/scheduler.strings.js:9 -msgid "EDIT SCHEDULE" -msgstr "MODIFIER PLANNING" - -#: client/src/management-jobs/scheduler/main.js:97 -msgid "EDIT SCHEDULED JOB" -msgstr "MODIFIER UN JOB PROGRAMMÉ" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:17 -msgid "EDIT SURVEY PROMPT" -msgstr "MODIFIER L'INVITE DU QUESTIONNAIRE" - -#: client/features/templates/templates.strings.js:108 -msgid "EDIT TEMPLATE" -msgstr "MODIFIER MODÈLE" - -#: client/lib/components/components.strings.js:9 -msgid "ENCRYPTED" -msgstr "CHIFFRÉ" - -#: client/features/output/output.strings.js:80 -msgid "EXAMPLES" -msgstr "EXEMPLES" - -#: client/src/shared/smart-search/smart-search.partial.html:36 -msgid "EXAMPLES:" -msgstr "EXEMPLES :" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:15 -msgid "EXECUTE COMMAND" -msgstr "EXÉCUTER LA COMMANDE" - -#: client/lib/components/code-mirror/code-mirror.strings.js:10 -msgid "EXPAND" -msgstr "AGRANDIR" - -#: client/features/applications/applications.strings.js:29 -#: client/features/users/tokens/tokens.strings.js:37 -msgid "EXPIRATION" -msgstr "EXPIRATION" - -#: client/features/users/tokens/tokens.strings.js:24 -msgid "EXPIRES" -msgstr "EXPIRE" - -#: client/lib/components/code-mirror/code-mirror.strings.js:48 -#: client/lib/components/code-mirror/code-mirror.strings.js:8 -msgid "EXTRA VARIABLES" -msgstr "VARIABLES SUPPLÉMENTAIRES" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:379 -msgid "" -"Each time a job runs using this inventory, refresh the inventory from the " -"selected source before executing job tasks." -msgstr "" -"Chaque fois qu’un job s’exécute avec cet inventaire, réalisez une mise à " -"jour de la source à un sélectionnée avant de lancer les tâches liées à un " -"job." - -#: client/src/projects/projects.form.js:180 -msgid "" -"Each time a job runs using this project, update the 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." - -#: client/src/credential-types/credential-types.list.js:56 -#: client/src/credentials/credentials.list.js:66 -#: client/src/inventories-hosts/inventories/inventory.list.js:98 -#: client/src/inventory-scripts/inventory-scripts.list.js:54 -#: client/src/notifications/notificationTemplates.list.js:66 -#: client/src/notifications/notificationTemplates.list.js:75 -#: client/src/scheduler/schedules.list.js:85 client/src/teams/teams.list.js:55 -#: client/src/templates/templates.list.js:80 client/src/users/users.list.js:60 -msgid "Edit" -msgstr "Modifier" - -#: client/src/templates/survey-maker/surveys/init.factory.js:22 -msgid "Edit Question" -msgstr "Modifier la question" - -#: client/src/shared/form-generator.js:1735 -#: client/src/templates/job_templates/job-template.form.js:475 -#: client/src/templates/workflows.form.js:212 -msgid "Edit Survey" -msgstr "Modifier le questionnaire" - -#: client/src/credential-types/credential-types.list.js:58 -msgid "Edit credential type" -msgstr "Modifier le type d'information d'identification" - -#: client/src/credentials/credentials.list.js:68 -msgid "Edit credential" -msgstr "Modifier les informations d’identification" - -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:85 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:96 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:84 -msgid "Edit group" -msgstr "Modifier le groupe" - -#: client/src/inventories-hosts/hosts/host.list.js:83 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:73 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:79 -#: client/src/inventories-hosts/inventory-hosts.strings.js:25 -msgid "Edit host" -msgstr "Modifier l’hôte" - -#: client/src/inventories-hosts/inventories/inventory.list.js:100 -msgid "Edit inventory" -msgstr "Modifier l’inventaire" - -#: client/src/inventory-scripts/inventory-scripts.list.js:56 -msgid "Edit inventory script" -msgstr "Modifier le script d’inventaire" - -#: client/src/notifications/notificationTemplates.list.js:68 -msgid "Edit notification" -msgstr "Modifier la notification" - -#: client/src/scheduler/schedules.list.js:88 -msgid "Edit schedule" -msgstr "Modifier Planning" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:83 -msgid "Edit source" -msgstr "Modifier la source" - -#: client/src/teams/teams.list.js:59 -msgid "Edit team" -msgstr "Modifier l’équipe" - -#: client/src/templates/templates.list.js:82 -msgid "Edit template" -msgstr "Modifier le modèle" - -#: client/src/projects/projects.list.js:87 -msgid "Edit the project" -msgstr "Modifier le projet" - -#: client/src/scheduler/scheduled-jobs.list.js:67 -#: client/src/workflow-results/workflow-results.controller.js:42 -msgid "Edit the schedule" -msgstr "Modifier le planning" - -#: client/src/workflow-results/workflow-results.controller.js:40 -msgid "Edit the user" -msgstr "Modifier l’utilisateur" - -#: client/src/workflow-results/workflow-results.controller.js:41 -msgid "Edit the workflow job template" -msgstr "Modifier le modèle de job de workflow" - -#: client/src/users/users.list.js:64 -msgid "Edit user" -msgstr "Modifier l’utilisateur" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:244 -msgid "" -"Either you do not have access or the SCM update process completed. Click the" -msgstr "" -"Vous n’avez pas accès, ou la mise à jour SCM est terminée. Cliquez sur " - -#: client/src/projects/list/projects-list.controller.js:286 -msgid "" -"Either you do not have access or the SCM update process completed. Click the" -" %sRefresh%s button to view the latest status." -msgstr "" -"Vous n’avez pas accès, ou la mise à jour SCM est terminée. Cliquez sur le " -"bouton %sActualiser%s pour voir l’état le plus récent." - -#: client/features/output/output.strings.js:90 -#: client/src/workflow-results/workflow-results.controller.js:61 -msgid "Elapsed" -msgstr "Temps écoulé" - -#: client/src/credentials/credentials.form.js:191 -#: client/src/users/users.form.js:53 -msgid "Email" -msgstr "Email" - -#: client/src/templates/job_templates/job-template.form.js:303 -#: client/src/templates/job_templates/job-template.form.js:308 -#: client/src/templates/workflows.form.js:100 -#: client/src/templates/workflows.form.js:105 -msgid "Enable Concurrent Jobs" -msgstr "Activer les jobs parallèles" - -#: client/src/configuration/system-form/configuration-system.partial.html:30 -msgid "Enable External Logging" -msgstr "Activer la journalisation externe" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:124 -#: client/src/templates/job_templates/job-template.form.js:279 -#: client/src/templates/job_templates/job-template.form.js:284 -msgid "Enable Privilege Escalation" -msgstr "Activer l’élévation des privilèges" - -#: client/src/templates/survey-maker/surveys/init.factory.js:518 -msgid "Enable survey" -msgstr "Activer le questionnaire" - -#: client/src/templates/job_templates/job-template.form.js:294 -msgid "" -"Enables creation of a provisioning callback URL. Using the URL a host can " -"contact {{BRAND_NAME}} and request a configuration update using this job " -"template." -msgstr "" -"Active la création d'une URL de rappel. Avec cette URL, un hôte peut " -"contacter {{BRAND_NAME}} et demander une mise à jour de la configuration à " -"l'aide de ce modèle de job." - -#: client/src/credentials/factories/credential-form-save.factory.js:73 -msgid "Encrypted credentials are not supported." -msgstr "" -"Les informations d’identification chiffrées ne sont pas prises en charge." - -#: client/src/scheduler/scheduler.strings.js:44 -msgid "End" -msgstr "Fin" - -#: client/src/scheduler/scheduler.strings.js:46 -msgid "End Date" -msgstr "Date de fin" - -#: client/src/scheduler/scheduler.strings.js:48 -msgid "End Time" -msgstr "Heure de fin" - -#: client/src/license/license.partial.html:113 -msgid "End User License Agreement" -msgstr "Contrat de licence de l’utilisateur final" - -#: client/src/inventories-hosts/hosts/host.form.js:73 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:72 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:72 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:68 -msgid "" -"Enter inventory variables using either JSON or YAML syntax. Use the radio " -"button to toggle between the two." -msgstr "" -"Entrez les variables d’inventaire avec la syntaxe JSON ou YAML. Utilisez le " -"bouton radio pour basculer entre les deux." - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:76 -msgid "" -"Enter inventory variables using either JSON or YAML syntax. Use the radio " -"button to toggle between the two. Refer to the Ansible Tower 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 " -"d’Ansible Tower pour avoir un exemple de syntaxe." - -#: client/src/notifications/notificationTemplates.form.js:155 -msgid "" -"Enter one HipChat channel per line. The pound symbol (#) is not required." -msgstr "" -"Saisir un canal HipChat par ligne. Le symbole dièse (#) n’est pas " -"nécessaire." - -#: client/src/notifications/notificationTemplates.form.js:433 -msgid "" -"Enter one IRC channel or username per line. The pound symbol (#) for " -"channels, and the at (@) symbol for users, are not 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." - -#: client/src/notifications/notificationTemplates.form.js:139 -msgid "" -"Enter one Slack channel per line. The pound symbol (#) is not required." -msgstr "" -"Saisir un canal Slack par ligne. Le symbole dièse (#) n’est pas nécessaire." - -#: client/src/notifications/notificationTemplates.form.js:97 -msgid "" -"Enter 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." - -#: client/src/notifications/notificationTemplates.form.js:209 -msgid "" -"Enter one phone number per line to specify where to route SMS messages." -msgstr "" -"Saisir un numéro de téléphone par ligne pour spécifier où envoyer les " -"messages SMS." - -#: client/src/credentials/factories/become-method-change.factory.js:81 -#: client/src/credentials/factories/kind-change.factory.js:138 -msgid "" -"Enter the URL for the virtual machine which %scorresponds to your CloudForms " -"instance. %sFor example, %s" -msgstr "" -"Veuillez saisir l’URL de la machine virtuelle qui correspond à votre " -"instance de CloudForm. %sPar exemple, %s" - -#: client/src/credentials/factories/become-method-change.factory.js:71 -#: client/src/credentials/factories/kind-change.factory.js:128 -msgid "" -"Enter the URL which corresponds to your %sRed Hat Satellite 6 server. %sFor " -"example, %s" -msgstr "" -"Veuillez saisir l’URL qui correspond à votre serveur %sRed Hat Satellite 6. " -"%sPar exemple, %s" - -#: client/src/credentials/factories/become-method-change.factory.js:49 -#: client/src/credentials/factories/kind-change.factory.js:106 -msgid "" -"Enter the hostname or IP address which corresponds to your VMware vCenter." -msgstr "" -"Entrez le nom d’hôte ou l’adresse IP qui correspond à votre VMware vCenter." - -#: client/src/notifications/notificationTemplates.form.js:195 -msgid "" -"Enter the number associated with the \"Messaging Service\" in Twilio in the " -"format +18005550199." -msgstr "" -"Numéro associé au \"Service de messagerie\" de Twilio sous le format " -"+18005550199." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:197 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:221 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:245 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:320 -msgid "" -"Enter variables using either JSON or YAML syntax. Use the radio button to " -"toggle between the two." -msgstr "" -"Entrez les variables avec la syntaxe JSON ou YAML. Utilisez le bouton radio " -"pour basculer entre les deux." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:187 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:194 -msgid "Environment Variables" -msgstr "Variables d'environnement" - -#: client/src/configuration/configuration.controller.js:141 -msgid "Error" -msgstr "Erreur" - -#: client/features/output/output.strings.js:65 -msgid "Error Details" -msgstr "Détails de l'erreur" - -#: client/lib/services/base-string.service.js:92 -#: client/src/configuration/configuration.controller.js:414 -#: client/src/configuration/configuration.controller.js:523 -#: client/src/configuration/configuration.controller.js:558 -#: client/src/configuration/configuration.controller.js:606 -#: client/src/configuration/system-form/configuration-system.controller.js:231 -#: client/src/credentials/factories/credential-form-save.factory.js:77 -#: client/src/credentials/factories/credential-form-save.factory.js:93 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:130 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:140 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:167 -#: client/src/job-submission/job-submission-factories/launchjob.factory.js:198 -#: client/src/job-submission/job-submission-factories/launchjob.factory.js:217 -#: client/src/management-jobs/card/card.controller.js:102 -#: client/src/management-jobs/card/card.controller.js:28 -#: client/src/projects/add/projects-add.controller.js:117 -#: client/src/projects/edit/projects-edit.controller.js:165 -#: client/src/projects/edit/projects-edit.controller.js:231 -#: client/src/projects/edit/projects-edit.controller.js:247 -#: client/src/projects/list/projects-list.controller.js:196 -#: client/src/projects/list/projects-list.controller.js:223 -#: client/src/projects/list/projects-list.controller.js:270 -#: client/src/projects/list/projects-list.controller.js:291 -#: client/src/projects/list/projects-list.controller.js:307 -#: client/src/projects/list/projects-list.controller.js:316 -#: client/src/shared/stateDefinitions.factory.js:230 -#: client/src/users/add/users-add.controller.js:100 -#: client/src/users/edit/users-edit.controller.js:178 -#: client/src/users/list/users-list.controller.js:84 -msgid "Error!" -msgstr "Erreur !" - -#: client/src/activity-stream/streams.list.js:40 -msgid "Event" -msgstr "Événement" - -#: client/src/activity-stream/factories/build-description.factory.js:120 -msgid "Event summary not available" -msgstr "Récapitulatif de l’événement non disponible" - -#: client/src/scheduler/scheduler.strings.js:29 -msgid "Every" -msgstr "Tous les" - -#: client/src/projects/add/projects-add.controller.js:138 -#: client/src/projects/edit/projects-edit.controller.js:274 -msgid "Example URLs for GIT SCM include:" -msgstr "Exemples d’URL pour le SCM GIT :" - -#: client/src/projects/add/projects-add.controller.js:159 -#: client/src/projects/edit/projects-edit.controller.js:294 -msgid "Example URLs for Mercurial SCM include:" -msgstr "Exemples d’URL pour le SCM Mercurial :" - -#: client/src/projects/add/projects-add.controller.js:150 -#: client/src/projects/edit/projects-edit.controller.js:285 -msgid "Example URLs for Subversion SCM include:" -msgstr "Exemples d’URL pour le SCM Subversion :" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:47 -msgid "Example: ansible_facts.ansible_distribution:\"RedHat\"" -msgstr "Exemple : ansible_facts.ansible_distribution:\"RedHat\"" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:76 -msgid "Existing Group" -msgstr "Groupe existant" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:125 -msgid "Existing Host" -msgstr "Hôte existant" - -#: client/features/output/output.strings.js:22 -#: client/src/workflow-results/workflow-results.controller.js:154 -#: client/src/workflow-results/workflow-results.controller.js:43 -msgid "Expand Output" -msgstr "Tout agrandir" - -#: client/src/license/license.partial.html:39 -msgid "Expires On" -msgstr "Arrive à expiration le" - -#: client/features/output/output.strings.js:50 -msgid "Explanation" -msgstr "Explication" - -#: client/features/output/output.strings.js:45 -#: client/features/templates/templates.strings.js:54 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:133 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:145 -#: client/src/job-submission/job-submission.partial.html:165 -#: client/src/partials/logviewer.html:8 -#: client/src/scheduler/scheduler.strings.js:53 -#: client/src/templates/job_templates/job-template.form.js:357 -#: client/src/templates/job_templates/job-template.form.js:364 -#: client/src/templates/workflows.form.js:83 -#: client/src/templates/workflows.form.js:90 -#: client/src/workflow-results/workflow-results.controller.js:122 -msgid "Extra Variables" -msgstr "Variables supplémentaires" - -#: client/src/inventories-hosts/shared/ansible-facts/ansible-facts.partial.html:4 -#: client/src/inventories-hosts/shared/ansible-facts/ansible-facts.route.js:7 -msgid "FACTS" -msgstr "FAITS" - -#: client/src/home/dashboard/graphs/job-status/job-status-graph.directive.js:65 -msgid "FAILED" -msgstr "ÉCHEC" - -#: client/features/output/output.strings.js:81 -msgid "FIELDS" -msgstr "CHAMPS" - -#: client/src/shared/smart-search/smart-search.partial.html:42 -msgid "FIELDS:" -msgstr "CHAMPS :" - -#: client/src/smart-status/smart-status.controller.js:43 -msgid "FINISHED" -msgstr "TERMINÉ" - -#: client/src/inventories-hosts/hosts/host.form.js:107 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:106 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:107 -msgid "Facts" -msgstr "Faits" - -#: client/lib/components/components.strings.js:100 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:80 -msgid "Failed" -msgstr "Échec" - -#: client/src/home/dashboard/counts/dashboard-counts.directive.js:44 -msgid "Failed Hosts" -msgstr "Échec des hôtes" - -#: client/src/users/add/users-add.controller.js:100 -msgid "Failed to add new user. POST returned status:" -msgstr "L’ajout de l’utilisateur a échoué. État POST renvoyé :" - -#: client/src/credentials/factories/credential-form-save.factory.js:78 -msgid "Failed to create new Credential. POST status:" -msgstr "La création des informations d’identification a échoué. État POST :" - -#: client/src/projects/add/projects-add.controller.js:118 -msgid "Failed to create new project. POST returned status:" -msgstr "La création du projet a échoué. État POST renvoyé :" - -#: client/src/job-submission/job-submission-factories/launchjob.factory.js:218 -msgid "Failed to retrieve job template extra variables." -msgstr "N’a pas pu extraire les variables supplémentaires du modèle de job." - -#: client/src/projects/edit/projects-edit.controller.js:166 -msgid "Failed to retrieve project: %s. GET status:" -msgstr "La récupération du projet a échoué : %s. État GET :" - -#: client/src/users/edit/users-edit.controller.js:179 -msgid "Failed to retrieve user: %s. GET status:" -msgstr "La récupération de l’utilisateur a échoué : %s. État GET :" - -#: client/src/configuration/configuration.controller.js:524 -msgid "Failed to save settings. Returned status:" -msgstr "L’enregistrement des paramètres a échoué. État renvoyé :" - -#: client/src/configuration/configuration.controller.js:559 -msgid "Failed to save toggle settings. Returned status:" -msgstr "" -"L’enregistrement des paramètres d’activation/désactivation a échoué. État " -"renvoyé :" - -#: client/src/credentials/factories/credential-form-save.factory.js:94 -msgid "Failed to update Credential. PUT status:" -msgstr "La mise à jour des informations d’identification a échoué. État PUT :" - -#: client/src/projects/edit/projects-edit.controller.js:231 -msgid "Failed to update project: %s. PUT status:" -msgstr "La mise à jour du projet a échoué : %s. État PUT :" - -#: client/features/output/output.strings.js:85 -msgid "Failed to update search results." -msgstr "N'a pas pu mettre à jour les résultats de recherche." - -#: client/src/job-submission/job-submission-factories/launchjob.factory.js:199 -#: client/src/management-jobs/card/card.controller.js:103 -msgid "Failed updating job %s with variables. POST returned: %d" -msgstr "" -"N’a pas pu mettre à jour le job %s avec les variables. Retour POST : %d" - -#: client/src/notifications/notifications.list.js:49 -msgid "Failure" -msgstr "Défaillance" - -#: client/src/scheduler/schedules.list.js:56 -msgid "Final Run" -msgstr "Dernière exécution" - -#: client/features/jobs/jobs.strings.js:10 -#: client/features/output/output.strings.js:40 -#: client/features/output/output.strings.js:46 -#: client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.directive.js:54 -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:44 -#: client/src/workflow-results/workflow-results.controller.js:50 -msgid "Finished" -msgstr "Terminé" - -#: client/src/access/rbac-multiselect/permissionsUsers.list.js:27 -#: client/src/users/users.form.js:29 client/src/users/users.list.js:33 -msgid "First Name" -msgstr "Prénom" - -#: client/src/scheduler/schedules.list.js:46 -msgid "First Run" -msgstr "Première exécution" - -#: client/src/templates/survey-maker/surveys/init.factory.js:19 -msgid "Float" -msgstr "Flottement" - -#: client/features/output/output.strings.js:77 -#: client/src/shared/smart-search/smart-search.partial.html:49 -msgid "" -"For additional information on advanced search syntax please see the Ansible " -"Tower" -msgstr "" -"Pour obtenir des informations supplémentaires sur la syntaxe de recherche " -"avancée, consulter la documentation Ansible Tower." - -#: client/src/credentials/factories/become-method-change.factory.js:63 -#: client/src/credentials/factories/kind-change.factory.js:120 -msgid "For example, %s" -msgstr "Par exemple, %s" - -#: client/src/inventories-hosts/hosts/host.form.js:36 -#: client/src/inventories-hosts/hosts/host.list.js:36 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:35 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:32 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:35 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:31 -#: client/src/inventories-hosts/inventory-hosts.strings.js:33 -msgid "" -"For hosts that are part of an external inventory, this flag cannot be " -"changed. It will be set by the inventory sync process." -msgstr "" -"Pour les hôtes qui font partie d’un inventaire externe, ce marqueur ne peut " -"pas être modifié. Il sera défini par le processus de synchronisation des " -"inventaires." - -#: client/src/templates/job_templates/job-template.form.js:54 -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." - -#: client/features/output/output.strings.js:47 -#: client/src/instance-groups/instance-groups.strings.js:48 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:110 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:97 -#: client/src/templates/job_templates/job-template.form.js:143 -#: client/src/templates/job_templates/job-template.form.js:153 -msgid "Forks" -msgstr "Forks" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:173 -#: client/src/scheduler/scheduler.strings.js:28 -msgid "Frequency Details" -msgstr "Informations sur la fréquence" - -#: client/src/scheduler/scheduler.strings.js:41 -msgid "Fri" -msgstr "Ven." - -#: client/src/notifications/notification-templates-list/add-notifications-action.partial.html:2 -msgid "GO TO NOTIFICATIONS TO" -msgstr "" -" SE RENDRE À NOTIFICATIONS POUR" - -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.route.js:45 -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.route.js:46 -msgid "GROUPS" -msgstr "GROUPES" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:38 -#: client/src/projects/edit/projects-edit.controller.js:136 -#: client/src/projects/list/projects-list.controller.js:76 -msgid "Get latest SCM revision" -msgstr "Obtenir la dernière révision SCM" - -#: client/src/credential-types/add/add.controller.js:41 -msgid "Getting Started with Credential Types" -msgstr "Guide de démarrage avec les types d'identifiants" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:153 -msgid "GitHub" -msgstr "GitHub" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:154 -msgid "GitHub Org" -msgstr "GitHub Org" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:155 -msgid "GitHub Team" -msgstr "Équipe GitHub" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:156 -msgid "Google OAuth2" -msgstr "Google OAuth2" - -#: client/src/teams/teams.form.js:158 client/src/users/users.form.js:216 -msgid "Grant Permission" -msgstr "Donner Permission" - -#: client/src/notifications/add/add.controller.js:79 -#: client/src/notifications/edit/edit.controller.js:126 -msgid "Gray" -msgstr "Gris" - -#: client/src/notifications/add/add.controller.js:80 -#: client/src/notifications/edit/edit.controller.js:127 -msgid "Green" -msgstr "Vert" - -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:51 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js:52 -msgid "Group Variables" -msgstr "Grouper des variables" - -#: client/src/inventories-hosts/hosts/host.form.js:115 -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.list.js:31 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:89 -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:32 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js:88 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:32 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:114 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:115 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:32 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:150 -msgid "Groups" -msgstr "Groupes" - -#: client/lib/components/components.strings.js:12 -#: client/lib/services/base-string.service.js:66 -#: client/src/templates/survey-maker/surveys/init.factory.js:483 -msgid "HIDE" -msgstr "MASQUER" - -#: client/lib/components/components.strings.js:43 -msgid "HINT: Drag and drop an SSH private key file on the field below." -msgstr "" -"INDICE : glissez-déposez un fichier de clé privé SSH dans le champ ci-" -"dessous." - -#: client/src/activity-stream/get-target-title.factory.js:41 -#: client/src/inventories-hosts/hosts/hosts.partial.html:9 -#: client/src/inventories-hosts/hosts/main.js:81 -#: client/src/inventories-hosts/inventories/inventories.partial.html:15 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.route.js:18 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-hosts.route.js:17 -msgid "HOSTS" -msgstr "HÔTES " - -#: client/src/notifications/notificationTemplates.form.js:320 -#: client/src/notifications/notificationTemplates.form.js:321 -msgid "HTTP Headers" -msgstr "En-têtes HTTP" - -#: client/src/bread-crumb/bread-crumb.directive.js:41 -msgid "Hide Activity Stream" -msgstr "Cacher le flux d’activité" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:23 -msgid "High" -msgstr "Élevée" - -#: client/src/credentials/credentials.form.js:139 -#: client/src/notifications/notificationTemplates.form.js:83 -msgid "Host" -msgstr "Hôte" - -#: client/src/credentials/factories/become-method-change.factory.js:52 -#: client/src/credentials/factories/kind-change.factory.js:109 -msgid "Host (Authentication URL)" -msgstr "Hôte (URL d’authentification)" - -#: client/src/templates/job_templates/job-template.form.js:339 -#: client/src/templates/job_templates/job-template.form.js:348 -msgid "Host Config Key" -msgstr "Clé de configuration de l’hôte" - -#: client/src/inventories-hosts/hosts/host.form.js:40 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:39 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:39 -msgid "Host Enabled" -msgstr "Hôte activé" - -#: client/src/inventories-hosts/hosts/host.form.js:46 -#: client/src/inventories-hosts/hosts/host.form.js:57 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:45 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:56 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:45 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:56 -msgid "Host Name" -msgstr "Nom d'hôte" - -#: client/src/inventories-hosts/hosts/host.form.js:80 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:79 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:79 -msgid "Host Variables" -msgstr "Variables d'hôte" - -#: client/src/inventories-hosts/shared/factories/set-enabled-msg.factory.js:6 -msgid "Host is available" -msgstr "Hôte disponible" - -#: client/src/inventories-hosts/shared/factories/set-enabled-msg.factory.js:10 -msgid "Host is available. Click to toggle." -msgstr "Hôte disponible. Cliquez pour activer ou désactiver." - -#: client/src/inventories-hosts/shared/factories/set-enabled-msg.factory.js:6 -msgid "Host is not available" -msgstr "Hôte indisponible." - -#: client/src/inventories-hosts/shared/factories/set-enabled-msg.factory.js:10 -msgid "Host is not available. Click to toggle." -msgstr "Hôte indisponible. Cliquez pour activer ou désactiver." - -#: client/features/output/output.strings.js:13 -msgid "Host status information for this job is unavailable." -msgstr "Le statut de l'hôte n'est pas disponible pour ce job." - -#: client/features/output/output.strings.js:93 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:27 -#: client/src/home/dashboard/counts/dashboard-counts.directive.js:39 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:98 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:57 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:56 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:149 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:159 -msgid "Hosts" -msgstr "Hôtes" - -#: client/src/license/license.partial.html:52 -msgid "Hosts Available" -msgstr "Hôtes disponibles" - -#: client/src/license/license.partial.html:64 -msgid "Hosts Remaining" -msgstr "Hôtes restants" - -#: client/src/license/license.partial.html:58 -msgid "Hosts Used" -msgstr "Hôtes utilisés" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:239 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:254 -msgid "Hosts are imported to" -msgstr "Hôtes importés dans" - -#: client/src/license/license.partial.html:121 -msgid "I agree to the End User License Agreement" -msgstr "J’accepte le Contrat de licence de l’utilisateur final" - -#: client/features/output/output.strings.js:102 -msgid "ID" -msgstr "ID" - -#: client/src/partials/job-template-details.html:2 -msgid "INFO" -msgstr "INFO" - -#: client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html:12 -msgid "INITIATED BY" -msgstr "INITIÉ PAR" - -#: client/src/inventories-hosts/inventories/insights/insights.route.js:7 -msgid "INSIGHTS" -msgstr "INSIGHTS" - -#: client/src/instance-groups/instance-groups.list.js:6 -#: client/src/instance-groups/instance-groups.list.js:7 -#: client/src/instance-groups/instance-groups.strings.js:16 -#: client/src/instance-groups/instance-groups.strings.js:8 -msgid "INSTANCE GROUPS" -msgstr "GROUPES D'INSTANCES" - -#: client/src/instance-groups/instance-groups.strings.js:25 -#: client/src/instance-groups/instance-groups.strings.js:9 -msgid "INSTANCES" -msgstr "INSTANCES" - -#: client/src/activity-stream/get-target-title.factory.js:14 -#: client/src/inventories-hosts/hosts/hosts.partial.html:8 -#: client/src/inventories-hosts/inventories/inventories.partial.html:14 -#: client/src/inventories-hosts/inventories/inventories.route.js:8 -#: client/src/inventories-hosts/inventories/inventory.list.js:14 -#: client/src/inventories-hosts/inventories/inventory.list.js:15 -#: client/src/organizations/linkout/organizations-linkout.route.js:144 -#: client/src/organizations/list/organizations-list.controller.js:67 -msgid "INVENTORIES" -msgstr "INVENTAIRES" - -#: client/src/job-submission/job-submission.partial.html:346 -#: client/src/partials/job-template-details.html:2 -msgid "INVENTORY" -msgstr "INVENTAIRE" - -#: client/src/inventory-scripts/inventory-scripts.form.js:23 -msgid "INVENTORY SCRIPT" -msgstr "SCRIPT D’INVENTAIRE" - -#: client/src/activity-stream/get-target-title.factory.js:35 -#: client/src/inventory-scripts/inventory-scripts.list.js:12 -#: client/src/inventory-scripts/main.js:65 -msgid "INVENTORY SCRIPTS" -msgstr "SCRIPTS D’INVENTAIRE" - -#: client/src/notifications/notificationTemplates.form.js:419 -msgid "IRC Nick" -msgstr "Surnom IRC" - -#: client/src/notifications/notificationTemplates.form.js:408 -msgid "IRC Server Address" -msgstr "Adresse du serveur IRC" - -#: client/src/notifications/shared/type-change.service.js:66 -msgid "IRC Server Password" -msgstr "Mot de passe du serveur IRC" - -#: client/src/notifications/shared/type-change.service.js:65 -msgid "IRC Server Port" -msgstr "Port du serveur IRC" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:79 -msgid "ISSUE: {{report.rule.description}}" -msgstr "PROBLÈME : {{report.rule.description}}" - -#: client/src/shared/paginate/paginate.partial.html:43 -msgid "ITEMS" -msgstr "ÉLÉMENTS" - -#: client/src/notifications/notificationTemplates.form.js:362 -#: client/src/notifications/notificationTemplates.form.js:394 -msgid "Icon URL" -msgstr "Icône URL" - -#: client/src/login/authenticationServices/timer.factory.js:157 -msgid "Idle Session" -msgstr "Session inactive" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:236 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:250 -msgid "If blank, all groups above are created except" -msgstr "" -"Si resté vide, cela indique que tous les groupes ci-dessus sont créés sauf" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:367 -msgid "" -"If checked, all variables for child groups and hosts will be removed and " -"replaced by those found 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." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:355 -msgid "" -"If checked, any hosts and groups that were previously present on the " -"external source but are now removed will be removed from the Tower " -"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." -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 de Tower. 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." - -#: client/src/templates/job_templates/job-template.form.js:282 -msgid "If enabled, run this playbook as an administrator." -msgstr "Si activé, exécuter ce playbook en tant qu'administrateur." - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:121 -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 Ansible’s -diff ." - -#: client/src/templates/job_templates/job-template.form.js:267 -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 Ansible’s --diff." - -#: client/src/templates/job_templates/job-template.form.js:306 -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 job en " -"simultané." - -#: client/src/templates/workflows.form.js:103 -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 " -"workflow en simultané." - -#: client/src/templates/job_templates/job-template.form.js:317 -msgid "" -"If enabled, use cached facts if available and store discovered facts in the " -"cache." -msgstr "" -"Si cette option est activée, utilisez les faits en cache le cas échéant et " -"les faits découverts par le magasin dans le cache." - -#: client/src/credentials/credentials.form.js:52 -msgid "" -"If no organization is given, the credential can only be used by the user " -"that creates the credential. Organization admins and system administrators " -"can assign an organization so that roles for the credential can be assigned " -"to users and teams in that organization." -msgstr "" -"Si aucune organisation n’est renseignée, les informations d’identification " -"ne peuvent être utilisées que par l’utilisateur qui les crée. Les " -"administrateurs d’organisation et les administrateurs système peuvent " -"assigner une organisation pour que les rôles liés aux informations " -"d’identification puissent être associés aux utilisateurs et aux équipes de " -"cette organisation." - -#: client/src/license/license.partial.html:70 -msgid "" -"If you are ready to upgrade, please contact us by clicking the button below" -msgstr "" -"Si vous êtes prêt à effectuer une mise à niveau, contactez-nous en cliquant " -"sur le bouton ci-dessous" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:227 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:241 -msgid "Image ID:" -msgstr "ID d'image :" - -#: client/src/inventories-hosts/hosts/host.form.js:34 -#: client/src/inventories-hosts/hosts/host.list.js:34 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:33 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:30 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:33 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:29 -#: client/src/inventories-hosts/inventory-hosts.strings.js:32 -msgid "" -"Indicates if a host is available and should be included in running jobs." -msgstr "" -"Indique si un hôte est disponible et doit être ajouté aux jobs en cours " -"d’exécution." - -#: client/src/activity-stream/activity-detail.form.js:31 -#: client/src/activity-stream/streams.list.js:33 -msgid "Initiated by" -msgstr "Initié par" - -#: client/src/credential-types/credential-types.form.js:53 -#: client/src/credential-types/credential-types.form.js:61 -msgid "Injector Configuration" -msgstr "Configuration d'Injector" - -#: client/src/credential-types/credential-types.form.js:39 -#: client/src/credential-types/credential-types.form.js:47 -msgid "Input Configuration" -msgstr "Configuration de l'entrée" - -#: client/src/inventories-hosts/hosts/host.form.js:123 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:122 -msgid "Insights" -msgstr "Insights" - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:52 -msgid "Insights Credential" -msgstr "Information d’identification à Insights" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:145 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:148 -msgid "Instance Filters" -msgstr "Filtres de l'instance" - -#: client/features/output/output.strings.js:48 -msgid "Instance Group" -msgstr "Groupe d'instance" - -#: client/src/instance-groups/instance-groups.strings.js:63 -msgid "Instance Group parameter is missing." -msgstr "Paramètre de groupe de l'instance manquant" - -#: client/lib/components/components.strings.js:84 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:54 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:57 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:61 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:64 -#: client/src/organizations/organizations.form.js:38 -#: client/src/organizations/organizations.form.js:41 -#: client/src/templates/job_templates/job-template.form.js:252 -#: client/src/templates/job_templates/job-template.form.js:255 -msgid "Instance Groups" -msgstr "Groupes d'instances" - -#: client/src/instance-groups/instance-groups.strings.js:32 -msgid "Instance Groups Help" -msgstr "Assistance Groupes d'instances" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:236 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:250 -msgid "Instance ID" -msgstr "ID d'instance" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:228 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:242 -msgid "Instance ID:" -msgstr "ID d'instance :" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:229 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:243 -msgid "Instance Type:" -msgstr "Type d'instance" - -#: client/lib/components/components.strings.js:83 -#: client/src/instance-groups/instance-groups.strings.js:17 -msgid "Instances" -msgstr "Instances" - -#: client/src/templates/survey-maker/surveys/init.factory.js:18 -msgid "Integer" -msgstr "Entier relatif" - -#: client/src/license/license.partial.html:11 -msgid "Invalid License" -msgstr "Licence non valide" - -#: client/src/license/license.controller.js:74 -#: client/src/license/license.controller.js:82 -msgid "Invalid file format. Please upload valid JSON." -msgstr "Format de fichier non valide. Chargez un fichier JSON valide." - -#: client/lib/components/components.strings.js:16 -msgid "Invalid input for this type." -msgstr "Entrée non valide pour ce type." - -#: client/features/output/output.strings.js:86 -msgid "Invalid search filter provided." -msgstr "Filtre de recherche fourni non valide." - -#: client/src/login/loginModal/loginModal.partial.html:34 -msgid "Invalid username and/or password. Please try again." -msgstr "Nom d’utilisateur et/ou mot de passe non valide. Veuillez réessayer." - -#: client/lib/components/components.strings.js:75 -#: client/lib/models/models.strings.js:16 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:122 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:52 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:28 -#: client/src/home/dashboard/counts/dashboard-counts.directive.js:50 -#: client/src/organizations/linkout/organizations-linkout.route.js:156 -#: client/src/organizations/linkout/organizations-linkout.route.js:158 -msgid "Inventories" -msgstr "Inventaires" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:44 -msgid "Inventories with sources cannot be copied" -msgstr "Les inventaires et les sources ne peuvent pas être copiés" - -#: client/features/jobs/jobs.strings.js:14 -#: client/features/output/output.strings.js:49 -#: client/features/templates/templates.strings.js:16 -#: client/features/templates/templates.strings.js:24 -#: client/src/inventories-hosts/hosts/host.list.js:69 -#: client/src/inventories-hosts/inventories/inventory.list.js:81 -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:76 -#: client/src/job-submission/job-submission.partial.html:17 -#: client/src/organizations/linkout/controllers/organizations-inventories.controller.js:70 -#: client/src/templates/job_templates/job-template.form.js:66 -#: client/src/templates/job_templates/job-template.form.js:80 -msgid "Inventory" -msgstr "Inventaire" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:110 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:124 -msgid "Inventory File" -msgstr "Fichier d'inventaire" - -#: client/lib/components/components.strings.js:80 -#: client/lib/models/models.strings.js:20 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:29 -msgid "Inventory Scripts" -msgstr "Scripts d’inventaire" - -#: client/lib/models/models.strings.js:25 -msgid "Inventory Sources" -msgstr "Source d'inventaire" - -#: client/features/templates/templates.strings.js:104 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:46 -#: client/src/workflow-results/workflow-results.controller.js:68 -msgid "Inventory Sync" -msgstr "Synchronisation des inventaires" - -#: client/src/home/dashboard/counts/dashboard-counts.directive.js:55 -msgid "Inventory Sync Failures" -msgstr "Erreurs de synchronisation des inventaires" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:75 -msgid "Inventory Variables" -msgstr "Variables d’inventaire" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:73 -msgid "Inventory contains 0 hosts." -msgstr "L'inventaire contient 0 hôtes." - -#: client/features/output/output.strings.js:35 -msgid "Isolated" -msgstr "Isolé" - -#: client/src/smart-status/smart-status.controller.js:43 -msgid "JOB ID" -msgstr "ID JOB" - -#: client/features/output/output.strings.js:84 -msgid "JOB IS STILL RUNNING" -msgstr "JOB EN COURS" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:4 -msgid "JOB STATUS" -msgstr "ÉTAT DU JOB" - -#: client/src/templates/job_templates/job-template.form.js:22 -msgid "JOB TEMPLATE" -msgstr "MODÈLE DE JOB" - -#: client/features/portalMode/portalMode.strings.js:8 -#: client/features/templates/routes/organizationsTemplatesList.route.js:20 -#: client/features/templates/routes/projectsTemplatesList.route.js:18 -#: client/src/organizations/list/organizations-list.controller.js:79 -msgid "JOB TEMPLATES" -msgstr "MODÈLES DE JOB" - -#: client/features/jobs/jobs.strings.js:8 -#: client/features/jobs/routes/instanceGroupJobs.route.js:13 -#: client/features/jobs/routes/instanceJobs.route.js:13 -#: client/features/jobs/routes/inventoryCompletedJobs.route.js:22 -#: client/features/jobs/routes/jobs.route.js:13 -#: client/features/portalMode/portalMode.strings.js:9 -#: client/features/templates/templates.strings.js:109 -#: client/src/activity-stream/get-target-title.factory.js:32 -#: client/src/home/dashboard/graphs/job-status/job-status-graph.directive.js:119 -#: client/src/instance-groups/instance-groups.strings.js:26 -msgid "JOBS" -msgstr "JOBS" - -#: client/lib/components/code-mirror/code-mirror.strings.js:12 -#: client/lib/services/base-string.service.js:70 -#: client/src/job-submission/job-submission.partial.html:173 -msgid "JSON" -msgstr "JSON" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:198 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:222 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:246 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:321 -msgid "JSON:" -msgstr "JSON :" - -#: client/features/jobs/jobs.strings.js:18 -#: client/src/workflow-results/workflow-results.controller.js:86 -msgid "Job" -msgstr "Job" - -#: client/features/output/output.strings.js:51 -#: client/features/templates/templates.strings.js:48 -#: client/src/job-submission/job-submission.partial.html:228 -#: client/src/templates/job_templates/job-template.form.js:190 -#: client/src/templates/job_templates/job-template.form.js:197 -msgid "Job Tags" -msgstr "Balises du Job" - -#: client/features/jobs/jobs.strings.js:13 -#: client/features/output/output.strings.js:52 -#: client/features/templates/templates.strings.js:13 -#: client/src/templates/templates.list.js:61 -msgid "Job Template" -msgstr "Modèle de job" - -#: client/lib/models/models.strings.js:30 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:103 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:36 -#: client/src/projects/projects.form.js:303 -msgid "Job Templates" -msgstr "Modèles de job" - -#: client/features/output/output.strings.js:53 -#: client/features/templates/templates.strings.js:50 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:32 -#: client/src/job-submission/job-submission.partial.html:202 -#: client/src/templates/job_templates/job-template.form.js:47 -#: client/src/templates/job_templates/job-template.form.js:55 -msgid "Job Type" -msgstr "Type de job" - -#: client/features/jobs/jobs.strings.js:19 -msgid "Job {{status}}. Click for details." -msgstr "Job {{status}}. Cliquez pour afficher les détails." - -#: client/lib/components/components.strings.js:69 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:30 -#: client/src/configuration/configuration.partial.html:22 -#: client/src/instance-groups/instance-groups.strings.js:52 -msgid "Jobs" -msgstr "Jobs" - -#: client/features/output/output.strings.js:82 -#: client/features/templates/templates.strings.js:99 -#: client/src/workflow-results/workflow-results.controller.js:69 -msgid "KEY" -msgstr "CLÉ" - -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:61 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:154 -#: client/src/shared/smart-search/smart-search.partial.html:14 -msgid "Key" -msgstr "Clé" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:230 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:244 -msgid "Key Name:" -msgstr "Nom de la clé :" - -#: client/src/credential-types/credential-types.list.js:31 -#: client/src/credentials/credentials.list.js:33 -msgid "Kind" -msgstr "Genre" - -#: client/features/applications/applications.strings.js:31 -msgid "LAST MODIFIED" -msgstr "DERNIÈRE MODIFICATION" - -#: client/features/users/tokens/tokens.strings.js:38 -msgid "LAST USED" -msgstr "DERNIÈRE UTILISATION" - -#: client/features/templates/templates.strings.js:30 -msgid "LAUNCH" -msgstr "LANCEMENT" - -#: client/src/job-submission/job-submission.partial.html:6 -msgid "LAUNCH JOB" -msgstr "LANCER JOB" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:157 -msgid "LDAP" -msgstr "LDAP" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:165 -msgid "LDAP 1 (Optional)" -msgstr "LDAP 1 (en option)" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:166 -msgid "LDAP 2 (Optional)" -msgstr "LDAP 2 (en option)" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:167 -msgid "LDAP 3 (Optional)" -msgstr "LDAP 3 (en option)" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:168 -msgid "LDAP 4 (Optional)" -msgstr "LDAP 4 (en option)" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:169 -msgid "LDAP 5 (Optional)" -msgstr "LDAP 5 (en option)" - -#: client/src/configuration/auth-form/configuration-auth.partial.html:17 -msgid "LDAP Server" -msgstr "Serveur LDAP" - -#: client/src/configuration/license.route.js:18 -#: client/src/license/license.route.js:18 -msgid "LICENSE" -msgstr "LICENCE" - -#: client/features/output/output.strings.js:54 -#: client/src/templates/job_templates/job-template.form.js:224 -#: client/src/templates/job_templates/job-template.form.js:228 -#: client/src/templates/templates.list.js:43 -#: client/src/templates/workflows.form.js:72 -#: client/src/templates/workflows.form.js:76 -#: client/src/workflow-results/workflow-results.controller.js:51 -msgid "Labels" -msgstr "Libellés" - -#: client/features/templates/templates.strings.js:19 -msgid "Last Modified" -msgstr "Dernière modification" - -#: client/src/access/rbac-multiselect/permissionsUsers.list.js:31 -#: client/src/users/users.form.js:36 client/src/users/users.list.js:37 -msgid "Last Name" -msgstr "Nom" - -#: client/features/templates/templates.strings.js:20 -msgid "Last Ran" -msgstr "Dernière exécution" - -#: client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.directive.js:56 -msgid "Last Sync" -msgstr "Dernière synchronisation" - -#: client/src/projects/projects.list.js:56 -msgid "Last Updated" -msgstr "Dernière mise à jour" - -#: client/src/shared/form-generator.js:1727 -msgid "Launch" -msgstr "Lancer" - -#: client/src/management-jobs/card/card.partial.html:23 -msgid "Launch Management Job" -msgstr "Lancer Job de gestion" - -#: client/features/jobs/jobs.strings.js:12 -#: client/features/output/output.strings.js:55 -#: client/src/workflow-results/workflow-results.controller.js:48 -msgid "Launched By" -msgstr "Lancé par" - -#: client/features/templates/templates.strings.js:38 -#: client/src/job-submission/job-submission.partial.html:99 -msgid "" -"Launching this job requires the passwords listed below. Enter and confirm " -"each password before continuing." -msgstr "" -"Le lancement de ce job requiert les mots de passe figurant ci-dessous. " -"Saisissez et confirmez chaque mot de passe avant de continuer." - -#: client/features/users/tokens/tokens.strings.js:32 -msgid "" -"Leaving this field blank will result in the creation of a Personal Access " -"Token which is not linked to an Application." -msgstr "" -"Si vous laissez ce champ vide, il sera créera un jeton d'accès personnalisé " -"non lié à l'application." - -#: client/features/credentials/legacy.credentials.js:350 -msgid "Legacy state configuration for does not exist" -msgstr "Configuration inexistante de l'état hérité pour." - -#: client/src/configuration/configuration.partial.html:43 -#: client/src/license/license.controller.js:44 -#: client/src/license/license.partial.html:8 -msgid "License" -msgstr "Licence" - -#: client/features/output/output.strings.js:56 -msgid "License Error" -msgstr "Erreur de licence" - -#: client/src/license/license.partial.html:104 -msgid "License File" -msgstr "Fichier de licence" - -#: client/src/license/license.partial.html:33 -msgid "License Key" -msgstr "Clé de licence" - -#: client/src/license/license.controller.js:46 -msgid "License Management" -msgstr "Gestion des licences" - -#: client/src/license/license.partial.html:21 -msgid "License Type" -msgstr "Type de licence" - -#: client/features/output/output.strings.js:57 -#: client/features/templates/templates.strings.js:49 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:45 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:55 -#: client/src/job-submission/job-submission.partial.html:220 -#: client/src/templates/job_templates/job-template.form.js:159 -#: client/src/templates/job_templates/job-template.form.js:163 -msgid "Limit" -msgstr "Limite" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:240 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:255 -msgid "Limit to hosts having a tag:" -msgstr "Limite Hôtes qui ont une balise :" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:242 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:257 -msgid "Limit to hosts using either key pair:" -msgstr "Limite Hôtes qui ont n'importe paire de clés :" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:244 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:259 -msgid "Limit to hosts where the Name tag begins with" -msgstr "Limite Hôtes qui ont une balise de nom commençant par" - -#: client/src/scheduler/scheduler.strings.js:51 -msgid "Limited to first 10" -msgstr "Limité aux 10 premiers" - -#: client/src/shared/socket/socket.service.js:213 -msgid "Live events: attempting to connect to the server." -msgstr "Événements en direct : tentative de connexion au serveur." - -#: client/src/shared/socket/socket.service.js:217 -msgid "" -"Live events: connected. Pages containing job status information will " -"automatically update in real-time." -msgstr "" -"Événements en direct : connecté. Les pages contenant des informations sur " -"l’état du Job seront automatiquement mises à jour en temps réel." - -#: client/src/shared/socket/socket.service.js:221 -msgid "Live events: error connecting to the server." -msgstr "Événements en direct : erreur de connexion au serveur." - -#: client/src/shared/form-generator.js:2005 -msgid "Loading..." -msgstr "Chargement en cours..." - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:133 -#: client/src/scheduler/scheduler.strings.js:26 -msgid "Local Time Zone" -msgstr "Fuseau horaire local" - -#: client/src/configuration/system-form/configuration-system.controller.js:225 -msgid "Log aggregator test failed.
Detail:" -msgstr "Échec du test de l'agrégateur de journalisation.
Détails :" - -#: client/src/configuration/system-form/configuration-system.controller.js:218 -msgid "Log aggregator test successful." -msgstr "Test de l'agrégateur de journalisation réussi." - -#: client/lib/components/components.strings.js:65 -msgid "Logged in as" -msgstr "Connecté en tant que" - -#: client/src/configuration/system-form/configuration-system.controller.js:89 -msgid "Logging" -msgstr "Journalisation" - -#: client/lib/components/components.strings.js:67 -msgid "Logout" -msgstr "Déconnexion" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:35 -msgid "Low" -msgstr "Basse" - -#: client/src/management-jobs/card/card.partial.html:6 -#: client/src/management-jobs/card/card.route.js:20 -msgid "MANAGEMENT JOBS" -msgstr "JOBS DE GESTION" - -#: client/src/instance-groups/instance-groups.strings.js:15 -msgid "MANUAL" -msgstr "MANUEL" - -#: client/features/output/output.strings.js:105 -msgid "MODULE" -msgstr "MODULE" - -#: client/features/portalMode/routes/portalModeTemplatesList.route.js:13 -msgid "MY VIEW" -msgstr "MON ÉCRAN" - -#: client/src/credentials/credentials.form.js:67 -#: client/src/job-submission/job-submission.partial.html:356 -msgid "Machine" -msgstr "Machine" - -#: client/features/output/output.strings.js:58 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:60 -msgid "Machine Credential" -msgstr "Informations d’identification de la machine" - -#: client/lib/components/components.strings.js:82 -msgid "Management Jobs" -msgstr "Jobs de gestion" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:54 -#: client/src/projects/edit/projects-edit.controller.js:143 -#: client/src/projects/list/projects-list.controller.js:89 -msgid "Manual projects do not require an SCM update" -msgstr "Les projets manuels ne nécessitent pas de mise à jour SCM" - -#: client/src/templates/job_templates/job-template.form.js:234 -msgid "Max 512 characters per label." -msgstr "512 caractères max par étiquette" - -#: client/src/login/loginModal/loginModal.partial.html:28 -msgid "Maximum per-user sessions reached. Please sign in." -msgstr "" -"Nombre maximum de sessions par utilisateur atteintes. Veuillez vous " -"connecter." - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:29 -msgid "Medium" -msgstr "Moyenne" - -#: client/src/configuration/system-form/configuration-system.controller.js:90 -msgid "Misc. System" -msgstr "Système divers" - -#: client/src/templates/workflows.form.js:35 -msgid "" -"Missing Job Templates found in the Workflow Editor" -msgstr "" -"Modèles de Job manquants dans Workflow Editor" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:22 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:30 -msgid "Module" -msgstr "Module" - -#: client/features/output/output.strings.js:59 -msgid "Module Args" -msgstr "Args de module" - -#: client/src/scheduler/scheduler.strings.js:37 -msgid "Mon" -msgstr "Lun." - -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:25 -msgid "Most recent job failed. Click to view jobs." -msgstr "Échec de la dernière tâche. Cliquez pour afficher les jobs." - -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:29 -msgid "Most recent job successful. Click to view jobs." -msgstr "Réussite du dernier job. Cliquez pour afficher les jobs." - -#: client/src/templates/survey-maker/surveys/init.factory.js:17 -msgid "Multiple Choice (multiple select)" -msgstr "Options à choix multiples (sélection multiple)" - -#: client/src/templates/survey-maker/surveys/init.factory.js:16 -msgid "Multiple Choice (single select)" -msgstr "Options à choix multiples (une seule sélection)" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:77 -msgid "Multiple Choice Options" -msgstr "Options à choix multiples." - -#: client/features/portalMode/index.view.html:26 -msgid "My Jobs" -msgstr "Mes jobs" - -#: client/lib/components/components.strings.js:71 -msgid "My View" -msgstr "Mon affichage" - -#: client/features/applications/applications.strings.js:24 -msgid "NEW APPLICATION" -msgstr "NOUVELLE APPLICATION" - -#: client/features/credentials/credentials.strings.js:26 -msgid "NEW CREDENTIAL" -msgstr "NOUVELLE INFORMATION D'IDENTIFICATION" - -#: client/src/credential-types/credential-types.form.js:16 -msgid "NEW CREDENTIAL TYPE" -msgstr "NOUVEAU TYPE D'INFORMATION D'IDENTIFICATION" - -#: client/src/inventory-scripts/inventory-scripts.form.js:16 -msgid "NEW CUSTOM INVENTORY" -msgstr "NOUVEL INVENTAIRE PERSONNALISÉ" - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:17 -msgid "NEW INVENTORY" -msgstr "NOUVEL INVENTAIRE" - -#: client/src/templates/job_templates/job-template.form.js:19 -msgid "NEW JOB TEMPLATE" -msgstr "NOUVEAU MODÈLE DE JOB" - -#: client/src/notifications/notificationTemplates.form.js:16 -msgid "NEW NOTIFICATION TEMPLATE" -msgstr "NOUVEAU MODÈLE DE NOTIFICATION" - -#: client/src/organizations/organizations.form.js:18 -msgid "NEW ORGANIZATION" -msgstr "NOUVELLE ORGANISATION" - -#: client/src/projects/projects.form.js:17 -msgid "NEW PROJECT" -msgstr "NOUVEAU PROJET" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:10 -msgid "NEW SMART INVENTORY" -msgstr "NOUVEL INVENTAIRE SMART" - -#: client/src/teams/teams.form.js:16 -msgid "NEW TEAM" -msgstr "NOUVELLE ÉQUIPE" - -#: client/src/users/users.form.js:16 -msgid "NEW USER" -msgstr "NOUVEL UTILISATEUR" - -#: client/src/templates/workflows.form.js:17 -msgid "NEW WORKFLOW JOB TEMPLATE" -msgstr "MODÈLE DE NOUVEAU WORKFLOW" - -#: client/lib/services/base-string.service.js:64 -msgid "NEXT" -msgstr "SUIVANT" - -#: client/src/inventories-hosts/hosts/hosts.partial.html:38 -msgid "NO HOSTS HAVE BEEN CREATED" -msgstr "AUCUN HÔTE CRÉÉ" - -#: client/lib/components/components.strings.js:39 -msgid "NO OPTIONS AVAILABLE" -msgstr "AUCUNE OPTION DISPONIBLE" - -#: client/src/login/loginModal/loginModal.partial.html:89 -msgid "NOTICE" -msgstr "ENVOI D’INFOS" - -#: client/src/notifications/notificationTemplates.form.js:21 -msgid "NOTIFICATION TEMPLATE" -msgstr "MODÈLE DE NOTIFICATION" - -#: client/src/activity-stream/get-target-title.factory.js:26 -#: client/src/notifications/notificationTemplates.list.js:14 -msgid "NOTIFICATION TEMPLATES" -msgstr "MODÈLES DE NOTIFICATION" - -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-notifications.route.js:9 -#: client/src/management-jobs/notifications/notification.route.js:46 -#: client/src/notifications/main.js:42 client/src/notifications/main.js:89 -#: client/src/notifications/notification-templates-list/add-notifications-action.partial.html:2 -msgid "NOTIFICATIONS" -msgstr "NOTIFICATIONS" - -#: client/features/output/output.strings.js:60 -#: client/src/credential-types/credential-types.form.js:27 -#: client/src/credential-types/credential-types.list.js:24 -#: client/src/credentials/credentials.form.js:32 -#: client/src/credentials/credentials.list.js:26 -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:14 -#: client/src/home/dashboard/lists/jobs/jobs-list.partial.html:13 -#: client/src/instance-groups/instance-groups.list.js:15 -#: client/src/inventories-hosts/hosts/host.list.js:61 -#: client/src/inventories-hosts/inventories/inventory.list.js:48 -#: client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.directive.js:55 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:32 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js:33 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:51 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:21 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:28 -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:45 -#: client/src/inventory-scripts/inventory-scripts.form.js:28 -#: client/src/inventory-scripts/inventory-scripts.list.js:20 -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:21 -#: client/src/notifications/notificationTemplates.form.js:32 -#: client/src/notifications/notificationTemplates.list.js:32 -#: client/src/notifications/notifications.list.js:27 -#: client/src/organizations/organizations.form.js:26 -#: client/src/projects/projects.form.js:30 -#: client/src/projects/projects.list.js:37 -#: client/src/scheduler/scheduled-jobs.list.js:31 -#: client/src/scheduler/scheduler.strings.js:21 -#: client/src/scheduler/schedules.list.js:41 -#: client/src/teams/teams.form.js:127 client/src/teams/teams.form.js:28 -#: client/src/teams/teams.list.js:23 -#: client/src/templates/job_templates/job-template.form.js:34 -#: client/src/templates/templates.list.js:24 -#: client/src/templates/workflows.form.js:42 -#: client/src/users/users.form.js:144 client/src/users/users.form.js:170 -#: client/src/users/users.form.js:196 -msgid "Name" -msgstr "Nom" - -#: client/src/credentials/credentials.form.js:71 -msgid "Network" -msgstr "Réseau" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:81 -msgid "New Group" -msgstr "Nouveau groupe" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:130 -msgid "New Host" -msgstr "Nouvel hôte" - -#: client/src/users/add/users-add.controller.js:92 -msgid "New user successfully created!" -msgstr "Création de l’utilisateur réussie" - -#: client/src/scheduler/scheduled-jobs.list.js:51 -#: client/src/scheduler/schedules.list.js:51 -msgid "Next Run" -msgstr "Exécution suivante" - -#: client/src/credentials/credentials.list.js:21 -msgid "No Credentials Have Been Created" -msgstr "Informations d’identification non créées" - -#: client/features/templates/templates.strings.js:62 -#: client/src/job-submission/lists/credential/job-sub-cred-list.controller.js:44 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js:15 -msgid "No Credentials Matching This Type Have Been Created" -msgstr "" -"Aucune information d'identification correspondant à ce type n'a été créée" - -#: client/features/output/host-event/host-event-codemirror.partial.html:3 -msgid "No JSON data returned by the module" -msgstr "Aucune donnée JSON retournée par ce module" - -#: client/src/projects/projects.list.js:20 -msgid "No Projects Have Been Created" -msgstr "Projets non créés" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:50 -msgid "No Remediation Playbook Available" -msgstr "Playbook de remédiation indisponible" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:199 -#: client/src/projects/list/projects-list.controller.js:186 -msgid "No SCM Configuration" -msgstr "Aucune configuration SCM" - -#: client/src/projects/factories/get-project-tool-tip.factory.js:9 -msgid "No SCM updates have run for this project" -msgstr "Aucune mise à jour SCM n’a été exécutée pour ce projet" - -#: client/src/access/rbac-multiselect/permissionsTeams.list.js:17 -msgid "No Teams exist" -msgstr "Aucune équipe existante" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:191 -#: client/src/projects/list/projects-list.controller.js:150 -msgid "No Updates Available" -msgstr "Aucune mise à jour disponible" - -#: client/src/access/rbac-multiselect/permissionsUsers.list.js:18 -msgid "No Users exist" -msgstr "Aucun utilisateur existant" - -#: client/features/templates/templates.strings.js:33 -msgid "No credentials selected" -msgstr "Aucune valeur d'identification sélectionnée" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:63 -msgid "No data is available. There are no issues to report." -msgstr "Aucune donnée disponible : aucun problème à signaler." - -#: client/src/license/license.controller.js:41 -msgid "No file selected." -msgstr "Aucun fichier sélectionné." - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:69 -msgid "No hosts with failures. Click for details." -msgstr "Aucun hôte avec des échecs. Cliquez pour obtenir plus d'informations." - -#: client/features/templates/templates.strings.js:34 -msgid "No inventory selected" -msgstr "Aucun inventaire sélectionné" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:52 -msgid "No inventory sync failures. Click for details." -msgstr "" -"Aucun échec de synchronisation d'inventaire. Cliquez pour obtenir plus " -"d'informations." - -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:16 -msgid "No job data" -msgstr "Aucune donnée pour ce job" - -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:75 -msgid "No job data available." -msgstr "Aucune donnée disponible pour ce job." - -#: client/src/inventories-hosts/inventories/related/groups/factories/get-hosts-status-msg.factory.js:22 -msgid "No job failures" -msgstr "Aucun échec pour ce job" - -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:51 -msgid "No job templates were recently used." -msgstr "Aucun modèle de job utilisé récemment." - -#: client/src/home/dashboard/lists/jobs/jobs-list.partial.html:44 -msgid "No jobs were recently run." -msgstr "Aucun job récemment exécuté." - -#: client/src/teams/teams.form.js:124 client/src/users/users.form.js:193 -msgid "No permissions have been granted" -msgstr "Aucune permission accordée" - -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:17 -msgid "No recent job data available for this host." -msgstr "Aucune donnée de job récent n'est disponible pour cet hôte." - -#: client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.directive.js:75 -msgid "No recent job data available for this inventory." -msgstr "Aucune donnée de job récente n'est disponible pour cet inventaire." - -#: client/src/notifications/notification-templates-list/list.controller.js:86 -msgid "No recent notifications." -msgstr "Aucun notification récente." - -#: client/src/inventories-hosts/hosts/hosts.partial.html:36 -#: client/src/shared/form-generator.js:1899 -#: client/src/shared/list-generator/list-generator.factory.js:240 -msgid "No records matched your search." -msgstr "Aucun enregistrement ne correspond à votre demande." - -#: client/features/output/output.strings.js:106 -msgid "No result found" -msgstr "Aucun résultat trouvé" - -#: client/src/scheduler/scheduled-jobs.list.js:16 -msgid "No schedules exist" -msgstr "Aucune planification existante" - -#: client/src/job-submission/job-submission.partial.html:348 -#: client/src/job-submission/job-submission.partial.html:353 -msgid "None selected" -msgstr "Aucune sélectionnée" - -#: client/src/users/add/users-add.controller.js:10 -#: client/src/users/edit/users-edit.controller.js:10 -#: client/src/users/list/users-list.controller.js:10 -msgid "Normal User" -msgstr "Utilisateur normal" - -#: client/features/output/output.strings.js:36 -#: client/src/workflow-results/workflow-results.controller.js:56 -msgid "Not Finished" -msgstr "Non terminé" - -#: client/features/output/output.strings.js:37 -#: client/src/workflow-results/workflow-results.controller.js:57 -msgid "Not Started" -msgstr "Non démarré" - -#: client/src/projects/list/projects-list.controller.js:91 -msgid "Not configured for SCM" -msgstr "Non configuré pour le SCM" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:59 -msgid "Not configured for inventory sync." -msgstr "Non configuré pour la synchronisation de l'inventaire." - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-disassociate.partial.html:25 -msgid "" -"Note that only hosts directly in this group can be disassociated. Hosts in " -"sub-groups must be disassociated 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." - -#: client/src/notifications/notificationTemplates.form.js:288 -#: client/src/notifications/notificationTemplates.form.js:289 -#: client/src/notifications/notificationTemplates.form.js:472 -#: client/src/notifications/notificationTemplates.form.js:473 -msgid "Notification Color" -msgstr "Couleur des notifications" - -#: client/src/notifications/notification-templates-list/list.controller.js:140 -msgid "Notification Failed." -msgstr "Échec de notification" - -#: client/src/notifications/notificationTemplates.form.js:277 -msgid "Notification Label" -msgstr "Étiquette de notification" - -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:31 -msgid "Notification Templates" -msgstr "Modèles de notification" - -#: client/lib/components/components.strings.js:81 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:20 -#: client/src/management-jobs/notifications/notification.route.js:21 -#: client/src/notifications/notifications.list.js:17 -msgid "Notifications" -msgstr "Notifications" - -#: client/src/notifications/notificationTemplates.form.js:302 -msgid "Notify Channel" -msgstr "Canal de notification" - -#: client/lib/services/base-string.service.js:68 -#: client/src/inventories-hosts/hosts/hosts.partial.html:55 -#: client/src/job-submission/job-submission.partial.html:269 -#: client/src/partials/survey-maker-modal.html:27 -#: client/src/shared/form-generator.js:545 -#: client/src/shared/form-generator.js:780 -#: client/src/shared/generator-helpers.js:554 -msgid "OFF" -msgstr "DÉSACTIVÉ" - -#: client/lib/services/base-string.service.js:63 -msgid "OK" -msgstr "OK" - -#: client/lib/services/base-string.service.js:67 -#: client/src/inventories-hosts/hosts/hosts.partial.html:54 -#: client/src/job-submission/job-submission.partial.html:267 -#: client/src/partials/survey-maker-modal.html:26 -#: client/src/shared/form-generator.js:541 -#: client/src/shared/form-generator.js:778 -#: client/src/shared/generator-helpers.js:550 -msgid "ON" -msgstr "ACTIVÉ" - -#: client/lib/components/components.strings.js:10 -msgid "OPTIONS" -msgstr "OPTIONS" - -#: client/features/applications/applications.strings.js:30 -msgid "ORG" -msgstr "ORG" - -#: client/src/activity-stream/get-target-title.factory.js:29 -#: client/src/organizations/list/organizations-list.partial.html:6 -#: client/src/organizations/main.js:51 -msgid "ORGANIZATIONS" -msgstr "ORGANISATIONS" - -#: client/src/scheduler/scheduler.strings.js:45 -msgid "Occurrences" -msgstr "Occurrences" - -#: client/src/workflow-results/workflow-results.controller.js:65 -msgid "On Fail" -msgstr "En cas d'échec" - -#: client/features/templates/templates.strings.js:101 -msgid "On Failure" -msgstr "En cas d'échec" - -#: client/features/templates/templates.strings.js:100 -#: client/src/workflow-results/workflow-results.controller.js:64 -msgid "On Success" -msgstr "En cas de succès" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:157 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:162 -msgid "Only Group By" -msgstr "Grouper seulement par" - -#: client/src/credentials/credentials.form.js:379 -msgid "" -"OpenStack domains define administrative boundaries. It is only needed for " -"Keystone v3 authentication URLs. Common scenarios include:" -msgstr "" -"Les domaines OpenStack définissent les limites administratives. Ils sont " -"nécessaires seulement pour les URL d’authentification Keystone v3. Les " -"scénarios courants incluent :" - -#: client/src/templates/job_templates/job-template.form.js:230 -#: client/src/templates/workflows.form.js:78 -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 job et les jobs terminés." - -#: client/src/notifications/notificationTemplates.form.js:453 -#: client/src/partials/logviewer.html:7 -#: client/src/templates/job_templates/job-template.form.js:275 -#: client/src/templates/workflows.form.js:96 -msgid "Options" -msgstr "Options" - -#: client/src/credentials/credentials.form.js:46 -#: client/src/credentials/credentials.form.js:53 -#: client/src/inventories-hosts/inventories/inventory.list.js:61 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:33 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:41 -#: client/src/inventory-scripts/inventory-scripts.form.js:40 -#: client/src/inventory-scripts/inventory-scripts.list.js:27 -#: client/src/notifications/notificationTemplates.form.js:44 -#: client/src/projects/projects.form.js:42 -#: client/src/projects/projects.form.js:48 client/src/teams/teams.form.js:40 -#: client/src/teams/teams.list.js:30 client/src/templates/workflows.form.js:55 -#: client/src/templates/workflows.form.js:61 client/src/users/users.form.js:42 -msgid "Organization" -msgstr "Organisation" - -#: client/lib/components/components.strings.js:77 -#: client/lib/models/models.strings.js:35 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:136 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:64 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:32 -#: client/src/users/users.form.js:134 -msgid "Organizations" -msgstr "Organisations" - -#: client/features/templates/templates.strings.js:27 -#: client/src/job-submission/job-submission.partial.html:19 -msgid "Other Prompts" -msgstr "Autres invites" - -#: client/src/credentials/credentials.form.js:79 -msgid "Others (Cloud Providers)" -msgstr "Autres (fournisseurs cloud)" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:317 -msgid "" -"Override variables found in azure_rm.ini and used by the inventory update " -"script. For a detailed description of these variables" -msgstr "" -"Remplacer les variables qui se trouvent dans azure_rm.ini et qui sont " -"utilisées par le script de mise à jour de l'inventaire. Pour obtenir une " -"description détaillée de ces variables" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:283 -msgid "" -"Override variables found in cloudforms.ini and used by the inventory update script. For an example variable configuration\n" -" \n" -" view cloudforms.ini in the Ansible github repo. Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax." -msgstr "" -"Remplacer les variables qui se trouvent dans cloudforms.ini et qui sont utilisées par le script de mise à jour de l'inventaire. Voici un exemple de configuration de variable\n" -" \n" -" view cloudforms.ini in the Ansible github repo.\n" -"Entrez les variables d’inventaire avec la syntaxe JSON ou YAML. Utilisez le bouton radio pour basculer entre les deux. Consultez la documentation d’Ansible Tower pour avoir un exemple de syntaxe." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:218 -msgid "" -"Override variables found in ec2.ini and used by the inventory update script." -" For a detailed description of these variables" -msgstr "" -"Remplacer les variables qui se trouvent dans ec2.ini et qui sont utilisées " -"par le script de mise à jour de l'inventaire. Pour obtenir une description " -"détaillée de ces variables" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:300 -msgid "" -"Override variables found in foreman.ini and used by the inventory update script. For an example variable configuration\n" -" \n" -" view foreman.ini in the Ansible github repo. Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax." -msgstr "" -"Remplacer les variables qui se trouvent dans foreman.ini et qui sont utilisées par le script de mise à jour de l'inventaire. Voici un exemple de configuration de variable\n" -" \n" -" view foreman.ini in the Ansible github repo.\n" -"Entrez les variables d’inventaire avec la syntaxe JSON ou YAML. Utilisez le bouton radio pour basculer entre les deux. Consultez la documentation d’Ansible Tower pour avoir un exemple de syntaxe." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:266 -msgid "" -"Override variables found in openstack.yml and used by the inventory update script. For an example variable configuration\n" -" \n" -" view openstack.yml in the Ansible github repo. Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax." -msgstr "" -"Remplacer les variables qui se trouvent dans openstack.yml et qui sont utilisées par le script de mise à jour de l'inventaire. Voici un exemple de configuration de variable\n" -" \n" -" view openstack.yml in the Ansible github repo.\n" -"Entrez les variables d’inventaire avec la syntaxe JSON ou YAML. Utilisez le bouton radio pour basculer entre les deux. Consultez la documentation d’Ansible Tower pour avoir un exemple de syntaxe." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:242 -msgid "" -"Override variables found in vmware.ini and used by the inventory update " -"script. For a detailed description of these variables" -msgstr "" -"Remplacer les variables qui se trouvent dans vmware.ini et qui sont " -"utilisées par le script de mise à jour de l'inventaire. Pour obtenir une " -"description détaillée de ces variables" - -#: client/features/output/output.strings.js:61 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:352 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:357 -msgid "Overwrite" -msgstr "Remplacer" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:364 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:369 -msgid "Overwrite Variables" -msgstr "Remplacer les variables" - -#: client/features/output/output.strings.js:62 -msgid "Overwrite Vars" -msgstr "Remplacer Vars" - -#: client/src/credentials/credentials.list.js:40 -msgid "Owners" -msgstr "Propriétaires" - -#: client/src/login/loginModal/loginModal.partial.html:68 -msgid "PASSWORD" -msgstr "MOT DE PASSE" - -#: client/features/credentials/legacy.credentials.js:117 -msgid "PERMISSIONS" -msgstr "PERMISSIONS" - -#: client/features/output/output.strings.js:103 -msgid "PLAY" -msgstr "LECTURE" - -#: client/src/partials/job-template-details.html:2 -msgid "PLAYBOOK" -msgstr "PLAYBOOK" - -#: client/src/partials/survey-maker-modal.html:45 -msgid "PLEASE ADD A SURVEY PROMPT." -msgstr "VEUILLEZ AJOUTER UNE INVITE AU QUESTIONNAIRE." - -#: client/src/organizations/list/organizations-list.partial.html:37 -#: client/src/shared/form-generator.js:1905 -#: client/src/shared/list-generator/list-generator.factory.js:248 -msgid "PLEASE ADD ITEMS TO THIS LIST" -msgstr "AJOUTEZ DES ÉLÉMENTS À CETTE LISTE" - -#: client/src/partials/survey-maker-modal.html:43 -msgid "PREVIEW" -msgstr "PRÉVISUALISATION" - -#: client/src/partials/job-template-details.html:2 -msgid "PROJECT" -msgstr "PROJET" - -#: client/src/activity-stream/get-target-title.factory.js:8 -#: client/src/organizations/linkout/organizations-linkout.route.js:196 -#: client/src/organizations/list/organizations-list.controller.js:73 -#: client/src/projects/main.js:92 client/src/projects/projects.list.js:14 -#: client/src/projects/projects.list.js:15 -msgid "PROJECTS" -msgstr "PROJETS" - -#: client/features/templates/templates.strings.js:26 -msgid "PROMPT" -msgstr "INVITE" - -#: client/src/shared/paginate/paginate.partial.html:33 -msgid "Page" -msgstr "Page" - -#: client/src/notifications/notificationTemplates.form.js:232 -msgid "Pagerduty subdomain" -msgstr "Sous-domaine Pagerduty" - -#: client/src/templates/job_templates/job-template.form.js:363 -msgid "" -"Pass extra command line variables to the playbook. Provide key/value pairs " -"using either YAML or JSON. Refer to the Ansible Tower documentation for " -"example syntax." -msgstr "" -"Transmettez des variables de ligne de commandes supplémentaires au playbook." -" Fournir la paire clé/valeur en utilisant YAML ou JSON. Consulter la " -"documentation Ansible Tower pour obtenir des exemples de syntaxe." - -#: client/src/templates/workflows.form.js:89 -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 Tower 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 Ansible Tower pour obtenir des exemples de syntaxe." - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:139 -msgid "" -"Pass extra command line variables. This is the %s or %s command line " -"parameter for %s. Provide key/value pairs using either YAML or JSON." -msgstr "" -"Transmettez des variables de ligne de commande supplémentaires. Il s'agit du" -" paramètre de ligne de commande %s ou %s pour %s. Entrez des paires " -"clé/valeur avec la syntaxe YAML ou JSON." - -#: client/src/credentials/credentials.form.js:226 -#: client/src/credentials/factories/become-method-change.factory.js:21 -#: client/src/credentials/factories/become-method-change.factory.js:40 -#: client/src/credentials/factories/become-method-change.factory.js:48 -#: client/src/credentials/factories/become-method-change.factory.js:68 -#: client/src/credentials/factories/become-method-change.factory.js:78 -#: client/src/credentials/factories/become-method-change.factory.js:88 -#: client/src/credentials/factories/kind-change.factory.js:105 -#: client/src/credentials/factories/kind-change.factory.js:125 -#: client/src/credentials/factories/kind-change.factory.js:135 -#: client/src/credentials/factories/kind-change.factory.js:145 -#: client/src/credentials/factories/kind-change.factory.js:30 -#: client/src/credentials/factories/kind-change.factory.js:78 -#: client/src/credentials/factories/kind-change.factory.js:97 -#: client/src/job-submission/job-submission.partial.html:104 -#: client/src/notifications/shared/type-change.service.js:30 -#: client/src/templates/survey-maker/surveys/init.factory.js:15 -#: client/src/users/users.form.js:70 -msgid "Password" -msgstr "Mot de passe" - -#: client/src/credentials/factories/kind-change.factory.js:58 -msgid "Password (API Key)" -msgstr "Mot de passe (clé API)" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:20 -msgid "Past 24 Hours" -msgstr "Après 24 heures" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:15 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:26 -msgid "Past Month" -msgstr "Le mois dernier" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:23 -msgid "Past Week" -msgstr "La semaine dernière" - -#: client/src/credentials/factories/become-method-change.factory.js:29 -#: client/src/credentials/factories/kind-change.factory.js:86 -msgid "" -"Paste the contents of the PEM file associated with the service account " -"email." -msgstr "" -"Collez le contenu du fichier PEM associé à l’adresse électronique du compte " -"de service." - -#: client/src/credentials/factories/kind-change.factory.js:51 -msgid "Paste the contents of the SSH private key file." -msgstr "Collez le contenu du fichier de clé privée SSH." - -#: client/src/credentials/factories/kind-change.factory.js:26 -msgid "Paste the contents of the SSH private key file.%s or click to close%s" -msgstr "" -"Collez le contenu du fichier de clé privée SSH.%s ou cliquez pour fermer%s" - -#: client/src/inventories-hosts/inventories/inventory.list.js:129 -msgid "Pending Delete" -msgstr "En attente de suppression" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:8 -msgid "Period" -msgstr "Période" - -#: client/src/projects/add/projects-add.controller.js:32 -#: client/src/users/add/users-add.controller.js:44 -msgid "Permission Error" -msgstr "Erreur de permission" - -#: client/features/credentials/credentials.strings.js:14 -#: client/features/credentials/legacy.credentials.js:63 -#: client/src/credentials/credentials.form.js:439 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:104 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:106 -#: client/src/projects/projects.form.js:247 client/src/teams/teams.form.js:120 -#: client/src/templates/job_templates/job-template.form.js:402 -#: client/src/templates/workflows.form.js:139 -#: client/src/users/users.form.js:189 -msgid "Permissions" -msgstr "Permissions" - -#: client/features/users/tokens/tokens.strings.js:41 -msgid "Personal Access Token" -msgstr "Jeton d'accès personnel" - -#: client/features/output/output.strings.js:63 -#: client/src/shared/form-generator.js:1085 -#: client/src/templates/job_templates/job-template.form.js:107 -#: client/src/templates/job_templates/job-template.form.js:115 -msgid "Playbook" -msgstr "Playbook" - -#: client/src/projects/projects.form.js:90 -msgid "Playbook Directory" -msgstr "Répertoire de playbooks" - -#: client/features/templates/templates.strings.js:60 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:52 -msgid "Playbook Run" -msgstr "Exécution du playbook" - -#: client/features/output/output.strings.js:91 -msgid "Plays" -msgstr "Lancements" - -#: client/lib/components/components.strings.js:108 -msgid "Please add items to this list." -msgstr "Veuillez ajouter des éléments à cette liste." - -#: client/src/users/users.form.js:128 -msgid "Please add user to an Organization." -msgstr "Veuillez ajouter un utilisateur à votre organisation." - -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:100 -msgid "Please assign roles to the selected resources" -msgstr "Veuillez allouer des rôles aux ressources sélectionnées" - -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:60 -msgid "Please assign roles to the selected users/teams" -msgstr "Veuillez allouer des rôles aux utilisateurs / équipes sélectionnées" - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:210 -msgid "" -"Please check the server and make sure the directory exists and file " -"permissions are set correctly." -msgstr "" -"Veuillez vérifier le serveur, s'assurer que le répertoire existe bien et que" -" les permissions d'accès aux fichiers sont définies correctement." - -#: client/src/license/license.partial.html:84 -msgid "" -"Please click the button below to visit Ansible's website to get a Tower " -"license key." -msgstr "" -"Cliquez sur le bouton ci-dessous pour visiter le site Web d'Ansible afin " -"d'obtenir une clé de licence Tower." - -#: client/src/inventories-hosts/inventory-hosts.strings.js:40 -msgid "Please click the icon to edit the host filter." -msgstr "Cliquez sur l'icône pour modifier le filtre de l'hôte." - -#: client/features/templates/templates.strings.js:110 -msgid "Please click the start button to build your workflow." -msgstr "" -"Veuillez cliquer sur le bouton de démarrage pour créer votre workflow." - -#: client/src/shared/form-generator.js:868 -#: client/src/shared/form-generator.js:963 -msgid "" -"Please enter a URL that begins with ssh, http or https. The URL may not " -"contain the '@' character." -msgstr "" -"Veuillez saisir une URL commençant par ssh, http ou https. L’URL ne doit pas" -" contenir le caractère '@'." - -#: client/src/shared/form-generator.js:1178 -msgid "Please enter a number greater than %d and less than %d." -msgstr "Entrez un nombre supérieur à %d et inférieur à %d." - -#: client/src/shared/form-generator.js:1180 -msgid "Please enter a number greater than %d." -msgstr "Entrez un nombre supérieur à %d." - -#: client/src/shared/form-generator.js:1172 -msgid "Please enter a number." -msgstr "Entrez un nombre." - -#: client/features/templates/templates.strings.js:39 -#: client/src/job-submission/job-submission.partial.html:112 -#: client/src/job-submission/job-submission.partial.html:126 -#: client/src/job-submission/job-submission.partial.html:140 -#: client/src/job-submission/job-submission.partial.html:154 -#: client/src/login/loginModal/loginModal.partial.html:78 -msgid "Please enter a password." -msgstr "Entrez un mot de passe." - -#: client/src/login/loginModal/loginModal.partial.html:58 -msgid "Please enter a username." -msgstr "Entrez un nom d’utilisateur." - -#: client/src/shared/form-generator.js:858 -#: client/src/shared/form-generator.js:953 -msgid "Please enter a valid email address." -msgstr "Entrez une adresse électronique valide." - -#: client/lib/components/components.strings.js:15 -#: client/src/shared/form-generator.js:1023 -#: client/src/shared/form-generator.js:853 -#: client/src/shared/form-generator.js:948 -msgid "Please enter a value." -msgstr "Entrez une valeur." - -#: client/src/job-submission/job-submission.partial.html:289 -#: client/src/job-submission/job-submission.partial.html:294 -#: client/src/job-submission/job-submission.partial.html:305 -#: client/src/job-submission/job-submission.partial.html:311 -#: client/src/job-submission/job-submission.partial.html:317 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:14 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:19 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:30 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:36 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:42 -msgid "Please enter an answer between" -msgstr "Veuillez saisir une réponse entre" - -#: client/features/templates/templates.strings.js:59 -#: client/src/job-submission/job-submission.partial.html:316 -msgid "Please enter an answer that is a decimal number." -msgstr "Veuillez saisir une réponse qui est un chiffre décimal." - -#: client/features/templates/templates.strings.js:58 -#: client/src/job-submission/job-submission.partial.html:310 -msgid "Please enter an answer that is a valid integer." -msgstr "Veuillez saisir une réponse qui est un entier valide." - -#: client/features/templates/templates.strings.js:56 -#: client/src/job-submission/job-submission.partial.html:288 -#: client/src/job-submission/job-submission.partial.html:293 -#: client/src/job-submission/job-submission.partial.html:304 -#: client/src/job-submission/job-submission.partial.html:309 -#: client/src/job-submission/job-submission.partial.html:315 -msgid "Please enter an answer." -msgstr "Veuillez saisir une réponse." - -#: client/src/inventories-hosts/inventory-hosts.strings.js:46 -msgid "Please enter at least one search term to create a new Smart Inventory." -msgstr "" -"Veuillez saisir une expression de recherche au moins pour créer un nouvel " -"inventaire Smart." - -#: client/features/templates/templates.strings.js:111 -msgid "Please hover over a template for additional options." -msgstr "" -"Veuillez pointer un modèle avec la souris pour obtenir des options " -"supplémentaires." - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:169 -msgid "Please input a number greater than 1." -msgstr "Veuillez saisir un nombre supérieur à 1." - -#: client/src/scheduler/scheduler.strings.js:47 -msgid "Please provide a valid date." -msgstr "Merci d'indiquer une date valide." - -#: client/src/scheduler/scheduler.strings.js:30 -msgid "Please provide a value between 1 and 999." -msgstr "Vous devez spécifier une valeur comprise entre 1 et 999." - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:54 -msgid "Please save before adding a survey to this job template." -msgstr "" -"Veuillez enregistrer avant d'ajouter un questionnaire à ce modèle de job." - -#: client/src/templates/workflows/add-workflow/workflow-add.controller.js:51 -msgid "Please save before adding a survey to this workflow." -msgstr "Veuillez enregistrer avant d'ajouter un questionnaire à ce workflow." - -#: client/src/notifications/notifications.list.js:15 -msgid "Please save before adding notifications." -msgstr "Veuillez enregistrer avant d'ajouter des notifications." - -#: client/src/organizations/organizations.form.js:80 -#: client/src/teams/teams.form.js:72 -msgid "Please save before adding users." -msgstr "Veuillez enregistrer avant d'ajouter des utilisateurs" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:100 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:102 -#: client/src/projects/projects.form.js:239 client/src/teams/teams.form.js:116 -#: client/src/templates/job_templates/job-template.form.js:395 -#: client/src/templates/workflows.form.js:132 -msgid "Please save before assigning permissions." -msgstr "Veuillez enregistrer avant d'attribuer des permissions." - -#: client/src/users/users.form.js:126 client/src/users/users.form.js:185 -msgid "Please save before assigning to organizations." -msgstr "Veuillez enregistrer avant l'attribution à des organisations." - -#: client/src/users/users.form.js:154 -msgid "Please save before assigning to teams." -msgstr "Veuillez enregistrer avant l'attribution à des équipes." - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:147 -msgid "Please save before creating groups." -msgstr "Veuillez enregistrer avant la création de groupes." - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:156 -msgid "Please save before creating hosts." -msgstr "Veuillez enregistrer avant la création d'hôtes." - -#: client/src/inventories-hosts/hosts/host.form.js:112 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:86 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:111 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:112 -msgid "Please save before defining groups." -msgstr "Veuillez enregistrer avant de définir des groupes." - -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:94 -msgid "Please save before defining hosts." -msgstr "Veuillez enregistrer avant de définir des hôtes." - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:165 -msgid "Please save before defining inventory sources." -msgstr "Veuillez enregistrer avant de définir des sources d'inventaire." - -#: client/src/templates/workflows/add-workflow/workflow-add.controller.js:50 -msgid "Please save before defining the workflow graph." -msgstr "Veuillez enregistrer avant de définir le graphique du workflow." - -#: client/src/inventories-hosts/hosts/host.form.js:121 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:120 -msgid "Please save before viewing Insights." -msgstr "Veuillez enregistrer avant d'afficher Insights." - -#: client/src/inventories-hosts/hosts/host.form.js:105 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:104 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:105 -msgid "Please save before viewing facts." -msgstr "Veuillez enregistrer avant d'afficher des facts." - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:146 -msgid "Please save before viewing hosts." -msgstr "Veuillez enregistrer avant d'afficher le hôtes." - -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:26 -msgid "Please select Users / Teams from the lists below." -msgstr "Sélectionnez des utilisateurs / équipes dans les listes ci-dessous." - -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:29 -msgid "Please select Users from the list below." -msgstr "Sélectionnez des utilisateurs dans la liste ci-dessous." - -#: client/src/shared/form-generator.js:1213 -msgid "Please select a number between" -msgstr "Sélectionnez un nombre compris entre" - -#: client/src/shared/form-generator.js:1209 -msgid "Please select a number." -msgstr "Sélectionnez un nombre." - -#: client/features/templates/templates.strings.js:57 -msgid "Please select a value" -msgstr "Sélectionnez une valeur." - -#: client/src/shared/form-generator.js:1097 -#: client/src/shared/form-generator.js:1169 -#: client/src/shared/form-generator.js:1290 -#: client/src/shared/form-generator.js:1398 -msgid "Please select a value." -msgstr "Sélectionnez une valeur." - -#: client/src/templates/job_templates/job-template.form.js:77 -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." - -#: client/src/inventories-hosts/inventory-hosts.strings.js:39 -msgid "Please select an organization before editing the host filter." -msgstr "Sélectionnez une organisation avant d'éditer le filtre de l'hôte." - -#: client/src/shared/form-generator.js:1206 -msgid "Please select at least one value." -msgstr "Sélectionnez une valeur au moins." - -#: client/src/scheduler/scheduler.strings.js:43 -msgid "Please select one or more days." -msgstr "Veuillez sélectionner un ou plusieurs jours." - -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:30 -msgid "Please select resources from the lists below." -msgstr "Sélectionnez des ressources dans les listes ci-dessous." - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:47 -msgid "Populate the hosts for this inventory by using a search filter." -msgstr "" -"Remplir les hôtes de cet inventaire en utilisant un filtre de recherche." - -#: client/src/notifications/shared/type-change.service.js:29 -msgid "Port" -msgstr "Port" - -#: client/features/templates/templates.strings.js:29 -msgid "Preview" -msgstr "Prévisualisation" - -#: client/src/credentials/credentials.form.js:257 -#: client/src/credentials/factories/kind-change.factory.js:21 -#: client/src/credentials/factories/kind-change.factory.js:45 -msgid "Private Key" -msgstr "Clé privée" - -#: client/features/templates/templates.strings.js:42 -#: client/src/credentials/credentials.form.js:264 -#: client/src/job-submission/job-submission.partial.html:118 -msgid "Private Key Passphrase" -msgstr "Phrase de passe pour la clé privée" - -#: client/src/credentials/credentials.form.js:279 -#: client/src/credentials/credentials.form.js:283 -msgid "Privilege Escalation" -msgstr "Élévation des privilèges" - -#: client/features/templates/templates.strings.js:43 -#: client/src/credentials/credentials.form.js:305 -#: client/src/job-submission/job-submission.partial.html:132 -msgid "Privilege Escalation Password" -msgstr "Mot de passe pour l’élévation des privilèges" - -#: client/src/credentials/credentials.form.js:295 -msgid "Privilege Escalation Username" -msgstr "Nom d’utilisateur pour l’élévation des privilèges" - -#: client/features/jobs/jobs.strings.js:15 -#: client/features/output/output.strings.js:64 -#: client/features/templates/templates.strings.js:17 -#: client/src/credentials/factories/become-method-change.factory.js:30 -#: client/src/credentials/factories/kind-change.factory.js:87 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:93 -#: client/src/templates/job_templates/job-template.form.js:100 -#: client/src/templates/job_templates/job-template.form.js:91 -msgid "Project" -msgstr "Projet" - -#: client/src/credentials/factories/become-method-change.factory.js:53 -#: client/src/credentials/factories/kind-change.factory.js:110 -msgid "Project (Tenant Name)" -msgstr "Projet (nom du client)" - -#: client/src/projects/projects.form.js:76 -#: client/src/projects/projects.form.js:84 -msgid "Project Base Path" -msgstr "Chemin de base du projet" - -#: client/src/credentials/credentials.form.js:365 -msgid "Project Name" -msgstr "Nom du projet" - -#: client/src/projects/projects.form.js:101 -msgid "Project Path" -msgstr "Chemin du projet" - -#: client/features/templates/templates.strings.js:103 -#: client/src/workflow-results/workflow-results.controller.js:67 -msgid "Project Sync" -msgstr "Sync Projet" - -#: client/src/home/dashboard/counts/dashboard-counts.directive.js:66 -msgid "Project Sync Failures" -msgstr "Erreurs de synchronisation du projet" - -#: client/src/projects/list/projects-list.controller.js:197 -msgid "Project lookup failed. GET returned:" -msgstr "La recherche de projet n’a pas abouti. GET renvoyé :" - -#: client/lib/components/components.strings.js:72 -#: client/lib/models/models.strings.js:40 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:115 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:47 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:33 -#: client/src/home/dashboard/counts/dashboard-counts.directive.js:61 -#: client/src/organizations/linkout/organizations-linkout.route.js:207 -#: client/src/organizations/linkout/organizations-linkout.route.js:209 -msgid "Projects" -msgstr "Projets" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:18 -msgid "Promote group" -msgid_plural "Promote groups" -msgstr[0] "Promouvoir Groupe" -msgstr[1] "Promouvoir Groupes" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:43 -msgid "Promote groups" -msgstr "Promouvoir des groupes" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:32 -msgid "Promote groups and hosts" -msgstr "Promouvoir des groupes et des hôtes" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:20 -msgid "Promote host" -msgid_plural "Promote hosts" -msgstr[0] "Promouvoir Hôte" -msgstr[1] "Promouvoir Hôtes" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:54 -msgid "Promote hosts" -msgstr "Promouvoir des hôtes" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:10 -msgid "Promote {{ group }} and {{ host }}" -msgstr "Promouvoir {{ group }} et {{ host }}" - -#: client/src/scheduler/scheduler.strings.js:54 -#: client/src/templates/survey-maker/shared/question-definition.form.js:27 -msgid "Prompt" -msgstr "Invite" - -#: client/lib/components/components.strings.js:34 -#: client/src/templates/job_templates/job-template.form.js:138 -#: client/src/templates/job_templates/job-template.form.js:168 -#: client/src/templates/job_templates/job-template.form.js:185 -#: client/src/templates/job_templates/job-template.form.js:202 -#: client/src/templates/job_templates/job-template.form.js:219 -#: client/src/templates/job_templates/job-template.form.js:270 -#: client/src/templates/job_templates/job-template.form.js:370 -#: client/src/templates/job_templates/job-template.form.js:60 -#: client/src/templates/job_templates/job-template.form.js:86 -msgid "Prompt on launch" -msgstr "Me le demander au lancement" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:238 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:253 -msgid "Provide a comma-separated list of filter expressions." -msgstr "Fournir une liste d'expressions de filtre séparées par des virgules." - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:254 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:271 -msgid "" -"Provide a comma-separated list of filter expressions. Hosts are imported " -"when all of the filters match. Refer to Ansible Tower documentation for more" -" detail." -msgstr "" -"Fournir une liste séparée par des virgules d'expressions de filtre. Les " -"hôtes sont importés lorsque tous les filtres correspondent. Reportez-vous à " -"la documentation de Ansible Tower pour plus de détails." - -#: client/src/inventories-hosts/hosts/host.form.js:50 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:49 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:49 -msgid "Provide a host name, ip address, or ip address:port. Examples include:" -msgstr "Fournir un nom d'hôte, une adresse ip ou ip address:port. Exemples :" - -#: client/src/templates/job_templates/job-template.form.js:162 -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." - -#: client/features/credentials/credentials.strings.js:22 -msgid "" -"Provide account information using Google Compute Engine JSON credentials " -"file." -msgstr "" -"Fournir des informations sur le compte à l'aide de Google Compute Engine " -"JSON." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:196 -msgid "Provide environment variables to pass to the custom inventory script." -msgstr "" -"Fournir des variables d'environnement à transmettre au script de " -"l'inventaire personnalisé." - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:257 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:274 -msgid "" -"Provide the named URL encoded name or id of the remote Tower inventory to be" -" imported." -msgstr "" -"Fournir le nom encodé de l'URL ou d'id de l'inventaire distant de Tower à " -"importer." - -#: client/src/templates/job_templates/job-template.form.js:326 -#: client/src/templates/job_templates/job-template.form.js:334 -msgid "Provisioning Callback URL" -msgstr "URL de rappel d’exécution de Tower job_template" - -#: client/src/notifications/add/add.controller.js:81 -#: client/src/notifications/edit/edit.controller.js:128 -msgid "Purple" -msgstr "Violet" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:158 -msgid "RADIUS" -msgstr "RADIUS" - -#: client/src/instance-groups/instance-groups.strings.js:47 -msgid "RAM" -msgstr "RAM" - -#: client/lib/components/code-mirror/code-mirror.strings.js:13 -msgid "READ ONLY" -msgstr "LECTURE SEULE" - -#: client/src/home/dashboard/lists/jobs/jobs-list.partial.html:4 -msgid "RECENT JOB RUNS" -msgstr "RÉCENTES EXÉCUTIONS DE JOBS" - -#: client/src/home/dashboard/lists/jobs/jobs-list.partial.html:40 -msgid "RECENTLY RUN JOBS" -msgstr "JOBS RÉCEMMENT EXÉCUTÉS" - -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:47 -msgid "RECENTLY USED JOB TEMPLATES" -msgstr "MODÈLES DE JOB RÉCEMMENT UTILISÉS" - -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:4 -msgid "RECENTLY USED TEMPLATES" -msgstr "MODÈLES RÉCEMMENT UTILISÉS" - -#: client/src/activity-stream/streams.list.js:54 -#: client/src/inventories-hosts/hosts/host.list.js:102 -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.list.js:46 -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:47 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:47 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:113 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:47 -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:54 -#: client/src/projects/projects.list.js:70 -#: client/src/scheduler/schedules.list.js:69 -msgid "REFRESH" -msgstr "ACTUALISER" - -#: client/features/users/tokens/tokens.strings.js:23 -msgid "REFRESH TOKEN" -msgstr "RÉACTUALISER JETON" - -#: client/src/shared/smart-search/smart-search.partial.html:45 -msgid "RELATED FIELDS:" -msgstr "CHAMPS ASSOCIÉS :" - -#: client/src/shared/directives.js:94 -msgid "REMOVE" -msgstr "SUPPRIMER" - -#: client/lib/components/components.strings.js:7 -msgid "REPLACE" -msgstr "REMPLACER" - -#: client/features/output/output.strings.js:8 -msgid "RESULTS" -msgstr "RÉSULTATS" - -#: client/features/templates/templates.strings.js:35 -#: client/lib/components/components.strings.js:8 -#: client/src/job-submission/job-submission.partial.html:44 -#: client/src/job-submission/job-submission.partial.html:87 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html:50 -msgid "REVERT" -msgstr "RÉTABLIR" - -#: client/src/credentials/factories/become-method-change.factory.js:25 -#: client/src/credentials/factories/kind-change.factory.js:82 -msgid "RSA Private Key" -msgstr "Clé privée RSA" - -#: client/features/templates/templates.strings.js:112 -msgid "RUN" -msgstr "EXÉCUTER" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.route.js:28 -msgid "RUN COMMAND" -msgstr "EXÉCUTER COMMANDE" - -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:56 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:56 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:101 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:114 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:56 -msgid "RUN COMMANDS" -msgstr "EXÉCUTER DES COMMANDES" - -#: client/src/notifications/add/add.controller.js:84 -#: client/src/notifications/edit/edit.controller.js:131 -msgid "Random" -msgstr "Aléatoire" - -#: client/features/users/tokens/tokens.strings.js:30 -msgid "Read" -msgstr "Lecture" - -#: client/src/workflow-results/workflow-results.controller.js:121 -msgid "Read only view of extra variables added to the workflow." -msgstr "" -"Affichage en lecture seule de variables supplémentaires ajoutées au " -"workflow." - -#: client/features/output/output.strings.js:23 -#: client/lib/components/code-mirror/code-mirror.strings.js:49 -msgid "Read-only view of extra variables added to the job template." -msgstr "" -"Affichage en lecture seule de variables supplémentaires ajoutées au modèle " -"de job." - -#: client/src/notifications/notificationTemplates.list.js:26 -msgid "Recent Notifications" -msgstr "Notifications récentes" - -#: client/src/notifications/notificationTemplates.form.js:94 -#: client/src/notifications/notificationTemplates.form.js:98 -msgid "Recipient List" -msgstr "Liste de destinataires" - -#: client/src/notifications/add/add.controller.js:82 -#: client/src/notifications/edit/edit.controller.js:129 -msgid "Red" -msgstr "Rouge" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:47 -msgid "" -"Refer to the Ansible Tower documentation for further syntax and examples." -msgstr "" -"Consultez la documentation d’Ansible Tower pour obtenir des exemples de " -"syntaxe." - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:245 -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:270 -msgid "Refresh" -msgstr "Recharger" - -#: client/src/activity-stream/streams.list.js:51 -#: client/src/bread-crumb/bread-crumb.partial.html:6 -#: client/src/inventories-hosts/hosts/host.list.js:98 -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.list.js:42 -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:43 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:43 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:109 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:43 -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:50 -#: client/src/projects/projects.list.js:66 -#: client/src/scheduler/schedules.list.js:65 -msgid "Refresh the page" -msgstr "Actualiser la page" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:231 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:245 -msgid "Region:" -msgstr "Région :" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:131 -msgid "Regions" -msgstr "Régions" - -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:65 -msgid "Related Groups" -msgstr "Groupes liés" - -#: client/lib/components/components.strings.js:98 -msgid "Relaunch On" -msgstr "Relancer sur" - -#: client/lib/components/components.strings.js:97 -msgid "Relaunch using host parameters" -msgstr "Relancer en utilisant les paramètres de l'hôte" - -#: client/lib/components/components.strings.js:96 -#: client/src/workflow-results/workflow-results.controller.js:37 -msgid "Relaunch using the same parameters" -msgstr "Relancer en utilisant les mêmes paramètres" - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:180 -msgid "Remediate Inventory" -msgstr "Remédier à l'inventaire" - -#: client/src/access/add-rbac-user-team/rbac-selected-list.directive.js:102 -#: client/src/access/add-rbac-user-team/rbac-selected-list.directive.js:103 -#: client/src/teams/teams.form.js:145 client/src/users/users.form.js:224 -msgid "Remove" -msgstr "Supprimer" - -#: client/src/projects/projects.form.js:159 -msgid "Remove any local modifications prior to performing an update." -msgstr "" -"Supprimez toutes les modifications locales avant d’effectuer une mise à " -"jour." - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:149 -#: client/src/scheduler/scheduler.strings.js:27 -msgid "Repeat frequency" -msgstr "Fréquence de répétition" - -#: client/src/license/license.partial.html:89 -msgid "Request License" -msgstr "Demander une licence" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:291 -msgid "Required" -msgstr "Obligatoire" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:154 -msgid "Reset" -msgstr "Réinitialisation" - -#: client/lib/components/components.strings.js:90 -msgid "Resources" -msgstr "Ressources" - -#: client/features/templates/templates.strings.js:85 -#: client/src/scheduler/schedules.list.js:24 -msgid "Resources are missing from this template." -msgstr "Ressources manquantes dans ce modèle." - -#: client/lib/services/base-string.service.js:88 -msgid "Return" -msgstr "Renvoi" - -#: client/features/users/tokens/tokens.strings.js:26 -msgid "Returned status:" -msgstr "État renvoyé :" - -#: client/src/shared/form-generator.js:697 -msgid "Revert" -msgstr "Revenir" - -#: client/src/configuration/auth-form/sub-forms/auth-azure.form.js:47 -#: client/src/configuration/auth-form/sub-forms/auth-github-org.form.js:51 -#: client/src/configuration/auth-form/sub-forms/auth-github-team.form.js:51 -#: client/src/configuration/auth-form/sub-forms/auth-github.form.js:47 -#: client/src/configuration/auth-form/sub-forms/auth-google-oauth2.form.js:59 -#: client/src/configuration/auth-form/sub-forms/auth-ldap.form.js:102 -#: client/src/configuration/auth-form/sub-forms/auth-ldap1.form.js:102 -#: client/src/configuration/auth-form/sub-forms/auth-ldap2.form.js:102 -#: client/src/configuration/auth-form/sub-forms/auth-ldap3.form.js:102 -#: client/src/configuration/auth-form/sub-forms/auth-ldap4.form.js:102 -#: client/src/configuration/auth-form/sub-forms/auth-ldap5.form.js:102 -#: client/src/configuration/auth-form/sub-forms/auth-radius.form.js:34 -#: client/src/configuration/auth-form/sub-forms/auth-saml.form.js:121 -#: client/src/configuration/auth-form/sub-forms/auth-tacacs.form.js:47 -#: client/src/configuration/jobs-form/configuration-jobs.form.js:73 -#: client/src/configuration/system-form/sub-forms/system-activity-stream.form.js:26 -#: client/src/configuration/system-form/sub-forms/system-logging.form.js:74 -#: client/src/configuration/system-form/sub-forms/system-misc.form.js:56 -#: client/src/configuration/ui-form/configuration-ui.form.js:36 -msgid "Revert all to default" -msgstr "Revenir aux valeurs par défaut" - -#: client/features/output/output.strings.js:66 -#: client/src/projects/projects.list.js:50 -msgid "Revision" -msgstr "Révision" - -#: client/src/projects/add/projects-add.controller.js:155 -#: client/src/projects/edit/projects-edit.controller.js:290 -msgid "Revision #" -msgstr "Révision n°" - -#: client/features/credentials/legacy.credentials.js:85 -#: client/src/credentials/credentials.form.js:462 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:130 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:132 -#: client/src/organizations/organizations.form.js:109 -#: client/src/projects/projects.form.js:270 client/src/teams/teams.form.js:101 -#: client/src/teams/teams.form.js:138 -#: client/src/templates/workflows.form.js:163 -#: client/src/users/users.form.js:207 -msgid "Role" -msgstr "Rôle" - -#: client/src/instance-groups/instance-groups.list.js:26 -#: client/src/instance-groups/instance-groups.strings.js:18 -#: client/src/instance-groups/instance-groups.strings.js:53 -msgid "Running Jobs" -msgstr "Jobs en cours d'exécution" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:159 -msgid "SAML" -msgstr "SAML" - -#: client/lib/services/base-string.service.js:62 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal.partial.html:17 -#: client/src/inventories-hosts/shared/associate-groups/associate-groups.partial.html:17 -#: client/src/inventories-hosts/shared/associate-hosts/associate-hosts.partial.html:17 -#: client/src/partials/survey-maker-modal.html:87 -#: client/src/shared/instance-groups-multiselect/instance-groups-modal/instance-groups-modal.partial.html:18 -msgid "SAVE" -msgstr "ENREGISTRER" - -#: client/src/scheduler/scheduled-jobs.list.js:13 -#: client/src/scheduler/scheduled-jobs.list.js:14 -msgid "SCHEDULED JOBS" -msgstr "JOBS PROGRAMMÉS" - -#: client/src/activity-stream/get-target-title.factory.js:38 -#: client/src/inventories-hosts/inventories/related/sources/list/schedule/sources-schedule.route.js:9 -#: client/src/management-jobs/scheduler/main.js:28 -#: client/src/management-jobs/scheduler/main.js:34 -#: client/src/scheduler/schedules.route.js:102 -#: client/src/scheduler/schedules.route.js:14 -#: client/src/scheduler/schedules.route.js:189 -#: client/src/scheduler/schedules.route.js:284 -msgid "SCHEDULES" -msgstr "PROGRAMMATIONS" - -#: client/src/projects/add/projects-add.controller.js:127 -#: client/src/projects/edit/projects-edit.controller.js:263 -msgid "SCM Branch" -msgstr "Branche SCM" - -#: client/src/projects/add/projects-add.controller.js:146 -#: client/src/projects/edit/projects-edit.controller.js:281 -msgid "SCM Branch/Tag/Commit" -msgstr "Branche SCM/Balise/Validation" - -#: client/src/projects/add/projects-add.controller.js:167 -#: client/src/projects/edit/projects-edit.controller.js:302 -msgid "SCM Branch/Tag/Revision" -msgstr "Branche SCM/Balise/Révision" - -#: client/src/projects/projects.form.js:160 -msgid "SCM Clean" -msgstr "Nettoyage SCM" - -#: client/src/projects/projects.form.js:171 -msgid "SCM Delete" -msgstr "Suppression SCM" - -#: client/src/credentials/factories/become-method-change.factory.js:20 -#: client/src/credentials/factories/kind-change.factory.js:77 -msgid "SCM Private Key" -msgstr "Clé privée SCM" - -#: client/src/projects/projects.form.js:56 -msgid "SCM Type" -msgstr "Type SCM" - -#: client/src/projects/projects.form.js:107 -msgid "SCM URL" -msgstr "URL du SCM" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:49 -#: client/src/projects/projects.form.js:181 -msgid "SCM Update" -msgstr "Mise à jour SCM" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:224 -#: client/src/projects/list/projects-list.controller.js:267 -msgid "SCM Update Cancel" -msgstr "Annulation de la mise à jour SCM" - -#: client/src/projects/projects.form.js:151 -msgid "SCM Update Options" -msgstr "Options de mise à jour SCM" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:119 -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:46 -#: client/src/projects/edit/projects-edit.controller.js:139 -#: client/src/projects/list/projects-list.controller.js:118 -#: client/src/projects/list/projects-list.controller.js:85 -msgid "SCM update currently running" -msgstr "Mise à jour SCM en cours" - -#: client/features/users/tokens/tokens.strings.js:39 -msgid "SCOPE" -msgstr "CHAMP d'APPLICATION" - -#: client/features/output/output.strings.js:83 -msgid "SEARCH" -msgstr "RECHERCHE" - -#: client/features/templates/templates.strings.js:114 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html:74 -msgid "SELECT" -msgstr "SÉLECTIONNER" - -#: client/features/credentials/credentials.strings.js:20 -msgid "SELECT A CREDENTIAL TYPE" -msgstr "SÉLECTIONNER UN TYPE D'INFORMATION D'IDENTIFICATION" - -#: client/features/users/tokens/tokens.strings.js:19 -msgid "SELECT AN APPLICATION" -msgstr "SÉLECTIONNER UNE APPLICATION" - -#: client/features/applications/applications.strings.js:35 -#: client/features/credentials/credentials.strings.js:19 -msgid "SELECT AN ORGANIZATION" -msgstr "SÉLECTIONNER UNE ORGANISATION" - -#: client/src/inventories-hosts/shared/associate-groups/associate-groups.partial.html:6 -msgid "SELECT GROUPS" -msgstr "SÉLECTIONNER DES GROUPES" - -#: client/src/inventories-hosts/shared/associate-hosts/associate-hosts.partial.html:6 -msgid "SELECT HOSTS" -msgstr "SÉLECTIONNER DES HÔTES" - -#: client/src/instance-groups/instance-groups.strings.js:36 -msgid "SELECT INSTANCE" -msgstr "SÉLECTIONNER UNE INSTANCE" - -#: client/features/templates/templates.strings.js:32 -msgid "SELECTED" -msgstr "SÉLECTIONNÉ" - -#: client/src/job-submission/job-submission.partial.html:29 -#: client/src/job-submission/job-submission.partial.html:56 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html:18 -msgid "SELECTED:" -msgstr "SÉLECTIONNÉ :" - -#: client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html:20 -msgid "SETTING CATEGORY" -msgstr "CATÉGORIE DE PARAMÈTRE" - -#: client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html:24 -msgid "SETTING NAME" -msgstr "NOM DU PARAMÈTRE" - -#: client/lib/components/components.strings.js:11 -#: client/lib/services/base-string.service.js:65 -#: client/src/templates/survey-maker/surveys/init.factory.js:486 -msgid "SHOW" -msgstr "AFFICHER" - -#: client/src/login/loginModal/loginModal.partial.html:97 -msgid "SIGN IN" -msgstr "SE CONNECTER" - -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.partial.html:2 -msgid "SIGN IN WITH" -msgstr "SE CONNECTER AVEC" - -#: client/src/inventories-hosts/hosts/host.list.js:110 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:14 -msgid "SMART INVENTORY" -msgstr "INVENTAIRE SMART" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.route.js:27 -msgid "SOURCES" -msgstr "SOURCES" - -#: client/src/credentials/factories/become-method-change.factory.js:89 -#: client/src/credentials/factories/kind-change.factory.js:146 -msgid "SSH Key" -msgstr "Clé SSH" - -#: client/features/templates/templates.strings.js:41 -msgid "SSH Password" -msgstr "Mot de passe SSH" - -#: client/src/credentials/credentials.form.js:255 -msgid "SSH key description" -msgstr "Description de la clé SSH" - -#: client/src/notifications/notificationTemplates.form.js:446 -msgid "SSL Connection" -msgstr "Connexion SSL" - -#: client/features/templates/templates.strings.js:117 -msgid "START" -msgstr "DÉMARRER" - -#: client/src/smart-status/smart-status.controller.js:43 -msgid "STATUS" -msgstr "STATUT" - -#: client/src/credentials/credentials.form.js:119 -#: client/src/credentials/credentials.form.js:127 -msgid "STS Token" -msgstr "Jeton STS" - -#: client/src/home/dashboard/graphs/job-status/job-status-graph.directive.js:62 -msgid "SUCCESSFUL" -msgstr "RÉUSSI" - -#: client/src/partials/survey-maker-modal.html:24 -msgid "SURVEY" -msgstr "QUESTIONNAIRE" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:62 -msgid "SYNC ALL" -msgstr "SYNCHRONISER TOUT" - -#: client/src/system-tracking/system-tracking.route.js:18 -msgid "SYSTEM TRACKING" -msgstr "SUIVI DU SYSTÈME" - -#: client/src/scheduler/scheduler.strings.js:42 -msgid "Sat" -msgstr "Sam." - -#: client/src/credentials/factories/become-method-change.factory.js:70 -#: client/src/credentials/factories/kind-change.factory.js:127 -msgid "Satellite 6 URL" -msgstr "URL Satellite 6" - -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:110 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:193 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:158 -#: client/src/scheduler/scheduler.strings.js:57 -#: client/src/shared/form-generator.js:1711 -msgid "Save" -msgstr "Enregistrer" - -#: client/src/configuration/configuration.controller.js:516 -msgid "Save Complete" -msgstr "Sauvegarde terminée" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:105 -#: client/src/configuration/configuration.controller.js:242 -#: client/src/configuration/configuration.controller.js:326 -#: client/src/configuration/system-form/configuration-system.controller.js:68 -msgid "Save changes" -msgstr "Enregistrer les modifications" - -#: client/src/license/license.partial.html:140 -msgid "Save successful!" -msgstr "Enregistrement réussi" - -#: client/src/scheduler/scheduler.strings.js:50 -msgid "Schedule Description" -msgstr "Description du planning" - -#: client/src/management-jobs/card/card.partial.html:28 -msgid "Schedule Management Job" -msgstr "Planifier le Job de gestion" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:7 -msgid "Schedule inventory syncs" -msgstr "Planifier des synchronisations d'inventaire futures" - -#: client/src/scheduler/scheduler.strings.js:14 -msgid "Schedule is active." -msgstr "Le planning est actif." - -#: client/src/scheduler/scheduler.strings.js:15 -msgid "Schedule is active. Click to stop." -msgstr "Le planning est actif. Cliquer pour le mettre à l'arrêt." - -#: client/src/scheduler/scheduler.strings.js:16 -msgid "Schedule is stopped." -msgstr "Le planning est à l'arrêt." - -#: client/src/scheduler/scheduler.strings.js:17 -msgid "Schedule is stopped. Click to activate." -msgstr "Le planning à l'arrêt. Cliquer pour l'activer." - -#: client/lib/components/components.strings.js:70 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:35 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:440 -#: client/src/projects/projects.form.js:290 -#: client/src/templates/job_templates/job-template.form.js:448 -#: client/src/templates/workflows.form.js:185 -msgid "Schedules" -msgstr "Plannings" - -#: client/src/shared/smart-search/smart-search.controller.js:122 -#: client/src/shared/smart-search/smart-search.controller.js:164 -msgid "Search" -msgstr "Rechercher" - -#: client/src/credentials/credentials.form.js:104 -msgid "Secret Key" -msgstr "Clé secrète" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:232 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:246 -msgid "Security Group:" -msgstr "Groupe de sécurité :" - -#: client/src/credentials/credentials.form.js:124 -msgid "" -"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." -msgstr "" -"Le service de jeton de sécurité (STS) est un service Web qui permet de " -"demander des informations d’identification provisoires avec des privilèges " -"limités pour les utilisateurs d’AWS Identity and Access Management (IAM)." - -#: client/src/shared/form-generator.js:1715 -#: client/src/shared/lookup/lookup-modal.directive.js:59 -#: client/src/shared/lookup/lookup-modal.partial.html:20 -msgid "Select" -msgstr "Sélectionner" - -#: client/src/shared/instance-groups-multiselect/instance-groups-modal/instance-groups-modal.partial.html:5 -msgid "Select Instance Groups" -msgstr "Sélectionner des groupes d'instance" - -#: client/src/job-submission/job-submission.directive.js:65 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js:66 -msgid "Select a credential" -msgstr "Sélectionner une information d'identification" - -#: client/src/access/add-rbac-user-team/rbac-user-team.controller.js:68 -msgid "Select a role" -msgstr "Sélectionner un rôle" - -#: client/features/users/tokens/tokens.strings.js:29 -msgid "Select a scope" -msgstr "Sélectionner un champ d'application" - -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:53 -msgid "" -"Select an inventory source by clicking the check box beside it. The " -"inventory source can be a single group or a selection of multiple groups." -msgstr "" -"Sélectionner une source d'inventaire en cliquant sur la case correspondante." -" La source de l'inventaire peut être un seul groupe ou une sélection de " -"plusieurs groupes." - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:53 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:98 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:53 -msgid "" -"Select an inventory source by clicking the check box beside it. The " -"inventory source can be a single group or host, a selection of multiple " -"hosts, or a selection of multiple groups." -msgstr "" -"Sélectionner une source d'inventaire en cliquant sur la case correspondante." -" La source de l'inventaire peut être un seul groupe ou hôte, une sélection " -"de plusieurs hôtes ou une sélection de plusieurs groupes." - -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:111 -msgid "" -"Select an inventory source by clicking the check box beside it. The " -"inventory source can be a single host or a selection of multiple hosts." -msgstr "" -"Sélectionner une source d'inventaire en cliquant sur la case correspondante." -" La source de l'inventaire peut être un seul hôte ou une sélection de " -"plusieurs hôtes." - -#: client/src/configuration/jobs-form/configuration-jobs.controller.js:109 -#: client/src/configuration/jobs-form/configuration-jobs.controller.js:134 -#: client/src/configuration/ui-form/configuration-ui.controller.js:95 -msgid "Select commands" -msgstr "Sélectionner des commandes" - -#: client/src/templates/job_templates/job-template.form.js:132 -msgid "" -"Select credentials that allow Tower to access 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 permettant à Tower d'accéder " -"aux nodes selon qui déterminent l'exécution de ce job. 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\" requiert la sélection des informations " -"d'identification de la machine lors de l'exécution. Si vous sélectionnez des" -" informations d'identification ET que vous cochez la case \"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." - -#: client/src/projects/projects.form.js:99 -msgid "" -"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." -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." - -#: client/src/configuration/auth-form/configuration-auth.controller.js:370 -#: client/src/configuration/auth-form/configuration-auth.controller.js:389 -msgid "Select group types" -msgstr "Sélectionner des types de groupe" - -#: client/src/access/rbac-multiselect/rbac-multiselect-role.directive.js:24 -msgid "Select roles" -msgstr "Sélectionner des rôles" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:56 -msgid "Select the Instance Groups for this Inventory to run on." -msgstr "" -"Sélectionnez les groupes d'instances sur lesquels exécuter cet inventaire." - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:63 -msgid "" -"Select the Instance Groups for this Inventory to run on. Refer to the " -"Ansible Tower documentation for more detail." -msgstr "" -"Sélectionnez les groupes d'instances sur lesquels exécuter cet inventaire. " -"Voir la documentation Ansible Tower pour obtenir plus d'informations." - -#: client/src/templates/job_templates/job-template.form.js:254 -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." - -#: client/src/organizations/organizations.form.js:40 -msgid "Select the Instance Groups for this Organization to run on." -msgstr "" -"Sélectionnez les groupes d'instances sur lesquels exécuter cette " -"organisation." - -#: client/src/templates/job_templates/job-template.form.js:244 -msgid "" -"Select the custom Python virtual environment for this job template to run " -"on." -msgstr "" -"Sélectionnez l'environnement virtuel Python personnalisé sur lequel exécuter" -" ce modèle de job." - -#: client/src/organizations/organizations.form.js:51 -msgid "" -"Select the custom Python virtual environment for this organization to run " -"on." -msgstr "" -"Sélectionnez l'environnement virtuel Python personnalisé sur lequel exécuter" -" ce modèle de job." - -#: client/src/projects/projects.form.js:211 -msgid "" -"Select the custom Python virtual environment for this project to run on." -msgstr "" -"Sélectionnez l'environnement virtuel Python personnalisé sur lequel exécuter" -" ce projet." - -#: client/src/templates/job_templates/job-template.form.js:79 -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 voir gérer " -"par ce Job." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:122 -msgid "" -"Select the inventory file to be synced by this source. You can select from " -"the dropdown or enter a file within the input." -msgstr "" -"Sélectionnez le fichier d'inventaire à synchroniser par cette source. Vous " -"pouvez le choisir dans le menu déroulant ou saisir un fichier dans l'entrée." - -#: client/src/templates/job_templates/job-template.form.js:114 -msgid "Select the playbook to be executed by this job." -msgstr "Sélectionnez le playbook qui devra être exécuté par ce job." - -#: client/src/templates/job_templates/job-template.form.js:99 -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." - -#: client/src/configuration/system-form/configuration-system.controller.js:197 -msgid "Select types" -msgstr "Sélectionner des types" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:224 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:238 -msgid "Select which groups to create automatically." -msgstr "Sélectionner quels groupes créer automatiquement." - -#: client/src/notifications/notificationTemplates.form.js:110 -msgid "Sender Email" -msgstr "Adresse électronique de l’expéditeur" - -#: client/src/credentials/factories/become-method-change.factory.js:24 -#: client/src/credentials/factories/kind-change.factory.js:81 -msgid "Service Account Email Address" -msgstr "Adresse électronique du compte de service" - -#: client/features/credentials/credentials.strings.js:21 -msgid "Service Account JSON File" -msgstr "Fichier JSON Compte de service" - -#: client/lib/components/components.strings.js:86 -msgid "Settings" -msgstr "Paramètres" - -#: client/src/job-submission/job-submission.partial.html:108 -#: client/src/job-submission/job-submission.partial.html:122 -#: client/src/job-submission/job-submission.partial.html:136 -#: client/src/job-submission/job-submission.partial.html:150 -#: client/src/job-submission/job-submission.partial.html:299 -#: client/src/shared/form-generator.js:883 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:24 -msgid "Show" -msgstr "Afficher" - -#: client/features/templates/templates.strings.js:46 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:115 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:118 -#: client/src/templates/job_templates/job-template.form.js:261 -#: client/src/templates/job_templates/job-template.form.js:264 -msgid "Show Changes" -msgstr "Afficher les modifications" - -#: client/features/output/output.strings.js:38 -msgid "Show Less" -msgstr "Afficher moins de détails" - -#: client/features/output/output.strings.js:39 -msgid "Show More" -msgstr "Afficher plus de détails" - -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:33 -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:44 -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:55 -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:76 -msgid "Sign in with %s" -msgstr "Se connecter avec %s" - -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:63 -msgid "Sign in with %s Organizations" -msgstr "Se connecter avec des organisations %s" - -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:61 -msgid "Sign in with %s Teams" -msgstr "Se connecter avec des équipes %s" - -#: client/features/output/output.strings.js:67 -#: client/features/templates/templates.strings.js:47 -#: client/src/job-submission/job-submission.partial.html:245 -#: client/src/templates/job_templates/job-template.form.js:207 -#: client/src/templates/job_templates/job-template.form.js:214 -msgid "Skip Tags" -msgstr "Balises de saut" - -#: client/src/templates/job_templates/job-template.form.js:213 -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 Ansible Tower documentation for details on the usage of tags." -msgstr "" -"Les balises de saut sont utiles si votre playbook est important et que vous " -"souhaitez ignorer certaines parties d’une scène ou d’une tâche. Utiliser des" -" virgules pour séparer plusieurs balises. Consulter la documentation Ansible" -" Tower pour obtenir des détails sur l'utilisation des balises." - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:44 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:48 -msgid "Smart Host Filter" -msgstr "Filtre d'hôte smart" - -#: client/src/inventories-hosts/inventories/inventory.list.js:86 -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:76 -#: client/src/organizations/linkout/controllers/organizations-inventories.controller.js:70 -#: client/src/shared/form-generator.js:1476 -msgid "Smart Inventory" -msgstr "Inventaire smart" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:44 -msgid "Solvable With Playbook" -msgstr "Solvable avec playbook" - -#: client/features/output/output.strings.js:68 -#: client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.directive.js:57 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:64 -msgid "Source" -msgstr "Source" - -#: client/src/credentials/credentials.form.js:75 -msgid "Source Control" -msgstr "Contrôle de la source" - -#: client/features/output/output.strings.js:69 -msgid "Source Credential" -msgstr "Informations d'identification de la source" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:47 -#: client/src/projects/projects.form.js:26 -msgid "Source Details" -msgstr "Détails de la source" - -#: client/src/notifications/notificationTemplates.form.js:192 -#: client/src/notifications/notificationTemplates.form.js:193 -msgid "Source Phone Number" -msgstr "Numéro de téléphone de la source" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:136 -msgid "Source Regions" -msgstr "Régions sources" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:209 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:216 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:233 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:240 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:257 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:264 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:274 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:281 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:291 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:298 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:308 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:315 -msgid "Source Variables" -msgstr "Variables sources" - -#: client/src/partials/logviewer.html:9 -msgid "Source Vars" -msgstr "Vars Source" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:34 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:167 -msgid "Sources" -msgstr "Sources" - -#: client/src/notifications/notificationTemplates.form.js:330 -msgid "" -"Specify HTTP Headers in JSON format. Refer to the Ansible Tower " -"documentation for example syntax." -msgstr "" -"Spécifier les En-têtes HTTP en format JSON. Voir la documentation Ansible " -"Tower pour obtenir des exemples de syntaxe." - -#: client/src/credentials/credentials.form.js:285 -msgid "" -"Specify a method for %s operations. This is equivalent to specifying the %s " -"parameter, where %s could be %s" -msgstr "" -"Spécifiez une méthode pour les opérations %s. Cela équivaut à définir le " -"paramètre %s, où %s peut être %s" - -#: client/src/notifications/notificationTemplates.form.js:478 -msgid "" -"Specify a notification color. Acceptable colors are hex 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) ." - -#: client/src/notifications/notificationTemplates.form.js:292 -msgid "" -"Specify a notification color. Acceptable colors are: yellow, green, red " -"purple, gray or random." -msgstr "" -"Spécifier une couleur de notification. Les couleurs acceptées sont : jaune, " -"vert, rouge, violet, gris, ou au hasard." - -#: client/features/users/tokens/tokens.strings.js:20 -msgid "Specify a scope for the token's access" -msgstr "Spécifier le champ d'application du jeton" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:253 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:270 -msgid "" -"Specify which groups to create automatically. Group names will be created " -"similar to the options selected. If blank, all groups above are created. " -"Refer to Ansible Tower documentation for more detail." -msgstr "" -"Spécifier quels groupes créer automatiquement. Les noms de groupe seront " -"créés de la même façon que les options sélectionnées. Si vide, tous les " -"groupes au-dessus seront créés. Voir la documentation Ansible Tower pour " -"obtenir plus d'informations." - -#: client/features/output/output.strings.js:108 -msgid "Standard Error" -msgstr "Erreur standard" - -#: client/features/output/output.strings.js:107 -#: client/src/partials/logviewer.html:5 -msgid "Standard Out" -msgstr "Standard Out" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:41 -#: client/src/scheduler/scheduler.strings.js:23 -msgid "Start Date" -msgstr "Date Début" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:56 -#: client/src/scheduler/scheduler.strings.js:24 -msgid "Start Time" -msgstr "Heure Début" - -#: client/lib/components/components.strings.js:104 -msgid "Start a job using this template" -msgstr "Démarrer un job avec ce modèle" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:6 -msgid "Start sync process" -msgstr "Démarrer le processus de synchronisation" - -#: client/features/jobs/jobs.strings.js:9 -#: client/features/output/output.strings.js:70 -#: client/src/workflow-results/workflow-results.controller.js:49 -msgid "Started" -msgstr "Démarré" - -#: client/features/output/output.strings.js:71 -#: client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.directive.js:53 -#: client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.directive.js:55 -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:43 -#: client/src/notifications/notification-templates-list/list.controller.js:71 -#: client/src/partials/logviewer.html:4 -#: client/src/workflow-results/workflow-results.controller.js:52 -msgid "Status" -msgstr "État" - -#: client/src/configuration/auth-form/configuration-auth.partial.html:4 -msgid "Sub Category" -msgstr "Sous-catégorie" - -#: client/src/license/license.partial.html:139 -msgid "Submit" -msgstr "Valider" - -#: client/src/license/license.partial.html:27 -msgid "Subscription" -msgstr "Abonnement" - -#: client/src/credentials/credentials.form.js:151 -#: client/src/credentials/credentials.form.js:162 -msgid "Subscription ID" -msgstr "ID d’abonnement" - -#: client/src/credentials/credentials.form.js:161 -msgid "Subscription ID is an Azure construct, which is mapped to a username." -msgstr "" -"L’ID d’abonnement est une construction Azure mappée à un nom d’utilisateur." - -#: client/src/notifications/notifications.list.js:39 -msgid "Success" -msgstr "Réussite" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:77 -msgid "Successful" -msgstr "Réussi" - -#: client/src/scheduler/scheduler.strings.js:36 -msgid "Sun" -msgstr "Dim." - -#: client/features/templates/templates.strings.js:28 -#: client/src/job-submission/job-submission.partial.html:20 -msgid "Survey" -msgstr "Questionnaire" - -#: client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js:62 -#: client/src/templates/workflows/edit-workflow/workflow-edit.controller.js:262 -msgid "" -"Surveys allow users to be prompted at job launch with a series of questions " -"related to the job. This allows for variables to be defined that affect the " -"playbook run at time of launch." -msgstr "" -"Les questionnaires permettent aux utilisateurs d'être invités lors du " -"lancement d'un job à répondre à une série de questions liées au job. Cela " -"permet de définir les variables affectant l'exécution du Playbook au moment " -"du lancement." - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:58 -msgid "Sync all inventory sources" -msgstr "Synchroniser toutes les sources d'inventaire" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:29 -msgid "Sync canceled. Click to view log." -msgstr "Synchronisation annulée. Cliquez pour afficher le journal." - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:35 -msgid "Sync completed. Click to view log." -msgstr "Synchronisation terminée. Cliquez pour afficher le journal." - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:32 -msgid "Sync failed. Click to view log." -msgstr "Échec de la synchronisation. Cliquez pour afficher le journal." - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:17 -msgid "Sync not performed. Click" -msgstr "Synchronisation non effectuée. Cliquez" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:38 -msgid "Sync pending." -msgstr "Synchronisation en attente." - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:45 -msgid "Sync running" -msgstr "Synchronisation en cours" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:46 -msgid "Sync running. Click to view log." -msgstr "Synchronisation en cours. Cliquez pour afficher le journal." - -#: client/src/configuration/configuration.partial.html:29 -msgid "System" -msgstr "Système" - -#: client/src/users/add/users-add.controller.js:12 -#: client/src/users/edit/users-edit.controller.js:12 -#: client/src/users/list/users-list.controller.js:12 -msgid "System Administrator" -msgstr "Administrateur système" - -#: client/src/users/add/users-add.controller.js:11 -#: client/src/users/edit/users-edit.controller.js:11 -#: client/src/users/list/users-list.controller.js:11 -msgid "System Auditor" -msgstr "Auditeur système" - -#: client/src/configuration/configuration.partial.html:3 -msgid "System auditors have read-only permissions in this section." -msgstr "" -"Les auditeurs de système n’ont que des permissions lecture-seule sur cette " -"section." - -#: client/src/configuration/auth-form/configuration-auth.controller.js:160 -msgid "TACACS+" -msgstr "TACACS+" - -#: client/features/output/output.strings.js:104 -msgid "TASK" -msgstr "TÂCHE" - -#: client/src/activity-stream/get-target-title.factory.js:23 -#: client/src/organizations/linkout/organizations-linkout.route.js:98 -#: client/src/organizations/list/organizations-list.controller.js:61 -#: client/src/teams/main.js:65 client/src/teams/teams.list.js:14 -#: client/src/teams/teams.list.js:15 -msgid "TEAMS" -msgstr "ÉQUIPES" - -#: client/features/templates/routes/templatesList.route.js:12 -#: client/features/templates/templates.strings.js:12 -#: client/features/templates/templates.strings.js:8 -#: client/src/activity-stream/get-target-title.factory.js:44 -#: client/src/templates/templates.list.js:15 -#: client/src/templates/templates.list.js:16 -msgid "TEMPLATES" -msgstr "MODÈLES" - -#: client/src/instance-groups/instance-groups.list.js:8 -msgid "THERE ARE CURRENTLY NO INSTANCE GROUPS DEFINED" -msgstr "IL N'Y AUCUN GROUPE D'INSTANCES ACTUELLEMENT DÉFINIS" - -#: client/src/home/dashboard/graphs/job-status/job-status-graph.directive.js:105 -msgid "TIME" -msgstr "DURÉE" - -#: client/features/users/tokens/tokens.strings.js:22 -msgid "TOKEN" -msgstr "JETON" - -#: client/features/users/tokens/tokens.strings.js:21 -msgid "TOKEN INFORMATION" -msgstr "INFORMATION JETON" - -#: client/features/applications/applications.strings.js:11 -#: client/features/users/tokens/tokens.strings.js:10 -#: client/features/users/tokens/tokens.strings.js:8 -#: client/features/users/tokens/users-tokens-list.route.js:24 -#: client/src/activity-stream/get-target-title.factory.js:50 -msgid "TOKENS" -msgstr "JETONS" - -#: client/features/templates/templates.strings.js:106 -msgid "TOTAL TEMPLATES" -msgstr "TOTAL MODÈLES" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:235 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:249 -msgid "Tag None:" -msgstr "Ne rien baliser :" - -#: client/src/templates/job_templates/job-template.form.js:196 -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 Ansible Tower documentation for details on the usage of tags." -msgstr "" -"Les balises sont utiles si votre playbook est important et que vous " -"souhaitez faire jouer certaines parties d’une scène ou exécuter une tâche. " -"Utiliser des virgules pour séparer plusieurs balises. Consulter la " -"documentation Ansible Tower pour obtenir des détails sur l'utilisation des " -"balises." - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:233 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:247 -msgid "Tags:" -msgstr "Balises :" - -#: client/src/notifications/notificationTemplates.form.js:309 -#: client/src/notifications/notificationTemplates.form.js:337 -#: client/src/notifications/notificationTemplates.form.js:376 -msgid "Target URL" -msgstr "URL cible" - -#: client/features/output/output.strings.js:92 -msgid "Tasks" -msgstr "Tâches" - -#: client/features/credentials/legacy.credentials.js:91 -#: client/src/credentials/credentials.form.js:468 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:136 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:138 -#: client/src/projects/projects.form.js:276 -#: client/src/templates/workflows.form.js:169 -msgid "Team Roles" -msgstr "Rôles d’équipe" - -#: client/lib/components/components.strings.js:79 -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:40 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:36 -#: client/src/organizations/linkout/organizations-linkout.route.js:109 -#: client/src/organizations/linkout/organizations-linkout.route.js:111 -#: client/src/shared/stateDefinitions.factory.js:426 -#: client/src/users/users.form.js:161 -msgid "Teams" -msgstr "Équipes" - -#: client/src/templates/templates.list.js:14 -#: client/src/workflow-results/workflow-results.controller.js:47 -msgid "Template" -msgstr "Modèle" - -#: client/features/templates/templates.strings.js:67 -msgid "Template parameter is missing." -msgstr "Paramètre du modèle manquant" - -#: client/lib/components/components.strings.js:76 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:37 -msgid "Templates" -msgstr "Modèles" - -#: client/src/credentials/credentials.form.js:337 -msgid "Tenant ID" -msgstr "ID Client" - -#: client/src/configuration/system-form/sub-forms/system-logging.form.js:79 -msgid "Test" -msgstr "Test" - -#: client/src/notifications/notificationTemplates.list.js:77 -msgid "Test notification" -msgstr "Notification test" - -#: client/src/templates/survey-maker/surveys/init.factory.js:13 -msgid "Text" -msgstr "Texte" - -#: client/src/templates/survey-maker/surveys/init.factory.js:14 -msgid "Textarea" -msgstr "Zone de texte" - -#: client/src/shared/form-generator.js:1406 -#: client/src/shared/form-generator.js:1412 -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." - -#: client/lib/components/components.strings.js:47 -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." - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:66 -msgid "The Insights Credential for {{inventory.name}} was not found." -msgstr "" -"Informations d'identification Insights introuvables pour {{inventory.name}}." - -#: client/src/credentials/factories/become-method-change.factory.js:32 -#: client/src/credentials/factories/kind-change.factory.js:89 -msgid "" -"The Project ID is the GCE assigned identification. It is constructed as two " -"words followed by a three digit number. Such as:" -msgstr "" -"L’ID du projet est l’identifiant attribué par GCE. Il se compose de deux " -"mots suivis d’un nombre à trois chiffres. Exemple :" - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:204 -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:207 -msgid "The Project selected has a status of" -msgstr "La projet sélectionné a un statut " - -#: client/src/projects/edit/projects-edit.controller.js:341 -msgid "The SCM update process is running." -msgstr "Le processus de mise à jour SCM est en cours d’exécution." - -#: client/src/scheduler/scheduler.strings.js:32 -msgid "The day must be between 1 and 31." -msgstr "Le jour doit être compris entre 1 et 31." - -#: client/src/credentials/credentials.form.js:190 -msgid "" -"The email address assigned to the Google Compute Engine %sservice account." -msgstr "" -"Adresse électronique attribuée au compte de service Google Compute Engine " -"%s." - -#: client/features/output/output.strings.js:12 -msgid "The host status bar will update when the job is complete." -msgstr "La barre de statut d'hôte s'actualisera une fois la tâche terminée." - -#: client/src/credentials/factories/become-method-change.factory.js:62 -#: client/src/credentials/factories/kind-change.factory.js:119 -msgid "The host to authenticate with." -msgstr "Hôte avec lequel s’authentifier." - -#: client/src/credentials/factories/kind-change.factory.js:60 -msgid "The host value" -msgstr "Valeur de l’hôte" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:154 -msgid "" -"The inventory will be in a pending status until the final delete is " -"processed." -msgstr "" -"L'inventaire présentera le statut en attente tant que la suppression " -"complète ne sera pas traitée." - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:105 -msgid "" -"The number of parallel or simultaneous processes to use while executing the " -"playbook. Inputting no value will use the default value from the %sansible " -"configuration file%s." -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 %sfichier de configuration ansible%s." - -#: client/src/templates/job_templates/job-template.form.js:151 -msgid "" -"The number of parallel or simultaneous processes to use while executing the " -"playbook. Value defaults to 0. Refer to the Ansible documentation for " -"details about the configuration file." -msgstr "" -"Nombre de processus parallèles ou simultanés à utiliser lors de l'exécution " -"du playbook. 0 indique la valeur par défaut. Voir la documentation Ansible " -"pour plus d'informations sur le fichier de configuration." - -#: client/src/credentials/factories/kind-change.factory.js:59 -msgid "The project value" -msgstr "Valeur du projet" - -#: client/src/scheduler/scheduler.strings.js:49 -msgid "" -"The scheduler options are invalid, incomplete, or a date is in the past." -msgstr "" -"Les options de planning ne sont pas valides, incomplètes, ou correspondent à" -" une date qui existe dans le passé." - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:210 -msgid "The selected project has a status of" -msgstr "La projet sélectionné a un statut " - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:199 -msgid "" -"The selected project is not configured for SCM. To configure for SCM, edit " -"the project and provide SCM settings and then run an update." -msgstr "" -"Le projet sélectionné n’est pas configuré pour SCM. Afin de le configurer " -"pour SCM, modifiez le projet et définissez les paramètres SCM, puis lancez " -"une mise à jour." - -#: client/src/projects/list/projects-list.controller.js:186 -msgid "" -"The selected project is not configured for SCM. To configure for SCM, edit " -"the project and provide SCM settings, and then run an update." -msgstr "" -"Le projet sélectionné n’est pas configuré pour SCM. Afin de le configurer " -"pour SCM, modifiez le projet et définissez les paramètres SCM, puis lancez " -"une mise à jour." - -#: client/src/templates/survey-maker/shared/question-definition.form.js:52 -msgid "" -"The suggested format for variable names is lowercase and underscore-" -"separated (for example, foo_bar, user_id, host_name, 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." - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:124 -#: client/src/scheduler/scheduler.strings.js:25 -msgid "The time must be in HH24:MM:SS format." -msgstr "L’heure doit être sous le format suivant HH24:MM:SS" - -#: client/lib/services/base-string.service.js:79 -msgid "The {{ resourceType }} is currently being used by other resources." -msgstr "" -"Le {{ resourceType }} est utilisé actuellement par d'autres ressources." - -#: client/src/activity-stream/streams.list.js:17 -msgid "There are no events to display at this time" -msgstr "Aucun événement à afficher pour le moment" - -#: client/features/jobs/jobs.strings.js:17 -msgid "There are no running jobs." -msgstr "Aucun job en cours d'exécution." - -#: client/src/projects/list/projects-list.controller.js:150 -msgid "" -"There is no SCM update information available for this project. An update has" -" not yet been completed. If you have not already done so, start an update " -"for this project." -msgstr "" -"Aucune information de mise à jour SCM n’est disponible pour ce projet. Une " -"mise à jour n’est pas encore terminée. Si vous n’avez pas encore lancé une " -"mise à jour pour ce projet, faites-le." - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:191 -msgid "" -"There is no SCM update information available for this project. An update has" -" not yet been completed. If you have not already done so, start an update " -"for this project." -msgstr "" -"Aucune information de mise à jour SCM n’est disponible pour ce projet. Une " -"mise à jour n’est pas encore terminée. Si vous n’avez pas encore lancé une " -"mise à jour pour ce projet, faites-le." - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:141 -msgid "There was an error deleting inventory source groups. Returned status:" -msgstr "" -"Une erreur s’est produite lors de la suppression des groupes de source " -"d'inventaire. État renvoyé :" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:131 -msgid "There was an error deleting inventory source hosts. Returned status:" -msgstr "" -"Une erreur s’est produite lors de la suppression des hôtes de source " -"d'inventaire. État renvoyé :" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:168 -msgid "There was an error deleting inventory source. Returned status:" -msgstr "" -"Une erreur s’est produite lors de la suppression de la source d'inventaire. " -"État renvoyé :" - -#: client/src/configuration/configuration.controller.js:142 -msgid "There was an error getting config values:" -msgstr "Erreur d’obtention des valeurs de configuration :" - -#: client/src/configuration/configuration.controller.js:415 -msgid "There was an error resetting value. Returned status:" -msgstr "" -"Une erreur s’est produite lors de la réinitialisation de la valeur. État " -"renvoyé :" - -#: client/src/configuration/configuration.controller.js:607 -msgid "There was an error resetting values. Returned status:" -msgstr "" -"Une erreur s’est produite lors de la réinitialisation des valeurs. État " -"renvoyé :" - -#: client/src/configuration/system-form/configuration-system.controller.js:232 -msgid "There was an error testing the log aggregator. Returned status:" -msgstr "" -"Une erreur s’est produite lors du test de l'agrégateur. État renvoyé :" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:29 -msgid "" -"These are the modules that {{BRAND_NAME}} supports running commands against." -msgstr "" -"Il s'agit des modules pris en charge par {{BRAND_NAME}} pour l'exécution de " -"commandes." - -#: client/features/templates/templates.strings.js:94 -msgid "" -"This Job Template has a credential that requires a password. Credentials " -"requiring passwords on launch are not permitted on workflow nodes." -msgstr "" -"Ce modèle de job a un identifiant qui exige un mot de passe. Les " -"identifiants qui exigent des mots de passe au lancement ne sont pas permis " -"sur les nodes de workflow." - -#: client/src/scheduler/scheduler.strings.js:59 -msgid "" -"This Job Template has a default credential that requires a password before " -"launch. Adding or editing schedules is prohibited while this credential is " -"selected. To add or edit a schedule, credentials that require a password " -"must be removed from the Job Template." -msgstr "" -"Ce modèle de travail a un identifiant par défaut qui nécessite un mot de " -"passe avant le lancement. Il est interdit d'ajouter ou de modifier des " -"plannings tant que cet identifiant est sélectionné. Pour ajouter ou " -"modifier un planning, les identifiants nécessitant un mot de passe doivent " -"être supprimées du modèle de travail." - -#: client/features/templates/templates.strings.js:93 -msgid "" -"This Job Template is missing a default inventory or project. This must be " -"addressed in the Job Template form before this node can be saved." -msgstr "" -"Ce Modèle de Job ne possède pas d'inventaire ou de projet par défaut. Ceci " -"doit être corrigé dans le formulaire de Modèle de Job avant que ce node " -"puisse être sauvegardé." - -#: client/src/credential-types/credential-types.strings.js:8 -msgid "" -"This credential type is currently being used by one or more credentials. " -"Credentials that use this credential type must be deleted before the " -"credential type can be deleted." -msgstr "" -"Ce type de justificatif d'identifiant est actuellement utilisé par un ou " -"plusieurs identifiants. Les identifiants qui utilisent ce type de " -"justificatif d'identité doivent être supprimés avant que le type " -"d'identifiant puisse être supprimé." - -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:24 -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:25 -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:26 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:24 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:25 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:26 -msgid "This group contains" -msgstr "Ce groupe contient" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:168 -msgid "This is not a valid number." -msgstr "Le nombre n’est pas valide." - -#: client/src/credentials/factories/become-method-change.factory.js:59 -#: client/src/credentials/factories/kind-change.factory.js:116 -msgid "" -"This is the tenant name. This value is usually the same as the username." -msgstr "" -"Il s’agit du nom du client. Cette valeur est habituellement la même que " -"celle du nom d’utilisateur." - -#: client/features/templates/templates.strings.js:63 -msgid "" -"This job template has a default {{typeLabel}} credential which must be " -"included or replaced before proceeding." -msgstr "" -"Ce modèle de job possède un identifiant {{typeLabel}} par défaut qui doit " -"être inclus ou remplacé avant de pouvoir continuer." - -#: client/src/organizations/linkout/organizations-linkout.route.js:158 -msgid "This list is populated by inventories added from the" -msgstr "Cette liste contient des inventaires ajoutés à partir de " - -#: client/src/notifications/notifications.list.js:21 -msgid "" -"This list is populated by notification templates added from the " -"%sNotifications%s section" -msgstr "" -"Cette liste contient des modèles de notification ajoutés à partir de la " -"section %sNotifications%s" - -#: client/src/organizations/linkout/organizations-linkout.route.js:209 -msgid "This list is populated by projects added from the" -msgstr "Cette liste contient des équipes ajoutés à partir de " - -#: client/src/organizations/linkout/organizations-linkout.route.js:111 -msgid "This list is populated by teams added from the" -msgstr "Cette liste contient des équipes ajoutées à partir de" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:4 -msgid "" -"This machine has not checked in with Insights in {{last_check_in}} hours" -msgstr "" -"Cette machine n'a pas été connectée à Insights depuis {{last_check_in}} " -"heures" - -#: client/src/shared/form-generator.js:753 -msgid "" -"This setting has been set manually in a settings file and is now disabled." -msgstr "" -"Cette valeur a été définie manuellement dans un fichier de configuration et " -"est maintenant désactivée." - -#: client/src/users/users.form.js:166 -msgid "This user is not a member of any teams" -msgstr "Cet utilisateur n’est pas membre d’une équipe" - -#: client/src/shared/form-generator.js:863 -#: client/src/shared/form-generator.js:958 -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." - -#: client/src/configuration/configuration.controller.js:632 -msgid "" -"This will reset all configuration values to their factory defaults. Are you " -"sure you want to proceed?" -msgstr "" -"Cette opération rétablit toutes les valeurs de configuration sur leurs " -"valeurs par défaut. Voulez-vous vraiment continuer ?" - -#: client/src/scheduler/scheduler.strings.js:40 -msgid "Thu" -msgstr "Jeu." - -#: client/src/activity-stream/streams.list.js:25 -#: client/src/home/dashboard/lists/jobs/jobs-list.partial.html:14 -#: client/src/notifications/notification-templates-list/list.controller.js:72 -msgid "Time" -msgstr "Durée" - -#: client/src/license/license.partial.html:45 -msgid "Time Remaining" -msgstr "Durée restante" - -#: client/src/projects/projects.form.js:197 -msgid "" -"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." -msgstr "" -"Délai en secondes à prévoir pour qu’un projet soit actualisé. Durant " -"l’exécution des jobs 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." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:411 -msgid "" -"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." -msgstr "" -"Délai en secondes à prévoir pour qu'une synchronisation d'inventaire soit " -"actualisée. Durant l’exécution des jobs 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." - -#: client/src/credentials/credentials.form.js:125 -msgid "" -"To learn more about the IAM STS Token, refer to the %sAmazon " -"documentation%s." -msgstr "" -"Pour en savoir plus sur le jeton STS d’IAM, reportez-vous à la documentation" -" d’%sAmazon%s." - -#: client/src/shared/form-generator.js:888 -msgid "Toggle the display of plaintext." -msgstr "Bascule l’affichage du texte en clair." - -#: client/src/notifications/shared/type-change.service.js:36 -#: client/src/notifications/shared/type-change.service.js:42 -msgid "Token" -msgstr "Jeton" - -#: client/features/applications/applications.strings.js:16 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:25 -#: client/src/users/users.form.js:235 -msgid "Tokens" -msgstr "Jetons" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:10 -msgid "Total Issues" -msgstr "Nombre de problèmes total" - -#: client/src/instance-groups/instance-groups.strings.js:19 -#: client/src/workflow-results/workflow-results.controller.js:60 -msgid "Total Jobs" -msgstr "Total Jobs" - -#: client/src/partials/logviewer.html:6 -msgid "Traceback" -msgstr "Traceback" - -#: client/src/scheduler/scheduler.strings.js:38 -msgid "Tue" -msgstr "Mar." - -#: client/src/credentials/credentials.form.js:60 -#: client/src/credentials/credentials.form.js:84 -#: client/src/inventories-hosts/inventories/inventory.list.js:56 -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:41 -#: client/src/notifications/notificationTemplates.form.js:54 -#: client/src/notifications/notificationTemplates.list.js:39 -#: client/src/notifications/notifications.list.js:32 -#: client/src/projects/projects.list.js:44 -#: client/src/scheduler/scheduled-jobs.list.js:42 -#: client/src/teams/teams.form.js:133 -#: client/src/templates/templates.list.js:31 -#: client/src/users/users.form.js:202 -msgid "Type" -msgstr "Type" - -#: client/features/credentials/credentials.strings.js:18 -#: client/src/credentials/credentials.form.js:23 -#: client/src/notifications/notificationTemplates.form.js:26 -msgid "Type Details" -msgstr "Détails sur le type" - -#: client/src/projects/add/projects-add.controller.js:178 -#: client/src/projects/edit/projects-edit.controller.js:313 -msgid "URL popover text" -msgstr "Texte popover de l’URL" - -#: client/src/login/loginModal/loginModal.partial.html:49 -msgid "USERNAME" -msgstr "NOM D’UTILISATEUR" - -#: client/src/activity-stream/get-target-title.factory.js:20 -#: client/src/organizations/linkout/organizations-linkout.route.js:43 -#: client/src/organizations/list/organizations-list.controller.js:55 -#: client/src/users/users.list.js:18 client/src/users/users.list.js:19 -#: client/src/users/users.route.js:8 -msgid "USERS" -msgstr "UTILISATEURS" - -#: client/lib/components/components.strings.js:24 -msgid "Unable to Submit" -msgstr "Validation impossible" - -#: client/features/templates/templates.strings.js:84 -msgid "Unable to copy template." -msgstr "Impossible de copier le modèle." - -#: client/src/instance-groups/instance-groups.strings.js:59 -msgid "Unable to delete instance group." -msgstr "Impossible de supprimer le groupe d'instances" - -#: client/features/templates/templates.strings.js:80 -msgid "Unable to delete template." -msgstr "Impossible de supprimer le modèle." - -#: client/features/templates/templates.strings.js:82 -msgid "Unable to determine template type." -msgstr "Impossible de déterminer le type de modèle." - -#: client/features/templates/templates.strings.js:69 -msgid "Unable to determine this template's type while copying." -msgstr "Impossible de déterminer ce type de modèle pendant la copie." - -#: client/features/templates/templates.strings.js:70 -msgid "Unable to determine this template's type while deleting." -msgstr "Impossible de déterminer ce type de modèle pendant la suppression." - -#: client/features/templates/templates.strings.js:71 -msgid "Unable to determine this template's type while editing." -msgstr "Impossible de déterminer ce type de modèle lors de la modification." - -#: client/features/templates/templates.strings.js:72 -msgid "Unable to determine this template's type while launching." -msgstr "Impossible de déterminer ce type de modèle pendant le lancement." - -#: client/features/templates/templates.strings.js:73 -msgid "Unable to determine this template's type while scheduling." -msgstr "Impossible de déterminer ce type de modèle lors de la programmation." - -#: client/features/templates/templates.strings.js:79 -msgid "Unable to edit template." -msgstr "Impossible de modifier le modèle." - -#: client/src/shared/stateDefinitions.factory.js:231 -msgid "Unable to get resource:" -msgstr "Impossible d'obtenir la ressource :" - -#: client/features/templates/templates.strings.js:81 -msgid "Unable to launch template." -msgstr "Impossible de lancer le modèle." - -#: client/features/templates/templates.strings.js:83 -msgid "Unable to schedule job." -msgstr "Impossible de programmer le job." - -#: client/src/instance-groups/instance-groups.strings.js:41 -msgid "Unavailable" -msgstr "Non disponible" - -#: client/src/instance-groups/instance-groups.strings.js:40 -msgid "Unavailable to run jobs." -msgstr "Non disponible pour exécuter les jobs." - -#: client/lib/components/components.strings.js:26 -msgid "Unexpected Error" -msgstr "Erreur inattendue" - -#: client/lib/components/components.strings.js:25 -msgid "Unexpected server error. View the console for more information" -msgstr "" -"Erreur de serveur inattendue. Affichez la console pour plus d'informations." - -#: client/lib/components/components.strings.js:38 -msgid "Unsupported display model type" -msgstr "Type de modèle d'affichage non pris en charge" - -#: client/lib/components/components.strings.js:30 -msgid "Unsupported input type" -msgstr "Type d'entrée non prise en charge" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:270 -#: client/src/projects/list/projects-list.controller.js:311 -msgid "Update Not Found" -msgstr "Mise à jour introuvable" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:345 -msgid "Update Options" -msgstr "Mettre à jour les options" - -#: client/src/projects/projects.form.js:178 -msgid "Update Revision on Launch" -msgstr "Mettre à jour Révision au lancement" - -#: client/src/projects/factories/get-project-tool-tip.factory.js:30 -msgid "Update canceled. Click for details" -msgstr "Mise à jour annulée. Cliquer pour plus de détails." - -#: client/src/projects/factories/get-project-tool-tip.factory.js:24 -msgid "Update failed. Click for details" -msgstr "Échec de la mise à jour. Cliquer pour plus de détails." - -#: client/src/projects/edit/projects-edit.controller.js:341 -msgid "Update in Progress" -msgstr "Mise à jour en cours" - -#: client/src/projects/factories/get-project-tool-tip.factory.js:27 -msgid "Update missing. Click for details" -msgstr "Mise à jour manquante. Cliquer pour plus de détails." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:376 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:381 -msgid "Update on Launch" -msgstr "Mettre à jour au lancement" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:394 -msgid "Update on Project Update" -msgstr "Mettre à jour lorsque le projet est actualisé" - -#: client/src/projects/factories/get-project-tool-tip.factory.js:14 -msgid "Update queued. Click for details" -msgstr "Mise à jour en file d'attente. Cliquer pour plus de détails." - -#: client/src/projects/factories/get-project-tool-tip.factory.js:18 -msgid "Update running. Click for details" -msgstr "Mise à jour en cours. Cliquer pour plus de détails." - -#: client/src/projects/factories/get-project-tool-tip.factory.js:21 -msgid "Update succeeded. Click for details" -msgstr "Mise à jour réussie. Cliquer pour plus de détails." - -#: client/src/license/license.partial.html:71 -msgid "Upgrade" -msgstr "Mettre à niveau" - -#: client/src/organizations/organizations.form.js:48 -#: client/src/projects/projects.form.js:209 -#: client/src/templates/job_templates/job-template.form.js:241 -msgid "Use Default Environment" -msgstr "Utiliser l'environnement par défaut" - -#: client/src/templates/job_templates/job-template.form.js:314 -#: client/src/templates/job_templates/job-template.form.js:319 -msgid "Use Fact Cache" -msgstr "Utiliser le cache des facts" - -#: client/src/notifications/notificationTemplates.form.js:466 -msgid "Use SSL" -msgstr "Utiliser SSL" - -#: client/src/notifications/notificationTemplates.form.js:461 -msgid "Use TLS" -msgstr "Utiliser TLS" - -#: client/src/instance-groups/instance-groups.strings.js:20 -#: client/src/instance-groups/instance-groups.strings.js:42 -msgid "Used Capacity" -msgstr "Capacité utilisée" - -#: client/src/credentials/credentials.form.js:76 -msgid "" -"Used to check out and synchronize playbook repositories with a remote source" -" control management system such as Git, Subversion (svn), or Mercurial (hg)." -" These credentials are used by Projects." -msgstr "" -"Utilisé pour vérifier et synchroniser les référentiels de playbooks avec un " -"SCM à distance tel que Git, Subversion (svn) ou Mercurial (hg). Ces " -"informations d’identification sont utilisées par les Projets." - -#: client/features/credentials/legacy.credentials.js:80 -#: client/src/credentials/credentials.form.js:457 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:125 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:127 -#: client/src/organizations/organizations.form.js:104 -#: client/src/projects/projects.form.js:265 client/src/teams/teams.form.js:96 -#: client/src/templates/workflows.form.js:158 -msgid "User" -msgstr "Utilisateur" - -#: client/src/configuration/configuration.partial.html:36 -msgid "User Interface" -msgstr "Interface utilisateur" - -#: client/src/users/users.form.js:97 -msgid "User Type" -msgstr "Type d’utilisateur" - -#: client/src/access/rbac-multiselect/permissionsUsers.list.js:36 -#: client/src/credentials/factories/become-method-change.factory.js:17 -#: client/src/credentials/factories/become-method-change.factory.js:38 -#: client/src/credentials/factories/kind-change.factory.js:17 -#: client/src/credentials/factories/kind-change.factory.js:41 -#: client/src/credentials/factories/kind-change.factory.js:74 -#: client/src/credentials/factories/kind-change.factory.js:95 -#: client/src/notifications/notificationTemplates.form.js:348 -#: client/src/notifications/notificationTemplates.form.js:387 -#: client/src/notifications/notificationTemplates.form.js:64 -#: client/src/users/users.form.js:60 client/src/users/users.list.js:29 -msgid "Username" -msgstr "Nom d’utilisateur" - -#: client/src/credentials/credentials.form.js:80 -msgid "" -"Usernames, passwords, and access keys for authenticating to the specified " -"cloud or infrastructure provider. These are used for smart inventory sources" -" and for cloud provisioning and deployment in playbook runs." -msgstr "" -"Noms d'utilisateur, mots de passe et clés d'accès pour s'authentifier auprès" -" du fournisseur de cloud ou d'infrastructure spécifié. Ceux-ci sont utilisés" -" pour les sources d'inventaire smarts et pour l'allocation de services dans " -"le cloud et leur déploiement dans les playbooks." - -#: client/lib/components/components.strings.js:78 -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:35 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:38 -#: client/src/organizations/organizations.form.js:86 -#: client/src/teams/teams.form.js:78 -msgid "Users" -msgstr "Utilisateurs" - -#: client/src/scheduler/schedulerList.controller.js:46 -msgid "" -"Using a credential that requires a password on launch is prohibited when " -"creating a Job Template schedule" -msgstr "" -"L'utilisation d'un identifiant exigeant un mot de passe au lancement n'est " -"pas permis lorsqu'on créer un planning de Modèle de job." - -#: client/lib/components/code-mirror/code-mirror.strings.js:9 -msgid "VARIABLES" -msgstr "VARIABLES" - -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:7 -#: client/src/home/dashboard/lists/jobs/jobs-list.partial.html:7 -msgid "VIEW ALL" -msgstr "TOUT AFFICHER" - -#: client/lib/components/components.strings.js:57 -msgid "VIEW LESS" -msgstr "AFFICHER MOINS DE DÉTAILS" - -#: client/lib/components/components.strings.js:56 -msgid "VIEW MORE" -msgstr "AFFICHER DES DÉTAILS SUPPLÉMENTAIRES" - -#: client/src/shared/paginate/paginate.partial.html:48 -msgid "VIEW PER PAGE" -msgstr "AFFICHER PAR PAGE" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:234 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:248 -msgid "VPC ID:" -msgstr "ID VPC :" - -#: client/src/license/license.partial.html:10 -msgid "Valid License" -msgstr "Licence valide" - -#: client/src/inventories-hosts/hosts/host.form.js:68 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:46 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js:47 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:67 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:67 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:63 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:71 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:77 -msgid "Variables" -msgstr "Variables" - -#: client/src/job-submission/job-submission.partial.html:364 -msgid "Vault" -msgstr "Coffre-fort" - -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js:25 -msgid "Vault ID" -msgstr "ID Archivage de sécurité" - -#: client/features/templates/templates.strings.js:44 -#: client/src/credentials/credentials.form.js:391 -#: client/src/job-submission/job-submission.partial.html:146 -msgid "Vault Password" -msgstr "Mot de passe Vault" - -#: client/features/output/output.strings.js:72 -#: client/features/templates/templates.strings.js:51 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:82 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:91 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:331 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:338 -#: client/src/job-submission/job-submission.partial.html:183 -#: client/src/templates/job_templates/job-template.form.js:173 -#: client/src/templates/job_templates/job-template.form.js:180 -msgid "Verbosity" -msgstr "Verbosité" - -#: client/src/license/license.partial.html:15 -msgid "Version" -msgstr "Version" - -#: client/src/activity-stream/streams.list.js:63 -#: client/src/credential-types/credential-types.list.js:64 -#: client/src/credentials/credentials.list.js:82 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:58 -#: client/src/inventories-hosts/inventories/inventory.list.js:114 -#: client/src/inventory-scripts/inventory-scripts.list.js:70 -#: client/src/notifications/notificationTemplates.list.js:91 -#: client/src/scheduler/schedules.list.js:93 client/src/teams/teams.list.js:64 -#: client/src/templates/templates.list.js:101 -#: client/src/users/users.list.js:70 -msgid "View" -msgstr "Afficher" - -#: client/src/bread-crumb/bread-crumb.directive.js:41 -msgid "View Activity Stream" -msgstr "Afficher le flux d’activité" - -#: client/lib/components/components.strings.js:66 -msgid "View Documentation" -msgstr "Afficher la documentation" - -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:86 -#: client/src/inventories-hosts/inventory-hosts.strings.js:27 -msgid "View Insights Data" -msgstr "Afficher les données des informations" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:202 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:226 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:250 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:325 -msgid "View JSON examples at" -msgstr "Afficher les exemples JSON à" - -#: client/src/inventories-hosts/hosts/host.form.js:78 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:77 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:77 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:73 -msgid "View JSON examples at %s" -msgstr "Afficher les exemples JSON à %s" - -#: client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.partial.html:13 -msgid "View Less" -msgstr "Afficher moins" - -#: client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.partial.html:11 -msgid "View More" -msgstr "Afficher plus" - -#: client/features/output/output.strings.js:27 -msgid "View Project checkout results" -msgstr "Afficher les résultats d'extraction du projet" - -#: client/src/shared/form-generator.js:1739 -#: client/src/templates/job_templates/job-template.form.js:459 -#: client/src/templates/workflows.form.js:196 -msgid "View Survey" -msgstr "Afficher le questionnaire" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:203 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:227 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:251 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:326 -msgid "View YAML examples at" -msgstr "Afficher les exemples YAML à" - -#: client/src/inventories-hosts/hosts/host.form.js:79 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:78 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:78 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:74 -msgid "View YAML examples at %s" -msgstr "Afficher les exemples YAML à %s" - -#: client/src/credentials/credentials.list.js:84 -msgid "View credential" -msgstr "Afficher les informations d’identification" - -#: client/src/credential-types/credential-types.list.js:66 -msgid "View credential type" -msgstr "Afficher le type d'informations d’identification" - -#: client/src/activity-stream/streams.list.js:67 -msgid "View event details" -msgstr "Afficher les détails de l’événement" - -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:93 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:103 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:91 -msgid "View group" -msgstr "Afficher le groupe" - -#: client/src/inventories-hosts/hosts/host.list.js:89 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:79 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:93 -#: client/src/inventories-hosts/inventory-hosts.strings.js:26 -msgid "View host" -msgstr "Afficher l'hôte" - -#: client/src/inventories-hosts/inventories/inventory.list.js:116 -msgid "View inventory" -msgstr "Afficher l’inventaire" - -#: client/src/inventory-scripts/inventory-scripts.list.js:72 -msgid "View inventory script" -msgstr "Afficher le script d’inventaire" - -#: client/src/notifications/notificationTemplates.list.js:93 -msgid "View notification" -msgstr "Afficher la notification" - -#: client/src/scheduler/schedules.list.js:95 -msgid "View schedule" -msgstr "Afficher Planning" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:110 -msgid "View source" -msgstr "Afficher la source" - -#: client/src/teams/teams.list.js:67 -msgid "View team" -msgstr "Afficher l’équipe" - -#: client/src/templates/templates.list.js:103 -msgid "View template" -msgstr "Afficher le modèle" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:246 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:261 -msgid "View the" -msgstr "Afficher " - -#: client/features/output/output.strings.js:21 -#: client/lib/components/components.strings.js:61 -msgid "View the Credential" -msgstr "Afficher les informations d’identification" - -#: client/features/output/output.strings.js:24 -msgid "View the Inventory" -msgstr "Afficher l’inventaire" - -#: client/features/output/output.strings.js:25 -msgid "View the Job Template" -msgstr "Afficher le Modèle de Job" - -#: client/features/output/output.strings.js:26 -msgid "View the Project" -msgstr "Afficher le projet" - -#: client/features/output/output.strings.js:28 -msgid "View the Schedule" -msgstr "Afficher le planning" - -#: client/features/output/output.strings.js:30 -msgid "View the User" -msgstr "Afficher cet utilisateur" - -#: client/src/projects/projects.list.js:109 -msgid "View the project" -msgstr "Afficher le projet" - -#: client/src/scheduler/scheduled-jobs.list.js:74 -msgid "View the schedule" -msgstr "Afficher le planning" - -#: client/features/output/output.strings.js:29 -msgid "View the source Workflow Job" -msgstr "Afficher source Job de workflow" - -#: client/src/users/users.list.js:73 -msgid "View user" -msgstr "Afficher l’utilisateur" - -#: client/lib/components/components.strings.js:89 -msgid "Views" -msgstr "Affichages" - -#: client/src/templates/workflows.form.js:20 -msgid "WORKFLOW" -msgstr "WORKFLOW" - -#: client/features/templates/templates.strings.js:119 -msgid "WORKFLOW VISUALIZER" -msgstr "VISUALISATEUR DE WORKFLOW" - -#: client/features/templates/templates.strings.js:105 -#: client/src/scheduler/scheduler.strings.js:58 -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:214 -msgid "Warning" -msgstr "Avertissement" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:92 -#: client/src/configuration/configuration.controller.js:229 -#: client/src/configuration/configuration.controller.js:314 -#: client/src/configuration/system-form/configuration-system.controller.js:55 -msgid "Warning: Unsaved Changes" -msgstr "Avertissement : modifications non enregistrées" - -#: client/src/scheduler/scheduler.strings.js:39 -msgid "Wed" -msgstr "Mer." - -#: client/src/license/license.partial.html:78 -msgid "" -"Welcome to Ansible Tower! Please complete the steps below to acquire a " -"license." -msgstr "" -"Bienvenue à Ansible Tower ! Veuillez suivre les étapes ci-dessous pour " -"obtenir une licence." - -#: client/src/login/loginModal/loginModal.partial.html:17 -msgid "Welcome to Ansible {{BRAND_NAME}}!  Please sign in." -msgstr "Bienvenue sur Ansible {{BRAND_NAME}} !  Connectez-vous." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:368 -msgid "" -"When not checked, a merge will be performed, combining local variables with " -"those found on the external source." -msgstr "" -"Si la case n'est pas cochée, une fusion aura lieu, combinant les variables " -"locales à celles qui se trouvent dans la source externe." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:356 -msgid "" -"When not checked, local child hosts and groups not found on the external " -"source will remain untouched by the inventory update process." -msgstr "" -"Si non cochée, 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." - -#: client/features/jobs/jobs.strings.js:11 -msgid "Workflow Job" -msgstr "Job de workflow" - -#: client/lib/models/models.strings.js:45 -msgid "Workflow Job Template Nodes" -msgstr "Nodes de modèles de Jobs de workflows" - -#: client/features/templates/templates.strings.js:14 -#: client/src/templates/templates.list.js:66 -msgid "Workflow Template" -msgstr "Modèle de workflow" - -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:109 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:41 -msgid "Workflow Templates" -msgstr "Modèles de workflow" - -#: client/src/shared/form-generator.js:1743 -#: client/src/templates/workflows.form.js:222 -msgid "Workflow Visualizer" -msgstr "Visualisateur de workflow" - -#: client/features/users/tokens/tokens.strings.js:31 -msgid "Write" -msgstr "Écriture" - -#: client/lib/components/code-mirror/code-mirror.strings.js:11 -#: client/lib/services/base-string.service.js:69 -#: client/src/job-submission/job-submission.partial.html:171 -msgid "YAML" -msgstr "YAML" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:200 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:224 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:248 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:323 -msgid "YAML:" -msgstr "YAML :" - -#: client/lib/services/base-string.service.js:73 -msgid "YES" -msgstr "OUI" - -#: client/src/notifications/add/add.controller.js:83 -#: client/src/notifications/edit/edit.controller.js:130 -msgid "Yellow" -msgstr "Jaune" - -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:53 -msgid "" -"You can create a job template here." -msgstr "" -"Vous pouvez créer un modèle de job ici." - -#: client/features/templates/templates.strings.js:89 -msgid "" -"You do not have access to all resources used by this workflow. Resources " -"that you don't have access to will not be copied and will result in an " -"incomplete workflow." -msgstr "" -"Vous n'avez pas accès à toutes les ressources utilisées par ce workflow. Les" -" ressources auxquelles vous n'avez pas accès ne seront pas copiées et donc, " -"le workflow ne sera pas complet." - -#: client/src/projects/edit/projects-edit.controller.js:64 -msgid "You do not have access to view this property" -msgstr "Vous n’avez pas d’accès pour afficher cette propriété" - -#: client/src/projects/add/projects-add.controller.js:32 -msgid "You do not have permission to add a project." -msgstr "Vous n’êtes pas autorisé à ajouter un projet." - -#: client/src/users/add/users-add.controller.js:44 -msgid "You do not have permission to add a user." -msgstr "Vous n’êtes pas autorisé à ajouter un utilisateur." - -#: client/src/access/rbac-multiselect/rbac-multiselect-list.directive.js:174 -msgid "You do not have permission to manage this user" -msgstr "Vous n'êtes pas autorisé à gérer cet utilisateur" - -#: client/features/templates/templates.strings.js:68 -msgid "You do not have permission to perform this action." -msgstr "Vous n'êtes pas autorisé à effectuer cette action." - -#: client/src/inventories-hosts/inventory-hosts.strings.js:41 -msgid "You do not have sufficient permissions to edit the host filter." -msgstr "" -"Vous ne disposez pas de suffisamment de permissions pour modifier le filtre " -"de l'hôte." - -#: client/src/configuration/auth-form/configuration-auth.controller.js:91 -#: client/src/configuration/configuration.controller.js:228 -#: client/src/configuration/configuration.controller.js:313 -#: client/src/configuration/system-form/configuration-system.controller.js:54 -msgid "" -"You have unsaved changes. Would you like to proceed without" -" saving?" -msgstr "" -"Des modifications n’ont pas été enregistrées. Voulez-vous continuer " -"sans les enregistrer ?" - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:204 -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:207 -msgid "" -"You must run a successful update before you can select a playbook. You will " -"not be able to save this Job Template without a valid playbook." -msgstr "" -"Vous devez procéder à la mise à jour avant de sélectionner un playbook. Vous" -" ne pourrez pas enregistrer ce modèle de job sans playbook valide." - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:224 -#: client/src/projects/list/projects-list.controller.js:267 -msgid "Your request to cancel the update was submitted to the task manager." -msgstr "" -"Votre demande d’annulation de la mise à jour a été envoyée au gestionnaire " -"de tâches." - -#: client/src/login/loginModal/loginModal.partial.html:22 -msgid "Your session timed out due to inactivity. Please sign in." -msgstr "" -"Votre session a expiré en raison d’un temps d’inactivité. Veuillez vous " -"connecter." - -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:24 -#: client/src/job-submission/job-submission.partial.html:317 -#: client/src/shared/form-generator.js:1213 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:42 -msgid "and" -msgstr "et" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:245 -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:270 -msgid "button to view the latest status." -msgstr "bouton pour voir le statut le plus récent." - -#: client/features/users/tokens/tokens.strings.js:27 -msgid "by" -msgstr "par" - -#: client/src/job-submission/job-submission.partial.html:289 -#: client/src/job-submission/job-submission.partial.html:294 -#: client/src/job-submission/job-submission.partial.html:305 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:14 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:19 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:30 -msgid "characters long." -msgstr "caractères." - -#: client/features/output/output.strings.js:79 -#: client/src/shared/smart-search/smart-search.partial.html:50 -msgid "documentation" -msgstr "documentation" - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:204 -msgid "failed" -msgstr "Échec" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:247 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:262 -msgid "for a complete list of supported filters." -msgstr "pour obtenir une liste complète des filtres pris en charge." - -#: client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.directive.js:82 -msgid "from the" -msgstr "à partir de " - -#: client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.directive.js:82 -#: client/src/inventories-hosts/inventory-hosts.strings.js:8 -msgid "group" -msgid_plural "groups" -msgstr[0] "group" -msgstr[1] "groupes" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:26 -msgid "groups" -msgstr "groupes" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:24 -msgid "groups and" -msgstr "groupes et" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:9 -msgid "host" -msgid_plural "hosts" -msgstr[0] "hôte" -msgstr[1] "hôtes" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:24 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:25 -msgid "hosts" -msgstr "hôtes" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:65 -msgid "hosts with failures. Click for details." -msgstr "hôtes avec échecs. Cliquez pour obtenir plus d'informations." - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:210 -msgid "missing" -msgstr "manquant" - -#: client/src/access/rbac-multiselect/permissionsTeams.list.js:21 -msgid "name" -msgstr "nom" - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:207 -msgid "never updated" -msgstr "jamais mis à jour" - -#: client/src/shared/paginate/paginate.partial.html:34 -msgid "of" -msgstr "de" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:239 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:254 -msgid "of the filters match." -msgstr "des filtres correspondent." - -#: client/src/scheduler/scheduler.strings.js:34 -msgid "on" -msgstr "le" - -#: client/src/scheduler/scheduler.strings.js:31 -msgid "on day" -msgstr "le" - -#: client/src/scheduler/scheduler.strings.js:35 -msgid "on days" -msgstr "les" - -#: client/src/scheduler/scheduler.strings.js:33 -msgid "on the" -msgstr "le" - -#: client/src/access/rbac-multiselect/permissionsTeams.list.js:24 -msgid "organization" -msgstr "organisation" - -#: client/src/shared/form-generator.js:1085 -msgid "playbook" -msgstr "playbook" - -#: client/src/organizations/linkout/organizations-linkout.route.js:111 -#: client/src/organizations/linkout/organizations-linkout.route.js:158 -#: client/src/organizations/linkout/organizations-linkout.route.js:209 -msgid "section" -msgstr "section" - -#: client/src/credentials/credentials.form.js:138 -#: client/src/credentials/credentials.form.js:364 -msgid "set in helpers/credentials" -msgstr "définir dans helpers/identifiants" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:48 -msgid "sources with sync failures. Click for details" -msgstr "" -"sources avec échecs de synchronisation. Cliquez pour obtenir plus " -"d'informations." - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:244 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:259 -msgid "test" -msgstr "test" - -#: client/src/job-submission/job-submission.partial.html:289 -#: client/src/job-submission/job-submission.partial.html:294 -#: client/src/job-submission/job-submission.partial.html:305 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:14 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:19 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:30 -msgid "to" -msgstr "pour" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:139 -msgid "" -"to include all regions. Only Hosts associated with the selected regions will" -" be updated." -msgstr "" -"pour inclure toutes les régions. Seuls les hôtes associés aux régions " -"sélectionnées sont actualisés." - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:17 -msgid "to start it now." -msgstr "pour commencer maintenant." - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:25 -msgid "to update." -msgstr "pour actualiser." - -#: client/src/credentials/credentials.form.js:381 -msgid "v2 URLs%s - leave blank" -msgstr "v2 URLs%s - laisser vide" - -#: client/src/credentials/credentials.form.js:382 -msgid "v3 default%s - set to 'default'" -msgstr "v3 default%s - définir sur 'default'" - -#: client/src/credentials/credentials.form.js:383 -msgid "v3 multi-domain%s - your domain name" -msgstr "v3 multi-domain%s - votre nom de domaine" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:319 -msgid "view azure_rm.ini in the Ansible github repo." -msgstr "view azure_rm.ini est dans le référentiel Ansible github." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:220 -msgid "view ec2.ini in the Ansible github repo." -msgstr "afficher ec2.ini dans le référentiel github Ansible." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:244 -msgid "view vmware_inventory.ini in the Ansible github repo." -msgstr "afficher vmware_inventory.ini dans le référentiel github Ansible." - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:239 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:254 -msgid "when" -msgstr "quand" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:225 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:239 -msgid "" -"will create group names similar to the following examples based on the " -"options selected:" -msgstr "" -"créera des noms de groupe semblables aux noms de groupes des exemples " -"suivants selon les options sélectionnées :" - -#: client/src/inventories-hosts/inventories/related/groups/factories/get-hosts-status-msg.factory.js:11 -msgid "with failed jobs." -msgstr "avec des jobs ayant échoué." - -#: client/features/users/tokens/tokens.strings.js:42 -msgid "{{ appName }} Token" -msgstr "{{ appName }} Jeton" - -#: client/lib/services/base-string.service.js:102 -msgid "{{ header }} {{ body }}" -msgstr "{{ header }} {{ body }}" - -#: client/lib/services/base-string.service.js:75 -msgid "{{ resource }} successfully created" -msgstr "{{ resource }} créé" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:31 -msgid "{{ str1 }}

{{ str2 }}

" -msgstr "{{ str1 }}

{{ str2 }}

" - -#: client/src/templates/prompt/steps/other-prompts/prompt-other-prompts.partial.html:5 -msgid "{{:: vm.strings.get('prompt.JOB_TYPE') }}" -msgstr "{{:: vm.strings.get('prompt.JOB_TYPE') }}" - -#: client/lib/components/input/label.partial.html:5 -msgid "{{::state._hint}}" -msgstr "{{::state._hint}}" - -#: client/src/shared/paginate/paginate.partial.html:55 -msgid "{{pageSize}}" -msgstr "{{pageSize}}" diff --git a/awx/ui/po/ja.po b/awx/ui/po/ja.po deleted file mode 100644 index 0db6649f1825..000000000000 --- a/awx/ui/po/ja.po +++ /dev/null @@ -1,6706 +0,0 @@ -# asasaki , 2017. #zanata -# mkim , 2017. #zanata -# myamamot , 2017. #zanata -# shanemcd , 2017. #zanata -# asasaki , 2018. #zanata -msgid "" -msgstr "" -"Project-Id-Version: \n" -"PO-Revision-Date: 2018-08-16 11:44+0000\n" -"Last-Translator: asasaki \n" -"Language-Team: Japanese\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: ja\n" -"Plural-Forms: nplurals=1; plural=0\n" -"X-Generator: Zanata 4.6.0\n" - -#: client/src/projects/add/projects-add.controller.js:162 -#: client/src/projects/edit/projects-edit.controller.js:297 -msgid "" -"%sNote:%s Mercurial does not support password authentication for SSH. Do not" -" put the username and key in the URL. If using Bitbucket and SSH, do not " -"supply your Bitbucket username." -msgstr "" -"%s注:%s Mercurial は SSH のパスワード認証をサポートしません。ユーザー名およびキーを URL " -"に入れないでください。Bitbucket および SSH を使用している場合は、Bitbucket ユーザー名を入力しないでください。" - -#: client/src/projects/add/projects-add.controller.js:141 -#: client/src/projects/edit/projects-edit.controller.js:276 -msgid "" -"%sNote:%s 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." -msgstr "" -"%s注:%s GitHub または Bitbucket の SSH プロトコルを使用している場合、SSH キーのみを入力し、ユーザー名 (git 以外)" -" を入力しないでください。また、GitHub および Bitbucket は、SSH の使用時のパスワード認証をサポートしません。GIT " -"の読み取り専用プロトコル (git://) はユーザー名またはパスワード情報を使用しません。" - -#: client/src/credentials/credentials.form.js:287 -msgid "(defaults to %s)" -msgstr "(%s にデフォルト設定)" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:402 -msgid "(seconds)" -msgstr "(秒)" - -#: client/src/shared/paginate/paginate.partial.html:66 -msgid "100" -msgstr "100" - -#: client/src/shared/paginate/paginate.partial.html:60 -msgid "20" -msgstr "20" - -#: client/src/shared/paginate/paginate.partial.html:63 -msgid "50" -msgstr "50" - -#: client/lib/components/code-mirror/code-mirror.strings.js:17 -msgid "" -"

\n" -" Enter inventory variables using either JSON or YAML\n" -" syntax. Use the radio button to toggle between the two.\n" -"

\n" -" JSON:\n" -"
\n" -"
\n" -" {\n" -"
\"somevar\": \"somevalue\",\n" -"
\"password\": \"magic\"\n" -"
\n" -" }\n" -"
\n" -" YAML:\n" -"
\n" -"
\n" -" ---\n" -"
somevar: somevalue\n" -"
password: magic\n" -"
\n" -"
\n" -"

\n" -" View JSON examples at\n" -" www.json.org\n" -"

\n" -"

\n" -" View YAML examples at\n" -" \n" -" docs.ansible.com\n" -"

" -msgstr "" -"

\n" -" JSON または YAML 構文のいずれかを使用してインベントリー変数を入力します。ラジオボタンを使用してこれらの間で切り替えを行います。\n" -"

\n" -" JSON:\n" -"
\n" -"
\n" -" {\n" -"
\"somevar\": \"somevalue\",\n" -"
\"password\": \"magic\"\n" -"
\n" -" }\n" -"
\n" -" YAML:\n" -"
\n" -"
\n" -" ---\n" -"
somevar: somevalue\n" -"
password: magic\n" -"
\n" -"
\n" -"

\n" -" JSON サンプルの表示\n" -" www.json.org\n" -"

\n" -"

\n" -" YAML サンプルの表示\n" -" \n" -" docs.ansible.com\n" -"

" - -#: client/features/templates/templates.strings.js:55 -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.

JSON:
{
"somevar": "somevalue",
"password": " -""magic"
}
YAML:
---
somevar: somevalue
password: magic
" -msgstr "" -"

追加のコマンドライン変数を Playbook に渡します。これは、ansible-playbook の -e または --extra-vars " -"コマンドラインパラメーターです。YAML または JSON のいずれかを使用してキーと値のペアを指定します。

JSON:
{
"somevar": "somevalue",
"password": "magic"
}
YAML:
---
somevar: somevalue
password: magic
" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:33 -#: client/src/scheduler/scheduler.strings.js:22 -msgid "A schedule name is required." -msgstr "スケジュール名が必要です。" - -#: client/src/users/add/users-add.controller.js:103 -msgid "A value is required" -msgstr "値が必要です" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:167 -msgid "A value is required." -msgstr "値が必要です。" - -#: client/src/about/about.route.js:10 -msgid "ABOUT" -msgstr "情報" - -#: client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html:16 -msgid "ACTION" -msgstr "アクション" - -#: client/src/activity-stream/activity-detail.form.js:23 -msgid "ACTIVITY DETAIL" -msgstr "アクティビティーの詳細" - -#: client/src/activity-stream/activitystream.route.js:28 -#: client/src/activity-stream/streams.list.js:14 -#: client/src/activity-stream/streams.list.js:15 -msgid "ACTIVITY STREAM" -msgstr "アクティビティーストリーム" - -#: client/src/organizations/linkout/addUsers/addUsers.partial.html:8 -msgid "ADD" -msgstr "追加" - -#: client/src/notifications/notification-templates-list/add-notifications-action.partial.html:3 -msgid "ADD A NEW TEMPLATE" -msgstr "新規テンプレートの追加" - -#: client/features/templates/templates.strings.js:107 -msgid "ADD A TEMPLATE" -msgstr "テンプレートの追加" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:16 -msgid "ADD SURVEY PROMPT" -msgstr "Survey プロンプトの追加" - -#: client/src/shared/smart-search/smart-search.partial.html:48 -msgid "ADDITIONAL INFORMATION" -msgstr "追加情報" - -#: client/features/output/output.strings.js:76 -msgid "ADDITIONAL_INFORMATION" -msgstr "ADDITIONAL_INFORMATION" - -#: client/src/organizations/linkout/organizations-linkout.route.js:258 -#: client/src/organizations/list/organizations-list.controller.js:85 -msgid "ADMINS" -msgstr "管理者" - -#: client/src/activity-stream/get-target-title.factory.js:4 -msgid "ALL ACTIVITY" -msgstr "すべてのアクティビティー" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:239 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:254 -msgid "ANY" -msgstr "任意" - -#: client/src/credentials/credentials.form.js:198 -msgid "API Key" -msgstr "API キー" - -#: client/src/notifications/notificationTemplates.form.js:243 -msgid "API Service/Integration Key" -msgstr "API サービス/統合キー" - -#: client/src/notifications/shared/type-change.service.js:60 -msgid "API Token" -msgstr "API トークン" - -#: client/features/users/tokens/tokens.strings.js:40 -msgid "APPLICATION" -msgstr "アプリケーション" - -#: client/features/applications/applications.strings.js:28 -#: client/features/applications/applications.strings.js:8 -#: client/src/activity-stream/get-target-title.factory.js:47 -msgid "APPLICATIONS" -msgstr "アプリケーション" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.route.js:19 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.route.js:19 -msgid "ASSOCIATED GROUPS" -msgstr "関連付けられているグループ" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.route.js:19 -msgid "ASSOCIATED HOSTS" -msgstr "関連付けられているホスト" - -#: client/lib/components/components.strings.js:87 -msgid "About" -msgstr "情報" - -#: client/lib/components/components.strings.js:91 -msgid "Access" -msgstr "アクセス" - -#: client/src/credentials/credentials.form.js:91 -msgid "Access Key" -msgstr "アクセスキー" - -#: client/src/notifications/notificationTemplates.form.js:221 -msgid "Account SID" -msgstr "アカウント SID" - -#: client/src/notifications/notificationTemplates.form.js:180 -msgid "Account Token" -msgstr "アカウントトークン" - -#: client/src/activity-stream/activity-detail.form.js:36 -msgid "Action" -msgstr "アクション" - -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:20 -#: client/src/inventories-hosts/hosts/hosts.partial.html:47 -#: client/src/shared/list-generator/list-generator.factory.js:591 -msgid "Actions" -msgstr "アクション" - -#: client/features/templates/templates.strings.js:15 -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:17 -#: client/src/templates/templates.list.js:36 -msgid "Activity" -msgstr "アクティビティー" - -#: client/src/configuration/system-form/configuration-system.controller.js:88 -msgid "Activity Stream" -msgstr "アクティビティーストリーム" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:113 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:115 -#: client/src/organizations/organizations.form.js:93 -#: client/src/teams/teams.form.js:85 -#: client/src/templates/workflows.form.js:147 -msgid "Add" -msgstr "追加" - -#: client/src/credentials/credentials.list.js:14 -msgid "Add Credentials" -msgstr "認証情報の追加" - -#: client/src/inventories-hosts/inventories/inventory.list.js:13 -msgid "Add Inventories" -msgstr "インベントリーの追加" - -#: client/src/shared/stateDefinitions.factory.js:304 -msgid "Add Permissions" -msgstr "パーミッションの追加" - -#: client/src/projects/projects.list.js:13 -msgid "Add Project" -msgstr "プロジェクトの追加" - -#: client/src/shared/form-generator.js:1731 -#: client/src/templates/job_templates/job-template.form.js:468 -#: client/src/templates/workflows.form.js:205 -msgid "Add Survey" -msgstr "Survey の追加" - -#: client/src/teams/teams.list.js:13 -msgid "Add Team" -msgstr "チームの追加" - -#: client/src/teams/teams.form.js:86 -msgid "Add User" -msgstr "ユーザーの追加" - -#: client/src/shared/stateDefinitions.factory.js:426 -#: client/src/shared/stateDefinitions.factory.js:594 -#: client/src/users/users.list.js:17 -msgid "Add Users" -msgstr "ユーザーの追加" - -#: client/src/organizations/organizations.form.js:94 -msgid "Add Users to this organization." -msgstr "ユーザーをこの組織に追加してください。" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:69 -msgid "Add a group" -msgstr "グループの追加" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:118 -msgid "Add a host" -msgstr "ホストの追加" - -#: client/src/scheduler/schedules.list.js:74 -msgid "Add a new schedule" -msgstr "新規スケジュールの追加" - -#: client/features/credentials/legacy.credentials.js:71 -#: client/src/credentials/credentials.form.js:448 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:115 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:117 -#: client/src/projects/projects.form.js:255 -#: client/src/templates/job_templates/job-template.form.js:411 -#: client/src/templates/workflows.form.js:148 -msgid "Add a permission" -msgstr "パーミッションの追加" - -#: client/src/shared/form-generator.js:1466 -msgid "Admin" -msgstr "管理者" - -#: client/lib/components/components.strings.js:92 -msgid "Administration" -msgstr "管理" - -#: client/src/organizations/linkout/organizations-linkout.route.js:281 -msgid "Admins" -msgstr "管理者" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:391 -msgid "" -"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." -msgstr "" -"SCM " -"リビジョンが変更されるプロジェクトの毎回の更新後に、選択されたソースのインベントリーを更新してからジョブのタスクを実行します。これは、Ansible " -"インベントリーの .ini ファイル形式のような静的コンテンツが対象であることが意図されています。" - -#: client/lib/components/components.strings.js:99 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:37 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:43 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:65 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:74 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:139 -msgid "All" -msgstr "すべて" - -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:23 -msgid "All Activity" -msgstr "すべてのアクティビティー" - -#: client/features/portalMode/index.view.html:33 -msgid "All Jobs" -msgstr "すべてのジョブ" - -#: client/src/templates/job_templates/job-template.form.js:290 -#: client/src/templates/job_templates/job-template.form.js:297 -msgid "Allow Provisioning Callbacks" -msgstr "プロビジョニングコールバックの許可" - -#: client/features/templates/templates.strings.js:102 -#: client/src/workflow-results/workflow-results.controller.js:66 -msgid "Always" -msgstr "常時" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:270 -msgid "An SCM update does not appear to be running for project:" -msgstr "SCM 更新がプロジェクトに対して実行されていないようです。" - -#: client/src/projects/list/projects-list.controller.js:311 -msgid "" -"An SCM update does not appear to be running for project: %s. Click the " -"%sRefresh%s button to view the latest status." -msgstr "SCM 更新がプロジェクトに対して実行されていないようです: %s。%s更新%s ボタンをクリックして最新のステータスを表示してください。" - -#: client/src/organizations/organizations.form.js:47 -#: client/src/organizations/organizations.form.js:52 -#: client/src/projects/projects.form.js:207 -#: client/src/projects/projects.form.js:212 -#: client/src/templates/job_templates/job-template.form.js:239 -#: client/src/templates/job_templates/job-template.form.js:245 -msgid "Ansible Environment" -msgstr "Ansible 環境" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:62 -#: client/src/templates/survey-maker/shared/question-definition.form.js:68 -msgid "Answer Type" -msgstr "回答タイプ" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:44 -#: client/src/templates/survey-maker/shared/question-definition.form.js:53 -msgid "Answer Variable Name" -msgstr "回答の変数名" - -#: client/lib/components/components.strings.js:85 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:24 -msgid "Applications" -msgstr "アプリケーション" - -#: client/src/notifications/notification-templates-list/list.controller.js:228 -msgid "Are you sure you want to delete this notification template?" -msgstr "この通知テンプレートを削除してもよろしいですか?" - -#: client/src/teams/list/teams-list.controller.js:80 -msgid "Are you sure you want to delete this team?" -msgstr "このチームを削除してもよろしいですか?" - -#: client/src/users/list/users-list.controller.js:93 -msgid "Are you sure you want to delete this user?" -msgstr "このユーザーを削除してもよろしいですか?" - -#: client/features/templates/templates.strings.js:98 -msgid "Are you sure you want to delete this workflow node?" -msgstr "このワークフローノードを削除してもよろしいですか?" - -#: client/lib/services/base-string.service.js:81 -msgid "Are you sure you want to delete this {{ resourceType }}?" -msgstr "この {{ resourceType }} を削除してもよろしいですか?" - -#: client/src/partials/survey-maker-modal.html:13 -msgid "Are you sure you want to delete this {{deleteMode}}?" -msgstr "この {{deleteMode}} を削除してもよろしいですか?" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-disassociate.partial.html:25 -msgid "Are you sure you want to disassociate the group below from" -msgstr "以下のグループの関連付けを解除してもよろしいですか?" - -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.partial.html:23 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-disassociate.partial.html:25 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-disassociate.partial.html:26 -msgid "Are you sure you want to disassociate the host below from" -msgstr "以下のホストの関連付けを解除してもよろしいですか?" - -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:47 -msgid "" -"Are you sure you want to permanently delete the group below from the " -"inventory?" -msgstr "インベントリーから以下のグループを完全に削除してもよろしいですか?" - -#: client/src/inventories-hosts/inventories/related/hosts/list/host-list.controller.js:106 -msgid "" -"Are you sure you want to permanently delete the host below from the " -"inventory?" -msgstr "インベントリーからホストを完全に削除してもよろしいですか?" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:69 -msgid "" -"Are you sure you want to permanently delete the inventory source below from " -"the inventory?" -msgstr "インベントリーから以下のインベントリーソースを完全に削除してもよろしいですか?" - -#: client/src/projects/edit/projects-edit.controller.js:253 -msgid "Are you sure you want to remove the %s below from %s?" -msgstr "%s から以下の %s を削除してもよろしいですか?" - -#: client/lib/services/base-string.service.js:86 -msgid "Are you sure you want to submit the request to cancel this job?" -msgstr "このジョブをキャンセルする要求を送信してよろしいですか?" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:39 -msgid "Arguments" -msgstr "引数" - -#: client/src/credentials/credentials.form.js:232 -#: client/src/credentials/credentials.form.js:271 -#: client/src/credentials/credentials.form.js:311 -#: client/src/credentials/credentials.form.js:397 -msgid "Ask at runtime?" -msgstr "実行時に確認しますか?" - -#: client/src/instance-groups/instance-groups.strings.js:31 -msgid "Associate an existing Instance" -msgstr "既存インスタンスの関連付け" - -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:69 -msgid "Associate an existing group" -msgstr "既存グループの関連付け" - -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.list.js:51 -msgid "Associate this host with a new group" -msgstr "新規グループへのこのホストの関連付け" - -#: client/src/shared/form-generator.js:1468 -msgid "Auditor" -msgstr "監査者" - -#: client/src/configuration/configuration.partial.html:15 -msgid "Authentication" -msgstr "認証" - -#: client/src/credentials/credentials.form.js:72 -msgid "" -"Authentication for network device access. This can include SSH keys, " -"usernames, passwords, and authorize information. Network credentials are " -"used when submitting jobs to run playbooks against network devices." -msgstr "" -"ネットワークデバイスアクセスの認証です。これには SSH " -"キー、ユーザー名、パスワードおよび認証情報が含まれることがあります。ネットワークの認証情報は、ジョブを送信し、ネットワークデバイスに対して " -"Playbook を実行する際に使用されます。" - -#: client/src/credentials/credentials.form.js:68 -msgid "" -"Authentication for remote machine access. This can include SSH keys, " -"usernames, passwords, and sudo information. Machine credentials are used " -"when submitting jobs to run playbooks against remote hosts." -msgstr "" -"リモートマシンアクセスの認証です。これには SSH キー、ユーザー名、パスワードおよび sudo " -"情報が含まれることがあります。マシンの認証情報は、ジョブを送信し、リモートホストに対して Playbook を実行する際に使用されます。" - -#: client/src/credentials/credentials.form.js:343 -msgid "Authorize" -msgstr "認証" - -#: client/src/credentials/credentials.form.js:351 -msgid "Authorize Password" -msgstr "認証パスワード" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:226 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:240 -msgid "Availability Zone:" -msgstr "アベイラビリティーゾーン" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:152 -msgid "Azure AD" -msgstr "Azure AD" - -#: client/src/shared/directives.js:91 -msgid "BROWSE" -msgstr "参照" - -#: client/features/output/output.strings.js:97 -msgid "Back to Top" -msgstr "トップに戻る" - -#: client/src/projects/projects.form.js:81 -msgid "" -"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." -msgstr "" -"Playbook を見つけるために使用されるベースパスです。このパス内にあるディレクトリーは Playbook " -"ディレクトリーのドロップダウンに一覧表示されます。ベースパスと選択された Playbook ディレクトリーは、Playbook " -"を見つけるために使用される完全なパスを提供します。" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:129 -msgid "Become Privilege Escalation" -msgstr "Become (権限昇格)" - -#: client/src/license/license.partial.html:107 -msgid "Browse" -msgstr "参照" - -#: client/src/license/license.partial.html:129 -msgid "" -"By default, Tower collects and transmits analytics data on Tower usage to Red Hat. This data is used to enhance future releases of the Tower Software and help streamline customer experience and success. For more information, see\n" -"\t\t\t\t\t\t\t\t\t\t\n" -"\t\t\t\t\t\t\t\t\t\t\t\tthis Tower documentation page\n" -"\t\t\t\t\t\t\t\t\t\t. Uncheck this box to disable this feature." -msgstr "" -"デフォルトで Tower は Tower の使用状況についてのアナリティクスデータを収集し、これを Red Hat に送信します。このデータは Tower ソフトウェアの今後のリリースの強化のために使用され、カスタマーエクスペリエンスとお客様の成功を支援するために使用されます。詳細は、\n" -"\t\t\t\t\t\t\t\t\t\t\n" -"\t\t\t\t\t\t\t\t\t\t\t\tこの Tower ドキュメントページ\n" -"\t\t\t\t\t\t\t\t\t\t を参照してください。この機能を無効にするには、このボックスのチェックを外してください。" - -#: client/lib/services/base-string.service.js:60 -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.partial.html:28 -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:51 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-disassociate.partial.html:29 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-disassociate.partial.html:29 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-disassociate.partial.html:30 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:73 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal.partial.html:16 -#: client/src/inventories-hosts/shared/associate-groups/associate-groups.partial.html:16 -#: client/src/inventories-hosts/shared/associate-hosts/associate-hosts.partial.html:16 -#: client/src/job-submission/job-submission.partial.html:370 -#: client/src/partials/survey-maker-modal.html:17 -#: client/src/partials/survey-maker-modal.html:85 -#: client/src/shared/instance-groups-multiselect/instance-groups-modal/instance-groups-modal.partial.html:17 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html:68 -msgid "CANCEL" -msgstr "取り消し" - -#: client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html:28 -msgid "CHANGES" -msgstr "変更" - -#: client/features/templates/templates.strings.js:113 -msgid "CHECK" -msgstr "チェック" - -#: client/lib/components/components.strings.js:20 -msgid "CHOOSE A FILE" -msgstr "ファイルの選択" - -#: client/features/output/output.strings.js:78 -#: client/src/shared/smart-search/smart-search.partial.html:26 -msgid "CLEAR ALL" -msgstr "すべてをクリア" - -#: client/lib/services/base-string.service.js:61 -#: client/lib/services/base-string.service.js:74 -#: client/src/partials/survey-maker-modal.html:86 -msgid "CLOSE" -msgstr "閉じる" - -#: client/features/jobs/routes/hostCompletedJobs.route.js:20 -#: client/features/jobs/routes/templateCompletedJobs.route.js:21 -#: client/features/jobs/routes/workflowJobTemplateCompletedJobs.route.js:21 -msgid "COMPLETED JOBS" -msgstr "完了したジョブ" - -#: client/src/configuration/configuration.partial.html:10 -msgid "CONFIGURE {{ BRAND_NAME }}" -msgstr "{{ BRAND_NAME }} の設定" - -#: client/features/templates/templates.strings.js:31 -#: client/src/scheduler/scheduler.strings.js:63 -msgid "CONFIRM" -msgstr "確認" - -#: client/lib/services/base-string.service.js:72 -msgid "COPY" -msgstr "コピー" - -#: client/features/users/tokens/tokens.strings.js:25 -msgid "COULD NOT CREATE TOKEN" -msgstr "トークンを作成できませんでした。" - -#: client/src/instance-groups/instance-groups.strings.js:46 -msgid "CPU" -msgstr "CPU" - -#: client/src/shared/stateDefinitions.factory.js:161 -msgid "CREATE %s" -msgstr "%sの作成" - -#: client/features/applications/applications.strings.js:9 -msgid "CREATE APPLICATION" -msgstr "アプリケーションの作成" - -#: client/features/credentials/credentials.strings.js:8 -#: client/src/credentials/credentials.form.js:16 -msgid "CREATE CREDENTIAL" -msgstr "認証情報の作成" - -#: client/src/inventories-hosts/inventories/related/groups/add/groups-add.route.js:8 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:16 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-add.route.js:8 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js:16 -msgid "CREATE GROUP" -msgstr "グループの作成" - -#: client/src/inventories-hosts/hosts/host.form.js:17 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-add.route.js:8 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:17 -#: client/src/inventories-hosts/inventories/related/hosts/add/host-add.route.js:8 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:17 -msgid "CREATE HOST" -msgstr "ホストの作成" - -#: client/src/instance-groups/instance-groups.strings.js:10 -msgid "CREATE INSTANCE GROUP" -msgstr "インスタンスグループの作成" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.route.js:8 -msgid "CREATE INVENTORY SOURCE" -msgstr "インベントリーソースの作成" - -#: client/src/inventories-hosts/inventories/related/sources/list/schedule/sources-schedule-add.route.js:9 -#: client/src/scheduler/scheduler.strings.js:8 -#: client/src/scheduler/schedules.route.js:161 -#: client/src/scheduler/schedules.route.js:242 -#: client/src/scheduler/schedules.route.js:73 -msgid "CREATE SCHEDULE" -msgstr "スケジュールの作成" - -#: client/src/management-jobs/scheduler/main.js:83 -msgid "CREATE SCHEDULED JOB" -msgstr "スケジュール済みジョブの作成" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:32 -msgid "CREATE SOURCE" -msgstr "ソースの作成" - -#: client/features/users/tokens/tokens.strings.js:18 -#: client/features/users/tokens/tokens.strings.js:9 -#: client/features/users/tokens/users-tokens-add.route.js:49 -msgid "CREATE TOKEN" -msgstr "トークンの作成" - -#: client/features/output/output.strings.js:101 -msgid "CREATED" -msgstr "作成済み" - -#: client/src/job-submission/job-submission.partial.html:351 -#: client/src/partials/job-template-details.html:2 -msgid "CREDENTIAL" -msgstr "認証情報" - -#: client/src/credential-types/credential-types.form.js:21 -msgid "CREDENTIAL TYPE" -msgstr "認証情報タイプ" - -#: client/src/job-submission/job-submission.partial.html:92 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html:60 -msgid "CREDENTIAL TYPE:" -msgstr "認証情報タイプ:" - -#: client/src/activity-stream/get-target-title.factory.js:11 -#: client/src/credential-types/credential-types.list.js:12 -#: client/src/credential-types/main.js:44 -msgid "CREDENTIAL TYPES" -msgstr "認証情報タイプ" - -#: client/features/credentials/legacy.credentials.js:11 -#: client/src/activity-stream/get-target-title.factory.js:17 -#: client/src/credentials/credentials.list.js:15 -#: client/src/credentials/credentials.list.js:16 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html:5 -msgid "CREDENTIALS" -msgstr "認証情報" - -#: client/features/credentials/credentials.strings.js:30 -msgid "CREDENTIALS PERMISSIONS" -msgstr "認証情報のパーミッション" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:402 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:414 -#: client/src/projects/projects.form.js:200 -msgid "Cache Timeout" -msgstr "キャッシュタイムアウト" - -#: client/src/projects/projects.form.js:189 -msgid "Cache Timeout%s (seconds)%s" -msgstr "キャッシュタイムアウト%s (seconds)%s" - -#: client/src/projects/list/projects-list.controller.js:224 -#: client/src/users/list/users-list.controller.js:85 -msgid "Call to %s failed. DELETE returned status:" -msgstr "%s の呼び出しに失敗しました。DELETE で返されたステータス:" - -#: client/src/projects/list/projects-list.controller.js:291 -#: client/src/projects/list/projects-list.controller.js:308 -msgid "Call to %s failed. GET status:" -msgstr "%s の呼び出しに失敗しました。GET ステータス:" - -#: client/src/projects/edit/projects-edit.controller.js:247 -msgid "Call to %s failed. POST returned status:" -msgstr "%s の呼び出しに失敗しました。POST で返されたステータス:" - -#: client/src/projects/list/projects-list.controller.js:270 -msgid "Call to %s failed. POST status:" -msgstr "%s の呼び出しに失敗しました。POST ステータス:" - -#: client/src/management-jobs/card/card.controller.js:29 -msgid "Call to %s failed. Return status: %d" -msgstr "%s の呼び出しが失敗しました。返されたステータス: %d" - -#: client/src/projects/list/projects-list.controller.js:317 -msgid "Call to get project failed. GET status:" -msgstr "プロジェクトを取得するための呼び出しに失敗しました。GET ステータス:" - -#: client/lib/services/base-string.service.js:93 -msgid "Call to {{ path }} failed. {{ action }} returned status: {{ status }}." -msgstr "{{ path }} の呼び出しに失敗しました。{{ action }} で返されたステータス: {{ status }}" - -#: client/features/output/output.strings.js:17 -#: client/lib/services/base-string.service.js:85 -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:105 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:188 -#: client/src/configuration/configuration.controller.js:617 -#: client/src/scheduler/scheduler.strings.js:56 -#: client/src/shared/form-generator.js:1719 -#: client/src/shared/lookup/lookup-modal.partial.html:19 -#: client/src/workflow-results/workflow-results.controller.js:38 -msgid "Cancel" -msgstr "取り消し" - -#: client/lib/services/base-string.service.js:87 -msgid "Cancel Job" -msgstr "ジョブの取り消し" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:244 -#: client/src/projects/list/projects-list.controller.js:286 -msgid "Cancel Not Allowed" -msgstr "取り消しは許可されていません" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:100 -msgid "Cancel sync process" -msgstr "同期プロセスの取り消し" - -#: client/src/projects/projects.list.js:122 -msgid "Cancel the SCM update" -msgstr "SCM 更新の取り消し" - -#: client/lib/services/base-string.service.js:99 -msgid "Cancel the {{resourceType}}" -msgstr "{{resourceType}} の取り消し" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:42 -#: client/src/projects/list/projects-list.controller.js:80 -msgid "Canceled. Click for details" -msgstr "取り消されました。クリックして詳細を確認してください。" - -#: client/src/shared/smart-search/smart-search.controller.js:162 -msgid "Cannot search running job" -msgstr "実行中のジョブを検索することはできません" - -#: client/src/instance-groups/instance-groups.list.js:22 -msgid "Capacity" -msgstr "容量" - -#: client/src/projects/projects.form.js:83 -msgid "Change %s under \"Configure {{BRAND_NAME}}\" to change this location." -msgstr "この場所を変更するには「{{BRAND_NAME}} の設定」下の %s を変更します。" - -#: client/src/activity-stream/activity-detail.form.js:41 -msgid "Changes" -msgstr "変更" - -#: client/src/notifications/notificationTemplates.form.js:355 -msgid "Channel" -msgstr "チャネル" - -#: client/features/templates/templates.strings.js:61 -msgid "Check" -msgstr "チェック" - -#: client/src/shared/form-generator.js:1087 -msgid "Choose a %s" -msgstr "%s の選択" - -#: client/features/templates/templates.strings.js:52 -msgid "Choose a job type" -msgstr "ジョブタイプを選択してください" - -#: client/features/templates/templates.strings.js:53 -msgid "Choose a verbosity" -msgstr "詳細レベルの選択" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:64 -msgid "Choose an answer type" -msgstr "回答タイプの選択" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:67 -msgid "" -"Choose an answer type or format you want as the prompt for the user. Refer " -"to the Ansible Tower Documentation for more additional information about " -"each option." -msgstr "" -"ユーザーのプロンプトが表示される際に、必要な回答タイプを選択します。それぞれのオプションの詳細については、Ansible Tower " -"ドキュメントを参照してください。" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:112 -msgid "Choose an inventory file" -msgstr "インベントリーファイルの選択" - -#: client/src/shared/directives.js:92 -msgid "Choose file" -msgstr "ファイルの選択" - -#: client/src/license/license.partial.html:97 -msgid "" -"Choose your license file, agree to the End User License Agreement, and click" -" submit." -msgstr "ライセンスファイルを選択し、使用許諾契約書に同意した後に「送信」をクリックします。" - -#: client/src/projects/projects.form.js:157 -msgid "Clean" -msgstr "クリーニング" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:299 -msgid "Clear" -msgstr "消去" - -#: client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.directive.js:64 -msgid "Click for details" -msgstr "クリックして詳細を確認してください。" - -#: client/src/templates/workflows/edit-workflow/workflow-edit.controller.js:261 -msgid "Click here to open the workflow visualizer." -msgstr "こちらをクリックしてワークフロービジュアライザーを開きます。" - -#: client/src/inventories-hosts/inventories/inventory.list.js:16 -msgid "" -"Click on a row to select it, and click Finished when done. Click the %s " -"button to create a new inventory." -msgstr "行をクリックしてこれを選択し、終了したら「終了」をクリックします。%s ボタンをクリックして新規インベントリーを作成します。" - -#: client/src/teams/teams.list.js:16 -msgid "" -"Click on a row to select it, and click Finished when done. Click the %s " -"button to create a new team." -msgstr "行をクリックしてこれを選択し、終了したら「終了」をクリックします。%s ボタンをクリックして新規チームを作成します。" - -#: client/src/templates/templates.list.js:17 -msgid "" -"Click on a row to select it, and click Finished when done. Use the %s button" -" to create a new job template." -msgstr "行をクリックしてこれを選択し、終了したら「終了」をクリックします。%s ボタンをクリックして新規ジョブテンプレートを作成します。" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:138 -msgid "" -"Click on the regions field to see a list of regions for your cloud provider." -" You can select multiple regions, or choose" -msgstr "リージョンフィールドをクリックして、クラウドプロバイダーの一覧を表示します。複数のリージョンを選択したり、以下を選択したりできます。" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:270 -msgid "Click the" -msgstr "次をクリックしてください:" - -#: client/src/scheduler/scheduler.strings.js:13 -msgid "Click to edit schedule." -msgstr "クリックしてスケジュールを編集します。" - -#: client/src/credentials/credentials.form.js:321 -msgid "Client ID" -msgstr "クライアント ID" - -#: client/src/notifications/notificationTemplates.form.js:254 -msgid "Client Identifier" -msgstr "クライアント識別子" - -#: client/src/credentials/credentials.form.js:330 -msgid "Client Secret" -msgstr "クライアントシークレット" - -#: client/src/scheduler/scheduler.strings.js:55 -#: client/src/shared/form-generator.js:1723 -msgid "Close" -msgstr "閉じる" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:26 -msgid "Cloud source not configured." -msgstr "クラウドリソースは設定されていません。" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:25 -msgid "Cloud source not configured. Click" -msgstr "クラウドソースは設定されていません。以下をクリックします。" - -#: client/src/credentials/factories/become-method-change.factory.js:80 -#: client/src/credentials/factories/kind-change.factory.js:137 -msgid "CloudForms URL" -msgstr "CloudForms URL" - -#: client/features/output/output.strings.js:18 -#: client/src/workflow-results/workflow-results.controller.js:152 -msgid "Collapse Output" -msgstr "出力の縮小" - -#: client/src/inventories-hosts/hosts/host.form.js:129 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:128 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:155 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:172 -#: client/src/templates/job_templates/job-template.form.js:443 -#: client/src/templates/workflows.form.js:180 -msgid "Completed Jobs" -msgstr "完了したジョブ" - -#: client/src/management-jobs/card/card.partial.html:34 -msgid "Configure Notifications" -msgstr "通知の設定" - -#: client/src/users/users.form.js:83 -msgid "Confirm Password" -msgstr "パスワードの確認" - -#: client/src/configuration/configuration.controller.js:624 -msgid "Confirm Reset" -msgstr "リセットの確認" - -#: client/src/configuration/configuration.controller.js:633 -msgid "Confirm factory reset" -msgstr "出荷時の設定へのリセットの確認" - -#: client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.directive.js:82 -msgid "Confirm the removal of the" -msgstr "以下の削除を確認:" - -#: client/src/teams/teams.form.js:24 client/src/users/users.form.js:25 -msgid "" -"Contact your System Administrator to grant you the appropriate permissions " -"to add and edit Users and Teams." -msgstr "ユーザーおびチームを追加し、編集するためのパーミッションについては、システム管理者にお問い合わせください。" - -#: client/src/inventories-hosts/inventories/related/groups/factories/get-hosts-status-msg.factory.js:18 -msgid "Contains 0 hosts." -msgstr "0 ホストが含まれています。" - -#: client/src/templates/job_templates/job-template.form.js:179 -msgid "" -"Control the level of output ansible will produce as the playbook executes." -msgstr "Playbook の実行時に Ansible が生成する出力のレベルを制御します。" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:337 -msgid "" -"Control the level of output ansible will produce for inventory source update" -" jobs." -msgstr "インベントリーソースの更新ジョブ用に Ansible が生成する出力のレベルを制御します。" - -#: client/lib/components/components.strings.js:52 -msgid "Copied to clipboard." -msgstr "クリップボードにコピーしました。" - -#: client/src/credentials/credentials.list.js:73 -#: client/src/inventories-hosts/inventories/inventory.list.js:105 -#: client/src/inventory-scripts/inventory-scripts.list.js:61 -#: client/src/notifications/notificationTemplates.list.js:82 -#: client/src/projects/projects.list.js:100 -#: client/src/templates/templates.list.js:93 -msgid "Copy" -msgstr "コピー" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:56 -msgid "Copy Inventory" -msgstr "インベントリーのコピー" - -#: client/src/credentials/credentials.list.js:76 -msgid "Copy credential" -msgstr "認証情報のコピー" - -#: client/lib/components/components.strings.js:51 -msgid "Copy full revision to clipboard." -msgstr "完全なリビジョンをクリップボードにコピーします。" - -#: client/src/inventory-scripts/inventory-scripts.list.js:64 -msgid "Copy inventory script" -msgstr "インベントリースクリプトのコピー" - -#: client/src/notifications/notificationTemplates.list.js:85 -msgid "Copy notification" -msgstr "通知のコピー" - -#: client/src/projects/projects.list.js:103 -msgid "Copy project" -msgstr "プロジェクトのコピー" - -#: client/src/templates/templates.list.js:96 -msgid "Copy template" -msgstr "テンプレートのコピー" - -#: client/lib/services/base-string.service.js:97 -msgid "Copy {{resourceType}}" -msgstr "{{resourceType}} のコピー" - -#: client/src/about/about.partial.html:27 -msgid "" -"Copyright © 2018 Red Hat, Inc.
\n" -" Visit Ansible.com for more information.
" -msgstr "" -"Copyright © 2018 Red Hat, Inc.
\n" -" 詳細は、 Ansible.com をご覧ください。
" - -#: client/lib/components/components.strings.js:88 -msgid "Copyright © 2018 Red Hat, Inc." -msgstr "Copyright © 2018 Red Hat, Inc." - -#: client/src/users/users.list.js:44 -msgid "Create New" -msgstr "新規作成" - -#: client/features/applications/applications.strings.js:20 -msgid "Create a new Application" -msgstr "新規アプリケーションの作成" - -#: client/src/instance-groups/instance-groups.strings.js:30 -msgid "Create a new Instance Group" -msgstr "新規インスタンスグループの作成" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:47 -msgid "" -"Create a new Smart Inventory from search results.

Note: changing " -"the organization of the Smart Inventory could change the hosts included in " -"the Smart Inventory." -msgstr "" -"検索結果から新規のスマートインベントリーを作成します。

注: " -"スマートインベントリーの組織を変更することで、スマートインベントリーに含まれるホストが変更される可能性があります。" - -#: client/src/credentials/credentials.list.js:52 -msgid "Create a new credential" -msgstr "新規認証情報の作成" - -#: client/src/credential-types/credential-types.list.js:42 -msgid "Create a new credential type" -msgstr "新規認証情報タイプの作成" - -#: client/src/inventory-scripts/inventory-scripts.list.js:40 -msgid "Create a new custom inventory" -msgstr "新規カスタムインベントリーの作成" - -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:69 -msgid "Create a new group" -msgstr "新規グループの作成" - -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:123 -msgid "Create a new host" -msgstr "新規ホストの作成" - -#: client/src/inventories-hosts/inventories/inventory.list.js:76 -msgid "Create a new inventory" -msgstr "新規インベントリーの作成" - -#: client/src/notifications/notificationTemplates.list.js:52 -msgid "Create a new notification template" -msgstr "新規通知テンプレートの作成" - -#: client/src/organizations/list/organizations-list.partial.html:21 -msgid "Create a new organization" -msgstr "新規組織の作成" - -#: client/src/projects/projects.list.js:75 -msgid "Create a new project" -msgstr "新規プロジェクトの作成" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:68 -msgid "Create a new source" -msgstr "新規ソースの作成" - -#: client/src/teams/teams.list.js:43 -msgid "Create a new team" -msgstr "新規チームの作成" - -#: client/src/templates/templates.list.js:56 -msgid "Create a new template" -msgstr "新規テンプレートの作成" - -#: client/src/users/users.list.js:48 -msgid "Create a new user" -msgstr "新規ユーザーの作成" - -#: client/features/output/output.strings.js:44 -#: client/features/templates/templates.strings.js:25 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:73 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:74 -#: client/src/job-submission/job-submission.partial.html:18 -#: client/src/templates/job_templates/job-template.form.js:121 -msgid "Credential" -msgstr "認証情報" - -#: client/features/templates/templates.strings.js:36 -msgid "Credential Type" -msgstr "認証情報タイプ" - -#: client/lib/components/components.strings.js:74 -#: client/lib/models/models.strings.js:12 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:34 -msgid "Credential Types" -msgstr "認証情報タイプ" - -#: client/features/jobs/jobs.strings.js:16 -#: client/features/templates/templates.strings.js:18 -#: client/lib/components/components.strings.js:73 -#: client/lib/models/models.strings.js:8 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:129 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:58 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:26 -#: client/src/templates/job_templates/job-template.form.js:133 -msgid "Credentials" -msgstr "認証情報" - -#: client/features/templates/templates.strings.js:37 -msgid "" -"Credentials that require passwords on launch are not permitted for template " -"schedules and workflow nodes. The following credentials must be removed or " -"replaced to proceed:" -msgstr "" -"起動時にパスワードを必要とする認証情報はテンプレートスケジュールおよびワークフローノードでは許可されません。以下の認証情報を削除するか、または置換して次に進んでください。" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:17 -msgid "Critical" -msgstr "重大" - -#: client/src/shared/directives.js:93 -msgid "Current Image:" -msgstr "現在のイメージ:" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:171 -msgid "Custom Inventory Script" -msgstr "カスタムインベントリースクリプト" - -#: client/src/inventory-scripts/inventory-scripts.form.js:50 -#: client/src/inventory-scripts/inventory-scripts.form.js:60 -msgid "Custom Script" -msgstr "カスタムスクリプト" - -#: client/src/home/home.route.js:21 -msgid "DASHBOARD" -msgstr "ダッシュボード" - -#: client/features/users/tokens/tokens.strings.js:28 -#: client/lib/services/base-string.service.js:71 -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:52 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:74 -#: client/src/notifications/notification-templates-list/list.controller.js:230 -#: client/src/organizations/list/organizations-list.controller.js:196 -#: client/src/partials/survey-maker-modal.html:18 -#: client/src/projects/edit/projects-edit.controller.js:255 -#: client/src/projects/list/projects-list.controller.js:254 -#: client/src/users/list/users-list.controller.js:95 -msgid "DELETE" -msgstr "削除" - -#: client/src/partials/survey-maker-modal.html:84 -msgid "DELETE SURVEY" -msgstr "Survey の削除" - -#: client/features/templates/templates.strings.js:116 -msgid "DELETED" -msgstr "削除済み" - -#: client/features/users/tokens/tokens.strings.js:36 -msgid "DESCRIPTION" -msgstr "説明" - -#: client/features/templates/templates.strings.js:118 -#: client/src/instance-groups/instance-groups.strings.js:24 -#: client/src/workflow-results/workflow-results.controller.js:55 -msgid "DETAILS" -msgstr "詳細" - -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.partial.html:29 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-disassociate.partial.html:30 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-disassociate.partial.html:30 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-disassociate.partial.html:31 -msgid "DISASSOCIATE" -msgstr "関連付けの解除" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal.partial.html:5 -msgid "DYNAMIC HOSTS" -msgstr "動的ホスト" - -#: client/lib/components/components.strings.js:68 -msgid "Dashboard" -msgstr "ダッシュボード" - -#: client/src/scheduler/scheduler.strings.js:52 -msgid "Date format" -msgstr "日付書式" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:164 -msgid "Default" -msgstr "デフォルト" - -#: client/features/output/output.strings.js:19 -#: client/lib/services/base-string.service.js:78 -#: client/src/credential-types/credential-types.list.js:73 -#: client/src/credential-types/list/list.controller.js:106 -#: client/src/credentials/credentials.list.js:92 -#: client/src/credentials/list/credentials-list.controller.js:176 -#: client/src/inventories-hosts/inventories/inventory.list.js:121 -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:157 -#: client/src/inventory-scripts/inventory-scripts.list.js:79 -#: client/src/inventory-scripts/list/list.controller.js:126 -#: client/src/notifications/notification-templates-list/list.controller.js:226 -#: client/src/notifications/notificationTemplates.list.js:100 -#: client/src/organizations/list/organizations-list.controller.js:192 -#: client/src/projects/edit/projects-edit.controller.js:252 -#: client/src/projects/list/projects-list.controller.js:250 -#: client/src/scheduler/schedules.list.js:100 -#: client/src/teams/teams.list.js:72 -#: client/src/templates/templates.list.js:109 -#: client/src/users/list/users-list.controller.js:91 -#: client/src/users/users.list.js:79 -#: client/src/workflow-results/workflow-results.controller.js:39 -msgid "Delete" -msgstr "削除" - -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:6 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:6 -msgid "Delete Group" -msgstr "グループの削除" - -#: client/src/templates/survey-maker/surveys/init.factory.js:23 -msgid "Delete Question" -msgstr "質問の削除" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:194 -msgid "Delete Source" -msgstr "ソースの削除" - -#: client/src/credentials/credentials.list.js:94 -msgid "Delete credential" -msgstr "認証情報の削除" - -#: client/src/credential-types/credential-types.list.js:75 -msgid "Delete credential type" -msgstr "認証情報タイプの削除" - -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:101 -#: client/src/inventories-hosts/inventory-hosts.strings.js:19 -msgid "Delete group" -msgid_plural "Delete groups" -msgstr[0] "グループの削除" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:48 -msgid "Delete groups" -msgstr "グループの削除" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:37 -msgid "Delete groups and hosts" -msgstr "グループおよびホストの削除" - -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:100 -#: client/src/inventories-hosts/inventory-hosts.strings.js:21 -msgid "Delete host" -msgid_plural "Delete hosts" -msgstr[0] "ホストの削除" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:59 -msgid "Delete hosts" -msgstr "ホストの削除" - -#: client/src/inventories-hosts/inventories/inventory.list.js:123 -msgid "Delete inventory" -msgstr "インベントリーの削除" - -#: client/src/inventory-scripts/inventory-scripts.list.js:81 -msgid "Delete inventory script" -msgstr "インベントリースクリプトの削除" - -#: client/src/notifications/notificationTemplates.list.js:102 -msgid "Delete notification" -msgstr "通知の削除" - -#: client/src/projects/projects.form.js:167 -msgid "Delete on Update" -msgstr "更新時の削除" - -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:27 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:27 -msgid "Delete or promote the group's children?" -msgstr "グループの子を削除またはプロモートしますか?" - -#: client/src/scheduler/schedules.list.js:103 -msgid "Delete schedule" -msgstr "スケジュールの削除" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:117 -msgid "Delete source" -msgstr "ソースの削除" - -#: client/src/teams/teams.list.js:76 -msgid "Delete team" -msgstr "チームの削除" - -#: client/src/templates/templates.list.js:112 -msgid "Delete template" -msgstr "テンプレートの削除" - -#: client/src/projects/projects.form.js:169 -msgid "" -"Delete the local repository in its entirety prior to performing an update." -msgstr "更新の実行前にローカルリポジトリーを完全に削除します。" - -#: client/src/projects/projects.list.js:116 -msgid "Delete the project" -msgstr "プロジェクトの削除" - -#: client/src/scheduler/scheduled-jobs.list.js:81 -msgid "Delete the schedule" -msgstr "スケジュールの削除" - -#: client/lib/services/base-string.service.js:98 -msgid "Delete the {{resourceType}}" -msgstr "{{resourceType}} の削除" - -#: client/src/users/users.list.js:83 -msgid "Delete user" -msgstr "ユーザーの削除" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:14 -msgid "Delete {{ group }} and {{ host }}" -msgstr "{{ group }} および {{ host }} の削除" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:23 -msgid "Deleting group" -msgstr "グループを削除しています" - -#: client/lib/services/base-string.service.js:80 -msgid "" -"Deleting this {{ resourceType }} will make the following resources " -"unavailable." -msgstr "この {{ resourceType }} を削除すると、以下のリソースが利用できなくなります。" - -#: client/src/projects/projects.form.js:169 -msgid "" -"Depending on the size of the repository this may significantly increase the " -"amount of time required to complete an update." -msgstr "リポジトリーのサイズにより、更新の完了までに必要な時間が大幅に長くなる可能性があります。" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:246 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:261 -msgid "Describe Instances documentation" -msgstr "インスタンスの概要ドキュメント" - -#: client/src/credential-types/credential-types.form.js:34 -#: client/src/credentials/credentials.form.js:39 -#: client/src/inventories-hosts/hosts/host.form.js:63 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:39 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js:40 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:62 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:62 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:58 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:28 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:36 -#: client/src/inventory-scripts/inventory-scripts.form.js:35 -#: client/src/notifications/notificationTemplates.form.js:39 -#: client/src/organizations/organizations.form.js:33 -#: client/src/projects/projects.form.js:37 client/src/teams/teams.form.js:35 -#: client/src/templates/job_templates/job-template.form.js:41 -#: client/src/templates/survey-maker/shared/question-definition.form.js:36 -#: client/src/templates/workflows.form.js:49 -#: client/src/users/users.form.js:147 client/src/users/users.form.js:173 -msgid "Description" -msgstr "説明" - -#: client/src/notifications/notificationTemplates.form.js:136 -#: client/src/notifications/notificationTemplates.form.js:140 -#: client/src/notifications/notificationTemplates.form.js:152 -#: client/src/notifications/notificationTemplates.form.js:156 -msgid "Destination Channels" -msgstr "送信先チャネル" - -#: client/src/notifications/notificationTemplates.form.js:430 -#: client/src/notifications/notificationTemplates.form.js:434 -msgid "Destination Channels or Users" -msgstr "送信先チャネルまたはユーザー" - -#: client/src/notifications/notificationTemplates.form.js:205 -#: client/src/notifications/notificationTemplates.form.js:206 -msgid "Destination SMS Number" -msgstr "送信先 SMS 番号" - -#: client/features/applications/applications.strings.js:15 -#: client/features/credentials/credentials.strings.js:13 -#: client/features/output/output.strings.js:34 -#: client/features/users/tokens/tokens.strings.js:14 -#: client/src/license/license.partial.html:5 -#: client/src/shared/form-generator.js:1501 -msgid "Details" -msgstr "詳細" - -#: client/src/job-submission/job-submission.partial.html:263 -msgid "Diff Mode" -msgstr "差分モード" - -#: client/src/notifications/notificationTemplates.form.js:369 -#: client/src/notifications/notificationTemplates.form.js:401 -msgid "Disable SSL Verification" -msgstr "SSL 検証の無効化" - -#: client/src/templates/survey-maker/surveys/init.factory.js:518 -msgid "Disable survey" -msgstr "Survey の無効化" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-disassociate.partial.html:6 -msgid "Disassociate Group From Group" -msgstr "グループの他のグループとの関連付けを解除" - -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.partial.html:6 -msgid "Disassociate Host" -msgstr "ホストの関連付けの解除" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-disassociate.partial.html:6 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-disassociate.partial.html:6 -msgid "Disassociate Host From Group" -msgstr "ホストのグループとの関連付けを解除" - -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.list.js:65 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:110 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:98 -msgid "Disassociate group" -msgstr "グループの関連付けの解除" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:87 -msgid "Disassociate host" -msgstr "ホストの関連付けの解除" - -#: client/src/templates/survey-maker/surveys/init.factory.js:21 -msgid "Disable Survey" -msgstr "Survey の無効化" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:94 -#: client/src/configuration/configuration.controller.js:231 -#: client/src/configuration/configuration.controller.js:316 -#: client/src/configuration/system-form/configuration-system.controller.js:57 -msgid "Discard changes" -msgstr "変更の破棄" - -#: client/src/teams/teams.form.js:149 -msgid "Dissassociate permission from team" -msgstr "チームからパーミッションの関連付けを解除" - -#: client/src/users/users.form.js:227 -msgid "Dissassociate permission from user" -msgstr "ユーザーからパーミッションの関連付けを解除" - -#: client/src/credentials/credentials.form.js:384 -#: client/src/credentials/factories/become-method-change.factory.js:54 -#: client/src/credentials/factories/kind-change.factory.js:111 -msgid "Domain Name" -msgstr "ドメイン名" - -#: client/features/output/output.strings.js:20 -msgid "Download Output" -msgstr "出力のダウンロード" - -#: client/src/inventory-scripts/inventory-scripts.form.js:59 -msgid "" -"Drag and drop your custom inventory script file here or create one in the " -"field to import your custom inventory. Refer to the Ansible Tower " -"documentation for example syntax." -msgstr "" -"カスタムインベントリーのスクリプトファイルをここにドラッグアンドドロップするか、またはこのフィールドにカスタムインベントリーをインポートするためのファイルを作成します。構文のサンプルについては、Ansible" -" Tower ドキュメントを参照してください。" - -#: client/src/templates/survey-maker/surveys/init.factory.js:24 -msgid "Drag to reorder question" -msgstr "ドラッグして質問を並び替える" - -#: client/src/partials/survey-maker-modal.html:77 -msgid "Drop question here to reorder" -msgstr "並び替える質問をドロップ" - -#: client/features/templates/templates.strings.js:115 -msgid "EDGE CONFLICT" -msgstr "エッジの競合" - -#: client/features/applications/applications.strings.js:10 -msgid "EDIT APPLICATION" -msgstr "アプリケーションの編集" - -#: client/src/configuration/configuration.route.js:28 -msgid "EDIT CONFIGURATION" -msgstr "設定の編集" - -#: client/features/credentials/credentials.strings.js:9 -msgid "EDIT CREDENTIAL" -msgstr "認証情報の編集" - -#: client/src/instance-groups/instance-groups.strings.js:11 -msgid "EDIT INSTANCE GROUP" -msgstr "インスタンスグループの編集" - -#: client/src/scheduler/scheduler.strings.js:9 -msgid "EDIT SCHEDULE" -msgstr "スケジュールの編集" - -#: client/src/management-jobs/scheduler/main.js:97 -msgid "EDIT SCHEDULED JOB" -msgstr "スケジュール済みジョブの編集" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:17 -msgid "EDIT SURVEY PROMPT" -msgstr "Survey プロンプトの編集" - -#: client/features/templates/templates.strings.js:108 -msgid "EDIT TEMPLATE" -msgstr "テンプレートの編集" - -#: client/lib/components/components.strings.js:9 -msgid "ENCRYPTED" -msgstr "暗号化" - -#: client/features/output/output.strings.js:80 -msgid "EXAMPLES" -msgstr "例" - -#: client/src/shared/smart-search/smart-search.partial.html:36 -msgid "EXAMPLES:" -msgstr "例:" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:15 -msgid "EXECUTE COMMAND" -msgstr "コマンドの実行" - -#: client/lib/components/code-mirror/code-mirror.strings.js:10 -msgid "EXPAND" -msgstr "展開" - -#: client/features/applications/applications.strings.js:29 -#: client/features/users/tokens/tokens.strings.js:37 -msgid "EXPIRATION" -msgstr "有効期限" - -#: client/features/users/tokens/tokens.strings.js:24 -msgid "EXPIRES" -msgstr "期限切れ" - -#: client/lib/components/code-mirror/code-mirror.strings.js:48 -#: client/lib/components/code-mirror/code-mirror.strings.js:8 -msgid "EXTRA VARIABLES" -msgstr "追加の変数" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:379 -msgid "" -"Each time a job runs using this inventory, refresh the inventory from the " -"selected source before executing job tasks." -msgstr "このインベントリーでジョブを実行する際は常に、選択されたソースのインベントリーを更新してからジョブのタスクを実行します。" - -#: client/src/projects/projects.form.js:180 -msgid "" -"Each time a job runs using this project, update the revision of the project " -"prior to starting the job." -msgstr "このプロジェクトでジョブを実行する際は常に、ジョブの開始前にプロジェクトのリビジョンを更新します。" - -#: client/src/credential-types/credential-types.list.js:56 -#: client/src/credentials/credentials.list.js:66 -#: client/src/inventories-hosts/inventories/inventory.list.js:98 -#: client/src/inventory-scripts/inventory-scripts.list.js:54 -#: client/src/notifications/notificationTemplates.list.js:66 -#: client/src/notifications/notificationTemplates.list.js:75 -#: client/src/scheduler/schedules.list.js:85 client/src/teams/teams.list.js:55 -#: client/src/templates/templates.list.js:80 client/src/users/users.list.js:60 -msgid "Edit" -msgstr "編集" - -#: client/src/templates/survey-maker/surveys/init.factory.js:22 -msgid "Edit Question" -msgstr "質問の編集" - -#: client/src/shared/form-generator.js:1735 -#: client/src/templates/job_templates/job-template.form.js:475 -#: client/src/templates/workflows.form.js:212 -msgid "Edit Survey" -msgstr "Survey の編集" - -#: client/src/credential-types/credential-types.list.js:58 -msgid "Edit credential type" -msgstr "認証情報タイプの編集" - -#: client/src/credentials/credentials.list.js:68 -msgid "Edit credential" -msgstr "認証情報の編集" - -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:85 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:96 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:84 -msgid "Edit group" -msgstr "グループの編集" - -#: client/src/inventories-hosts/hosts/host.list.js:83 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:73 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:79 -#: client/src/inventories-hosts/inventory-hosts.strings.js:25 -msgid "Edit host" -msgstr "ホストの編集" - -#: client/src/inventories-hosts/inventories/inventory.list.js:100 -msgid "Edit inventory" -msgstr "インベントリーの編集" - -#: client/src/inventory-scripts/inventory-scripts.list.js:56 -msgid "Edit inventory script" -msgstr "インベントリースクリプトの編集" - -#: client/src/notifications/notificationTemplates.list.js:68 -msgid "Edit notification" -msgstr "通知の編集" - -#: client/src/scheduler/schedules.list.js:88 -msgid "Edit schedule" -msgstr "スケジュールの編集" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:83 -msgid "Edit source" -msgstr "ソースの編集" - -#: client/src/teams/teams.list.js:59 -msgid "Edit team" -msgstr "チームの編集" - -#: client/src/templates/templates.list.js:82 -msgid "Edit template" -msgstr "テンプレートの編集" - -#: client/src/projects/projects.list.js:87 -msgid "Edit the project" -msgstr "プロジェクトの編集" - -#: client/src/scheduler/scheduled-jobs.list.js:67 -#: client/src/workflow-results/workflow-results.controller.js:42 -msgid "Edit the schedule" -msgstr "スケジュールの編集" - -#: client/src/workflow-results/workflow-results.controller.js:40 -msgid "Edit the user" -msgstr "ユーザーの編集" - -#: client/src/workflow-results/workflow-results.controller.js:41 -msgid "Edit the workflow job template" -msgstr "ワークフロージョブテンプレートの編集" - -#: client/src/users/users.list.js:64 -msgid "Edit user" -msgstr "ユーザーの編集" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:244 -msgid "" -"Either you do not have access or the SCM update process completed. Click the" -msgstr "アクセスがないか、または SCM 更新プロセスが完了しました。次をクリックしてください: " - -#: client/src/projects/list/projects-list.controller.js:286 -msgid "" -"Either you do not have access or the SCM update process completed. Click the" -" %sRefresh%s button to view the latest status." -msgstr "アクセスがないか、または SCM 更新プロセスが完了しました。%s更新%s ボタンをクリックして最新のステータスを表示します。" - -#: client/features/output/output.strings.js:90 -#: client/src/workflow-results/workflow-results.controller.js:61 -msgid "Elapsed" -msgstr "経過時間" - -#: client/src/credentials/credentials.form.js:191 -#: client/src/users/users.form.js:53 -msgid "Email" -msgstr "メール" - -#: client/src/templates/job_templates/job-template.form.js:303 -#: client/src/templates/job_templates/job-template.form.js:308 -#: client/src/templates/workflows.form.js:100 -#: client/src/templates/workflows.form.js:105 -msgid "Enable Concurrent Jobs" -msgstr "同時実行ジョブの有効化" - -#: client/src/configuration/system-form/configuration-system.partial.html:30 -msgid "Enable External Logging" -msgstr "外部ログの有効化" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:124 -#: client/src/templates/job_templates/job-template.form.js:279 -#: client/src/templates/job_templates/job-template.form.js:284 -msgid "Enable Privilege Escalation" -msgstr "権限昇格の有効化" - -#: client/src/templates/survey-maker/surveys/init.factory.js:518 -msgid "Enable survey" -msgstr "Survey の有効化" - -#: client/src/templates/job_templates/job-template.form.js:294 -msgid "" -"Enables creation of a provisioning callback URL. Using the URL a host can " -"contact {{BRAND_NAME}} and request a configuration update using this job " -"template." -msgstr "" -"プロビジョニングコールバック URL の作成を有効にします。この URL を使用してホストは {{BRAND_NAME}} " -"に接続でき、このジョブテンプレートを使用して設定の更新を要求できます。" - -#: client/src/credentials/factories/credential-form-save.factory.js:73 -msgid "Encrypted credentials are not supported." -msgstr "暗号化された認証情報はサポートされていません。" - -#: client/src/scheduler/scheduler.strings.js:44 -msgid "End" -msgstr "終了" - -#: client/src/scheduler/scheduler.strings.js:46 -msgid "End Date" -msgstr "終了日" - -#: client/src/scheduler/scheduler.strings.js:48 -msgid "End Time" -msgstr "終了時間" - -#: client/src/license/license.partial.html:113 -msgid "End User License Agreement" -msgstr "使用許諾契約書" - -#: client/src/inventories-hosts/hosts/host.form.js:73 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:72 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:72 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:68 -msgid "" -"Enter inventory variables using either JSON or YAML syntax. Use the radio " -"button to toggle between the two." -msgstr "" -"JSON または YAML 構文のいずれかを使用してインベントリー変数を入力します。ラジオボタンを使用して 2 つの間の切り替えを行います。" - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:76 -msgid "" -"Enter inventory variables using either JSON or YAML syntax. Use the radio " -"button to toggle between the two. Refer to the Ansible Tower documentation " -"for example syntax." -msgstr "" -"JSON または YAML " -"構文のいずれかを使用してインベントリー変数を入力します。ラジオボタンを使用してこれらの間で切り替えを行います。構文のサンプルについては Ansible " -"Tower ドキュメントを参照してください。" - -#: client/src/notifications/notificationTemplates.form.js:155 -msgid "" -"Enter one HipChat channel per line. The pound symbol (#) is not required." -msgstr "各行に 1 つの HipChat チャンネルを入力します。シャープ記号 (#) は不要です。" - -#: client/src/notifications/notificationTemplates.form.js:433 -msgid "" -"Enter one IRC channel or username per line. The pound symbol (#) for " -"channels, and the at (@) symbol for users, are not required." -msgstr "" -"各行に 1 つの IRC チャンネルまたはユーザー名を入力します。チャンネルのシャープ記号 (#) およびユーザーのアットマーク (@) " -"記号は不要です。" - -#: client/src/notifications/notificationTemplates.form.js:139 -msgid "" -"Enter one Slack channel per line. The pound symbol (#) is not required." -msgstr "各行に 1 つの Slack チャンネルを入力します。シャープ記号 (#) は不要です。" - -#: client/src/notifications/notificationTemplates.form.js:97 -msgid "" -"Enter one email address per line to create a recipient list for this type of" -" notification." -msgstr "各行に 1 つのメールアドレスを入力し、この通知タイプの受信者リストを作成します。" - -#: client/src/notifications/notificationTemplates.form.js:209 -msgid "" -"Enter one phone number per line to specify where to route SMS messages." -msgstr "各行に 1 つの電話番号を入力し、SMS メッセージのルート先を指定します。" - -#: client/src/credentials/factories/become-method-change.factory.js:81 -#: client/src/credentials/factories/kind-change.factory.js:138 -msgid "" -"Enter the URL for the virtual machine which %scorresponds to your CloudForms " -"instance. %sFor example, %s" -msgstr "CloudForms インスタンスに対応する %s仮想マシンの URL を入力します (%s例: %s)。" - -#: client/src/credentials/factories/become-method-change.factory.js:71 -#: client/src/credentials/factories/kind-change.factory.js:128 -msgid "" -"Enter the URL which corresponds to your %sRed Hat Satellite 6 server. %sFor " -"example, %s" -msgstr "Red Hat Satellite 6 Server に対応する %sURL を入力します (%s例: %s)。" - -#: client/src/credentials/factories/become-method-change.factory.js:49 -#: client/src/credentials/factories/kind-change.factory.js:106 -msgid "" -"Enter the hostname or IP address which corresponds to your VMware vCenter." -msgstr "VMware vCenter に対応するホスト名または IP アドレスを入力します。" - -#: client/src/notifications/notificationTemplates.form.js:195 -msgid "" -"Enter the number associated with the \"Messaging Service\" in Twilio in the " -"format +18005550199." -msgstr "Twilio の \"メッセージングサービス\" に関連付けられた番号を入力します (形式: +18005550199)。 " - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:197 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:221 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:245 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:320 -msgid "" -"Enter variables using either JSON or YAML syntax. Use the radio button to " -"toggle between the two." -msgstr "JSON または YAML 構文のいずれかを使用して変数を入力します。ラジオボタンを使用して 2 つの間の切り替えを行います。" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:187 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:194 -msgid "Environment Variables" -msgstr "環境変数" - -#: client/src/configuration/configuration.controller.js:141 -msgid "Error" -msgstr "エラー" - -#: client/features/output/output.strings.js:65 -msgid "Error Details" -msgstr "エラーの詳細" - -#: client/lib/services/base-string.service.js:92 -#: client/src/configuration/configuration.controller.js:414 -#: client/src/configuration/configuration.controller.js:523 -#: client/src/configuration/configuration.controller.js:558 -#: client/src/configuration/configuration.controller.js:606 -#: client/src/configuration/system-form/configuration-system.controller.js:231 -#: client/src/credentials/factories/credential-form-save.factory.js:77 -#: client/src/credentials/factories/credential-form-save.factory.js:93 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:130 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:140 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:167 -#: client/src/job-submission/job-submission-factories/launchjob.factory.js:198 -#: client/src/job-submission/job-submission-factories/launchjob.factory.js:217 -#: client/src/management-jobs/card/card.controller.js:102 -#: client/src/management-jobs/card/card.controller.js:28 -#: client/src/projects/add/projects-add.controller.js:117 -#: client/src/projects/edit/projects-edit.controller.js:165 -#: client/src/projects/edit/projects-edit.controller.js:231 -#: client/src/projects/edit/projects-edit.controller.js:247 -#: client/src/projects/list/projects-list.controller.js:196 -#: client/src/projects/list/projects-list.controller.js:223 -#: client/src/projects/list/projects-list.controller.js:270 -#: client/src/projects/list/projects-list.controller.js:291 -#: client/src/projects/list/projects-list.controller.js:307 -#: client/src/projects/list/projects-list.controller.js:316 -#: client/src/shared/stateDefinitions.factory.js:230 -#: client/src/users/add/users-add.controller.js:100 -#: client/src/users/edit/users-edit.controller.js:178 -#: client/src/users/list/users-list.controller.js:84 -msgid "Error!" -msgstr "エラー!" - -#: client/src/activity-stream/streams.list.js:40 -msgid "Event" -msgstr "イベント" - -#: client/src/activity-stream/factories/build-description.factory.js:120 -msgid "Event summary not available" -msgstr "イベントの概要はありません" - -#: client/src/scheduler/scheduler.strings.js:29 -msgid "Every" -msgstr "実行頻度" - -#: client/src/projects/add/projects-add.controller.js:138 -#: client/src/projects/edit/projects-edit.controller.js:274 -msgid "Example URLs for GIT SCM include:" -msgstr "GIT SCM のサンプル URL には以下が含まれます:" - -#: client/src/projects/add/projects-add.controller.js:159 -#: client/src/projects/edit/projects-edit.controller.js:294 -msgid "Example URLs for Mercurial SCM include:" -msgstr "Mercurial SCM のサンプル URL には以下が含まれます:" - -#: client/src/projects/add/projects-add.controller.js:150 -#: client/src/projects/edit/projects-edit.controller.js:285 -msgid "Example URLs for Subversion SCM include:" -msgstr "Subversion SCM のサンプル URL には以下が含まれます:" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:47 -msgid "Example: ansible_facts.ansible_distribution:\"RedHat\"" -msgstr "例: ansible_facts.ansible_distribution:\"RedHat\"" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:76 -msgid "Existing Group" -msgstr "既存グループ" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:125 -msgid "Existing Host" -msgstr "既存ホスト" - -#: client/features/output/output.strings.js:22 -#: client/src/workflow-results/workflow-results.controller.js:154 -#: client/src/workflow-results/workflow-results.controller.js:43 -msgid "Expand Output" -msgstr "出力の展開" - -#: client/src/license/license.partial.html:39 -msgid "Expires On" -msgstr "有効期限" - -#: client/features/output/output.strings.js:50 -msgid "Explanation" -msgstr "説明" - -#: client/features/output/output.strings.js:45 -#: client/features/templates/templates.strings.js:54 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:133 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:145 -#: client/src/job-submission/job-submission.partial.html:165 -#: client/src/partials/logviewer.html:8 -#: client/src/scheduler/scheduler.strings.js:53 -#: client/src/templates/job_templates/job-template.form.js:357 -#: client/src/templates/job_templates/job-template.form.js:364 -#: client/src/templates/workflows.form.js:83 -#: client/src/templates/workflows.form.js:90 -#: client/src/workflow-results/workflow-results.controller.js:122 -msgid "Extra Variables" -msgstr "追加変数" - -#: client/src/inventories-hosts/shared/ansible-facts/ansible-facts.partial.html:4 -#: client/src/inventories-hosts/shared/ansible-facts/ansible-facts.route.js:7 -msgid "FACTS" -msgstr "ファクト" - -#: client/src/home/dashboard/graphs/job-status/job-status-graph.directive.js:65 -msgid "FAILED" -msgstr "失敗" - -#: client/features/output/output.strings.js:81 -msgid "FIELDS" -msgstr "フィールド" - -#: client/src/shared/smart-search/smart-search.partial.html:42 -msgid "FIELDS:" -msgstr "フィールド:" - -#: client/src/smart-status/smart-status.controller.js:43 -msgid "FINISHED" -msgstr "完了" - -#: client/src/inventories-hosts/hosts/host.form.js:107 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:106 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:107 -msgid "Facts" -msgstr "ファクト" - -#: client/lib/components/components.strings.js:100 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:80 -msgid "Failed" -msgstr "失敗" - -#: client/src/home/dashboard/counts/dashboard-counts.directive.js:44 -msgid "Failed Hosts" -msgstr "失敗したホスト" - -#: client/src/users/add/users-add.controller.js:100 -msgid "Failed to add new user. POST returned status:" -msgstr "新規ユーザーを追加できませんでした。POST で返されたステータス:" - -#: client/src/credentials/factories/credential-form-save.factory.js:78 -msgid "Failed to create new Credential. POST status:" -msgstr "新規の認証情報を作成できませんでした。POST ステータス:" - -#: client/src/projects/add/projects-add.controller.js:118 -msgid "Failed to create new project. POST returned status:" -msgstr "新規プロジェクトを作成できませんでした。POST で返されたステータス:" - -#: client/src/job-submission/job-submission-factories/launchjob.factory.js:218 -msgid "Failed to retrieve job template extra variables." -msgstr "ジョブテンプレートの追加変数を取得できませんでした。" - -#: client/src/projects/edit/projects-edit.controller.js:166 -msgid "Failed to retrieve project: %s. GET status:" -msgstr "プロジェクトを取得できませんでした: %s. GET ステータス:" - -#: client/src/users/edit/users-edit.controller.js:179 -msgid "Failed to retrieve user: %s. GET status:" -msgstr "ユーザーを取得できませんでした: %s. GET ステータス:" - -#: client/src/configuration/configuration.controller.js:524 -msgid "Failed to save settings. Returned status:" -msgstr "設定を保存できませんでした。返されたステータス:" - -#: client/src/configuration/configuration.controller.js:559 -msgid "Failed to save toggle settings. Returned status:" -msgstr "トグルの設定を保存できませんでした。返されたステータス:" - -#: client/src/credentials/factories/credential-form-save.factory.js:94 -msgid "Failed to update Credential. PUT status:" -msgstr "認証情報を更新できませんでした。PUT ステータス:" - -#: client/src/projects/edit/projects-edit.controller.js:231 -msgid "Failed to update project: %s. PUT status:" -msgstr "プロジェクトを更新できませんでした: %s. PUT ステータス:" - -#: client/features/output/output.strings.js:85 -msgid "Failed to update search results." -msgstr "検索結果の更新に失敗しました。" - -#: client/src/job-submission/job-submission-factories/launchjob.factory.js:199 -#: client/src/management-jobs/card/card.controller.js:103 -msgid "Failed updating job %s with variables. POST returned: %d" -msgstr "変数でジョブ %s を更新できませんでした。POST で返されたステータス: %d" - -#: client/src/notifications/notifications.list.js:49 -msgid "Failure" -msgstr "失敗" - -#: client/src/scheduler/schedules.list.js:56 -msgid "Final Run" -msgstr "最終実行日時" - -#: client/features/jobs/jobs.strings.js:10 -#: client/features/output/output.strings.js:40 -#: client/features/output/output.strings.js:46 -#: client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.directive.js:54 -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:44 -#: client/src/workflow-results/workflow-results.controller.js:50 -msgid "Finished" -msgstr "終了日時" - -#: client/src/access/rbac-multiselect/permissionsUsers.list.js:27 -#: client/src/users/users.form.js:29 client/src/users/users.list.js:33 -msgid "First Name" -msgstr "名" - -#: client/src/scheduler/schedules.list.js:46 -msgid "First Run" -msgstr "初回実行日時" - -#: client/src/templates/survey-maker/surveys/init.factory.js:19 -msgid "Float" -msgstr "浮動" - -#: client/features/output/output.strings.js:77 -#: client/src/shared/smart-search/smart-search.partial.html:49 -msgid "" -"For additional information on advanced search syntax please see the Ansible " -"Tower" -msgstr "検索構文についての詳細は、Ansible Tower ドキュメントを参照してください。" - -#: client/src/credentials/factories/become-method-change.factory.js:63 -#: client/src/credentials/factories/kind-change.factory.js:120 -msgid "For example, %s" -msgstr "例: %s" - -#: client/src/inventories-hosts/hosts/host.form.js:36 -#: client/src/inventories-hosts/hosts/host.list.js:36 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:35 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:32 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:35 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:31 -#: client/src/inventories-hosts/inventory-hosts.strings.js:33 -msgid "" -"For hosts that are part of an external inventory, this flag cannot be " -"changed. It will be set by the inventory sync process." -msgstr "外部インベントリーの一部であるホストの場合、このフラグを変更できません。これはインベントリー同期プロセスで設定されます。" - -#: client/src/templates/job_templates/job-template.form.js:54 -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 " -"構文、テスト環境セットアップおよびレポートの問題のみを検査するチェックを選択します。" - -#: client/features/output/output.strings.js:47 -#: client/src/instance-groups/instance-groups.strings.js:48 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:110 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:97 -#: client/src/templates/job_templates/job-template.form.js:143 -#: client/src/templates/job_templates/job-template.form.js:153 -msgid "Forks" -msgstr "フォーク" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:173 -#: client/src/scheduler/scheduler.strings.js:28 -msgid "Frequency Details" -msgstr "頻度の詳細" - -#: client/src/scheduler/scheduler.strings.js:41 -msgid "Fri" -msgstr "金" - -#: client/src/notifications/notification-templates-list/add-notifications-action.partial.html:2 -msgid "GO TO NOTIFICATIONS TO" -msgstr "通知へ移動:" - -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.route.js:45 -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.route.js:46 -msgid "GROUPS" -msgstr "グループ" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:38 -#: client/src/projects/edit/projects-edit.controller.js:136 -#: client/src/projects/list/projects-list.controller.js:76 -msgid "Get latest SCM revision" -msgstr "最新 SCM リビジョンの取得" - -#: client/src/credential-types/add/add.controller.js:41 -msgid "Getting Started with Credential Types" -msgstr "認証情報タイプの使用を開始する" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:153 -msgid "GitHub" -msgstr "GitHub" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:154 -msgid "GitHub Org" -msgstr "GitHub 組織" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:155 -msgid "GitHub Team" -msgstr "GitHub チーム" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:156 -msgid "Google OAuth2" -msgstr "Google OAuth2" - -#: client/src/teams/teams.form.js:158 client/src/users/users.form.js:216 -msgid "Grant Permission" -msgstr "パーミッションの付与" - -#: client/src/notifications/add/add.controller.js:79 -#: client/src/notifications/edit/edit.controller.js:126 -msgid "Gray" -msgstr "灰色" - -#: client/src/notifications/add/add.controller.js:80 -#: client/src/notifications/edit/edit.controller.js:127 -msgid "Green" -msgstr "緑" - -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:51 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js:52 -msgid "Group Variables" -msgstr "グループ変数" - -#: client/src/inventories-hosts/hosts/host.form.js:115 -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.list.js:31 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:89 -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:32 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js:88 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:32 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:114 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:115 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:32 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:150 -msgid "Groups" -msgstr "グループ" - -#: client/lib/components/components.strings.js:12 -#: client/lib/services/base-string.service.js:66 -#: client/src/templates/survey-maker/surveys/init.factory.js:483 -msgid "HIDE" -msgstr "非表示" - -#: client/lib/components/components.strings.js:43 -msgid "HINT: Drag and drop an SSH private key file on the field below." -msgstr "ヒント: 以下のフィールドに SSH 秘密鍵ファイルをドラッグアンドドロップします。" - -#: client/src/activity-stream/get-target-title.factory.js:41 -#: client/src/inventories-hosts/hosts/hosts.partial.html:9 -#: client/src/inventories-hosts/hosts/main.js:81 -#: client/src/inventories-hosts/inventories/inventories.partial.html:15 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.route.js:18 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-hosts.route.js:17 -msgid "HOSTS" -msgstr "ホスト" - -#: client/src/notifications/notificationTemplates.form.js:320 -#: client/src/notifications/notificationTemplates.form.js:321 -msgid "HTTP Headers" -msgstr "HTTP ヘッダー" - -#: client/src/bread-crumb/bread-crumb.directive.js:41 -msgid "Hide Activity Stream" -msgstr "アクティビティーストリームの非表示" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:23 -msgid "High" -msgstr "高" - -#: client/src/credentials/credentials.form.js:139 -#: client/src/notifications/notificationTemplates.form.js:83 -msgid "Host" -msgstr "ホスト" - -#: client/src/credentials/factories/become-method-change.factory.js:52 -#: client/src/credentials/factories/kind-change.factory.js:109 -msgid "Host (Authentication URL)" -msgstr "ホスト (認証 URL)" - -#: client/src/templates/job_templates/job-template.form.js:339 -#: client/src/templates/job_templates/job-template.form.js:348 -msgid "Host Config Key" -msgstr "ホスト設定キー" - -#: client/src/inventories-hosts/hosts/host.form.js:40 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:39 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:39 -msgid "Host Enabled" -msgstr "有効なホスト" - -#: client/src/inventories-hosts/hosts/host.form.js:46 -#: client/src/inventories-hosts/hosts/host.form.js:57 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:45 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:56 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:45 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:56 -msgid "Host Name" -msgstr "ホスト名" - -#: client/src/inventories-hosts/hosts/host.form.js:80 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:79 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:79 -msgid "Host Variables" -msgstr "ホスト変数" - -#: client/src/inventories-hosts/shared/factories/set-enabled-msg.factory.js:6 -msgid "Host is available" -msgstr "ホストが利用可能です。" - -#: client/src/inventories-hosts/shared/factories/set-enabled-msg.factory.js:10 -msgid "Host is available. Click to toggle." -msgstr "ホストが利用可能です。クリックして切り替えます。" - -#: client/src/inventories-hosts/shared/factories/set-enabled-msg.factory.js:6 -msgid "Host is not available" -msgstr "ホストを利用できません。" - -#: client/src/inventories-hosts/shared/factories/set-enabled-msg.factory.js:10 -msgid "Host is not available. Click to toggle." -msgstr "ホストを利用できません。クリックして切り替えます。" - -#: client/features/output/output.strings.js:13 -msgid "Host status information for this job is unavailable." -msgstr "このジョブのホストのステータス情報は利用できません。" - -#: client/features/output/output.strings.js:93 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:27 -#: client/src/home/dashboard/counts/dashboard-counts.directive.js:39 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:98 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:57 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:56 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:149 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:159 -msgid "Hosts" -msgstr "ホスト" - -#: client/src/license/license.partial.html:52 -msgid "Hosts Available" -msgstr "利用可能なホスト" - -#: client/src/license/license.partial.html:64 -msgid "Hosts Remaining" -msgstr "残りのホスト" - -#: client/src/license/license.partial.html:58 -msgid "Hosts Used" -msgstr "使用されたホスト" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:239 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:254 -msgid "Hosts are imported to" -msgstr "ホストのインポート先" - -#: client/src/license/license.partial.html:121 -msgid "I agree to the End User License Agreement" -msgstr "使用許諾契約書に同意します。" - -#: client/features/output/output.strings.js:102 -msgid "ID" -msgstr "ID" - -#: client/src/partials/job-template-details.html:2 -msgid "INFO" -msgstr "情報" - -#: client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html:12 -msgid "INITIATED BY" -msgstr "開始:" - -#: client/src/inventories-hosts/inventories/insights/insights.route.js:7 -msgid "INSIGHTS" -msgstr "INSIGHTS" - -#: client/src/instance-groups/instance-groups.list.js:6 -#: client/src/instance-groups/instance-groups.list.js:7 -#: client/src/instance-groups/instance-groups.strings.js:16 -#: client/src/instance-groups/instance-groups.strings.js:8 -msgid "INSTANCE GROUPS" -msgstr "インスタンスグループ" - -#: client/src/instance-groups/instance-groups.strings.js:25 -#: client/src/instance-groups/instance-groups.strings.js:9 -msgid "INSTANCES" -msgstr "インスタンス" - -#: client/src/activity-stream/get-target-title.factory.js:14 -#: client/src/inventories-hosts/hosts/hosts.partial.html:8 -#: client/src/inventories-hosts/inventories/inventories.partial.html:14 -#: client/src/inventories-hosts/inventories/inventories.route.js:8 -#: client/src/inventories-hosts/inventories/inventory.list.js:14 -#: client/src/inventories-hosts/inventories/inventory.list.js:15 -#: client/src/organizations/linkout/organizations-linkout.route.js:144 -#: client/src/organizations/list/organizations-list.controller.js:67 -msgid "INVENTORIES" -msgstr "インベントリー" - -#: client/src/job-submission/job-submission.partial.html:346 -#: client/src/partials/job-template-details.html:2 -msgid "INVENTORY" -msgstr "インベントリー" - -#: client/src/inventory-scripts/inventory-scripts.form.js:23 -msgid "INVENTORY SCRIPT" -msgstr "インベントリースクリプト" - -#: client/src/activity-stream/get-target-title.factory.js:35 -#: client/src/inventory-scripts/inventory-scripts.list.js:12 -#: client/src/inventory-scripts/main.js:65 -msgid "INVENTORY SCRIPTS" -msgstr "インベントリースクリプト" - -#: client/src/notifications/notificationTemplates.form.js:419 -msgid "IRC Nick" -msgstr "IRC ニック" - -#: client/src/notifications/notificationTemplates.form.js:408 -msgid "IRC Server Address" -msgstr "IRC サーバーアドレス" - -#: client/src/notifications/shared/type-change.service.js:66 -msgid "IRC Server Password" -msgstr "IRC サーバーパスワード" - -#: client/src/notifications/shared/type-change.service.js:65 -msgid "IRC Server Port" -msgstr "IRC サーバーポート" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:79 -msgid "ISSUE: {{report.rule.description}}" -msgstr "問題: {{report.rule.description}}" - -#: client/src/shared/paginate/paginate.partial.html:43 -msgid "ITEMS" -msgstr "項目" - -#: client/src/notifications/notificationTemplates.form.js:362 -#: client/src/notifications/notificationTemplates.form.js:394 -msgid "Icon URL" -msgstr "アイコン URL" - -#: client/src/login/authenticationServices/timer.factory.js:157 -msgid "Idle Session" -msgstr "アイドル状態のセッション" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:236 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:250 -msgid "If blank, all groups above are created except" -msgstr "空白の場合には、以下を除く上のすべてのグループが作成されます。" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:367 -msgid "" -"If checked, all variables for child groups and hosts will be removed and " -"replaced by those found on the external source." -msgstr "チェックが付けられている場合、子グループおよびホストのすべての変数が削除され、外部ソースにあるものによって置き換えられます。" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:355 -msgid "" -"If checked, any hosts and groups that were previously present on the " -"external source but are now removed will be removed from the Tower " -"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." -msgstr "" -"チェックが付けられている場合、以前は外部ソースにあり、現在は削除されているホストおよびグループが Tower " -"インベントリーから削除されます。インベントリーソースで管理されていなかったホストおよびグループは次に手動で作成されるグループにプロモートされるか、またはプロモート先となる手動で作成されたグループがない場合は、それらはインベントリーの「すべて」のデフォルトグループに残ります。" - -#: client/src/templates/job_templates/job-template.form.js:282 -msgid "If enabled, run this playbook as an administrator." -msgstr "有効にされている場合、この Playbook を管理者として実行します。" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:121 -msgid "" -"If enabled, show the changes made by Ansible tasks, where supported. This is" -" equivalent to Ansible’s --diff mode." -msgstr "" -"Ansible タスクで加えられた変更を表示します (有効にされ、サポートされている場合)。これは Ansible の --diff " -"モードに相当します。" - -#: client/src/templates/job_templates/job-template.form.js:267 -msgid "" -"If enabled, show the changes made by Ansible tasks, where supported. This is" -" equivalent to Ansible’s --diff mode." -msgstr "" -"Ansible タスクで加えられた変更を表示します (有効にされ、サポートされている場合)。これは Ansible の --diff " -"モードに相当します。" - -#: client/src/templates/job_templates/job-template.form.js:306 -msgid "If enabled, simultaneous runs of this job template will be allowed." -msgstr "このジョブテンプレートの同時実行が許可されます (有効にされている場合)。" - -#: client/src/templates/workflows.form.js:103 -msgid "" -"If enabled, simultaneous runs of this workflow job template will be allowed." -msgstr "このワークフロージョブテンプレートの同時実行が許可されます (有効にされている場合)。" - -#: client/src/templates/job_templates/job-template.form.js:317 -msgid "" -"If enabled, use cached facts if available and store discovered facts in the " -"cache." -msgstr "キャッシュされたファクトを使用し (有効にされ、ファクトが利用可能な場合)、検出されたファクトをキャッシュに保存します。" - -#: client/src/credentials/credentials.form.js:52 -msgid "" -"If no organization is given, the credential can only be used by the user " -"that creates the credential. Organization admins and system administrators " -"can assign an organization so that roles for the credential can be assigned " -"to users and teams in that organization." -msgstr "" -"組織が指定されない場合、認証情報はそれを作成するユーザーのみに使用されます。組織管理者およびシステム管理者は組織を割り当て、認証情報のロールを組織内のユーザーおよびチームに割り当てられるようにします。" - -#: client/src/license/license.partial.html:70 -msgid "" -"If you are ready to upgrade, please contact us by clicking the button below" -msgstr "アップグレードの準備ができましたら、以下のボタンをクリックしてお問い合わせください。" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:227 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:241 -msgid "Image ID:" -msgstr "イメージ ID:" - -#: client/src/inventories-hosts/hosts/host.form.js:34 -#: client/src/inventories-hosts/hosts/host.list.js:34 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:33 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:30 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:33 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:29 -#: client/src/inventories-hosts/inventory-hosts.strings.js:32 -msgid "" -"Indicates if a host is available and should be included in running jobs." -msgstr "ホストが利用可能かどうか、また実行中のジョブに組み込む必要があるかどうかを示します。" - -#: client/src/activity-stream/activity-detail.form.js:31 -#: client/src/activity-stream/streams.list.js:33 -msgid "Initiated by" -msgstr "開始:" - -#: client/src/credential-types/credential-types.form.js:53 -#: client/src/credential-types/credential-types.form.js:61 -msgid "Injector Configuration" -msgstr "インジェクターの設定" - -#: client/src/credential-types/credential-types.form.js:39 -#: client/src/credential-types/credential-types.form.js:47 -msgid "Input Configuration" -msgstr "入力の設定" - -#: client/src/inventories-hosts/hosts/host.form.js:123 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:122 -msgid "Insights" -msgstr "Insights" - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:52 -msgid "Insights Credential" -msgstr "Insights 認証情報" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:145 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:148 -msgid "Instance Filters" -msgstr "インスタンスフィルター" - -#: client/features/output/output.strings.js:48 -msgid "Instance Group" -msgstr "インスタンスグループ" - -#: client/src/instance-groups/instance-groups.strings.js:63 -msgid "Instance Group parameter is missing." -msgstr "インスタンスグループのパラメーターがありません。" - -#: client/lib/components/components.strings.js:84 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:54 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:57 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:61 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:64 -#: client/src/organizations/organizations.form.js:38 -#: client/src/organizations/organizations.form.js:41 -#: client/src/templates/job_templates/job-template.form.js:252 -#: client/src/templates/job_templates/job-template.form.js:255 -msgid "Instance Groups" -msgstr "インスタンスグループ" - -#: client/src/instance-groups/instance-groups.strings.js:32 -msgid "Instance Groups Help" -msgstr "インスタンスグループのヘルプ" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:236 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:250 -msgid "Instance ID" -msgstr "インスタンス ID" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:228 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:242 -msgid "Instance ID:" -msgstr "インスタンス ID:" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:229 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:243 -msgid "Instance Type:" -msgstr "インスタンスタイプ:" - -#: client/lib/components/components.strings.js:83 -#: client/src/instance-groups/instance-groups.strings.js:17 -msgid "Instances" -msgstr "インスタンス" - -#: client/src/templates/survey-maker/surveys/init.factory.js:18 -msgid "Integer" -msgstr "整数" - -#: client/src/license/license.partial.html:11 -msgid "Invalid License" -msgstr "無効なライセンス" - -#: client/src/license/license.controller.js:74 -#: client/src/license/license.controller.js:82 -msgid "Invalid file format. Please upload valid JSON." -msgstr "無効なファイル形式です。有効な JSON をアップロードしてください。" - -#: client/lib/components/components.strings.js:16 -msgid "Invalid input for this type." -msgstr "このタイプの無効な入力です。" - -#: client/features/output/output.strings.js:86 -msgid "Invalid search filter provided." -msgstr "無効な検索フィルターが指定されました。" - -#: client/src/login/loginModal/loginModal.partial.html:34 -msgid "Invalid username and/or password. Please try again." -msgstr "無効なユーザー名および/またはパスワードです。やり直してください。" - -#: client/lib/components/components.strings.js:75 -#: client/lib/models/models.strings.js:16 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:122 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:52 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:28 -#: client/src/home/dashboard/counts/dashboard-counts.directive.js:50 -#: client/src/organizations/linkout/organizations-linkout.route.js:156 -#: client/src/organizations/linkout/organizations-linkout.route.js:158 -msgid "Inventories" -msgstr "インベントリー" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:44 -msgid "Inventories with sources cannot be copied" -msgstr "ソースを含むインベントリーはコピーできません。" - -#: client/features/jobs/jobs.strings.js:14 -#: client/features/output/output.strings.js:49 -#: client/features/templates/templates.strings.js:16 -#: client/features/templates/templates.strings.js:24 -#: client/src/inventories-hosts/hosts/host.list.js:69 -#: client/src/inventories-hosts/inventories/inventory.list.js:81 -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:76 -#: client/src/job-submission/job-submission.partial.html:17 -#: client/src/organizations/linkout/controllers/organizations-inventories.controller.js:70 -#: client/src/templates/job_templates/job-template.form.js:66 -#: client/src/templates/job_templates/job-template.form.js:80 -msgid "Inventory" -msgstr "インベントリー" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:110 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:124 -msgid "Inventory File" -msgstr "インベントリーファイル" - -#: client/lib/components/components.strings.js:80 -#: client/lib/models/models.strings.js:20 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:29 -msgid "Inventory Scripts" -msgstr "インベントリースクリプト" - -#: client/lib/models/models.strings.js:25 -msgid "Inventory Sources" -msgstr "インベントリーソース" - -#: client/features/templates/templates.strings.js:104 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:46 -#: client/src/workflow-results/workflow-results.controller.js:68 -msgid "Inventory Sync" -msgstr "インベントリー同期" - -#: client/src/home/dashboard/counts/dashboard-counts.directive.js:55 -msgid "Inventory Sync Failures" -msgstr "インベントリーの同期の失敗" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:75 -msgid "Inventory Variables" -msgstr "インベントリー変数" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:73 -msgid "Inventory contains 0 hosts." -msgstr "インベントリーには 0 ホストが含まれています。" - -#: client/features/output/output.strings.js:35 -msgid "Isolated" -msgstr "分離" - -#: client/src/smart-status/smart-status.controller.js:43 -msgid "JOB ID" -msgstr "ジョブ ID" - -#: client/features/output/output.strings.js:84 -msgid "JOB IS STILL RUNNING" -msgstr "ジョブが実行中です" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:4 -msgid "JOB STATUS" -msgstr "ジョブステータス" - -#: client/src/templates/job_templates/job-template.form.js:22 -msgid "JOB TEMPLATE" -msgstr "ジョブテンプレート" - -#: client/features/portalMode/portalMode.strings.js:8 -#: client/features/templates/routes/organizationsTemplatesList.route.js:20 -#: client/features/templates/routes/projectsTemplatesList.route.js:18 -#: client/src/organizations/list/organizations-list.controller.js:79 -msgid "JOB TEMPLATES" -msgstr "ジョブテンプレート" - -#: client/features/jobs/jobs.strings.js:8 -#: client/features/jobs/routes/instanceGroupJobs.route.js:13 -#: client/features/jobs/routes/instanceJobs.route.js:13 -#: client/features/jobs/routes/inventoryCompletedJobs.route.js:22 -#: client/features/jobs/routes/jobs.route.js:13 -#: client/features/portalMode/portalMode.strings.js:9 -#: client/features/templates/templates.strings.js:109 -#: client/src/activity-stream/get-target-title.factory.js:32 -#: client/src/home/dashboard/graphs/job-status/job-status-graph.directive.js:119 -#: client/src/instance-groups/instance-groups.strings.js:26 -msgid "JOBS" -msgstr "ジョブ" - -#: client/lib/components/code-mirror/code-mirror.strings.js:12 -#: client/lib/services/base-string.service.js:70 -#: client/src/job-submission/job-submission.partial.html:173 -msgid "JSON" -msgstr "JSON" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:198 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:222 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:246 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:321 -msgid "JSON:" -msgstr "JSON:" - -#: client/features/jobs/jobs.strings.js:18 -#: client/src/workflow-results/workflow-results.controller.js:86 -msgid "Job" -msgstr "ジョブ" - -#: client/features/output/output.strings.js:51 -#: client/features/templates/templates.strings.js:48 -#: client/src/job-submission/job-submission.partial.html:228 -#: client/src/templates/job_templates/job-template.form.js:190 -#: client/src/templates/job_templates/job-template.form.js:197 -msgid "Job Tags" -msgstr "ジョブタグ" - -#: client/features/jobs/jobs.strings.js:13 -#: client/features/output/output.strings.js:52 -#: client/features/templates/templates.strings.js:13 -#: client/src/templates/templates.list.js:61 -msgid "Job Template" -msgstr "ジョブテンプレート" - -#: client/lib/models/models.strings.js:30 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:103 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:36 -#: client/src/projects/projects.form.js:303 -msgid "Job Templates" -msgstr "ジョブテンプレート" - -#: client/features/output/output.strings.js:53 -#: client/features/templates/templates.strings.js:50 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:32 -#: client/src/job-submission/job-submission.partial.html:202 -#: client/src/templates/job_templates/job-template.form.js:47 -#: client/src/templates/job_templates/job-template.form.js:55 -msgid "Job Type" -msgstr "ジョブタイプ" - -#: client/features/jobs/jobs.strings.js:19 -msgid "Job {{status}}. Click for details." -msgstr "ジョブ {{status}}。クリックして詳細を確認してください。" - -#: client/lib/components/components.strings.js:69 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:30 -#: client/src/configuration/configuration.partial.html:22 -#: client/src/instance-groups/instance-groups.strings.js:52 -msgid "Jobs" -msgstr "ジョブ" - -#: client/features/output/output.strings.js:82 -#: client/features/templates/templates.strings.js:99 -#: client/src/workflow-results/workflow-results.controller.js:69 -msgid "KEY" -msgstr "キー" - -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:61 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:154 -#: client/src/shared/smart-search/smart-search.partial.html:14 -msgid "Key" -msgstr "キー" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:230 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:244 -msgid "Key Name:" -msgstr "キー名:" - -#: client/src/credential-types/credential-types.list.js:31 -#: client/src/credentials/credentials.list.js:33 -msgid "Kind" -msgstr "種類" - -#: client/features/applications/applications.strings.js:31 -msgid "LAST MODIFIED" -msgstr "最終更新日" - -#: client/features/users/tokens/tokens.strings.js:38 -msgid "LAST USED" -msgstr "最終使用時間" - -#: client/features/templates/templates.strings.js:30 -msgid "LAUNCH" -msgstr "起動" - -#: client/src/job-submission/job-submission.partial.html:6 -msgid "LAUNCH JOB" -msgstr "ジョブの起動" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:157 -msgid "LDAP" -msgstr "LDAP" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:165 -msgid "LDAP 1 (Optional)" -msgstr "LDAP 1 (オプション)" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:166 -msgid "LDAP 2 (Optional)" -msgstr "LDAP 2 (オプション)" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:167 -msgid "LDAP 3 (Optional)" -msgstr "LDAP 3 (オプション)" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:168 -msgid "LDAP 4 (Optional)" -msgstr "LDAP 4 (オプション)" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:169 -msgid "LDAP 5 (Optional)" -msgstr "LDAP 5 (オプション)" - -#: client/src/configuration/auth-form/configuration-auth.partial.html:17 -msgid "LDAP Server" -msgstr "LDAP サーバー" - -#: client/src/configuration/license.route.js:18 -#: client/src/license/license.route.js:18 -msgid "LICENSE" -msgstr "ライセンス" - -#: client/features/output/output.strings.js:54 -#: client/src/templates/job_templates/job-template.form.js:224 -#: client/src/templates/job_templates/job-template.form.js:228 -#: client/src/templates/templates.list.js:43 -#: client/src/templates/workflows.form.js:72 -#: client/src/templates/workflows.form.js:76 -#: client/src/workflow-results/workflow-results.controller.js:51 -msgid "Labels" -msgstr "ラベル" - -#: client/features/templates/templates.strings.js:19 -msgid "Last Modified" -msgstr "最終更新日" - -#: client/src/access/rbac-multiselect/permissionsUsers.list.js:31 -#: client/src/users/users.form.js:36 client/src/users/users.list.js:37 -msgid "Last Name" -msgstr "姓" - -#: client/features/templates/templates.strings.js:20 -msgid "Last Ran" -msgstr "最終実行日時" - -#: client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.directive.js:56 -msgid "Last Sync" -msgstr "最終同期" - -#: client/src/projects/projects.list.js:56 -msgid "Last Updated" -msgstr "最終更新日時" - -#: client/src/shared/form-generator.js:1727 -msgid "Launch" -msgstr "起動" - -#: client/src/management-jobs/card/card.partial.html:23 -msgid "Launch Management Job" -msgstr "管理ジョブの起動" - -#: client/features/jobs/jobs.strings.js:12 -#: client/features/output/output.strings.js:55 -#: client/src/workflow-results/workflow-results.controller.js:48 -msgid "Launched By" -msgstr "起動:" - -#: client/features/templates/templates.strings.js:38 -#: client/src/job-submission/job-submission.partial.html:99 -msgid "" -"Launching this job requires the passwords listed below. Enter and confirm " -"each password before continuing." -msgstr "このジョブの起動には以下に記載されているパスワードが必要です。それぞれのパスワードを入力し、確認してから続行します。" - -#: client/features/users/tokens/tokens.strings.js:32 -msgid "" -"Leaving this field blank will result in the creation of a Personal Access " -"Token which is not linked to an Application." -msgstr "このフィールドを空白のままにすると、アプリケーションにリンクされていないパーソナルアクセストークンが作成されます。" - -#: client/features/credentials/legacy.credentials.js:350 -msgid "Legacy state configuration for does not exist" -msgstr "レガシー状態の設定は存在しません。" - -#: client/src/configuration/configuration.partial.html:43 -#: client/src/license/license.controller.js:44 -#: client/src/license/license.partial.html:8 -msgid "License" -msgstr "ライセンス" - -#: client/features/output/output.strings.js:56 -msgid "License Error" -msgstr "ライセンスエラー" - -#: client/src/license/license.partial.html:104 -msgid "License File" -msgstr "ライセンスファイル" - -#: client/src/license/license.partial.html:33 -msgid "License Key" -msgstr "ライセンスキー" - -#: client/src/license/license.controller.js:46 -msgid "License Management" -msgstr "ライセンス管理" - -#: client/src/license/license.partial.html:21 -msgid "License Type" -msgstr "ライセンスタイプ" - -#: client/features/output/output.strings.js:57 -#: client/features/templates/templates.strings.js:49 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:45 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:55 -#: client/src/job-submission/job-submission.partial.html:220 -#: client/src/templates/job_templates/job-template.form.js:159 -#: client/src/templates/job_templates/job-template.form.js:163 -msgid "Limit" -msgstr "制限" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:240 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:255 -msgid "Limit to hosts having a tag:" -msgstr "タグを持つホストに制限:" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:242 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:257 -msgid "Limit to hosts using either key pair:" -msgstr "いずれかのキーペアを使用するホストに制限:" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:244 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:259 -msgid "Limit to hosts where the Name tag begins with" -msgstr "名前タグの開始点となるホストに制限" - -#: client/src/scheduler/scheduler.strings.js:51 -msgid "Limited to first 10" -msgstr "最初の 10 件に制限" - -#: client/src/shared/socket/socket.service.js:213 -msgid "Live events: attempting to connect to the server." -msgstr "ライブイベント: サーバーへの接続を試行しています。" - -#: client/src/shared/socket/socket.service.js:217 -msgid "" -"Live events: connected. Pages containing job status information will " -"automatically update in real-time." -msgstr "ライブイベント: 接続されています。ジョブステータス情報を含むページは自動的にリアルタイムで更新されます。" - -#: client/src/shared/socket/socket.service.js:221 -msgid "Live events: error connecting to the server." -msgstr "ライブイベント: サーバーへの接続時にエラーが発生しました。" - -#: client/src/shared/form-generator.js:2005 -msgid "Loading..." -msgstr "ロード中..." - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:133 -#: client/src/scheduler/scheduler.strings.js:26 -msgid "Local Time Zone" -msgstr "ローカルタイムゾーン" - -#: client/src/configuration/system-form/configuration-system.controller.js:225 -msgid "Log aggregator test failed.
Detail:" -msgstr "ログアグリゲーターのテストに失敗しました。
詳細:" - -#: client/src/configuration/system-form/configuration-system.controller.js:218 -msgid "Log aggregator test successful." -msgstr "ログアグリゲーターのテストが成功しました。" - -#: client/lib/components/components.strings.js:65 -msgid "Logged in as" -msgstr "次の名前でログイン" - -#: client/src/configuration/system-form/configuration-system.controller.js:89 -msgid "Logging" -msgstr "ロギング" - -#: client/lib/components/components.strings.js:67 -msgid "Logout" -msgstr "ログアウト" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:35 -msgid "Low" -msgstr "低" - -#: client/src/management-jobs/card/card.partial.html:6 -#: client/src/management-jobs/card/card.route.js:20 -msgid "MANAGEMENT JOBS" -msgstr "管理ジョブ" - -#: client/src/instance-groups/instance-groups.strings.js:15 -msgid "MANUAL" -msgstr "手動" - -#: client/features/output/output.strings.js:105 -msgid "MODULE" -msgstr "モジュール" - -#: client/features/portalMode/routes/portalModeTemplatesList.route.js:13 -msgid "MY VIEW" -msgstr "マイビュー" - -#: client/src/credentials/credentials.form.js:67 -#: client/src/job-submission/job-submission.partial.html:356 -msgid "Machine" -msgstr "マシン" - -#: client/features/output/output.strings.js:58 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:60 -msgid "Machine Credential" -msgstr "マシンの認証情報" - -#: client/lib/components/components.strings.js:82 -msgid "Management Jobs" -msgstr "管理ジョブ" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:54 -#: client/src/projects/edit/projects-edit.controller.js:143 -#: client/src/projects/list/projects-list.controller.js:89 -msgid "Manual projects do not require an SCM update" -msgstr "手動プロジェクトに SCM 更新は不要です" - -#: client/src/templates/job_templates/job-template.form.js:234 -msgid "Max 512 characters per label." -msgstr "最大 512 文字 (ラベルあたり)" - -#: client/src/login/loginModal/loginModal.partial.html:28 -msgid "Maximum per-user sessions reached. Please sign in." -msgstr "ユーザーあたりの最大セッション数に達しました。サインインしてください。" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:29 -msgid "Medium" -msgstr "中" - -#: client/src/configuration/system-form/configuration-system.controller.js:90 -msgid "Misc. System" -msgstr "その他のシステム" - -#: client/src/templates/workflows.form.js:35 -msgid "" -"Missing Job Templates found in the Workflow Editor" -msgstr "" -"欠落していたジョブテンプレートはワークフロービジュアライザーにあります。" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:22 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:30 -msgid "Module" -msgstr "モジュール" - -#: client/features/output/output.strings.js:59 -msgid "Module Args" -msgstr "モジュールの引数" - -#: client/src/scheduler/scheduler.strings.js:37 -msgid "Mon" -msgstr "月" - -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:25 -msgid "Most recent job failed. Click to view jobs." -msgstr "最新のジョブが失敗しました。クリックしてジョブを表示します。" - -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:29 -msgid "Most recent job successful. Click to view jobs." -msgstr "最新のジョブが成功しました。クリックしてジョブを表示します。" - -#: client/src/templates/survey-maker/surveys/init.factory.js:17 -msgid "Multiple Choice (multiple select)" -msgstr "複数の選択 (複数の選択)" - -#: client/src/templates/survey-maker/surveys/init.factory.js:16 -msgid "Multiple Choice (single select)" -msgstr "複数の選択 (単一の選択)" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:77 -msgid "Multiple Choice Options" -msgstr "複数の選択オプション" - -#: client/features/portalMode/index.view.html:26 -msgid "My Jobs" -msgstr "マイジョブ" - -#: client/lib/components/components.strings.js:71 -msgid "My View" -msgstr "マイビュー" - -#: client/features/applications/applications.strings.js:24 -msgid "NEW APPLICATION" -msgstr "新規アプリケーション" - -#: client/features/credentials/credentials.strings.js:26 -msgid "NEW CREDENTIAL" -msgstr "新規の認証情報" - -#: client/src/credential-types/credential-types.form.js:16 -msgid "NEW CREDENTIAL TYPE" -msgstr "新規の認証情報タイプ" - -#: client/src/inventory-scripts/inventory-scripts.form.js:16 -msgid "NEW CUSTOM INVENTORY" -msgstr "新規カスタムインベントリー" - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:17 -msgid "NEW INVENTORY" -msgstr "新規インベントリー" - -#: client/src/templates/job_templates/job-template.form.js:19 -msgid "NEW JOB TEMPLATE" -msgstr "新規ジョブテンプレート" - -#: client/src/notifications/notificationTemplates.form.js:16 -msgid "NEW NOTIFICATION TEMPLATE" -msgstr "新規通知テンプレート" - -#: client/src/organizations/organizations.form.js:18 -msgid "NEW ORGANIZATION" -msgstr "新規組織" - -#: client/src/projects/projects.form.js:17 -msgid "NEW PROJECT" -msgstr "新規プロジェクト" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:10 -msgid "NEW SMART INVENTORY" -msgstr "新規スマートインベントリー" - -#: client/src/teams/teams.form.js:16 -msgid "NEW TEAM" -msgstr "新規チーム" - -#: client/src/users/users.form.js:16 -msgid "NEW USER" -msgstr "新規ユーザー" - -#: client/src/templates/workflows.form.js:17 -msgid "NEW WORKFLOW JOB TEMPLATE" -msgstr "新規ワークフロージョブテンプレート" - -#: client/lib/services/base-string.service.js:64 -msgid "NEXT" -msgstr "次へ" - -#: client/src/inventories-hosts/hosts/hosts.partial.html:38 -msgid "NO HOSTS HAVE BEEN CREATED" -msgstr "ホストが作成されていません" - -#: client/lib/components/components.strings.js:39 -msgid "NO OPTIONS AVAILABLE" -msgstr "利用可能なオプションがありません" - -#: client/src/login/loginModal/loginModal.partial.html:89 -msgid "NOTICE" -msgstr "通知" - -#: client/src/notifications/notificationTemplates.form.js:21 -msgid "NOTIFICATION TEMPLATE" -msgstr "通知テンプレート" - -#: client/src/activity-stream/get-target-title.factory.js:26 -#: client/src/notifications/notificationTemplates.list.js:14 -msgid "NOTIFICATION TEMPLATES" -msgstr "通知テンプレート" - -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-notifications.route.js:9 -#: client/src/management-jobs/notifications/notification.route.js:46 -#: client/src/notifications/main.js:42 client/src/notifications/main.js:89 -#: client/src/notifications/notification-templates-list/add-notifications-action.partial.html:2 -msgid "NOTIFICATIONS" -msgstr "通知" - -#: client/features/output/output.strings.js:60 -#: client/src/credential-types/credential-types.form.js:27 -#: client/src/credential-types/credential-types.list.js:24 -#: client/src/credentials/credentials.form.js:32 -#: client/src/credentials/credentials.list.js:26 -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:14 -#: client/src/home/dashboard/lists/jobs/jobs-list.partial.html:13 -#: client/src/instance-groups/instance-groups.list.js:15 -#: client/src/inventories-hosts/hosts/host.list.js:61 -#: client/src/inventories-hosts/inventories/inventory.list.js:48 -#: client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.directive.js:55 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:32 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js:33 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:51 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:21 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:28 -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:45 -#: client/src/inventory-scripts/inventory-scripts.form.js:28 -#: client/src/inventory-scripts/inventory-scripts.list.js:20 -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:21 -#: client/src/notifications/notificationTemplates.form.js:32 -#: client/src/notifications/notificationTemplates.list.js:32 -#: client/src/notifications/notifications.list.js:27 -#: client/src/organizations/organizations.form.js:26 -#: client/src/projects/projects.form.js:30 -#: client/src/projects/projects.list.js:37 -#: client/src/scheduler/scheduled-jobs.list.js:31 -#: client/src/scheduler/scheduler.strings.js:21 -#: client/src/scheduler/schedules.list.js:41 -#: client/src/teams/teams.form.js:127 client/src/teams/teams.form.js:28 -#: client/src/teams/teams.list.js:23 -#: client/src/templates/job_templates/job-template.form.js:34 -#: client/src/templates/templates.list.js:24 -#: client/src/templates/workflows.form.js:42 -#: client/src/users/users.form.js:144 client/src/users/users.form.js:170 -#: client/src/users/users.form.js:196 -msgid "Name" -msgstr "名前" - -#: client/src/credentials/credentials.form.js:71 -msgid "Network" -msgstr "ネットワーク" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:81 -msgid "New Group" -msgstr "新規グループ" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:130 -msgid "New Host" -msgstr "新規ホスト" - -#: client/src/users/add/users-add.controller.js:92 -msgid "New user successfully created!" -msgstr "新規ユーザーが正常に作成されました!" - -#: client/src/scheduler/scheduled-jobs.list.js:51 -#: client/src/scheduler/schedules.list.js:51 -msgid "Next Run" -msgstr "次回実行日時" - -#: client/src/credentials/credentials.list.js:21 -msgid "No Credentials Have Been Created" -msgstr "認証情報が作成されていません" - -#: client/features/templates/templates.strings.js:62 -#: client/src/job-submission/lists/credential/job-sub-cred-list.controller.js:44 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js:15 -msgid "No Credentials Matching This Type Have Been Created" -msgstr "このタイプに一致する認証情報が作成されていません" - -#: client/features/output/host-event/host-event-codemirror.partial.html:3 -msgid "No JSON data returned by the module" -msgstr "JSON データがモジュールによって返されていません" - -#: client/src/projects/projects.list.js:20 -msgid "No Projects Have Been Created" -msgstr "プロジェクトが作成されていません" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:50 -msgid "No Remediation Playbook Available" -msgstr "修復 Playbook を使用できません" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:199 -#: client/src/projects/list/projects-list.controller.js:186 -msgid "No SCM Configuration" -msgstr "SCM 設定がありません" - -#: client/src/projects/factories/get-project-tool-tip.factory.js:9 -msgid "No SCM updates have run for this project" -msgstr "このプロジェクトで実行された SCM 更新はありません" - -#: client/src/access/rbac-multiselect/permissionsTeams.list.js:17 -msgid "No Teams exist" -msgstr "チームが存在しません" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:191 -#: client/src/projects/list/projects-list.controller.js:150 -msgid "No Updates Available" -msgstr "利用可能な更新がありません" - -#: client/src/access/rbac-multiselect/permissionsUsers.list.js:18 -msgid "No Users exist" -msgstr "ユーザーが存在しません" - -#: client/features/templates/templates.strings.js:33 -msgid "No credentials selected" -msgstr "認証情報が選択されていません" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:63 -msgid "No data is available. There are no issues to report." -msgstr "使用できるデータがありません。報告する問題がありません。" - -#: client/src/license/license.controller.js:41 -msgid "No file selected." -msgstr "ファイルが選択されていません。" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:69 -msgid "No hosts with failures. Click for details." -msgstr "障害のあるホストがありません。クリックして詳細を確認してください。" - -#: client/features/templates/templates.strings.js:34 -msgid "No inventory selected" -msgstr "インベントリーが選択されていません" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:52 -msgid "No inventory sync failures. Click for details." -msgstr "インベントリーの同期に障害が発生していません。クリックして詳細を確認してください。" - -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:16 -msgid "No job data" -msgstr "ジョブデータがありません" - -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:75 -msgid "No job data available." -msgstr "利用可能なジョブデータがありません。" - -#: client/src/inventories-hosts/inventories/related/groups/factories/get-hosts-status-msg.factory.js:22 -msgid "No job failures" -msgstr "ジョブに障害が発生していません" - -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:51 -msgid "No job templates were recently used." -msgstr "最近使用されたジョブテンプレートはありません。" - -#: client/src/home/dashboard/lists/jobs/jobs-list.partial.html:44 -msgid "No jobs were recently run." -msgstr "最近実行されたジョブがありません。" - -#: client/src/teams/teams.form.js:124 client/src/users/users.form.js:193 -msgid "No permissions have been granted" -msgstr "パーミッションが付与されていません" - -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:17 -msgid "No recent job data available for this host." -msgstr "このホストに利用できる最新のジョブデータがありません。" - -#: client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.directive.js:75 -msgid "No recent job data available for this inventory." -msgstr "このインベントリーに利用できる最新のジョブデータがありません。" - -#: client/src/notifications/notification-templates-list/list.controller.js:86 -msgid "No recent notifications." -msgstr "最新の通知はありません。" - -#: client/src/inventories-hosts/hosts/hosts.partial.html:36 -#: client/src/shared/form-generator.js:1899 -#: client/src/shared/list-generator/list-generator.factory.js:240 -msgid "No records matched your search." -msgstr "検索に一致するレコードはありません" - -#: client/features/output/output.strings.js:106 -msgid "No result found" -msgstr "結果が見つかりません" - -#: client/src/scheduler/scheduled-jobs.list.js:16 -msgid "No schedules exist" -msgstr "スケジュールがありません" - -#: client/src/job-submission/job-submission.partial.html:348 -#: client/src/job-submission/job-submission.partial.html:353 -msgid "None selected" -msgstr "いずれも選択されていません" - -#: client/src/users/add/users-add.controller.js:10 -#: client/src/users/edit/users-edit.controller.js:10 -#: client/src/users/list/users-list.controller.js:10 -msgid "Normal User" -msgstr "標準ユーザー" - -#: client/features/output/output.strings.js:36 -#: client/src/workflow-results/workflow-results.controller.js:56 -msgid "Not Finished" -msgstr "終了していません" - -#: client/features/output/output.strings.js:37 -#: client/src/workflow-results/workflow-results.controller.js:57 -msgid "Not Started" -msgstr "開始されていません" - -#: client/src/projects/list/projects-list.controller.js:91 -msgid "Not configured for SCM" -msgstr "SCM 用に設定されていません" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:59 -msgid "Not configured for inventory sync." -msgstr "インベントリーの同期に設定されていません。" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-disassociate.partial.html:25 -msgid "" -"Note that only hosts directly in this group can be disassociated. Hosts in " -"sub-groups must be disassociated directly from the sub-group level that they" -" belong." -msgstr "" -"このグループに直接含まれるホストのみの関連付けを解除できることに注意してください。サブグループのホストの関連付けの解除については、それらのホストが属するサブグループのレベルで直接実行する必要があります。" - -#: client/src/notifications/notificationTemplates.form.js:288 -#: client/src/notifications/notificationTemplates.form.js:289 -#: client/src/notifications/notificationTemplates.form.js:472 -#: client/src/notifications/notificationTemplates.form.js:473 -msgid "Notification Color" -msgstr "通知の色" - -#: client/src/notifications/notification-templates-list/list.controller.js:140 -msgid "Notification Failed." -msgstr "通知に失敗しました。" - -#: client/src/notifications/notificationTemplates.form.js:277 -msgid "Notification Label" -msgstr "通知レベル" - -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:31 -msgid "Notification Templates" -msgstr "通知テンプレート" - -#: client/lib/components/components.strings.js:81 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:20 -#: client/src/management-jobs/notifications/notification.route.js:21 -#: client/src/notifications/notifications.list.js:17 -msgid "Notifications" -msgstr "通知" - -#: client/src/notifications/notificationTemplates.form.js:302 -msgid "Notify Channel" -msgstr "通知チャネル" - -#: client/lib/services/base-string.service.js:68 -#: client/src/inventories-hosts/hosts/hosts.partial.html:55 -#: client/src/job-submission/job-submission.partial.html:269 -#: client/src/partials/survey-maker-modal.html:27 -#: client/src/shared/form-generator.js:545 -#: client/src/shared/form-generator.js:780 -#: client/src/shared/generator-helpers.js:554 -msgid "OFF" -msgstr "オフ" - -#: client/lib/services/base-string.service.js:63 -msgid "OK" -msgstr "OK" - -#: client/lib/services/base-string.service.js:67 -#: client/src/inventories-hosts/hosts/hosts.partial.html:54 -#: client/src/job-submission/job-submission.partial.html:267 -#: client/src/partials/survey-maker-modal.html:26 -#: client/src/shared/form-generator.js:541 -#: client/src/shared/form-generator.js:778 -#: client/src/shared/generator-helpers.js:550 -msgid "ON" -msgstr "オン" - -#: client/lib/components/components.strings.js:10 -msgid "OPTIONS" -msgstr "オプション" - -#: client/features/applications/applications.strings.js:30 -msgid "ORG" -msgstr "組織" - -#: client/src/activity-stream/get-target-title.factory.js:29 -#: client/src/organizations/list/organizations-list.partial.html:6 -#: client/src/organizations/main.js:51 -msgid "ORGANIZATIONS" -msgstr "組織" - -#: client/src/scheduler/scheduler.strings.js:45 -msgid "Occurrences" -msgstr "発生" - -#: client/src/workflow-results/workflow-results.controller.js:65 -msgid "On Fail" -msgstr "失敗時" - -#: client/features/templates/templates.strings.js:101 -msgid "On Failure" -msgstr "障害発生時" - -#: client/features/templates/templates.strings.js:100 -#: client/src/workflow-results/workflow-results.controller.js:64 -msgid "On Success" -msgstr "成功時" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:157 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:162 -msgid "Only Group By" -msgstr "グループ化のみ" - -#: client/src/credentials/credentials.form.js:379 -msgid "" -"OpenStack domains define administrative boundaries. It is only needed for " -"Keystone v3 authentication URLs. Common scenarios include:" -msgstr "" -"OpenStack ドメインは管理上の境界を定義します。これは Keystone v3 認証 URL " -"にのみ必要です。共通するシナリオには以下が含まれます:" - -#: client/src/templates/job_templates/job-template.form.js:230 -#: client/src/templates/workflows.form.js:78 -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」などのこのジョブテンプレートを説明するオプションラベルです。ラベルを使用し、ジョブテンプレートおよび完了したジョブの分類およびフィルターを実行できます。" - -#: client/src/notifications/notificationTemplates.form.js:453 -#: client/src/partials/logviewer.html:7 -#: client/src/templates/job_templates/job-template.form.js:275 -#: client/src/templates/workflows.form.js:96 -msgid "Options" -msgstr "オプション" - -#: client/src/credentials/credentials.form.js:46 -#: client/src/credentials/credentials.form.js:53 -#: client/src/inventories-hosts/inventories/inventory.list.js:61 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:33 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:41 -#: client/src/inventory-scripts/inventory-scripts.form.js:40 -#: client/src/inventory-scripts/inventory-scripts.list.js:27 -#: client/src/notifications/notificationTemplates.form.js:44 -#: client/src/projects/projects.form.js:42 -#: client/src/projects/projects.form.js:48 client/src/teams/teams.form.js:40 -#: client/src/teams/teams.list.js:30 client/src/templates/workflows.form.js:55 -#: client/src/templates/workflows.form.js:61 client/src/users/users.form.js:42 -msgid "Organization" -msgstr "組織" - -#: client/lib/components/components.strings.js:77 -#: client/lib/models/models.strings.js:35 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:136 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:64 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:32 -#: client/src/users/users.form.js:134 -msgid "Organizations" -msgstr "組織" - -#: client/features/templates/templates.strings.js:27 -#: client/src/job-submission/job-submission.partial.html:19 -msgid "Other Prompts" -msgstr "他のプロンプト" - -#: client/src/credentials/credentials.form.js:79 -msgid "Others (Cloud Providers)" -msgstr "その他 (クラウドプロバイダー)" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:317 -msgid "" -"Override variables found in azure_rm.ini and used by the inventory update " -"script. For a detailed description of these variables" -msgstr "" -"azure_rm.ini にあり、インベントリー更新スクリプトで使用される変数を上書きします。これらの変数の詳細な説明については、次を参照してください。" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:283 -msgid "" -"Override variables found in cloudforms.ini and used by the inventory update script. For an example variable configuration\n" -" \n" -" view cloudforms.ini in the Ansible github repo. Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax." -msgstr "" -"cloudforms.ini にあり、インベントリー更新スクリプトで使用される変数を上書きします。たとえば、変数の設定\n" -" \n" -" は Ansible github リポジトリーで cloudforms.ini を表示します。 JSON または YAML 構文のいずれかを使用してインベントリー変数を入力します。ラジオボタンを使用して 2 つの間の切り替えを行います。構文のサンプルについては、Ansible Tower ドキュメントを参照してください。" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:218 -msgid "" -"Override variables found in ec2.ini and used by the inventory update script." -" For a detailed description of these variables" -msgstr "" -"ec2.ini にあり、インベントリー更新スクリプトで使用される変数を上書きします。これらの変数の詳細な説明については、次を参照してください。" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:300 -msgid "" -"Override variables found in foreman.ini and used by the inventory update script. For an example variable configuration\n" -" \n" -" view foreman.ini in the Ansible github repo. Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax." -msgstr "" -"foreman.ini にあり、インベントリー更新スクリプトで使用される変数を上書きします。たとえば、変数の設定\n" -" \n" -" は Ansible github リポジトリーで foreman.ini を表示します。 JSON または YAML 構文のいずれかを使用してインベントリー変数を入力します。ラジオボタンを使用して 2 つの間の切り替えを行います。構文のサンプルについては、Ansible Tower ドキュメントを参照してください。" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:266 -msgid "" -"Override variables found in openstack.yml and used by the inventory update script. For an example variable configuration\n" -" \n" -" view openstack.yml in the Ansible github repo. Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax." -msgstr "" -"openstack.yml にあり、インベントリー更新スクリプトで使用される変数を上書きします。たとえば、変数の設定\n" -" \n" -" は Ansible github リポジトリーで openstack.yml を表示します。 JSON または YAML 構文のいずれかを使用してインベントリー変数を入力します。ラジオボタンを使用して 2 つの間の切り替えを行います。構文のサンプルについては、Ansible Tower ドキュメントを参照してください。" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:242 -msgid "" -"Override variables found in vmware.ini and used by the inventory update " -"script. For a detailed description of these variables" -msgstr "" -"vmware.ini にあり、インベントリー更新スクリプトで使用される変数を上書きします。これらの変数の詳細な説明については、次を参照してください。" - -#: client/features/output/output.strings.js:61 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:352 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:357 -msgid "Overwrite" -msgstr "上書き" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:364 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:369 -msgid "Overwrite Variables" -msgstr "変数の上書き" - -#: client/features/output/output.strings.js:62 -msgid "Overwrite Vars" -msgstr "変数の上書き" - -#: client/src/credentials/credentials.list.js:40 -msgid "Owners" -msgstr "所有者" - -#: client/src/login/loginModal/loginModal.partial.html:68 -msgid "PASSWORD" -msgstr "パスワード" - -#: client/features/credentials/legacy.credentials.js:117 -msgid "PERMISSIONS" -msgstr "パーミッション" - -#: client/features/output/output.strings.js:103 -msgid "PLAY" -msgstr "プレイ" - -#: client/src/partials/job-template-details.html:2 -msgid "PLAYBOOK" -msgstr "PLAYBOOK" - -#: client/src/partials/survey-maker-modal.html:45 -msgid "PLEASE ADD A SURVEY PROMPT." -msgstr "Survey プロンプトを追加してください。" - -#: client/src/organizations/list/organizations-list.partial.html:37 -#: client/src/shared/form-generator.js:1905 -#: client/src/shared/list-generator/list-generator.factory.js:248 -msgid "PLEASE ADD ITEMS TO THIS LIST" -msgstr "項目をこの一覧に追加してください" - -#: client/src/partials/survey-maker-modal.html:43 -msgid "PREVIEW" -msgstr "プレビュー" - -#: client/src/partials/job-template-details.html:2 -msgid "PROJECT" -msgstr "プロジェクト" - -#: client/src/activity-stream/get-target-title.factory.js:8 -#: client/src/organizations/linkout/organizations-linkout.route.js:196 -#: client/src/organizations/list/organizations-list.controller.js:73 -#: client/src/projects/main.js:92 client/src/projects/projects.list.js:14 -#: client/src/projects/projects.list.js:15 -msgid "PROJECTS" -msgstr "プロジェクト" - -#: client/features/templates/templates.strings.js:26 -msgid "PROMPT" -msgstr "プロンプト" - -#: client/src/shared/paginate/paginate.partial.html:33 -msgid "Page" -msgstr "ページ" - -#: client/src/notifications/notificationTemplates.form.js:232 -msgid "Pagerduty subdomain" -msgstr "Pagerduty サブドメイン" - -#: client/src/templates/job_templates/job-template.form.js:363 -msgid "" -"Pass extra command line variables to the playbook. Provide key/value pairs " -"using either YAML or JSON. Refer to the Ansible Tower documentation for " -"example syntax." -msgstr "" -"追加のコマンドライン変数を Playbook に渡します。YAML または JSON " -"のいずれかを使用してキーと値のペアを指定します。構文のサンプルについては Ansible Tower ドキュメントを参照してください。" - -#: client/src/templates/workflows.form.js:89 -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 Tower documentation for" -" example syntax." -msgstr "" -"追加のコマンドライン変数を Playbook に渡します。これは、ansible-playbook の -e または --extra-vars " -"コマンドラインパラメーターです。YAML または JSON のいずれかを使用してキーと値のペアを指定します。構文のサンプルについては Ansible " -"Tower ドキュメントを参照してください。" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:139 -msgid "" -"Pass extra command line variables. This is the %s or %s command line " -"parameter for %s. Provide key/value pairs using either YAML or JSON." -msgstr "" -"追加のコマンドライン変数を渡します。これは、%s の %s または %s コマンドラインパラメーターです。YAML または JSON " -"のいずれかを使用してキーと値のペアを指定します。" - -#: client/src/credentials/credentials.form.js:226 -#: client/src/credentials/factories/become-method-change.factory.js:21 -#: client/src/credentials/factories/become-method-change.factory.js:40 -#: client/src/credentials/factories/become-method-change.factory.js:48 -#: client/src/credentials/factories/become-method-change.factory.js:68 -#: client/src/credentials/factories/become-method-change.factory.js:78 -#: client/src/credentials/factories/become-method-change.factory.js:88 -#: client/src/credentials/factories/kind-change.factory.js:105 -#: client/src/credentials/factories/kind-change.factory.js:125 -#: client/src/credentials/factories/kind-change.factory.js:135 -#: client/src/credentials/factories/kind-change.factory.js:145 -#: client/src/credentials/factories/kind-change.factory.js:30 -#: client/src/credentials/factories/kind-change.factory.js:78 -#: client/src/credentials/factories/kind-change.factory.js:97 -#: client/src/job-submission/job-submission.partial.html:104 -#: client/src/notifications/shared/type-change.service.js:30 -#: client/src/templates/survey-maker/surveys/init.factory.js:15 -#: client/src/users/users.form.js:70 -msgid "Password" -msgstr "パスワード" - -#: client/src/credentials/factories/kind-change.factory.js:58 -msgid "Password (API Key)" -msgstr "パスワード (API キー)" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:20 -msgid "Past 24 Hours" -msgstr "過去 24 時間" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:15 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:26 -msgid "Past Month" -msgstr "過去 1 ヵ月" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:23 -msgid "Past Week" -msgstr "過去 1 週間" - -#: client/src/credentials/factories/become-method-change.factory.js:29 -#: client/src/credentials/factories/kind-change.factory.js:86 -msgid "" -"Paste the contents of the PEM file associated with the service account " -"email." -msgstr "サービスアカウントメールに関連付けられた PEM ファイルの内容を貼り付けます。" - -#: client/src/credentials/factories/kind-change.factory.js:51 -msgid "Paste the contents of the SSH private key file." -msgstr "SSH 秘密鍵ファイルの内容を貼り付けます。" - -#: client/src/credentials/factories/kind-change.factory.js:26 -msgid "Paste the contents of the SSH private key file.%s or click to close%s" -msgstr "SSH 秘密鍵ファイルの内容を貼り付けます。%s またはクリックして %s を閉じます。" - -#: client/src/inventories-hosts/inventories/inventory.list.js:129 -msgid "Pending Delete" -msgstr "保留中の削除" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:8 -msgid "Period" -msgstr "期間" - -#: client/src/projects/add/projects-add.controller.js:32 -#: client/src/users/add/users-add.controller.js:44 -msgid "Permission Error" -msgstr "パーミッションのエラー" - -#: client/features/credentials/credentials.strings.js:14 -#: client/features/credentials/legacy.credentials.js:63 -#: client/src/credentials/credentials.form.js:439 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:104 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:106 -#: client/src/projects/projects.form.js:247 client/src/teams/teams.form.js:120 -#: client/src/templates/job_templates/job-template.form.js:402 -#: client/src/templates/workflows.form.js:139 -#: client/src/users/users.form.js:189 -msgid "Permissions" -msgstr "パーミッション" - -#: client/features/users/tokens/tokens.strings.js:41 -msgid "Personal Access Token" -msgstr "パーソナルアクセストークン" - -#: client/features/output/output.strings.js:63 -#: client/src/shared/form-generator.js:1085 -#: client/src/templates/job_templates/job-template.form.js:107 -#: client/src/templates/job_templates/job-template.form.js:115 -msgid "Playbook" -msgstr "Playbook" - -#: client/src/projects/projects.form.js:90 -msgid "Playbook Directory" -msgstr "Playbook ディレクトリー" - -#: client/features/templates/templates.strings.js:60 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:52 -msgid "Playbook Run" -msgstr "Playbook 実行" - -#: client/features/output/output.strings.js:91 -msgid "Plays" -msgstr "プレイ" - -#: client/lib/components/components.strings.js:108 -msgid "Please add items to this list." -msgstr "項目をこの一覧に追加してください。" - -#: client/src/users/users.form.js:128 -msgid "Please add user to an Organization." -msgstr "ユーザーを組織に追加してください。" - -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:100 -msgid "Please assign roles to the selected resources" -msgstr "ロールを選択したリソースに割り当ててください。" - -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:60 -msgid "Please assign roles to the selected users/teams" -msgstr "ロールを選択したユーザー/チームに割り当ててください。" - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:210 -msgid "" -"Please check the server and make sure the directory exists and file " -"permissions are set correctly." -msgstr "サーバーを確認し、ディレクトリーが存在し、ファイルのパーミッションが正常に設定されていることを確認してください。" - -#: client/src/license/license.partial.html:84 -msgid "" -"Please click the button below to visit Ansible's website to get a Tower " -"license key." -msgstr "以下のボタンをクリックし、Ansible の web サイトに移動して Tower ライセンスキーを取得します。" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:40 -msgid "Please click the icon to edit the host filter." -msgstr "アイコンをクリックしてホストフィルターを編集します。" - -#: client/features/templates/templates.strings.js:110 -msgid "Please click the start button to build your workflow." -msgstr "開始ボタンをクリックしてワークフローを構築してください。" - -#: client/src/shared/form-generator.js:868 -#: client/src/shared/form-generator.js:963 -msgid "" -"Please enter a URL that begins with ssh, http or https. The URL may not " -"contain the '@' character." -msgstr "ssh、http または https で始まる URL を入力します。URL には「@」文字を含めることはできません。" - -#: client/src/shared/form-generator.js:1178 -msgid "Please enter a number greater than %d and less than %d." -msgstr "%d より大きく、%d より小さい数値を入力してください。" - -#: client/src/shared/form-generator.js:1180 -msgid "Please enter a number greater than %d." -msgstr "%d より大きい数値を入力してください。" - -#: client/src/shared/form-generator.js:1172 -msgid "Please enter a number." -msgstr "数値を入力してください。" - -#: client/features/templates/templates.strings.js:39 -#: client/src/job-submission/job-submission.partial.html:112 -#: client/src/job-submission/job-submission.partial.html:126 -#: client/src/job-submission/job-submission.partial.html:140 -#: client/src/job-submission/job-submission.partial.html:154 -#: client/src/login/loginModal/loginModal.partial.html:78 -msgid "Please enter a password." -msgstr "パスワードを入力してください。" - -#: client/src/login/loginModal/loginModal.partial.html:58 -msgid "Please enter a username." -msgstr "ユーザー名を入力してください。" - -#: client/src/shared/form-generator.js:858 -#: client/src/shared/form-generator.js:953 -msgid "Please enter a valid email address." -msgstr "有効なメールアドレスを入力してください。" - -#: client/lib/components/components.strings.js:15 -#: client/src/shared/form-generator.js:1023 -#: client/src/shared/form-generator.js:853 -#: client/src/shared/form-generator.js:948 -msgid "Please enter a value." -msgstr "値を入力してください。" - -#: client/src/job-submission/job-submission.partial.html:289 -#: client/src/job-submission/job-submission.partial.html:294 -#: client/src/job-submission/job-submission.partial.html:305 -#: client/src/job-submission/job-submission.partial.html:311 -#: client/src/job-submission/job-submission.partial.html:317 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:14 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:19 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:30 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:36 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:42 -msgid "Please enter an answer between" -msgstr "次の範囲で回答を入力してください:" - -#: client/features/templates/templates.strings.js:59 -#: client/src/job-submission/job-submission.partial.html:316 -msgid "Please enter an answer that is a decimal number." -msgstr "10 進数の回答を入力してください。" - -#: client/features/templates/templates.strings.js:58 -#: client/src/job-submission/job-submission.partial.html:310 -msgid "Please enter an answer that is a valid integer." -msgstr "有効な整数の回答を入力してください。" - -#: client/features/templates/templates.strings.js:56 -#: client/src/job-submission/job-submission.partial.html:288 -#: client/src/job-submission/job-submission.partial.html:293 -#: client/src/job-submission/job-submission.partial.html:304 -#: client/src/job-submission/job-submission.partial.html:309 -#: client/src/job-submission/job-submission.partial.html:315 -msgid "Please enter an answer." -msgstr "回答を入力してください。" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:46 -msgid "Please enter at least one search term to create a new Smart Inventory." -msgstr "新規スマートインベントリーを作成するために 1 つ以上の検索語句を入力してください。" - -#: client/features/templates/templates.strings.js:111 -msgid "Please hover over a template for additional options." -msgstr "テンプレートにマウスオーバーして追加のオプションを確認してください。" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:169 -msgid "Please input a number greater than 1." -msgstr "1 より大きい数値を入力してください。" - -#: client/src/scheduler/scheduler.strings.js:47 -msgid "Please provide a valid date." -msgstr "有効な日付を指定してください。" - -#: client/src/scheduler/scheduler.strings.js:30 -msgid "Please provide a value between 1 and 999." -msgstr "1 から 999 の間の値を指定してください。" - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:54 -msgid "Please save before adding a survey to this job template." -msgstr "Survey をこのジョブテンプレートに追加する前に保存してください。" - -#: client/src/templates/workflows/add-workflow/workflow-add.controller.js:51 -msgid "Please save before adding a survey to this workflow." -msgstr "Survey をこのワークフローに追加する前に保存してください。" - -#: client/src/notifications/notifications.list.js:15 -msgid "Please save before adding notifications." -msgstr "通知を追加する前に保存してください。" - -#: client/src/organizations/organizations.form.js:80 -#: client/src/teams/teams.form.js:72 -msgid "Please save before adding users." -msgstr "ユーザーを追加する前に保存してください。" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:100 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:102 -#: client/src/projects/projects.form.js:239 client/src/teams/teams.form.js:116 -#: client/src/templates/job_templates/job-template.form.js:395 -#: client/src/templates/workflows.form.js:132 -msgid "Please save before assigning permissions." -msgstr "パーミッションを割り当てる前に保存してください。" - -#: client/src/users/users.form.js:126 client/src/users/users.form.js:185 -msgid "Please save before assigning to organizations." -msgstr "組織に割り当てる前に保存してください。" - -#: client/src/users/users.form.js:154 -msgid "Please save before assigning to teams." -msgstr "チームに割り当てる前に保存してください。" - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:147 -msgid "Please save before creating groups." -msgstr "グループを作成する前に保存してください。" - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:156 -msgid "Please save before creating hosts." -msgstr "ホストを作成する前に保存してください。" - -#: client/src/inventories-hosts/hosts/host.form.js:112 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:86 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:111 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:112 -msgid "Please save before defining groups." -msgstr "グループを定義する前に保存してください。" - -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:94 -msgid "Please save before defining hosts." -msgstr "ホストを定義する前に保存してください。" - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:165 -msgid "Please save before defining inventory sources." -msgstr "インベントリーソースを定義する前に保存してください。" - -#: client/src/templates/workflows/add-workflow/workflow-add.controller.js:50 -msgid "Please save before defining the workflow graph." -msgstr "ワークフローグラフを定義する前に保存してください。" - -#: client/src/inventories-hosts/hosts/host.form.js:121 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:120 -msgid "Please save before viewing Insights." -msgstr "Insights を表示する前に保存してください。" - -#: client/src/inventories-hosts/hosts/host.form.js:105 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:104 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:105 -msgid "Please save before viewing facts." -msgstr "ファクトを表示する前に保存してください。" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:146 -msgid "Please save before viewing hosts." -msgstr "ホストを表示する前に保存してください。" - -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:26 -msgid "Please select Users / Teams from the lists below." -msgstr "以下の一覧からユーザー/チームを選択してください。" - -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:29 -msgid "Please select Users from the list below." -msgstr "以下の一覧からユーザーを選択してください。" - -#: client/src/shared/form-generator.js:1213 -msgid "Please select a number between" -msgstr "Please select a number between" - -#: client/src/shared/form-generator.js:1209 -msgid "Please select a number." -msgstr "数値を選択してください。" - -#: client/features/templates/templates.strings.js:57 -msgid "Please select a value" -msgstr "値を選択してください。" - -#: client/src/shared/form-generator.js:1097 -#: client/src/shared/form-generator.js:1169 -#: client/src/shared/form-generator.js:1290 -#: client/src/shared/form-generator.js:1398 -msgid "Please select a value." -msgstr "値を選択してください。" - -#: client/src/templates/job_templates/job-template.form.js:77 -msgid "Please select an Inventory or check the Prompt on launch option." -msgstr "インベントリーを選択するか、または「起動プロンプト」オプションにチェックを付けてください。" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:39 -msgid "Please select an organization before editing the host filter." -msgstr "組織を選択してからホストフィルターを編集します。" - -#: client/src/shared/form-generator.js:1206 -msgid "Please select at least one value." -msgstr "1 つ以上の値を選択してください。" - -#: client/src/scheduler/scheduler.strings.js:43 -msgid "Please select one or more days." -msgstr "1 つまたは複数の曜日を選択してください。" - -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:30 -msgid "Please select resources from the lists below." -msgstr "以下の一覧からリソースを選択してください。" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:47 -msgid "Populate the hosts for this inventory by using a search filter." -msgstr "検索フィルターを使用してこのインベントリーのホストを設定します。" - -#: client/src/notifications/shared/type-change.service.js:29 -msgid "Port" -msgstr "ポート" - -#: client/features/templates/templates.strings.js:29 -msgid "Preview" -msgstr "プレビュー" - -#: client/src/credentials/credentials.form.js:257 -#: client/src/credentials/factories/kind-change.factory.js:21 -#: client/src/credentials/factories/kind-change.factory.js:45 -msgid "Private Key" -msgstr "秘密鍵" - -#: client/features/templates/templates.strings.js:42 -#: client/src/credentials/credentials.form.js:264 -#: client/src/job-submission/job-submission.partial.html:118 -msgid "Private Key Passphrase" -msgstr "秘密鍵のパスフレーズ" - -#: client/src/credentials/credentials.form.js:279 -#: client/src/credentials/credentials.form.js:283 -msgid "Privilege Escalation" -msgstr "権限昇格" - -#: client/features/templates/templates.strings.js:43 -#: client/src/credentials/credentials.form.js:305 -#: client/src/job-submission/job-submission.partial.html:132 -msgid "Privilege Escalation Password" -msgstr "権限昇格のパスワード" - -#: client/src/credentials/credentials.form.js:295 -msgid "Privilege Escalation Username" -msgstr "権限昇格のユーザー名" - -#: client/features/jobs/jobs.strings.js:15 -#: client/features/output/output.strings.js:64 -#: client/features/templates/templates.strings.js:17 -#: client/src/credentials/factories/become-method-change.factory.js:30 -#: client/src/credentials/factories/kind-change.factory.js:87 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:93 -#: client/src/templates/job_templates/job-template.form.js:100 -#: client/src/templates/job_templates/job-template.form.js:91 -msgid "Project" -msgstr "プロジェクト" - -#: client/src/credentials/factories/become-method-change.factory.js:53 -#: client/src/credentials/factories/kind-change.factory.js:110 -msgid "Project (Tenant Name)" -msgstr "プロジェクト (テナント名)" - -#: client/src/projects/projects.form.js:76 -#: client/src/projects/projects.form.js:84 -msgid "Project Base Path" -msgstr "プロジェクトのベースパス" - -#: client/src/credentials/credentials.form.js:365 -msgid "Project Name" -msgstr "プロジェクト名" - -#: client/src/projects/projects.form.js:101 -msgid "Project Path" -msgstr "プロジェクトパス" - -#: client/features/templates/templates.strings.js:103 -#: client/src/workflow-results/workflow-results.controller.js:67 -msgid "Project Sync" -msgstr "プロジェクトの同期" - -#: client/src/home/dashboard/counts/dashboard-counts.directive.js:66 -msgid "Project Sync Failures" -msgstr "プロジェクトの同期の失敗" - -#: client/src/projects/list/projects-list.controller.js:197 -msgid "Project lookup failed. GET returned:" -msgstr "プロジェクトの検索に失敗しました。GET で以下が返されました:" - -#: client/lib/components/components.strings.js:72 -#: client/lib/models/models.strings.js:40 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:115 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:47 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:33 -#: client/src/home/dashboard/counts/dashboard-counts.directive.js:61 -#: client/src/organizations/linkout/organizations-linkout.route.js:207 -#: client/src/organizations/linkout/organizations-linkout.route.js:209 -msgid "Projects" -msgstr "プロジェクト" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:18 -msgid "Promote group" -msgid_plural "Promote groups" -msgstr[0] "グループのプロモート" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:43 -msgid "Promote groups" -msgstr "グループのプロモート" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:32 -msgid "Promote groups and hosts" -msgstr "グループおよびホストのプロモート" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:20 -msgid "Promote host" -msgid_plural "Promote hosts" -msgstr[0] "ホストのプロモート" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:54 -msgid "Promote hosts" -msgstr "ホストのプロモート" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:10 -msgid "Promote {{ group }} and {{ host }}" -msgstr "{{ group }} および {{ host }} のプロモート" - -#: client/src/scheduler/scheduler.strings.js:54 -#: client/src/templates/survey-maker/shared/question-definition.form.js:27 -msgid "Prompt" -msgstr "プロンプト" - -#: client/lib/components/components.strings.js:34 -#: client/src/templates/job_templates/job-template.form.js:138 -#: client/src/templates/job_templates/job-template.form.js:168 -#: client/src/templates/job_templates/job-template.form.js:185 -#: client/src/templates/job_templates/job-template.form.js:202 -#: client/src/templates/job_templates/job-template.form.js:219 -#: client/src/templates/job_templates/job-template.form.js:270 -#: client/src/templates/job_templates/job-template.form.js:370 -#: client/src/templates/job_templates/job-template.form.js:60 -#: client/src/templates/job_templates/job-template.form.js:86 -msgid "Prompt on launch" -msgstr "起動プロンプト" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:238 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:253 -msgid "Provide a comma-separated list of filter expressions." -msgstr "フィルター式のカンマ区切りリストを指定します。" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:254 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:271 -msgid "" -"Provide a comma-separated list of filter expressions. Hosts are imported " -"when all of the filters match. Refer to Ansible Tower documentation for more" -" detail." -msgstr "" -"カンマ区切りのフィルター式の一覧を提供します。ホストは、フィルターのすべてが一致する場合にインポートされます。詳細については、Ansible Tower" -" ドキュメントを参照してください。" - -#: client/src/inventories-hosts/hosts/host.form.js:50 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:49 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:49 -msgid "Provide a host name, ip address, or ip address:port. Examples include:" -msgstr "host name、ip address、または ip address:port を指定してください。例:" - -#: client/src/templates/job_templates/job-template.form.js:162 -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" -" ドキュメントを参照してください。" - -#: client/features/credentials/credentials.strings.js:22 -msgid "" -"Provide account information using Google Compute Engine JSON credentials " -"file." -msgstr "Google Compute Engine JSON 認証情報ファイルを使用してアカウント情報を指定します。" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:196 -msgid "Provide environment variables to pass to the custom inventory script." -msgstr "カスタムインベントリースクリプトに渡す環境変数を指定します。" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:257 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:274 -msgid "" -"Provide the named URL encoded name or id of the remote Tower inventory to be" -" imported." -msgstr "インポートされるリモート Tower インベントリーの名前付き URL のエンコードされた名前または ID を指定します。" - -#: client/src/templates/job_templates/job-template.form.js:326 -#: client/src/templates/job_templates/job-template.form.js:334 -msgid "Provisioning Callback URL" -msgstr "プロビジョニングコールバック URL" - -#: client/src/notifications/add/add.controller.js:81 -#: client/src/notifications/edit/edit.controller.js:128 -msgid "Purple" -msgstr "紫" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:158 -msgid "RADIUS" -msgstr "RADIUS" - -#: client/src/instance-groups/instance-groups.strings.js:47 -msgid "RAM" -msgstr "RAM" - -#: client/lib/components/code-mirror/code-mirror.strings.js:13 -msgid "READ ONLY" -msgstr "読み取り専用" - -#: client/src/home/dashboard/lists/jobs/jobs-list.partial.html:4 -msgid "RECENT JOB RUNS" -msgstr "最近のジョブの実行" - -#: client/src/home/dashboard/lists/jobs/jobs-list.partial.html:40 -msgid "RECENTLY RUN JOBS" -msgstr "最近実行されたジョブ" - -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:47 -msgid "RECENTLY USED JOB TEMPLATES" -msgstr "最近使用されたジョブテンプレート" - -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:4 -msgid "RECENTLY USED TEMPLATES" -msgstr "最近使用されたテンプレート" - -#: client/src/activity-stream/streams.list.js:54 -#: client/src/inventories-hosts/hosts/host.list.js:102 -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.list.js:46 -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:47 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:47 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:113 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:47 -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:54 -#: client/src/projects/projects.list.js:70 -#: client/src/scheduler/schedules.list.js:69 -msgid "REFRESH" -msgstr "更新" - -#: client/features/users/tokens/tokens.strings.js:23 -msgid "REFRESH TOKEN" -msgstr "トークンの更新" - -#: client/src/shared/smart-search/smart-search.partial.html:45 -msgid "RELATED FIELDS:" -msgstr "関連フィールド:" - -#: client/src/shared/directives.js:94 -msgid "REMOVE" -msgstr "削除" - -#: client/lib/components/components.strings.js:7 -msgid "REPLACE" -msgstr "置換" - -#: client/features/output/output.strings.js:8 -msgid "RESULTS" -msgstr "結果" - -#: client/features/templates/templates.strings.js:35 -#: client/lib/components/components.strings.js:8 -#: client/src/job-submission/job-submission.partial.html:44 -#: client/src/job-submission/job-submission.partial.html:87 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html:50 -msgid "REVERT" -msgstr "元に戻す" - -#: client/src/credentials/factories/become-method-change.factory.js:25 -#: client/src/credentials/factories/kind-change.factory.js:82 -msgid "RSA Private Key" -msgstr "RSA 秘密鍵" - -#: client/features/templates/templates.strings.js:112 -msgid "RUN" -msgstr "実行" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.route.js:28 -msgid "RUN COMMAND" -msgstr "コマンドの実行" - -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:56 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:56 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:101 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:114 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:56 -msgid "RUN COMMANDS" -msgstr "コマンドの実行" - -#: client/src/notifications/add/add.controller.js:84 -#: client/src/notifications/edit/edit.controller.js:131 -msgid "Random" -msgstr "ランダム" - -#: client/features/users/tokens/tokens.strings.js:30 -msgid "Read" -msgstr "読み取り" - -#: client/src/workflow-results/workflow-results.controller.js:121 -msgid "Read only view of extra variables added to the workflow." -msgstr "追加変数の読み取り専用ビューがワークフローに追加されました。" - -#: client/features/output/output.strings.js:23 -#: client/lib/components/code-mirror/code-mirror.strings.js:49 -msgid "Read-only view of extra variables added to the job template." -msgstr "追加変数の読み取り専用ビューがジョブテンプレートに追加されました。" - -#: client/src/notifications/notificationTemplates.list.js:26 -msgid "Recent Notifications" -msgstr "最近の通知" - -#: client/src/notifications/notificationTemplates.form.js:94 -#: client/src/notifications/notificationTemplates.form.js:98 -msgid "Recipient List" -msgstr "受信者リスト" - -#: client/src/notifications/add/add.controller.js:82 -#: client/src/notifications/edit/edit.controller.js:129 -msgid "Red" -msgstr "赤" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:47 -msgid "" -"Refer to the Ansible Tower documentation for further syntax and examples." -msgstr "追加の構文およびサンプルについては、Ansible Tower ドキュメントを参照してください。" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:245 -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:270 -msgid "Refresh" -msgstr "更新" - -#: client/src/activity-stream/streams.list.js:51 -#: client/src/bread-crumb/bread-crumb.partial.html:6 -#: client/src/inventories-hosts/hosts/host.list.js:98 -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.list.js:42 -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:43 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:43 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:109 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:43 -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:50 -#: client/src/projects/projects.list.js:66 -#: client/src/scheduler/schedules.list.js:65 -msgid "Refresh the page" -msgstr "ページの更新" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:231 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:245 -msgid "Region:" -msgstr "リージョン:" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:131 -msgid "Regions" -msgstr "リージョン" - -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:65 -msgid "Related Groups" -msgstr "関連するグループ" - -#: client/lib/components/components.strings.js:98 -msgid "Relaunch On" -msgstr "再起動時" - -#: client/lib/components/components.strings.js:97 -msgid "Relaunch using host parameters" -msgstr "ホストパラメーターを使用した再起動" - -#: client/lib/components/components.strings.js:96 -#: client/src/workflow-results/workflow-results.controller.js:37 -msgid "Relaunch using the same parameters" -msgstr "同一パラメーターを使用した再起動" - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:180 -msgid "Remediate Inventory" -msgstr "インベントリーの修復" - -#: client/src/access/add-rbac-user-team/rbac-selected-list.directive.js:102 -#: client/src/access/add-rbac-user-team/rbac-selected-list.directive.js:103 -#: client/src/teams/teams.form.js:145 client/src/users/users.form.js:224 -msgid "Remove" -msgstr "削除" - -#: client/src/projects/projects.form.js:159 -msgid "Remove any local modifications prior to performing an update." -msgstr "更新の実行前にローカルの変更を削除します。" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:149 -#: client/src/scheduler/scheduler.strings.js:27 -msgid "Repeat frequency" -msgstr "繰り返しの頻度" - -#: client/src/license/license.partial.html:89 -msgid "Request License" -msgstr "ライセンスの要求" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:291 -msgid "Required" -msgstr "必須" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:154 -msgid "Reset" -msgstr "リセット" - -#: client/lib/components/components.strings.js:90 -msgid "Resources" -msgstr "リソース" - -#: client/features/templates/templates.strings.js:85 -#: client/src/scheduler/schedules.list.js:24 -msgid "Resources are missing from this template." -msgstr "リソースがこのテンプレートにありません。" - -#: client/lib/services/base-string.service.js:88 -msgid "Return" -msgstr "戻る" - -#: client/features/users/tokens/tokens.strings.js:26 -msgid "Returned status:" -msgstr "返されたステータス:" - -#: client/src/shared/form-generator.js:697 -msgid "Revert" -msgstr "元に戻す" - -#: client/src/configuration/auth-form/sub-forms/auth-azure.form.js:47 -#: client/src/configuration/auth-form/sub-forms/auth-github-org.form.js:51 -#: client/src/configuration/auth-form/sub-forms/auth-github-team.form.js:51 -#: client/src/configuration/auth-form/sub-forms/auth-github.form.js:47 -#: client/src/configuration/auth-form/sub-forms/auth-google-oauth2.form.js:59 -#: client/src/configuration/auth-form/sub-forms/auth-ldap.form.js:102 -#: client/src/configuration/auth-form/sub-forms/auth-ldap1.form.js:102 -#: client/src/configuration/auth-form/sub-forms/auth-ldap2.form.js:102 -#: client/src/configuration/auth-form/sub-forms/auth-ldap3.form.js:102 -#: client/src/configuration/auth-form/sub-forms/auth-ldap4.form.js:102 -#: client/src/configuration/auth-form/sub-forms/auth-ldap5.form.js:102 -#: client/src/configuration/auth-form/sub-forms/auth-radius.form.js:34 -#: client/src/configuration/auth-form/sub-forms/auth-saml.form.js:121 -#: client/src/configuration/auth-form/sub-forms/auth-tacacs.form.js:47 -#: client/src/configuration/jobs-form/configuration-jobs.form.js:73 -#: client/src/configuration/system-form/sub-forms/system-activity-stream.form.js:26 -#: client/src/configuration/system-form/sub-forms/system-logging.form.js:74 -#: client/src/configuration/system-form/sub-forms/system-misc.form.js:56 -#: client/src/configuration/ui-form/configuration-ui.form.js:36 -msgid "Revert all to default" -msgstr "すべてをデフォルトに戻す" - -#: client/features/output/output.strings.js:66 -#: client/src/projects/projects.list.js:50 -msgid "Revision" -msgstr "リビジョン" - -#: client/src/projects/add/projects-add.controller.js:155 -#: client/src/projects/edit/projects-edit.controller.js:290 -msgid "Revision #" -msgstr "リビジョン #" - -#: client/features/credentials/legacy.credentials.js:85 -#: client/src/credentials/credentials.form.js:462 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:130 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:132 -#: client/src/organizations/organizations.form.js:109 -#: client/src/projects/projects.form.js:270 client/src/teams/teams.form.js:101 -#: client/src/teams/teams.form.js:138 -#: client/src/templates/workflows.form.js:163 -#: client/src/users/users.form.js:207 -msgid "Role" -msgstr "ロール" - -#: client/src/instance-groups/instance-groups.list.js:26 -#: client/src/instance-groups/instance-groups.strings.js:18 -#: client/src/instance-groups/instance-groups.strings.js:53 -msgid "Running Jobs" -msgstr "実行中のジョブ" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:159 -msgid "SAML" -msgstr "SAML" - -#: client/lib/services/base-string.service.js:62 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal.partial.html:17 -#: client/src/inventories-hosts/shared/associate-groups/associate-groups.partial.html:17 -#: client/src/inventories-hosts/shared/associate-hosts/associate-hosts.partial.html:17 -#: client/src/partials/survey-maker-modal.html:87 -#: client/src/shared/instance-groups-multiselect/instance-groups-modal/instance-groups-modal.partial.html:18 -msgid "SAVE" -msgstr "保存" - -#: client/src/scheduler/scheduled-jobs.list.js:13 -#: client/src/scheduler/scheduled-jobs.list.js:14 -msgid "SCHEDULED JOBS" -msgstr "スケジュール済みジョブ" - -#: client/src/activity-stream/get-target-title.factory.js:38 -#: client/src/inventories-hosts/inventories/related/sources/list/schedule/sources-schedule.route.js:9 -#: client/src/management-jobs/scheduler/main.js:28 -#: client/src/management-jobs/scheduler/main.js:34 -#: client/src/scheduler/schedules.route.js:102 -#: client/src/scheduler/schedules.route.js:14 -#: client/src/scheduler/schedules.route.js:189 -#: client/src/scheduler/schedules.route.js:284 -msgid "SCHEDULES" -msgstr "スケジュール" - -#: client/src/projects/add/projects-add.controller.js:127 -#: client/src/projects/edit/projects-edit.controller.js:263 -msgid "SCM Branch" -msgstr "SCM ブランチ" - -#: client/src/projects/add/projects-add.controller.js:146 -#: client/src/projects/edit/projects-edit.controller.js:281 -msgid "SCM Branch/Tag/Commit" -msgstr "SCM ブランチ/タグ/コミット" - -#: client/src/projects/add/projects-add.controller.js:167 -#: client/src/projects/edit/projects-edit.controller.js:302 -msgid "SCM Branch/Tag/Revision" -msgstr "SCM ブランチ/タグ/リビジョン" - -#: client/src/projects/projects.form.js:160 -msgid "SCM Clean" -msgstr "SCM クリーニング" - -#: client/src/projects/projects.form.js:171 -msgid "SCM Delete" -msgstr "SCM 削除" - -#: client/src/credentials/factories/become-method-change.factory.js:20 -#: client/src/credentials/factories/kind-change.factory.js:77 -msgid "SCM Private Key" -msgstr "SCM 秘密鍵" - -#: client/src/projects/projects.form.js:56 -msgid "SCM Type" -msgstr "SCM タイプ" - -#: client/src/projects/projects.form.js:107 -msgid "SCM URL" -msgstr "SCM URL" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:49 -#: client/src/projects/projects.form.js:181 -msgid "SCM Update" -msgstr "SCM 更新" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:224 -#: client/src/projects/list/projects-list.controller.js:267 -msgid "SCM Update Cancel" -msgstr "SCM 更新の取り消し" - -#: client/src/projects/projects.form.js:151 -msgid "SCM Update Options" -msgstr "SCM 更新オプション" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:119 -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:46 -#: client/src/projects/edit/projects-edit.controller.js:139 -#: client/src/projects/list/projects-list.controller.js:118 -#: client/src/projects/list/projects-list.controller.js:85 -msgid "SCM update currently running" -msgstr "現在実行中の SCM 更新" - -#: client/features/users/tokens/tokens.strings.js:39 -msgid "SCOPE" -msgstr "スコープ" - -#: client/features/output/output.strings.js:83 -msgid "SEARCH" -msgstr "検索" - -#: client/features/templates/templates.strings.js:114 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html:74 -msgid "SELECT" -msgstr "選択" - -#: client/features/credentials/credentials.strings.js:20 -msgid "SELECT A CREDENTIAL TYPE" -msgstr "認証情報タイプの選択" - -#: client/features/users/tokens/tokens.strings.js:19 -msgid "SELECT AN APPLICATION" -msgstr "アプリケーションの選択" - -#: client/features/applications/applications.strings.js:35 -#: client/features/credentials/credentials.strings.js:19 -msgid "SELECT AN ORGANIZATION" -msgstr "組織の選択" - -#: client/src/inventories-hosts/shared/associate-groups/associate-groups.partial.html:6 -msgid "SELECT GROUPS" -msgstr "グループの選択" - -#: client/src/inventories-hosts/shared/associate-hosts/associate-hosts.partial.html:6 -msgid "SELECT HOSTS" -msgstr "ホストの選択" - -#: client/src/instance-groups/instance-groups.strings.js:36 -msgid "SELECT INSTANCE" -msgstr "インスタンスの選択" - -#: client/features/templates/templates.strings.js:32 -msgid "SELECTED" -msgstr "選択済み" - -#: client/src/job-submission/job-submission.partial.html:29 -#: client/src/job-submission/job-submission.partial.html:56 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html:18 -msgid "SELECTED:" -msgstr "選択済み:" - -#: client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html:20 -msgid "SETTING CATEGORY" -msgstr "カテゴリーの設定" - -#: client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html:24 -msgid "SETTING NAME" -msgstr "名前の設定" - -#: client/lib/components/components.strings.js:11 -#: client/lib/services/base-string.service.js:65 -#: client/src/templates/survey-maker/surveys/init.factory.js:486 -msgid "SHOW" -msgstr "表示" - -#: client/src/login/loginModal/loginModal.partial.html:97 -msgid "SIGN IN" -msgstr "サインイン" - -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.partial.html:2 -msgid "SIGN IN WITH" -msgstr "サインイン:" - -#: client/src/inventories-hosts/hosts/host.list.js:110 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:14 -msgid "SMART INVENTORY" -msgstr "スマートインベントリー" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.route.js:27 -msgid "SOURCES" -msgstr "ソース" - -#: client/src/credentials/factories/become-method-change.factory.js:89 -#: client/src/credentials/factories/kind-change.factory.js:146 -msgid "SSH Key" -msgstr "SSH キー" - -#: client/features/templates/templates.strings.js:41 -msgid "SSH Password" -msgstr "SSH パスワード" - -#: client/src/credentials/credentials.form.js:255 -msgid "SSH key description" -msgstr "SSH キーの説明" - -#: client/src/notifications/notificationTemplates.form.js:446 -msgid "SSL Connection" -msgstr "SSL 接続" - -#: client/features/templates/templates.strings.js:117 -msgid "START" -msgstr "開始" - -#: client/src/smart-status/smart-status.controller.js:43 -msgid "STATUS" -msgstr "ステータス" - -#: client/src/credentials/credentials.form.js:119 -#: client/src/credentials/credentials.form.js:127 -msgid "STS Token" -msgstr "STS トークン" - -#: client/src/home/dashboard/graphs/job-status/job-status-graph.directive.js:62 -msgid "SUCCESSFUL" -msgstr "成功" - -#: client/src/partials/survey-maker-modal.html:24 -msgid "SURVEY" -msgstr "Survey " - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:62 -msgid "SYNC ALL" -msgstr "すべてを同期する" - -#: client/src/system-tracking/system-tracking.route.js:18 -msgid "SYSTEM TRACKING" -msgstr "システムトラッキング" - -#: client/src/scheduler/scheduler.strings.js:42 -msgid "Sat" -msgstr "土" - -#: client/src/credentials/factories/become-method-change.factory.js:70 -#: client/src/credentials/factories/kind-change.factory.js:127 -msgid "Satellite 6 URL" -msgstr "Satellite 6 URL" - -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:110 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:193 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:158 -#: client/src/scheduler/scheduler.strings.js:57 -#: client/src/shared/form-generator.js:1711 -msgid "Save" -msgstr "保存" - -#: client/src/configuration/configuration.controller.js:516 -msgid "Save Complete" -msgstr "保存が完了しました" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:105 -#: client/src/configuration/configuration.controller.js:242 -#: client/src/configuration/configuration.controller.js:326 -#: client/src/configuration/system-form/configuration-system.controller.js:68 -msgid "Save changes" -msgstr "変更の保存" - -#: client/src/license/license.partial.html:140 -msgid "Save successful!" -msgstr "正常に保存が実行されました!" - -#: client/src/scheduler/scheduler.strings.js:50 -msgid "Schedule Description" -msgstr "スケジュールの詳細" - -#: client/src/management-jobs/card/card.partial.html:28 -msgid "Schedule Management Job" -msgstr "管理ジョブのスケジュール" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:7 -msgid "Schedule inventory syncs" -msgstr "インベントリー同期のスケジュール" - -#: client/src/scheduler/scheduler.strings.js:14 -msgid "Schedule is active." -msgstr "スケジュールはアクティブです。" - -#: client/src/scheduler/scheduler.strings.js:15 -msgid "Schedule is active. Click to stop." -msgstr "スケジュールはアクティブです。クリックして停止してください。" - -#: client/src/scheduler/scheduler.strings.js:16 -msgid "Schedule is stopped." -msgstr "スケジュールは停止しています。" - -#: client/src/scheduler/scheduler.strings.js:17 -msgid "Schedule is stopped. Click to activate." -msgstr "スケジュールは停止しています。クリックしてアクティブにしてください。" - -#: client/lib/components/components.strings.js:70 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:35 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:440 -#: client/src/projects/projects.form.js:290 -#: client/src/templates/job_templates/job-template.form.js:448 -#: client/src/templates/workflows.form.js:185 -msgid "Schedules" -msgstr "スケジュール" - -#: client/src/shared/smart-search/smart-search.controller.js:122 -#: client/src/shared/smart-search/smart-search.controller.js:164 -msgid "Search" -msgstr "検索" - -#: client/src/credentials/credentials.form.js:104 -msgid "Secret Key" -msgstr "シークレットキー" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:232 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:246 -msgid "Security Group:" -msgstr "セキュリティーグループ:" - -#: client/src/credentials/credentials.form.js:124 -msgid "" -"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." -msgstr "" -"セキュリティートークンサービス (STS) は、AWS Identity and Access Management (IAM) " -"ユーザーの一時的な、権限の制限された認証情報を要求できる web サービスです。" - -#: client/src/shared/form-generator.js:1715 -#: client/src/shared/lookup/lookup-modal.directive.js:59 -#: client/src/shared/lookup/lookup-modal.partial.html:20 -msgid "Select" -msgstr "選択" - -#: client/src/shared/instance-groups-multiselect/instance-groups-modal/instance-groups-modal.partial.html:5 -msgid "Select Instance Groups" -msgstr "インスタンスグループの選択" - -#: client/src/job-submission/job-submission.directive.js:65 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js:66 -msgid "Select a credential" -msgstr "認証情報の選択" - -#: client/src/access/add-rbac-user-team/rbac-user-team.controller.js:68 -msgid "Select a role" -msgstr "ロールの選択" - -#: client/features/users/tokens/tokens.strings.js:29 -msgid "Select a scope" -msgstr "スコープの選択" - -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:53 -msgid "" -"Select an inventory source by clicking the check box beside it. The " -"inventory source can be a single group or a selection of multiple groups." -msgstr "" -"チェックボックスをクリックしてインベントリーソースを選択します。インベントリーソースは単一グループにすることも、複数グループのセレクションにすることもできます。" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:53 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:98 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:53 -msgid "" -"Select an inventory source by clicking the check box beside it. The " -"inventory source can be a single group or host, a selection of multiple " -"hosts, or a selection of multiple groups." -msgstr "" -"チェックボックスをクリックしてインベントリーソースを選択します。インベントリーソースは単一グループや複数ホストのセレクション、または複数グループのセレクションにすることができます。" - -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:111 -msgid "" -"Select an inventory source by clicking the check box beside it. The " -"inventory source can be a single host or a selection of multiple hosts." -msgstr "" -"チェックボックスをクリックしてインベントリーソースを選択します。インベントリーソースは単一ホストにすることも、複数ホストのセレクションにすることもできます。" - -#: client/src/configuration/jobs-form/configuration-jobs.controller.js:109 -#: client/src/configuration/jobs-form/configuration-jobs.controller.js:134 -#: client/src/configuration/ui-form/configuration-ui.controller.js:95 -msgid "Select commands" -msgstr "コマンドの選択" - -#: client/src/templates/job_templates/job-template.form.js:132 -msgid "" -"Select credentials that allow Tower to access 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 "" -"Tower のこのジョブが実行されるノードへのアクセスを許可する認証情報を選択します。各タイプにつき 1 つの認証情報のみを選択できます。マシン認証情報" -" (SSH) " -"については、認証情報を選択せずに「起動プロンプト」を選択すると、実行時にマシン認証情報を選択する必要があります。認証情報を選択し、「起動プロンプト」にチェックを付けている場合、選択した認証情報が実行時に更新できるデフォルトになります。" - -#: client/src/projects/projects.form.js:99 -msgid "" -"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." -msgstr "" -"プロジェクトのベースパスにあるデイレクトリーの一覧から選択します。ベースパスと Playbook ディレクトリーは、Playbook " -"を見つけるために使用される完全なパスを提供します。" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:370 -#: client/src/configuration/auth-form/configuration-auth.controller.js:389 -msgid "Select group types" -msgstr "グループタイプの選択" - -#: client/src/access/rbac-multiselect/rbac-multiselect-role.directive.js:24 -msgid "Select roles" -msgstr "ロールの選択" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:56 -msgid "Select the Instance Groups for this Inventory to run on." -msgstr "このインベントリーが実行されるインスタンスグループを選択します。" - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:63 -msgid "" -"Select the Instance Groups for this Inventory to run on. Refer to the " -"Ansible Tower documentation for more detail." -msgstr "" -"このインベントリーが実行されるインスタンスグループを選択します。詳細については、Ansible Tower ドキュメントを参照してください。" - -#: client/src/templates/job_templates/job-template.form.js:254 -msgid "Select the Instance Groups for this Job Template to run on." -msgstr "このジョブテンプレートが実行されるインスタンスグループを選択します。" - -#: client/src/organizations/organizations.form.js:40 -msgid "Select the Instance Groups for this Organization to run on." -msgstr "この組織が実行されるインスタンスグループを選択します。" - -#: client/src/templates/job_templates/job-template.form.js:244 -msgid "" -"Select the custom Python virtual environment for this job template to run " -"on." -msgstr "このジョブテンプレートの実行に使用するカスタム Python 仮想環境を選択します。" - -#: client/src/organizations/organizations.form.js:51 -msgid "" -"Select the custom Python virtual environment for this organization to run " -"on." -msgstr "この組織の実行に使用するカスタム Python 仮想環境を選択します。" - -#: client/src/projects/projects.form.js:211 -msgid "" -"Select the custom Python virtual environment for this project to run on." -msgstr "このプロジェクトの実行に使用する Python 仮想環境を選択します。" - -#: client/src/templates/job_templates/job-template.form.js:79 -msgid "Select the inventory containing the hosts you want this job to manage." -msgstr "このジョブで管理するホストが含まれるインベントリーを選択してください。" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:122 -msgid "" -"Select the inventory file to be synced by this source. You can select from " -"the dropdown or enter a file within the input." -msgstr "このソースで同期されるインベントリーファイルを選択します。ドロップダウンから選択するか、入力にファイルを指定できます。" - -#: client/src/templates/job_templates/job-template.form.js:114 -msgid "Select the playbook to be executed by this job." -msgstr "このジョブで実行される Playbook を選択してください。" - -#: client/src/templates/job_templates/job-template.form.js:99 -msgid "" -"Select the project containing the playbook you want this job to execute." -msgstr "このジョブで実行する Playbook が含まれるプロジェクトを選択してください。" - -#: client/src/configuration/system-form/configuration-system.controller.js:197 -msgid "Select types" -msgstr "タイプの選択" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:224 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:238 -msgid "Select which groups to create automatically." -msgstr "自動作成するグループを選択します。" - -#: client/src/notifications/notificationTemplates.form.js:110 -msgid "Sender Email" -msgstr "送信者のメール" - -#: client/src/credentials/factories/become-method-change.factory.js:24 -#: client/src/credentials/factories/kind-change.factory.js:81 -msgid "Service Account Email Address" -msgstr "サービスアカウントのメールアドレス" - -#: client/features/credentials/credentials.strings.js:21 -msgid "Service Account JSON File" -msgstr "サービスアカウント JSON ファイル" - -#: client/lib/components/components.strings.js:86 -msgid "Settings" -msgstr "設定" - -#: client/src/job-submission/job-submission.partial.html:108 -#: client/src/job-submission/job-submission.partial.html:122 -#: client/src/job-submission/job-submission.partial.html:136 -#: client/src/job-submission/job-submission.partial.html:150 -#: client/src/job-submission/job-submission.partial.html:299 -#: client/src/shared/form-generator.js:883 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:24 -msgid "Show" -msgstr "表示" - -#: client/features/templates/templates.strings.js:46 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:115 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:118 -#: client/src/templates/job_templates/job-template.form.js:261 -#: client/src/templates/job_templates/job-template.form.js:264 -msgid "Show Changes" -msgstr "変更の表示" - -#: client/features/output/output.strings.js:38 -msgid "Show Less" -msgstr "簡易表示" - -#: client/features/output/output.strings.js:39 -msgid "Show More" -msgstr "詳細を表示" - -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:33 -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:44 -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:55 -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:76 -msgid "Sign in with %s" -msgstr "%s でサインイン" - -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:63 -msgid "Sign in with %s Organizations" -msgstr "%s 組織でサインイン" - -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:61 -msgid "Sign in with %s Teams" -msgstr "%s チームでサインイン" - -#: client/features/output/output.strings.js:67 -#: client/features/templates/templates.strings.js:47 -#: client/src/job-submission/job-submission.partial.html:245 -#: client/src/templates/job_templates/job-template.form.js:207 -#: client/src/templates/job_templates/job-template.form.js:214 -msgid "Skip Tags" -msgstr "スキップタグ" - -#: client/src/templates/job_templates/job-template.form.js:213 -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 Ansible Tower documentation for details on the usage of tags." -msgstr "" -"スキップタグは、Playbook " -"のサイズが大きい場合にプレイまたはタスクの特定の部分をスキップする必要がある場合に役立ちます。カンマを使って複数のタグを区切ります。タグの使用方法の詳細については、Ansible" -" Tower ドキュメントを参照してください。" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:44 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:48 -msgid "Smart Host Filter" -msgstr "スマートホストフィルター" - -#: client/src/inventories-hosts/inventories/inventory.list.js:86 -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:76 -#: client/src/organizations/linkout/controllers/organizations-inventories.controller.js:70 -#: client/src/shared/form-generator.js:1476 -msgid "Smart Inventory" -msgstr "スマートインベントリー" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:44 -msgid "Solvable With Playbook" -msgstr "Playbook で解決可能" - -#: client/features/output/output.strings.js:68 -#: client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.directive.js:57 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:64 -msgid "Source" -msgstr "ソース" - -#: client/src/credentials/credentials.form.js:75 -msgid "Source Control" -msgstr "ソースコントロール" - -#: client/features/output/output.strings.js:69 -msgid "Source Credential" -msgstr "ソース認証情報" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:47 -#: client/src/projects/projects.form.js:26 -msgid "Source Details" -msgstr "ソース詳細" - -#: client/src/notifications/notificationTemplates.form.js:192 -#: client/src/notifications/notificationTemplates.form.js:193 -msgid "Source Phone Number" -msgstr "発信元の電話番号" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:136 -msgid "Source Regions" -msgstr "ソースリージョン" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:209 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:216 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:233 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:240 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:257 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:264 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:274 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:281 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:291 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:298 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:308 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:315 -msgid "Source Variables" -msgstr "ソース変数" - -#: client/src/partials/logviewer.html:9 -msgid "Source Vars" -msgstr "ソース変数" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:34 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:167 -msgid "Sources" -msgstr "ソース" - -#: client/src/notifications/notificationTemplates.form.js:330 -msgid "" -"Specify HTTP Headers in JSON format. Refer to the Ansible Tower " -"documentation for example syntax." -msgstr "JSON 形式で HTTP ヘッダーを指定します。構文のサンプルについては Ansible Tower ドキュメントを参照してください。" - -#: client/src/credentials/credentials.form.js:285 -msgid "" -"Specify a method for %s operations. This is equivalent to specifying the %s " -"parameter, where %s could be %s" -msgstr "%s 操作のメソッドを指定します。これは %s を指定することに相当します。%s は %s にすることができます。" - -#: client/src/notifications/notificationTemplates.form.js:478 -msgid "" -"Specify a notification color. Acceptable colors are hex color code (example:" -" #3af or #789abc) ." -msgstr "通知の色を指定します。使用できる色: 16 進数の色コード (例: #3af または #789abc) " - -#: client/src/notifications/notificationTemplates.form.js:292 -msgid "" -"Specify a notification color. Acceptable colors are: yellow, green, red " -"purple, gray or random." -msgstr "通知の色を指定します。使用できる色: 黄、緑、赤紫、灰色、またはランダム" - -#: client/features/users/tokens/tokens.strings.js:20 -msgid "Specify a scope for the token's access" -msgstr "トークンのアクセスのスコープを指定します" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:253 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:270 -msgid "" -"Specify which groups to create automatically. Group names will be created " -"similar to the options selected. If blank, all groups above are created. " -"Refer to Ansible Tower documentation for more detail." -msgstr "" -"自動作成するグループを指定します。選択したオプションと同様のグループ名が作成されます。空白の場合は、上のすべてのグループが作成されます。詳細については、Ansible" -" Tower ドキュメントを参照してください。" - -#: client/features/output/output.strings.js:108 -msgid "Standard Error" -msgstr "標準エラー" - -#: client/features/output/output.strings.js:107 -#: client/src/partials/logviewer.html:5 -msgid "Standard Out" -msgstr "標準出力" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:41 -#: client/src/scheduler/scheduler.strings.js:23 -msgid "Start Date" -msgstr "開始日" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:56 -#: client/src/scheduler/scheduler.strings.js:24 -msgid "Start Time" -msgstr "開始時間" - -#: client/lib/components/components.strings.js:104 -msgid "Start a job using this template" -msgstr "このテンプレートを使用したジョブの開始" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:6 -msgid "Start sync process" -msgstr "同期プロセスの開始" - -#: client/features/jobs/jobs.strings.js:9 -#: client/features/output/output.strings.js:70 -#: client/src/workflow-results/workflow-results.controller.js:49 -msgid "Started" -msgstr "開始日時" - -#: client/features/output/output.strings.js:71 -#: client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.directive.js:53 -#: client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.directive.js:55 -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:43 -#: client/src/notifications/notification-templates-list/list.controller.js:71 -#: client/src/partials/logviewer.html:4 -#: client/src/workflow-results/workflow-results.controller.js:52 -msgid "Status" -msgstr "ステータス" - -#: client/src/configuration/auth-form/configuration-auth.partial.html:4 -msgid "Sub Category" -msgstr "サブカテゴリー" - -#: client/src/license/license.partial.html:139 -msgid "Submit" -msgstr "送信" - -#: client/src/license/license.partial.html:27 -msgid "Subscription" -msgstr "サブスクリプション" - -#: client/src/credentials/credentials.form.js:151 -#: client/src/credentials/credentials.form.js:162 -msgid "Subscription ID" -msgstr "サブスクリプション ID" - -#: client/src/credentials/credentials.form.js:161 -msgid "Subscription ID is an Azure construct, which is mapped to a username." -msgstr "サブスクリプション ID は、ユーザー名にマップされる Azure コンストラクトです。" - -#: client/src/notifications/notifications.list.js:39 -msgid "Success" -msgstr "成功" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:77 -msgid "Successful" -msgstr "成功" - -#: client/src/scheduler/scheduler.strings.js:36 -msgid "Sun" -msgstr "日" - -#: client/features/templates/templates.strings.js:28 -#: client/src/job-submission/job-submission.partial.html:20 -msgid "Survey" -msgstr "Survey" - -#: client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js:62 -#: client/src/templates/workflows/edit-workflow/workflow-edit.controller.js:262 -msgid "" -"Surveys allow users to be prompted at job launch with a series of questions " -"related to the job. This allows for variables to be defined that affect the " -"playbook run at time of launch." -msgstr "" -"Survey により、ジョブに関連する一連の質問によるジョブ起動時のユーザーのプロモートが可能になります。これにより、起動時の Playbook " -"実行に影響を与える変数を定義できます。" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:58 -msgid "Sync all inventory sources" -msgstr "すべてのインベントリーソースの同期" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:29 -msgid "Sync canceled. Click to view log." -msgstr "同期が取り消されました。クリックしてログを表示します。" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:35 -msgid "Sync completed. Click to view log." -msgstr "同期が完了しました。クリックしてログを表示します。" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:32 -msgid "Sync failed. Click to view log." -msgstr "同期が失敗しました。クリックしてログを表示します。" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:17 -msgid "Sync not performed. Click" -msgstr "同期が実行されていません。次をクリックします。" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:38 -msgid "Sync pending." -msgstr "同期が保留中です。" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:45 -msgid "Sync running" -msgstr "同期が実行中です。" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:46 -msgid "Sync running. Click to view log." -msgstr "同期が実行中です。クリックしてログを表示します。" - -#: client/src/configuration/configuration.partial.html:29 -msgid "System" -msgstr "システム" - -#: client/src/users/add/users-add.controller.js:12 -#: client/src/users/edit/users-edit.controller.js:12 -#: client/src/users/list/users-list.controller.js:12 -msgid "System Administrator" -msgstr "システム管理者" - -#: client/src/users/add/users-add.controller.js:11 -#: client/src/users/edit/users-edit.controller.js:11 -#: client/src/users/list/users-list.controller.js:11 -msgid "System Auditor" -msgstr "システム監査者" - -#: client/src/configuration/configuration.partial.html:3 -msgid "System auditors have read-only permissions in this section." -msgstr "システム監査者はこのセクションで読み取り専用パーミッションを持ちます。" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:160 -msgid "TACACS+" -msgstr "TACACS+" - -#: client/features/output/output.strings.js:104 -msgid "TASK" -msgstr "タスク" - -#: client/src/activity-stream/get-target-title.factory.js:23 -#: client/src/organizations/linkout/organizations-linkout.route.js:98 -#: client/src/organizations/list/organizations-list.controller.js:61 -#: client/src/teams/main.js:65 client/src/teams/teams.list.js:14 -#: client/src/teams/teams.list.js:15 -msgid "TEAMS" -msgstr "チーム" - -#: client/features/templates/routes/templatesList.route.js:12 -#: client/features/templates/templates.strings.js:12 -#: client/features/templates/templates.strings.js:8 -#: client/src/activity-stream/get-target-title.factory.js:44 -#: client/src/templates/templates.list.js:15 -#: client/src/templates/templates.list.js:16 -msgid "TEMPLATES" -msgstr "テンプレート" - -#: client/src/instance-groups/instance-groups.list.js:8 -msgid "THERE ARE CURRENTLY NO INSTANCE GROUPS DEFINED" -msgstr "現時点で定義されたインスタンスグループはありません" - -#: client/src/home/dashboard/graphs/job-status/job-status-graph.directive.js:105 -msgid "TIME" -msgstr "時間" - -#: client/features/users/tokens/tokens.strings.js:22 -msgid "TOKEN" -msgstr "トークン" - -#: client/features/users/tokens/tokens.strings.js:21 -msgid "TOKEN INFORMATION" -msgstr "トークン情報" - -#: client/features/applications/applications.strings.js:11 -#: client/features/users/tokens/tokens.strings.js:10 -#: client/features/users/tokens/tokens.strings.js:8 -#: client/features/users/tokens/users-tokens-list.route.js:24 -#: client/src/activity-stream/get-target-title.factory.js:50 -msgid "TOKENS" -msgstr "トークン" - -#: client/features/templates/templates.strings.js:106 -msgid "TOTAL TEMPLATES" -msgstr "合計テンプレート" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:235 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:249 -msgid "Tag None:" -msgstr "タグ None:" - -#: client/src/templates/job_templates/job-template.form.js:196 -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 Ansible Tower documentation for details on the usage of tags." -msgstr "" -"タグは、Playbook " -"のサイズが大きい場合にプレイまたはタスクの特定の部分を実行する必要がある場合に役立ちます。カンマを使って複数のタグを区切ります。タグの使用方法の詳細については、Ansible" -" Tower ドキュメントを参照してください。" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:233 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:247 -msgid "Tags:" -msgstr "タグ:" - -#: client/src/notifications/notificationTemplates.form.js:309 -#: client/src/notifications/notificationTemplates.form.js:337 -#: client/src/notifications/notificationTemplates.form.js:376 -msgid "Target URL" -msgstr "ターゲット URL" - -#: client/features/output/output.strings.js:92 -msgid "Tasks" -msgstr "タスク" - -#: client/features/credentials/legacy.credentials.js:91 -#: client/src/credentials/credentials.form.js:468 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:136 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:138 -#: client/src/projects/projects.form.js:276 -#: client/src/templates/workflows.form.js:169 -msgid "Team Roles" -msgstr "チームロール" - -#: client/lib/components/components.strings.js:79 -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:40 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:36 -#: client/src/organizations/linkout/organizations-linkout.route.js:109 -#: client/src/organizations/linkout/organizations-linkout.route.js:111 -#: client/src/shared/stateDefinitions.factory.js:426 -#: client/src/users/users.form.js:161 -msgid "Teams" -msgstr "チーム" - -#: client/src/templates/templates.list.js:14 -#: client/src/workflow-results/workflow-results.controller.js:47 -msgid "Template" -msgstr "テンプレート" - -#: client/features/templates/templates.strings.js:67 -msgid "Template parameter is missing." -msgstr "テンプレートパラメーターがありません。" - -#: client/lib/components/components.strings.js:76 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:37 -msgid "Templates" -msgstr "テンプレート" - -#: client/src/credentials/credentials.form.js:337 -msgid "Tenant ID" -msgstr "テナント ID" - -#: client/src/configuration/system-form/sub-forms/system-logging.form.js:79 -msgid "Test" -msgstr "テスト" - -#: client/src/notifications/notificationTemplates.list.js:77 -msgid "Test notification" -msgstr "テスト通知" - -#: client/src/templates/survey-maker/surveys/init.factory.js:13 -msgid "Text" -msgstr "テキスト" - -#: client/src/templates/survey-maker/surveys/init.factory.js:14 -msgid "Textarea" -msgstr "Textarea" - -#: client/src/shared/form-generator.js:1406 -#: client/src/shared/form-generator.js:1412 -msgid "That value was not found. Please enter or select a valid value." -msgstr "値が見つかりませんでした。有効な値を入力または選択してください。" - -#: client/lib/components/components.strings.js:47 -msgid "That value was not found. Please enter or select a valid value." -msgstr "値が見つかりませんでした。有効な値を入力または選択してください。" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:66 -msgid "The Insights Credential for {{inventory.name}} was not found." -msgstr "{{inventory.name}} の Insights 認証情報が見つかりませんでした。" - -#: client/src/credentials/factories/become-method-change.factory.js:32 -#: client/src/credentials/factories/kind-change.factory.js:89 -msgid "" -"The Project ID is the GCE assigned identification. It is constructed as two " -"words followed by a three digit number. Such as:" -msgstr "プロジェクト ID は GCE によって割り当てられる識別情報です。これは、以下のように 2 語とそれに続く 3 桁の数字で構成されます。" - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:204 -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:207 -msgid "The Project selected has a status of" -msgstr "選択されたプロジェクトのステータス" - -#: client/src/projects/edit/projects-edit.controller.js:341 -msgid "The SCM update process is running." -msgstr "SCM 更新プロセスが実行中です。" - -#: client/src/scheduler/scheduler.strings.js:32 -msgid "The day must be between 1 and 31." -msgstr "日付の値は 1 から 31 の間の値である必要があります。" - -#: client/src/credentials/credentials.form.js:190 -msgid "" -"The email address assigned to the Google Compute Engine %sservice account." -msgstr "Google Compute Engine %sサービスアカウントに割り当てられたメールアドレス。" - -#: client/features/output/output.strings.js:12 -msgid "The host status bar will update when the job is complete." -msgstr "ジョブの完了時にホストのステータスバーが更新されます。" - -#: client/src/credentials/factories/become-method-change.factory.js:62 -#: client/src/credentials/factories/kind-change.factory.js:119 -msgid "The host to authenticate with." -msgstr "認証するホスト。" - -#: client/src/credentials/factories/kind-change.factory.js:60 -msgid "The host value" -msgstr "ホスト値" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:154 -msgid "" -"The inventory will be in a pending status until the final delete is " -"processed." -msgstr "最終の削除が処理されるまでインベントリーは保留状態になります。" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:105 -msgid "" -"The number of parallel or simultaneous processes to use while executing the " -"playbook. Inputting no value will use the default value from the %sansible " -"configuration file%s." -msgstr "" -"Playbook の実行中に使用する並列または同時プロセスの数です。いずれの値も入力しないと、 %sansible 設定ファイル%s " -"のデフォルト値が使用されます。" - -#: client/src/templates/job_templates/job-template.form.js:151 -msgid "" -"The number of parallel or simultaneous processes to use while executing the " -"playbook. Value defaults to 0. Refer to the Ansible documentation for " -"details about the configuration file." -msgstr "" -"Playbook の実行中に使用する並列または同時プロセスの数です。デフォルト値は 0 になります。設定ファイルについての詳細は、Ansible " -"ドキュメントを参照してください。" - -#: client/src/credentials/factories/kind-change.factory.js:59 -msgid "The project value" -msgstr "プロジェクト値" - -#: client/src/scheduler/scheduler.strings.js:49 -msgid "" -"The scheduler options are invalid, incomplete, or a date is in the past." -msgstr "スケジューラーのオプションは無効であるか、完了していないか、また過去の日付になっています。" - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:210 -msgid "The selected project has a status of" -msgstr "選択されたプロジェクトのステータス" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:199 -msgid "" -"The selected project is not configured for SCM. To configure for SCM, edit " -"the project and provide SCM settings and then run an update." -msgstr "" -"選択されたプロジェクトは SCM に対して設定されていません。SCM の設定を行うには、プロジェクトを編集して SCM " -"設定を指定してから更新を実行します。" - -#: client/src/projects/list/projects-list.controller.js:186 -msgid "" -"The selected project is not configured for SCM. To configure for SCM, edit " -"the project and provide SCM settings, and then run an update." -msgstr "" -"選択されたプロジェクトは SCM に対して設定されていません。SCM の設定を行うには、プロジェクトを編集して SCM " -"設定を指定してから更新を実行します。" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:52 -msgid "" -"The suggested format for variable names is lowercase and underscore-" -"separated (for example, foo_bar, user_id, host_name, etc.). Variable names " -"with spaces are not allowed." -msgstr "" -"推奨される変数名の形式は小文字のみを使用し、それらをアンダースコアで区切る形です (foo_bar、user_id、host_name " -"など)。スペースを含む変数名は許可されません。" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:124 -#: client/src/scheduler/scheduler.strings.js:25 -msgid "The time must be in HH24:MM:SS format." -msgstr "時間は HH24:MM:SS 形式で表示される必要があります。" - -#: client/lib/services/base-string.service.js:79 -msgid "The {{ resourceType }} is currently being used by other resources." -msgstr "{{ resourceType }} は現在他のリソースで使用されています。" - -#: client/src/activity-stream/streams.list.js:17 -msgid "There are no events to display at this time" -msgstr "現時点で表示できるイベントはありません" - -#: client/features/jobs/jobs.strings.js:17 -msgid "There are no running jobs." -msgstr "実行中のジョブがありません。" - -#: client/src/projects/list/projects-list.controller.js:150 -msgid "" -"There is no SCM update information available for this project. An update has" -" not yet been completed. If you have not already done so, start an update " -"for this project." -msgstr "" -"このプロジェクトに利用できる SCM " -"更新情報はありません。更新はまだ完了していません。まだ更新を実行していない場合は、このプロジェクトの更新を開始してください。" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:191 -msgid "" -"There is no SCM update information available for this project. An update has" -" not yet been completed. If you have not already done so, start an update " -"for this project." -msgstr "" -"このプロジェクトに利用できる SCM " -"更新情報はありません。更新はまだ完了していません。まだ更新を実行していない場合は、このプロジェクトの更新を開始してください。" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:141 -msgid "There was an error deleting inventory source groups. Returned status:" -msgstr "インベントリーソースグループの削除中にエラーが発生しました。返されたステータス:" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:131 -msgid "There was an error deleting inventory source hosts. Returned status:" -msgstr "インベントリーソースホストの削除中にエラーが発生しました。返されたステータス:" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:168 -msgid "There was an error deleting inventory source. Returned status:" -msgstr "インベントリーソースの削除中にエラーが発生しました。返されたステータス:" - -#: client/src/configuration/configuration.controller.js:142 -msgid "There was an error getting config values:" -msgstr "設定値の取得時にエラーが発生しました。" - -#: client/src/configuration/configuration.controller.js:415 -msgid "There was an error resetting value. Returned status:" -msgstr "値のリセット中にエラーが発生しました。返されたステータス:" - -#: client/src/configuration/configuration.controller.js:607 -msgid "There was an error resetting values. Returned status:" -msgstr "値のリセット中にエラーが発生しました。返されたステータス:" - -#: client/src/configuration/system-form/configuration-system.controller.js:232 -msgid "There was an error testing the log aggregator. Returned status:" -msgstr "ログアグリゲーターのテスト中にエラーが発生しました。返されたステータス:" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:29 -msgid "" -"These are the modules that {{BRAND_NAME}} supports running commands against." -msgstr "これらは {{BRAND_NAME}} がコマンドの実行をサポートするモジュールです。" - -#: client/features/templates/templates.strings.js:94 -msgid "" -"This Job Template has a credential that requires a password. Credentials " -"requiring passwords on launch are not permitted on workflow nodes." -msgstr "" -"このジョブテンプレートにはパスワードが必要な認証情報があります。起動時にパスワードを必要とする認証情報はワークフローノードでは許可されません。" - -#: client/src/scheduler/scheduler.strings.js:59 -msgid "" -"This Job Template has a default credential that requires a password before " -"launch. Adding or editing schedules is prohibited while this credential is " -"selected. To add or edit a schedule, credentials that require a password " -"must be removed from the Job Template." -msgstr "" -"このジョブテンプレートには、起動前にパスワードを必要とするデフォルト認証情報があります。この認証情報が選択されている場合、スケジュールの追加または編集は許可されません。スケジュールを追加または編集するには、パスワードを必要とする認証情報をジョブテンプレートから削除する必要があります。" - -#: client/features/templates/templates.strings.js:93 -msgid "" -"This Job Template is missing a default inventory or project. This must be " -"addressed in the Job Template form before this node can be saved." -msgstr "" -"このジョブテンプレートにはデフォルトのインベントリーまたはプロジェクトがありません。これについては、このノードを保存する前にジョブテンプレートフォームで対応する必要があります。" - -#: client/src/credential-types/credential-types.strings.js:8 -msgid "" -"This credential type is currently being used by one or more credentials. " -"Credentials that use this credential type must be deleted before the " -"credential type can be deleted." -msgstr "" -"この認証情報タイプは現在 1 " -"つ以上の認証情報で使用されています。この認証情報タイプを使用する認証情報を削除してから認証情報タイプを削除することができます。" - -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:24 -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:25 -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:26 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:24 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:25 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:26 -msgid "This group contains" -msgstr "このグループには以下が含まれます。" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:168 -msgid "This is not a valid number." -msgstr "これは無効な数値です。" - -#: client/src/credentials/factories/become-method-change.factory.js:59 -#: client/src/credentials/factories/kind-change.factory.js:116 -msgid "" -"This is the tenant name. This value is usually the same as the username." -msgstr "これはテナント名です。通常、この値はユーザー名と同じです。" - -#: client/features/templates/templates.strings.js:63 -msgid "" -"This job template has a default {{typeLabel}} credential which must be " -"included or replaced before proceeding." -msgstr "" -"このジョブテンプレートにはデフォルトの {{typeLabel}} 認証情報があります。これを組み込むか、または置き換えてから次に進んでください。" - -#: client/src/organizations/linkout/organizations-linkout.route.js:158 -msgid "This list is populated by inventories added from the" -msgstr "この一覧は、以下から追加されるインベントリーで事前に設定されます:" - -#: client/src/notifications/notifications.list.js:21 -msgid "" -"This list is populated by notification templates added from the " -"%sNotifications%s section" -msgstr "この一覧は、%s通知%sセクションから追加される通知テンプレートで事前に設定されます。" - -#: client/src/organizations/linkout/organizations-linkout.route.js:209 -msgid "This list is populated by projects added from the" -msgstr "この一覧は、以下から追加されるプロジェクトで事前に設定されます:" - -#: client/src/organizations/linkout/organizations-linkout.route.js:111 -msgid "This list is populated by teams added from the" -msgstr "この一覧は、以下から追加されるチームで事前に設定されます:" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:4 -msgid "" -"This machine has not checked in with Insights in {{last_check_in}} hours" -msgstr "このマシンは {{last_check_in}} 時間 Insights にチェックインしていません" - -#: client/src/shared/form-generator.js:753 -msgid "" -"This setting has been set manually in a settings file and is now disabled." -msgstr "この値は設定ファイルに手動で設定され、現在は無効にされています。" - -#: client/src/users/users.form.js:166 -msgid "This user is not a member of any teams" -msgstr "このユーザーはいずれのチームのメンバーでもありません。" - -#: client/src/shared/form-generator.js:863 -#: client/src/shared/form-generator.js:958 -msgid "" -"This value does not match the password you entered previously. Please " -"confirm that password." -msgstr "この値は以前に入力されたパスワードと一致しません。パスワードを確認してください。" - -#: client/src/configuration/configuration.controller.js:632 -msgid "" -"This will reset all configuration values to their factory defaults. Are you " -"sure you want to proceed?" -msgstr "これにより、すべての設定値が出荷時の設定にリセットされます。本当に続行してもよいですか?" - -#: client/src/scheduler/scheduler.strings.js:40 -msgid "Thu" -msgstr "木" - -#: client/src/activity-stream/streams.list.js:25 -#: client/src/home/dashboard/lists/jobs/jobs-list.partial.html:14 -#: client/src/notifications/notification-templates-list/list.controller.js:72 -msgid "Time" -msgstr "時間" - -#: client/src/license/license.partial.html:45 -msgid "Time Remaining" -msgstr "残りの時間" - -#: client/src/projects/projects.form.js:197 -msgid "" -"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." -msgstr "" -"プロジェクトが最新であることを判別するために使用される時間 (秒単位) " -"です。タスクシステムは、ジョブ実行およびコールバック時にプロジェクト更新の最新のタイムスタンプを検証します。これがキャッシュタイムアウトよりも古い場合は最新とは見なされず、新規のプロジェクト更新が実行されます。" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:411 -msgid "" -"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." -msgstr "" -"インベントリーの同期が最新の状態であることを判別するために使用される時間 (秒単位) " -"です。ジョブの実行およびコールバック時に、タスクシステムは最新の同期のタイムスタンプを評価します。これがキャッシュタイムアウトよりも古い場合は最新とは見なされず、インベントリーの同期が新たに実行されます。" - -#: client/src/credentials/credentials.form.js:125 -msgid "" -"To learn more about the IAM STS Token, refer to the %sAmazon " -"documentation%s." -msgstr "IAM STS トークンについての詳細は、%sAmazon ドキュメント%s を参照してください。" - -#: client/src/shared/form-generator.js:888 -msgid "Toggle the display of plaintext." -msgstr "プレーンテキストの表示を切り替えます。" - -#: client/src/notifications/shared/type-change.service.js:36 -#: client/src/notifications/shared/type-change.service.js:42 -msgid "Token" -msgstr "トークン" - -#: client/features/applications/applications.strings.js:16 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:25 -#: client/src/users/users.form.js:235 -msgid "Tokens" -msgstr "トークン" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:10 -msgid "Total Issues" -msgstr "問題の合計" - -#: client/src/instance-groups/instance-groups.strings.js:19 -#: client/src/workflow-results/workflow-results.controller.js:60 -msgid "Total Jobs" -msgstr "ジョブの合計" - -#: client/src/partials/logviewer.html:6 -msgid "Traceback" -msgstr "トレースバック" - -#: client/src/scheduler/scheduler.strings.js:38 -msgid "Tue" -msgstr "火" - -#: client/src/credentials/credentials.form.js:60 -#: client/src/credentials/credentials.form.js:84 -#: client/src/inventories-hosts/inventories/inventory.list.js:56 -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:41 -#: client/src/notifications/notificationTemplates.form.js:54 -#: client/src/notifications/notificationTemplates.list.js:39 -#: client/src/notifications/notifications.list.js:32 -#: client/src/projects/projects.list.js:44 -#: client/src/scheduler/scheduled-jobs.list.js:42 -#: client/src/teams/teams.form.js:133 -#: client/src/templates/templates.list.js:31 -#: client/src/users/users.form.js:202 -msgid "Type" -msgstr "タイプ" - -#: client/features/credentials/credentials.strings.js:18 -#: client/src/credentials/credentials.form.js:23 -#: client/src/notifications/notificationTemplates.form.js:26 -msgid "Type Details" -msgstr "タイプの詳細" - -#: client/src/projects/add/projects-add.controller.js:178 -#: client/src/projects/edit/projects-edit.controller.js:313 -msgid "URL popover text" -msgstr "URL ポップオーバーテキスト" - -#: client/src/login/loginModal/loginModal.partial.html:49 -msgid "USERNAME" -msgstr "ユーザー名" - -#: client/src/activity-stream/get-target-title.factory.js:20 -#: client/src/organizations/linkout/organizations-linkout.route.js:43 -#: client/src/organizations/list/organizations-list.controller.js:55 -#: client/src/users/users.list.js:18 client/src/users/users.list.js:19 -#: client/src/users/users.route.js:8 -msgid "USERS" -msgstr "ユーザー" - -#: client/lib/components/components.strings.js:24 -msgid "Unable to Submit" -msgstr "送信できません" - -#: client/features/templates/templates.strings.js:84 -msgid "Unable to copy template." -msgstr "テンプレートをコピーできませんでした。" - -#: client/src/instance-groups/instance-groups.strings.js:59 -msgid "Unable to delete instance group." -msgstr "インスタンスグループを削除できませんでした。" - -#: client/features/templates/templates.strings.js:80 -msgid "Unable to delete template." -msgstr "テンプレートを削除できませんでした。" - -#: client/features/templates/templates.strings.js:82 -msgid "Unable to determine template type." -msgstr "テンプレートタイプを判別できませんでした。" - -#: client/features/templates/templates.strings.js:69 -msgid "Unable to determine this template's type while copying." -msgstr "コピー中にこのテンプレートのタイプを判別できませんでした。" - -#: client/features/templates/templates.strings.js:70 -msgid "Unable to determine this template's type while deleting." -msgstr "削除中にこのテンプレートのタイプを判別できませんでした。" - -#: client/features/templates/templates.strings.js:71 -msgid "Unable to determine this template's type while editing." -msgstr "編集中にこのテンプレートのタイプを判別できませんでした。" - -#: client/features/templates/templates.strings.js:72 -msgid "Unable to determine this template's type while launching." -msgstr "起動中にこのテンプレートのタイプを判別できませんでした。" - -#: client/features/templates/templates.strings.js:73 -msgid "Unable to determine this template's type while scheduling." -msgstr "スケジュール中にこのテンプレートのタイプを判別できませんでした。" - -#: client/features/templates/templates.strings.js:79 -msgid "Unable to edit template." -msgstr "テンプレートを編集できませんでした。" - -#: client/src/shared/stateDefinitions.factory.js:231 -msgid "Unable to get resource:" -msgstr "リソースを取得できませんでした:" - -#: client/features/templates/templates.strings.js:81 -msgid "Unable to launch template." -msgstr "テンプレートを起動できませんでした。" - -#: client/features/templates/templates.strings.js:83 -msgid "Unable to schedule job." -msgstr "ジョブをスケジュールできませんでした。" - -#: client/src/instance-groups/instance-groups.strings.js:41 -msgid "Unavailable" -msgstr "利用できません" - -#: client/src/instance-groups/instance-groups.strings.js:40 -msgid "Unavailable to run jobs." -msgstr "ジョブの実行に使用できません。" - -#: client/lib/components/components.strings.js:26 -msgid "Unexpected Error" -msgstr "予期しないエラー" - -#: client/lib/components/components.strings.js:25 -msgid "Unexpected server error. View the console for more information" -msgstr "予期しないサーバーエラーが発生しました。コンソールで詳細情報を表示してください。" - -#: client/lib/components/components.strings.js:38 -msgid "Unsupported display model type" -msgstr "サポートされない表示モデルタイプ" - -#: client/lib/components/components.strings.js:30 -msgid "Unsupported input type" -msgstr "サポートされない入力タイプ" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:270 -#: client/src/projects/list/projects-list.controller.js:311 -msgid "Update Not Found" -msgstr "更新が見つかりません" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:345 -msgid "Update Options" -msgstr "オプションの更新" - -#: client/src/projects/projects.form.js:178 -msgid "Update Revision on Launch" -msgstr "起動時のリビジョン更新" - -#: client/src/projects/factories/get-project-tool-tip.factory.js:30 -msgid "Update canceled. Click for details" -msgstr "更新が取り消されました。クリックして詳細を確認してください。" - -#: client/src/projects/factories/get-project-tool-tip.factory.js:24 -msgid "Update failed. Click for details" -msgstr "更新に失敗しました。クリックして詳細を確認してください。" - -#: client/src/projects/edit/projects-edit.controller.js:341 -msgid "Update in Progress" -msgstr "更新が進行中です" - -#: client/src/projects/factories/get-project-tool-tip.factory.js:27 -msgid "Update missing. Click for details" -msgstr "更新がありません。クリックして詳細を確認してください。" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:376 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:381 -msgid "Update on Launch" -msgstr "起動時の更新" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:394 -msgid "Update on Project Update" -msgstr "プロジェクト更新時の更新" - -#: client/src/projects/factories/get-project-tool-tip.factory.js:14 -msgid "Update queued. Click for details" -msgstr "更新がキューに入れられました。クリックして詳細を確認してください。" - -#: client/src/projects/factories/get-project-tool-tip.factory.js:18 -msgid "Update running. Click for details" -msgstr "更新が実行中です。クリックして詳細を確認してください。" - -#: client/src/projects/factories/get-project-tool-tip.factory.js:21 -msgid "Update succeeded. Click for details" -msgstr "更新が正常に実行されました。クリックして詳細を確認してください。" - -#: client/src/license/license.partial.html:71 -msgid "Upgrade" -msgstr "アップグレード" - -#: client/src/organizations/organizations.form.js:48 -#: client/src/projects/projects.form.js:209 -#: client/src/templates/job_templates/job-template.form.js:241 -msgid "Use Default Environment" -msgstr "デフォルト環境の使用" - -#: client/src/templates/job_templates/job-template.form.js:314 -#: client/src/templates/job_templates/job-template.form.js:319 -msgid "Use Fact Cache" -msgstr "ファクトのキャッシュの使用" - -#: client/src/notifications/notificationTemplates.form.js:466 -msgid "Use SSL" -msgstr "SSL の使用" - -#: client/src/notifications/notificationTemplates.form.js:461 -msgid "Use TLS" -msgstr "TLS の使用" - -#: client/src/instance-groups/instance-groups.strings.js:20 -#: client/src/instance-groups/instance-groups.strings.js:42 -msgid "Used Capacity" -msgstr "使用済み容量" - -#: client/src/credentials/credentials.form.js:76 -msgid "" -"Used to check out and synchronize playbook repositories with a remote source" -" control management system such as Git, Subversion (svn), or Mercurial (hg)." -" These credentials are used by Projects." -msgstr "" -"Git、Subversion (svn)、または Mercurial (hg) などのリモートソースコントロール管理システムで Playbook " -"リポジトリーをチェックアウトし、同期するために使用されます。これらの認証情報はプロジェクトで使用されます。" - -#: client/features/credentials/legacy.credentials.js:80 -#: client/src/credentials/credentials.form.js:457 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:125 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:127 -#: client/src/organizations/organizations.form.js:104 -#: client/src/projects/projects.form.js:265 client/src/teams/teams.form.js:96 -#: client/src/templates/workflows.form.js:158 -msgid "User" -msgstr "ユーザー" - -#: client/src/configuration/configuration.partial.html:36 -msgid "User Interface" -msgstr "ユーザーインターフェース" - -#: client/src/users/users.form.js:97 -msgid "User Type" -msgstr "ユーザータイプ" - -#: client/src/access/rbac-multiselect/permissionsUsers.list.js:36 -#: client/src/credentials/factories/become-method-change.factory.js:17 -#: client/src/credentials/factories/become-method-change.factory.js:38 -#: client/src/credentials/factories/kind-change.factory.js:17 -#: client/src/credentials/factories/kind-change.factory.js:41 -#: client/src/credentials/factories/kind-change.factory.js:74 -#: client/src/credentials/factories/kind-change.factory.js:95 -#: client/src/notifications/notificationTemplates.form.js:348 -#: client/src/notifications/notificationTemplates.form.js:387 -#: client/src/notifications/notificationTemplates.form.js:64 -#: client/src/users/users.form.js:60 client/src/users/users.list.js:29 -msgid "Username" -msgstr "ユーザー名" - -#: client/src/credentials/credentials.form.js:80 -msgid "" -"Usernames, passwords, and access keys for authenticating to the specified " -"cloud or infrastructure provider. These are used for smart inventory sources" -" and for cloud provisioning and deployment in playbook runs." -msgstr "" -"指定されたクラウドまたはインフラストラクチャープロバイダーに対する認証を行うためのユーザー名、パスワード、およびアクセスキーです。これらはスマートインベントリーソース、および" -" Playbook 実行におけるクラウドプロビジョニングおよびデプロイメントに使用されます。" - -#: client/lib/components/components.strings.js:78 -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:35 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:38 -#: client/src/organizations/organizations.form.js:86 -#: client/src/teams/teams.form.js:78 -msgid "Users" -msgstr "ユーザー" - -#: client/src/scheduler/schedulerList.controller.js:46 -msgid "" -"Using a credential that requires a password on launch is prohibited when " -"creating a Job Template schedule" -msgstr "ジョブテンプレートスケジュールの作成時に、起動時にパスワードを必要とする認証情報は禁止されています。" - -#: client/lib/components/code-mirror/code-mirror.strings.js:9 -msgid "VARIABLES" -msgstr "変数" - -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:7 -#: client/src/home/dashboard/lists/jobs/jobs-list.partial.html:7 -msgid "VIEW ALL" -msgstr "すべてを表示" - -#: client/lib/components/components.strings.js:57 -msgid "VIEW LESS" -msgstr "簡易表示" - -#: client/lib/components/components.strings.js:56 -msgid "VIEW MORE" -msgstr "詳細を表示" - -#: client/src/shared/paginate/paginate.partial.html:48 -msgid "VIEW PER PAGE" -msgstr "ページ別のビュー" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:234 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:248 -msgid "VPC ID:" -msgstr "VPC ID:" - -#: client/src/license/license.partial.html:10 -msgid "Valid License" -msgstr "有効なライセンス" - -#: client/src/inventories-hosts/hosts/host.form.js:68 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:46 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js:47 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:67 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:67 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:63 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:71 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:77 -msgid "Variables" -msgstr "変数" - -#: client/src/job-submission/job-submission.partial.html:364 -msgid "Vault" -msgstr "Vault" - -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js:25 -msgid "Vault ID" -msgstr "Vault ID" - -#: client/features/templates/templates.strings.js:44 -#: client/src/credentials/credentials.form.js:391 -#: client/src/job-submission/job-submission.partial.html:146 -msgid "Vault Password" -msgstr "Vault パスワード" - -#: client/features/output/output.strings.js:72 -#: client/features/templates/templates.strings.js:51 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:82 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:91 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:331 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:338 -#: client/src/job-submission/job-submission.partial.html:183 -#: client/src/templates/job_templates/job-template.form.js:173 -#: client/src/templates/job_templates/job-template.form.js:180 -msgid "Verbosity" -msgstr "詳細" - -#: client/src/license/license.partial.html:15 -msgid "Version" -msgstr "バージョン" - -#: client/src/activity-stream/streams.list.js:63 -#: client/src/credential-types/credential-types.list.js:64 -#: client/src/credentials/credentials.list.js:82 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:58 -#: client/src/inventories-hosts/inventories/inventory.list.js:114 -#: client/src/inventory-scripts/inventory-scripts.list.js:70 -#: client/src/notifications/notificationTemplates.list.js:91 -#: client/src/scheduler/schedules.list.js:93 client/src/teams/teams.list.js:64 -#: client/src/templates/templates.list.js:101 -#: client/src/users/users.list.js:70 -msgid "View" -msgstr "表示" - -#: client/src/bread-crumb/bread-crumb.directive.js:41 -msgid "View Activity Stream" -msgstr "アクティビティーストリームの表示" - -#: client/lib/components/components.strings.js:66 -msgid "View Documentation" -msgstr "ドキュメントの表示" - -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:86 -#: client/src/inventories-hosts/inventory-hosts.strings.js:27 -msgid "View Insights Data" -msgstr "Insights データの表示" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:202 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:226 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:250 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:325 -msgid "View JSON examples at" -msgstr "JSON サンプルの表示: " - -#: client/src/inventories-hosts/hosts/host.form.js:78 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:77 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:77 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:73 -msgid "View JSON examples at %s" -msgstr "JSON サンプルを %s に表示" - -#: client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.partial.html:13 -msgid "View Less" -msgstr "簡易表示" - -#: client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.partial.html:11 -msgid "View More" -msgstr "詳細表示" - -#: client/features/output/output.strings.js:27 -msgid "View Project checkout results" -msgstr "プロジェクトのチェックアウト結果を表示" - -#: client/src/shared/form-generator.js:1739 -#: client/src/templates/job_templates/job-template.form.js:459 -#: client/src/templates/workflows.form.js:196 -msgid "View Survey" -msgstr "Survey の表示" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:203 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:227 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:251 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:326 -msgid "View YAML examples at" -msgstr "YAML サンプルの表示: " - -#: client/src/inventories-hosts/hosts/host.form.js:79 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:78 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:78 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:74 -msgid "View YAML examples at %s" -msgstr "YAML サンプルを %s に表示" - -#: client/src/credentials/credentials.list.js:84 -msgid "View credential" -msgstr "認証情報の表示" - -#: client/src/credential-types/credential-types.list.js:66 -msgid "View credential type" -msgstr "認証情報タイプの表示" - -#: client/src/activity-stream/streams.list.js:67 -msgid "View event details" -msgstr "イベント詳細の表示" - -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:93 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:103 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:91 -msgid "View group" -msgstr "グループの表示" - -#: client/src/inventories-hosts/hosts/host.list.js:89 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:79 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:93 -#: client/src/inventories-hosts/inventory-hosts.strings.js:26 -msgid "View host" -msgstr "ホストの表示" - -#: client/src/inventories-hosts/inventories/inventory.list.js:116 -msgid "View inventory" -msgstr "インベントリーの表示" - -#: client/src/inventory-scripts/inventory-scripts.list.js:72 -msgid "View inventory script" -msgstr "インベントリースクリプトの表示" - -#: client/src/notifications/notificationTemplates.list.js:93 -msgid "View notification" -msgstr "通知の表示" - -#: client/src/scheduler/schedules.list.js:95 -msgid "View schedule" -msgstr "スケジュールの表示" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:110 -msgid "View source" -msgstr "ソースの表示" - -#: client/src/teams/teams.list.js:67 -msgid "View team" -msgstr "チームの表示" - -#: client/src/templates/templates.list.js:103 -msgid "View template" -msgstr "テンプレートの表示" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:246 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:261 -msgid "View the" -msgstr "次の表示:" - -#: client/features/output/output.strings.js:21 -#: client/lib/components/components.strings.js:61 -msgid "View the Credential" -msgstr "認証情報の表示" - -#: client/features/output/output.strings.js:24 -msgid "View the Inventory" -msgstr "インベントリーの表示" - -#: client/features/output/output.strings.js:25 -msgid "View the Job Template" -msgstr "ジョブテンプレートの表示" - -#: client/features/output/output.strings.js:26 -msgid "View the Project" -msgstr "プロジェクトの表示" - -#: client/features/output/output.strings.js:28 -msgid "View the Schedule" -msgstr "スケジュールの表示" - -#: client/features/output/output.strings.js:30 -msgid "View the User" -msgstr "ユーザーの表示" - -#: client/src/projects/projects.list.js:109 -msgid "View the project" -msgstr "プロジェクトの表示" - -#: client/src/scheduler/scheduled-jobs.list.js:74 -msgid "View the schedule" -msgstr "スケジュールの表示" - -#: client/features/output/output.strings.js:29 -msgid "View the source Workflow Job" -msgstr "ソースのワークフロージョブの表示" - -#: client/src/users/users.list.js:73 -msgid "View user" -msgstr "ユーザーの表示" - -#: client/lib/components/components.strings.js:89 -msgid "Views" -msgstr "表示" - -#: client/src/templates/workflows.form.js:20 -msgid "WORKFLOW" -msgstr "ワークフロー" - -#: client/features/templates/templates.strings.js:119 -msgid "WORKFLOW VISUALIZER" -msgstr "ワークフロービジュアライザー" - -#: client/features/templates/templates.strings.js:105 -#: client/src/scheduler/scheduler.strings.js:58 -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:214 -msgid "Warning" -msgstr "警告" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:92 -#: client/src/configuration/configuration.controller.js:229 -#: client/src/configuration/configuration.controller.js:314 -#: client/src/configuration/system-form/configuration-system.controller.js:55 -msgid "Warning: Unsaved Changes" -msgstr "警告: 変更が保存されていません" - -#: client/src/scheduler/scheduler.strings.js:39 -msgid "Wed" -msgstr "水" - -#: client/src/license/license.partial.html:78 -msgid "" -"Welcome to Ansible Tower! Please complete the steps below to acquire a " -"license." -msgstr "Ansible Tower へようこそ! ライセンスを取得するために以下のステップを完了してください。" - -#: client/src/login/loginModal/loginModal.partial.html:17 -msgid "Welcome to Ansible {{BRAND_NAME}}!  Please sign in." -msgstr "Ansible {{BRAND_NAME}} へようこそ!  サインインしてください。" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:368 -msgid "" -"When not checked, a merge will be performed, combining local variables with " -"those found on the external source." -msgstr "チェックが付けられていない場合、ローカル変数と外部ソースにあるものを組み合わせるマージが実行されます。" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:356 -msgid "" -"When not checked, local child hosts and groups not found on the external " -"source will remain untouched by the inventory update process." -msgstr "" -"チェックが付けられていない場合、外部ソースにないローカルの子ホストおよびグループは、インベントリーの更新プロセスによって処理されないままになります。" - -#: client/features/jobs/jobs.strings.js:11 -msgid "Workflow Job" -msgstr "ワークフロージョブ" - -#: client/lib/models/models.strings.js:45 -msgid "Workflow Job Template Nodes" -msgstr "ワークフロージョブテンプレートのノード" - -#: client/features/templates/templates.strings.js:14 -#: client/src/templates/templates.list.js:66 -msgid "Workflow Template" -msgstr "ワークフローテンプレート" - -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:109 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:41 -msgid "Workflow Templates" -msgstr "ワークフローテンプレート" - -#: client/src/shared/form-generator.js:1743 -#: client/src/templates/workflows.form.js:222 -msgid "Workflow Visualizer" -msgstr "ワークフロービジュアライザー" - -#: client/features/users/tokens/tokens.strings.js:31 -msgid "Write" -msgstr "書き込み" - -#: client/lib/components/code-mirror/code-mirror.strings.js:11 -#: client/lib/services/base-string.service.js:69 -#: client/src/job-submission/job-submission.partial.html:171 -msgid "YAML" -msgstr "YAML" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:200 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:224 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:248 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:323 -msgid "YAML:" -msgstr "YAML:" - -#: client/lib/services/base-string.service.js:73 -msgid "YES" -msgstr "YES" - -#: client/src/notifications/add/add.controller.js:83 -#: client/src/notifications/edit/edit.controller.js:130 -msgid "Yellow" -msgstr "黄" - -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:53 -msgid "" -"You can create a job template here." -msgstr "ジョブテンプレートの作成はここから実行できます。" - -#: client/features/templates/templates.strings.js:89 -msgid "" -"You do not have access to all resources used by this workflow. Resources " -"that you don't have access to will not be copied and will result in an " -"incomplete workflow." -msgstr "" -"このワークフローで使用されるすべてのリソースへのアクセスがありません。アクセスのないリソースはコピーされず、結果としてワークフローが不完全になります。" - -#: client/src/projects/edit/projects-edit.controller.js:64 -msgid "You do not have access to view this property" -msgstr "これを適切に表示するためのアクセス権がありません。" - -#: client/src/projects/add/projects-add.controller.js:32 -msgid "You do not have permission to add a project." -msgstr "プロジェクトを追加するパーミッションがありません。" - -#: client/src/users/add/users-add.controller.js:44 -msgid "You do not have permission to add a user." -msgstr "ユーザーを追加するパーミッションがありません。" - -#: client/src/access/rbac-multiselect/rbac-multiselect-list.directive.js:174 -msgid "You do not have permission to manage this user" -msgstr "このユーザーを管理するパーミッションがありません。" - -#: client/features/templates/templates.strings.js:68 -msgid "You do not have permission to perform this action." -msgstr "このアクションを実行するパーミッションがありません。" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:41 -msgid "You do not have sufficient permissions to edit the host filter." -msgstr "ホストフィルターの編集に必要なパーミッションがありません。" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:91 -#: client/src/configuration/configuration.controller.js:228 -#: client/src/configuration/configuration.controller.js:313 -#: client/src/configuration/system-form/configuration-system.controller.js:54 -msgid "" -"You have unsaved changes. Would you like to proceed without" -" saving?" -msgstr "保存されていない変更があります。変更せずに次に進みますか?" - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:204 -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:207 -msgid "" -"You must run a successful update before you can select a playbook. You will " -"not be able to save this Job Template without a valid playbook." -msgstr "" -"Playbook を選択する前に正常な更新を実行する必要があります。有効な Playbook がないとこのジョブテンプレートを保存することはできません。" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:224 -#: client/src/projects/list/projects-list.controller.js:267 -msgid "Your request to cancel the update was submitted to the task manager." -msgstr "更新を取り消す要求がタスクマネージャーに送信されました。" - -#: client/src/login/loginModal/loginModal.partial.html:22 -msgid "Your session timed out due to inactivity. Please sign in." -msgstr "アイドル時間によりセッションがタイムアウトしました。サインインしてください。" - -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:24 -#: client/src/job-submission/job-submission.partial.html:317 -#: client/src/shared/form-generator.js:1213 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:42 -msgid "and" -msgstr "and" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:245 -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:270 -msgid "button to view the latest status." -msgstr "最新ステータスを表示するボタン" - -#: client/features/users/tokens/tokens.strings.js:27 -msgid "by" -msgstr " " - -#: client/src/job-submission/job-submission.partial.html:289 -#: client/src/job-submission/job-submission.partial.html:294 -#: client/src/job-submission/job-submission.partial.html:305 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:14 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:19 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:30 -msgid "characters long." -msgstr "文字の長さ。" - -#: client/features/output/output.strings.js:79 -#: client/src/shared/smart-search/smart-search.partial.html:50 -msgid "documentation" -msgstr "ドキュメント" - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:204 -msgid "failed" -msgstr "失敗" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:247 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:262 -msgid "for a complete list of supported filters." -msgstr "サポートされるフィルターの詳細の一覧" - -#: client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.directive.js:82 -msgid "from the" -msgstr " 削除元:" - -#: client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.directive.js:82 -#: client/src/inventories-hosts/inventory-hosts.strings.js:8 -msgid "group" -msgid_plural "groups" -msgstr[0] "グループ" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:26 -msgid "groups" -msgstr "グループ" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:24 -msgid "groups and" -msgstr "グループおよび" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:9 -msgid "host" -msgid_plural "hosts" -msgstr[0] "ホスト" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:24 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:25 -msgid "hosts" -msgstr "ホスト" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:65 -msgid "hosts with failures. Click for details." -msgstr "障害のあるホストです。クリックして詳細を確認してください。" - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:210 -msgid "missing" -msgstr "不明" - -#: client/src/access/rbac-multiselect/permissionsTeams.list.js:21 -msgid "name" -msgstr "名前" - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:207 -msgid "never updated" -msgstr "未更新" - -#: client/src/shared/paginate/paginate.partial.html:34 -msgid "of" -msgstr " /" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:239 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:254 -msgid "of the filters match." -msgstr "フィルターの一致:" - -#: client/src/scheduler/scheduler.strings.js:34 -msgid "on" -msgstr " " - -#: client/src/scheduler/scheduler.strings.js:31 -msgid "on day" -msgstr "日数" - -#: client/src/scheduler/scheduler.strings.js:35 -msgid "on days" -msgstr "日数" - -#: client/src/scheduler/scheduler.strings.js:33 -msgid "on the" -msgstr "実行日" - -#: client/src/access/rbac-multiselect/permissionsTeams.list.js:24 -msgid "organization" -msgstr "組織" - -#: client/src/shared/form-generator.js:1085 -msgid "playbook" -msgstr "Playbook" - -#: client/src/organizations/linkout/organizations-linkout.route.js:111 -#: client/src/organizations/linkout/organizations-linkout.route.js:158 -#: client/src/organizations/linkout/organizations-linkout.route.js:209 -msgid "section" -msgstr "セクション" - -#: client/src/credentials/credentials.form.js:138 -#: client/src/credentials/credentials.form.js:364 -msgid "set in helpers/credentials" -msgstr "ヘルパー/認証情報で設定" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:48 -msgid "sources with sync failures. Click for details" -msgstr "同期が失敗しているソースです。クリックして詳細を確認してください。" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:244 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:259 -msgid "test" -msgstr "テスト" - -#: client/src/job-submission/job-submission.partial.html:289 -#: client/src/job-submission/job-submission.partial.html:294 -#: client/src/job-submission/job-submission.partial.html:305 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:14 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:19 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:30 -msgid "to" -msgstr " " - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:139 -msgid "" -"to include all regions. Only Hosts associated with the selected regions will" -" be updated." -msgstr "すべてのリージョンが含まれます。選択したリージョンに関連付けられたホストのみが更新されます。" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:17 -msgid "to start it now." -msgstr "すぐに開始します。" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:25 -msgid "to update." -msgstr "更新します。" - -#: client/src/credentials/credentials.form.js:381 -msgid "v2 URLs%s - leave blank" -msgstr "v2 URL%s - 空白にする" - -#: client/src/credentials/credentials.form.js:382 -msgid "v3 default%s - set to 'default'" -msgstr "v3 デフォルト%s - 「デフォルト」に設定" - -#: client/src/credentials/credentials.form.js:383 -msgid "v3 multi-domain%s - your domain name" -msgstr "v3 マルチドメイン%s - ドメイン名" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:319 -msgid "view azure_rm.ini in the Ansible github repo." -msgstr "azure_rm.ini を Ansible github リポジトリーで表示します。" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:220 -msgid "view ec2.ini in the Ansible github repo." -msgstr "ec2.ini を Ansible github リポジトリーで表示します。" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:244 -msgid "view vmware_inventory.ini in the Ansible github repo." -msgstr "vmware_inventory.ini を Ansible github リポジトリーで表示します。" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:239 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:254 -msgid "when" -msgstr "日付" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:225 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:239 -msgid "" -"will create group names similar to the following examples based on the " -"options selected:" -msgstr "選択されたオプションに基づいて以下のサンプルと同様のグループ名が作成されます。" - -#: client/src/inventories-hosts/inventories/related/groups/factories/get-hosts-status-msg.factory.js:11 -msgid "with failed jobs." -msgstr "ジョブの失敗" - -#: client/features/users/tokens/tokens.strings.js:42 -msgid "{{ appName }} Token" -msgstr "{{ appName }} トークン" - -#: client/lib/services/base-string.service.js:102 -msgid "{{ header }} {{ body }}" -msgstr "{{ header }} {{ body }}" - -#: client/lib/services/base-string.service.js:75 -msgid "{{ resource }} successfully created" -msgstr "{{ resource }} が正常に作成されました" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:31 -msgid "{{ str1 }}

{{ str2 }}

" -msgstr "{{ str1 }}

{{ str2 }}

" - -#: client/src/templates/prompt/steps/other-prompts/prompt-other-prompts.partial.html:5 -msgid "{{:: vm.strings.get('prompt.JOB_TYPE') }}" -msgstr "{{:: vm.strings.get('prompt.JOB_TYPE') }}" - -#: client/lib/components/input/label.partial.html:5 -msgid "{{::state._hint}}" -msgstr "{{::state._hint}}" - -#: client/src/shared/paginate/paginate.partial.html:55 -msgid "{{pageSize}}" -msgstr "{{pageSize}}" diff --git a/awx/ui/po/nl.po b/awx/ui/po/nl.po deleted file mode 100644 index cc0e8c5f4da5..000000000000 --- a/awx/ui/po/nl.po +++ /dev/null @@ -1,6984 +0,0 @@ -# Froebel Flores , 2017. #zanata -# helena , 2017. #zanata -# helena02 , 2017. #zanata -# helena , 2018. #zanata -msgid "" -msgstr "" -"Project-Id-Version: \n" -"PO-Revision-Date: 2018-08-17 09:57+0000\n" -"Last-Translator: helena \n" -"Language-Team: Dutch\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: nl\n" -"Plural-Forms: nplurals=2; plural=(n != 1)\n" -"X-Generator: Zanata 4.6.0\n" - -#: client/src/projects/add/projects-add.controller.js:162 -#: client/src/projects/edit/projects-edit.controller.js:297 -msgid "" -"%sNote:%s Mercurial does not support password authentication for SSH. Do not" -" put the username and key in the URL. If using Bitbucket and SSH, do not " -"supply your Bitbucket username." -msgstr "" -"%sLet op:%s wachtwoordauthenticatie voor SSH wordt niet ondersteund door " -"Mercurial. Zet de gebruikersnaam en sleutel niet in de URL. Indien u " -"Bitbucket en SSH gebruikt, noem uw Bitbucket-gebruikersnaam dan niet." - -#: client/src/projects/add/projects-add.controller.js:141 -#: client/src/projects/edit/projects-edit.controller.js:276 -msgid "" -"%sNote:%s 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." -msgstr "" -"%sLet op:%s 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." - -#: client/src/credentials/credentials.form.js:287 -msgid "(defaults to %s)" -msgstr "(wordt standaard %s)" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:402 -msgid "(seconds)" -msgstr "(seconden)" - -#: client/src/shared/paginate/paginate.partial.html:66 -msgid "100" -msgstr "100" - -#: client/src/shared/paginate/paginate.partial.html:60 -msgid "20" -msgstr "20" - -#: client/src/shared/paginate/paginate.partial.html:63 -msgid "50" -msgstr "50" - -#: client/lib/components/code-mirror/code-mirror.strings.js:17 -msgid "" -"

\n" -" Enter inventory variables using either JSON or YAML\n" -" syntax. Use the radio button to toggle between the two.\n" -"

\n" -" JSON:\n" -"
\n" -"
\n" -" {\n" -"
\"somevar\": \"somevalue\",\n" -"
\"password\": \"magic\"\n" -"
\n" -" }\n" -"
\n" -" YAML:\n" -"
\n" -"
\n" -" ---\n" -"
somevar: somevalue\n" -"
password: magic\n" -"
\n" -"
\n" -"

\n" -" View JSON examples at\n" -" www.json.org\n" -"

\n" -"

\n" -" View YAML examples at\n" -" \n" -" docs.ansible.com\n" -"

" -msgstr "" -"

\n" -" Voeg de variabelen van het inventaris in met JSON- of YAML-syntax. Gebruik de radio-knop om tussen de twee te wisselen.\n" -"

\n" -" JSON:\n" -"
\n" -"
\n" -" {\n" -"
\"somevar\": \"somevalue\",,\n" -"
\"wachtwoord\": \"magic\"\n" -"
\n" -" }\n" -"
\n" -" YAML:\n" -"
\n" -"
\n" -" ---\n" -"
somevar: somevalue\n" -"
wachtwoord: magic\n" -"
\n" -"
\n" -"

\n" -" Raadpleeg\n" -" www.json.org voor voorbeelden van JSON\n" -"

\n" -"

\n" -" Raadpleeg \n" -" \n" -" docs.ansible.com voor voorbeelden van YAML\n" -"

" - -#: client/features/templates/templates.strings.js:55 -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.

JSON:
{
"somevar": "somevalue",
"password": " -""magic"
}
YAML:
---
somevar: somevalue
password: magic
" -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.

JSON:
{
"somevar": "somevalue",
"password": " -""magic"
}
YAML:
---
somevar: somevalue
password: magic
" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:33 -#: client/src/scheduler/scheduler.strings.js:22 -msgid "A schedule name is required." -msgstr "Een naam voor het schema is vereist." - -#: client/src/users/add/users-add.controller.js:103 -msgid "A value is required" -msgstr "Een waarde is vereist" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:167 -msgid "A value is required." -msgstr "Een waarde is vereist." - -#: client/src/about/about.route.js:10 -msgid "ABOUT" -msgstr "OVER" - -#: client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html:16 -msgid "ACTION" -msgstr "ACTIE" - -#: client/src/activity-stream/activity-detail.form.js:23 -msgid "ACTIVITY DETAIL" -msgstr "ACTIVITEITSGEGEVENS" - -#: client/src/activity-stream/activitystream.route.js:28 -#: client/src/activity-stream/streams.list.js:14 -#: client/src/activity-stream/streams.list.js:15 -msgid "ACTIVITY STREAM" -msgstr "ACTIVITEITENLOGBOEK" - -#: client/src/organizations/linkout/addUsers/addUsers.partial.html:8 -msgid "ADD" -msgstr "TOEVOEGEN" - -#: client/src/notifications/notification-templates-list/add-notifications-action.partial.html:3 -msgid "ADD A NEW TEMPLATE" -msgstr "EEN NIEUW SJABLOON TOEVOEGEN" - -#: client/features/templates/templates.strings.js:107 -msgid "ADD A TEMPLATE" -msgstr "EEN SJABLOON TOEVOEGEN" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:16 -msgid "ADD SURVEY PROMPT" -msgstr "MELDING VRAGENLIJST TOEVOEGEN" - -#: client/src/shared/smart-search/smart-search.partial.html:48 -msgid "ADDITIONAL INFORMATION" -msgstr "AANVULLENDE INFORMATIE" - -#: client/features/output/output.strings.js:76 -msgid "ADDITIONAL_INFORMATION" -msgstr "AANVULLENDE_INFORMATIE" - -#: client/src/organizations/linkout/organizations-linkout.route.js:258 -#: client/src/organizations/list/organizations-list.controller.js:85 -msgid "ADMINS" -msgstr "BEHEERDERS" - -#: client/src/activity-stream/get-target-title.factory.js:4 -msgid "ALL ACTIVITY" -msgstr "ALLE ACTIVITEIT" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:239 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:254 -msgid "ANY" -msgstr "IEDERE" - -#: client/src/credentials/credentials.form.js:198 -msgid "API Key" -msgstr "API-sleutel" - -#: client/src/notifications/notificationTemplates.form.js:243 -msgid "API Service/Integration Key" -msgstr "Service-/integratiesleutel API" - -#: client/src/notifications/shared/type-change.service.js:60 -msgid "API Token" -msgstr "API-token" - -#: client/features/users/tokens/tokens.strings.js:40 -msgid "APPLICATION" -msgstr "TOEPASSING" - -#: client/features/applications/applications.strings.js:28 -#: client/features/applications/applications.strings.js:8 -#: client/src/activity-stream/get-target-title.factory.js:47 -msgid "APPLICATIONS" -msgstr "TOEPASSINGEN" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.route.js:19 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.route.js:19 -msgid "ASSOCIATED GROUPS" -msgstr "VERBONDEN GROEPEN" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.route.js:19 -msgid "ASSOCIATED HOSTS" -msgstr "VERBONDEN HOSTS" - -#: client/lib/components/components.strings.js:87 -msgid "About" -msgstr "Over" - -#: client/lib/components/components.strings.js:91 -msgid "Access" -msgstr "Toegang" - -#: client/src/credentials/credentials.form.js:91 -msgid "Access Key" -msgstr "Toegangssleutel" - -#: client/src/notifications/notificationTemplates.form.js:221 -msgid "Account SID" -msgstr "SID account" - -#: client/src/notifications/notificationTemplates.form.js:180 -msgid "Account Token" -msgstr "Accounttoken" - -#: client/src/activity-stream/activity-detail.form.js:36 -msgid "Action" -msgstr "Actie" - -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:20 -#: client/src/inventories-hosts/hosts/hosts.partial.html:47 -#: client/src/shared/list-generator/list-generator.factory.js:591 -msgid "Actions" -msgstr "Acties" - -#: client/features/templates/templates.strings.js:15 -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:17 -#: client/src/templates/templates.list.js:36 -msgid "Activity" -msgstr "Activiteit" - -#: client/src/configuration/system-form/configuration-system.controller.js:88 -msgid "Activity Stream" -msgstr "Activiteitenlogboek" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:113 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:115 -#: client/src/organizations/organizations.form.js:93 -#: client/src/teams/teams.form.js:85 -#: client/src/templates/workflows.form.js:147 -msgid "Add" -msgstr "Toevoegen" - -#: client/src/credentials/credentials.list.js:14 -msgid "Add Credentials" -msgstr "Toegangsgegevens toevoegen" - -#: client/src/inventories-hosts/inventories/inventory.list.js:13 -msgid "Add Inventories" -msgstr "Inventarissen toevoegen" - -#: client/src/shared/stateDefinitions.factory.js:304 -msgid "Add Permissions" -msgstr "Machtigingen toevoegen" - -#: client/src/projects/projects.list.js:13 -msgid "Add Project" -msgstr "Project toevoegen" - -#: client/src/shared/form-generator.js:1731 -#: client/src/templates/job_templates/job-template.form.js:468 -#: client/src/templates/workflows.form.js:205 -msgid "Add Survey" -msgstr "Vragenlijst toevoegen" - -#: client/src/teams/teams.list.js:13 -msgid "Add Team" -msgstr "Team toevoegen" - -#: client/src/teams/teams.form.js:86 -msgid "Add User" -msgstr "Gebruiker toevoegen" - -#: client/src/shared/stateDefinitions.factory.js:426 -#: client/src/shared/stateDefinitions.factory.js:594 -#: client/src/users/users.list.js:17 -msgid "Add Users" -msgstr "Gebruikers toevoegen" - -#: client/src/organizations/organizations.form.js:94 -msgid "Add Users to this organization." -msgstr "Gebruikers toevoegen aan deze organisatie." - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:69 -msgid "Add a group" -msgstr "Een groep toevoegen" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:118 -msgid "Add a host" -msgstr "Een host toevoegen" - -#: client/src/scheduler/schedules.list.js:74 -msgid "Add a new schedule" -msgstr "Een nieuw schema toevoegen" - -#: client/features/credentials/legacy.credentials.js:71 -#: client/src/credentials/credentials.form.js:448 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:115 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:117 -#: client/src/projects/projects.form.js:255 -#: client/src/templates/job_templates/job-template.form.js:411 -#: client/src/templates/workflows.form.js:148 -msgid "Add a permission" -msgstr "Een machtiging toevoegen" - -#: client/src/shared/form-generator.js:1466 -msgid "Admin" -msgstr "Beheerder" - -#: client/lib/components/components.strings.js:92 -msgid "Administration" -msgstr "Beheer" - -#: client/src/organizations/linkout/organizations-linkout.route.js:281 -msgid "Admins" -msgstr "Beheerders" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:391 -msgid "" -"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." -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." - -#: client/lib/components/components.strings.js:99 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:37 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:43 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:65 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:74 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:139 -msgid "All" -msgstr "Alle" - -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:23 -msgid "All Activity" -msgstr "Alle activiteit" - -#: client/features/portalMode/index.view.html:33 -msgid "All Jobs" -msgstr "Alle taken" - -#: client/src/templates/job_templates/job-template.form.js:290 -#: client/src/templates/job_templates/job-template.form.js:297 -msgid "Allow Provisioning Callbacks" -msgstr "Provisioning terugkoppelingen toestaan" - -#: client/features/templates/templates.strings.js:102 -#: client/src/workflow-results/workflow-results.controller.js:66 -msgid "Always" -msgstr "Altijd" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:270 -msgid "An SCM update does not appear to be running for project:" -msgstr "Het lijkt er op dat er geen SCM-update bezig is voor project:" - -#: client/src/projects/list/projects-list.controller.js:311 -msgid "" -"An SCM update does not appear to be running for project: %s. Click the " -"%sRefresh%s button to view the latest status." -msgstr "" -"Het lijkt er op dat er geen SCM-update bezig is voor project: %s. Klik op de" -" knop %sVernieuwen%s om de nieuwste status in te zien." - -#: client/src/organizations/organizations.form.js:47 -#: client/src/organizations/organizations.form.js:52 -#: client/src/projects/projects.form.js:207 -#: client/src/projects/projects.form.js:212 -#: client/src/templates/job_templates/job-template.form.js:239 -#: client/src/templates/job_templates/job-template.form.js:245 -msgid "Ansible Environment" -msgstr "Ansible-omgeving" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:62 -#: client/src/templates/survey-maker/shared/question-definition.form.js:68 -msgid "Answer Type" -msgstr "Antwoordtype" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:44 -#: client/src/templates/survey-maker/shared/question-definition.form.js:53 -msgid "Answer Variable Name" -msgstr "Antwoord naam variabele" - -#: client/lib/components/components.strings.js:85 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:24 -msgid "Applications" -msgstr "Toepassingen" - -#: client/src/notifications/notification-templates-list/list.controller.js:228 -msgid "Are you sure you want to delete this notification template?" -msgstr "Weet u zeker dat u dit berichtsjabloon wilt verwijderen?" - -#: client/src/teams/list/teams-list.controller.js:80 -msgid "Are you sure you want to delete this team?" -msgstr "Weet u zeker dat u dit team wilt verwijderen?" - -#: client/src/users/list/users-list.controller.js:93 -msgid "Are you sure you want to delete this user?" -msgstr "Weet u zeker dat u deze gebruiker wilt verwijderen?" - -#: client/features/templates/templates.strings.js:98 -msgid "Are you sure you want to delete this workflow node?" -msgstr "Weet u zeker dat u dit workflowknooppunt wilt verwijderen?" - -#: client/lib/services/base-string.service.js:81 -msgid "Are you sure you want to delete this {{ resourceType }}?" -msgstr "Weet u zeker dat u dit {{ resourceType }} wilt verwijderen?" - -#: client/src/partials/survey-maker-modal.html:13 -msgid "Are you sure you want to delete this {{deleteMode}}?" -msgstr "Weet u zeker dat u deze {{deleteMode}} wilt verwijderen?" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-disassociate.partial.html:25 -msgid "Are you sure you want to disassociate the group below from" -msgstr "Weet u zeker dat u onderstaande groep los wilt koppelen van" - -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.partial.html:23 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-disassociate.partial.html:25 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-disassociate.partial.html:26 -msgid "Are you sure you want to disassociate the host below from" -msgstr "Weet u zeker dat u onderstaande host los wilt koppelen van" - -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:47 -msgid "" -"Are you sure you want to permanently delete the group below from the " -"inventory?" -msgstr "" -"Weet u zeker dat u onderstaande groep permanent wilt verwijderen uit de " -"inventaris?" - -#: client/src/inventories-hosts/inventories/related/hosts/list/host-list.controller.js:106 -msgid "" -"Are you sure you want to permanently delete the host below from the " -"inventory?" -msgstr "Weet u zeker dat u onderstaande host permanent wilt verwijderen?" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:69 -msgid "" -"Are you sure you want to permanently delete the inventory source below from " -"the inventory?" -msgstr "" -"Weet u zeker dat u onderstaande inventarisbron permanent wilt verwijderen " -"uit de inventaris?" - -#: client/src/projects/edit/projects-edit.controller.js:253 -msgid "Are you sure you want to remove the %s below from %s?" -msgstr "Weet u zeker dat u onderstaande %s wilt verwijderen uit de %s?" - -#: client/lib/services/base-string.service.js:86 -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?" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:39 -msgid "Arguments" -msgstr "Argumenten" - -#: client/src/credentials/credentials.form.js:232 -#: client/src/credentials/credentials.form.js:271 -#: client/src/credentials/credentials.form.js:311 -#: client/src/credentials/credentials.form.js:397 -msgid "Ask at runtime?" -msgstr "Vragen bij runtime?" - -#: client/src/instance-groups/instance-groups.strings.js:31 -msgid "Associate an existing Instance" -msgstr "Een bestaande instantie verbinden" - -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:69 -msgid "Associate an existing group" -msgstr "Een bestaande groep verbinden" - -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.list.js:51 -msgid "Associate this host with a new group" -msgstr "Deze host verbinden met een nieuwe groep" - -#: client/src/shared/form-generator.js:1468 -msgid "Auditor" -msgstr "Auditor" - -#: client/src/configuration/configuration.partial.html:15 -msgid "Authentication" -msgstr "Authenticatie" - -#: client/src/credentials/credentials.form.js:72 -msgid "" -"Authentication for network device access. This can include SSH keys, " -"usernames, passwords, and authorize information. Network credentials are " -"used when submitting jobs to run playbooks against network devices." -msgstr "" -"Authenticatie voor toegang tot het netwerkapparaat. Hieronder kunnen vallen:" -" SSH-sleutels, gebruikersnamen, wachtwoorden en autorisatie-informatie. " -"Netwerktoegangsgegevens worden gebruikt voor het indienen van taken die " -"draaiboeken afspelen tegen netwerkapparaten." - -#: client/src/credentials/credentials.form.js:68 -msgid "" -"Authentication for remote machine access. This can include SSH keys, " -"usernames, passwords, and sudo information. Machine credentials are used " -"when submitting jobs to run playbooks against remote hosts." -msgstr "" -"Authenticatie voor machinetoegang op afstand. Hieronder kunnen vallen: SSH-" -"sleutels, gebruikersnamen, wachtwoorden en sudo-informatie. " -"Machinetoegangsgegevens worden gebruikt voor het indienen van taken die " -"draaiboeken afspelen tegen hosts op afstand." - -#: client/src/credentials/credentials.form.js:343 -msgid "Authorize" -msgstr "Autoriseren" - -#: client/src/credentials/credentials.form.js:351 -msgid "Authorize Password" -msgstr "Wachtwoord autoriseren" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:226 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:240 -msgid "Availability Zone:" -msgstr "Beschikbaarheidsgebied:" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:152 -msgid "Azure AD" -msgstr "Azure AD" - -#: client/src/shared/directives.js:91 -msgid "BROWSE" -msgstr "BLADEREN" - -#: client/features/output/output.strings.js:97 -msgid "Back to Top" -msgstr "Omhoog" - -#: client/src/projects/projects.form.js:81 -msgid "" -"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." -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." - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:129 -msgid "Become Privilege Escalation" -msgstr "Verhoging van rechten worden" - -#: client/src/license/license.partial.html:107 -msgid "Browse" -msgstr "Bladeren" - -#: client/src/license/license.partial.html:129 -msgid "" -"By default, Tower collects and transmits analytics data on Tower usage to Red Hat. This data is used to enhance future releases of the Tower Software and help streamline customer experience and success. For more information, see\n" -"\t\t\t\t\t\t\t\t\t\t\n" -"\t\t\t\t\t\t\t\t\t\t\t\tthis Tower documentation page\n" -"\t\t\t\t\t\t\t\t\t\t. Uncheck this box to disable this feature." -msgstr "" -"Tower verzamelt standaard analysegegevens over het gebruik van Tower en verstuurt deze naar Red Hat. Deze gegevens worden gebruikt om toekomstige uitgaven van de Tower-software te verbeteren en de ervaring en het succes van klanten te helpen optimaliseren. Raadpleeg voor meer informatie \n" -"deze documentatiepagina van Tower\n" -". Zet het vinkje van dit selectievakje uit om deze functie uit te schakelen." - -#: client/lib/services/base-string.service.js:60 -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.partial.html:28 -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:51 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-disassociate.partial.html:29 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-disassociate.partial.html:29 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-disassociate.partial.html:30 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:73 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal.partial.html:16 -#: client/src/inventories-hosts/shared/associate-groups/associate-groups.partial.html:16 -#: client/src/inventories-hosts/shared/associate-hosts/associate-hosts.partial.html:16 -#: client/src/job-submission/job-submission.partial.html:370 -#: client/src/partials/survey-maker-modal.html:17 -#: client/src/partials/survey-maker-modal.html:85 -#: client/src/shared/instance-groups-multiselect/instance-groups-modal/instance-groups-modal.partial.html:17 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html:68 -msgid "CANCEL" -msgstr "ANNULEREN" - -#: client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html:28 -msgid "CHANGES" -msgstr "WIJZIGINGEN" - -#: client/features/templates/templates.strings.js:113 -msgid "CHECK" -msgstr "CONTROLE" - -#: client/lib/components/components.strings.js:20 -msgid "CHOOSE A FILE" -msgstr "KIES EEN BESTAND" - -#: client/features/output/output.strings.js:78 -#: client/src/shared/smart-search/smart-search.partial.html:26 -msgid "CLEAR ALL" -msgstr "ALLES WISSEN" - -#: client/lib/services/base-string.service.js:61 -#: client/lib/services/base-string.service.js:74 -#: client/src/partials/survey-maker-modal.html:86 -msgid "CLOSE" -msgstr "SLUITEN" - -#: client/features/jobs/routes/hostCompletedJobs.route.js:20 -#: client/features/jobs/routes/templateCompletedJobs.route.js:21 -#: client/features/jobs/routes/workflowJobTemplateCompletedJobs.route.js:21 -msgid "COMPLETED JOBS" -msgstr "VOLTOOIDE TAKEN" - -#: client/src/configuration/configuration.partial.html:10 -msgid "CONFIGURE {{ BRAND_NAME }}" -msgstr "{{ BRAND_NAME }} CONFIGUREREN" - -#: client/features/templates/templates.strings.js:31 -#: client/src/scheduler/scheduler.strings.js:63 -msgid "CONFIRM" -msgstr "BEVESTIGEN" - -#: client/lib/services/base-string.service.js:72 -msgid "COPY" -msgstr "KOPIËREN" - -#: client/features/users/tokens/tokens.strings.js:25 -msgid "COULD NOT CREATE TOKEN" -msgstr "KON GEEN TOKEN AANMAKEN" - -#: client/src/instance-groups/instance-groups.strings.js:46 -msgid "CPU" -msgstr "CPU" - -#: client/src/shared/stateDefinitions.factory.js:161 -msgid "CREATE %s" -msgstr "%s AANMAKEN" - -#: client/features/applications/applications.strings.js:9 -msgid "CREATE APPLICATION" -msgstr "TOEPASSING AANMAKEN" - -#: client/features/credentials/credentials.strings.js:8 -#: client/src/credentials/credentials.form.js:16 -msgid "CREATE CREDENTIAL" -msgstr "TOEGANGSGEGEVENS AANMAKEN" - -#: client/src/inventories-hosts/inventories/related/groups/add/groups-add.route.js:8 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:16 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-add.route.js:8 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js:16 -msgid "CREATE GROUP" -msgstr "GROEP AANMAKEN" - -#: client/src/inventories-hosts/hosts/host.form.js:17 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-add.route.js:8 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:17 -#: client/src/inventories-hosts/inventories/related/hosts/add/host-add.route.js:8 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:17 -msgid "CREATE HOST" -msgstr "HOST AANMAKEN" - -#: client/src/instance-groups/instance-groups.strings.js:10 -msgid "CREATE INSTANCE GROUP" -msgstr "INSTANTIEGROEP AANMAKEN" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.route.js:8 -msgid "CREATE INVENTORY SOURCE" -msgstr "INVENTARISBRON AANMAKEN" - -#: client/src/inventories-hosts/inventories/related/sources/list/schedule/sources-schedule-add.route.js:9 -#: client/src/scheduler/scheduler.strings.js:8 -#: client/src/scheduler/schedules.route.js:161 -#: client/src/scheduler/schedules.route.js:242 -#: client/src/scheduler/schedules.route.js:73 -msgid "CREATE SCHEDULE" -msgstr "SCHEMA AANMAKEN" - -#: client/src/management-jobs/scheduler/main.js:83 -msgid "CREATE SCHEDULED JOB" -msgstr "GEPLANDE TAAK AANMAKEN" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:32 -msgid "CREATE SOURCE" -msgstr "BRON AANMAKEN" - -#: client/features/users/tokens/tokens.strings.js:18 -#: client/features/users/tokens/tokens.strings.js:9 -#: client/features/users/tokens/users-tokens-add.route.js:49 -msgid "CREATE TOKEN" -msgstr "TOKEN AANMAKEN" - -#: client/features/output/output.strings.js:101 -msgid "CREATED" -msgstr "AANGEMAAKT" - -#: client/src/job-submission/job-submission.partial.html:351 -#: client/src/partials/job-template-details.html:2 -msgid "CREDENTIAL" -msgstr "TOEGANGSGEGEVENS" - -#: client/src/credential-types/credential-types.form.js:21 -msgid "CREDENTIAL TYPE" -msgstr "SOORT TOEGANGSGEGEVENS" - -#: client/src/job-submission/job-submission.partial.html:92 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html:60 -msgid "CREDENTIAL TYPE:" -msgstr "SOORT TOEGANGSGEGEVENS:" - -#: client/src/activity-stream/get-target-title.factory.js:11 -#: client/src/credential-types/credential-types.list.js:12 -#: client/src/credential-types/main.js:44 -msgid "CREDENTIAL TYPES" -msgstr "SOORTEN TOEGANGSGEGEVENS" - -#: client/features/credentials/legacy.credentials.js:11 -#: client/src/activity-stream/get-target-title.factory.js:17 -#: client/src/credentials/credentials.list.js:15 -#: client/src/credentials/credentials.list.js:16 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html:5 -msgid "CREDENTIALS" -msgstr "TOEGANGSGEGEVENS" - -#: client/features/credentials/credentials.strings.js:30 -msgid "CREDENTIALS PERMISSIONS" -msgstr "MACHTIGINGEN TOEGANGSGEGEVENS" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:402 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:414 -#: client/src/projects/projects.form.js:200 -msgid "Cache Timeout" -msgstr "Cache time-out" - -#: client/src/projects/projects.form.js:189 -msgid "Cache Timeout%s (seconds)%s" -msgstr "Cache time-out %s (seconden)%s" - -#: client/src/projects/list/projects-list.controller.js:224 -#: client/src/users/list/users-list.controller.js:85 -msgid "Call to %s failed. DELETE returned status:" -msgstr "Oproepen %s mislukt. Geretourneerde status VERWIJDEREN:" - -#: client/src/projects/list/projects-list.controller.js:291 -#: client/src/projects/list/projects-list.controller.js:308 -msgid "Call to %s failed. GET status:" -msgstr "Oproepen %s mislukt. Status OPHALEN:" - -#: client/src/projects/edit/projects-edit.controller.js:247 -msgid "Call to %s failed. POST returned status:" -msgstr "Oproepen %s mislukt. Geretourneerde status POSTEN:" - -#: client/src/projects/list/projects-list.controller.js:270 -msgid "Call to %s failed. POST status:" -msgstr "Oproepen %s mislukt. Status POSTEN:" - -#: client/src/management-jobs/card/card.controller.js:29 -msgid "Call to %s failed. Return status: %d" -msgstr "Oproepen %s mislukt. Status retourneren: %d" - -#: client/src/projects/list/projects-list.controller.js:317 -msgid "Call to get project failed. GET status:" -msgstr "Oproep om project op te halen mislukt. Status OPHALEN:" - -#: client/lib/services/base-string.service.js:93 -msgid "Call to {{ path }} failed. {{ action }} returned status: {{ status }}." -msgstr "" -"Oproepen {{ path }} mislukt. {{ action }} heeft de volgende status " -"teruggegeven: {{ status }}." - -#: client/features/output/output.strings.js:17 -#: client/lib/services/base-string.service.js:85 -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:105 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:188 -#: client/src/configuration/configuration.controller.js:617 -#: client/src/scheduler/scheduler.strings.js:56 -#: client/src/shared/form-generator.js:1719 -#: client/src/shared/lookup/lookup-modal.partial.html:19 -#: client/src/workflow-results/workflow-results.controller.js:38 -msgid "Cancel" -msgstr "Annuleren" - -#: client/lib/services/base-string.service.js:87 -msgid "Cancel Job" -msgstr "Taak annuleren" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:244 -#: client/src/projects/list/projects-list.controller.js:286 -msgid "Cancel Not Allowed" -msgstr "Annuleren niet toegestaan" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:100 -msgid "Cancel sync process" -msgstr "Synchronisatieproces annuleren" - -#: client/src/projects/projects.list.js:122 -msgid "Cancel the SCM update" -msgstr "SCM-update annuleren" - -#: client/lib/services/base-string.service.js:99 -msgid "Cancel the {{resourceType}}" -msgstr "Annuleer de {{resourceType}}" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:42 -#: client/src/projects/list/projects-list.controller.js:80 -msgid "Canceled. Click for details" -msgstr "Geannuleerd. Klik voor meer informatie" - -#: client/src/shared/smart-search/smart-search.controller.js:162 -msgid "Cannot search running job" -msgstr "Kan taak in uitvoering niet zoeken" - -#: client/src/instance-groups/instance-groups.list.js:22 -msgid "Capacity" -msgstr "Capaciteit" - -#: client/src/projects/projects.form.js:83 -msgid "Change %s under \"Configure {{BRAND_NAME}}\" to change this location." -msgstr "" -"Wijzig %s onder \"{{BRAND_NAME}} configureren\" om deze locatie te wijzigen." - -#: client/src/activity-stream/activity-detail.form.js:41 -msgid "Changes" -msgstr "Wijzigingen" - -#: client/src/notifications/notificationTemplates.form.js:355 -msgid "Channel" -msgstr "Kanaal" - -#: client/features/templates/templates.strings.js:61 -msgid "Check" -msgstr "Controleren" - -#: client/src/shared/form-generator.js:1087 -msgid "Choose a %s" -msgstr "Kies een %s" - -#: client/features/templates/templates.strings.js:52 -msgid "Choose a job type" -msgstr "Kies een soort taak" - -#: client/features/templates/templates.strings.js:53 -msgid "Choose a verbosity" -msgstr "Kies een verbositeit" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:64 -msgid "Choose an answer type" -msgstr "Kies een antwoordtype" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:67 -msgid "" -"Choose an answer type or format you want as the prompt for the user. Refer " -"to the Ansible Tower Documentation for more additional 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." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:112 -msgid "Choose an inventory file" -msgstr "Kies een inventarisbestand" - -#: client/src/shared/directives.js:92 -msgid "Choose file" -msgstr "Bestand kiezen" - -#: client/src/license/license.partial.html:97 -msgid "" -"Choose your license file, agree to the End User License Agreement, and click" -" submit." -msgstr "" -"Kies uw licentiebestand, ga akkoord met de Licentie-overeenkomst voor " -"eindgebruikers en klik op indienen." - -#: client/src/projects/projects.form.js:157 -msgid "Clean" -msgstr "Opschonen" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:299 -msgid "Clear" -msgstr "Wissen" - -#: client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.directive.js:64 -msgid "Click for details" -msgstr "Klik voor meer informatie" - -#: client/src/templates/workflows/edit-workflow/workflow-edit.controller.js:261 -msgid "Click here to open the workflow visualizer." -msgstr "Klik hier om de editor om de workflowweergave te openen." - -#: client/src/inventories-hosts/inventories/inventory.list.js:16 -msgid "" -"Click on a row to select it, and click Finished when done. Click the %s " -"button to create a new inventory." -msgstr "" -"Klik op een rij om deze te selecteren en klik op Klaar wanneer u klaar bent." -" Klik op de knop %s om een nieuwe inventaris aan te maken." - -#: client/src/teams/teams.list.js:16 -msgid "" -"Click on a row to select it, and click Finished when done. Click the %s " -"button to create a new team." -msgstr "" -"Klik op een rij om deze te selecteren en klik op Klaar wanneer u klaar bent." -" Klik op de knop %s om een nieuw team aan te maken." - -#: client/src/templates/templates.list.js:17 -msgid "" -"Click on a row to select it, and click Finished when done. Use the %s button" -" to create a new job template." -msgstr "" -"Klik op een rij om deze te selecteren en klik op Klaar wanneer u klaar bent." -" Klik op de knop %s om een nieuw taaksjabloon aan te maken." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:138 -msgid "" -"Click on the regions field to see a list of regions for your cloud provider." -" You can select multiple regions, or choose" -msgstr "" -"Klik op de regiovelden om een lijst van regio's voor uw cloudprovider in te " -"zien. U kunt meerdere regio's selecteren, of kiezen" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:270 -msgid "Click the" -msgstr "Klik op" - -#: client/src/scheduler/scheduler.strings.js:13 -msgid "Click to edit schedule." -msgstr "Klik om het schema te wijzigen." - -#: client/src/credentials/credentials.form.js:321 -msgid "Client ID" -msgstr "Klant-ID" - -#: client/src/notifications/notificationTemplates.form.js:254 -msgid "Client Identifier" -msgstr "Klant-identificeerder" - -#: client/src/credentials/credentials.form.js:330 -msgid "Client Secret" -msgstr "Klant-geheim" - -#: client/src/scheduler/scheduler.strings.js:55 -#: client/src/shared/form-generator.js:1723 -msgid "Close" -msgstr "Sluiten" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:26 -msgid "Cloud source not configured." -msgstr "Cloudbron niet geconfigureerd." - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:25 -msgid "Cloud source not configured. Click" -msgstr "Cloudbron niet geconfigureerd. Klik op" - -#: client/src/credentials/factories/become-method-change.factory.js:80 -#: client/src/credentials/factories/kind-change.factory.js:137 -msgid "CloudForms URL" -msgstr "CloudForms URL" - -#: client/features/output/output.strings.js:18 -#: client/src/workflow-results/workflow-results.controller.js:152 -msgid "Collapse Output" -msgstr "Output samenvouwen" - -#: client/src/inventories-hosts/hosts/host.form.js:129 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:128 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:155 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:172 -#: client/src/templates/job_templates/job-template.form.js:443 -#: client/src/templates/workflows.form.js:180 -msgid "Completed Jobs" -msgstr "Voltooide taken" - -#: client/src/management-jobs/card/card.partial.html:34 -msgid "Configure Notifications" -msgstr "Notificaties configureren" - -#: client/src/users/users.form.js:83 -msgid "Confirm Password" -msgstr "Wachtwoord bevestigen" - -#: client/src/configuration/configuration.controller.js:624 -msgid "Confirm Reset" -msgstr "Reset bevestigen" - -#: client/src/configuration/configuration.controller.js:633 -msgid "Confirm factory reset" -msgstr "Reset naar fabrieksinstellingen bevestigen" - -#: client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.directive.js:82 -msgid "Confirm the removal of the" -msgstr "Bevestig het verwijderen van de" - -#: client/src/teams/teams.form.js:24 client/src/users/users.form.js:25 -msgid "" -"Contact your System Administrator to grant you the appropriate permissions " -"to add and edit Users and Teams." -msgstr "" -"Neem contact op met uw systeembeheerder om de nodige toestemmingen te " -"krijgen om Gebruikers en Teams toe te voegen en te wijzigen." - -#: client/src/inventories-hosts/inventories/related/groups/factories/get-hosts-status-msg.factory.js:18 -msgid "Contains 0 hosts." -msgstr "Bevat 0 hosts." - -#: client/src/templates/job_templates/job-template.form.js:179 -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." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:337 -msgid "" -"Control the level of output ansible will produce for inventory source update" -" jobs." -msgstr "" -"Stel in hoeveel output Ansible produceert bij taken die de inventarisbron " -"updaten." - -#: client/lib/components/components.strings.js:52 -msgid "Copied to clipboard." -msgstr "Gekopieerd naar klembord." - -#: client/src/credentials/credentials.list.js:73 -#: client/src/inventories-hosts/inventories/inventory.list.js:105 -#: client/src/inventory-scripts/inventory-scripts.list.js:61 -#: client/src/notifications/notificationTemplates.list.js:82 -#: client/src/projects/projects.list.js:100 -#: client/src/templates/templates.list.js:93 -msgid "Copy" -msgstr "Kopiëren" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:56 -msgid "Copy Inventory" -msgstr "Inventaris kopiëren" - -#: client/src/credentials/credentials.list.js:76 -msgid "Copy credential" -msgstr "Toegangsgegevens kopiëren" - -#: client/lib/components/components.strings.js:51 -msgid "Copy full revision to clipboard." -msgstr "Volledige herziening kopiëren naar klembord." - -#: client/src/inventory-scripts/inventory-scripts.list.js:64 -msgid "Copy inventory script" -msgstr "Inventarisscript kopiëren" - -#: client/src/notifications/notificationTemplates.list.js:85 -msgid "Copy notification" -msgstr "Bericht kopiëren" - -#: client/src/projects/projects.list.js:103 -msgid "Copy project" -msgstr "Project kopiëren" - -#: client/src/templates/templates.list.js:96 -msgid "Copy template" -msgstr "Sjabloon kopiëren" - -#: client/lib/services/base-string.service.js:97 -msgid "Copy {{resourceType}}" -msgstr "Kopieer {{resourceType}}" - -#: client/src/about/about.partial.html:27 -msgid "" -"Copyright © 2018 Red Hat, Inc.
\n" -" Visit Ansible.com for more information.
" -msgstr "" -"Auteursrechten © 2018 Red Hat, Inc.
\n" -" Ga naar Ansible.com voor meer informatie.
" - -#: client/lib/components/components.strings.js:88 -msgid "Copyright © 2018 Red Hat, Inc." -msgstr "Auteursrechten @ 2018 Red Hat, Inc." - -#: client/src/users/users.list.js:44 -msgid "Create New" -msgstr "Nieuw aanmaken" - -#: client/features/applications/applications.strings.js:20 -msgid "Create a new Application" -msgstr "Een nieuwe toepassing aanmaken" - -#: client/src/instance-groups/instance-groups.strings.js:30 -msgid "Create a new Instance Group" -msgstr "Een nieuwe instantiegroep aanmaken" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:47 -msgid "" -"Create a new Smart Inventory from search results.

Note: changing " -"the organization of the Smart Inventory could change the hosts included in " -"the Smart Inventory." -msgstr "" -"Een nieuwe Smart-inventaris aanmaken vanuit zoekresultaten.

Let " -"op: wijzigen van de organisatie van de Smart-inventaris kon de hosts in de " -"Smart-inventaris niet wijzigen." - -#: client/src/credentials/credentials.list.js:52 -msgid "Create a new credential" -msgstr "Nieuwe toegangsgegevens aanmaken" - -#: client/src/credential-types/credential-types.list.js:42 -msgid "Create a new credential type" -msgstr "Een nieuwe soort toegangsgegevens aanmaken" - -#: client/src/inventory-scripts/inventory-scripts.list.js:40 -msgid "Create a new custom inventory" -msgstr "Een nieuwe aangepaste inventaris aanmaken" - -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:69 -msgid "Create a new group" -msgstr "Een nieuwe groep aanmaken" - -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:123 -msgid "Create a new host" -msgstr "Een nieuwe host aanmaken" - -#: client/src/inventories-hosts/inventories/inventory.list.js:76 -msgid "Create a new inventory" -msgstr "Een nieuwe inventaris aanmaken" - -#: client/src/notifications/notificationTemplates.list.js:52 -msgid "Create a new notification template" -msgstr "Een nieuw berichtsjabloon aanmaken" - -#: client/src/organizations/list/organizations-list.partial.html:21 -msgid "Create a new organization" -msgstr "Een nieuwe organisatie aanmaken" - -#: client/src/projects/projects.list.js:75 -msgid "Create a new project" -msgstr "Een nieuw project aanmaken" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:68 -msgid "Create a new source" -msgstr "Een nieuwe bron aanmaken" - -#: client/src/teams/teams.list.js:43 -msgid "Create a new team" -msgstr "Een nieuw team aanmaken" - -#: client/src/templates/templates.list.js:56 -msgid "Create a new template" -msgstr "Een nieuw sjabloon aanmaken" - -#: client/src/users/users.list.js:48 -msgid "Create a new user" -msgstr "Een nieuwe gebruiker aanmaken" - -#: client/features/output/output.strings.js:44 -#: client/features/templates/templates.strings.js:25 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:73 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:74 -#: client/src/job-submission/job-submission.partial.html:18 -#: client/src/templates/job_templates/job-template.form.js:121 -msgid "Credential" -msgstr "Toegangsgegevens" - -#: client/features/templates/templates.strings.js:36 -msgid "Credential Type" -msgstr "Soort toegangsgegevens" - -#: client/lib/components/components.strings.js:74 -#: client/lib/models/models.strings.js:12 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:34 -msgid "Credential Types" -msgstr "Soorten toegangsgegevens" - -#: client/features/jobs/jobs.strings.js:16 -#: client/features/templates/templates.strings.js:18 -#: client/lib/components/components.strings.js:73 -#: client/lib/models/models.strings.js:8 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:129 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:58 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:26 -#: client/src/templates/job_templates/job-template.form.js:133 -msgid "Credentials" -msgstr "Toegangsgegevens" - -#: client/features/templates/templates.strings.js:37 -msgid "" -"Credentials that require passwords on launch are not permitted for template " -"schedules and workflow nodes. The following credentials must be removed or " -"replaced to proceed:" -msgstr "" -"Toegangsgegevens die wachtwoorden nodig hebben bij het starten kunnen niet " -"gebruikt worden bij sjabloonschema's en workflowknooppunten. De volgende " -"toegangsgegevens moeten verwijderd of vervangen worden voordat u door kunt " -"gaan:" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:17 -msgid "Critical" -msgstr "Cruciaal" - -#: client/src/shared/directives.js:93 -msgid "Current Image:" -msgstr "Huidige afbeelding:" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:171 -msgid "Custom Inventory Script" -msgstr "Aangepast inventarisscript" - -#: client/src/inventory-scripts/inventory-scripts.form.js:50 -#: client/src/inventory-scripts/inventory-scripts.form.js:60 -msgid "Custom Script" -msgstr "Aangepast script" - -#: client/src/home/home.route.js:21 -msgid "DASHBOARD" -msgstr "DASHBOARD" - -#: client/features/users/tokens/tokens.strings.js:28 -#: client/lib/services/base-string.service.js:71 -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:52 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:74 -#: client/src/notifications/notification-templates-list/list.controller.js:230 -#: client/src/organizations/list/organizations-list.controller.js:196 -#: client/src/partials/survey-maker-modal.html:18 -#: client/src/projects/edit/projects-edit.controller.js:255 -#: client/src/projects/list/projects-list.controller.js:254 -#: client/src/users/list/users-list.controller.js:95 -msgid "DELETE" -msgstr "VERWIJDEREN" - -#: client/src/partials/survey-maker-modal.html:84 -msgid "DELETE SURVEY" -msgstr "VRAGENLIJST VERWIJDEREN" - -#: client/features/templates/templates.strings.js:116 -msgid "DELETED" -msgstr "VERWIJDERD" - -#: client/features/users/tokens/tokens.strings.js:36 -msgid "DESCRIPTION" -msgstr "BESCHRIJVING" - -#: client/features/templates/templates.strings.js:118 -#: client/src/instance-groups/instance-groups.strings.js:24 -#: client/src/workflow-results/workflow-results.controller.js:55 -msgid "DETAILS" -msgstr "MEER INFORMATIE" - -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.partial.html:29 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-disassociate.partial.html:30 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-disassociate.partial.html:30 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-disassociate.partial.html:31 -msgid "DISASSOCIATE" -msgstr "LOSKOPPELEN" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal.partial.html:5 -msgid "DYNAMIC HOSTS" -msgstr "DYNAMISCHE HOSTS" - -#: client/lib/components/components.strings.js:68 -msgid "Dashboard" -msgstr "Dashboard" - -#: client/src/scheduler/scheduler.strings.js:52 -msgid "Date format" -msgstr "Datumindeling" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:164 -msgid "Default" -msgstr "Standaard" - -#: client/features/output/output.strings.js:19 -#: client/lib/services/base-string.service.js:78 -#: client/src/credential-types/credential-types.list.js:73 -#: client/src/credential-types/list/list.controller.js:106 -#: client/src/credentials/credentials.list.js:92 -#: client/src/credentials/list/credentials-list.controller.js:176 -#: client/src/inventories-hosts/inventories/inventory.list.js:121 -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:157 -#: client/src/inventory-scripts/inventory-scripts.list.js:79 -#: client/src/inventory-scripts/list/list.controller.js:126 -#: client/src/notifications/notification-templates-list/list.controller.js:226 -#: client/src/notifications/notificationTemplates.list.js:100 -#: client/src/organizations/list/organizations-list.controller.js:192 -#: client/src/projects/edit/projects-edit.controller.js:252 -#: client/src/projects/list/projects-list.controller.js:250 -#: client/src/scheduler/schedules.list.js:100 -#: client/src/teams/teams.list.js:72 -#: client/src/templates/templates.list.js:109 -#: client/src/users/list/users-list.controller.js:91 -#: client/src/users/users.list.js:79 -#: client/src/workflow-results/workflow-results.controller.js:39 -msgid "Delete" -msgstr "Verwijderen" - -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:6 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:6 -msgid "Delete Group" -msgstr "Groep verwijderen" - -#: client/src/templates/survey-maker/surveys/init.factory.js:23 -msgid "Delete Question" -msgstr "Vraag verwijderen" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:194 -msgid "Delete Source" -msgstr "Bron verwijderen" - -#: client/src/credentials/credentials.list.js:94 -msgid "Delete credential" -msgstr "Toegangsgegevens verwijderen" - -#: client/src/credential-types/credential-types.list.js:75 -msgid "Delete credential type" -msgstr "Soort toegangsgegevens verwijderen" - -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:101 -#: client/src/inventories-hosts/inventory-hosts.strings.js:19 -msgid "Delete group" -msgid_plural "Delete groups" -msgstr[0] "Groep verwijderen" -msgstr[1] "Groepen verwijderen" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:48 -msgid "Delete groups" -msgstr "Groepen verwijderen" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:37 -msgid "Delete groups and hosts" -msgstr "Groepen en hosts verwijderen" - -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:100 -#: client/src/inventories-hosts/inventory-hosts.strings.js:21 -msgid "Delete host" -msgid_plural "Delete hosts" -msgstr[0] "Host verwijderen" -msgstr[1] "Hosts verwijderen" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:59 -msgid "Delete hosts" -msgstr "Hosts verwijderen" - -#: client/src/inventories-hosts/inventories/inventory.list.js:123 -msgid "Delete inventory" -msgstr "Inventaris verwijderen" - -#: client/src/inventory-scripts/inventory-scripts.list.js:81 -msgid "Delete inventory script" -msgstr "Inventarisscript verwijderen" - -#: client/src/notifications/notificationTemplates.list.js:102 -msgid "Delete notification" -msgstr "Bericht verwijderen" - -#: client/src/projects/projects.form.js:167 -msgid "Delete on Update" -msgstr "Verwijderen bij update" - -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:27 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:27 -msgid "Delete or promote the group's children?" -msgstr "De kinderen van de groep verwijderen of promoveren?" - -#: client/src/scheduler/schedules.list.js:103 -msgid "Delete schedule" -msgstr "Schema verwijderen" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:117 -msgid "Delete source" -msgstr "Bron verwijderen" - -#: client/src/teams/teams.list.js:76 -msgid "Delete team" -msgstr "Team verwijderen" - -#: client/src/templates/templates.list.js:112 -msgid "Delete template" -msgstr "Sjabloon verwijderen" - -#: client/src/projects/projects.form.js:169 -msgid "" -"Delete the local repository in its entirety prior to performing an update." -msgstr "" -"De lokale opslagplaats dient volledig verwijderd te worden voordat een " -"update uitgevoerd wordt." - -#: client/src/projects/projects.list.js:116 -msgid "Delete the project" -msgstr "Het project verwijderen" - -#: client/src/scheduler/scheduled-jobs.list.js:81 -msgid "Delete the schedule" -msgstr "Het schema verwijderen" - -#: client/lib/services/base-string.service.js:98 -msgid "Delete the {{resourceType}}" -msgstr "Verwijder de {{resourceType}}" - -#: client/src/users/users.list.js:83 -msgid "Delete user" -msgstr "Gebruiker verwijderen" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:14 -msgid "Delete {{ group }} and {{ host }}" -msgstr "{{ group }} en {{ host }} verwijderen" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:23 -msgid "Deleting group" -msgstr "Groep wordt verwijderd" - -#: client/lib/services/base-string.service.js:80 -msgid "" -"Deleting this {{ resourceType }} will make the following resources " -"unavailable." -msgstr "" -"Als u deze {{ resourceType }} verwijdert, zijn de volgende hulpbronnen niet " -"meer beschikbaar." - -#: client/src/projects/projects.form.js:169 -msgid "" -"Depending on the size of the repository this may significantly increase the " -"amount of time required to complete an update." -msgstr "" -"Afhankelijk van het formaat van de opslagplaats kan de tijd die nodig is om " -"een update uit te voeren hierdoor sterk verlengd worden." - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:246 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:261 -msgid "Describe Instances documentation" -msgstr "Documentatie Instances beschrijven" - -#: client/src/credential-types/credential-types.form.js:34 -#: client/src/credentials/credentials.form.js:39 -#: client/src/inventories-hosts/hosts/host.form.js:63 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:39 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js:40 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:62 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:62 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:58 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:28 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:36 -#: client/src/inventory-scripts/inventory-scripts.form.js:35 -#: client/src/notifications/notificationTemplates.form.js:39 -#: client/src/organizations/organizations.form.js:33 -#: client/src/projects/projects.form.js:37 client/src/teams/teams.form.js:35 -#: client/src/templates/job_templates/job-template.form.js:41 -#: client/src/templates/survey-maker/shared/question-definition.form.js:36 -#: client/src/templates/workflows.form.js:49 -#: client/src/users/users.form.js:147 client/src/users/users.form.js:173 -msgid "Description" -msgstr "Omschrijving" - -#: client/src/notifications/notificationTemplates.form.js:136 -#: client/src/notifications/notificationTemplates.form.js:140 -#: client/src/notifications/notificationTemplates.form.js:152 -#: client/src/notifications/notificationTemplates.form.js:156 -msgid "Destination Channels" -msgstr "Bestemmingskanalen" - -#: client/src/notifications/notificationTemplates.form.js:430 -#: client/src/notifications/notificationTemplates.form.js:434 -msgid "Destination Channels or Users" -msgstr "Bestemmingskanalen of -gebruikers" - -#: client/src/notifications/notificationTemplates.form.js:205 -#: client/src/notifications/notificationTemplates.form.js:206 -msgid "Destination SMS Number" -msgstr "Sms-nummer bestemming" - -#: client/features/applications/applications.strings.js:15 -#: client/features/credentials/credentials.strings.js:13 -#: client/features/output/output.strings.js:34 -#: client/features/users/tokens/tokens.strings.js:14 -#: client/src/license/license.partial.html:5 -#: client/src/shared/form-generator.js:1501 -msgid "Details" -msgstr "Meer informatie" - -#: client/src/job-submission/job-submission.partial.html:263 -msgid "Diff Mode" -msgstr "Diff-modus" - -#: client/src/notifications/notificationTemplates.form.js:369 -#: client/src/notifications/notificationTemplates.form.js:401 -msgid "Disable SSL Verification" -msgstr "SSL-verificatie uitschakelen" - -#: client/src/templates/survey-maker/surveys/init.factory.js:518 -msgid "Disable survey" -msgstr "Vragenlijst uitschakelen" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups-disassociate.partial.html:6 -msgid "Disassociate Group From Group" -msgstr "Groep van groep loskoppelen" - -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.partial.html:6 -msgid "Disassociate Host" -msgstr "Host loskoppelen" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-disassociate.partial.html:6 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups-disassociate.partial.html:6 -msgid "Disassociate Host From Group" -msgstr "Host van groep loskoppelen" - -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.list.js:65 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:110 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:98 -msgid "Disassociate group" -msgstr "Groep loskoppelen" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:87 -msgid "Disassociate host" -msgstr "Host loskoppelen" - -#: client/src/templates/survey-maker/surveys/init.factory.js:21 -msgid "Disable Survey" -msgstr "Vragenlijst uitschkelen" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:94 -#: client/src/configuration/configuration.controller.js:231 -#: client/src/configuration/configuration.controller.js:316 -#: client/src/configuration/system-form/configuration-system.controller.js:57 -msgid "Discard changes" -msgstr "Wijzigingen annuleren" - -#: client/src/teams/teams.form.js:149 -msgid "Dissassociate permission from team" -msgstr "Machtiging loskoppelen van team" - -#: client/src/users/users.form.js:227 -msgid "Dissassociate permission from user" -msgstr "Machtiging loskoppelen van gebruiker" - -#: client/src/credentials/credentials.form.js:384 -#: client/src/credentials/factories/become-method-change.factory.js:54 -#: client/src/credentials/factories/kind-change.factory.js:111 -msgid "Domain Name" -msgstr "Domeinnaam" - -#: client/features/output/output.strings.js:20 -msgid "Download Output" -msgstr "Download output" - -#: client/src/inventory-scripts/inventory-scripts.form.js:59 -msgid "" -"Drag and drop your custom inventory script file here or create one in the " -"field to import your custom inventory. Refer to the Ansible Tower " -"documentation for example syntax." -msgstr "" -"Sleep uw aangepaste inventarisscriptbestand hierheen of maak een nieuwe aan " -"in het veld om uw aangepaste inventaris te importeren. Raadpleeg de " -"documentatie van Ansible Tower voor voorbeeldsyntaxis." - -#: client/src/templates/survey-maker/surveys/init.factory.js:24 -msgid "Drag to reorder question" -msgstr "Sleep om vragen opnieuw te ordenen" - -#: client/src/partials/survey-maker-modal.html:77 -msgid "Drop question here to reorder" -msgstr "Sleep een vraag hierheen om opnieuw te ordenen" - -#: client/features/templates/templates.strings.js:115 -msgid "EDGE CONFLICT" -msgstr "RANDCONFLICT" - -#: client/features/applications/applications.strings.js:10 -msgid "EDIT APPLICATION" -msgstr "TOEPASSING WIJZIGEN" - -#: client/src/configuration/configuration.route.js:28 -msgid "EDIT CONFIGURATION" -msgstr "CONFIGURATIE WIJZIGEN" - -#: client/features/credentials/credentials.strings.js:9 -msgid "EDIT CREDENTIAL" -msgstr "TOEGANGSGEGEVENS WIJZIGEN" - -#: client/src/instance-groups/instance-groups.strings.js:11 -msgid "EDIT INSTANCE GROUP" -msgstr "INSTANTIEGROEP WIJZIGEN" - -#: client/src/scheduler/scheduler.strings.js:9 -msgid "EDIT SCHEDULE" -msgstr "SCHEMA WIJZIGEN" - -#: client/src/management-jobs/scheduler/main.js:97 -msgid "EDIT SCHEDULED JOB" -msgstr "GEPLANDE TAAK WIJZIGEN" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:17 -msgid "EDIT SURVEY PROMPT" -msgstr "MELDING VRAGENLIJST WIJZIGEN" - -#: client/features/templates/templates.strings.js:108 -msgid "EDIT TEMPLATE" -msgstr "SJABLOON WIJZIGEN" - -#: client/lib/components/components.strings.js:9 -msgid "ENCRYPTED" -msgstr "VERSLEUTELD" - -#: client/features/output/output.strings.js:80 -msgid "EXAMPLES" -msgstr "VOORBEELDEN" - -#: client/src/shared/smart-search/smart-search.partial.html:36 -msgid "EXAMPLES:" -msgstr "VOORBEELDEN:" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:15 -msgid "EXECUTE COMMAND" -msgstr "COMMANDO UITVOEREN" - -#: client/lib/components/code-mirror/code-mirror.strings.js:10 -msgid "EXPAND" -msgstr "UITKLAPPEN" - -#: client/features/applications/applications.strings.js:29 -#: client/features/users/tokens/tokens.strings.js:37 -msgid "EXPIRATION" -msgstr "VERLOOPDATUM" - -#: client/features/users/tokens/tokens.strings.js:24 -msgid "EXPIRES" -msgstr "VERLOOPT OP" - -#: client/lib/components/code-mirror/code-mirror.strings.js:48 -#: client/lib/components/code-mirror/code-mirror.strings.js:8 -msgid "EXTRA VARIABLES" -msgstr "EXTRA VARIABELEN" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:379 -msgid "" -"Each time a job runs using this inventory, refresh the inventory from the " -"selected source before 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." - -#: client/src/projects/projects.form.js:180 -msgid "" -"Each time a job runs using this project, update the 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." - -#: client/src/credential-types/credential-types.list.js:56 -#: client/src/credentials/credentials.list.js:66 -#: client/src/inventories-hosts/inventories/inventory.list.js:98 -#: client/src/inventory-scripts/inventory-scripts.list.js:54 -#: client/src/notifications/notificationTemplates.list.js:66 -#: client/src/notifications/notificationTemplates.list.js:75 -#: client/src/scheduler/schedules.list.js:85 client/src/teams/teams.list.js:55 -#: client/src/templates/templates.list.js:80 client/src/users/users.list.js:60 -msgid "Edit" -msgstr "Wijzigen" - -#: client/src/templates/survey-maker/surveys/init.factory.js:22 -msgid "Edit Question" -msgstr "Vraag wijzigen" - -#: client/src/shared/form-generator.js:1735 -#: client/src/templates/job_templates/job-template.form.js:475 -#: client/src/templates/workflows.form.js:212 -msgid "Edit Survey" -msgstr "Vragenlijst wijzigen" - -#: client/src/credential-types/credential-types.list.js:58 -msgid "Edit credential type" -msgstr "Soort toegangsgegevens wijzigen" - -#: client/src/credentials/credentials.list.js:68 -msgid "Edit credential" -msgstr "Toegangsgegevens wijzigen" - -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:85 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:96 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:84 -msgid "Edit group" -msgstr "Groep wijzigen" - -#: client/src/inventories-hosts/hosts/host.list.js:83 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:73 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:79 -#: client/src/inventories-hosts/inventory-hosts.strings.js:25 -msgid "Edit host" -msgstr "Host wijzigen" - -#: client/src/inventories-hosts/inventories/inventory.list.js:100 -msgid "Edit inventory" -msgstr "Inventaris wijzigen" - -#: client/src/inventory-scripts/inventory-scripts.list.js:56 -msgid "Edit inventory script" -msgstr "Inventarisscript wijzigen" - -#: client/src/notifications/notificationTemplates.list.js:68 -msgid "Edit notification" -msgstr "Bericht wijzigen" - -#: client/src/scheduler/schedules.list.js:88 -msgid "Edit schedule" -msgstr "Schema wijzigen" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:83 -msgid "Edit source" -msgstr "Bron wijzigen" - -#: client/src/teams/teams.list.js:59 -msgid "Edit team" -msgstr "Team wijzigen" - -#: client/src/templates/templates.list.js:82 -msgid "Edit template" -msgstr "Sjabloon wijzigen" - -#: client/src/projects/projects.list.js:87 -msgid "Edit the project" -msgstr "Het project wijzigen" - -#: client/src/scheduler/scheduled-jobs.list.js:67 -#: client/src/workflow-results/workflow-results.controller.js:42 -msgid "Edit the schedule" -msgstr "Het schema wijzigen" - -#: client/src/workflow-results/workflow-results.controller.js:40 -msgid "Edit the user" -msgstr "De gebruiker wijzigen" - -#: client/src/workflow-results/workflow-results.controller.js:41 -msgid "Edit the workflow job template" -msgstr "Het workflow-taaksjabloon wijzigen" - -#: client/src/users/users.list.js:64 -msgid "Edit user" -msgstr "Gebruiker wijzigen" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:244 -msgid "" -"Either you do not have access or the SCM update process completed. Click the" -msgstr "" -"U heeft mogelijk geen toegang, of het SCM-updateproces is al voltooid. Klik " -"op" - -#: client/src/projects/list/projects-list.controller.js:286 -msgid "" -"Either you do not have access or the SCM update process completed. Click the" -" %sRefresh%s button to view the latest status." -msgstr "" -"U heeft mogelijk geen toegang, of het SCM-updateproces is al voltooid. Klik " -"op de knop %sVernieuwen%s om de nieuwste status te zien." - -#: client/features/output/output.strings.js:90 -#: client/src/workflow-results/workflow-results.controller.js:61 -msgid "Elapsed" -msgstr "Verlopen" - -#: client/src/credentials/credentials.form.js:191 -#: client/src/users/users.form.js:53 -msgid "Email" -msgstr "E-mail" - -#: client/src/templates/job_templates/job-template.form.js:303 -#: client/src/templates/job_templates/job-template.form.js:308 -#: client/src/templates/workflows.form.js:100 -#: client/src/templates/workflows.form.js:105 -msgid "Enable Concurrent Jobs" -msgstr "Gelijktijdige taken inschakelen" - -#: client/src/configuration/system-form/configuration-system.partial.html:30 -msgid "Enable External Logging" -msgstr "Externe logboekregistratie inschakelen" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:124 -#: client/src/templates/job_templates/job-template.form.js:279 -#: client/src/templates/job_templates/job-template.form.js:284 -msgid "Enable Privilege Escalation" -msgstr "Verhoging van rechten inschakelen" - -#: client/src/templates/survey-maker/surveys/init.factory.js:518 -msgid "Enable survey" -msgstr "Vragenlijst inschakelen" - -#: client/src/templates/job_templates/job-template.form.js:294 -msgid "" -"Enables creation of a provisioning callback URL. Using the URL a host can " -"contact {{BRAND_NAME}} 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 {{BRAND_NAME}} en een verzoek " -"indienen voor een configuratie-update met behulp van dit taaksjabloon." - -#: client/src/credentials/factories/credential-form-save.factory.js:73 -msgid "Encrypted credentials are not supported." -msgstr "Versleutelde toegangsgegevens worden niet ondersteund." - -#: client/src/scheduler/scheduler.strings.js:44 -msgid "End" -msgstr "Einde" - -#: client/src/scheduler/scheduler.strings.js:46 -msgid "End Date" -msgstr "Einddatum" - -#: client/src/scheduler/scheduler.strings.js:48 -msgid "End Time" -msgstr "Eindtijd" - -#: client/src/license/license.partial.html:113 -msgid "End User License Agreement" -msgstr "Licentie-overeenkomst voor eindgebruikers" - -#: client/src/inventories-hosts/hosts/host.form.js:73 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:72 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:72 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:68 -msgid "" -"Enter inventory variables using either JSON or YAML syntax. Use the radio " -"button to toggle between the two." -msgstr "" -"Voer variabelen van de inventaris in met JSON- of YAML-syntaxis. Gebruik de " -"radioknop om tussen de twee te wisselen." - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:76 -msgid "" -"Enter inventory variables using either JSON or YAML syntax. Use the radio " -"button to toggle between the two. Refer to the Ansible Tower documentation " -"for example syntax." -msgstr "" -"Voer de variabelen van het inventaris in met JSON- of YAML-syntax. Gebruik " -"de radio-knop om tussen de twee te wisselen. Raadpleeg de documentatie van " -"Ansible Tower voor voorbeeldsyntaxis." - -#: client/src/notifications/notificationTemplates.form.js:155 -msgid "" -"Enter one HipChat channel per line. The pound symbol (#) is not required." -msgstr "" -"Voer één HipChat-kanaal per regel in. Het hekje (#) is hierbij niet vereist." - -#: client/src/notifications/notificationTemplates.form.js:433 -msgid "" -"Enter one IRC channel or username per line. The pound symbol (#) for " -"channels, and the at (@) symbol for users, are not required." -msgstr "" -"Voer één IRC-kanaal of gebruikersnaam per regel in. Het hekje (#) voor " -"kanalen en het apenstaartje (@) voor gebruikers zijn hierbij niet vereist." - -#: client/src/notifications/notificationTemplates.form.js:139 -msgid "" -"Enter one Slack channel per line. The pound symbol (#) is not required." -msgstr "" -"Voer één Slack-kanaal per regel in. Het hekje (#) is hierbij niet vereist." - -#: client/src/notifications/notificationTemplates.form.js:97 -msgid "" -"Enter 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." - -#: client/src/notifications/notificationTemplates.form.js:209 -msgid "" -"Enter one phone number per line to specify where to route SMS messages." -msgstr "" -"Voer één telefoonnummer per regel in om aan te geven waar SMS-berichten " -"naartoe gestuurd moeten worden." - -#: client/src/credentials/factories/become-method-change.factory.js:81 -#: client/src/credentials/factories/kind-change.factory.js:138 -msgid "" -"Enter the URL for the virtual machine which %scorresponds to your CloudForms " -"instance. %sFor example, %s" -msgstr "" -"Voer de URL in voor de virtuele machine die %sovereenkomt met uw CloudForm-" -"instantie. %sBijvoorbeeld %s" - -#: client/src/credentials/factories/become-method-change.factory.js:71 -#: client/src/credentials/factories/kind-change.factory.js:128 -msgid "" -"Enter the URL which corresponds to your %sRed Hat Satellite 6 server. %sFor " -"example, %s" -msgstr "" -"Voer de URL in die overeenkomt met uw %sRed Had Satellite 6-server. " -"%sBijvoorbeeld %s" - -#: client/src/credentials/factories/become-method-change.factory.js:49 -#: client/src/credentials/factories/kind-change.factory.js:106 -msgid "" -"Enter the hostname or IP address which corresponds to your VMware vCenter." -msgstr "" -"Voer de hostnaam of het IP-adres in dat overeenkomt met uw VMware vCenter." - -#: client/src/notifications/notificationTemplates.form.js:195 -msgid "" -"Enter the number associated with the \"Messaging Service\" in Twilio in the " -"format +18005550199." -msgstr "" -"Voer het telefoonnummer in dat hoort bij de 'Berichtenservice' in Twilio in " -"de indeling +18005550199." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:197 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:221 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:245 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:320 -msgid "" -"Enter variables using either 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." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:187 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:194 -msgid "Environment Variables" -msgstr "Omgevingsvariabelen" - -#: client/src/configuration/configuration.controller.js:141 -msgid "Error" -msgstr "Fout" - -#: client/features/output/output.strings.js:65 -msgid "Error Details" -msgstr "Foutinformatie" - -#: client/lib/services/base-string.service.js:92 -#: client/src/configuration/configuration.controller.js:414 -#: client/src/configuration/configuration.controller.js:523 -#: client/src/configuration/configuration.controller.js:558 -#: client/src/configuration/configuration.controller.js:606 -#: client/src/configuration/system-form/configuration-system.controller.js:231 -#: client/src/credentials/factories/credential-form-save.factory.js:77 -#: client/src/credentials/factories/credential-form-save.factory.js:93 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:130 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:140 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:167 -#: client/src/job-submission/job-submission-factories/launchjob.factory.js:198 -#: client/src/job-submission/job-submission-factories/launchjob.factory.js:217 -#: client/src/management-jobs/card/card.controller.js:102 -#: client/src/management-jobs/card/card.controller.js:28 -#: client/src/projects/add/projects-add.controller.js:117 -#: client/src/projects/edit/projects-edit.controller.js:165 -#: client/src/projects/edit/projects-edit.controller.js:231 -#: client/src/projects/edit/projects-edit.controller.js:247 -#: client/src/projects/list/projects-list.controller.js:196 -#: client/src/projects/list/projects-list.controller.js:223 -#: client/src/projects/list/projects-list.controller.js:270 -#: client/src/projects/list/projects-list.controller.js:291 -#: client/src/projects/list/projects-list.controller.js:307 -#: client/src/projects/list/projects-list.controller.js:316 -#: client/src/shared/stateDefinitions.factory.js:230 -#: client/src/users/add/users-add.controller.js:100 -#: client/src/users/edit/users-edit.controller.js:178 -#: client/src/users/list/users-list.controller.js:84 -msgid "Error!" -msgstr "Fout!" - -#: client/src/activity-stream/streams.list.js:40 -msgid "Event" -msgstr "Gebeurtenis" - -#: client/src/activity-stream/factories/build-description.factory.js:120 -msgid "Event summary not available" -msgstr "Samenvatting van de gebeurtenis niet beschikbaar" - -#: client/src/scheduler/scheduler.strings.js:29 -msgid "Every" -msgstr "Iedere" - -#: client/src/projects/add/projects-add.controller.js:138 -#: client/src/projects/edit/projects-edit.controller.js:274 -msgid "Example URLs for GIT SCM include:" -msgstr "Voorbeelden van URL's voor GIT SCM zijn onder andere:" - -#: client/src/projects/add/projects-add.controller.js:159 -#: client/src/projects/edit/projects-edit.controller.js:294 -msgid "Example URLs for Mercurial SCM include:" -msgstr "Voorbeelden van URL's voor Mercurial SCM zijn onder andere:" - -#: client/src/projects/add/projects-add.controller.js:150 -#: client/src/projects/edit/projects-edit.controller.js:285 -msgid "Example URLs for Subversion SCM include:" -msgstr "Voorbeelden van URL's voor Subversion SCM zijn onder andere:" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:47 -msgid "Example: ansible_facts.ansible_distribution:\"RedHat\"" -msgstr "Voorbeeld: ansible_facts.ansible_distribution:\"RedHat\"" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:76 -msgid "Existing Group" -msgstr "Bestaande groep" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:125 -msgid "Existing Host" -msgstr "Bestaande host" - -#: client/features/output/output.strings.js:22 -#: client/src/workflow-results/workflow-results.controller.js:154 -#: client/src/workflow-results/workflow-results.controller.js:43 -msgid "Expand Output" -msgstr "Output uitklappen" - -#: client/src/license/license.partial.html:39 -msgid "Expires On" -msgstr "Verloopt op" - -#: client/features/output/output.strings.js:50 -msgid "Explanation" -msgstr "Uitleg" - -#: client/features/output/output.strings.js:45 -#: client/features/templates/templates.strings.js:54 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:133 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:145 -#: client/src/job-submission/job-submission.partial.html:165 -#: client/src/partials/logviewer.html:8 -#: client/src/scheduler/scheduler.strings.js:53 -#: client/src/templates/job_templates/job-template.form.js:357 -#: client/src/templates/job_templates/job-template.form.js:364 -#: client/src/templates/workflows.form.js:83 -#: client/src/templates/workflows.form.js:90 -#: client/src/workflow-results/workflow-results.controller.js:122 -msgid "Extra Variables" -msgstr "Extra variabelen" - -#: client/src/inventories-hosts/shared/ansible-facts/ansible-facts.partial.html:4 -#: client/src/inventories-hosts/shared/ansible-facts/ansible-facts.route.js:7 -msgid "FACTS" -msgstr "FEITEN" - -#: client/src/home/dashboard/graphs/job-status/job-status-graph.directive.js:65 -msgid "FAILED" -msgstr "MISLUKT" - -#: client/features/output/output.strings.js:81 -msgid "FIELDS" -msgstr "VELDEN" - -#: client/src/shared/smart-search/smart-search.partial.html:42 -msgid "FIELDS:" -msgstr "VELDEN:" - -#: client/src/smart-status/smart-status.controller.js:43 -msgid "FINISHED" -msgstr "VOLTOOID" - -#: client/src/inventories-hosts/hosts/host.form.js:107 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:106 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:107 -msgid "Facts" -msgstr "Feiten" - -#: client/lib/components/components.strings.js:100 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:80 -msgid "Failed" -msgstr "Mislukt" - -#: client/src/home/dashboard/counts/dashboard-counts.directive.js:44 -msgid "Failed Hosts" -msgstr "Mislukte hosts" - -#: client/src/users/add/users-add.controller.js:100 -msgid "Failed to add new user. POST returned status:" -msgstr "Nieuwe gebruiker toevoegen mislukt. Geretourneerde statut POSTEN:" - -#: client/src/credentials/factories/credential-form-save.factory.js:78 -msgid "Failed to create new Credential. POST status:" -msgstr "Nieuwe toegangsgegevens aanmaken mislukt. Status POSTEN:" - -#: client/src/projects/add/projects-add.controller.js:118 -msgid "Failed to create new project. POST returned status:" -msgstr "Nieuw project aanmaken mislukt. Geretourneerde status POSTEN:" - -#: client/src/job-submission/job-submission-factories/launchjob.factory.js:218 -msgid "Failed to retrieve job template extra variables." -msgstr "Extra variabelen van taaksjabloon ophalen mislukt." - -#: client/src/projects/edit/projects-edit.controller.js:166 -msgid "Failed to retrieve project: %s. GET status:" -msgstr "Project ophalen mislukt: %s. status OPHALEN:" - -#: client/src/users/edit/users-edit.controller.js:179 -msgid "Failed to retrieve user: %s. GET status:" -msgstr "Gebruiker ophalen mislukt: %s. Status OPHALEN:" - -#: client/src/configuration/configuration.controller.js:524 -msgid "Failed to save settings. Returned status:" -msgstr "Instellingen opslaan mislukt. Geretourneerde status:" - -#: client/src/configuration/configuration.controller.js:559 -msgid "Failed to save toggle settings. Returned status:" -msgstr "Instellingen wisselen mislukt. Geretourneerde status:" - -#: client/src/credentials/factories/credential-form-save.factory.js:94 -msgid "Failed to update Credential. PUT status:" -msgstr "Toegangsgegevens updaten mislukt. Status PLAATSEN:" - -#: client/src/projects/edit/projects-edit.controller.js:231 -msgid "Failed to update project: %s. PUT status:" -msgstr "Project updaten mislukt: %s. Status PLAATSEN:" - -#: client/features/output/output.strings.js:85 -msgid "Failed to update search results." -msgstr "Zoekresultaten bijwerken mislukt." - -#: client/src/job-submission/job-submission-factories/launchjob.factory.js:199 -#: client/src/management-jobs/card/card.controller.js:103 -msgid "Failed updating job %s with variables. POST returned: %d" -msgstr "Taak %s met variabelen bijwerken mislukt. Geretourneerde POSTEN: %d" - -#: client/src/notifications/notifications.list.js:49 -msgid "Failure" -msgstr "Mislukking" - -#: client/src/scheduler/schedules.list.js:56 -msgid "Final Run" -msgstr "Laatste uitvoering" - -#: client/features/jobs/jobs.strings.js:10 -#: client/features/output/output.strings.js:40 -#: client/features/output/output.strings.js:46 -#: client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.directive.js:54 -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:44 -#: client/src/workflow-results/workflow-results.controller.js:50 -msgid "Finished" -msgstr "Voltooid" - -#: client/src/access/rbac-multiselect/permissionsUsers.list.js:27 -#: client/src/users/users.form.js:29 client/src/users/users.list.js:33 -msgid "First Name" -msgstr "Voornaam" - -#: client/src/scheduler/schedules.list.js:46 -msgid "First Run" -msgstr "Eerste uitvoering" - -#: client/src/templates/survey-maker/surveys/init.factory.js:19 -msgid "Float" -msgstr "Drijven" - -#: client/features/output/output.strings.js:77 -#: client/src/shared/smart-search/smart-search.partial.html:49 -msgid "" -"For additional information on advanced search syntax please see the Ansible " -"Tower" -msgstr "" -"Zie de Ansible Tower voor meer informatie over geavanceerde zoeksyntaxis" - -#: client/src/credentials/factories/become-method-change.factory.js:63 -#: client/src/credentials/factories/kind-change.factory.js:120 -msgid "For example, %s" -msgstr "Bijvoorbeeld %s" - -#: client/src/inventories-hosts/hosts/host.form.js:36 -#: client/src/inventories-hosts/hosts/host.list.js:36 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:35 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:32 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:35 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:31 -#: client/src/inventories-hosts/inventory-hosts.strings.js:33 -msgid "" -"For hosts that are part of an external inventory, this flag cannot be " -"changed. It will be set by the inventory sync process." -msgstr "" -"Deze vlag kan niet aangepast worden bij hosts die onderdeel zijn van een " -"externe inventaris. Hij wordt ingesteld door het synchronisatieproces van de" -" inventaris." - -#: client/src/templates/job_templates/job-template.form.js:54 -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." - -#: client/features/output/output.strings.js:47 -#: client/src/instance-groups/instance-groups.strings.js:48 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:110 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:97 -#: client/src/templates/job_templates/job-template.form.js:143 -#: client/src/templates/job_templates/job-template.form.js:153 -msgid "Forks" -msgstr "Vorken" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:173 -#: client/src/scheduler/scheduler.strings.js:28 -msgid "Frequency Details" -msgstr "Frequentie-informatie" - -#: client/src/scheduler/scheduler.strings.js:41 -msgid "Fri" -msgstr "Vrij" - -#: client/src/notifications/notification-templates-list/add-notifications-action.partial.html:2 -msgid "GO TO NOTIFICATIONS TO" -msgstr "GA NAAR NOTIFICATIONS OM" - -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.route.js:45 -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.route.js:46 -msgid "GROUPS" -msgstr "GROEPEN" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:38 -#: client/src/projects/edit/projects-edit.controller.js:136 -#: client/src/projects/list/projects-list.controller.js:76 -msgid "Get latest SCM revision" -msgstr "De nieuwste SCM-herziening ophalen" - -#: client/src/credential-types/add/add.controller.js:41 -msgid "Getting Started with Credential Types" -msgstr "Beginnen met soorten toegangsgegevens" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:153 -msgid "GitHub" -msgstr "GitHub" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:154 -msgid "GitHub Org" -msgstr "GitHub Org" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:155 -msgid "GitHub Team" -msgstr "GitHub Team" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:156 -msgid "Google OAuth2" -msgstr "Google OAuth2" - -#: client/src/teams/teams.form.js:158 client/src/users/users.form.js:216 -msgid "Grant Permission" -msgstr "Machtiging toekennen" - -#: client/src/notifications/add/add.controller.js:79 -#: client/src/notifications/edit/edit.controller.js:126 -msgid "Gray" -msgstr "Grijs" - -#: client/src/notifications/add/add.controller.js:80 -#: client/src/notifications/edit/edit.controller.js:127 -msgid "Green" -msgstr "Groen" - -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:51 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js:52 -msgid "Group Variables" -msgstr "Variabelen ordenen" - -#: client/src/inventories-hosts/hosts/host.form.js:115 -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.list.js:31 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:89 -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:32 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js:88 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:32 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:114 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:115 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:32 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:150 -msgid "Groups" -msgstr "Groepen" - -#: client/lib/components/components.strings.js:12 -#: client/lib/services/base-string.service.js:66 -#: client/src/templates/survey-maker/surveys/init.factory.js:483 -msgid "HIDE" -msgstr "VERBERGEN" - -#: client/lib/components/components.strings.js:43 -msgid "HINT: Drag and drop an SSH private key file on the field below." -msgstr "TIP: sleep een SSH-privésleutelbestand naar het onderstaande veld." - -#: client/src/activity-stream/get-target-title.factory.js:41 -#: client/src/inventories-hosts/hosts/hosts.partial.html:9 -#: client/src/inventories-hosts/hosts/main.js:81 -#: client/src/inventories-hosts/inventories/inventories.partial.html:15 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.route.js:18 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-hosts.route.js:17 -msgid "HOSTS" -msgstr "HOSTS" - -#: client/src/notifications/notificationTemplates.form.js:320 -#: client/src/notifications/notificationTemplates.form.js:321 -msgid "HTTP Headers" -msgstr "HTTP-koppen" - -#: client/src/bread-crumb/bread-crumb.directive.js:41 -msgid "Hide Activity Stream" -msgstr "Activiteitenlogboek verbergen" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:23 -msgid "High" -msgstr "Hoog" - -#: client/src/credentials/credentials.form.js:139 -#: client/src/notifications/notificationTemplates.form.js:83 -msgid "Host" -msgstr "Host" - -#: client/src/credentials/factories/become-method-change.factory.js:52 -#: client/src/credentials/factories/kind-change.factory.js:109 -msgid "Host (Authentication URL)" -msgstr "Host (authenticatie-URL)" - -#: client/src/templates/job_templates/job-template.form.js:339 -#: client/src/templates/job_templates/job-template.form.js:348 -msgid "Host Config Key" -msgstr "Configuratiesleutel host" - -#: client/src/inventories-hosts/hosts/host.form.js:40 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:39 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:39 -msgid "Host Enabled" -msgstr "Host ingeschakeld" - -#: client/src/inventories-hosts/hosts/host.form.js:46 -#: client/src/inventories-hosts/hosts/host.form.js:57 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:45 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:56 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:45 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:56 -msgid "Host Name" -msgstr "Hostnaam" - -#: client/src/inventories-hosts/hosts/host.form.js:80 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:79 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:79 -msgid "Host Variables" -msgstr "Hostvariabelen" - -#: client/src/inventories-hosts/shared/factories/set-enabled-msg.factory.js:6 -msgid "Host is available" -msgstr "Host is beschikbaar" - -#: client/src/inventories-hosts/shared/factories/set-enabled-msg.factory.js:10 -msgid "Host is available. Click to toggle." -msgstr "Host is beschikbaar. Klik om te wisselen." - -#: client/src/inventories-hosts/shared/factories/set-enabled-msg.factory.js:6 -msgid "Host is not available" -msgstr "Host is niet beschikbaar" - -#: client/src/inventories-hosts/shared/factories/set-enabled-msg.factory.js:10 -msgid "Host is not available. Click to toggle." -msgstr "Host is niet beschikbaar. Klik om te wisselen." - -#: client/features/output/output.strings.js:13 -msgid "Host status information for this job is unavailable." -msgstr "Statusinformatie van de host is niet beschikbaar voor deze taak." - -#: client/features/output/output.strings.js:93 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:27 -#: client/src/home/dashboard/counts/dashboard-counts.directive.js:39 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:98 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:57 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:56 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:149 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:159 -msgid "Hosts" -msgstr "Hosts" - -#: client/src/license/license.partial.html:52 -msgid "Hosts Available" -msgstr "Beschikbare hosts" - -#: client/src/license/license.partial.html:64 -msgid "Hosts Remaining" -msgstr "Overgebleven hosts" - -#: client/src/license/license.partial.html:58 -msgid "Hosts Used" -msgstr "Gebruikte hosts" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:239 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:254 -msgid "Hosts are imported to" -msgstr "Hosts worden geïmporteerd naar" - -#: client/src/license/license.partial.html:121 -msgid "I agree to the End User License Agreement" -msgstr "Ik ga akkoord met de licentie-overeenkomst voor eindgebruikers" - -#: client/features/output/output.strings.js:102 -msgid "ID" -msgstr "ID" - -#: client/src/partials/job-template-details.html:2 -msgid "INFO" -msgstr "INFO" - -#: client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html:12 -msgid "INITIATED BY" -msgstr "GESTART DOOR" - -#: client/src/inventories-hosts/inventories/insights/insights.route.js:7 -msgid "INSIGHTS" -msgstr "INZICHTEN" - -#: client/src/instance-groups/instance-groups.list.js:6 -#: client/src/instance-groups/instance-groups.list.js:7 -#: client/src/instance-groups/instance-groups.strings.js:16 -#: client/src/instance-groups/instance-groups.strings.js:8 -msgid "INSTANCE GROUPS" -msgstr "INSTANTIEGROEPEN" - -#: client/src/instance-groups/instance-groups.strings.js:25 -#: client/src/instance-groups/instance-groups.strings.js:9 -msgid "INSTANCES" -msgstr "INSTANTIES" - -#: client/src/activity-stream/get-target-title.factory.js:14 -#: client/src/inventories-hosts/hosts/hosts.partial.html:8 -#: client/src/inventories-hosts/inventories/inventories.partial.html:14 -#: client/src/inventories-hosts/inventories/inventories.route.js:8 -#: client/src/inventories-hosts/inventories/inventory.list.js:14 -#: client/src/inventories-hosts/inventories/inventory.list.js:15 -#: client/src/organizations/linkout/organizations-linkout.route.js:144 -#: client/src/organizations/list/organizations-list.controller.js:67 -msgid "INVENTORIES" -msgstr "INVENTARISSEN" - -#: client/src/job-submission/job-submission.partial.html:346 -#: client/src/partials/job-template-details.html:2 -msgid "INVENTORY" -msgstr "INVENTARIS" - -#: client/src/inventory-scripts/inventory-scripts.form.js:23 -msgid "INVENTORY SCRIPT" -msgstr "INVENTARISSCRIPT" - -#: client/src/activity-stream/get-target-title.factory.js:35 -#: client/src/inventory-scripts/inventory-scripts.list.js:12 -#: client/src/inventory-scripts/main.js:65 -msgid "INVENTORY SCRIPTS" -msgstr "INVENTARISSCRIPTS" - -#: client/src/notifications/notificationTemplates.form.js:419 -msgid "IRC Nick" -msgstr "IRC-bijnaam" - -#: client/src/notifications/notificationTemplates.form.js:408 -msgid "IRC Server Address" -msgstr "IRC-serveradres" - -#: client/src/notifications/shared/type-change.service.js:66 -msgid "IRC Server Password" -msgstr "IRC-serverwachtwoord" - -#: client/src/notifications/shared/type-change.service.js:65 -msgid "IRC Server Port" -msgstr "IRC-serverpoort" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:79 -msgid "ISSUE: {{report.rule.description}}" -msgstr "PROBLEEM: {{report.rule.description}}" - -#: client/src/shared/paginate/paginate.partial.html:43 -msgid "ITEMS" -msgstr "ITEMS" - -#: client/src/notifications/notificationTemplates.form.js:362 -#: client/src/notifications/notificationTemplates.form.js:394 -msgid "Icon URL" -msgstr "Icoon-URL" - -#: client/src/login/authenticationServices/timer.factory.js:157 -msgid "Idle Session" -msgstr "Inactieve sessie" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:236 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:250 -msgid "If blank, all groups above are created except" -msgstr "" -"Als dit vakje leeg wordt gelaten, worden alle bovenstaande groepen " -"aangemaakt, behalve" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:367 -msgid "" -"If checked, all variables for child groups and hosts will be removed and " -"replaced by those found on the external source." -msgstr "" -"Als dit vakje aangevinkt is, worden alle variabelen voor onderliggende " -"groepen en hosts verwijderd en worden ze vervangen door de variabelen die " -"aangetroffen worden in de externe bron." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:355 -msgid "" -"If checked, any hosts and groups that were previously present on the " -"external source but are now removed will be removed from the Tower " -"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." -msgstr "" -"Als dit vakje aangevinkt is, 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'." - -#: client/src/templates/job_templates/job-template.form.js:282 -msgid "If enabled, run this playbook as an administrator." -msgstr "" -"Als deze optie ingeschakeld is, wordt het draaiboek uitgevoerd als " -"beheerder." - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:121 -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." - -#: client/src/templates/job_templates/job-template.form.js:267 -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." - -#: client/src/templates/job_templates/job-template.form.js:306 -msgid "If enabled, simultaneous runs of this job template will be allowed." -msgstr "" -"Indien deze mogelijkheid ingeschakeld is, zijn gelijktijdige uitvoeringen " -"van deze taaksjabloon toegestaan." - -#: client/src/templates/workflows.form.js:103 -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." - -#: client/src/templates/job_templates/job-template.form.js:317 -msgid "" -"If enabled, use cached facts if available and store discovered facts in the " -"cache." -msgstr "" -"Gebruik gecachete feiten als deze beschikbaar zijn en sla feiten die ontdekt" -" zijn op in de cache, indien deze mogelijkheid ingeschakeld is." - -#: client/src/credentials/credentials.form.js:52 -msgid "" -"If no organization is given, the credential can only be used by the user " -"that creates the credential. Organization admins and system administrators " -"can assign an organization so that roles for the credential can be assigned " -"to users and teams in that organization." -msgstr "" -"Als er geen organisatie opgegeven is, kunnen de toegangsgegevens alleen " -"gebruikt worden door de gebruiker die de toegangsgegevens aangemaakt heeft. " -"Beheerders van organisaties en systeembeheerders kunnen een organisatie " -"toewijzen zodat rollen voor de toegangsgegevens toegewezen kunnen worden aan" -" gebruikers en teams binnen die organisatie." - -#: client/src/license/license.partial.html:70 -msgid "" -"If you are ready to upgrade, please contact us by clicking the button below" -msgstr "" -"Neem zodra u klaar bent om te updaten contact met ons op door op de " -"onderstaande knop te klikken" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:227 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:241 -msgid "Image ID:" -msgstr "Afbeelding-ID:" - -#: client/src/inventories-hosts/hosts/host.form.js:34 -#: client/src/inventories-hosts/hosts/host.list.js:34 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:33 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:30 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:33 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:29 -#: client/src/inventories-hosts/inventory-hosts.strings.js:32 -msgid "" -"Indicates if a host is available and should be included in running jobs." -msgstr "" -"Geeft aan of een host beschikbaar is, moet opgenomen worden in taken die in " -"uitvoering zijn." - -#: client/src/activity-stream/activity-detail.form.js:31 -#: client/src/activity-stream/streams.list.js:33 -msgid "Initiated by" -msgstr "Gestart door" - -#: client/src/credential-types/credential-types.form.js:53 -#: client/src/credential-types/credential-types.form.js:61 -msgid "Injector Configuration" -msgstr "Configuratie-injector" - -#: client/src/credential-types/credential-types.form.js:39 -#: client/src/credential-types/credential-types.form.js:47 -msgid "Input Configuration" -msgstr "Configuratie-input" - -#: client/src/inventories-hosts/hosts/host.form.js:123 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:122 -msgid "Insights" -msgstr "Inzichten" - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:52 -msgid "Insights Credential" -msgstr "Inzichten toegangsgegevens" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:145 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:148 -msgid "Instance Filters" -msgstr "Instantiefilters" - -#: client/features/output/output.strings.js:48 -msgid "Instance Group" -msgstr "Instantiegroep" - -#: client/src/instance-groups/instance-groups.strings.js:63 -msgid "Instance Group parameter is missing." -msgstr "Parameter van instantiegroep ontbreekt." - -#: client/lib/components/components.strings.js:84 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:54 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:57 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:61 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:64 -#: client/src/organizations/organizations.form.js:38 -#: client/src/organizations/organizations.form.js:41 -#: client/src/templates/job_templates/job-template.form.js:252 -#: client/src/templates/job_templates/job-template.form.js:255 -msgid "Instance Groups" -msgstr "Instantiegroepen" - -#: client/src/instance-groups/instance-groups.strings.js:32 -msgid "Instance Groups Help" -msgstr "Instantiegroepen, Help" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:236 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:250 -msgid "Instance ID" -msgstr "Instantie-ID" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:228 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:242 -msgid "Instance ID:" -msgstr "Instantie-ID:" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:229 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:243 -msgid "Instance Type:" -msgstr "Instantietype:" - -#: client/lib/components/components.strings.js:83 -#: client/src/instance-groups/instance-groups.strings.js:17 -msgid "Instances" -msgstr "Instanties" - -#: client/src/templates/survey-maker/surveys/init.factory.js:18 -msgid "Integer" -msgstr "Geheel getal" - -#: client/src/license/license.partial.html:11 -msgid "Invalid License" -msgstr "Ongeldige licentie" - -#: client/src/license/license.controller.js:74 -#: client/src/license/license.controller.js:82 -msgid "Invalid file format. Please upload valid JSON." -msgstr "Ongeldige bestandsindeling. Upload een geldige JSON." - -#: client/lib/components/components.strings.js:16 -msgid "Invalid input for this type." -msgstr "Ongeldige input voor dit type." - -#: client/features/output/output.strings.js:86 -msgid "Invalid search filter provided." -msgstr "Ongeldige zoekfilter verschaft." - -#: client/src/login/loginModal/loginModal.partial.html:34 -msgid "Invalid username and/or password. Please try again." -msgstr "Ongeldige gebruikersnaam en/of wachtwoord. Probeer het opnieuw." - -#: client/lib/components/components.strings.js:75 -#: client/lib/models/models.strings.js:16 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:122 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:52 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:28 -#: client/src/home/dashboard/counts/dashboard-counts.directive.js:50 -#: client/src/organizations/linkout/organizations-linkout.route.js:156 -#: client/src/organizations/linkout/organizations-linkout.route.js:158 -msgid "Inventories" -msgstr "Inventarissen" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:44 -msgid "Inventories with sources cannot be copied" -msgstr "Inventarissen met bronnen kunnen niet gekopieerd worden" - -#: client/features/jobs/jobs.strings.js:14 -#: client/features/output/output.strings.js:49 -#: client/features/templates/templates.strings.js:16 -#: client/features/templates/templates.strings.js:24 -#: client/src/inventories-hosts/hosts/host.list.js:69 -#: client/src/inventories-hosts/inventories/inventory.list.js:81 -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:76 -#: client/src/job-submission/job-submission.partial.html:17 -#: client/src/organizations/linkout/controllers/organizations-inventories.controller.js:70 -#: client/src/templates/job_templates/job-template.form.js:66 -#: client/src/templates/job_templates/job-template.form.js:80 -msgid "Inventory" -msgstr "Inventaris" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:110 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:124 -msgid "Inventory File" -msgstr "Inventarisbestand" - -#: client/lib/components/components.strings.js:80 -#: client/lib/models/models.strings.js:20 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:29 -msgid "Inventory Scripts" -msgstr "Inventarisscript" - -#: client/lib/models/models.strings.js:25 -msgid "Inventory Sources" -msgstr "Inventarisbronnen" - -#: client/features/templates/templates.strings.js:104 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:46 -#: client/src/workflow-results/workflow-results.controller.js:68 -msgid "Inventory Sync" -msgstr "Inventarissynchronisatie" - -#: client/src/home/dashboard/counts/dashboard-counts.directive.js:55 -msgid "Inventory Sync Failures" -msgstr "Fouten bij inventarissynchronisatie" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:75 -msgid "Inventory Variables" -msgstr "Inventarisvariabelen" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:73 -msgid "Inventory contains 0 hosts." -msgstr "Inventaris bevat 0 hosts." - -#: client/features/output/output.strings.js:35 -msgid "Isolated" -msgstr "Geïsoleerd" - -#: client/src/smart-status/smart-status.controller.js:43 -msgid "JOB ID" -msgstr "TAAK-ID" - -#: client/features/output/output.strings.js:84 -msgid "JOB IS STILL RUNNING" -msgstr "TAAK NOG IN UITVOERING" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:4 -msgid "JOB STATUS" -msgstr "TAAKSTATUS" - -#: client/src/templates/job_templates/job-template.form.js:22 -msgid "JOB TEMPLATE" -msgstr "TAAKSJABLOON" - -#: client/features/portalMode/portalMode.strings.js:8 -#: client/features/templates/routes/organizationsTemplatesList.route.js:20 -#: client/features/templates/routes/projectsTemplatesList.route.js:18 -#: client/src/organizations/list/organizations-list.controller.js:79 -msgid "JOB TEMPLATES" -msgstr "TAAKSJABLONEN" - -#: client/features/jobs/jobs.strings.js:8 -#: client/features/jobs/routes/instanceGroupJobs.route.js:13 -#: client/features/jobs/routes/instanceJobs.route.js:13 -#: client/features/jobs/routes/inventoryCompletedJobs.route.js:22 -#: client/features/jobs/routes/jobs.route.js:13 -#: client/features/portalMode/portalMode.strings.js:9 -#: client/features/templates/templates.strings.js:109 -#: client/src/activity-stream/get-target-title.factory.js:32 -#: client/src/home/dashboard/graphs/job-status/job-status-graph.directive.js:119 -#: client/src/instance-groups/instance-groups.strings.js:26 -msgid "JOBS" -msgstr "TAKEN" - -#: client/lib/components/code-mirror/code-mirror.strings.js:12 -#: client/lib/services/base-string.service.js:70 -#: client/src/job-submission/job-submission.partial.html:173 -msgid "JSON" -msgstr "JSON" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:198 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:222 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:246 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:321 -msgid "JSON:" -msgstr "JSON:" - -#: client/features/jobs/jobs.strings.js:18 -#: client/src/workflow-results/workflow-results.controller.js:86 -msgid "Job" -msgstr "Taak" - -#: client/features/output/output.strings.js:51 -#: client/features/templates/templates.strings.js:48 -#: client/src/job-submission/job-submission.partial.html:228 -#: client/src/templates/job_templates/job-template.form.js:190 -#: client/src/templates/job_templates/job-template.form.js:197 -msgid "Job Tags" -msgstr "Taaktags" - -#: client/features/jobs/jobs.strings.js:13 -#: client/features/output/output.strings.js:52 -#: client/features/templates/templates.strings.js:13 -#: client/src/templates/templates.list.js:61 -msgid "Job Template" -msgstr "Taaksjabloon" - -#: client/lib/models/models.strings.js:30 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:103 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:36 -#: client/src/projects/projects.form.js:303 -msgid "Job Templates" -msgstr "Taaksjablonen" - -#: client/features/output/output.strings.js:53 -#: client/features/templates/templates.strings.js:50 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:32 -#: client/src/job-submission/job-submission.partial.html:202 -#: client/src/templates/job_templates/job-template.form.js:47 -#: client/src/templates/job_templates/job-template.form.js:55 -msgid "Job Type" -msgstr "Soort taak" - -#: client/features/jobs/jobs.strings.js:19 -msgid "Job {{status}}. Click for details." -msgstr "Taak {{status}}. Klik voor meer informatie." - -#: client/lib/components/components.strings.js:69 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:30 -#: client/src/configuration/configuration.partial.html:22 -#: client/src/instance-groups/instance-groups.strings.js:52 -msgid "Jobs" -msgstr "Taken" - -#: client/features/output/output.strings.js:82 -#: client/features/templates/templates.strings.js:99 -#: client/src/workflow-results/workflow-results.controller.js:69 -msgid "KEY" -msgstr "SLEUTEL" - -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:61 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:154 -#: client/src/shared/smart-search/smart-search.partial.html:14 -msgid "Key" -msgstr "Sleutel" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:230 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:244 -msgid "Key Name:" -msgstr "Naam sleutel:" - -#: client/src/credential-types/credential-types.list.js:31 -#: client/src/credentials/credentials.list.js:33 -msgid "Kind" -msgstr "Type" - -#: client/features/applications/applications.strings.js:31 -msgid "LAST MODIFIED" -msgstr "LAATST AANGEPAST" - -#: client/features/users/tokens/tokens.strings.js:38 -msgid "LAST USED" -msgstr "LAATST GEBRUIKT" - -#: client/features/templates/templates.strings.js:30 -msgid "LAUNCH" -msgstr "OPSTARTEN" - -#: client/src/job-submission/job-submission.partial.html:6 -msgid "LAUNCH JOB" -msgstr "TAAK STARTEN" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:157 -msgid "LDAP" -msgstr "LDAP" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:165 -msgid "LDAP 1 (Optional)" -msgstr "LDAP 1 (optioneel)" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:166 -msgid "LDAP 2 (Optional)" -msgstr "LDAP 2 (optioneel)" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:167 -msgid "LDAP 3 (Optional)" -msgstr "LDAP 3 (optioneel)" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:168 -msgid "LDAP 4 (Optional)" -msgstr "LDAP 4 (optioneel)" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:169 -msgid "LDAP 5 (Optional)" -msgstr "LDAP 5 (optioneel)" - -#: client/src/configuration/auth-form/configuration-auth.partial.html:17 -msgid "LDAP Server" -msgstr "LDAP-server" - -#: client/src/configuration/license.route.js:18 -#: client/src/license/license.route.js:18 -msgid "LICENSE" -msgstr "LICENTIE" - -#: client/features/output/output.strings.js:54 -#: client/src/templates/job_templates/job-template.form.js:224 -#: client/src/templates/job_templates/job-template.form.js:228 -#: client/src/templates/templates.list.js:43 -#: client/src/templates/workflows.form.js:72 -#: client/src/templates/workflows.form.js:76 -#: client/src/workflow-results/workflow-results.controller.js:51 -msgid "Labels" -msgstr "Labels" - -#: client/features/templates/templates.strings.js:19 -msgid "Last Modified" -msgstr "Laatst aangepast" - -#: client/src/access/rbac-multiselect/permissionsUsers.list.js:31 -#: client/src/users/users.form.js:36 client/src/users/users.list.js:37 -msgid "Last Name" -msgstr "Achternaam" - -#: client/features/templates/templates.strings.js:20 -msgid "Last Ran" -msgstr "Laatst uitgevoerd" - -#: client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.directive.js:56 -msgid "Last Sync" -msgstr "Laatste synchronisatie" - -#: client/src/projects/projects.list.js:56 -msgid "Last Updated" -msgstr "Laatst geüpdatet" - -#: client/src/shared/form-generator.js:1727 -msgid "Launch" -msgstr "Starten" - -#: client/src/management-jobs/card/card.partial.html:23 -msgid "Launch Management Job" -msgstr "Beheertaak starten" - -#: client/features/jobs/jobs.strings.js:12 -#: client/features/output/output.strings.js:55 -#: client/src/workflow-results/workflow-results.controller.js:48 -msgid "Launched By" -msgstr "Gestart door" - -#: client/features/templates/templates.strings.js:38 -#: client/src/job-submission/job-submission.partial.html:99 -msgid "" -"Launching this job requires the passwords listed below. Enter and confirm " -"each password before continuing." -msgstr "" -"Voor het starten van deze taak zijn onderstaande wachtwoorden nodig. Ieder " -"wachtwoord dient ingevoerd en bevestigd te worden voordat u verdergaat." - -#: client/features/users/tokens/tokens.strings.js:32 -msgid "" -"Leaving this field blank will result in the creation of a Personal Access " -"Token which is not linked to an Application." -msgstr "" -"Als dit veld leeg wordt gelaten, wordt er een persoonlijke toegangstoken " -"aangemaakt die niet gekoppeld is aan een toepassing." - -#: client/features/credentials/legacy.credentials.js:350 -msgid "Legacy state configuration for does not exist" -msgstr "Er bestaat geen oude staat van configuratie voor" - -#: client/src/configuration/configuration.partial.html:43 -#: client/src/license/license.controller.js:44 -#: client/src/license/license.partial.html:8 -msgid "License" -msgstr "Licentie" - -#: client/features/output/output.strings.js:56 -msgid "License Error" -msgstr "Licentiefout" - -#: client/src/license/license.partial.html:104 -msgid "License File" -msgstr "Licentiebestand" - -#: client/src/license/license.partial.html:33 -msgid "License Key" -msgstr "Licentiesleutel" - -#: client/src/license/license.controller.js:46 -msgid "License Management" -msgstr "Licentiebeheer" - -#: client/src/license/license.partial.html:21 -msgid "License Type" -msgstr "Licentiesoort" - -#: client/features/output/output.strings.js:57 -#: client/features/templates/templates.strings.js:49 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:45 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:55 -#: client/src/job-submission/job-submission.partial.html:220 -#: client/src/templates/job_templates/job-template.form.js:159 -#: client/src/templates/job_templates/job-template.form.js:163 -msgid "Limit" -msgstr "Limiet" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:240 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:255 -msgid "Limit to hosts having a tag:" -msgstr "Beperken tot hosts met een tag:" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:242 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:257 -msgid "Limit to hosts using either key pair:" -msgstr "Beperken tot hosts die één van de sleutelparen gebruiken:" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:244 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:259 -msgid "Limit to hosts where the Name tag begins with" -msgstr "Beperken tot hosts waarvan de naam begint met" - -#: client/src/scheduler/scheduler.strings.js:51 -msgid "Limited to first 10" -msgstr "Beperkt tot de eerste 10" - -#: client/src/shared/socket/socket.service.js:213 -msgid "Live events: attempting to connect to the server." -msgstr "" -"Livegebeurtenissen: er wordt geprobeerd verbinding met de server te maken." - -#: client/src/shared/socket/socket.service.js:217 -msgid "" -"Live events: connected. Pages containing job status information will " -"automatically update in real-time." -msgstr "" -"Livegebeurtenissen: verbonden. Pagina's met informatie over de status van " -"taken worden automatisch geüpdatet in realtime." - -#: client/src/shared/socket/socket.service.js:221 -msgid "Live events: error connecting to the server." -msgstr "Livegebeurtenissen: fout bij het verbinding maken met de server." - -#: client/src/shared/form-generator.js:2005 -msgid "Loading..." -msgstr "Laden..." - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:133 -#: client/src/scheduler/scheduler.strings.js:26 -msgid "Local Time Zone" -msgstr "Lokale tijdzone" - -#: client/src/configuration/system-form/configuration-system.controller.js:225 -msgid "Log aggregator test failed.
Detail:" -msgstr "Log aggregator-test mislukt.
Details:" - -#: client/src/configuration/system-form/configuration-system.controller.js:218 -msgid "Log aggregator test successful." -msgstr "Log aggregator-test gelukt." - -#: client/lib/components/components.strings.js:65 -msgid "Logged in as" -msgstr "Aangemeld als" - -#: client/src/configuration/system-form/configuration-system.controller.js:89 -msgid "Logging" -msgstr "Bezig met dataregistratie" - -#: client/lib/components/components.strings.js:67 -msgid "Logout" -msgstr "Afmelden" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:35 -msgid "Low" -msgstr "Laag" - -#: client/src/management-jobs/card/card.partial.html:6 -#: client/src/management-jobs/card/card.route.js:20 -msgid "MANAGEMENT JOBS" -msgstr "BEHEERDERSTAKEN" - -#: client/src/instance-groups/instance-groups.strings.js:15 -msgid "MANUAL" -msgstr "HANDMATIG" - -#: client/features/output/output.strings.js:105 -msgid "MODULE" -msgstr "MODULE" - -#: client/features/portalMode/routes/portalModeTemplatesList.route.js:13 -msgid "MY VIEW" -msgstr "MIJN BEELD" - -#: client/src/credentials/credentials.form.js:67 -#: client/src/job-submission/job-submission.partial.html:356 -msgid "Machine" -msgstr "Machine" - -#: client/features/output/output.strings.js:58 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:60 -msgid "Machine Credential" -msgstr "Toegangsgegevens machine" - -#: client/lib/components/components.strings.js:82 -msgid "Management Jobs" -msgstr "Beheerderstaken" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:54 -#: client/src/projects/edit/projects-edit.controller.js:143 -#: client/src/projects/list/projects-list.controller.js:89 -msgid "Manual projects do not require an SCM update" -msgstr "Voor handmatige projecten is geen SCM-update nodig" - -#: client/src/templates/job_templates/job-template.form.js:234 -msgid "Max 512 characters per label." -msgstr "Max. 152 tekens per label." - -#: client/src/login/loginModal/loginModal.partial.html:28 -msgid "Maximum per-user sessions reached. Please sign in." -msgstr "Maximaal aantal sessies per gebruiker bereikt. Meld u aan." - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:29 -msgid "Medium" -msgstr "Medium" - -#: client/src/configuration/system-form/configuration-system.controller.js:90 -msgid "Misc. System" -msgstr "Overig systeem" - -#: client/src/templates/workflows.form.js:35 -msgid "" -"Missing Job Templates found in the Workflow Editor" -msgstr "" -"Ontbrekende taaksjablonen gevonden in de Workfloweditor" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:22 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:30 -msgid "Module" -msgstr "Module" - -#: client/features/output/output.strings.js:59 -msgid "Module Args" -msgstr "Module Args" - -#: client/src/scheduler/scheduler.strings.js:37 -msgid "Mon" -msgstr "Ma" - -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:25 -msgid "Most recent job failed. Click to view jobs." -msgstr "Meest recente taak is mislukt. Klik om taken te bekijken." - -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:29 -msgid "Most recent job successful. Click to view jobs." -msgstr "Meest recente taak is gelukt. Klik om taken te bekijken." - -#: client/src/templates/survey-maker/surveys/init.factory.js:17 -msgid "Multiple Choice (multiple select)" -msgstr "Meerkeuze-opties (meerdere keuzes mogelijk)" - -#: client/src/templates/survey-maker/surveys/init.factory.js:16 -msgid "Multiple Choice (single select)" -msgstr "Meerkeuze-opties (één keuze mogelijk)" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:77 -msgid "Multiple Choice Options" -msgstr "Meerkeuze-opties" - -#: client/features/portalMode/index.view.html:26 -msgid "My Jobs" -msgstr "Mijn taken" - -#: client/lib/components/components.strings.js:71 -msgid "My View" -msgstr "Mijn beeld" - -#: client/features/applications/applications.strings.js:24 -msgid "NEW APPLICATION" -msgstr "NIEUWE TOEPASSING" - -#: client/features/credentials/credentials.strings.js:26 -msgid "NEW CREDENTIAL" -msgstr "NIEUWE TOEGANGSGEGEVENS" - -#: client/src/credential-types/credential-types.form.js:16 -msgid "NEW CREDENTIAL TYPE" -msgstr "NIEUWE SOORT TOEGANGSGEGEVENS" - -#: client/src/inventory-scripts/inventory-scripts.form.js:16 -msgid "NEW CUSTOM INVENTORY" -msgstr "NIEUWE AANGEPASTE INVENTARIS" - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:17 -msgid "NEW INVENTORY" -msgstr "NIEUWE INVENTARIS" - -#: client/src/templates/job_templates/job-template.form.js:19 -msgid "NEW JOB TEMPLATE" -msgstr "NIEUWE TAAKSJABLOON" - -#: client/src/notifications/notificationTemplates.form.js:16 -msgid "NEW NOTIFICATION TEMPLATE" -msgstr "NIEUWE BERICHTSJABLOON" - -#: client/src/organizations/organizations.form.js:18 -msgid "NEW ORGANIZATION" -msgstr "NIEUWE ORGANISATIE" - -#: client/src/projects/projects.form.js:17 -msgid "NEW PROJECT" -msgstr "NIEUW PROJECT" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:10 -msgid "NEW SMART INVENTORY" -msgstr "NIEUW SMART-INVENTARIS" - -#: client/src/teams/teams.form.js:16 -msgid "NEW TEAM" -msgstr "NIEUW TEAM" - -#: client/src/users/users.form.js:16 -msgid "NEW USER" -msgstr "NIEUWE GEBRUIKER" - -#: client/src/templates/workflows.form.js:17 -msgid "NEW WORKFLOW JOB TEMPLATE" -msgstr "NIEUWE WORKFLOW-TAAKSJABLOON" - -#: client/lib/services/base-string.service.js:64 -msgid "NEXT" -msgstr "VOLGENDE" - -#: client/src/inventories-hosts/hosts/hosts.partial.html:38 -msgid "NO HOSTS HAVE BEEN CREATED" -msgstr "GEEN HOSTS AANGEMAAKT" - -#: client/lib/components/components.strings.js:39 -msgid "NO OPTIONS AVAILABLE" -msgstr "GEEN OPTIES BESCHIKBAAR" - -#: client/src/login/loginModal/loginModal.partial.html:89 -msgid "NOTICE" -msgstr "MEDEDELING" - -#: client/src/notifications/notificationTemplates.form.js:21 -msgid "NOTIFICATION TEMPLATE" -msgstr "BERICHTSJABLOON" - -#: client/src/activity-stream/get-target-title.factory.js:26 -#: client/src/notifications/notificationTemplates.list.js:14 -msgid "NOTIFICATION TEMPLATES" -msgstr "BERICHTSJABLONEN" - -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-notifications.route.js:9 -#: client/src/management-jobs/notifications/notification.route.js:46 -#: client/src/notifications/main.js:42 client/src/notifications/main.js:89 -#: client/src/notifications/notification-templates-list/add-notifications-action.partial.html:2 -msgid "NOTIFICATIONS" -msgstr "BERICHTEN" - -#: client/features/output/output.strings.js:60 -#: client/src/credential-types/credential-types.form.js:27 -#: client/src/credential-types/credential-types.list.js:24 -#: client/src/credentials/credentials.form.js:32 -#: client/src/credentials/credentials.list.js:26 -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:14 -#: client/src/home/dashboard/lists/jobs/jobs-list.partial.html:13 -#: client/src/instance-groups/instance-groups.list.js:15 -#: client/src/inventories-hosts/hosts/host.list.js:61 -#: client/src/inventories-hosts/inventories/inventory.list.js:48 -#: client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.directive.js:55 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:32 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js:33 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:51 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:21 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:28 -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:45 -#: client/src/inventory-scripts/inventory-scripts.form.js:28 -#: client/src/inventory-scripts/inventory-scripts.list.js:20 -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:21 -#: client/src/notifications/notificationTemplates.form.js:32 -#: client/src/notifications/notificationTemplates.list.js:32 -#: client/src/notifications/notifications.list.js:27 -#: client/src/organizations/organizations.form.js:26 -#: client/src/projects/projects.form.js:30 -#: client/src/projects/projects.list.js:37 -#: client/src/scheduler/scheduled-jobs.list.js:31 -#: client/src/scheduler/scheduler.strings.js:21 -#: client/src/scheduler/schedules.list.js:41 -#: client/src/teams/teams.form.js:127 client/src/teams/teams.form.js:28 -#: client/src/teams/teams.list.js:23 -#: client/src/templates/job_templates/job-template.form.js:34 -#: client/src/templates/templates.list.js:24 -#: client/src/templates/workflows.form.js:42 -#: client/src/users/users.form.js:144 client/src/users/users.form.js:170 -#: client/src/users/users.form.js:196 -msgid "Name" -msgstr "Naam" - -#: client/src/credentials/credentials.form.js:71 -msgid "Network" -msgstr "Netwerk" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:81 -msgid "New Group" -msgstr "Nieuwe groep" - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:130 -msgid "New Host" -msgstr "Nieuwe host" - -#: client/src/users/add/users-add.controller.js:92 -msgid "New user successfully created!" -msgstr "Nieuwe gebruiker aanmaken gelukt!" - -#: client/src/scheduler/scheduled-jobs.list.js:51 -#: client/src/scheduler/schedules.list.js:51 -msgid "Next Run" -msgstr "Volgende uitvoering" - -#: client/src/credentials/credentials.list.js:21 -msgid "No Credentials Have Been Created" -msgstr "Nog geen toegangsgegevens aangemaakt" - -#: client/features/templates/templates.strings.js:62 -#: client/src/job-submission/lists/credential/job-sub-cred-list.controller.js:44 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js:15 -msgid "No Credentials Matching This Type Have Been Created" -msgstr "Nog geen toegangsgegevens die overeenkomen met deze soort aangemaakt" - -#: client/features/output/host-event/host-event-codemirror.partial.html:3 -msgid "No JSON data returned by the module" -msgstr "Module heeft geen JSON-gegevens geretourneerd" - -#: client/src/projects/projects.list.js:20 -msgid "No Projects Have Been Created" -msgstr "Nog geen projecten aangemaakt" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:50 -msgid "No Remediation Playbook Available" -msgstr "Geen draaiboek voor herstel beschikbaar" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:199 -#: client/src/projects/list/projects-list.controller.js:186 -msgid "No SCM Configuration" -msgstr "Geen SCM-configuratie" - -#: client/src/projects/factories/get-project-tool-tip.factory.js:9 -msgid "No SCM updates have run for this project" -msgstr "Geen SCM-updates uitgevoerd voor dit project" - -#: client/src/access/rbac-multiselect/permissionsTeams.list.js:17 -msgid "No Teams exist" -msgstr "Er bestaan geen teams" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:191 -#: client/src/projects/list/projects-list.controller.js:150 -msgid "No Updates Available" -msgstr "Er zijn geen updates beschikbaar" - -#: client/src/access/rbac-multiselect/permissionsUsers.list.js:18 -msgid "No Users exist" -msgstr "Er bestaan geen gebruikers" - -#: client/features/templates/templates.strings.js:33 -msgid "No credentials selected" -msgstr "Geen toegangsgegevens geselecteerd" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:63 -msgid "No data is available. There are no issues to report." -msgstr "Geen gegevens beschikbaar. Er zijn geen problemen om te rapporteren." - -#: client/src/license/license.controller.js:41 -msgid "No file selected." -msgstr "Geen bestand geselecteerd." - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:69 -msgid "No hosts with failures. Click for details." -msgstr "Geen host met mislukkingen. Klik voor meer informatie." - -#: client/features/templates/templates.strings.js:34 -msgid "No inventory selected" -msgstr "Geen inventaris geselecteerd" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:52 -msgid "No inventory sync failures. Click for details." -msgstr "Geen mislukte inventarissynchronisaties. Klik voor meer informatie." - -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:16 -msgid "No job data" -msgstr "Geen taakgegevens" - -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:75 -msgid "No job data available." -msgstr "Geen taakgegevens beschikbaar." - -#: client/src/inventories-hosts/inventories/related/groups/factories/get-hosts-status-msg.factory.js:22 -msgid "No job failures" -msgstr "Geen mislukte taken" - -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:51 -msgid "No job templates were recently used." -msgstr "Geen taaksjablonen die recent gebruikt zijn." - -#: client/src/home/dashboard/lists/jobs/jobs-list.partial.html:44 -msgid "No jobs were recently run." -msgstr "Er zijn recent geen taken uitgevoerd." - -#: client/src/teams/teams.form.js:124 client/src/users/users.form.js:193 -msgid "No permissions have been granted" -msgstr "Geen machtigingen toegekend" - -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:17 -msgid "No recent job data available for this host." -msgstr "Er zijn geen recente taakgegevens beschikbaar voor deze host." - -#: client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.directive.js:75 -msgid "No recent job data available for this inventory." -msgstr "Er zijn geen recente taakgegevens beschikbaar voor dit inventaris." - -#: client/src/notifications/notification-templates-list/list.controller.js:86 -msgid "No recent notifications." -msgstr "Geen recente berichten." - -#: client/src/inventories-hosts/hosts/hosts.partial.html:36 -#: client/src/shared/form-generator.js:1899 -#: client/src/shared/list-generator/list-generator.factory.js:240 -msgid "No records matched your search." -msgstr "Er zijn geen gegevens die overeenkomen met uw zoekopdracht." - -#: client/features/output/output.strings.js:106 -msgid "No result found" -msgstr "Geen resultaat gevonden" - -#: client/src/scheduler/scheduled-jobs.list.js:16 -msgid "No schedules exist" -msgstr "Er bestaan geen schema's" - -#: client/src/job-submission/job-submission.partial.html:348 -#: client/src/job-submission/job-submission.partial.html:353 -msgid "None selected" -msgstr "Geen geselecteerd" - -#: client/src/users/add/users-add.controller.js:10 -#: client/src/users/edit/users-edit.controller.js:10 -#: client/src/users/list/users-list.controller.js:10 -msgid "Normal User" -msgstr "Normale gebruiker" - -#: client/features/output/output.strings.js:36 -#: client/src/workflow-results/workflow-results.controller.js:56 -msgid "Not Finished" -msgstr "Niet voltooid" - -#: client/features/output/output.strings.js:37 -#: client/src/workflow-results/workflow-results.controller.js:57 -msgid "Not Started" -msgstr "Niet gestart" - -#: client/src/projects/list/projects-list.controller.js:91 -msgid "Not configured for SCM" -msgstr "Niet geconfigureerd voor SCM" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:59 -msgid "Not configured for inventory sync." -msgstr "Niet geconfigureerd voor inventarissynchronisatie." - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts-disassociate.partial.html:25 -msgid "" -"Note that only hosts directly in this group can be disassociated. Hosts in " -"sub-groups must be disassociated 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." - -#: client/src/notifications/notificationTemplates.form.js:288 -#: client/src/notifications/notificationTemplates.form.js:289 -#: client/src/notifications/notificationTemplates.form.js:472 -#: client/src/notifications/notificationTemplates.form.js:473 -msgid "Notification Color" -msgstr "Berichtkleur" - -#: client/src/notifications/notification-templates-list/list.controller.js:140 -msgid "Notification Failed." -msgstr "Bericht mislukt" - -#: client/src/notifications/notificationTemplates.form.js:277 -msgid "Notification Label" -msgstr "Berichtlabel" - -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:31 -msgid "Notification Templates" -msgstr "Berichtsjablonen" - -#: client/lib/components/components.strings.js:81 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:20 -#: client/src/management-jobs/notifications/notification.route.js:21 -#: client/src/notifications/notifications.list.js:17 -msgid "Notifications" -msgstr "Berichten" - -#: client/src/notifications/notificationTemplates.form.js:302 -msgid "Notify Channel" -msgstr "Bericht naar kanaal sturen" - -#: client/lib/services/base-string.service.js:68 -#: client/src/inventories-hosts/hosts/hosts.partial.html:55 -#: client/src/job-submission/job-submission.partial.html:269 -#: client/src/partials/survey-maker-modal.html:27 -#: client/src/shared/form-generator.js:545 -#: client/src/shared/form-generator.js:780 -#: client/src/shared/generator-helpers.js:554 -msgid "OFF" -msgstr "UIT" - -#: client/lib/services/base-string.service.js:63 -msgid "OK" -msgstr "OK" - -#: client/lib/services/base-string.service.js:67 -#: client/src/inventories-hosts/hosts/hosts.partial.html:54 -#: client/src/job-submission/job-submission.partial.html:267 -#: client/src/partials/survey-maker-modal.html:26 -#: client/src/shared/form-generator.js:541 -#: client/src/shared/form-generator.js:778 -#: client/src/shared/generator-helpers.js:550 -msgid "ON" -msgstr "AAN" - -#: client/lib/components/components.strings.js:10 -msgid "OPTIONS" -msgstr "OPTIES" - -#: client/features/applications/applications.strings.js:30 -msgid "ORG" -msgstr "ORG" - -#: client/src/activity-stream/get-target-title.factory.js:29 -#: client/src/organizations/list/organizations-list.partial.html:6 -#: client/src/organizations/main.js:51 -msgid "ORGANIZATIONS" -msgstr "ORGANISATIES" - -#: client/src/scheduler/scheduler.strings.js:45 -msgid "Occurrences" -msgstr "Voorvallen" - -#: client/src/workflow-results/workflow-results.controller.js:65 -msgid "On Fail" -msgstr "Bij mislukken" - -#: client/features/templates/templates.strings.js:101 -msgid "On Failure" -msgstr "Bij mislukken" - -#: client/features/templates/templates.strings.js:100 -#: client/src/workflow-results/workflow-results.controller.js:64 -msgid "On Success" -msgstr "Bij slagen" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:157 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:162 -msgid "Only Group By" -msgstr "Alleen ordenen op" - -#: client/src/credentials/credentials.form.js:379 -msgid "" -"OpenStack domains define administrative boundaries. It is only needed for " -"Keystone v3 authentication URLs. Common scenarios include:" -msgstr "" -"Domeinen van OpenStack bepalen administratieve grenzen. Het is alleen nodig " -"voor Keystone v3 authenticatie-URL's. Veel voorkomende scenario's zijn:" - -#: client/src/templates/job_templates/job-template.form.js:230 -#: client/src/templates/workflows.form.js:78 -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." - -#: client/src/notifications/notificationTemplates.form.js:453 -#: client/src/partials/logviewer.html:7 -#: client/src/templates/job_templates/job-template.form.js:275 -#: client/src/templates/workflows.form.js:96 -msgid "Options" -msgstr "Opties" - -#: client/src/credentials/credentials.form.js:46 -#: client/src/credentials/credentials.form.js:53 -#: client/src/inventories-hosts/inventories/inventory.list.js:61 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:33 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:41 -#: client/src/inventory-scripts/inventory-scripts.form.js:40 -#: client/src/inventory-scripts/inventory-scripts.list.js:27 -#: client/src/notifications/notificationTemplates.form.js:44 -#: client/src/projects/projects.form.js:42 -#: client/src/projects/projects.form.js:48 client/src/teams/teams.form.js:40 -#: client/src/teams/teams.list.js:30 client/src/templates/workflows.form.js:55 -#: client/src/templates/workflows.form.js:61 client/src/users/users.form.js:42 -msgid "Organization" -msgstr "Organisatie" - -#: client/lib/components/components.strings.js:77 -#: client/lib/models/models.strings.js:35 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:136 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:64 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:32 -#: client/src/users/users.form.js:134 -msgid "Organizations" -msgstr "Organisaties" - -#: client/features/templates/templates.strings.js:27 -#: client/src/job-submission/job-submission.partial.html:19 -msgid "Other Prompts" -msgstr "Overige meldingen" - -#: client/src/credentials/credentials.form.js:79 -msgid "Others (Cloud Providers)" -msgstr "Overigen (cloudproviders)" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:317 -msgid "" -"Override variables found in azure_rm.ini and used by the inventory update " -"script. For a detailed description of these variables" -msgstr "" -"Variabelen overschrijven die aangetroffen zijn in azure_rm.ini en die " -"gebruikt worden in het script van de inventarisupdate. Voor een " -"gedetailleerde beschrijving van deze variabelen" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:283 -msgid "" -"Override variables found in cloudforms.ini and used by the inventory update script. For an example variable configuration\n" -" \n" -" view cloudforms.ini in the Ansible github repo. Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax." -msgstr "" -"Variabelen overschrijven die zijn aangetroffen in cloudforms.ini en die gebruikt worden door het script van de inventarisupdate. Zie voor een voorbeeld van de configuratie van de variabele \n" -" \n" -" cloudfroms.ini in de Ansible github repo.Voer de variabelen van de inventaris in met JSON- of YAML-syntaxis. Gebruik de radio-knop om tussen de twee de wisselen. Raadpleeg de documentatie van Ansible Tower voor voorbeeldsyntaxis." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:218 -msgid "" -"Override variables found in ec2.ini and used by the inventory update script." -" For a detailed description of these variables" -msgstr "" -"Variabelen overschrijven die aangetroffen zijn in ec2.ini en die gebruikt " -"worden door het script van de inventarisupdate. Voor een gedetailleerde " -"beschrijving van deze variabelen" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:300 -msgid "" -"Override variables found in foreman.ini and used by the inventory update script. For an example variable configuration\n" -" \n" -" view foreman.ini in the Ansible github repo. Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax." -msgstr "" -"Variabelen overschrijven die aangetroffen zijn in foreman.ini en die gebruikt worden door het script van de inventarisupdate. Zie voor een voorbeeld van de configuratie van de variabelen\n" -" \n" -" foreman.ini in de Ansible github repo.Voer de variabelen van de inventaris in met JSON- of YAML-syntaxis. Gebruik de radioknop om tussen de twee te wisselen. Raadpleeg de documentatie van Ansible Tower voor voorbeeldsyntaxis." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:266 -msgid "" -"Override variables found in openstack.yml and used by the inventory update script. For an example variable configuration\n" -" \n" -" view openstack.yml in the Ansible github repo. Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Tower documentation for example syntax." -msgstr "" -"Variabelen overschrijven die aangetroffen zijn in openstack.yml en die gebruikt worden door het script van de inventarisupdate. Zie voor een voorbeeld van de configuratie van de variabelen\n" -" \n" -" openstack.yml in de Ansible github repo.Voer de variabelen van de inventaris in met JSON- of YAML-syntaxis. Gebruik de radioknop om tussen de twee te wisselen. Raadpleeg de documentatie van Ansible Tower voor voorbeeldsyntaxis." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:242 -msgid "" -"Override variables found in vmware.ini and used by the inventory update " -"script. For a detailed description of these variables" -msgstr "" -"Variabelen overschrijven die aangetroffen zijn in vmware.ini en die gebruikt" -" worden in het script van de inventarisupdate. Voor een gedetailleerde " -"beschrijving van deze variabelen" - -#: client/features/output/output.strings.js:61 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:352 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:357 -msgid "Overwrite" -msgstr "Overschrijven" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:364 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:369 -msgid "Overwrite Variables" -msgstr "Variabelen overschrijven" - -#: client/features/output/output.strings.js:62 -msgid "Overwrite Vars" -msgstr "Variabelen overschrijven" - -#: client/src/credentials/credentials.list.js:40 -msgid "Owners" -msgstr "Eigenaren" - -#: client/src/login/loginModal/loginModal.partial.html:68 -msgid "PASSWORD" -msgstr "WACHTWOORD" - -#: client/features/credentials/legacy.credentials.js:117 -msgid "PERMISSIONS" -msgstr "MACHTIGINGEN" - -#: client/features/output/output.strings.js:103 -msgid "PLAY" -msgstr "AFSPELEN" - -#: client/src/partials/job-template-details.html:2 -msgid "PLAYBOOK" -msgstr "DRAAIBOEK" - -#: client/src/partials/survey-maker-modal.html:45 -msgid "PLEASE ADD A SURVEY PROMPT." -msgstr "VOEG EEN MELDING VOOR DE VRAGENLIJST TOE." - -#: client/src/organizations/list/organizations-list.partial.html:37 -#: client/src/shared/form-generator.js:1905 -#: client/src/shared/list-generator/list-generator.factory.js:248 -msgid "PLEASE ADD ITEMS TO THIS LIST" -msgstr "VOEG ITEMS AAN DEZE LIJST TOE" - -#: client/src/partials/survey-maker-modal.html:43 -msgid "PREVIEW" -msgstr "VOORVERTONING" - -#: client/src/partials/job-template-details.html:2 -msgid "PROJECT" -msgstr "PROJECT" - -#: client/src/activity-stream/get-target-title.factory.js:8 -#: client/src/organizations/linkout/organizations-linkout.route.js:196 -#: client/src/organizations/list/organizations-list.controller.js:73 -#: client/src/projects/main.js:92 client/src/projects/projects.list.js:14 -#: client/src/projects/projects.list.js:15 -msgid "PROJECTS" -msgstr "PROJECTEN" - -#: client/features/templates/templates.strings.js:26 -msgid "PROMPT" -msgstr "MELDING" - -#: client/src/shared/paginate/paginate.partial.html:33 -msgid "Page" -msgstr "Pagina" - -#: client/src/notifications/notificationTemplates.form.js:232 -msgid "Pagerduty subdomain" -msgstr "Subdomein Pagerduty" - -#: client/src/templates/job_templates/job-template.form.js:363 -msgid "" -"Pass extra command line variables to the playbook. Provide key/value pairs " -"using either YAML or JSON. Refer to the Ansible Tower documentation for " -"example syntax." -msgstr "" -"Geef extra commandoregelvariabelen op in het draaiboek. Geef sleutel/waarde-" -"paren op met YAML of JSON. Raadpleeg de documentatie van Ansible Tower voor " -"voorbeeldsyntaxis." - -#: client/src/templates/workflows.form.js:89 -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 Tower 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." - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:139 -msgid "" -"Pass extra command line variables. This is the %s or %s command line " -"parameter for %s. Provide key/value pairs using either YAML or JSON." -msgstr "" -"Geef extra commandoregelvariabelen op. Dit is de %s of %s " -"commandoregelparameter voor %s. Geef sleutel/waarde-paren op met YAML of " -"JSON." - -#: client/src/credentials/credentials.form.js:226 -#: client/src/credentials/factories/become-method-change.factory.js:21 -#: client/src/credentials/factories/become-method-change.factory.js:40 -#: client/src/credentials/factories/become-method-change.factory.js:48 -#: client/src/credentials/factories/become-method-change.factory.js:68 -#: client/src/credentials/factories/become-method-change.factory.js:78 -#: client/src/credentials/factories/become-method-change.factory.js:88 -#: client/src/credentials/factories/kind-change.factory.js:105 -#: client/src/credentials/factories/kind-change.factory.js:125 -#: client/src/credentials/factories/kind-change.factory.js:135 -#: client/src/credentials/factories/kind-change.factory.js:145 -#: client/src/credentials/factories/kind-change.factory.js:30 -#: client/src/credentials/factories/kind-change.factory.js:78 -#: client/src/credentials/factories/kind-change.factory.js:97 -#: client/src/job-submission/job-submission.partial.html:104 -#: client/src/notifications/shared/type-change.service.js:30 -#: client/src/templates/survey-maker/surveys/init.factory.js:15 -#: client/src/users/users.form.js:70 -msgid "Password" -msgstr "Wachtwoord" - -#: client/src/credentials/factories/kind-change.factory.js:58 -msgid "Password (API Key)" -msgstr "Wachtwoord (API-sleutel)" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:20 -msgid "Past 24 Hours" -msgstr "Afgelopen 24 uur" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:15 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:26 -msgid "Past Month" -msgstr "Afgelopen maand" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:23 -msgid "Past Week" -msgstr "Afgelopen week" - -#: client/src/credentials/factories/become-method-change.factory.js:29 -#: client/src/credentials/factories/kind-change.factory.js:86 -msgid "" -"Paste the contents of the PEM file associated with the service account " -"email." -msgstr "" -"Plak hier de inhoud van het PEM-bestand dat bij de e-mail van het " -"serviceaccount hoor." - -#: client/src/credentials/factories/kind-change.factory.js:51 -msgid "Paste the contents of the SSH private key file." -msgstr "Plak hier de inhoud van het SSH-privésleutelbestand." - -#: client/src/credentials/factories/kind-change.factory.js:26 -msgid "Paste the contents of the SSH private key file.%s or click to close%s" -msgstr "" -"Plak hier de inhoud van het SSH-privésleutelbestand.%s of klik om af te " -"sluiten%s" - -#: client/src/inventories-hosts/inventories/inventory.list.js:129 -msgid "Pending Delete" -msgstr "In afwachting om verwijderd te worden" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:8 -msgid "Period" -msgstr "Periode" - -#: client/src/projects/add/projects-add.controller.js:32 -#: client/src/users/add/users-add.controller.js:44 -msgid "Permission Error" -msgstr "Machtigingsfout" - -#: client/features/credentials/credentials.strings.js:14 -#: client/features/credentials/legacy.credentials.js:63 -#: client/src/credentials/credentials.form.js:439 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:104 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:106 -#: client/src/projects/projects.form.js:247 client/src/teams/teams.form.js:120 -#: client/src/templates/job_templates/job-template.form.js:402 -#: client/src/templates/workflows.form.js:139 -#: client/src/users/users.form.js:189 -msgid "Permissions" -msgstr "Machtigingen" - -#: client/features/users/tokens/tokens.strings.js:41 -msgid "Personal Access Token" -msgstr "Persoonlijke toegangstoken" - -#: client/features/output/output.strings.js:63 -#: client/src/shared/form-generator.js:1085 -#: client/src/templates/job_templates/job-template.form.js:107 -#: client/src/templates/job_templates/job-template.form.js:115 -msgid "Playbook" -msgstr "Draaiboek" - -#: client/src/projects/projects.form.js:90 -msgid "Playbook Directory" -msgstr "Draaiboekmap" - -#: client/features/templates/templates.strings.js:60 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:52 -msgid "Playbook Run" -msgstr "Draaiboek uitvoering" - -#: client/features/output/output.strings.js:91 -msgid "Plays" -msgstr "Uitvoeringen van het draaiboek" - -#: client/lib/components/components.strings.js:108 -msgid "Please add items to this list." -msgstr "Voeg items aan deze lijst toe." - -#: client/src/users/users.form.js:128 -msgid "Please add user to an Organization." -msgstr "Voeg gebruiker toe aan een organisatie" - -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:100 -msgid "Please assign roles to the selected resources" -msgstr "Wijs rollen toe aan de geselecteerde bronnen" - -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:60 -msgid "Please assign roles to the selected users/teams" -msgstr "Wijs rollen toe aan de geselecteerde gebruikers/teams" - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:210 -msgid "" -"Please check the server and make sure the directory exists and file " -"permissions are set correctly." -msgstr "" -"Controleer de server om er zeker van te zijn dat de map bestaat en de " -"bestandsmachtigingen juist zijn ingesteld." - -#: client/src/license/license.partial.html:84 -msgid "" -"Please click the button below to visit Ansible's website to get a Tower " -"license key." -msgstr "" -"Klik op onderstaande knop om de website van Ansible te bezoeken en een " -"licentiesleutel voor Tower te bemachtigen." - -#: client/src/inventories-hosts/inventory-hosts.strings.js:40 -msgid "Please click the icon to edit the host filter." -msgstr "Klik op het icoon om de hostfilter te wijzigen." - -#: client/features/templates/templates.strings.js:110 -msgid "Please click the start button to build your workflow." -msgstr "Klik op de startknop om uw workflow op te bouwen." - -#: client/src/shared/form-generator.js:868 -#: client/src/shared/form-generator.js:963 -msgid "" -"Please enter a URL that begins with ssh, http or https. The URL may not " -"contain the '@' character." -msgstr "" -"Voer een URL in die begint met ssh, http of https. De URL mag niet het teken" -" '@' bevatten." - -#: client/src/shared/form-generator.js:1178 -msgid "Please enter a number greater than %d and less than %d." -msgstr "Voer een getal in dat groter is dan %d en kleiner dan %d." - -#: client/src/shared/form-generator.js:1180 -msgid "Please enter a number greater than %d." -msgstr "Voer een getal in dat groter is dan %d." - -#: client/src/shared/form-generator.js:1172 -msgid "Please enter a number." -msgstr "Voer een getal in." - -#: client/features/templates/templates.strings.js:39 -#: client/src/job-submission/job-submission.partial.html:112 -#: client/src/job-submission/job-submission.partial.html:126 -#: client/src/job-submission/job-submission.partial.html:140 -#: client/src/job-submission/job-submission.partial.html:154 -#: client/src/login/loginModal/loginModal.partial.html:78 -msgid "Please enter a password." -msgstr "Voer een wachtwoord in." - -#: client/src/login/loginModal/loginModal.partial.html:58 -msgid "Please enter a username." -msgstr "Voer een gebruikersnaam in." - -#: client/src/shared/form-generator.js:858 -#: client/src/shared/form-generator.js:953 -msgid "Please enter a valid email address." -msgstr "Voer een geldig e-mailadres in." - -#: client/lib/components/components.strings.js:15 -#: client/src/shared/form-generator.js:1023 -#: client/src/shared/form-generator.js:853 -#: client/src/shared/form-generator.js:948 -msgid "Please enter a value." -msgstr "Voer een waarde in." - -#: client/src/job-submission/job-submission.partial.html:289 -#: client/src/job-submission/job-submission.partial.html:294 -#: client/src/job-submission/job-submission.partial.html:305 -#: client/src/job-submission/job-submission.partial.html:311 -#: client/src/job-submission/job-submission.partial.html:317 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:14 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:19 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:30 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:36 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:42 -msgid "Please enter an answer between" -msgstr "Voer een antwoord in tussen" - -#: client/features/templates/templates.strings.js:59 -#: client/src/job-submission/job-submission.partial.html:316 -msgid "Please enter an answer that is a decimal number." -msgstr "Voer een antwoord in dat een decimaal getal is." - -#: client/features/templates/templates.strings.js:58 -#: client/src/job-submission/job-submission.partial.html:310 -msgid "Please enter an answer that is a valid integer." -msgstr "Voer een antwoord in dat een geldig geheel getal is." - -#: client/features/templates/templates.strings.js:56 -#: client/src/job-submission/job-submission.partial.html:288 -#: client/src/job-submission/job-submission.partial.html:293 -#: client/src/job-submission/job-submission.partial.html:304 -#: client/src/job-submission/job-submission.partial.html:309 -#: client/src/job-submission/job-submission.partial.html:315 -msgid "Please enter an answer." -msgstr "Voer een antwoord in." - -#: client/src/inventories-hosts/inventory-hosts.strings.js:46 -msgid "Please enter at least one search term to create a new Smart Inventory." -msgstr "" -"Voer ten minste één zoekterm in om een nieuwe SMAT-inventaris aan te maken." - -#: client/features/templates/templates.strings.js:111 -msgid "Please hover over a template for additional options." -msgstr "Houd de muis een tijdje op een sjabloon om meer opties te zien." - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:169 -msgid "Please input a number greater than 1." -msgstr "Voer een getal in dat groter is dan 1." - -#: client/src/scheduler/scheduler.strings.js:47 -msgid "Please provide a valid date." -msgstr "Geef een geldige datum op." - -#: client/src/scheduler/scheduler.strings.js:30 -msgid "Please provide a value between 1 and 999." -msgstr "Geef een waarde tussen 1 en 999 op." - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:54 -msgid "Please save before adding a survey to this job template." -msgstr "Sla op voordat u een een vragenlijst toevoegt aan deze taaksjabloon." - -#: client/src/templates/workflows/add-workflow/workflow-add.controller.js:51 -msgid "Please save before adding a survey to this workflow." -msgstr "Sla op voordat u een vragenlijst toevoegt aan deze workflow." - -#: client/src/notifications/notifications.list.js:15 -msgid "Please save before adding notifications." -msgstr "Sla op voordat u berichten toevoegt." - -#: client/src/organizations/organizations.form.js:80 -#: client/src/teams/teams.form.js:72 -msgid "Please save before adding users." -msgstr "Sla op voordat u gebruikers toevoegt." - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:100 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:102 -#: client/src/projects/projects.form.js:239 client/src/teams/teams.form.js:116 -#: client/src/templates/job_templates/job-template.form.js:395 -#: client/src/templates/workflows.form.js:132 -msgid "Please save before assigning permissions." -msgstr "Sla op voordat u machtigingen toekent." - -#: client/src/users/users.form.js:126 client/src/users/users.form.js:185 -msgid "Please save before assigning to organizations." -msgstr "Sla op voordat u toewijst aan organisaties." - -#: client/src/users/users.form.js:154 -msgid "Please save before assigning to teams." -msgstr "Sla op voordat u toewijst aan teams." - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:147 -msgid "Please save before creating groups." -msgstr "Sla op voordat u groepen aanmaakt." - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:156 -msgid "Please save before creating hosts." -msgstr "Sla op voordat u hosts aanmaakt." - -#: client/src/inventories-hosts/hosts/host.form.js:112 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:86 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:111 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:112 -msgid "Please save before defining groups." -msgstr "Sla op voordat u groepen bepaalt." - -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:94 -msgid "Please save before defining hosts." -msgstr "Sla op voordat u hosts bepaalt." - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:165 -msgid "Please save before defining inventory sources." -msgstr "Sla op voordat u inventarisbronnen bepaalt." - -#: client/src/templates/workflows/add-workflow/workflow-add.controller.js:50 -msgid "Please save before defining the workflow graph." -msgstr "Sla op voordat u de workflowgrafiek bepaalt." - -#: client/src/inventories-hosts/hosts/host.form.js:121 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:120 -msgid "Please save before viewing Insights." -msgstr "Sla op voordat u Insights bekijkt." - -#: client/src/inventories-hosts/hosts/host.form.js:105 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:104 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:105 -msgid "Please save before viewing facts." -msgstr "Sla op voordat u feiten bekijkt." - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:146 -msgid "Please save before viewing hosts." -msgstr "Sla op voordat u hosts bekijkt." - -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:26 -msgid "Please select Users / Teams from the lists below." -msgstr "Selecteer gebruikers/teams uit de onderstaande lijsten." - -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:29 -msgid "Please select Users from the list below." -msgstr "Selecteer gebruikers uit de onderstaande lijst." - -#: client/src/shared/form-generator.js:1213 -msgid "Please select a number between" -msgstr "Selecteer een getal tussen" - -#: client/src/shared/form-generator.js:1209 -msgid "Please select a number." -msgstr "Selecteer een getal." - -#: client/features/templates/templates.strings.js:57 -msgid "Please select a value" -msgstr "Selecteer een waarde" - -#: client/src/shared/form-generator.js:1097 -#: client/src/shared/form-generator.js:1169 -#: client/src/shared/form-generator.js:1290 -#: client/src/shared/form-generator.js:1398 -msgid "Please select a value." -msgstr "Selecteer een waarde." - -#: client/src/templates/job_templates/job-template.form.js:77 -msgid "Please select an Inventory or check the Prompt on launch option." -msgstr "Selecteer een inventaris of vink de optie Melding bij opstarten aan." - -#: client/src/inventories-hosts/inventory-hosts.strings.js:39 -msgid "Please select an organization before editing the host filter." -msgstr "Selecteer een organisatie voordat u het hostfilter wijzigt." - -#: client/src/shared/form-generator.js:1206 -msgid "Please select at least one value." -msgstr "Selecteer ten minste één waarde." - -#: client/src/scheduler/scheduler.strings.js:43 -msgid "Please select one or more days." -msgstr "Selecteer ten minste één dag." - -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:30 -msgid "Please select resources from the lists below." -msgstr "Selecteer hulpbronnen uit de onderstaande lijsten." - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:47 -msgid "Populate the hosts for this inventory by using a search filter." -msgstr "Vul de hosts voor dit inventaris in door een zoekfilter te gebruiken." - -#: client/src/notifications/shared/type-change.service.js:29 -msgid "Port" -msgstr "Poort" - -#: client/features/templates/templates.strings.js:29 -msgid "Preview" -msgstr "Voorvertoning" - -#: client/src/credentials/credentials.form.js:257 -#: client/src/credentials/factories/kind-change.factory.js:21 -#: client/src/credentials/factories/kind-change.factory.js:45 -msgid "Private Key" -msgstr "Privésleutel" - -#: client/features/templates/templates.strings.js:42 -#: client/src/credentials/credentials.form.js:264 -#: client/src/job-submission/job-submission.partial.html:118 -msgid "Private Key Passphrase" -msgstr "Privésleutel wachtwoordzin" - -#: client/src/credentials/credentials.form.js:279 -#: client/src/credentials/credentials.form.js:283 -msgid "Privilege Escalation" -msgstr "Verhoging van rechten" - -#: client/features/templates/templates.strings.js:43 -#: client/src/credentials/credentials.form.js:305 -#: client/src/job-submission/job-submission.partial.html:132 -msgid "Privilege Escalation Password" -msgstr "Wachtwoord verhoging van rechten" - -#: client/src/credentials/credentials.form.js:295 -msgid "Privilege Escalation Username" -msgstr "Gebruikersnaam verhoging van rechten" - -#: client/features/jobs/jobs.strings.js:15 -#: client/features/output/output.strings.js:64 -#: client/features/templates/templates.strings.js:17 -#: client/src/credentials/factories/become-method-change.factory.js:30 -#: client/src/credentials/factories/kind-change.factory.js:87 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:93 -#: client/src/templates/job_templates/job-template.form.js:100 -#: client/src/templates/job_templates/job-template.form.js:91 -msgid "Project" -msgstr "Projecten" - -#: client/src/credentials/factories/become-method-change.factory.js:53 -#: client/src/credentials/factories/kind-change.factory.js:110 -msgid "Project (Tenant Name)" -msgstr "Projecten (naam huurder)" - -#: client/src/projects/projects.form.js:76 -#: client/src/projects/projects.form.js:84 -msgid "Project Base Path" -msgstr "Basispad project" - -#: client/src/credentials/credentials.form.js:365 -msgid "Project Name" -msgstr "Projectnaam" - -#: client/src/projects/projects.form.js:101 -msgid "Project Path" -msgstr "Projectpad" - -#: client/features/templates/templates.strings.js:103 -#: client/src/workflow-results/workflow-results.controller.js:67 -msgid "Project Sync" -msgstr "Projectsynchronisatie" - -#: client/src/home/dashboard/counts/dashboard-counts.directive.js:66 -msgid "Project Sync Failures" -msgstr "Mislukte projectsynchronisaties" - -#: client/src/projects/list/projects-list.controller.js:197 -msgid "Project lookup failed. GET returned:" -msgstr "Ophalen project mislukt. Geretourneerde OPHALEN:" - -#: client/lib/components/components.strings.js:72 -#: client/lib/models/models.strings.js:40 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:115 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:47 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:33 -#: client/src/home/dashboard/counts/dashboard-counts.directive.js:61 -#: client/src/organizations/linkout/organizations-linkout.route.js:207 -#: client/src/organizations/linkout/organizations-linkout.route.js:209 -msgid "Projects" -msgstr "Projecten" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:18 -msgid "Promote group" -msgid_plural "Promote groups" -msgstr[0] "Groep promoveren" -msgstr[1] "Groepen promoveren" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:43 -msgid "Promote groups" -msgstr "Groepen promoveren" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:32 -msgid "Promote groups and hosts" -msgstr "Groepen en hosts promoveren" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:20 -msgid "Promote host" -msgid_plural "Promote hosts" -msgstr[0] "Host promoveren" -msgstr[1] "Hosts promoveren" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:54 -msgid "Promote hosts" -msgstr "Hosts promoveren" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:10 -msgid "Promote {{ group }} and {{ host }}" -msgstr "{{ group }} en {{ host }} promoveren" - -#: client/src/scheduler/scheduler.strings.js:54 -#: client/src/templates/survey-maker/shared/question-definition.form.js:27 -msgid "Prompt" -msgstr "Melding" - -#: client/lib/components/components.strings.js:34 -#: client/src/templates/job_templates/job-template.form.js:138 -#: client/src/templates/job_templates/job-template.form.js:168 -#: client/src/templates/job_templates/job-template.form.js:185 -#: client/src/templates/job_templates/job-template.form.js:202 -#: client/src/templates/job_templates/job-template.form.js:219 -#: client/src/templates/job_templates/job-template.form.js:270 -#: client/src/templates/job_templates/job-template.form.js:370 -#: client/src/templates/job_templates/job-template.form.js:60 -#: client/src/templates/job_templates/job-template.form.js:86 -msgid "Prompt on launch" -msgstr "Melding bij opstarten" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:238 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:253 -msgid "Provide a comma-separated list of filter expressions." -msgstr "Voer een lijst van filteruitdrukkingen in, gescheiden door komma's." - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:254 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:271 -msgid "" -"Provide a comma-separated list of filter expressions. Hosts are imported " -"when all of the filters match. Refer to Ansible Tower documentation for more" -" detail." -msgstr "" -"Voer een lijst van filteruitdrukkingen in, gescheiden door komma's. Hosts " -"worden geïmporteerd wanneer alle filters overeenkomen. Raadpleeg de " -"documentatie van Ansible Tower voor meer informatie." - -#: client/src/inventories-hosts/hosts/host.form.js:50 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:49 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:49 -msgid "Provide a host name, ip address, or ip address:port. Examples include:" -msgstr "Geef een hostnaam, IP-adres, of IP-adres:poort op. Voorbeelden:" - -#: client/src/templates/job_templates/job-template.form.js:162 -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." - -#: client/features/credentials/credentials.strings.js:22 -msgid "" -"Provide account information using Google Compute Engine JSON credentials " -"file." -msgstr "" -"Geef accountgegevens met het JSON-toegangsgegevensbestand van Google Compute" -" Engine." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:196 -msgid "Provide environment variables to pass to the custom inventory script." -msgstr "" -"Geef omgevingsvariabelen op om door te geven aan het aangepaste " -"inventarisscript." - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:257 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:274 -msgid "" -"Provide the named URL encoded name or id of the remote Tower inventory to be" -" imported." -msgstr "" -"Voer de URL, versleutelde naam of ID of de externe inventaris in die " -"geïmporteerd moet worden." - -#: client/src/templates/job_templates/job-template.form.js:326 -#: client/src/templates/job_templates/job-template.form.js:334 -msgid "Provisioning Callback URL" -msgstr "Provisioning terugkoppelings-URL" - -#: client/src/notifications/add/add.controller.js:81 -#: client/src/notifications/edit/edit.controller.js:128 -msgid "Purple" -msgstr "Paars" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:158 -msgid "RADIUS" -msgstr "RADIUS" - -#: client/src/instance-groups/instance-groups.strings.js:47 -msgid "RAM" -msgstr "RAM" - -#: client/lib/components/code-mirror/code-mirror.strings.js:13 -msgid "READ ONLY" -msgstr "ALLEEN-LEZEN" - -#: client/src/home/dashboard/lists/jobs/jobs-list.partial.html:4 -msgid "RECENT JOB RUNS" -msgstr "RECENTE TAAKUITVOERINGEN" - -#: client/src/home/dashboard/lists/jobs/jobs-list.partial.html:40 -msgid "RECENTLY RUN JOBS" -msgstr "TAKEN DIE RECENT UITGEVOERD ZIJN" - -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:47 -msgid "RECENTLY USED JOB TEMPLATES" -msgstr "TAAKSJABLONEN DIE RECENT GEBRUIKT ZIJN" - -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:4 -msgid "RECENTLY USED TEMPLATES" -msgstr "SJABLONEN DIE RECENT GEBRUIKT ZIJN" - -#: client/src/activity-stream/streams.list.js:54 -#: client/src/inventories-hosts/hosts/host.list.js:102 -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.list.js:46 -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:47 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:47 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:113 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:47 -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:54 -#: client/src/projects/projects.list.js:70 -#: client/src/scheduler/schedules.list.js:69 -msgid "REFRESH" -msgstr "VERNIEUWEN" - -#: client/features/users/tokens/tokens.strings.js:23 -msgid "REFRESH TOKEN" -msgstr "TOKEN VERVERSEN" - -#: client/src/shared/smart-search/smart-search.partial.html:45 -msgid "RELATED FIELDS:" -msgstr "VERWANTE VELDEN:" - -#: client/src/shared/directives.js:94 -msgid "REMOVE" -msgstr "VERWIJDEREN" - -#: client/lib/components/components.strings.js:7 -msgid "REPLACE" -msgstr "VERVANGEN" - -#: client/features/output/output.strings.js:8 -msgid "RESULTS" -msgstr "RESULTATEN" - -#: client/features/templates/templates.strings.js:35 -#: client/lib/components/components.strings.js:8 -#: client/src/job-submission/job-submission.partial.html:44 -#: client/src/job-submission/job-submission.partial.html:87 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html:50 -msgid "REVERT" -msgstr "TERUGZETTEN" - -#: client/src/credentials/factories/become-method-change.factory.js:25 -#: client/src/credentials/factories/kind-change.factory.js:82 -msgid "RSA Private Key" -msgstr "RSA-privésleutel" - -#: client/features/templates/templates.strings.js:112 -msgid "RUN" -msgstr "UITVOEREN" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.route.js:28 -msgid "RUN COMMAND" -msgstr "COMMANDO UITVOEREN" - -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:56 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:56 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:101 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:114 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:56 -msgid "RUN COMMANDS" -msgstr "COMMANDO'S UITVOEREN" - -#: client/src/notifications/add/add.controller.js:84 -#: client/src/notifications/edit/edit.controller.js:131 -msgid "Random" -msgstr "Willekeurig" - -#: client/features/users/tokens/tokens.strings.js:30 -msgid "Read" -msgstr "Lezen" - -#: client/src/workflow-results/workflow-results.controller.js:121 -msgid "Read only view of extra variables added to the workflow." -msgstr "Alleen-lezen-beeld van extra variabelen toegevoegd aan de workflow." - -#: client/features/output/output.strings.js:23 -#: client/lib/components/code-mirror/code-mirror.strings.js:49 -msgid "Read-only view of extra variables added to the job template." -msgstr "Alleen-lezen-beeld van extra variabelen toegevoegd aan taaksjabloon." - -#: client/src/notifications/notificationTemplates.list.js:26 -msgid "Recent Notifications" -msgstr "Recente berichten" - -#: client/src/notifications/notificationTemplates.form.js:94 -#: client/src/notifications/notificationTemplates.form.js:98 -msgid "Recipient List" -msgstr "Lijst van ontvangers" - -#: client/src/notifications/add/add.controller.js:82 -#: client/src/notifications/edit/edit.controller.js:129 -msgid "Red" -msgstr "Rood" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:47 -msgid "" -"Refer to the Ansible Tower documentation for further syntax and examples." -msgstr "" -"Raadpleeg de documentatie van Ansible Tower voor meer syntaxis en " -"voorbeelden." - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:245 -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:270 -msgid "Refresh" -msgstr "Vernieuwen" - -#: client/src/activity-stream/streams.list.js:51 -#: client/src/bread-crumb/bread-crumb.partial.html:6 -#: client/src/inventories-hosts/hosts/host.list.js:98 -#: client/src/inventories-hosts/hosts/related/groups/hosts-related-groups.list.js:42 -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:43 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:43 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:109 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:43 -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:50 -#: client/src/projects/projects.list.js:66 -#: client/src/scheduler/schedules.list.js:65 -msgid "Refresh the page" -msgstr "Pagina vernieuwen" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:231 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:245 -msgid "Region:" -msgstr "Regio:" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:131 -msgid "Regions" -msgstr "Regio's" - -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:65 -msgid "Related Groups" -msgstr "Gerelateerde groepen" - -#: client/lib/components/components.strings.js:98 -msgid "Relaunch On" -msgstr "Opnieuw starten bij" - -#: client/lib/components/components.strings.js:97 -msgid "Relaunch using host parameters" -msgstr "Opnieuw opstarten met hostparameters" - -#: client/lib/components/components.strings.js:96 -#: client/src/workflow-results/workflow-results.controller.js:37 -msgid "Relaunch using the same parameters" -msgstr "Opnieuw opstarten met dezelfde parameters" - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:180 -msgid "Remediate Inventory" -msgstr "Inventaris herstellen" - -#: client/src/access/add-rbac-user-team/rbac-selected-list.directive.js:102 -#: client/src/access/add-rbac-user-team/rbac-selected-list.directive.js:103 -#: client/src/teams/teams.form.js:145 client/src/users/users.form.js:224 -msgid "Remove" -msgstr "Verwijderen" - -#: client/src/projects/projects.form.js:159 -msgid "Remove any local modifications prior to performing an update." -msgstr "" -"Verwijder alle plaatselijke aanpassingen voordat een update uitgevoerd " -"wordt." - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:149 -#: client/src/scheduler/scheduler.strings.js:27 -msgid "Repeat frequency" -msgstr "Frequentie herhalen" - -#: client/src/license/license.partial.html:89 -msgid "Request License" -msgstr "Licentie opvragen" - -#: client/src/templates/survey-maker/shared/question-definition.form.js:291 -msgid "Required" -msgstr "Vereist" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:154 -msgid "Reset" -msgstr "Resetten" - -#: client/lib/components/components.strings.js:90 -msgid "Resources" -msgstr "Hulpbronnen" - -#: client/features/templates/templates.strings.js:85 -#: client/src/scheduler/schedules.list.js:24 -msgid "Resources are missing from this template." -msgstr "Er ontbreken hulpbronnen uit deze sjabloon." - -#: client/lib/services/base-string.service.js:88 -msgid "Return" -msgstr "Teruggeven" - -#: client/features/users/tokens/tokens.strings.js:26 -msgid "Returned status:" -msgstr "Teruggegeven status:" - -#: client/src/shared/form-generator.js:697 -msgid "Revert" -msgstr "Terugzetten" - -#: client/src/configuration/auth-form/sub-forms/auth-azure.form.js:47 -#: client/src/configuration/auth-form/sub-forms/auth-github-org.form.js:51 -#: client/src/configuration/auth-form/sub-forms/auth-github-team.form.js:51 -#: client/src/configuration/auth-form/sub-forms/auth-github.form.js:47 -#: client/src/configuration/auth-form/sub-forms/auth-google-oauth2.form.js:59 -#: client/src/configuration/auth-form/sub-forms/auth-ldap.form.js:102 -#: client/src/configuration/auth-form/sub-forms/auth-ldap1.form.js:102 -#: client/src/configuration/auth-form/sub-forms/auth-ldap2.form.js:102 -#: client/src/configuration/auth-form/sub-forms/auth-ldap3.form.js:102 -#: client/src/configuration/auth-form/sub-forms/auth-ldap4.form.js:102 -#: client/src/configuration/auth-form/sub-forms/auth-ldap5.form.js:102 -#: client/src/configuration/auth-form/sub-forms/auth-radius.form.js:34 -#: client/src/configuration/auth-form/sub-forms/auth-saml.form.js:121 -#: client/src/configuration/auth-form/sub-forms/auth-tacacs.form.js:47 -#: client/src/configuration/jobs-form/configuration-jobs.form.js:73 -#: client/src/configuration/system-form/sub-forms/system-activity-stream.form.js:26 -#: client/src/configuration/system-form/sub-forms/system-logging.form.js:74 -#: client/src/configuration/system-form/sub-forms/system-misc.form.js:56 -#: client/src/configuration/ui-form/configuration-ui.form.js:36 -msgid "Revert all to default" -msgstr "Alles terugzetten naar standaardinstellingen" - -#: client/features/output/output.strings.js:66 -#: client/src/projects/projects.list.js:50 -msgid "Revision" -msgstr "Herziening" - -#: client/src/projects/add/projects-add.controller.js:155 -#: client/src/projects/edit/projects-edit.controller.js:290 -msgid "Revision #" -msgstr "Herziening #" - -#: client/features/credentials/legacy.credentials.js:85 -#: client/src/credentials/credentials.form.js:462 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:130 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:132 -#: client/src/organizations/organizations.form.js:109 -#: client/src/projects/projects.form.js:270 client/src/teams/teams.form.js:101 -#: client/src/teams/teams.form.js:138 -#: client/src/templates/workflows.form.js:163 -#: client/src/users/users.form.js:207 -msgid "Role" -msgstr "Rol" - -#: client/src/instance-groups/instance-groups.list.js:26 -#: client/src/instance-groups/instance-groups.strings.js:18 -#: client/src/instance-groups/instance-groups.strings.js:53 -msgid "Running Jobs" -msgstr "Taken in uitvoering" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:159 -msgid "SAML" -msgstr "SAML" - -#: client/lib/services/base-string.service.js:62 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory-host-filter/host-filter-modal/host-filter-modal.partial.html:17 -#: client/src/inventories-hosts/shared/associate-groups/associate-groups.partial.html:17 -#: client/src/inventories-hosts/shared/associate-hosts/associate-hosts.partial.html:17 -#: client/src/partials/survey-maker-modal.html:87 -#: client/src/shared/instance-groups-multiselect/instance-groups-modal/instance-groups-modal.partial.html:18 -msgid "SAVE" -msgstr "OPSLAAN" - -#: client/src/scheduler/scheduled-jobs.list.js:13 -#: client/src/scheduler/scheduled-jobs.list.js:14 -msgid "SCHEDULED JOBS" -msgstr "GEPLANDE TAKEN" - -#: client/src/activity-stream/get-target-title.factory.js:38 -#: client/src/inventories-hosts/inventories/related/sources/list/schedule/sources-schedule.route.js:9 -#: client/src/management-jobs/scheduler/main.js:28 -#: client/src/management-jobs/scheduler/main.js:34 -#: client/src/scheduler/schedules.route.js:102 -#: client/src/scheduler/schedules.route.js:14 -#: client/src/scheduler/schedules.route.js:189 -#: client/src/scheduler/schedules.route.js:284 -msgid "SCHEDULES" -msgstr "SCHEMA'S" - -#: client/src/projects/add/projects-add.controller.js:127 -#: client/src/projects/edit/projects-edit.controller.js:263 -msgid "SCM Branch" -msgstr "SCM-vertakking" - -#: client/src/projects/add/projects-add.controller.js:146 -#: client/src/projects/edit/projects-edit.controller.js:281 -msgid "SCM Branch/Tag/Commit" -msgstr "SCM-vertakking/tag/binding" - -#: client/src/projects/add/projects-add.controller.js:167 -#: client/src/projects/edit/projects-edit.controller.js:302 -msgid "SCM Branch/Tag/Revision" -msgstr "SCM-vertakking/tag/herziening" - -#: client/src/projects/projects.form.js:160 -msgid "SCM Clean" -msgstr "SCM-zuiveren" - -#: client/src/projects/projects.form.js:171 -msgid "SCM Delete" -msgstr "SCM-verwijderen" - -#: client/src/credentials/factories/become-method-change.factory.js:20 -#: client/src/credentials/factories/kind-change.factory.js:77 -msgid "SCM Private Key" -msgstr "SCM-privésleutel" - -#: client/src/projects/projects.form.js:56 -msgid "SCM Type" -msgstr "SCM-soort" - -#: client/src/projects/projects.form.js:107 -msgid "SCM URL" -msgstr "SCM URL" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:49 -#: client/src/projects/projects.form.js:181 -msgid "SCM Update" -msgstr "SCM-update" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:224 -#: client/src/projects/list/projects-list.controller.js:267 -msgid "SCM Update Cancel" -msgstr "SCM-updatekanaal" - -#: client/src/projects/projects.form.js:151 -msgid "SCM Update Options" -msgstr "SCM-update-opties" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:119 -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:46 -#: client/src/projects/edit/projects-edit.controller.js:139 -#: client/src/projects/list/projects-list.controller.js:118 -#: client/src/projects/list/projects-list.controller.js:85 -msgid "SCM update currently running" -msgstr "SCM-update nu in uitvoering" - -#: client/features/users/tokens/tokens.strings.js:39 -msgid "SCOPE" -msgstr "BEREIK" - -#: client/features/output/output.strings.js:83 -msgid "SEARCH" -msgstr "ZOEKEN" - -#: client/features/templates/templates.strings.js:114 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html:74 -msgid "SELECT" -msgstr "SELECTEREN" - -#: client/features/credentials/credentials.strings.js:20 -msgid "SELECT A CREDENTIAL TYPE" -msgstr "SELECTEER EEN SOORT TOEGANGSGEGEVENS" - -#: client/features/users/tokens/tokens.strings.js:19 -msgid "SELECT AN APPLICATION" -msgstr "TOEPASSING SELECTEREN" - -#: client/features/applications/applications.strings.js:35 -#: client/features/credentials/credentials.strings.js:19 -msgid "SELECT AN ORGANIZATION" -msgstr "SELECTEER EEN ORGANISATIE" - -#: client/src/inventories-hosts/shared/associate-groups/associate-groups.partial.html:6 -msgid "SELECT GROUPS" -msgstr "SELECTEER GROEPEN" - -#: client/src/inventories-hosts/shared/associate-hosts/associate-hosts.partial.html:6 -msgid "SELECT HOSTS" -msgstr "SELECTEER HOSTS" - -#: client/src/instance-groups/instance-groups.strings.js:36 -msgid "SELECT INSTANCE" -msgstr "INSTANTIE SELECTEREN" - -#: client/features/templates/templates.strings.js:32 -msgid "SELECTED" -msgstr "GESELECTEERD" - -#: client/src/job-submission/job-submission.partial.html:29 -#: client/src/job-submission/job-submission.partial.html:56 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.partial.html:18 -msgid "SELECTED:" -msgstr "GESELECTEERD:" - -#: client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html:20 -msgid "SETTING CATEGORY" -msgstr "CATEGORIE SELECTEREN" - -#: client/src/activity-stream/streamDetailModal/streamDetailModal.partial.html:24 -msgid "SETTING NAME" -msgstr "NAAM INSTELLEN" - -#: client/lib/components/components.strings.js:11 -#: client/lib/services/base-string.service.js:65 -#: client/src/templates/survey-maker/surveys/init.factory.js:486 -msgid "SHOW" -msgstr "TONEN" - -#: client/src/login/loginModal/loginModal.partial.html:97 -msgid "SIGN IN" -msgstr "AANMELDEN" - -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.partial.html:2 -msgid "SIGN IN WITH" -msgstr "AANMELDEN MET" - -#: client/src/inventories-hosts/hosts/host.list.js:110 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:14 -msgid "SMART INVENTORY" -msgstr "SMART-INVENTARIS" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.route.js:27 -msgid "SOURCES" -msgstr "BRONNEN" - -#: client/src/credentials/factories/become-method-change.factory.js:89 -#: client/src/credentials/factories/kind-change.factory.js:146 -msgid "SSH Key" -msgstr "SSH-sleutel" - -#: client/features/templates/templates.strings.js:41 -msgid "SSH Password" -msgstr "SSH-wachtwoord" - -#: client/src/credentials/credentials.form.js:255 -msgid "SSH key description" -msgstr "Beschrijving SSH-sleutel" - -#: client/src/notifications/notificationTemplates.form.js:446 -msgid "SSL Connection" -msgstr "SSL-verbinding" - -#: client/features/templates/templates.strings.js:117 -msgid "START" -msgstr "BEGINNEN" - -#: client/src/smart-status/smart-status.controller.js:43 -msgid "STATUS" -msgstr "STATUS" - -#: client/src/credentials/credentials.form.js:119 -#: client/src/credentials/credentials.form.js:127 -msgid "STS Token" -msgstr "STS-token" - -#: client/src/home/dashboard/graphs/job-status/job-status-graph.directive.js:62 -msgid "SUCCESSFUL" -msgstr "GESLAAGD" - -#: client/src/partials/survey-maker-modal.html:24 -msgid "SURVEY" -msgstr "VRAGENLIJST" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:62 -msgid "SYNC ALL" -msgstr "ALLES SYNCHRONISEREN" - -#: client/src/system-tracking/system-tracking.route.js:18 -msgid "SYSTEM TRACKING" -msgstr "SYSTEEMTRACKING" - -#: client/src/scheduler/scheduler.strings.js:42 -msgid "Sat" -msgstr "Zat" - -#: client/src/credentials/factories/become-method-change.factory.js:70 -#: client/src/credentials/factories/kind-change.factory.js:127 -msgid "Satellite 6 URL" -msgstr "Satellite 6-URL" - -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:110 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:193 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:158 -#: client/src/scheduler/scheduler.strings.js:57 -#: client/src/shared/form-generator.js:1711 -msgid "Save" -msgstr "Opslaan" - -#: client/src/configuration/configuration.controller.js:516 -msgid "Save Complete" -msgstr "Opslaan voltooid" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:105 -#: client/src/configuration/configuration.controller.js:242 -#: client/src/configuration/configuration.controller.js:326 -#: client/src/configuration/system-form/configuration-system.controller.js:68 -msgid "Save changes" -msgstr "Wijzigingen opslaan" - -#: client/src/license/license.partial.html:140 -msgid "Save successful!" -msgstr "Opslaan gelukt!" - -#: client/src/scheduler/scheduler.strings.js:50 -msgid "Schedule Description" -msgstr "Omschrijving schema" - -#: client/src/management-jobs/card/card.partial.html:28 -msgid "Schedule Management Job" -msgstr "Schema beheertaak" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:7 -msgid "Schedule inventory syncs" -msgstr "Inventarissynchronisaties inplannen" - -#: client/src/scheduler/scheduler.strings.js:14 -msgid "Schedule is active." -msgstr "Schema is actief." - -#: client/src/scheduler/scheduler.strings.js:15 -msgid "Schedule is active. Click to stop." -msgstr "Schema is actief. Klik om stop te zetten." - -#: client/src/scheduler/scheduler.strings.js:16 -msgid "Schedule is stopped." -msgstr "Schema is gestopt." - -#: client/src/scheduler/scheduler.strings.js:17 -msgid "Schedule is stopped. Click to activate." -msgstr "Schema is gestopt. Klik om te activeren." - -#: client/lib/components/components.strings.js:70 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:35 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:440 -#: client/src/projects/projects.form.js:290 -#: client/src/templates/job_templates/job-template.form.js:448 -#: client/src/templates/workflows.form.js:185 -msgid "Schedules" -msgstr "Schema's" - -#: client/src/shared/smart-search/smart-search.controller.js:122 -#: client/src/shared/smart-search/smart-search.controller.js:164 -msgid "Search" -msgstr "Zoeken" - -#: client/src/credentials/credentials.form.js:104 -msgid "Secret Key" -msgstr "Geheime sleutel" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:232 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:246 -msgid "Security Group:" -msgstr "Veiligheidsgroep:" - -#: client/src/credentials/credentials.form.js:124 -msgid "" -"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." -msgstr "" -"Security Token Service (STS) is een webdienst waarmee u tijdelijke " -"toegangsgegevens met beperkte rechten aan kunt vragen voor gebruikers van " -"AWS Identity en Access Management (IAM)" - -#: client/src/shared/form-generator.js:1715 -#: client/src/shared/lookup/lookup-modal.directive.js:59 -#: client/src/shared/lookup/lookup-modal.partial.html:20 -msgid "Select" -msgstr "Selecteren" - -#: client/src/shared/instance-groups-multiselect/instance-groups-modal/instance-groups-modal.partial.html:5 -msgid "Select Instance Groups" -msgstr "Instantiegroepen selecteren" - -#: client/src/job-submission/job-submission.directive.js:65 -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js:66 -msgid "Select a credential" -msgstr "Toegangsgegevens selecteren" - -#: client/src/access/add-rbac-user-team/rbac-user-team.controller.js:68 -msgid "Select a role" -msgstr "Rol selecteren" - -#: client/features/users/tokens/tokens.strings.js:29 -msgid "Select a scope" -msgstr "Een bereik kiezen" - -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:53 -msgid "" -"Select an inventory source by clicking the check box beside it. The " -"inventory source can be a single group or a selection of multiple groups." -msgstr "" -"Selecteer een inventarisbron door het selectievak ernaast aan te klikken. De" -" inventarisbron kan een enkele groep of een selectie van meerdere groepen " -"zijn." - -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:53 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:98 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:53 -msgid "" -"Select an inventory source by clicking the check box beside it. The " -"inventory source can be a single group or host, a selection of multiple " -"hosts, or a selection of multiple groups." -msgstr "" -"Selecteer een inventarisbron door het selectievak ernaast aan te klikken. De" -" inventarisbron kan een enkele groep of host, een selectie van meerdere " -"hosts of een selectie van meerdere groepen zijn." - -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:111 -msgid "" -"Select an inventory source by clicking the check box beside it. The " -"inventory source can be a single host or a selection of multiple hosts." -msgstr "" -"Selecteer een inventarisbron door het selectievak ernaast aan te klikken. De" -" inventarisbron kan een enkele host of een selectie van meerdere hosts zijn." - -#: client/src/configuration/jobs-form/configuration-jobs.controller.js:109 -#: client/src/configuration/jobs-form/configuration-jobs.controller.js:134 -#: client/src/configuration/ui-form/configuration-ui.controller.js:95 -msgid "Select commands" -msgstr "Commando's selecteren" - -#: client/src/templates/job_templates/job-template.form.js:132 -msgid "" -"Select credentials that allow Tower to access 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 waarmee Tower toegang kan 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." - -#: client/src/projects/projects.form.js:99 -msgid "" -"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." -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." - -#: client/src/configuration/auth-form/configuration-auth.controller.js:370 -#: client/src/configuration/auth-form/configuration-auth.controller.js:389 -msgid "Select group types" -msgstr "Selecteer soorten groepen" - -#: client/src/access/rbac-multiselect/rbac-multiselect-role.directive.js:24 -msgid "Select roles" -msgstr "Selecteer rollen" - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:56 -msgid "Select the Instance Groups for this Inventory to run on." -msgstr "" -"Selecteer de instantiegroepen waar deze inventaris op uitgevoerd wordt." - -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:63 -msgid "" -"Select the Instance Groups for this Inventory to run on. Refer to the " -"Ansible Tower documentation for more detail." -msgstr "" -"Selecteer de instantiegroepen waar deze inventaris op uitgevoerd wordt. " -"Raadpleeg de documentatie van Ansible Tower voor meer informatie." - -#: client/src/templates/job_templates/job-template.form.js:254 -msgid "Select the Instance Groups for this Job Template to run on." -msgstr "" -"Selecteer de instantiegroepen waar deze taaksjabloon op uitgevoerd wordt." - -#: client/src/organizations/organizations.form.js:40 -msgid "Select the Instance Groups for this Organization to run on." -msgstr "" -"Selecteer de instantiegroepen waar de organisatie op uitgevoerd wordt." - -#: client/src/templates/job_templates/job-template.form.js:244 -msgid "" -"Select the custom Python virtual environment for this job template to run " -"on." -msgstr "" -"Selecteer de aangepaste virtuele Python-omgeving waarop u deze taaksjabloon " -"uit wilt voeren." - -#: client/src/organizations/organizations.form.js:51 -msgid "" -"Select the custom Python virtual environment for this organization to run " -"on." -msgstr "" -"Selecteer de aangepaste virtuele Python-omgeving waarop u deze organisatie " -"uit wilt voeren." - -#: client/src/projects/projects.form.js:211 -msgid "" -"Select the custom Python virtual environment for this project to run on." -msgstr "" -"Selecteer de aangepaste virtuele Python-omgeving waarop dit project " -"uitgevoerd moet worden." - -#: client/src/templates/job_templates/job-template.form.js:79 -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." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:122 -msgid "" -"Select the inventory file to be synced by this source. You can select from " -"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." - -#: client/src/templates/job_templates/job-template.form.js:114 -msgid "Select the playbook to be executed by this job." -msgstr "Selecteer het draaiboek dat uitgevoerd moet worden door deze taak." - -#: client/src/templates/job_templates/job-template.form.js:99 -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." - -#: client/src/configuration/system-form/configuration-system.controller.js:197 -msgid "Select types" -msgstr "Selecteer soorten" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:224 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:238 -msgid "Select which groups to create automatically." -msgstr "Selecteer welke groepen automatisch aangemaakt moeten worden." - -#: client/src/notifications/notificationTemplates.form.js:110 -msgid "Sender Email" -msgstr "Afzender e-mail" - -#: client/src/credentials/factories/become-method-change.factory.js:24 -#: client/src/credentials/factories/kind-change.factory.js:81 -msgid "Service Account Email Address" -msgstr "E-mailadres service-account" - -#: client/features/credentials/credentials.strings.js:21 -msgid "Service Account JSON File" -msgstr "JSON-bestand service-account" - -#: client/lib/components/components.strings.js:86 -msgid "Settings" -msgstr "Instellingen" - -#: client/src/job-submission/job-submission.partial.html:108 -#: client/src/job-submission/job-submission.partial.html:122 -#: client/src/job-submission/job-submission.partial.html:136 -#: client/src/job-submission/job-submission.partial.html:150 -#: client/src/job-submission/job-submission.partial.html:299 -#: client/src/shared/form-generator.js:883 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:24 -msgid "Show" -msgstr "Tonen" - -#: client/features/templates/templates.strings.js:46 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:115 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:118 -#: client/src/templates/job_templates/job-template.form.js:261 -#: client/src/templates/job_templates/job-template.form.js:264 -msgid "Show Changes" -msgstr "Wijzigingen tonen" - -#: client/features/output/output.strings.js:38 -msgid "Show Less" -msgstr "Minder tonen" - -#: client/features/output/output.strings.js:39 -msgid "Show More" -msgstr "Meer weergeven" - -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:33 -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:44 -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:55 -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:76 -msgid "Sign in with %s" -msgstr "Aanmelden met %s" - -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:63 -msgid "Sign in with %s Organizations" -msgstr "Aanmelden met %s organisaties" - -#: client/src/login/loginModal/thirdPartySignOn/thirdPartySignOn.service.js:61 -msgid "Sign in with %s Teams" -msgstr "Aanmelden met %s teams" - -#: client/features/output/output.strings.js:67 -#: client/features/templates/templates.strings.js:47 -#: client/src/job-submission/job-submission.partial.html:245 -#: client/src/templates/job_templates/job-template.form.js:207 -#: client/src/templates/job_templates/job-template.form.js:214 -msgid "Skip Tags" -msgstr "Tags overslaan" - -#: client/src/templates/job_templates/job-template.form.js:213 -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 Ansible Tower 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 van Ansible " -"Tower voor meer informatie over het gebruik van tags." - -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:44 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:48 -msgid "Smart Host Filter" -msgstr "Smart-hostfilter" - -#: client/src/inventories-hosts/inventories/inventory.list.js:86 -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:76 -#: client/src/organizations/linkout/controllers/organizations-inventories.controller.js:70 -#: client/src/shared/form-generator.js:1476 -msgid "Smart Inventory" -msgstr "Smart-inventaris" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:44 -msgid "Solvable With Playbook" -msgstr "Op te lossen met draaiboek" - -#: client/features/output/output.strings.js:68 -#: client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.directive.js:57 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:64 -msgid "Source" -msgstr "Bron" - -#: client/src/credentials/credentials.form.js:75 -msgid "Source Control" -msgstr "Broncontrole" - -#: client/features/output/output.strings.js:69 -msgid "Source Credential" -msgstr "Toegangsgegevens bron" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:47 -#: client/src/projects/projects.form.js:26 -msgid "Source Details" -msgstr "Broninformatie" - -#: client/src/notifications/notificationTemplates.form.js:192 -#: client/src/notifications/notificationTemplates.form.js:193 -msgid "Source Phone Number" -msgstr "Brontelefoonnummer" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:136 -msgid "Source Regions" -msgstr "Bronregio's" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:209 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:216 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:233 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:240 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:257 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:264 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:274 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:281 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:291 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:298 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:308 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:315 -msgid "Source Variables" -msgstr "Bronvariabelen" - -#: client/src/partials/logviewer.html:9 -msgid "Source Vars" -msgstr "Bronvariabelen" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:34 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:167 -msgid "Sources" -msgstr "Bronnen" - -#: client/src/notifications/notificationTemplates.form.js:330 -msgid "" -"Specify HTTP Headers in JSON format. Refer to the Ansible Tower " -"documentation for example syntax." -msgstr "" -"Specificeer HTTP-koppen in JSON-formaat. Raadpleeg de documentatie van " -"Ansible Tower voor voorbeeldsyntaxis." - -#: client/src/credentials/credentials.form.js:285 -msgid "" -"Specify a method for %s operations. This is equivalent to specifying the %s " -"parameter, where %s could be %s" -msgstr "" -"Specificeer een methode voor %s-operaties. Dit staat gelijk aan het " -"specificeren van de %s-parameter, waar %s %s kan zijn" - -#: client/src/notifications/notificationTemplates.form.js:478 -msgid "" -"Specify a notification color. Acceptable colors are hex color code (example:" -" #3af or #789abc) ." -msgstr "" -"Kies een berichtkleur. Mogelijke kleuren zijn kleuren uit de hexidecimale " -"kleurencode (bijvoorbeeld: #3af of #789abc)." - -#: client/src/notifications/notificationTemplates.form.js:292 -msgid "" -"Specify a notification color. Acceptable colors are: yellow, green, red " -"purple, gray or random." -msgstr "" -"Kies een berichtkleur. Mogelijke kleuren zijn: geel, groen, rood, paars, " -"grijs of willekeurig." - -#: client/features/users/tokens/tokens.strings.js:20 -msgid "Specify a scope for the token's access" -msgstr "Geef een bereik op voor de toegang van de token" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:253 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:270 -msgid "" -"Specify which groups to create automatically. Group names will be created " -"similar to the options selected. If blank, all groups above are created. " -"Refer to Ansible Tower documentation for more detail." -msgstr "" -"Specificeer welke groepen automatisch aangemaakt moeten worden. De namen van" -" de groepen zullen vergelijkbaar zijn met de geselecteerde opties. Indien " -"dit leeg gelaten wordt, zullen alle bovenstaande groepen aangemaakt worden. " -"Raadpleeg de documentatie van Ansible Tower voor meer informatie." - -#: client/features/output/output.strings.js:108 -msgid "Standard Error" -msgstr "Standaardfout" - -#: client/features/output/output.strings.js:107 -#: client/src/partials/logviewer.html:5 -msgid "Standard Out" -msgstr "Standaardoutput" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:41 -#: client/src/scheduler/scheduler.strings.js:23 -msgid "Start Date" -msgstr "Startdatum" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:56 -#: client/src/scheduler/scheduler.strings.js:24 -msgid "Start Time" -msgstr "Starttijd" - -#: client/lib/components/components.strings.js:104 -msgid "Start a job using this template" -msgstr "Start een taak met deze sjabloon" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:6 -msgid "Start sync process" -msgstr "Start het synchronisatieproces" - -#: client/features/jobs/jobs.strings.js:9 -#: client/features/output/output.strings.js:70 -#: client/src/workflow-results/workflow-results.controller.js:49 -msgid "Started" -msgstr "Gestart" - -#: client/features/output/output.strings.js:71 -#: client/src/inventories-hosts/inventories/list/host-summary-popover/host-summary-popover.directive.js:53 -#: client/src/inventories-hosts/inventories/list/source-summary-popover/source-summary-popover.directive.js:55 -#: client/src/inventories-hosts/shared/factories/set-status.factory.js:43 -#: client/src/notifications/notification-templates-list/list.controller.js:71 -#: client/src/partials/logviewer.html:4 -#: client/src/workflow-results/workflow-results.controller.js:52 -msgid "Status" -msgstr "Status" - -#: client/src/configuration/auth-form/configuration-auth.partial.html:4 -msgid "Sub Category" -msgstr "Subcategorie" - -#: client/src/license/license.partial.html:139 -msgid "Submit" -msgstr "Indienen" - -#: client/src/license/license.partial.html:27 -msgid "Subscription" -msgstr "Abonnement" - -#: client/src/credentials/credentials.form.js:151 -#: client/src/credentials/credentials.form.js:162 -msgid "Subscription ID" -msgstr "Abonnement-ID" - -#: client/src/credentials/credentials.form.js:161 -msgid "Subscription ID is an Azure construct, which is mapped to a username." -msgstr "" -"Abonnement-ID is een concept van Azure en is gelinkt aan een gebruikersnaam." - -#: client/src/notifications/notifications.list.js:39 -msgid "Success" -msgstr "Geslaagd" - -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:77 -msgid "Successful" -msgstr "Geslaagd" - -#: client/src/scheduler/scheduler.strings.js:36 -msgid "Sun" -msgstr "Zon" - -#: client/features/templates/templates.strings.js:28 -#: client/src/job-submission/job-submission.partial.html:20 -msgid "Survey" -msgstr "Vragenlijst" - -#: client/src/templates/job_templates/edit-job-template/job-template-edit.controller.js:62 -#: client/src/templates/workflows/edit-workflow/workflow-edit.controller.js:262 -msgid "" -"Surveys allow users to be prompted at job launch with a series of questions " -"related to the job. This allows for variables to be defined that affect the " -"playbook run at time of launch." -msgstr "" -"Met vragenlijsten kunnen gebruikers bij het starten van een taak een melding" -" krijgen met een lijst vragen die bij de taak horen. Zo kunnen variabelen " -"die van invloed zijn op de uitvoering van het draaiboek bepaald worden bij " -"het opstarten." - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:58 -msgid "Sync all inventory sources" -msgstr "Alle inventarisbronnen synchroniseren" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:29 -msgid "Sync canceled. Click to view log." -msgstr "Synchronisatie geannuleerd. Klik om logboek te bekijken." - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:35 -msgid "Sync completed. Click to view log." -msgstr "Synchronisatie voltooid. Klik om logboek te bekijken." - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:32 -msgid "Sync failed. Click to view log." -msgstr "Synchronisatie mislukt. Klik om logboek te bekijken." - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:17 -msgid "Sync not performed. Click" -msgstr "Synchronisatie niet uitgevoerd. Klik" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:38 -msgid "Sync pending." -msgstr "Synchronisatie in afwachting." - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:45 -msgid "Sync running" -msgstr "Synchronisatie in uitvoering" - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:46 -msgid "Sync running. Click to view log." -msgstr "Synchronisatie in uitvoering. Klik om logboek te bekijken." - -#: client/src/configuration/configuration.partial.html:29 -msgid "System" -msgstr "Systeem" - -#: client/src/users/add/users-add.controller.js:12 -#: client/src/users/edit/users-edit.controller.js:12 -#: client/src/users/list/users-list.controller.js:12 -msgid "System Administrator" -msgstr "Systeembeheerder" - -#: client/src/users/add/users-add.controller.js:11 -#: client/src/users/edit/users-edit.controller.js:11 -#: client/src/users/list/users-list.controller.js:11 -msgid "System Auditor" -msgstr "Systeemcontroleur" - -#: client/src/configuration/configuration.partial.html:3 -msgid "System auditors have read-only permissions in this section." -msgstr "Systeemcontroleurs hebben een alleen-lezen-machtiging in deze sectie." - -#: client/src/configuration/auth-form/configuration-auth.controller.js:160 -msgid "TACACS+" -msgstr "TACACS+" - -#: client/features/output/output.strings.js:104 -msgid "TASK" -msgstr "TAAK" - -#: client/src/activity-stream/get-target-title.factory.js:23 -#: client/src/organizations/linkout/organizations-linkout.route.js:98 -#: client/src/organizations/list/organizations-list.controller.js:61 -#: client/src/teams/main.js:65 client/src/teams/teams.list.js:14 -#: client/src/teams/teams.list.js:15 -msgid "TEAMS" -msgstr "TEAMS" - -#: client/features/templates/routes/templatesList.route.js:12 -#: client/features/templates/templates.strings.js:12 -#: client/features/templates/templates.strings.js:8 -#: client/src/activity-stream/get-target-title.factory.js:44 -#: client/src/templates/templates.list.js:15 -#: client/src/templates/templates.list.js:16 -msgid "TEMPLATES" -msgstr "SJABLONEN" - -#: client/src/instance-groups/instance-groups.list.js:8 -msgid "THERE ARE CURRENTLY NO INSTANCE GROUPS DEFINED" -msgstr "ER ZIJN OP DIT MOMENT GEEN INSTANTIEGROEPEN GEDEFINIEERD" - -#: client/src/home/dashboard/graphs/job-status/job-status-graph.directive.js:105 -msgid "TIME" -msgstr "TIJD" - -#: client/features/users/tokens/tokens.strings.js:22 -msgid "TOKEN" -msgstr "TOKEN" - -#: client/features/users/tokens/tokens.strings.js:21 -msgid "TOKEN INFORMATION" -msgstr "TOKENINFORMATIE" - -#: client/features/applications/applications.strings.js:11 -#: client/features/users/tokens/tokens.strings.js:10 -#: client/features/users/tokens/tokens.strings.js:8 -#: client/features/users/tokens/users-tokens-list.route.js:24 -#: client/src/activity-stream/get-target-title.factory.js:50 -msgid "TOKENS" -msgstr "TOKENS" - -#: client/features/templates/templates.strings.js:106 -msgid "TOTAL TEMPLATES" -msgstr "TOTALE SJABLONEN" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:235 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:249 -msgid "Tag None:" -msgstr "Tag geen:" - -#: client/src/templates/job_templates/job-template.form.js:196 -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 Ansible Tower 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 van Ansible Tower " -"voor meer informatie over het gebruik van tags." - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:233 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:247 -msgid "Tags:" -msgstr "Tags:" - -#: client/src/notifications/notificationTemplates.form.js:309 -#: client/src/notifications/notificationTemplates.form.js:337 -#: client/src/notifications/notificationTemplates.form.js:376 -msgid "Target URL" -msgstr "Doel-URL" - -#: client/features/output/output.strings.js:92 -msgid "Tasks" -msgstr "Taken" - -#: client/features/credentials/legacy.credentials.js:91 -#: client/src/credentials/credentials.form.js:468 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:136 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:138 -#: client/src/projects/projects.form.js:276 -#: client/src/templates/workflows.form.js:169 -msgid "Team Roles" -msgstr "Teamrollen" - -#: client/lib/components/components.strings.js:79 -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:40 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:36 -#: client/src/organizations/linkout/organizations-linkout.route.js:109 -#: client/src/organizations/linkout/organizations-linkout.route.js:111 -#: client/src/shared/stateDefinitions.factory.js:426 -#: client/src/users/users.form.js:161 -msgid "Teams" -msgstr "Teams" - -#: client/src/templates/templates.list.js:14 -#: client/src/workflow-results/workflow-results.controller.js:47 -msgid "Template" -msgstr "Sjabloon" - -#: client/features/templates/templates.strings.js:67 -msgid "Template parameter is missing." -msgstr "Sjabloonparameter ontbreekt." - -#: client/lib/components/components.strings.js:76 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:37 -msgid "Templates" -msgstr "Sjablonen" - -#: client/src/credentials/credentials.form.js:337 -msgid "Tenant ID" -msgstr "Huurder-ID" - -#: client/src/configuration/system-form/sub-forms/system-logging.form.js:79 -msgid "Test" -msgstr "Test" - -#: client/src/notifications/notificationTemplates.list.js:77 -msgid "Test notification" -msgstr "Testbericht" - -#: client/src/templates/survey-maker/surveys/init.factory.js:13 -msgid "Text" -msgstr "Tekst" - -#: client/src/templates/survey-maker/surveys/init.factory.js:14 -msgid "Textarea" -msgstr "Tekstgebied" - -#: client/src/shared/form-generator.js:1406 -#: client/src/shared/form-generator.js:1412 -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." - -#: client/lib/components/components.strings.js:47 -msgid "That value was not found. Please enter or select a valid value." -msgstr "" -"Die waarde kon niet gevonden worden. Voer een geldige waarde in of selecteer" -" er een." - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:66 -msgid "The Insights Credential for {{inventory.name}} was not found." -msgstr "" -"De Insights-toegangsgegevens voor {{inventory.name}} konden niet gevonden " -"worden." - -#: client/src/credentials/factories/become-method-change.factory.js:32 -#: client/src/credentials/factories/kind-change.factory.js:89 -msgid "" -"The Project ID is the GCE assigned identification. It is constructed as two " -"words followed by a three digit number. Such as:" -msgstr "" -"Het project-ID is de toegewezen GCE-identificatie. Dit bestaat uit twee " -"woorden, gevolgd door drie getallen, zoals:" - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:204 -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:207 -msgid "The Project selected has a status of" -msgstr "Het geselecteerde project heeft de status" - -#: client/src/projects/edit/projects-edit.controller.js:341 -msgid "The SCM update process is running." -msgstr "Het SCM-updateproces is nu in uitvoering." - -#: client/src/scheduler/scheduler.strings.js:32 -msgid "The day must be between 1 and 31." -msgstr "De dag moet tussen 1 en 31 vallen" - -#: client/src/credentials/credentials.form.js:190 -msgid "" -"The email address assigned to the Google Compute Engine %sservice account." -msgstr "" -"Het e-mailadres dat toegewezen is aan het Google Compute Engine " -"%sserviceaccount." - -#: client/features/output/output.strings.js:12 -msgid "The host status bar will update when the job is complete." -msgstr "" -"De statusbalk van de host wordt bijgewerkt wanneer de taak voltooid is." - -#: client/src/credentials/factories/become-method-change.factory.js:62 -#: client/src/credentials/factories/kind-change.factory.js:119 -msgid "The host to authenticate with." -msgstr "De host waarmee geauthenticeerd moet worden." - -#: client/src/credentials/factories/kind-change.factory.js:60 -msgid "The host value" -msgstr "De hostwaarde" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:154 -msgid "" -"The inventory will be in a pending status until the final delete is " -"processed." -msgstr "" -"De inventaris zal de status 'in afwachting' hebben totdat het laatste " -"verwijderproces verwerkt is." - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:105 -msgid "" -"The number of parallel or simultaneous processes to use while executing the " -"playbook. Inputting no value will use the default value from the %sansible " -"configuration file%s." -msgstr "" -"Het aantal parallelle of gelijktijdige processen dat gebruikt wordt bij het " -"uitvoeren van het draaiboeken. Als u geen waarde invoert, wordt de " -"standaardwaarde van de %sgeregistreerde Ansible-configuratie%s gebruikt." - -#: client/src/templates/job_templates/job-template.form.js:151 -msgid "" -"The number of parallel or simultaneous processes to use while executing the " -"playbook. Value defaults to 0. Refer to the Ansible documentation for " -"details about the configuration file." -msgstr "" -"Het aantal parallelle of gelijktijdige processen dat gebruikt wordt bij het " -"uitvoeren van het draaiboeken. Waarde is standaard 0. Raadpleeg de " -"documentatie van Ansible voor informatie over het configuratiebestand." - -#: client/src/credentials/factories/kind-change.factory.js:59 -msgid "The project value" -msgstr "De projectwaarde" - -#: client/src/scheduler/scheduler.strings.js:49 -msgid "" -"The scheduler options are invalid, incomplete, or a date is in the past." -msgstr "" -"De opties voor de planner zijn ongeldig, niet compleet, of een datum is al " -"voorbij. " - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:210 -msgid "The selected project has a status of" -msgstr "Het geselecteerde project heeft de status" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:199 -msgid "" -"The selected project is not configured for SCM. To configure for SCM, edit " -"the project and provide SCM settings and then run an update." -msgstr "" -"Het geselecteerde project is niet geconfigureerd voor SCM. Om het project te" -" configureren voor SCM, dient u het te wijzigen en SCM-instellingen op te " -"geven, om vervolgens een update uit te voeren." - -#: client/src/projects/list/projects-list.controller.js:186 -msgid "" -"The selected project is not configured for SCM. To configure for SCM, edit " -"the project and provide SCM settings, and then run an update." -msgstr "" -"Het geselecteerde project is niet geconfigureerd voor SCM. Om het project te" -" configureren voor SCM, dient u het te wijzigen en SCM-instellingen op te " -"geven, om vervolgens een update uit te voeren." - -#: client/src/templates/survey-maker/shared/question-definition.form.js:52 -msgid "" -"The suggested format for variable names is lowercase and underscore-" -"separated (for example, foo_bar, user_id, host_name, 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." - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:124 -#: client/src/scheduler/scheduler.strings.js:25 -msgid "The time must be in HH24:MM:SS format." -msgstr "De tijd moet weergegeven worden in de indeling HH24:MM:SS." - -#: client/lib/services/base-string.service.js:79 -msgid "The {{ resourceType }} is currently being used by other resources." -msgstr "" -"De {{ resourceType }} wordt op dit moment gebruikt door andere hulpbronnen." - -#: client/src/activity-stream/streams.list.js:17 -msgid "There are no events to display at this time" -msgstr "Er zijn op dit moment geen evenementen om weer te geven" - -#: client/features/jobs/jobs.strings.js:17 -msgid "There are no running jobs." -msgstr "Er worden op dit moment geen taken uitgevoerd." - -#: client/src/projects/list/projects-list.controller.js:150 -msgid "" -"There is no SCM update information available for this project. An update has" -" not yet been completed. If you have not already done so, start an update " -"for this project." -msgstr "" -"Er is geen SCM-update-informatie beschikbaar voor dit project. Er is nog " -"geen update voltooid. Start een update voor dit project, indien u dit nog " -"niet gedaan heeft." - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:191 -msgid "" -"There is no SCM update information available for this project. An update has" -" not yet been completed. If you have not already done so, start an update " -"for this project." -msgstr "" -"Er is geen SCM-update-informatie beschikbaar voor dit project. Er is nog " -"geen update voltooid. Start een update voor dit project, indien u dit nog " -"niet gedaan heeft." - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:141 -msgid "There was an error deleting inventory source groups. Returned status:" -msgstr "" -"Er is iets misgegaan bij het verwijderen van de inventarisbrongroepen. " -"Status:" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:131 -msgid "There was an error deleting inventory source hosts. Returned status:" -msgstr "" -"Er is iets misgegaan bij het verwijderen van de inventarisbrongroepen. " -"Status:" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.controller.js:168 -msgid "There was an error deleting inventory source. Returned status:" -msgstr "" -"Er is iets misgegaan bij het verwijderen van de inventarisbron. Status:" - -#: client/src/configuration/configuration.controller.js:142 -msgid "There was an error getting config values:" -msgstr "Er is een fout opgetreden bij het ophalen van de configuratiewaarden:" - -#: client/src/configuration/configuration.controller.js:415 -msgid "There was an error resetting value. Returned status:" -msgstr "Er was een fout bij het resetten van de waarde. Teruggegeven status:" - -#: client/src/configuration/configuration.controller.js:607 -msgid "There was an error resetting values. Returned status:" -msgstr "Er was een fout bij het resetten van de waardes. Teruggegeven status:" - -#: client/src/configuration/system-form/configuration-system.controller.js:232 -msgid "There was an error testing the log aggregator. Returned status:" -msgstr "" -"Er was een fout bij het testen van de log aggregator. Teruggegeven status:" - -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:29 -msgid "" -"These are the modules that {{BRAND_NAME}} supports running commands against." -msgstr "" -"Dit zijn de modules waar {{BRAND_NAME}} commando's tegen kan uitvoeren." - -#: client/features/templates/templates.strings.js:94 -msgid "" -"This Job Template has a credential that requires a password. Credentials " -"requiring passwords on launch are not permitted on workflow nodes." -msgstr "" -"Deze taaksjabloon heeft toegangsgegevens waar een wachtwoord bijhoort. " -"Toegangsgegevens die wachtwoordgegevens nodig hebben bij het opstarten zijn " -"niet toegestaan op workflowknooppunten." - -#: client/src/scheduler/scheduler.strings.js:59 -msgid "" -"This Job Template has a default credential that requires a password before " -"launch. Adding or editing schedules is prohibited while this credential is " -"selected. To add or edit a schedule, credentials that require a password " -"must be removed from the Job Template." -msgstr "" -"Deze taaksjabloon heeft standaardtoegangsgegevens waar een wachtwoord voor " -"nodig is bij het opstarten. Zo lang deze toegangsgegevens geselecteerd zijn," -" is het niet toegestaan schema's toe te voegen of te wijzigen. Om een schema" -" toe te voegen of te wijzigen moeten de toegangsgegevens waar een wachtwoord" -" voor nodig is, eerst verwijderd worden uit de taaksjabloon." - -#: client/features/templates/templates.strings.js:93 -msgid "" -"This Job Template is missing a default inventory or project. This must be " -"addressed in the Job Template form before this node can be saved." -msgstr "" -"Er ontbreekt een standaardinventaris of -project uit deze taaksjabloon. Dit " -"moet behandeld worden in het formulier voor de taaksjabloon voordat dit " -"knooppunt opgeslagen kan worden." - -#: client/src/credential-types/credential-types.strings.js:8 -msgid "" -"This credential type is currently being used by one or more credentials. " -"Credentials that use this credential type must be deleted before the " -"credential type can be deleted." -msgstr "" -"Deze soort toegangsgegevens wordt op dit moment door één of meerdere " -"toegangsgegevens gebruikt. Toegangsgegevens die deze soort gebruiken moeten " -"verwijderd worden voordat de soort verwijderd kan worden." - -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:24 -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:25 -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:26 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:24 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:25 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:26 -msgid "This group contains" -msgstr "Deze groep bevat" - -#: client/src/management-jobs/scheduler/schedulerForm.partial.html:168 -msgid "This is not a valid number." -msgstr "Dit is geen geldig nummer." - -#: client/src/credentials/factories/become-method-change.factory.js:59 -#: client/src/credentials/factories/kind-change.factory.js:116 -msgid "" -"This is the tenant name. This value is usually the same as the username." -msgstr "" -"Dit is de naam van de huurder. Deze waarde is vaak gelijk aan de " -"gebruikersnaam." - -#: client/features/templates/templates.strings.js:63 -msgid "" -"This job template has a default {{typeLabel}} credential which must be " -"included or replaced before proceeding." -msgstr "" -"Deze taaksjabloon heeft een standaard {{typeLabel}}-toegangsgegevens die " -"bijgevoegd of vervangen moet worden voordat u verder kunt gaan." - -#: client/src/organizations/linkout/organizations-linkout.route.js:158 -msgid "This list is populated by inventories added from the" -msgstr "Deze lijst is gevuld met inventarissen die toegevoegd zijn vanuit de" - -#: client/src/notifications/notifications.list.js:21 -msgid "" -"This list is populated by notification templates added from the " -"%sNotifications%s section" -msgstr "" -"Deze lijst is gevuld met berichtsjablonen die toegevoegd zijn vanuit de " -"sectie %sBerichten%s" - -#: client/src/organizations/linkout/organizations-linkout.route.js:209 -msgid "This list is populated by projects added from the" -msgstr "Deze lijst is gevuld met projecten die toegevoegd zijn vanuit de" - -#: client/src/organizations/linkout/organizations-linkout.route.js:111 -msgid "This list is populated by teams added from the" -msgstr "Deze lijst is gevuld met teams die toegevoegd zijn vanuit de" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:4 -msgid "" -"This machine has not checked in with Insights in {{last_check_in}} hours" -msgstr "" -"Deze machine heeft {{last_check_in}} uur geleden voor het laatst ingecheckt " -"bij Insights" - -#: client/src/shared/form-generator.js:753 -msgid "" -"This setting has been set manually in a settings file and is now disabled." -msgstr "" -"Deze instelling is handmatig gekozen in een instellingenbestand en is nu " -"uitgeschakeld." - -#: client/src/users/users.form.js:166 -msgid "This user is not a member of any teams" -msgstr "Deze gebruiker is niet lid van een team" - -#: client/src/shared/form-generator.js:863 -#: client/src/shared/form-generator.js:958 -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." - -#: client/src/configuration/configuration.controller.js:632 -msgid "" -"This will reset all configuration values to their factory defaults. Are you " -"sure you want to proceed?" -msgstr "" -"Op deze manier worden alle configuratiewaarden gereset naar de " -"fabrieksinstellingen. Weet u zeker dat u verder wilt gaan?" - -#: client/src/scheduler/scheduler.strings.js:40 -msgid "Thu" -msgstr "Do" - -#: client/src/activity-stream/streams.list.js:25 -#: client/src/home/dashboard/lists/jobs/jobs-list.partial.html:14 -#: client/src/notifications/notification-templates-list/list.controller.js:72 -msgid "Time" -msgstr "Tijd" - -#: client/src/license/license.partial.html:45 -msgid "Time Remaining" -msgstr "Tijd over" - -#: client/src/projects/projects.form.js:197 -msgid "" -"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." -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." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:411 -msgid "" -"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." -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." - -#: client/src/credentials/credentials.form.js:125 -msgid "" -"To learn more about the IAM STS Token, refer to the %sAmazon " -"documentation%s." -msgstr "" -"Raadpleeg de %sAmazondocumentatie%s voor meer informatie over de IAM STS-" -"token." - -#: client/src/shared/form-generator.js:888 -msgid "Toggle the display of plaintext." -msgstr "Tekst tonen/verbergen" - -#: client/src/notifications/shared/type-change.service.js:36 -#: client/src/notifications/shared/type-change.service.js:42 -msgid "Token" -msgstr "Token" - -#: client/features/applications/applications.strings.js:16 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:25 -#: client/src/users/users.form.js:235 -msgid "Tokens" -msgstr "Tokens" - -#: client/src/inventories-hosts/inventories/insights/insights.partial.html:10 -msgid "Total Issues" -msgstr "Totale problemen" - -#: client/src/instance-groups/instance-groups.strings.js:19 -#: client/src/workflow-results/workflow-results.controller.js:60 -msgid "Total Jobs" -msgstr "Totale taken" - -#: client/src/partials/logviewer.html:6 -msgid "Traceback" -msgstr "Traceback" - -#: client/src/scheduler/scheduler.strings.js:38 -msgid "Tue" -msgstr "Di" - -#: client/src/credentials/credentials.form.js:60 -#: client/src/credentials/credentials.form.js:84 -#: client/src/inventories-hosts/inventories/inventory.list.js:56 -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:41 -#: client/src/notifications/notificationTemplates.form.js:54 -#: client/src/notifications/notificationTemplates.list.js:39 -#: client/src/notifications/notifications.list.js:32 -#: client/src/projects/projects.list.js:44 -#: client/src/scheduler/scheduled-jobs.list.js:42 -#: client/src/teams/teams.form.js:133 -#: client/src/templates/templates.list.js:31 -#: client/src/users/users.form.js:202 -msgid "Type" -msgstr "Soort" - -#: client/features/credentials/credentials.strings.js:18 -#: client/src/credentials/credentials.form.js:23 -#: client/src/notifications/notificationTemplates.form.js:26 -msgid "Type Details" -msgstr "Soortdetails" - -#: client/src/projects/add/projects-add.controller.js:178 -#: client/src/projects/edit/projects-edit.controller.js:313 -msgid "URL popover text" -msgstr "URL-popovertekst" - -#: client/src/login/loginModal/loginModal.partial.html:49 -msgid "USERNAME" -msgstr "GEBRUIKERSNAAM" - -#: client/src/activity-stream/get-target-title.factory.js:20 -#: client/src/organizations/linkout/organizations-linkout.route.js:43 -#: client/src/organizations/list/organizations-list.controller.js:55 -#: client/src/users/users.list.js:18 client/src/users/users.list.js:19 -#: client/src/users/users.route.js:8 -msgid "USERS" -msgstr "GEBRUIKERS" - -#: client/lib/components/components.strings.js:24 -msgid "Unable to Submit" -msgstr "Kon niet indienen" - -#: client/features/templates/templates.strings.js:84 -msgid "Unable to copy template." -msgstr "Kon sjabloon niet kopiëren." - -#: client/src/instance-groups/instance-groups.strings.js:59 -msgid "Unable to delete instance group." -msgstr "Kon instantiegroep niet verwijderen." - -#: client/features/templates/templates.strings.js:80 -msgid "Unable to delete template." -msgstr "Kon sjabloon niet verwijderen." - -#: client/features/templates/templates.strings.js:82 -msgid "Unable to determine template type." -msgstr "Kon soort sjabloon niet bepalen." - -#: client/features/templates/templates.strings.js:69 -msgid "Unable to determine this template's type while copying." -msgstr "Kon het soort van dit sjabloon niet bepalen tijdens het kopiëren." - -#: client/features/templates/templates.strings.js:70 -msgid "Unable to determine this template's type while deleting." -msgstr "Kon het soort van dit sjabloon niet bepalen tijdens het verwijderen." - -#: client/features/templates/templates.strings.js:71 -msgid "Unable to determine this template's type while editing." -msgstr "Kon het soort van dit sjabloon niet bepalen tijdens het bewerken." - -#: client/features/templates/templates.strings.js:72 -msgid "Unable to determine this template's type while launching." -msgstr "Kon het soort van dit sjabloon niet bepalen tijdens het opstarten." - -#: client/features/templates/templates.strings.js:73 -msgid "Unable to determine this template's type while scheduling." -msgstr "Kon het soort van dit sjabloon niet bepalen tijdens het inplannen." - -#: client/features/templates/templates.strings.js:79 -msgid "Unable to edit template." -msgstr "Kon het sjabloon niet bewerken." - -#: client/src/shared/stateDefinitions.factory.js:231 -msgid "Unable to get resource:" -msgstr "Kon de volgende hulpbron niet ophalen:" - -#: client/features/templates/templates.strings.js:81 -msgid "Unable to launch template." -msgstr "Kon sjabloon niet opstarten." - -#: client/features/templates/templates.strings.js:83 -msgid "Unable to schedule job." -msgstr "Kon taak niet inplannen." - -#: client/src/instance-groups/instance-groups.strings.js:41 -msgid "Unavailable" -msgstr "Niet beschikbaar" - -#: client/src/instance-groups/instance-groups.strings.js:40 -msgid "Unavailable to run jobs." -msgstr "Kan geen taken uitvoeren." - -#: client/lib/components/components.strings.js:26 -msgid "Unexpected Error" -msgstr "Onverwachte fout" - -#: client/lib/components/components.strings.js:25 -msgid "Unexpected server error. View the console for more information" -msgstr "Onverwachte serverfout. Bekijk de console voor meer informatie" - -#: client/lib/components/components.strings.js:38 -msgid "Unsupported display model type" -msgstr "Soort weergavemodel niet ondersteund" - -#: client/lib/components/components.strings.js:30 -msgid "Unsupported input type" -msgstr "Soort input niet ondersteund" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:270 -#: client/src/projects/list/projects-list.controller.js:311 -msgid "Update Not Found" -msgstr "Update niet gevonden" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:345 -msgid "Update Options" -msgstr "Update-opties" - -#: client/src/projects/projects.form.js:178 -msgid "Update Revision on Launch" -msgstr "Herziening updaten bij opstarten" - -#: client/src/projects/factories/get-project-tool-tip.factory.js:30 -msgid "Update canceled. Click for details" -msgstr "Update geannuleerd. Klik voor meer informatie" - -#: client/src/projects/factories/get-project-tool-tip.factory.js:24 -msgid "Update failed. Click for details" -msgstr "Update mislukt. Klik voor meer informatie" - -#: client/src/projects/edit/projects-edit.controller.js:341 -msgid "Update in Progress" -msgstr "Update in uitvoering" - -#: client/src/projects/factories/get-project-tool-tip.factory.js:27 -msgid "Update missing. Click for details" -msgstr "Update ontbreekt. Klik voor meer informatie" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:376 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:381 -msgid "Update on Launch" -msgstr "Update bij opstarten" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:394 -msgid "Update on Project Update" -msgstr "Update voor projectupdate" - -#: client/src/projects/factories/get-project-tool-tip.factory.js:14 -msgid "Update queued. Click for details" -msgstr "Update in de wachtrij gezet. Klik voor meer informatie" - -#: client/src/projects/factories/get-project-tool-tip.factory.js:18 -msgid "Update running. Click for details" -msgstr "Update in uitvoering. Klik voor meer informatie" - -#: client/src/projects/factories/get-project-tool-tip.factory.js:21 -msgid "Update succeeded. Click for details" -msgstr "Update gelukt. Klik voor meer informatie" - -#: client/src/license/license.partial.html:71 -msgid "Upgrade" -msgstr "Upgrade" - -#: client/src/organizations/organizations.form.js:48 -#: client/src/projects/projects.form.js:209 -#: client/src/templates/job_templates/job-template.form.js:241 -msgid "Use Default Environment" -msgstr "Standaardomgeving gebruiken" - -#: client/src/templates/job_templates/job-template.form.js:314 -#: client/src/templates/job_templates/job-template.form.js:319 -msgid "Use Fact Cache" -msgstr "Feitcache gebruiken" - -#: client/src/notifications/notificationTemplates.form.js:466 -msgid "Use SSL" -msgstr "SSL gebruiken" - -#: client/src/notifications/notificationTemplates.form.js:461 -msgid "Use TLS" -msgstr "TLS gebruiken" - -#: client/src/instance-groups/instance-groups.strings.js:20 -#: client/src/instance-groups/instance-groups.strings.js:42 -msgid "Used Capacity" -msgstr "Gebruikte capaciteit" - -#: client/src/credentials/credentials.form.js:76 -msgid "" -"Used to check out and synchronize playbook repositories with a remote source" -" control management system such as Git, Subversion (svn), or Mercurial (hg)." -" These credentials are used by Projects." -msgstr "" -"Wordt gebruikt om draaiboekopslagplaatsen te bekijken en te synchroniseren " -"met een broncontrole-beheersysteem op afstand, zoals Git, Subversion (svn) " -"of Mercurial (hg). Deze toegangsgegevens worden gebruikt door projecten." - -#: client/features/credentials/legacy.credentials.js:80 -#: client/src/credentials/credentials.form.js:457 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:125 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:127 -#: client/src/organizations/organizations.form.js:104 -#: client/src/projects/projects.form.js:265 client/src/teams/teams.form.js:96 -#: client/src/templates/workflows.form.js:158 -msgid "User" -msgstr "Gebruiker" - -#: client/src/configuration/configuration.partial.html:36 -msgid "User Interface" -msgstr "Gebruikersinterface" - -#: client/src/users/users.form.js:97 -msgid "User Type" -msgstr "Soort gebruiker" - -#: client/src/access/rbac-multiselect/permissionsUsers.list.js:36 -#: client/src/credentials/factories/become-method-change.factory.js:17 -#: client/src/credentials/factories/become-method-change.factory.js:38 -#: client/src/credentials/factories/kind-change.factory.js:17 -#: client/src/credentials/factories/kind-change.factory.js:41 -#: client/src/credentials/factories/kind-change.factory.js:74 -#: client/src/credentials/factories/kind-change.factory.js:95 -#: client/src/notifications/notificationTemplates.form.js:348 -#: client/src/notifications/notificationTemplates.form.js:387 -#: client/src/notifications/notificationTemplates.form.js:64 -#: client/src/users/users.form.js:60 client/src/users/users.list.js:29 -msgid "Username" -msgstr "Gebruikersnaam" - -#: client/src/credentials/credentials.form.js:80 -msgid "" -"Usernames, passwords, and access keys for authenticating to the specified " -"cloud or infrastructure provider. These are used for smart inventory sources" -" and for cloud provisioning and deployment in playbook runs." -msgstr "" -"Gebruikersnamen, wachtwoorden en toegangssleutels voor authenticatie bij de " -"gespecificeerde cloud- of infrastructuurprovider. Deze worden gebruikt voor " -"smart-inventarisbronnen, voor cloudvoorziening en om in te zetten bij " -"uitvoeringen van het draaiboek." - -#: client/lib/components/components.strings.js:78 -#: client/src/access/add-rbac-resource/rbac-resource.partial.html:35 -#: client/src/activity-stream/streamDropdownNav/stream-dropdown-nav.directive.js:38 -#: client/src/organizations/organizations.form.js:86 -#: client/src/teams/teams.form.js:78 -msgid "Users" -msgstr "Gebruikers" - -#: client/src/scheduler/schedulerList.controller.js:46 -msgid "" -"Using a credential that requires a password on launch is prohibited when " -"creating a Job Template schedule" -msgstr "" -"Het is niet toegestaan toegangsgegevens die een wachtwoord nodig hebben bij " -"het opstarten te gebruiken bij het aanmaken van een taaksjabloonschema" - -#: client/lib/components/code-mirror/code-mirror.strings.js:9 -msgid "VARIABLES" -msgstr "VARIABELEN" - -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:7 -#: client/src/home/dashboard/lists/jobs/jobs-list.partial.html:7 -msgid "VIEW ALL" -msgstr "ALLE WEERGEVEN" - -#: client/lib/components/components.strings.js:57 -msgid "VIEW LESS" -msgstr "MINDER WEERGEVEN" - -#: client/lib/components/components.strings.js:56 -msgid "VIEW MORE" -msgstr "MEER WEERGEVEN" - -#: client/src/shared/paginate/paginate.partial.html:48 -msgid "VIEW PER PAGE" -msgstr "WEERGEVEN PER PAGINA" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:234 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:248 -msgid "VPC ID:" -msgstr "VPC-ID:" - -#: client/src/license/license.partial.html:10 -msgid "Valid License" -msgstr "Geldige licentie" - -#: client/src/inventories-hosts/hosts/host.form.js:68 -#: client/src/inventories-hosts/inventories/related/groups/groups.form.js:46 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.form.js:47 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:67 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:67 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:63 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:71 -#: client/src/inventories-hosts/inventories/standard-inventory/inventory.form.js:77 -msgid "Variables" -msgstr "Variabelen" - -#: client/src/job-submission/job-submission.partial.html:364 -msgid "Vault" -msgstr "Kluis" - -#: client/src/templates/job_templates/multi-credential/multi-credential-modal.directive.js:25 -msgid "Vault ID" -msgstr "Kluis-id" - -#: client/features/templates/templates.strings.js:44 -#: client/src/credentials/credentials.form.js:391 -#: client/src/job-submission/job-submission.partial.html:146 -msgid "Vault Password" -msgstr "Wachtwoord kluis" - -#: client/features/output/output.strings.js:72 -#: client/features/templates/templates.strings.js:51 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:82 -#: client/src/inventories-hosts/inventories/adhoc/adhoc.form.js:91 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:331 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:338 -#: client/src/job-submission/job-submission.partial.html:183 -#: client/src/templates/job_templates/job-template.form.js:173 -#: client/src/templates/job_templates/job-template.form.js:180 -msgid "Verbosity" -msgstr "Verbositeit" - -#: client/src/license/license.partial.html:15 -msgid "Version" -msgstr "Versie" - -#: client/src/activity-stream/streams.list.js:63 -#: client/src/credential-types/credential-types.list.js:64 -#: client/src/credentials/credentials.list.js:82 -#: client/src/home/dashboard/graphs/dashboard-graphs.partial.html:58 -#: client/src/inventories-hosts/inventories/inventory.list.js:114 -#: client/src/inventory-scripts/inventory-scripts.list.js:70 -#: client/src/notifications/notificationTemplates.list.js:91 -#: client/src/scheduler/schedules.list.js:93 client/src/teams/teams.list.js:64 -#: client/src/templates/templates.list.js:101 -#: client/src/users/users.list.js:70 -msgid "View" -msgstr "weergeven" - -#: client/src/bread-crumb/bread-crumb.directive.js:41 -msgid "View Activity Stream" -msgstr "Activiteitenlogboek weergeven" - -#: client/lib/components/components.strings.js:66 -msgid "View Documentation" -msgstr "Documentatie weergeven" - -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:86 -#: client/src/inventories-hosts/inventory-hosts.strings.js:27 -msgid "View Insights Data" -msgstr "Insights-gegevens weergeven" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:202 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:226 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:250 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:325 -msgid "View JSON examples at" -msgstr "Bekijk JSON-voorbeelden op" - -#: client/src/inventories-hosts/hosts/host.form.js:78 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:77 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:77 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:73 -msgid "View JSON examples at %s" -msgstr "Zie JSON-voorbeelden op %s" - -#: client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.partial.html:13 -msgid "View Less" -msgstr "Minder weergeven" - -#: client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.partial.html:11 -msgid "View More" -msgstr "Meer weergeven" - -#: client/features/output/output.strings.js:27 -msgid "View Project checkout results" -msgstr "Resultaten project-checkout weergeven" - -#: client/src/shared/form-generator.js:1739 -#: client/src/templates/job_templates/job-template.form.js:459 -#: client/src/templates/workflows.form.js:196 -msgid "View Survey" -msgstr "Vragenlijst weergeven" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:203 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:227 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:251 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:326 -msgid "View YAML examples at" -msgstr "Bekijk YAML-voorbeelden op" - -#: client/src/inventories-hosts/hosts/host.form.js:79 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.form.js:78 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.form.js:78 -#: client/src/inventories-hosts/inventories/smart-inventory/smart-inventory.form.js:74 -msgid "View YAML examples at %s" -msgstr "Zie YAML-voorbeelden op %s" - -#: client/src/credentials/credentials.list.js:84 -msgid "View credential" -msgstr "Toegangsgegevens weergeven" - -#: client/src/credential-types/credential-types.list.js:66 -msgid "View credential type" -msgstr "Soort toegangsgegevens weergeven" - -#: client/src/activity-stream/streams.list.js:67 -msgid "View event details" -msgstr "Evenementinformatie weergeven" - -#: client/src/inventories-hosts/inventories/related/groups/groups.list.js:93 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-groups/group-nested-groups.list.js:103 -#: client/src/inventories-hosts/inventories/related/hosts/related/nested-groups/host-nested-groups.list.js:91 -msgid "View group" -msgstr "Groep weergeven" - -#: client/src/inventories-hosts/hosts/host.list.js:89 -#: client/src/inventories-hosts/inventories/related/groups/related/nested-hosts/group-nested-hosts.list.js:79 -#: client/src/inventories-hosts/inventories/related/hosts/related-host.list.js:93 -#: client/src/inventories-hosts/inventory-hosts.strings.js:26 -msgid "View host" -msgstr "Host weergeven" - -#: client/src/inventories-hosts/inventories/inventory.list.js:116 -msgid "View inventory" -msgstr "Inventaris weergeven" - -#: client/src/inventory-scripts/inventory-scripts.list.js:72 -msgid "View inventory script" -msgstr "Inventarisscript weergeven" - -#: client/src/notifications/notificationTemplates.list.js:93 -msgid "View notification" -msgstr "Bericht weergeven" - -#: client/src/scheduler/schedules.list.js:95 -msgid "View schedule" -msgstr "Schema weergeven" - -#: client/src/inventories-hosts/inventories/related/sources/sources.list.js:110 -msgid "View source" -msgstr "Bron weergeven" - -#: client/src/teams/teams.list.js:67 -msgid "View team" -msgstr "Team weergeven" - -#: client/src/templates/templates.list.js:103 -msgid "View template" -msgstr "Sjabloon weergeven" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:246 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:261 -msgid "View the" -msgstr "Bekijk de" - -#: client/features/output/output.strings.js:21 -#: client/lib/components/components.strings.js:61 -msgid "View the Credential" -msgstr "De toegangsgegevens weergeven" - -#: client/features/output/output.strings.js:24 -msgid "View the Inventory" -msgstr "Het inventaris weergeven" - -#: client/features/output/output.strings.js:25 -msgid "View the Job Template" -msgstr "Het taaksjabloon weergeven" - -#: client/features/output/output.strings.js:26 -msgid "View the Project" -msgstr "Het project weergeven" - -#: client/features/output/output.strings.js:28 -msgid "View the Schedule" -msgstr "Het schema weergeven" - -#: client/features/output/output.strings.js:30 -msgid "View the User" -msgstr "De gebruiker weergeven" - -#: client/src/projects/projects.list.js:109 -msgid "View the project" -msgstr "Het project weergeven" - -#: client/src/scheduler/scheduled-jobs.list.js:74 -msgid "View the schedule" -msgstr "Het schema weergeven" - -#: client/features/output/output.strings.js:29 -msgid "View the source Workflow Job" -msgstr "De bron-workflowtaak weergeven" - -#: client/src/users/users.list.js:73 -msgid "View user" -msgstr "Gebruiker weergeven" - -#: client/lib/components/components.strings.js:89 -msgid "Views" -msgstr "Weergaven" - -#: client/src/templates/workflows.form.js:20 -msgid "WORKFLOW" -msgstr "WORKFLOW" - -#: client/features/templates/templates.strings.js:119 -msgid "WORKFLOW VISUALIZER" -msgstr "WORKFLOWWEERGAVE" - -#: client/features/templates/templates.strings.js:105 -#: client/src/scheduler/scheduler.strings.js:58 -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:214 -msgid "Warning" -msgstr "Waarschuwing" - -#: client/src/configuration/auth-form/configuration-auth.controller.js:92 -#: client/src/configuration/configuration.controller.js:229 -#: client/src/configuration/configuration.controller.js:314 -#: client/src/configuration/system-form/configuration-system.controller.js:55 -msgid "Warning: Unsaved Changes" -msgstr "Waarschuwing: niet-opgeslagen wijzigingen" - -#: client/src/scheduler/scheduler.strings.js:39 -msgid "Wed" -msgstr "Woe" - -#: client/src/license/license.partial.html:78 -msgid "" -"Welcome to Ansible Tower! Please complete the steps below to acquire a " -"license." -msgstr "" -"Welkom bij Ansible Tower! Volg de onderstaande stappen op om een licentie te" -" verkrijgen." - -#: client/src/login/loginModal/loginModal.partial.html:17 -msgid "Welcome to Ansible {{BRAND_NAME}}!  Please sign in." -msgstr "Welkom bij Ansible {{BRAND_NAME}}! Meld u eerst aan." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:368 -msgid "" -"When not checked, a merge will be performed, combining local variables with " -"those found on the external source." -msgstr "" -"Als dit vakje niet aangevinkt is, worden lokale variabelen samengevoegd met " -"de variabelen die aangetroffen zijn in de externe bron." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:356 -msgid "" -"When not checked, local child hosts and groups not found on the external " -"source will remain 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." - -#: client/features/jobs/jobs.strings.js:11 -msgid "Workflow Job" -msgstr "Workflowtaak" - -#: client/lib/models/models.strings.js:45 -msgid "Workflow Job Template Nodes" -msgstr "Sjabloonknooppunten workflowtaak" - -#: client/features/templates/templates.strings.js:14 -#: client/src/templates/templates.list.js:66 -msgid "Workflow Template" -msgstr "Workflowsjabloon" - -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:109 -#: client/src/access/add-rbac-user-team/rbac-user-team.partial.html:41 -msgid "Workflow Templates" -msgstr "Workflowsjablonen" - -#: client/src/shared/form-generator.js:1743 -#: client/src/templates/workflows.form.js:222 -msgid "Workflow Visualizer" -msgstr "Workflowweergave" - -#: client/features/users/tokens/tokens.strings.js:31 -msgid "Write" -msgstr "Schrijven" - -#: client/lib/components/code-mirror/code-mirror.strings.js:11 -#: client/lib/services/base-string.service.js:69 -#: client/src/job-submission/job-submission.partial.html:171 -msgid "YAML" -msgstr "YAML" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:200 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:224 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:248 -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:323 -msgid "YAML:" -msgstr "YAML:" - -#: client/lib/services/base-string.service.js:73 -msgid "YES" -msgstr "JA" - -#: client/src/notifications/add/add.controller.js:83 -#: client/src/notifications/edit/edit.controller.js:130 -msgid "Yellow" -msgstr "Geel" - -#: client/src/home/dashboard/lists/job-templates/job-templates-list.partial.html:53 -msgid "" -"You can create a job template here." -msgstr "" -"U kunt hier een taaksjabloon " -"maken." - -#: client/features/templates/templates.strings.js:89 -msgid "" -"You do not have access to all resources used by this workflow. Resources " -"that you don't have access to will not be copied and will result in an " -"incomplete workflow." -msgstr "" -"U heeft geen toegang tot alle hulpbronnen die door deze workflow gebruikt " -"worden. Als u geen toegang heeft tot een hulpbron, wordt deze niet " -"gekopieerd en krijgt u een incomplete workflow." - -#: client/src/projects/edit/projects-edit.controller.js:64 -msgid "You do not have access to view this property" -msgstr "U heeft geen toestemming om dit eigendom weer te geven" - -#: client/src/projects/add/projects-add.controller.js:32 -msgid "You do not have permission to add a project." -msgstr "U heeft geen machtiging om een project toe te voegen." - -#: client/src/users/add/users-add.controller.js:44 -msgid "You do not have permission to add a user." -msgstr "U heeft geen machtiging om een gebruiker toe te voegen." - -#: client/src/access/rbac-multiselect/rbac-multiselect-list.directive.js:174 -msgid "You do not have permission to manage this user" -msgstr "U bent niet gemachtigd deze gebruiker te beheren" - -#: client/features/templates/templates.strings.js:68 -msgid "You do not have permission to perform this action." -msgstr "U bent niet gemachtigd deze actie uit te voeren." - -#: client/src/inventories-hosts/inventory-hosts.strings.js:41 -msgid "You do not have sufficient permissions to edit the host filter." -msgstr "U hebt geen toestemming om het hostfilter te bewerken." - -#: client/src/configuration/auth-form/configuration-auth.controller.js:91 -#: client/src/configuration/configuration.controller.js:228 -#: client/src/configuration/configuration.controller.js:313 -#: client/src/configuration/system-form/configuration-system.controller.js:54 -msgid "" -"You have unsaved changes. Would you like to proceed without" -" saving?" -msgstr "" -"U heeft niet-opgeslagen wijzigingen. Wilt u doorgaan zonder" -" op te slaan?" - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:204 -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:207 -msgid "" -"You must run a successful update before you can select a playbook. You will " -"not be able to save this Job Template without a valid playbook." -msgstr "" -"U dient een succesvolle update uit te voeren voordat u een draaiboek kunt " -"selecteren. U kunt deze taaksjabloon niet opslaan zonder geldig draaiboek." - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:224 -#: client/src/projects/list/projects-list.controller.js:267 -msgid "Your request to cancel the update was submitted to the task manager." -msgstr "" -"Uw verzoek om de update te annuleren is ingediend bij de taakbeheerder." - -#: client/src/login/loginModal/loginModal.partial.html:22 -msgid "Your session timed out due to inactivity. Please sign in." -msgstr "Uw sessie is verlopen vanwege inactiviteit. Meld u opnieuw aan." - -#: client/src/inventories-hosts/inventories/related/groups/list/groups-list.partial.html:24 -#: client/src/job-submission/job-submission.partial.html:317 -#: client/src/shared/form-generator.js:1213 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:42 -msgid "and" -msgstr "en" - -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:245 -#: client/src/organizations/linkout/controllers/organizations-projects.controller.js:270 -msgid "button to view the latest status." -msgstr "knop om de nieuwste status te zien." - -#: client/features/users/tokens/tokens.strings.js:27 -msgid "by" -msgstr "door" - -#: client/src/job-submission/job-submission.partial.html:289 -#: client/src/job-submission/job-submission.partial.html:294 -#: client/src/job-submission/job-submission.partial.html:305 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:14 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:19 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:30 -msgid "characters long." -msgstr "tekens lang." - -#: client/features/output/output.strings.js:79 -#: client/src/shared/smart-search/smart-search.partial.html:50 -msgid "documentation" -msgstr "documentatie" - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:204 -msgid "failed" -msgstr "mislukt" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:247 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:262 -msgid "for a complete list of supported filters." -msgstr "voor een volledige lijst van ondersteunde filters." - -#: client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.directive.js:82 -msgid "from the" -msgstr "van de" - -#: client/src/inventories-hosts/inventories/related/hosts/related-groups-labels/relatedGroupsLabelsList.directive.js:82 -#: client/src/inventories-hosts/inventory-hosts.strings.js:8 -msgid "group" -msgid_plural "groups" -msgstr[0] "groep" -msgstr[1] "groepen" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:26 -msgid "groups" -msgstr "groepen" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:24 -msgid "groups and" -msgstr "groepen en" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:9 -msgid "host" -msgid_plural "hosts" -msgstr[0] "host" -msgstr[1] "hosts" - -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:24 -#: client/src/inventories-hosts/inventories/related/sources/list/sources-list.partial.html:25 -msgid "hosts" -msgstr "hosts" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:65 -msgid "hosts with failures. Click for details." -msgstr "hosts met mislukkingen. Klik voor meer informatie." - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:210 -msgid "missing" -msgstr "ontbreekt" - -#: client/src/access/rbac-multiselect/permissionsTeams.list.js:21 -msgid "name" -msgstr "naam" - -#: client/src/templates/job_templates/add-job-template/job-template-add.controller.js:207 -msgid "never updated" -msgstr "nooit bijgewerkt" - -#: client/src/shared/paginate/paginate.partial.html:34 -msgid "of" -msgstr "van" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:239 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:254 -msgid "of the filters match." -msgstr "van de filters komen overeen." - -#: client/src/scheduler/scheduler.strings.js:34 -msgid "on" -msgstr "op" - -#: client/src/scheduler/scheduler.strings.js:31 -msgid "on day" -msgstr "op dag" - -#: client/src/scheduler/scheduler.strings.js:35 -msgid "on days" -msgstr "op dagen" - -#: client/src/scheduler/scheduler.strings.js:33 -msgid "on the" -msgstr "op de" - -#: client/src/access/rbac-multiselect/permissionsTeams.list.js:24 -msgid "organization" -msgstr "organisatie" - -#: client/src/shared/form-generator.js:1085 -msgid "playbook" -msgstr "draaiboek" - -#: client/src/organizations/linkout/organizations-linkout.route.js:111 -#: client/src/organizations/linkout/organizations-linkout.route.js:158 -#: client/src/organizations/linkout/organizations-linkout.route.js:209 -msgid "section" -msgstr "sectie" - -#: client/src/credentials/credentials.form.js:138 -#: client/src/credentials/credentials.form.js:364 -msgid "set in helpers/credentials" -msgstr "ingesteld in helpers/toegangsgegevens" - -#: client/src/inventories-hosts/inventories/list/inventory-list.controller.js:48 -msgid "sources with sync failures. Click for details" -msgstr "bronnen met synchronisatiefouten. Klik voor meer informatie" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:244 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:259 -msgid "test" -msgstr "test" - -#: client/src/job-submission/job-submission.partial.html:289 -#: client/src/job-submission/job-submission.partial.html:294 -#: client/src/job-submission/job-submission.partial.html:305 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:14 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:19 -#: client/src/templates/prompt/steps/survey/prompt-survey.partial.html:30 -msgid "to" -msgstr "om" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:139 -msgid "" -"to include all regions. Only Hosts associated with the selected regions will" -" be updated." -msgstr "" -"om alle regio's te omvatten. Alleen hosts die bij de gekozen regio's horen " -"worden geüpdatet." - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:17 -msgid "to start it now." -msgstr "om nu te beginnen." - -#: client/src/inventories-hosts/inventories/related/sources/factories/get-sync-status-msg.factory.js:25 -msgid "to update." -msgstr "om te updaten." - -#: client/src/credentials/credentials.form.js:381 -msgid "v2 URLs%s - leave blank" -msgstr "v2 URL's%s - leeg laten" - -#: client/src/credentials/credentials.form.js:382 -msgid "v3 default%s - set to 'default'" -msgstr "v3 standaard%s - instellen op 'standaard'" - -#: client/src/credentials/credentials.form.js:383 -msgid "v3 multi-domain%s - your domain name" -msgstr "v3 multi-domein%s - uw domeinnaam" - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:319 -msgid "view azure_rm.ini in the Ansible github repo." -msgstr "zie azure_rm.ini in de Ansible github repo." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:220 -msgid "view ec2.ini in the Ansible github repo." -msgstr "zie ec2.ini in de Ansible github repo." - -#: client/src/inventories-hosts/inventories/related/sources/sources.form.js:244 -msgid "view vmware_inventory.ini in the Ansible github repo." -msgstr "zie vmware_inventory.ini in de Ansible github repo." - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:239 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:254 -msgid "when" -msgstr "wanneer" - -#: client/src/inventories-hosts/inventories/related/sources/add/sources-add.controller.js:225 -#: client/src/inventories-hosts/inventories/related/sources/edit/sources-edit.controller.js:239 -msgid "" -"will create group names similar to the following examples based on the " -"options selected:" -msgstr "" -"zullen groepsnamen aangemaakt worden die vergelijkbaar zijn met de volgende " -"voorbeelden op basis van de geselecteerde opties:" - -#: client/src/inventories-hosts/inventories/related/groups/factories/get-hosts-status-msg.factory.js:11 -msgid "with failed jobs." -msgstr "met mislukte taken." - -#: client/features/users/tokens/tokens.strings.js:42 -msgid "{{ appName }} Token" -msgstr "{{ appName }}-token" - -#: client/lib/services/base-string.service.js:102 -msgid "{{ header }} {{ body }}" -msgstr "{{ header }} {{ body }}" - -#: client/lib/services/base-string.service.js:75 -msgid "{{ resource }} successfully created" -msgstr "{{resource}} aangemaakt" - -#: client/src/inventories-hosts/inventory-hosts.strings.js:31 -msgid "{{ str1 }}

{{ str2 }}

" -msgstr "{{ str1 }}

{{ str2 }}

" - -#: client/src/templates/prompt/steps/other-prompts/prompt-other-prompts.partial.html:5 -msgid "{{:: vm.strings.get('prompt.JOB_TYPE') }}" -msgstr "{{:: vm.strings.get('prompt.JOB_TYPE') }}" - -#: client/lib/components/input/label.partial.html:5 -msgid "{{::state._hint}}" -msgstr "{{::state._hint}}" - -#: client/src/shared/paginate/paginate.partial.html:55 -msgid "{{pageSize}}" -msgstr "{{pageSize}}" diff --git a/awx/ui/test/e2e/.babelrc b/awx/ui/test/e2e/.babelrc deleted file mode 100644 index 05758ba5a89d..000000000000 --- a/awx/ui/test/e2e/.babelrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "presets": [ - ["env", { - "targets": { - "node": 6 - } - }] - ] -} diff --git a/awx/ui/test/e2e/.eslintrc.js b/awx/ui/test/e2e/.eslintrc.js deleted file mode 100644 index 02959f42e37f..000000000000 --- a/awx/ui/test/e2e/.eslintrc.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - rules: { - 'no-unused-expressions': 'off', - 'no-unused-vars': 'off', - } -}; diff --git a/awx/ui/test/e2e/README.md b/awx/ui/test/e2e/README.md deleted file mode 100644 index c22c280487c4..000000000000 --- a/awx/ui/test/e2e/README.md +++ /dev/null @@ -1,82 +0,0 @@ -## AWX E2E -#### Introduction -This is an automated functional test suite for the front end. - -#### Technology -The tests are written in Node.js and use the [Nightwatch](https://github.com/nightwatchjs/nightwatch) test runner. - -#### Requirements -- node.js 8.x LTS -- npm 5.x LTS - -#### Installation -A successful invocation of `make ui-devel` will prepare your environment with the software -dependencies required to run these tests. - -#### Configuration -Three inputs are required: - -*AWX_E2E_URL* - -> A valid url for a running AWX instance that is reachable by your machine. This can be your local -development instance or other awx instance. Defaults to *https://localhost:8043*. - -*AWX_E2E_USERNAME* - -> A valid admin username for the target awx instance. Defaults to *awx-e2e*. - -*AWX_E2E_PASSWORD* - -> A valid password for the admin. Defaults to *password*. - -These inputs can be provided as environment variables or defined as default values in [settings](settings.js). -The settings file also contains all other configurable input values to this test suite. - -#### Usage -```shell -# run all of the tests with a live browser -npm --prefix awx/ui run e2e - -# run a subset of the tests -npm --prefix awx/ui run e2e -- --filter="test-credentials*" -``` -**Note:** -- Use `npm --prefix awx/ui run e2e -- --help` to see additional usage information for the test runner. -- All example commands in this document assume that you are working from the root directory of the awx project. - -#### File Overview -All nightwatch.js tests are present in the `tests` directory. When writing -these tests, you may import needed functions from [fixtures.js](fixtures.js), which provides a convenient way to create resources needed for tests -via API, which might include organizations, users, and job templates. - -The `commands` directory provides extra functions for the client object in -nightwatch.js tests. These functions are automatically made available for use by the -client object. For more information on these functions and how to -create your own, refer to the [nightwatch.js documentation on custom commands] -(http://nightwatchjs.org/guide/#writing-custom-commands). - -#### CI Container Debugging -To reproduce test runs in the ci container locally, you'll want to use the provided `docker-compose.yml` file as well as some override files -to link the containers to your development environment. - -```shell -# docker-compose.yml - the default configuration for ci -# docker-compose.devel-override.yml - link ci container to development containers -# docker-compose.debug-override.hml - make chrome and firefox nodes accessible over vnc -docker-compose \ - -f awx/ui/test/e2e/cluster/docker-compose.yml \ - -f awx/ui/test/e2e/cluster/docker-compose.devel-override.yml \ - -f awx/ui/test/e2e/cluster/docker-compose.debug-override.yml \ - run -e AWX_E2E_URL=https://awx:8043 -e AWX_E2E_USERNAME=awx -e AWX_E2E_PASSWORD=password e2e '--filter=*smoke*' -``` - -Once running, you can connect to nodes over vnc at `vnc://localhost:5900` and `vnc://localhost:5901`. - -**Note:** -- On macOS, safari has a built-in vnc client and you should be able to use these urls directly. -- On linux, you'll need to have your favorite vnc client ready (like `tigervnc`). Depending on the vnc client you use, you may need to visit `localhost:5900` and input the password `secret` separately. -- For the chrome and firefox nodes, the development container instance of awx is mapped to hostname `awx` (https://awx:8043) - -#### Known Issues -- ```2019-07-30 13:35:47.883 chromedriver[66032:1305791] pid(66032)/euid(501) is calling TIS/TSM in non-main thread environment, ERROR : This is NOT allowed. Please call TIS/TSM in main thread!!!``` - Specific to MacOS High Sierra. More here: https://github.com/processing/processing/issues/5462 diff --git a/awx/ui/test/e2e/api.js b/awx/ui/test/e2e/api.js deleted file mode 100644 index f411e8ed5674..000000000000 --- a/awx/ui/test/e2e/api.js +++ /dev/null @@ -1,61 +0,0 @@ -import https from 'https'; - -import axios from 'axios'; - -import { - AWX_E2E_URL, - AWX_E2E_USERNAME, - AWX_E2E_PASSWORD -} from './settings'; - -const session = axios.create({ - baseURL: AWX_E2E_URL, - xsrfHeaderName: 'X-CSRFToken', - xsrfCookieName: 'csrftoken', - httpsAgent: new https.Agent({ - rejectUnauthorized: false - }), - auth: { - username: AWX_E2E_USERNAME, - password: AWX_E2E_PASSWORD - } -}); - -const getEndpoint = location => { - if (location.indexOf('/api/v') === 0 || location.indexOf('://') > 0) { - return location; - } - - return `${AWX_E2E_URL}/api/v2${location}`; -}; - -const request = (method, location, data) => { - const uri = getEndpoint(location); - const action = session[method.toLowerCase()]; - - return action(uri, data) - .then(res => { - console.log([ // eslint-disable-line no-console - res.config.method.toUpperCase(), - res.config.url, - res.status, - res.statusText - ].join(' ')); - - return res; - }); -}; - -const get = (endpoint, data) => request('GET', endpoint, data); -const options = endpoint => request('OPTIONS', endpoint); -const post = (endpoint, data) => request('POST', endpoint, data); -const patch = (endpoint, data) => request('PATCH', endpoint, data); -const put = (endpoint, data) => request('PUT', endpoint, data); - -module.exports = { - get, - options, - post, - patch, - put, -}; diff --git a/awx/ui/test/e2e/cluster/Dockerfile b/awx/ui/test/e2e/cluster/Dockerfile deleted file mode 100644 index 73cbe3906eb1..000000000000 --- a/awx/ui/test/e2e/cluster/Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -FROM centos:7 - -ARG NPMRC_FILE=awx/ui/.npmrc -ENV NPMRC_FILE=${NPMRC_FILE} - -RUN yum -y update && yum -y install epel-release && yum -y install https://centos7.iuscommunity.org/ius-release.rpm - -RUN yum install -y \ - bzip2 \ - gcc-c++ \ - git2u \ - git2u-core \ - make \ - nodejs \ - npm - -WORKDIR /awx - -COPY awx/ui/package.json awx/ui/package.json - -COPY ${NPMRC_FILE} awx/ui/.npmrc - -RUN npm --prefix=awx/ui config list && npm --prefix=awx/ui install - -COPY awx/ui/test/e2e awx/ui/test/e2e - -ENTRYPOINT ["npm", "--prefix=awx/ui", "run", "e2e", "--", "--env=cluster"] diff --git a/awx/ui/test/e2e/cluster/docker-compose.debug-override.yml b/awx/ui/test/e2e/cluster/docker-compose.debug-override.yml deleted file mode 100644 index 0d0a0ca3f354..000000000000 --- a/awx/ui/test/e2e/cluster/docker-compose.debug-override.yml +++ /dev/null @@ -1,11 +0,0 @@ ---- -version: '2' -services: - chrome: - # tests are observable at vnc://localhost:secret@localhost:5900 - image: selenium/node-chrome-debug:${SELENIUM_DOCKER_TAG} - ports: ['5900:5900'] - firefox: - # tests are observable at vnc://localhost:secret@localhost:5901 - image: selenium/node-firefox-debug:${SELENIUM_DOCKER_TAG} - ports: ['5901:5900'] diff --git a/awx/ui/test/e2e/cluster/docker-compose.devel-override.yml b/awx/ui/test/e2e/cluster/docker-compose.devel-override.yml deleted file mode 100644 index eed4e15a9436..000000000000 --- a/awx/ui/test/e2e/cluster/docker-compose.devel-override.yml +++ /dev/null @@ -1,18 +0,0 @@ ---- -version: '2' -networks: - default: - external: - name: tools_default -services: - chrome: - external_links: - - tools_awx_1:awx - firefox: - external_links: - - tools_awx_1:awx - e2e: - external_links: - - tools_awx_1:awx - environment: - AWX_E2E_URL: https://awx:8043 diff --git a/awx/ui/test/e2e/cluster/docker-compose.yml b/awx/ui/test/e2e/cluster/docker-compose.yml deleted file mode 100644 index 2b537514658b..000000000000 --- a/awx/ui/test/e2e/cluster/docker-compose.yml +++ /dev/null @@ -1,43 +0,0 @@ ---- -version: '2' -services: - hub: - image: selenium/hub:${SELENIUM_DOCKER_TAG} - ports: - - 4444:4444 - environment: - DBUS_SESSION_BUS_ADDRESS: /dev/null - chrome: - image: selenium/node-chrome:${SELENIUM_DOCKER_TAG} - links: - - hub - volumes: - - /dev/shm:/dev/shm - environment: - HUB_PORT_4444_TCP_ADDR: hub - HUB_PORT_4444_TCP_PORT: 4444 - DBUS_SESSION_BUS_ADDRESS: /dev/null - firefox: - image: selenium/node-firefox:${SELENIUM_DOCKER_TAG} - links: - - hub - environment: - HUB_PORT_4444_TCP_ADDR: hub - HUB_PORT_4444_TCP_PORT: 4444 - e2e: - image: awx_e2e - build: - context: ../../../../../ - dockerfile: awx/ui/test/e2e/cluster/Dockerfile - args: - NPMRC_FILE: ${NPMRC_FILE} - depends_on: - - chrome - links: - - hub - volumes: - - ..:/awx/awx/ui/test/e2e - environment: - AWX_E2E_CLUSTER_HOST: hub - AWX_E2E_CLUSTER_PORT: 4444 - DBUS_SESSION_BUS_ADDRESS: /dev/null diff --git a/awx/ui/test/e2e/commands/findThenClick.js b/awx/ui/test/e2e/commands/findThenClick.js deleted file mode 100644 index d7a0fb331e5e..000000000000 --- a/awx/ui/test/e2e/commands/findThenClick.js +++ /dev/null @@ -1,20 +0,0 @@ -/* Utility function for clicking elements; attempts to scroll to - * the element if necessary, and waits for the page to finish loading. - * - * @param selector - xpath or css selector of the element to click. - * @param [locatoryStrategy='xpath'] - locator strategy used. */ - -exports.command = function findThenClick (selector, locatorStrategy = 'xpath') { - this.waitForElementPresent(selector, () => { - this.moveToElement(selector, 0, 0, () => { - this.click(selector, () => { - if (locatorStrategy === 'css') { - this.waitForElementNotVisible('.spinny'); - } else { - this.waitForElementNotVisible('//*[contains(@class, "spinny")]'); - } - }); - }); - }); - return this; -}; diff --git a/awx/ui/test/e2e/commands/inject.js b/awx/ui/test/e2e/commands/inject.js deleted file mode 100644 index 3ecbbcd61c43..000000000000 --- a/awx/ui/test/e2e/commands/inject.js +++ /dev/null @@ -1,25 +0,0 @@ -exports.command = function inject (deps, script, callback) { - this.executeAsync( - `let args = Array.prototype.slice.call(arguments,0); - - return function(deps, done) { - let injector = angular.element('body').injector(); - let loaded = deps.map(d => { - if (typeof(d) === "string") { - return injector.get(d); - } else { - return d; - } - }); - (${script.toString()}).apply(this, loaded).then(done); - }.apply(this, args);`, - [deps], - function handleResult (result) { - if (typeof callback === 'function') { - callback.call(this, result.value); - } - } - ); - - return this; -}; diff --git a/awx/ui/test/e2e/commands/login.js b/awx/ui/test/e2e/commands/login.js deleted file mode 100644 index 5be191570d41..000000000000 --- a/awx/ui/test/e2e/commands/login.js +++ /dev/null @@ -1,52 +0,0 @@ -import { EventEmitter } from 'events'; -import { inherits } from 'util'; - -import { - AWX_E2E_USERNAME, - AWX_E2E_PASSWORD, - AWX_E2E_TIMEOUT_LONG -} from '../settings'; - -function Login () { - EventEmitter.call(this); -} - -inherits(Login, EventEmitter); - -Login.prototype.command = function command (username, password) { - username = username || AWX_E2E_USERNAME; - password = password || AWX_E2E_PASSWORD; - - const loginPage = this.api.page.login(); - - loginPage - .navigate() - .waitForElementVisible('@submit', AWX_E2E_TIMEOUT_LONG) - .waitForElementNotVisible('div.spinny', AWX_E2E_TIMEOUT_LONG) - .setValue('@username', username) - .setValue('@password', password) - .click('@submit') - .waitForElementVisible('div.spinny', AWX_E2E_TIMEOUT_LONG) - .waitForElementNotVisible('div.spinny', AWX_E2E_TIMEOUT_LONG); - - // temporary hack while login issue is resolved - this.api.elements('css selector', '.LoginModal-alert', result => { - let alertVisible = false; - result.value.map(i => i.ELEMENT).forEach(id => { - this.api.elementIdDisplayed(id, ({ value }) => { - if (!alertVisible && value) { - alertVisible = true; - loginPage.setValue('@username', username); - loginPage.setValue('@password', password); - loginPage.click('@submit'); - loginPage.waitForElementVisible('div.spinny', AWX_E2E_TIMEOUT_LONG); - loginPage.waitForElementNotVisible('div.spinny', AWX_E2E_TIMEOUT_LONG); - } - }); - }); - - this.emit('complete'); - }); -}; - -module.exports = Login; diff --git a/awx/ui/test/e2e/commands/logout.js b/awx/ui/test/e2e/commands/logout.js deleted file mode 100644 index 5320c6db146c..000000000000 --- a/awx/ui/test/e2e/commands/logout.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Logout from the current session by clicking on the power off button on the - * navigation menu. - */ -exports.command = function logout () { - const logoutButton = '.at-Layout-topNav i.fa-power-off'; - this - // protective wait for immediate login/logout - .waitForElementNotPresent('.LoginModal-backDrop') - .waitForElementNotVisible('.spinny') - .findThenClick(logoutButton, 'css') - .waitForElementPresent('#login-button'); -}; diff --git a/awx/ui/test/e2e/commands/navigateTo.js b/awx/ui/test/e2e/commands/navigateTo.js deleted file mode 100644 index d896f1e28286..000000000000 --- a/awx/ui/test/e2e/commands/navigateTo.js +++ /dev/null @@ -1,15 +0,0 @@ -const spinny = 'div.spinny'; - -exports.command = function navigateTo (url, expectSpinny = true) { - this.url('data:,'); // https://github.com/nightwatchjs/nightwatch/issues/1724 - this.url(url); - - if (expectSpinny) { - this.waitForElementVisible(spinny, () => { - // If a process is running, give spinny a little more time before timing out. - this.waitForElementNotVisible(spinny, 30000); - }); - } - - return this; -}; diff --git a/awx/ui/test/e2e/commands/pushFileToWorker.js b/awx/ui/test/e2e/commands/pushFileToWorker.js deleted file mode 100644 index 191164f77890..000000000000 --- a/awx/ui/test/e2e/commands/pushFileToWorker.js +++ /dev/null @@ -1,57 +0,0 @@ -import { basename } from 'path'; -import { EventEmitter } from 'events'; -import { inherits } from 'util'; - -import archiver from 'archiver'; - -function pushFileToWorker (localFilePath, callback) { - const name = basename(localFilePath); - - const push = handler => { - const archive = archiver('zip'); - - const buffers = []; - - archive - .on('data', data => buffers.push(data)) - .on('error', err => { throw err; }) - .on('finish', () => { - const file = Buffer.concat(buffers).toString('base64'); - - this.api.session(session => { - const params = { - path: `/session/${session.sessionId}/file`, - method: 'POST', - data: { file }, - }; - - this.client.runProtocolAction(params, handler).send(); - }); - }); - - archive.file(localFilePath, { name }); - archive.finalize(); - }; - - push(({ status, value }) => { - if (status !== 0) { - throw new Error(value.message); - } - - if (typeof callback === 'function') { - callback.call(this, value); - } - - this.emit('complete'); - }); - - return this; -} - -function PushFileToWorker () { EventEmitter.call(this); } - -inherits(PushFileToWorker, EventEmitter); - -PushFileToWorker.prototype.command = pushFileToWorker; - -module.exports = PushFileToWorker; diff --git a/awx/ui/test/e2e/commands/waitForAngular.js b/awx/ui/test/e2e/commands/waitForAngular.js deleted file mode 100644 index 4daf737dea02..000000000000 --- a/awx/ui/test/e2e/commands/waitForAngular.js +++ /dev/null @@ -1,20 +0,0 @@ -import { AWX_E2E_TIMEOUT_ASYNC } from '../settings'; - -/* Post-login utility function that waits for the application to fully load. */ -exports.command = function waitForAngular (callback) { - this.timeoutsAsyncScript(AWX_E2E_TIMEOUT_ASYNC, () => { - this.executeAsync(done => { - if (angular && angular.getTestability) { - angular.getTestability(document.body).whenStable(done); - } else { - done(); - } - }, [], result => { - if (typeof callback === 'function') { - callback.call(this, result); - } - }); - }); - - return this; -}; diff --git a/awx/ui/test/e2e/commands/waitForSpinny.js b/awx/ui/test/e2e/commands/waitForSpinny.js deleted file mode 100644 index b4a55a0cacd7..000000000000 --- a/awx/ui/test/e2e/commands/waitForSpinny.js +++ /dev/null @@ -1,13 +0,0 @@ -/* Utility function to wait for the working spinner to disappear. */ -exports.command = function waitForSpinny (useXpath = false) { - let selector = 'div.spinny'; - if (useXpath) { - selector = '//*[contains(@class, "spinny")]'; - } - this.waitForElementVisible(selector); - // if a process is running for an extended period, - // spinny might last longer than five seconds. - // this gives it a max of 30 secs before failing. - this.waitForElementNotVisible(selector, 30000); - return this; -}; diff --git a/awx/ui/test/e2e/e2e-pipeline.groovy b/awx/ui/test/e2e/e2e-pipeline.groovy deleted file mode 100644 index efd27124e570..000000000000 --- a/awx/ui/test/e2e/e2e-pipeline.groovy +++ /dev/null @@ -1 +0,0 @@ -e2ePipeline() diff --git a/awx/ui/test/e2e/fixtures.js b/awx/ui/test/e2e/fixtures.js deleted file mode 100644 index 5e8c662d5835..000000000000 --- a/awx/ui/test/e2e/fixtures.js +++ /dev/null @@ -1,565 +0,0 @@ -import uuid from 'uuid'; - -import { AWX_E2E_PASSWORD } from './settings'; - -import { - get, - post, -} from './api'; - -const session = `e2e-${uuid().substr(0, 8)}`; -const store = {}; - -/* Utility function for accessing awx resources. This includes resources like - * users, organizations, and job templates. Retrieves the end point, and creates - * it if it does not exist. - * - * @param endpoint - The REST API url suffix. - * @param data - Attributes used to create a new endpoint. - * @param [unique] - An array of keys used to uniquely identify previously - * created resources from the endpoint. - * - */ -const getOrCreate = (endpoint, data, unique = ['name']) => { - const identifiers = Object.keys(data).filter(key => unique.indexOf(key) > -1); - - if (identifiers.length < 1) { - throw new Error('A unique key value must be provided.'); - } - - const lookup = `${endpoint}/${identifiers.map(key => data[key]).join('-')}`; - const params = Object.assign(...identifiers.map(key => ({ [key]: data[key] }))); - - store[lookup] = store[lookup] || get(endpoint, { params }) - .then(res => { - if (res.data.results.length > 1) { - return Promise.reject(new Error('More than one matching result.')); - } - - if (res.data.results.length === 1) { - return get(res.data.results[0].url); - } - - if (res.data.results.length === 0) { - return post(endpoint, data); - } - - return Promise.reject(new Error(`unexpected response: ${res}`)); - }); - - return store[lookup].then(created => created.data); -}; - -/* Retrieves an organization, and creates it if it does not exist. - * - * @param [namespace] - A unique name prefix for the organization. - * - */ -const getOrganization = (namespace = session) => getOrCreate('/organizations/', { - name: `${namespace}-organization`, - description: namespace -}); - -/* Retrieves an inventory, and creates it if it does not exist. - * Also creates an organization with the same name prefix if needed. - * - * @param [namespace] - A unique name prefix for the inventory. - * - */ -const getInventory = (namespace = session) => getOrganization(namespace) - .then(organization => getOrCreate('/inventories/', { - name: `${namespace}-inventory`, - description: namespace, - organization: organization.id - }).then(inventory => getOrCreate('/hosts/', { - name: `${namespace}-host`, - description: namespace, - inventory: inventory.id, - variables: JSON.stringify({ ansible_connection: 'local' }), - }, ['name', 'inventory']).then(() => inventory))); - -/* Identical to getInventory except it provides a unique suffix, - * "*-inventory-nosource". - * - * @param[namespace] - A unique name prefix for the inventory. -*/ -const getInventoryNoSource = (namespace = session) => getOrganization(namespace) - .then(organization => getOrCreate('/inventories/', { - name: `${namespace}-inventory-nosource`, - description: namespace, - organization: organization.id - }).then(inventory => getOrCreate('/hosts/', { - name: `${namespace}-host`, - description: namespace, - inventory: inventory.id, - variables: JSON.stringify({ ansible_connection: 'local' }), - }, ['name', 'inventory']).then(() => inventory))); - -/* Retrieves a host with the given name prefix, and creates it if it does not exist. - * If an inventory does not exist with the same prefix, it is created as well. - * - * @param[namespace] - A unique name prefix for the host. - */ -const getHost = (namespace = session) => getInventory(namespace) - .then(inventory => getOrCreate('/hosts/', { - name: `${namespace}-host`, - description: namespace, - inventory: inventory.id, - variables: JSON.stringify({ ansible_connection: 'local' }), - }, ['name', 'inventory'])); - -/* Retrieves an inventory script with the given name prefix, and creates it if it - * does not exist. If an organization does not exist with the same prefix, it is - * created as well. - * - * @param[namespace] - A unique name prefix for the host. - */ -const getInventoryScript = (namespace = session) => getOrganization(namespace) - .then(organization => getOrCreate('/inventory_scripts/', { - name: `${namespace}-inventory-script`, - description: namespace, - organization: organization.id, - script: '#!/usr/bin/env python' - })); - -/* Retrieves an inventory source, and creates it if it does not exist. If the - * required dependent inventory and inventory script do not exist, they are also - * created. - * - * @param[namespace] - A unique name prefix for the inventory source. - */ -const getInventorySource = (namespace = session) => { - const promises = [ - getInventory(namespace), - getInventoryScript(namespace) - ]; - - return Promise.all(promises) - .then(([inventory, inventoryScript]) => getOrCreate('/inventory_sources/', { - name: `${namespace}-inventory-source-custom`, - description: namespace, - source: 'custom', - inventory: inventory.id, - source_script: inventoryScript.id - })); -}; - -/* Retrieves an AWS credential, and creates it if it does not exist. - * - * @param[namespace] - A unique name prefix for the AWS credential. - */ -const getAdminAWSCredential = (namespace = session) => { - const promises = [ - get('/me/'), - getOrCreate('/credential_types/', { - name: 'Amazon Web Services' - }) - ]; - - return Promise.all(promises) - .then(([me, credentialType]) => { - const [admin] = me.data.results; - - return getOrCreate('/credentials/', { - name: `${namespace}-credential-aws`, - description: namespace, - credential_type: credentialType.id, - user: admin.id, - inputs: { - username: 'admin', - password: 'password', - security_token: 'AAAAAAAAAAAAAAAA' - } - }); - }); -}; - -/* Retrieves a machine credential, and creates it if it does not exist. - * - * @param[namespace] - A unique name prefix for the machine credential. - */ -const getAdminMachineCredential = (namespace = session) => { - const promises = [ - get('/me/'), - getOrCreate('/credential_types/', { name: 'Machine' }) - ]; - - return Promise.all(promises) - .then(([me, credentialType]) => { - const [admin] = me.data.results; - return getOrCreate('/credentials/', { - name: `${namespace}-credential-machine-admin`, - description: namespace, - credential_type: credentialType.id, - user: admin.id - }); - }); -}; - -/* Retrieves a team, and creates it if it does not exist. - * If an organization does not exist with the same prefix, it is - * created as well. - * - * @param[namespace] - A unique name prefix for the team. - */ -const getTeam = (namespace = session) => getOrganization(namespace) - .then(organization => getOrCreate(`/organizations/${organization.id}/teams/`, { - name: `${namespace}-team`, - description: namespace, - organization: organization.id, - })); - -/* Retrieves a smart inventory, and creates it if it does not exist. - * name prefix. If an organization does not exist with the same prefix, it is - * created as well. - * - * @param[namespace] - A unique name prefix for the smart inventory. - */ -const getSmartInventory = (namespace = session) => getOrganization(namespace) - .then(organization => getOrCreate('/inventories/', { - name: `${namespace}-smart-inventory`, - description: namespace, - organization: organization.id, - host_filter: 'search=localhost', - kind: 'smart' - })); - -/* Retrieves a notification template, and creates it if it does not exist. - * name prefix. If an organization does not exist with the same prefix, it is - * created as well. - * - * @param[namespace] - A unique name prefix for the notification template. - */ -const getNotificationTemplate = (namespace = session) => getOrganization(namespace) - .then(organization => getOrCreate(`/organizations/${organization.id}/notification_templates/`, { - name: `${namespace}-notification-template`, - description: namespace, - organization: organization.id, - notification_type: 'slack', - notification_configuration: { - token: '54321GFEDCBAABCDEFG12345', - channels: ['awx-e2e'] - } - })); - -const waitForJob = endpoint => { - const interval = 2000; - const statuses = ['successful', 'failed', 'error', 'canceled']; - - let attempts = 30; - - return new Promise((resolve, reject) => { - (function pollStatus () { - get(endpoint).then(update => { - const completed = statuses.indexOf(update.data.status) > -1; - - if (completed) { - return resolve(update.data); - } - - if (--attempts <= 0) { - return reject(new Error('Retry limit exceeded.')); - } - - return setTimeout(pollStatus, interval); - }); - }()); - }); -}; - -/* Retrieves a project, and creates it if it does not exist. - * name prefix. If an organization does not exist with the same prefix, it is - * created as well. - * - * @param[namespace] - A unique name prefix for the host. - * @param[scmUrl] - The url of the repository. - * @param[scmType] - The type of scm (git, etc.) - */ -const getProject = ( - namespace = session, - scmUrl = 'https://github.com/ansible/ansible-tower-samples', - scmType = 'git' -) => getOrganization(namespace) - .then(organization => getOrCreate(`/organizations/${organization.id}/projects/`, { - name: `${namespace}-project`, - description: namespace, - organization: organization.id, - scm_url: `${scmUrl}`, - scm_type: `${scmType}` - })); - -const getUpdatedProject = (namespace = session) => { - const promises = [ - getProject(namespace), - ]; - return Promise.all(promises) - .then(([project]) => - post(`/api/v2/projects/${project.id}/update/`, {}) - .then(update => waitForJob(update.data.url)) - .then(() => getProject(namespace))); -}; - -/* Retrieves a job template, and creates it if it does not exist. - * name prefix. This function also runs getOrCreate for an inventory, - * credential, and project with the same prefix. - * - * @param [namespace] - Name prefix for associated dependencies. - * @param [playbook] - Playbook for the job template. - * @param [name] - Unique name prefix for the job template. - * @param [updateProject] - Choose whether to sync the project with its repository. - * */ -const getJobTemplate = ( - namespace = session, - playbook = 'hello_world.yml', - name = `${namespace}-job-template`, - updateProject = true, - jobSliceCount = 1 -) => { - const promises = [ - getInventory(namespace), - getAdminMachineCredential(namespace), - ]; - if (updateProject) { - promises.push(getUpdatedProject(namespace)); - } else { - promises.push(getProject(namespace)); - } - - return Promise.all(promises) - .then(([inventory, credential, project]) => getOrCreate('/job_templates/', { - name: `${name}`, - description: namespace, - inventory: inventory.id, - credential: credential.id, - project: project.id, - playbook: `${playbook}`, - job_slice_count: `${jobSliceCount}`, - })); -}; - -/* Similar to getJobTemplate, except that it also launches the job. - * - * @param[namespace] - A unique name prefix for the job and its dependencies. - * @param[playbook] - The playbook file to be run by the job template. - * @param[name] - A unique name for the job template. - * @param[wait] - Choose whether to return the result of the completed job. - */ -const getJob = ( - namespace = session, - playbook = 'hello_world.yml', - name = `${namespace}-job-template`, - wait = true -) => getJobTemplate(namespace, playbook, name) - .then(template => { - const launchURL = template.related.launch; - return post(launchURL, {}).then(response => { - const jobURL = response.data.url; - if (wait) { - return waitForJob(jobURL).then(() => response.data); - } - return response.data; - }); - }); - -/* Retrieves a workflow template, and creates it if it does not exist. - * name prefix. If an organization does not exist with the same prefix, it is - * created as well. A basic workflow node setup is also created. - * - * @param[namespace] - A unique name prefix for the workflow template. - */ -const getWorkflowTemplate = (namespace = session) => { - const workflowTemplatePromise = getOrganization(namespace) - .then(organization => getOrCreate(`/organizations/${organization.id}/workflow_job_templates/`, { - name: `${namespace}-workflow-template`, - organization: organization.id, - variables: '---', - extra_vars: '', - })); - - const resources = [ - workflowTemplatePromise, - getInventorySource(namespace), - getUpdatedProject(namespace), - getJobTemplate(namespace), - ]; - - const workflowNodePromise = Promise.all(resources) - .then(([workflowTemplate, source, project, jobTemplate]) => { - const workflowNodes = workflowTemplate.related.workflow_nodes; - const unique = 'unified_job_template'; - - const nodes = [ - getOrCreate(workflowNodes, { [unique]: project.id }, [unique]), - getOrCreate(workflowNodes, { [unique]: jobTemplate.id }, [unique]), - getOrCreate(workflowNodes, { [unique]: source.id }, [unique]), - ]; - - const createSuccessNodes = ([projectNode, jobNode, sourceNode]) => Promise.all([ - getOrCreate(projectNode.related.success_nodes, { id: jobNode.id }, ['id']), - getOrCreate(jobNode.related.success_nodes, { id: sourceNode.id }, ['id']), - ]); - - return Promise.all(nodes) - .then(createSuccessNodes); - }); - - return Promise.all([workflowTemplatePromise, workflowNodePromise]) - .then(([workflowTemplate, nodes]) => workflowTemplate); -}; - -/* Retrieves a auditor user, and creates it if it does not exist. - * name prefix. If an organization does not exist with the same prefix, - * it is also created. - * - * @param[namespace] - A unique name prefix for the auditor. - */ -const getAuditor = (namespace = session) => getOrganization(namespace) - .then(organization => getOrCreate(`/organizations/${organization.id}/users/`, { - username: `auditor-${uuid().substr(0, 8)}`, - organization: organization.id, - first_name: 'auditor', - last_name: 'last', - email: 'null@ansible.com', - is_superuser: false, - is_system_auditor: true, - password: AWX_E2E_PASSWORD - }, ['username'])); - -/* Retrieves a user, and creates it if it does not exist. - * name prefix. If an organization does not exist with the same prefix, - * it is also created. - * - * @param[namespace] - A unique name prefix for the user's organization. - * @param[username] - A unique name for the user. - */ -const getUser = ( - namespace = session, - // unique substrings are needed to avoid the edge case - // where a user and org both exist, but the user is not in the organization. - // this ensures a new user is always created. - username = `user-${uuid().substr(0, 8)}`, - password = AWX_E2E_PASSWORD, - isSuperuser = false, - isSystemAuditor = false, - email = `email-${uuid().substr(0, 8)}@example.com`, - firstName = `first-name-${uuid().substr(0, 8)}`, - lastName = `last-name-${uuid().substr(0, 8)}` -) => getOrganization(namespace) - .then(organization => getOrCreate(`/organizations/${organization.id}/users/`, { - email, - first_name: firstName, - is_superuser: isSuperuser, - is_system_auditor: isSystemAuditor, - last_name: lastName, - organization: organization.id, - password, - username, - }, ['username'])); - -/* Retrieves a job template admin, and creates it if it does not exist. - * If a job template or organization does not exist with the same - * prefix, they are also created. - * - * @param[namespace] - A unique name prefix for the template admin. - */ -const getJobTemplateAdmin = (namespace = session) => { - const rolePromise = getJobTemplate(namespace) - .then(obj => obj.summary_fields.object_roles.admin_role); - - const userPromise = getOrganization(namespace) - .then(obj => getOrCreate(`/organizations/${obj.id}/users/`, { - username: `job-template-admin-${uuid().substr(0, 8)}`, - organization: obj.id, - first_name: 'firstname', - last_name: 'lastname', - email: 'null@ansible.com', - is_superuser: false, - is_system_auditor: false, - password: AWX_E2E_PASSWORD - }, ['username'])); - - const assignRolePromise = Promise.all([userPromise, rolePromise]) - .then(([user, role]) => post(`/api/v2/roles/${role.id}/users/`, { id: user.id })); - - return Promise.all([userPromise, assignRolePromise]) - .then(([user, assignment]) => user); -}; - -/* Retrieves a project admin, and creates it if it does not exist. - * If a job template or organization does not exist with the same - * prefix, they are also created. - * - * @param[namespace] - A unique name prefix for the project admin. - */ -const getProjectAdmin = (namespace = session) => { - const rolePromise = getUpdatedProject(namespace) - .then(obj => obj.summary_fields.object_roles.admin_role); - - const userPromise = getOrganization(namespace) - .then(obj => getOrCreate(`/organizations/${obj.id}/users/`, { - username: `project-admin-${uuid().substr(0, 8)}`, - organization: obj.id, - first_name: 'firstname', - last_name: 'lastname', - email: 'null@ansible.com', - is_superuser: false, - is_system_auditor: false, - password: AWX_E2E_PASSWORD - }, ['username'])); - - const assignRolePromise = Promise.all([userPromise, rolePromise]) - .then(([user, role]) => post(`/api/v2/roles/${role.id}/users/`, { id: user.id })); - - return Promise.all([userPromise, assignRolePromise]) - .then(([user, assignment]) => user); -}; - -/* Retrieves a inventory source schedule, and creates it if it does not exist. - * If an inventory source does not exist with the same prefix, it is also created. - * - * @param[namespace] - A unique name prefix for the schedule. - */ -const getInventorySourceSchedule = (namespace = session) => getInventorySource(namespace) - .then(source => getOrCreate(source.related.schedules, { - name: `${source.name}-schedule`, - description: namespace, - rrule: 'DTSTART:20171104T040000Z RRULE:FREQ=DAILY;INTERVAL=1;COUNT=1' - })); - -/* Retrieves a job template schedule, and creates it if it does not exist. - * If an job template does not exist with the same prefix, it is also created. - * - * @param[namespace] - A unique name prefix for the schedule. - */ -const getJobTemplateSchedule = (namespace = session) => getJobTemplate(namespace) - .then(template => getOrCreate(template.related.schedules, { - name: `${template.name}-schedule`, - description: namespace, - rrule: 'DTSTART:20351104T040000Z RRULE:FREQ=DAILY;INTERVAL=1;COUNT=1' - })); - -module.exports = { - getAdminAWSCredential, - getAdminMachineCredential, - getAuditor, - getHost, - getInventory, - getInventoryNoSource, - getInventoryScript, - getInventorySource, - getInventorySourceSchedule, - getJob, - getJobTemplate, - getJobTemplateAdmin, - getJobTemplateSchedule, - getNotificationTemplate, - getOrganization, - getOrCreate, - getProject, - getProjectAdmin, - getSmartInventory, - getTeam, - getUpdatedProject, - getUser, - getWorkflowTemplate, -}; diff --git a/awx/ui/test/e2e/nightwatch.conf.js b/awx/ui/test/e2e/nightwatch.conf.js deleted file mode 100644 index 1d9e27a96a1d..000000000000 --- a/awx/ui/test/e2e/nightwatch.conf.js +++ /dev/null @@ -1,89 +0,0 @@ -import path from 'path'; - -import chromedriver from 'chromedriver'; - -import { - AWX_E2E_CLUSTER_HOST, - AWX_E2E_CLUSTER_PORT, - AWX_E2E_CLUSTER_WORKERS, - AWX_E2E_LAUNCH_URL, - AWX_E2E_TIMEOUT_ASYNC, - AWX_E2E_TIMEOUT_MEDIUM, - AWX_E2E_SCREENSHOTS_ENABLED, - AWX_E2E_SCREENSHOTS_ON_ERROR, - AWX_E2E_SCREENSHOTS_ON_FAILURE, - AWX_E2E_SCREENSHOTS_PATH, -} from './settings'; - -const resolve = location => path.resolve(__dirname, location); - -module.exports = { - src_folders: [resolve('tests')], - output_folder: resolve('reports'), - custom_commands_path: resolve('commands'), - page_objects_path: resolve('objects'), - test_settings: { - default: { - selenium_host: 'localhost', - selenium_port: 9515, - default_path_prefix: '', - desiredCapabilities: { - browserName: 'chrome', - chromeOptions: { - w3c: false, - args: [ - 'window-size=1024,768' - ] - } - }, - test_workers: { enabled: false }, - globals: { - launch_url: AWX_E2E_LAUNCH_URL, - retryAssertionTimeout: AWX_E2E_TIMEOUT_MEDIUM, - waitForConditionTimeout: AWX_E2E_TIMEOUT_MEDIUM, - asyncHookTimeout: AWX_E2E_TIMEOUT_ASYNC, - before (done) { - chromedriver.start(['--port=9515']); - done(); - }, - after (done) { - chromedriver.stop(); - done(); - } - }, - screenshots: { - enabled: AWX_E2E_SCREENSHOTS_ENABLED, - on_error: AWX_E2E_SCREENSHOTS_ON_ERROR, - on_failure: AWX_E2E_SCREENSHOTS_ON_FAILURE, - path: AWX_E2E_SCREENSHOTS_PATH, - } - }, - headless: { - desiredCapabilities: { - browserName: 'chrome', - chromeOptions: { - w3c: false, - args: [ - 'headless', - 'disable-web-security', - 'ignore-certificate-errors', - 'no-sandbox', - 'disable-gpu' - ] - } - }, - }, - // Note: These are environment-specific overrides to the default - // test settings defined above. - cluster: { - selenium_host: AWX_E2E_CLUSTER_HOST, - selenium_port: AWX_E2E_CLUSTER_PORT, - default_path_prefix: '/wd/hub', - test_workers: { - enabled: (AWX_E2E_CLUSTER_WORKERS > 0), - workers: AWX_E2E_CLUSTER_WORKERS - }, - globals: { before: {}, after: {} } - } - } -}; diff --git a/awx/ui/test/e2e/nightwatchxsl.xsl b/awx/ui/test/e2e/nightwatchxsl.xsl deleted file mode 100644 index 229dd26103de..000000000000 --- a/awx/ui/test/e2e/nightwatchxsl.xsl +++ /dev/null @@ -1,151 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/awx/ui/test/e2e/objects/activityStream.js b/awx/ui/test/e2e/objects/activityStream.js deleted file mode 100644 index 961ef6056e5a..000000000000 --- a/awx/ui/test/e2e/objects/activityStream.js +++ /dev/null @@ -1,16 +0,0 @@ -module.exports = { - url () { - return `${this.api.globals.launch_url}/#/activity_stream`; - }, - commands: [{ - load () { - this.api.url('data:,'); // https://github.com/nightwatchjs/nightwatch/issues/1724 - return this.navigate(); - }, - }], - elements: { - title: '.List-titleText', - subtitle: '.List-titleLockup', - category: '#stream-dropdown-nav' - } -}; diff --git a/awx/ui/test/e2e/objects/applications.js b/awx/ui/test/e2e/objects/applications.js deleted file mode 100644 index e34d762ecd32..000000000000 --- a/awx/ui/test/e2e/objects/applications.js +++ /dev/null @@ -1,153 +0,0 @@ -import actions from './sections/actions'; -import breadcrumb from './sections/breadcrumb'; -import createTableSection from './sections/createTableSection'; -import header from './sections/header'; -import lookupModal from './sections/lookupModal'; -import navigation from './sections/navigation'; -import pagination from './sections/pagination'; -import permissions from './sections/permissions'; -import search from './sections/search'; - -const row = '.at-List-container .at-Row'; - -const addEditElements = { - name: '#application_name_group input', - description: '#application_description_group input', - organization: '#application_organization_group input', - authorizationGrantType: '#application_authorization_grant_type_group select', - redirectUris: '#application_redirect_uris_group input', - clientType: '#application_client_type_group select', - save: 'button[type=save]', - tokensTab: 'div.card button.at-Tab:nth-of-type(2)', -}; - -const authorizationGrantTypeOptions = { - authorizationCode: 'Authorization code', - resourceOwnerPasswordBased: 'Resource owner password-based', -}; - -const clientTypeOptions = { - confidential: 'Confidential', - public: 'Public', -}; - -module.exports = { - url () { - return `${this.api.globals.launch_url}/#/applications`; - }, - commands: [{ - load () { - this.api.url('data:,'); // https://github.com/nightwatchjs/nightwatch/issues/1724 - return this.navigate(); - }, - create (application, organization) { - this.section.list - .waitForElementVisible('@add') - .click('@add'); - this.section.add - .waitForElementVisible('@name') - .setValue('@name', application.name) - .setValue('@organization', organization.name) - .setValue('@authorizationGrantType', application.authorizationGrantType) - .setValue('@clientType', application.clientType); - if (application.description) { - this.section.add.setValue('@description', application.description); - } - if (application.redirectUris) { - this.section.add.setValue('@redirectUris', application.redirectUris); - } - this.section.add.click('@save'); // flake avoidance. triple click ensures it works. - this.section.add.click('@save'); - this.section.add.click('@save'); - this - .waitForElementVisible('#alert-modal-msg') - .expect.element('#alert-modal-msg').text.contain(application.name); - this.findThenClick('#alert_ok_btn', 'css'); - this.waitForElementNotVisible('#alert-modal-msg'); - }, - delete (name) { - this.search(name); - const deleteButton = `${row} i[class*="fa-trash"]`; - const modalAction = '.modal-dialog #prompt_action_btn'; - this - .waitForElementVisible(deleteButton) - .click(deleteButton) - .waitForElementVisible(modalAction) - .click(modalAction) - .waitForSpinny(); - const searchResults = '.at-List--empty'; - this - .waitForElementVisible(searchResults) - .expect.element(searchResults).text.equal('PLEASE ADD ITEMS TO THIS LIST.'); - }, - search (name) { - const searchSection = this.section.list.section.search; - searchSection.setValue('@input', name); - searchSection.expect.element('@searchButton').to.be.enabled.before(200); - searchSection.click('@searchButton'); - this.waitForSpinny(); - this.waitForElementNotPresent(`${row}:nth-of-type(2)`); - this.expect.element('.at-Panel-headingTitleBadge').text.to.equal('1'); - this.expect.element(`${row} .at-RowItem-header`).text.equal(name); - }, - }], - sections: { - header, - navigation, - breadcrumb, - lookupModal, - add: { - selector: 'div[ui-view="add"]', - sections: { - // details - }, - elements: addEditElements, - }, - edit: { - selector: 'div[ui-view="edit"]', - sections: { - // details, - permissions - }, - elements: addEditElements, - }, - list: { - selector: 'div[ui-view="list"]', - elements: { - badge: 'span[class~="badge"]', - title: 'h3[class~="Panel-headingTitle"]', - add: '#button-add' - }, - sections: { - search, - pagination, - table: createTableSection({ - elements: { - username: 'td[class~="username-column"]', - first_name: 'td[class~="first_name-column"]', - last_name: 'td[class~="last_name-column"]' - }, - sections: { - actions - } - }) - } - }, - tokens: { - selector: 'div.card', - elements: { - list: '.at-List-container', - } - } - }, - elements: { - cancel: 'button[class*="Form-cancelButton"]', - save: 'button[class*="Form-saveButton"]' - }, - props () { - return { - authorizationGrantTypeOptions, - clientTypeOptions, - }; - } -}; diff --git a/awx/ui/test/e2e/objects/configuration.js b/awx/ui/test/e2e/objects/configuration.js deleted file mode 100644 index d6e8c0d4f5d1..000000000000 --- a/awx/ui/test/e2e/objects/configuration.js +++ /dev/null @@ -1,51 +0,0 @@ -import breadcrumb from './sections/breadcrumb'; -import header from './sections/header'; -import navigation from './sections/navigation'; - -const sections = { - header, - navigation, - breadcrumb, -}; - -const commands = [{ - load () { - this.api.url('data:,'); // https://github.com/nightwatchjs/nightwatch/issues/1724 - return this.navigate(); - }, - selectSubcategory (name) { - const spinny = 'div.spinny'; - const categoryName = `//*[text() = '${name}']`; - - this.api.useXpath(); - this.api.waitForElementVisible(categoryName); - this.api.click(categoryName); - this.api.useCss(); - - return this; - }, - selectDropDownContainer (name) { - const spinny = 'div.spinny'; - const select = '#configure-dropdown-nav'; - const arrow = `${select} + span span[class$="arrow"]`; - const option = `//li[contains(text(), "${name}")]`; - - this.api.waitForElementVisible(arrow); - this.api.click(arrow); - - this.api.useXpath(); - this.api.waitForElementVisible(option); - this.api.click(option); - this.api.useCss(); - - return this; - }, -}]; - -module.exports = { - url () { - return `${this.api.globals.launch_url}/#/configuration`; - }, - sections, - commands, -}; diff --git a/awx/ui/test/e2e/objects/credentialTypes.js b/awx/ui/test/e2e/objects/credentialTypes.js deleted file mode 100644 index 7ea9ec298d1b..000000000000 --- a/awx/ui/test/e2e/objects/credentialTypes.js +++ /dev/null @@ -1,68 +0,0 @@ -import actions from './sections/actions'; -import breadcrumb from './sections/breadcrumb'; -import createFormSection from './sections/createFormSection'; -import createTableSection from './sections/createTableSection'; -import header from './sections/header'; -import search from './sections/search'; -import pagination from './sections/pagination'; - -const addEditPanel = { - selector: 'div[ui-view="form"]', - elements: { - title: 'div[class="Form-title"]', - }, - sections: { - details: createFormSection({ - selector: '#credential_type_form', - labels: { - name: 'Name', - description: 'Description', - inputConfiguration: 'Input Configuration', - injectorConfiguration: 'Injector Configuration' - }, - strategy: 'legacy' - }) - } -}; - -const listPanel = { - selector: 'div[ui-view="list"]', - elements: { - add: '#button-add', - badge: 'div[class="List-titleBadge]', - titleText: 'div[class="List-titleText"]', - noitems: 'div[class="List-noItems"]' - }, - sections: { - search, - pagination, - table: createTableSection({ - elements: { - name: 'td[class~="name-column"]', - kind: 'td[class~="kind-column"]', - }, - sections: { - actions - } - }) - } -}; - -module.exports = { - url () { - return `${this.api.globals.launch_url}/#/credential_types`; - }, - commands: [{ - load () { - this.api.url('data:,'); // https://github.com/nightwatchjs/nightwatch/issues/1724 - return this.navigate(); - }, - }], - sections: { - header, - breadcrumb, - add: addEditPanel, - edit: addEditPanel, - list: listPanel - } -}; diff --git a/awx/ui/test/e2e/objects/credentials.js b/awx/ui/test/e2e/objects/credentials.js deleted file mode 100644 index 391ac7da2198..000000000000 --- a/awx/ui/test/e2e/objects/credentials.js +++ /dev/null @@ -1,271 +0,0 @@ -import _ from 'lodash'; - -import actions from './sections/actions'; -import breadcrumb from './sections/breadcrumb'; -import createFormSection from './sections/createFormSection'; -import createTableSection from './sections/createTableSection'; -import dynamicSection from './sections/dynamicSection'; -import header from './sections/header'; -import lookupModal from './sections/lookupModal'; -import navigation from './sections/navigation'; -import pagination from './sections/pagination'; -import permissions from './sections/permissions'; -import search from './sections/search'; - -const common = createFormSection({ - selector: 'form', - labels: { - name: 'Name', - description: 'Description', - organization: 'Organization', - type: 'Credential Type' - } -}); - -const machine = createFormSection({ - selector: '.at-InputGroup-inset', - labels: { - username: 'Username', - password: 'Password', - sshKeyData: 'SSH Private Key', - sshKeyUnlock: 'Private Key Passphrase', - becomeMethod: 'Privilege Escalation Method', - becomeUsername: 'Privilege Escalation Username', - becomePassword: 'Privilege Escalation Password' - } -}); - -const vault = createFormSection({ - selector: '.at-InputGroup-inset', - labels: { - vaultPassword: 'Vault Password', - vaultIdentifier: 'Vault Identifier' - } -}); - -const scm = createFormSection({ - selector: '.at-InputGroup-inset', - labels: { - username: 'Username', - password: 'Password', - sshKeyData: 'SCM Private Key', - sshKeyUnlock: 'Private Key Passphrase', - } -}); - -const aws = createFormSection({ - selector: '.at-InputGroup-inset', - labels: { - accessKey: 'Access Key', - secretKey: 'Secret Key', - securityToken: 'STS Token', - } -}); - -const gce = createFormSection({ - selector: '.at-InputGroup-inset', - labels: { - email: 'Service Account Email Address', - project: 'Project', - sshKeyData: 'RSA Private Key', - serviceAccountFile: 'Service Account JSON File' - } -}); - -const vmware = createFormSection({ - selector: '.at-InputGroup-inset', - labels: { - host: 'VCenter Host', - username: 'Username', - password: 'Password', - } -}); - -const azureClassic = createFormSection({ - selector: '.at-InputGroup-inset', - labels: { - subscription: 'Subscription ID', - sshKeyData: 'Management Certificate', - } -}); - -const azure = createFormSection({ - selector: '.at-InputGroup-inset', - labels: { - subscription: 'Subscription ID', - username: 'Username', - password: 'Password', - client: 'Client ID', - secret: 'Client Secret', - tenant: 'Tenant ID', - } -}); - -const openStack = createFormSection({ - selector: '.at-InputGroup-inset', - labels: { - username: 'Username', - password: 'Password (API Key)', - host: 'Host (Authentication URL)', - project: 'Project (Tenant Name)', - domain: 'Domain Name', - } -}); - -const rackspace = createFormSection({ - selector: '.at-InputGroup-inset', - labels: { - username: 'Username', - password: 'Password', - } -}); - -const cloudForms = createFormSection({ - selector: '.at-InputGroup-inset', - labels: { - host: 'Cloudforms URL', - username: 'Username', - password: 'Password', - } -}); - -const network = createFormSection({ - selector: '.at-InputGroup-inset', - labels: { - sshKeyData: 'SSH Private Key', - sshKeyUnlock: 'Private Key Passphrase', - username: 'Username', - password: 'Password', - authorizePassword: 'Authorize Password', - } -}); - -network.elements.authorize = { - locateStrategy: 'xpath', - selector: '//input[../p/text() = "Authorize"]' -}; - -const sat6 = createFormSection({ - selector: '.at-InputGroup-inset', - labels: { - host: 'Satellite 6 URL', - username: 'Username', - password: 'Password', - } -}); - -const insights = createFormSection({ - selector: '.at-InputGroup-inset', - labels: { - username: 'Username', - password: 'Password', - }, -}); - -const details = _.merge({}, common, { - elements: { - cancel: '.btn[type="cancel"]', - save: '.btn[type="save"]', - }, - sections: { - aws, - azure, - azureClassic, - cloudForms, - dynamicSection, - gce, - insights, - machine, - network, - rackspace, - sat6, - scm, - openStack, - vault, - vmware - }, - commands: [{ - custom ({ name, inputs }) { - const labels = {}; - inputs.fields.forEach(f => { labels[f.id] = f.label; }); - - const selector = '.at-InputGroup-inset'; - const generated = createFormSection({ selector, labels }); - - const params = _.merge({ name }, generated); - return this.section.dynamicSection.create(params); - }, - clear () { - this.clearValue('@name'); - this.clearValue('@organization'); - this.clearValue('@description'); - this.clearValue('@type'); - this.waitForElementNotVisible('.at-InputGroup-inset'); - return this; - }, - clearAndSelectType (type) { - this.clear(); - this.setValue('@type', type); - this.waitForElementVisible('.at-InputGroup-inset'); - return this; - } - }] -}); - -module.exports = { - url () { - return `${this.api.globals.launch_url}/#/credentials`; - }, - commands: [{ - load () { - this.api.url('data:,'); // https://github.com/nightwatchjs/nightwatch/issues/1724 - return this.navigate(); - }, - }], - sections: { - header, - navigation, - breadcrumb, - lookupModal, - add: { - selector: 'div[ui-view="add"]', - sections: { - details - }, - elements: { - title: 'h3[class*="at-Panel-headingTitle"]' - } - }, - edit: { - selector: 'div[ui-view="edit"]', - sections: { - details, - permissions - }, - elements: { - title: 'h3[class*="at-Panel-headingTitle"]' - } - }, - list: { - selector: 'div[ui-view="list"]', - elements: { - badge: 'span[class~="badge"]', - title: 'div[class="List-titleText"]', - add: '#button-add' - }, - sections: { - search, - pagination, - table: createTableSection({ - elements: { - name: 'td[class~="name-column"]', - kind: 'td[class~="kind-column"]' - }, - sections: { - actions - } - }) - } - }, - } -}; diff --git a/awx/ui/test/e2e/objects/dashboard.js b/awx/ui/test/e2e/objects/dashboard.js deleted file mode 100644 index ca6695ca5c2c..000000000000 --- a/awx/ui/test/e2e/objects/dashboard.js +++ /dev/null @@ -1,24 +0,0 @@ -import breadcrumb from './sections/breadcrumb'; -import header from './sections/header'; -import navigation from './sections/navigation'; - -const sections = { - header, - navigation, - breadcrumb, -}; - -const commands = [{ - load () { - this.api.url('data:,'); // https://github.com/nightwatchjs/nightwatch/issues/1724 - return this.navigate(); - } -}]; - -module.exports = { - url () { - return `${this.api.globals.launch_url}/#/home`; - }, - sections, - commands, -}; diff --git a/awx/ui/test/e2e/objects/inventories.js b/awx/ui/test/e2e/objects/inventories.js deleted file mode 100644 index 059b6a869220..000000000000 --- a/awx/ui/test/e2e/objects/inventories.js +++ /dev/null @@ -1,136 +0,0 @@ -import actions from './sections/actions'; -import breadcrumb from './sections/breadcrumb'; -import createFormSection from './sections/createFormSection'; -import createTableSection from './sections/createTableSection'; -import header from './sections/header'; -import lookupModal from './sections/lookupModal'; -import navigation from './sections/navigation'; -import pagination from './sections/pagination'; -import permissions from './sections/permissions'; -import search from './sections/search'; - -const standardInvDetails = createFormSection({ - selector: 'form', - props: { - formElementSelectors: [ - '#inventory_form .Form-textInput', - '#inventory_form select.Form-dropDown', - '#inventory_form .Form-textArea', - '#inventory_form input[type="checkbox"]', - '#inventory_form .ui-spinner-input', - '#inventory_form .atSwitch-inner' - ] - }, - labels: { - name: 'Name', - description: 'Description', - organization: 'Organization' - } -}); - -const smartInvDetails = createFormSection({ - selector: 'form', - props: { - formElementSelectors: [ - '#smartinventory_form input.Form-textInput', - '#smartinventory_form textarea.Form-textArea', - '#smartinventory_form .Form-lookupButton', - '#smartinventory_form #InstanceGroups' - ] - } -}); - -module.exports = { - url () { - return `${this.api.globals.launch_url}/#/inventories`; - }, - sections: { - header, - navigation, - breadcrumb, - lookupModal, - addStandardInventory: { - selector: 'div[ui-view="form"]', - sections: { - standardInvDetails - }, - elements: { - title: 'div[class^="Form-title"]' - } - }, - editStandardInventory: { - selector: 'div[ui-view="form"]', - sections: { - standardInvDetails, - permissions - }, - elements: { - title: 'div[class^="Form-title"]' - } - }, - addSmartInventory: { - selector: 'div[ui-view="form"]', - sections: { - smartInvDetails - }, - elements: { - title: 'div[class^="Form-title"]' - } - }, - editSmartInventory: { - selector: 'div[ui-view="form"]', - sections: { - smartInvDetails, - permissions - }, - elements: { - title: 'div[class^="Form-title"]' - } - }, - list: { - selector: '.at-Panel', - elements: { - badge: 'span[class~="badge"]', - title: 'div[class="List-titleText"]', - add: 'button[class~="List-dropdownButton"]' - }, - sections: { - search, - pagination, - table: createTableSection({ - elements: { - status: 'td[class~="status-column"]', - name: 'td[class~="name-column"]', - kind: 'td[class~="kind-column"]', - organization: 'td[class~="organization-column"]' - }, - sections: { - actions - } - }) - } - } - }, - elements: { - cancel: 'button[class*="Form-cancelButton"]', - save: 'button[class*="Form-saveButton"]' - }, - commands: [{ - load () { - this.api.url('data:,'); // https://github.com/nightwatchjs/nightwatch/issues/1724 - return this.navigate(); - }, - selectAdd (name) { - this.api.waitForElementVisible('#button-add'); - this.expect.element('#button-add').enabled; - this.api.click('#button-add'); - - this.api.useXpath(); - this.api.waitForElementVisible(`.//a[normalize-space(text())="${name}"]`); - this.api.click(`//a[normalize-space(text())="${name}"]`); - this.api.useCss(); - - return this; - } - }] -}; diff --git a/awx/ui/test/e2e/objects/inventoryScripts.js b/awx/ui/test/e2e/objects/inventoryScripts.js deleted file mode 100644 index e2f49fe1cca0..000000000000 --- a/awx/ui/test/e2e/objects/inventoryScripts.js +++ /dev/null @@ -1,83 +0,0 @@ -import actions from './sections/actions'; -import breadcrumb from './sections/breadcrumb'; -import createFormSection from './sections/createFormSection'; -import createTableSection from './sections/createTableSection'; -import header from './sections/header'; -import lookupModal from './sections/lookupModal'; -import navigation from './sections/navigation'; -import pagination from './sections/pagination'; -import permissions from './sections/permissions'; -import search from './sections/search'; - -const details = createFormSection({ - selector: 'form', - props: { - formElementSelectors: [ - '#inventory_script_form .Form-textInput', - '#inventory_script_form .Form-textArea', - '#inventory_script_form .Form-lookupButton' - ] - } -}); - -module.exports = { - url () { - return `${this.api.globals.launch_url}/#/inventory_scripts`; - }, - commands: [{ - load () { - this.api.url('data:,'); // https://github.com/nightwatchjs/nightwatch/issues/1724 - return this.navigate(); - }, - }], - sections: { - header, - navigation, - breadcrumb, - lookupModal, - add: { - selector: 'div[ui-view="form"]', - sections: { - details - }, - elements: { - title: 'div[class^="Form-title"]' - } - }, - edit: { - selector: 'div[ui-view="form"]', - sections: { - details, - permissions - }, - elements: { - title: 'div[class^="Form-title"]' - } - }, - list: { - selector: 'div[ui-view="list"]', - elements: { - badge: 'span[class~="badge"]', - title: 'div[class="List-titleText"]', - add: '#button-add' - }, - sections: { - search, - pagination, - table: createTableSection({ - elements: { - name: 'td[class~="name-column"]', - organization: 'td[class~="organization-column"]', - }, - sections: { - actions - } - }) - } - } - }, - elements: { - cancel: 'button[class*="Form-cancelButton"]', - save: 'button[class*="Form-saveButton"]' - } -}; diff --git a/awx/ui/test/e2e/objects/jobs.js b/awx/ui/test/e2e/objects/jobs.js deleted file mode 100644 index 8e1b3fe82e3e..000000000000 --- a/awx/ui/test/e2e/objects/jobs.js +++ /dev/null @@ -1,15 +0,0 @@ -import _ from 'lodash'; - -module.exports = { - url () { - return `${this.api.globals.launch_url}/#/jobs`; - }, - sections: {}, // TODO: Fill this out - elements: {}, // TODO: Fill this out - commands: [{ - load () { - this.api.url('data:,'); // https://github.com/nightwatchjs/nightwatch/issues/1724 - return this.navigate(); - }, - }], -}; diff --git a/awx/ui/test/e2e/objects/login.js b/awx/ui/test/e2e/objects/login.js deleted file mode 100644 index 75623f130097..000000000000 --- a/awx/ui/test/e2e/objects/login.js +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = { - url () { - return `${this.api.globals.launch_url}/#/login`; - }, - commands: [{ - load () { - this.api.url('data:,'); // https://github.com/nightwatchjs/nightwatch/issues/1724 - return this.navigate(); - }, - }], - elements: { - username: '#login-username', - password: '#login-password', - submit: '#login-button', - logo: '#main_menu_logo' - } -}; diff --git a/awx/ui/test/e2e/objects/notificationTemplates.js b/awx/ui/test/e2e/objects/notificationTemplates.js deleted file mode 100644 index 8af6689359ed..000000000000 --- a/awx/ui/test/e2e/objects/notificationTemplates.js +++ /dev/null @@ -1,88 +0,0 @@ -import actions from './sections/actions'; -import breadcrumb from './sections/breadcrumb'; -import createFormSection from './sections/createFormSection'; -import createTableSection from './sections/createTableSection'; -import header from './sections/header'; -import lookupModal from './sections/lookupModal'; -import navigation from './sections/navigation'; -import pagination from './sections/pagination'; -import permissions from './sections/permissions'; -import search from './sections/search'; - -const details = createFormSection({ - selector: 'form', - props: { - formElementSelectors: [ - '#notification_template_form .Form-textInput', - '#notification_template_form select.Form-dropDown', - '#notification_template_form input[type="checkbox"]', - '#notification_template_form input[type="radio"]', - '#notification_template_form .ui-spinner-input', - '#notification_template_form .Form-textArea', - '#notification_template_form .atSwitch-outer', - '#notification_template_form .Form-lookupButton' - ] - } -}); - -module.exports = { - url () { - return `${this.api.globals.launch_url}/#/notification_templates`; - }, - commands: [{ - load () { - this.api.url('data:,'); // https://github.com/nightwatchjs/nightwatch/issues/1724 - return this.navigate(); - }, - }], - sections: { - header, - navigation, - breadcrumb, - lookupModal, - add: { - selector: 'div[ui-view="form"]', - sections: { - details - }, - elements: { - title: 'div[class^="Form-title"]' - } - }, - edit: { - selector: 'div[ui-view="form"]', - sections: { - details, - permissions - }, - elements: { - title: 'div[class^="Form-title"]' - } - }, - list: { - selector: 'div[ui-view="list"]', - elements: { - badge: 'span[class~="badge"]', - title: 'div[class="List-titleText"]', - add: '#button-add' - }, - sections: { - search, - pagination, - table: createTableSection({ - elements: { - name: 'td[class~="name-column"]', - organization: 'td[class~="organization-column"]', - }, - sections: { - actions - } - }) - } - } - }, - elements: { - cancel: 'button[class*="Form-cancelButton"]', - save: 'button[class*="Form-saveButton"]' - } -}; diff --git a/awx/ui/test/e2e/objects/organizations.js b/awx/ui/test/e2e/objects/organizations.js deleted file mode 100644 index 40fef9a2567b..000000000000 --- a/awx/ui/test/e2e/objects/organizations.js +++ /dev/null @@ -1,76 +0,0 @@ -import breadcrumb from './sections/breadcrumb'; -import createFormSection from './sections/createFormSection'; -import header from './sections/header'; -import lookupModal from './sections/lookupModal'; -import navigation from './sections/navigation'; -import pagination from './sections/pagination'; -import permissions from './sections/permissions'; -import search from './sections/search'; - -const details = createFormSection({ - selector: 'form', - props: { - formElementSelectors: [ - '#organization_form input.Form-textInput', - '#organization_form .Form-lookupButton', - '#organization_form #InstanceGroups' - ] - }, - labels: { - name: 'Name', - description: 'Description' - } -}); - -module.exports = { - url () { - return `${this.api.globals.launch_url}/#/organizations`; - }, - commands: [{ - load () { - this.api.url('data:,'); // https://github.com/nightwatchjs/nightwatch/issues/1724 - return this.navigate(); - }, - }], - sections: { - header, - navigation, - breadcrumb, - lookupModal, - add: { - selector: 'div[ui-view="form"]', - sections: { - details - }, - elements: { - title: 'div[class^="Form-title"]' - } - }, - edit: { - selector: 'div[ui-view="form"]', - sections: { - details, - permissions - }, - elements: { - title: 'div[class^="Form-title"]' - } - }, - list: { - selector: '#organizations', - elements: { - badge: 'span[class~="badge"]', - title: 'div[class="List-titleText"]', - add: '#button-add' - }, - sections: { - search, - pagination - } - } - }, - elements: { - cancel: 'button[class*="Form-cancelButton"]', - save: 'button[class*="Form-saveButton"]' - } -}; diff --git a/awx/ui/test/e2e/objects/portal.js b/awx/ui/test/e2e/objects/portal.js deleted file mode 100644 index 0ec48e894248..000000000000 --- a/awx/ui/test/e2e/objects/portal.js +++ /dev/null @@ -1,13 +0,0 @@ -module.exports = { - url () { - return `${this.api.globals.launch_url}/#/portal/myjobs`; - }, - sections: {}, - elements: {}, - commands: [{ - load () { - this.api.url('data:,'); // https://github.com/nightwatchjs/nightwatch/issues/1724 - return this.navigate(); - }, - }], -}; diff --git a/awx/ui/test/e2e/objects/projects.js b/awx/ui/test/e2e/objects/projects.js deleted file mode 100644 index 345bf1664ab0..000000000000 --- a/awx/ui/test/e2e/objects/projects.js +++ /dev/null @@ -1,86 +0,0 @@ -import actions from './sections/actions'; -import breadcrumb from './sections/breadcrumb'; -import createFormSection from './sections/createFormSection'; -import createTableSection from './sections/createTableSection'; -import header from './sections/header'; -import lookupModal from './sections/lookupModal'; -import navigation from './sections/navigation'; -import pagination from './sections/pagination'; -import permissions from './sections/permissions'; -import search from './sections/search'; - -const details = createFormSection({ - selector: 'form', - props: { - formElementSelectors: [ - '#project_form .Form-textInput', - '#project_form select.Form-dropDown', - '#project_form input[type="checkbox"]', - '#project_form .ui-spinner-input', - ] - } -}); - -module.exports = { - url () { - return `${this.api.globals.launch_url}/#/projects`; - }, - commands: [{ - load () { - this.api.url('data:,'); // https://github.com/nightwatchjs/nightwatch/issues/1724 - return this.navigate(); - }, - }], - sections: { - header, - navigation, - breadcrumb, - lookupModal, - add: { - selector: 'div[ui-view="form"]', - sections: { - details - }, - elements: { - title: 'div[class^="Form-title"]' - } - }, - edit: { - selector: 'div[ui-view="form"]', - sections: { - details, - permissions - }, - elements: { - title: 'div[class^="Form-title"]' - } - }, - list: { - selector: '.at-Panel', - elements: { - badge: '.at-Panel-headingTitleBadge', - title: '.at-Panel-headingTitle', - add: '#button-add' - }, - sections: { - search, - pagination, - table: createTableSection({ - elements: { - status: 'td[class~="status-column"]', - name: 'td[class~="name-column"]', - scm_type: 'td[class~="scm_type-column"]', - last_updated: 'td[class~="last_updated-column"]' - }, - sections: { - actions - } - }) - } - } - }, - elements: { - cancel: 'button[class*="Form-cancelButton"]', - save: 'button[class*="Form-saveButton"]' - } -}; diff --git a/awx/ui/test/e2e/objects/sections/actions.js b/awx/ui/test/e2e/objects/sections/actions.js deleted file mode 100644 index 805c2f4ee701..000000000000 --- a/awx/ui/test/e2e/objects/sections/actions.js +++ /dev/null @@ -1,15 +0,0 @@ -const actions = { - selector: 'td[class="List-actionsContainer"]', - elements: { - launch: 'i[class="fa icon-launch"]', - schedule: 'i[class="fa icon-schedule"]', - copy: 'i[class="fa icon-copy"]', - edit: 'i[class="fa icon-pencil"]', - delete: 'i[class="fa icon-trash-o"]', - view: 'i[class="fa fa-search-plus"]', - sync: 'i[class="fa fa-refresh"]', - test: 'i[class="fa fa-bell-o' - } -}; - -module.exports = actions; diff --git a/awx/ui/test/e2e/objects/sections/breadcrumb.js b/awx/ui/test/e2e/objects/sections/breadcrumb.js deleted file mode 100644 index 62231b95c225..000000000000 --- a/awx/ui/test/e2e/objects/sections/breadcrumb.js +++ /dev/null @@ -1,8 +0,0 @@ -const breadcrumb = { - selector: 'bread-crumb > div', - elements: { - activity: 'i[class$="icon-activity-stream"]' - } -}; - -module.exports = breadcrumb; diff --git a/awx/ui/test/e2e/objects/sections/createFormSection.js b/awx/ui/test/e2e/objects/sections/createFormSection.js deleted file mode 100644 index 8d823b92370c..000000000000 --- a/awx/ui/test/e2e/objects/sections/createFormSection.js +++ /dev/null @@ -1,136 +0,0 @@ -import { merge } from 'lodash'; - -const translated = "translate(text(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')"; -const normalized = `normalize-space(${translated})`; - -const inputContainerElements = { - lookup: 'button > i[class="fa fa-search"]', - error: '.at-InputMessage--rejected', - help: 'i[class$="fa-question-circle"]', - hint: '.at-InputLabel-hint', - label: 'label', - popover: '.at-Popover-container', - yaml: 'input[type="radio", value="yaml"]', - json: 'input[type="radio", value="json"]', - reset: 'a[class~="reset"]', - down: 'span[class^="fa-angle-down"]', - up: 'span[class^="fa-angle-up"]', - prompt: { - locateStrategy: 'xpath', - selector: `.//p[${normalized}='prompt on launch']/preceding-sibling::input` - }, - show: { - locateStrategy: 'xpath', - selector: './/i[contains(@class, "fa fa-eye")]' - }, - hide: { - locateStrategy: 'xpath', - selector: './/i[contains(@class, "fa fa-eye-slash")]' - }, - on: { - locateStrategy: 'xpath', - selector: `.//button[${normalized}='on']` - }, - off: { - locateStrategy: 'xpath', - selector: `.//button[${normalized}='off']` - }, - replace: { - locateStrategy: 'xpath', - selector: './/i[contains(@class, "fa fa-undo")]' - }, - revert: { - locateStrategy: 'xpath', - selector: './/i[contains(@class, "fa fa-undo fa-flip-horizontal")]' - } -}; - -const legacyContainerElements = merge({}, inputContainerElements, { - prompt: { - locateStrategy: 'xpath', - selector: `.//label[${normalized}='prompt on launch']/input` - }, - error: 'div[class~="error"]', - popover: ':root div[id^="popover"]', -}); - -const generateInputSelectors = (label, containerElements) => { - // descend until span with matching text attribute is encountered - const span = `.//span[text()="${label}"]`; - // recurse upward until div with form-group in class attribute is encountered - const container = `${span}/ancestor::div[contains(@class, 'form-group')]`; - // descend until element with form-control in class attribute is encountered - const input = `${container}//*[contains(@class, 'form-control')]`; - - const inputContainer = { - locateStrategy: 'xpath', - selector: container, - elements: containerElements - }; - - const inputElement = { - locateStrategy: 'xpath', - selector: input - }; - - return { inputElement, inputContainer }; -}; - -function checkAllFieldsDisabled () { - const client = this.client.api; - - const selectors = this.props.formElementSelectors ? this.props.formElementSelectors : [ - '.at-Input' - ]; - - selectors.forEach(selector => { - client.elements('css selector', selector, inputs => { - inputs.value.map(o => o.ELEMENT).forEach(id => { - if (selector.includes('atSwitch')) { - client.elementIdAttribute(id, 'class', ({ value }) => { - const isDisabled = value && value.includes('atSwitch-disabled'); - client.assert.equal(isDisabled, true); - }); - } else { - client.elementIdAttribute(id, 'disabled', ({ value }) => { - client.assert.equal(value, 'true'); - }); - } - }); - }); - }); -} - -const generatorOptions = { - default: inputContainerElements, - legacy: legacyContainerElements -}; - -const createFormSection = ({ selector, labels, strategy, props }) => { - const options = generatorOptions[strategy || 'default']; - - const formSection = { - props, - selector, - sections: {}, - elements: {}, - commands: [{ checkAllFieldsDisabled }] - }; - - if (!labels) { - return formSection; - } - - Object.keys(labels) - .forEach(key => { - const label = labels[key]; - const { inputElement, inputContainer } = generateInputSelectors(label, options); - - formSection.elements[key] = inputElement; - formSection.sections[key] = inputContainer; - }); - - return formSection; -}; - -module.exports = createFormSection; diff --git a/awx/ui/test/e2e/objects/sections/createTableSection.js b/awx/ui/test/e2e/objects/sections/createTableSection.js deleted file mode 100644 index 96607940e50e..000000000000 --- a/awx/ui/test/e2e/objects/sections/createTableSection.js +++ /dev/null @@ -1,76 +0,0 @@ -import dynamicSection from './dynamicSection'; - -const header = { - selector: 'thead', - sections: { - dynamicSection - }, - commands: [{ - findColumnByText (text) { - return this.section.dynamicSection.create({ - name: `column[${text}]`, - locateStrategy: 'xpath', - selector: `.//*[normalize-space(text())='${text}']/ancestor-or-self::th`, - elements: { - sortable: { - locateStrategy: 'xpath', - selector: './/*[contains(@class, "fa-sort")]' - }, - sorted: { - locateStrategy: 'xpath', - selector: './/*[contains(@class, "fa-sort-")]' - }, - } - }); - } - }] -}; - -const createTableSection = ({ elements, sections, commands }) => { - const tableSection = { - selector: 'table', - sections: { - header, - dynamicSection - }, - commands: [{ - findRowByText (text) { - return this.section.dynamicSection.create({ - elements, - sections, - commands, - name: `row[${text}]`, - locateStrategy: 'xpath', - selector: `.//tbody/tr/td//*[normalize-space(text())='${text}']/ancestor::tr` - }); - }, - findRowByIndex (index) { - return this.section.dynamicSection.create({ - elements, - sections, - commands, - name: `row[${index}]`, - locateStrategy: 'xpath', - selector: `.//tbody/tr[${index}]` - }); - }, - clickRowByIndex (index) { - this.findRowByIndex(index).click('@self'); - return this; - }, - waitForRowCount (count) { - const countReached = this.findRowByIndex(count); - countReached.waitForElementVisible('@self', 10000); - - const countExceeded = this.findRowByIndex(count + 1); - countExceeded.waitForElementNotPresent('@self', 10000); - - return this; - } - }] - }; - - return tableSection; -}; - -module.exports = createTableSection; diff --git a/awx/ui/test/e2e/objects/sections/dynamicSection.js b/awx/ui/test/e2e/objects/sections/dynamicSection.js deleted file mode 100644 index 347806631829..000000000000 --- a/awx/ui/test/e2e/objects/sections/dynamicSection.js +++ /dev/null @@ -1,26 +0,0 @@ -const dynamicSection = { - selector: '.', - commands: [{ - create ({ name, locateStrategy, selector, elements, sections, commands }) { - const Section = this.constructor; - - const options = Object.assign(Object.create(this), { - name, - locateStrategy, - elements, - selector, - sections, - commands - }); - - options.elements.self = { - locateStrategy: 'xpath', - selector: '.' - }; - - return new Section(options); - } - }] -}; - -module.exports = dynamicSection; diff --git a/awx/ui/test/e2e/objects/sections/header.js b/awx/ui/test/e2e/objects/sections/header.js deleted file mode 100644 index ceea0f004bda..000000000000 --- a/awx/ui/test/e2e/objects/sections/header.js +++ /dev/null @@ -1,11 +0,0 @@ -const header = { - selector: 'div[class="at-Layout-topNav"]', - elements: { - logo: 'div[class$="logo"] img', - user: 'i[class="fa fa-user"] + span', - documentation: 'i[class="fa fa-book"]', - logout: 'i[class="fa fa-power-off"]', - } -}; - -module.exports = header; diff --git a/awx/ui/test/e2e/objects/sections/lookupModal.js b/awx/ui/test/e2e/objects/sections/lookupModal.js deleted file mode 100644 index 4f345113236f..000000000000 --- a/awx/ui/test/e2e/objects/sections/lookupModal.js +++ /dev/null @@ -1,27 +0,0 @@ -import createTableSection from './createTableSection'; -import pagination from './pagination'; -import search from './search'; - -const lookupModal = { - selector: '#form-modal', - elements: { - close: 'i[class="fa fa-times-circle"]', - title: 'div[class^="Form-title"]', - select: 'button[class*="save"]', - cancel: 'button[class*="cancel"]', - save: 'button[class*="save"]' - }, - sections: { - search, - pagination, - table: createTableSection({ - elements: { - name: 'td[class~="name-column"]', - selected: 'input[type="radio", value="1"]', - } - - }) - } -}; - -module.exports = lookupModal; diff --git a/awx/ui/test/e2e/objects/sections/navigation.js b/awx/ui/test/e2e/objects/sections/navigation.js deleted file mode 100644 index 2f5c9b3a3157..000000000000 --- a/awx/ui/test/e2e/objects/sections/navigation.js +++ /dev/null @@ -1,28 +0,0 @@ -const navigation = { - selector: 'div[class^="at-Layout-side"]', - elements: { - expand: 'i[class*="fa-bars"]', - dashboard: 'i[class*="fa-tachometer"]', - jobs: 'i[class*="fa-spinner"]', - schedules: 'i[class*="fa-calendar"]', - portal: 'i[class*="fa-columns"]', - projects: 'i[class*="fa-folder-open"]', - credentials: 'i[class*="fa-key"]', - credentialTypes: 'i[class*="fa-list-alt"]', - inventories: 'i[class*="fa-sitemap"]', - templates: 'i[class*="fa-pencil-square-o"]', - organizations: 'i[class*="fa-building"]', - users: 'i[class*="fa-user"]', - teams: 'i[class*="fa-users"]', - inventoryScripts: 'i[class*="fa-code"]', - notifications: 'i[class*="fa-bell"]', - managementJobs: 'i[class*="fa-wrench"]', - instanceGroups: 'i[class*="fa-server"]', - settings: 'i[class*="fa-cog"]', - settingsSubPane: '.at-SettingsSubPane', - settingsSubPaneSystem: 'a[href="#/settings/system"]', - settingsSubPaneAuth: 'a[href="#/settings/auth"]' - } -}; - -module.exports = navigation; diff --git a/awx/ui/test/e2e/objects/sections/pagination.js b/awx/ui/test/e2e/objects/sections/pagination.js deleted file mode 100644 index 5973ff2dd44a..000000000000 --- a/awx/ui/test/e2e/objects/sections/pagination.js +++ /dev/null @@ -1,13 +0,0 @@ -const pagination = { - selector: 'paginate div', - elements: { - first: 'i[class="fa fa-angle-double-left"]', - previous: 'i[class="fa fa-angle-left"]', - next: 'i[class="fa fa-angle-right"]', - last: 'i[class="fa fa-angle-double-right"]', - pageCount: 'span[class~="pageof"]', - itemCount: 'span[class~="itemsOf"]', - } -}; - -module.exports = pagination; diff --git a/awx/ui/test/e2e/objects/sections/permissions.js b/awx/ui/test/e2e/objects/sections/permissions.js deleted file mode 100644 index 958568157ba4..000000000000 --- a/awx/ui/test/e2e/objects/sections/permissions.js +++ /dev/null @@ -1,28 +0,0 @@ -import actions from './actions'; -import createTableSection from './createTableSection'; -import pagination from './pagination'; -import search from './search'; - -const permissions = { - selector: 'div[ui-view="related"]', - elements: { - add: '#button-add', - badge: 'div[class="List-titleBadge]', - titleText: 'div[class="List-titleText"]', - noitems: 'div[class="List-noItems"]' - }, - sections: { - search, - pagination, - table: createTableSection({ - elements: { - username: 'td[class~="username"]', - roles: 'td role-list:nth-of-type(1)', - teamRoles: 'td role-list:nth-of-type(2)' - }, - sections: { actions } - }) - } -}; - -module.exports = permissions; diff --git a/awx/ui/test/e2e/objects/sections/search.js b/awx/ui/test/e2e/objects/sections/search.js deleted file mode 100644 index 64fd557da59e..000000000000 --- a/awx/ui/test/e2e/objects/sections/search.js +++ /dev/null @@ -1,12 +0,0 @@ -const search = { - selector: 'smart-search', - locateStrategy: 'css selector', - elements: { - clearAll: 'a[class*="clearAll"]', - searchButton: 'i[class*="fa-search"]', - input: 'input', - tags: '.SmartSearch-tagContainer' - } -}; - -module.exports = search; diff --git a/awx/ui/test/e2e/objects/teams.js b/awx/ui/test/e2e/objects/teams.js deleted file mode 100644 index 485206b00cf6..000000000000 --- a/awx/ui/test/e2e/objects/teams.js +++ /dev/null @@ -1,82 +0,0 @@ -import actions from './sections/actions'; -import breadcrumb from './sections/breadcrumb'; -import createFormSection from './sections/createFormSection'; -import createTableSection from './sections/createTableSection'; -import header from './sections/header'; -import lookupModal from './sections/lookupModal'; -import navigation from './sections/navigation'; -import pagination from './sections/pagination'; -import permissions from './sections/permissions'; -import search from './sections/search'; - -const details = createFormSection({ - selector: 'form', - props: { - formElementSelectors: [ - '#team_form input.Form-textInput', - '#team_form .Form-lookupButton' - ] - } -}); - -module.exports = { - url () { - return `${this.api.globals.launch_url}/#/teams`; - }, - commands: [{ - load () { - this.api.url('data:,'); // https://github.com/nightwatchjs/nightwatch/issues/1724 - return this.navigate(); - }, - }], - sections: { - header, - navigation, - breadcrumb, - lookupModal, - add: { - selector: 'div[ui-view="form"]', - sections: { - details - }, - elements: { - title: 'div[class^="Form-title"]' - } - }, - edit: { - selector: 'div[ui-view="form"]', - sections: { - details, - permissions - }, - elements: { - title: 'div[class^="Form-title"]' - } - }, - list: { - selector: 'div[ui-view="list"]', - elements: { - badge: 'span[class~="badge"]', - title: 'div[class="List-titleText"]', - add: '#button-add' - }, - sections: { - search, - pagination, - table: createTableSection({ - elements: { - name: 'td[class~="name-column"]', - organization: 'td[class~="organization-column"]' - }, - sections: { - actions - } - }) - } - } - }, - elements: { - cancel: 'button[class*="Form-cancelButton"]', - save: 'button[class*="Form-saveButton"]' - } -}; diff --git a/awx/ui/test/e2e/objects/templates.js b/awx/ui/test/e2e/objects/templates.js deleted file mode 100644 index 47fd84430590..000000000000 --- a/awx/ui/test/e2e/objects/templates.js +++ /dev/null @@ -1,279 +0,0 @@ -import _ from 'lodash'; - -import breadcrumb from './sections/breadcrumb'; -import createFormSection from './sections/createFormSection'; -import header from './sections/header'; -import lookupModal from './sections/lookupModal'; -import navigation from './sections/navigation'; -import pagination from './sections/pagination'; -import permissions from './sections/permissions'; -import search from './sections/search'; - -const jtDetails = createFormSection({ - selector: 'form', - props: { - formElementSelectors: [ - '#job_template_form .Form-textInput', - '#job_template_form select.Form-dropDown', - '#job_template_form .Form-textArea', - '#job_template_form input[type="checkbox"]', - '#job_template_form .ui-spinner-input', - '#job_template_form .atSwitch-inner' - ] - }, - labels: { - name: 'Name', - description: 'Description', - playbook: 'Playbook' - - } -}); - -const wfjtDetails = createFormSection({ - selector: 'form', - props: { - formElementSelectors: [ - '#workflow_job_template_form .Form-textInput', - '#workflow_job_template_form select.Form-dropDown', - '#workflow_job_template_form .Form-textArea', - '#workflow_job_template_form input[type="checkbox"]', - '#workflow_job_template_form .ui-spinner-input', - '#workflow_job_template_form .atSwitch-inner' - ] - }, - labels: { - name: 'Name', - description: 'Description' - - } -}); - -const lookupInventory = _.merge({}, lookupModal, { - locateStrategy: 'xpath', - selector: './/div[text()="Select inventory"]/ancestor::div[contains(@class, "modal-content")]' -}); - -const lookupProject = _.merge({}, lookupModal, { - locateStrategy: 'xpath', - selector: './/div[text()="Select project"]/ancestor::div[contains(@class, "modal-content")]' -}); - -module.exports = { - url () { - return `${this.api.globals.launch_url}/#/templates`; - }, - sections: { - header, - navigation, - breadcrumb, - lookupInventory, - lookupProject, - addJobTemplate: { - selector: 'div[ui-view="form"]', - sections: { - jtDetails - }, - elements: { - title: 'div[class^="Form-title"]' - } - }, - editJobTemplate: { - selector: 'div[ui-view="form"]', - sections: { - jtDetails, - permissions - }, - elements: { - title: 'div[class^="Form-title"]' - } - }, - addWorkflowJobTemplate: { - selector: 'div[ui-view="form"]', - sections: { - wfjtDetails - }, - elements: { - title: 'div[class^="Form-title"]', - visualizerButton: '#workflow_job_template_workflow_visualizer_btn', - } - }, - editWorkflowJobTemplate: { - selector: 'div[ui-view="form"]', - sections: { - wfjtDetails, - permissions - }, - elements: { - title: 'div[class^="Form-title"]', - visualizerButton: '#workflow_job_template_workflow_visualizer_btn', - } - }, - list: { - selector: '.at-Panel', - elements: { - badge: 'span[class~="badge"]', - title: 'div[class="List-titleText"]', - add: '#button-add' - }, - sections: { - search, - pagination - } - } - }, - elements: { - cancel: 'button[class*="Form-cancelButton"]', - save: 'button[class*="Form-saveButton"]' - }, - commands: [{ - load () { - this.api.url('data:,'); // https://github.com/nightwatchjs/nightwatch/issues/1724 - return this.navigate(); - }, - clickWhenEnabled (selector) { - this.api.waitForElementVisible(selector); - this.expect.element(selector).enabled; - this.click(selector); - return this; - }, - selectAdd (name) { - this.clickWhenEnabled('#button-add'); - - this.api - .useXpath() - .waitForElementVisible(`.//a[normalize-space(text())="${name}"]`) - .click(`//a[normalize-space(text())="${name}"]`) - .useCss(); - - return this; - }, - selectPlaybook (name) { - this.clickWhenEnabled('label[for="playbook"] + div span[class$="arrow"]'); - - this.api - .useXpath() - .waitForElementVisible(`//li[contains(text(), "${name}")]`) - .click(`//li[contains(text(), "${name}")]`) - .useCss(); - - return this; - }, - selectInventory (name) { - this.clickWhenEnabled('label[for="inventory"] + div i[class$="search"]'); - - this.api - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny'); - - this.section.lookupInventory.section.search - .waitForElementVisible('@input') - .waitForElementVisible('@searchButton') - .sendKeys('@input', name) - .click('@searchButton') - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny'); - - this.api - .waitForElementNotPresent('#inventories_table .List-tableRow:nth-child(2)') - .waitForElementVisible('#inventories_table .List-tableRow:nth-child(1) input[type="radio"]') - .click('#inventories_table .List-tableRow:nth-child(1) input[type="radio"]'); - - this.section.lookupInventory.expect.element('@save').enabled; - - this.section.lookupInventory - .click('@save'); - - return this; - }, - selectProject (name) { - this.clickWhenEnabled('label[for="project"] + div i[class$="search"]'); - - this.api - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny'); - - this.section.lookupProject.section.search - .waitForElementVisible('@input') - .waitForElementVisible('@searchButton') - .sendKeys('@input', name) - .click('@searchButton') - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny'); - - this.api - .waitForElementNotPresent('#projects_table .List-tableRow:nth-child(2)') - .waitForElementVisible('#projects_table .List-tableRow:nth-child(1) input[type="radio"]') - .click('#projects_table .List-tableRow:nth-child(1) input[type="radio"]'); - - this.section.lookupProject.expect.element('@save').enabled; - - this.section.lookupProject - .click('@save') - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny'); - - return this; - }, - selectVaultCredential (name) { - this.clickWhenEnabled('label[for="credential"] + div i[class$="search"]'); - - this.api - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny') - .waitForElementVisible('#multi-credential-kind-select + span span[class$="arrow"]') - .click('#multi-credential-kind-select + span span[class$="arrow"]') - .useXpath() - .waitForElementVisible('//li[contains(text(), "Vault")]') - .click('//li[contains(text(), "Vault")]') - .useCss() - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny') - .waitForElementVisible('multi-credential-modal smart-search input') - .waitForElementVisible('multi-credential-modal smart-search i[class$="search"]') - .sendKeys('multi-credential-modal smart-search input', name) - .click('multi-credential-modal smart-search i[class$="search"]') - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny') - .click('multi-credential-modal smart-search a[class*="clear"]') - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny') - .sendKeys('multi-credential-modal smart-search input', name) - .click('multi-credential-modal smart-search i[class$="search"]') - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny') - .waitForElementNotPresent('multi-credential-modal .List-tableRow:nth-child(2)') - .waitForElementVisible('multi-credential-modal .List-tableRow:nth-child(1) input[type="checkbox"]') - .click('multi-credential-modal .List-tableRow:nth-child(1) input[type="checkbox"]') - .click('multi-credential-modal button[class*="save"]') - .pause(1000); - - return this; - }, - selectMachineCredential (name) { - this.clickWhenEnabled('label[for="credential"] + div i[class$="search"]'); - - this.api - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny') - .waitForElementVisible('#multi-credential-kind-select + span span[class$="arrow"]') - .click('#multi-credential-kind-select + span span[class$="arrow"]') - .useXpath() - .waitForElementVisible('//li[contains(text(), "Machine")]') - .click('//li[contains(text(), "Machine")]') - .useCss() - .waitForElementVisible('multi-credential-modal smart-search input') - .waitForElementVisible('multi-credential-modal smart-search i[class$="search"]') - .sendKeys('multi-credential-modal smart-search input', name) - .click('multi-credential-modal smart-search i[class$="search"]') - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny') - .waitForElementNotPresent('multi-credential-modal .List-tableRow:nth-child(2)') - .waitForElementVisible('multi-credential-modal .List-tableRow:nth-child(1) input[type="radio"]') - .click('multi-credential-modal .List-tableRow:nth-child(1) input[type="radio"]') - .click('multi-credential-modal button[class*="save"]') - .pause(1000); - - return this; - } - }] -}; diff --git a/awx/ui/test/e2e/objects/users.js b/awx/ui/test/e2e/objects/users.js deleted file mode 100644 index 9662fef0199e..000000000000 --- a/awx/ui/test/e2e/objects/users.js +++ /dev/null @@ -1,217 +0,0 @@ -import actions from './sections/actions'; -import breadcrumb from './sections/breadcrumb'; -import createFormSection from './sections/createFormSection'; -import createTableSection from './sections/createTableSection'; -import header from './sections/header'; -import lookupModal from './sections/lookupModal'; -import navigation from './sections/navigation'; -import pagination from './sections/pagination'; -import permissions from './sections/permissions'; -import search from './sections/search'; - -const row = '#users_table .List-tableRow'; - -const details = createFormSection({ - selector: 'form', - props: { - formElementSelectors: [ - '#user_form .Form-textInput', - '#user_form select.Form-dropDown' - ] - } -}); - -const addEditElements = { - title: 'div[class^="Form-title"]', - confirmPassword: '#user_password_confirm_input', - email: '#user_email', - firstName: '#user_first_name', - lastName: '#user_last_name', - organization: 'input[name="organization_name"]', - password: '#user_password_input', - save: '#user_save_btn', - username: '#user_username', - type: '#select2-user_user_type-container', -}; - -module.exports = { - url () { - return `${this.api.globals.launch_url}/#/users`; - }, - commands: [{ - load () { - this.api.url('data:,'); // https://github.com/nightwatchjs/nightwatch/issues/1724 - return this.navigate(); - }, - create (user, organization) { - this.section.list - .waitForElementVisible('@add') - .click('@add'); - this.section.add - .waitForElementVisible('@title') - .setValue('@organization', organization.name) - .setValue('@email', user.email) - .setValue('@username', user.username) - .setValue('@password', user.password) - .setValue('@confirmPassword', user.password); - if (user.firstName) { - this.section.add.setValue('@firstName', user.firstName); - } - if (user.lastName) { - this.section.add.setValue('@lastName', user.lastName); - } - if (user.type) { - this.section.add - .click('@type') - .click(`li[id$=${user.type}]`); - } - this.section.add.click('@save'); - this.waitForSpinny(); - }, - delete (username) { - this.search(username); - const deleteButton = `${row} i[class*="fa-trash-o"]`; - const modalAction = '.modal-dialog #prompt_action_btn'; - this - .waitForElementVisible(deleteButton) - .click(deleteButton) - .waitForElementVisible(modalAction) - .click(modalAction) - .waitForSpinny(); - const searchResults = '.List-searchNoResults'; - this - .waitForElementVisible(searchResults) - .expect.element(searchResults).text.contain('No records matched your search.'); - }, - search (username) { - this.section.list.section.search - .setValue('@input', username) - .click('@searchButton'); - this.waitForSpinny(); - this.waitForElementNotPresent(`${row}:nth-of-type(2)`); - this.expect.element('.List-titleBadge').text.to.contain('1'); - this.expect.element(row).text.contain(username); - }, - createToken (username, token, callback) { - this.search(username); - const editButton = `${row} i[class*="fa-pencil"]`; - this - .waitForElementVisible(editButton) - .click(editButton); - this.section.tokens - .waitForElementVisible('@tokensTab') - .click('@tokensTab') - .waitForSpinny() - .click('@add'); - const { createToken } = this.section; - createToken.waitForElementVisible('@application'); - this.expect.element(this.section.breadcrumb.selector).text.to.contain(`USERS ${username} TOKENS CREATE TOKEN`); - if (token.application) { - createToken.setValue('@application', token.application); - } - if (token.description) { - createToken.setValue('@description', token.description); - } - if (token.scope) { - createToken - .click('@scope') - .click(`option[label=${token.scope}]`); - } - createToken.click('@saveButton'); - this.waitForSpinny() - .waitForElementVisible('#alert-modal-msg'); - - const tokenInfo = { - token: null, - refreshToken: null, - expires: null, - }; - - this.getText( - '#alert-modal-msg .PopupModal:nth-of-type(1) .PopupModal-value', - (result) => { tokenInfo.token = result.value; } - ); - this.getText( - '#alert-modal-msg .PopupModal:nth-of-type(2) .PopupModal-value', - (result) => { tokenInfo.refreshToken = result.value; } - ); - this.getText( - '#alert-modal-msg .PopupModal:nth-of-type(3) .PopupModal-value', - (result) => { tokenInfo.expires = result.value; } - ); - - this.findThenClick('#alert_ok_btn', 'css') - .waitForElementNotVisible('#alert-modal-msg'); - - this.api.perform(() => { - if (typeof callback === 'function') { - callback.call(this.api, tokenInfo); - } - }); - }, - }], - sections: { - header, - navigation, - breadcrumb, - lookupModal, - add: { - selector: 'div[ui-view="form"]', - sections: { - details - }, - elements: addEditElements, - }, - edit: { - selector: 'div[ui-view="form"]', - sections: { - details, - permissions - }, - elements: addEditElements, - }, - list: { - selector: 'div[ui-view="list"]', - elements: { - badge: 'span[class~="badge"]', - title: 'div[class="List-titleText"]', - add: '#button-add' - }, - sections: { - search, - pagination, - table: createTableSection({ - elements: { - username: 'td[class~="username-column"]', - first_name: 'td[class~="first_name-column"]', - last_name: 'td[class~="last_name-column"]' - }, - sections: { - actions - } - }) - } - }, - tokens: { - selector: 'div[ui-view="form"]', - elements: { - add: '#button-add', - tokensTab: '#tokens_tab', - }, - }, - createToken: { - selector: 'div[ui-view="preFormView"]', - elements: { - application: 'input[tabindex="1"]', - description: 'input[tabindex="2"]', - scope: 'select[tabindex="3"]', - cancelButton: 'button[type="cancel"]', - saveButton: 'button[type="save"]', - } - }, - }, - elements: { - cancel: 'button[class*="Form-cancelButton"]', - save: 'button[class*="Form-saveButton"]' - } -}; diff --git a/awx/ui/test/e2e/runner.js b/awx/ui/test/e2e/runner.js deleted file mode 100755 index 494d059abf3d..000000000000 --- a/awx/ui/test/e2e/runner.js +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env node -require('babel-core/register'); -require('nightwatch/bin/runner.js'); diff --git a/awx/ui/test/e2e/settings.js b/awx/ui/test/e2e/settings.js deleted file mode 100644 index 6fd9f54a6523..000000000000 --- a/awx/ui/test/e2e/settings.js +++ /dev/null @@ -1,39 +0,0 @@ -const normalizeURL = s => s - .replace(/([^:]\/)\/+/g, '$1') // remove duplicate slashes - .replace(/\/+$/, ''); // remove trailing slash if there is one - -const AWX_E2E_CLUSTER_HOST = process.env.AWX_E2E_CLUSTER_HOST || 'localhost'; -const AWX_E2E_CLUSTER_PORT = process.env.AWX_E2E_CLUSTER_PORT || 4444; -const AWX_E2E_CLUSTER_WORKERS = process.env.AWX_E2E_CLUSTER_WORKERS || 0; -const AWX_E2E_PASSWORD = process.env.AWX_E2E_PASSWORD || 'password'; -const AWX_E2E_URL = normalizeURL(process.env.AWX_E2E_URL || 'https://localhost:8043'); -const AWX_E2E_USERNAME = process.env.AWX_E2E_USERNAME || 'awx-e2e'; -const AWX_E2E_TIMEOUT_ASYNC = process.env.AWX_E2E_TIMEOUT_ASYNC || 120000; -const AWX_E2E_TIMEOUT_LONG = process.env.AWX_E2E_TIMEOUT_LONG || 10000; -const AWX_E2E_TIMEOUT_MEDIUM = process.env.AWX_E2E_TIMEOUT_MEDIUM || 5000; -const AWX_E2E_TIMEOUT_SHORT = process.env.AWX_E2E_TIMEOUT_SHORT || 1000; -const AWX_E2E_LAUNCH_URL = normalizeURL(process.env.AWX_E2E_LAUNCH_URL || AWX_E2E_URL); - -// Screenshot capture settings -const AWX_E2E_SCREENSHOTS_ENABLED = process.env.AWX_E2E_SCREENSHOTS_ENABLED || false; -const AWX_E2E_SCREENSHOTS_ON_ERROR = process.env.AWX_E2E_SCREENSHOTS_ON_ERROR || true; -const AWX_E2E_SCREENSHOTS_ON_FAILURE = process.env.AWX_E2E_SCREENSHOTS_ON_FAILURE || true; -const AWX_E2E_SCREENSHOTS_PATH = process.env.AWX_E2E_SCREENSHOTS_PATH || ''; - -module.exports = { - AWX_E2E_CLUSTER_HOST, - AWX_E2E_CLUSTER_PORT, - AWX_E2E_CLUSTER_WORKERS, - AWX_E2E_LAUNCH_URL, - AWX_E2E_PASSWORD, - AWX_E2E_URL, - AWX_E2E_USERNAME, - AWX_E2E_TIMEOUT_ASYNC, - AWX_E2E_TIMEOUT_LONG, - AWX_E2E_TIMEOUT_MEDIUM, - AWX_E2E_TIMEOUT_SHORT, - AWX_E2E_SCREENSHOTS_ENABLED, - AWX_E2E_SCREENSHOTS_ON_ERROR, - AWX_E2E_SCREENSHOTS_ON_FAILURE, - AWX_E2E_SCREENSHOTS_PATH, -}; diff --git a/awx/ui/test/e2e/tests/gce.alt.json b/awx/ui/test/e2e/tests/gce.alt.json deleted file mode 100644 index 1080223f07a3..000000000000 --- a/awx/ui/test/e2e/tests/gce.alt.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "type": "service_account", - "project_id": "test321", - "private_key_id": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", - "private_key": "-----BEGIN PRIVATE KEY-----\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\nBBBBBBBBBBBBBBBBBBBBBBBB\n-----END PRIVATE KEY-----\n", - "client_email": "test321.iam.gserviceaccount.com", - "client_id": "321987654321987654321", - "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "token_uri": "https://accounts.google.com/o/oauth2/token", - "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", - "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/test321%40test.iam.gserviceaccount.com" -} diff --git a/awx/ui/test/e2e/tests/gce.invalid.json b/awx/ui/test/e2e/tests/gce.invalid.json deleted file mode 100644 index 9977a2836c1a..000000000000 --- a/awx/ui/test/e2e/tests/gce.invalid.json +++ /dev/null @@ -1 +0,0 @@ -invalid diff --git a/awx/ui/test/e2e/tests/gce.json b/awx/ui/test/e2e/tests/gce.json deleted file mode 100644 index dd3055f05982..000000000000 --- a/awx/ui/test/e2e/tests/gce.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "type": "service_account", - "project_id": "test123", - "private_key_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "private_key": "-----BEGIN PRIVATE KEY-----\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAA\n-----END PRIVATE KEY-----\n", - "client_email": "test123.iam.gserviceaccount.com", - "client_id": "123456789123456789123", - "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "token_uri": "https://accounts.google.com/o/oauth2/token", - "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", - "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/test123%40test.iam.gserviceaccount.com" -} diff --git a/awx/ui/test/e2e/tests/gce.missing.json b/awx/ui/test/e2e/tests/gce.missing.json deleted file mode 100644 index 5d0d1e2968be..000000000000 --- a/awx/ui/test/e2e/tests/gce.missing.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "type": "service_account", - "project_id": "test654", - "private_key_id": "cccccccccccccccccccccccccccccccccccccccc", - "private_key": "-----BEGIN PRIVATE KEY-----\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\nCCCCCCCCCCCCCCCCCCCCCCCC\n-----END PRIVATE KEY-----\n", - "client_id": "654321987654321987654", - "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "token_uri": "https://accounts.google.com/o/oauth2/token", - "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", - "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/test654%40test.iam.gserviceaccount.com" -} diff --git a/awx/ui/test/e2e/tests/smoke-vars.yml b/awx/ui/test/e2e/tests/smoke-vars.yml deleted file mode 100644 index 7834c3944fad..000000000000 --- a/awx/ui/test/e2e/tests/smoke-vars.yml +++ /dev/null @@ -1,7 +0,0 @@ -$ANSIBLE_VAULT;1.1;AES256 -36663862313439656164323336396466323737666532643061323738646337623166653338663939 -6365663839623936363566393461643961363963613333650a333463653831386463646564376265 -39376362376231303064326464303738343136643931336432396638333933356337383866326539 -3830316634306466310a366234333566613733633232623434363434376534663466393833666435 -36373961636564353865653162393266383135363362336139613435323464356136393034636539 -6166663537623261336262363732346238343066393861643134 diff --git a/awx/ui/test/e2e/tests/smoke.js b/awx/ui/test/e2e/tests/smoke.js deleted file mode 100644 index 7b5271523f42..000000000000 --- a/awx/ui/test/e2e/tests/smoke.js +++ /dev/null @@ -1,288 +0,0 @@ -import uuid from 'uuid'; - -const data = {}; - -const initializeData = () => { - const id = uuid().substr(0, 8); - - data.INVENTORY_NAME = `inventory-${id}`; - data.MACHINE_CREDENTIAL_NAME = `credential-machine-${id}`; - data.ORGANIZATION_NAME = `organization-${id}`; - data.PROJECT_NAME = `project-${id}`; - data.PROJECT_URL = 'https://github.com/ansible/test-playbooks'; - data.PROJECT_BRANCH = 'master'; - data.PLAYBOOK_NAME = 'multivault.yml'; - data.TEMPLATE_NAME = `template-${id}`; - data.VAULT_CREDENTIAL_NAME_1 = `credential-vault-${id}-1`; - data.VAULT_CREDENTIAL_NAME_2 = `credential-vault-${id}-2`; -}; - -module.exports = { - 'login to awx': client => { - initializeData(); - - client.login(); - client.waitForAngular(); - }, - 'create organization': client => { - const organizations = client.page.organizations(); - const { details } = organizations.section.add.section; - - organizations.section.navigation.waitForElementVisible('@organizations'); - organizations.section.navigation.expect.element('@organizations').enabled; - organizations.section.navigation.click('@organizations'); - - organizations.waitForElementVisible('div.spinny'); - organizations.waitForElementNotVisible('div.spinny'); - - organizations.section.list.expect.element('@add').visible; - organizations.section.list.expect.element('@add').enabled; - organizations.section.list.click('@add'); - - details.waitForElementVisible('@name'); - details.expect.element('@name').enabled; - - details.setValue('@name', data.ORGANIZATION_NAME); - - organizations.waitForElementVisible('@save'); - organizations.expect.element('@save').enabled; - organizations.click('@save'); - - organizations.waitForElementVisible('div.spinny'); - organizations.waitForElementNotVisible('div.spinny'); - }, - 'create project': client => { - const projects = client.page.projects(); - - projects.section.navigation.waitForElementVisible('@projects'); - projects.section.navigation.expect.element('@projects').enabled; - projects.section.navigation.click('@projects'); - - projects.waitForElementVisible('div.spinny'); - projects.waitForElementNotVisible('div.spinny'); - - projects.section.list.waitForElementVisible('@add'); - projects.section.list.expect.element('@add').enabled; - - client.pause(1000); projects.section.list.click('@add'); - - projects.waitForElementVisible('label[for="name"] + div input'); - projects.waitForElementVisible('label[for="organization"] + div input'); - projects.waitForElementPresent('label[for="scm_type"] + div > div select option[value="git"]'); - - projects.setValue('label[for="name"] + div input', data.PROJECT_NAME); - projects.clearValue('label[for="organization"] + div input'); - projects.setValue('label[for="organization"] + div input', data.ORGANIZATION_NAME); - projects.click('label[for="scm_type"] + div > div select option[value="git"]'); - - projects.waitForElementVisible('.sourceSubForm'); - projects.waitForElementVisible('label[for="scm_url"] + div input'); - projects.waitForElementVisible('label[for="scm_branch"] + div input'); - - projects.setValue('label[for="scm_url"] + div input', data.PROJECT_URL); - projects.setValue('label[for="scm_branch"] + div input', data.PROJECT_BRANCH); - - projects.expect.element('#project_save_btn').enabled; - projects.click('#project_save_btn'); - - projects.waitForElementVisible('div.spinny'); - projects.waitForElementNotVisible('div.spinny'); - - projects.expect.element('smart-search input').enabled; - projects.sendKeys('smart-search input', `${data.PROJECT_NAME}${client.Keys.ENTER}`); - - projects.waitForElementVisible('div.spinny'); - projects.waitForElementNotVisible('div.spinny'); - - projects.waitForElementVisible('div.spinny', 120000); - projects.waitForElementNotVisible('div.spinny'); - projects.expect.element('i[class$="success"]').visible; - - projects.expect.element('#project_cancel_btn').visible; - projects.click('#project_cancel_btn'); - client.refresh(); - client.waitForElementVisible('div.spinny'); - client.waitForElementNotVisible('div.spinny'); - }, - 'create inventory': client => { - const inventories = client.page.inventories(); - const details = inventories.section.addStandardInventory.section.standardInvDetails; - - inventories.section.navigation.waitForElementVisible('@inventories'); - inventories.section.navigation.expect.element('@inventories').enabled; - inventories.section.navigation.click('@inventories'); - - inventories.waitForElementVisible('div.spinny'); - inventories.waitForElementNotVisible('div.spinny'); - - inventories.selectAdd('Inventory'); - - details.waitForElementVisible('@name'); - details.waitForElementVisible('@organization'); - - details.expect.element('@name').enabled; - details.expect.element('@organization').enabled; - - details.setValue('@name', data.INVENTORY_NAME); - details.setValue('@organization', data.ORGANIZATION_NAME); - - inventories.waitForElementVisible('@save'); - inventories.expect.element('@save').enabled; - - inventories.click('@save'); - - inventories.waitForElementVisible('div.spinny'); - inventories.waitForElementNotVisible('div.spinny'); - }, - 'create host': client => { - const addHost = '.hostsList #button-add'; - - client.expect.element('#hosts_tab').enabled; - client.expect.element('#hosts_tab').css('opacity').equal('1'); - client.expect.element('#hosts_tab').css('background-color').contain('255, 255, 255'); - client.click('#hosts_tab'); - - client.waitForElementVisible('div.spinny'); - client.waitForElementNotVisible('div.spinny'); - - client.expect.element('#hosts_tab').css('background-color').contain('100, 105, 114'); - - client.useCss(); - client.waitForElementVisible(addHost); - client.expect.element(addHost).enabled; - client.click(addHost); - - client.waitForElementVisible('#host_name'); - client.sendKeys('#host_name', 'localhost'); - - client.click('div[class="CodeMirror-scroll"]'); - client.sendKeys('.CodeMirror textarea', client.Keys.ENTER); - client.sendKeys('.CodeMirror textarea', 'ansible_connection: local'); - client.click('#host_variables_parse_type label[class$="hollow"]'); - client.click('#host_variables_parse_type label[class$="hollow"]'); - - client.expect.element('#host_save_btn').enabled; - client.click('#host_save_btn'); - - client.waitForElementVisible('div.spinny'); - client.waitForElementNotVisible('div.spinny'); - }, - 'create vault credentials': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - - client.navigateTo(`${credentials.url()}/add`); - - details.waitForElementVisible('@save'); - details.clearAndSelectType('Vault'); - details.setValue('@organization', data.ORGANIZATION_NAME); - details.setValue('@name', data.VAULT_CREDENTIAL_NAME_1); - - details.section.vault.setValue('@vaultPassword', 'secret1'); - details.section.vault.setValue('@vaultIdentifier', 'first'); - - details.expect.element('@save').enabled; - details.click('@save'); - - credentials.waitForElementVisible('div.spinny'); - credentials.waitForElementNotVisible('div.spinny'); - - client.navigateTo(`${credentials.url()}/add`); - - details.waitForElementVisible('@save'); - details.clearAndSelectType('Vault'); - details.setValue('@organization', data.ORGANIZATION_NAME); - details.setValue('@name', data.VAULT_CREDENTIAL_NAME_2); - - details.section.vault.setValue('@vaultPassword', 'secret2'); - details.section.vault.setValue('@vaultIdentifier', 'second'); - - details.expect.element('@save').enabled; - details.click('@save'); - - credentials.waitForElementVisible('div.spinny'); - credentials.waitForElementNotVisible('div.spinny'); - }, - 'create machine credential': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - - client.navigateTo(`${credentials.url()}/add`); - - details.waitForElementVisible('@save'); - details.clearAndSelectType('Machine'); - details.setValue('@organization', data.ORGANIZATION_NAME); - details.setValue('@name', data.MACHINE_CREDENTIAL_NAME); - - details.expect.element('@save').enabled; - details.click('@save'); - - credentials.waitForElementVisible('div.spinny'); - credentials.waitForElementNotVisible('div.spinny'); - }, - 'create job template': client => { - const templates = client.page.templates(); - - client.navigateTo(templates.url()); - - templates.selectAdd('Job Template'); - templates.selectInventory(data.INVENTORY_NAME); - templates.selectProject(data.PROJECT_NAME); - templates.selectVaultCredential(data.VAULT_CREDENTIAL_NAME_1); - templates.selectVaultCredential(data.VAULT_CREDENTIAL_NAME_2); - templates.selectMachineCredential(data.MACHINE_CREDENTIAL_NAME); - templates.selectPlaybook(data.PLAYBOOK_NAME); - templates.sendKeys('label[for="name"] + div input', data.TEMPLATE_NAME); - - templates.expect.element('#job_template_save_btn').enabled; - templates.click('#job_template_save_btn'); - - templates.waitForElementVisible('div.spinny'); - templates.waitForElementNotVisible('div.spinny'); - - templates.expect.element('#job_template_save_btn').enabled; - }, - 'launch job': client => { - const templates = client.page.templates(); - - templates.waitForElementVisible('smart-search input'); - templates.expect.element('smart-search input').enabled; - - client.pause(1000).waitForElementNotVisible('div.spinny'); - templates.sendKeys('smart-search input', `${data.TEMPLATE_NAME}${client.Keys.ENTER}`); - templates.waitForElementVisible('div.spinny'); - templates.waitForElementNotVisible('div.spinny'); - - templates.sendKeys('smart-search input', `${data.TEMPLATE_NAME}${client.Keys.ENTER}`); - templates.waitForElementVisible('div.spinny'); - templates.waitForElementNotVisible('div.spinny'); - - templates.waitForElementPresent('i[class$="launch"]'); - templates.waitForElementNotPresent('i[class$="launch"]:nth-of-type(2)'); - - templates.expect.element('.at-Panel-headingTitleBadge').text.equal('1'); - - templates.waitForElementVisible('i[class$="launch"]'); - templates.click('i[class$="launch"]'); - }, - 'verify expected job results': client => { - const output1 = './/span[normalize-space(text())=\'"first": "First!"\']'; - const output2 = './/span[normalize-space(text())=\'"second": "Second!"\']'; - const running = 'i[class$="icon-job-running"]'; - const success = 'i[class$="icon-job-successful"]'; - - client.waitForElementVisible('div.spinny'); - client.waitForElementNotVisible('div.spinny'); - - client.waitForElementVisible('at-job-details'); - client.waitForElementNotPresent(running, 60000); - client.waitForElementVisible(success, 60000); - - client.useXpath(); - client.waitForElementVisible(output1, 60000); - client.waitForElementVisible(output2, 60000); - client.useCss(); - - client.end(); - } -}; diff --git a/awx/ui/test/e2e/tests/smoke.yml b/awx/ui/test/e2e/tests/smoke.yml deleted file mode 100644 index 10edd0ab0f85..000000000000 --- a/awx/ui/test/e2e/tests/smoke.yml +++ /dev/null @@ -1,25 +0,0 @@ ---- -# ansible-playbook multivault.yml --vault-id var1@prompt --vault-id var2@prompt -# Vault password (var1): secret1 -# Vault password (var2): secret2 - -- hosts: all - gather_facts: false - vars: - first: !vault | - $ANSIBLE_VAULT;1.2;AES256;first - 30326539376633656433636231653132623266336338316462356132366361653566303364353335 - 6665626463633737666336643334353262373836613332650a353531666262636531383430363935 - 33633465306165393538323336323135393730383563653738666163633835383262396135353765 - 6238333837306332630a336538623333313636353363326666613564353666623635373432386162 - 3562 - second: !vault | - $ANSIBLE_VAULT;1.2;AES256;second - 34653738643565633930336534363230343562343362643432616165373034376565353833366361 - 6264346330376564643262643166623164323433336631360a396336353866323663613935383534 - 33643034373439326435373539323433313832366437303764353562653834623966663533613464 - 3961663934613264360a613763346638636566386461333235366335336564353935356232316265 - 3164 - tasks: - - debug: var=first - - debug: var=second diff --git a/awx/ui/test/e2e/tests/test-404-behavior.js b/awx/ui/test/e2e/tests/test-404-behavior.js deleted file mode 100644 index d12181a4ad90..000000000000 --- a/awx/ui/test/e2e/tests/test-404-behavior.js +++ /dev/null @@ -1,44 +0,0 @@ -import { - AWX_E2E_URL, - AWX_E2E_TIMEOUT_MEDIUM -} from '../settings'; - -module.exports = { - before: (client) => { - client - .login() - .waitForAngular(); - }, - 'Test that default the 404 behavior redirects to the dashboard': client => { - client.navigateTo(`${AWX_E2E_URL}#/brokenurl`, false); - client.useXpath().waitForElementVisible('//job-status-graph'); - client.assert.urlContains('#/home'); - }, - 'Test 404 modal for job templates': client => { - client.navigateTo(`${AWX_E2E_URL}#/templates/job_template/99999`, false); - client.expect.element('//*[@id="alert-modal"]') - .to.be.visible.after(AWX_E2E_TIMEOUT_MEDIUM); - client.findThenClick('//*[@id="alert_ok_btn"]'); - }, - 'Test 404 modal for workflow job templates': client => { - client.navigateTo(`${AWX_E2E_URL}#/templates/workflow_job_template/99999`, false); - client.expect.element('//*[@id="alert-modal"]') - .to.be.visible.after(AWX_E2E_TIMEOUT_MEDIUM); - client.findThenClick('//*[@id="alert_ok_btn"]'); - }, - 'Test 404 modal for credentials': client => { - client.navigateTo(`${AWX_E2E_URL}#/credentials/99999`, false); - client.expect.element('//*[@id="alert-modal"]') - .to.be.visible.after(AWX_E2E_TIMEOUT_MEDIUM); - client.findThenClick('//*[@id="alert_ok_btn"]'); - }, - 'Test 404 modal for projects': client => { - client.navigateTo(`${AWX_E2E_URL}#/projects/99999`, false); - client.expect.element('//*[@id="alert-modal"]') - .to.be.visible.after(AWX_E2E_TIMEOUT_MEDIUM); - client.findThenClick('//*[@id="alert_ok_btn"]'); - }, - after: client => { - client.end(); - } -}; diff --git a/awx/ui/test/e2e/tests/test-applications.js b/awx/ui/test/e2e/tests/test-applications.js deleted file mode 100644 index 8d0b191d3f67..000000000000 --- a/awx/ui/test/e2e/tests/test-applications.js +++ /dev/null @@ -1,158 +0,0 @@ -/* Tests for applications. */ -import uuid from 'uuid'; -import { - getOrganization, - getUser -} from '../fixtures'; - -const row = '.at-List-container .at-Row'; -const editLink = `${row} a.ng-binding`; -const testID = uuid().substr(0, 8); -const namespace = 'test-applications'; - -const store = { - organization: { - name: `${namespace}-organization` - }, - application: { - name: `name-${testID}`, - description: `description-${testID}`, - // authorizationGrantType: `authorization-grand-type-${testID}`, - redirectUris: `https://example.com/${testID}/`, - // clientType: `client-type-${testID}`, - }, - adminUser: { - username: `admin-${testID}`, - password: `admin-${testID}`, - isSuperuser: true, - }, -}; - -let data; - -module.exports = { - before: (client, done) => { - const resources = [ - getOrganization(store.organization.name), - getUser( - namespace, - store.adminUser.username, - store.adminUser.password, - store.adminUser.isSuperuser - ), - ]; - Promise.all(resources) - .then(([org]) => { - data = { org }; - done(); - }); - - client.login(); - client.waitForAngular(); - }, - 'create an application': client => { - const applications = client.page.applications(); - applications.load(); - client.waitForSpinny(); - const newApplication = { - name: store.application.name, - redirectUris: store.application.redirectUris, - authorizationGrantType: - applications.props.authorizationGrantTypeOptions.authorizationCode, - clientType: applications.props.clientTypeOptions.confidential, - }; - applications.create(newApplication, store.organization); - applications.search(store.application.name); - }, - 'edit an application': client => { - // Given the application created on the previous test - const applications = client.page.applications(); - applications.load(); - client.waitForSpinny(); - applications.search(store.application.name); - client - .waitForElementVisible(editLink) - .expect.element(editLink).text.to.equal(store.application.name); - client.click(editLink); - applications.section.edit - .waitForElementVisible('@description') - .setValue('@description', store.application.description) - .click('@save'); - client.waitForSpinny(); - applications.load(); - client.waitForSpinny(); - applications.search(store.application.name); - client - .waitForElementVisible(editLink) - .expect.element(editLink).text.to.equal(store.application.name); - client.click(editLink); - applications.section.edit - .waitForElementVisible('@description') - .expect.element('@description').value.to.equal(store.application.description); - }, - 'add a read scoped application token': client => { - client.logout(); - client.login(store.adminUser.username, store.adminUser.password); - - // Given the application created on the previous test - const token = { - application: store.application.name, - description: `Read scoped token for ${store.application.name}`, - scope: 'Read', - }; - const users = client.page.users(); - users.load(); - client.waitForSpinny(); - users.createToken(store.adminUser.username, token, (tokenInfo) => { - const applications = client.page.applications(); - applications.load(); - client.waitForSpinny(); - applications.search(store.application.name); - client.findThenClick(editLink, 'css'); - applications.section.edit - .waitForElementVisible('@tokensTab') - .click('@tokensTab') - .waitForSpinny(); - // FIXME: Search is not properly working, update this when it is working. - applications.section.tokens.expect.element('@list').text.to.contain(`${store.adminUser.username}\n${token.description}\nEXPIRATION ${tokenInfo.expires}`); - }); - }, - 'add a write scoped application token': client => { - // Given the application created on the previous test - const token = { - application: store.application.name, - description: `Write scoped token for ${store.application.name}`, - scope: 'Write', - }; - - const users = client.page.users(); - users.load(); - client.waitForSpinny(); - users.createToken(store.adminUser.username, token, (tokenInfo) => { - const applications = client.page.applications(); - applications.load(); - client.waitForSpinny(); - applications.search(store.application.name); - client.findThenClick(editLink, 'css'); - applications.section.edit - .waitForElementVisible('@tokensTab') - .click('@tokensTab') - .waitForSpinny(); - // FIXME: Search is not properly working, update this when it is working. - applications.section.tokens.expect.element('@list').text.to.contain(`${store.adminUser.username}\n${token.description}\nEXPIRATION ${tokenInfo.expires}`); - }); - - client.logout(); - client.login(); - }, - 'delete an application': client => { - // Given the application created on the create application test - const applications = client.page.applications(); - applications.load(); - client.waitForSpinny(); - applications.delete(store.application.name); - }, - after: client => { - client.end(); - }, -}; diff --git a/awx/ui/test/e2e/tests/test-auditor-read-only-forms.js b/awx/ui/test/e2e/tests/test-auditor-read-only-forms.js deleted file mode 100644 index ede45a1a8e37..000000000000 --- a/awx/ui/test/e2e/tests/test-auditor-read-only-forms.js +++ /dev/null @@ -1,196 +0,0 @@ -import { - getAdminAWSCredential, - getAdminMachineCredential, - getAuditor, - getInventory, - getInventoryScript, - getNotificationTemplate, - getOrganization, - getSmartInventory, - getTeam, - getUpdatedProject, - getUser -} from '../fixtures'; - -const data = {}; - -let credentials; -let inventoryScripts; -let notificationTemplates; -let organizations; -let projects; -// let templates; -let users; -let inventories; -let teams; - -module.exports = { - before: (client, done) => { - const promises = [ - getAuditor().then(obj => { data.auditor = obj; }), - getOrganization().then(obj => { data.organization = obj; }), - getInventory().then(obj => { data.inventory = obj; }), - getInventoryScript().then(obj => { data.inventoryScript = obj; }), - getAdminAWSCredential().then(obj => { data.adminAWSCredential = obj; }), - getAdminMachineCredential().then(obj => { data.adminMachineCredential = obj; }), - getSmartInventory().then(obj => { data.smartInventory = obj; }), - getTeam().then(obj => { data.team = obj; }), - getUser().then(obj => { data.user = obj; }), - getNotificationTemplate().then(obj => { data.notificationTemplate = obj; }), - getUpdatedProject().then(obj => { data.project = obj; }) - ]; - - Promise.all(promises) - .then(() => { - client.useCss(); - - credentials = client.page.credentials(); - inventoryScripts = client.page.inventoryScripts(); - // templates = client.page.templates(); - notificationTemplates = client.page.notificationTemplates(); - organizations = client.page.organizations(); - projects = client.page.projects(); - users = client.page.users(); - inventories = client.page.inventories(); - teams = client.page.teams(); - - client.login(data.auditor.username); - client.waitForAngular(); - - done(); - }); - }, - 'verify an auditor\'s credentials inputs are read-only': client => { - const url = `${credentials.url()}/${data.adminAWSCredential.id}/`; - - client.navigateTo(url); - - credentials.section.edit - .expect.element('@title').text.contain(data.adminAWSCredential.name); - - credentials.section.edit.section.details.checkAllFieldsDisabled(); - }, - 'verify an auditor\'s inventory scripts inputs are read-only': client => { - const url = `${inventoryScripts.url()}/${data.inventoryScript.id}/`; - - client.navigateTo(url); - - inventoryScripts.section.edit - .expect.element('@title').text.contain(data.inventoryScript.name); - - inventoryScripts.section.edit.section.details.checkAllFieldsDisabled(); - }, - 'verify save button hidden from auditor on inventory scripts form': () => { - inventoryScripts.expect.element('@save').to.not.be.visible; - }, - // TODO: re-enable these tests when JT edit has been re-factored to reliably show/remove the - // loading spinner only one time. Without this, we can't tell when all the requisite data is - // available. - // - // 'verify an auditor\'s job template inputs are read-only': function (client) { - // const url = `${templates.url()}/job_template/${data.jobTemplate.id}/`; - // client.navigateTo(url)'' - // - // templates.section.editJobTemplate - // .expect.element('@title').text.contain(data.jobTemplate.name); - // - // templates.section.edit.section.details.checkAllFieldsDisabled(); - // }, - // 'verify save button hidden from auditor on job templates form': function () { - // templates.expect.element('@save').to.not.be.visible; - // }, - 'verify an auditor\'s notification templates inputs are read-only': client => { - const url = `${notificationTemplates.url()}/${data.notificationTemplate.id}/`; - - client.navigateTo(url); - - notificationTemplates.section.edit - .expect.element('@title').text.contain(data.notificationTemplate.name); - - notificationTemplates.section.edit.section.details.checkAllFieldsDisabled(); - }, - 'verify save button hidden from auditor on notification templates page': () => { - notificationTemplates.expect.element('@save').to.not.be.visible; - }, - 'verify an auditor\'s organizations inputs are read-only': client => { - const url = `${organizations.url()}/${data.organization.id}/`; - - client.navigateTo(url); - - organizations.section.edit - .expect.element('@title').text.contain(data.organization.name); - - organizations.section.edit.section.details.checkAllFieldsDisabled(); - }, - 'verify save button hidden from auditor on organizations form': () => { - organizations.expect.element('@save').to.not.be.visible; - }, - 'verify an auditor\'s smart inventory inputs are read-only': client => { - const url = `${inventories.url()}/smart/${data.smartInventory.id}/`; - - client.navigateTo(url); - - inventories.section.editSmartInventory - .expect.element('@title').text.contain(data.smartInventory.name); - - inventories.section.editSmartInventory.section.smartInvDetails.checkAllFieldsDisabled(); - }, - 'verify save button hidden from auditor on smart inventories form': () => { - inventories.expect.element('@save').to.not.be.visible; - }, - 'verify an auditor\'s project inputs are read-only': client => { - const url = `${projects.url()}/${data.project.id}/`; - - client.navigateTo(url); - - projects.section.edit - .expect.element('@title').text.contain(data.project.name); - - projects.section.edit.section.details.checkAllFieldsDisabled(); - }, - 'verify save button hidden from auditor on projects form': () => { - projects.expect.element('@save').to.not.be.visible; - }, - 'verify an auditor\'s standard inventory inputs are read-only': client => { - const url = `${inventories.url()}/inventory/${data.inventory.id}/`; - - client.navigateTo(url); - - inventories.section.editStandardInventory - .expect.element('@title').text.contain(data.inventory.name); - - inventories.section.editStandardInventory.section.standardInvDetails - .checkAllFieldsDisabled(); - }, - 'verify save button hidden from auditor on standard inventory form': () => { - inventories.expect.element('@save').to.not.be.visible; - }, - 'verify an auditor\'s teams inputs are read-only': client => { - const url = `${teams.url()}/${data.team.id}/`; - - client.navigateTo(url); - - teams.section.edit - .expect.element('@title').text.contain(data.team.name); - - teams.section.edit.section.details.checkAllFieldsDisabled(); - }, - 'verify save button hidden from auditor on teams form': () => { - teams.expect.element('@save').to.not.be.visible; - }, - 'verify an auditor\'s user inputs are read-only': client => { - const url = `${users.url()}/${data.user.id}/`; - - client.navigateTo(url); - - users.section.edit - .expect.element('@title').text.contain(data.user.username); - - users.section.edit.section.details.checkAllFieldsDisabled(); - }, - 'verify save button hidden from auditor on users form': client => { - users.expect.element('@save').to.not.be.visible; - - client.end(); - } -}; diff --git a/awx/ui/test/e2e/tests/test-configuration-ldap-fields.js b/awx/ui/test/e2e/tests/test-configuration-ldap-fields.js deleted file mode 100644 index 159b5fcc584b..000000000000 --- a/awx/ui/test/e2e/tests/test-configuration-ldap-fields.js +++ /dev/null @@ -1,60 +0,0 @@ -module.exports = { - 'expected LDAP codemirror fields are rendered when returning from another tab': client => { - const authView = 'div[ui-view="auth"]'; - const ldapForm = '#configuration_ldap_template_form'; - const systemView = 'div[ui-view="system"]'; - - const { navigation } = client.page.dashboard().section; - const configuration = client.page.configuration(); - - client.login(); - client.waitForAngular(); - - navigation - .waitForElementVisible('@settings') - .moveToElement('@settings', 0, 0) - .waitForElementVisible('@settingsSubPaneSystem') - .click('@settingsSubPaneSystem'); - - configuration.waitForElementVisible(systemView); - - navigation - .waitForElementVisible('@settings') - .moveToElement('@settings', 0, 0) - .waitForElementVisible('@settingsSubPane') - .waitForElementVisible('@settingsSubPaneAuth') - .click('@settingsSubPaneAuth'); - - configuration.waitForElementVisible(authView); - - // works as xpath const categoryName = - // `//*[@id="configuration_edit"]/div[1]/div/div/div[4]`; - configuration.selectSubcategory('LDAP'); - configuration.waitForElementVisible(ldapForm); - - const expectedCodemirrorFields = [ - 'AUTH_LDAP_USER_SEARCH', - 'AUTH_LDAP_GROUP_SEARCH', - 'AUTH_LDAP_USER_ATTR_MAP', - 'AUTH_LDAP_GROUP_TYPE_PARAMS', - 'AUTH_LDAP_USER_FLAGS_BY_GROUP', - 'AUTH_LDAP_ORGANIZATION_MAP', - 'AUTH_LDAP_TEAM_MAP', - ]; - - const ldapCodeMirrors = `${ldapForm} div[class^="CodeMirror"] textarea`; - - client.elements('css selector', ldapCodeMirrors, ({ value }) => { - client.assert.equal(value.length, expectedCodemirrorFields.length); - }); - - expectedCodemirrorFields.forEach(fieldName => { - const codemirror = `#cm-${fieldName}-container div[class^="CodeMirror"]`; - - configuration.expect.element(codemirror).visible; - configuration.expect.element(codemirror).enabled; - }); - - client.end(); - }, -}; diff --git a/awx/ui/test/e2e/tests/test-credential-types-add-edit.js b/awx/ui/test/e2e/tests/test-credential-types-add-edit.js deleted file mode 100644 index 6108db871c1c..000000000000 --- a/awx/ui/test/e2e/tests/test-credential-types-add-edit.js +++ /dev/null @@ -1,29 +0,0 @@ -module.exports = { - before: (client, done) => { - const credentialTypes = client.page.credentialTypes(); - - client.login(); - client.waitForAngular(); - - credentialTypes.navigateTo(`${credentialTypes.url()}/add/`); - - credentialTypes.section.add - .waitForElementVisible('@title', done); - }, - 'expected fields are present and enabled': client => { - const credentialTypes = client.page.credentialTypes(); - const { details } = credentialTypes.section.add.section; - - details.expect.element('@name').visible; - details.expect.element('@description').visible; - details.section.inputConfiguration.expect.element('.CodeMirror').visible; - details.section.injectorConfiguration.expect.element('.CodeMirror').visible; - - details.expect.element('@name').enabled; - details.expect.element('@description').enabled; - details.expect.element('@inputConfiguration').enabled; - details.expect.element('@injectorConfiguration').enabled; - - client.end(); - } -}; diff --git a/awx/ui/test/e2e/tests/test-credentials-add-edit-aws.js b/awx/ui/test/e2e/tests/test-credentials-add-edit-aws.js deleted file mode 100644 index 5e1bf4df683c..000000000000 --- a/awx/ui/test/e2e/tests/test-credentials-add-edit-aws.js +++ /dev/null @@ -1,135 +0,0 @@ -import uuid from 'uuid'; - -const testID = uuid().substr(0, 8); - -const store = { - organization: { - name: `org-${testID}` - }, - credential: { - name: `cred-${testID}` - }, -}; - -module.exports = { - before: (client, done) => { - const credentials = client.page.credentials(); - - client.login(); - client.waitForAngular(); - - client.inject( - [store, 'OrganizationModel'], - (_store_, Model) => new Model().http.post({ data: _store_.organization }), - ({ data }) => { store.organization = data; } - ); - - credentials.section.navigation - .waitForElementVisible('@credentials') - .click('@credentials'); - - credentials - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny'); - - credentials.section.list - .waitForElementVisible('@add') - .click('@add'); - - credentials.section.add.section.details - .waitForElementVisible('@save') - .setValue('@name', store.credential.name) - .setValue('@organization', store.organization.name) - .setValue('@type', 'Amazon Web Services', done); - }, - 'expected fields are visible and enabled': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - - details.expect.element('@name').visible; - details.expect.element('@description').visible; - details.expect.element('@organization').visible; - details.expect.element('@type').visible; - details.section.aws.expect.element('@accessKey').visible; - details.section.aws.expect.element('@secretKey').visible; - details.section.aws.expect.element('@securityToken').visible; - - details.expect.element('@name').enabled; - details.expect.element('@description').enabled; - details.expect.element('@organization').enabled; - details.expect.element('@type').enabled; - details.section.aws.expect.element('@accessKey').enabled; - details.section.aws.expect.element('@secretKey').enabled; - details.section.aws.expect.element('@securityToken').enabled; - }, - 'required fields display \'*\'': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - const required = [ - details.section.name, - details.section.type, - details.section.aws.section.accessKey, - details.section.aws.section.secretKey - ]; - - required.forEach(s => { - s.expect.element('@label').text.to.contain('*'); - }); - }, - 'save button becomes enabled after providing required fields': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - - details - .clearAndSelectType('Amazon Web Services') - .setValue('@name', store.credential.name); - - details.expect.element('@save').not.enabled; - details.section.aws.setValue('@accessKey', 'AAAAAAAAAAAAA'); - details.section.aws.setValue('@secretKey', 'AAAAAAAAAAAAA'); - details.expect.element('@save').enabled; - }, - 'create aws credential': client => { - const credentials = client.page.credentials(); - const { add } = credentials.section; - const { edit } = credentials.section; - - add.section.details - .clearAndSelectType('Amazon Web Services') - .setValue('@name', store.credential.name) - .setValue('@organization', store.organization.name); - - add.section.details.section.aws - .setValue('@accessKey', 'ABCD123456789') - .setValue('@secretKey', '987654321DCBA'); - - add.section.details.click('@save'); - - credentials - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny'); - - edit.expect.element('@title').text.equal(store.credential.name); - }, - 'edit details panel remains open after saving': client => { - const credentials = client.page.credentials(); - - credentials.section.edit.expect.section('@details').visible; - }, - 'credential is searchable after saving': client => { - const credentials = client.page.credentials(); - const row = '#credentials_table .List-tableRow'; - - const { search } = credentials.section.list.section; - - search - .waitForElementVisible('@input') - .setValue('@input', `name:${store.credential.name}`) - .click('@searchButton'); - - credentials.waitForElementNotPresent(`${row}:nth-of-type(2)`); - credentials.expect.element(row).text.contain(store.credential.name); - - client.end(); - } -}; diff --git a/awx/ui/test/e2e/tests/test-credentials-add-edit-custom.js b/awx/ui/test/e2e/tests/test-credentials-add-edit-custom.js deleted file mode 100644 index 8aac716e4e2d..000000000000 --- a/awx/ui/test/e2e/tests/test-credentials-add-edit-custom.js +++ /dev/null @@ -1,175 +0,0 @@ -import uuid from 'uuid'; - -const store = { - credentialType: { - name: `credentialType-${uuid().substr(0, 8)}`, - description: 'custom cloud credential', - kind: 'cloud', - inputs: { - fields: [ - { - id: 'project', - label: 'Project', - type: 'string', - help_text: 'Name of your project' - }, - { - id: 'token', - label: 'Token', - secret: true, - type: 'string', - help_text: 'help' - }, - { - id: 'secret_key_data', - label: 'Secret Key', - type: 'string', - secret: true, - multiline: true, - help_text: 'help', - }, - { - id: 'public_key_data', - label: 'Public Key', - type: 'string', - secret: true, - multiline: true, - help_text: 'help', - }, - { - id: 'secret_key_unlock', - label: 'Private Key Passphrase', - type: 'string', - secret: true, - // help_text: 'help' - }, - { - id: 'color', - label: 'Favorite Color', - choices: [ - '', - 'red', - 'orange', - 'yellow', - 'green', - 'blue', - 'indigo', - 'violet' - ], - help_text: 'help', - }, - ], - required: ['project', 'token'] - }, - injectors: { - env: { - CUSTOM_CREDENTIAL_TOKEN: '{{ token }}' - } - } - } -}; - -const { inputs } = store.credentialType; -const { fields } = inputs; -const help = fields.filter(f => f.help_text); -const required = fields.filter(f => inputs.required.indexOf(f.id) > -1); -const strings = fields.filter(f => f.type === undefined || f.type === 'string'); - -const getObjects = client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - const type = details.custom(store.credentialType); - - return { credentials, details, type }; -}; - -module.exports = { - before: (client, done) => { - const credentials = client.page.credentials(); - - client.login(); - client.waitForAngular(); - - client.inject( - [store.credentialType, 'CredentialTypeModel'], - (data, Model) => new Model().http.post({ data }), - ({ data }) => { store.credentialType.response = data; } - ); - - credentials.section.navigation - .waitForElementVisible('@credentials') - .click('@credentials'); - - credentials - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny'); - - credentials.section.list - .waitForElementVisible('@add') - .click('@add'); - - credentials.section.add.section.details - .waitForElementVisible('@save') - .setValue('@name', `cred-${uuid()}`) - .setValue('@type', store.credentialType.name, done); - }, - 'all fields are visible': client => { - const { type } = getObjects(client); - fields.forEach(f => { - type.expect.element(`@${f.id}`).visible; - }); - }, - 'helplinks open popovers showing expected content': client => { - const { type } = getObjects(client); - - help.forEach(f => { - const group = type.section[f.id]; - - group.expect.element('@popover').not.visible; - group.click('@help'); - group.expect.element('@popover').visible; - group.expect.element('@popover').text.to.contain(f.help_text); - group.click('@help'); - }); - - help.forEach(f => { - const group = type.section[f.id]; - group.expect.element('@popover').not.visible; - }); - }, - 'secret field buttons hide and unhide input': client => { - const { type } = getObjects(client); - const secrets = strings.filter(f => f.secret && !f.multiline); - - secrets.forEach(f => { - const group = type.section[f.id]; - const input = `@${f.id}`; - - group.expect.element('@show').visible; - group.expect.element('@hide').not.visible; - - type.setValue(input, 'SECRET'); - type.expect.element(input).text.equal(''); - - group.click('@show'); - group.expect.element('@show').not.visible; - group.expect.element('@hide').visible; - type.expect.element(input).value.contain('SECRET'); - - group.click('@hide'); - group.expect.element('@show').visible; - group.expect.element('@hide').not.visible; - type.expect.element(input).text.equal(''); - }); - }, - 'required fields show * symbol': client => { - const { type } = getObjects(client); - - required.forEach(f => { - const group = type.section[f.id]; - group.expect.element('@label').text.to.contain('*'); - }); - - client.end(); - } -}; diff --git a/awx/ui/test/e2e/tests/test-credentials-add-edit-gce-file.js b/awx/ui/test/e2e/tests/test-credentials-add-edit-gce-file.js deleted file mode 100644 index 725fcfdcde87..000000000000 --- a/awx/ui/test/e2e/tests/test-credentials-add-edit-gce-file.js +++ /dev/null @@ -1,288 +0,0 @@ -import path from 'path'; -import uuid from 'uuid'; - -const GCE_SERVICE_ACCOUNT_FILE = path.resolve(__dirname, 'gce.json'); -const GCE_SERVICE_ACCOUNT_FILE_ALT = path.resolve(__dirname, 'gce.alt.json'); -const GCE_SERVICE_ACCOUNT_FILE_INVALID = path.resolve(__dirname, 'gce.invalid.json'); -const GCE_SERVICE_ACCOUNT_FILE_MISSING = path.resolve(__dirname, 'gce.missing.json'); - -let credentials; - -module.exports = { - before: (client, done) => { - credentials = client.page.credentials(); - - client.login(); - client.waitForAngular(); - - credentials.section.navigation - .waitForElementVisible('@credentials') - .click('@credentials'); - - credentials - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny'); - - credentials.section.list - .waitForElementVisible('@add') - .click('@add'); - - credentials.section.add.section.details - .waitForElementVisible('@save') - .setValue('@name', `credential-${uuid().substr(0, 8)}`) - .setValue('@type', 'Google Compute Engine', done); - }, - 'expected fields are initially visible and enabled': client => { - const { details } = credentials.section.add.section; - const { gce } = details.section; - - details.expect.element('@name').visible; - details.expect.element('@description').visible; - details.expect.element('@organization').visible; - details.expect.element('@type').visible; - - gce.expect.element('@email').visible; - gce.expect.element('@sshKeyData').visible; - gce.expect.element('@project').visible; - gce.expect.element('@serviceAccountFile').visible; - - details.expect.element('@name').enabled; - details.expect.element('@description').enabled; - details.expect.element('@organization').enabled; - details.expect.element('@type').enabled; - - gce.expect.element('@email').enabled; - gce.expect.element('@sshKeyData').enabled; - gce.expect.element('@project').enabled; - gce.expect.element('@serviceAccountFile').enabled; - - gce.section.serviceAccountFile.expect.element('form i[class*="folder"]').visible; - gce.section.serviceAccountFile.expect.element('form i[class*="folder"]').enabled; - gce.section.serviceAccountFile.expect.element('form i[class*="trash"]').not.present; - }, - 'select valid credential file': client => { - const { details } = credentials.section.add.section; - const { gce } = details.section; - - client.pushFileToWorker(GCE_SERVICE_ACCOUNT_FILE, file => { - gce.section.serviceAccountFile.setValue('form input[type="file"]', file); - }); - - gce.expect.element('@email').not.enabled; - gce.expect.element('@sshKeyData').not.enabled; - gce.expect.element('@project').not.enabled; - gce.expect.element('@serviceAccountFile').enabled; - - gce.section.serviceAccountFile.expect.element('form i[class*="trash"]').visible; - gce.section.serviceAccountFile.expect.element('form i[class*="trash"]').enabled; - gce.section.serviceAccountFile.expect.element('form i[class*="folder"]').not.present; - }, - 'deselect valid credential file': client => { - const { details } = credentials.section.add.section; - const { gce } = details.section; - - gce.section.serviceAccountFile.click('form i[class*="trash"]'); - - gce.expect.element('@email').enabled; - gce.expect.element('@sshKeyData').enabled; - gce.expect.element('@project').enabled; - gce.expect.element('@serviceAccountFile').enabled; - - gce.section.serviceAccountFile.expect.element('form i[class*="folder"]').visible; - gce.section.serviceAccountFile.expect.element('form i[class*="folder"]').enabled; - gce.section.serviceAccountFile.expect.element('form i[class*="trash"]').not.present; - - gce.section.email.expect.element('@error').visible; - gce.section.sshKeyData.expect.element('@error').visible; - - gce.section.project.expect.element('@error').not.present; - gce.section.serviceAccountFile.expect.element('@error').not.present; - }, - 'select credential file with missing field': client => { - const { details } = credentials.section.add.section; - const { gce } = details.section; - - client.pushFileToWorker(GCE_SERVICE_ACCOUNT_FILE_MISSING, file => { - gce.section.serviceAccountFile.setValue('form input[type="file"]', file); - }); - - gce.expect.element('@email').not.enabled; - gce.expect.element('@sshKeyData').not.enabled; - gce.expect.element('@project').not.enabled; - gce.expect.element('@serviceAccountFile').enabled; - - gce.section.serviceAccountFile.expect.element('form i[class*="trash"]').visible; - gce.section.serviceAccountFile.expect.element('form i[class*="trash"]').enabled; - gce.section.serviceAccountFile.expect.element('form i[class*="folder"]').not.present; - - gce.section.email.expect.element('@error').visible; - - gce.section.project.expect.element('@error').not.present; - gce.section.serviceAccountFile.expect.element('@error').not.present; - gce.section.sshKeyData.expect.element('@error').not.present; - }, - 'deselect credential file with missing field': client => { - const { details } = credentials.section.add.section; - const { gce } = details.section; - - gce.section.serviceAccountFile.click('form i[class*="trash"]'); - - gce.expect.element('@email').enabled; - gce.expect.element('@sshKeyData').enabled; - gce.expect.element('@project').enabled; - gce.expect.element('@serviceAccountFile').enabled; - - gce.section.serviceAccountFile.expect.element('form i[class*="folder"]').visible; - gce.section.serviceAccountFile.expect.element('form i[class*="folder"]').enabled; - gce.section.serviceAccountFile.expect.element('form i[class*="trash"]').not.present; - - gce.section.email.expect.element('@error').visible; - gce.section.sshKeyData.expect.element('@error').visible; - - gce.section.project.expect.element('@error').not.present; - gce.section.serviceAccountFile.expect.element('@error').not.present; - }, - 'select invalid credential file': client => { - const { details } = credentials.section.add.section; - const { gce } = details.section; - - client.pushFileToWorker(GCE_SERVICE_ACCOUNT_FILE_INVALID, file => { - gce.section.serviceAccountFile.setValue('form input[type="file"]', file); - }); - - gce.expect.element('@email').not.enabled; - gce.expect.element('@sshKeyData').not.enabled; - gce.expect.element('@project').not.enabled; - gce.expect.element('@serviceAccountFile').enabled; - - gce.section.serviceAccountFile.expect.element('form i[class*="trash"]').visible; - gce.section.serviceAccountFile.expect.element('form i[class*="trash"]').enabled; - gce.section.serviceAccountFile.expect.element('form i[class*="folder"]').not.present; - - gce.section.email.expect.element('@error').visible; - gce.section.serviceAccountFile.expect.element('@error').visible; - gce.section.sshKeyData.expect.element('@error').visible; - - gce.section.project.expect.element('@error').not.present; - }, - 'deselect invalid credential file': client => { - const { details } = credentials.section.add.section; - const { gce } = details.section; - - gce.section.serviceAccountFile.click('form i[class*="trash"]'); - - gce.expect.element('@email').enabled; - gce.expect.element('@sshKeyData').enabled; - gce.expect.element('@project').enabled; - gce.expect.element('@serviceAccountFile').enabled; - - gce.section.serviceAccountFile.expect.element('form i[class*="folder"]').visible; - gce.section.serviceAccountFile.expect.element('form i[class*="folder"]').enabled; - gce.section.serviceAccountFile.expect.element('form i[class*="trash"]').not.present; - - gce.section.email.expect.element('@error').visible; - gce.section.sshKeyData.expect.element('@error').visible; - - gce.section.project.expect.element('@error').not.present; - gce.section.serviceAccountFile.expect.element('@error').not.present; - }, - 'save valid credential file': client => { - const add = credentials.section.add.section.details; - const edit = credentials.section.edit.section.details; - - client.pushFileToWorker(GCE_SERVICE_ACCOUNT_FILE, file => { - add.section.gce.section.serviceAccountFile.setValue('form input[type="file"]', file); - }); - - add.section.gce.expect.element('@email').not.enabled; - add.section.gce.expect.element('@sshKeyData').not.enabled; - add.section.gce.expect.element('@project').not.enabled; - add.section.gce.expect.element('@serviceAccountFile').enabled; - - add.section.gce.section.serviceAccountFile.expect.element('form i[class*="trash"]').visible; - add.section.gce.section.serviceAccountFile.expect.element('form i[class*="trash"]').enabled; - add.section.gce.section.serviceAccountFile.expect.element('form i[class*="folder"]').not.present; - - add.click('@save'); - - credentials - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny'); - - edit.section.gce.expect.element('@email').enabled; - edit.section.gce.expect.element('@project').enabled; - - edit.section.gce.expect.element('@serviceAccountFile').not.enabled; - edit.section.gce.expect.element('@sshKeyData').not.enabled; - }, - 'select and deselect credential file when replacing private key': client => { - const taggedTextArea = '.at-InputTaggedTextarea'; - const textArea = '.at-InputTextarea'; - const replace = 'button i[class="fa fa-undo"]'; - const revert = 'button i[class="fa fa-undo fa-flip-horizontal"]'; - const { gce } = credentials.section.edit.section.details.section; - - gce.waitForElementVisible(replace); - // eslint-disable-next-line prefer-arrow-callback - client.execute(function clickReplace (selector) { - document.querySelector(selector).click(); - }, [replace]); - - gce.expect.element('@email').enabled; - gce.expect.element('@project').enabled; - gce.expect.element(textArea).enabled; - gce.expect.element(taggedTextArea).not.present; - gce.expect.element('@serviceAccountFile').enabled; - - gce.section.sshKeyData.expect.element('@error').visible; - - gce.section.email.expect.element('@error').not.present; - gce.section.project.expect.element('@error').not.present; - gce.section.serviceAccountFile.expect.element('@error').not.present; - - client.pushFileToWorker(GCE_SERVICE_ACCOUNT_FILE_ALT, file => { - gce.section.serviceAccountFile.setValue('form input[type="file"]', file); - }); - - gce.expect.element('@serviceAccountFile').enabled; - - gce.expect.element('@email').not.enabled; - gce.expect.element('@sshKeyData').not.enabled; - gce.expect.element('@project').not.enabled; - - gce.section.email.expect.element('@error').not.present; - gce.section.project.expect.element('@error').not.present; - gce.section.sshKeyData.expect.element('@error').not.present; - gce.section.serviceAccountFile.expect.element('@error').not.present; - - gce.expect.element(replace).not.present; - gce.expect.element(revert).present; - gce.expect.element('.input-group-append button').not.enabled; - gce.section.serviceAccountFile.click('form i[class*="trash"]'); - - gce.expect.element('@email').enabled; - gce.expect.element('@sshKeyData').enabled; - gce.expect.element('@project').enabled; - gce.expect.element('@serviceAccountFile').enabled; - - gce.section.sshKeyData.expect.element('@error').visible; - - gce.section.email.expect.element('@error').not.present; - gce.section.project.expect.element('@error').not.present; - gce.section.serviceAccountFile.expect.element('@error').not.present; - - gce.expect.element('.input-group-append button').enabled; - // eslint-disable-next-line prefer-arrow-callback - client.execute(function clickRevert (selector) { - document.querySelector(selector).click(); - }, [revert]); - - gce.expect.element('@email').enabled; - gce.expect.element('@project').enabled; - - gce.expect.element('@serviceAccountFile').not.enabled; - gce.expect.element('@sshKeyData').not.enabled; - - client.end(); - } -}; diff --git a/awx/ui/test/e2e/tests/test-credentials-add-edit-gce.js b/awx/ui/test/e2e/tests/test-credentials-add-edit-gce.js deleted file mode 100644 index f1733f78f58d..000000000000 --- a/awx/ui/test/e2e/tests/test-credentials-add-edit-gce.js +++ /dev/null @@ -1,175 +0,0 @@ -import uuid from 'uuid'; - -import { AWX_E2E_TIMEOUT_LONG } from '../settings'; - -const testID = uuid().substr(0, 8); - -const store = { - organization: { - name: `org-${testID}` - }, - credential: { - name: `cred-${testID}` - }, -}; - -module.exports = { - before: (client, done) => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - - client.login(); - client.waitForAngular(); - - client.inject( - [store, 'OrganizationModel'], - (_store_, Model) => new Model().http.post({ data: _store_.organization }), - ({ data }) => { store.organization = data; } - ); - - credentials.section.navigation - .waitForElementVisible('@credentials') - .click('@credentials'); - - credentials - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny'); - - credentials.section.list - .waitForElementVisible('@add') - .click('@add'); - - details - .waitForElementVisible('@save') - .setValue('@name', store.credential.name) - .setValue('@organization', store.organization.name) - .setValue('@type', 'Google Compute Engine', done); - }, - 'expected fields are visible and enabled': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - - details.expect.element('@name').visible; - details.expect.element('@description').visible; - details.expect.element('@organization').visible; - details.expect.element('@type').visible; - details.section.gce.expect.element('@email').visible; - details.section.gce.expect.element('@sshKeyData').visible; - details.section.gce.expect.element('@project').visible; - - details.expect.element('@name').enabled; - details.expect.element('@description').enabled; - details.expect.element('@organization').enabled; - details.expect.element('@type').enabled; - details.section.gce.expect.element('@email').enabled; - details.section.gce.expect.element('@sshKeyData').enabled; - details.section.gce.expect.element('@project').enabled; - - details.section.organization.expect.element('@lookup').visible; - }, - 'required fields display \'*\'': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - const required = [ - details.section.name, - details.section.type, - details.section.gce.section.email, - details.section.gce.section.sshKeyData - ]; - - required.forEach(s => { - s.expect.element('@label').text.to.contain('*'); - }); - }, - 'save button becomes enabled after providing required fields': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - - details - .clearAndSelectType('Google Compute Engine') - .setValue('@name', store.credential.name); - - details.expect.element('@save').not.enabled; - - details.section.gce - .setValue('@email', 'abc@123.com') - .sendKeys('@sshKeyData', '-----BEGIN RSA PRIVATE KEY-----') - .sendKeys('@sshKeyData', client.Keys.ENTER) - .sendKeys('@sshKeyData', 'AAAA') - .sendKeys('@sshKeyData', client.Keys.ENTER) - .sendKeys('@sshKeyData', '-----END RSA PRIVATE KEY-----'); - - details.expect.element('@save').enabled; - }, - 'error displayed for invalid ssh key data': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - const { sshKeyData } = details.section.gce.section; - - details - .clearAndSelectType('Google Compute Engine') - .setValue('@name', store.credential.name); - - details.section.gce - .setValue('@email', 'abc@123.com') - .setValue('@sshKeyData', 'invalid'); - - details.click('@save'); - - sshKeyData.expect.element('@error').visible; - sshKeyData.expect.element('@error').text.to.contain('Invalid certificate or key'); - - details.section.gce.clearValue('@sshKeyData'); - - sshKeyData.expect.element('@error').visible; - sshKeyData.expect.element('@error').text.to.contain('Please enter a value'); - - details.section.gce.setValue('@sshKeyData', 'AAAA'); - sshKeyData.expect.element('@error').not.present; - }, - 'create gce credential': client => { - const credentials = client.page.credentials(); - const { add } = credentials.section; - const { edit } = credentials.section; - - add.section.details - .clearAndSelectType('Google Compute Engine') - .setValue('@name', store.credential.name) - .setValue('@organization', store.organization.name); - - add.section.details.section.gce - .setValue('@email', 'abc@123.com') - .sendKeys('@sshKeyData', '-----BEGIN RSA PRIVATE KEY-----') - .sendKeys('@sshKeyData', client.Keys.ENTER) - .sendKeys('@sshKeyData', 'AAAA') - .sendKeys('@sshKeyData', client.Keys.ENTER) - .sendKeys('@sshKeyData', '-----END RSA PRIVATE KEY-----'); - - add.section.details.click('@save'); - - credentials - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny'); - - edit.expect.element('@title').text.equal(store.credential.name); - }, - 'edit details panel remains open after saving': client => { - const credentials = client.page.credentials(); - - credentials.section.edit.expect.section('@details').visible; - }, - 'credential is searchable after saving': client => { - const credentials = client.page.credentials(); - const row = '#credentials_table .List-tableRow'; - - credentials.section.list.section.search - .waitForElementVisible('@input', AWX_E2E_TIMEOUT_LONG) - .setValue('@input', `name:${store.credential.name}`) - .click('@searchButton'); - - credentials.waitForElementNotPresent(`${row}:nth-of-type(2)`); - credentials.expect.element(row).text.contain(store.credential.name); - - client.end(); - } -}; diff --git a/awx/ui/test/e2e/tests/test-credentials-add-edit-insights.js b/awx/ui/test/e2e/tests/test-credentials-add-edit-insights.js deleted file mode 100644 index 85e62312c936..000000000000 --- a/awx/ui/test/e2e/tests/test-credentials-add-edit-insights.js +++ /dev/null @@ -1,137 +0,0 @@ -import uuid from 'uuid'; - -import { AWX_E2E_TIMEOUT_LONG } from '../settings'; - -const testID = uuid().substr(0, 8); - -const store = { - organization: { - name: `org-${testID}` - }, - credential: { - name: `cred-${testID}` - }, -}; - -module.exports = { - before: (client, done) => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - - client.login(); - client.waitForAngular(); - - client.inject( - [store, 'OrganizationModel'], - (_store_, Model) => new Model().http.post({ data: _store_.organization }), - ({ data }) => { store.organization = data; } - ); - - credentials.section.navigation - .waitForElementVisible('@credentials') - .click('@credentials'); - - credentials - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny'); - - credentials.section.list - .waitForElementVisible('@add') - .click('@add'); - - details - .waitForElementVisible('@save') - .setValue('@name', store.credential.name) - .setValue('@organization', store.organization.name) - .setValue('@type', 'Insights', done); - }, - 'expected fields are visible and enabled': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - - details.expect.element('@name').visible; - details.expect.element('@description').visible; - details.expect.element('@organization').visible; - details.expect.element('@type').visible; - details.section.insights.expect.element('@username').visible; - details.section.insights.expect.element('@password').visible; - - details.expect.element('@name').enabled; - details.expect.element('@description').enabled; - details.expect.element('@organization').enabled; - details.expect.element('@type').enabled; - details.section.insights.expect.element('@username').enabled; - details.section.insights.expect.element('@password').enabled; - }, - 'required fields display \'*\'': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - const required = [ - details.section.name, - details.section.type, - details.section.insights.section.username, - details.section.insights.section.password - ]; - - required.forEach(s => { - s.expect.element('@label').text.to.contain('*'); - }); - }, - 'save button becomes enabled after providing required fields': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - - details - .clearAndSelectType('Insights') - .setValue('@name', store.credential.name); - - details.expect.element('@save').not.enabled; - - details.section.insights - .setValue('@username', 'wrosellini') - .setValue('@password', 'quintus'); - - details.expect.element('@save').enabled; - }, - 'create insights credential': client => { - const credentials = client.page.credentials(); - const { add } = credentials.section; - const { edit } = credentials.section; - - add.section.details - .clearAndSelectType('Insights') - .setValue('@name', store.credential.name) - .setValue('@organization', store.organization.name); - - add.section.details.section.insights - .setValue('@username', 'wrosellini') - .setValue('@password', 'quintus'); - - add.section.details.click('@save'); - - credentials - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny'); - - edit.expect.element('@title').text.equal(store.credential.name); - }, - 'edit details panel remains open after saving': client => { - const credentials = client.page.credentials(); - - credentials.section.edit.expect.section('@details').visible; - }, - 'credential is searchable after saving': client => { - const credentials = client.page.credentials(); - const row = '#credentials_table .List-tableRow'; - - credentials.section.list.section.search - .waitForElementVisible('@input', AWX_E2E_TIMEOUT_LONG) - .setValue('@input', `name:${store.credential.name}`) - .click('@searchButton'); - - credentials.waitForElementNotPresent(`${row}:nth-of-type(2)`); - credentials.expect.element(row).text.contain(store.credential.name); - - client.end(); - } -}; diff --git a/awx/ui/test/e2e/tests/test-credentials-add-edit-machine.js b/awx/ui/test/e2e/tests/test-credentials-add-edit-machine.js deleted file mode 100644 index 34279e8217ed..000000000000 --- a/awx/ui/test/e2e/tests/test-credentials-add-edit-machine.js +++ /dev/null @@ -1,210 +0,0 @@ -import uuid from 'uuid'; - -import { AWX_E2E_TIMEOUT_LONG } from '../settings'; - -const testID = uuid().substr(0, 8); - -const store = { - organization: { - name: `org-${testID}` - }, - credential: { - name: `cred-${testID}` - }, -}; - -module.exports = { - before: (client, done) => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - - client.login(); - client.waitForAngular(); - - client.inject( - [store, 'OrganizationModel'], - (_store_, Model) => new Model().http.post({ data: _store_.organization }), - ({ data }) => { store.organization = data; } - ); - - credentials.section.navigation - .waitForElementVisible('@credentials') - .click('@credentials'); - - credentials - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny'); - - credentials.section.list - .waitForElementVisible('@add') - .click('@add'); - - details.waitForElementVisible('@save', done); - }, - 'common fields are visible and enabled': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - - details.expect.element('@name').visible; - details.expect.element('@description').visible; - details.expect.element('@organization').visible; - details.expect.element('@type').visible; - - details.expect.element('@name').enabled; - details.expect.element('@description').enabled; - details.expect.element('@organization').enabled; - details.expect.element('@type').enabled; - }, - 'required common fields display \'*\'': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - - details.section.name.expect.element('@label').text.to.contain('*'); - details.section.type.expect.element('@label').text.to.contain('*'); - }, - 'save button becomes enabled after providing required fields': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - - details.expect.element('@save').not.enabled; - - details - .setValue('@name', store.credential.name) - .setValue('@organization', store.organization.name) - .setValue('@type', 'Machine'); - - details.expect.element('@save').enabled; - }, - 'machine credential fields are visible after choosing type': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - const { machine } = details.section; - - machine.expect.element('@username').visible; - machine.expect.element('@password').visible; - machine.expect.element('@becomeUsername').visible; - machine.expect.element('@becomePassword').visible; - machine.expect.element('@sshKeyData').visible; - machine.expect.element('@sshKeyUnlock').visible; - }, - 'error displayed for invalid ssh key data': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - const { sshKeyData } = details.section.machine.section; - - details - .clearAndSelectType('Machine') - .setValue('@name', store.credential.name); - - details.section.machine.setValue('@sshKeyData', 'invalid'); - - details.click('@save'); - - sshKeyData.expect.element('@error').visible; - sshKeyData.expect.element('@error').text.to.contain('Invalid certificate or key'); - - details.section.machine.clearValue('@sshKeyData'); - sshKeyData.expect.element('@error').not.present; - }, - 'error displayed for unencrypted ssh key with passphrase': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - const { sshKeyUnlock } = details.section.machine.section; - - details - .clearAndSelectType('Machine') - .setValue('@name', store.credential.name); - - details.section.machine - .setValue('@sshKeyUnlock', 'password') - .sendKeys('@sshKeyData', '-----BEGIN RSA PRIVATE KEY-----') - .sendKeys('@sshKeyData', client.Keys.ENTER) - .sendKeys('@sshKeyData', 'AAAA') - .sendKeys('@sshKeyData', client.Keys.ENTER) - .sendKeys('@sshKeyData', '-----END RSA PRIVATE KEY-----'); - - details.click('@save'); - - sshKeyUnlock.expect.element('@error').visible; - sshKeyUnlock.expect.element('@error').text.to.contain('not encrypted'); - - details.section.machine.clearValue('@sshKeyUnlock'); - sshKeyUnlock.expect.element('@error').not.present; - }, - 'create machine credential': client => { - const credentials = client.page.credentials(); - const { add } = credentials.section; - const { edit } = credentials.section; - - add.section.details - .clearAndSelectType('Machine') - .setValue('@name', store.credential.name) - .setValue('@organization', store.organization.name); - - add.section.details.section.machine - .setValue('@username', 'dsarif') - .setValue('@password', 'freneticpny') - .setValue('@becomeMethod', 'sudo') - .setValue('@becomeUsername', 'dsarif') - .setValue('@becomePassword', 'freneticpny') - .sendKeys('@sshKeyData', '-----BEGIN RSA PRIVATE KEY-----') - .sendKeys('@sshKeyData', client.Keys.ENTER) - .sendKeys('@sshKeyData', 'AAAA') - .sendKeys('@sshKeyData', client.Keys.ENTER) - .sendKeys('@sshKeyData', '-----END RSA PRIVATE KEY-----'); - - add.section.details.click('@save'); - - credentials - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny'); - - edit.expect.element('@title').text.equal(store.credential.name); - }, - 'edit details panel remains open after saving': client => { - const credentials = client.page.credentials(); - - credentials.section.edit.expect.section('@details').visible; - }, - 'change the password after saving': client => { - const credentials = client.page.credentials(); - const { edit } = credentials.section; - const { machine } = edit.section.details.section; - - machine.section.password.expect.element('@replace').visible; - machine.section.password.expect.element('@replace').enabled; - machine.section.password.expect.element('@revert').not.visible; - - machine.expect.element('@password').not.enabled; - - machine.section.password.click('@replace'); - - machine.section.password.expect.element('@replace').not.visible; - machine.section.password.expect.element('@revert').visible; - - machine.expect.element('@password').enabled; - machine.setValue('@password', 'newpassword'); - - edit.section.details.click('@save'); - - credentials - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny'); - }, - 'credential is searchable after saving': client => { - const credentials = client.page.credentials(); - const row = '#credentials_table .List-tableRow'; - - const { search } = credentials.section.list.section; - - search - .waitForElementVisible('@input') - .setValue('@input', `name:${store.credential.name}`) - .click('@searchButton'); - - credentials.waitForElementNotPresent(`${row}:nth-of-type(2)`); - credentials.expect.element(row).text.contain(store.credential.name); - - client.end(); - }, -}; diff --git a/awx/ui/test/e2e/tests/test-credentials-add-edit-network.js b/awx/ui/test/e2e/tests/test-credentials-add-edit-network.js deleted file mode 100644 index 2afd82ab1a8f..000000000000 --- a/awx/ui/test/e2e/tests/test-credentials-add-edit-network.js +++ /dev/null @@ -1,208 +0,0 @@ -import uuid from 'uuid'; - -import { AWX_E2E_TIMEOUT_LONG } from '../settings'; - -const testID = uuid().substr(0, 8); - -const store = { - organization: { - name: `org-${testID}` - }, - credential: { - name: `cred-${testID}` - }, -}; - -module.exports = { - before: (client, done) => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - - client.login(); - client.waitForAngular(); - - client.inject( - [store, 'OrganizationModel'], - (_store_, Model) => new Model().http.post({ data: _store_.organization }), - ({ data }) => { store.organization = data; } - ); - - credentials.section.navigation - .waitForElementVisible('@credentials') - .click('@credentials'); - - credentials - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny'); - - credentials.section.list - .waitForElementVisible('@add') - .click('@add'); - - details.waitForElementVisible('@save', done); - }, - 'common fields are visible and enabled': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - - details.expect.element('@name').visible; - details.expect.element('@description').visible; - details.expect.element('@organization').visible; - details.expect.element('@type').visible; - - details.expect.element('@name').enabled; - details.expect.element('@description').enabled; - details.expect.element('@organization').enabled; - details.expect.element('@type').enabled; - }, - 'required common fields display \'*\'': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - - details.section.name.expect.element('@label').text.to.contain('*'); - details.section.type.expect.element('@label').text.to.contain('*'); - }, - 'save button becomes enabled after providing required fields': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - - details.expect.element('@save').not.enabled; - - details - .setValue('@name', store.credential.name) - .setValue('@organization', store.organization.name) - .setValue('@type', 'Network'); - - details.section.network - .waitForElementVisible('@username') - .setValue('@username', 'sgrimes'); - - details.expect.element('@save').enabled; - }, - 'network credential fields are visible after choosing type': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - const { network } = details.section; - - network.expect.element('@username').visible; - network.expect.element('@password').visible; - network.expect.element('@authorizePassword').visible; - network.expect.element('@sshKeyData').visible; - network.expect.element('@sshKeyUnlock').visible; - }, - 'error displayed for invalid ssh key data': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - const { sshKeyData } = details.section.network.section; - - details - .clearAndSelectType('Network') - .setValue('@name', store.credential.name); - - details.section.network - .setValue('@username', 'sgrimes') - .setValue('@sshKeyData', 'invalid'); - - details.click('@save'); - - sshKeyData.expect.element('@error').visible; - sshKeyData.expect.element('@error').text.to.contain('Invalid certificate or key'); - - details.section.network.clearValue('@sshKeyData'); - sshKeyData.expect.element('@error').not.present; - }, - 'error displayed for unencrypted ssh key with passphrase': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - const { sshKeyUnlock } = details.section.network.section; - - details - .clearAndSelectType('Network') - .setValue('@name', store.credential.name); - - details.section.network - .setValue('@username', 'sgrimes') - .setValue('@sshKeyUnlock', 'password') - .sendKeys('@sshKeyData', '-----BEGIN RSA PRIVATE KEY-----') - .sendKeys('@sshKeyData', client.Keys.ENTER) - .sendKeys('@sshKeyData', 'AAAA') - .sendKeys('@sshKeyData', client.Keys.ENTER) - .sendKeys('@sshKeyData', '-----END RSA PRIVATE KEY-----'); - - details.click('@save'); - - sshKeyUnlock.expect.element('@error').visible; - sshKeyUnlock.expect.element('@error').text.to.contain('not encrypted'); - - details.section.network.clearValue('@sshKeyUnlock'); - sshKeyUnlock.expect.element('@error').not.present; - }, - 'error displayed for authorize password without authorize enabled': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - const { authorizePassword } = details.section.network.section; - - details - .clearAndSelectType('Network') - .setValue('@name', store.credential.name); - - details.section.network - .setValue('@username', 'sgrimes') - .setValue('@authorizePassword', 'ovid'); - - details.click('@save'); - - const expected = 'cannot be set unless "Authorize" is set'; - authorizePassword.expect.element('@error').visible; - authorizePassword.expect.element('@error').text.to.equal(expected); - - details.section.network.clearValue('@authorizePassword'); - authorizePassword.expect.element('@error').not.present; - }, - 'create network credential': client => { - const credentials = client.page.credentials(); - const { add } = credentials.section; - const { edit } = credentials.section; - - add.section.details - .clearAndSelectType('Network') - .setValue('@name', store.credential.name) - .setValue('@organization', store.organization.name); - - add.section.details.section.network - .setValue('@username', 'sgrimes') - .setValue('@password', 'ovid') - .sendKeys('@sshKeyData', '-----BEGIN RSA PRIVATE KEY-----') - .sendKeys('@sshKeyData', client.Keys.ENTER) - .sendKeys('@sshKeyData', 'AAAA') - .sendKeys('@sshKeyData', client.Keys.ENTER) - .sendKeys('@sshKeyData', '-----END RSA PRIVATE KEY-----'); - - add.section.details.click('@save'); - - credentials - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny'); - - edit.expect.element('@title').text.equal(store.credential.name); - }, - 'edit details panel remains open after saving': client => { - const credentials = client.page.credentials(); - - credentials.section.edit.expect.section('@details').visible; - }, - 'credential is searchable after saving': client => { - const credentials = client.page.credentials(); - const row = '#credentials_table .List-tableRow'; - - credentials.section.list.section.search - .waitForElementVisible('@input', AWX_E2E_TIMEOUT_LONG) - .setValue('@input', `name:${store.credential.name}`) - .click('@searchButton'); - - credentials.waitForElementNotPresent(`${row}:nth-of-type(2)`); - credentials.expect.element(row).text.contain(store.credential.name); - - client.end(); - } -}; diff --git a/awx/ui/test/e2e/tests/test-credentials-add-edit-scm.js b/awx/ui/test/e2e/tests/test-credentials-add-edit-scm.js deleted file mode 100644 index 863ab2473fdb..000000000000 --- a/awx/ui/test/e2e/tests/test-credentials-add-edit-scm.js +++ /dev/null @@ -1,177 +0,0 @@ -import uuid from 'uuid'; - -import { AWX_E2E_TIMEOUT_LONG } from '../settings'; - -const testID = uuid().substr(0, 8); - -const store = { - organization: { - name: `org-${testID}` - }, - credential: { - name: `cred-${testID}` - }, -}; - -module.exports = { - before: (client, done) => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - - client.login(); - client.waitForAngular(); - - client.inject( - [store, 'OrganizationModel'], - (_store_, Model) => new Model().http.post({ data: _store_.organization }), - ({ data }) => { store.organization = data; } - ); - - credentials.section.navigation - .waitForElementVisible('@credentials') - .click('@credentials'); - - credentials - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny'); - - credentials.section.list - .waitForElementVisible('@add') - .click('@add'); - - details.waitForElementVisible('@save', done); - }, - 'common fields are visible and enabled': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - - details.expect.element('@name').visible; - details.expect.element('@description').visible; - details.expect.element('@organization').visible; - details.expect.element('@type').visible; - - details.expect.element('@name').enabled; - details.expect.element('@description').enabled; - details.expect.element('@organization').enabled; - details.expect.element('@type').enabled; - }, - 'required common fields display \'*\'': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - - details.section.name.expect.element('@label').text.to.contain('*'); - details.section.type.expect.element('@label').text.to.contain('*'); - }, - 'save button becomes enabled after providing required fields': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - - details.expect.element('@save').not.enabled; - - details - .setValue('@name', store.credential.name) - .setValue('@organization', store.organization.name) - .setValue('@type', 'Source Control'); - - details.expect.element('@save').enabled; - }, - 'scm credential fields are visible after choosing type': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - - details.section.scm.expect.element('@username').visible; - details.section.scm.expect.element('@password').visible; - details.section.scm.expect.element('@sshKeyData').visible; - details.section.scm.expect.element('@sshKeyUnlock').visible; - }, - 'error displayed for invalid ssh key data': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - const { sshKeyData } = details.section.scm.section; - - details - .clearAndSelectType('Source Control') - .setValue('@name', store.credential.name); - - details.section.scm.setValue('@sshKeyData', 'invalid'); - - details.click('@save'); - - sshKeyData.expect.element('@error').visible; - sshKeyData.expect.element('@error').text.to.contain('Invalid certificate or key'); - - details.section.scm.clearValue('@sshKeyData'); - sshKeyData.expect.element('@error').not.present; - }, - 'error displayed for unencrypted ssh key with passphrase': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - const { sshKeyUnlock } = details.section.scm.section; - - details - .clearAndSelectType('Source Control') - .setValue('@name', store.credential.name); - - details.section.scm - .setValue('@sshKeyUnlock', 'password') - .sendKeys('@sshKeyData', '-----BEGIN RSA PRIVATE KEY-----') - .sendKeys('@sshKeyData', client.Keys.ENTER) - .sendKeys('@sshKeyData', 'AAAA') - .sendKeys('@sshKeyData', client.Keys.ENTER) - .sendKeys('@sshKeyData', '-----END RSA PRIVATE KEY-----'); - - details.click('@save'); - - sshKeyUnlock.expect.element('@error').visible; - sshKeyUnlock.expect.element('@error').text.to.contain('not encrypted'); - - details.section.scm.clearValue('@sshKeyUnlock'); - sshKeyUnlock.expect.element('@error').not.present; - }, - 'create SCM credential': client => { - const credentials = client.page.credentials(); - const { add } = credentials.section; - const { edit } = credentials.section; - - add.section.details - .clearAndSelectType('Source Control') - .setValue('@name', store.credential.name) - .setValue('@organization', store.organization.name); - - add.section.details.section.scm - .setValue('@username', 'gthorpe') - .setValue('@password', 'hydro') - .sendKeys('@sshKeyData', '-----BEGIN RSA PRIVATE KEY-----') - .sendKeys('@sshKeyData', client.Keys.ENTER) - .sendKeys('@sshKeyData', 'AAAA') - .sendKeys('@sshKeyData', client.Keys.ENTER) - .sendKeys('@sshKeyData', '-----END RSA PRIVATE KEY-----'); - - add.section.details.click('@save'); - - credentials - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny'); - - edit.expect.element('@title').text.equal(store.credential.name); - }, - 'edit details panel remains open after saving': client => { - const credentials = client.page.credentials(); - - credentials.section.edit.expect.section('@details').visible; - }, - 'credential is searchable after saving': client => { - const credentials = client.page.credentials(); - const row = '#credentials_table .List-tableRow'; - - credentials.section.list.section.search - .waitForElementVisible('@input', AWX_E2E_TIMEOUT_LONG) - .setValue('@input', `name:${store.credential.name}`) - .click('@searchButton'); - - credentials.waitForElementNotPresent(`${row}:nth-of-type(2)`); - credentials.expect.element(row).text.to.contain(store.credential.name); - - client.end(); - } -}; diff --git a/awx/ui/test/e2e/tests/test-credentials-add-edit-vault.js b/awx/ui/test/e2e/tests/test-credentials-add-edit-vault.js deleted file mode 100644 index 207fe2b255d9..000000000000 --- a/awx/ui/test/e2e/tests/test-credentials-add-edit-vault.js +++ /dev/null @@ -1,138 +0,0 @@ -import uuid from 'uuid'; - -import { AWX_E2E_TIMEOUT_LONG } from '../settings'; - -const testID = uuid().substr(0, 8); - -const store = { - organization: { - name: `org-${testID}` - }, - credential: { - name: `cred-${testID}` - } -}; - -module.exports = { - before: (client, done) => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - - client.login(); - client.waitForAngular(); - - client.inject( - [store, 'OrganizationModel'], - (_store_, Model) => new Model().http.post({ data: _store_.organization }), - ({ data }) => { store.organization = data; } - ); - - credentials.section.navigation - .waitForElementVisible('@credentials') - .click('@credentials'); - - credentials - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny'); - - credentials.section.list - .waitForElementVisible('@add') - .click('@add'); - - details - .waitForElementVisible('@save') - .setValue('@name', store.credential.name) - .setValue('@organization', store.organization.name) - .setValue('@type', 'Vault', done); - }, - 'expected fields are visible and enabled': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - - details.expect.element('@name').visible; - details.expect.element('@description').visible; - details.expect.element('@organization').visible; - details.expect.element('@type').visible; - details.section.vault.expect.element('@vaultPassword').visible; - - details.expect.element('@name').enabled; - details.expect.element('@description').enabled; - details.expect.element('@organization').enabled; - details.expect.element('@type').enabled; - details.section.vault.expect.element('@vaultPassword').enabled; - }, - 'required fields display \'*\'': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - const required = [ - details.section.name, - details.section.type, - details.section.vault.section.vaultPassword, - ]; - - required.map(s => s.expect.element('@label').text.to.contain('*')); - }, - 'save button becomes enabled after providing required fields': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - - details - .clearAndSelectType('Vault') - .setValue('@name', store.credential.name); - - details.expect.element('@save').not.enabled; - details.section.vault.setValue('@vaultPassword', 'ch@ng3m3'); - details.expect.element('@save').enabled; - }, - 'vault password field is disabled when prompt on launch is selected': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - - details - .clearAndSelectType('Vault') - .setValue('@name', store.credential.name); - - details.section.vault.expect.element('@vaultPassword').enabled; - details.section.vault.section.vaultPassword.click('@prompt'); - details.section.vault.expect.element('@vaultPassword').not.enabled; - }, - 'create vault credential': client => { - const credentials = client.page.credentials(); - const { add } = credentials.section; - const { edit } = credentials.section; - - add.section.details - .clearAndSelectType('Vault') - .setValue('@name', store.credential.name) - .setValue('@organization', store.organization.name); - - add.section.details.section.vault.setValue('@vaultPassword', 'ch@ng3m3'); - - add.section.details.click('@save'); - - credentials - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny'); - - edit.expect.element('@title').text.equal(store.credential.name); - }, - 'edit details panel remains open after saving': client => { - const credentials = client.page.credentials(); - - credentials.section.edit.expect.section('@details').visible; - }, - 'credential is searchable after saving': client => { - const credentials = client.page.credentials(); - const row = '#credentials_table .List-tableRow'; - - credentials.section.list.section.search - .waitForElementVisible('@input', AWX_E2E_TIMEOUT_LONG) - .setValue('@input', `name:${store.credential.name}`) - .click('@searchButton'); - - credentials.waitForElementNotPresent(`${row}:nth-of-type(2)`); - credentials.expect.element(row).text.contain(store.credential.name); - - client.end(); - } -}; diff --git a/awx/ui/test/e2e/tests/test-credentials-list-actions.js b/awx/ui/test/e2e/tests/test-credentials-list-actions.js deleted file mode 100644 index 117a4a943b61..000000000000 --- a/awx/ui/test/e2e/tests/test-credentials-list-actions.js +++ /dev/null @@ -1,67 +0,0 @@ -import { getAdminMachineCredential } from '../fixtures'; - -const data = {}; - -module.exports = { - before: (client, done) => { - getAdminMachineCredential('test-actions') - .then(obj => { data.credential = obj; }) - .then(done); - }, - 'copy credential': client => { - const credentials = client.page.credentials(); - - client.useCss(); - client.login(); - client.waitForAngular(); - - credentials.load(); - credentials.waitForElementVisible('div.spinny'); - credentials.waitForElementNotVisible('div.spinny'); - - credentials.section.list.expect.element('smart-search').visible; - credentials.section.list.expect.element('smart-search input').enabled; - - credentials.section.list - .sendKeys('smart-search input', `id:>${data.credential.id - 1} id:<${data.credential.id + 1}`) - .sendKeys('smart-search input', client.Keys.ENTER); - - credentials.waitForElementVisible('div.spinny'); - credentials.waitForElementNotVisible('div.spinny'); - - credentials.expect.element(`#credentials_table .List-tableRow[id="${data.credential.id}"]`).visible; - credentials.expect.element('i[class*="copy"]').visible; - credentials.expect.element('i[class*="copy"]').enabled; - - credentials.click('i[class*="copy"]'); - credentials.waitForElementVisible('div.spinny'); - credentials.waitForElementNotVisible('div.spinny'); - - const activityStream = 'bread-crumb > div i[class$="icon-activity-stream"]'; - const activityRow = '#activities_table .List-tableCell[class*="description-column"] a'; - const toast = 'div[class="Toast-icon"]'; - - credentials.waitForElementNotPresent(toast); - credentials.expect.element(activityStream).visible; - credentials.expect.element(activityStream).enabled; - credentials.click(activityStream); - credentials.waitForElementVisible('div.spinny'); - credentials.waitForElementNotVisible('div.spinny'); - - client - .waitForElementVisible(activityRow) - .click(activityRow); - - credentials.waitForElementVisible('div.spinny'); - credentials.waitForElementNotVisible('div.spinny'); - - credentials.expect.element('div[ui-view="edit"] form').visible; - credentials.section.edit.expect.element('@title').visible; - credentials.section.edit.expect.element('@title').text.contain(data.credential.name); - credentials.section.edit.expect.element('@title').text.not.equal(data.credential.name); - credentials.section.edit.section.details.expect.element('@save').visible; - credentials.section.edit.section.details.expect.element('@save').enabled; - - client.end(); - } -}; diff --git a/awx/ui/test/e2e/tests/test-credentials-lookup-credential-type.js b/awx/ui/test/e2e/tests/test-credentials-lookup-credential-type.js deleted file mode 100644 index 72b1c891a14d..000000000000 --- a/awx/ui/test/e2e/tests/test-credentials-lookup-credential-type.js +++ /dev/null @@ -1,47 +0,0 @@ -module.exports = { - before: (client, done) => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - - client.login(); - client.waitForAngular(); - - credentials.section.navigation - .waitForElementVisible('@credentials') - .click('@credentials'); - - credentials - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny'); - - credentials.section.list - .waitForElementVisible('@add') - .click('@add'); - - details - .waitForElementVisible('@save', done); - }, - 'open the lookup modal': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - - const modal = 'div[class="modal-body"]'; - const title = 'div[class^="Form-title"]'; - - details.expect.element('@type').visible; - details.expect.element('@type').enabled; - - details.section.type.expect.element('@lookup').visible; - details.section.type.expect.element('@lookup').enabled; - - details.section.type.click('@lookup'); - - client.expect.element(modal).present; - - const expected = 'SELECT CREDENTIAL TYPE'; - client.expect.element(title).visible; - client.expect.element(title).text.equal(expected); - - client.end(); - } -}; diff --git a/awx/ui/test/e2e/tests/test-credentials-lookup-organization.js b/awx/ui/test/e2e/tests/test-credentials-lookup-organization.js deleted file mode 100644 index 264a098d580f..000000000000 --- a/awx/ui/test/e2e/tests/test-credentials-lookup-organization.js +++ /dev/null @@ -1,152 +0,0 @@ -import { range } from 'lodash'; - -import { getOrganization } from '../fixtures'; - -module.exports = { - before: (client, done) => { - const resources = range(100).map(n => getOrganization(`test-lookup-${n}`)); - - Promise.all(resources) - .then(() => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - - client.login(); - client.waitForAngular(); - - credentials.section.navigation.waitForElementVisible('@credentials'); - credentials.section.navigation.click('@credentials'); - - credentials.waitForElementVisible('div.spinny'); - credentials.waitForElementNotVisible('div.spinny'); - - credentials.section.list.waitForElementVisible('@add'); - credentials.section.list.click('@add'); - - details.waitForElementVisible('@save'); - - done(); - }); - }, - 'open the lookup modal': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - const { lookupModal } = credentials.section; - - details.expect.element('@organization').visible; - details.expect.element('@organization').enabled; - - details.section.organization.expect.element('@lookup').visible; - details.section.organization.expect.element('@lookup').enabled; - - details.section.organization.click('@lookup'); - - credentials.expect.section('@lookupModal').present; - - const expected = 'SELECT ORGANIZATION'; - lookupModal.expect.element('@title').visible; - lookupModal.expect.element('@title').text.equal(expected); - }, - 'select button is disabled until item is selected': client => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; - const { lookupModal } = credentials.section; - - details.section.organization.expect.element('@lookup').visible; - - credentials.expect.section('@lookupModal').present; - - lookupModal.expect.element('.List-tableRow:nth-child(1) input[type="radio"]').not.selected; - lookupModal.expect.element('.List-tableRow:nth-child(2) input[type="radio"]').not.selected; - lookupModal.expect.element('.List-tableRow:nth-child(3) input[type="radio"]').not.selected; - lookupModal.expect.element('.List-tableRow:nth-child(4) input[type="radio"]').not.selected; - lookupModal.expect.element('.List-tableRow:nth-child(5) input[type="radio"]').not.selected; - lookupModal.expect.element('.List-tableRow:nth-child(6) input[type="radio"]').not.present; - - lookupModal.expect.element('@select').visible; - lookupModal.expect.element('@select').not.enabled; - - lookupModal.click('.List-tableRow:nth-child(2)'); - lookupModal.expect.element('.List-tableRow:nth-child(2) input[type="radio"]').selected; - - lookupModal.expect.element('@select').visible; - lookupModal.expect.element('@select').enabled; - }, - 'sort and unsort the table by name with an item selected': client => { - const credentials = client.page.credentials(); - const { lookupModal } = credentials.section; - - lookupModal.expect.element('#organization-name-header').visible; - lookupModal.expect.element('#organization-name-header').visible; - - lookupModal.click('#organization-name-header'); - credentials.waitForElementVisible('div.spinny'); - credentials.waitForElementNotVisible('div.spinny'); - - lookupModal.expect.element('.List-tableRow:nth-child(1) input[type="radio"]').not.selected; - lookupModal.expect.element('.List-tableRow:nth-child(2) input[type="radio"]').not.selected; - lookupModal.expect.element('.List-tableRow:nth-child(3) input[type="radio"]').not.selected; - lookupModal.expect.element('.List-tableRow:nth-child(4) input[type="radio"]').not.selected; - lookupModal.expect.element('.List-tableRow:nth-child(5) input[type="radio"]').not.selected; - - lookupModal.click('#organization-name-header'); - credentials.waitForElementVisible('div.spinny'); - credentials.waitForElementNotVisible('div.spinny'); - - lookupModal.expect.element('.List-tableRow:nth-child(1) input[type="radio"]').not.selected; - lookupModal.expect.element('.List-tableRow:nth-child(2) input[type="radio"]').selected; - lookupModal.expect.element('.List-tableRow:nth-child(3) input[type="radio"]').not.selected; - lookupModal.expect.element('.List-tableRow:nth-child(4) input[type="radio"]').not.selected; - lookupModal.expect.element('.List-tableRow:nth-child(5) input[type="radio"]').not.selected; - }, - 'use the pagination controls with an item selected': client => { - const credentials = client.page.credentials(); - const { lookupModal } = credentials.section; - const { pagination } = lookupModal.section; - - pagination.click('@next'); - credentials.waitForElementVisible('div.spinny'); - credentials.waitForElementNotVisible('div.spinny'); - - lookupModal.expect.element('.List-tableRow:nth-child(1) input[type="radio"]').not.selected; - lookupModal.expect.element('.List-tableRow:nth-child(2) input[type="radio"]').not.selected; - lookupModal.expect.element('.List-tableRow:nth-child(3) input[type="radio"]').not.selected; - lookupModal.expect.element('.List-tableRow:nth-child(4) input[type="radio"]').not.selected; - lookupModal.expect.element('.List-tableRow:nth-child(5) input[type="radio"]').not.selected; - pagination.click('@previous'); - credentials.waitForElementVisible('div.spinny'); - credentials.waitForElementNotVisible('div.spinny'); - - lookupModal.expect.element('.List-tableRow:nth-child(1) input[type="radio"]').not.selected; - lookupModal.expect.element('.List-tableRow:nth-child(2) input[type="radio"]').selected; - lookupModal.expect.element('.List-tableRow:nth-child(3) input[type="radio"]').not.selected; - lookupModal.expect.element('.List-tableRow:nth-child(4) input[type="radio"]').not.selected; - lookupModal.expect.element('.List-tableRow:nth-child(5) input[type="radio"]').not.selected; - - pagination.click('@last'); - credentials.waitForElementVisible('div.spinny'); - credentials.waitForElementNotVisible('div.spinny'); - - pagination.click('@previous'); - credentials.waitForElementVisible('div.spinny'); - credentials.waitForElementNotVisible('div.spinny'); - - lookupModal.expect.element('.List-tableRow:nth-child(1) input[type="radio"]').not.selected; - lookupModal.expect.element('.List-tableRow:nth-child(2) input[type="radio"]').not.selected; - lookupModal.expect.element('.List-tableRow:nth-child(3) input[type="radio"]').not.selected; - lookupModal.expect.element('.List-tableRow:nth-child(4) input[type="radio"]').not.selected; - lookupModal.expect.element('.List-tableRow:nth-child(5) input[type="radio"]').not.selected; - - pagination.click('@first'); - credentials.waitForElementVisible('div.spinny'); - credentials.waitForElementNotVisible('div.spinny'); - - lookupModal.expect.element('.List-tableRow:nth-child(1) input[type="radio"]').not.selected; - lookupModal.expect.element('.List-tableRow:nth-child(2) input[type="radio"]').selected; - lookupModal.expect.element('.List-tableRow:nth-child(3) input[type="radio"]').not.selected; - lookupModal.expect.element('.List-tableRow:nth-child(4) input[type="radio"]').not.selected; - lookupModal.expect.element('.List-tableRow:nth-child(5) input[type="radio"]').not.selected; - - client.end(); - } -}; diff --git a/awx/ui/test/e2e/tests/test-credentials-navigation-click-through.js b/awx/ui/test/e2e/tests/test-credentials-navigation-click-through.js deleted file mode 100644 index 9fe4a7c79b89..000000000000 --- a/awx/ui/test/e2e/tests/test-credentials-navigation-click-through.js +++ /dev/null @@ -1,32 +0,0 @@ -module.exports = { - beforeEach: (client, done) => { - const credentials = client.useCss().page.credentials(); - - client.login(); - client.waitForAngular(); - - credentials - .load() - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny', done); - }, - 'activity link is visible and takes user to activity stream': client => { - const credentials = client.page.credentials(); - const activityStream = client.page.activityStream(); - - credentials.expect.section('@breadcrumb').visible; - credentials.section.breadcrumb.expect.element('@activity').visible; - credentials.section.breadcrumb.click('@activity'); - - activityStream - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny') - .waitForElementVisible('@title') - .waitForElementVisible('@category'); - - activityStream.expect.element('@title').text.contain('CREDENTIALS'); - activityStream.expect.element('@category').value.contain('credential'); - - client.end(); - } -}; diff --git a/awx/ui/test/e2e/tests/test-credentials-search-sort.js b/awx/ui/test/e2e/tests/test-credentials-search-sort.js deleted file mode 100644 index 7267961bb673..000000000000 --- a/awx/ui/test/e2e/tests/test-credentials-search-sort.js +++ /dev/null @@ -1,40 +0,0 @@ -module.exports = { - before: (client, done) => { - const credentials = client.page.credentials(); - - client.login(); - client.waitForAngular(); - - credentials - .load() - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny'); - - credentials.waitForElementVisible('#credentials_table', done); - }, - 'expected table columns are visible': client => { - const credentials = client.page.credentials(); - - credentials.expect.element('#credential-name-header').visible; - credentials.expect.element('#credential-kind-header').visible; - credentials.expect.element('#credential-owners-header').visible; - credentials.expect.element('#credential-actions-header').visible; - }, - 'only fields expected to be sortable show sort icon': client => { - const credentials = client.page.credentials(); - - credentials.expect.element('#credential-name-header > i.columnSortIcon').visible; - }, - 'sort all columns expected to be sortable': client => { - const credentials = client.page.credentials(); - - credentials.expect.element('#credential-name-header > i.columnSortIcon.fa-sort-up').visible; - credentials.click('#credential-name-header'); - credentials - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny'); - credentials.expect.element('#credential-name-header > i.columnSortIcon.fa-sort-down').visible; - - client.end(); - } -}; diff --git a/awx/ui/test/e2e/tests/test-form-error-handling.js b/awx/ui/test/e2e/tests/test-form-error-handling.js deleted file mode 100644 index d4709bdf0d98..000000000000 --- a/awx/ui/test/e2e/tests/test-form-error-handling.js +++ /dev/null @@ -1,221 +0,0 @@ -/* Tests for validation checks during form creation and editing. */ - -import { - getJobTemplate -} from '../fixtures'; - -import { - AWX_E2E_TIMEOUT_MEDIUM, - AWX_E2E_TIMEOUT_SHORT -} from '../settings'; - -let data; - -const templatesNavTab = '//at-side-nav-item[contains(@name, "TEMPLATES")]'; -const orgsNavTab = '//at-side-nav-item[contains(@name, "ORGANIZATIONS")]'; - -module.exports = { - before: (client, done) => { - const resources = [ - getJobTemplate('test-form-error-handling'), - getJobTemplate('test-form-error-handling-2') - ]; - - Promise.all(resources) - .then(([jt, jt2]) => { - data = { jt, jt2 }; - // We login and load the test page *after* data setup is finished. - // This helps avoid flakiness due to unpredictable spinners, etc. - // caused by first-time project syncs when creating the job templates. - client - .login() - .waitForAngular() - .useXpath() - .findThenClick(templatesNavTab) - .findThenClick('//*[@id="button-add"]') - .findThenClick('//a[@ui-sref="templates.addJobTemplate"]'); - done(); - }); - }, - 'Test max character limit when creating a job template': client => { - client - .waitForElementVisible('//input[@id="job_template_name"]') - .setValue( - '//input[@id="job_template_name"]', - ['a'.repeat(513), client.Keys.ENTER] - ) - .setValue( - '//input[@name="inventory_name"]', - ['test-form-error-handling-inventory', client.Keys.ENTER] - ) - .setValue( - '//input[@name="project_name"]', - ['test-form-error-handling-project', client.Keys.ENTER] - ) - // After the test sets a value for the project, a few seconds are - // needed while the UI fetches the playbooks names with a network - // call. There's no obvious dom element here to poll, so we wait a - // reasonably safe amount of time for the form to settle down - // before proceeding. - .pause(AWX_E2E_TIMEOUT_MEDIUM) - .waitForElementNotVisible('//*[contains(@class, "spinny")]') - .findThenClick('//*[@id="select2-playbook-select-container"]') - .findThenClick('//li[text()="hello_world.yml"]') - .findThenClick('//*[@id="job_template_save_btn"]') - .findThenClick('//*[@id="alert_ok_btn"]'); - - client.expect.element('//div[@id="job_template_name_group"]' + - '//div[@id="job_template-name-api-error"]').to.be.visible; - }, - 'Test duplicate template name handling when creating a job template': client => { - client - .waitForElementNotVisible('//*[@id="alert_ok_btn"]') - .waitForElementVisible('//div[contains(@class, "Form-title")]') - .clearValue('//input[@id="job_template_name"]') - .setValue( - '//input[@id="job_template_name"]', - ['test-form-error-handling-job-template', client.Keys.ENTER] - ) - .findThenClick('//*[@id="job_template_save_btn"]') - .findThenClick('//*[@id="alert_ok_btn"]'); - }, - - 'Test incorrect format when creating a job template': client => { - client - .waitForElementNotVisible('//*[@id="alert_ok_btn"]') - .waitForElementVisible('//div[contains(@class, "Form-title")]') - .clearValue('//input[@name="inventory_name"]') - .setValue( - '//input[@name="inventory_name"]', - ['invalid inventory name', client.Keys.ENTER] - ) - .clearValue('//input[@name="project_name"]') - .setValue( - '//input[@name="project_name"]', - ['invalid project', client.Keys.ENTER] - ); - - client.expect.element('//*[@id="job_template-inventory-notfound-error"]') - .to.be.visible.after(AWX_E2E_TIMEOUT_SHORT); - client.expect.element('//*[@id="job_template-project-notfound-error"]') - .to.be.visible.after(AWX_E2E_TIMEOUT_SHORT); - }, - - 'Test max character limit when editing a job template': client => { - client - .waitForElementNotVisible('//*[@id="alert_ok_btn"]') - .setValue( - '//input[contains(@class, "SmartSearch-input")]', - ['name.iexact:test-form-error-handling-job-template', client.Keys.ENTER] - ) - // double click to make field active - .findThenClick('//a[text()="test-form-error-handling-job-template"]') - .findThenClick('//a[text()="test-form-error-handling-job-template"]') - .findThenClick('//div[contains(@class, "Form-title") and text()="test-form-error-handling-job-template"]') - .clearValue('//input[@id="job_template_name"]') - .setValue( - '//input[@id="job_template_name"]', - ['a'.repeat(513), client.Keys.ENTER] - ) - .findThenClick('//*[@id="job_template_save_btn"]') - .findThenClick('//*[@id="alert_ok_btn"]'); - - client.expect.element('//div[@id="job_template_name_group"]' + - '//div[@id="job_template-name-api-error"]').to.be.visible.after(AWX_E2E_TIMEOUT_SHORT); - }, - - 'Test duplicate template name handling when editing a job template': client => { - client - .waitForElementNotVisible('//*[@id="alert_ok_btn"]') - .findThenClick('//div[contains(@class, "Form-title")]') - .clearValue('//input[@id="job_template_name"]') - .setValue( - '//input[@id="job_template_name"]', - ['test-form-error-handling-2-job-template', client.Keys.ENTER] - ) - .findThenClick('//*[@id="job_template_save_btn"]') - .findThenClick('//*[@id="alert_ok_btn"]'); - }, - - 'Test incorrect format when editing a job template': client => { - client - .waitForElementNotVisible('//*[@id="alert_ok_btn"]') - .waitForElementVisible('//div[contains(@class, "Form-title")]') - .clearValue('//input[@name="inventory_name"]') - .setValue( - '//input[@name="inventory_name"]', - ['invalid inventory name', client.Keys.ENTER] - ) - .clearValue('//input[@name="project_name"]') - .setValue( - '//input[@name="project_name"]', - ['invalid project', client.Keys.ENTER] - ); - - client.expect.element('//*[@id="job_template-inventory-notfound-error"]') - .to.be.visible.after(AWX_E2E_TIMEOUT_SHORT); - client.expect.element('//*[@id="job_template-project-notfound-error"]') - .to.be.visible.after(AWX_E2E_TIMEOUT_SHORT); - }, - - 'Test max character limit when creating an organization': client => { - client - .findThenClick(orgsNavTab) - .findThenClick('//*[@id="button-add"]') - .waitForElementVisible('//div[contains(@class, "Form-title")]') - .setValue( - '//input[@id="organization_name"]', - ['a'.repeat(513), client.Keys.ENTER] - ) - .findThenClick('//*[@id="organization_save_btn"]') - .findThenClick('//*[@id="alert_ok_btn"]') - .waitForElementNotVisible('//*[@id="alert_ok_btn"]'); - }, - - 'Test duplicate template name handling when creating an organization': client => { - client - .setValue( - '//input[@id="organization_name"]', - ['test-form-error-handling-2-organization)', client.Keys.ENTER] - ) - .findThenClick('//*[@id="organization_save_btn"]') - .findThenClick('//*[@id="alert_ok_btn"]') - .waitForElementNotVisible('//*[@id="alert_ok_btn"]'); - }, - - 'Test max character limit when editing an organization': client => { - client - .setValue( - '//input[contains(@class, "SmartSearch-input")]', - ['name.iexact:test-form-error-handling-organization', client.Keys.ENTER] - ) - // double click to make field active - .findThenClick('//a[normalize-space(text())= "test-form-error-handling-organization"]') - .findThenClick('//a[normalize-space(text())= "test-form-error-handling-organization"]') - .findThenClick('//*[contains(@class, "Form-title") and text()="test-form-error-handling-organization"]') - .clearValue('//input[@id="organization_name"]') - .setValue( - '//input[@id="organization_name"]', - ['a'.repeat(513), client.Keys.ENTER] - ) - .findThenClick('//*[@id="organization_save_btn"]') - .findThenClick('//*[@id="alert_ok_btn"]') - .waitForElementNotVisible('//*[@id="alert_ok_btn"]'); - }, - - 'Test duplicate template name handling when editing an organization': client => { - client - .clearValue('//input[@id="organization_name"]') - .setValue( - '//input[@id="organization_name"]', - ['test-form-error-handling-2-organization', client.Keys.ENTER] - ) - .findThenClick('//*[@id="organization_save_btn"]') - .findThenClick('//*[@id="alert_ok_btn"]') - .waitForElementNotVisible('//*[@id="alert_ok_btn"]'); - }, - - after: client => { - client.end(); - } -}; diff --git a/awx/ui/test/e2e/tests/test-inventories-list-actions.js b/awx/ui/test/e2e/tests/test-inventories-list-actions.js deleted file mode 100644 index c198d537f124..000000000000 --- a/awx/ui/test/e2e/tests/test-inventories-list-actions.js +++ /dev/null @@ -1,106 +0,0 @@ -import { - getInventory, - getInventoryNoSource, - getInventorySource, -} from '../fixtures'; - -let data; - -module.exports = { - before: (client, done) => { - const resources = [ - getInventory('test-actions'), - getInventoryNoSource('test-actions'), - getInventorySource('test-actions'), - ]; - - Promise.all(resources) - .then(([inventory, inventoryNoSource]) => { - data = { inventory, inventoryNoSource }; - done(); - }); - }, - 'copy inventory': client => { - const inventories = client.page.inventories(); - - client.useCss(); - client.login(); - client.waitForAngular(); - - inventories.load(); - inventories.waitForElementVisible('div.spinny'); - inventories.waitForElementNotVisible('div.spinny'); - - inventories.section.list.expect.element('smart-search').visible; - inventories.section.list.section.search.expect.element('@input').enabled; - - inventories.section.list.section.search - .sendKeys('@input', `id:>${data.inventoryNoSource.id - 1} id:<${data.inventoryNoSource.id + 1}`) - .sendKeys('@input', client.Keys.ENTER); - - inventories.waitForElementVisible('div.spinny'); - inventories.waitForElementNotVisible('div.spinny'); - - inventories.expect.element(`#inventories_table .List-tableRow[id="${data.inventoryNoSource.id}"]`).visible; - inventories.expect.element('i[class*="copy"]').visible; - inventories.expect.element('i[class*="copy"]').enabled; - - inventories.click('i[class*="copy"]'); - inventories.waitForElementVisible('div.spinny'); - inventories.waitForElementNotVisible('div.spinny'); - - const activityStream = 'bread-crumb > div i[class$="icon-activity-stream"]'; - const activityRow = '#activities_table .List-tableCell[class*="description-column"] a'; - const toast = 'div[class="Toast-icon"]'; - - inventories.waitForElementNotPresent(toast); - inventories.expect.element(activityStream).visible; - inventories.expect.element(activityStream).enabled; - inventories.click(activityStream); - inventories.waitForElementVisible('div.spinny'); - inventories.waitForElementNotVisible('div.spinny'); - - client - .waitForElementVisible(activityRow) - .click(activityRow); - - inventories.waitForElementVisible('div.spinny'); - inventories.waitForElementNotVisible('div.spinny'); - - inventories.expect.element('#inventory_form').visible; - inventories.section.editStandardInventory.expect.element('@title').visible; - inventories.section.editStandardInventory.expect.element('@title').text.contain(data.inventoryNoSource.name); - inventories.section.editStandardInventory.expect.element('@title').text.not.equal(data.inventoryNoSource.name); - inventories.expect.element('@save').visible; - inventories.expect.element('@save').enabled; - - client.end(); - }, - 'verify inventories with sources cannot be copied': client => { - const inventories = client.page.inventories(); - - client.useCss(); - client.login(); - client.waitForAngular(); - - inventories.load(); - inventories.waitForElementVisible('div.spinny'); - inventories.waitForElementNotVisible('div.spinny'); - - inventories.section.list.expect.element('smart-search').visible; - inventories.section.list.section.search.expect.element('@input').enabled; - - inventories.section.list.section.search - .sendKeys('@input', `id:>${data.inventory.id - 1} id:<${data.inventory.id + 1}`) - .sendKeys('@input', client.Keys.ENTER); - - inventories.waitForElementVisible('div.spinny'); - inventories.waitForElementNotVisible('div.spinny'); - - inventories.expect.element(`#inventories_table .List-tableRow[id="${data.inventory.id}"]`).visible; - inventories.expect.element('#copy-action').visible; - inventories.expect.element('#copy-action[class*="btn-disabled"]').present; - - client.end(); - } -}; diff --git a/awx/ui/test/e2e/tests/test-inventory-scripts-list-actions.js b/awx/ui/test/e2e/tests/test-inventory-scripts-list-actions.js deleted file mode 100644 index 9d1827f8c18c..000000000000 --- a/awx/ui/test/e2e/tests/test-inventory-scripts-list-actions.js +++ /dev/null @@ -1,67 +0,0 @@ -import { getInventoryScript } from '../fixtures'; - -const data = {}; - -module.exports = { - before: (client, done) => { - getInventoryScript('test-actions') - .then(obj => { data.inventoryScript = obj; }) - .then(done); - }, - 'copy inventory script': client => { - const inventoryScripts = client.page.inventoryScripts(); - - client.useCss(); - client.login(); - client.waitForAngular(); - - inventoryScripts.load(); - inventoryScripts.waitForElementVisible('div.spinny'); - inventoryScripts.waitForElementNotVisible('div.spinny'); - - inventoryScripts.section.list.expect.element('smart-search').visible; - inventoryScripts.section.list.expect.element('smart-search input').enabled; - - inventoryScripts.section.list - .sendKeys('smart-search input', `id:>${data.inventoryScript.id - 1} id:<${data.inventoryScript.id + 1}`) - .sendKeys('smart-search input', client.Keys.ENTER); - - inventoryScripts.waitForElementVisible('div.spinny'); - inventoryScripts.waitForElementNotVisible('div.spinny'); - - inventoryScripts.expect.element(`#inventory_scripts_table .List-tableRow[id="${data.inventoryScript.id}"]`).visible; - inventoryScripts.expect.element('i[class*="copy"]').visible; - inventoryScripts.expect.element('i[class*="copy"]').enabled; - - inventoryScripts.click('i[class*="copy"]'); - inventoryScripts.waitForElementVisible('div.spinny'); - inventoryScripts.waitForElementNotVisible('div.spinny'); - - const activityStream = 'bread-crumb > div i[class$="icon-activity-stream"]'; - const activityRow = '#activities_table .List-tableCell[class*="description-column"] a'; - const toast = 'div[class="Toast-icon"]'; - - inventoryScripts.waitForElementNotPresent(toast); - inventoryScripts.expect.element(activityStream).visible; - inventoryScripts.expect.element(activityStream).enabled; - inventoryScripts.click(activityStream); - inventoryScripts.waitForElementVisible('div.spinny'); - inventoryScripts.waitForElementNotVisible('div.spinny'); - - client - .waitForElementVisible(activityRow) - .click(activityRow); - - inventoryScripts.waitForElementVisible('div.spinny'); - inventoryScripts.waitForElementNotVisible('div.spinny'); - - inventoryScripts.expect.element('#inventory_script_form').visible; - inventoryScripts.section.edit.expect.element('@title').visible; - inventoryScripts.section.edit.expect.element('@title').text.contain(data.inventoryScript.name); - inventoryScripts.section.edit.expect.element('@title').text.not.equal(data.inventoryScript.name); - inventoryScripts.expect.element('@save').visible; - inventoryScripts.expect.element('@save').enabled; - - client.end(); - } -}; diff --git a/awx/ui/test/e2e/tests/test-jobs-portal-list-actions.js b/awx/ui/test/e2e/tests/test-jobs-portal-list-actions.js deleted file mode 100644 index 6cafdf6d2794..000000000000 --- a/awx/ui/test/e2e/tests/test-jobs-portal-list-actions.js +++ /dev/null @@ -1,64 +0,0 @@ -import { - getJob, - getJobTemplateAdmin -} from '../fixtures'; - -let data; - -module.exports = { - before: (client, done) => { - client.login(); - client.waitForAngular(); - - const resources = [ - getJobTemplateAdmin('test-actions'), - getJob('test-actions'), - ]; - - Promise.all(resources) - .then(([admin, job]) => { - data = { admin, job }; - done(); - }); - }, - 'relaunch a job from the \'all jobs\' list': client => { - const portal = client.page.portal(); - - const allJobsButton = '#all-jobs-btn'; - const relaunch = `#job-${data.job.id} i[class*="launch"]`; - const search = '#portal-container-jobs smart-search input'; - - portal.load(); - - portal.waitForElementVisible(allJobsButton); - portal.expect.element(allJobsButton).enabled; - portal.click(allJobsButton); - portal.waitForSpinny(); - portal.assert.cssClassPresent(allJobsButton, 'btn-primary'); - - const query = `id:>${data.job.id - 1} id:<${data.job.id + 1}`; - - portal.waitForElementVisible(search); - portal.expect.element(search).enabled; - portal.sendKeys(search, query); - portal.sendKeys(search, client.Keys.ENTER); - - portal.waitForSpinny(); - - portal.waitForElementVisible(relaunch); - portal.expect.element(relaunch).enabled; - portal.click(relaunch); - - portal.waitForSpinny(); - - const jobDetails = 'at-job-details'; - const output = './/span[normalize-space(text())=\'"msg": "Hello World!"\']'; - - client.waitForElementVisible(jobDetails, 10000); - client.useXpath(); - client.waitForElementVisible(output, 60000); - client.useCss(); - - client.end(); - } -}; diff --git a/awx/ui/test/e2e/tests/test-launch-jt.js b/awx/ui/test/e2e/tests/test-launch-jt.js deleted file mode 100644 index 0fbc14989cd8..000000000000 --- a/awx/ui/test/e2e/tests/test-launch-jt.js +++ /dev/null @@ -1,163 +0,0 @@ -import { post, patch } from '../api'; -import { - getOrCreate, - getUpdatedProject, - getInventory -} from '../fixtures'; - -let templateReferences = {}; - -module.exports = { - before: (client, done) => { - const resources = [ - getUpdatedProject('test-launch-jt'), - getInventory('test-launch-jt') - ]; - - Promise.all(resources) - .then(([project, inventory]) => { - const noPromptPromise = getOrCreate('/job_templates/', { - name: 'test-launch-jt-no-prompts', - inventory: inventory.id, - project: project.id, - playbook: 'hello_world.yml', - }); - - const promptNoPassPromise = getOrCreate('/job_templates/', { - name: 'test-launch-jt-prompts-no-pass', - inventory: inventory.id, - ask_inventory_on_launch: true, - project: project.id, - playbook: 'hello_world.yml', - ask_diff_mode_on_launch: true, - ask_variables_on_launch: true, - ask_limit_on_launch: true, - ask_tags_on_launch: true, - ask_skip_tags_on_launch: true, - ask_job_type_on_launch: true, - ask_verbosity_on_launch: true, - ask_credential_on_launch: true - }); - - Promise.all([noPromptPromise, promptNoPassPromise]) - .then(([noPrompt, promptNoPass]) => { - templateReferences = { noPrompt, promptNoPass }; - const surveyPost = post(promptNoPass.related.survey_spec, { - name: '', - description: '', - spec: [{ - question_name: 'Foo', - question_description: '', - required: true, - type: 'text', - variable: 'foo', - min: 0, - max: 1024, - default: 'bar', - choices: '', - new_question: true - }] - }); - - surveyPost - .then(() => patch(promptNoPass.url, { survey_enabled: true })) - .then(done); - }); - }); - }, - 'login to awx': client => { - client.login(); - client.waitForAngular(); - }, - 'expected jt launch with no prompts to navigate to job details': client => { - const templates = client.page.templates(); - templates.load(); - templates.waitForElementVisible('input[class*="SmartSearch-input"]'); - templates.section.list.section.search - .sendKeys('@input', `id:${templateReferences.noPrompt.id}`); - templates.section.list.section.search.getValue('@input', (result) => { - client.assert.equal(result.value, `id:${templateReferences.noPrompt.id}`); - }); - client.pause(1000).waitForElementNotVisible('div.spinny'); - templates.waitForElementVisible('i[class$="search"]'); - templates.section.list.section.search.click('i[class$="search"]'); - templates.waitForElementVisible('div.spinny'); - templates.waitForElementNotVisible('div.spinny'); - templates.expect.element('.at-Panel-headingTitleBadge').text.equal('1'); - templates.expect.element(`#templates_list .at-Row[id="row-${templateReferences.noPrompt.id}"]`).visible; - templates.expect.element('i[class*="icon-launch"]').visible; - templates.expect.element('i[class*="icon-launch"]').enabled; - templates.click('i[class*="icon-launch"]'); - templates.waitForElementVisible('div.spinny'); - templates.waitForElementNotVisible('div.spinny'); - client.waitForElementVisible('at-job-details', 10000); - - client.useXpath(); - client.waitForElementVisible('.//span[normalize-space(text())=\'"msg": "Hello World!"\']', 60000); - client.useCss(); - }, - 'expected jt launch with prompts but no changes to navigate to job details': client => { - const templates = client.page.templates(); - templates.load(); - templates.waitForElementVisible('input[class*="SmartSearch-input"]'); - templates.section.list.section.search - .sendKeys('@input', `id:${templateReferences.promptNoPass.id}`); - templates.section.list.section.search.getValue('@input', (result) => { - client.assert.equal(result.value, `id:${templateReferences.promptNoPass.id}`); - }); - client.pause(1000).waitForElementNotVisible('div.spinny'); - templates.waitForElementVisible('i[class$="search"]'); - templates.section.list.section.search.click('i[class$="search"]'); - templates.waitForElementVisible('div.spinny'); - templates.waitForElementNotVisible('div.spinny'); - templates.expect.element('.at-Panel-headingTitleBadge').text.equal('1'); - templates.expect.element(`#templates_list .at-Row[id="row-${templateReferences.promptNoPass.id}"]`).visible; - templates.expect.element('i[class*="icon-launch"]').visible; - templates.expect.element('i[class*="icon-launch"]').enabled; - templates.click('i[class*="icon-launch"]'); - templates.waitForElementVisible('#prompt-inventory'); - templates.expect.element('#prompt_inventory_tab').visible; - templates.expect.element('#prompt_inventory_tab').to.have.attribute('class').which.contains('at-Tab--active'); - templates.expect.element('#prompt_inventory_next').visible; - templates.expect.element('#prompt_inventory_next').enabled; - templates.waitForElementNotVisible('div.spinny'); - templates.click('#prompt_inventory_next'); - templates.waitForElementVisible('#prompt_credential_step'); - templates.expect.element('#prompt_credential_tab').visible; - templates.expect.element('#prompt_credential_tab').to.have.attribute('class').which.contains('at-Tab--active'); - templates.expect.element('#prompt_credential_next').visible; - templates.expect.element('#prompt_credential_next').enabled; - templates.waitForElementNotVisible('div.spinny'); - templates.click('#prompt_credential_next'); - templates.waitForElementVisible('#prompt_other_prompts_step'); - templates.expect.element('#prompt_other_prompts_tab').visible; - templates.expect.element('#prompt_other_prompts_tab').to.have.attribute('class').which.contains('at-Tab--active'); - templates.expect.element('#prompt_other_prompts_next').visible; - templates.expect.element('#prompt_other_prompts_next').enabled; - templates.waitForElementNotVisible('div.spinny'); - templates.click('#prompt_other_prompts_next'); - templates.waitForElementVisible('#prompt_survey_step'); - templates.expect.element('#prompt_survey_tab').visible; - templates.expect.element('#prompt_survey_tab').to.have.attribute('class').which.contains('at-Tab--active'); - templates.expect.element('#prompt_survey_next').visible; - templates.expect.element('#prompt_survey_next').enabled; - templates.waitForElementNotVisible('div.spinny'); - templates.click('#prompt_survey_next'); - templates.waitForElementVisible('#prompt_preview_step'); - templates.expect.element('#prompt_preview_tab').visible; - templates.expect.element('#prompt_preview_tab').to.have.attribute('class').which.contains('at-Tab--active'); - templates.expect.element('#prompt_finish').visible; - templates.expect.element('#prompt_finish').enabled; - templates.waitForElementNotVisible('div.spinny'); - templates.click('#prompt_finish'); - templates.waitForElementVisible('div.spinny'); - templates.waitForElementNotVisible('div.spinny'); - client.waitForElementVisible('at-job-details', 10000); - - client.useXpath(); - client.waitForElementVisible('.//span[normalize-space(text())=\'"msg": "Hello World!"\']', 60000); - client.useCss(); - - client.end(); - } -}; diff --git a/awx/ui/test/e2e/tests/test-notifications-list-actions.js b/awx/ui/test/e2e/tests/test-notifications-list-actions.js deleted file mode 100644 index 11e4114c8472..000000000000 --- a/awx/ui/test/e2e/tests/test-notifications-list-actions.js +++ /dev/null @@ -1,67 +0,0 @@ -import { getNotificationTemplate } from '../fixtures'; - -const data = {}; - -module.exports = { - before: (client, done) => { - getNotificationTemplate('test-actions') - .then(obj => { data.notification = obj; }) - .then(done); - }, - 'copy notification template': client => { - const notifications = client.page.notificationTemplates(); - - client.useCss(); - client.login(); - client.waitForAngular(); - - notifications.load(); - notifications.waitForElementVisible('div.spinny'); - notifications.waitForElementNotVisible('div.spinny'); - - notifications.section.list.expect.element('smart-search').visible; - notifications.section.list.expect.element('smart-search input').enabled; - - notifications.section.list - .sendKeys('smart-search input', `id:>${data.notification.id - 1} id:<${data.notification.id + 1}`) - .sendKeys('smart-search input', client.Keys.ENTER); - - notifications.waitForElementVisible('div.spinny'); - notifications.waitForElementNotVisible('div.spinny'); - - notifications.expect.element(`#notification_templates_table .List-tableRow[id="${data.notification.id}"]`).visible; - notifications.expect.element('i[class*="copy"]').visible; - notifications.expect.element('i[class*="copy"]').enabled; - - notifications.click('i[class*="copy"]'); - notifications.waitForElementVisible('div.spinny'); - notifications.waitForElementNotVisible('div.spinny'); - - const activityStream = 'bread-crumb > div i[class$="icon-activity-stream"]'; - const activityRow = '#activities_table .List-tableCell[class*="description-column"] a'; - const toast = 'div[class="Toast-icon"]'; - - notifications.waitForElementNotPresent(toast); - notifications.expect.element(activityStream).visible; - notifications.expect.element(activityStream).enabled; - notifications.click(activityStream); - notifications.waitForElementVisible('div.spinny'); - notifications.waitForElementNotVisible('div.spinny'); - - client - .waitForElementVisible(activityRow) - .click(activityRow); - - notifications.waitForElementVisible('div.spinny'); - notifications.waitForElementNotVisible('div.spinny'); - - notifications.expect.element('#notification_template_form').visible; - notifications.section.edit.expect.element('@title').visible; - notifications.section.edit.expect.element('@title').text.contain(data.notification.name); - notifications.section.edit.expect.element('@title').text.not.equal(data.notification.name); - notifications.expect.element('@save').visible; - notifications.expect.element('@save').enabled; - - client.end(); - } -}; diff --git a/awx/ui/test/e2e/tests/test-org-permissions.js b/awx/ui/test/e2e/tests/test-org-permissions.js deleted file mode 100644 index 2a12fafe2406..000000000000 --- a/awx/ui/test/e2e/tests/test-org-permissions.js +++ /dev/null @@ -1,137 +0,0 @@ -import { - getOrganization, - getUser, - getTeam, -} from '../fixtures'; - -import { - AWX_E2E_URL -} from '../settings'; - -const namespace = 'test-org-permissions'; - -let data; -const spinny = "//*[contains(@class, 'spinny')]"; -const checkbox = '//input[@type="checkbox"]'; - -const searchBar = "//input[contains(@class, 'SmartSearch-input')]"; -const modalSearchBar = '//*[@ui-view="modal"]//*[contains(@class, "SmartSearch-input")]'; -const teamSearchBar = '//*[@django-model="teams"]//input'; -const userRoleSearchBar = '//li//input[contains(@type, "search")]'; -const modalOrgsSearchBar = '//smart-search[@django-model="organizations"]//input'; - -const orgsNavTab = "//at-side-nav-item[contains(@name, 'ORGANIZATIONS')]"; -const teamsNavTab = "//at-side-nav-item[contains(@name, 'TEAMS')]"; - -const orgTab = '//div[not(@ng-show="showSection2Container()")]/div[@class="Form-tabHolder"]/div[@ng-click="selectTab(\'organizations\')"]'; -const teamsTab = '//*[@id="teams_tab"]'; -const permissionsTab = '//*[@id="permissions_tab"]'; -const usersTab = '//*[@id="users_tab"]'; - -const orgsText = `name.iexact:"${namespace}-organization"`; -const orgsCheckbox = '//select-list-item[@item="organization"]//input[@type="checkbox"]'; -const orgDetails = '//*[contains(@class, "OrgCards-label")]'; -const orgRoleSelector = '//*[contains(@aria-labelledby, "select2-organizations")]'; -const readRole = '//*[contains(@id, "organizations-role") and text()="Read"]'; - -const memberRoleText = 'member'; -const readRoleText = 'read'; - -const teamsSelector = `//a[contains(text(), '${namespace}-team')]`; -const teamsText = `name.iexact:"${namespace}-team"`; -const teamsSearchBadgeCount = '//span[contains(@class, "List-titleBadge") and contains(text(), "1")]'; -const teamCheckbox = '//*[@item="team"]//input[@type="checkbox"]'; -const addUserToTeam = '//*[@aw-tool-tip="Add User"]'; - -const saveButton = '//*[text()="Save"]'; - -const addPermission = '//*[@aw-tool-tip="Grant Permission"]'; -const addTeamPermission = '//*[@aw-tool-tip="Add a permission"]'; -const verifyTeamPermissions = '//*[contains(@class, "List-tableRow")]//*[text()="Read"]'; -const readOrgPermissionResults = `//*[@id="permissions_table"]//*[text()="${namespace}-organization"]/parent::*/parent::*//*[contains(text(), "Read")]`; - -module.exports = { - before: (client, done) => { - const resources = [ - getUser(namespace, `${namespace}-user`), - getOrganization(namespace), - getTeam(namespace), - ]; - - Promise.all(resources) - .then(([user, org, team]) => { - data = { user, org, team }; - done(); - }); - client - .login() - .waitForAngular() - .useXpath() - .findThenClick(teamsNavTab) - .clearValue(searchBar) - .setValue(searchBar, [teamsText, client.Keys.ENTER]) - .waitForElementNotVisible(spinny) - .waitForElementVisible(teamsSearchBadgeCount) - .findThenClick(teamsSelector); - }, - 'test orgs permissions tab in teams view': client => { - client - .useXpath() - .findThenClick(permissionsTab) - .findThenClick(addPermission) - .findThenClick(orgTab) - .clearValue(modalOrgsSearchBar) - .setValue(modalOrgsSearchBar, [orgsText, client.Keys.ENTER]) - .waitForElementNotVisible(spinny) - .findThenClick(orgsCheckbox) - .findThenClick(orgRoleSelector) - .findThenClick(readRole) - .findThenClick(saveButton) - .clearValue(searchBar) - .setValue(searchBar, [orgsText, client.Keys.ENTER]) - .waitForElementNotVisible(spinny) - .waitForElementPresent(readOrgPermissionResults); - }, - 'test adding team permissions in orgs view': client => { - // add user to team, then add team-wide permissions to org - client - .useXpath() - .findThenClick(usersTab) - .findThenClick(addUserToTeam) - .waitForElementVisible(modalSearchBar) - .clearValue(modalSearchBar) - .setValue(modalSearchBar, [`username.iexact:${data.user.username}`, client.Keys.ENTER]) - .waitForElementNotVisible(spinny) - .findThenClick(checkbox) - .findThenClick(userRoleSearchBar) - .setValue(userRoleSearchBar, [memberRoleText, client.Keys.ENTER]) - .waitForElementNotVisible(spinny) - .findThenClick(saveButton) - // add team-wide permissions to an organization - .findThenClick(orgsNavTab) - .navigateTo(`${AWX_E2E_URL}/#/organizations`, false) - .waitForElementVisible(searchBar) - .clearValue(searchBar) - .setValue(searchBar, [orgsText, client.Keys.ENTER]) - .waitForElementNotVisible(spinny) - .findThenClick(orgDetails) - .pause(3000) // overlay is in the way sometimes, not spinny - .findThenClick(permissionsTab) - .findThenClick(addTeamPermission) - .findThenClick(teamsTab) - .clearValue(teamSearchBar) - .setValue(teamSearchBar, [teamsText, client.Keys.ENTER]) - .waitForElementNotVisible(spinny) - .findThenClick(teamCheckbox) - .findThenClick(userRoleSearchBar) - .setValue(userRoleSearchBar, [readRoleText, client.Keys.ENTER]) - .waitForElementNotVisible(spinny) - .findThenClick(saveButton) - .clearValue(searchBar) - .setValue(searchBar, [`username.iexact:${data.user.username}`, client.Keys.ENTER]) - .waitForElementVisible(verifyTeamPermissions); - }, - after: client => { - client.end(); - } -}; diff --git a/awx/ui/test/e2e/tests/test-pagination.js b/awx/ui/test/e2e/tests/test-pagination.js deleted file mode 100644 index f9f5ec619159..000000000000 --- a/awx/ui/test/e2e/tests/test-pagination.js +++ /dev/null @@ -1,78 +0,0 @@ -import { - getJobTemplate, - getUpdatedProject, -} from '../fixtures'; - -import { - AWX_E2E_TIMEOUT_MEDIUM, -} from '../settings'; - -const namespace = 'test-pagination'; - -module.exports = { - - before: (client, done) => { - const resources = [getUpdatedProject(namespace)]; - - Promise.all(resources) - .then(() => { - for (let i = 0; i < 25; i++) { - // Create enough job templates to make at least 2 pages of data - resources.push(getJobTemplate(namespace, 'hello_world.yml', `${namespace}-job-template-${i}`, false)); - } - Promise.all(resources) - .then(() => done()); - }); - - client - .login() - .waitForAngular(); - }, - 'Test job template pagination': client => { - client - .useCss() - .findThenClick('[ui-sref="templates"]', 'css') - .waitForElementVisible('.SmartSearch-input') - .clearValue('.SmartSearch-input'); - const firstRow = client - .getText('#templates_list .at-RowItem-header > a:nth-of-type(1)'); - client.findThenClick('.Paginate-controls--next', 'css'); - client.expect.element('#templates_list .at-RowItem-header > a:nth-of-type(1)') - .to.have.value.not.equals(firstRow).before(AWX_E2E_TIMEOUT_MEDIUM); - client.findThenClick('.Paginate-controls--previous', 'css'); - }, - 'Test filtered job template pagination': client => { - client - .useCss() - .waitForElementVisible('.SmartSearch-input') - .clearValue('.SmartSearch-input') - .setValue( - '.SmartSearch-input', - [`name.istartswith:"${namespace}"`, client.Keys.ENTER] - ); - client.useXpath().expect.element('//a[text()="test-pagination-job-template-0"]') - .to.be.visible.after(AWX_E2E_TIMEOUT_MEDIUM); - client.useCss().findThenClick('.Paginate-controls--next', 'css'); - - // Default search sort uses alphanumeric sorting, so template #9 is last - client.useXpath().expect.element('//a[text()="test-pagination-job-template-9"]') - .to.be.visible.after(AWX_E2E_TIMEOUT_MEDIUM); - client.useXpath() - .expect.element('//*[contains(@class, "Paginate-controls--active") and text()="2"]') - .to.be.visible.after(AWX_E2E_TIMEOUT_MEDIUM); - - client - .useCss() - .findThenClick('.Paginate-controls--previous', 'css'); - // Default search sort uses alphanumeric sorting, so template #9 is last - client.useXpath().expect.element('//a[text()="test-pagination-job-template-0"]') - .to.be.visible.after(AWX_E2E_TIMEOUT_MEDIUM); - client.useXpath() - .expect.element('//*[contains(@class, "Paginate-controls--active") and text()="1"]') - .to.be.visible.after(AWX_E2E_TIMEOUT_MEDIUM); - }, - - after: client => { - client.end(); - } -}; diff --git a/awx/ui/test/e2e/tests/test-projects-list-actions.js b/awx/ui/test/e2e/tests/test-projects-list-actions.js deleted file mode 100644 index 0c2acc3a73f5..000000000000 --- a/awx/ui/test/e2e/tests/test-projects-list-actions.js +++ /dev/null @@ -1,68 +0,0 @@ -import { getUpdatedProject } from '../fixtures'; - -const data = {}; - -module.exports = { - before: (client, done) => { - getUpdatedProject('test-actions') - .then(obj => { data.project = obj; }) - .then(done); - }, - 'copy project': client => { - const projects = client.page.projects(); - - client.useCss(); - client.login(); - client.waitForAngular(); - - projects.load(); - projects.waitForElementVisible('div.spinny'); - projects.waitForElementNotVisible('div.spinny'); - - projects.section.list.expect.element('smart-search').visible; - projects.section.list.section.search.expect.element('@input').enabled; - - projects.section.list.section.search - .sendKeys('@input', `id:>${data.project.id - 1} id:<${data.project.id + 1}`) - .sendKeys('@input', client.Keys.ENTER); - - projects.waitForElementVisible('div.spinny'); - projects.waitForElementNotVisible('div.spinny'); - - projects.section.list.expect.element('@badge').text.equal('1'); - projects.expect.element(`#row-${data.project.id}`).visible; - projects.expect.element('i[class*="copy"]').visible; - projects.expect.element('i[class*="copy"]').enabled; - - projects.click('i[class*="copy"]'); - projects.waitForElementVisible('div.spinny'); - projects.waitForElementNotVisible('div.spinny'); - - const activityStream = 'bread-crumb > div i[class$="icon-activity-stream"]'; - const activityRow = '#activities_table .List-tableCell[class*="description-column"] a'; - const toast = 'div[class="Toast-icon"]'; - - projects.waitForElementNotPresent(toast); - projects.expect.element(activityStream).visible; - projects.expect.element(activityStream).enabled; - projects.click(activityStream); - projects.waitForElementVisible('div.spinny'); - projects.waitForElementNotVisible('div.spinny'); - - client - .waitForElementVisible(activityRow) - .click(activityRow); - - projects.waitForElementVisible('div.spinny'); - projects.waitForElementNotVisible('div.spinny'); - - projects.expect.element('#project_form').visible; - projects.section.edit.expect.element('@title').visible; - projects.section.edit.expect.element('@title').text.contain(data.project.name); - projects.section.edit.expect.element('@title').text.not.equal(data.project.name); - projects.expect.element('@save').visible; - projects.expect.element('@save').enabled; - - client.end(); - } -}; diff --git a/awx/ui/test/e2e/tests/test-search-tag-add-remove.js b/awx/ui/test/e2e/tests/test-search-tag-add-remove.js deleted file mode 100644 index 294f7aa58255..000000000000 --- a/awx/ui/test/e2e/tests/test-search-tag-add-remove.js +++ /dev/null @@ -1,124 +0,0 @@ -import { range } from 'lodash'; - -import { getAdminMachineCredential } from '../fixtures'; - -const spinny = 'div.spinny'; -const searchInput = 'smart-search input'; -const searchSubmit = 'smart-search i[class*="search"]'; -const searchTags = 'smart-search .SmartSearch-tagContainer'; -const searchClearAll = 'smart-search .SmartSearch-clearAll'; -const searchTagDelete = 'i[class*="fa-times"]'; - -const createTagSelector = n => `${searchTags}:nth-of-type(${n})`; -const createTagDeleteSelector = n => `${searchTags}:nth-of-type(${n}) ${searchTagDelete}`; - -const checkTags = (client, tags) => { - const strategy = 'css selector'; - - const countReached = createTagSelector(tags.length); - const countExceeded = createTagSelector(tags.length + 1); - - if (tags.length > 0) { - client.waitForElementVisible(countReached); - client.waitForElementNotPresent(countExceeded); - } - - client.elements(strategy, searchTags, tagElements => { - client.assert.equal(tagElements.value.length, tags.length); - - let n = -1; - tagElements.value.map(o => o.ELEMENT).forEach(id => { - client.elementIdText(id, ({ value }) => { - client.assert.equal(value, tags[++n]); - }); - }); - }); -}; - -module.exports = { - before: (client, done) => { - const resources = range(25).map(n => getAdminMachineCredential(`test-search-${n}`)); - - Promise.all(resources).then(done); - }, - 'add and remove search tags': client => { - const credentials = client.page.credentials(); - - client.login(); - client.waitForAngular(); - - credentials.section.navigation.waitForElementVisible('@credentials'); - credentials.section.navigation.click('@credentials'); - - client.waitForElementVisible(spinny); - client.waitForElementNotVisible(spinny); - - client.waitForElementVisible(searchInput); - client.waitForElementVisible(searchSubmit); - - client.expect.element(searchInput).enabled; - client.expect.element(searchSubmit).enabled; - - checkTags(client, []); - - client.setValue(searchInput, 'foo'); - client.click(searchSubmit); - client.waitForElementVisible(spinny); - client.waitForElementNotVisible(spinny); - - checkTags(client, ['foo']); - - client.setValue(searchInput, 'bar e2e'); - client.click(searchSubmit); - client.waitForElementVisible(spinny); - client.waitForElementNotVisible(spinny); - - checkTags(client, ['foo', 'bar', 'e2e']); - - client.click(searchClearAll); - client.waitForElementVisible(spinny); - client.waitForElementNotVisible(spinny); - - checkTags(client, []); - - client.setValue(searchInput, 'fiz name:foo'); - client.click(searchSubmit); - client.waitForElementVisible(spinny); - client.waitForElementNotVisible(spinny); - - checkTags(client, ['fiz', 'name:foo']); - - client.click(searchClearAll); - client.waitForElementVisible(spinny); - client.waitForElementNotVisible(spinny); - - checkTags(client, []); - - client.setValue(searchInput, 'hello name:world fiz'); - client.click(searchSubmit); - client.waitForElementVisible(spinny); - client.waitForElementNotVisible(spinny); - - checkTags(client, ['hello', 'fiz', 'name:world']); - - client.click(createTagDeleteSelector(2)); - client.waitForElementVisible(spinny); - client.waitForElementNotVisible(spinny); - - checkTags(client, ['hello', 'name:world']); - - client.click(createTagDeleteSelector(1)); - client.waitForElementVisible(spinny); - client.waitForElementNotVisible(spinny); - - checkTags(client, ['name:world']); - - client.click(createTagDeleteSelector(1)); - client.waitForElementVisible(spinny); - client.waitForElementNotVisible(spinny); - - checkTags(client, []); - - client.end(); - }, -}; diff --git a/awx/ui/test/e2e/tests/test-templates-copy-delete-warnings.js b/awx/ui/test/e2e/tests/test-templates-copy-delete-warnings.js deleted file mode 100644 index 094323a5e14d..000000000000 --- a/awx/ui/test/e2e/tests/test-templates-copy-delete-warnings.js +++ /dev/null @@ -1,212 +0,0 @@ -import { post } from '../api'; -import { - getInventoryScript, - getInventorySource, - getJobTemplate, - getOrCreate, - getOrganization, - getProject, - getUser, - getWorkflowTemplate, -} from '../fixtures'; - -let data; - -const promptHeader = '#prompt-header'; -const promptWarning = '#prompt-body'; -const promptResource = 'span[class="Prompt-warningResourceTitle"]'; -const promptResourceCount = 'span[class="badge List-titleBadge"]'; -const promptCancelButton = '#prompt_cancel_btn'; -const promptActionButton = '#prompt_action_btn'; -const promptCloseButton = '#prompt-header + div i[class*="times-circle"]'; - -module.exports = { - before: (client, done) => { - const resources = [ - getUser('test-warnings'), - getOrganization('test-warnings'), - getWorkflowTemplate('test-warnings'), - getProject('test-warnings'), - getJobTemplate('test-warnings'), - getInventoryScript('external-org'), - getInventorySource('external-org'), - ]; - - Promise.all(resources) - .then(([user, org, workflow, project, template, script, source]) => { - const unique = 'unified_job_template'; - const nodes = workflow.related.workflow_nodes; - const nodePromise = getOrCreate(nodes, { [unique]: source.id }, [unique]); - - const permissions = [ - [org, 'admin_role'], - [workflow, 'admin_role'], - [project, 'admin_role'], - [template, 'admin_role'], - [script, 'read_role'], - ]; - - const assignments = permissions - .map(([resource, name]) => resource.summary_fields.object_roles[name]) - .map(role => `/api/v2/roles/${role.id}/users/`) - .map(url => post(url, { id: user.id })) - .concat([nodePromise]); - - Promise.all(assignments) - .then(() => { data = { user, project, source, template, workflow }; }) - .then(done); - }); - }, - 'verify job template delete warning': client => { - const templates = client.page.templates(); - - client.useCss(); - client.login(); - client.waitForAngular(); - - templates.load(); - templates.waitForElementVisible('div.spinny'); - templates.waitForElementNotVisible('div.spinny'); - - templates.expect.element('smart-search').visible; - templates.expect.element('smart-search input').enabled; - - templates - .sendKeys('smart-search input', `id:>${data.template.id - 1} id:<${data.template.id + 1}`) - .sendKeys('smart-search input', client.Keys.ENTER); - - templates.waitForElementVisible('div.spinny'); - templates.waitForElementNotVisible('div.spinny'); - - templates.expect.element('.at-Panel-headingTitleBadge').text.equal('1'); - templates.expect.element(`#row-${data.template.id}`).visible; - templates.expect.element('i[class*="trash"]').visible; - templates.expect.element('i[class*="trash"]').enabled; - - templates.click('i[class*="trash"]'); - - templates.expect.element(promptHeader).visible; - templates.expect.element(promptWarning).visible; - templates.expect.element(promptResource).visible; - templates.expect.element(promptResourceCount).visible; - templates.expect.element(promptCancelButton).visible; - templates.expect.element(promptActionButton).visible; - templates.expect.element(promptCloseButton).visible; - - templates.expect.element(promptCancelButton).enabled; - templates.expect.element(promptActionButton).enabled; - templates.expect.element(promptCloseButton).enabled; - - templates.expect.element(promptHeader).text.contain('DELETE'); - templates.expect.element(promptHeader).text.contain(`${data.template.name.toUpperCase()}`); - - templates.expect.element(promptWarning).text.contain('job template'); - - templates.expect.element(promptResource).text.contain('Workflow Job Template Nodes'); - templates.expect.element(promptResourceCount).text.contain('1'); - - templates.click(promptCancelButton); - - templates.expect.element(promptHeader).not.visible; - - client.end(); - }, - 'verify workflow template delete warning': client => { - const templates = client.page.templates(); - - client.useCss(); - client.login(); - client.waitForAngular(); - - templates.load(); - templates.waitForElementVisible('div.spinny'); - templates.waitForElementNotVisible('div.spinny'); - - templates.expect.element('smart-search').visible; - templates.expect.element('smart-search input').enabled; - - templates - .sendKeys('smart-search input', `id:>${data.workflow.id - 1} id:<${data.workflow.id + 1}`) - .sendKeys('smart-search input', client.Keys.ENTER); - - templates.waitForElementVisible('div.spinny'); - templates.waitForElementNotVisible('div.spinny'); - - templates.expect.element('.at-Panel-headingTitleBadge').text.equal('1'); - templates.expect.element(`#row-${data.workflow.id}`).visible; - templates.expect.element('i[class*="trash"]').visible; - templates.expect.element('i[class*="trash"]').enabled; - - templates.click('i[class*="trash"]'); - - templates.expect.element(promptHeader).visible; - templates.expect.element(promptWarning).visible; - templates.expect.element(promptCancelButton).visible; - templates.expect.element(promptActionButton).visible; - templates.expect.element(promptCloseButton).visible; - - templates.expect.element(promptCancelButton).enabled; - templates.expect.element(promptActionButton).enabled; - templates.expect.element(promptCloseButton).enabled; - - templates.expect.element(promptHeader).text.contain('DELETE'); - templates.expect.element(promptHeader).text.contain(`${data.workflow.name.toUpperCase()}`); - - templates.expect.element(promptWarning).text.contain('workflow template'); - - templates.click(promptCancelButton); - - templates.expect.element(promptHeader).not.visible; - - client.end(); - }, - 'verify workflow restricted copy warning': client => { - const templates = client.page.templates(); - - client.useCss(); - client.login(data.user.username); - client.waitForAngular(); - - templates.load(); - templates.waitForElementVisible('div.spinny'); - templates.waitForElementNotVisible('div.spinny'); - - templates.expect.element('smart-search').visible; - templates.expect.element('smart-search input').enabled; - - templates - .sendKeys('smart-search input', `id:>${data.workflow.id - 1} id:<${data.workflow.id + 1}`) - .sendKeys('smart-search input', client.Keys.ENTER); - - templates.waitForElementVisible('div.spinny'); - templates.waitForElementNotVisible('div.spinny'); - - templates.expect.element('.at-Panel-headingTitleBadge').text.equal('1'); - templates.expect.element(`#row-${data.workflow.id}`).visible; - templates.expect.element('i[class*="copy"]').visible; - templates.expect.element('i[class*="copy"]').enabled; - - templates.click('i[class*="copy"]'); - - templates.expect.element(promptHeader).visible; - templates.expect.element(promptWarning).visible; - templates.expect.element(promptCancelButton).visible; - templates.expect.element(promptActionButton).visible; - templates.expect.element(promptCloseButton).visible; - - templates.expect.element(promptCancelButton).enabled; - templates.expect.element(promptActionButton).enabled; - templates.expect.element(promptCloseButton).enabled; - - templates.expect.element(promptHeader).text.contain('COPY'); - templates.expect.element(promptHeader).text.contain(`${data.workflow.name.toUpperCase()}`); - templates.expect.element(promptWarning).text.contain('Unified Job Templates'); - templates.expect.element(promptWarning).text.contain(`${data.source.name}`); - - templates.click(promptCancelButton); - - templates.expect.element(promptHeader).not.visible; - - client.end(); - }, -}; diff --git a/awx/ui/test/e2e/tests/test-templates-list-actions.js b/awx/ui/test/e2e/tests/test-templates-list-actions.js deleted file mode 100644 index f08934a5ca9d..000000000000 --- a/awx/ui/test/e2e/tests/test-templates-list-actions.js +++ /dev/null @@ -1,190 +0,0 @@ -import { - getInventorySource, - getJobTemplate, - getProject, - getWorkflowTemplate -} from '../fixtures'; - -let data; - -module.exports = { - before: (client, done) => { - const resources = [ - getInventorySource('test-actions'), - getJobTemplate('test-actions'), - getProject('test-actions'), - getWorkflowTemplate('test-actions'), - ]; - - Promise.all(resources) - .then(([source, template, project, workflow]) => { - data = { source, template, project, workflow }; - done(); - }); - }, - 'copy job template': client => { - const templates = client.page.templates(); - - client.useCss(); - client.login(); - client.waitForAngular(); - - templates.load(); - templates.waitForElementVisible('div.spinny'); - templates.waitForElementNotVisible('div.spinny'); - - templates.expect.element('smart-search').visible; - templates.expect.element('smart-search input').enabled; - - templates - .sendKeys('smart-search input', `id:>${data.template.id - 1} id:<${data.template.id + 1}`) - .sendKeys('smart-search input', client.Keys.ENTER); - - templates.waitForElementVisible('div.spinny'); - templates.waitForElementNotVisible('div.spinny'); - - templates.expect.element('.at-Panel-headingTitleBadge').text.equal('1'); - templates.expect.element(`#row-${data.template.id}`).visible; - templates.expect.element('i[class*="copy"]').visible; - templates.expect.element('i[class*="copy"]').enabled; - - templates.click('i[class*="copy"]'); - templates.waitForElementVisible('div.spinny'); - templates.waitForElementNotVisible('div.spinny'); - - const activityStream = 'bread-crumb > div i[class$="icon-activity-stream"]'; - const activityRow = '#activities_table .List-tableCell[class*="description-column"] a'; - const toast = 'div[class="Toast-icon"]'; - - templates.waitForElementNotPresent(toast); - templates.expect.element(activityStream).visible; - templates.expect.element(activityStream).enabled; - templates.click(activityStream); - templates.waitForElementVisible('div.spinny'); - templates.waitForElementNotVisible('div.spinny'); - - client - .waitForElementVisible(activityRow) - .click(activityRow); - - templates.waitForElementVisible('div.spinny'); - templates.waitForElementNotVisible('div.spinny'); - - templates.expect.element('#job_template_form').visible; - templates.section.addJobTemplate.expect.element('@title').visible; - templates.section.addJobTemplate.expect.element('@title').text.contain(data.template.name); - templates.section.addJobTemplate.expect.element('@title').text.not.equal(data.template.name); - templates.expect.element('@save').visible; - templates.expect.element('@save').enabled; - - client.end(); - }, - 'copy workflow template': client => { - const templates = client.page.templates(); - - client - .useCss() - .resizeWindow(1200, 800) - .login() - .waitForAngular(); - - templates.load(); - templates.waitForElementVisible('div.spinny'); - templates.waitForElementNotVisible('div.spinny'); - - templates.expect.element('smart-search').visible; - templates.expect.element('smart-search input').enabled; - - templates - .sendKeys('smart-search input', `id:>${data.workflow.id - 1} id:<${data.workflow.id + 1}`) - .sendKeys('smart-search input', client.Keys.ENTER); - - templates.waitForElementVisible('div.spinny'); - templates.waitForElementNotVisible('div.spinny'); - - templates.expect.element('.at-Panel-headingTitleBadge').text.equal('1'); - templates.expect.element(`#row-${data.workflow.id}`).visible; - templates.expect.element('i[class*="copy"]').visible; - templates.expect.element('i[class*="copy"]').enabled; - - templates - .click('i[class*="copy"]') - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny') - .waitForAngular(); - - const activityStream = 'bread-crumb > div i[class$="icon-activity-stream"]'; - const activityRow = '#activities_table .List-tableCell[class*="description-column"] a'; - const toast = 'div[class="Toast-icon"]'; - - templates.waitForElementNotPresent(toast); - templates.expect.element(activityStream).visible; - templates.expect.element(activityStream).enabled; - templates.click(activityStream); - templates.waitForElementVisible('div.spinny'); - templates.waitForElementNotVisible('div.spinny'); - - client - .waitForElementVisible(activityRow) - .click(activityRow); - - templates.waitForElementVisible('div.spinny'); - templates.waitForElementNotVisible('div.spinny'); - - templates.expect.element('#workflow_job_template_form').visible; - templates.section.editWorkflowJobTemplate.expect.element('@title').visible; - templates.section.editWorkflowJobTemplate.expect.element('@title').text.contain(data.workflow.name); - templates.section.editWorkflowJobTemplate.expect.element('@title').text.not.equal(data.workflow.name); - - templates.expect.element('@save').visible; - templates.expect.element('@save').enabled; - client.pause(1000); - - templates.section.editWorkflowJobTemplate - .waitForElementVisible('@visualizerButton') - .click('@visualizerButton') - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny') - .waitForAngular(); - - client.expect.element('#workflow-modal-dialog').visible; - client.expect.element('#workflow-modal-dialog span[class^="badge"]').visible; - client.expect.element('#workflow-modal-dialog span[class^="badge"]').text.equal('3'); - client.expect.element('div[class="WorkflowMaker-title"]').visible; - client.expect.element('div[class="WorkflowMaker-title"]').text.contain(data.workflow.name); - client.expect.element('div[class="WorkflowMaker-title"]').text.not.equal(data.workflow.name); - - client.expect.element('#workflow-modal-dialog i[class*="fa-cog"]').visible; - client.expect.element('#workflow-modal-dialog i[class*="fa-cog"]').enabled; - - client.click('#workflow-modal-dialog i[class*="fa-cog"]'); - - client.waitForElementVisible('workflow-controls'); - client.waitForElementVisible('div[class*="-zoomPercentage"]'); - - client.click('i[class*="fa-home"]').expect.element('div[class*="-zoomPercentage"]').text.equal('100%'); - client.click('i[class*="fa-minus"]').expect.element('div[class*="-zoomPercentage"]').text.equal('90%'); - client.click('i[class*="fa-minus"]').expect.element('div[class*="-zoomPercentage"]').text.equal('80%'); - client.click('i[class*="fa-minus"]').expect.element('div[class*="-zoomPercentage"]').text.equal('70%'); - client.click('i[class*="fa-minus"]').expect.element('div[class*="-zoomPercentage"]').text.equal('60%'); - - client.expect.element('#node-1').visible; - client.expect.element('#node-2').visible; - client.expect.element('#node-3').visible; - client.expect.element('#node-4').visible; - - client.expect.element('#node-1 text').text.not.equal('').after(5000); - client.expect.element('#node-2 text').text.not.equal('').after(5000); - client.expect.element('#node-3 text').text.not.equal('').after(5000); - client.expect.element('#node-4 text').text.not.equal('').after(5000); - - client.useXpath().waitForElementVisible('//*[contains(@class, "WorkflowChart-nameText") and contains(text(), "test-actions-job")]/..'); - client.useXpath().waitForElementVisible('//*[contains(@class, "WorkflowChart-nameText") and contains(text(), "test-actions-project")]/..'); - client.useXpath().waitForElementVisible('//*[contains(@class, "WorkflowChart-nameText") and contains(text(), "test-actions-inventory")]/..'); - - templates.expect.element('@save').visible; - templates.expect.element('@save').enabled; - - client.end(); - } -}; diff --git a/awx/ui/test/e2e/tests/test-users-crud.js b/awx/ui/test/e2e/tests/test-users-crud.js deleted file mode 100644 index 648a7f4b3346..000000000000 --- a/awx/ui/test/e2e/tests/test-users-crud.js +++ /dev/null @@ -1,146 +0,0 @@ -/* Tests for the user CRUD operations. */ -import uuid from 'uuid'; -import { - getAuditor, - getOrganization, - getUser -} from '../fixtures'; - -const row = '#users_table .List-tableRow'; -const testID = uuid().substr(0, 8); - -let data; -const store = { - organization: { - name: `org-${testID}` - }, - admin: { - email: `email-admin-${testID}@example.com`, - firstName: `first-admin-${testID}`, - lastName: `last-admin-${testID}`, - password: `admin-${testID}`, - username: `admin-${testID}`, - usernameDefault: `admin-${testID}`, - type: 'administrator', - }, - auditor: { - email: `email-auditor-${testID}@example.com`, - firstName: `first-auditor-${testID}`, - lastName: `last-auditor-${testID}`, - password: `auditor-${testID}`, - username: `auditor-${testID}`, - usernameDefault: `auditor-${testID}`, - type: 'auditor', - }, - user: { - email: `email-${testID}@example.com`, - firstName: `first-${testID}`, - lastName: `last-${testID}`, - password: `${testID}`, - username: `user-${testID}`, - usernameDefault: `user-${testID}`, - type: 'normal', - }, -}; - -module.exports = { - before: (client, done) => { - // generate a unique username on each attempt. - const uniqueUser = uuid().substr(0, 8); - Object.keys(store).forEach(key => { - if ('username' in store[key]) { - store[key].username = `${store[key].usernameDefault}-${uniqueUser}`; - } - }); - const resources = [ - getOrganization(store.organization.name), - ]; - - Promise.all(resources) - .then(([organization, auditor, user, admin]) => { - store.organization.name = `${store.organization.name}-organization`; - data = { organization }; - done(); - }); - client.login(); - client.waitForAngular(); - }, - 'create a system administrator': (client) => { - client.login(); - const users = client.page.users(); - users.load(); - client.waitForSpinny(); - users.create(store.admin, store.organization); - users.search(store.admin.username); - client.logout(); - }, - 'create a system auditor': (client) => { - client.login(store.admin.username, store.admin.password); - const users = client.page.users(); - users.load(); - client.waitForSpinny(); - users.create(store.auditor, store.organization); - users.search(store.auditor.username); - client.logout(); - }, - 'check if the new system auditor can login': (client) => { - client.login(store.auditor.username, store.auditor.password); - client.logout(); - }, - 'create an user': client => { - client.login(store.admin.username, store.admin.password); - const users = client.page.users(); - users.load(); - client.waitForSpinny(); - const newUser = { - email: store.user.email, - password: store.user.password, - username: store.user.username, - }; - users.create(newUser, store.organization); - users.search(newUser.username); - }, - 'edit an user': client => { - const users = client.page.users(); - users.load(); - client.waitForSpinny(); - users.search(store.user.username); - const editButton = `${row} i[class*="fa-pencil"]`; - users.waitForElementVisible(editButton).click(editButton); - users.section.edit - .waitForElementVisible('@title') - .setValue('@firstName', store.user.firstName) - .setValue('@lastName', store.user.lastName) - .click('@save'); - client.waitForSpinny(); - users.search(store.user.username); - users.expect.element(row).text.contain(`${store.user.username}\n${store.user.firstName}\n${store.user.lastName}`); - client.logout(); - }, - 'check if the new user can login': (client) => { - client.login(store.user.username, store.user.password); - client.logout(); - }, - 'delete admin': (client) => { - client.login(); - const users = client.page.users(); - users.load(); - client.waitForSpinny(); - users.delete(store.admin.username); - }, - 'delete auditor': (client) => { - const users = client.page.users(); - users.load(); - client.waitForSpinny(); - users.delete(store.auditor.username); - }, - 'delete user': (client) => { - const users = client.page.users(); - users.load(); - client.waitForSpinny(); - users.delete(store.user.username); - }, - after: client => { - client.end(); - }, -}; diff --git a/awx/ui/test/e2e/tests/test-websockets.js b/awx/ui/test/e2e/tests/test-websockets.js deleted file mode 100644 index 101796e57a2e..000000000000 --- a/awx/ui/test/e2e/tests/test-websockets.js +++ /dev/null @@ -1,164 +0,0 @@ -/* Websocket tests. These tests verify that items like the sparkline (colored box rows which - * display job status) and other status icons update correctly as the jobs progress. - */ -import { - getInventorySource, - getOrganization, - getProject, - getJob, - getJobTemplate, - getUpdatedProject -} from '../fixtures'; - -import { - AWX_E2E_URL, - AWX_E2E_TIMEOUT_ASYNC, - AWX_E2E_TIMEOUT_LONG -} from '../settings'; - -let data; - -// Xpath selectors for recently run job templates on the dashboard. -const successfulJt = '//a[contains(text(), "test-websockets-successful")]/../..'; -const failedJt = '//a[contains(text(), "test-websockets-failed")]/../..'; -const splitJt = '//a[contains(text(), "test-ws-split-job-template")]/../..'; -const sparklineIcon = '//div[contains(@class, "SmartStatus-iconContainer")]'; - -// Xpath selectors for sparkline icon statuses. -const running = '//div[@ng-show="job.status === \'running\'"]'; -const success = '//div[contains(@class, "SmartStatus-iconIndicator--success")]'; -const fail = '//div[contains(@class, "SmartStatus-iconIndicator--failed")]'; - -module.exports = { - - before: (client, done) => { - const resources = [ - getInventorySource('test-websockets'), - getProject('test-websockets', 'https://github.com/ansible/test-playbooks'), - getOrganization('test-websockets'), - // launch job templates once before running tests so that they appear on the dashboard. - getJob('test-websockets', 'debug.yml', 'test-websockets-successful', true, done), - getJob('test-websockets', 'fail_unless.yml', 'test-websockets-failed', true, done), - getJobTemplate('test-websockets', 'debug.yml', 'test-ws-split-job-template', true, '2'), - getJob('test-websockets', 'debug.yml', 'test-ws-split-job-template', false, done) - ]; - - Promise.all(resources) - .then(([inventory, project, org, jt1, jt2, sjt, sj]) => { - data = { inventory, project, org, jt1, jt2, sjt, sj }; - done(); - }); - - client - .login() - .waitForAngular(); - }, - - 'Test job template status updates for a successful job on dashboard': client => { - client - .useCss() - .navigateTo(`${AWX_E2E_URL}/#/home`, false); - getJob('test-websockets', 'debug.yml', 'test-websockets-successful', false); - client.useXpath().expect.element(`${sparklineIcon}[1]${running}`) - .to.be.present.before(AWX_E2E_TIMEOUT_ASYNC); - client.useXpath().expect.element(`${successfulJt}${sparklineIcon}[1]${success}`) - .to.be.present.before(AWX_E2E_TIMEOUT_ASYNC); - }, - - 'Test job template status updates for a failed job on dashboard': client => { - client - .useCss() - .navigateTo(`${AWX_E2E_URL}/#/home`, false); - getJob('test-websockets', 'fail_unless.yml', 'test-websockets-failed', false); - client.useXpath().expect.element(`${sparklineIcon}[1]${running}`) - .to.be.present.before(AWX_E2E_TIMEOUT_ASYNC); - client.useXpath().expect.element(`${failedJt}${sparklineIcon}[1]${fail}`) - .to.be.present.before(AWX_E2E_TIMEOUT_ASYNC); - }, - - 'Test projects list blinking icon': client => { - client - .useCss() - .navigateTo(`${AWX_E2E_URL}/#/projects`) - .waitForElementVisible('.SmartSearch-input') - .clearValue('.SmartSearch-input') - .setValue( - '.SmartSearch-input', - ['name.iexact:"test-websockets-project"', client.Keys.ENTER] - ); - getUpdatedProject('test-websockets'); - getUpdatedProject('test-websockets'); // occasionally 1st update is too quick - - client.expect.element('i.icon-job-running') - .to.be.present.before(AWX_E2E_TIMEOUT_ASYNC); - client.expect.element('i.icon-job-success') - .to.be.present.before(AWX_E2E_TIMEOUT_ASYNC); - }, - - 'Test successful job within an organization view': client => { - client - .useCss() - .navigateTo(`${AWX_E2E_URL}/#/organizations/${data.org.id}/job_templates`, false) - .waitForElementVisible('[ui-view=templatesList] .SmartSearch-input') - .clearValue('[ui-view=templatesList] .SmartSearch-input') - .setValue( - '[ui-view=templatesList] .SmartSearch-input', - ['test-websockets-successful', client.Keys.ENTER] - ); - getJob('test-websockets', 'debug.yml', 'test-websockets-successful', false); - - client.useXpath().expect.element(`${sparklineIcon}[1]${running}`) - .to.be.visible.before(AWX_E2E_TIMEOUT_ASYNC); - client.useXpath().expect.element(`${sparklineIcon}[1]${success}`) - .to.be.present.before(AWX_E2E_TIMEOUT_ASYNC); - }, - 'Test failed job within an organization view': client => { - client - .useCss() - .navigateTo(`${AWX_E2E_URL}/#/organizations/${data.org.id}/job_templates`, false) - .waitForElementVisible('[ui-view=templatesList] .SmartSearch-input') - .clearValue('[ui-view=templatesList] .SmartSearch-input') - .setValue( - '[ui-view=templatesList] .SmartSearch-input', - ['test-websockets-failed', client.Keys.ENTER] - ); - getJob('test-websockets', 'debug.yml', 'test-websockets-failed', false); - - client.useXpath().expect.element(`${sparklineIcon}[1]${running}`) - .to.be.visible.before(AWX_E2E_TIMEOUT_ASYNC); - client.useXpath().expect.element(`${sparklineIcon}[1]${fail}`) - .to.be.present.before(AWX_E2E_TIMEOUT_ASYNC); - }, - 'Test project blinking icon within an organization view': client => { - client - .useCss() - .navigateTo(`${AWX_E2E_URL}/#/organizations/${data.org.id}/projects`) - .waitForElementVisible('.projectsList .SmartSearch-input') - .clearValue('.projectsList .SmartSearch-input') - .setValue( - '.projectsList .SmartSearch-input', - ['test-websockets-project', client.Keys.ENTER] - ); - client.useXpath().waitForElementVisible('//i[contains(@class, "icon-job")]'); - client.useCss(); - getUpdatedProject('test-websockets'); - getUpdatedProject('test-websockets'); // sometimes project update is too fast - - client.expect.element('i.icon-job-running') - .to.be.visible.before(AWX_E2E_TIMEOUT_ASYNC); - client.expect.element('i.icon-job-success') - .to.be.present.before(AWX_E2E_TIMEOUT_ASYNC); - }, - 'Test job slicing sparkline behavior': client => { - client.navigateTo(`${AWX_E2E_URL}/#/home`, false); - getJob('test-websockets', 'debug.yml', 'test-ws-split-job-template', false); - - client.useXpath().expect.element(`${sparklineIcon}[1]${running}`) - .to.be.visible.before(AWX_E2E_TIMEOUT_ASYNC); - client.useXpath().expect.element(`${splitJt}${sparklineIcon}[1]${success}`) - .to.be.present.before(AWX_E2E_TIMEOUT_ASYNC); - }, - after: client => { - client.end(); - } -}; diff --git a/awx/ui/test/e2e/tests/test-workflow-visualizer.js b/awx/ui/test/e2e/tests/test-workflow-visualizer.js deleted file mode 100644 index 112b1b8942e1..000000000000 --- a/awx/ui/test/e2e/tests/test-workflow-visualizer.js +++ /dev/null @@ -1,196 +0,0 @@ -import uuid from 'uuid'; - -import { - getInventorySource, - getJobTemplate, - getProject, - getWorkflowTemplate -} from '../fixtures'; - -import { - AWX_E2E_URL, -} from '../settings'; - -let data; -const spinny = "//*[contains(@class, 'spinny')]"; -const workflowVisualizerBtn = "//button[contains(@id, 'workflow_job_template_workflow_visualizer_btn')]"; -const workflowSearchBar = "//input[contains(@class, 'SmartSearch-input')]"; - -const startNodeId = '1'; -let initialJobNodeId; -let initialProjectNodeId; -let initialInventoryNodeId; -let newChildNodeId; -let leafNodeId; -const nodeAdd = "//*[contains(@class, 'WorkflowChart-nodeAddIcon')]"; -const nodeRemove = "//*[contains(@class, 'WorkflowChart-nodeRemoveIcon')]"; - -// search bar for visualizer templates -const jobSearchBar = "//*[contains(@id, 'workflow-jobs-list')]//input[contains(@class, 'SmartSearch-input')]"; - -// dropdown bar which lets you select edge type -const edgeTypeDropdownBar = "//span[contains(@id, 'select2-workflow_node_edge-container')]"; -const alwaysDropdown = "//li[contains(@id, 'select2-workflow_node_edge') and text()='Always']"; -const successDropdown = "//li[contains(@id, 'select2-workflow_node_edge') and text()='On Success']"; -const failureDropdown = "//li[contains(@id, 'select2-workflow_node_edge') and text()='On Failure']"; -const linkEdgeTypeDropdownBar = "//span[contains(@id, 'select2-workflow_link_edge-container')]"; -const linkAlwaysDropdown = "//li[contains(@id, 'select2-workflow_link_edge') and text()='Always']"; -const linkSuccessDropdown = "//li[contains(@id, 'select2-workflow_link_edge') and text()='On Success']"; -const linkFailureDropdown = "//li[contains(@id, 'select2-workflow_link_edge') and text()='On Failure']"; -const nodeSelectButton = "//*[@id='workflow_maker_select_node_btn']"; -const linkSelectButton = "//*[@id='workflow_maker_select_link_btn']"; -const nodeCancelButton = "//*[@id='workflow_maker_cancel_node_btn']"; -const deleteConfirmation = "//button[@ng-click='confirmDeleteNode()']"; - -const xPathNodeById = (id) => `//*[@id='node-${id}']`; -const xPathLinkById = (sourceId, targetId) => `//*[@id='link-${sourceId}-${targetId}']//*[contains(@class, 'WorkflowChart-linkPath')]`; -const xPathNodeByName = (name) => `//*[contains(@class, "WorkflowChart-nameText") and contains(text(), "${name}")]/..`; - -module.exports = { - before: (client, done) => { - // Ensure deterministic state on retries - const testID = uuid().substr(0, 8); - const namespace = `test-actions-${testID}`; - const resources = [ - getInventorySource(namespace), - getJobTemplate(namespace), - getProject(namespace), - getWorkflowTemplate(namespace), - ]; - - Promise.all(resources) - .then(([inventory, template, project, workflow]) => { - data = { inventory, template, project, workflow }; - client - .login() - .waitForAngular() - .resizeWindow(1200, 1000) - .navigateTo(`${AWX_E2E_URL}/#/templates`, false) - .useXpath() - .waitForElementVisible(workflowSearchBar) - .setValue(workflowSearchBar, [`name.iexact:"${data.workflow.name}"`]) - .click('//*[contains(@class, "SmartSearch-searchButton")]') - .waitForSpinny(true) - .click(`//a[text()="${namespace}-workflow-template"]`) - .waitForElementVisible(workflowVisualizerBtn) - .click(workflowVisualizerBtn) - .waitForSpinny(true); - client.waitForElementVisible(xPathNodeByName(`${namespace}-job`)); - // Grab the ids of the nodes - client.getAttribute(xPathNodeByName(`${namespace}-job`), 'id', (res) => { - initialJobNodeId = res.value.split('-')[1]; - }); - client.getAttribute(xPathNodeByName(`${namespace}-pro`), 'id', (res) => { - initialProjectNodeId = res.value.split('-')[1]; - }); - client.getAttribute(xPathNodeByName(`${namespace}-inv`), 'id', (res) => { - initialInventoryNodeId = res.value.split('-')[1]; - }); - done(); - }); - }, - 'verify that workflow visualizer new root node can only be set to always': client => { - client - .useXpath() - .findThenClick(xPathNodeById(startNodeId)) - .waitForElementPresent(edgeTypeDropdownBar) - .findThenClick(edgeTypeDropdownBar) - .waitForElementNotPresent(successDropdown) - .waitForElementNotPresent(failureDropdown) - .waitForElementPresent(alwaysDropdown) - .click(nodeCancelButton) - // Make sure that the animation finishes before moving on to the next test - .pause(500); - }, - 'verify that a link can be changed': client => { - client - .useXpath() - .moveToElement(xPathLinkById(initialJobNodeId, initialInventoryNodeId), 20, 0, () => { - client.waitForElementNotVisible(spinny); - client.mouseButtonClick(0); - }) - .waitForElementPresent(linkEdgeTypeDropdownBar) - .findThenClick(linkEdgeTypeDropdownBar) - .waitForElementPresent(linkSuccessDropdown) - .waitForElementPresent(linkFailureDropdown) - .waitForElementPresent(linkAlwaysDropdown) - .findThenClick(linkSuccessDropdown) - .click(linkSelectButton); - }, - 'verify that a new sibling node can be any edge type': client => { - client - .useXpath() - .moveToElement(xPathNodeById(initialJobNodeId), 0, 0, () => { - client.pause(500); - client.waitForElementNotVisible(spinny); - // Concatenating the xpaths lets us click the proper node - client.click(xPathNodeById(initialJobNodeId) + nodeAdd); - }) - .pause(1000) - .waitForElementNotVisible(spinny); - - // Grab the id of the new child node for later - client.getAttribute('//*[contains(@class, "WorkflowChart-isNodeBeingAdded")]/..', 'id', (res) => { - newChildNodeId = res.value.split('-')[1]; - }); - - client - .waitForElementVisible(jobSearchBar) - .clearValue(jobSearchBar) - .setValue(jobSearchBar, [`name.iexact:"${data.template.name}"`, client.Keys.ENTER]) - .pause(1000) - .findThenClick(`//div[contains(@class, "List-tableCell") and contains(text(), "${data.template.name}")]`) - .pause(1000) - .waitForElementNotVisible(spinny) - .findThenClick(edgeTypeDropdownBar) - .waitForElementPresent(successDropdown) - .waitForElementPresent(failureDropdown) - .waitForElementPresent(alwaysDropdown) - .findThenClick(alwaysDropdown) - .click(nodeSelectButton); - }, - 'Verify node-shifting behavior upon deletion': client => { - client - .moveToElement(xPathNodeById(newChildNodeId), 0, 0, () => { - client.pause(500); - client.waitForElementNotVisible(spinny); - client.click(xPathNodeById(newChildNodeId) + nodeAdd); - }) - .pause(1000) - .waitForElementNotVisible(spinny); - - // Grab the id of the new child node for later - client.getAttribute('//*[contains(@class, "WorkflowChart-isNodeBeingAdded")]/..', 'id', (res) => { - // I had to nest this logic in order to ensure that leafNodeId was available later on. - // Separating this out resulted in leafNodeId being `undefined` when sent to - // xPathLinkById - leafNodeId = res.value.split('-')[1]; - client - .waitForElementVisible(jobSearchBar) - .clearValue(jobSearchBar) - .setValue(jobSearchBar, [`name.iexact:"${data.template.name}"`, client.Keys.ENTER]) - .pause(1000) - .findThenClick(`//div[contains(@class, "List-tableCell") and contains(text(), "${data.template.name}")]`) - .pause(1000) - .waitForElementNotVisible(spinny) - .findThenClick(edgeTypeDropdownBar) - .waitForElementPresent(successDropdown) - .waitForElementPresent(failureDropdown) - .waitForElementPresent(alwaysDropdown) - .findThenClick(alwaysDropdown) - .click(nodeSelectButton) - .moveToElement(xPathNodeById(newChildNodeId), 0, 0, () => { - client.pause(500); - client.waitForElementNotVisible(spinny); - client.click(xPathNodeById(newChildNodeId) + nodeRemove); - }) - .pause(1000) - .waitForElementNotVisible(spinny) - .findThenClick(deleteConfirmation) - .waitForElementVisible(xPathLinkById(initialJobNodeId, leafNodeId)); - }); - }, - after: client => { - client.end(); - } -}; diff --git a/awx/ui/test/e2e/tests/test-xss.js b/awx/ui/test/e2e/tests/test-xss.js deleted file mode 100644 index 82de6c8efac7..000000000000 --- a/awx/ui/test/e2e/tests/test-xss.js +++ /dev/null @@ -1,756 +0,0 @@ -import { - getAdminMachineCredential, - getHost, - getInventory, - getInventoryScript, - getInventorySource, - getInventorySourceSchedule, - getJobTemplate, - getJobTemplateSchedule, - getNotificationTemplate, - getOrganization, - getProjectAdmin, - getSmartInventory, - getTeam, - getUpdatedProject, - getJob, -} from '../fixtures'; - -const data = {}; -const urls = {}; -const pages = {}; - -module.exports = { - before: (client, done) => { - const namespace = '
test
'; - const namespaceShort = '
t
'; - - const resources = [ - getOrganization(namespace).then(obj => { data.organization = obj; }), - getHost(namespaceShort).then(obj => { data.host = obj; }), - getInventory(namespace).then(obj => { data.inventory = obj; }), - getInventoryScript(namespace).then(obj => { data.inventoryScript = obj; }), - getSmartInventory(namespace).then(obj => { data.smartInventory = obj; }), - getInventorySource(namespace).then(obj => { data.inventorySource = obj; }), - getInventorySourceSchedule(namespace).then(obj => { data.sourceSchedule = obj; }), - getUpdatedProject(namespace).then(obj => { data.project = obj; }), - getAdminMachineCredential(namespace).then(obj => { data.credential = obj; }), - getJobTemplate(namespace).then(obj => { data.jobTemplate = obj; }), - getJobTemplateSchedule(namespace).then(obj => { data.jobTemplateSchedule = obj; }), - getTeam(namespace).then(obj => { data.team = obj; }), - getProjectAdmin(namespace).then(obj => { data.user = obj; }), - getNotificationTemplate(namespace).then(obj => { data.notification = obj; }), - getJob(namespace).then(obj => { data.job = obj; }), - ]; - - Promise.all(resources) - .then(() => { - pages.organizations = client.page.organizations(); - pages.inventories = client.page.inventories(); - pages.inventoryScripts = client.page.inventoryScripts(); - pages.projects = client.page.projects(); - pages.credentials = client.page.credentials(); - pages.templates = client.page.templates(); - pages.teams = client.page.teams(); - pages.users = client.page.users(); - pages.notificationTemplates = client.page.notificationTemplates(); - pages.jobs = client.page.jobs(); - - urls.organization = `${pages.organizations.url()}/${data.organization.id}`; - urls.inventory = `${pages.inventories.url()}/inventory/${data.inventory.id}`; - urls.inventoryScript = `${pages.inventoryScripts.url()}/${data.inventoryScript.id}`; - urls.inventorySource = `${urls.inventory}/inventory_sources/edit/${data.inventorySource.id}`; - urls.sourceSchedule = `${urls.inventorySource}/schedules/${data.sourceSchedule.id}`; - urls.smartInventory = `${pages.inventories.url()}/smart/${data.smartInventory.id}`; - urls.project = `${pages.projects.url()}/${data.project.id}`; - urls.credential = `${pages.credentials.url()}/${data.credential.id}`; - urls.jobTemplate = `${pages.templates.url()}/job_template/${data.jobTemplate.id}`; - urls.jobTemplateSchedule = `${urls.jobTemplate}/schedules/${data.jobTemplateSchedule.id}`; - urls.team = `${pages.teams.url()}/${data.team.id}`; - urls.user = `${pages.users.url()}/${data.user.id}`; - urls.notification = `${pages.notificationTemplates.url()}/${data.notification.id}`; - urls.jobs = `${pages.jobs.url()}`; - urls.jobsSchedules = `${pages.jobs.url()}/schedules`; - urls.inventoryHosts = `${pages.inventories.url()}/inventory/${data.host.summary_fields.inventory.id}/hosts`; - - client - .useCss() - .login() - .waitForAngular() - .resizeWindow(1200, 800); - - done(); - }); - }, - 'check template form for unsanitized content': client => { - const multiCredentialOpen = 'multi-credential button i[class*="search"]'; - - client.navigateTo(urls.jobTemplate, false); - - client.expect.element('#job_template_form').visible; - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - - client.expect.element(multiCredentialOpen).visible; - client.expect.element(multiCredentialOpen).enabled; - - client.pause(2000).click(multiCredentialOpen); - - client.expect.element('#multi-credential-modal').visible; - - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - - client.pause(500); - - client.waitForElementVisible('#multi-credential-modal .Form-exit'); - client.waitForElementNotVisible('.overlay'); - - client.click('#multi-credential-modal .Form-exit'); - - client.waitForElementNotPresent('#multi-credential-modal'); - }, - 'check template list for unsanitized content': client => { - const itemRow = `#row-${data.jobTemplate.id}`; - const itemName = `${itemRow} .at-RowItem-header`; - - client.expect.element('.at-Panel smart-search').visible; - client.expect.element('.at-Panel smart-search input').enabled; - - client.sendKeys('.at-Panel smart-search input', `id:>${data.jobTemplate.id - 1} id:<${data.jobTemplate.id + 1}`); - client.sendKeys('.at-Panel smart-search input', client.Keys.ENTER); - - client.expect.element('div.spinny').not.visible; - - client.expect.element('.at-Panel-headingTitleBadge').text.equal('1'); - client.expect.element(itemName).visible; - - // TODO: uncomment when tooltips are added - // client.moveToElement(itemName, 0, 0, () => { - // client.expect.element(itemName).attribute('aria-describedby'); - // - // client.getAttribute(itemName, 'aria-describedby', ({ value }) => { - // const tooltip = `#${value}`; - // - // client.expect.element(tooltip).present; - // client.expect.element(tooltip).visible; - // - // client.expect.element('#xss').not.present; - // client.expect.element('[class=xss]').not.present; - // client.expect.element(tooltip).attribute('innerHTML') - // .contains('<div id="xss" class="xss">test</div>'); - // }); - // }); - - client.click(`${itemRow} i[class*="trash"]`); - - client.expect.element('#prompt-header').visible; - client.expect.element('#prompt_cancel_btn').enabled; - - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - - client.click('#prompt_cancel_btn'); - if (client.isVisible('#prompt_cancel_btn')) { - client.click('#prompt_cancel_btn'); - } - - client.expect.element('#prompt-header').not.visible; - }, - 'check user form for unsanitized content': client => { - client.navigateTo(urls.user); - - client.expect.element('#user_form').visible; - - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - }, - 'check user roles list for unsanitized content': client => { - const adminRole = data.project.summary_fields.object_roles.admin_role; - const itemDelete = `#permissions_table .List-tableRow[id="${adminRole.id}"] #delete-action`; - - client.expect.element('#permissions_tab').visible; - client.expect.element('#permissions_tab').enabled; - - client.pause(2000); - client.findThenClick('#permissions_tab', 'css'); - - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - - client.expect.element('div[ui-view="related"]').visible; - client.expect.element('div[ui-view="related"] smart-search input').enabled; - - client.sendKeys('div[ui-view="related"] smart-search input', `id:>${adminRole.id - 1} id:<${adminRole.id + 1}`); - client.sendKeys('div[ui-view="related"] smart-search input', client.Keys.ENTER); - - client.expect.element('div.spinny').not.visible; - - client.expect.element(itemDelete).visible; - client.expect.element(itemDelete).enabled; - - client.click(itemDelete); - - client.expect.element('#prompt-header').visible; - client.expect.element('#prompt-header').text.equal('REMOVE ROLE'); - client.expect.element('#prompt_cancel_btn').enabled; - - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - - client.click('#prompt_cancel_btn'); - - client.expect.element('#prompt-header').not.visible; - }, - 'check user permissions view for unsanitized content': client => { - client.expect.element('button[aw-tool-tip="Grant Permission"]').enabled; - - client.click('button[aw-tool-tip="Grant Permission"]'); - - client.expect.element('div.spinny').visible; - client.expect.element('div.spinny').not.visible; - client.expect.element('div[class="AddPermissions-header"]').visible; - - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - - client.expect.element('div[class="AddPermissions-dialog"] button[class*="exit"]').enabled; - - client.click('div[class="AddPermissions-dialog"] button[class*="exit"]'); - - client.expect.element('div.spinny').visible; - client.expect.element('div.spinny').not.visible; - }, - 'check notification form for unsanitized content': client => { - client.navigateTo(urls.notification); - - client.expect.element('#notification_template_form').visible; - - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - }, - 'check notification list for unsanitized content': client => { - const itemRow = `#notification_templates_table .List-tableRow[id="${data.notification.id}"]`; - const itemName = `${itemRow} .List-tableCell[class*="name-"] a`; - - client.expect.element('div.at-Panel smart-search').visible; - client.expect.element('div.at-Panel smart-search input').enabled; - - client.sendKeys('div.at-Panel smart-search input', `id:>${data.notification.id - 1} id:<${data.notification.id + 1}`); - client.waitForElementNotPresent('div.at-Panel smart-search .SmartSearch-searchButton--disabled'); - client.waitForElementNotVisible('.overlay'); - client.pause(2000); - client.click('div.at-Panel smart-search .SmartSearch-searchButton'); - - client.expect.element('div.spinny').visible; - client.expect.element('div.spinny').not.visible; - - client.expect.element('.List-titleBadge').text.equal('1'); - client.expect.element(itemName).visible; - - client.moveToElement(itemName, 0, 0, () => { - client.expect.element(itemName).attribute('aria-describedby'); - - client.getAttribute(itemName, 'aria-describedby', ({ value }) => { - const tooltip = `#${value}`; - - client.expect.element(tooltip).present; - client.expect.element(tooltip).visible; - - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - client.expect.element(tooltip).attribute('innerHTML') - .contains('<div id="xss" class="xss">test</div>'); - }); - }); - - client.click(`${itemRow} i[class*="trash"]`); - - client.expect.element('#prompt-header').visible; - client.expect.element('#prompt_cancel_btn').enabled; - - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - - client.click('#prompt_cancel_btn'); - if (client.isVisible('#prompt_cancel_btn')) { - client.click('#prompt_cancel_btn'); - } - - client.expect.element('#prompt-header').not.visible; - }, - 'check organization form for unsanitized content': client => { - client.navigateTo(urls.organization); - - client.expect.element('#organization_form').visible; - - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - }, - 'check organization list for unsanitized content': client => { - const itemName = '#OrgCards h3[class*="-label"]'; - - client.expect.element('div.at-Panel smart-search').visible; - client.expect.element('div.at-Panel smart-search input').enabled; - - client.sendKeys('div.at-Panel smart-search input', `id:>${data.organization.id - 1} id:<${data.organization.id + 1}`); - client.waitForElementNotPresent('div.at-Panel smart-search .SmartSearch-searchButton--disabled'); - client.waitForElementNotVisible('.overlay'); - client.click('div.at-Panel smart-search .SmartSearch-searchButton'); - - client.expect.element('div.spinny').visible; - client.expect.element('div.spinny').not.visible; - - client.expect.element(itemName).visible; - - client.moveToElement(itemName, 0, 0, () => { - client.expect.element(itemName).attribute('aria-describedby'); - - client.getAttribute(itemName, 'aria-describedby', ({ value }) => { - const tooltip = `#${value}`; - - client.expect.element(tooltip).present; - client.expect.element(tooltip).visible; - - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - client.expect.element(tooltip).attribute('innerHTML') - .contains('<div id="xss" class="xss">test</div>'); - }); - }); - - client.click('#OrgCards i[class*="trash"]'); - - client.expect.element('#prompt-header').visible; - client.expect.element('#prompt_cancel_btn').enabled; - - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - - client.click('#prompt_cancel_btn'); - if (client.isVisible('#prompt_cancel_btn')) { - client.click('#prompt_cancel_btn'); - } - - client.expect.element('#prompt-header').not.visible; - }, - 'check inventory form for unsanitized content': client => { - client.navigateTo(urls.inventory); - - client.expect.element('#inventory_form').visible; - - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - }, - 'check inventory list for unsanitized content': client => { - const itemRow = `#inventories_table .List-tableRow[id="${data.inventory.id}"]`; - const itemName = `${itemRow} .List-tableCell[class*="name-"] a`; - - client.expect.element('div.at-Panel smart-search').visible; - client.expect.element('div.at-Panel smart-search input').enabled; - - client.sendKeys('div.at-Panel smart-search input', `id:>${data.inventory.id - 1} id:<${data.inventory.id + 1}`); - client.waitForElementNotPresent('div.at-Panel smart-search .SmartSearch-searchButton--disabled'); - client.waitForElementNotVisible('.overlay'); - client.click('div.at-Panel smart-search .SmartSearch-searchButton'); - - client.expect.element('div.spinny').visible; - client.expect.element('div.spinny').not.visible; - - // client.expect.element('.List-titleBadge').text.equal('1'); - client.expect.element(itemName).visible; - - client.moveToElement(itemName, 0, 0, () => { - client.expect.element(itemName).attribute('aria-describedby'); - - client.getAttribute(itemName, 'aria-describedby', ({ value }) => { - const tooltip = `#${value}`; - - client.expect.element(tooltip).present; - client.expect.element(tooltip).visible; - - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - client.expect.element(tooltip).attribute('innerHTML') - .contains('<div id="xss" class="xss">test</div>'); - }); - }); - - client.click(`${itemRow} i[class*="trash"]`); - - client.expect.element('#prompt-header').visible; - client.expect.element('#prompt_cancel_btn').enabled; - - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - - client.click('#prompt_cancel_btn'); - if (client.isVisible('#prompt_cancel_btn')) { - client.click('#prompt_cancel_btn'); - } - - client.expect.element('#prompt-header').not.visible; - }, - 'check smart inventory form for unsanitized content': client => { - client.navigateTo(urls.smartInventory, false); - - client.expect.element('#smartinventory_form').visible; - - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - }, - 'check inventory script form for unsanitized content': client => { - client.navigateTo(urls.inventoryScript); - - client.expect.element('#inventory_script_form').visible; - - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - }, - 'check inventory script list for unsanitized content': client => { - const itemRow = `#inventory_scripts_table .List-tableRow[id="${data.inventoryScript.id}"]`; - const itemName = `${itemRow} .List-tableCell[class*="name-"] a`; - - client.expect.element('div.at-Panel smart-search').visible; - client.expect.element('div.at-Panel smart-search input').enabled; - - client.sendKeys('div.at-Panel smart-search input', `id:>${data.inventoryScript.id - 1} id:<${data.inventoryScript.id + 1}`); - client.waitForElementNotPresent('div.at-Panel smart-search .SmartSearch-searchButton--disabled'); - client.waitForElementNotVisible('.overlay'); - client.click('div.at-Panel smart-search .SmartSearch-searchButton'); - - client.expect.element('div.spinny').visible; - client.expect.element('div.spinny').not.visible; - - client.expect.element('.List-titleBadge').text.equal('1'); - client.expect.element(itemName).visible; - - client.moveToElement(itemName, 0, 0, () => { - client.expect.element(itemName).attribute('aria-describedby'); - - client.getAttribute(itemName, 'aria-describedby', ({ value }) => { - const tooltip = `#${value}`; - - client.expect.element(tooltip).present; - client.expect.element(tooltip).visible; - - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - client.expect.element(tooltip).attribute('innerHTML') - .contains('<div id="xss" class="xss">test</div>'); - }); - }); - - client.click(`${itemRow} i[class*="trash"]`); - - client.expect.element('#prompt-header').visible; - client.expect.element('#prompt_cancel_btn').enabled; - - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - - client.click('#prompt_cancel_btn'); - if (client.isVisible('#prompt_cancel_btn')) { - client.click('#prompt_cancel_btn'); - } - - client.expect.element('#prompt-header').not.visible; - }, - 'check project form for unsanitized content': client => { - client.navigateTo(urls.project); - - client.expect.element('#project_form').visible; - - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - }, - 'check project roles list for unsanitized content': client => { - const itemDelete = `#permissions_table .List-tableRow[id="${data.user.id}"] div[class*="RoleList-deleteContainer"]`; - - client.expect.element('#permissions_tab').visible; - client.expect.element('#permissions_tab').enabled; - - client.pause(1000); - client.click('#permissions_tab'); - client.click('#permissions_tab'); - - client.expect.element('div.spinny').visible; - client.expect.element('div.spinny').not.visible; - - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - - client.expect.element('div[ui-view="related"]').visible; - client.expect.element('div[ui-view="related"] smart-search input').enabled; - - client.sendKeys('div[ui-view="related"] smart-search input', `id:>${data.user.id - 1} id:<${data.user.id + 1}`); - client.waitForElementNotPresent('div[ui-view="related"] smart-search .SmartSearch-searchButton--disabled'); - client.waitForElementNotVisible('.overlay'); - client.click('div[ui-view="related"] smart-search .SmartSearch-searchButton'); - - client.expect.element('div.spinny').not.visible; - - client.expect.element(itemDelete).visible; - client.expect.element(itemDelete).enabled; - - client.click(itemDelete); - - client.expect.element('#prompt-header').visible; - client.expect.element('#prompt-header').text.equal('USER ACCESS REMOVAL'); - client.expect.element('#prompt_cancel_btn').enabled; - - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - - client.click('#prompt_cancel_btn'); - - client.expect.element('#prompt-header').not.visible; - }, - 'check project permissions view for unsanitized content': client => { - client.expect.element('button[aw-tool-tip="Add a permission"]').visible; - client.expect.element('button[aw-tool-tip="Add a permission"]').enabled; - - client.click('button[aw-tool-tip="Add a permission"]'); - client.expect.element('div.spinny').not.visible; - - client.expect.element('div[class="AddPermissions-header"]').visible; - client.expect.element('div[class="AddPermissions-header"]').attribute('innerHTML') - .contains('<div id="xss" class="xss">test</div>'); - - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - - client.expect.element('div[class="AddPermissions-dialog"] button[class*="exit"]').enabled; - - client.click('div[class="AddPermissions-dialog"] button[class*="exit"]'); - - client.expect.element('div.spinny').visible; - client.expect.element('div.spinny').not.visible; - - // client.expect.element('div.spinny').visible; - client.expect.element('div.spinny').not.visible; - client.waitForAngular(); - - client.expect.element('#project_tab').enabled; - - client.click('#project_tab'); - - client.expect.element('#project_form').visible; - }, - 'check project list for unsanitized content': client => { - const itemRow = `#row-${data.project.id}`; - const itemName = `${itemRow} .at-RowItem-header`; - - client.expect.element('.at-Panel smart-search').visible; - client.expect.element('.at-Panel smart-search input').enabled; - - client.sendKeys('.at-Panel smart-search input', `id:>${data.project.id - 1} id:<${data.project.id + 1}`); - client.waitForElementNotPresent('div.at-Panel smart-search .SmartSearch-searchButton--disabled'); - client.waitForElementNotVisible('.overlay'); - client.click('div.at-Panel smart-search .SmartSearch-searchButton'); - - client.expect.element('div.spinny').not.visible; - - client.expect.element('.at-Panel-headingTitleBadge').text.equal('1'); - client.expect.element(itemName).visible; - - // TODO: uncomment when tooltips are added - // client.moveToElement(itemName, 0, 0, () => { - // client.expect.element(itemName).attribute('aria-describedby'); - // - // client.getAttribute(itemName, 'aria-describedby', ({ value }) => { - // const tooltip = `#${value}`; - // - // client.expect.element(tooltip).present; - // client.expect.element(tooltip).visible; - // - // client.expect.element('#xss').not.present; - // client.expect.element('[class=xss]').not.present; - // client.expect.element(tooltip).attribute('innerHTML') - // .contains('<div id="xss" class="xss">test</div>'); - // }); - // }); - - client.click(`${itemRow} i[class*="trash"]`); - - client.expect.element('#prompt-header').visible; - client.expect.element('#prompt_cancel_btn').enabled; - - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - - client.click('#prompt_cancel_btn'); - if (client.isVisible('#prompt_cancel_btn')) { - client.click('#prompt_cancel_btn'); - } - - client.expect.element('#prompt-header').not.visible; - }, - 'check credential form for unsanitized content': client => { - client.navigateTo(urls.credential); - - client.expect.element('div[ui-view="edit"] form').visible; - - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - }, - 'check credential list for unsanitized content': client => { - const itemRow = `#credentials_table .List-tableRow[id="${data.credential.id}"]`; - const itemName = `${itemRow} .List-tableCell[class*="name-"] a`; - - client.expect.element('div[ui-view="list"] smart-search').visible; - client.expect.element('div[ui-view="list"] smart-search input').enabled; - - client.sendKeys('div[ui-view="list"] smart-search input', `id:>${data.credential.id - 1} id:<${data.credential.id + 1}`); - client.waitForElementNotPresent('div[ui-view="list"] smart-search .SmartSearch-searchButton--disabled'); - client.waitForElementNotVisible('.overlay'); - client.click('div[ui-view="list"] smart-search .SmartSearch-searchButton'); - - client.expect.element('div.spinny').visible; - client.expect.element('div.spinny').not.visible; - - client.expect.element('.List-titleBadge').text.equal('1'); - client.expect.element(itemName).visible; - - client.moveToElement(itemName, 0, 0, () => { - client.expect.element(itemName).attribute('aria-describedby'); - - client.getAttribute(itemName, 'aria-describedby', ({ value }) => { - const tooltip = `#${value}`; - - client.expect.element(tooltip).present; - client.expect.element(tooltip).visible; - - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - client.expect.element(tooltip).attribute('innerHTML') - .contains('<div id="xss" class="xss">test</div>'); - }); - }); - - client.click(`${itemRow} i[class*="trash"]`); - - client.expect.element('#prompt-header').visible; - client.expect.element('#prompt_cancel_btn').enabled; - - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - - client.click('#prompt_cancel_btn'); - if (client.isVisible('#prompt_cancel_btn')) { - client.click('#prompt_cancel_btn'); - } - - client.expect.element('#prompt-header').not.visible; - }, - 'check team form for unsanitized content': client => { - client.navigateTo(urls.team); - - client.expect.element('#team_form').visible; - - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - }, - 'check team list for unsanitized content': client => { - const itemRow = `#teams_table .List-tableRow[id="${data.team.id}"]`; - const itemName = `${itemRow} .List-tableCell[class*="name-"] a`; - - client.expect.element('div.at-Panel smart-search').visible; - client.expect.element('div.at-Panel smart-search input').enabled; - - client.sendKeys('div.at-Panel smart-search input', `id:>${data.team.id - 1} id:<${data.team.id + 1}`); - client.waitForElementNotPresent('div.at-Panel smart-search .SmartSearch-searchButton--disabled'); - client.waitForElementNotVisible('.overlay'); - client.click('div.at-Panel smart-search .SmartSearch-searchButton'); - - client.expect.element('div.spinny').visible; - client.expect.element('div.spinny').not.visible; - - client.expect.element('.List-titleBadge').text.equal('1'); - client.expect.element(itemName).visible; - - client.moveToElement(itemName, 0, 0, () => { - client.expect.element(itemName).attribute('aria-describedby'); - - client.getAttribute(itemName, 'aria-describedby', ({ value }) => { - const tooltip = `#${value}`; - - client.expect.element(tooltip).present; - client.expect.element(tooltip).visible; - - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - client.expect.element(tooltip).attribute('innerHTML') - .contains('<div id="xss" class="xss">test</div>'); - }); - }); - - client.click(`${itemRow} i[class*="trash"]`); - - client.expect.element('#prompt-header').visible; - client.expect.element('#prompt_cancel_btn').enabled; - - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - - client.click('#prompt_cancel_btn'); - if (client.isVisible('#prompt_cancel_btn')) { - client.click('#prompt_cancel_btn'); - } - - client.expect.element('#prompt-header').not.visible; - }, - 'check inventory source schedule view for unsanitized content': client => { - client.navigateTo(urls.sourceSchedule); - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - }, - 'check job template schedule view for unsanitized content': client => { - client.navigateTo(urls.jobTemplateSchedule); - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - }, - 'check job schedules view for unsanitized content': client => { - const itemRow = `#schedules_table .List-tableRow[id="${data.jobTemplateSchedule.id}"]`; - const itemName = `${itemRow} .List-tableCell[class*="name-"] a`; - - client.navigateTo(urls.jobsSchedules); - - client.moveToElement(itemName, 0, 0, () => { - client.expect.element(itemName).attribute('aria-describedby'); - client.getAttribute(itemName, 'aria-describedby', ({ value }) => { - const tooltip = `#${value}`; - client.expect.element(tooltip).present; - client.expect.element(tooltip).visible; - - client.expect.element('#xss').not.present; - client.expect.element('[class=xss]').not.present; - client.expect.element(tooltip).attribute('innerHTML') - .contains('<div id="xss" class="xss">test</div>'); - }); - }); - }, - 'check host recent jobs popup for unsanitized content': client => { - const itemRow = `#hosts_table .List-tableRow[id="${data.host.id}"]`; - const itemName = `${itemRow} .List-tableCell[class*="active_failures-"] a`; - - client.navigateTo(urls.inventoryHosts); - client.expect.element('div.at-Panel smart-search').visible; - client.expect.element('div.at-Panel smart-search input').enabled; - - client.sendKeys('div[ui-view="form"] smart-search input', `id:>${data.host.id - 1} id:<${data.host.id + 1}`); - client.waitForElementNotPresent('div[ui-view="form"] smart-search .SmartSearch-searchButton--disabled'); - client.waitForElementNotVisible('.overlay'); - client.click('div[ui-view="form"] smart-search .SmartSearch-searchButton'); - - client.expect.element('div.spinny').visible; - client.expect.element('div.spinny').not.visible; - - client.click(itemName); - client.expect.element('body > div.popover').present; - - client.expect.element('[class=xss]').not.present; - - client.end(); - }, -}; diff --git a/awx/ui/test/spec/column-sort/column-sort.directive-test.js b/awx/ui/test/spec/column-sort/column-sort.directive-test.js deleted file mode 100644 index 64b0eb497103..000000000000 --- a/awx/ui/test/spec/column-sort/column-sort.directive-test.js +++ /dev/null @@ -1,107 +0,0 @@ -'use strict'; - -xdescribe('Directive: column-sort', () =>{ - - let $scope, template, $compile, QuerySet, GetBasePath; - - beforeEach(angular.mock.module('templateUrl')); - beforeEach(function(){ - - this.mock = { - dataset: [ - {name: 'zero', idx: 0}, - {name: 'one', idx: 1}, - {name: 'two', idx: 2} - ] - }; - - this.name_field = angular.element(` - `); - - this.idx_field = angular.element(` - `); - - this.$state = { - params: {}, - go: jasmine.createSpy('go') - }; - - this.$stateParams = {}; - - var mockFilter = function (value) { - return value; - }; - - angular.mock.module('ColumnSortModule', ($provide) =>{ - - QuerySet = jasmine.createSpyObj('qs', ['search']); - QuerySet.search.and.callFake(() => { return { then: function(){} }; }); - GetBasePath = jasmine.createSpy('GetBasePath'); - $provide.value('QuerySet', QuerySet); - $provide.value('GetBasePath', GetBasePath); - $provide.value('$state', this.$state); - $provide.value('$stateParams', this.$stateParams); - $provide.value("translateFilter", mockFilter); - - }); - }); - - beforeEach(angular.mock.inject(($templateCache, _$rootScope_, _$compile_) => { - template = window.__html__['client/src/shared/column-sort/column-sort.partial.html']; - $templateCache.put('/static/partials/shared/column-sort/column-sort.partial.html', template); - - $compile = _$compile_; - $scope = _$rootScope_.$new(); - })); - - it('should be ordered by name', function(){ - - this.$state.params = { - mock_search: {order_by: 'name'} - }; - - $compile(this.name_field)($scope); - $compile(this.idx_field)($scope); - - $scope.$digest(); - expect( $(this.name_field).find('.columnSortIcon').hasClass('fa-sort-up') ).toEqual(true); - expect( $(this.idx_field).find('.columnSortIcon').hasClass('fa-sort') ).toEqual(true); - }); - - it('should toggle to ascending name order, then ascending idx, then descending idx', function(){ - - this.$state.params = { - mock_search: {order_by: 'idx'} - }; - - $compile(this.name_field)($scope); - $compile(this.idx_field)($scope); - - $scope.$digest(); - - $(this.name_field).click(); - expect( $(this.name_field).find('.columnSortIcon').hasClass('fa-sort-up') ).toEqual(true); - expect( $(this.idx_field).find('.columnSortIcon').hasClass('fa-sort') ).toEqual(true); - - $(this.idx_field).click(); - expect( $(this.name_field).find('.columnSortIcon').hasClass('fa-sort') ).toEqual(true); - expect( $(this.idx_field).find('.columnSortIcon').hasClass('fa-sort-up') ).toEqual(true); - - $(this.idx_field).click(); - expect( $(this.name_field).find('.columnSortIcon').hasClass('fa-sort') ).toEqual(true); - expect( $(this.idx_field).find('.columnSortIcon').hasClass('fa-sort-down') ).toEqual(true); - }); - -}); diff --git a/awx/ui/test/spec/instance-groups/instance-groups.service-test.js b/awx/ui/test/spec/instance-groups/instance-groups.service-test.js deleted file mode 100644 index 082cd782a260..000000000000 --- a/awx/ui/test/spec/instance-groups/instance-groups.service-test.js +++ /dev/null @@ -1,73 +0,0 @@ -'use strict'; - -const mockUrl = ''; -const mockSavedInstanceGroups = { - data: { - results: [ - { id: 1 }, - { id: 2 }, - { id: 3 }, - { id: 4 }, - { id: 5 }, - { id: 6 }, - ] - } -}; - -const _postData = []; -function MockRest() { - return { - setUrl (){}, - get () { - return Promise.resolve(mockSavedInstanceGroups); - }, - post (data) { - _postData.push(data); - return Promise.resolve({}); - }, - } -} - -describe('instanceGroupsService', () => { - let instanceGroupsService; - - beforeEach(() => { - angular.mock.module('awApp'); - angular.mock.module($provide => { - $provide.service('Rest', MockRest); - }); - angular.mock.module('instanceGroups'); - angular.mock.inject($injector => { - instanceGroupsService = $injector.get('InstanceGroupsService'); - }); - }); - - describe('editInstanceGroups', () => { - it('makes the expected requests', (done) => { - const selectedInstanceGroups = [ - { id: 1 }, - { id: 2 }, - { id: 4 }, - { id: 5 }, - { id: 6 }, - { id: 7 }, - { id: 3 }, - ]; - instanceGroupsService.editInstanceGroups(mockUrl, selectedInstanceGroups) - .then(() => { - expect(_postData).toEqual([ - { id: 3, disassociate: true }, - { id: 4, disassociate: true }, - { id: 5, disassociate: true }, - { id: 6, disassociate: true }, - { id: 4, associate: true }, - { id: 5, associate: true }, - { id: 6, associate: true }, - { id: 7, associate: true }, - { id: 3, associate: true }, - ]); - }) - .finally(() => done()); - }); - }); -}); diff --git a/awx/ui/test/spec/inventories/insights/data/high.insights-data.js b/awx/ui/test/spec/inventories/insights/data/high.insights-data.js deleted file mode 100644 index bd0671cc8f1e..000000000000 --- a/awx/ui/test/spec/inventories/insights/data/high.insights-data.js +++ /dev/null @@ -1,149 +0,0 @@ -export default [ - { - "details": { - "vulnerable_setting": "hosts: files dns", - "affected_package": "glibc-2.17-55.el7", - "error_key": "GLIBC_CVE_2015_7547" - }, - "id": 709784455, - "rule_id": "CVE_2015_7547_glibc|GLIBC_CVE_2015_7547", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

A critical security flaw in the glibc library was found. It allows an attacker to crash an application built against that library or, potentially, execute arbitrary code with privileges of the user running the application.

\n", - "generic_html": "

The glibc library is vulnerable to a stack-based buffer overflow security flaw. A remote attacker could create specially crafted DNS responses that could cause the libresolv part of the library, which performs dual A/AAAA DNS queries, to crash or potentially execute code with the permissions of the user running the library. The issue is only exposed when libresolv is called from the nss_dns NSS service module. This flaw is known as CVE-2015-7547.

\n", - "more_info_html": "\n", - "severity": "ERROR", - "ansible": true, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "CVE_2015_7547_glibc|GLIBC_CVE_2015_7547", - "error_key": "GLIBC_CVE_2015_7547", - "plugin": "CVE_2015_7547_glibc", - "description": "Remote code execution vulnerability in libresolv via crafted DNS response (CVE-2015-7547)", - "summary": "A critical security flaw in the `glibc` library was found. It allows an attacker to crash an application built against that library or, potentially, execute arbitrary code with privileges of the user running the application.", - "generic": "The `glibc` library is vulnerable to a stack-based buffer overflow security flaw. A remote attacker could create specially crafted DNS responses that could cause the `libresolv` part of the library, which performs dual A/AAAA DNS queries, to crash or potentially execute code with the permissions of the user running the library. The issue is only exposed when `libresolv` is called from the nss_dns NSS service module. This flaw is known as [CVE-2015-7547](https://access.redhat.com/security/cve/CVE-2015-7547).", - "reason": "

This host is vulnerable because it has vulnerable package glibc-2.17-55.el7 installed and DNS is enabled in /etc/nsswitch.conf:

\n
hosts:      files dns\n

The glibc library is vulnerable to a stack-based buffer overflow security flaw. A remote attacker could create specially crafted DNS responses that could cause the libresolv part of the library, which performs dual A/AAAA DNS queries, to crash or potentially execute code with the permissions of the user running the library. The issue is only exposed when libresolv is called from the nss_dns NSS service module. This flaw is known as CVE-2015-7547.

\n", - "type": null, - "more_info": "* For more information about the flaw see [CVE-2015-7547](https://access.redhat.com/security/cve/CVE-2015-7547).\n* To learn how to upgrade packages, see \"[What is yum and how do I use it?](https://access.redhat.com/solutions/9934)\"\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat Products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).", - "active": true, - "node_id": "2168451", - "category": "Security", - "retired": false, - "reboot_required": false, - "publish_date": "2016-10-31T04:08:35.000Z", - "rec_impact": 4, - "rec_likelihood": 2, - "resolution": "

Red Hat recommends updating glibc and restarting the affected system:

\n
# yum update glibc\n# reboot\n

Alternatively, you can restart all affected services, but because this vulnerability affects a large amount of applications on the system, the best solution is to restart the system.

\n" - }, - "maintenance_actions": [] - }, { - "details": { - "mitigation_conf": "no", - "sysctl_live_ack_limit": "100", - "package_name": "kernel", - "sysctl_live_ack_limit_line": "net.ipv4.tcp_challenge_ack_limit = 100", - "error_key": "KERNEL_CVE_2016_5696_URGENT", - "vulnerable_kernel": "3.10.0-123.el7", - "sysctl_conf_ack_limit": "100", - "sysctl_conf_ack_limit_line": "net.ipv4.tcp_challenge_ack_limit=100", - "mitigation_live": "no" - }, - "id": 766342155, - "rule_id": "CVE_2016_5696_kernel|KERNEL_CVE_2016_5696_URGENT", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

A flaw in the Linux kernel's TCP/IP networking subsystem implementation of the RFC 5961 challenge ACK rate limiting was found that could allow an attacker located on different subnet to inject or take over a TCP connection between a server and client without needing to use a traditional man-in-the-middle (MITM) attack.

\n", - "generic_html": "

A flaw was found in the implementation of the Linux kernel's handling of networking challenge ack (RFC 5961) where an attacker is able to determine the\nshared counter. This flaw allows an attacker located on different subnet to inject or take over a TCP connection between a server and client without needing to use a traditional man-in-the-middle (MITM) attack.

\n

Red Hat recommends that you update the kernel package or apply mitigations.

\n", - "more_info_html": "\n", - "severity": "ERROR", - "ansible": true, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "CVE_2016_5696_kernel|KERNEL_CVE_2016_5696_URGENT", - "error_key": "KERNEL_CVE_2016_5696_URGENT", - "plugin": "CVE_2016_5696_kernel", - "description": "Kernel vulnerable to man-in-the-middle via payload injection", - "summary": "A flaw in the Linux kernel's TCP/IP networking subsystem implementation of the [RFC 5961](https://tools.ietf.org/html/rfc5961) challenge ACK rate limiting was found that could allow an attacker located on different subnet to inject or take over a TCP connection between a server and client without needing to use a traditional man-in-the-middle (MITM) attack.", - "generic": "A flaw was found in the implementation of the Linux kernel's handling of networking challenge ack ([RFC 5961](https://tools.ietf.org/html/rfc5961)) where an attacker is able to determine the\nshared counter. This flaw allows an attacker located on different subnet to inject or take over a TCP connection between a server and client without needing to use a traditional man-in-the-middle (MITM) attack. \n\nRed Hat recommends that you update the kernel package or apply mitigations.", - "reason": "

A flaw was found in the implementation of the Linux kernel's handling of networking challenge ack (RFC 5961) where an attacker is able to determine the\nshared counter. This flaw allows an attacker located on different subnet to inject or take over a TCP connection between a server and client without needing to use a traditional man-in-the-middle (MITM) attack.

\n

This host is affected because it is running kernel 3.10.0-123.el7.

\n

Your currently loaded kernel configuration contains this setting:

\n
net.ipv4.tcp_challenge_ack_limit = 100\n

Your currently stored kernel configuration is:

\n
net.ipv4.tcp_challenge_ack_limit=100\n

There is currently no mitigation applied and your system is vulnerable.

\n", - "type": null, - "more_info": "* For more information about the flaw see [CVE-2016-5696](https://access.redhat.com/security/cve/CVE-2016-5696)\n* To learn how to upgrade packages, see \"[What is yum and how do I use it?](https://access.redhat.com/solutions/9934)\"\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat Products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).", - "active": true, - "node_id": "2438571", - "category": "Security", - "retired": false, - "reboot_required": false, - "publish_date": "2016-10-31T04:08:32.000Z", - "rec_impact": 4, - "rec_likelihood": 2, - "resolution": "

Red Hat recommends that you update the kernel package and restart the system:

\n
# yum update kernel\n# reboot\n

or

\n

Alternatively, this issue can be addressed by applying the following mitigations until the machine is restarted with the updated kernel package.

\n

Edit /etc/sysctl.conf file as root, add the mitigation configuration, and reload the kernel configuration:

\n
# echo "net.ipv4.tcp_challenge_ack_limit = 2147483647" >> /etc/sysctl.conf \n# sysctl -p\n
" - }, - "maintenance_actions": [{ - "done": false, - "id": 56045, - "maintenance_plan": { - "maintenance_id": 15875, - "name": "Payload Injection Fix", - "description": "", - "start": "2017-06-01T02:00:00.000Z", - "end": "2017-06-01T03:00:00.000Z", - "created_by": "rhn-support-wnix", - "silenced": false, - "hidden": false, - "suggestion": null, - "remote_branch": null - } - }, { - "done": false, - "id": 61575, - "maintenance_plan": { - "maintenance_id": 16825, - "name": "Summit 2017 Plan 1", - "description": "", - "start": null, - "end": null, - "created_by": "rhn-support-wnix", - "silenced": false, - "hidden": false, - "suggestion": null, - "remote_branch": null - } - }, { - "done": false, - "id": 66175, - "maintenance_plan": { - "maintenance_id": 19435, - "name": null, - "description": null, - "start": null, - "end": null, - "created_by": "rhn-support-wnix", - "silenced": false, - "hidden": false, - "suggestion": null, - "remote_branch": null - } - }, { - "done": false, - "id": 71015, - "maintenance_plan": { - "maintenance_id": 19835, - "name": "Optum Payload", - "description": "", - "start": "2017-05-27T02:00:00.000Z", - "end": "2017-05-27T03:00:00.000Z", - "created_by": "rhn-support-wnix", - "silenced": false, - "hidden": false, - "suggestion": null, - "remote_branch": null - } - }] - } -] diff --git a/awx/ui/test/spec/inventories/insights/data/insights-data.js b/awx/ui/test/spec/inventories/insights/data/insights-data.js deleted file mode 100644 index 0d36f8af6a7e..000000000000 --- a/awx/ui/test/spec/inventories/insights/data/insights-data.js +++ /dev/null @@ -1,662 +0,0 @@ -export default { - "toString": "ansible1.tronik-insights440.atl.redhat.com", - "isCheckingIn": true, - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "display_name": null, - "remote_branch": null, - "remote_leaf": null, - "account_number": "540155", - "hostname": "ansible1.tronik-insights440.atl.redhat.com", - "parent_id": null, - "system_type_id": 105, - "last_check_in": "2017-05-25T14:01:19.000Z", - "stale_ack": false, - "type": "machine", - "product": "rhel", - "created_at": "2016-07-26T23:31:13.000Z", - "updated_at": "2017-05-25T14:01:19.000Z", - "unregistered_at": null, - "reports": [{ - "details": { - "vulnerable_setting": "hosts: files dns", - "affected_package": "glibc-2.17-55.el7", - "error_key": "GLIBC_CVE_2015_7547" - }, - "id": 709784455, - "rule_id": "CVE_2015_7547_glibc|GLIBC_CVE_2015_7547", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

A critical security flaw in the glibc library was found. It allows an attacker to crash an application built against that library or, potentially, execute arbitrary code with privileges of the user running the application.

\n", - "generic_html": "

The glibc library is vulnerable to a stack-based buffer overflow security flaw. A remote attacker could create specially crafted DNS responses that could cause the libresolv part of the library, which performs dual A/AAAA DNS queries, to crash or potentially execute code with the permissions of the user running the library. The issue is only exposed when libresolv is called from the nss_dns NSS service module. This flaw is known as CVE-2015-7547.

\n", - "more_info_html": "\n", - "severity": "ERROR", - "ansible": true, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "CVE_2015_7547_glibc|GLIBC_CVE_2015_7547", - "error_key": "GLIBC_CVE_2015_7547", - "plugin": "CVE_2015_7547_glibc", - "description": "Remote code execution vulnerability in libresolv via crafted DNS response (CVE-2015-7547)", - "summary": "A critical security flaw in the `glibc` library was found. It allows an attacker to crash an application built against that library or, potentially, execute arbitrary code with privileges of the user running the application.", - "generic": "The `glibc` library is vulnerable to a stack-based buffer overflow security flaw. A remote attacker could create specially crafted DNS responses that could cause the `libresolv` part of the library, which performs dual A/AAAA DNS queries, to crash or potentially execute code with the permissions of the user running the library. The issue is only exposed when `libresolv` is called from the nss_dns NSS service module. This flaw is known as [CVE-2015-7547](https://access.redhat.com/security/cve/CVE-2015-7547).", - "reason": "

This host is vulnerable because it has vulnerable package glibc-2.17-55.el7 installed and DNS is enabled in /etc/nsswitch.conf:

\n
hosts:      files dns\n

The glibc library is vulnerable to a stack-based buffer overflow security flaw. A remote attacker could create specially crafted DNS responses that could cause the libresolv part of the library, which performs dual A/AAAA DNS queries, to crash or potentially execute code with the permissions of the user running the library. The issue is only exposed when libresolv is called from the nss_dns NSS service module. This flaw is known as CVE-2015-7547.

\n", - "type": null, - "more_info": "* For more information about the flaw see [CVE-2015-7547](https://access.redhat.com/security/cve/CVE-2015-7547).\n* To learn how to upgrade packages, see \"[What is yum and how do I use it?](https://access.redhat.com/solutions/9934)\"\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat Products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).", - "active": true, - "node_id": "2168451", - "category": "Security", - "retired": false, - "reboot_required": false, - "publish_date": "2016-10-31T04:08:35.000Z", - "rec_impact": 4, - "rec_likelihood": 2, - "resolution": "

Red Hat recommends updating glibc and restarting the affected system:

\n
# yum update glibc\n# reboot\n

Alternatively, you can restart all affected services, but because this vulnerability affects a large amount of applications on the system, the best solution is to restart the system.

\n" - }, - "maintenance_actions": [] - }, { - "details": { - "affected_kernel": "3.10.0-123.el7", - "error_key": "KERNEL_CVE-2016-0728" - }, - "id": 709784465, - "rule_id": "CVE_2016_0728_kernel|KERNEL_CVE-2016-0728", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

A vulnerability in the Linux kernel allowing local privilege escalation was discovered. The issue was reported as CVE-2016-0728.

\n", - "generic_html": "

A vulnerability in the Linux kernel rated Important was discovered. The use-after-free flaw relates to the way the Linux kernel's key management subsystem handles keyring object reference counting in certain error paths of the join_session_keyring() function. A local, unprivileged user could use this flaw to escalate their privileges on the system. The issue was reported as CVE-2016-0728.

\n

Red Hat recommends that you update the kernel and reboot the system. If you cannot reboot now, consider applying the systemtap patch to update your running kernel.

\n", - "more_info_html": "\n", - "severity": "WARN", - "ansible": true, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "CVE_2016_0728_kernel|KERNEL_CVE-2016-0728", - "error_key": "KERNEL_CVE-2016-0728", - "plugin": "CVE_2016_0728_kernel", - "description": "Kernel key management subsystem vulnerable to local privilege escalation (CVE-2016-0728)", - "summary": "A vulnerability in the Linux kernel allowing local privilege escalation was discovered. The issue was reported as [CVE-2016-0728](https://access.redhat.com/security/cve/cve-2016-0728).", - "generic": "A vulnerability in the Linux kernel rated **Important** was discovered. The use-after-free flaw relates to the way the Linux kernel's key management subsystem handles keyring object reference counting in certain error paths of the join_session_keyring() function. A local, unprivileged user could use this flaw to escalate their privileges on the system. The issue was reported as [CVE-2016-0728](https://access.redhat.com/security/cve/cve-2016-0728).\n\nRed Hat recommends that you update the kernel and reboot the system. If you cannot reboot now, consider applying the [systemtap patch](https://bugzilla.redhat.com/attachment.cgi?id=1116284&action=edit) to update your running kernel.", - "reason": "

A vulnerability in the Linux kernel rated Important was discovered. The use-after-free flaw relates to the way the Linux kernel's key management subsystem handles keyring object reference counting in certain error paths of the join_session_keyring() function. A local, unprivileged user could use this flaw to escalate their privileges on the system. The issue was reported as CVE-2016-0728.

\n

The host is vulnerable as it is running kernel-3.10.0-123.el7.

\n", - "type": null, - "more_info": "* For more information about the flaws and versions of the package that are vulnerable see [CVE-2016-0728](https://access.redhat.com/security/cve/cve-2016-0728).\n* To learn how to upgrade packages, see \"[What is yum and how do I use it?](https://access.redhat.com/solutions/9934)\"\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat Products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).", - "active": true, - "node_id": "2130791", - "category": "Security", - "retired": false, - "reboot_required": false, - "publish_date": "2016-10-31T04:08:37.000Z", - "rec_impact": 2, - "rec_likelihood": 2, - "resolution": "

Red Hat recommends that you update kernel and reboot. If you cannot reboot now, consider applying the systemtap patch to update your running kernel.

\n
# yum update kernel\n# reboot\n-or-\n# debuginfo-install kernel     (or equivalent)\n# stap -vgt -Gfix_p=1 -Gtrace_p=0 cve20160728e.stp\n
" - }, - "maintenance_actions": [] - }, { - "details": { - "processes_listening_int": [], - "processes_listening_ext": [], - "error_key": "OPENSSL_CVE_2016_0800_SPECIAL_DROWN", - "processes_listening": [], - "processes_names": [], - "vulnerable_package": "openssl-libs-1.0.1e-34.el7" - }, - "id": 709784475, - "rule_id": "CVE_2016_0800_openssl_drown|OPENSSL_CVE_2016_0800_SPECIAL_DROWN", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

A new cross-protocol attack against SSLv2 protocol has been found. It has been assigned CVE-2016-0800 and is referred to as DROWN - Decrypting RSA using Obsolete and Weakened eNcryption. An attacker can decrypt passively collected TLS sessions between up-to-date client and server which supports SSLv2.

\n", - "generic_html": "

A new cross-protocol attack against a vulnerability in the SSLv2 protocol has been found. It can be used to passively decrypt collected TLS/SSL sessions from any connection that used an RSA key exchange cypher suite on a server that supports SSLv2. Even if a given service does not support SSLv2 the connection is still vulnerable if another service does and shares the same RSA private key.

\n

A more efficient variant of the attack exists against unpatched OpenSSL servers using versions that predate security advisories released on March 19, 2015 (see CVE-2015-0293).

\n", - "more_info_html": "\n", - "severity": "WARN", - "ansible": true, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "CVE_2016_0800_openssl_drown|OPENSSL_CVE_2016_0800_SPECIAL_DROWN", - "error_key": "OPENSSL_CVE_2016_0800_SPECIAL_DROWN", - "plugin": "CVE_2016_0800_openssl_drown", - "description": "OpenSSL vulnerable to very efficient session decryption (CVE-2016-0800/Special DROWN)", - "summary": "A new cross-protocol attack against SSLv2 protocol has been found. It has been assigned [CVE-2016-0800](https://access.redhat.com/security/cve/CVE-2016-0800) and is referred to as DROWN - Decrypting RSA using Obsolete and Weakened eNcryption. An attacker can decrypt passively collected TLS sessions between up-to-date client and server which supports SSLv2.", - "generic": "A new cross-protocol attack against a vulnerability in the SSLv2 protocol has been found. It can be used to passively decrypt collected TLS/SSL sessions from any connection that used an RSA key exchange cypher suite on a server that supports SSLv2. Even if a given service does not support SSLv2 the connection is still vulnerable if another service does and shares the same RSA private key.\n\nA more efficient variant of the attack exists against unpatched OpenSSL servers using versions that predate security advisories released on March 19, 2015 (see [CVE-2015-0293](https://access.redhat.com/security/cve/CVE-2015-0293)).", - "reason": "

This host is vulnerable because it has vulnerable package openssl-libs-1.0.1e-34.el7 installed.

\n

This package does not have a patch for CVE-2015-0293 applied, which makes the system especially vulnerable. This is known as Special DROWN. An attacker can use this flaw to perform active man-in-the-middle (MITM) attacks and impersonate a TLS server to connecting TLS client in a matter of minutes.

\n

Fortunately, it does not seem to run any processes that use OpenSSL libraries.

\n

A new cross-protocol attack against a vulnerability in the SSLv2 protocol has been found. It can be used to passively decrypt collected TLS/SSL sessions from any connection that used an RSA key exchange cypher suite on a server that supports SSLv2. Even if a given service does not support SSLv2 the connection is still vulnerable if another service does and shares the same RSA private key.

\n

A more efficient variant of the attack exists against unpatched OpenSSL servers using versions that predate security advisories released on March 19, 2015 (see CVE-2015-0293).

\n", - "type": null, - "more_info": "* For more information about the flaw see [CVE-2016-0800](https://access.redhat.com/security/cve/CVE-2016-0800)\n* To learn how to upgrade packages, see \"[What is yum and how do I use it?](https://access.redhat.com/solutions/9934)\"\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat Products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).", - "active": true, - "node_id": "2174451", - "category": "Security", - "retired": false, - "reboot_required": false, - "publish_date": "2016-10-31T04:08:32.000Z", - "rec_impact": 3, - "rec_likelihood": 2, - "resolution": "

Red Hat recommends that you update openssl and restart the affected system:

\n
# yum update openssl\n# reboot\n

Alternatively, you can restart all affected services (that is, the ones linked to the openssl library), especially those listening on public IP addresses.

\n" - }, - "maintenance_actions": [] - }, { - "details": { - "vulnerable_kernel": "3.10.0-123.el7", - "package_name": "kernel", - "error_key": "KERNEL_CVE_2016_5195" - }, - "id": 709784485, - "rule_id": "CVE_2016_5195_kernel|KERNEL_CVE_2016_5195", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

A flaw was found in the Linux kernel's memory subsystem. An unprivileged local user could use this flaw to write to files they would normally only have read-only access to and thus increase their privileges on the system.

\n", - "generic_html": "

A race condition was found in the way Linux kernel's memory subsystem handled breakage of the the read only shared mappings COW situation on write access. An unprivileged local user could use this flaw to write to files they should normally have read-only access to, and thus increase their privileges on the system.

\n

A process that is able to mmap a file is able to race Copy on Write (COW) page creation (within get_user_pages) with madvise(MADV_DONTNEED) kernel system calls. This would allow modified pages to bypass the page protection mechanism and modify the mapped file. The vulnerability could be abused by allowing an attacker to modify existing setuid files with instructions to elevate permissions. This attack has been found in the wild.

\n

Red Hat recommends that you update the kernel package or apply mitigations.

\n", - "more_info_html": "\n", - "severity": "WARN", - "ansible": true, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "CVE_2016_5195_kernel|KERNEL_CVE_2016_5195", - "error_key": "KERNEL_CVE_2016_5195", - "plugin": "CVE_2016_5195_kernel", - "description": "Kernel vulnerable to privilege escalation via permission bypass (CVE-2016-5195)", - "summary": "A flaw was found in the Linux kernel's memory subsystem. An unprivileged local user could use this flaw to write to files they would normally only have read-only access to and thus increase their privileges on the system.", - "generic": "A race condition was found in the way Linux kernel's memory subsystem handled breakage of the the read only shared mappings COW situation on write access. An unprivileged local user could use this flaw to write to files they should normally have read-only access to, and thus increase their privileges on the system.\n\nA process that is able to mmap a file is able to race Copy on Write (COW) page creation (within get_user_pages) with madvise(MADV_DONTNEED) kernel system calls. This would allow modified pages to bypass the page protection mechanism and modify the mapped file. The vulnerability could be abused by allowing an attacker to modify existing setuid files with instructions to elevate permissions. This attack has been found in the wild. \n\nRed Hat recommends that you update the kernel package or apply mitigations.", - "reason": "

A flaw was found in the Linux kernel's memory subsystem. An unprivileged local user could use this flaw to write to files they would normally have read-only access to and thus increase their privileges on the system.

\n

This host is affected because it is running kernel 3.10.0-123.el7.

\n

There is currently no mitigation applied and your system is vulnerable.

\n", - "type": null, - "more_info": "* For more information about the flaw see [CVE-2016-5195](https://access.redhat.com/security/cve/CVE-2016-5195)\n* To learn how to upgrade packages, see \"[What is yum and how do I use it?](https://access.redhat.com/solutions/9934)\"\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat Products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).", - "active": true, - "node_id": "2706661", - "category": "Security", - "retired": false, - "reboot_required": false, - "publish_date": "2016-10-31T04:08:33.000Z", - "rec_impact": 2, - "rec_likelihood": 2, - "resolution": "

Red Hat recommends that you update the kernel package and restart the system:

\n
# yum update kernel\n# reboot\n

or

\n

Alternatively, this issue can be addressed by applying mitigations until the machine is restarted with the updated kernel package.

\n

Please refer to the Resolve Tab in the vulnerability article for information about the mitigation and the latest information.

\n" - }, - "maintenance_actions": [{ - "done": false, - "id": 29885, - "maintenance_plan": { - "maintenance_id": 12195, - "name": null, - "description": null, - "start": null, - "end": null, - "created_by": "rhn-support-jnewton", - "silenced": false, - "hidden": true, - "suggestion": "proposed", - "remote_branch": null - } - }] - }, { - "details": { - "package": "bash-4.2.45-5.el7", - "error_key": "VULNERABLE_BASH_DETECTED" - }, - "id": 709784505, - "rule_id": "bash_injection|VULNERABLE_BASH_DETECTED", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

In September 2014, an exploitable bug known as Shellshock was discovered in commonly shipped versions of the bash shell.

\n", - "generic_html": "

Hosts running earlier versions of bash are affected by the code injection vulnerability known as Shellshock.

\n", - "more_info_html": "

For further information about this critical vulnerability, see:

\n\n", - "severity": "WARN", - "ansible": true, - "ansible_fix": true, - "ansible_mitigation": false, - "rule_id": "bash_injection|VULNERABLE_BASH_DETECTED", - "error_key": "VULNERABLE_BASH_DETECTED", - "plugin": "bash_injection", - "description": "Bash locally vulnerable via environment variables (CVE-2014-6271, CVE-2014-7169/Shellshock)", - "summary": "In September 2014, an exploitable bug known as Shellshock was discovered in commonly shipped versions of the bash shell.", - "generic": "Hosts running earlier versions of `bash` are affected by the code injection vulnerability known as **Shellshock**.", - "reason": "

This host is running a version of bash that is affected by the code injection vulnerability known as Shellshock.

\n

The package affected is bash-4.2.45-5.el7.

\n", - "type": null, - "more_info": "For further information about this **critical** vulnerability, see:\n* [Bash Code Injection Vulnerability via Specially Crafted Environment Variables (CVE-2014-6271, CVE-2014-7169)](https://access.redhat.com/articles/1200223)\n* [CVE-2014-6271](https://access.redhat.com/security/cve/CVE-2014-6271)\n* [CVE-2014-7169](https://access.redhat.com/security/cve/CVE-2014-7169)", - "active": true, - "node_id": "1200223", - "category": "Security", - "retired": false, - "reboot_required": false, - "publish_date": "2016-10-31T04:08:36.000Z", - "rec_impact": 2, - "rec_likelihood": 2, - "resolution": "

Red Hat recommends that you upgrade bash immediately:

\n
# yum update bash\n
" - }, - "maintenance_actions": [] - }, { - "details": { - "detected_problem_log_perms": [{ - "log_perms_dirfilename": "/var/log/cron", - "log_perms_sensitive": true, - "log_perms_ls_line": "-rw-r--r--. 1 root root 15438 May 25 10:01 cron" - }], - "error_key": "HARDENING_LOGGING_3_LOG_PERMS" - }, - "id": 709784525, - "rule_id": "hardening_logging_log_perms|HARDENING_LOGGING_3_LOG_PERMS", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

Issues related to system logging and auditing were detected on your system. Important services are disabled or log file permissions are not secure.

\n", - "generic_html": "

Issues related to system logging and auditing were detected on your system.

\n

Red Hat recommends that the logging service rsyslog and the auditing service auditd are enabled and that log files in /var/log have secure permissions.

\n", - "more_info_html": "\n", - "severity": "INFO", - "ansible": false, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "hardening_logging_log_perms|HARDENING_LOGGING_3_LOG_PERMS", - "error_key": "HARDENING_LOGGING_3_LOG_PERMS", - "plugin": "hardening_logging_log_perms", - "description": "Decreased security in system logging permissions", - "summary": "Issues related to system logging and auditing were detected on your system. Important services are disabled or log file permissions are not secure.\n", - "generic": "Issues related to system logging and auditing were detected on your system.\n\nRed Hat recommends that the logging service `rsyslog` and the auditing service `auditd` are enabled and that log files in `/var/log` have secure permissions.\n", - "reason": "

Log files have permission issues.

\n

The following files or directories in /var/log have file permissions that differ from the default RHEL configuration and are possibly non-secure. Red Hat recommends that the file permissions be adjusted to more secure settings.

\n\n \n \n \n \n \n\n\n\n\n\n\n\n
File or directory nameDetected problemOutput from ls -l
/var/log/cronUsers other than root can read or write.-rw-r--r--. 1 root root 15438 May 25 10:01 cron
\n\n\n\n", - "type": null, - "more_info": "* [Why is `/var/log/cron` world readable in RHEL7?](https://access.redhat.com/solutions/1491573)\n* [Using the chkconfig Utility](https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Deployment_Guide/s2-services-chkconfig.html) to configure services on RHEL 6\n* [Managing System Services](https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/System_Administrators_Guide/sect-Managing_Services_with_systemd-Services.html) to configure services on RHEL 7\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).\n", - "active": true, - "node_id": null, - "category": "Security", - "retired": false, - "reboot_required": false, - "publish_date": "2017-05-16T04:08:34.000Z", - "rec_impact": 1, - "rec_likelihood": 1, - "resolution": "

Red Hat recommends that you perform the following adjustments:

\n

Fixing permission issues depends on whether there is a designated safe group on your system that has Read access to the log files. This situation might exist if you want to allow certain administrators to see the log files without becoming root. To prevent log tampering, no other user than root should have permissions to Write to the log files. (The btmp and wtmp files are owned by the utmp group but other users should still be unable to write to them.)

\n

Fix for a default RHEL configuration

\n

(No designated group for reading log files)

\n
chown root:root /var/log/cron\nchmod u=rw,g-x,o-rwx /var/log/cron\n

Fix for a configuration with a designated safe group for reading log files

\n

In the following lines, substitute the name of your designated safe group for the string safegroup:

\n
chown root:safegroup /var/log/cron\nchmod u=rw,g-x,o-rwx /var/log/cron\n
" - }, - "maintenance_actions": [] - }, { - "details": { - "filesystems": [{ - "usage": "99", - "mountpoint": "/" - }, { - "usage": "99", - "mountpoint": "/" - }], - "error_key": "FILESYSTEM_CAPACITY" - }, - "id": 709784535, - "rule_id": "filesystem_capacity|FILESYSTEM_CAPACITY", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

File systems nearing full capacity can cause performance issues because blocks must be used from different block groups. \nBesides, file systems at or exceeding capacity will have stability issues because applications will no longer be able to write to the file system.

\n", - "generic_html": "

File systems nearing full capacity can cause performance issues because blocks must be used from different block groups. \nBesides, file systems at or exceeding capacity will have stability issues because applications will no longer be able to write to the file system.

\n", - "more_info_html": "

How to increase the filesystem size?\nHow do I find out what is using disk space?

\n", - "severity": "WARN", - "ansible": false, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "filesystem_capacity|FILESYSTEM_CAPACITY", - "error_key": "FILESYSTEM_CAPACITY", - "plugin": "filesystem_capacity", - "description": "Decreased stability and/or performance due to filesystem over 95% capacity", - "summary": "File systems nearing full capacity can cause performance issues because blocks must be used from different block groups. \nBesides, file systems at or exceeding capacity will have stability issues because applications will no longer be able to write to the file system.\n", - "generic": "File systems nearing full capacity can cause performance issues because blocks must be used from different block groups. \nBesides, file systems at or exceeding capacity will have stability issues because applications will no longer be able to write to the file system.\n", - "reason": "

This host has the following file systems nearing or at capacity:

\n
    \n\n
  • Filesystem: / Usage: 99%
  • \n\n
  • Filesystem: / Usage: 99%
  • \n\n
", - "type": null, - "more_info": "[How to increase the filesystem size?](https://access.redhat.com/solutions/21820)\n[How do I find out what is using disk space?](https://access.redhat.com/solutions/1154683)\n", - "active": true, - "node_id": "1154683", - "category": "Stability", - "retired": false, - "reboot_required": false, - "publish_date": "2016-10-31T04:08:36.000Z", - "rec_impact": 2, - "rec_likelihood": 3, - "resolution": "

To solve the issue, Red Hat recommends that you either add more storage capacity to the identified file systems, or remove unnecessary files to reduce the current usage.\nPlease refer to more_information part for more detailed steps.

\n" - }, - "maintenance_actions": [] - }, { - "details": { - "msg": "[ 0.000000] crashkernel=auto resulted in zero bytes of reserved memory.", - "auto_with_low_ram": true, - "rhel_ver": 7, - "error_key": "CRASHKERNEL_RESERVATION_FAILED" - }, - "id": 709784555, - "rule_id": "crashkernel_reservation_failed|CRASHKERNEL_RESERVATION_FAILED", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

The crashkernel configuration has failed to produce a working kdump environment. Configuration changes must be made to enable vmcore capture.

\n", - "generic_html": "

Kdump is unable to reserve memory for the kdump kernel. The kdump service has not started and a vmcore will not be captured if the host crashes, which will make it difficult for our support technicians to determine why the machine crashed.

\n", - "more_info_html": "", - "severity": "WARN", - "ansible": false, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "crashkernel_reservation_failed|CRASHKERNEL_RESERVATION_FAILED", - "error_key": "CRASHKERNEL_RESERVATION_FAILED", - "plugin": "crashkernel_reservation_failed", - "description": "Kdump crashkernel reservation failed due to improper configuration of crashkernel parameter", - "summary": "The crashkernel configuration has failed to produce a working kdump environment. Configuration changes must be made to enable vmcore capture.\n", - "generic": "Kdump is unable to reserve memory for the kdump kernel. The kdump service has not started and a vmcore will not be captured if the host crashes, which will make it difficult for our support technicians to determine why the machine crashed.", - "reason": "

This host is unable to reserve memory for the kdump kernel:

\n
[    0.000000] crashkernel=auto resulted in zero bytes of reserved memory.\n

This means the kdump service has not started and a vmcore will not be captured if the host crashes, which will make it difficult for our support technicians to determine why the machine crashed.

\n", - "type": null, - "more_info": null, - "active": true, - "node_id": "59432", - "category": "Stability", - "retired": false, - "reboot_required": false, - "publish_date": "2016-10-31T04:08:33.000Z", - "rec_impact": 1, - "rec_likelihood": 3, - "resolution": "

To fix this issue, Red Hat recommends that you change the crashkernel setting in the grub.conf file.

\n

This host failed to reserved memory with auto crashkernel parameter due to low physical memory. The memory must be reserved by explicitly requesting the reservation size, for example: crashkernel=128M.

\n

For details of crashkernel setting, please refer to the Knowledge article How should the crashkernel parameter be configured for using kdump on RHEL7? to pickup the setting specifically for your host.

\n" - }, - "maintenance_actions": [] - }, { - "details": { - "error_key": "TZDATA_NEED_UPGRADE_INFO_NEED_MANUAL_ACTION" - }, - "id": 709784565, - "rule_id": "tzdata_need_upgrade|TZDATA_NEED_UPGRADE_INFO_NEED_MANUAL_ACTION", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

System clock inaccurate when a leap second event happens in a non-NTP system without following the TAI timescale.

\n", - "generic_html": "

System clock inaccurate when a leap second event happens in a non-NTP system without following the TAI timescale.

\n", - "more_info_html": "", - "severity": "INFO", - "ansible": false, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "tzdata_need_upgrade|TZDATA_NEED_UPGRADE_INFO_NEED_MANUAL_ACTION", - "error_key": "TZDATA_NEED_UPGRADE_INFO_NEED_MANUAL_ACTION", - "plugin": "tzdata_need_upgrade", - "description": "System clock inaccurate when a leap second event happens in a non-NTP system without following the TAI timescale", - "summary": "System clock inaccurate when a leap second event happens in a non-NTP system without following the TAI timescale.\n", - "generic": "System clock inaccurate when a leap second event happens in a non-NTP system without following the TAI timescale.\n", - "reason": "

This system running as a non-NTP system is following the UTC timescale. In this situation, manual correction is required to avoid system clock inaccuracy when a leap second event happens.

\n", - "type": null, - "more_info": null, - "active": true, - "node_id": "1465713", - "category": "Stability", - "retired": false, - "reboot_required": false, - "publish_date": null, - "rec_impact": 2, - "rec_likelihood": 1, - "resolution": "

The system clock of this system needs manual correction when a leap second event happens. For example:

\n
\n\n# date -s \"20170101 HH:MM:SS\"\n\n
\n\n

You need to replace "HH:MM:SS" with the accurate time after the leap second occurs.

\n" - }, - "maintenance_actions": [] - }, { - "details": { - "selinux_info": true, - "package_name": "kernel", - "selinux_enforcing": true, - "selinux_can_help": true, - "minimal_selinux_policy": "selinux-policy-3.13.1-81.el7", - "selinux_enabled": true, - "vulnerable_kernel": "3.10.0-123.el7", - "active_policy": "selinux-policy-3.12.1-153.el7", - "dccp_loading_disabled": null, - "error_key": "KERNEL_CVE_2017_6074", - "enough_policy": false, - "dccp_loaded": null, - "mitigation_info": false - }, - "id": 709784575, - "rule_id": "CVE_2017_6074_kernel|KERNEL_CVE_2017_6074", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

A use-after-free flaw was found in the Linux kernel IPv6 DCCP network protocol code. It has been assigned CVE-2017-6074. An unprivileged local user could use this flaw to execute arbitrary code in kernel memory and increase their privileges on the system.

\n", - "generic_html": "

A use-after-free flaw was found in the Linux kernel IPv6 DCCP network protocol code. It has been assigned CVE-2017-6074.

\n

An unprivileged local user could use this flaw to execute arbitrary code in kernel memory and increase their privileges on the system. A local user could initiate a DCCP network connection on any local system network interface and then create specially-crafted memory allocations containing malicious instructions that can then either cause a crash or potentially escalate the user's privileges.

\n

An attacker must have access to a local account on the system; this is not a remote attack and it requires IPv6 support to be enabled on the system.

\n

Red Hat recommends that you update the kernel when possible. Otherwise, you can use proposed mitigation to disable DCCP. SELinux in enforcing mode can also mitigate the issue under specific circumstances.

\n", - "more_info_html": "\n", - "severity": "WARN", - "ansible": true, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "CVE_2017_6074_kernel|KERNEL_CVE_2017_6074", - "error_key": "KERNEL_CVE_2017_6074", - "plugin": "CVE_2017_6074_kernel", - "description": "Kernel vulnerable to local privilege escalation via DCCP module (CVE-2017-6074)", - "summary": "A use-after-free flaw was found in the Linux kernel IPv6 DCCP network protocol code. It has been assigned [CVE-2017-6074](https://access.redhat.com/security/cve/CVE-2017-6074). An unprivileged local user could use this flaw to execute arbitrary code in kernel memory and increase their privileges on the system.\n", - "generic": "A use-after-free flaw was found in the Linux kernel IPv6 DCCP network protocol code. It has been assigned CVE-2017-6074. \n\nAn unprivileged local user could use this flaw to execute arbitrary code in kernel memory and increase their privileges on the system. A local user could initiate a DCCP network connection on any local system network interface and then create specially-crafted memory allocations containing malicious instructions that can then either cause a crash or potentially escalate the user's privileges.\n\nAn attacker must have access to a local account on the system; this is not a remote attack and it requires IPv6 support to be enabled on the system.\n\nRed Hat recommends that you update the kernel when possible. Otherwise, you can use proposed mitigation to disable DCCP. SELinux in enforcing mode can also mitigate the issue under specific circumstances.\n", - "reason": "

A use-after-free flaw was found within the Linux kernel IPv6 DCCP network protocol code.

\n

This host is affected because:

\n
  • It is running kernel 3.10.0-123.el7.
  • SELinux policy is outdated.
\n\n\n\n\n\n\n

Your installed SELinux policy is selinux-policy-3.12.1-153.el7; however, to mitigate the issue, the earliest required version is selinux-policy-3.13.1-81.el7.

\n", - "type": null, - "more_info": "* For more information about the flaw, see [CVE-2017-6074](https://access.redhat.com/security/cve/CVE-2017-6074).\n* To learn how to upgrade packages, see [What is yum and how do I use it?](https://access.redhat.com/solutions/9934).\n* For more information about SELinux, see [Benefits of running SELinux](https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/SELinux_Users_and_Administrators_Guide/chap-Security-Enhanced_Linux-Introduction.html#sect-Security-Enhanced_Linux-Introduction-Benefits_of_running_SELinux).\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).\n", - "active": true, - "node_id": null, - "category": "Security", - "retired": false, - "reboot_required": false, - "publish_date": null, - "rec_impact": 2, - "rec_likelihood": 2, - "resolution": "

Red Hat recommends updating the kernel package and rebooting the system.

\n
# yum update kernel\n# reboot\n

Alternatively, apply one of the following mitigations:

\n
Update SELinux policy
\n

Update your SELinux policy:

\n
# yum update selinux-policy\n

The system does not provide enough information for Insights about loaded kernel modules. It is not possible to recommend a mitigation based on kernel modules.

\n" - }, - "maintenance_actions": [] - }, { - "details": { - "mitigation_conf": "no", - "sysctl_live_ack_limit": "100", - "package_name": "kernel", - "sysctl_live_ack_limit_line": "net.ipv4.tcp_challenge_ack_limit = 100", - "error_key": "KERNEL_CVE_2016_5696_URGENT", - "vulnerable_kernel": "3.10.0-123.el7", - "sysctl_conf_ack_limit": "100", - "sysctl_conf_ack_limit_line": "net.ipv4.tcp_challenge_ack_limit=100", - "mitigation_live": "no" - }, - "id": 766342155, - "rule_id": "CVE_2016_5696_kernel|KERNEL_CVE_2016_5696_URGENT", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

A flaw in the Linux kernel's TCP/IP networking subsystem implementation of the RFC 5961 challenge ACK rate limiting was found that could allow an attacker located on different subnet to inject or take over a TCP connection between a server and client without needing to use a traditional man-in-the-middle (MITM) attack.

\n", - "generic_html": "

A flaw was found in the implementation of the Linux kernel's handling of networking challenge ack (RFC 5961) where an attacker is able to determine the\nshared counter. This flaw allows an attacker located on different subnet to inject or take over a TCP connection between a server and client without needing to use a traditional man-in-the-middle (MITM) attack.

\n

Red Hat recommends that you update the kernel package or apply mitigations.

\n", - "more_info_html": "\n", - "severity": "ERROR", - "ansible": true, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "CVE_2016_5696_kernel|KERNEL_CVE_2016_5696_URGENT", - "error_key": "KERNEL_CVE_2016_5696_URGENT", - "plugin": "CVE_2016_5696_kernel", - "description": "Kernel vulnerable to man-in-the-middle via payload injection", - "summary": "A flaw in the Linux kernel's TCP/IP networking subsystem implementation of the [RFC 5961](https://tools.ietf.org/html/rfc5961) challenge ACK rate limiting was found that could allow an attacker located on different subnet to inject or take over a TCP connection between a server and client without needing to use a traditional man-in-the-middle (MITM) attack.", - "generic": "A flaw was found in the implementation of the Linux kernel's handling of networking challenge ack ([RFC 5961](https://tools.ietf.org/html/rfc5961)) where an attacker is able to determine the\nshared counter. This flaw allows an attacker located on different subnet to inject or take over a TCP connection between a server and client without needing to use a traditional man-in-the-middle (MITM) attack. \n\nRed Hat recommends that you update the kernel package or apply mitigations.", - "reason": "

A flaw was found in the implementation of the Linux kernel's handling of networking challenge ack (RFC 5961) where an attacker is able to determine the\nshared counter. This flaw allows an attacker located on different subnet to inject or take over a TCP connection between a server and client without needing to use a traditional man-in-the-middle (MITM) attack.

\n

This host is affected because it is running kernel 3.10.0-123.el7.

\n

Your currently loaded kernel configuration contains this setting:

\n
net.ipv4.tcp_challenge_ack_limit = 100\n

Your currently stored kernel configuration is:

\n
net.ipv4.tcp_challenge_ack_limit=100\n

There is currently no mitigation applied and your system is vulnerable.

\n", - "type": null, - "more_info": "* For more information about the flaw see [CVE-2016-5696](https://access.redhat.com/security/cve/CVE-2016-5696)\n* To learn how to upgrade packages, see \"[What is yum and how do I use it?](https://access.redhat.com/solutions/9934)\"\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat Products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).", - "active": true, - "node_id": "2438571", - "category": "Security", - "retired": false, - "reboot_required": false, - "publish_date": "2016-10-31T04:08:32.000Z", - "rec_impact": 4, - "rec_likelihood": 2, - "resolution": "

Red Hat recommends that you update the kernel package and restart the system:

\n
# yum update kernel\n# reboot\n

or

\n

Alternatively, this issue can be addressed by applying the following mitigations until the machine is restarted with the updated kernel package.

\n

Edit /etc/sysctl.conf file as root, add the mitigation configuration, and reload the kernel configuration:

\n
# echo "net.ipv4.tcp_challenge_ack_limit = 2147483647" >> /etc/sysctl.conf \n# sysctl -p\n
" - }, - "maintenance_actions": [{ - "done": false, - "id": 56045, - "maintenance_plan": { - "maintenance_id": 15875, - "name": "Payload Injection Fix", - "description": "", - "start": "2017-06-01T02:00:00.000Z", - "end": "2017-06-01T03:00:00.000Z", - "created_by": "rhn-support-wnix", - "silenced": false, - "hidden": false, - "suggestion": null, - "remote_branch": null - } - }, { - "done": false, - "id": 61575, - "maintenance_plan": { - "maintenance_id": 16825, - "name": "Summit 2017 Plan 1", - "description": "", - "start": null, - "end": null, - "created_by": "rhn-support-wnix", - "silenced": false, - "hidden": false, - "suggestion": null, - "remote_branch": null - } - }, { - "done": false, - "id": 66175, - "maintenance_plan": { - "maintenance_id": 19435, - "name": null, - "description": null, - "start": null, - "end": null, - "created_by": "rhn-support-wnix", - "silenced": false, - "hidden": false, - "suggestion": null, - "remote_branch": null - } - }, { - "done": false, - "id": 71015, - "maintenance_plan": { - "maintenance_id": 19835, - "name": "Optum Payload", - "description": "", - "start": "2017-05-27T02:00:00.000Z", - "end": "2017-05-27T03:00:00.000Z", - "created_by": "rhn-support-wnix", - "silenced": false, - "hidden": false, - "suggestion": null, - "remote_branch": null - } - }] - }, { - "details": { - "mod_loading_disabled": null, - "package_name": "kernel", - "error_key": "KERNEL_CVE_2017_2636", - "vulnerable_kernel": "3.10.0-123.el7", - "mod_loaded": null, - "mitigation_info": false - }, - "id": 766342165, - "rule_id": "CVE_2017_2636_kernel|KERNEL_CVE_2017_2636", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

A vulnerability in the Linux kernel allowing local privilege escalation was discovered.\nThe issue was reported as CVE-2017-2636.

\n", - "generic_html": "

A use-after-free flaw was found in the Linux kernel implementation of the HDLC (High-Level Data Link Control) TTY line discipline implementation. It has been assigned CVE-2017-2636.

\n

An unprivileged local user could use this flaw to execute arbitrary code in kernel memory and increase their privileges on the system. The kernel uses a TTY subsystem to take and show terminal output to connected systems. An attacker crafting specific-sized memory allocations could abuse this mechanism to place a kernel function pointer with malicious instructions to be executed on behalf of the attacker.

\n

An attacker must have access to a local account on the system; this is not a remote attack. Exploiting this flaw does not require Microgate or SyncLink hardware to be in use.

\n

Red Hat recommends that you use the proposed mitigation to disable the N_HDLC module.

\n", - "more_info_html": "\n", - "severity": "WARN", - "ansible": true, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "CVE_2017_2636_kernel|KERNEL_CVE_2017_2636", - "error_key": "KERNEL_CVE_2017_2636", - "plugin": "CVE_2017_2636_kernel", - "description": "Kernel vulnerable to local privilege escalation via n_hdlc module (CVE-2017-2636)", - "summary": "A vulnerability in the Linux kernel allowing local privilege escalation was discovered.\nThe issue was reported as [CVE-2017-2636](https://access.redhat.com/security/cve/CVE-2017-2636).\n", - "generic": "A use-after-free flaw was found in the Linux kernel implementation of the HDLC (High-Level Data Link Control) TTY line discipline implementation. It has been assigned CVE-2017-2636.\n\nAn unprivileged local user could use this flaw to execute arbitrary code in kernel memory and increase their privileges on the system. The kernel uses a TTY subsystem to take and show terminal output to connected systems. An attacker crafting specific-sized memory allocations could abuse this mechanism to place a kernel function pointer with malicious instructions to be executed on behalf of the attacker.\n\nAn attacker must have access to a local account on the system; this is not a remote attack. Exploiting this flaw does not require Microgate or SyncLink hardware to be in use.\n\nRed Hat recommends that you use the proposed mitigation to disable the N_HDLC module.\n", - "reason": "

A use-after-free flaw was found in the Linux kernel implementation of the HDLC (High-Level Data Link Control) TTY line discipline implementation.

\n

This host is affected because it is running kernel 3.10.0-123.el7.

\n", - "type": null, - "more_info": "* For more information about the flaw, see [CVE-2017-2636](https://access.redhat.com/security/cve/CVE-2017-2636) and [CVE-2017-2636 article](https://access.redhat.com/security/vulnerabilities/CVE-2017-2636).\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).\n", - "active": true, - "node_id": null, - "category": "Security", - "retired": false, - "reboot_required": false, - "publish_date": null, - "rec_impact": 2, - "rec_likelihood": 2, - "resolution": "

Red Hat recommends updating the kernel package and rebooting the system.

\n
# yum update kernel\n# reboot\n
" - }, - "maintenance_actions": [{ - "done": false, - "id": 58335, - "maintenance_plan": { - "maintenance_id": 16545, - "name": "Insights Summit 2017 - n_HDLC", - "description": "", - "start": "2017-05-06T02:00:00.000Z", - "end": "2017-05-06T03:00:00.000Z", - "created_by": "rhn-support-wnix", - "silenced": false, - "hidden": false, - "suggestion": null, - "remote_branch": null - } - }, { - "done": false, - "id": 61895, - "maintenance_plan": { - "maintenance_id": 16835, - "name": "Summit 2017 N_HDLC", - "description": "", - "start": null, - "end": null, - "created_by": "rhn-support-wnix", - "silenced": false, - "hidden": false, - "suggestion": null, - "remote_branch": null - } - }, { - "done": false, - "id": 66225, - "maintenance_plan": { - "maintenance_id": 19445, - "name": "Seattle's Best Plan", - "description": null, - "start": null, - "end": null, - "created_by": "rhn-support-wnix", - "silenced": false, - "hidden": false, - "suggestion": null, - "remote_branch": null - } - }, { - "done": false, - "id": 71075, - "maintenance_plan": { - "maintenance_id": 19845, - "name": "Optum N_HDLC FIX", - "description": null, - "start": null, - "end": null, - "created_by": "rhn-support-wnix", - "silenced": false, - "hidden": false, - "suggestion": null, - "remote_branch": null - } - }] - }] -} diff --git a/awx/ui/test/spec/inventories/insights/data/low.insights-data.js b/awx/ui/test/spec/inventories/insights/data/low.insights-data.js deleted file mode 100644 index 568e1d76e488..000000000000 --- a/awx/ui/test/spec/inventories/insights/data/low.insights-data.js +++ /dev/null @@ -1,84 +0,0 @@ -export default [ - { - "details": { - "detected_problem_log_perms": [{ - "log_perms_dirfilename": "/var/log/cron", - "log_perms_sensitive": true, - "log_perms_ls_line": "-rw-r--r--. 1 root root 15438 May 25 10:01 cron" - }], - "error_key": "HARDENING_LOGGING_3_LOG_PERMS" - }, - "id": 709784525, - "rule_id": "hardening_logging_log_perms|HARDENING_LOGGING_3_LOG_PERMS", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

Issues related to system logging and auditing were detected on your system. Important services are disabled or log file permissions are not secure.

\n", - "generic_html": "

Issues related to system logging and auditing were detected on your system.

\n

Red Hat recommends that the logging service rsyslog and the auditing service auditd are enabled and that log files in /var/log have secure permissions.

\n", - "more_info_html": "\n", - "severity": "INFO", - "ansible": false, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "hardening_logging_log_perms|HARDENING_LOGGING_3_LOG_PERMS", - "error_key": "HARDENING_LOGGING_3_LOG_PERMS", - "plugin": "hardening_logging_log_perms", - "description": "Decreased security in system logging permissions", - "summary": "Issues related to system logging and auditing were detected on your system. Important services are disabled or log file permissions are not secure.\n", - "generic": "Issues related to system logging and auditing were detected on your system.\n\nRed Hat recommends that the logging service `rsyslog` and the auditing service `auditd` are enabled and that log files in `/var/log` have secure permissions.\n", - "reason": "

Log files have permission issues.

\n

The following files or directories in /var/log have file permissions that differ from the default RHEL configuration and are possibly non-secure. Red Hat recommends that the file permissions be adjusted to more secure settings.

\n\n \n \n \n \n \n\n\n\n\n\n\n\n
File or directory nameDetected problemOutput from ls -l
/var/log/cronUsers other than root can read or write.-rw-r--r--. 1 root root 15438 May 25 10:01 cron
\n\n\n\n", - "type": null, - "more_info": "* [Why is `/var/log/cron` world readable in RHEL7?](https://access.redhat.com/solutions/1491573)\n* [Using the chkconfig Utility](https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Deployment_Guide/s2-services-chkconfig.html) to configure services on RHEL 6\n* [Managing System Services](https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/System_Administrators_Guide/sect-Managing_Services_with_systemd-Services.html) to configure services on RHEL 7\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).\n", - "active": true, - "node_id": null, - "category": "Security", - "retired": false, - "reboot_required": false, - "publish_date": "2017-05-16T04:08:34.000Z", - "rec_impact": 1, - "rec_likelihood": 1, - "resolution": "

Red Hat recommends that you perform the following adjustments:

\n

Fixing permission issues depends on whether there is a designated safe group on your system that has Read access to the log files. This situation might exist if you want to allow certain administrators to see the log files without becoming root. To prevent log tampering, no other user than root should have permissions to Write to the log files. (The btmp and wtmp files are owned by the utmp group but other users should still be unable to write to them.)

\n

Fix for a default RHEL configuration

\n

(No designated group for reading log files)

\n
chown root:root /var/log/cron\nchmod u=rw,g-x,o-rwx /var/log/cron\n

Fix for a configuration with a designated safe group for reading log files

\n

In the following lines, substitute the name of your designated safe group for the string safegroup:

\n
chown root:safegroup /var/log/cron\nchmod u=rw,g-x,o-rwx /var/log/cron\n
" - }, - "maintenance_actions": [] - }, { - "details": { - "error_key": "TZDATA_NEED_UPGRADE_INFO_NEED_MANUAL_ACTION" - }, - "id": 709784565, - "rule_id": "tzdata_need_upgrade|TZDATA_NEED_UPGRADE_INFO_NEED_MANUAL_ACTION", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

System clock inaccurate when a leap second event happens in a non-NTP system without following the TAI timescale.

\n", - "generic_html": "

System clock inaccurate when a leap second event happens in a non-NTP system without following the TAI timescale.

\n", - "more_info_html": "", - "severity": "INFO", - "ansible": false, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "tzdata_need_upgrade|TZDATA_NEED_UPGRADE_INFO_NEED_MANUAL_ACTION", - "error_key": "TZDATA_NEED_UPGRADE_INFO_NEED_MANUAL_ACTION", - "plugin": "tzdata_need_upgrade", - "description": "System clock inaccurate when a leap second event happens in a non-NTP system without following the TAI timescale", - "summary": "System clock inaccurate when a leap second event happens in a non-NTP system without following the TAI timescale.\n", - "generic": "System clock inaccurate when a leap second event happens in a non-NTP system without following the TAI timescale.\n", - "reason": "

This system running as a non-NTP system is following the UTC timescale. In this situation, manual correction is required to avoid system clock inaccuracy when a leap second event happens.

\n", - "type": null, - "more_info": null, - "active": true, - "node_id": "1465713", - "category": "Stability", - "retired": false, - "reboot_required": false, - "publish_date": null, - "rec_impact": 2, - "rec_likelihood": 1, - "resolution": "

The system clock of this system needs manual correction when a leap second event happens. For example:

\n
\n\n# date -s \"20170101 HH:MM:SS\"\n\n
\n\n

You need to replace "HH:MM:SS" with the accurate time after the leap second occurs.

\n" - }, - "maintenance_actions": [] - } -] diff --git a/awx/ui/test/spec/inventories/insights/data/medium.insights-data.js b/awx/ui/test/spec/inventories/insights/data/medium.insights-data.js deleted file mode 100644 index 0e251f8d745a..000000000000 --- a/awx/ui/test/spec/inventories/insights/data/medium.insights-data.js +++ /dev/null @@ -1,418 +0,0 @@ -export default [ - { - "details": { - "affected_kernel": "3.10.0-123.el7", - "error_key": "KERNEL_CVE-2016-0728" - }, - "id": 709784465, - "rule_id": "CVE_2016_0728_kernel|KERNEL_CVE-2016-0728", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

A vulnerability in the Linux kernel allowing local privilege escalation was discovered. The issue was reported as CVE-2016-0728.

\n", - "generic_html": "

A vulnerability in the Linux kernel rated Important was discovered. The use-after-free flaw relates to the way the Linux kernel's key management subsystem handles keyring object reference counting in certain error paths of the join_session_keyring() function. A local, unprivileged user could use this flaw to escalate their privileges on the system. The issue was reported as CVE-2016-0728.

\n

Red Hat recommends that you update the kernel and reboot the system. If you cannot reboot now, consider applying the systemtap patch to update your running kernel.

\n", - "more_info_html": "\n", - "severity": "WARN", - "ansible": true, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "CVE_2016_0728_kernel|KERNEL_CVE-2016-0728", - "error_key": "KERNEL_CVE-2016-0728", - "plugin": "CVE_2016_0728_kernel", - "description": "Kernel key management subsystem vulnerable to local privilege escalation (CVE-2016-0728)", - "summary": "A vulnerability in the Linux kernel allowing local privilege escalation was discovered. The issue was reported as [CVE-2016-0728](https://access.redhat.com/security/cve/cve-2016-0728).", - "generic": "A vulnerability in the Linux kernel rated **Important** was discovered. The use-after-free flaw relates to the way the Linux kernel's key management subsystem handles keyring object reference counting in certain error paths of the join_session_keyring() function. A local, unprivileged user could use this flaw to escalate their privileges on the system. The issue was reported as [CVE-2016-0728](https://access.redhat.com/security/cve/cve-2016-0728).\n\nRed Hat recommends that you update the kernel and reboot the system. If you cannot reboot now, consider applying the [systemtap patch](https://bugzilla.redhat.com/attachment.cgi?id=1116284&action=edit) to update your running kernel.", - "reason": "

A vulnerability in the Linux kernel rated Important was discovered. The use-after-free flaw relates to the way the Linux kernel's key management subsystem handles keyring object reference counting in certain error paths of the join_session_keyring() function. A local, unprivileged user could use this flaw to escalate their privileges on the system. The issue was reported as CVE-2016-0728.

\n

The host is vulnerable as it is running kernel-3.10.0-123.el7.

\n", - "type": null, - "more_info": "* For more information about the flaws and versions of the package that are vulnerable see [CVE-2016-0728](https://access.redhat.com/security/cve/cve-2016-0728).\n* To learn how to upgrade packages, see \"[What is yum and how do I use it?](https://access.redhat.com/solutions/9934)\"\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat Products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).", - "active": true, - "node_id": "2130791", - "category": "Security", - "retired": false, - "reboot_required": false, - "publish_date": "2016-10-31T04:08:37.000Z", - "rec_impact": 2, - "rec_likelihood": 2, - "resolution": "

Red Hat recommends that you update kernel and reboot. If you cannot reboot now, consider applying the systemtap patch to update your running kernel.

\n
# yum update kernel\n# reboot\n-or-\n# debuginfo-install kernel     (or equivalent)\n# stap -vgt -Gfix_p=1 -Gtrace_p=0 cve20160728e.stp\n
" - }, - "maintenance_actions": [] - }, { - "details": { - "processes_listening_int": [], - "processes_listening_ext": [], - "error_key": "OPENSSL_CVE_2016_0800_SPECIAL_DROWN", - "processes_listening": [], - "processes_names": [], - "vulnerable_package": "openssl-libs-1.0.1e-34.el7" - }, - "id": 709784475, - "rule_id": "CVE_2016_0800_openssl_drown|OPENSSL_CVE_2016_0800_SPECIAL_DROWN", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

A new cross-protocol attack against SSLv2 protocol has been found. It has been assigned CVE-2016-0800 and is referred to as DROWN - Decrypting RSA using Obsolete and Weakened eNcryption. An attacker can decrypt passively collected TLS sessions between up-to-date client and server which supports SSLv2.

\n", - "generic_html": "

A new cross-protocol attack against a vulnerability in the SSLv2 protocol has been found. It can be used to passively decrypt collected TLS/SSL sessions from any connection that used an RSA key exchange cypher suite on a server that supports SSLv2. Even if a given service does not support SSLv2 the connection is still vulnerable if another service does and shares the same RSA private key.

\n

A more efficient variant of the attack exists against unpatched OpenSSL servers using versions that predate security advisories released on March 19, 2015 (see CVE-2015-0293).

\n", - "more_info_html": "\n", - "severity": "WARN", - "ansible": true, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "CVE_2016_0800_openssl_drown|OPENSSL_CVE_2016_0800_SPECIAL_DROWN", - "error_key": "OPENSSL_CVE_2016_0800_SPECIAL_DROWN", - "plugin": "CVE_2016_0800_openssl_drown", - "description": "OpenSSL vulnerable to very efficient session decryption (CVE-2016-0800/Special DROWN)", - "summary": "A new cross-protocol attack against SSLv2 protocol has been found. It has been assigned [CVE-2016-0800](https://access.redhat.com/security/cve/CVE-2016-0800) and is referred to as DROWN - Decrypting RSA using Obsolete and Weakened eNcryption. An attacker can decrypt passively collected TLS sessions between up-to-date client and server which supports SSLv2.", - "generic": "A new cross-protocol attack against a vulnerability in the SSLv2 protocol has been found. It can be used to passively decrypt collected TLS/SSL sessions from any connection that used an RSA key exchange cypher suite on a server that supports SSLv2. Even if a given service does not support SSLv2 the connection is still vulnerable if another service does and shares the same RSA private key.\n\nA more efficient variant of the attack exists against unpatched OpenSSL servers using versions that predate security advisories released on March 19, 2015 (see [CVE-2015-0293](https://access.redhat.com/security/cve/CVE-2015-0293)).", - "reason": "

This host is vulnerable because it has vulnerable package openssl-libs-1.0.1e-34.el7 installed.

\n

This package does not have a patch for CVE-2015-0293 applied, which makes the system especially vulnerable. This is known as Special DROWN. An attacker can use this flaw to perform active man-in-the-middle (MITM) attacks and impersonate a TLS server to connecting TLS client in a matter of minutes.

\n

Fortunately, it does not seem to run any processes that use OpenSSL libraries.

\n

A new cross-protocol attack against a vulnerability in the SSLv2 protocol has been found. It can be used to passively decrypt collected TLS/SSL sessions from any connection that used an RSA key exchange cypher suite on a server that supports SSLv2. Even if a given service does not support SSLv2 the connection is still vulnerable if another service does and shares the same RSA private key.

\n

A more efficient variant of the attack exists against unpatched OpenSSL servers using versions that predate security advisories released on March 19, 2015 (see CVE-2015-0293).

\n", - "type": null, - "more_info": "* For more information about the flaw see [CVE-2016-0800](https://access.redhat.com/security/cve/CVE-2016-0800)\n* To learn how to upgrade packages, see \"[What is yum and how do I use it?](https://access.redhat.com/solutions/9934)\"\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat Products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).", - "active": true, - "node_id": "2174451", - "category": "Security", - "retired": false, - "reboot_required": false, - "publish_date": "2016-10-31T04:08:32.000Z", - "rec_impact": 3, - "rec_likelihood": 2, - "resolution": "

Red Hat recommends that you update openssl and restart the affected system:

\n
# yum update openssl\n# reboot\n

Alternatively, you can restart all affected services (that is, the ones linked to the openssl library), especially those listening on public IP addresses.

\n" - }, - "maintenance_actions": [] - }, { - "details": { - "vulnerable_kernel": "3.10.0-123.el7", - "package_name": "kernel", - "error_key": "KERNEL_CVE_2016_5195" - }, - "id": 709784485, - "rule_id": "CVE_2016_5195_kernel|KERNEL_CVE_2016_5195", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

A flaw was found in the Linux kernel's memory subsystem. An unprivileged local user could use this flaw to write to files they would normally only have read-only access to and thus increase their privileges on the system.

\n", - "generic_html": "

A race condition was found in the way Linux kernel's memory subsystem handled breakage of the the read only shared mappings COW situation on write access. An unprivileged local user could use this flaw to write to files they should normally have read-only access to, and thus increase their privileges on the system.

\n

A process that is able to mmap a file is able to race Copy on Write (COW) page creation (within get_user_pages) with madvise(MADV_DONTNEED) kernel system calls. This would allow modified pages to bypass the page protection mechanism and modify the mapped file. The vulnerability could be abused by allowing an attacker to modify existing setuid files with instructions to elevate permissions. This attack has been found in the wild.

\n

Red Hat recommends that you update the kernel package or apply mitigations.

\n", - "more_info_html": "\n", - "severity": "WARN", - "ansible": true, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "CVE_2016_5195_kernel|KERNEL_CVE_2016_5195", - "error_key": "KERNEL_CVE_2016_5195", - "plugin": "CVE_2016_5195_kernel", - "description": "Kernel vulnerable to privilege escalation via permission bypass (CVE-2016-5195)", - "summary": "A flaw was found in the Linux kernel's memory subsystem. An unprivileged local user could use this flaw to write to files they would normally only have read-only access to and thus increase their privileges on the system.", - "generic": "A race condition was found in the way Linux kernel's memory subsystem handled breakage of the the read only shared mappings COW situation on write access. An unprivileged local user could use this flaw to write to files they should normally have read-only access to, and thus increase their privileges on the system.\n\nA process that is able to mmap a file is able to race Copy on Write (COW) page creation (within get_user_pages) with madvise(MADV_DONTNEED) kernel system calls. This would allow modified pages to bypass the page protection mechanism and modify the mapped file. The vulnerability could be abused by allowing an attacker to modify existing setuid files with instructions to elevate permissions. This attack has been found in the wild. \n\nRed Hat recommends that you update the kernel package or apply mitigations.", - "reason": "

A flaw was found in the Linux kernel's memory subsystem. An unprivileged local user could use this flaw to write to files they would normally have read-only access to and thus increase their privileges on the system.

\n

This host is affected because it is running kernel 3.10.0-123.el7.

\n

There is currently no mitigation applied and your system is vulnerable.

\n", - "type": null, - "more_info": "* For more information about the flaw see [CVE-2016-5195](https://access.redhat.com/security/cve/CVE-2016-5195)\n* To learn how to upgrade packages, see \"[What is yum and how do I use it?](https://access.redhat.com/solutions/9934)\"\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat Products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).", - "active": true, - "node_id": "2706661", - "category": "Security", - "retired": false, - "reboot_required": false, - "publish_date": "2016-10-31T04:08:33.000Z", - "rec_impact": 2, - "rec_likelihood": 2, - "resolution": "

Red Hat recommends that you update the kernel package and restart the system:

\n
# yum update kernel\n# reboot\n

or

\n

Alternatively, this issue can be addressed by applying mitigations until the machine is restarted with the updated kernel package.

\n

Please refer to the Resolve Tab in the vulnerability article for information about the mitigation and the latest information.

\n" - }, - "maintenance_actions": [{ - "done": false, - "id": 29885, - "maintenance_plan": { - "maintenance_id": 12195, - "name": null, - "description": null, - "start": null, - "end": null, - "created_by": "rhn-support-jnewton", - "silenced": false, - "hidden": true, - "suggestion": "proposed", - "remote_branch": null - } - }] - }, { - "details": { - "package": "bash-4.2.45-5.el7", - "error_key": "VULNERABLE_BASH_DETECTED" - }, - "id": 709784505, - "rule_id": "bash_injection|VULNERABLE_BASH_DETECTED", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

In September 2014, an exploitable bug known as Shellshock was discovered in commonly shipped versions of the bash shell.

\n", - "generic_html": "

Hosts running earlier versions of bash are affected by the code injection vulnerability known as Shellshock.

\n", - "more_info_html": "

For further information about this critical vulnerability, see:

\n\n", - "severity": "WARN", - "ansible": true, - "ansible_fix": true, - "ansible_mitigation": false, - "rule_id": "bash_injection|VULNERABLE_BASH_DETECTED", - "error_key": "VULNERABLE_BASH_DETECTED", - "plugin": "bash_injection", - "description": "Bash locally vulnerable via environment variables (CVE-2014-6271, CVE-2014-7169/Shellshock)", - "summary": "In September 2014, an exploitable bug known as Shellshock was discovered in commonly shipped versions of the bash shell.", - "generic": "Hosts running earlier versions of `bash` are affected by the code injection vulnerability known as **Shellshock**.", - "reason": "

This host is running a version of bash that is affected by the code injection vulnerability known as Shellshock.

\n

The package affected is bash-4.2.45-5.el7.

\n", - "type": null, - "more_info": "For further information about this **critical** vulnerability, see:\n* [Bash Code Injection Vulnerability via Specially Crafted Environment Variables (CVE-2014-6271, CVE-2014-7169)](https://access.redhat.com/articles/1200223)\n* [CVE-2014-6271](https://access.redhat.com/security/cve/CVE-2014-6271)\n* [CVE-2014-7169](https://access.redhat.com/security/cve/CVE-2014-7169)", - "active": true, - "node_id": "1200223", - "category": "Security", - "retired": false, - "reboot_required": false, - "publish_date": "2016-10-31T04:08:36.000Z", - "rec_impact": 2, - "rec_likelihood": 2, - "resolution": "

Red Hat recommends that you upgrade bash immediately:

\n
# yum update bash\n
" - }, - "maintenance_actions": [] - }, { - "details": { - "filesystems": [{ - "usage": "99", - "mountpoint": "/" - }, { - "usage": "99", - "mountpoint": "/" - }], - "error_key": "FILESYSTEM_CAPACITY" - }, - "id": 709784535, - "rule_id": "filesystem_capacity|FILESYSTEM_CAPACITY", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

File systems nearing full capacity can cause performance issues because blocks must be used from different block groups. \nBesides, file systems at or exceeding capacity will have stability issues because applications will no longer be able to write to the file system.

\n", - "generic_html": "

File systems nearing full capacity can cause performance issues because blocks must be used from different block groups. \nBesides, file systems at or exceeding capacity will have stability issues because applications will no longer be able to write to the file system.

\n", - "more_info_html": "

How to increase the filesystem size?\nHow do I find out what is using disk space?

\n", - "severity": "WARN", - "ansible": false, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "filesystem_capacity|FILESYSTEM_CAPACITY", - "error_key": "FILESYSTEM_CAPACITY", - "plugin": "filesystem_capacity", - "description": "Decreased stability and/or performance due to filesystem over 95% capacity", - "summary": "File systems nearing full capacity can cause performance issues because blocks must be used from different block groups. \nBesides, file systems at or exceeding capacity will have stability issues because applications will no longer be able to write to the file system.\n", - "generic": "File systems nearing full capacity can cause performance issues because blocks must be used from different block groups. \nBesides, file systems at or exceeding capacity will have stability issues because applications will no longer be able to write to the file system.\n", - "reason": "

This host has the following file systems nearing or at capacity:

\n
    \n\n
  • Filesystem: / Usage: 99%
  • \n\n
  • Filesystem: / Usage: 99%
  • \n\n
", - "type": null, - "more_info": "[How to increase the filesystem size?](https://access.redhat.com/solutions/21820)\n[How do I find out what is using disk space?](https://access.redhat.com/solutions/1154683)\n", - "active": true, - "node_id": "1154683", - "category": "Stability", - "retired": false, - "reboot_required": false, - "publish_date": "2016-10-31T04:08:36.000Z", - "rec_impact": 2, - "rec_likelihood": 3, - "resolution": "

To solve the issue, Red Hat recommends that you either add more storage capacity to the identified file systems, or remove unnecessary files to reduce the current usage.\nPlease refer to more_information part for more detailed steps.

\n" - }, - "maintenance_actions": [] - }, { - "details": { - "msg": "[ 0.000000] crashkernel=auto resulted in zero bytes of reserved memory.", - "auto_with_low_ram": true, - "rhel_ver": 7, - "error_key": "CRASHKERNEL_RESERVATION_FAILED" - }, - "id": 709784555, - "rule_id": "crashkernel_reservation_failed|CRASHKERNEL_RESERVATION_FAILED", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

The crashkernel configuration has failed to produce a working kdump environment. Configuration changes must be made to enable vmcore capture.

\n", - "generic_html": "

Kdump is unable to reserve memory for the kdump kernel. The kdump service has not started and a vmcore will not be captured if the host crashes, which will make it difficult for our support technicians to determine why the machine crashed.

\n", - "more_info_html": "", - "severity": "WARN", - "ansible": false, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "crashkernel_reservation_failed|CRASHKERNEL_RESERVATION_FAILED", - "error_key": "CRASHKERNEL_RESERVATION_FAILED", - "plugin": "crashkernel_reservation_failed", - "description": "Kdump crashkernel reservation failed due to improper configuration of crashkernel parameter", - "summary": "The crashkernel configuration has failed to produce a working kdump environment. Configuration changes must be made to enable vmcore capture.\n", - "generic": "Kdump is unable to reserve memory for the kdump kernel. The kdump service has not started and a vmcore will not be captured if the host crashes, which will make it difficult for our support technicians to determine why the machine crashed.", - "reason": "

This host is unable to reserve memory for the kdump kernel:

\n
[    0.000000] crashkernel=auto resulted in zero bytes of reserved memory.\n

This means the kdump service has not started and a vmcore will not be captured if the host crashes, which will make it difficult for our support technicians to determine why the machine crashed.

\n", - "type": null, - "more_info": null, - "active": true, - "node_id": "59432", - "category": "Stability", - "retired": false, - "reboot_required": false, - "publish_date": "2016-10-31T04:08:33.000Z", - "rec_impact": 1, - "rec_likelihood": 3, - "resolution": "

To fix this issue, Red Hat recommends that you change the crashkernel setting in the grub.conf file.

\n

This host failed to reserved memory with auto crashkernel parameter due to low physical memory. The memory must be reserved by explicitly requesting the reservation size, for example: crashkernel=128M.

\n

For details of crashkernel setting, please refer to the Knowledge article How should the crashkernel parameter be configured for using kdump on RHEL7? to pickup the setting specifically for your host.

\n" - }, - "maintenance_actions": [] - }, { - "details": { - "selinux_info": true, - "package_name": "kernel", - "selinux_enforcing": true, - "selinux_can_help": true, - "minimal_selinux_policy": "selinux-policy-3.13.1-81.el7", - "selinux_enabled": true, - "vulnerable_kernel": "3.10.0-123.el7", - "active_policy": "selinux-policy-3.12.1-153.el7", - "dccp_loading_disabled": null, - "error_key": "KERNEL_CVE_2017_6074", - "enough_policy": false, - "dccp_loaded": null, - "mitigation_info": false - }, - "id": 709784575, - "rule_id": "CVE_2017_6074_kernel|KERNEL_CVE_2017_6074", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

A use-after-free flaw was found in the Linux kernel IPv6 DCCP network protocol code. It has been assigned CVE-2017-6074. An unprivileged local user could use this flaw to execute arbitrary code in kernel memory and increase their privileges on the system.

\n", - "generic_html": "

A use-after-free flaw was found in the Linux kernel IPv6 DCCP network protocol code. It has been assigned CVE-2017-6074.

\n

An unprivileged local user could use this flaw to execute arbitrary code in kernel memory and increase their privileges on the system. A local user could initiate a DCCP network connection on any local system network interface and then create specially-crafted memory allocations containing malicious instructions that can then either cause a crash or potentially escalate the user's privileges.

\n

An attacker must have access to a local account on the system; this is not a remote attack and it requires IPv6 support to be enabled on the system.

\n

Red Hat recommends that you update the kernel when possible. Otherwise, you can use proposed mitigation to disable DCCP. SELinux in enforcing mode can also mitigate the issue under specific circumstances.

\n", - "more_info_html": "\n", - "severity": "WARN", - "ansible": true, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "CVE_2017_6074_kernel|KERNEL_CVE_2017_6074", - "error_key": "KERNEL_CVE_2017_6074", - "plugin": "CVE_2017_6074_kernel", - "description": "Kernel vulnerable to local privilege escalation via DCCP module (CVE-2017-6074)", - "summary": "A use-after-free flaw was found in the Linux kernel IPv6 DCCP network protocol code. It has been assigned [CVE-2017-6074](https://access.redhat.com/security/cve/CVE-2017-6074). An unprivileged local user could use this flaw to execute arbitrary code in kernel memory and increase their privileges on the system.\n", - "generic": "A use-after-free flaw was found in the Linux kernel IPv6 DCCP network protocol code. It has been assigned CVE-2017-6074. \n\nAn unprivileged local user could use this flaw to execute arbitrary code in kernel memory and increase their privileges on the system. A local user could initiate a DCCP network connection on any local system network interface and then create specially-crafted memory allocations containing malicious instructions that can then either cause a crash or potentially escalate the user's privileges.\n\nAn attacker must have access to a local account on the system; this is not a remote attack and it requires IPv6 support to be enabled on the system.\n\nRed Hat recommends that you update the kernel when possible. Otherwise, you can use proposed mitigation to disable DCCP. SELinux in enforcing mode can also mitigate the issue under specific circumstances.\n", - "reason": "

A use-after-free flaw was found within the Linux kernel IPv6 DCCP network protocol code.

\n

This host is affected because:

\n
  • It is running kernel 3.10.0-123.el7.
  • SELinux policy is outdated.
\n\n\n\n\n\n\n

Your installed SELinux policy is selinux-policy-3.12.1-153.el7; however, to mitigate the issue, the earliest required version is selinux-policy-3.13.1-81.el7.

\n", - "type": null, - "more_info": "* For more information about the flaw, see [CVE-2017-6074](https://access.redhat.com/security/cve/CVE-2017-6074).\n* To learn how to upgrade packages, see [What is yum and how do I use it?](https://access.redhat.com/solutions/9934).\n* For more information about SELinux, see [Benefits of running SELinux](https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/SELinux_Users_and_Administrators_Guide/chap-Security-Enhanced_Linux-Introduction.html#sect-Security-Enhanced_Linux-Introduction-Benefits_of_running_SELinux).\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).\n", - "active": true, - "node_id": null, - "category": "Security", - "retired": false, - "reboot_required": false, - "publish_date": null, - "rec_impact": 2, - "rec_likelihood": 2, - "resolution": "

Red Hat recommends updating the kernel package and rebooting the system.

\n
# yum update kernel\n# reboot\n

Alternatively, apply one of the following mitigations:

\n
Update SELinux policy
\n

Update your SELinux policy:

\n
# yum update selinux-policy\n

The system does not provide enough information for Insights about loaded kernel modules. It is not possible to recommend a mitigation based on kernel modules.

\n" - }, - "maintenance_actions": [] - }, { - "details": { - "mod_loading_disabled": null, - "package_name": "kernel", - "error_key": "KERNEL_CVE_2017_2636", - "vulnerable_kernel": "3.10.0-123.el7", - "mod_loaded": null, - "mitigation_info": false - }, - "id": 766342165, - "rule_id": "CVE_2017_2636_kernel|KERNEL_CVE_2017_2636", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

A vulnerability in the Linux kernel allowing local privilege escalation was discovered.\nThe issue was reported as CVE-2017-2636.

\n", - "generic_html": "

A use-after-free flaw was found in the Linux kernel implementation of the HDLC (High-Level Data Link Control) TTY line discipline implementation. It has been assigned CVE-2017-2636.

\n

An unprivileged local user could use this flaw to execute arbitrary code in kernel memory and increase their privileges on the system. The kernel uses a TTY subsystem to take and show terminal output to connected systems. An attacker crafting specific-sized memory allocations could abuse this mechanism to place a kernel function pointer with malicious instructions to be executed on behalf of the attacker.

\n

An attacker must have access to a local account on the system; this is not a remote attack. Exploiting this flaw does not require Microgate or SyncLink hardware to be in use.

\n

Red Hat recommends that you use the proposed mitigation to disable the N_HDLC module.

\n", - "more_info_html": "\n", - "severity": "WARN", - "ansible": true, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "CVE_2017_2636_kernel|KERNEL_CVE_2017_2636", - "error_key": "KERNEL_CVE_2017_2636", - "plugin": "CVE_2017_2636_kernel", - "description": "Kernel vulnerable to local privilege escalation via n_hdlc module (CVE-2017-2636)", - "summary": "A vulnerability in the Linux kernel allowing local privilege escalation was discovered.\nThe issue was reported as [CVE-2017-2636](https://access.redhat.com/security/cve/CVE-2017-2636).\n", - "generic": "A use-after-free flaw was found in the Linux kernel implementation of the HDLC (High-Level Data Link Control) TTY line discipline implementation. It has been assigned CVE-2017-2636.\n\nAn unprivileged local user could use this flaw to execute arbitrary code in kernel memory and increase their privileges on the system. The kernel uses a TTY subsystem to take and show terminal output to connected systems. An attacker crafting specific-sized memory allocations could abuse this mechanism to place a kernel function pointer with malicious instructions to be executed on behalf of the attacker.\n\nAn attacker must have access to a local account on the system; this is not a remote attack. Exploiting this flaw does not require Microgate or SyncLink hardware to be in use.\n\nRed Hat recommends that you use the proposed mitigation to disable the N_HDLC module.\n", - "reason": "

A use-after-free flaw was found in the Linux kernel implementation of the HDLC (High-Level Data Link Control) TTY line discipline implementation.

\n

This host is affected because it is running kernel 3.10.0-123.el7.

\n", - "type": null, - "more_info": "* For more information about the flaw, see [CVE-2017-2636](https://access.redhat.com/security/cve/CVE-2017-2636) and [CVE-2017-2636 article](https://access.redhat.com/security/vulnerabilities/CVE-2017-2636).\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).\n", - "active": true, - "node_id": null, - "category": "Security", - "retired": false, - "reboot_required": false, - "publish_date": null, - "rec_impact": 2, - "rec_likelihood": 2, - "resolution": "

Red Hat recommends updating the kernel package and rebooting the system.

\n
# yum update kernel\n# reboot\n
" - }, - "maintenance_actions": [{ - "done": false, - "id": 58335, - "maintenance_plan": { - "maintenance_id": 16545, - "name": "Insights Summit 2017 - n_HDLC", - "description": "", - "start": "2017-05-06T02:00:00.000Z", - "end": "2017-05-06T03:00:00.000Z", - "created_by": "rhn-support-wnix", - "silenced": false, - "hidden": false, - "suggestion": null, - "remote_branch": null - } - }, { - "done": false, - "id": 61895, - "maintenance_plan": { - "maintenance_id": 16835, - "name": "Summit 2017 N_HDLC", - "description": "", - "start": null, - "end": null, - "created_by": "rhn-support-wnix", - "silenced": false, - "hidden": false, - "suggestion": null, - "remote_branch": null - } - }, { - "done": false, - "id": 66225, - "maintenance_plan": { - "maintenance_id": 19445, - "name": "Seattle's Best Plan", - "description": null, - "start": null, - "end": null, - "created_by": "rhn-support-wnix", - "silenced": false, - "hidden": false, - "suggestion": null, - "remote_branch": null - } - }, { - "done": false, - "id": 71075, - "maintenance_plan": { - "maintenance_id": 19845, - "name": "Optum N_HDLC FIX", - "description": null, - "start": null, - "end": null, - "created_by": "rhn-support-wnix", - "silenced": false, - "hidden": false, - "suggestion": null, - "remote_branch": null - } - }] - } -] diff --git a/awx/ui/test/spec/inventories/insights/data/not_solvable.insights-data.js b/awx/ui/test/spec/inventories/insights/data/not_solvable.insights-data.js deleted file mode 100644 index 66469645856c..000000000000 --- a/awx/ui/test/spec/inventories/insights/data/not_solvable.insights-data.js +++ /dev/null @@ -1,381 +0,0 @@ -export default [ - { - "details": { - "vulnerable_setting": "hosts: files dns", - "affected_package": "glibc-2.17-55.el7", - "error_key": "GLIBC_CVE_2015_7547" - }, - "id": 709784455, - "rule_id": "CVE_2015_7547_glibc|GLIBC_CVE_2015_7547", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

A critical security flaw in the glibc library was found. It allows an attacker to crash an application built against that library or, potentially, execute arbitrary code with privileges of the user running the application.

\n", - "generic_html": "

The glibc library is vulnerable to a stack-based buffer overflow security flaw. A remote attacker could create specially crafted DNS responses that could cause the libresolv part of the library, which performs dual A/AAAA DNS queries, to crash or potentially execute code with the permissions of the user running the library. The issue is only exposed when libresolv is called from the nss_dns NSS service module. This flaw is known as CVE-2015-7547.

\n", - "more_info_html": "\n", - "severity": "ERROR", - "ansible": true, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "CVE_2015_7547_glibc|GLIBC_CVE_2015_7547", - "error_key": "GLIBC_CVE_2015_7547", - "plugin": "CVE_2015_7547_glibc", - "description": "Remote code execution vulnerability in libresolv via crafted DNS response (CVE-2015-7547)", - "summary": "A critical security flaw in the `glibc` library was found. It allows an attacker to crash an application built against that library or, potentially, execute arbitrary code with privileges of the user running the application.", - "generic": "The `glibc` library is vulnerable to a stack-based buffer overflow security flaw. A remote attacker could create specially crafted DNS responses that could cause the `libresolv` part of the library, which performs dual A/AAAA DNS queries, to crash or potentially execute code with the permissions of the user running the library. The issue is only exposed when `libresolv` is called from the nss_dns NSS service module. This flaw is known as [CVE-2015-7547](https://access.redhat.com/security/cve/CVE-2015-7547).", - "reason": "

This host is vulnerable because it has vulnerable package glibc-2.17-55.el7 installed and DNS is enabled in /etc/nsswitch.conf:

\n
hosts:      files dns\n

The glibc library is vulnerable to a stack-based buffer overflow security flaw. A remote attacker could create specially crafted DNS responses that could cause the libresolv part of the library, which performs dual A/AAAA DNS queries, to crash or potentially execute code with the permissions of the user running the library. The issue is only exposed when libresolv is called from the nss_dns NSS service module. This flaw is known as CVE-2015-7547.

\n", - "type": null, - "more_info": "* For more information about the flaw see [CVE-2015-7547](https://access.redhat.com/security/cve/CVE-2015-7547).\n* To learn how to upgrade packages, see \"[What is yum and how do I use it?](https://access.redhat.com/solutions/9934)\"\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat Products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).", - "active": true, - "node_id": "2168451", - "category": "Security", - "retired": false, - "reboot_required": false, - "publish_date": "2016-10-31T04:08:35.000Z", - "rec_impact": 4, - "rec_likelihood": 2, - "resolution": "

Red Hat recommends updating glibc and restarting the affected system:

\n
# yum update glibc\n# reboot\n

Alternatively, you can restart all affected services, but because this vulnerability affects a large amount of applications on the system, the best solution is to restart the system.

\n" - }, - "maintenance_actions": [] - }, { - "details": { - "affected_kernel": "3.10.0-123.el7", - "error_key": "KERNEL_CVE-2016-0728" - }, - "id": 709784465, - "rule_id": "CVE_2016_0728_kernel|KERNEL_CVE-2016-0728", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

A vulnerability in the Linux kernel allowing local privilege escalation was discovered. The issue was reported as CVE-2016-0728.

\n", - "generic_html": "

A vulnerability in the Linux kernel rated Important was discovered. The use-after-free flaw relates to the way the Linux kernel's key management subsystem handles keyring object reference counting in certain error paths of the join_session_keyring() function. A local, unprivileged user could use this flaw to escalate their privileges on the system. The issue was reported as CVE-2016-0728.

\n

Red Hat recommends that you update the kernel and reboot the system. If you cannot reboot now, consider applying the systemtap patch to update your running kernel.

\n", - "more_info_html": "\n", - "severity": "WARN", - "ansible": true, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "CVE_2016_0728_kernel|KERNEL_CVE-2016-0728", - "error_key": "KERNEL_CVE-2016-0728", - "plugin": "CVE_2016_0728_kernel", - "description": "Kernel key management subsystem vulnerable to local privilege escalation (CVE-2016-0728)", - "summary": "A vulnerability in the Linux kernel allowing local privilege escalation was discovered. The issue was reported as [CVE-2016-0728](https://access.redhat.com/security/cve/cve-2016-0728).", - "generic": "A vulnerability in the Linux kernel rated **Important** was discovered. The use-after-free flaw relates to the way the Linux kernel's key management subsystem handles keyring object reference counting in certain error paths of the join_session_keyring() function. A local, unprivileged user could use this flaw to escalate their privileges on the system. The issue was reported as [CVE-2016-0728](https://access.redhat.com/security/cve/cve-2016-0728).\n\nRed Hat recommends that you update the kernel and reboot the system. If you cannot reboot now, consider applying the [systemtap patch](https://bugzilla.redhat.com/attachment.cgi?id=1116284&action=edit) to update your running kernel.", - "reason": "

A vulnerability in the Linux kernel rated Important was discovered. The use-after-free flaw relates to the way the Linux kernel's key management subsystem handles keyring object reference counting in certain error paths of the join_session_keyring() function. A local, unprivileged user could use this flaw to escalate their privileges on the system. The issue was reported as CVE-2016-0728.

\n

The host is vulnerable as it is running kernel-3.10.0-123.el7.

\n", - "type": null, - "more_info": "* For more information about the flaws and versions of the package that are vulnerable see [CVE-2016-0728](https://access.redhat.com/security/cve/cve-2016-0728).\n* To learn how to upgrade packages, see \"[What is yum and how do I use it?](https://access.redhat.com/solutions/9934)\"\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat Products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).", - "active": true, - "node_id": "2130791", - "category": "Security", - "retired": false, - "reboot_required": false, - "publish_date": "2016-10-31T04:08:37.000Z", - "rec_impact": 2, - "rec_likelihood": 2, - "resolution": "

Red Hat recommends that you update kernel and reboot. If you cannot reboot now, consider applying the systemtap patch to update your running kernel.

\n
# yum update kernel\n# reboot\n-or-\n# debuginfo-install kernel     (or equivalent)\n# stap -vgt -Gfix_p=1 -Gtrace_p=0 cve20160728e.stp\n
" - }, - "maintenance_actions": [] - }, { - "details": { - "processes_listening_int": [], - "processes_listening_ext": [], - "error_key": "OPENSSL_CVE_2016_0800_SPECIAL_DROWN", - "processes_listening": [], - "processes_names": [], - "vulnerable_package": "openssl-libs-1.0.1e-34.el7" - }, - "id": 709784475, - "rule_id": "CVE_2016_0800_openssl_drown|OPENSSL_CVE_2016_0800_SPECIAL_DROWN", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

A new cross-protocol attack against SSLv2 protocol has been found. It has been assigned CVE-2016-0800 and is referred to as DROWN - Decrypting RSA using Obsolete and Weakened eNcryption. An attacker can decrypt passively collected TLS sessions between up-to-date client and server which supports SSLv2.

\n", - "generic_html": "

A new cross-protocol attack against a vulnerability in the SSLv2 protocol has been found. It can be used to passively decrypt collected TLS/SSL sessions from any connection that used an RSA key exchange cypher suite on a server that supports SSLv2. Even if a given service does not support SSLv2 the connection is still vulnerable if another service does and shares the same RSA private key.

\n

A more efficient variant of the attack exists against unpatched OpenSSL servers using versions that predate security advisories released on March 19, 2015 (see CVE-2015-0293).

\n", - "more_info_html": "\n", - "severity": "WARN", - "ansible": true, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "CVE_2016_0800_openssl_drown|OPENSSL_CVE_2016_0800_SPECIAL_DROWN", - "error_key": "OPENSSL_CVE_2016_0800_SPECIAL_DROWN", - "plugin": "CVE_2016_0800_openssl_drown", - "description": "OpenSSL vulnerable to very efficient session decryption (CVE-2016-0800/Special DROWN)", - "summary": "A new cross-protocol attack against SSLv2 protocol has been found. It has been assigned [CVE-2016-0800](https://access.redhat.com/security/cve/CVE-2016-0800) and is referred to as DROWN - Decrypting RSA using Obsolete and Weakened eNcryption. An attacker can decrypt passively collected TLS sessions between up-to-date client and server which supports SSLv2.", - "generic": "A new cross-protocol attack against a vulnerability in the SSLv2 protocol has been found. It can be used to passively decrypt collected TLS/SSL sessions from any connection that used an RSA key exchange cypher suite on a server that supports SSLv2. Even if a given service does not support SSLv2 the connection is still vulnerable if another service does and shares the same RSA private key.\n\nA more efficient variant of the attack exists against unpatched OpenSSL servers using versions that predate security advisories released on March 19, 2015 (see [CVE-2015-0293](https://access.redhat.com/security/cve/CVE-2015-0293)).", - "reason": "

This host is vulnerable because it has vulnerable package openssl-libs-1.0.1e-34.el7 installed.

\n

This package does not have a patch for CVE-2015-0293 applied, which makes the system especially vulnerable. This is known as Special DROWN. An attacker can use this flaw to perform active man-in-the-middle (MITM) attacks and impersonate a TLS server to connecting TLS client in a matter of minutes.

\n

Fortunately, it does not seem to run any processes that use OpenSSL libraries.

\n

A new cross-protocol attack against a vulnerability in the SSLv2 protocol has been found. It can be used to passively decrypt collected TLS/SSL sessions from any connection that used an RSA key exchange cypher suite on a server that supports SSLv2. Even if a given service does not support SSLv2 the connection is still vulnerable if another service does and shares the same RSA private key.

\n

A more efficient variant of the attack exists against unpatched OpenSSL servers using versions that predate security advisories released on March 19, 2015 (see CVE-2015-0293).

\n", - "type": null, - "more_info": "* For more information about the flaw see [CVE-2016-0800](https://access.redhat.com/security/cve/CVE-2016-0800)\n* To learn how to upgrade packages, see \"[What is yum and how do I use it?](https://access.redhat.com/solutions/9934)\"\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat Products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).", - "active": true, - "node_id": "2174451", - "category": "Security", - "retired": false, - "reboot_required": false, - "publish_date": "2016-10-31T04:08:32.000Z", - "rec_impact": 3, - "rec_likelihood": 2, - "resolution": "

Red Hat recommends that you update openssl and restart the affected system:

\n
# yum update openssl\n# reboot\n

Alternatively, you can restart all affected services (that is, the ones linked to the openssl library), especially those listening on public IP addresses.

\n" - }, - "maintenance_actions": [] - }, { - "details": { - "package": "bash-4.2.45-5.el7", - "error_key": "VULNERABLE_BASH_DETECTED" - }, - "id": 709784505, - "rule_id": "bash_injection|VULNERABLE_BASH_DETECTED", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

In September 2014, an exploitable bug known as Shellshock was discovered in commonly shipped versions of the bash shell.

\n", - "generic_html": "

Hosts running earlier versions of bash are affected by the code injection vulnerability known as Shellshock.

\n", - "more_info_html": "

For further information about this critical vulnerability, see:

\n\n", - "severity": "WARN", - "ansible": true, - "ansible_fix": true, - "ansible_mitigation": false, - "rule_id": "bash_injection|VULNERABLE_BASH_DETECTED", - "error_key": "VULNERABLE_BASH_DETECTED", - "plugin": "bash_injection", - "description": "Bash locally vulnerable via environment variables (CVE-2014-6271, CVE-2014-7169/Shellshock)", - "summary": "In September 2014, an exploitable bug known as Shellshock was discovered in commonly shipped versions of the bash shell.", - "generic": "Hosts running earlier versions of `bash` are affected by the code injection vulnerability known as **Shellshock**.", - "reason": "

This host is running a version of bash that is affected by the code injection vulnerability known as Shellshock.

\n

The package affected is bash-4.2.45-5.el7.

\n", - "type": null, - "more_info": "For further information about this **critical** vulnerability, see:\n* [Bash Code Injection Vulnerability via Specially Crafted Environment Variables (CVE-2014-6271, CVE-2014-7169)](https://access.redhat.com/articles/1200223)\n* [CVE-2014-6271](https://access.redhat.com/security/cve/CVE-2014-6271)\n* [CVE-2014-7169](https://access.redhat.com/security/cve/CVE-2014-7169)", - "active": true, - "node_id": "1200223", - "category": "Security", - "retired": false, - "reboot_required": false, - "publish_date": "2016-10-31T04:08:36.000Z", - "rec_impact": 2, - "rec_likelihood": 2, - "resolution": "

Red Hat recommends that you upgrade bash immediately:

\n
# yum update bash\n
" - }, - "maintenance_actions": [] - }, { - "details": { - "detected_problem_log_perms": [{ - "log_perms_dirfilename": "/var/log/cron", - "log_perms_sensitive": true, - "log_perms_ls_line": "-rw-r--r--. 1 root root 15438 May 25 10:01 cron" - }], - "error_key": "HARDENING_LOGGING_3_LOG_PERMS" - }, - "id": 709784525, - "rule_id": "hardening_logging_log_perms|HARDENING_LOGGING_3_LOG_PERMS", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

Issues related to system logging and auditing were detected on your system. Important services are disabled or log file permissions are not secure.

\n", - "generic_html": "

Issues related to system logging and auditing were detected on your system.

\n

Red Hat recommends that the logging service rsyslog and the auditing service auditd are enabled and that log files in /var/log have secure permissions.

\n", - "more_info_html": "\n", - "severity": "INFO", - "ansible": false, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "hardening_logging_log_perms|HARDENING_LOGGING_3_LOG_PERMS", - "error_key": "HARDENING_LOGGING_3_LOG_PERMS", - "plugin": "hardening_logging_log_perms", - "description": "Decreased security in system logging permissions", - "summary": "Issues related to system logging and auditing were detected on your system. Important services are disabled or log file permissions are not secure.\n", - "generic": "Issues related to system logging and auditing were detected on your system.\n\nRed Hat recommends that the logging service `rsyslog` and the auditing service `auditd` are enabled and that log files in `/var/log` have secure permissions.\n", - "reason": "

Log files have permission issues.

\n

The following files or directories in /var/log have file permissions that differ from the default RHEL configuration and are possibly non-secure. Red Hat recommends that the file permissions be adjusted to more secure settings.

\n\n \n \n \n \n \n\n\n\n\n\n\n\n
File or directory nameDetected problemOutput from ls -l
/var/log/cronUsers other than root can read or write.-rw-r--r--. 1 root root 15438 May 25 10:01 cron
\n\n\n\n", - "type": null, - "more_info": "* [Why is `/var/log/cron` world readable in RHEL7?](https://access.redhat.com/solutions/1491573)\n* [Using the chkconfig Utility](https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Deployment_Guide/s2-services-chkconfig.html) to configure services on RHEL 6\n* [Managing System Services](https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/System_Administrators_Guide/sect-Managing_Services_with_systemd-Services.html) to configure services on RHEL 7\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).\n", - "active": true, - "node_id": null, - "category": "Security", - "retired": false, - "reboot_required": false, - "publish_date": "2017-05-16T04:08:34.000Z", - "rec_impact": 1, - "rec_likelihood": 1, - "resolution": "

Red Hat recommends that you perform the following adjustments:

\n

Fixing permission issues depends on whether there is a designated safe group on your system that has Read access to the log files. This situation might exist if you want to allow certain administrators to see the log files without becoming root. To prevent log tampering, no other user than root should have permissions to Write to the log files. (The btmp and wtmp files are owned by the utmp group but other users should still be unable to write to them.)

\n

Fix for a default RHEL configuration

\n

(No designated group for reading log files)

\n
chown root:root /var/log/cron\nchmod u=rw,g-x,o-rwx /var/log/cron\n

Fix for a configuration with a designated safe group for reading log files

\n

In the following lines, substitute the name of your designated safe group for the string safegroup:

\n
chown root:safegroup /var/log/cron\nchmod u=rw,g-x,o-rwx /var/log/cron\n
" - }, - "maintenance_actions": [] - }, { - "details": { - "filesystems": [{ - "usage": "99", - "mountpoint": "/" - }, { - "usage": "99", - "mountpoint": "/" - }], - "error_key": "FILESYSTEM_CAPACITY" - }, - "id": 709784535, - "rule_id": "filesystem_capacity|FILESYSTEM_CAPACITY", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

File systems nearing full capacity can cause performance issues because blocks must be used from different block groups. \nBesides, file systems at or exceeding capacity will have stability issues because applications will no longer be able to write to the file system.

\n", - "generic_html": "

File systems nearing full capacity can cause performance issues because blocks must be used from different block groups. \nBesides, file systems at or exceeding capacity will have stability issues because applications will no longer be able to write to the file system.

\n", - "more_info_html": "

How to increase the filesystem size?\nHow do I find out what is using disk space?

\n", - "severity": "WARN", - "ansible": false, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "filesystem_capacity|FILESYSTEM_CAPACITY", - "error_key": "FILESYSTEM_CAPACITY", - "plugin": "filesystem_capacity", - "description": "Decreased stability and/or performance due to filesystem over 95% capacity", - "summary": "File systems nearing full capacity can cause performance issues because blocks must be used from different block groups. \nBesides, file systems at or exceeding capacity will have stability issues because applications will no longer be able to write to the file system.\n", - "generic": "File systems nearing full capacity can cause performance issues because blocks must be used from different block groups. \nBesides, file systems at or exceeding capacity will have stability issues because applications will no longer be able to write to the file system.\n", - "reason": "

This host has the following file systems nearing or at capacity:

\n
    \n\n
  • Filesystem: / Usage: 99%
  • \n\n
  • Filesystem: / Usage: 99%
  • \n\n
", - "type": null, - "more_info": "[How to increase the filesystem size?](https://access.redhat.com/solutions/21820)\n[How do I find out what is using disk space?](https://access.redhat.com/solutions/1154683)\n", - "active": true, - "node_id": "1154683", - "category": "Stability", - "retired": false, - "reboot_required": false, - "publish_date": "2016-10-31T04:08:36.000Z", - "rec_impact": 2, - "rec_likelihood": 3, - "resolution": "

To solve the issue, Red Hat recommends that you either add more storage capacity to the identified file systems, or remove unnecessary files to reduce the current usage.\nPlease refer to more_information part for more detailed steps.

\n" - }, - "maintenance_actions": [] - }, { - "details": { - "msg": "[ 0.000000] crashkernel=auto resulted in zero bytes of reserved memory.", - "auto_with_low_ram": true, - "rhel_ver": 7, - "error_key": "CRASHKERNEL_RESERVATION_FAILED" - }, - "id": 709784555, - "rule_id": "crashkernel_reservation_failed|CRASHKERNEL_RESERVATION_FAILED", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

The crashkernel configuration has failed to produce a working kdump environment. Configuration changes must be made to enable vmcore capture.

\n", - "generic_html": "

Kdump is unable to reserve memory for the kdump kernel. The kdump service has not started and a vmcore will not be captured if the host crashes, which will make it difficult for our support technicians to determine why the machine crashed.

\n", - "more_info_html": "", - "severity": "WARN", - "ansible": false, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "crashkernel_reservation_failed|CRASHKERNEL_RESERVATION_FAILED", - "error_key": "CRASHKERNEL_RESERVATION_FAILED", - "plugin": "crashkernel_reservation_failed", - "description": "Kdump crashkernel reservation failed due to improper configuration of crashkernel parameter", - "summary": "The crashkernel configuration has failed to produce a working kdump environment. Configuration changes must be made to enable vmcore capture.\n", - "generic": "Kdump is unable to reserve memory for the kdump kernel. The kdump service has not started and a vmcore will not be captured if the host crashes, which will make it difficult for our support technicians to determine why the machine crashed.", - "reason": "

This host is unable to reserve memory for the kdump kernel:

\n
[    0.000000] crashkernel=auto resulted in zero bytes of reserved memory.\n

This means the kdump service has not started and a vmcore will not be captured if the host crashes, which will make it difficult for our support technicians to determine why the machine crashed.

\n", - "type": null, - "more_info": null, - "active": true, - "node_id": "59432", - "category": "Stability", - "retired": false, - "reboot_required": false, - "publish_date": "2016-10-31T04:08:33.000Z", - "rec_impact": 1, - "rec_likelihood": 3, - "resolution": "

To fix this issue, Red Hat recommends that you change the crashkernel setting in the grub.conf file.

\n

This host failed to reserved memory with auto crashkernel parameter due to low physical memory. The memory must be reserved by explicitly requesting the reservation size, for example: crashkernel=128M.

\n

For details of crashkernel setting, please refer to the Knowledge article How should the crashkernel parameter be configured for using kdump on RHEL7? to pickup the setting specifically for your host.

\n" - }, - "maintenance_actions": [] - }, { - "details": { - "error_key": "TZDATA_NEED_UPGRADE_INFO_NEED_MANUAL_ACTION" - }, - "id": 709784565, - "rule_id": "tzdata_need_upgrade|TZDATA_NEED_UPGRADE_INFO_NEED_MANUAL_ACTION", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

System clock inaccurate when a leap second event happens in a non-NTP system without following the TAI timescale.

\n", - "generic_html": "

System clock inaccurate when a leap second event happens in a non-NTP system without following the TAI timescale.

\n", - "more_info_html": "", - "severity": "INFO", - "ansible": false, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "tzdata_need_upgrade|TZDATA_NEED_UPGRADE_INFO_NEED_MANUAL_ACTION", - "error_key": "TZDATA_NEED_UPGRADE_INFO_NEED_MANUAL_ACTION", - "plugin": "tzdata_need_upgrade", - "description": "System clock inaccurate when a leap second event happens in a non-NTP system without following the TAI timescale", - "summary": "System clock inaccurate when a leap second event happens in a non-NTP system without following the TAI timescale.\n", - "generic": "System clock inaccurate when a leap second event happens in a non-NTP system without following the TAI timescale.\n", - "reason": "

This system running as a non-NTP system is following the UTC timescale. In this situation, manual correction is required to avoid system clock inaccuracy when a leap second event happens.

\n", - "type": null, - "more_info": null, - "active": true, - "node_id": "1465713", - "category": "Stability", - "retired": false, - "reboot_required": false, - "publish_date": null, - "rec_impact": 2, - "rec_likelihood": 1, - "resolution": "

The system clock of this system needs manual correction when a leap second event happens. For example:

\n
\n\n# date -s \"20170101 HH:MM:SS\"\n\n
\n\n

You need to replace "HH:MM:SS" with the accurate time after the leap second occurs.

\n" - }, - "maintenance_actions": [] - }, { - "details": { - "selinux_info": true, - "package_name": "kernel", - "selinux_enforcing": true, - "selinux_can_help": true, - "minimal_selinux_policy": "selinux-policy-3.13.1-81.el7", - "selinux_enabled": true, - "vulnerable_kernel": "3.10.0-123.el7", - "active_policy": "selinux-policy-3.12.1-153.el7", - "dccp_loading_disabled": null, - "error_key": "KERNEL_CVE_2017_6074", - "enough_policy": false, - "dccp_loaded": null, - "mitigation_info": false - }, - "id": 709784575, - "rule_id": "CVE_2017_6074_kernel|KERNEL_CVE_2017_6074", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

A use-after-free flaw was found in the Linux kernel IPv6 DCCP network protocol code. It has been assigned CVE-2017-6074. An unprivileged local user could use this flaw to execute arbitrary code in kernel memory and increase their privileges on the system.

\n", - "generic_html": "

A use-after-free flaw was found in the Linux kernel IPv6 DCCP network protocol code. It has been assigned CVE-2017-6074.

\n

An unprivileged local user could use this flaw to execute arbitrary code in kernel memory and increase their privileges on the system. A local user could initiate a DCCP network connection on any local system network interface and then create specially-crafted memory allocations containing malicious instructions that can then either cause a crash or potentially escalate the user's privileges.

\n

An attacker must have access to a local account on the system; this is not a remote attack and it requires IPv6 support to be enabled on the system.

\n

Red Hat recommends that you update the kernel when possible. Otherwise, you can use proposed mitigation to disable DCCP. SELinux in enforcing mode can also mitigate the issue under specific circumstances.

\n", - "more_info_html": "\n", - "severity": "WARN", - "ansible": true, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "CVE_2017_6074_kernel|KERNEL_CVE_2017_6074", - "error_key": "KERNEL_CVE_2017_6074", - "plugin": "CVE_2017_6074_kernel", - "description": "Kernel vulnerable to local privilege escalation via DCCP module (CVE-2017-6074)", - "summary": "A use-after-free flaw was found in the Linux kernel IPv6 DCCP network protocol code. It has been assigned [CVE-2017-6074](https://access.redhat.com/security/cve/CVE-2017-6074). An unprivileged local user could use this flaw to execute arbitrary code in kernel memory and increase their privileges on the system.\n", - "generic": "A use-after-free flaw was found in the Linux kernel IPv6 DCCP network protocol code. It has been assigned CVE-2017-6074. \n\nAn unprivileged local user could use this flaw to execute arbitrary code in kernel memory and increase their privileges on the system. A local user could initiate a DCCP network connection on any local system network interface and then create specially-crafted memory allocations containing malicious instructions that can then either cause a crash or potentially escalate the user's privileges.\n\nAn attacker must have access to a local account on the system; this is not a remote attack and it requires IPv6 support to be enabled on the system.\n\nRed Hat recommends that you update the kernel when possible. Otherwise, you can use proposed mitigation to disable DCCP. SELinux in enforcing mode can also mitigate the issue under specific circumstances.\n", - "reason": "

A use-after-free flaw was found within the Linux kernel IPv6 DCCP network protocol code.

\n

This host is affected because:

\n
  • It is running kernel 3.10.0-123.el7.
  • SELinux policy is outdated.
\n\n\n\n\n\n\n

Your installed SELinux policy is selinux-policy-3.12.1-153.el7; however, to mitigate the issue, the earliest required version is selinux-policy-3.13.1-81.el7.

\n", - "type": null, - "more_info": "* For more information about the flaw, see [CVE-2017-6074](https://access.redhat.com/security/cve/CVE-2017-6074).\n* To learn how to upgrade packages, see [What is yum and how do I use it?](https://access.redhat.com/solutions/9934).\n* For more information about SELinux, see [Benefits of running SELinux](https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/SELinux_Users_and_Administrators_Guide/chap-Security-Enhanced_Linux-Introduction.html#sect-Security-Enhanced_Linux-Introduction-Benefits_of_running_SELinux).\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).\n", - "active": true, - "node_id": null, - "category": "Security", - "retired": false, - "reboot_required": false, - "publish_date": null, - "rec_impact": 2, - "rec_likelihood": 2, - "resolution": "

Red Hat recommends updating the kernel package and rebooting the system.

\n
# yum update kernel\n# reboot\n

Alternatively, apply one of the following mitigations:

\n
Update SELinux policy
\n

Update your SELinux policy:

\n
# yum update selinux-policy\n

The system does not provide enough information for Insights about loaded kernel modules. It is not possible to recommend a mitigation based on kernel modules.

\n" - }, - "maintenance_actions": [] - } -] diff --git a/awx/ui/test/spec/inventories/insights/data/solvable.insights-data.js b/awx/ui/test/spec/inventories/insights/data/solvable.insights-data.js deleted file mode 100644 index 5e563e991202..000000000000 --- a/awx/ui/test/spec/inventories/insights/data/solvable.insights-data.js +++ /dev/null @@ -1,269 +0,0 @@ -export default [ - { - "details": { - "vulnerable_kernel": "3.10.0-123.el7", - "package_name": "kernel", - "error_key": "KERNEL_CVE_2016_5195" - }, - "id": 709784485, - "rule_id": "CVE_2016_5195_kernel|KERNEL_CVE_2016_5195", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

A flaw was found in the Linux kernel's memory subsystem. An unprivileged local user could use this flaw to write to files they would normally only have read-only access to and thus increase their privileges on the system.

\n", - "generic_html": "

A race condition was found in the way Linux kernel's memory subsystem handled breakage of the the read only shared mappings COW situation on write access. An unprivileged local user could use this flaw to write to files they should normally have read-only access to, and thus increase their privileges on the system.

\n

A process that is able to mmap a file is able to race Copy on Write (COW) page creation (within get_user_pages) with madvise(MADV_DONTNEED) kernel system calls. This would allow modified pages to bypass the page protection mechanism and modify the mapped file. The vulnerability could be abused by allowing an attacker to modify existing setuid files with instructions to elevate permissions. This attack has been found in the wild.

\n

Red Hat recommends that you update the kernel package or apply mitigations.

\n", - "more_info_html": "\n", - "severity": "WARN", - "ansible": true, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "CVE_2016_5195_kernel|KERNEL_CVE_2016_5195", - "error_key": "KERNEL_CVE_2016_5195", - "plugin": "CVE_2016_5195_kernel", - "description": "Kernel vulnerable to privilege escalation via permission bypass (CVE-2016-5195)", - "summary": "A flaw was found in the Linux kernel's memory subsystem. An unprivileged local user could use this flaw to write to files they would normally only have read-only access to and thus increase their privileges on the system.", - "generic": "A race condition was found in the way Linux kernel's memory subsystem handled breakage of the the read only shared mappings COW situation on write access. An unprivileged local user could use this flaw to write to files they should normally have read-only access to, and thus increase their privileges on the system.\n\nA process that is able to mmap a file is able to race Copy on Write (COW) page creation (within get_user_pages) with madvise(MADV_DONTNEED) kernel system calls. This would allow modified pages to bypass the page protection mechanism and modify the mapped file. The vulnerability could be abused by allowing an attacker to modify existing setuid files with instructions to elevate permissions. This attack has been found in the wild. \n\nRed Hat recommends that you update the kernel package or apply mitigations.", - "reason": "

A flaw was found in the Linux kernel's memory subsystem. An unprivileged local user could use this flaw to write to files they would normally have read-only access to and thus increase their privileges on the system.

\n

This host is affected because it is running kernel 3.10.0-123.el7.

\n

There is currently no mitigation applied and your system is vulnerable.

\n", - "type": null, - "more_info": "* For more information about the flaw see [CVE-2016-5195](https://access.redhat.com/security/cve/CVE-2016-5195)\n* To learn how to upgrade packages, see \"[What is yum and how do I use it?](https://access.redhat.com/solutions/9934)\"\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat Products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).", - "active": true, - "node_id": "2706661", - "category": "Security", - "retired": false, - "reboot_required": false, - "publish_date": "2016-10-31T04:08:33.000Z", - "rec_impact": 2, - "rec_likelihood": 2, - "resolution": "

Red Hat recommends that you update the kernel package and restart the system:

\n
# yum update kernel\n# reboot\n

or

\n

Alternatively, this issue can be addressed by applying mitigations until the machine is restarted with the updated kernel package.

\n

Please refer to the Resolve Tab in the vulnerability article for information about the mitigation and the latest information.

\n" - }, - "maintenance_actions": [{ - "done": false, - "id": 29885, - "maintenance_plan": { - "maintenance_id": 12195, - "name": null, - "description": null, - "start": null, - "end": null, - "created_by": "rhn-support-jnewton", - "silenced": false, - "hidden": true, - "suggestion": "proposed", - "remote_branch": null - } - }] - }, - { - "details": { - "mitigation_conf": "no", - "sysctl_live_ack_limit": "100", - "package_name": "kernel", - "sysctl_live_ack_limit_line": "net.ipv4.tcp_challenge_ack_limit = 100", - "error_key": "KERNEL_CVE_2016_5696_URGENT", - "vulnerable_kernel": "3.10.0-123.el7", - "sysctl_conf_ack_limit": "100", - "sysctl_conf_ack_limit_line": "net.ipv4.tcp_challenge_ack_limit=100", - "mitigation_live": "no" - }, - "id": 766342155, - "rule_id": "CVE_2016_5696_kernel|KERNEL_CVE_2016_5696_URGENT", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

A flaw in the Linux kernel's TCP/IP networking subsystem implementation of the RFC 5961 challenge ACK rate limiting was found that could allow an attacker located on different subnet to inject or take over a TCP connection between a server and client without needing to use a traditional man-in-the-middle (MITM) attack.

\n", - "generic_html": "

A flaw was found in the implementation of the Linux kernel's handling of networking challenge ack (RFC 5961) where an attacker is able to determine the\nshared counter. This flaw allows an attacker located on different subnet to inject or take over a TCP connection between a server and client without needing to use a traditional man-in-the-middle (MITM) attack.

\n

Red Hat recommends that you update the kernel package or apply mitigations.

\n", - "more_info_html": "\n", - "severity": "ERROR", - "ansible": true, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "CVE_2016_5696_kernel|KERNEL_CVE_2016_5696_URGENT", - "error_key": "KERNEL_CVE_2016_5696_URGENT", - "plugin": "CVE_2016_5696_kernel", - "description": "Kernel vulnerable to man-in-the-middle via payload injection", - "summary": "A flaw in the Linux kernel's TCP/IP networking subsystem implementation of the [RFC 5961](https://tools.ietf.org/html/rfc5961) challenge ACK rate limiting was found that could allow an attacker located on different subnet to inject or take over a TCP connection between a server and client without needing to use a traditional man-in-the-middle (MITM) attack.", - "generic": "A flaw was found in the implementation of the Linux kernel's handling of networking challenge ack ([RFC 5961](https://tools.ietf.org/html/rfc5961)) where an attacker is able to determine the\nshared counter. This flaw allows an attacker located on different subnet to inject or take over a TCP connection between a server and client without needing to use a traditional man-in-the-middle (MITM) attack. \n\nRed Hat recommends that you update the kernel package or apply mitigations.", - "reason": "

A flaw was found in the implementation of the Linux kernel's handling of networking challenge ack (RFC 5961) where an attacker is able to determine the\nshared counter. This flaw allows an attacker located on different subnet to inject or take over a TCP connection between a server and client without needing to use a traditional man-in-the-middle (MITM) attack.

\n

This host is affected because it is running kernel 3.10.0-123.el7.

\n

Your currently loaded kernel configuration contains this setting:

\n
net.ipv4.tcp_challenge_ack_limit = 100\n

Your currently stored kernel configuration is:

\n
net.ipv4.tcp_challenge_ack_limit=100\n

There is currently no mitigation applied and your system is vulnerable.

\n", - "type": null, - "more_info": "* For more information about the flaw see [CVE-2016-5696](https://access.redhat.com/security/cve/CVE-2016-5696)\n* To learn how to upgrade packages, see \"[What is yum and how do I use it?](https://access.redhat.com/solutions/9934)\"\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat Products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).", - "active": true, - "node_id": "2438571", - "category": "Security", - "retired": false, - "reboot_required": false, - "publish_date": "2016-10-31T04:08:32.000Z", - "rec_impact": 4, - "rec_likelihood": 2, - "resolution": "

Red Hat recommends that you update the kernel package and restart the system:

\n
# yum update kernel\n# reboot\n

or

\n

Alternatively, this issue can be addressed by applying the following mitigations until the machine is restarted with the updated kernel package.

\n

Edit /etc/sysctl.conf file as root, add the mitigation configuration, and reload the kernel configuration:

\n
# echo "net.ipv4.tcp_challenge_ack_limit = 2147483647" >> /etc/sysctl.conf \n# sysctl -p\n
" - }, - "maintenance_actions": [{ - "done": false, - "id": 56045, - "maintenance_plan": { - "maintenance_id": 15875, - "name": "Payload Injection Fix", - "description": "", - "start": "2017-06-01T02:00:00.000Z", - "end": "2017-06-01T03:00:00.000Z", - "created_by": "rhn-support-wnix", - "silenced": false, - "hidden": false, - "suggestion": null, - "remote_branch": null - } - }, { - "done": false, - "id": 61575, - "maintenance_plan": { - "maintenance_id": 16825, - "name": "Summit 2017 Plan 1", - "description": "", - "start": null, - "end": null, - "created_by": "rhn-support-wnix", - "silenced": false, - "hidden": false, - "suggestion": null, - "remote_branch": null - } - }, { - "done": false, - "id": 66175, - "maintenance_plan": { - "maintenance_id": 19435, - "name": null, - "description": null, - "start": null, - "end": null, - "created_by": "rhn-support-wnix", - "silenced": false, - "hidden": false, - "suggestion": null, - "remote_branch": null - } - }, { - "done": false, - "id": 71015, - "maintenance_plan": { - "maintenance_id": 19835, - "name": "Optum Payload", - "description": "", - "start": "2017-05-27T02:00:00.000Z", - "end": "2017-05-27T03:00:00.000Z", - "created_by": "rhn-support-wnix", - "silenced": false, - "hidden": false, - "suggestion": null, - "remote_branch": null - } - }] - }, - { - "details": { - "mod_loading_disabled": null, - "package_name": "kernel", - "error_key": "KERNEL_CVE_2017_2636", - "vulnerable_kernel": "3.10.0-123.el7", - "mod_loaded": null, - "mitigation_info": false - }, - "id": 766342165, - "rule_id": "CVE_2017_2636_kernel|KERNEL_CVE_2017_2636", - "system_id": "f31b6265939d4a8492d3ce9655dc94be", - "account_number": "540155", - "uuid": "d195e3c5e5e6469781c4e59fa3f5ba87", - "date": "2017-05-25T14:01:19.000Z", - "rule": { - "summary_html": "

A vulnerability in the Linux kernel allowing local privilege escalation was discovered.\nThe issue was reported as CVE-2017-2636.

\n", - "generic_html": "

A use-after-free flaw was found in the Linux kernel implementation of the HDLC (High-Level Data Link Control) TTY line discipline implementation. It has been assigned CVE-2017-2636.

\n

An unprivileged local user could use this flaw to execute arbitrary code in kernel memory and increase their privileges on the system. The kernel uses a TTY subsystem to take and show terminal output to connected systems. An attacker crafting specific-sized memory allocations could abuse this mechanism to place a kernel function pointer with malicious instructions to be executed on behalf of the attacker.

\n

An attacker must have access to a local account on the system; this is not a remote attack. Exploiting this flaw does not require Microgate or SyncLink hardware to be in use.

\n

Red Hat recommends that you use the proposed mitigation to disable the N_HDLC module.

\n", - "more_info_html": "\n", - "severity": "WARN", - "ansible": true, - "ansible_fix": false, - "ansible_mitigation": false, - "rule_id": "CVE_2017_2636_kernel|KERNEL_CVE_2017_2636", - "error_key": "KERNEL_CVE_2017_2636", - "plugin": "CVE_2017_2636_kernel", - "description": "Kernel vulnerable to local privilege escalation via n_hdlc module (CVE-2017-2636)", - "summary": "A vulnerability in the Linux kernel allowing local privilege escalation was discovered.\nThe issue was reported as [CVE-2017-2636](https://access.redhat.com/security/cve/CVE-2017-2636).\n", - "generic": "A use-after-free flaw was found in the Linux kernel implementation of the HDLC (High-Level Data Link Control) TTY line discipline implementation. It has been assigned CVE-2017-2636.\n\nAn unprivileged local user could use this flaw to execute arbitrary code in kernel memory and increase their privileges on the system. The kernel uses a TTY subsystem to take and show terminal output to connected systems. An attacker crafting specific-sized memory allocations could abuse this mechanism to place a kernel function pointer with malicious instructions to be executed on behalf of the attacker.\n\nAn attacker must have access to a local account on the system; this is not a remote attack. Exploiting this flaw does not require Microgate or SyncLink hardware to be in use.\n\nRed Hat recommends that you use the proposed mitigation to disable the N_HDLC module.\n", - "reason": "

A use-after-free flaw was found in the Linux kernel implementation of the HDLC (High-Level Data Link Control) TTY line discipline implementation.

\n

This host is affected because it is running kernel 3.10.0-123.el7.

\n", - "type": null, - "more_info": "* For more information about the flaw, see [CVE-2017-2636](https://access.redhat.com/security/cve/CVE-2017-2636) and [CVE-2017-2636 article](https://access.redhat.com/security/vulnerabilities/CVE-2017-2636).\n* The Customer Portal page for the [Red Hat Security Team](https://access.redhat.com/security/) contains more information about policies, procedures, and alerts for Red Hat products.\n* The Security Team also maintains a frequently updated blog at [securityblog.redhat.com](https://securityblog.redhat.com).\n", - "active": true, - "node_id": null, - "category": "Security", - "retired": false, - "reboot_required": false, - "publish_date": null, - "rec_impact": 2, - "rec_likelihood": 2, - "resolution": "

Red Hat recommends updating the kernel package and rebooting the system.

\n
# yum update kernel\n# reboot\n
" - }, - "maintenance_actions": [{ - "done": false, - "id": 58335, - "maintenance_plan": { - "maintenance_id": 16545, - "name": "Insights Summit 2017 - n_HDLC", - "description": "", - "start": "2017-05-06T02:00:00.000Z", - "end": "2017-05-06T03:00:00.000Z", - "created_by": "rhn-support-wnix", - "silenced": false, - "hidden": false, - "suggestion": null, - "remote_branch": null - } - }, { - "done": false, - "id": 61895, - "maintenance_plan": { - "maintenance_id": 16835, - "name": "Summit 2017 N_HDLC", - "description": "", - "start": null, - "end": null, - "created_by": "rhn-support-wnix", - "silenced": false, - "hidden": false, - "suggestion": null, - "remote_branch": null - } - }, { - "done": false, - "id": 66225, - "maintenance_plan": { - "maintenance_id": 19445, - "name": "Seattle's Best Plan", - "description": null, - "start": null, - "end": null, - "created_by": "rhn-support-wnix", - "silenced": false, - "hidden": false, - "suggestion": null, - "remote_branch": null - } - }, { - "done": false, - "id": 71075, - "maintenance_plan": { - "maintenance_id": 19845, - "name": "Optum N_HDLC FIX", - "description": null, - "start": null, - "end": null, - "created_by": "rhn-support-wnix", - "silenced": false, - "hidden": false, - "suggestion": null, - "remote_branch": null - } - }] - } -] diff --git a/awx/ui/test/spec/inventories/insights/insights.service-test.js b/awx/ui/test/spec/inventories/insights/insights.service-test.js deleted file mode 100644 index 93024a86b9a9..000000000000 --- a/awx/ui/test/spec/inventories/insights/insights.service-test.js +++ /dev/null @@ -1,62 +0,0 @@ -'use strict'; - -import insights_json from './data/insights-data.js'; -import solvable_insights_json from './data/solvable.insights-data.js'; -import not_solvable_insights_json from './data/not_solvable.insights-data.js'; -import high_insights_json from './data/high.insights-data.js'; -import medium_insights_json from './data/medium.insights-data.js'; -import low_insights_json from './data/low.insights-data.js'; - -describe('Service: InsightsService', () => { - let InsightsService; - - beforeEach(angular.mock.module('awApp')); - - beforeEach(angular.mock.inject(( _InsightsService_) => { - InsightsService = _InsightsService_; - })); - - describe('filter()', () => { - it('filter for "total" returns the total set of reports', () => { - let filteredSet = InsightsService.filter('total', insights_json.reports); - expect(filteredSet).toEqual(insights_json.reports); - expect(filteredSet.length).toBe(12); - }); - - it('properly filters the reports dataset for solvable reports', () => { - let filteredSet = InsightsService.filter('solvable', insights_json.reports); - expect(filteredSet).toEqual(solvable_insights_json); - expect(filteredSet.length).toBe(3); - }); - - it('properly filters the reports dataset for not-solvable reports', () => { - let filteredSet = InsightsService.filter('not_solvable', insights_json.reports); - expect(filteredSet).toEqual(not_solvable_insights_json); - expect(filteredSet.length).toBe(9); - }); - - it('properly filters the reports dataset for CRITICAL reports', () => { - let filteredSet = InsightsService.filter('critical', insights_json.reports); - expect(filteredSet).toEqual([]); - expect(filteredSet.length).toBe(0); - }); - - it('properly filters the reports dataset for ERROR reports', () => { - let filteredSet = InsightsService.filter('high', insights_json.reports); - expect(filteredSet).toEqual(high_insights_json); - expect(filteredSet.length).toBe(2); - }); - - it('properly filters the reports dataset for WARN reports', () => { - let filteredSet = InsightsService.filter('medium', insights_json.reports); - expect(filteredSet).toEqual(medium_insights_json); - expect(filteredSet.length).toBe(8); - }); - - it('properly filters the reports dataset for INFO reports', () => { - let filteredSet = InsightsService.filter('low', insights_json.reports); - expect(filteredSet).toEqual(low_insights_json); - expect(filteredSet.length).toBe(2); - }); - }); -}); diff --git a/awx/ui/test/spec/inventories/manage/inventory-manage.service-test.js b/awx/ui/test/spec/inventories/manage/inventory-manage.service-test.js deleted file mode 100644 index c72e49c32e1d..000000000000 --- a/awx/ui/test/spec/inventories/manage/inventory-manage.service-test.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -describe('Service: InventoriesService', () => { - - let Rest, - InventoriesService; - - beforeEach(angular.mock.module('awApp'), ($provide)=>{ - $provide.value('Rest', Rest); - }); - beforeEach(angular.mock.module('inventoryManage')); - beforeEach(angular.mock.inject(($httpBackend, _InventoriesService_) =>{ - Rest = $httpBackend; - InventoriesService = _InventoriesService_; - })); - - xdescribe('RESTy methods should handle errors', () => { - - beforeEach(() => { - spyOn(InventoriesService, 'error'); - }); - it('InventoriesService.getInventory should handle errors', () => { - Rest.expectGET('/api/v2/inventory:id/').respond(400, {}); - Rest.flush(); - expect(InventoriesService.error).toHaveBeenCalled(); - }); - }); - - // Unit tests often reveal which pieces of our code should be factored out - xit('RESTy methods should start/stop spinny', function(){ - - }); - - afterEach(function() { - Rest.verifyNoOutstandingExpectation(); - Rest.verifyNoOutstandingRequest(); - }); -}); diff --git a/awx/ui/test/spec/karma.spec.js b/awx/ui/test/spec/karma.spec.js deleted file mode 100644 index 5a4cc572fd12..000000000000 --- a/awx/ui/test/spec/karma.spec.js +++ /dev/null @@ -1,52 +0,0 @@ -const path = require('path'); -const webpackConfig = require('./webpack.spec'); - -process.env.CHROME_BIN = require('puppeteer').executablePath(); - -const SRC_PATH = path.resolve(__dirname, '../../client/src'); -const NODE_MODULES = path.resolve(__dirname, '../../node_modules'); - -module.exports = config => { - config.set({ - basePath: '../..', - autoWatch: true, - colors: true, - browsers: ['Chrome', 'Firefox'], - frameworks: ['jasmine'], - reporters: ['progress', 'junit'], - files:[ - 'client/src/vendor.js', - path.join(NODE_MODULES, 'angular-mocks/angular-mocks.js'), - path.join(SRC_PATH, 'app.js'), - 'client/src/**/*.html', - 'test/spec/**/*-test.js', - ], - preprocessors: { - 'client/src/vendor.js': 'webpack', - [path.join(SRC_PATH, 'app.js')]: 'webpack', - 'client/src/**/*.html': 'html2js', - 'test/spec/**/*-test.js': 'webpack' - }, - webpack: webpackConfig, - webpackMiddleware: { - noInfo: true - }, - junitReporter: { - outputDir: 'reports', - outputFile: 'results.spec.xml', - useBrowserName: false - }, - customLaunchers: { - chromeHeadless: { - base: 'Chrome', - flags: [ - '--no-sandbox', - '--disable-setuid-sandbox', - '--headless', - '--disable-gpu', - '--remote-debugging-port=9222', - ], - }, - }, - }); -}; diff --git a/awx/ui/test/spec/license/license.controller-test.js b/awx/ui/test/spec/license/license.controller-test.js deleted file mode 100644 index 660e918e790d..000000000000 --- a/awx/ui/test/spec/license/license.controller-test.js +++ /dev/null @@ -1,78 +0,0 @@ -'use strict'; - -describe('Controller: LicenseController', () => { - // Setup - let scope, - LicenseController, - ConfigService, - ProcessErrors, - config, - rhCreds; - - beforeEach(angular.mock.module('awApp')); - beforeEach(angular.mock.module('license', ($provide) => { - ConfigService = jasmine.createSpyObj('ConfigService', [ - 'getConfig', - 'delete' - ]); - - config = { - license_info: { - time_remaining: 1234567 // seconds - }, - version: '3.1.0-devel' - }; - - rhCreds = { - password: '$encrypted$', - username: 'foo', - } - - ProcessErrors = jasmine.createSpy('ProcessErrors'); - - $provide.value('ConfigService', ConfigService); - $provide.value('ProcessErrors', ProcessErrors); - $provide.value('config', config); - $provide.value('rhCreds', rhCreds); - })); - - beforeEach(angular.mock.inject( ($rootScope, $controller, _ConfigService_, _ProcessErrors_, _config_, _rhCreds_) => { - scope = $rootScope.$new(); - ConfigService = _ConfigService_; - ProcessErrors = _ProcessErrors_; - config = _config_; - rhCreds = _rhCreds_; - LicenseController = $controller('licenseController', { - $scope: scope, - ConfigService: ConfigService, - ProcessErrors: ProcessErrors, - config: config, - rhCreds: rhCreds - }); - })); - - xit('should show correct expiration date', ()=>{ - let date = new Date(), - options = { - year: 'numeric', - month: '2-digit', - day: '2-digit' - }; - date.setDate(date.getDate() + 14); - expect(scope.time.expiresOn).toEqual(date.toLocaleDateString(undefined, options)); - }); - - it('should show correct time remaining', ()=>{ - expect(scope.time.remaining).toMatch('14 Days'); - }); - - xit('should throw an error if provided license is invalid JSON', ()=>{ - let event = { - target: {files: [new File(['asdf'], 'license.txt', {type: 'text/html'})]} - }; - scope.getKey(event); - expect(ProcessErrors).toHaveBeenCalled(); - }); - - xit('should submit a valid license'); -}); diff --git a/awx/ui/test/spec/lookup/lookup-modal.directive-test.js b/awx/ui/test/spec/lookup/lookup-modal.directive-test.js deleted file mode 100644 index 7800d8d65451..000000000000 --- a/awx/ui/test/spec/lookup/lookup-modal.directive-test.js +++ /dev/null @@ -1,129 +0,0 @@ -'use strict'; - -xdescribe('Directive: lookupModal', () => { - - let dom, element, listHtml, listDefinition, Dataset, - lookupTemplate, paginateTemplate, searchTemplate, columnSortTemplate, - $scope, $parent, $compile, $state; - - // mock dependency chains - // (shared requires RestServices requires Authorization etc) - beforeEach(angular.mock.module('login')); - beforeEach(angular.mock.module('shared')); - - beforeEach(angular.mock.module('LookupModalModule', ($provide) => { - $provide.value('smartSearch', angular.noop); - $provide.value('columnSort', angular.noop); - $provide.value('paginate', angular.noop); - $state = jasmine.createSpyObj('$state', ['go']); - })); - - beforeEach(angular.mock.inject(($templateCache, _$rootScope_, _$compile_, _generateList_) => { - listDefinition = { - name: 'mocks', - iterator: 'mock', - fields: { - name: {} - } - }; - - listHtml = _generateList_.build({ - mode: 'lookup', - list: listDefinition, - input_type: 'radio' - }); - - Dataset = { - data: { - results: [ - { id: 1, name: 'Mock Resource 1' }, - { id: 2, name: 'Mock Resource 2' }, - { id: 3, name: 'Mock Resource 3' }, - { id: 4, name: 'Mock Resource 4' }, - { id: 5, name: 'Mock Resource 5' }, - ] - } - }; - - dom = angular.element(`${listHtml}`); - - // populate $templateCache with directive.templateUrl at test runtime, - lookupTemplate = window.__html__['client/src/shared/lookup/lookup-modal.partial.html']; - paginateTemplate = window.__html__['client/src/shared/paginate/paginate.partial.html']; - searchTemplate = window.__html__['client/src/shared/smart-search/smart-search.partial.html']; - columnSortTemplate = window.__html__['client/src/shared/column-sort/column-sort.partial.html']; - - $templateCache.put('/static/partials/shared/lookup/lookup-modal.partial.html', lookupTemplate); - $templateCache.put('/static/partials/shared/paginate/paginate.partial.html', paginateTemplate); - $templateCache.put('/static/partials/shared/smart-search/smart-search.partial.html', searchTemplate); - $templateCache.put('/static/partials/shared/column-sort/column-sort.partial.html', columnSortTemplate); - - $compile = _$compile_; - $parent = _$rootScope_.$new(); - - // mock resolvables - $scope = $parent.$new(); - $scope.$resolve = { - ListDefinition: listDefinition, - Dataset: Dataset - }; - })); - - it('Resource is pre-selected in form - corresponding radio should initialize checked', () => { - $parent.mock = 1; // resource id - $parent.mock_name = 'Mock Resource 1'; // resource name - - element = $compile(dom)($scope); - $scope.$digest(); - - expect($(':radio')[0].is(':checked')).toEqual(true); - }); - - it('No resource pre-selected in form - no radio should initialize checked', () => { - element = $compile(dom)($scope); - $scope.$digest(); - - _.forEach($(':radio'), (radio) => { - expect(radio.is('checked')).toEqual(false); - }); - }); - - it('Should update $parent / form scope and exit $state on save', () => { - element = $compile(dom)($scope); - $scope.$digest(); - $(':radio')[1].click(); - $('.Lookup-save')[0].click(); - - expect($parent.mock).toEqual(2); - expect($parent.mock_name).toEqual('Mock Resource 2'); - expect($state.go).toHaveBeenCalled(); - }); - - it('Should not update $parent / form scope on exit via header', () => { - $parent.mock = 3; // resource id - $parent.mock_name = 'Mock Resource 3'; // resource name - element = $compile(dom)($scope); - $scope.$digest(); - - $(':radio')[1].click(); - $('.Form-exit')[0].click(); - - expect($parent.mock).toEqual(3); - expect($parent.mock_name).toEqual('Mock Resource 3'); - expect($state.go).toHaveBeenCalled(); - }); - - it('Should not update $parent / form scope on exit via cancel button', () => { - $parent.mock = 3; // resource id - $parent.mock_name = 'Mock Resource 3'; // resource name - element = $compile(dom)($scope); - $scope.$digest(); - - $(':radio')[1].click(); - $('.Lookup-cancel')[0].click(); - - expect($parent.mock).toEqual(3); - expect($parent.mock_name).toEqual('Mock Resource 3'); - expect($state.go).toHaveBeenCalled(); - }); -}); diff --git a/awx/ui/test/spec/paginate/paginate.directive-test.js b/awx/ui/test/spec/paginate/paginate.directive-test.js deleted file mode 100644 index b8d180272598..000000000000 --- a/awx/ui/test/spec/paginate/paginate.directive-test.js +++ /dev/null @@ -1,116 +0,0 @@ -'use strict'; - -xdescribe('Directive: Paginate', () => { - var dom = angular.element(''), - template, - element, - $scope, - $compile, - $state, - $stateParams = {}; - - beforeEach(angular.mock.module('shared'), ($provide) =>{ - $provide.value('Rest', angular.noop); - }); - beforeEach(angular.mock.module('PaginateModule', ($provide) => { - $state = jasmine.createSpyObj('$state', ['go']); - - $provide.value('$stateParams', $stateParams); - $provide.value('Rest', angular.noop); - })); - beforeEach(angular.mock.inject(($templateCache, _$rootScope_, _$compile_) => { - // populate $templateCache with directive.templateUrl at test runtime, - template = window.__html__['client/src/shared/paginate/paginate.partial.html']; - $templateCache.put('/static/partials/shared/paginate/paginate.partial.html', template); - - $compile = _$compile_; - $scope = _$rootScope_.$new(); - })); - - it('should be hidden if only 1 page of data', () => { - - $scope.mock_dataset = {count: 19}; - $scope.pageSize = 20; - element = $compile(dom)($scope); - $scope.$digest(); - - expect($('.Paginate-wrapper', element)).hasClass('ng-hide'); - }); - describe('it should show expected page range', () => { - - - it('should show 7 pages', () =>{ - - $scope.pageSize = 1; - $scope.mock_dataset = {count: 7}; - element = $compile(dom)($scope); - $scope.$digest(); - // next, previous, 7 pages - expect($('.Paginate-controls--item', element)).length.toEqual(9); - }); - it('should show 100 pages', () =>{ - $scope.pageSize = 1; - $scope.mock_dataset = {count: 100}; - element = $compile(dom)($scope); - $scope.$digest(); - // first, next, previous, last, 100 pages - expect($('.Paginate-controls--item', element)).length.toEqual(104); - }); - }); - describe('it should get expected page', () => { - - it('should get the next page', () =>{ - - $scope.mock_dataset = { - count: 42, - }; - - $stateParams.mock_search = { - page_size: 5, - page: 1 - }; - - element = $compile(dom)($scope); - $('.Paginate-controls--next').click(); - expect($stateParams.mock_search.page).toEqual(2); - }); - - it('should get the previous page', ()=>{ - - $scope.mock_dataset = { - count: 42 - }; - $stateParams.mock_search = { - page_size: 10, - page: 3 - }; - - element = $compile(dom)($scope); - $('.Paginate-controls--previous'); - expect($stateParams.mock_search.page).toEqual(2); - }); - it('should get the last page', ()=>{ - $scope.mock_dataset = { - count: 110 - }; - $stateParams.mock_search = { - page_size: 5, - page: 1 - }; - $('.Paginate-controls--last').click(); - expect($stateParams.mock_search.page).toEqual(42); - }); - it('should get the first page', () => { - $scope.mock_dataset = { - count: 110 - }; - $stateParams.mock_search = { - page_size: 5, - page: 35 - }; - $('.Paginate-controls--first').click(); - expect($stateParams.mock_search.page).toEqual(1); - }); - - }); -}); diff --git a/awx/ui/test/spec/shared/filters/append.filter-test.js b/awx/ui/test/spec/shared/filters/append.filter-test.js deleted file mode 100644 index b687797aefde..000000000000 --- a/awx/ui/test/spec/shared/filters/append.filter-test.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -describe('Filter: append', () => { - var filter; - - beforeEach(angular.mock.module('awApp')); - - beforeEach(function(){ - inject(function($injector){ - filter = $injector.get('$filter')('append'); - }); - }); - - it('should append the two parameters passed', function(){ - expect(filter("foo", "bar")).toBe("foobar"); - }); - - it('should return string if no append param passed', function(){ - expect(filter("foo")).toBe("foo"); - }); - - it('should return empty string if no params passed', function(){ - expect(filter()).toBe(""); - }); -}); diff --git a/awx/ui/test/spec/shared/filters/capitalize.filter-test.js b/awx/ui/test/spec/shared/filters/capitalize.filter-test.js deleted file mode 100644 index 671385ce4ada..000000000000 --- a/awx/ui/test/spec/shared/filters/capitalize.filter-test.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; - -describe('Filter: capitalize', () => { - var filter; - - beforeEach(angular.mock.module('awApp')); - - beforeEach(function(){ - inject(function($injector){ - filter = $injector.get('$filter')('capitalize'); - }); - }); - - it('should capitalize the first letter', function(){ - expect(filter("foo")).toBe("Foo"); - expect(filter("Foo")).toBe("Foo"); - expect(filter("FOO")).toBe("Foo"); - expect(filter("FoO")).toBe("Foo"); - }); -}); diff --git a/awx/ui/test/spec/shared/filters/format-epoch.filter-test.js b/awx/ui/test/spec/shared/filters/format-epoch.filter-test.js deleted file mode 100644 index 5b6d54d2858f..000000000000 --- a/awx/ui/test/spec/shared/filters/format-epoch.filter-test.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict'; - -describe('Filter: formatEpoch', () => { - var filter; - - beforeEach(angular.mock.module('awApp')); - - beforeEach(function(){ - inject(function($injector){ - filter = $injector.get('$filter')('formatEpoch'); - }); - }); - - // TODO: this test is opinionated - it assumes that the - // system date on the machine that's running it is EST. - // I'm not quite sure how to foce a moment to use a specific - // timezone. If we can figure that out then we can re-enable - // these tests. - - xit('should convert epoch to datetime string', function(){ - expect(filter(11111)).toBe("Dec 31, 1969 10:05 PM"); - expect(filter(610430400)).toBe("May 6, 1989 12:00 AM"); - }); -}); diff --git a/awx/ui/test/spec/shared/filters/is-empty.filter-test.js b/awx/ui/test/spec/shared/filters/is-empty.filter-test.js deleted file mode 100644 index db6c87ea2aad..000000000000 --- a/awx/ui/test/spec/shared/filters/is-empty.filter-test.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -describe('Filter: isEmpty', () => { - var filter; - - beforeEach(angular.mock.module('awApp')); - - beforeEach(function(){ - inject(function($injector){ - filter = $injector.get('$filter')('isEmpty'); - }); - }); - - it('check if an object is empty', function(){ - expect(filter({})).toBe(true); - expect(filter({foo: 'bar'})).toBe(false); - }); -}); diff --git a/awx/ui/test/spec/shared/filters/long-date.filter-test.js b/awx/ui/test/spec/shared/filters/long-date.filter-test.js deleted file mode 100644 index 3bd3857f7a6e..000000000000 --- a/awx/ui/test/spec/shared/filters/long-date.filter-test.js +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; - -describe('Filter: longDate', () => { - var filter; - - beforeEach(angular.mock.module('awApp')); - - beforeEach(function(){ - inject(function($injector){ - filter = $injector.get('$filter')('longDate'); - }); - }); - - // TODO: this test is opinionated - it assumes that the - // system date on the machine that's running it is EST. - // I'm not quite sure how to foce a moment to use a specific - // timezone. If we can figure that out then we can re-enable - // these tests. - - xit('should convert the timestamp to a UI friendly date and time', function(){ - expect(filter("2017-02-13T22:00:14.106Z")).toBe("2/13/2017 5:00:14 PM"); - }); - it('should return an empty string if no timestamp is passed', function(){ - expect(filter()).toBe(""); - }); -}); diff --git a/awx/ui/test/spec/shared/filters/prepend.filter-test.js b/awx/ui/test/spec/shared/filters/prepend.filter-test.js deleted file mode 100644 index 424b023fe8da..000000000000 --- a/awx/ui/test/spec/shared/filters/prepend.filter-test.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -describe('Filter: prepend', () => { - var filter; - - beforeEach(angular.mock.module('awApp')); - - beforeEach(function(){ - inject(function($injector){ - filter = $injector.get('$filter')('prepend'); - }); - }); - - it('should prepend the second param to the first', function(){ - expect(filter("foo", "bar")).toBe("barfoo"); - }); - - it('should return string if no prepend param passed', function(){ - expect(filter("foo")).toBe("foo"); - }); - - it('should return empty string if no params passed', function(){ - expect(filter()).toBe(""); - }); -}); diff --git a/awx/ui/test/spec/shared/filters/xss-sanitizer.filter-test.js b/awx/ui/test/spec/shared/filters/xss-sanitizer.filter-test.js deleted file mode 100644 index 2f4db9df83ae..000000000000 --- a/awx/ui/test/spec/shared/filters/xss-sanitizer.filter-test.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict'; - -describe('Filter: sanitize', () => { - var filter; - - beforeEach(angular.mock.module('awApp')); - - beforeEach(function(){ - inject(function($injector){ - filter = $injector.get('$filter')('sanitize'); - }); - }); - - it('should sanitize xss-vulnerable strings', function(){ - expect(filter("
foobar
")).toBe("<div>foobar</div>"); - }); -}); diff --git a/awx/ui/test/spec/smart-search/queryset.service-test.js b/awx/ui/test/spec/smart-search/queryset.service-test.js deleted file mode 100644 index a2771f93449e..000000000000 --- a/awx/ui/test/spec/smart-search/queryset.service-test.js +++ /dev/null @@ -1,127 +0,0 @@ -'use strict'; - -describe('Service: QuerySet', () => { - let $httpBackend, - QuerySet, - Authorization, - SmartSearchService; - - beforeEach(angular.mock.module('awApp', ($provide) =>{ - // @todo: improve app source / write testing utilities for interim - // we don't want to be concerned with this provision in every test that involves the Rest module - Authorization = { - getToken: () => true, - isUserLoggedIn: angular.noop - }; - $provide.value('LoadBasePaths', angular.noop); - $provide.value('Authorization', Authorization); - })); - beforeEach(angular.mock.module('RestServices')); - - beforeEach(angular.mock.inject((_$httpBackend_, _QuerySet_, _SmartSearchService_) => { - $httpBackend = _$httpBackend_; - QuerySet = _QuerySet_; - SmartSearchService = _SmartSearchService_; - - // @todo: improve app source - // config.js / local_settings emit $http requests in the app's run block - $httpBackend - .whenGET(/\/static\/*/) - .respond(200, {}); - // @todo: improve appsource - // provide api version via package.json config block - $httpBackend - .whenGET(/^\/api\/?$/) - .respond(200, ''); - })); - - describe('fn encodeParam', () => { - it('should encode parameters properly', () =>{ - expect(QuerySet.encodeParam({term: "name:foo", searchTerm: true})).toEqual({"name__icontains_DEFAULT" : "foo"}); - expect(QuerySet.encodeParam({term: "-name:foo", searchTerm: true})).toEqual({"not__name__icontains_DEFAULT" : "foo"}); - expect(QuerySet.encodeParam({term: "name:'foo bar'", searchTerm: true})).toEqual({"name__icontains_DEFAULT" : "'foo%20bar'"}); - expect(QuerySet.encodeParam({term: "-name:'foo bar'", searchTerm: true})).toEqual({"not__name__icontains_DEFAULT" : "'foo%20bar'"}); - expect(QuerySet.encodeParam({term: "organization:foo", relatedSearchTerm: true})).toEqual({"organization__search_DEFAULT" : "foo"}); - expect(QuerySet.encodeParam({term: "-organization:foo", relatedSearchTerm: true})).toEqual({"not__organization__search_DEFAULT" : "foo"}); - expect(QuerySet.encodeParam({term: "organization.name:foo", relatedSearchTerm: true})).toEqual({"organization__name" : "foo"}); - expect(QuerySet.encodeParam({term: "-organization.name:foo", relatedSearchTerm: true})).toEqual({"not__organization__name" : "foo"}); - expect(QuerySet.encodeParam({term: "id:11", searchTerm: true})).toEqual({"id__icontains_DEFAULT" : "11"}); - expect(QuerySet.encodeParam({term: "-id:11", searchTerm: true})).toEqual({"not__id__icontains_DEFAULT" : "11"}); - expect(QuerySet.encodeParam({term: "id:>11", searchTerm: true})).toEqual({"id__gt" : "11"}); - expect(QuerySet.encodeParam({term: "-id:>11", searchTerm: true})).toEqual({"not__id__gt" : "11"}); - expect(QuerySet.encodeParam({term: "id:>=11", searchTerm: true})).toEqual({"id__gte" : "11"}); - expect(QuerySet.encodeParam({term: "-id:>=11", searchTerm: true})).toEqual({"not__id__gte" : "11"}); - expect(QuerySet.encodeParam({term: "id:<11", searchTerm: true})).toEqual({"id__lt" : "11"}); - expect(QuerySet.encodeParam({term: "-id:<11", searchTerm: true})).toEqual({"not__id__lt" : "11"}); - expect(QuerySet.encodeParam({term: "id:<=11", searchTerm: true})).toEqual({"id__lte" : "11"}); - expect(QuerySet.encodeParam({term: "-id:<=11", searchTerm: true})).toEqual({"not__id__lte" : "11"}); - }); - }); - - describe('getSearchInputQueryset', () => { - it('creates the expected queryset', () =>{ - spyOn(QuerySet, 'encodeParam').and.callThrough(); - - const term = 'name:foo'; - const isFilterableBaseField = (termParts) => termParts[0] === 'name'; - const isRelatedField = () => false; - - expect(QuerySet.getSearchInputQueryset(term, isFilterableBaseField, isRelatedField)).toEqual({ name__icontains_DEFAULT: 'foo' }); - expect(QuerySet.encodeParam).toHaveBeenCalledWith({ term: "name:foo", searchTerm: true, singleSearchParam: null }); - expect(QuerySet.getSearchInputQueryset('foo', isFilterableBaseField, null, null, 'host_filter')).toEqual({ host_filter: 'search=foo' }); - expect(QuerySet.getSearchInputQueryset('foo bar', isFilterableBaseField, null, null, 'host_filter')).toEqual({ host_filter: 'search=foo%20and%20search=bar' }); - expect(QuerySet.getSearchInputQueryset('foo or bar', isFilterableBaseField, null, null, 'host_filter')).toEqual({ host_filter: 'search=foo%20or%20search=bar' }); - expect(QuerySet.getSearchInputQueryset('name:foo or bar', isFilterableBaseField, null, null, 'host_filter')).toEqual({ host_filter: 'name__icontains=foo%20or%20search=bar' }); - expect(QuerySet.getSearchInputQueryset('name:foo bar', isFilterableBaseField, null, null, 'host_filter')).toEqual({ host_filter: 'name__icontains=foo%20and%20search=bar' }); - expect(QuerySet.getSearchInputQueryset('foo or name:bar', isFilterableBaseField, null, null, 'host_filter')).toEqual({ host_filter: 'search=foo%20or%20name__icontains=bar' }); - expect(QuerySet.getSearchInputQueryset('foo name:bar', isFilterableBaseField, null, null, 'host_filter')).toEqual({ host_filter: 'search=foo%20and%20name__icontains=bar' }); - expect(QuerySet.getSearchInputQueryset('name:foo or name:bar', isFilterableBaseField, null, null, 'host_filter')).toEqual({ host_filter: 'name__icontains=foo%20or%20name__icontains=bar' }); - expect(QuerySet.getSearchInputQueryset('name:foo name:bar', isFilterableBaseField, null, null, 'host_filter')).toEqual({ host_filter: 'name__icontains=foo%20and%20name__icontains=bar' }); - expect(QuerySet.getSearchInputQueryset('name:foo name:bar or baz', isFilterableBaseField, null, null, 'host_filter')).toEqual({ host_filter: 'name__icontains=foo%20and%20name__icontains=bar%20or%20search=baz' }); - expect(QuerySet.getSearchInputQueryset('baz or name:foo name:bar', isFilterableBaseField, null, null, 'host_filter')).toEqual({ host_filter: 'search=baz%20or%20name__icontains=foo%20and%20name__icontains=bar' }); - }); - }); - - describe('removeTermsFromQueryset', () => { - it('creates the expected queryset', () =>{ - spyOn(QuerySet, 'encodeParam').and.callThrough(); - - const queryset = { page_size: "20", order_by: "name", project__search_DEFAULT: "foo" }; - const term = 'project:foo'; - const isFilterableBaseField = () => false; - const isRelatedField = () => true; - - expect(QuerySet.removeTermsFromQueryset(queryset, term, isFilterableBaseField, isRelatedField)).toEqual({ page_size: "20", order_by: "name" }); - expect(QuerySet.encodeParam).toHaveBeenCalledWith({ term: 'project:foo', relatedSearchTerm: true, singleSearchParam: null }); - }); - }); - - describe('fn search', () => { - let pattern = /\/api\/v2\/inventories\/(.+)\/groups\/*/, - endpoint = '/api/v2/inventories/1/groups/', - params = { - or__name: 'borg', - or__description__icontains: 'assimilate' - }; - - it('should GET expected URL', () =>{ - $httpBackend - .expectGET(pattern) - .respond(200, {}); - QuerySet.search(endpoint, params).then((data) =>{ - expect(data.config.url).toEqual('/api/v2/inventories/1/groups/?or__name=borg&or__description__icontains=assimilate'); - }); - $httpBackend.flush(); - }); - - xit('should memoize new DjangoModel', ()=>{}); - xit('should not replace memoized DjangoModel', ()=>{}); - xit('should provide an alias interface', ()=>{}); - - afterEach(() => { - $httpBackend.verifyNoOutstandingExpectation(); - $httpBackend.verifyNoOutstandingRequest(); - }); - }); - -}); diff --git a/awx/ui/test/spec/smart-search/smart-search.directive-test.js b/awx/ui/test/spec/smart-search/smart-search.directive-test.js deleted file mode 100644 index 6e8f1ab03786..000000000000 --- a/awx/ui/test/spec/smart-search/smart-search.directive-test.js +++ /dev/null @@ -1,132 +0,0 @@ -'use strict'; - -describe('Directive: Smart Search', () => { - let $scope, - $q, - template, - element, - dom, - $compile, - $state = {}, - GetBasePath, - QuerySet, - ConfigService = {}, - i18n, - $transitions, - translateFilter; - - beforeEach(angular.mock.module('shared')); - beforeEach(angular.mock.module('SmartSearchModule', ($provide) => { - QuerySet = jasmine.createSpyObj('QuerySet', [ - 'decodeParam', - 'search', - 'stripDefaultParams', - 'createSearchTagsFromQueryset', - 'initFieldset' - ]); - QuerySet.decodeParam.and.callFake((key, value) => { - return `${key.split('__').join(':')}:${value}`; - }); - QuerySet.stripDefaultParams.and.returnValue([]); - QuerySet.createSearchTagsFromQueryset.and.returnValue([]); - - $transitions = jasmine.createSpyObj('$transitions', [ - 'onSuccess' - ]); - $transitions.onSuccess.and.returnValue({}); - - ConfigService = jasmine.createSpyObj('ConfigService', [ - 'getConfig' - ]); - - GetBasePath = jasmine.createSpy('GetBasePath'); - translateFilter = jasmine.createSpy('translateFilter'); - i18n = jasmine.createSpy('i18n'); - $state = jasmine.createSpyObj('$state', ['go']); - $state.go.and.callFake(() => { return { then: function(){} }; }); - - $provide.value('ConfigService', ConfigService); - $provide.value('QuerySet', QuerySet); - $provide.value('GetBasePath', GetBasePath); - $provide.value('$state', $state); - $provide.value('i18n', { '_': (a) => { return a; } }); - $provide.value('translateFilter', translateFilter); - })); - beforeEach(angular.mock.inject(($templateCache, _$rootScope_, _$compile_, _$q_) => { - $q = _$q_; - $compile = _$compile_; - $scope = _$rootScope_.$new(); - - ConfigService.getConfig.and.returnValue($q.when({})); - QuerySet.search.and.returnValue($q.when({})); - - QuerySet.initFieldset.and.callFake(() => { - var deferred = $q.defer(); - deferred.resolve({ - models: { - mock: { - base: {} - } - }, - options: { - data: null - } - }); - return deferred.promise; - }); - - // populate $templateCache with directive.templateUrl at test runtime, - template = window.__html__['client/src/shared/smart-search/smart-search.partial.html']; - $templateCache.put('/static/partials/shared/smart-search/smart-search.partial.html', template); - })); - - describe('clear all', () => { - it('should revert search back to non-null defaults and remove page', () => { - $state.$current = { - path: { - mock: { - params: { - mock_search: { - config: { - value: { - page_size: '20', - order_by: '-finished', - page: '1', - some_null_param: null - } - } - } - } - } - } - }; - $state.params = { - mock_search: { - page_size: '25', - order_by: 'name', - page: '11', - description_icontains: 'ansible', - name_icontains: 'ansible' - } - }; - $scope.list = { - iterator: 'mock' - }; - dom = angular.element(` - `); - element = $compile(dom)($scope); - $scope.$digest(); - const scope = element.isolateScope(); - scope.clearAllTerms(); - expect(QuerySet.search).toHaveBeenCalledWith('mock', {page_size: '20',order_by: '-finished',}); - }); - }); -}); diff --git a/awx/ui/test/spec/smart-search/smart-search.service-test.js b/awx/ui/test/spec/smart-search/smart-search.service-test.js deleted file mode 100644 index cd7aef484862..000000000000 --- a/awx/ui/test/spec/smart-search/smart-search.service-test.js +++ /dev/null @@ -1,63 +0,0 @@ -'use strict'; - -describe('Service: SmartSearch', () => { - let SmartSearchService; - - beforeEach(angular.mock.module('awApp')); - - beforeEach(angular.mock.module('SmartSearchModule')); - - beforeEach(angular.mock.inject((_SmartSearchService_) => { - SmartSearchService = _SmartSearchService_; - })); - - describe('fn splitSearchIntoTerms', () => { - it('should convert the search string to an array tag strings', () =>{ - expect(SmartSearchService.splitSearchIntoTerms('foo')).toEqual(["foo"]); - expect(SmartSearchService.splitSearchIntoTerms('foo bar')).toEqual(["foo", "bar"]); - expect(SmartSearchService.splitSearchIntoTerms('name:foo bar')).toEqual(["name:foo", "bar"]); - expect(SmartSearchService.splitSearchIntoTerms('name:foo description:bar')).toEqual(["name:foo", "description:bar"]); - expect(SmartSearchService.splitSearchIntoTerms('name:"foo bar"')).toEqual(["name:\"foo bar\""]); - expect(SmartSearchService.splitSearchIntoTerms('name:"foo bar" description:"bar foo"')).toEqual(["name:\"foo bar\"", "description:\"bar foo\""]); - expect(SmartSearchService.splitSearchIntoTerms('name:"foo bar" description:"bar foo"')).toEqual(["name:\"foo bar\"", "description:\"bar foo\""]); - expect(SmartSearchService.splitSearchIntoTerms('name:\'foo bar\'')).toEqual(["name:\'foo bar\'"]); - expect(SmartSearchService.splitSearchIntoTerms('name:\'foo bar\' description:\'bar foo\'')).toEqual(["name:\'foo bar\'", "description:\'bar foo\'"]); - expect(SmartSearchService.splitSearchIntoTerms('name:\'foo bar\' description:\'bar foo\'')).toEqual(["name:\'foo bar\'", "description:\'bar foo\'"]); - expect(SmartSearchService.splitSearchIntoTerms('name:\"foo bar\" description:\'bar foo\'')).toEqual(["name:\"foo bar\"", "description:\'bar foo\'"]); - expect(SmartSearchService.splitSearchIntoTerms('name:\"foo bar\" foo')).toEqual(["name:\"foo bar\"", "foo"]); - expect(SmartSearchService.splitSearchIntoTerms('inventory:¯\_(ツ)_/¯')).toEqual(["inventory:¯\_(ツ)_/¯"]); - expect(SmartSearchService.splitSearchIntoTerms('inventory:¯\_(ツ)_/¯ inventory.name:¯\_(ツ)_/¯')).toEqual(["inventory:¯\_(ツ)_/¯", "inventory.name:¯\_(ツ)_/¯"]); - }); - }); - - describe('fn splitTermIntoParts', () => { - it('should convert the search term to a key and value', () =>{ - expect(SmartSearchService.splitTermIntoParts('foo')).toEqual(["foo"]); - expect(SmartSearchService.splitTermIntoParts('foo:bar')).toEqual(["foo", "bar"]); - expect(SmartSearchService.splitTermIntoParts('foo:bar:foobar')).toEqual(["foo", "bar:foobar"]); - expect(SmartSearchService.splitTermIntoParts('name:\"foo bar\"')).toEqual(["name", "\"foo bar\""]); - expect(SmartSearchService.splitTermIntoParts('name:\"foo:bar\"')).toEqual(["name", "\"foo:bar\""]); - expect(SmartSearchService.splitTermIntoParts('name:\'foo bar\'')).toEqual(["name", "\'foo bar\'"]); - expect(SmartSearchService.splitTermIntoParts('name:\'foo:bar\'')).toEqual(["name", "\'foo:bar\'"]); - }); - }); - - describe('fn splitFilterIntoTerms', () => { - it('should convert the filter term to a key and value with encode quotes and spaces', () => { - expect(SmartSearchService.splitFilterIntoTerms()).toEqual(null); - expect(SmartSearchService.splitFilterIntoTerms('foo')).toEqual(["foo"]); - expect(SmartSearchService.splitFilterIntoTerms('foo bar')).toEqual(["foo", "bar"]); - expect(SmartSearchService.splitFilterIntoTerms('name:foo bar')).toEqual(["name:foo", "bar"]); - expect(SmartSearchService.splitFilterIntoTerms('name:foo description:bar')).toEqual(["name:foo", "description:bar"]); - expect(SmartSearchService.splitFilterIntoTerms('name:"foo bar"')).toEqual(["name:%22foo%20bar%22"]); - expect(SmartSearchService.splitFilterIntoTerms('name:"foo bar" description:"bar foo"')).toEqual(["name:%22foo%20bar%22", "description:%22bar%20foo%22"]); - expect(SmartSearchService.splitFilterIntoTerms('name:"foo bar" a b c')).toEqual(["name:%22foo%20bar%22", 'a', 'b', 'c']); - expect(SmartSearchService.splitFilterIntoTerms('name:"1"')).toEqual(["name:%221%22"]); - expect(SmartSearchService.splitFilterIntoTerms('name:1')).toEqual(["name:1"]); - expect(SmartSearchService.splitFilterIntoTerms('name:"foo ba\'r" a b c')).toEqual(["name:%22foo%20ba%27r%22", 'a', 'b', 'c']); - expect(SmartSearchService.splitFilterIntoTerms('name:"foobar" other:"barbaz"')).toEqual(["name:%22foobar%22", "other:%22barbaz%22"]); - expect(SmartSearchService.splitFilterIntoTerms('name:"foobar" other:"bar baz"')).toEqual(["name:%22foobar%22", "other:%22bar%20baz%22"]); - }); - }); - -}); diff --git a/awx/ui/test/spec/socket/socket.service-test.js b/awx/ui/test/spec/socket/socket.service-test.js deleted file mode 100644 index 8585fd2ddebc..000000000000 --- a/awx/ui/test/spec/socket/socket.service-test.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; - -xdescribe('Service: SocketService', () => { - - let SocketService, - rootScope, - event; - - beforeEach(angular.mock.module('shared')); - beforeEach(angular.mock.module('socket', function($provide){ - $provide.value('$rootScope', rootScope); - $provide.value('$location', {url: function(){}}); - })); - beforeEach(angular.mock.inject(($rootScope, _SocketService_) => { - var rootScope = $rootScope.$new(); - rootScope.$emit = jasmine.createSpy('$emit'); - SocketService = _SocketService_; - })); - - describe('socket onmessage() should broadcast to correct event listener', function(){ - - xit('should send to ws-jobs-summary', function(){ - event = {data : {group_name: "jobs"}}; - event.data = JSON.stringify(event.data); - SocketService.onMessage(event); - expect(rootScope.$emit).toHaveBeenCalledWith('ws-jobs-summary', event.data); - }); - }); - -}); diff --git a/awx/ui/test/spec/webpack.spec.js b/awx/ui/test/spec/webpack.spec.js deleted file mode 100644 index 099d6a996483..000000000000 --- a/awx/ui/test/spec/webpack.spec.js +++ /dev/null @@ -1,16 +0,0 @@ -const webpack = require('webpack'); -const merge = require('webpack-merge'); -const base = require('../../build/webpack.base'); - -const STATIC_URL = '/static/'; - -const test = { - devtool: 'inline-source-map', - plugins: [ - new webpack.DefinePlugin({ - $basePath: STATIC_URL - }) - ] -}; - -module.exports = merge(base, test); diff --git a/awx/ui/test/spec/workflow--results/data/workflow_job.js b/awx/ui/test/spec/workflow--results/data/workflow_job.js deleted file mode 100644 index 6893b6321601..000000000000 --- a/awx/ui/test/spec/workflow--results/data/workflow_job.js +++ /dev/null @@ -1,63 +0,0 @@ -export default { - "id": 109, - "type": "workflow_job", - "url": "/api/v2/workflow_jobs/109/", - "related": { - "created_by": "/api/v2/users/1/", - "unified_job_template": "/api/v2/workflow_job_templates/27/", - "workflow_job_template": "/api/v2/workflow_job_templates/27/", - "notifications": "/api/v2/workflow_jobs/109/notifications/", - "workflow_nodes": "/api/v2/workflow_jobs/109/workflow_nodes/", - "labels": "/api/v2/workflow_jobs/109/labels/", - "activity_stream": "/api/v2/workflow_jobs/109/activity_stream/", - "relaunch": "/api/v2/workflow_jobs/109/relaunch/", - "cancel": "/api/v2/workflow_jobs/109/cancel/" - }, - "summary_fields": { - "workflow_job_template": { - "id": 27, - "name": "workflow timer", - "description": "" - }, - "unified_job_template": { - "id": 27, - "name": "workflow timer", - "description": "", - "unified_job_type": "workflow_job" - }, - "created_by": { - "id": 1, - "username": "admin", - "first_name": "", - "last_name": "" - }, - "user_capabilities": { - "start": true, - "delete": true - }, - "labels": { - "count": 0, - "results": [] - } - }, - "created": "2017-02-01T14:56:47.416Z", - "modified": "2017-02-01T14:57:14.189Z", - "name": "workflow timer", - "description": "", - "unified_job_template": 27, - "launch_type": "manual", - "status": "successful", - "failed": false, - "started": "2017-02-01T14:56:47.754897Z", - "finished": "2017-02-01T14:57:14.182780Z", - "elapsed": 26.428, - "job_args": "", - "job_cwd": "", - "job_env": {}, - "job_explanation": "", - "result_stdout": "stdout capture is missing", - "execution_node": "", - "result_traceback": "", - "workflow_job_template": 27, - "extra_vars": "{}" -}; \ No newline at end of file diff --git a/awx/ui/test/spec/workflow--results/data/workflow_job_options.js b/awx/ui/test/spec/workflow--results/data/workflow_job_options.js deleted file mode 100644 index 657d6f41fe83..000000000000 --- a/awx/ui/test/spec/workflow--results/data/workflow_job_options.js +++ /dev/null @@ -1,203 +0,0 @@ -export default { - "name": "Workflow Job Detail", - "description": "# Retrieve Workflow Job:\n\nMake GET request to this resource to retrieve a single workflow job\nrecord containing the following fields:\n\n* `id`: Database ID for this workflow job. (integer)\n* `type`: Data type for this workflow job. (choice)\n* `url`: URL for this workflow job. (string)\n* `related`: Data structure with URLs of related resources. (object)\n* `summary_fields`: Data structure with name/description for related resources. (object)\n* `created`: Timestamp when this workflow job was created. (datetime)\n* `modified`: Timestamp when this workflow job was last modified. (datetime)\n* `name`: Name of this workflow job. (string)\n* `description`: Optional description of this workflow job. (string)\n* `unified_job_template`: (field)\n* `launch_type`: (choice)\n - `manual`: Manual\n - `relaunch`: Relaunch\n - `callback`: Callback\n - `scheduled`: Scheduled\n - `dependency`: Dependency\n - `workflow`: Workflow\n - `sync`: Sync\n* `status`: (choice)\n - `new`: New\n - `pending`: Pending\n - `waiting`: Waiting\n - `running`: Running\n - `successful`: Successful\n - `failed`: Failed\n - `error`: Error\n - `canceled`: Canceled\n* `failed`: (boolean)\n* `started`: The date and time the job was queued for starting. (datetime)\n* `finished`: The date and time the job finished execution. (datetime)\n* `elapsed`: Elapsed time in seconds that the job ran. (decimal)\n* `job_args`: (string)\n* `job_cwd`: (string)\n* `job_env`: (field)\n* `job_explanation`: A status field to indicate the state of the job if it wasn't able to run and capture stdout (string)\n* `result_stdout`: (field)\n* `execution_node`: The Tower node the job executed on. (string)\n* `result_traceback`: (string)\n* `workflow_job_template`: (field)\n* `extra_vars`: (string)\n\n\n\n# Delete Workflow Job:\n\nMake a DELETE request to this resource to delete this workflow job.\n\n\n\n\n\n\n\n\n\n\n\n> _New in Ansible Tower 3.1.0_", - "renders": [ - "application/json", - "text/html" - ], - "parses": [ - "application/json" - ], - "actions": { - "GET": { - "id": { - "type": "integer", - "label": "ID", - "help_text": "Database ID for this workflow job." - }, - "type": { - "type": "choice", - "label": "Type", - "help_text": "Data type for this workflow job.", - "choices": [ - [ - "workflow_job", - "Workflow Job" - ] - ] - }, - "url": { - "type": "string", - "label": "Url", - "help_text": "URL for this workflow job." - }, - "related": { - "type": "object", - "label": "Related", - "help_text": "Data structure with URLs of related resources." - }, - "summary_fields": { - "type": "object", - "label": "Summary fields", - "help_text": "Data structure with name/description for related resources." - }, - "created": { - "type": "datetime", - "label": "Created", - "help_text": "Timestamp when this workflow job was created." - }, - "modified": { - "type": "datetime", - "label": "Modified", - "help_text": "Timestamp when this workflow job was last modified." - }, - "name": { - "type": "string", - "label": "Name", - "help_text": "Name of this workflow job." - }, - "description": { - "type": "string", - "label": "Description", - "help_text": "Optional description of this workflow job." - }, - "unified_job_template": { - "type": "field", - "label": "unified job template" - }, - "launch_type": { - "type": "choice", - "label": "Launch type", - "choices": [ - [ - "manual", - "Manual" - ], - [ - "relaunch", - "Relaunch" - ], - [ - "callback", - "Callback" - ], - [ - "scheduled", - "Scheduled" - ], - [ - "dependency", - "Dependency" - ], - [ - "workflow", - "Workflow" - ], - [ - "sync", - "Sync" - ] - ] - }, - "status": { - "type": "choice", - "label": "Status", - "choices": [ - [ - "new", - "New" - ], - [ - "pending", - "Pending" - ], - [ - "waiting", - "Waiting" - ], - [ - "running", - "Running" - ], - [ - "successful", - "Successful" - ], - [ - "failed", - "Failed" - ], - [ - "error", - "Error" - ], - [ - "canceled", - "Canceled" - ] - ] - }, - "failed": { - "type": "boolean", - "label": "Failed" - }, - "started": { - "type": "datetime", - "label": "Started", - "help_text": "The date and time the job was queued for starting." - }, - "finished": { - "type": "datetime", - "label": "Finished", - "help_text": "The date and time the job finished execution." - }, - "elapsed": { - "type": "decimal", - "label": "Elapsed", - "help_text": "Elapsed time in seconds that the job ran." - }, - "job_args": { - "type": "string", - "label": "Job args" - }, - "job_cwd": { - "type": "string", - "label": "Job cwd" - }, - "job_env": { - "type": "field", - "label": "job_env" - }, - "job_explanation": { - "type": "string", - "label": "Job explanation", - "help_text": "A status field to indicate the state of the job if it wasn't able to run and capture stdout" - }, - "result_stdout": { - "type": "field", - "label": "Result stdout" - }, - "execution_node": { - "type": "string", - "label": "Execution node", - "help_text": "The Tower node the job executed on." - }, - "result_traceback": { - "type": "string", - "label": "Result traceback" - }, - "workflow_job_template": { - "type": "field", - "label": "Workflow job template" - }, - "extra_vars": { - "type": "string", - "label": "Extra vars" - } - } - }, - "added_in_version": "3.1.0", - "types": [ - "workflow_job" - ] -} \ No newline at end of file diff --git a/awx/ui/test/spec/workflow--results/workflow-results.controller-test.js b/awx/ui/test/spec/workflow--results/workflow-results.controller-test.js deleted file mode 100644 index 15c68045ceb6..000000000000 --- a/awx/ui/test/spec/workflow--results/workflow-results.controller-test.js +++ /dev/null @@ -1,132 +0,0 @@ -'use strict'; -import moment from 'moment'; -import workflow_job_options_json from './data/workflow_job_options.js'; -import workflow_job_json from './data/workflow_job.js'; - -describe('Controller: workflowResults', () => { - let $controller; - let workflowResults; - let $rootScope; - let workflowResultsService; - let $interval; - - let treeData = { - data: { - children: [] - } - }; - - beforeEach(angular.mock.module('workflowResults', ($provide) => { - ['PromptDialog', 'Prompt', 'Wait', 'Rest', '$state', 'ProcessErrors', - 'jobLabels', 'workflowNodes', 'count', 'WorkflowJobModel', 'ComponentsStrings' - ].forEach((item) => { - $provide.value(item, {}); - }); - $provide.value('$stateExtender', { addState: jasmine.createSpy('addState'), }); - $provide.value('moment', moment); - $provide.value('workflowData', workflow_job_json); - $provide.value('workflowDataOptions', workflow_job_options_json); - $provide.value('ParseTypeChange', function() {}); - $provide.value('ParseVariableString', function() {}); - $provide.value('i18n', { '_': (a) => { return a; } }); - $provide.provider('$stateProvider', { '$get': function() { return function() {}; } }); - $provide.service('WorkflowChartService', function($q) { - return { - generateArraysOfNodesAndLinks: function() { - var deferred = $q.defer(); - deferred.resolve(); - return deferred.promise; - } - }; - }); - })); - - beforeEach(angular.mock.inject(function(_$controller_, _$rootScope_, _workflowResultsService_, _$interval_){ - $controller = _$controller_; - $rootScope = _$rootScope_; - workflowResultsService = _workflowResultsService_; - $interval = _$interval_; - })); - - describe('elapsed timer', () => { - let scope; - - beforeEach(() => { - scope = $rootScope.$new(); - spyOn(workflowResultsService, 'createOneSecondTimer').and.callThrough(); - spyOn(workflowResultsService, 'destroyTimer').and.callThrough(); - }); - - - function jobWaitingWorkflowResultsControllerFixture(started, status) { - workflow_job_json.started = started; - workflow_job_json.status = status; - workflowResults = $controller('workflowResultsController', { - $scope: scope, - $rootScope: $rootScope, - }); - } - - describe('init()', () => { - describe('job running', () => { - beforeEach(() => { - jobWaitingWorkflowResultsControllerFixture(moment(), 'running'); - }); - - // Note: Ensuring the outside service method is called to create a timer may - // be overkill. Especially since we validate the side effect in the next test. - it('should call to start timer on load when job is already running', () => { - expect(workflowResultsService.createOneSecondTimer).toHaveBeenCalled(); - expect(workflowResultsService.createOneSecondTimer.calls.argsFor(0)[0]).toBe(workflow_job_json.started); - }); - - it('should set update scope var with elapsed time', () => { - $interval.flush(10 * 1000); - - // TODO: mock moment() so when we fast-forward time with $interval - // the system clocks seems to fast forward too. - //expect(scope.workflow.elapsed).toBe(10); - }); - - it('should call to destroy timer on destroy', () => { - scope.$destroy(); - expect(workflowResultsService.destroyTimer).toHaveBeenCalled(); - expect(workflowResultsService.destroyTimer.calls.argsFor(0)[0]).not.toBe(null); - }); - }); - - describe('job waiting', () => { - beforeEach(() => { - jobWaitingWorkflowResultsControllerFixture(null, 'waiting'); - }); - - it('should not start elapsed timer', () => { - expect(workflowResultsService.createOneSecondTimer).not.toHaveBeenCalled(); - }); - - }); - - describe('job finished', () => { - beforeEach(() => { - jobWaitingWorkflowResultsControllerFixture(moment(), 'successful'); - }); - - it('should start elapsed timer', () => { - expect(workflowResultsService.createOneSecondTimer).not.toHaveBeenCalled(); - }); - }); - }); - - describe('job transitions to running', () => { - beforeEach(() => { - jobWaitingWorkflowResultsControllerFixture(null, 'waiting'); - $rootScope.$broadcast('ws-jobs', { unified_job_id: workflow_job_json.id, status: "running" }); - }); - - it('should start elapsed timer', () => { - expect(scope.workflow.status).toBe("running"); - expect(workflowResultsService.createOneSecondTimer).toHaveBeenCalled(); - }); - }); - }); -}); diff --git a/awx/ui/test/spec/workflow--results/workflow-results.service-test.js b/awx/ui/test/spec/workflow--results/workflow-results.service-test.js deleted file mode 100644 index 8a1bd3b94cd7..000000000000 --- a/awx/ui/test/spec/workflow--results/workflow-results.service-test.js +++ /dev/null @@ -1,60 +0,0 @@ -'use strict'; -import moment from 'moment'; - -describe('workflowResultsService', () => { - let workflowResultsService; - let $interval; - - beforeEach(angular.mock.module('workflowResults', ($provide) => { - ['i18n', 'PromptDialog', 'Prompt', 'Wait', 'Rest', 'ProcessErrors', '$state', 'WorkflowJobModel', 'ComponentsStrings'] - .forEach(function(item) { - $provide.value(item, {}); - }); - $provide.value('$stateExtender', { addState: jasmine.createSpy('addState'), }); - $provide.value('moment', moment); - })); - - beforeEach(angular.mock.inject((_workflowResultsService_, _$interval_) => { - workflowResultsService = _workflowResultsService_; - $interval = _$interval_; - })); - - describe('createOneSecondTimer()', () => { - it('should create a timer that runs every second, incremented by a second', (done) => { - let ticks = 0; - let ticks_expected = 10; - - workflowResultsService.createOneSecondTimer(moment(), function() { - ticks += 1; - if (ticks >= ticks_expected) { - expect(ticks).toBe(ticks_expected); - // TODO: should verify time is 10 but this requires mocking moment() - // because we "artificially" accelerate time. - done(); - } - }); - - $interval.flush(ticks_expected * 1000); - }); - }); - - describe('destroyTimer()', () => { - beforeEach(() => { - $interval.cancel = jasmine.createSpy('cancel'); - }); - - it('should not destroy null timer', () => { - workflowResultsService.destroyTimer(null); - - expect($interval.cancel).not.toHaveBeenCalled(); - }); - - it('should destroy passed in timer', () => { - let timer = jasmine.createSpy('timer'); - - workflowResultsService.destroyTimer(timer); - - expect($interval.cancel).toHaveBeenCalledWith(timer); - }); - }); -}); diff --git a/awx/ui/test/spec/workflows/workflow-add.controller-test.js b/awx/ui/test/spec/workflows/workflow-add.controller-test.js deleted file mode 100644 index 4edf61f6941e..000000000000 --- a/awx/ui/test/spec/workflows/workflow-add.controller-test.js +++ /dev/null @@ -1,176 +0,0 @@ -'use strict'; - -describe('Controller: WorkflowAdd', () => { - // Setup - let scope, - state, - WorkflowAdd, - Alert, - GenerateForm, - TemplatesService, - q, - createWorkflowJobTemplateDeferred, - httpBackend, - ProcessErrors, - CreateSelect2, - Wait, - ParseTypeChange, - ToJSON, - availableLabels, - resolvedModels; - - beforeEach(angular.mock.module('awApp')); - beforeEach(angular.mock.module('RestServices')); - beforeEach(angular.mock.module('templates', ($provide) => { - - state = jasmine.createSpyObj('state', [ - '$get', - 'transitionTo', - 'go' - ]); - - GenerateForm = jasmine.createSpyObj('GenerateForm', [ - 'inject', - 'reset', - 'clearApiErrors', - 'applyDefaults' - ]); - - TemplatesService = { - getLabelOptions: function(){ - return angular.noop; - }, - createWorkflowJobTemplate: function(){ - return angular.noop; - } - }; - - availableLabels = [{ - name: "foo", - id: "1" - }]; - - resolvedModels = [ - {}, - { - options: () => { - return true; - } - } - ]; - - Alert = jasmine.createSpy('Alert'); - ProcessErrors = jasmine.createSpy('ProcessErrors'); - CreateSelect2 = jasmine.createSpy('CreateSelect2'); - Wait = jasmine.createSpy('Wait'); - ParseTypeChange = jasmine.createSpy('ParseTypeChange'); - ToJSON = jasmine.createSpy('ToJSON'); - - $provide.value('Alert', Alert); - $provide.value('GenerateForm', GenerateForm); - $provide.value('state', state); - $provide.value('ProcessErrors', ProcessErrors); - $provide.value('CreateSelect2', CreateSelect2); - $provide.value('Wait', Wait); - $provide.value('ParseTypeChange', ParseTypeChange); - $provide.value('ToJSON', ToJSON); - $provide.value('availableLabels', availableLabels); - $provide.value('resolvedModels', resolvedModels); - })); - - beforeEach(angular.mock.inject( ($rootScope, $controller, $q, $httpBackend, _state_, _ConfigService_, _GetChoices_, _Alert_, _GenerateForm_, _ProcessErrors_, _CreateSelect2_, _Wait_, _ParseTypeChange_, _ToJSON_, _availableLabels_) => { - scope = $rootScope.$new(); - state = _state_; - q = $q; - Alert = _Alert_; - GenerateForm = _GenerateForm_; - httpBackend = $httpBackend; - ProcessErrors = _ProcessErrors_; - CreateSelect2 = _CreateSelect2_; - Wait = _Wait_; - createWorkflowJobTemplateDeferred = q.defer(); - ParseTypeChange = _ParseTypeChange_; - ToJSON = _ToJSON_; - availableLabels = _availableLabels_; - - $httpBackend - .whenGET(/^\/api\/?$/) - .respond(200, ''); - - $httpBackend - .when('OPTIONS', '/') - .respond(200, ''); - - $httpBackend - .whenGET(/\/static\/*/) - .respond(200, {}); - - TemplatesService.createWorkflowJobTemplate = jasmine.createSpy('createWorkflowJobTemplate').and.returnValue(createWorkflowJobTemplateDeferred.promise); - - WorkflowAdd = $controller('WorkflowAdd', { - $scope: scope, - $state: state, - Alert: Alert, - GenerateForm: GenerateForm, - TemplatesService: TemplatesService, - ProcessErrors: ProcessErrors, - CreateSelect2: CreateSelect2, - Wait: Wait, - ParseTypeChange: ParseTypeChange, - availableLabels: availableLabels, - ToJSON - }); - })); - - it('should get/set the label options and select2-ify the input', ()=>{ - // We expect the digest cycle to fire off this call to /static/config.js so we go ahead and handle it - httpBackend.expectGET('/static/config.js').respond(200); - scope.$digest(); - expect(scope.labelOptions).toEqual([{ - label: "foo", - value: "1" - }]); - expect(CreateSelect2).toHaveBeenCalledWith({ - element:'#workflow_job_template_labels', - multiple: true, - addNew: true - }); - }); - - describe('scope.formSave()', () => { - - it('should call TemplatesService.createWorkflowJobTemplate', ()=>{ - scope.name = "Test Workflow"; - scope.description = "This is a test description"; - scope.formSave(); - expect(TemplatesService.createWorkflowJobTemplate).toHaveBeenCalledWith({ - name: "Test Workflow", - description: "This is a test description", - organization: undefined, - inventory: undefined, - limit: undefined, - scm_branch: undefined, - labels: undefined, - variables: undefined, - allow_simultaneous: undefined, - webhook_service: '', - webhook_credential: null, - ask_inventory_on_launch: false, - ask_variables_on_launch: false, - ask_limit_on_launch: false, - ask_scm_branch_on_launch: false, - extra_vars: undefined - }); - }); - }); - - describe('scope.formCancel()', () => { - - it('should transition to templates', ()=>{ - scope.formCancel(); - expect(state.transitionTo).toHaveBeenCalledWith('templates'); - }); - - }); - -}); diff --git a/awx/ui/test/unit/.eslintrc.js b/awx/ui/test/unit/.eslintrc.js deleted file mode 100644 index c8aebfd81341..000000000000 --- a/awx/ui/test/unit/.eslintrc.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - env: { - jasmine: true - } -}; - diff --git a/awx/ui/test/unit/components/file.unit.js b/awx/ui/test/unit/components/file.unit.js deleted file mode 100644 index 1bf7041b56fd..000000000000 --- a/awx/ui/test/unit/components/file.unit.js +++ /dev/null @@ -1,61 +0,0 @@ -describe('Components | Input | File', () => { - let $scope; - let element; - let state; - let controller; - - const getMockFileEvent = file => ({ target: { files: [file] } }); - - beforeEach(() => { - angular.mock.module('at.lib.services'); - angular.mock.module('at.lib.components'); - }); - - describe('AtInputFileController', () => { - beforeEach(angular.mock.inject(($rootScope, $compile) => { - const component = ''; - const dom = angular.element(`${component}`); - - $scope = $rootScope.$new(); - $scope.vm = { form: { disabled: false, unit: {} } }; - - $compile(dom)($scope); - $scope.$digest(); - - element = dom.find('#unit'); - state = $scope.vm.form.unit; - controller = element.controller('atInputFile'); - })); - - it('should initialize without a value by default', () => { - expect(state._value).not.toBeDefined(); - expect(state._displayValue).not.toBeDefined(); - }); - - it('should update display value with file name when file is read', () => { - const name = 'notavirus.exe'; - const reader = { result: 'AAAAAAA' }; - - controller.check = jasmine.createSpy('check'); - - controller.readFile(reader, getMockFileEvent({ name })); - - $scope.$digest(); - - expect(state._value).toBeDefined(); - expect(state._displayValue).toEqual(name); - - expect(controller.check).toHaveBeenCalled(); - }); - - it('should notify handler on file input change event', () => { - controller.handleFileChangeEvent = jasmine.createSpy('handleFileChangeEvent'); - - element.find('input')[0].dispatchEvent(new Event('change')); - - $scope.$digest(); - - expect(controller.handleFileChangeEvent).toHaveBeenCalled(); - }); - }); -}); diff --git a/awx/ui/test/unit/components/index.js b/awx/ui/test/unit/components/index.js deleted file mode 100644 index 7ae48d5dbcc2..000000000000 --- a/awx/ui/test/unit/components/index.js +++ /dev/null @@ -1,11 +0,0 @@ -// Import angular and angular-mocks to the global scope -import 'angular-mocks'; - -// Import tests -import './file.unit'; -import './layout.unit'; -import './side-nav.unit'; -import './side-nav-item.unit'; -import './jobs-list.unit'; -import './job-details-split-jobs.unit'; -import './stream.unit'; diff --git a/awx/ui/test/unit/components/job-details-split-jobs.unit.js b/awx/ui/test/unit/components/job-details-split-jobs.unit.js deleted file mode 100644 index 39099969aac4..000000000000 --- a/awx/ui/test/unit/components/job-details-split-jobs.unit.js +++ /dev/null @@ -1,185 +0,0 @@ -import moment from 'moment'; - -describe('View: Job Details', () => { - let JobDetails; - let scope; - let state; - let OutputStrings; - let Prompt; - let filter; - let ProcessErrors; - let Wait; - let httpBackend; - let ParseVariableString; - let subscribe; - let OutputStatusService; - - let mockData = { - job_slice_count: 2, - job_slice_number: 2, - labels: { - SLICE_JOB: 'foo' - }, - tooltips: { - SLICE_JOB_DETAILS: 'bar' - } - }; - const resource = { - id: '147', - type: 'playbook', - model: { - get: (obj) => obj.split('.').reduce((i, o) => i && i[o] || null, mockData), - has: jasmine.createSpy('has'), - options: jasmine.createSpy('options'), - }, - events: {}, - ws: {} - }; - - beforeEach(angular.mock.module('at.features.output', ($provide) => { - state = { - params: { - job_search: {} - }, - go: jasmine.createSpy('go'), - includes: jasmine.createSpy('includes') - }; - - OutputStrings = { - get: (obj) => obj.split('.').reduce((i, o) => i && i[o] || null, mockData), - }; - - OutputStatusService = { - subscribe: jasmine.createSpy('subscribe') - }; - - ProcessErrors = jasmine.createSpy('ProcessErrors'); - Wait = jasmine.createSpy('Wait'); - Prompt = jasmine.createSpy('Prompt'); - - $provide.value('state', state); - $provide.value('ProcessErrors', ProcessErrors); - $provide.value('Wait', Wait); - $provide.value('Prompt', Prompt); - $provide.value('OutputStrings', OutputStrings); - $provide.value('ParseVariableString', angular.noop); - $provide.value('OutputStatusService', OutputStatusService); - - $provide.provider('$stateProvider', { $get: jasmine.createSpy('$get'), }); - $provide.value('$stateExtender', { addState: jasmine.createSpy('addState'), }); - $provide.value('$stateRegistry', { register: jasmine.createSpy('regster'), }); - $provide.value('sanitizeFilter', angular.noop); - $provide.value('subscribe', subscribe); - $provide.value('moment', moment); - $provide.value('longDateFilter', angular.noop); - })); - - beforeEach(angular.mock.inject(( - $injector, $componentController, $rootScope, - $httpBackend, _state_, _OutputStrings_, _ParseVariableString_, _Prompt_, - _ProcessErrors_, _Wait_, _OutputStatusService_ - ) => { - scope = $rootScope.$new(); - state = _state_; - OutputStrings = _OutputStrings_; - Prompt = _Prompt_; - filter = $injector.get('$filter'); - ProcessErrors = _ProcessErrors_; - Wait = _Wait_; - ParseVariableString = _ParseVariableString_; - httpBackend = $httpBackend; - OutputStatusService = _OutputStatusService_; - - JobDetails = $componentController('atJobDetails', { - $scope: scope, - $state: state, - OutputStrings, - ProcessErrors, - Wait, - Prompt, - $filter: filter, - ParseVariableString, - httpBackend, - OutputStatusService, - }, { resource }); - JobDetails.$onInit(); - })); - - describe('JobDetails Component', () => { - it('is created successfully', () => { - expect(JobDetails).toBeDefined(); - }); - it('has method "sliceJobDetails"', () => { - expect(JobDetails.sliceJobDetails).toBeDefined(); - }); - describe('splitJobDetails method', () => { - it('returned values are strings', () => { - const result = JobDetails.sliceJobDetails; - const { label, offset, tooltip } = result; - expect(offset).toEqual('2/2'); - expect(label).toEqual('foo'); - expect(tooltip).toEqual('bar'); - }); - it('returns null if label, offset, or tooltip is undefined', () => { - mockData = { - job_slice_count: 2, - job_slice_number: 2, - labels: { - SLICE_JOB: null - }, - tooltips: { - SLICE_JOB_DETAILS: null - } - }; - JobDetails.$onInit(); - const result = JobDetails.sliceJobDetails; - expect(result).toBeNull(); - }); - it('returns null if job_slice_count is undefined or null', () => { - mockData = { - job_slice_count: null, - job_slice_number: 2, - labels: { - SLICE_JOB: 'foo' - }, - tooltips: { - SLICE_JOB_DETAILS: 'bar' - } - }; - JobDetails.$onInit(); - const result = JobDetails.sliceJobDetails; - expect(result).toBeNull(); - }); - it('returns null if job_slice_number is undefined or null', () => { - mockData = { - job_slice_count: 2, - job_slice_number: null, - labels: { - SLICE_JOB: 'foo' - }, - tooltips: { - SLICE_JOB_DETAILS: 'bar' - } - }; - JobDetails.$onInit(); - const result = JobDetails.sliceJobDetails; - expect(result).toBeNull(); - }); - it('returns null if job is a non-sliced job', () => { - mockData = { - job_slice_count: 1, - job_slice_number: null, - labels: { - SLICE_JOB: 'foo' - }, - tooltips: { - SLICE_JOB_DETAILS: 'bar' - } - }; - JobDetails.$onInit(); - const result = JobDetails.sliceJobDetails; - expect(result).toBeNull(); - }); - }); - }); -}); diff --git a/awx/ui/test/unit/components/jobs-list.unit.js b/awx/ui/test/unit/components/jobs-list.unit.js deleted file mode 100644 index 845225201c26..000000000000 --- a/awx/ui/test/unit/components/jobs-list.unit.js +++ /dev/null @@ -1,149 +0,0 @@ -describe('View: Split Jobs List', () => { - let JobList; - let scope; - let state; - let Dataset; - let resolvedModels; - let JobsStrings; - let QuerySet; - let Prompt; - let filter; - let ProcessErrors; - let Wait; - let Rest; - let SearchBasePath; - - beforeEach(angular.mock.module('at.features.jobs', ($provide) => { - Dataset = { - data: { - results: {} - } - }; - state = { - params: { - job_search: {} - }, - go: jasmine.createSpy('go'), - includes: jasmine.createSpy('includes') - }; - resolvedModels = [ - { - options: () => ['foo', 'bar'], - } - ]; - JobsStrings = { - get: (str) => { - if (str === 'list.SLICE_JOB') { - return 'Slice Job'; - } - if (str === 'list.ROW_ITEM_LABEL_WEBHOOK') { - return 'Webhook'; - } - return ''; - } - }; - - ProcessErrors = jasmine.createSpy('ProcessErrors'); - Wait = jasmine.createSpy('Wait'); - Prompt = jasmine.createSpy('Prompt'); - - $provide.value('state', state); - $provide.value('Dataset', Dataset); - $provide.value('resolvedModels', resolvedModels); - $provide.value('ProcessErrors', ProcessErrors); - $provide.value('Wait', Wait); - $provide.value('Prompt', Prompt); - $provide.value('Rest', angular.noop); - $provide.value('SearchBasePath', ''); - $provide.value('JobsStrings', JobsStrings); - $provide.value('QuerySet', angular.noop); - - $provide.provider('$stateProvider', { $get: jasmine.createSpy('$get'), }); - $provide.value('$stateExtender', { addState: jasmine.createSpy('addState'), }); - })); - - beforeEach(angular.mock.inject(( - $controller, $rootScope, _state_, _Dataset_, _resolvedModels_, _JobsStrings_, - _QuerySet_, _Prompt_, _$filter_, _ProcessErrors_, _Wait_, _Rest_, _SearchBasePath_ - ) => { - scope = $rootScope.$new(); - state = _state_; - Dataset = _Dataset_; - resolvedModels = _resolvedModels_; - JobsStrings = _JobsStrings_; - QuerySet = _QuerySet_; - Prompt = _Prompt_; - filter = _$filter_; - ProcessErrors = _ProcessErrors_; - Wait = _Wait_; - Rest = _Rest_; - SearchBasePath = _SearchBasePath_; - - JobList = $controller('jobsListController', { - $scope: scope, - $state: state, - Dataset, - resolvedModels, - JobsStrings, - ProcessErrors, - QuerySet, - Wait, - Prompt, - $filter: filter, - Rest, - SearchBasePath, - }); - })); - - describe('JobList Controller', () => { - it('is created successfully', () => { - expect(JobList).toBeDefined(); - }); - it('has method "getSecondaryTagLabel"', () => { - expect(JobList.getSecondaryTagLabel).toBeDefined(); - }); - it('returns the expected string when slice data is available', () => { - const data = { - job_slice_number: 1, - job_slice_count: 2, - launch_type: 'manual', - }; - const result = JobList.getSecondaryTagLabel(data); - expect(result).toEqual('Slice Job 1/2'); - }); - it('returns null when slice data is null', () => { - const data = { - job_slice_number: null, - job_slice_count: null, - launch_type: 'manual', - }; - const result = JobList.getSecondaryTagLabel(data); - expect(result).toBeNull(); - }); - it('returns null when slice data is undefined', () => { - const data = { - job_slice_number: undefined, - job_slice_count: undefined, - launch_type: 'manual', - }; - const result = JobList.getSecondaryTagLabel(data); - expect(result).toBeNull(); - }); - it('returns null when job is not a sliced or webhook job', () => { - const data = { - job_slice_number: null, - job_slice_count: 1, - launch_type: 'manual', - }; - const result = JobList.getSecondaryTagLabel(data); - expect(result).toBeNull(); - }); - it('returns the expected string for webhook jobs', () => { - const data = { - launch_type: 'webhook', - }; - const result = JobList.getSecondaryTagLabel(data); - expect(result).toEqual('Webhook'); - }); - }); -}); diff --git a/awx/ui/test/unit/components/layout.unit.js b/awx/ui/test/unit/components/layout.unit.js deleted file mode 100644 index e6434ebf820e..000000000000 --- a/awx/ui/test/unit/components/layout.unit.js +++ /dev/null @@ -1,171 +0,0 @@ -describe('Components | Layout', () => { - let $compile; - let $rootScope; - let $httpBackend; - let element; - let scope; - - beforeEach(() => { - angular.mock.module('gettext'); - angular.mock.module('I18N'); - angular.mock.module('ui.router'); - angular.mock.module('at.lib.services'); - angular.mock.module('at.lib.components'); - angular.mock.module('Utilities'); - angular.mock.module('ngCookies'); - }); - - beforeEach(angular.mock.inject((_$compile_, _$rootScope_, _$httpBackend_) => { - $compile = _$compile_; - $rootScope = _$rootScope_; - $httpBackend = _$httpBackend_; - scope = $rootScope.$new(); - - element = angular.element(''); - element = $compile(element)(scope); - scope.$digest(); - })); - - describe('AtLayoutController', () => { - let controller; - - beforeEach(() => { - const mockOrgAdminResponse = { - data: { - count: 3 - } - }; - - const mockNotificationAdminResponse = { - data: { - count: 1 - } - }; - - controller = element.controller('atLayout'); - $httpBackend.when('GET', /admin_of_organizations/) - .respond(mockOrgAdminResponse); - - $httpBackend.when('GET', /organizations\/\?role_level=notification_admin_role/) - .respond(mockNotificationAdminResponse); - }); - - xit('$scope.$on($stateChangeSuccess) should assign toState name to currentState', () => { - const next = { name: 'dashboard' }; - $rootScope.$broadcast('$stateChangeSuccess', next); - expect(controller.currentState).toBe('dashboard'); - }); - - describe('$root.current_user watcher should assign value to ', () => { - beforeEach(() => { - const val = { - username: 'admin', - id: 1 - }; - $rootScope.current_user = val; - scope.$digest(); - }); - - it('isLoggedIn', () => { - expect(controller.isLoggedIn).toBe('admin'); - - $rootScope.current_user = { id: 1 }; - scope.$digest(); - expect(controller.isLoggedIn).not.toBeDefined(); - }); - - it('isSuperUser', () => { - $rootScope.current_user = 'one'; - $rootScope.user_is_superuser = true; - $rootScope.user_is_system_auditor = false; - scope.$digest(); - expect(controller.isSuperUser).toBe(true); - - $rootScope.current_user = 'two'; - $rootScope.user_is_superuser = false; - $rootScope.user_is_system_auditor = true; - scope.$digest(); - expect(controller.isSuperUser).toBe(true); - - $rootScope.current_user = 'three'; - $rootScope.user_is_superuser = true; - $rootScope.user_is_system_auditor = true; - scope.$digest(); - expect(controller.isSuperUser).toBe(true); - - $rootScope.current_user = 'four'; - $rootScope.user_is_superuser = false; - $rootScope.user_is_system_auditor = false; - scope.$digest(); - expect(controller.isSuperUser).toBe(false); - }); - - it('currentUsername', () => { - expect(controller.currentUsername).toBeTruthy(); - expect(controller.currentUsername).toBe('admin'); - }); - - it('currentUserId', () => { - expect(controller.currentUserId).toBeTruthy(); - expect(controller.currentUserId).toBe(1); - }); - }); - - describe('$root.socketStatus watcher should assign newStatus to', () => { - const statuses = ['connecting', 'error', 'ok']; - - it('socketState', () => { - _.forEach(statuses, (status) => { - $rootScope.socketStatus = status; - scope.$digest(); - expect(controller.socketState).toBeTruthy(); - expect(controller.socketState).toBe(status); - }); - }); - - it('socketIconClass', () => { - _.forEach(statuses, (status) => { - $rootScope.socketStatus = status; - scope.$digest(); - expect(controller.socketIconClass).toBe(`icon-socket-${status}`); - }); - }); - }); - - describe('$root.licenseMissing watcher should assign true or false to', () => { - it('licenseIsMissing', () => { - $rootScope.licenseMissing = true; - scope.$digest(); - expect(controller.licenseIsMissing).toBe(true); - - $rootScope.licenseMissing = false; - scope.$digest(); - expect(controller.licenseIsMissing).toBe(false); - }); - }); - - describe('getString()', () => { - it('calls ComponentsStrings get() method', angular.mock.inject((_ComponentsStrings_) => { - spyOn(_ComponentsStrings_, 'get'); - controller.getString('VIEW_DOCS'); - expect(_ComponentsStrings_.get).toHaveBeenCalled(); - })); - - it('ComponentsStrings get() method should return undefined if string is not a property name of the layout class', () => { - expect(controller.getString('SUBMISSION_ERROR_TITLE')).toBe(undefined); - }); - - it('should return layout string', () => { - const layoutStrings = { - CURRENT_USER_LABEL: 'Logged in as', - VIEW_DOCS: 'View Documentation', - LOGOUT: 'Logout', - }; - - _.forEach(layoutStrings, (value, key) => { - expect(controller.getString(key)).toBe(value); - }); - }); - }); - }); -}); diff --git a/awx/ui/test/unit/components/side-nav-item.unit.js b/awx/ui/test/unit/components/side-nav-item.unit.js deleted file mode 100644 index e0deffa245de..000000000000 --- a/awx/ui/test/unit/components/side-nav-item.unit.js +++ /dev/null @@ -1,55 +0,0 @@ -describe('Components | Side Nav Item', () => { - let $compile; - let $rootScope; - let element; - let scope; - - beforeEach(() => { - angular.mock.module('gettext'); - angular.mock.module('I18N'); - angular.mock.module('ui.router'); - angular.mock.module('at.lib.services'); - angular.mock.module('at.lib.components'); - angular.mock.module('Utilities'); - angular.mock.module('ngCookies'); - }); - - beforeEach(angular.mock.inject((_$compile_, _$rootScope_) => { - $compile = _$compile_; - $rootScope = _$rootScope_; - scope = $rootScope.$new(); - - element = angular.element(''); - element = $compile(element)(scope); - scope.name = 'dashboard'; - scope.$digest(); - })); - - describe('Side Nav Item Controller', () => { - let SideNavItem; - let SideNavItemCtrl; - - beforeEach(() => { - SideNavItem = angular.element(element[0].querySelector('at-side-nav-item')); - SideNavItemCtrl = SideNavItem.controller('atSideNavItem'); - }); - - xit('layoutVm.currentState watcher should assign isRoute', () => { - let current = { name: 'dashboard' }; - $rootScope.$broadcast('$stateChangeSuccess', current); - scope.$digest(); - expect(SideNavItemCtrl.isRoute).toBe(true); - - current = { name: 'inventories' }; - $rootScope.$broadcast('$stateChangeSuccess', current); - scope.$digest(); - expect(SideNavItemCtrl.isRoute).toBe(false); - }); - - it('should load name, icon, and route from scope', () => { - expect(SideNavItem.isolateScope().name).toBeDefined(); - expect(SideNavItem.isolateScope().iconClass).toBeDefined(); - expect(SideNavItem.isolateScope().route).toBeDefined(); - }); - }); -}); diff --git a/awx/ui/test/unit/components/side-nav.unit.js b/awx/ui/test/unit/components/side-nav.unit.js deleted file mode 100644 index e460528a2e43..000000000000 --- a/awx/ui/test/unit/components/side-nav.unit.js +++ /dev/null @@ -1,80 +0,0 @@ -describe('Components | Side Nav', () => { - let $compile; - let $rootScope; - let element; - let scope; - const windowMock = { - innerWidth: 500 - }; - - beforeEach(() => { - angular.mock.module('gettext'); - angular.mock.module('I18N'); - angular.mock.module('ui.router'); - angular.mock.module('at.lib.services'); - angular.mock.module('at.lib.components', ($provide) => { - $provide.value('$window', windowMock); - }); - angular.mock.module('Utilities'); - angular.mock.module('ngCookies'); - }); - - beforeEach(angular.mock.inject((_$compile_, _$rootScope_) => { - $compile = _$compile_; - $rootScope = _$rootScope_; - scope = $rootScope.$new(); - - element = angular.element(''); - element = $compile(element)(scope); - scope.$digest(); - })); - - describe('Side Nav Controller', () => { - let sideNav; - let sideNavCtrl; - - beforeEach(() => { - sideNav = angular.element(element[0].querySelector('.at-Layout-side')); - sideNavCtrl = sideNav.controller('atSideNav'); - }); - - it('isExpanded defaults to false', () => { - expect(sideNavCtrl.isExpanded).toBe(false); - }); - - it('toggleExpansion()', () => { - expect(sideNavCtrl.isExpanded).toBe(false); - - sideNavCtrl.toggleExpansion(); - expect(sideNavCtrl.isExpanded).toBe(true); - - sideNavCtrl.toggleExpansion(); - expect(sideNavCtrl.isExpanded).toBe(false); - - sideNavCtrl.toggleExpansion(); - expect(sideNavCtrl.isExpanded).toBe(true); - - sideNavCtrl.toggleExpansion(); - expect(sideNavCtrl.isExpanded).toBe(false); - }); - - xit('isExpanded should be false after state change event', () => { - sideNavCtrl.isExpanded = true; - - const current = { - name: 'dashboard' - }; - $rootScope.$broadcast('$stateChangeSuccess', current); - scope.$digest(); - expect(sideNavCtrl.isExpanded).toBe(false); - }); - - it('clickOutsideSideNav watcher should assign isExpanded to false', () => { - sideNavCtrl.isExpanded = true; - - $rootScope.$broadcast('clickOutsideSideNav'); - scope.$digest(); - expect(sideNavCtrl.isExpanded).toBe(false); - }); - }); -}); diff --git a/awx/ui/test/unit/components/stream.unit.js b/awx/ui/test/unit/components/stream.unit.js deleted file mode 100644 index c4343d59b7f0..000000000000 --- a/awx/ui/test/unit/components/stream.unit.js +++ /dev/null @@ -1,97 +0,0 @@ -import StreamService from '~features/output/stream.service'; - -describe('Output | StreamService', () => { - angular.module('test', []).service('StreamService', StreamService); - let stream; - - beforeEach(() => { - angular.mock.module('test'); - }); - - beforeEach(angular.mock.inject(($injector) => { - stream = $injector.get('StreamService'); - - const onFrames = angular.noop; - const onFrameRate = angular.noop; - - stream.init({ onFrames, onFrameRate }); - })); - - describe('calcFactors', () => { - it('returns the expected values', () => { - const params = [ - [-1, [1]], - [0, [1]], - [1, [1]], - [1.0, [1]], - [1.1, [1]], - [2, [1, 2]], - ['1', [1]], - [{}, [1]], - [null, [1]], - [undefined, [1]], - [250, [1, 2, 5, 10, 25, 50, 125, 250]] - ]; - - params.forEach(([size, expected]) => - expect(stream.calcFactors(size)).toEqual(expected)); - }); - }); - - describe('setMissingCounterThreshold', () => { - it('returns the correct counter threshold', () => { - const gt = 2; - stream.setMissingCounterThreshold(gt); - expect(stream.counters.min).toEqual(gt); - - const lt = -1; - stream.setMissingCounterThreshold(lt); - expect(stream.counters.min).toEqual(gt); - }); - }); - - describe('isReadyToRender', () => { - it("it's never ready to render unless the result of getReadyCount is greater than 0", () => { - const params = [ - [-1, false], - [0, false], - [1, true] - ]; - const spy = spyOn(stream, 'getReadyCount'); - - params.forEach(([readyCount, expected]) => { - spy.and.returnValue(readyCount); - expect(stream.isReadyToRender()).toEqual(expected); - }); - }); - }); - - describe('getMaxCounter', () => { - it('returns the same value as max counter', () => { - const res = stream.getMaxCounter(); - expect(res).toEqual(stream.counters.max); - }); - }); - - describe('getReadyCount', () => { - it('references min and max counters', () => { - expect(stream.getReadyCount()).toEqual(stream.counters.max - stream.counters.min + 1); - }); - it('returns expected values if min or max value is a non-integer', () => { - const params = [ - [null, 1, 0], - [undefined, 1, NaN], - ['1', 1, 1], - [-1, -3, 3], - [0, 0, 1], - [6, 5, 2] - ]; - - params.forEach(([max, min, expected]) => { - stream.counters.ready = max; - stream.counters.min = min; - expect(stream.getReadyCount()).toEqual(expected); - }); - }); - }); -}); diff --git a/awx/ui/test/unit/index.js b/awx/ui/test/unit/index.js deleted file mode 100644 index 7c8967ef87b2..000000000000 --- a/awx/ui/test/unit/index.js +++ /dev/null @@ -1,2 +0,0 @@ -import './components'; -import './models'; diff --git a/awx/ui/test/unit/karma.unit.js b/awx/ui/test/unit/karma.unit.js deleted file mode 100644 index 57779c0359c3..000000000000 --- a/awx/ui/test/unit/karma.unit.js +++ /dev/null @@ -1,58 +0,0 @@ -const path = require('path'); -const webpackConfig = require('../../build/webpack.test.js'); - -process.env.CHROME_BIN = require('puppeteer').executablePath(); - -const SRC_PATH = path.resolve(__dirname, '../../client/src'); - -module.exports = config => { - config.set({ - basePath: '', - singleRun: true, - autoWatch: false, - colors: true, - frameworks: ['jasmine'], - browsers: ['chromeHeadless'], - reporters: ['progress', 'junit'], - files: [ - path.join(SRC_PATH, 'vendor.js'), - path.join(SRC_PATH, 'app.js'), - path.join(SRC_PATH, '**/*.html'), - 'index.js' - ], - plugins: [ - 'karma-webpack', - 'karma-jasmine', - 'karma-junit-reporter', - 'karma-chrome-launcher', - 'karma-html2js-preprocessor' - ], - preprocessors: { - [path.join(SRC_PATH, 'vendor.js')]: 'webpack', - [path.join(SRC_PATH, 'app.js')]: 'webpack', - [path.join(SRC_PATH, '**/*.html')]: 'html2js', - 'index.js': 'webpack' - }, - webpack: webpackConfig, - webpackMiddleware: { - noInfo: 'errors-only' - }, - junitReporter: { - outputDir: 'reports', - outputFile: 'results.unit.xml', - useBrowserName: false - }, - customLaunchers: { - chromeHeadless: { - base: 'Chrome', - flags: [ - '--no-sandbox', - '--disable-setuid-sandbox', - '--headless', - '--disable-gpu', - '--remote-debugging-port=9222', - ], - }, - }, - }); -}; diff --git a/awx/ui/test/unit/models/base.unit.js b/awx/ui/test/unit/models/base.unit.js deleted file mode 100644 index f721c12a0ad9..000000000000 --- a/awx/ui/test/unit/models/base.unit.js +++ /dev/null @@ -1,25 +0,0 @@ -describe('Models | BaseModel', () => { - let baseModel; - - beforeEach(() => { - angular.mock.module('at.lib.services'); - angular.mock.module('at.lib.models'); - }); - - beforeEach(angular.mock.inject(($injector) => { - baseModel = new ($injector.get('BaseModel'))('test'); - })); - - describe('parseRequestConfig', () => { - it('always returns the expected configuration', () => { - const { parseRequestConfig } = baseModel; - const data = { name: 'foo' }; - - expect(parseRequestConfig('get')).toEqual({ method: 'get', resource: undefined }); - expect(parseRequestConfig('get', 1)).toEqual({ method: 'get', resource: 1 }); - expect(parseRequestConfig('post', { data })).toEqual({ method: 'post', data }); - expect(parseRequestConfig(['get', 'post'], [1, 2], { data })) - .toEqual({ resource: [1, 2], method: ['get', 'post'] }); - }); - }); -}); diff --git a/awx/ui/test/unit/models/index.js b/awx/ui/test/unit/models/index.js deleted file mode 100644 index a1a9b3452a2d..000000000000 --- a/awx/ui/test/unit/models/index.js +++ /dev/null @@ -1,5 +0,0 @@ -// Import angular and angular-mocks to the global scope -import 'angular-mocks'; - -// Import tests -import './base.unit'; diff --git a/awx/ui/test/unit/webpack.unit.js b/awx/ui/test/unit/webpack.unit.js deleted file mode 100644 index 7ec1a9a7235f..000000000000 --- a/awx/ui/test/unit/webpack.unit.js +++ /dev/null @@ -1,16 +0,0 @@ -const webpack = require('webpack'); -const merge = require('webpack-merge'); -const base = require('../../build/webpack.base'); - -const STATIC_URL = '/static/'; - -const test = { - devtool: 'cheap-source-map', - plugins: [ - new webpack.DefinePlugin({ - $basePath: STATIC_URL - }) - ] -}; - -module.exports = merge(base, test); diff --git a/awx/ui/urls.py b/awx/ui/urls.py deleted file mode 100644 index 1b4e6775d2c3..000000000000 --- a/awx/ui/urls.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright (c) 2015 Ansible, Inc. -# All Rights Reserved. - -from django.conf.urls import url -from awx.ui.views import ( - index, - portal_redirect, - migrations_notran, -) - - -app_name = 'ui' -urlpatterns = [ - url(r'^$', index, name='index'), - url(r'^migrations_notran/$', migrations_notran, name='migrations_notran'), - url(r'^portal/$', portal_redirect, name='portal_redirect'), -] diff --git a/awx/ui/utils/get_licenses.js b/awx/ui/utils/get_licenses.js deleted file mode 100755 index 17ceb79b8c08..000000000000 --- a/awx/ui/utils/get_licenses.js +++ /dev/null @@ -1,240 +0,0 @@ -#!/usr/bin/env node -/* eslint no-console: ["error", { allow: ["log", "warn", "error"] }] */ - -const { lstatSync, readdirSync, readFileSync, existsSync, writeFileSync, mkdirSync } = require('fs'); -const { join } = require('path'); -const licenseTexts = require('./license_texts'); - -// path to shrinkwrap file -const SHRINKWRAP_PATH = `${__dirname}/../package-lock.json`; -// folder that npm install node_modules to -const NODE_MODULES_FOLDER = `${__dirname}/../node_modules`; -// the folder which we will put the ui license files -const UI_LICENSE_FOLDER = `${__dirname}/../../../docs/licenses/ui`; -// these folders in node_modules should be ommited -const OMITTED_NODE_MODULES_FOLDERS = ['@uirouter', '.bin', 'cycle']; -// all the ways in which deps with license files have license files named -const LICENSE_FILE_NAMES = ['LICENSE', 'LICENCE', 'LICENSE.md', 'LICENSE.txt', 'MIT-LICENSE.txt', 'LICENSE-MIT.txt', 'LICENSE-MIT', 'LICENSE.MIT', 'LICENSE.APACHE2', 'LICENSE.BSD']; -// all the ways in which deps with license info included in readme have the license header -const LICENSE_HEADER_NAMES = ['## License']; -// all the ways in which deps with license info included in readme have readme files named -const README_FILE_NAMES = ['README', 'README.md', 'README.markdown']; -// deps that we need to manually grab the license info (and that info) -const MANUAL_NODE_MODULES_LICENSE_INFO = []; -// commenting out for now as cycle is a dev dependency, -// leaving in to show the format expected for this array -// { -// module_name: 'cycle', -// license_info: 'cycle was released as JSON-js -// under the public domain (original repo here: https://github.com/douglascrockford/JSON-js) -// and published to npm as cycle (repo here: https://github.com/dscape/cycle)' -// } -// ]; - -// texts of the licenses when the license attr is grabbed from package.json -const LICENSE_TEXTS = licenseTexts; - -// below are helper functions the getters and main script execution functions -// call to piece together the license info -const isDirectory = source => lstatSync(source).isDirectory(); - -const manualNodeModulesSubDirectories = source => [join(source, '@uirouter/angularjs'), join(source, '@uirouter/core')]; - -const getNonDevDependencyModulesFromShrinkwrap = () => { - const getModDeps = (deps) => { - const depNamesArr = Object.keys(deps); - let arr = []; - depNamesArr.forEach(name => { - if (deps[name].dependecies) { - arr = arr.concat(getModDeps(deps[name].dependecies)); - } - if (!deps[name].dev) { - arr.push(name); - } - }); - return arr; - }; - - const shrinkwrap = JSON.parse(readFileSync(SHRINKWRAP_PATH).toString()); - - return getModDeps(shrinkwrap.dependencies); -}; - -const getSubdirectories = source => { - const listOfNonDevDependencyModules = getNonDevDependencyModulesFromShrinkwrap(); - const fromNodeModsDir = readdirSync(source) - .filter(name => OMITTED_NODE_MODULES_FOLDERS.indexOf(name) === -1) - .filter(name => listOfNonDevDependencyModules.indexOf(name) !== -1) - .map(name => join(source, name)) - .filter(isDirectory); - return fromNodeModsDir.concat(manualNodeModulesSubDirectories(source)); -}; - -const getModulename = path => { - let updatedPath; - if (path.includes('@uirouter')) { - updatedPath = path.split('/').slice(-2).join('-'); - } else { - updatedPath = path.split('/').slice(-1).join(''); - } - - return updatedPath; -}; - -const licenseTextIncludedInReadme = (readmeText, returnLicenseText) => LICENSE_HEADER_NAMES - .reduce((a, b) => { - let licenseVal; - if (!returnLicenseText) { - licenseVal = a || readmeText.includes(b); - } else if (a !== false) { - licenseVal = a; - } else if (readmeText.includes(b)) { - licenseVal = readmeText.split(b).slice(1, readmeText.split(b).length); - } else { - licenseVal = false; - } - return licenseVal; - }, false); - -const readmeIncludedInLicense = (path, returnLicenseText) => { - const readmeText = readFileSync(path).toString(); - return licenseTextIncludedInReadme(readmeText, returnLicenseText); -}; - -const licenseAttrInPackageJSON = (path, returnLicenseType) => { - const packageJSON = JSON.parse(readFileSync(path).toString()); - let isInPackageJSON; - if (!returnLicenseType) { - isInPackageJSON = packageJSON.license !== undefined || packageJSON.licenses !== undefined; - } else if (packageJSON.license && packageJSON.license.type) { - isInPackageJSON = packageJSON.license.type.toString(); - } else if (packageJSON.licenses && Array - .isArray(packageJSON.licenses) && packageJSON.licenses[0] && packageJSON.licenses[0].type) { - isInPackageJSON = packageJSON.licenses[0].type.toString(); - } else if (packageJSON.licenses) { - isInPackageJSON = packageJSON.licenses.toString(); - } else { - isInPackageJSON = packageJSON.license.toString(); - } - return isInPackageJSON; -}; - -// below are getters for the various types of ways licenses are included in the packages - -const hasLicenseFile = (path, returnFileName) => LICENSE_FILE_NAMES - .reduce((a, b) => { - let isLicenseFile; - if (!returnFileName) { - isLicenseFile = a || existsSync(join(path, b)); - } else if (a !== false) { - isLicenseFile = a; - } else if (existsSync(join(path, b))) { - isLicenseFile = join(path, b); - } else { - isLicenseFile = false; - } - return isLicenseFile; - }, false); - -const hasLicenseAttrInNPM = (path, returnLicenseType) => { - const packageJSONPath = join(path, 'package.json'); - return existsSync(packageJSONPath) && - licenseAttrInPackageJSON(packageJSONPath, returnLicenseType); -}; - -const hasLicenseInReadme = (path, returnLicenseText) => README_FILE_NAMES - .reduce((a, b) => { - const readmePath = join(path, b); - const readmeIncluded = existsSync(readmePath) && readmeIncludedInLicense(readmePath); - let isLicenseInReadme; - if (!returnLicenseText) { - isLicenseInReadme = a || readmeIncluded; - } else if (a !== false) { - isLicenseInReadme = a; - } else if (readmeIncluded) { - isLicenseInReadme = readmeIncludedInLicense(readmePath, returnLicenseText); - } else { - isLicenseInReadme = false; - } - return isLicenseInReadme; - }, false); - -const hasManualLicenseInfo = (path) => Object.prototype.hasOwnProperty - .call(MANUAL_NODE_MODULES_LICENSE_INFO, path); - -// checks to make sure all deps have some sort of license info associated -const licenseCheck = () => { - console.log('Checking each module for license.'); - - const noLicensePackage = getSubdirectories(NODE_MODULES_FOLDER) - .filter(path => !hasLicenseFile(path) && - !hasLicenseAttrInNPM(path) && - !hasLicenseInReadme(path) && - !hasManualLicenseInfo(path)); - - if (noLicensePackage.length === 0) { - console.log('Success! All modules probably have a license associated.'); - } else { - console.log(`ERROR! The following modules do not have license info associated with them: ${noLicensePackage.join(', ')}.`); - process.exit(1); - } -}; - -// copies the license info from the deps into a licenses folder -const licenseWrite = () => { - // create the ui license folder if it doesn't exist - if (!existsSync(UI_LICENSE_FOLDER)) { - mkdirSync(UI_LICENSE_FOLDER); - } - - console.log('Copying licenses from modules with license files.'); - - const modulesWithLicenseFile = getSubdirectories(NODE_MODULES_FOLDER) - .filter(path => hasLicenseFile(path)); - - console.log(`${modulesWithLicenseFile.length} modules with license files.`); - - modulesWithLicenseFile.forEach(path => { - writeFileSync( - join(UI_LICENSE_FOLDER, `${getModulename(path)}.txt`), - readFileSync(hasLicenseFile(path, true)).toString() - ); - }); - - const modulesWithPackageJSONLicenseAttr = getSubdirectories(NODE_MODULES_FOLDER) - .filter(path => !hasLicenseFile(path) && hasLicenseAttrInNPM(path)); - - console.log(`${modulesWithPackageJSONLicenseAttr.length} modules with license attr in package.json.`); - - modulesWithPackageJSONLicenseAttr.forEach(path => { - const licenseType = hasLicenseAttrInNPM(path, true); - const licenseText = LICENSE_TEXTS[licenseType]; - - if (!licenseText) { - console.log(`ERROR! License text for ${licenseType} is not in license_texts.js.`); - process.exit(1); - } - - writeFileSync(join(UI_LICENSE_FOLDER, `${getModulename(path)}.txt`), licenseText); - }); - - const modulesWithLicenseInfoInReadme = getSubdirectories(NODE_MODULES_FOLDER) - .filter(path => !hasLicenseFile(path) && - !hasLicenseAttrInNPM(path) && - hasLicenseInReadme(path)); - - console.log(`${modulesWithLicenseInfoInReadme.length} modules with license text in readme.`); - - modulesWithLicenseInfoInReadme.forEach(path => { - writeFileSync(join(UI_LICENSE_FOLDER, `${getModulename(path)}.txt`), hasLicenseInReadme(path, true)); - }); - - console.log(`${MANUAL_NODE_MODULES_LICENSE_INFO.length} modules with license info manually added to this script.`); - - MANUAL_NODE_MODULES_LICENSE_INFO.forEach(mod => { - writeFileSync(join(UI_LICENSE_FOLDER, `${getModulename(mod.module_name)}.txt`), mod.license_info); - }); -}; - -licenseCheck(); -licenseWrite(); diff --git a/awx/ui/utils/license_texts.js b/awx/ui/utils/license_texts.js deleted file mode 100644 index 0c0f2ee1f15c..000000000000 --- a/awx/ui/utils/license_texts.js +++ /dev/null @@ -1,539 +0,0 @@ -module.exports = { - MIT: ` -MIT -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - `, - 'CC0-1.0': ` -Creative Commons Legal Code - -CC0 1.0 Universal - - CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE - LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN - ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS - INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES - REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS - PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM - THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED - HEREUNDER. - -Statement of Purpose - -The laws of most jurisdictions throughout the world automatically confer -exclusive Copyright and Related Rights (defined below) upon the creator -and subsequent owner(s) (each and all, an "owner") of an original work of -authorship and/or a database (each, a "Work"). - -Certain owners wish to permanently relinquish those rights to a Work for -the purpose of contributing to a commons of creative, cultural and -scientific works ("Commons") that the public can reliably and without fear -of later claims of infringement build upon, modify, incorporate in other -works, reuse and redistribute as freely as possible in any form whatsoever -and for any purposes, including without limitation commercial purposes. -These owners may contribute to the Commons to promote the ideal of a free -culture and the further production of creative, cultural and scientific -works, or to gain reputation or greater distribution for their Work in -part through the use and efforts of others. - -For these and/or other purposes and motivations, and without any -expectation of additional consideration or compensation, the person -associating CC0 with a Work (the "Affirmer"), to the extent that he or she -is an owner of Copyright and Related Rights in the Work, voluntarily -elects to apply CC0 to the Work and publicly distribute the Work under its -terms, with knowledge of his or her Copyright and Related Rights in the -Work and the meaning and intended legal effect of CC0 on those rights. - -1. Copyright and Related Rights. A Work made available under CC0 may be -protected by copyright and related or neighboring rights ("Copyright and -Related Rights"). Copyright and Related Rights include, but are not -limited to, the following: - - i. the right to reproduce, adapt, distribute, perform, display, - communicate, and translate a Work; - ii. moral rights retained by the original author(s) and/or performer(s); -iii. publicity and privacy rights pertaining to a person's image or - likeness depicted in a Work; - iv. rights protecting against unfair competition in regards to a Work, - subject to the limitations in paragraph 4(a), below; - v. rights protecting the extraction, dissemination, use and reuse of data - in a Work; - vi. database rights (such as those arising under Directive 96/9/EC of the - European Parliament and of the Council of 11 March 1996 on the legal - protection of databases, and under any national implementation - thereof, including any amended or successor version of such - directive); and -vii. other similar, equivalent or corresponding rights throughout the - world based on applicable law or treaty, and any national - implementations thereof. - -2. Waiver. To the greatest extent permitted by, but not in contravention -of, applicable law, Affirmer hereby overtly, fully, permanently, -irrevocably and unconditionally waives, abandons, and surrenders all of -Affirmer's Copyright and Related Rights and associated claims and causes -of action, whether now known or unknown (including existing as well as -future claims and causes of action), in the Work (i) in all territories -worldwide, (ii) for the maximum duration provided by applicable law or -treaty (including future time extensions), (iii) in any current or future -medium and for any number of copies, and (iv) for any purpose whatsoever, -including without limitation commercial, advertising or promotional -purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each -member of the public at large and to the detriment of Affirmer's heirs and -successors, fully intending that such Waiver shall not be subject to -revocation, rescission, cancellation, termination, or any other legal or -equitable action to disrupt the quiet enjoyment of the Work by the public -as contemplated by Affirmer's express Statement of Purpose. - -3. Public License Fallback. Should any part of the Waiver for any reason -be judged legally invalid or ineffective under applicable law, then the -Waiver shall be preserved to the maximum extent permitted taking into -account Affirmer's express Statement of Purpose. In addition, to the -extent the Waiver is so judged Affirmer hereby grants to each affected -person a royalty-free, non transferable, non sublicensable, non exclusive, -irrevocable and unconditional license to exercise Affirmer's Copyright and -Related Rights in the Work (i) in all territories worldwide, (ii) for the -maximum duration provided by applicable law or treaty (including future -time extensions), (iii) in any current or future medium and for any number -of copies, and (iv) for any purpose whatsoever, including without -limitation commercial, advertising or promotional purposes (the -"License"). The License shall be deemed effective as of the date CC0 was -applied by Affirmer to the Work. Should any part of the License for any -reason be judged legally invalid or ineffective under applicable law, such -partial invalidity or ineffectiveness shall not invalidate the remainder -of the License, and in such case Affirmer hereby affirms that he or she -will not (i) exercise any of his or her remaining Copyright and Related -Rights in the Work or (ii) assert any associated claims and causes of -action with respect to the Work, in either case contrary to Affirmer's -express Statement of Purpose. - -4. Limitations and Disclaimers. - - a. No trademark or patent rights held by Affirmer are waived, abandoned, - surrendered, licensed or otherwise affected by this document. - b. Affirmer offers the Work as-is and makes no representations or - warranties of any kind concerning the Work, express, implied, - statutory or otherwise, including without limitation warranties of - title, merchantability, fitness for a particular purpose, non - infringement, or the absence of latent or other defects, accuracy, or - the present or absence of errors, whether or not discoverable, all to - the greatest extent permissible under applicable law. - c. Affirmer disclaims responsibility for clearing rights of other persons - that may apply to the Work or any use thereof, including without - limitation any person's Copyright and Related Rights in the Work. - Further, Affirmer disclaims responsibility for obtaining any necessary - consents, permissions or other rights required for any use of the - Work. - d. Affirmer understands and acknowledges that Creative Commons is not a - party to this document and has no duty or obligation with respect to - this CC0 or use of the Work. - `, - 'MIT OR GPL-2.0': ` -MIT -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.`, - ISC: ` -ISC -Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - `, - 'MIT,OFL-1.1': ` -MIT -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -SIL OPEN FONT LICENSE -Version 1.1 - 26 February 2007 - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting - in part or in whole - any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. - `, - BSD: ` -BSD -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - `, - 'BSD-2-Clause': ` -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - `, - 'AFLv2.1': ` -The Academic Free License -v. 2.1 - -This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following notice immediately following the copyright notice for the Original Work: - -Licensed under the Academic Free License version 2.1 - -1. Grant of Copyright License. Licensor hereby grants You a -world-wide, royalty-free, non-exclusive, perpetual, sublicenseable -license to do the following: - -a. to reproduce the Original Work in copies; - -b. to prepare derivative works ("Derivative Works") based upon the Original Work; - -c. to distribute copies of the Original Work and Derivative Works to the public; - -d. to perform the Original Work publicly; and - -e. to display the Original Work publicly. - -2. Grant of Patent License. Licensor hereby grants You a world-wide, -royalty-free, non-exclusive, perpetual, sublicenseable license, under -patent claims owned or controlled by the Licensor that are embodied in -the Original Work as furnished by the Licensor, to make, use, sell and -offer for sale the Original Work and Derivative Works. - -3. Grant of Source Code License. The term "Source Code" means the -preferred form of the Original Work for making modifications to it and -all available documentation describing how to modify the Original -Work. Licensor hereby agrees to provide a machine-readable copy of the -Source Code of the Original Work along with each copy of the Original -Work that Licensor distributes. Licensor reserves the right to satisfy -this obligation by placing a machine-readable copy of the Source Code -in an information repository reasonably calculated to permit -inexpensive and convenient access by You for as long as Licensor -continues to distribute the Original Work, and by publishing the -address of that information repository in a notice immediately -following the copyright notice that applies to the Original Work. - -4. Exclusions From License Grant. Neither the names of Licensor, nor -the names of any contributors to the Original Work, nor any of their -trademarks or service marks, may be used to endorse or promote -products derived from this Original Work without express prior written -permission of the Licensor. Nothing in this License shall be deemed to -grant any rights to trademarks, copyrights, patents, trade secrets or -any other intellectual property of Licensor except as expressly stated -herein. No patent license is granted to make, use, sell or offer to -sell embodiments of any patent claims other than the licensed claims -defined in Section 2. No right is granted to the trademarks of -Licensor even if such marks are included in the Original Work. Nothing -in this License shall be interpreted to prohibit Licensor from -licensing under different terms from this License any Original Work -that Licensor otherwise would have a right to license. - -5. This section intentionally omitted. - -6. Attribution Rights. You must retain, in the Source Code of any -Derivative Works that You create, all copyright, patent or trademark -notices from the Source Code of the Original Work, as well as any -notices of licensing and any descriptive text identified therein as an -"Attribution Notice." You must cause the Source Code for any -Derivative Works that You create to carry a prominent Attribution -Notice reasonably calculated to inform recipients that You have -modified the Original Work. - -7. Warranty of Provenance and Disclaimer of Warranty. Licensor -warrants that the copyright in and to the Original Work and the patent -rights granted herein by Licensor are owned by the Licensor or are -sublicensed to You under the terms of this License with the permission -of the contributor(s) of those copyrights and patent rights. Except as -expressly stated in the immediately proceeding sentence, the Original -Work is provided under this License on an "AS IS" BASIS and WITHOUT -WARRANTY, either express or implied, including, without limitation, -the warranties of NON-INFRINGEMENT, MERCHANTABILITY or FITNESS FOR A -PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL -WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential -part of this License. No license to Original Work is granted hereunder -except under this disclaimer. - -8. Limitation of Liability. Under no circumstances and under no legal -theory, whether in tort (including negligence), contract, or -otherwise, shall the Licensor be liable to any person for any direct, -indirect, special, incidental, or consequential damages of any -character arising as a result of this License or the use of the -Original Work including, without limitation, damages for loss of -goodwill, work stoppage, computer failure or malfunction, or any and -all other commercial damages or losses. This limitation of liability -shall not apply to liability for death or personal injury resulting -from Licensor's negligence to the extent applicable law prohibits such -limitation. Some jurisdictions do not allow the exclusion or -limitation of incidental or consequential damages, so this exclusion -and limitation may not apply to You. - -9. Acceptance and Termination. If You distribute copies of the -Original Work or a Derivative Work, You must make a reasonable effort -under the circumstances to obtain the express assent of recipients to -the terms of this License. Nothing else but this License (or another -written agreement between Licensor and You) grants You permission to -create Derivative Works based upon the Original Work or to exercise -any of the rights granted in Section 1 herein, and any attempt to do -so except under the terms of this License (or another written -agreement between Licensor and You) is expressly prohibited by -U.S. copyright law, the equivalent laws of other countries, and by -international treaty. Therefore, by exercising any of the rights -granted to You in Section 1 herein, You indicate Your acceptance of -this License and all of its terms and conditions. - -10. Termination for Patent Action. This License shall terminate -automatically and You may no longer exercise any of the rights granted -to You by this License as of the date You commence an action, -including a cross-claim or counterclaim, against Licensor or any -licensee alleging that the Original Work infringes a patent. This -termination provision shall not apply for an action alleging patent -infringement by combinations of the Original Work with other software -or hardware. - -11. Jurisdiction, Venue and Governing Law. Any action or suit relating -to this License may be brought only in the courts of a jurisdiction -wherein the Licensor resides or in which Licensor conducts its primary -business, and under the laws of that jurisdiction excluding its -conflict-of-law provisions. The application of the United Nations -Convention on Contracts for the International Sale of Goods is -expressly excluded. Any use of the Original Work outside the scope of -this License or after its termination shall be subject to the -requirements and penalties of the U.S. Copyright Act, 17 U.S.C. √Ç¬ß 101 -et seq., the equivalent laws of other countries, and international -treaty. This section shall survive the termination of this License. - -12. Attorneys Fees. In any action to enforce the terms of this License -or seeking damages relating thereto, the prevailing party shall be -entitled to recover its costs and expenses, including, without -limitation, reasonable attorneys' fees and costs incurred in -connection with such action, including any appeal of such action. This -section shall survive the termination of this License. - -13. Miscellaneous. This License represents the complete agreement -concerning the subject matter hereof. If any provision of this License -is held to be unenforceable, such provision shall be reformed only to -the extent necessary to make it enforceable. - -14. Definition of "You" in This License. "You" throughout this -License, whether in upper or lower case, means an individual or a -legal entity exercising rights under, and complying with all of the -terms of, this License. For legal entities, "You" includes any entity -that controls, is controlled by, or is under common control with -you. For purposes of this definition, "control" means (i) the power, -direct or indirect, to cause the direction or management of such -entity, whether by contract or otherwise, or (ii) ownership of fifty -percent (50%) or more of the outstanding shares, or (iii) beneficial -ownership of such entity. - -15. Right to Use. You may use the Original Work in all ways not -otherwise restricted or conditioned by this License or by law, and -Licensor promises not to interfere with or be responsible for such -uses by You. - -This license is Copyright (C) 2003-2004 Lawrence E. Rosen. All rights -reserved. Permission is hereby granted to copy and distribute this -license without modification. This license may not be modified without -the express written permission of its copyright owner. - `, - 'Public Domain': ` -This library is in the public domain. - `, - 'Apache License, Version 2.0': ` -Apache License - -Version 2.0, January 2004 - -http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - -"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. - -"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - -"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. - -"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. - -"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. - -"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: - -You must give any other recipients of the Work or Derivative Works a copy of this License; and -You must cause any modified files to carry prominent notices stating that You changed the files; and -You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and -If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. - -You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. -5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - `, - 'CC-BY-3.0': ` -Creative Commons Attribution 3.0 Unported - -CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE. - -License - -THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. - -BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. - -1. Definitions -a. "Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License. -b. "Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined above) for the purposes of this License. -c. "Distribute" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership. -d. "Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License. -e. "Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast. -f. "Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work. -g. "You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. -h. "Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images. -i. "Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium. -2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws. -3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: -a. to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections; -b. to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified."; -c. to Distribute and Publicly Perform the Work including as incorporated in Collections; and, -d. to Distribute and Publicly Perform Adaptations. -e. For the avoidance of doubt: -i. Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; -ii. Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and, -iii. Voluntary License Schemes. The Licensor waives the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License. -The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved. - -4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: -a. You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(b), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(b), as requested. -b. If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and (iv), consistent with Section 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4 (b) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties. -c. Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise. -5. Representations, Warranties and Disclaimer -UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. - -6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. -7. Termination -a. This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. -b. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. -8. Miscellaneous -a. Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. -b. Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. -c. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. -d. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. -e. This License may not be modified without the mutual written agreement of the Licensor and You. -f. The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law. -Creative Commons Notice - -Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor. - -Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, Creative Commons does not authorize the use by either party of the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. For the avoidance of doubt, this trademark restriction does not form part of this License. - -Creative Commons may be contacted at http://creativecommons.org/. - ` -}; diff --git a/awx/ui/views.py b/awx/ui/views.py deleted file mode 100644 index 9e99ad9d22b1..000000000000 --- a/awx/ui/views.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (c) 2015 Ansible, Inc. -# All Rights Reserved. - -from django.views.generic.base import TemplateView, RedirectView -from django.conf import settings - -class IndexView(TemplateView): - - template_name = 'ui/index.html' - - def get_context_data(self, **kwargs): - context = super(IndexView, self).get_context_data(**kwargs) - context['UI_LIVE_UPDATES_ENABLED'] = settings.UI_LIVE_UPDATES_ENABLED - # Add any additional context info here. - return context - -index = IndexView.as_view() - -class PortalRedirectView(RedirectView): - - permanent = True - url = '/#/portal' - -portal_redirect = PortalRedirectView.as_view() - -class MigrationsNotran(TemplateView): - - template_name = 'ui/installing.html' - -migrations_notran = MigrationsNotran.as_view() diff --git a/awx/ui_next/.eslintignore b/awx/ui_next/.eslintignore index 096662151e7c..eb4a217ff602 100644 --- a/awx/ui_next/.eslintignore +++ b/awx/ui_next/.eslintignore @@ -6,4 +6,5 @@ coverage build node_modules dist -images \ No newline at end of file +images +instrumented \ No newline at end of file diff --git a/awx/ui_next/.eslintrc b/awx/ui_next/.eslintrc index 367f465aa699..e2bf9305bdbb 100644 --- a/awx/ui_next/.eslintrc +++ b/awx/ui_next/.eslintrc @@ -8,16 +8,9 @@ "modules": true } }, - "plugins": [ - "react-hooks" - ], - "extends": ["airbnb", "prettier", "prettier/react"], + "plugins": ["react-hooks", "jsx-a11y"], + "extends": ["airbnb", "prettier", "prettier/react", "plugin:jsx-a11y/strict"], "settings": { - "import/resolver": { - "webpack": { - "config": "webpack.config.js" - } - }, "react": { "version": "16.5.2" } @@ -34,7 +27,7 @@ "camelcase": "off", "arrow-parens": "off", "comma-dangle": "off", - "//": "https://github.com/benmosher/eslint-plugin-import/issues/479#issuecomment-252500896", + // https://github.com/benmosher/eslint-plugin-import/issues/479#issuecomment-252500896 "import/no-extraneous-dependencies": "off", "max-len": [ "error", diff --git a/awx/ui_next/.linguirc b/awx/ui_next/.linguirc index 7af86ec8ad0d..5b9ca7902b21 100644 --- a/awx/ui_next/.linguirc +++ b/awx/ui_next/.linguirc @@ -1,5 +1,6 @@ { - "localeDir": "build/locales/", + "localeDir": "src/locales/", "srcPathDirs": ["src/"], - "format": "po" + "format": "po", + "sourceLocale": "en" } diff --git a/awx/ui_next/.npmrc b/awx/ui_next/.npmrc index e69de29bb2d1..c42da845b449 100644 --- a/awx/ui_next/.npmrc +++ b/awx/ui_next/.npmrc @@ -0,0 +1 @@ +engine-strict = true diff --git a/awx/ui_next/CONTRIBUTING.md b/awx/ui_next/CONTRIBUTING.md index 105935734fd0..c0a3eaefc44a 100644 --- a/awx/ui_next/CONTRIBUTING.md +++ b/awx/ui_next/CONTRIBUTING.md @@ -6,23 +6,34 @@ Have questions about this document or anything not covered here? Feel free to re ## Table of contents -* [Things to know prior to submitting code](#things-to-know-prior-to-submitting-code) -* [Setting up your development environment](#setting-up-your-development-environment) - * [Prerequisites](#prerequisites) - * [Node and npm](#node-and-npm) -* [Build the user interface](#build-the-user-interface) -* [Accessing the AWX web interface](#accessing-the-awx-web-interface) -* [AWX REST API Interaction](#awx-rest-api-interaction) -* [Handling API Errors](#handling-api-errors) -* [Working with React](#working-with-react) - * [App structure](#app-structure) - * [Naming files](#naming-files) - * [Class constructors vs Class properties](#class-constructors-vs-class-properties) - * [Binding](#binding) - * [Typechecking with PropTypes](#typechecking-with-proptypes) - * [Naming Functions](#naming-functions) - * [Default State Initialization](#default-state-initialization) -* [Internationalization](#internationalization) +- [Ansible AWX UI With PatternFly](#ansible-awx-ui-with-patternfly) + - [Table of contents](#table-of-contents) + - [Things to know prior to submitting code](#things-to-know-prior-to-submitting-code) + - [Setting up your development environment](#setting-up-your-development-environment) + - [Prerequisites](#prerequisites) + - [Node and npm](#node-and-npm) + - [Build the User Interface](#build-the-user-interface) + - [Accessing the AWX web interface](#accessing-the-awx-web-interface) + - [AWX REST API Interaction](#awx-rest-api-interaction) + - [Handling API Errors](#handling-api-errors) + - [Forms](#forms) + - [Working with React](#working-with-react) + - [App structure](#app-structure) + - [Patterns](#patterns) + - [Bootstrapping the application (root src/ files)](#bootstrapping-the-application-root-src-files) + - [Naming files](#naming-files) + - [Naming components that use the context api](#naming-components-that-use-the-context-api) + - [Class constructors vs Class properties](#class-constructors-vs-class-properties) + - [Binding](#binding) + - [Typechecking with PropTypes](#typechecking-with-proptypes) + - [Custom Hooks](#custom-hooks) + - [Naming Functions](#naming-functions) + - [Default State Initialization](#default-state-initialization) + - [Testing components that use contexts](#testing-components-that-use-contexts) + - [Internationalization](#internationalization) + - [Marking strings for translation and replacement in the UI](#marking-strings-for-translation-and-replacement-in-the-ui) + - [Setting up .po files to give to translation team](#setting-up-po-files-to-give-to-translation-team) + - [Marking an issue to be translated](#marking-an-issue-to-be-translated) ## Things to know prior to submitting code @@ -30,6 +41,11 @@ Have questions about this document or anything not covered here? Feel free to re - All code submissions are done through pull requests against the `devel` branch. - If collaborating with someone else on the same branch, please use `--force-with-lease` instead of `--force` when pushing up code. This will prevent you from accidentally overwriting commits pushed by someone else. For more information, see https://git-scm.com/docs/git-push#git-push---force-with-leaseltrefnamegt - We use a [code formatter](https://prettier.io/). Before adding a new commit or opening a PR, please apply the formatter using `npm run prettier` +- We adopt the following code style guide: + - functions should adopt camelCase + - constructors/classes should adopt PascalCase + - constants to be exported should adopt UPPERCASE +- For strings, we adopt the `sentence capitalization` since it is a [Patternfly style guide](https://www.patternfly.org/v4/design-guidelines/content/grammar-and-terminology#capitalization). ## Setting up your development environment @@ -41,7 +57,7 @@ The UI is built using [ReactJS](https://reactjs.org/docs/getting-started.html) a The AWX UI requires the following: -- Node 10.x LTS +- Node 14.x LTS - NPM 6.x LTS Run the following to install all the dependencies: @@ -76,7 +92,7 @@ Note that mixins can be chained. See the example below. Example of a model using multiple mixins: -``` +```javascript import NotificationsMixin from '../mixins/Notifications.mixin'; import InstanceGroupsMixin from '../mixins/InstanceGroups.mixin'; @@ -91,7 +107,7 @@ export default Organizations; Example of mocking a specific method for every test in a suite: -``` +```javascript import { OrganizationsAPI } from '../../../../src/api'; // Mocks out all available methods. Comparable to: @@ -124,6 +140,9 @@ API requests can and will fail occasionally so they should include explicit erro - **other errors** - Most errors will fall into the first two categories, but for miscellaneous actions like toggling notifications, deleting a list item, etc. we display an alert modal to notify the user that their requested action couldn't be performed. +## Forms +Our forms should have a known, consistent, and fully-resolved starting state before it is possible for a user, keyboard-mouse, screen reader, or automated test to interact with them. If multiple network calls are needed to populate a form, resolve them all before displaying the form or showing a content error. When multiple requests are needed to create or update the resources represented by a form, resolve them all before transitioning the ui to a success or failure state. + ## Working with React ### App structure @@ -134,10 +153,14 @@ Inside these folders, the internal structure is: - **/api** - All classes used to interact with API's are found here. See [AWX REST API Interaction](#awx-rest-api-interaction) for more information. - **/components** - All generic components that are meant to be used in multiple contexts throughout awx. Things like buttons, tabs go here. - **/contexts** - Components which utilize react's context api. +- **/locales** - [Internationalization](#internationalization) config and source files. - **/screens** - Based on the various routes of awx. - **/shared** - Components that are meant to be used specifically by a particular route, but might be sharable across pages of that route. For example, a form component which is used on both add and edit screens. - **/util** - Stateless helper functions that aren't tied to react. +### Patterns +- A **screen** shouldn't import from another screen. If a component _needs_ to be shared between two or more screens, it is a generic and should be moved to `src/components`. + #### Bootstrapping the application (root src/ files) In the root of `/src`, there are a few files which are used to initialize the react app. These are @@ -161,7 +184,7 @@ Ideally, files should be named the same as the component they export, and tests **File naming** - Since contexts export both consumer and provider (and potentially in withContext function form), the file can be simplified to be named after the consumer export. In other words, the file containing the `Network` context components would be named `Network.jsx`. -**Component naming and conventions** - In order to provide a consistent interface with react-router and lingui, as well as make their usage easier and less verbose, context components follow these conventions: +**Component naming and conventions** - In order to provide a consistent interface with react-router and [lingui](https://lingui.js.org/), as well as make their usage easier and less verbose, context components follow these conventions: - Providers are wrapped in a component in the `FooProvider` format. - The value prop of the provider should be pulled from state. This is recommended by the react docs, [here](https://reactjs.org/docs/context.html#caveats). - The provider should also be able to accept its value by prop for testing. @@ -221,24 +244,33 @@ About.defaultProps = { }; ``` +### Custom Hooks + +There are currently a few custom hooks: + +1. [useRequest](https://github.com/ansible/awx/blob/devel/awx/ui_next/src/util/useRequest.js#L21) encapsulates main actions related to requests. +2. [useDismissableError](https://github.com/ansible/awx/blob/devel/awx/ui_next/src/util/useRequest.js#L71) provides controls for "dismissing" an error message. +3. [useDeleteItems](https://github.com/ansible/awx/blob/devel/awx/ui_next/src/util/useRequest.js#L98) handles deletion of items from a paginated item list. +4. [useSelected](https://github.com/ansible/awx/blob/devel/awx/ui_next/src/util/useSelected.jsx#L14) provides a way to read and update a selected list. + ### Naming Functions Here are the guidelines for how to name functions. -| Naming Convention | Description | -|----------|-------------| -|`handle`| Use for methods that process events | -|`on`| Use for component prop names | -|`toggle`| Use for methods that flip one value to the opposite value | -|`show`| Use for methods that always set a value to show or add an element | -|`hide`| Use for methods that always set a value to hide or remove an element | -|`create`| Use for methods that make API `POST` requests | -|`read`| Use for methods that make API `GET` requests | -|`update`| Use for methods that make API `PATCH` requests | -|`destroy`| Use for methods that make API `DESTROY` requests | -|`replace`| Use for methods that make API `PUT` requests | -|`disassociate`| Use for methods that pass `{ disassociate: true }` as a data param to an endpoint | -|`associate`| Use for methods that pass a resource id as a data param to an endpoint | -|`can`| Use for props dealing with RBAC to denote whether a user has access to something | +| Naming Convention | Description | +| ----------------- | --------------------------------------------------------------------------------- | +| `handle` | Use for methods that process events | +| `on` | Use for component prop names | +| `toggle` | Use for methods that flip one value to the opposite value | +| `show` | Use for methods that always set a value to show or add an element | +| `hide` | Use for methods that always set a value to hide or remove an element | +| `create` | Use for methods that make API `POST` requests | +| `read` | Use for methods that make API `GET` requests | +| `update` | Use for methods that make API `PATCH` requests | +| `destroy` | Use for methods that make API `DESTROY` requests | +| `replace` | Use for methods that make API `PUT` requests | +| `disassociate` | Use for methods that pass `{ disassociate: true }` as a data param to an endpoint | +| `associate` | Use for methods that pass a resource id as a data param to an endpoint | +| `can` | Use for props dealing with RBAC to denote whether a user has access to something | ### Default State Initialization When declaring empty initial states, prefer the following instead of leaving them undefined: @@ -259,7 +291,7 @@ We have several React contexts that wrap much of the app, including those from r If you want to stub the value of a context, or assert actions taken on it, you can customize a contexts value by passing a second parameter to `mountWithContexts`. For example, this provides a custom value for the `Config` context: -``` +```javascript const config = { custom_virtualenvs: ['foo', 'bar'], }; @@ -273,7 +305,7 @@ them is rendering properly. The object containing context values looks for five known contexts, identified by the keys `linguiPublisher`, `router`, `config`, `network`, and `dialog` — the latter three each referring to the contexts defined in `src/contexts`. You can pass `false` for any of these values, and the corresponding context will be omitted from your test. For example, this will mount your component without the dialog context: -``` +```javascript mountWithContexts(< { context: { dialog: false, @@ -298,12 +330,18 @@ The lingui library provides various React helpers for dealing with both marking **Note:** We try to avoid the `I18n` consumer, `i18nMark` function, or `` component lingui gives us access to in this repo. i18nMark does not actually replace the string in the UI (leading to the potential for untranslated bugs), and the other helpers are redundant. Settling on a consistent, single pattern helps us ease the mental overhead of the need to understand the ins and outs of the lingui API. -You can learn more about the ways lingui and its React helpers at [this link](https://lingui.js.org/tutorials/react-patterns.html). +You can learn more about the ways lingui and its React helpers at [this link](https://lingui.js.org/tutorials/react-patterns.html). ### Setting up .po files to give to translation team 1) `npm run add-locale` to add the language that you want to translate to (we should only have to do this once and the commit to repo afaik). Example: `npm run add-locale en es fr` # Add English, Spanish and French locale -2) `npm run extract-strings` to create .po files for each language specified. The .po files will be placed in src/locales but this is configurable. +2) `npm run extract-strings` to create .po files for each language specified. The .po files will be placed in src/locales. 3) Open up the .po file for the language you want to test and add some translations. In production we would pass this .po file off to the translation team. 4) Once you've edited your .po file (or we've gotten a .po file back from the translation team) run `npm run compile-strings`. This command takes the .po files and turns them into a minified JSON object and can be seen in the `messages.js` file in each locale directory. These files get loaded at the App root level (see: App.jsx). 5) Change the language in your browser and reload the page. You should see your specified translations in place of English strings. + +### Marking an issue to be translated + +1) Issues marked with `component:I10n` should not be closed after the issue was fixed. +2) Remove the label `state:needs_devel`. +3) Add the label `state:pending_translations`. At this point, the translations will be batch translated by a maintainer, creating relevant entries in the PO files. Then after those translations have been merged, the issue can be closed. diff --git a/awx/ui_next/Dockerfile b/awx/ui_next/Dockerfile index bbf2ef65f950..a710a820cd7c 100644 --- a/awx/ui_next/Dockerfile +++ b/awx/ui_next/Dockerfile @@ -1,20 +1,15 @@ -FROM node:10 +FROM node:14 ARG NPMRC_FILE=.npmrc ENV NPMRC_FILE=${NPMRC_FILE} -ARG TARGET_HOST='awx' -ENV TARGET_HOST=${TARGET_HOST} -ARG TARGET_PORT=8043 -ENV TARGET_PORT=${TARGET_PORT} +ARG TARGET='https://awx:8043' +ENV TARGET=${TARGET} +ENV CI=true WORKDIR /ui_next -ADD build build -ADD dist dist -ADD images images +ADD public public ADD package.json package.json ADD package-lock.json package-lock.json COPY ${NPMRC_FILE} .npmrc RUN npm install -ADD babel.config.js babel.config.js -ADD webpack.config.js webpack.config.js ADD src src EXPOSE 3001 -CMD [ "npm", "run", "start" ] +CMD [ "npm", "start" ] diff --git a/awx/ui_next/README.md b/awx/ui_next/README.md index 59bf37714e9c..2755f136d93d 100644 --- a/awx/ui_next/README.md +++ b/awx/ui_next/README.md @@ -1,33 +1,90 @@ # AWX-PF ## Requirements -- node 10.x LTS, npm 6.x LTS, make, git +- node 14.x LTS, npm 6.x LTS, make, git -## Usage +## Development +The API development server will need to be running. See [CONTRIBUTING.md](../../CONTRIBUTING.md). -* `git clone git@github.com:ansible/awx.git` -* cd awx/ui_next -* npm install -* npm start -* visit `https://127.0.0.1:3001/` +```shell +# install +npm --prefix=awx/ui_next install + +# Start the ui development server. While running, the ui will be reachable +# at https://127.0.0.1:3001 and updated automatically when code changes. +npm --prefix=awx/ui_next start +``` + +### Build for the Development Containers +If you just want to build a ui for the container-based awx development +environment, use these make targets: + +```shell +# The ui will be reachable at https://localhost:8043 or +# http://localhost:8013 +make ui-devel -**note:** These instructions assume you have the [awx](https://github.com/ansible/awx/blob/devel/CONTRIBUTING.md#running-the-environment) development api server up and running at `localhost:8043`. You can use a different backend server with the `TAGET_HOST` and `TARGET_PORT` environment variables when starting the development server: +# clean up +make clean-ui +``` + +### Using an External Server +If you normally run awx on an external host/server (in this example, `awx.local`), +you'll need use the `TARGET` environment variable when starting the ui development +server: ```shell -# use a non-default host and port when starting the development server -TARGET_HOST='ec2-awx.amazonaws.com' TARGET_PORT='443' npm run start +TARGET='https://awx.local:8043' npm --prefix awx/ui_next start ``` -## Unit Tests +## Testing +```shell +# run code formatting check +npm --prefix awx/ui_next run prettier-check + +# run lint checks +npm --prefix awx/ui_next run lint + +# run all unit tests +npm --prefix awx/ui_next run test -To run the unit tests on files that you've changed: -* `npm test` +# run a single test (in this case the login page test): +npm --prefix awx/ui_next test -- src/screens/Login/Login.test.jsx -To run a single test (in this case the login page test): -* `npm test -- src/screens/Login/Login.test.jsx` +# start the test watcher and run tests on files that you've changed +npm --prefix awx/ui_next run test-watch +``` +#### Note: +- Once the test watcher is up and running you can hit `a` to run all the tests. +- All commands are run on your host machine and not in the api development containers. -**note:** Once the test watcher is up and running you can hit `a` to run all the tests +## Adding Dependencies +```shell +# add an exact development or build dependency +npm --prefix awx/ui_next install --save-dev --save-exact dev-package@1.2.3 + +# add an exact production dependency +npm --prefix awx/ui_next install --save --save-exact prod-package@1.23 + +# add the updated package.json and package-lock.json files to scm +git add awx/ui_next_next/package.json awx/ui_next_next/package-lock.json +``` + +## Removing Dependencies +```shell +# remove a development or build dependency +npm --prefix awx/ui_next uninstall --save-dev dev-package + +# remove a production dependency +npm --prefix awx/ui_next uninstall --save prod-package +``` + +## Building for Production +```shell +# built files are placed in awx/ui_next/build +npm --prefix awx/ui_next run build +``` ## CI Container @@ -36,7 +93,7 @@ To run: ```shell cd awx/awx/ui_next docker build -t awx-ui-next . -docker run --name tools_ui_next_1 --network tools_default --link 'tools_awx_1:awx' -e TARGET_HOST=awx -p '3001:3001' --rm -v $(pwd)/src:/ui_next/src awx-ui-next +docker run --name tools_ui_next_1 --network tools_default --link 'tools_awx_1:awx' -e TARGET="https://awx:8043" -p '3001:3001' --rm -v $(pwd)/src:/ui_next/src awx-ui-next ``` -**note:** This is for CI, test systems, zuul, etc. For local development, see [usage](https://github.com/ansible/awx/blob/devel/awx/ui_next/README.md#usage) +**Note:** This is for CI, test systems, zuul, etc. For local development, see [usage](https://github.com/ansible/awx/blob/devel/awx/ui_next/README.md#Development) diff --git a/awx/ui_next/SEARCH.md b/awx/ui_next/SEARCH.md index 497d424248fa..111dfb2f56b0 100644 --- a/awx/ui_next/SEARCH.md +++ b/awx/ui_next/SEARCH.md @@ -2,7 +2,7 @@ ## UX Considerations -Historically, the code that powers search in the AngularJS version of the AWX/Tower UI is very complex and prone to bugs. In order to reduce that complexity, we've made some UX desicions to help make the code easier to maintain. +Historically, the code that powers search in the AngularJS version of the AWX/Tower UI is very complex and prone to bugs. In order to reduce that complexity, we've made some UX decisions to help make the code easier to maintain. **ALL query params namespaced and in url bar** diff --git a/awx/ui_next/__mocks__/fileMock.js b/awx/ui_next/__mocks__/fileMock.js deleted file mode 100644 index 0bf40cb41909..000000000000 --- a/awx/ui_next/__mocks__/fileMock.js +++ /dev/null @@ -1,7 +0,0 @@ -const path = require('path'); - -module.exports = { - process (src, filename) { - return `module.exports=${JSON.stringify(path.basename(filename))};`; - }, -}; diff --git a/awx/ui_next/__mocks__/styleMock.js b/awx/ui_next/__mocks__/styleMock.js deleted file mode 100644 index f053ebf7976e..000000000000 --- a/awx/ui_next/__mocks__/styleMock.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = {}; diff --git a/awx/ui_next/babel.config.js b/awx/ui_next/babel.config.js deleted file mode 100644 index aca16f724691..000000000000 --- a/awx/ui_next/babel.config.js +++ /dev/null @@ -1,18 +0,0 @@ -module.exports = api => { - api.cache(false); - return { - plugins: [ - 'babel-plugin-styled-components', - '@babel/plugin-proposal-class-properties', - 'macros' - ], - presets: [ - ['@babel/preset-env', { - targets: { - node: '8.11' - } - }], - '@babel/preset-react' - ] - }; -}; diff --git a/awx/ui_next/build/locales/en/messages.js b/awx/ui_next/build/locales/en/messages.js deleted file mode 100644 index 61cb8198b85b..000000000000 --- a/awx/ui_next/build/locales/en/messages.js +++ /dev/null @@ -1 +0,0 @@ -/* eslint-disable */module.exports={languageData:{"plurals":function(n,ord){var s=String(n).split("."),v0=!s[1],t0=Number(s[0])==n,n10=t0&&s[0].slice(-1),n100=t0&&s[0].slice(-2);if(ord)return n10==1&&n100!=11?"one":n10==2&&n100!=12?"two":n10==3&&n100!=13?"few":"other";return n==1&&v0?"one":"other"}},messages:{"404":"404","> add":"> add","> edit":"> edit","AWX Logo":"AWX Logo","About":"About","AboutModal Logo":"AboutModal Logo","Access":"Access","Add":"Add","Add Roles":"Add Roles","Add Team Roles":"Add Team Roles","Add User Roles":"Add User Roles","Administration":"Administration","Admins":"Admins","Ansible Environment":"Ansible Environment","Ansible Version":"Ansible Version","Applications":"Applications","Apply roles":"Apply roles","Are you sure you want to delete:":"Are you sure you want to delete:","Are you sure you want to remove {0} access from {1}? Doing so affects all members of the team.":function(a){return["Are you sure you want to remove ",a("0")," access from ",a("1"),"? Doing so affects all members of the team."]},"Are you sure you want to remove {0} access from {username}?":function(a){return["Are you sure you want to remove ",a("0")," access from ",a("username"),"?"]},"Authentication":"Authentication","Authentication Settings":"Authentication Settings","Brand Image":"Brand Image","Cancel":"Cancel","Cannot find organization with ID":"Cannot find organization with ID","Cannot find resource.":"Cannot find resource.","Cannot find route {0}.":function(a){return["Cannot find route ",a("0"),"."]},"Close":"Close","Collapse":"Collapse","Copyright 2018 Red Hat, Inc.":"Copyright 2018 Red Hat, Inc.","Copyright 2019 Red Hat, Inc.":"Copyright 2019 Red Hat, Inc.","Create New Organization":"Create New Organization","Created":"Created","Credential Types":"Credential Types","Credentials":"Credentials","Current page":"Current page","Dashboard":"Dashboard","Delete":"Delete","Delete {0}":function(a){return["Delete ",a("0")]},"Delete {itemName}":function(a){return["Delete ",a("itemName")]},"Description":"Description","Details":"Details","Edit":"Edit","Edit Details":"Edit Details","Expand":"Expand","Failure":"Failure","First":"First","Go to first page":"Go to first page","Go to last page":"Go to last page","Go to next page":"Go to next page","Go to previous page":"Go to previous page","Help":"Help","If you {0} want to remove access for this particular user, please remove them from the team.":function(a){return["If you ",a("0")," want to remove access for this particular user, please remove them from the team."]},"Info":"Info","Instance Groups":"Instance Groups","Integrations":"Integrations","Invalid username or password. Please try again.":"Invalid username or password. Please try again.","Inventories":"Inventories","Inventory Scripts":"Inventory Scripts","Items Per Page":"Items Per Page","Items per page":"Items per page","Items {itemMin} \u2013 {itemMax} of {count}":function(a){return["Items ",a("itemMin")," \u2013 ",a("itemMax")," of ",a("count")]},"Jobs":"Jobs","Jobs Settings":"Jobs Settings","Last":"Last","Last Modified":"Last Modified","Last Name":"Last Name","License":"License","Loading...":"Loading...","Logout":"Logout","Management Jobs":"Management Jobs","Members":"Members","Modified":"Modified","My View":"My View","Name":"Name","Next":"Next","No {0} Found":function(a){return["No ",a("0")," Found"]},"Notification Templates":"Notification Templates","Notifications":"Notifications","Organization Add":"Organization Add","Organization detail tabs":"Organization detail tabs","Organizations":"Organizations","Organizations List":"Organizations List","Page":"Page","Page <0/> of {pageCount}":function(a){return["Page <0/> of ",a("pageCount")]},"Page Number":"Page Number","Pagination":"Pagination","Password":"Password","Per Page":"Per Page","Please add {0} to populate this list":function(a){return["Please add ",a("0")," to populate this list"]},"Please add {0} {itemName} to populate this list":function(a){return["Please add ",a("0")," ",a("itemName")," to populate this list"]},"Portal Mode":"Portal Mode","Previous":"Previous","Primary Navigation":"Primary Navigation","Projects":"Projects","Remove {0} Access":function(a){return["Remove ",a("0")," Access"]},"Resources":"Resources","Save":"Save","Schedules":"Schedules","Search":"Search","Search text input":"Search text input","Select":"Select","Select Input":"Select Input","Select Users Or Teams":"Select Users Or Teams","Select a row to delete":"Select a row to delete","Select all":"Select all","Select items from list":"Select items from list","Select the Instance Groups for this Organization to run on.":"Select the Instance Groups for this Organization to run on.","Select {header}":function(a){return["Select ",a("header")]},"Selected":"Selected","Settings":"Settings","Sort":"Sort","Successful":"Successful","System":"System","System Settings":"System Settings","Team":"Team","Team Roles":"Team Roles","Teams":"Teams","Templates":"Templates","This field must not be blank":"This field must not be blank","This field must not exceed {max} characters":function(a){return["This field must not exceed ",a("max")," characters"]},"Toggle notification failure":"Toggle notification failure","Toggle notification success":"Toggle notification success","Use Default {label}":function(a){return["Use Default ",a("label")]},"User":"User","User Details":"User Details","User Interface":"User Interface","User Interface Settings":"User Interface Settings","User Roles":"User Roles","Username":"Username","Users":"Users","Views":"Views","Welcome to Ansible {brandName}! Please Sign In.":function(a){return["Welcome to Ansible ",a("brandName"),"! Please Sign In."]},"You do not have permission to delete the following {0}: {itemsUnableToDelete}":function(a){return["You do not have permission to delete the following ",a("0"),": ",a("itemsUnableToDelete")]},"You have been logged out.":"You have been logged out.","add {currentTab}":function(a){return["add ",a("currentTab")]},"adding {currentTab}":function(a){return["adding ",a("currentTab")]},"cancel delete":"cancel delete","confirm delete":"confirm delete","confirm removal of {currentTab}/cancel and go back to {currentTab} view.":function(a){return["confirm removal of ",a("currentTab"),"/cancel and go back to ",a("currentTab")," view."]},"delete {currentTab}":function(a){return["delete ",a("currentTab")]},"deleting {currentTab} association with orgs":function(a){return["deleting ",a("currentTab")," association with orgs"]},"edit view":"edit view","items":"items","of {pageCount}":function(a){return["of ",a("pageCount")]},"pages":"pages","per page":"per page","save/cancel and go back to view":"save/cancel and go back to view","save/cancel and go back to {currentTab} view":function(a){return["save/cancel and go back to ",a("currentTab")," view"]},"select organization {itemId}":function(a){return["select organization ",a("itemId")]},"{0}":function(a){return[a("0")]},"{0} List":function(a){return[a("0")," List"]},"{currentTab} detail view":function(a){return[a("currentTab")," detail view"]},"{itemMin} - {itemMax} of {count}":function(a){return[a("itemMin")," - ",a("itemMax")," of ",a("count")]}}}; \ No newline at end of file diff --git a/awx/ui_next/build/locales/ja/messages.js b/awx/ui_next/build/locales/ja/messages.js deleted file mode 100644 index b3b6c304bea8..000000000000 --- a/awx/ui_next/build/locales/ja/messages.js +++ /dev/null @@ -1 +0,0 @@ -/* eslint-disable */module.exports={languageData:{"plurals":function(n,ord){if(ord)return"other";return"other"}},messages:{"404":"404","> add":"> add","> edit":"> edit","AWX Logo":"AWX Logo","About":"About","AboutModal Logo":"AboutModal Logo","Access":"Access","Add":"Add","Add Roles":"Add Roles","Add Team Roles":"Add Team Roles","Add User Roles":"Add User Roles","Administration":"Administration","Admins":"Admins","Ansible Environment":"Ansible Environment","Ansible Version":"Ansible Version","Applications":"Applications","Apply roles":"Apply roles","Are you sure you want to delete:":"Are you sure you want to delete:","Are you sure you want to remove {0} access from {1}? Doing so affects all members of the team.":function(a){return["Are you sure you want to remove ",a("0")," access from ",a("1"),"? Doing so affects all members of the team."]},"Are you sure you want to remove {0} access from {username}?":function(a){return["Are you sure you want to remove ",a("0")," access from ",a("username"),"?"]},"Authentication":"Authentication","Authentication Settings":"Authentication Settings","Brand Image":"Brand Image","Cancel":"Cancel","Cannot find organization with ID":"Cannot find organization with ID","Cannot find resource.":"Cannot find resource.","Cannot find route {0}.":function(a){return["Cannot find route ",a("0"),"."]},"Close":"Close","Collapse":"Collapse","Copyright 2018 Red Hat, Inc.":"Copyright 2018 Red Hat, Inc.","Copyright 2019 Red Hat, Inc.":"Copyright 2019 Red Hat, Inc.","Create New Organization":"Create New Organization","Created":"Created","Credential Types":"Credential Types","Credentials":"Credentials","Current page":"Current page","Dashboard":"Dashboard","Delete":"Delete","Delete {0}":function(a){return["Delete ",a("0")]},"Delete {itemName}":function(a){return["Delete ",a("itemName")]},"Description":"Description","Details":"Details","Edit":"Edit","Edit Details":"Edit Details","Expand":"Expand","Failure":"Failure","First":"First","Go to first page":"Go to first page","Go to last page":"Go to last page","Go to next page":"Go to next page","Go to previous page":"Go to previous page","Help":"Help","If you {0} want to remove access for this particular user, please remove them from the team.":function(a){return["If you ",a("0")," want to remove access for this particular user, please remove them from the team."]},"Info":"Info","Instance Groups":"Instance Groups","Integrations":"Integrations","Invalid username or password. Please try again.":"Invalid username or password. Please try again.","Inventories":"Inventories","Inventory Scripts":"Inventory Scripts","Items Per Page":"Items Per Page","Items per page":"Items per page","Items {itemMin} \u2013 {itemMax} of {count}":function(a){return["Items ",a("itemMin")," \u2013 ",a("itemMax")," of ",a("count")]},"Jobs":"Jobs","Jobs Settings":"Jobs Settings","Last":"Last","Last Modified":"Last Modified","Last Name":"Last Name","License":"License","Loading...":"Loading...","Logout":"Logout","Management Jobs":"Management Jobs","Members":"Members","Modified":"Modified","My View":"My View","Name":"Name","Next":"Next","No {0} Found":function(a){return["No ",a("0")," Found"]},"Notification Templates":"Notification Templates","Notifications":"Notifications","Organization Add":"Organization Add","Organization detail tabs":"Organization detail tabs","Organizations":"Organizations","Organizations List":"Organizations List","Page":"Page","Page <0/> of {pageCount}":function(a){return["Page <0/> of ",a("pageCount")]},"Page Number":"Page Number","Pagination":"Pagination","Password":"Password","Per Page":"Per Page","Please add {0} to populate this list":function(a){return["Please add ",a("0")," to populate this list"]},"Please add {0} {itemName} to populate this list":function(a){return["Please add ",a("0")," ",a("itemName")," to populate this list"]},"Portal Mode":"Portal Mode","Previous":"Previous","Primary Navigation":"Primary Navigation","Projects":"Projects","Remove {0} Access":function(a){return["Remove ",a("0")," Access"]},"Resources":"Resources","Save":"Save","Schedules":"Schedules","Search":"Search","Search text input":"Search text input","Select":"Select","Select Input":"Select Input","Select Users Or Teams":"Select Users Or Teams","Select a row to delete":"Select a row to delete","Select all":"Select all","Select items from list":"Select items from list","Select the Instance Groups for this Organization to run on.":"Select the Instance Groups for this Organization to run on.","Select {header}":function(a){return["Select ",a("header")]},"Selected":"Selected","Settings":"Settings","Sort":"Sort","Successful":"Successful","System":"System","System Settings":"System Settings","Team":"Team","Team Roles":"Team Roles","Teams":"Teams","Templates":"Templates","This field must not be blank":"This field must not be blank","This field must not exceed {max} characters":function(a){return["This field must not exceed ",a("max")," characters"]},"Toggle notification failure":"Toggle notification failure","Toggle notification success":"Toggle notification success","Use Default {label}":function(a){return["Use Default ",a("label")]},"User":"User","User Details":"User Details","User Interface":"User Interface","User Interface Settings":"User Interface Settings","User Roles":"User Roles","Username":"Username","Users":"Users","Views":"Views","Welcome to Ansible {brandName}! Please Sign In.":function(a){return["Welcome to Ansible ",a("brandName"),"! Please Sign In."]},"You do not have permission to delete the following {0}: {itemsUnableToDelete}":function(a){return["You do not have permission to delete the following ",a("0"),": ",a("itemsUnableToDelete")]},"You have been logged out.":"You have been logged out.","add {currentTab}":function(a){return["add ",a("currentTab")]},"adding {currentTab}":function(a){return["adding ",a("currentTab")]},"cancel delete":"cancel delete","confirm delete":"confirm delete","confirm removal of {currentTab}/cancel and go back to {currentTab} view.":function(a){return["confirm removal of ",a("currentTab"),"/cancel and go back to ",a("currentTab")," view."]},"delete {currentTab}":function(a){return["delete ",a("currentTab")]},"deleting {currentTab} association with orgs":function(a){return["deleting ",a("currentTab")," association with orgs"]},"edit view":"edit view","items":"items","of {pageCount}":function(a){return["of ",a("pageCount")]},"pages":"pages","per page":"per page","save/cancel and go back to view":"save/cancel and go back to view","save/cancel and go back to {currentTab} view":function(a){return["save/cancel and go back to ",a("currentTab")," view"]},"select organization {itemId}":function(a){return["select organization ",a("itemId")]},"{0}":function(a){return[a("0")]},"{0} List":function(a){return[a("0")," List"]},"{currentTab} detail view":function(a){return[a("currentTab")," detail view"]},"{itemMin} - {itemMax} of {count}":function(a){return[a("itemMin")," - ",a("itemMax")," of ",a("count")]}}}; \ No newline at end of file diff --git a/awx/ui_next/dist/index.html b/awx/ui_next/dist/index.html deleted file mode 100644 index 10a4088e0125..000000000000 --- a/awx/ui_next/dist/index.html +++ /dev/null @@ -1,7 +0,0 @@ - - - -
- - - diff --git a/awx/ui_next/docs/APP_ARCHITECTURE.md b/awx/ui_next/docs/APP_ARCHITECTURE.md new file mode 100644 index 000000000000..4be18f17d23e --- /dev/null +++ b/awx/ui_next/docs/APP_ARCHITECTURE.md @@ -0,0 +1,27 @@ +# Application Architecture + +## Local Storage Integration +The `useStorage` hook integrates with the browser's localStorage api. +It accepts a localStorage key as its only argument and returns a state +variable and setter function for that state variable. The hook enables +bidirectional data transfer between tabs via an event listener that +is registered with the Web Storage api. + + +![Sequence Diagram for useStorage](images/useStorage.png) + +The `useStorage` hook currently lives in the `AppContainer` component. It +can be relocated to a more general location should and if the need +ever arise + +## Session Expiration +Session timeout state is communicated to the client in the HTTP(S) +response headers. Every HTTP(S) response is intercepted to read the +session expiration time before being passed into the rest of the +application. A timeout date is computed from the intercepted HTTP(S) +headers and is pushed into local storage, where it can be read using +standard Web Storage apis or other utilities, such as `useStorage`. + + +![Sequence Diagram for session expiration](images/sessionExpiration.png) + diff --git a/awx/ui_next/docs/images/sessionExpiration.png b/awx/ui_next/docs/images/sessionExpiration.png new file mode 100644 index 000000000000..fa740c44a5a6 Binary files /dev/null and b/awx/ui_next/docs/images/sessionExpiration.png differ diff --git a/awx/ui_next/docs/images/useStorage.png b/awx/ui_next/docs/images/useStorage.png new file mode 100644 index 000000000000..712b47712162 Binary files /dev/null and b/awx/ui_next/docs/images/useStorage.png differ diff --git a/awx/ui_next/jest.config.js b/awx/ui_next/jest.config.js deleted file mode 100644 index fb24626f6f78..000000000000 --- a/awx/ui_next/jest.config.js +++ /dev/null @@ -1,42 +0,0 @@ -module.exports = { - collectCoverageFrom: [ - 'src/**/*.{js,jsx}', - 'testUtils/**/*.{js,jsx}' - ], - coveragePathIgnorePatterns: [ - '/src/locales', - 'index.js' - ], - moduleNameMapper: { - '\\.(css|scss|less)$': '/__mocks__/styleMock.js', - '^@api(.*)$': '/src/api$1', - '^@components(.*)$': '/src/components$1', - '^@contexts(.*)$': '/src/contexts$1', - '^@screens(.*)$': '/src/screens$1', - '^@util(.*)$': '/src/util$1', - '^@types(.*)$': '/src/types$1', - '^@testUtils(.*)$': '/testUtils$1', - }, - setupFiles: [ - '@nteract/mockument' - ], - setupFilesAfterEnv: ['/jest.setup.js'], - snapshotSerializers: [ - "enzyme-to-json/serializer" - ], - testMatch: [ - '/**/*.test.{js,jsx}' - ], - testEnvironment: 'jsdom', - testURL: 'http://127.0.0.1:3001', - transform: { - '^.+\\.(js|jsx)$': 'babel-jest', - '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '/__mocks__/fileMock.js', - }, - transformIgnorePatterns: [ - '[/\\\\]node_modules[/\\\\].+\\.(?!(axios)/)(js|jsx)$' - ], - watchPathIgnorePatterns: [ - '/node_modules' - ] -}; diff --git a/awx/ui_next/jest.setup.js b/awx/ui_next/jest.setup.js deleted file mode 100644 index 6dd8eff3bc56..000000000000 --- a/awx/ui_next/jest.setup.js +++ /dev/null @@ -1,11 +0,0 @@ -require('@babel/polyfill'); - -// eslint-disable-next-line import/prefer-default-export -export const asyncFlush = () => new Promise((resolve) => setImmediate(resolve)); - -const enzyme = require('enzyme'); -const Adapter = require('enzyme-adapter-react-16'); - -jest.setTimeout(5000 * 4); - -enzyme.configure({ adapter: new Adapter() }); diff --git a/awx/ui_next/package-lock.json b/awx/ui_next/package-lock.json index 57363aa6d9aa..a64e834f552b 100644 --- a/awx/ui_next/package-lock.json +++ b/awx/ui_next/package-lock.json @@ -1,1283 +1,1456 @@ { - "name": "awx-react", - "version": "1.0.0", + "name": "ui_next", + "version": "0.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { "@babel/code-frame": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", - "dev": true, + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", "requires": { - "@babel/highlight": "^7.0.0" + "@babel/highlight": "^7.10.4" } }, + "@babel/compat-data": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.12.7.tgz", + "integrity": "sha512-YaxPMGs/XIWtYqrdEOZOCPsVWfEoriXopnsz3/i7apYPXQ3698UFhS6dVT1KN5qOsWmVgw/FOrmQgpRaZayGsw==", + "dev": true + }, "@babel/core": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.2.0.tgz", - "integrity": "sha512-7pvAdC4B+iKjFFp9Ztj0QgBndJ++qaMeonT185wAqUnhipw8idm9Rv1UMyBuKtYjfl6ORNkgEgcsYLfHX/GpLw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.2.0", - "@babel/helpers": "^7.2.0", - "@babel/parser": "^7.2.0", - "@babel/template": "^7.1.2", - "@babel/traverse": "^7.1.6", - "@babel/types": "^7.2.0", - "convert-source-map": "^1.1.0", + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.10.tgz", + "integrity": "sha512-eTAlQKq65zHfkHZV0sIVODCPGVgoo1HdBlbSLi9CqOzuZanMv2ihzY+4paiKr1mH+XmYESMAmJ/dpZ68eN6d8w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.12.10", + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helpers": "^7.12.5", + "@babel/parser": "^7.12.10", + "@babel/template": "^7.12.7", + "@babel/traverse": "^7.12.10", + "@babel/types": "^7.12.10", + "convert-source-map": "^1.7.0", "debug": "^4.1.0", - "json5": "^2.1.0", - "lodash": "^4.17.10", - "resolve": "^1.3.2", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.19", "semver": "^5.4.1", "source-map": "^0.5.0" }, "dependencies": { - "@babel/generator": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.2.0.tgz", - "integrity": "sha512-BA75MVfRlFQG2EZgFYIwyT1r6xSkwfP2bdkY/kLZusEYWiJs4xCowab/alaEaT0wSvmVuXGqiefeBlP+7V1yKg==", - "dev": true, - "requires": { - "@babel/types": "^7.2.0", - "jsesc": "^2.5.1", - "lodash": "^4.17.10", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" - } - }, - "@babel/parser": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.2.0.tgz", - "integrity": "sha512-M74+GvK4hn1eejD9lZ7967qAwvqTZayQa3g10ag4s9uewgR7TKjeaT0YMyoq+gVfKYABiWZ4MQD701/t5e1Jhg==", - "dev": true - }, - "@babel/traverse": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.1.6.tgz", - "integrity": "sha512-CXedit6GpISz3sC2k2FsGCUpOhUqKdyL0lqNrImQojagnUMXf8hex4AxYFRuMkNGcvJX5QAFGzB5WJQmSv8SiQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.1.6", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.0.0", - "@babel/parser": "^7.1.6", - "@babel/types": "^7.1.6", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.10" - } - }, - "@babel/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.0.tgz", - "integrity": "sha512-b4v7dyfApuKDvmPb+O488UlGuR1WbwMXFsO/cyqMrnfvRAChZKJAYeeglWTjUO1b9UghKKgepAQM5tsvBJca6A==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.10", - "to-fast-properties": "^2.0.0" - } - }, "debug": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", - "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "globals": { - "version": "11.9.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.9.0.tgz", - "integrity": "sha512-5cJVtyXWH8PiJPVLZzzoIizXx944O4OmRro5MWKx5fT4MgcN7OfaMutPeaTdJCCURwbWdhhcCWcKIffPnmTzBg==", - "dev": true - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json5": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", - "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { - "minimist": "^1.2.0" + "ms": "2.1.2" } }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "minimist": { - "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true } } }, "@babel/generator": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.1.3.tgz", - "integrity": "sha512-ZoCZGcfIJFJuZBqxcY9OjC1KW2lWK64qrX1o4UYL3yshVhwKFYgzpWZ0vvtGMNJdTlvkw0W+HR1VnYN8q3QPFQ==", - "dev": true, + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.11.tgz", + "integrity": "sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA==", "requires": { - "@babel/types": "^7.1.3", + "@babel/types": "^7.12.11", "jsesc": "^2.5.1", - "lodash": "^4.17.10", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" - }, - "dependencies": { - "jsesc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", - "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } + "source-map": "^0.5.0" } }, "@babel/helper-annotate-as-pure": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz", - "integrity": "sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==", + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.10.tgz", + "integrity": "sha512-XplmVbC1n+KY6jL8/fgLVXXUauDIB+lD5+GsQEh6F6GBF1dq1qy4DP4yXWzDKcoqXB3X58t61e85Fitoww4JVQ==", "requires": { - "@babel/types": "^7.0.0" + "@babel/types": "^7.12.10" } }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz", - "integrity": "sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz", + "integrity": "sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==", "dev": true, "requires": { - "@babel/helper-explode-assignable-expression": "^7.1.0", - "@babel/types": "^7.0.0" + "@babel/helper-explode-assignable-expression": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-builder-react-jsx": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.0.0.tgz", - "integrity": "sha512-ebJ2JM6NAKW0fQEqN8hOLxK84RbRz9OkUhGS/Xd5u56ejMfVbayJ4+LykERZCOUM6faa6Fp3SZNX3fcT16MKHw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.10.4.tgz", + "integrity": "sha512-5nPcIZ7+KKDxT1427oBivl9V9YTal7qk0diccnh7RrcgrT/pGFOjgGw1dgryyx1GvHEpXVfoDF6Ak3rTiWh8Rg==", "dev": true, "requires": { - "@babel/types": "^7.0.0", - "esutils": "^2.0.0" + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/types": "^7.10.4" } }, - "@babel/helper-call-delegate": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.1.0.tgz", - "integrity": "sha512-YEtYZrw3GUK6emQHKthltKNZwszBcHK58Ygcis+gVUrF4/FmTVr5CCqQNSfmvg2y+YDEANyYoaLz/SHsnusCwQ==", + "@babel/helper-builder-react-jsx-experimental": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.12.11.tgz", + "integrity": "sha512-4oGVOekPI8dh9JphkPXC68iIuP6qp/RPbaPmorRmEFbRAHZjSqxPjqHudn18GVDPgCuFM/KdFXc63C17Ygfa9w==", "dev": true, "requires": { - "@babel/helper-hoist-variables": "^7.0.0", - "@babel/traverse": "^7.1.0", - "@babel/types": "^7.0.0" + "@babel/helper-annotate-as-pure": "^7.12.10", + "@babel/helper-module-imports": "^7.12.5", + "@babel/types": "^7.12.11" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.12.5.tgz", + "integrity": "sha512-+qH6NrscMolUlzOYngSBMIOQpKUGPPsc61Bu5W10mg84LxZ7cmvnBHzARKbDoFxVvqqAbj6Tg6N7bSrWSPXMyw==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.12.5", + "@babel/helper-validator-option": "^7.12.1", + "browserslist": "^4.14.5", + "semver": "^5.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.1.tgz", + "integrity": "sha512-hkL++rWeta/OVOBTRJc9a5Azh5mt5WgZUGAKMD8JM141YsE08K//bp1unBBieO6rUKkIPyUE0USQ30jAy3Sk1w==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-member-expression-to-functions": "^7.12.1", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-replace-supers": "^7.12.1", + "@babel/helper-split-export-declaration": "^7.10.4" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.7.tgz", + "integrity": "sha512-idnutvQPdpbduutvi3JVfEgcVIHooQnhvhx0Nk9isOINOIGYkZea1Pk2JlJRiUnMefrlvr0vkByATBY/mB4vjQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "regexpu-core": "^4.7.1" } }, "@babel/helper-define-map": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.1.0.tgz", - "integrity": "sha512-yPPcW8dc3gZLN+U1mhYV91QU3n5uTbx7DUdf8NnPbjS0RMwBuHi9Xt2MUgppmNz7CJxTBWsGczTiEp1CSOTPRg==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz", + "integrity": "sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.1.0", - "@babel/types": "^7.0.0", - "lodash": "^4.17.10" + "@babel/helper-function-name": "^7.10.4", + "@babel/types": "^7.10.5", + "lodash": "^4.17.19" } }, "@babel/helper-explode-assignable-expression": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz", - "integrity": "sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.12.1.tgz", + "integrity": "sha512-dmUwH8XmlrUpVqgtZ737tK88v07l840z9j3OEhCLwKTkjlvKpfqXVIZ0wpK3aeOxspwGrf/5AP5qLx4rO3w5rA==", "dev": true, "requires": { - "@babel/traverse": "^7.1.0", - "@babel/types": "^7.0.0" + "@babel/types": "^7.12.1" } }, "@babel/helper-function-name": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", - "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", - "dev": true, + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", + "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", "requires": { - "@babel/helper-get-function-arity": "^7.0.0", - "@babel/template": "^7.1.0", - "@babel/types": "^7.0.0" + "@babel/helper-get-function-arity": "^7.12.10", + "@babel/template": "^7.12.7", + "@babel/types": "^7.12.11" } }, "@babel/helper-get-function-arity": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", - "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", - "dev": true, + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", + "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", "requires": { - "@babel/types": "^7.0.0" + "@babel/types": "^7.12.10" } }, "@babel/helper-hoist-variables": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.0.0.tgz", - "integrity": "sha512-Ggv5sldXUeSKsuzLkddtyhyHe2YantsxWKNi7A+7LeD12ExRDWTRk29JCXpaHPAbMaIPZSil7n+lq78WY2VY7w==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz", + "integrity": "sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==", "dev": true, "requires": { - "@babel/types": "^7.0.0" + "@babel/types": "^7.10.4" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0.tgz", - "integrity": "sha512-avo+lm/QmZlv27Zsi0xEor2fKcqWG56D5ae9dzklpIaY7cQMK5N8VSpaNVPPagiqmy7LrEjK1IWdGMOqPu5csg==", + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.7.tgz", + "integrity": "sha512-DCsuPyeWxeHgh1Dus7APn7iza42i/qXqiFPWyBDdOFtvS581JQePsc1F/nD+fHrcswhLlRc2UpYS1NwERxZhHw==", "dev": true, "requires": { - "@babel/types": "^7.0.0" + "@babel/types": "^7.12.7" } }, "@babel/helper-module-imports": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz", - "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz", + "integrity": "sha512-SR713Ogqg6++uexFRORf/+nPXMmWIn80TALu0uaFb+iQIUoR7bOC7zBWyzBs5b3tBBJXuyD0cRu1F15GyzjOWA==", "requires": { - "@babel/types": "^7.0.0" + "@babel/types": "^7.12.5" } }, "@babel/helper-module-transforms": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.1.0.tgz", - "integrity": "sha512-0JZRd2yhawo79Rcm4w0LwSMILFmFXjugG3yqf+P/UsKsRS1mJCmMwwlHDlMg7Avr9LrvSpp4ZSULO9r8jpCzcw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz", + "integrity": "sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/helper-simple-access": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.0.0", - "@babel/template": "^7.1.0", - "@babel/types": "^7.0.0", - "lodash": "^4.17.10" + "@babel/helper-module-imports": "^7.12.1", + "@babel/helper-replace-supers": "^7.12.1", + "@babel/helper-simple-access": "^7.12.1", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/helper-validator-identifier": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.12.1", + "@babel/types": "^7.12.1", + "lodash": "^4.17.19" } }, "@babel/helper-optimise-call-expression": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz", - "integrity": "sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g==", + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.10.tgz", + "integrity": "sha512-4tpbU0SrSTjjt65UMWSrUOPZTsgvPgGG4S8QSTNHacKzpS51IVWGDj0yCwyeZND/i+LSN2g/O63jEXEWm49sYQ==", "dev": true, "requires": { - "@babel/types": "^7.0.0" + "@babel/types": "^7.12.10" } }, "@babel/helper-plugin-utils": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", - "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==" + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", + "dev": true }, - "@babel/helper-regex": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.0.0.tgz", - "integrity": "sha512-TR0/N0NDCcUIUEbqV6dCO+LptmmSQFQ7q70lfcEB4URsjD0E1HzicrwUH+ap6BAQ2jhCX9Q4UqZy4wilujWlkg==", + "@babel/helper-remap-async-to-generator": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.12.1.tgz", + "integrity": "sha512-9d0KQCRM8clMPcDwo8SevNs+/9a8yWVVmaE80FGJcEP8N1qToREmWEGnBn8BUlJhYRFz6fqxeRL1sl5Ogsed7A==", "dev": true, "requires": { - "lodash": "^4.17.10" + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-wrap-function": "^7.10.4", + "@babel/types": "^7.12.1" } }, - "@babel/helper-remap-async-to-generator": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz", - "integrity": "sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg==", + "@babel/helper-replace-supers": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.11.tgz", + "integrity": "sha512-q+w1cqmhL7R0FNzth/PLLp2N+scXEK/L2AHbXUyydxp828F4FEa5WcVoqui9vFRiHDQErj9Zof8azP32uGVTRA==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.0.0", - "@babel/helper-wrap-function": "^7.1.0", - "@babel/template": "^7.1.0", - "@babel/traverse": "^7.1.0", - "@babel/types": "^7.0.0" + "@babel/helper-member-expression-to-functions": "^7.12.7", + "@babel/helper-optimise-call-expression": "^7.12.10", + "@babel/traverse": "^7.12.10", + "@babel/types": "^7.12.11" } }, - "@babel/helper-replace-supers": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.1.0.tgz", - "integrity": "sha512-BvcDWYZRWVuDeXTYZWxekQNO5D4kO55aArwZOTFXw6rlLQA8ZaDicJR1sO47h+HrnCiDFiww0fSPV0d713KBGQ==", + "@babel/helper-simple-access": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz", + "integrity": "sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.0.0", - "@babel/helper-optimise-call-expression": "^7.0.0", - "@babel/traverse": "^7.1.0", - "@babel/types": "^7.0.0" + "@babel/types": "^7.12.1" } }, - "@babel/helper-simple-access": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz", - "integrity": "sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w==", + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz", + "integrity": "sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA==", "dev": true, "requires": { - "@babel/template": "^7.1.0", - "@babel/types": "^7.0.0" + "@babel/types": "^7.12.1" } }, "@babel/helper-split-export-declaration": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz", - "integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==", - "dev": true, + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz", + "integrity": "sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g==", "requires": { - "@babel/types": "^7.0.0" + "@babel/types": "^7.12.11" } }, + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==" + }, + "@babel/helper-validator-option": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.11.tgz", + "integrity": "sha512-TBFCyj939mFSdeX7U7DDj32WtzYY7fDcalgq8v3fBZMNOJQNn7nOYzMaUCiPxPYfCup69mtIpqlKgMZLvQ8Xhw==", + "dev": true + }, "@babel/helper-wrap-function": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.1.0.tgz", - "integrity": "sha512-R6HU3dete+rwsdAfrOzTlE9Mcpk4RjU3aX3gi9grtmugQY0u79X7eogUvfXA5sI81Mfq1cn6AgxihfN33STjJA==", + "version": "7.12.3", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.12.3.tgz", + "integrity": "sha512-Cvb8IuJDln3rs6tzjW3Y8UeelAOdnpB8xtQ4sme2MSZ9wOxrbThporC0y/EtE16VAtoyEfLM404Xr1e0OOp+ow==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.1.0", - "@babel/template": "^7.1.0", - "@babel/traverse": "^7.1.0", - "@babel/types": "^7.0.0" + "@babel/helper-function-name": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helpers": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.2.0.tgz", - "integrity": "sha512-Fr07N+ea0dMcMN8nFpuK6dUIT7/ivt9yKQdEEnjVS83tG2pHwPi03gYmk/tyuwONnZ+sY+GFFPlWGgCtW1hF9A==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.5.tgz", + "integrity": "sha512-lgKGMQlKqA8meJqKsW6rUnc4MdUk35Ln0ATDqdM1a/UpARODdI4j5Y5lVfUScnSNkJcdCRAaWkspykNoFg9sJA==", "dev": true, "requires": { - "@babel/template": "^7.1.2", - "@babel/traverse": "^7.1.5", - "@babel/types": "^7.2.0" - }, - "dependencies": { - "@babel/generator": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.2.0.tgz", - "integrity": "sha512-BA75MVfRlFQG2EZgFYIwyT1r6xSkwfP2bdkY/kLZusEYWiJs4xCowab/alaEaT0wSvmVuXGqiefeBlP+7V1yKg==", - "dev": true, - "requires": { - "@babel/types": "^7.2.0", - "jsesc": "^2.5.1", - "lodash": "^4.17.10", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" - } - }, - "@babel/parser": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.2.0.tgz", - "integrity": "sha512-M74+GvK4hn1eejD9lZ7967qAwvqTZayQa3g10ag4s9uewgR7TKjeaT0YMyoq+gVfKYABiWZ4MQD701/t5e1Jhg==", - "dev": true - }, - "@babel/traverse": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.1.6.tgz", - "integrity": "sha512-CXedit6GpISz3sC2k2FsGCUpOhUqKdyL0lqNrImQojagnUMXf8hex4AxYFRuMkNGcvJX5QAFGzB5WJQmSv8SiQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.1.6", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.0.0", - "@babel/parser": "^7.1.6", - "@babel/types": "^7.1.6", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.10" - } - }, - "@babel/types": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.2.0.tgz", - "integrity": "sha512-b4v7dyfApuKDvmPb+O488UlGuR1WbwMXFsO/cyqMrnfvRAChZKJAYeeglWTjUO1b9UghKKgepAQM5tsvBJca6A==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.10", - "to-fast-properties": "^2.0.0" - } - }, - "debug": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", - "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "globals": { - "version": "11.9.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.9.0.tgz", - "integrity": "sha512-5cJVtyXWH8PiJPVLZzzoIizXx944O4OmRro5MWKx5fT4MgcN7OfaMutPeaTdJCCURwbWdhhcCWcKIffPnmTzBg==", - "dev": true - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.12.5", + "@babel/types": "^7.12.5" } }, "@babel/highlight": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", - "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", - "dev": true, + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", "requires": { + "@babel/helper-validator-identifier": "^7.10.4", "chalk": "^2.0.0", - "esutils": "^2.0.2", "js-tokens": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } } }, "@babel/parser": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.1.3.tgz", - "integrity": "sha512-gqmspPZOMW3MIRb9HlrnbZHXI1/KHTOroBwN1NcLL6pWxzqzEKGvRTq0W/PxS45OtQGbaFikSQpkS5zbnsQm2w==", - "dev": true + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", + "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==" }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.1.0.tgz", - "integrity": "sha512-Fq803F3Jcxo20MXUSDdmZZXrPe6BWyGcWBPPNB/M7WaUYESKDeKMOGIxEzQOjGSmW/NWb6UaPZrtTB2ekhB/ew==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.1.tgz", + "integrity": "sha512-d+/o30tJxFxrA1lhzJqiUcEJdI6jKlNregCv5bASeGf2Q4MXmnwH7viDo7nhx1/ohf09oaH8j1GVYG/e3Yqk6A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-remap-async-to-generator": "^7.1.0", - "@babel/plugin-syntax-async-generators": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.12.1", + "@babel/plugin-syntax-async-generators": "^7.8.0" } }, "@babel/plugin-proposal-class-properties": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.1.0.tgz", - "integrity": "sha512-/PCJWN+CKt5v1xcGn4vnuu13QDoV+P7NcICP44BoonAJoPSGwVkgrXihFIQGiEjjPlUDBIw1cM7wYFLARS2/hw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.12.1.tgz", + "integrity": "sha512-cKp3dlQsFsEs5CWKnN7BnSHOd0EOW8EKpEjkoz1pO2E5KzIDNV9Ros1b0CnmbVgAGXJubOYVBOGCT1OmJwOI7w==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-member-expression-to-functions": "^7.0.0", - "@babel/helper-optimise-call-expression": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-replace-supers": "^7.1.0", - "@babel/plugin-syntax-class-properties": "^7.0.0" + "@babel/helper-create-class-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" } }, - "@babel/plugin-proposal-json-strings": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.0.0.tgz", - "integrity": "sha512-kfVdUkIAGJIVmHmtS/40i/fg/AGnw/rsZBCaapY5yjeO5RA9m165Xbw9KMOu2nqXP5dTFjEjHdfNdoVcHv133Q==", + "@babel/plugin-proposal-decorators": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.8.3.tgz", + "integrity": "sha512-e3RvdvS4qPJVTe288DlXjwKflpfy1hr0j5dz5WpIYYeP7vQZg2WfAEIp8k5/Lwis/m5REXEteIz6rrcDtXXG7w==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-json-strings": "^7.0.0" + "@babel/helper-create-class-features-plugin": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-decorators": "^7.8.3" } }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.0.0.tgz", - "integrity": "sha512-14fhfoPcNu7itSen7Py1iGN0gEm87hX/B+8nZPqkdmANyyYWYMY2pjA3r8WXbWVKMzfnSNS0xY8GVS0IjXi/iw==", + "@babel/plugin-proposal-dynamic-import": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.12.1.tgz", + "integrity": "sha512-a4rhUSZFuq5W8/OO8H7BL5zspjnc1FLd9hlOxIK/f7qG4a0qsqk8uvF/ywgBA8/OmjsapjpvaEOYItfGG1qIvQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-object-rest-spread": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-dynamic-import": "^7.8.0" } }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.0.0.tgz", - "integrity": "sha512-JPqAvLG1s13B/AuoBjdBYvn38RqW6n1TzrQO839/sIpqLpbnXKacsAgpZHzLD83Sm8SDXMkkrAvEnJ25+0yIpw==", + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.1.tgz", + "integrity": "sha512-6CThGf0irEkzujYS5LQcjBx8j/4aQGiVv7J9+2f7pGfxqyKh3WnmVJYW3hdrQjyksErMGBPQrCnHfOtna+WLbw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" } }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.0.0.tgz", - "integrity": "sha512-tM3icA6GhC3ch2SkmSxv7J/hCWKISzwycub6eGsDrFDgukD4dZ/I+x81XgW0YslS6mzNuQ1Cbzh5osjIMgepPQ==", + "@babel/plugin-proposal-json-strings": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.12.1.tgz", + "integrity": "sha512-GoLDUi6U9ZLzlSda2Df++VSqDJg3CG+dR0+iWsv6XRw1rEq+zwt4DirM9yrxW6XWaTpmai1cWJLMfM8qQJf+yw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-regex": "^7.0.0", - "regexpu-core": "^4.2.0" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.0" } }, - "@babel/plugin-syntax-async-generators": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.0.0.tgz", - "integrity": "sha512-im7ged00ddGKAjcZgewXmp1vxSZQQywuQXe2B1A7kajjZmDeY/ekMPmWr9zJgveSaQH0k7BcGrojQhcK06l0zA==", + "@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.12.1.tgz", + "integrity": "sha512-k8ZmVv0JU+4gcUGeCDZOGd0lCIamU/sMtIiX3UWnUc5yzgq6YUGyEolNYD+MLYKfSzgECPcqetVcJP9Afe/aCA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" } }, - "@babel/plugin-syntax-class-properties": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.0.0.tgz", - "integrity": "sha512-cR12g0Qzn4sgkjrbrzWy2GE7m9vMl/sFkqZ3gIpAQdrvPDnLM8180i+ANDFIXfjHo9aqp0ccJlQ0QNZcFUbf9w==", + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.12.1.tgz", + "integrity": "sha512-nZY0ESiaQDI1y96+jk6VxMOaL4LPo/QDHBqL+SF3/vl6dHkTwHlOI8L4ZwuRBHgakRBw5zsVylel7QPbbGuYgg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" } }, - "@babel/plugin-syntax-json-strings": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.0.0.tgz", - "integrity": "sha512-UlSfNydC+XLj4bw7ijpldc1uZ/HB84vw+U6BTuqMdIEmz/LDe63w/GHtpQMdXWdqQZFeAI9PjnHe/vDhwirhKA==", + "@babel/plugin-proposal-numeric-separator": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.7.tgz", + "integrity": "sha512-8c+uy0qmnRTeukiGsjLGy6uVs/TFjJchGXUeBqlG4VWYOdJWkhhVPdQ3uHwbmalfJwv2JsV0qffXP4asRfL2SQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" } }, - "@babel/plugin-syntax-jsx": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.0.0.tgz", - "integrity": "sha512-PdmL2AoPsCLWxhIr3kG2+F9v4WH06Q3z+NoGVpQgnUNGcagXHq5sB3OXxkSahKq9TLdNMN/AJzFYSOo8UKDMHg==", + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz", + "integrity": "sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-transform-parameters": "^7.12.1" } }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.0.0.tgz", - "integrity": "sha512-5A0n4p6bIiVe5OvQPxBnesezsgFJdHhSs3uFSvaPdMqtsovajLZ+G2vZyvNe10EzJBWWo3AcHGKhAFUxqwp2dw==", + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.12.1.tgz", + "integrity": "sha512-hFvIjgprh9mMw5v42sJWLI1lzU5L2sznP805zeT6rySVRA0Y18StRhDqhSxlap0oVgItRsB6WSROp4YnJTJz0g==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" } }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.0.0.tgz", - "integrity": "sha512-Wc+HVvwjcq5qBg1w5RG9o9RVzmCaAg/Vp0erHCKpAYV8La6I94o4GQAmFYNmkzoMO6gzoOSulpKeSSz6mPEoZw==", + "@babel/plugin-proposal-optional-chaining": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.12.7.tgz", + "integrity": "sha512-4ovylXZ0PWmwoOvhU2vhnzVNnm88/Sm9nx7V8BPgMvAzn5zDou3/Awy0EjglyubVHasJj+XCEkr/r1X3P5elCA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", + "@babel/plugin-syntax-optional-chaining": "^7.8.0" } }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.0.0.tgz", - "integrity": "sha512-2EZDBl1WIO/q4DIkIp4s86sdp4ZifL51MoIviLY/gG/mLSuOIEg7J8o6mhbxOTvUJkaN50n+8u41FVsr5KLy/w==", + "@babel/plugin-proposal-private-methods": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.12.1.tgz", + "integrity": "sha512-mwZ1phvH7/NHK6Kf8LP7MYDogGV+DKB1mryFOEwx5EBNQrosvIczzZFTUmWaeujd5xT6G1ELYWUz3CutMhjE1w==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-create-class-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" } }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.1.0.tgz", - "integrity": "sha512-rNmcmoQ78IrvNCIt/R9U+cixUHeYAzgusTFgIAv+wQb9HJU4szhpDD6e5GCACmj/JP5KxuCwM96bX3L9v4ZN/g==", + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.1.tgz", + "integrity": "sha512-MYq+l+PvHuw/rKUz1at/vb6nCnQ2gmJBNaM62z0OgH7B2W1D9pvkpYtlti9bGtizNIU1K3zm4bZF9F91efVY0w==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-remap-async-to-generator": "^7.1.0" + "@babel/helper-create-regexp-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz", + "integrity": "sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-decorators": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.12.1.tgz", + "integrity": "sha512-ir9YW5daRrTYiy9UJ2TzdNIJEZu8KclVzDcfSt4iEmOtwQ4llPtWInNKJyKnVXp1vE4bbVd5S31M/im3mYMO1w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-flow": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.12.1.tgz", + "integrity": "sha512-1lBLLmtxrwpm4VKmtVFselI/P3pX+G63fAtUUt6b2Nzgao77KNDwyuRt90Mj2/9pKobtt68FdvjfqohZjg/FCA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz", + "integrity": "sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz", + "integrity": "sha512-i7ooMZFS+a/Om0crxZodrTzNEPJHZrlMVGMTEpFAj6rYY/bKCddB0Dk/YxfPuYXOopuhKk/e1jV6h+WUU9XN3A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.12.1.tgz", + "integrity": "sha512-UZNEcCY+4Dp9yYRCAHrHDU+9ZXLYaY9MgBXSRLkB9WjYFRR6quJBumfVrEkUxrePPBwFcpWfNKXqVRQQtm7mMA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.12.1.tgz", + "integrity": "sha512-5QB50qyN44fzzz4/qxDPQMBCTHgxg3n0xRBLJUmBlLoU/sFvxVWGZF/ZUfMVDQuJUKXaBhbupxIzIfZ6Fwk/0A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.12.1.tgz", + "integrity": "sha512-SDtqoEcarK1DFlRJ1hHRY5HvJUj5kX4qmtpMAm2QnhOlyuMC4TMdCRgW6WXpv93rZeYNeLP22y8Aq2dbcDRM1A==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.12.1" } }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.0.0.tgz", - "integrity": "sha512-AOBiyUp7vYTqz2Jibe1UaAWL0Hl9JUXEgjFvvvcSc9MVDItv46ViXFw2F7SVt1B5k+KWjl44eeXOAk3UDEaJjQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.1.tgz", + "integrity": "sha512-5OpxfuYnSgPalRpo8EWGPzIYf0lHBWORCkj5M0oLBwHdlux9Ri36QqGW3/LR13RSVOAoUUMzoPI/jpE4ABcHoA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.0.0.tgz", - "integrity": "sha512-GWEMCrmHQcYWISilUrk9GDqH4enf3UmhOEbNbNrlNAX1ssH3MsS1xLOS6rdjRVPgA7XXVPn87tRkdTEoA/dxEg==", + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.11.tgz", + "integrity": "sha512-atR1Rxc3hM+VPg/NvNvfYw0npQEAcHuJ+MGZnFn6h3bo+1U3BWXMdFMlvVRApBTWKQMX7SOwRJZA5FBF/JQbvA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "lodash": "^4.17.10" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-classes": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.1.0.tgz", - "integrity": "sha512-rNaqoD+4OCBZjM7VaskladgqnZ1LO6o2UxuWSDzljzW21pN1KXkB7BstAVweZdxQkHAujps5QMNOTWesBciKFg==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.0.0", - "@babel/helper-define-map": "^7.1.0", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-optimise-call-expression": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-replace-supers": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.0.0", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.12.1.tgz", + "integrity": "sha512-/74xkA7bVdzQTBeSUhLLJgYIcxw/dpEpCdRDiHgPJ3Mv6uC11UhjpOhl72CgqbBCmt1qtssCyB2xnJm1+PFjog==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-define-map": "^7.10.4", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.12.1", + "@babel/helper-split-export-declaration": "^7.10.4", "globals": "^11.1.0" - }, - "dependencies": { - "globals": { - "version": "11.8.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.8.0.tgz", - "integrity": "sha512-io6LkyPVuzCHBSQV9fmOwxZkUk6nIaGmxheLDgmuFv89j0fm2aqDbIXKAGfzCMHqz3HLF2Zf8WSG6VqMh2qFmA==", - "dev": true - } } }, "@babel/plugin-transform-computed-properties": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.0.0.tgz", - "integrity": "sha512-ubouZdChNAv4AAWAgU7QKbB93NU5sHwInEWfp+/OzJKA02E6Woh9RVoX4sZrbRwtybky/d7baTUqwFx+HgbvMA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.12.1.tgz", + "integrity": "sha512-vVUOYpPWB7BkgUWPo4C44mUQHpTZXakEqFjbv8rQMg7TC6S6ZhGZ3otQcRH6u7+adSlE5i0sp63eMC/XGffrzg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-destructuring": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.1.3.tgz", - "integrity": "sha512-Mb9M4DGIOspH1ExHOUnn2UUXFOyVTiX84fXCd+6B5iWrQg/QMeeRmSwpZ9lnjYLSXtZwiw80ytVMr3zue0ucYw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.12.1.tgz", + "integrity": "sha512-fRMYFKuzi/rSiYb2uRLiUENJOKq4Gnl+6qOv5f8z0TZXg3llUwUhsNNwrwaT/6dUhJTzNpBr+CUvEWBtfNY1cw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.0.0.tgz", - "integrity": "sha512-00THs8eJxOJUFVx1w8i1MBF4XH4PsAjKjQ1eqN/uCH3YKwP21GCKfrn6YZFZswbOk9+0cw1zGQPHVc1KBlSxig==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.1.tgz", + "integrity": "sha512-B2pXeRKoLszfEW7J4Hg9LoFaWEbr/kzo3teWHmtFCszjRNa/b40f9mfeqZsIDLLt/FjwQ6pz/Gdlwy85xNckBA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-regex": "^7.0.0", - "regexpu-core": "^4.1.3" + "@babel/helper-create-regexp-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.0.0.tgz", - "integrity": "sha512-w2vfPkMqRkdxx+C71ATLJG30PpwtTpW7DDdLqYt2acXU7YjztzeWW2Jk1T6hKqCLYCcEA5UQM/+xTAm+QCSnuQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.1.tgz", + "integrity": "sha512-iRght0T0HztAb/CazveUpUQrZY+aGKKaWXMJ4uf9YJtqxSUe09j3wteztCUDRHs+SRAL7yMuFqUsLoAKKzgXjw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.1.0.tgz", - "integrity": "sha512-uZt9kD1Pp/JubkukOGQml9tqAeI8NkE98oZnHZ2qHRElmeKCodbTZgOEUtujSCSLhHSBWbzNiFSDIMC4/RBTLQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.1.tgz", + "integrity": "sha512-7tqwy2bv48q+c1EHbXK0Zx3KXd2RVQp6OC7PbwFNt/dPTAV3Lu5sWtWuAj8owr5wqtWnqHfl2/mJlUmqkChKug==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-flow-strip-types": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.9.0.tgz", + "integrity": "sha512-7Qfg0lKQhEHs93FChxVLAvhBshOPQDtJUTVHr/ZwQNRccCm4O9D79r9tVSoV8iNwjP1YgfD+e/fgHcPkN1qEQg==", "dev": true, "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.1.0", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-flow": "^7.8.3" } }, "@babel/plugin-transform-for-of": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.0.0.tgz", - "integrity": "sha512-TlxKecN20X2tt2UEr2LNE6aqA0oPeMT1Y3cgz8k4Dn1j5ObT8M3nl9aA37LLklx0PBZKETC9ZAf9n/6SujTuXA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.12.1.tgz", + "integrity": "sha512-Zaeq10naAsuHo7heQvyV0ptj4dlZJwZgNAtBYBnu5nNKJoW62m0zKcIEyVECrUKErkUkg6ajMy4ZfnVZciSBhg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-function-name": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.1.0.tgz", - "integrity": "sha512-VxOa1TMlFMtqPW2IDYZQaHsFrq/dDoIjgN098NowhexhZcz3UGlvPgZXuE1jEvNygyWyxRacqDpCZt+par1FNg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.1.tgz", + "integrity": "sha512-JF3UgJUILoFrFMEnOJLJkRHSk6LUSXLmEFsA23aR2O5CSLUxbeUX1IZ1YQ7Sn0aXb601Ncwjx73a+FVqgcljVw==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-literals": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.0.0.tgz", - "integrity": "sha512-1NTDBWkeNXgpUcyoVFxbr9hS57EpZYXpje92zv0SUzjdu3enaRwF/l3cmyRnXLtIdyJASyiS6PtybK+CgKf7jA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.1.tgz", + "integrity": "sha512-+PxVGA+2Ag6uGgL0A5f+9rklOnnMccwEBzwYFL3EUaKuiyVnUipyXncFcfjSkbimLrODoqki1U9XxZzTvfN7IQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.1.tgz", + "integrity": "sha512-1sxePl6z9ad0gFMB9KqmYofk34flq62aqMt9NqliS/7hPEpURUCMbyHXrMPlo282iY7nAvUB1aQd5mg79UD9Jg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-modules-amd": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.1.0.tgz", - "integrity": "sha512-wt8P+xQ85rrnGNr2x1iV3DW32W8zrB6ctuBkYBbf5/ZzJY99Ob4MFgsZDFgczNU76iy9PWsy4EuxOliDjdKw6A==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.12.1.tgz", + "integrity": "sha512-tDW8hMkzad5oDtzsB70HIQQRBiTKrhfgwC/KkJeGsaNFTdWhKNt/BiE8c5yj19XiGyrxpbkOfH87qkNg1YGlOQ==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.1.0", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.1.0.tgz", - "integrity": "sha512-wtNwtMjn1XGwM0AXPspQgvmE6msSJP15CX2RVfpTSTNPLhKhaOjaIfBaVfj4iUZ/VrFSodcFedwtPg/NxwQlPA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.12.1.tgz", + "integrity": "sha512-dY789wq6l0uLY8py9c1B48V8mVL5gZh/+PQ5ZPrylPYsnAvnEMjqsUXkuoDVPeVK+0VyGar+D08107LzDQ6pag==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.1.0", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-simple-access": "^7.1.0" + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-simple-access": "^7.12.1", + "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.1.3.tgz", - "integrity": "sha512-PvTxgjxQAq4pvVUZF3mD5gEtVDuId8NtWkJsZLEJZMZAW3TvgQl1pmydLLN1bM8huHFVVU43lf0uvjQj9FRkKw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.12.1.tgz", + "integrity": "sha512-Hn7cVvOavVh8yvW6fLwveFqSnd7rbQN3zJvoPNyNaQSvgfKmDBO9U1YL9+PCXGRlZD9tNdWTy5ACKqMuzyn32Q==", "dev": true, "requires": { - "@babel/helper-hoist-variables": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-hoist-variables": "^7.10.4", + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-validator-identifier": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-umd": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.1.0.tgz", - "integrity": "sha512-enrRtn5TfRhMmbRwm7F8qOj0qEYByqUvTttPEGimcBH4CJHphjyK1Vg7sdU7JjeEmgSpM890IT/efS2nMHwYig==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.12.1.tgz", + "integrity": "sha512-aEIubCS0KHKM0zUos5fIoQm+AZUMt1ZvMpqz0/H5qAQ7vWylr9+PLYurT+Ic7ID/bKLd4q8hDovaG3Zch2uz5Q==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.1.tgz", + "integrity": "sha512-tB43uQ62RHcoDp9v2Nsf+dSM8sbNodbEicbQNA53zHz8pWUhsgHSJCGpt7daXxRydjb0KnfmB+ChXOv3oADp1Q==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.1.0", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-create-regexp-features-plugin": "^7.12.1" } }, "@babel/plugin-transform-new-target": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.0.0.tgz", - "integrity": "sha512-yin069FYjah+LbqfGeTfzIBODex/e++Yfa0rH0fpfam9uTbuEeEOx5GLGr210ggOV77mVRNoeqSYqeuaqSzVSw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.1.tgz", + "integrity": "sha512-+eW/VLcUL5L9IvJH7rT1sT0CzkdUTvPrXC2PXTn/7z7tXLBuKvezYbGdxD5WMRoyvyaujOq2fWoKl869heKjhw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-object-super": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.1.0.tgz", - "integrity": "sha512-/O02Je1CRTSk2SSJaq0xjwQ8hG4zhZGNjE8psTsSNPXyLRCODv7/PBozqT5AmQMzp7MI3ndvMhGdqp9c96tTEw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.1.tgz", + "integrity": "sha512-AvypiGJH9hsquNUn+RXVcBdeE3KHPZexWRdimhuV59cSoOt5kFBmqlByorAeUlGG2CJWd0U+4ZtNKga/TB0cAw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-replace-supers": "^7.1.0" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.12.1" } }, "@babel/plugin-transform-parameters": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.1.0.tgz", - "integrity": "sha512-vHV7oxkEJ8IHxTfRr3hNGzV446GAb+0hgbA7o/0Jd76s+YzccdWuTU296FOCOl/xweU4t/Ya4g41yWz80RFCRw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.12.1.tgz", + "integrity": "sha512-xq9C5EQhdPK23ZeCdMxl8bbRnAgHFrw5EOC3KJUsSylZqdkCaFEXxGSBuTSObOpiiHHNyb82es8M1QYgfQGfNg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.1.tgz", + "integrity": "sha512-6MTCR/mZ1MQS+AwZLplX4cEySjCpnIF26ToWo942nqn8hXSm7McaHQNeGx/pt7suI1TWOWMfa/NgBhiqSnX0cQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-react-constant-elements": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.12.1.tgz", + "integrity": "sha512-KOHd0tIRLoER+J+8f9DblZDa1fLGPwaaN1DI1TVHuQFOpjHV22C3CUB3obeC4fexHY9nx+fH0hQNvLFFfA1mxA==", "dev": true, "requires": { - "@babel/helper-call-delegate": "^7.1.0", - "@babel/helper-get-function-arity": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-react-display-name": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.0.0.tgz", - "integrity": "sha512-BX8xKuQTO0HzINxT6j/GiCwoJB0AOMs0HmLbEnAvcte8U8rSkNa/eSCAY+l1OA4JnCVq2jw2p6U8QQryy2fTPg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.12.1.tgz", + "integrity": "sha512-cAzB+UzBIrekfYxyLlFqf/OagTvHLcVBb5vpouzkYkBclRPraiygVnafvAoipErZLI8ANv8Ecn6E/m5qPXD26w==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-react-jsx": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.0.0.tgz", - "integrity": "sha512-0TMP21hXsSUjIQJmu/r7RiVxeFrXRcMUigbKu0BLegJK9PkYodHstaszcig7zxXfaBji2LYUdtqIkHs+hgYkJQ==", + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.12.11.tgz", + "integrity": "sha512-5nWOw6mTylaFU72BdZfa0dP1HsGdY3IMExpxn8LBE8dNmkQjB+W+sR+JwIdtbzkPvVuFviT3zyNbSUkuVTVxbw==", "dev": true, "requires": { - "@babel/helper-builder-react-jsx": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-jsx": "^7.0.0" + "@babel/helper-builder-react-jsx": "^7.10.4", + "@babel/helper-builder-react-jsx-experimental": "^7.12.11", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-jsx": "^7.12.1" + } + }, + "@babel/plugin-transform-react-jsx-development": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.12.11.tgz", + "integrity": "sha512-5MvsGschXeXJsbzQGR/BH89ATMzCsM7rx95n+R7/852cGoK2JgMbacDw/A9Pmrfex4tArdMab0L5SBV4SB/Nxg==", + "dev": true, + "requires": { + "@babel/helper-builder-react-jsx-experimental": "^7.12.11", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-jsx": "^7.12.1" } }, "@babel/plugin-transform-react-jsx-self": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.0.0.tgz", - "integrity": "sha512-pymy+AK12WO4safW1HmBpwagUQRl9cevNX+82AIAtU1pIdugqcH+nuYP03Ja6B+N4gliAaKWAegIBL/ymALPHA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.12.1.tgz", + "integrity": "sha512-FbpL0ieNWiiBB5tCldX17EtXgmzeEZjFrix72rQYeq9X6nUK38HCaxexzVQrZWXanxKJPKVVIU37gFjEQYkPkA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-jsx": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-react-jsx-source": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.0.0.tgz", - "integrity": "sha512-OSeEpFJEH5dw/TtxTg4nijl4nHBbhqbKL94Xo/Y17WKIf2qJWeIk/QeXACF19lG1vMezkxqruwnTjVizaW7u7w==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.12.1.tgz", + "integrity": "sha512-keQ5kBfjJNRc6zZN1/nVHCd6LLIHq4aUKcVnvE/2l+ZZROSbqoiGFRtT5t3Is89XJxBQaP7NLZX2jgGHdZvvFQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-jsx": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-react-pure-annotations": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.12.1.tgz", + "integrity": "sha512-RqeaHiwZtphSIUZ5I85PEH19LOSzxfuEazoY7/pWASCAIBuATQzpSVD+eT6MebeeZT2F4eSL0u4vw6n4Nm0Mjg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-regenerator": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.0.0.tgz", - "integrity": "sha512-sj2qzsEx8KDVv1QuJc/dEfilkg3RRPvPYx/VnKLtItVQRWt1Wqf5eVCOLZm29CiGFfYYsA3VPjfizTCV0S0Dlw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.1.tgz", + "integrity": "sha512-gYrHqs5itw6i4PflFX3OdBPMQdPbF4bj2REIUxlMRUFk0/ZOAIpDFuViuxPjUL7YC8UPnf+XG7/utJvqXdPKng==", + "dev": true, + "requires": { + "regenerator-transform": "^0.14.2" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.1.tgz", + "integrity": "sha512-pOnUfhyPKvZpVyBHhSBoX8vfA09b7r00Pmm1sH+29ae2hMTKVmSp4Ztsr8KBKjLjx17H0eJqaRC3bR2iThM54A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-runtime": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.9.0.tgz", + "integrity": "sha512-pUu9VSf3kI1OqbWINQ7MaugnitRss1z533436waNXp+0N3ur3zfut37sXiQMxkuCF4VUjwZucen/quskCh7NHw==", "dev": true, "requires": { - "regenerator-transform": "^0.13.3" + "@babel/helper-module-imports": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3", + "resolve": "^1.8.1", + "semver": "^5.5.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.0.0.tgz", - "integrity": "sha512-g/99LI4vm5iOf5r1Gdxq5Xmu91zvjhEG5+yZDJW268AZELAu4J1EiFLnkSG3yuUsZyOipVOVUKoGPYwfsTymhw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.1.tgz", + "integrity": "sha512-GFZS3c/MhX1OusqB1MZ1ct2xRzX5ppQh2JU1h2Pnfk88HtFTM+TWQqJNfwkmxtPQtb/s1tk87oENfXJlx7rSDw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-spread": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.0.0.tgz", - "integrity": "sha512-L702YFy2EvirrR4shTj0g2xQp7aNwZoWNCkNu2mcoU0uyzMl0XRwDSwzB/xp6DSUFiBmEXuyAyEN16LsgVqGGQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.12.1.tgz", + "integrity": "sha512-vuLp8CP0BE18zVYjsEBZ5xoCecMK6LBMMxYzJnh01rxQRvhNhH1csMMmBfNo5tGpGO+NhdSNW2mzIvBu3K1fng==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1" } }, "@babel/plugin-transform-sticky-regex": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.0.0.tgz", - "integrity": "sha512-LFUToxiyS/WD+XEWpkx/XJBrUXKewSZpzX68s+yEOtIbdnsRjpryDw9U06gYc6klYEij/+KQVRnD3nz3AoKmjw==", + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.7.tgz", + "integrity": "sha512-VEiqZL5N/QvDbdjfYQBhruN0HYjSPjC4XkeqW4ny/jNtH9gcbgaqBIXYEZCNnESMAGs0/K/R7oFGMhOyu/eIxg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-regex": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-template-literals": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.0.0.tgz", - "integrity": "sha512-vA6rkTCabRZu7Nbl9DfLZE1imj4tzdWcg5vtdQGvj+OH9itNNB6hxuRMHuIY8SGnEt1T9g5foqs9LnrHzsqEFg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.12.1.tgz", + "integrity": "sha512-b4Zx3KHi+taXB1dVRBhVJtEPi9h1THCeKmae2qP0YdUHIFhVjtpqqNfxeVAa1xeHVhAy4SbHxEwx5cltAu5apw==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.0.0.tgz", - "integrity": "sha512-1r1X5DO78WnaAIvs5uC48t41LLckxsYklJrZjNKcevyz83sF2l4RHbw29qrCPr/6ksFsdfRpT/ZgxNWHXRnffg==", + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.10.tgz", + "integrity": "sha512-JQ6H8Rnsogh//ijxspCjc21YPd3VLVoYtAwv3zQmqAt8YGYUtdo5usNhdl4b9/Vir2kPFZl6n1h0PfUz4hJhaA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-typescript": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.12.1.tgz", + "integrity": "sha512-VrsBByqAIntM+EYMqSm59SiMEf7qkmI9dqMt6RbD/wlwueWmYcI0FFK5Fj47pP6DRZm+3teXjosKlwcZJ5lIMw==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-typescript": "^7.12.1" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.1.tgz", + "integrity": "sha512-I8gNHJLIc7GdApm7wkVnStWssPNbSRMPtgHdmH3sRM1zopz09UWPS4x5V4n1yz/MIWTVnJ9sp6IkuXdWM4w+2Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.0.0.tgz", - "integrity": "sha512-uJBrJhBOEa3D033P95nPHu3nbFwFE9ZgXsfEitzoIXIwqAZWk7uXcg06yFKXz9FSxBH5ucgU/cYdX0IV8ldHKw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.1.tgz", + "integrity": "sha512-SqH4ClNngh/zGwHZOOQMTD+e8FGWexILV+ePMyiDJttAWRh5dhDL8rcl5lSgU3Huiq6Zn6pWTMvdPAb21Dwdyg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-regex": "^7.0.0", - "regexpu-core": "^4.1.3" + "@babel/helper-create-regexp-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/polyfill": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.0.0.tgz", - "integrity": "sha512-dnrMRkyyr74CRelJwvgnnSUDh2ge2NCTyHVwpOdvRMHtJUyxLtMAfhBN3s64pY41zdw0kgiLPh6S20eb1NcX6Q==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.12.1.tgz", + "integrity": "sha512-X0pi0V6gxLi6lFZpGmeNa4zxtwEmCs42isWLNjZZDE0Y8yVfgu0T2OAHlzBbdYlqbW/YXVvoBHpATEM+goCj8g==", "dev": true, "requires": { - "core-js": "^2.5.7", - "regenerator-runtime": "^0.11.1" + "core-js": "^2.6.5", + "regenerator-runtime": "^0.13.4" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", + "dev": true + } } }, "@babel/preset-env": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.1.0.tgz", - "integrity": "sha512-ZLVSynfAoDHB/34A17/JCZbyrzbQj59QC1Anyueb4Bwjh373nVPq5/HMph0z+tCmcDjXDe+DlKQq9ywQuvWrQg==", + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.12.11.tgz", + "integrity": "sha512-j8Tb+KKIXKYlDBQyIOy4BLxzv1NUOwlHfZ74rvW+Z0Gp4/cI2IMDPBWAgWceGcE7aep9oL/0K9mlzlMGxA8yNw==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.12.7", + "@babel/helper-compilation-targets": "^7.12.5", + "@babel/helper-module-imports": "^7.12.5", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-validator-option": "^7.12.11", + "@babel/plugin-proposal-async-generator-functions": "^7.12.1", + "@babel/plugin-proposal-class-properties": "^7.12.1", + "@babel/plugin-proposal-dynamic-import": "^7.12.1", + "@babel/plugin-proposal-export-namespace-from": "^7.12.1", + "@babel/plugin-proposal-json-strings": "^7.12.1", + "@babel/plugin-proposal-logical-assignment-operators": "^7.12.1", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.12.1", + "@babel/plugin-proposal-numeric-separator": "^7.12.7", + "@babel/plugin-proposal-object-rest-spread": "^7.12.1", + "@babel/plugin-proposal-optional-catch-binding": "^7.12.1", + "@babel/plugin-proposal-optional-chaining": "^7.12.7", + "@babel/plugin-proposal-private-methods": "^7.12.1", + "@babel/plugin-proposal-unicode-property-regex": "^7.12.1", + "@babel/plugin-syntax-async-generators": "^7.8.0", + "@babel/plugin-syntax-class-properties": "^7.12.1", + "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.0", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.0", + "@babel/plugin-syntax-top-level-await": "^7.12.1", + "@babel/plugin-transform-arrow-functions": "^7.12.1", + "@babel/plugin-transform-async-to-generator": "^7.12.1", + "@babel/plugin-transform-block-scoped-functions": "^7.12.1", + "@babel/plugin-transform-block-scoping": "^7.12.11", + "@babel/plugin-transform-classes": "^7.12.1", + "@babel/plugin-transform-computed-properties": "^7.12.1", + "@babel/plugin-transform-destructuring": "^7.12.1", + "@babel/plugin-transform-dotall-regex": "^7.12.1", + "@babel/plugin-transform-duplicate-keys": "^7.12.1", + "@babel/plugin-transform-exponentiation-operator": "^7.12.1", + "@babel/plugin-transform-for-of": "^7.12.1", + "@babel/plugin-transform-function-name": "^7.12.1", + "@babel/plugin-transform-literals": "^7.12.1", + "@babel/plugin-transform-member-expression-literals": "^7.12.1", + "@babel/plugin-transform-modules-amd": "^7.12.1", + "@babel/plugin-transform-modules-commonjs": "^7.12.1", + "@babel/plugin-transform-modules-systemjs": "^7.12.1", + "@babel/plugin-transform-modules-umd": "^7.12.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.12.1", + "@babel/plugin-transform-new-target": "^7.12.1", + "@babel/plugin-transform-object-super": "^7.12.1", + "@babel/plugin-transform-parameters": "^7.12.1", + "@babel/plugin-transform-property-literals": "^7.12.1", + "@babel/plugin-transform-regenerator": "^7.12.1", + "@babel/plugin-transform-reserved-words": "^7.12.1", + "@babel/plugin-transform-shorthand-properties": "^7.12.1", + "@babel/plugin-transform-spread": "^7.12.1", + "@babel/plugin-transform-sticky-regex": "^7.12.7", + "@babel/plugin-transform-template-literals": "^7.12.1", + "@babel/plugin-transform-typeof-symbol": "^7.12.10", + "@babel/plugin-transform-unicode-escapes": "^7.12.1", + "@babel/plugin-transform-unicode-regex": "^7.12.1", + "@babel/preset-modules": "^0.1.3", + "@babel/types": "^7.12.11", + "core-js-compat": "^3.8.0", + "semver": "^5.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "@babel/preset-modules": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", + "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.0.0", "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-async-generator-functions": "^7.1.0", - "@babel/plugin-proposal-json-strings": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.0.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.0.0", - "@babel/plugin-syntax-async-generators": "^7.0.0", - "@babel/plugin-syntax-object-rest-spread": "^7.0.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.0.0", - "@babel/plugin-transform-arrow-functions": "^7.0.0", - "@babel/plugin-transform-async-to-generator": "^7.1.0", - "@babel/plugin-transform-block-scoped-functions": "^7.0.0", - "@babel/plugin-transform-block-scoping": "^7.0.0", - "@babel/plugin-transform-classes": "^7.1.0", - "@babel/plugin-transform-computed-properties": "^7.0.0", - "@babel/plugin-transform-destructuring": "^7.0.0", - "@babel/plugin-transform-dotall-regex": "^7.0.0", - "@babel/plugin-transform-duplicate-keys": "^7.0.0", - "@babel/plugin-transform-exponentiation-operator": "^7.1.0", - "@babel/plugin-transform-for-of": "^7.0.0", - "@babel/plugin-transform-function-name": "^7.1.0", - "@babel/plugin-transform-literals": "^7.0.0", - "@babel/plugin-transform-modules-amd": "^7.1.0", - "@babel/plugin-transform-modules-commonjs": "^7.1.0", - "@babel/plugin-transform-modules-systemjs": "^7.0.0", - "@babel/plugin-transform-modules-umd": "^7.1.0", - "@babel/plugin-transform-new-target": "^7.0.0", - "@babel/plugin-transform-object-super": "^7.1.0", - "@babel/plugin-transform-parameters": "^7.1.0", - "@babel/plugin-transform-regenerator": "^7.0.0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0", - "@babel/plugin-transform-spread": "^7.0.0", - "@babel/plugin-transform-sticky-regex": "^7.0.0", - "@babel/plugin-transform-template-literals": "^7.0.0", - "@babel/plugin-transform-typeof-symbol": "^7.0.0", - "@babel/plugin-transform-unicode-regex": "^7.0.0", - "browserslist": "^4.1.0", - "invariant": "^2.2.2", - "js-levenshtein": "^1.1.3", - "semver": "^5.3.0" + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" } }, "@babel/preset-react": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.0.0.tgz", - "integrity": "sha512-oayxyPS4Zj+hF6Et11BwuBkmpgT/zMxyuZgFrMeZID6Hdh3dGlk4sHCAhdBCpuCKW2ppBfl2uCCetlrUIJRY3w==", + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.12.10.tgz", + "integrity": "sha512-vtQNjaHRl4DUpp+t+g4wvTHsLQuye+n0H/wsXIZRn69oz/fvNC7gQ4IK73zGJBaxvHoxElDvnYCthMcT7uzFoQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-react-jsx-self": "^7.0.0", - "@babel/plugin-transform-react-jsx-source": "^7.0.0" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-transform-react-display-name": "^7.12.1", + "@babel/plugin-transform-react-jsx": "^7.12.10", + "@babel/plugin-transform-react-jsx-development": "^7.12.7", + "@babel/plugin-transform-react-pure-annotations": "^7.12.1" + } + }, + "@babel/preset-typescript": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.9.0.tgz", + "integrity": "sha512-S4cueFnGrIbvYJgwsVFKdvOmpiL0XGw9MFW9D0vgRys5g36PBhZRL8NX8Gr2akz8XRtzq6HuDXPD/1nniagNUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-transform-typescript": "^7.9.0" } }, "@babel/runtime": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.3.tgz", - "integrity": "sha512-9lsJwJLxDh/T3Q3SZszfWOTkk3pHbkmH+3KY+zwIDmsNlxsumuhS2TH3NIpktU4kNvfzy+k3eLT7aTJSPTo0OA==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz", + "integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==", "requires": { - "regenerator-runtime": "^0.13.2" + "regenerator-runtime": "^0.13.4" }, "dependencies": { "regenerator-runtime": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz", - "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==" + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" } } }, - "@babel/template": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.1.2.tgz", - "integrity": "sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag==", + "@babel/runtime-corejs3": { + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.12.5.tgz", + "integrity": "sha512-roGr54CsTmNPPzZoCP1AmDXuBoNao7tnSA83TXTwt+UK5QVyh1DIJnrgYRPWKCF2flqZQXwa7Yr8v7VmLzF0YQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.1.2", - "@babel/types": "^7.1.2" + "core-js-pure": "^3.0.0", + "regenerator-runtime": "^0.13.4" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", + "dev": true + } } }, - "@babel/traverse": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.1.4.tgz", - "integrity": "sha512-my9mdrAIGdDiSVBuMjpn/oXYpva0/EZwWL3sm3Wcy/AVWO2eXnsoZruOT9jOGNRXU8KbCIu5zsKnXcAJ6PcV6Q==", - "dev": true, + "@babel/template": { + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", + "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.1.3", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.0.0", - "@babel/parser": "^7.1.3", - "@babel/types": "^7.1.3", - "debug": "^3.1.0", + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.12.7", + "@babel/types": "^7.12.7" + } + }, + "@babel/traverse": { + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.10.tgz", + "integrity": "sha512-6aEtf0IeRgbYWzta29lePeYSk+YAFIC3kyqESeft8o5CkFlYIMX+EQDDWEiAQ9LHOA3d0oHdgrSsID/CKqXJlg==", + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.12.10", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.12.10", + "@babel/types": "^7.12.10", + "debug": "^4.1.0", "globals": "^11.1.0", - "lodash": "^4.17.10" + "lodash": "^4.17.19" }, "dependencies": { "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, - "globals": { - "version": "11.8.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.8.0.tgz", - "integrity": "sha512-io6LkyPVuzCHBSQV9fmOwxZkUk6nIaGmxheLDgmuFv89j0fm2aqDbIXKAGfzCMHqz3HLF2Zf8WSG6VqMh2qFmA==", - "dev": true - }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, "@babel/types": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.1.3.tgz", - "integrity": "sha512-RpPOVfK+yatXyn8n4PB1NW6k9qjinrXrRR8ugBN8fD6hCy5RXI6PSbVqpOJBO9oSaY7Nom4ohj35feb0UR9hSA==", + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.11.tgz", + "integrity": "sha512-ukA9SQtKThINm++CX1CwmliMrE54J6nIYB5XTwL5f/CLFW9owfls+YSU8tVW15RQ2w+a3fSbPjC6HdQNtWZkiA==", "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.10", + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", "to-fast-properties": "^2.0.0" } }, "@cnakazawa/watch": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.3.tgz", - "integrity": "sha512-r5160ogAvGyHsal38Kux7YYtodEKOj89RGb28ht1jh3SJb08VwRwAKKJL0bGb04Zd/3r9FL3BFIc3bBidYffCA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", + "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", "dev": true, "requires": { "exec-sh": "^0.3.2", "minimist": "^1.2.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } } }, - "@emotion/babel-utils": { - "version": "0.6.10", - "resolved": "https://registry.npmjs.org/@emotion/babel-utils/-/babel-utils-0.6.10.tgz", - "integrity": "sha512-/fnkM/LTEp3jKe++T0KyTszVGWNKPNOUJfjNKLO17BzQ6QPxgbg3whayom1Qr2oLFH3V92tDymU+dT5q676uow==", - "requires": { - "@emotion/hash": "^0.6.6", - "@emotion/memoize": "^0.6.6", - "@emotion/serialize": "^0.9.1", - "convert-source-map": "^1.5.1", - "find-root": "^1.1.0", - "source-map": "^0.7.2" - }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" - } - } + "@csstools/convert-colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz", + "integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==", + "dev": true }, - "@emotion/hash": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.6.6.tgz", - "integrity": "sha512-ojhgxzUHZ7am3D2jHkMzPpsBAiB005GF5YU4ea+8DNPybMk01JJUM9V9YRlF/GE95tcOm8DxQvWA2jq19bGalQ==" + "@csstools/normalize.css": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-10.1.0.tgz", + "integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg==", + "dev": true }, - "@emotion/is-prop-valid": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.7.3.tgz", - "integrity": "sha512-uxJqm/sqwXw3YPA5GXX365OBcJGFtxUVkB6WyezqFHlNe9jqUWH5ur2O2M8dGBz61kn1g3ZBlzUunFQXQIClhA==", + "@cypress/instrument-cra": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@cypress/instrument-cra/-/instrument-cra-1.4.0.tgz", + "integrity": "sha512-gXf540xL0jcUXkWyrA2Ug9rzs+jRkc9EPhnRi8XfbnRjdF4lvnn108N6x0lgTApMTbbpCDbVuskHGXDmIuD3CQ==", + "dev": true, "requires": { - "@emotion/memoize": "0.7.1" + "babel-plugin-istanbul": "6.0.0", + "debug": "4.2.0", + "find-yarn-workspace-root": "^2.0.0" }, "dependencies": { - "@emotion/memoize": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.1.tgz", - "integrity": "sha512-Qv4LTqO11jepd5Qmlp3M1YEjBumoTHcHFdgPTQ+sFlIL5myi/7xu/POwP7IRu6odBdmLXdtIs1D6TuW6kbwbbg==" - } + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, - "@emotion/memoize": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.6.6.tgz", - "integrity": "sha512-h4t4jFjtm1YV7UirAFuSuFGyLa+NNxjdkq6DpFLANNQY5rHueFZHVY+8Cu1HYVP6DrheB0kv4m5xPjo7eKT7yQ==" - }, - "@emotion/serialize": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.9.1.tgz", - "integrity": "sha512-zTuAFtyPvCctHBEL8KZ5lJuwBanGSutFEncqLn/m9T1a6a93smBStK+bZzcNPgj4QS8Rkw9VTwJGhRIUVO8zsQ==", + "@emotion/is-prop-valid": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", + "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", "requires": { - "@emotion/hash": "^0.6.6", - "@emotion/memoize": "^0.6.6", - "@emotion/unitless": "^0.6.7", - "@emotion/utils": "^0.8.2" + "@emotion/memoize": "0.7.4" } }, - "@emotion/stylis": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.7.1.tgz", - "integrity": "sha512-/SLmSIkN13M//53TtNxgxo57mcJk/UJIDFRKwOiLIBEyBHEcipgR6hNMQ/59Sl4VjCJ0Z/3zeAZyvnSLPG/1HQ==" + "@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" }, "@emotion/unitless": { - "version": "0.6.7", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.6.7.tgz", - "integrity": "sha512-Arj1hncvEVqQ2p7Ega08uHLr1JuRYBuO5cIvcA+WWEQ5+VmkOE3ZXzl04NbQxeQpWX78G7u6MqxKuNX3wvYZxg==" + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" }, - "@emotion/utils": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.8.2.tgz", - "integrity": "sha512-rLu3wcBWH4P5q1CGoSSH/i9hrXs7SlbRLkoq9IGuoPYNGQvDJ3pt/wmOM+XgYjIDRMVIdkUWt0RsfzF50JfnCw==" + "@hapi/address": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", + "integrity": "sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ==", + "dev": true + }, + "@hapi/bourne": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-1.3.2.tgz", + "integrity": "sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA==", + "dev": true + }, + "@hapi/hoek": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.1.tgz", + "integrity": "sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow==", + "dev": true + }, + "@hapi/joi": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-15.1.1.tgz", + "integrity": "sha512-entf8ZMOK8sc+8YfeOlM8pCfg3b5+WZIKBfUaaJT8UsjAAPjartzxIYm3TIbjvA4u+u++KbcXD38k682nVHDAQ==", + "dev": true, + "requires": { + "@hapi/address": "2.x.x", + "@hapi/bourne": "1.x.x", + "@hapi/hoek": "8.x.x", + "@hapi/topo": "3.x.x" + } }, - "@fortawesome/fontawesome-common-types": { - "version": "0.2.25", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.25.tgz", - "integrity": "sha512-3RuZPDuuPELd7RXtUqTCfed14fcny9UiPOkdr2i+cYxBoTOfQgxcDoq77fHiiHcgWuo1LoBUpvGxFF1H/y7s3Q==" + "@hapi/topo": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.6.tgz", + "integrity": "sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ==", + "dev": true, + "requires": { + "@hapi/hoek": "^8.3.0" + } }, - "@fortawesome/free-brands-svg-icons": { - "version": "5.11.2", - "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.11.2.tgz", - "integrity": "sha512-wKK5znpHiZ2S0VgOvbeAnYuzkk3H86rxWajD9PVpfBj3s/kySEWTFKh/uLPyxiTOx8Tsd0OGN4En/s9XudVHLQ==", + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, "requires": { - "@fortawesome/fontawesome-common-types": "^0.2.25" + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" } }, + "@istanbuljs/schema": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", + "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", + "dev": true + }, "@jest/console": { - "version": "24.7.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.7.1.tgz", - "integrity": "sha512-iNhtIy2M8bXlAOULWVTUxmnelTLFneTNEkHCgPmgd+zNwy9zVddJ6oS5rZ9iwoscNdT5mMwUd0C51v/fSlzItg==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz", + "integrity": "sha512-Zuj6b8TnKXi3q4ymac8EQfc3ea/uhLeCGThFqXeC8H9/raaH8ARPUTdId+XyGd03Z4In0/VjD2OYFcBF09fNLQ==", "dev": true, "requires": { - "@jest/source-map": "^24.3.0", + "@jest/source-map": "^24.9.0", "chalk": "^2.0.1", "slash": "^2.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } } }, "@jest/core": { - "version": "24.7.1", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-24.7.1.tgz", - "integrity": "sha512-ivlZ8HX/FOASfHcb5DJpSPFps8ydfUYzLZfgFFqjkLijYysnIEOieg72YRhO4ZUB32xu40hsSMmaw+IGYeKONA==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-24.9.0.tgz", + "integrity": "sha512-Fogg3s4wlAr1VX7q+rhV9RVnUv5tD7VuWfYy1+whMiWUrvl7U3QJSJyWcDio9Lq2prqYsZaeTv2Rz24pWGkJ2A==", "dev": true, "requires": { "@jest/console": "^24.7.1", - "@jest/reporters": "^24.7.1", - "@jest/test-result": "^24.7.1", - "@jest/transform": "^24.7.1", - "@jest/types": "^24.7.0", + "@jest/reporters": "^24.9.0", + "@jest/test-result": "^24.9.0", + "@jest/transform": "^24.9.0", + "@jest/types": "^24.9.0", "ansi-escapes": "^3.0.0", "chalk": "^2.0.1", "exit": "^0.1.2", "graceful-fs": "^4.1.15", - "jest-changed-files": "^24.7.0", - "jest-config": "^24.7.1", - "jest-haste-map": "^24.7.1", - "jest-message-util": "^24.7.1", + "jest-changed-files": "^24.9.0", + "jest-config": "^24.9.0", + "jest-haste-map": "^24.9.0", + "jest-message-util": "^24.9.0", "jest-regex-util": "^24.3.0", - "jest-resolve-dependencies": "^24.7.1", - "jest-runner": "^24.7.1", - "jest-runtime": "^24.7.1", - "jest-snapshot": "^24.7.1", - "jest-util": "^24.7.1", - "jest-validate": "^24.7.0", - "jest-watcher": "^24.7.1", + "jest-resolve": "^24.9.0", + "jest-resolve-dependencies": "^24.9.0", + "jest-runner": "^24.9.0", + "jest-runtime": "^24.9.0", + "jest-snapshot": "^24.9.0", + "jest-util": "^24.9.0", + "jest-validate": "^24.9.0", + "jest-watcher": "^24.9.0", "micromatch": "^3.1.10", "p-each-series": "^1.0.0", - "pirates": "^4.0.1", "realpath-native": "^1.1.0", "rimraf": "^2.5.4", + "slash": "^2.0.0", "strip-ansi": "^5.0.0" }, "dependencies": { @@ -1287,82 +1460,111 @@ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, - "graceful-fs": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true - }, - "jest-get-type": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.3.0.tgz", - "integrity": "sha512-HYF6pry72YUlVcvUx3sEpMRwXEWGEPlJ0bSPVnB3b3n++j4phUEoSPcS6GC0pPJ9rpyPSe4cb5muFo6D39cXow==", - "dev": true - }, - "jest-regex-util": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.3.0.tgz", - "integrity": "sha512-tXQR1NEOyGlfylyEjg1ImtScwMq8Oh3iJbGTjN7p0J23EuVX1MA8rwU69K4sLbCmwzgCUbVkm0FkSF9TdzOhtg==", + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, - "jest-validate": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-24.7.0.tgz", - "integrity": "sha512-cgai/gts9B2chz1rqVdmLhzYxQbgQurh1PEQSvSgPZ8KGa1AqXsqC45W5wKEwzxKrWqypuQrQxnF4+G9VejJJA==", + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "@jest/types": "^24.7.0", - "camelcase": "^5.0.0", - "chalk": "^2.0.1", - "jest-get-type": "^24.3.0", - "leven": "^2.1.0", - "pretty-format": "^24.7.0" + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, - "pretty-format": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.7.0.tgz", - "integrity": "sha512-apen5cjf/U4dj7tHetpC7UEFCvtAgnNZnBDkfPv3fokzIqyOJckAG9OlAPC1BlFALnqT/lGB2tl9EJjlK6eCsA==", + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "@jest/types": "^24.7.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" } }, - "react-is": { - "version": "16.8.6", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", - "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==", - "dev": true - }, "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", @@ -1372,109 +1574,103 @@ "ansi-regex": "^4.1.0" } }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "dev": true, "requires": { - "has-flag": "^3.0.0" + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" } } } }, "@jest/environment": { - "version": "24.7.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-24.7.1.tgz", - "integrity": "sha512-wmcTTYc4/KqA+U5h1zQd5FXXynfa7VGP2NfF+c6QeGJ7c+2nStgh65RQWNX62SC716dTtqheTRrZl0j+54oGHw==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-24.9.0.tgz", + "integrity": "sha512-5A1QluTPhvdIPFYnO3sZC3smkNeXPVELz7ikPbhUj0bQjB07EoE9qtLrem14ZUYWdVayYbsjVwIiL4WBIMV4aQ==", "dev": true, "requires": { - "@jest/fake-timers": "^24.7.1", - "@jest/transform": "^24.7.1", - "@jest/types": "^24.7.0", - "jest-mock": "^24.7.0" + "@jest/fake-timers": "^24.9.0", + "@jest/transform": "^24.9.0", + "@jest/types": "^24.9.0", + "jest-mock": "^24.9.0" } }, "@jest/fake-timers": { - "version": "24.7.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-24.7.1.tgz", - "integrity": "sha512-4vSQJDKfR2jScOe12L9282uiwuwQv9Lk7mgrCSZHA9evB9efB/qx8i0KJxsAKtp8fgJYBJdYY7ZU6u3F4/pyjA==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-24.9.0.tgz", + "integrity": "sha512-eWQcNa2YSwzXWIMC5KufBh3oWRIijrQFROsIqt6v/NS9Io/gknw1jsAC9c+ih/RQX4A3O7SeWAhQeN0goKhT9A==", "dev": true, "requires": { - "@jest/types": "^24.7.0", - "jest-message-util": "^24.7.1", - "jest-mock": "^24.7.0" + "@jest/types": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-mock": "^24.9.0" } }, "@jest/reporters": { - "version": "24.7.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-24.7.1.tgz", - "integrity": "sha512-bO+WYNwHLNhrjB9EbPL4kX/mCCG4ZhhfWmO3m4FSpbgr7N83MFejayz30kKjgqr7smLyeaRFCBQMbXpUgnhAJw==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-24.9.0.tgz", + "integrity": "sha512-mu4X0yjaHrffOsWmVLzitKmmmWSQ3GGuefgNscUSWNiUNcEOSEQk9k3pERKEQVBb0Cnn88+UESIsZEMH3o88Gw==", "dev": true, "requires": { - "@jest/environment": "^24.7.1", - "@jest/test-result": "^24.7.1", - "@jest/transform": "^24.7.1", - "@jest/types": "^24.7.0", + "@jest/environment": "^24.9.0", + "@jest/test-result": "^24.9.0", + "@jest/transform": "^24.9.0", + "@jest/types": "^24.9.0", "chalk": "^2.0.1", "exit": "^0.1.2", "glob": "^7.1.2", - "istanbul-api": "^2.1.1", "istanbul-lib-coverage": "^2.0.2", "istanbul-lib-instrument": "^3.0.1", + "istanbul-lib-report": "^2.0.4", "istanbul-lib-source-maps": "^3.0.1", - "jest-haste-map": "^24.7.1", - "jest-resolve": "^24.7.1", - "jest-runtime": "^24.7.1", - "jest-util": "^24.7.1", + "istanbul-reports": "^2.2.6", + "jest-haste-map": "^24.9.0", + "jest-resolve": "^24.9.0", + "jest-runtime": "^24.9.0", + "jest-util": "^24.9.0", "jest-worker": "^24.6.0", - "node-notifier": "^5.2.1", + "node-notifier": "^5.4.2", "slash": "^2.0.0", "source-map": "^0.6.0", "string-length": "^2.0.0" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "istanbul-lib-instrument": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", + "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "@babel/generator": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "istanbul-lib-coverage": "^2.0.5", + "semver": "^6.0.0" } }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } } } }, "@jest/source-map": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-24.3.0.tgz", - "integrity": "sha512-zALZt1t2ou8le/crCeeiRYzvdnTzaIlpOWaet45lNSqNJUnXbppUUFR4ZUAlzgDmKee4Q5P/tKXypI1RiHwgag==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-24.9.0.tgz", + "integrity": "sha512-/Xw7xGlsZb4MJzNDgB7PW5crou5JqWiBQaz6xyPd3ArOg2nfn/PunV8+olXbbEZzNl591o5rWKE9BRDaFAuIBg==", "dev": true, "requires": { "callsites": "^3.0.0", @@ -1488,260 +1684,393 @@ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, - "graceful-fs": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true } } }, "@jest/test-result": { - "version": "24.7.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-24.7.1.tgz", - "integrity": "sha512-3U7wITxstdEc2HMfBX7Yx3JZgiNBubwDqQMh+BXmZXHa3G13YWF3p6cK+5g0hGkN3iufg/vGPl3hLxQXD74Npg==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-24.9.0.tgz", + "integrity": "sha512-XEFrHbBonBJ8dGp2JmF8kP/nQI/ImPpygKHwQ/SY+es59Z3L5PI4Qb9TQQMAEeYsThG1xF0k6tmG0tIKATNiiA==", "dev": true, "requires": { - "@jest/console": "^24.7.1", - "@jest/types": "^24.7.0", + "@jest/console": "^24.9.0", + "@jest/types": "^24.9.0", "@types/istanbul-lib-coverage": "^2.0.0" } }, "@jest/test-sequencer": { - "version": "24.7.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-24.7.1.tgz", - "integrity": "sha512-84HQkCpVZI/G1zq53gHJvSmhUer4aMYp9tTaffW28Ih5OxfCg8hGr3nTSbL1OhVDRrFZwvF+/R9gY6JRkDUpUA==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-24.9.0.tgz", + "integrity": "sha512-6qqsU4o0kW1dvA95qfNog8v8gkRN9ph6Lz7r96IvZpHdNipP2cBcb07J1Z45mz/VIS01OHJ3pY8T5fUY38tg4A==", "dev": true, "requires": { - "@jest/test-result": "^24.7.1", - "jest-haste-map": "^24.7.1", - "jest-runner": "^24.7.1", - "jest-runtime": "^24.7.1" + "@jest/test-result": "^24.9.0", + "jest-haste-map": "^24.9.0", + "jest-runner": "^24.9.0", + "jest-runtime": "^24.9.0" } }, "@jest/transform": { - "version": "24.7.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-24.7.1.tgz", - "integrity": "sha512-EsOUqP9ULuJ66IkZQhI5LufCHlTbi7hrcllRMUEV/tOgqBVQi93+9qEvkX0n8mYpVXQ8VjwmICeRgg58mrtIEw==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-24.9.0.tgz", + "integrity": "sha512-TcQUmyNRxV94S0QpMOnZl0++6RMiqpbH/ZMccFB/amku6Uwvyb1cjYX7xkp5nGNkbX4QPH/FcB6q1HBTHynLmQ==", "dev": true, "requires": { "@babel/core": "^7.1.0", - "@jest/types": "^24.7.0", + "@jest/types": "^24.9.0", "babel-plugin-istanbul": "^5.1.0", "chalk": "^2.0.1", "convert-source-map": "^1.4.0", "fast-json-stable-stringify": "^2.0.0", "graceful-fs": "^4.1.15", - "jest-haste-map": "^24.7.1", - "jest-regex-util": "^24.3.0", - "jest-util": "^24.7.1", + "jest-haste-map": "^24.9.0", + "jest-regex-util": "^24.9.0", + "jest-util": "^24.9.0", "micromatch": "^3.1.10", + "pirates": "^4.0.1", "realpath-native": "^1.1.0", "slash": "^2.0.0", "source-map": "^0.6.1", "write-file-atomic": "2.4.1" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "babel-plugin-istanbul": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz", + "integrity": "sha512-5LphC0USA8t4i1zCtjbbNb6jJj/9+X6P37Qfirc/70EQ34xKlMW+a1RHGwxGI+SwWpNwZ27HqvzAobeqaXwiZw==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "@babel/helper-plugin-utils": "^7.0.0", + "find-up": "^3.0.0", + "istanbul-lib-instrument": "^3.3.0", + "test-exclude": "^5.2.3" } }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, - "graceful-fs": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } }, - "jest-regex-util": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.3.0.tgz", - "integrity": "sha512-tXQR1NEOyGlfylyEjg1ImtScwMq8Oh3iJbGTjN7p0J23EuVX1MA8rwU69K4sLbCmwzgCUbVkm0FkSF9TdzOhtg==", - "dev": true + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "@jest/types": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.7.0.tgz", - "integrity": "sha512-ipJUa2rFWiKoBqMKP63Myb6h9+iT3FHRTF2M8OR6irxWzItisa8i4dcSg14IbvmXUnBlHBlUQPYUHWyX3UPpYA==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/yargs": "^12.0.9" - } + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", + "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", + "dev": true, + "requires": { + "@babel/generator": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "istanbul-lib-coverage": "^2.0.5", + "semver": "^6.0.0" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "dev": true, + "requires": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "test-exclude": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", + "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", + "dev": true, + "requires": { + "glob": "^7.1.3", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^2.0.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } }, "@lingui/babel-plugin-extract-messages": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/@lingui/babel-plugin-extract-messages/-/babel-plugin-extract-messages-2.7.4.tgz", - "integrity": "sha512-/0XNXLg3gKLvmvaQ9+A2lxuRtG6g1zhp3KGMdC+cwUU4fX5Sfim3x3vRxlkMk69j/GkKC5OlKKTPZlI05NsReg==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@lingui/babel-plugin-extract-messages/-/babel-plugin-extract-messages-2.9.2.tgz", + "integrity": "sha512-nkRufTupyWjRpzX5ZXB1qMKWT9B+gAuMXYD4blZ/HHCJlEOXeds9W5bugVd3N8Ts5m4o9iRoqeaCuVcH7sJ8Wg==", "dev": true, "requires": { - "@lingui/conf": "2.7.4", + "@lingui/conf": "2.9.2", "babel-generator": "^6.26.1" } }, "@lingui/babel-plugin-transform-js": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/@lingui/babel-plugin-transform-js/-/babel-plugin-transform-js-2.7.4.tgz", - "integrity": "sha512-jqs1AR3507e1vqaVQB0bG15p5VaujlD48Vm7/ElL3ieFvx/gyvZp5b1wv/A8kpVuPcEhFm7oWJzeUfkh1mdOTg==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@lingui/babel-plugin-transform-js/-/babel-plugin-transform-js-2.9.2.tgz", + "integrity": "sha512-yWoyhOfjRa9744TbVb/WN1OWxZYFLuXcWH5aVCu/sZ2b1YpsGCtfhplc5lRVWN8QcsfpjYmFiPqzU6swE5OFdQ==", "dev": true }, "@lingui/babel-plugin-transform-react": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@lingui/babel-plugin-transform-react/-/babel-plugin-transform-react-2.7.2.tgz", - "integrity": "sha512-50+GM9LL7V4mB6ekY7hUGmyMLewDQ9bzVXTyRtbTl/78xJoQKSv8Wuz+jlvPcP0fKAFcmcHo3QJPPOkSAbgnOw==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@lingui/babel-plugin-transform-react/-/babel-plugin-transform-react-2.9.2.tgz", + "integrity": "sha512-bxvrepiS6J9vZqRtpRiAgBIASQscjvu7aFmPqH4Y6001TDXrYuyhhNRt1BI3k2E6C2SckHh5vRtSpsqpjEiY3A==", "dev": true }, "@lingui/cli": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/@lingui/cli/-/cli-2.7.4.tgz", - "integrity": "sha512-Px/b3N8ryNdpnPj2pduX+pj91I7Zm3dFKj8dXRbgFmVjXcf3od0hK8yoPj9nBuuTa9VBB4wbZwBJSwuqYuHfgg==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@lingui/cli/-/cli-2.9.2.tgz", + "integrity": "sha512-j46vUe8hSgvsm3j2V4sPLxOdd2HacacGC5E+bWx4wHEhd/yxV4nwPfWpuC7wLoBwM/y2bcF8Q2V7ahEznKSO6A==", "dev": true, "requires": { - "@lingui/babel-plugin-extract-messages": "2.7.4", - "@lingui/babel-plugin-transform-js": "2.7.4", - "@lingui/babel-plugin-transform-react": "2.7.4", - "@lingui/conf": "2.7.4", + "@lingui/babel-plugin-extract-messages": "2.9.2", + "@lingui/babel-plugin-transform-js": "2.9.2", + "@lingui/babel-plugin-transform-react": "2.9.2", + "@lingui/conf": "2.9.2", "babel-generator": "^6.26.1", "babel-plugin-syntax-jsx": "^6.18.0", "babel-runtime": "^6.26.0", "babel-types": "^6.26.0", "babylon": "^6.18.0", - "bcp-47": "^1.0.4", + "bcp-47": "^1.0.5", "chalk": "^2.3.0", "cli-table": "^0.3.1", - "commander": "^2.17.1", + "commander": "^2.20.0", "date-fns": "^1.29.0", "fuzzaldrin": "^2.1.0", - "glob": "^7.1.2", - "inquirer": "^6.2.0", + "glob": "^7.1.4", + "inquirer": "^6.3.1", "make-plural": "^4.1.1", "messageformat-parser": "^2.0.0", "mkdirp": "^0.5.1", - "opencollective": "^1.0.3", - "ora": "^3.0.0", + "ora": "^3.4.0", "pofile": "^1.0.11", "pseudolocale": "^1.1.0", - "ramda": "^0.25.0", - "typescript": "^2.9.2" - }, - "dependencies": { - "@lingui/babel-plugin-transform-react": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/@lingui/babel-plugin-transform-react/-/babel-plugin-transform-react-2.7.4.tgz", - "integrity": "sha512-U4ocmtqOIjqDQX9tYrOLDNR7Tp8EmDPoBUtXyXfAPb4ukA3CePphuz89N8lDZVsTUQwBDNDynP/3qiLr1pW4QQ==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "ramda": "^0.26.1" } }, "@lingui/conf": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-2.7.4.tgz", - "integrity": "sha512-v/tr1aLYrUozu09yHBzRHBB2gPYD8Vwth51RuTOyzsthQVAtfKY7VV7m2wB0JQFMD9gGbPE5a+roqH6RkB7qUw==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-2.9.2.tgz", + "integrity": "sha512-xHfH+zLhM7PaMawqeK1G+Pq+reVPYR8eU7XixH4VRHWK8n/itTb4fRl24xc5IUgeXJx+NX1qCzBYVz0i13xlVg==", "dev": true, "requires": { "chalk": "^2.3.0", - "cosmiconfig": "^5.0.6", - "jest-regex-util": "^23.3.0", - "jest-validate": "^23.5.0", - "pkg-conf": "^2.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "cosmiconfig": "^5.2.1", + "jest-regex-util": "^24.3.0", + "jest-validate": "^24.8.0", + "pkg-conf": "^3.1.0" } }, "@lingui/core": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@lingui/core/-/core-2.7.2.tgz", - "integrity": "sha512-LLMNcxfXmjhxG18sQmIb4azNuwVyhMUhcVFgOQ682eVQEGaSIncPsnSQRGhgYbiqY1LGXlZEXZnYdyyDMSZR0A==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@lingui/core/-/core-2.9.2.tgz", + "integrity": "sha512-prrEGlhbvqbyvHMfgKXaaGDM4cGCofca1lOfIOEEX1rZreRjG7Y+cga0oEEQJ9xS59uMht9GGFOwQJsGHYIU0g==", "requires": { "babel-runtime": "^6.26.0", "make-plural": "^4.1.1", @@ -1749,37 +2078,42 @@ } }, "@lingui/macro": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@lingui/macro/-/macro-2.7.2.tgz", - "integrity": "sha512-kU5fl/2MvHQLfVRju2QR28uKOHsUqlLZOJwMXNAm/eq42D6ORa5vZR2ovlBRF9IdcYGqkCCPjeTLN5NC7b5gyQ==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@lingui/macro/-/macro-2.9.2.tgz", + "integrity": "sha512-IFv9h3LL/vjMz98JiDpckIsAlIcnCNuCD4+/C8KK33qGsU9MbCF+zjIztdEWlm3JlnFNm1/qIw7CkrtdoH0RpA==", "dev": true, "requires": { - "@lingui/babel-plugin-transform-react": "2.7.2", - "babel-plugin-macros": "^2.2.0" + "@lingui/babel-plugin-transform-react": "2.9.2" } }, "@lingui/react": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@lingui/react/-/react-2.7.2.tgz", - "integrity": "sha512-dYfhojpYLKyXp3V1i3twjipIXGK80rBBLNMZOXBBCqm5Lypef3d7Ip1jLXuhk4Ni++ijWJfHC40F3gWNBNFSjw==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@lingui/react/-/react-2.9.2.tgz", + "integrity": "sha512-grlarLgJt6vu9wkvGetLunbrImRlL5bsnc4CdtXCNwm0r+8srI+mow5PimtK+MrtOSjXSqLdt/giMYFBE3MOSw==", "requires": { - "@lingui/core": "2.7.2", + "@lingui/core": "2.9.2", "babel-runtime": "^6.26.0", "hash-sum": "^1.0.2", - "hoist-non-react-statics": "3.0.1", - "prop-types": "^15.6.2" - }, - "dependencies": { - "hoist-non-react-statics": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.0.1.tgz", - "integrity": "sha512-1kXwPsOi0OGQIZNVMPvgWJ9tSnGMiMfJdihqEzrPEXlHOBh9AAHXX/QYmAJTXztnz/K+PQ8ryCb4eGaN6HlGbQ==", - "requires": { - "react-is": "^16.3.2" - } - } + "hoist-non-react-statics": "3.3.0", + "prop-types": "^15.7.2" + } + }, + "@mrmlnc/readdir-enhanced": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", + "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", + "dev": true, + "requires": { + "call-me-maybe": "^1.0.1", + "glob-to-regexp": "^0.3.0" } }, + "@nodelib/fs.stat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", + "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", + "dev": true + }, "@nteract/mockument": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@nteract/mockument/-/mockument-1.0.4.tgz", @@ -1787,245 +2121,196 @@ "dev": true }, "@patternfly/patternfly": { - "version": "2.46.1", - "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-2.46.1.tgz", - "integrity": "sha512-3lReQMQvedwEhKOcOw7rE3RPRXMtRit+Yj1IOO7fl5EHaZaNqA1/3w9mWNCpx52M+WD8scBkgqtVx74OU7Jemw==" + "version": "4.70.2", + "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-4.70.2.tgz", + "integrity": "sha512-XKCHnOjx1JThY3s98AJhsApSsGHPvEdlY7r+b18OecqUnmThVGw3nslzYYrwfCGlJ/xQtV5so29SduH2/uhHzA==" }, "@patternfly/react-core": { - "version": "3.129.3", - "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-3.129.3.tgz", - "integrity": "sha512-QiTTUqA0y55YbDtzjlzKmZ6pGQqxyCF14TBQFH3rXI2RV8Z4C6HyyILm09BD/D/ITQIhT82dp+6nRY/mQOqlkw==", - "requires": { - "@patternfly/react-icons": "^3.14.28", - "@patternfly/react-styles": "^3.6.15", - "@patternfly/react-tokens": "^2.7.14", - "emotion": "^9.2.9", - "exenv": "^1.2.2", - "focus-trap-react": "^4.0.1", - "tippy.js": "5.1.2" + "version": "4.84.3", + "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-4.84.3.tgz", + "integrity": "sha512-VeCv/r09ay6yIER7Eb8Dp5ZhbDu6SCW9smwgUTNp80kt83wIfKvGvQOKg+/7cev/GC6VzfgPHhiS04Jm/N5loA==", + "requires": { + "@patternfly/react-icons": "^4.7.22", + "@patternfly/react-styles": "^4.7.22", + "@patternfly/react-tokens": "^4.9.22", + "focus-trap": "6.2.2", + "react-dropzone": "9.0.0", + "tippy.js": "5.1.2", + "tslib": "1.13.0" + } + }, + "@patternfly/react-icons": { + "version": "4.7.22", + "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-4.7.22.tgz", + "integrity": "sha512-JDsnebr9CNIyrv9yjaGFQ56OChbV6KcxMYBIpNc8/sZdU4TXHWNC7P7rlUM/BuGpbWvyaOJtscRuf5uteIKX3A==" + }, + "@patternfly/react-styles": { + "version": "4.7.22", + "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-4.7.22.tgz", + "integrity": "sha512-ojNuSNJx6CkNtsSFseZ2SJEVyzPMFYh0jOs204ICzYM1+fn9acsIi3Co0bcskFRzw8F6e2/x+8uVNx6QI8elxg==" + }, + "@patternfly/react-table": { + "version": "4.19.45", + "resolved": "https://registry.npmjs.org/@patternfly/react-table/-/react-table-4.19.45.tgz", + "integrity": "sha512-HFOefgyZ8SGbLflGlEqefHrdxrV+X+x7+bBRvsB+zFZAYWDVau3Z6Pkv8gY5JselBsMywZYPb5juHBoBkzuezg==", + "requires": { + "@patternfly/patternfly": "4.70.2", + "@patternfly/react-core": "^4.84.4", + "@patternfly/react-icons": "^4.7.22", + "@patternfly/react-styles": "^4.7.22", + "@patternfly/react-tokens": "^4.9.22", + "lodash": "^4.17.19", + "tslib": "1.13.0" }, "dependencies": { - "@patternfly/react-icons": { - "version": "3.14.28", - "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-3.14.28.tgz", - "integrity": "sha512-xrmcaLaHvkixPdTuBfR+vPD2prUYxKq97TGs97lfo0K4g7Wi6lD30zMlmwzonWy1IuOHATiEwf3j7mXAqQXHlQ==", - "requires": { - "@fortawesome/free-brands-svg-icons": "^5.8.1" + "@patternfly/react-core": { + "version": "4.84.4", + "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-4.84.4.tgz", + "integrity": "sha512-GIv6zJl+NGYIrSm2pATQFggRt4ZFYUKVNbLSsna2x8+eYeZscqIUFSPmBM+eNpQkGaoJk254Lu09pgm4hM/b4g==", + "requires": { + "@patternfly/react-icons": "^4.7.22", + "@patternfly/react-styles": "^4.7.22", + "@patternfly/react-tokens": "^4.9.22", + "focus-trap": "6.2.2", + "react-dropzone": "9.0.0", + "tippy.js": "5.1.2", + "tslib": "1.13.0" } - }, - "@patternfly/react-tokens": { - "version": "2.7.14", - "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-2.7.14.tgz", - "integrity": "sha512-HVa1fe7H4NRRv6lmezpvW2TfIDF7bSbKvhMmCVqBk80Fd3wfLcPhacnWdt6PLWq7WX4dVx7dF7+v4sFh8RczSg==" } } }, - "@patternfly/react-icons": { - "version": "3.14.28", - "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-3.14.28.tgz", - "integrity": "sha512-xrmcaLaHvkixPdTuBfR+vPD2prUYxKq97TGs97lfo0K4g7Wi6lD30zMlmwzonWy1IuOHATiEwf3j7mXAqQXHlQ==", + "@patternfly/react-tokens": { + "version": "4.9.22", + "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-4.9.22.tgz", + "integrity": "sha512-hN/8u7mFR62naFB2hdO7nl1p/0lCXtNq+VY+BAbp4UFC2/QyjNP0IOPBR+mR9Pbj5JwxrURI7G5blLp+k9RLvQ==" + }, + "@svgr/babel-plugin-add-jsx-attribute": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz", + "integrity": "sha512-j7KnilGyZzYr/jhcrSYS3FGWMZVaqyCG0vzMCwzvei0coIkczuYMcniK07nI0aHJINciujjH11T72ICW5eL5Ig==", + "dev": true + }, + "@svgr/babel-plugin-remove-jsx-attribute": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-4.2.0.tgz", + "integrity": "sha512-3XHLtJ+HbRCH4n28S7y/yZoEQnRpl0tvTZQsHqvaeNXPra+6vE5tbRliH3ox1yZYPCxrlqaJT/Mg+75GpDKlvQ==", + "dev": true + }, + "@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-4.2.0.tgz", + "integrity": "sha512-yTr2iLdf6oEuUE9MsRdvt0NmdpMBAkgK8Bjhl6epb+eQWk6abBaX3d65UZ3E3FWaOwePyUgNyNCMVG61gGCQ7w==", + "dev": true + }, + "@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-4.2.0.tgz", + "integrity": "sha512-U9m870Kqm0ko8beHawRXLGLvSi/ZMrl89gJ5BNcT452fAjtF2p4uRzXkdzvGJJJYBgx7BmqlDjBN/eCp5AAX2w==", + "dev": true + }, + "@svgr/babel-plugin-svg-dynamic-title": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-4.3.3.tgz", + "integrity": "sha512-w3Be6xUNdwgParsvxkkeZb545VhXEwjGMwExMVBIdPQJeyMQHqm9Msnb2a1teHBqUYL66qtwfhNkbj1iarCG7w==", + "dev": true + }, + "@svgr/babel-plugin-svg-em-dimensions": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-4.2.0.tgz", + "integrity": "sha512-C0Uy+BHolCHGOZ8Dnr1zXy/KgpBOkEUYY9kI/HseHVPeMbluaX3CijJr7D4C5uR8zrc1T64nnq/k63ydQuGt4w==", + "dev": true + }, + "@svgr/babel-plugin-transform-react-native-svg": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-4.2.0.tgz", + "integrity": "sha512-7YvynOpZDpCOUoIVlaaOUU87J4Z6RdD6spYN4eUb5tfPoKGSF9OG2NuhgYnq4jSkAxcpMaXWPf1cePkzmqTPNw==", + "dev": true + }, + "@svgr/babel-plugin-transform-svg-component": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-4.2.0.tgz", + "integrity": "sha512-hYfYuZhQPCBVotABsXKSCfel2slf/yvJY8heTVX1PCTaq/IgASq1IyxPPKJ0chWREEKewIU/JMSsIGBtK1KKxw==", + "dev": true + }, + "@svgr/babel-preset": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-4.3.3.tgz", + "integrity": "sha512-6PG80tdz4eAlYUN3g5GZiUjg2FMcp+Wn6rtnz5WJG9ITGEF1pmFdzq02597Hn0OmnQuCVaBYQE1OVFAnwOl+0A==", + "dev": true, "requires": { - "@fortawesome/free-brands-svg-icons": "^5.8.1" + "@svgr/babel-plugin-add-jsx-attribute": "^4.2.0", + "@svgr/babel-plugin-remove-jsx-attribute": "^4.2.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "^4.2.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "^4.2.0", + "@svgr/babel-plugin-svg-dynamic-title": "^4.3.3", + "@svgr/babel-plugin-svg-em-dimensions": "^4.2.0", + "@svgr/babel-plugin-transform-react-native-svg": "^4.2.0", + "@svgr/babel-plugin-transform-svg-component": "^4.2.0" } }, - "@patternfly/react-styles": { - "version": "3.6.15", - "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-3.6.15.tgz", - "integrity": "sha512-9phudtz138QV82o60XvbNkeYPzLgz0DekEeu8cIX2A2yO1WzZbgXL5VPWB8bF/y+9EFyl+w8tu3ReQcvh7ULEw==", - "requires": { - "@babel/helper-plugin-utils": "^7.0.0-beta.48", - "camel-case": "^3.0.0", - "css": "^2.2.3", - "cssom": "^0.3.4", - "cssstyle": "^0.3.1", - "emotion": "^9.2.9", - "emotion-server": "^9.2.9", - "fbjs-scripts": "^0.8.3", - "fs-extra": "^6.0.1", - "jsdom": "^15.1.0", - "relative": "^3.0.2", - "resolve-from": "^4.0.0", - "typescript": "3.4.5" - }, - "dependencies": { - "acorn": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz", - "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==" - }, - "acorn-globals": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz", - "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==", - "requires": { - "acorn": "^6.0.1", - "acorn-walk": "^6.0.1" - }, - "dependencies": { - "acorn": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.0.tgz", - "integrity": "sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw==" - } - } - }, - "escodegen": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.12.0.tgz", - "integrity": "sha512-TuA+EhsanGcme5T3R0L80u4t8CpbXQjegRmf7+FPTJrtCTErXFeelblRgHQa1FofEzqYYJmJ/OqjTwREp9qgmg==", - "requires": { - "esprima": "^3.1.3", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - } - }, - "esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=" - }, - "jsdom": { - "version": "15.2.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-15.2.1.tgz", - "integrity": "sha512-fAl1W0/7T2G5vURSyxBzrJ1LSdQn6Tr5UX/xD4PXDx/PDgwygedfW6El/KIj3xJ7FU61TTYnc/l/B7P49Eqt6g==", - "requires": { - "abab": "^2.0.0", - "acorn": "^7.1.0", - "acorn-globals": "^4.3.2", - "array-equal": "^1.0.0", - "cssom": "^0.4.1", - "cssstyle": "^2.0.0", - "data-urls": "^1.1.0", - "domexception": "^1.0.1", - "escodegen": "^1.11.1", - "html-encoding-sniffer": "^1.0.2", - "nwsapi": "^2.2.0", - "parse5": "5.1.0", - "pn": "^1.1.0", - "request": "^2.88.0", - "request-promise-native": "^1.0.7", - "saxes": "^3.1.9", - "symbol-tree": "^3.2.2", - "tough-cookie": "^3.0.1", - "w3c-hr-time": "^1.0.1", - "w3c-xmlserializer": "^1.1.2", - "webidl-conversions": "^4.0.2", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^7.0.0", - "ws": "^7.0.0", - "xml-name-validator": "^3.0.0" - }, - "dependencies": { - "cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==" - }, - "cssstyle": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.0.0.tgz", - "integrity": "sha512-QXSAu2WBsSRXCPjvI43Y40m6fMevvyRm8JVAuF9ksQz5jha4pWP1wpaK7Yu5oLFc6+XAY+hj8YhefyXcBB53gg==", - "requires": { - "cssom": "~0.3.6" - }, - "dependencies": { - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" - } - } - } - } - }, - "nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==" - }, - "parse5": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", - "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==" - }, - "request-promise-core": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", - "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", - "requires": { - "lodash": "^4.17.15" - } - }, - "request-promise-native": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.8.tgz", - "integrity": "sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==", - "requires": { - "request-promise-core": "1.1.3", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - }, - "dependencies": { - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - } - } - }, - "tough-cookie": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", - "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", - "requires": { - "ip-regex": "^2.1.0", - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "typescript": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.5.tgz", - "integrity": "sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw==" - }, - "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" - }, - "whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, - "ws": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.1.tgz", - "integrity": "sha512-sucePNSafamSKoOqoNfBd8V0StlkzJKL2ZAhGQinCfNQ+oacw+Pk7lcdAElecBF2VkLNZRiIb5Oi1Q5lVUVt2A==" - } + "@svgr/core": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-4.3.3.tgz", + "integrity": "sha512-qNuGF1QON1626UCaZamWt5yedpgOytvLj5BQZe2j1k1B8DUG4OyugZyfEwBeXozCUwhLEpsrgPrE+eCu4fY17w==", + "dev": true, + "requires": { + "@svgr/plugin-jsx": "^4.3.3", + "camelcase": "^5.3.1", + "cosmiconfig": "^5.2.1" } }, - "@patternfly/react-tokens": { - "version": "2.7.14", - "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-2.7.14.tgz", - "integrity": "sha512-HVa1fe7H4NRRv6lmezpvW2TfIDF7bSbKvhMmCVqBk80Fd3wfLcPhacnWdt6PLWq7WX4dVx7dF7+v4sFh8RczSg==" + "@svgr/hast-util-to-babel-ast": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-4.3.2.tgz", + "integrity": "sha512-JioXclZGhFIDL3ddn4Kiq8qEqYM2PyDKV0aYno8+IXTLuYt6TOgHUbUAAFvqtb0Xn37NwP0BTHglejFoYr8RZg==", + "dev": true, + "requires": { + "@babel/types": "^7.4.4" + } + }, + "@svgr/plugin-jsx": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-4.3.3.tgz", + "integrity": "sha512-cLOCSpNWQnDB1/v+SUENHH7a0XY09bfuMKdq9+gYvtuwzC2rU4I0wKGFEp1i24holdQdwodCtDQdFtJiTCWc+w==", + "dev": true, + "requires": { + "@babel/core": "^7.4.5", + "@svgr/babel-preset": "^4.3.3", + "@svgr/hast-util-to-babel-ast": "^4.3.2", + "svg-parser": "^2.0.0" + } + }, + "@svgr/plugin-svgo": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-4.3.1.tgz", + "integrity": "sha512-PrMtEDUWjX3Ea65JsVCwTIXuSqa3CG9px+DluF1/eo9mlDrgrtFE7NE/DjdhjJgSM9wenlVBzkzneSIUgfUI/w==", + "dev": true, + "requires": { + "cosmiconfig": "^5.2.1", + "merge-deep": "^3.0.2", + "svgo": "^1.2.2" + } + }, + "@svgr/webpack": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-4.3.3.tgz", + "integrity": "sha512-bjnWolZ6KVsHhgyCoYRFmbd26p8XVbulCzSG53BDQqAr+JOAderYK7CuYrB3bDjHJuF6LJ7Wrr42+goLRV9qIg==", + "dev": true, + "requires": { + "@babel/core": "^7.4.5", + "@babel/plugin-transform-react-constant-elements": "^7.0.0", + "@babel/preset-env": "^7.4.5", + "@babel/preset-react": "^7.0.0", + "@svgr/core": "^4.3.3", + "@svgr/plugin-jsx": "^4.3.3", + "@svgr/plugin-svgo": "^4.3.1", + "loader-utils": "^1.2.3" + } }, "@types/babel__core": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.1.tgz", - "integrity": "sha512-+hjBtgcFPYyCTo0A15+nxrCVJL7aC6Acg87TXd5OW3QhHswdrOLoles+ldL2Uk8q++7yIfl4tURtztccdeeyOw==", + "version": "7.1.12", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.12.tgz", + "integrity": "sha512-wMTHiiTiBAAPebqaPiPDLFA4LYPKr6Ph0Xq/6rq1Ur3v66HXyG+clfR9CNETkD7MQS8ZHvpQOtA53DLws5WAEQ==", "dev": true, "requires": { "@babel/parser": "^7.1.0", @@ -2036,18 +2321,18 @@ } }, "@types/babel__generator": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.0.2.tgz", - "integrity": "sha512-NHcOfab3Zw4q5sEE2COkpfXjoE7o+PmqD9DQW4koUT3roNxwziUdXGnRndMat/LJNUtePwn1TlP4do3uoe3KZQ==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.2.tgz", + "integrity": "sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ==", "dev": true, "requires": { "@babel/types": "^7.0.0" } }, "@types/babel__template": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.2.tgz", - "integrity": "sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.0.tgz", + "integrity": "sha512-NTPErx4/FiPCGScH7foPyr+/1Dkzkni+rHiYHHoTjvwou7AQzJkNeD60A9CXRy+ZEN2B1bggmkTMCDb+Mv5k+A==", "dev": true, "requires": { "@babel/parser": "^7.1.0", @@ -2055,37 +2340,107 @@ } }, "@types/babel__traverse": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.6.tgz", - "integrity": "sha512-XYVgHF2sQ0YblLRMLNPB3CkFMewzFmlDsH/TneZFHUXDlABQgh88uOxuez7ZcXxayLFrqLwtDH1t+FmlFwNZxw==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.11.0.tgz", + "integrity": "sha512-kSjgDMZONiIfSH1Nxcr5JIRMwUetDki63FSQfpTCz8ogF3Ulqm8+mr5f78dUYs6vMiB6gBusQqfQmBvHZj/lwg==", "dev": true, "requires": { "@babel/types": "^7.3.0" - }, - "dependencies": { - "@babel/types": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.0.tgz", - "integrity": "sha512-aPvkXyU2SPOnztlgo8n9cEiXW755mgyvueUPcpStqdzoSPm0fjO0vQBjLkt3JKJW7ufikfcnMTTPsN1xaTsBPA==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.11", - "to-fast-properties": "^2.0.0" - } - } + } + }, + "@types/cheerio": { + "version": "0.22.23", + "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.23.tgz", + "integrity": "sha512-QfHLujVMlGqcS/ePSf3Oe5hK3H8wi/yN2JYuxSB1U10VvW1fO3K8C+mURQesFYS1Hn7lspOsTT75SKq/XtydQg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", + "dev": true + }, + "@types/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", + "dev": true, + "requires": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/http-proxy": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.4.tgz", + "integrity": "sha512-IrSHl2u6AWXduUaDLqYpt45tLVCtYv7o4Z0s1KghBCDgIIS9oW5K1H8mZG/A2CfeLdEa7rTd1ACOiHBc1EMT2Q==", + "dev": true, + "requires": { + "@types/node": "*" } }, "@types/istanbul-lib-coverage": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.0.tgz", - "integrity": "sha512-eAtOAFZefEnfJiRFQBGw1eYqa5GTLCZ1y86N0XSI/D6EB+E8z6VPV/UL7Gi5UEclFqoQk+6NRqEDsfmDLXn8sg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz", + "integrity": "sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*", + "@types/istanbul-lib-report": "*" + } + }, + "@types/json-schema": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", + "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", + "dev": true + }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", "dev": true }, "@types/node": { - "version": "12.7.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.2.tgz", - "integrity": "sha512-dyYO+f6ihZEtNPDcWNR1fkoTDf3zAK3lAABDze3mz6POyIercH0lEUawUFXlG8xaQZmm1yEBON/4TsYv/laDYg==", + "version": "14.14.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.14.tgz", + "integrity": "sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ==", + "dev": true + }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "@types/q": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", + "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==", "dev": true }, "@types/stack-utils": { @@ -2095,11 +2450,116 @@ "dev": true }, "@types/yargs": { - "version": "12.0.12", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-12.0.12.tgz", - "integrity": "sha512-SOhuU4wNBxhhTHxYaiG5NY4HBhDIDnJF60GU+2LqHAdKKer86//e4yg69aENCtQ04n0ovz+tq2YPME5t5yp4pw==", + "version": "13.0.11", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.11.tgz", + "integrity": "sha512-NRqD6T4gktUrDi1o1wLH3EKC1o2caCr7/wR87ODcbVITQF106OM3sFN92ysZ++wqelOd1CTzatnOBRDYYG6wGQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", + "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==", "dev": true }, + "@typescript-eslint/eslint-plugin": { + "version": "2.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.34.0.tgz", + "integrity": "sha512-4zY3Z88rEE99+CNvTbXSyovv2z9PNOVffTWD2W8QF5s2prBQtwN2zadqERcrHpcR7O/+KMI3fcTAmUUhK/iQcQ==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "2.34.0", + "functional-red-black-tree": "^1.0.1", + "regexpp": "^3.0.0", + "tsutils": "^3.17.1" + }, + "dependencies": { + "regexpp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "dev": true + } + } + }, + "@typescript-eslint/experimental-utils": { + "version": "2.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz", + "integrity": "sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/typescript-estree": "2.34.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + }, + "dependencies": { + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + } + } + }, + "@typescript-eslint/parser": { + "version": "2.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.34.0.tgz", + "integrity": "sha512-03ilO0ucSD0EPTw2X4PntSIRFtDPWjrVq7C3/Z3VQHRC7+13YB55rcJI3Jt+YgeHbjUdJPcPa7b23rXCBokuyA==", + "dev": true, + "requires": { + "@types/eslint-visitor-keys": "^1.0.0", + "@typescript-eslint/experimental-utils": "2.34.0", + "@typescript-eslint/typescript-estree": "2.34.0", + "eslint-visitor-keys": "^1.1.0" + } + }, + "@typescript-eslint/typescript-estree": { + "version": "2.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz", + "integrity": "sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "eslint-visitor-keys": "^1.1.0", + "glob": "^7.1.6", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, "@webassemblyjs/ast": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", @@ -2289,35 +2749,31 @@ "dev": true }, "abab": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", - "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==" - }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", + "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", + "dev": true }, "accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", "dev": true, "requires": { - "mime-types": "~2.1.18", - "negotiator": "0.6.1" + "mime-types": "~2.1.24", + "negotiator": "0.6.2" } }, "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true }, "acorn-globals": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.0.tgz", - "integrity": "sha512-hMtHj3s5RnuhvHPowpBYvJVj3rAar82JiDQHvGs1zO0l10ocX/xEdBShNHTJaboucJUsScghp74pH3s7EnHHQw==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz", + "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==", "dev": true, "requires": { "acorn": "^6.0.1", @@ -2325,181 +2781,150 @@ }, "dependencies": { "acorn": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.2.tgz", - "integrity": "sha512-GXmKIvbrN3TV7aVqAzVFaMW8F8wzVX7voEBRO3bDA64+EX37YSayggRJP5Xig6HYHBkWKpFg9W5gg6orklubhg==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", "dev": true } } }, "acorn-jsx": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.0.tgz", - "integrity": "sha512-XkB50fn0MURDyww9+UYL3c1yLbOBz0ZFvrdYlGB8l+Ije1oSC75qAqrzSPjYQbdnQUzhlUGNKuesryAv0gxZOg==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", + "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", "dev": true }, "acorn-walk": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.0.tgz", - "integrity": "sha512-ugTb7Lq7u4GfWSqqpwE0bGyoBZNMTok/zDBXxfEG0QM50jNlGhIWjRC1pPN7bvV1anhF+bs+/gNcRw+o55Evbg==" + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", + "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==", + "dev": true }, - "airbnb-prop-types": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/airbnb-prop-types/-/airbnb-prop-types-2.15.0.tgz", - "integrity": "sha512-jUh2/hfKsRjNFC4XONQrxo/n/3GG4Tn6Hl0WlFQN5PY9OMC9loSCoAYKnZsWaP8wEfd5xcrPloK0Zg6iS1xwVA==", + "address": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/address/-/address-1.1.2.tgz", + "integrity": "sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA==", + "dev": true + }, + "adjust-sourcemap-loader": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-3.0.0.tgz", + "integrity": "sha512-YBrGyT2/uVQ/c6Rr+t6ZJXniY03YtHGMJQYal368burRGYKqhx9qGTWqcBU5s1CwYY9E/ri63RYyG1IacMZtqw==", "dev": true, "requires": { - "array.prototype.find": "^2.1.0", - "function.prototype.name": "^1.1.1", - "has": "^1.0.3", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object.assign": "^4.1.0", - "object.entries": "^1.1.0", - "prop-types": "^15.7.2", - "prop-types-exact": "^1.2.0", - "react-is": "^16.9.0" + "loader-utils": "^2.0.0", + "regex-parser": "^2.2.11" }, "dependencies": { - "function.prototype.name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.1.tgz", - "integrity": "sha512-e1NzkiJuw6xqVH7YSdiW/qDHebcmMhPNe6w+4ZYYEg0VA+LaLzx37RimbPLuonHhYGFGPx1ME2nSi74JiaCr/Q==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "function-bind": "^1.1.1", - "functions-have-names": "^1.1.1", - "is-callable": "^1.1.4" - } - }, - "object.entries": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz", - "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.12.0", - "function-bind": "^1.1.1", - "has": "^1.0.3" - } - }, - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", "dev": true, "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" } - }, - "react-is": { - "version": "16.9.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.9.0.tgz", - "integrity": "sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw==", - "dev": true } } }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "airbnb-prop-types": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/airbnb-prop-types/-/airbnb-prop-types-2.16.0.tgz", + "integrity": "sha512-7WHOFolP/6cS96PhKNrslCLMYAI8yB1Pp6u6XmxozQOiZbsI5ycglZr5cHhBFfuRcQQjzCMith5ZPZdYiJCxUg==", + "dev": true, + "requires": { + "array.prototype.find": "^2.1.1", + "function.prototype.name": "^1.1.2", + "is-regex": "^1.1.0", + "object-is": "^1.1.2", + "object.assign": "^4.1.0", + "object.entries": "^1.1.2", + "prop-types": "^15.7.2", + "prop-types-exact": "^1.2.0", + "react-is": "^16.13.1" + } + }, "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", + "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, "ajv-errors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.0.tgz", - "integrity": "sha1-7PAh+hCP0X37Xms4Py3SM+Mf/Fk=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", "dev": true }, "ajv-keywords": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", - "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "alphanum-sort": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", + "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", "dev": true }, "ansi-colors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", - "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", - "requires": { - "ansi-wrap": "^0.1.0" - } - }, - "ansi-cyan": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz", - "integrity": "sha1-U4rlKK+JgvKK4w2G8vF0VtJgmHM=", - "requires": { - "ansi-wrap": "0.1.0" - } + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", + "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", + "dev": true }, "ansi-escapes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", - "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", "dev": true }, - "ansi-gray": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", - "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", - "requires": { - "ansi-wrap": "0.1.0" - } - }, "ansi-html": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", "dev": true }, - "ansi-red": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", - "integrity": "sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw=", - "requires": { - "ansi-wrap": "0.1.0" - } - }, "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" }, "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } }, "ansi-to-html": { - "version": "0.6.11", - "resolved": "https://registry.npmjs.org/ansi-to-html/-/ansi-to-html-0.6.11.tgz", - "integrity": "sha512-88XZtrcwrfkyn6fGstHnkaF1kl7hGtNCYh4vSmItgEV+6JnQHryDBf7udF4f2RhTRQmYvJvPcTtqgaqrxzc9oA==", + "version": "0.6.14", + "resolved": "https://registry.npmjs.org/ansi-to-html/-/ansi-to-html-0.6.14.tgz", + "integrity": "sha512-7ZslfB1+EnFSDO5Ju+ue5Y6It19DRnZXWv8jrGHgIlPna5Mh4jz7BV5jCbQneXNFurQcKoolaaAjHtgSBfOIuA==", "requires": { - "entities": "^1.1.1" + "entities": "^1.1.2" } }, - "ansi-wrap": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", - "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=" - }, "anymatch": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", @@ -2510,18 +2935,6 @@ "normalize-path": "^2.1.1" }, "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, "braces": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", @@ -2551,155 +2964,6 @@ } } }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -2723,34 +2987,11 @@ } } }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true }, "is-number": { "version": "3.0.0", @@ -2772,16 +3013,10 @@ } } }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, "micromatch": { @@ -2804,66 +3039,25 @@ "snapdragon": "^0.8.1", "to-regex": "^3.0.2" } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } } } }, - "append-transform": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", - "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", - "dev": true, - "requires": { - "default-require-extensions": "^2.0.0" - } - }, "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true }, - "are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", - "dev": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -2873,38 +3067,44 @@ } }, "aria-query": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", - "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", "dev": true, "requires": { - "ast-types-flow": "0.0.7", - "commander": "^2.11.0" + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" } }, + "arity-n": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arity-n/-/arity-n-1.0.4.tgz", + "integrity": "sha1-2edrEXM+CFacCEeuezmyhgswt0U=", + "dev": true + }, "arr-diff": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", - "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", - "requires": { - "arr-flatten": "^1.0.1", - "array-slice": "^0.2.3" - } + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true }, "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true }, "arr-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", - "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true }, "array-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", - "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=" + "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", + "dev": true }, "array-filter": { "version": "1.0.0", @@ -2918,12 +3118,6 @@ "integrity": "sha1-bI4obRHtdoMn+OYuzuhzU8o+eLg=", "dev": true }, - "array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", - "dev": true - }, "array-flatten": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", @@ -2931,20 +3125,18 @@ "dev": true }, "array-includes": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", - "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.2.tgz", + "integrity": "sha512-w2GspexNQpx+PutG3QpT437/BenZBj0M/MZGn5mzv/MofYqo0xmRHzn4lFsoDlWJ+THYsGJmFlW68WlDFx7VRw==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.7.0" + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1", + "get-intrinsic": "^1.0.1", + "is-string": "^1.0.5" } }, - "array-slice": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", - "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=" - }, "array-union": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", @@ -2967,45 +3159,56 @@ "dev": true }, "array.prototype.find": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.1.0.tgz", - "integrity": "sha512-Wn41+K1yuO5p7wRZDl7890c3xvv5UBrfVXTVIe28rSQb6LS0fZMDrQB6PAcxQFRFy6vJTLDc3A2+3CjQdzVKRg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.1.1.tgz", + "integrity": "sha512-mi+MYNJYLTx2eNYy+Yh6raoQacCsNeeMUaspFPh9Y141lFSsWxxB8V9mM2ye+eqiRs917J6/pJ4M9ZPzenWckA==", "dev": true, "requires": { "define-properties": "^1.1.3", - "es-abstract": "^1.13.0" + "es-abstract": "^1.17.4" }, "dependencies": { "es-abstract": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", - "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", "dev": true, "requires": { - "es-to-primitive": "^1.2.0", + "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "has": "^1.0.3", - "is-callable": "^1.1.4", - "is-regex": "^1.0.4", - "object-keys": "^1.0.12" + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" } - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true } } }, "array.prototype.flat": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.1.tgz", - "integrity": "sha512-rVqIs330nLJvfC7JqYvEWwqVr5QjYF1ib02i3YJtR/fICO6527Tjpc/e4Mvmxh3GIePPreRXMdaGyC99YphWEw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz", + "integrity": "sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1" + } + }, + "array.prototype.flatmap": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.4.tgz", + "integrity": "sha512-r9Z0zYoxqHz60vvQbWEdXIEtCwHF0yxaWfno9qzXeNHvfyl3BZqygmGzb84dsubyaXLH4husF+NFgMSdpZhk2Q==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.10.0", + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1", "function-bind": "^1.1.1" } }, @@ -3018,33 +3221,45 @@ "asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", + "dev": true }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, "requires": { "safer-buffer": "~2.1.0" } }, "asn1.js": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", - "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", "dev": true, "requires": { "bn.js": "^4.0.0", "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } } }, "assert": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz", - "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", "dev": true, "requires": { + "object-assign": "^4.1.1", "util": "0.10.3" }, "dependencies": { @@ -3068,7 +3283,8 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true }, "assign-symbols": { "version": "1.0.0", @@ -3089,101 +3305,174 @@ "dev": true }, "async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", - "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", "dev": true, "requires": { - "lodash": "^4.17.11" + "lodash": "^4.17.14" } }, "async-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", - "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", - "dev": true - }, - "async-foreach": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", - "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", "dev": true }, "async-limiter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", "dev": true }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true }, "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "attr-accept": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-1.1.3.tgz", + "integrity": "sha512-iT40nudw8zmCweivz6j58g+RT33I4KbaIvRUhjNmDwO2WmsQUxFEZZYZ5w3vXe5x5MX9D7mfvA/XaLOZYFR9EQ==", + "requires": { + "core-js": "^2.5.0" + } + }, + "autoprefixer": { + "version": "9.8.6", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz", + "integrity": "sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg==", + "dev": true, + "requires": { + "browserslist": "^4.12.0", + "caniuse-lite": "^1.0.30001109", + "colorette": "^1.2.1", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^7.0.32", + "postcss-value-parser": "^4.1.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", + "dev": true + } + } }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true }, "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "dev": true + }, + "axe-core": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.1.1.tgz", + "integrity": "sha512-5Kgy8Cz6LPC9DJcNb3yjAXTu3XihQgEdnIg50c//zOC/MyLP0Clg+Y8Sh9ZjjnvBrDZU4DgXS9C3T9r4/scGZQ==", + "dev": true }, "axios": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.1.tgz", - "integrity": "sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", "requires": { - "follow-redirects": "1.5.10", - "is-buffer": "^2.0.2" + "follow-redirects": "^1.10.0" }, "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - }, "follow-redirects": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", - "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", - "requires": { - "debug": "=3.1.0" - } - }, - "is-buffer": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", - "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==" + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz", + "integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg==" } } }, "axobject-query": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz", - "integrity": "sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==", - "dev": true, - "requires": { - "ast-types-flow": "0.0.7" - } + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", + "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==", + "dev": true }, "babel-code-frame": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, "requires": { "chalk": "^1.1.3", "esutils": "^2.0.2", "js-tokens": "^3.0.2" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } } }, "babel-core": { @@ -3193,35 +3482,33 @@ "dev": true }, "babel-eslint": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.1.tgz", - "integrity": "sha512-z7OT1iNV+TjOwHNLLyJk+HN+YVWX+CLE6fPD2SymJZOZQBs+QIexFjhm4keGTm8MW9xr4EC9Q0PbaLB24V5GoQ==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", + "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.0.0", - "@babel/traverse": "^7.0.0", - "@babel/types": "^7.0.0", - "eslint-scope": "3.7.1", - "eslint-visitor-keys": "^1.0.0" - }, - "dependencies": { - "eslint-scope": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", - "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - } + "@babel/parser": "^7.7.0", + "@babel/traverse": "^7.7.0", + "@babel/types": "^7.7.0", + "eslint-visitor-keys": "^1.0.0", + "resolve": "^1.12.0" + } + }, + "babel-extract-comments": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/babel-extract-comments/-/babel-extract-comments-1.0.0.tgz", + "integrity": "sha512-qWWzi4TlddohA91bFwgt6zO/J0X+io7Qp184Fw0m2JYRSTZnJbFR8+07KmzudHCZgOiKRCrjhylwv9Xd8gfhVQ==", + "dev": true, + "requires": { + "babylon": "^6.18.0" } }, "babel-generator": { "version": "6.26.1", "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "dev": true, "requires": { "babel-messages": "^6.23.0", "babel-runtime": "^6.26.0", @@ -3233,230 +3520,81 @@ "trim-right": "^1.0.1" }, "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + "jsesc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "dev": true } } }, - "babel-helper-builder-react-jsx": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz", - "integrity": "sha1-Of+DE7dci2Xc7/HzHTg+D/KkCKA=", - "requires": { - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "esutils": "^2.0.2" - } - }, - "babel-helper-call-delegate": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", - "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", - "requires": { - "babel-helper-hoist-variables": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-helper-define-map": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz", - "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=", - "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "lodash": "^4.17.4" - } - }, - "babel-helper-function-name": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", - "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", - "requires": { - "babel-helper-get-function-arity": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-helper-get-function-arity": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", - "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-helper-hoist-variables": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", - "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-helper-optimise-call-expression": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", - "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-helper-replace-supers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", - "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", - "requires": { - "babel-helper-optimise-call-expression": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-helpers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", - "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", - "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, "babel-jest": { - "version": "24.7.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.7.1.tgz", - "integrity": "sha512-GPnLqfk8Mtt0i4OemjWkChi73A3ALs4w2/QbG64uAj8b5mmwzxc7jbJVRZt8NJkxi6FopVHog9S3xX6UJKb2qg==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.9.0.tgz", + "integrity": "sha512-ntuddfyiN+EhMw58PTNL1ph4C9rECiQXjI4nMMBKBaNjXvqLdkXpPRcMSr4iyBrJg/+wz9brFUD6RhOAT6r4Iw==", "dev": true, "requires": { - "@jest/transform": "^24.7.1", - "@jest/types": "^24.7.0", + "@jest/transform": "^24.9.0", + "@jest/types": "^24.9.0", "@types/babel__core": "^7.1.0", "babel-plugin-istanbul": "^5.1.0", - "babel-preset-jest": "^24.6.0", + "babel-preset-jest": "^24.9.0", "chalk": "^2.4.2", "slash": "^2.0.0" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "babel-plugin-istanbul": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz", + "integrity": "sha512-5LphC0USA8t4i1zCtjbbNb6jJj/9+X6P37Qfirc/70EQ34xKlMW+a1RHGwxGI+SwWpNwZ27HqvzAobeqaXwiZw==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "@babel/helper-plugin-utils": "^7.0.0", + "find-up": "^3.0.0", + "istanbul-lib-instrument": "^3.3.0", + "test-exclude": "^5.2.3" } }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "locate-path": "^3.0.0" } }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", "dev": true }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "istanbul-lib-instrument": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", + "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "@babel/generator": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "istanbul-lib-coverage": "^2.0.5", + "semver": "^6.0.0" } - } - } - }, - "babel-loader": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.4.tgz", - "integrity": "sha512-fhBhNkUToJcW9nV46v8w87AJOwAJDz84c1CL57n3Stj73FANM/b9TbCUK4YhdOwEyZ+OxhYpdeZDNzSI29Firw==", - "dev": true, - "requires": { - "find-cache-dir": "^1.0.0", - "loader-utils": "^1.0.2", - "mkdirp": "^0.5.1", - "util.promisify": "^1.0.0" - } - }, - "babel-messages": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-check-es2015-constants": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", - "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-emotion": { - "version": "9.2.11", - "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-9.2.11.tgz", - "integrity": "sha512-dgCImifnOPPSeXod2znAmgc64NhaaOjGEHROR/M+lmStb3841yK1sgaDYAYMnlvWNz8GnpwIPN0VmNpbWYZ+VQ==", - "requires": { - "@babel/helper-module-imports": "^7.0.0", - "@emotion/babel-utils": "^0.6.4", - "@emotion/hash": "^0.6.2", - "@emotion/memoize": "^0.6.1", - "@emotion/stylis": "^0.7.0", - "babel-plugin-macros": "^2.0.0", - "babel-plugin-syntax-jsx": "^6.18.0", - "convert-source-map": "^1.5.0", - "find-root": "^1.1.0", - "mkdirp": "^0.5.1", - "source-map": "^0.5.7", - "touch": "^2.0.1" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - } - } - }, - "babel-plugin-istanbul": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.1.2.tgz", - "integrity": "sha512-U3ZVajC+Z69Gim7ZzmD4Wcsq76i/1hqDamBfowc1tWzWjybRy70iWfngP2ME+1CrgcgZ/+muIbPY/Yi0dxdIkQ==", - "dev": true, - "requires": { - "find-up": "^3.0.0", - "istanbul-lib-instrument": "^3.2.0", - "test-exclude": "^5.2.2" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", "dev": true, "requires": { - "locate-path": "^3.0.0" + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" } }, "locate-path": { @@ -3469,15 +3607,6 @@ "path-exists": "^3.0.0" } }, - "p-limit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", - "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, "p-locate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", @@ -3487,393 +3616,442 @@ "p-limit": "^2.0.0" } }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "dev": true, + "requires": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + } + }, + "test-exclude": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", + "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", + "dev": true, + "requires": { + "glob": "^7.1.3", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^2.0.0" + } } } }, - "babel-plugin-jest-hoist": { - "version": "24.6.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.6.0.tgz", - "integrity": "sha512-3pKNH6hMt9SbOv0F3WVmy5CWQ4uogS3k0GY5XLyQHJ9EGpAT9XWkFd2ZiXXtkwFHdAHa5j7w7kfxSP5lAIwu7w==", + "babel-loader": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.1.0.tgz", + "integrity": "sha512-7q7nC1tYOrqvUrN3LQK4GwSk/TQorZSOlO9C+RZDZpODgyN4ZlCqE5q9cDsyWOliN+aU9B4JX01xK9eJXowJLw==", "dev": true, "requires": { - "@types/babel__traverse": "^7.0.6" + "find-cache-dir": "^2.1.0", + "loader-utils": "^1.4.0", + "mkdirp": "^0.5.3", + "pify": "^4.0.1", + "schema-utils": "^2.6.5" } }, - "babel-plugin-macros": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.4.2.tgz", - "integrity": "sha512-NBVpEWN4OQ/bHnu1fyDaAaTPAjnhXCEPqr1RwqxrU7b6tZ2hypp+zX4hlNfmVGfClD5c3Sl6Hfj5TJNF5VG5aA==", + "babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true, "requires": { - "cosmiconfig": "^5.0.5", - "resolve": "^1.8.1" + "babel-runtime": "^6.22.0" } }, - "babel-plugin-styled-components": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-1.10.0.tgz", - "integrity": "sha512-sQVKG8irFXx14ZfaK1bBePirfkacl3j8nZwSZK+ZjsbnadRHKQTbhXbe/RB1vT6Vgkz45E+V95LBq4KqdhZUNw==", + "babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.0.0", - "@babel/helper-module-imports": "^7.0.0", - "babel-plugin-syntax-jsx": "^6.18.0", - "lodash": "^4.17.10" - } - }, - "babel-plugin-syntax-class-properties": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz", - "integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=" - }, - "babel-plugin-syntax-flow": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz", - "integrity": "sha1-TDqyCiryaqIM0lmVw5jE63AxDI0=" - }, - "babel-plugin-syntax-jsx": { - "version": "6.18.0", - "resolved": "http://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", - "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=" - }, - "babel-plugin-syntax-object-rest-spread": { - "version": "6.13.0", - "resolved": "http://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", - "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=" - }, - "babel-plugin-syntax-trailing-function-commas": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz", - "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=" - }, - "babel-plugin-transform-class-properties": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz", - "integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=", - "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-plugin-syntax-class-properties": "^6.8.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-arrow-functions": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", - "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-block-scoped-functions": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", - "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-block-scoping": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz", - "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=", - "requires": { - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "lodash": "^4.17.4" - } - }, - "babel-plugin-transform-es2015-classes": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", - "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", - "requires": { - "babel-helper-define-map": "^6.24.1", - "babel-helper-function-name": "^6.24.1", - "babel-helper-optimise-call-expression": "^6.24.1", - "babel-helper-replace-supers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-computed-properties": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", - "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", - "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-destructuring": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", - "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-for-of": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", - "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-function-name": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", - "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", - "requires": { - "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-plugin-transform-es2015-literals": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", - "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-es2015-modules-commonjs": { - "version": "6.26.2", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz", - "integrity": "sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==", - "requires": { - "babel-plugin-transform-strict-mode": "^6.24.1", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-types": "^6.26.0" - } - }, - "babel-plugin-transform-es2015-object-super": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", - "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", - "requires": { - "babel-helper-replace-supers": "^6.24.1", - "babel-runtime": "^6.22.0" + "object.assign": "^4.1.0" } }, - "babel-plugin-transform-es2015-parameters": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", - "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", + "babel-plugin-istanbul": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", + "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", + "dev": true, "requires": { - "babel-helper-call-delegate": "^6.24.1", - "babel-helper-get-function-arity": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1" + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^4.0.0", + "test-exclude": "^6.0.0" } }, - "babel-plugin-transform-es2015-shorthand-properties": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", - "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", + "babel-plugin-jest-hoist": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.9.0.tgz", + "integrity": "sha512-2EMA2P8Vp7lG0RAzr4HXqtYwacfMErOuv1U3wrvxHX6rD1sV6xS3WXG3r8TRQ2r6w8OhvSdWt+z41hQNwNm3Xw==", + "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" + "@types/babel__traverse": "^7.0.6" } }, - "babel-plugin-transform-es2015-spread": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", - "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", + "babel-plugin-macros": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz", + "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==", + "dev": true, "requires": { - "babel-runtime": "^6.22.0" + "@babel/runtime": "^7.7.2", + "cosmiconfig": "^6.0.0", + "resolve": "^1.12.0" + }, + "dependencies": { + "cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + } + }, + "import-fresh": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.2.tgz", + "integrity": "sha512-cTPNrlvJT6twpYy+YmKUKrTSjWFs3bjYjAhCwm+z4EOCubZxAuO+hHpRN64TqjEaYSHs7tJAE0w1CKMGmsG/lw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "parse-json": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + } } }, - "babel-plugin-transform-es2015-template-literals": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", - "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", - "requires": { - "babel-runtime": "^6.22.0" - } + "babel-plugin-named-asset-import": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.7.tgz", + "integrity": "sha512-squySRkf+6JGnvjoUtDEjSREJEBirnXi9NqP6rjSYsylxQxqBTz+pkmf395i9E2zsvmYUaI40BHo6SqZUdydlw==", + "dev": true }, - "babel-plugin-transform-es3-member-expression-literals": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es3-member-expression-literals/-/babel-plugin-transform-es3-member-expression-literals-6.22.0.tgz", - "integrity": "sha1-cz00RPPsxBvvjtGmpOCWV7iWnrs=", + "babel-plugin-styled-components": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-1.12.0.tgz", + "integrity": "sha512-FEiD7l5ZABdJPpLssKXjBUJMYqzbcNzBowfXDCdJhOpbhWiewapUaY+LZGT8R4Jg2TwOjGjG4RKeyrO5p9sBkA==", "requires": { - "babel-runtime": "^6.22.0" + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-module-imports": "^7.0.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.11" } }, - "babel-plugin-transform-es3-property-literals": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es3-property-literals/-/babel-plugin-transform-es3-property-literals-6.22.0.tgz", - "integrity": "sha1-sgeNWELiKr9A9z6M3pzTcRq9V1g=", - "requires": { - "babel-runtime": "^6.22.0" - } + "babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=" }, - "babel-plugin-transform-flow-strip-types": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz", - "integrity": "sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988=", - "requires": { - "babel-plugin-syntax-flow": "^6.18.0", - "babel-runtime": "^6.22.0" - } + "babel-plugin-syntax-object-rest-spread": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", + "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=", + "dev": true }, "babel-plugin-transform-object-rest-spread": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz", "integrity": "sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY=", + "dev": true, "requires": { "babel-plugin-syntax-object-rest-spread": "^6.8.0", "babel-runtime": "^6.26.0" } }, - "babel-plugin-transform-react-display-name": { - "version": "6.25.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz", - "integrity": "sha1-Z+K/Hx6ck6sI25Z5LgU5K/LMKNE=", - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-react-jsx": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz", - "integrity": "sha1-hAoCjn30YN/DotKfDA2R9jduZqM=", - "requires": { - "babel-helper-builder-react-jsx": "^6.24.1", - "babel-plugin-syntax-jsx": "^6.8.0", - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-strict-mode": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", - "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", - "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1" - } - }, - "babel-polyfill": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.23.0.tgz", - "integrity": "sha1-g2TKYt+Or7gwSZ9pkXdGbDsDSZ0=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "core-js": "^2.4.0", - "regenerator-runtime": "^0.10.0" - }, - "dependencies": { - "regenerator-runtime": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", - "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", - "dev": true - } - } - }, - "babel-preset-fbjs": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/babel-preset-fbjs/-/babel-preset-fbjs-2.3.0.tgz", - "integrity": "sha512-ZOpAI1/bN0Y3J1ZAK9gRsFkHy9gGgJoDRUjtUCla/129LC7uViq9nIK22YdHfey8szohYoZY3f9L2lGOv0Edqw==", - "requires": { - "babel-plugin-check-es2015-constants": "^6.8.0", - "babel-plugin-syntax-class-properties": "^6.8.0", - "babel-plugin-syntax-flow": "^6.8.0", - "babel-plugin-syntax-jsx": "^6.8.0", - "babel-plugin-syntax-object-rest-spread": "^6.8.0", - "babel-plugin-syntax-trailing-function-commas": "^6.8.0", - "babel-plugin-transform-class-properties": "^6.8.0", - "babel-plugin-transform-es2015-arrow-functions": "^6.8.0", - "babel-plugin-transform-es2015-block-scoped-functions": "^6.8.0", - "babel-plugin-transform-es2015-block-scoping": "^6.8.0", - "babel-plugin-transform-es2015-classes": "^6.8.0", - "babel-plugin-transform-es2015-computed-properties": "^6.8.0", - "babel-plugin-transform-es2015-destructuring": "^6.8.0", - "babel-plugin-transform-es2015-for-of": "^6.8.0", - "babel-plugin-transform-es2015-function-name": "^6.8.0", - "babel-plugin-transform-es2015-literals": "^6.8.0", - "babel-plugin-transform-es2015-modules-commonjs": "^6.8.0", - "babel-plugin-transform-es2015-object-super": "^6.8.0", - "babel-plugin-transform-es2015-parameters": "^6.8.0", - "babel-plugin-transform-es2015-shorthand-properties": "^6.8.0", - "babel-plugin-transform-es2015-spread": "^6.8.0", - "babel-plugin-transform-es2015-template-literals": "^6.8.0", - "babel-plugin-transform-es3-member-expression-literals": "^6.8.0", - "babel-plugin-transform-es3-property-literals": "^6.8.0", - "babel-plugin-transform-flow-strip-types": "^6.8.0", - "babel-plugin-transform-object-rest-spread": "^6.8.0", - "babel-plugin-transform-react-display-name": "^6.8.0", - "babel-plugin-transform-react-jsx": "^6.8.0" - } + "babel-plugin-transform-react-remove-prop-types": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", + "integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==", + "dev": true }, "babel-preset-jest": { - "version": "24.6.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.6.0.tgz", - "integrity": "sha512-pdZqLEdmy1ZK5kyRUfvBb2IfTPb2BUvIJczlPspS8fWmBQslNNDBqVfh7BW5leOVJMDZKzjD8XEyABTk6gQ5yw==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.9.0.tgz", + "integrity": "sha512-izTUuhE4TMfTRPF92fFwD2QfdXaZW08qvWTFCI51V8rW5x00UuPgc3ajRoWofXOuxjfcOM5zzSYsQS3H8KGCAg==", "dev": true, "requires": { "@babel/plugin-syntax-object-rest-spread": "^7.0.0", - "babel-plugin-jest-hoist": "^24.6.0" - } - }, - "babel-register": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", - "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", - "requires": { - "babel-core": "^6.26.0", - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "home-or-tmp": "^2.0.0", - "lodash": "^4.17.4", - "mkdirp": "^0.5.1", - "source-map-support": "^0.4.15" + "babel-plugin-jest-hoist": "^24.9.0" + } + }, + "babel-preset-react-app": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/babel-preset-react-app/-/babel-preset-react-app-9.1.2.tgz", + "integrity": "sha512-k58RtQOKH21NyKtzptoAvtAODuAJJs3ZhqBMl456/GnXEQ/0La92pNmwgWoMn5pBTrsvk3YYXdY7zpY4e3UIxA==", + "dev": true, + "requires": { + "@babel/core": "7.9.0", + "@babel/plugin-proposal-class-properties": "7.8.3", + "@babel/plugin-proposal-decorators": "7.8.3", + "@babel/plugin-proposal-nullish-coalescing-operator": "7.8.3", + "@babel/plugin-proposal-numeric-separator": "7.8.3", + "@babel/plugin-proposal-optional-chaining": "7.9.0", + "@babel/plugin-transform-flow-strip-types": "7.9.0", + "@babel/plugin-transform-react-display-name": "7.8.3", + "@babel/plugin-transform-runtime": "7.9.0", + "@babel/preset-env": "7.9.0", + "@babel/preset-react": "7.9.1", + "@babel/preset-typescript": "7.9.0", + "@babel/runtime": "7.9.0", + "babel-plugin-macros": "2.8.0", + "babel-plugin-transform-react-remove-prop-types": "0.4.24" }, "dependencies": { - "babel-core": { - "version": "6.26.3", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", - "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", - "requires": { - "babel-code-frame": "^6.26.0", - "babel-generator": "^6.26.0", - "babel-helpers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-register": "^6.26.0", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "convert-source-map": "^1.5.1", - "debug": "^2.6.9", - "json5": "^0.5.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.4", - "path-is-absolute": "^1.0.1", - "private": "^0.1.8", - "slash": "^1.0.0", - "source-map": "^0.5.7" + "@babel/core": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.0.tgz", + "integrity": "sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.9.0", + "@babel/helper-module-transforms": "^7.9.0", + "@babel/helpers": "^7.9.0", + "@babel/parser": "^7.9.0", + "@babel/template": "^7.8.6", + "@babel/traverse": "^7.9.0", + "@babel/types": "^7.9.0", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.13", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.8.3.tgz", + "integrity": "sha512-EqFhbo7IosdgPgZggHaNObkmO1kNUe3slaKu54d5OWvy+p9QIKOzK1GAEpAIsZtWVtPXUHSMcT4smvDrCfY4AA==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" + } + }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.8.3.tgz", + "integrity": "sha512-jWioO1s6R/R+wEHizfaScNsAx+xKgwTLNXSh7tTC4Usj3ItsPEhYkEpU4h+lpnBwq7NBVOJXfO6cRFYcX69JUQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz", + "integrity": "sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.0" + } + }, + "@babel/plugin-transform-react-display-name": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.8.3.tgz", + "integrity": "sha512-3Jy/PCw8Fe6uBKtEgz3M82ljt+lTg+xJaM4og+eyu83qLT87ZUSckn0wy7r31jflURWLO83TW6Ylf7lyXj3m5A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/preset-env": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.9.0.tgz", + "integrity": "sha512-712DeRXT6dyKAM/FMbQTV/FvRCms2hPCx+3weRjZ8iQVQWZejWWk1wwG6ViWMyqb/ouBbGOl5b6aCk0+j1NmsQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.9.0", + "@babel/helper-compilation-targets": "^7.8.7", + "@babel/helper-module-imports": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-proposal-async-generator-functions": "^7.8.3", + "@babel/plugin-proposal-dynamic-import": "^7.8.3", + "@babel/plugin-proposal-json-strings": "^7.8.3", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-proposal-numeric-separator": "^7.8.3", + "@babel/plugin-proposal-object-rest-spread": "^7.9.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.8.3", + "@babel/plugin-proposal-optional-chaining": "^7.9.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.8.3", + "@babel/plugin-syntax-async-generators": "^7.8.0", + "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-json-strings": "^7.8.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", + "@babel/plugin-syntax-numeric-separator": "^7.8.0", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.0", + "@babel/plugin-syntax-top-level-await": "^7.8.3", + "@babel/plugin-transform-arrow-functions": "^7.8.3", + "@babel/plugin-transform-async-to-generator": "^7.8.3", + "@babel/plugin-transform-block-scoped-functions": "^7.8.3", + "@babel/plugin-transform-block-scoping": "^7.8.3", + "@babel/plugin-transform-classes": "^7.9.0", + "@babel/plugin-transform-computed-properties": "^7.8.3", + "@babel/plugin-transform-destructuring": "^7.8.3", + "@babel/plugin-transform-dotall-regex": "^7.8.3", + "@babel/plugin-transform-duplicate-keys": "^7.8.3", + "@babel/plugin-transform-exponentiation-operator": "^7.8.3", + "@babel/plugin-transform-for-of": "^7.9.0", + "@babel/plugin-transform-function-name": "^7.8.3", + "@babel/plugin-transform-literals": "^7.8.3", + "@babel/plugin-transform-member-expression-literals": "^7.8.3", + "@babel/plugin-transform-modules-amd": "^7.9.0", + "@babel/plugin-transform-modules-commonjs": "^7.9.0", + "@babel/plugin-transform-modules-systemjs": "^7.9.0", + "@babel/plugin-transform-modules-umd": "^7.9.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3", + "@babel/plugin-transform-new-target": "^7.8.3", + "@babel/plugin-transform-object-super": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.8.7", + "@babel/plugin-transform-property-literals": "^7.8.3", + "@babel/plugin-transform-regenerator": "^7.8.7", + "@babel/plugin-transform-reserved-words": "^7.8.3", + "@babel/plugin-transform-shorthand-properties": "^7.8.3", + "@babel/plugin-transform-spread": "^7.8.3", + "@babel/plugin-transform-sticky-regex": "^7.8.3", + "@babel/plugin-transform-template-literals": "^7.8.3", + "@babel/plugin-transform-typeof-symbol": "^7.8.4", + "@babel/plugin-transform-unicode-regex": "^7.8.3", + "@babel/preset-modules": "^0.1.3", + "@babel/types": "^7.9.0", + "browserslist": "^4.9.1", + "core-js-compat": "^3.6.2", + "invariant": "^2.2.2", + "levenary": "^1.1.1", + "semver": "^5.5.0" + } + }, + "@babel/preset-react": { + "version": "7.9.1", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.9.1.tgz", + "integrity": "sha512-aJBYF23MPj0RNdp/4bHnAP0NVqqZRr9kl0NAOP4nJCex6OYVio59+dnQzsAWFuogdLyeaKA1hmfUIVZkY5J+TQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-transform-react-display-name": "^7.8.3", + "@babel/plugin-transform-react-jsx": "^7.9.1", + "@babel/plugin-transform-react-jsx-development": "^7.9.0", + "@babel/plugin-transform-react-jsx-self": "^7.9.0", + "@babel/plugin-transform-react-jsx-source": "^7.9.0" + } + }, + "@babel/runtime": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.0.tgz", + "integrity": "sha512-cTIudHnzuWLS56ik4DnRnqqNf8MkdUzV4iFFI1h7Jo9xvrpQROYaAnaSd2mHLQAzzZAPfATynX5ord6YlNYNMA==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" } }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true } } }, @@ -3886,38 +4064,11 @@ "regenerator-runtime": "^0.11.0" } }, - "babel-template": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", - "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", - "requires": { - "babel-runtime": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "lodash": "^4.17.4" - } - }, - "babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", - "requires": { - "babel-code-frame": "^6.26.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "debug": "^2.6.8", - "globals": "^9.18.0", - "invariant": "^2.2.2", - "lodash": "^4.17.4" - } - }, "babel-types": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, "requires": { "babel-runtime": "^6.26.0", "esutils": "^2.0.2", @@ -3928,19 +4079,22 @@ "to-fast-properties": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=" + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true } } }, "babylon": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==" + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true }, "base": { "version": "0.11.2", @@ -3995,24 +4149,18 @@ "kind-of": "^6.0.2" } }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true } } }, "base64-js": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", - "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true }, "batch": { @@ -4022,9 +4170,9 @@ "dev": true }, "bcp-47": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-1.0.4.tgz", - "integrity": "sha512-KquGHKBVXDBnOOntjqkqINNyNX0eKhDXYbK+83pDJXWO7lV6D7Ey1IQNIDbVQOHxNv6rdynnfS/RfPLVz5X0WA==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-1.0.8.tgz", + "integrity": "sha512-Y9y1QNBBtYtv7hcmoX0tR+tUNSFZGZ6OL6vKPObq8BbOhkCoyayF6ogfLTgAli/KuAEbsYHYUNq2AQuY6IuLag==", "dev": true, "requires": { "is-alphabetical": "^1.0.0", @@ -4036,29 +4184,31 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, "requires": { "tweetnacl": "^0.14.3" } }, "big.js": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", - "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", "dev": true }, "binary-extensions": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.12.0.tgz", - "integrity": "sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", "dev": true }, - "block-stream": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", - "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", "dev": true, + "optional": true, "requires": { - "inherits": "~2.0.0" + "file-uri-to-path": "1.0.0" } }, "bluebird": { @@ -4068,37 +4218,49 @@ "dev": true }, "bn.js": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", - "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz", + "integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==", "dev": true }, "body-parser": { - "version": "1.18.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", - "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", "dev": true, "requires": { - "bytes": "3.0.0", + "bytes": "3.1.0", "content-type": "~1.0.4", "debug": "2.6.9", "depd": "~1.1.2", - "http-errors": "~1.6.3", - "iconv-lite": "0.4.23", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", "on-finished": "~2.3.0", - "qs": "6.5.2", - "raw-body": "2.3.3", - "type-is": "~1.6.16" + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" }, "dependencies": { - "iconv-lite": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "ms": "2.0.0" } + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "dev": true } } }, @@ -4126,44 +4288,19 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } + "fill-range": "^7.0.1" } }, "brorand": { @@ -4173,9 +4310,10 @@ "dev": true }, "browser-process-hrtime": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", - "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true }, "browser-resolve": { "version": "1.11.3", @@ -4196,7 +4334,7 @@ }, "browserify-aes": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, "requires": { @@ -4232,28 +4370,38 @@ } }, "browserify-rsa": { - "version": "4.0.1", - "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", - "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", "dev": true, "requires": { - "bn.js": "^4.1.0", + "bn.js": "^5.0.0", "randombytes": "^2.0.1" } }, "browserify-sign": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", - "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", - "dev": true, - "requires": { - "bn.js": "^4.1.1", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.2", - "elliptic": "^6.0.0", - "inherits": "^2.0.1", - "parse-asn1": "^5.0.0" + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", + "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", + "dev": true, + "requires": { + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.3", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } } }, "browserify-zlib": { @@ -4266,29 +4414,31 @@ } }, "browserslist": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.3.4.tgz", - "integrity": "sha512-u5iz+ijIMUlmV8blX82VGFrB9ecnUg5qEt55CMZ/YJEhha+d8qpBfOFuutJ6F/VKRXjZoD33b6uvarpPxcl3RA==", + "version": "4.16.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.0.tgz", + "integrity": "sha512-/j6k8R0p3nxOC6kx5JGAxsnhc9ixaWJfYc+TNTzxg6+ARaESAvQGV7h0uNOB4t+pLQJZWzcrMxXOxjgsCj3dqQ==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30000899", - "electron-to-chromium": "^1.3.82", - "node-releases": "^1.0.1" + "caniuse-lite": "^1.0.30001165", + "colorette": "^1.2.1", + "electron-to-chromium": "^1.3.621", + "escalade": "^3.1.1", + "node-releases": "^1.1.67" } }, "bser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.0.0.tgz", - "integrity": "sha1-mseNPtXZFYBP2HrLFYvHlxR6Fxk=", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", "dev": true, "requires": { "node-int64": "^0.4.0" } }, "buffer": { - "version": "4.9.1", - "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", - "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", "dev": true, "requires": { "base64-js": "^1.0.2", @@ -4305,9 +4455,10 @@ } }, "buffer-from": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-0.1.2.tgz", - "integrity": "sha512-RiWIenusJsmI2KcvqQABB83tLxCByE3upSP8QU3rJDMVFGPWLvPQJt/O1Su9moRWeH7d+Q2HYb68f6+v+tw2vg==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true }, "buffer-indexof": { "version": "1.1.1", @@ -4321,12 +4472,6 @@ "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", "dev": true }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true - }, "builtin-status-codes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", @@ -4340,48 +4485,31 @@ "dev": true }, "cacache": { - "version": "12.0.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.3.tgz", - "integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==", + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-13.0.1.tgz", + "integrity": "sha512-5ZvAxd05HDDU+y9BVvcqYu2LLXmPnQ0hW62h32g4xBTgL/MppR4/04NHfj/ycM2y6lmTnbw6HVi+1eN0Psba6w==", "dev": true, "requires": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", + "chownr": "^1.1.2", "figgy-pudding": "^3.5.1", + "fs-minipass": "^2.0.0", "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", + "graceful-fs": "^4.2.2", + "infer-owner": "^1.0.4", "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", + "minipass": "^3.0.0", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", "mkdirp": "^0.5.1", "move-concurrently": "^1.0.1", + "p-map": "^3.0.0", "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" + "rimraf": "^2.7.1", + "ssri": "^7.0.0", + "unique-filename": "^1.1.1" }, "dependencies": { - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -4400,12 +4528,6 @@ "glob": "^7.1.3" } }, - "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true - }, "yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -4429,73 +4551,93 @@ "to-object-path": "^0.3.0", "union-value": "^1.0.0", "unset-value": "^1.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } } }, - "caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "call-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz", + "integrity": "sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==", "dev": true, "requires": { - "callsites": "^0.2.0" + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.0" } }, - "callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "call-me-maybe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", + "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", "dev": true }, - "camel-case": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", - "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "dev": true, "requires": { - "no-case": "^2.2.0", - "upper-case": "^1.1.1" + "callsites": "^2.0.0" } }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "dev": true, + "requires": { + "caller-callsite": "^2.0.0" + } + }, + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", "dev": true }, - "camelcase-keys": { - "version": "2.1.0", - "resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", "dev": true, "requires": { - "camelcase": "^2.0.0", - "map-obj": "^1.0.0" + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" }, "dependencies": { - "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==", "dev": true } } }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, "camelize": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" }, + "caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, "caniuse-lite": { - "version": "1.0.30000902", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000902.tgz", - "integrity": "sha512-EZG6qrRHkW715hOFjOrshH2JygbLfhaC8NjjkE5EdGJZhCYbtnJMaRdicB+2AP8xKX3QzW9g3mkDUTHUoBG5rQ==", + "version": "1.0.30001168", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001168.tgz", + "integrity": "sha512-P2zmX7swIXKu+GMMR01TWa4csIKELTNnZKc+f1CjebmZJQtTAEXmpQSoKVJVVcvPGAA0TEYTOUp3VehavZSFPQ==", "dev": true }, "capture-exit": { @@ -4507,31 +4649,26 @@ "rsvp": "^4.8.4" } }, + "case-sensitive-paths-webpack-plugin": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.3.0.tgz", + "integrity": "sha512-/4YgnZS8y1UXXmC02xD5rRrBEu6T5ub+mQHLNRj0fzTRbgdBYhsNo2V5EqwgqrExjxsjtF/OpAKAMkKsxbD5XQ==", + "dev": true + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true }, "chalk": { - "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "requires": { - "ansi-regex": "^2.0.0" - } - } + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, "chardet": { @@ -4552,151 +4689,46 @@ "htmlparser2": "^3.9.1", "lodash": "^4.15.0", "parse5": "^3.0.1" - }, - "dependencies": { - "parse5": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", - "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", - "dev": true, - "requires": { - "@types/node": "*" - } - } } }, "chokidar": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", - "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", - "dev": true, - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.0", - "braces": "^2.3.0", - "fsevents": "^1.2.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.1", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "lodash.debounce": "^4.0.8", - "normalize-path": "^2.1.1", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.0.0", - "upath": "^1.0.5" + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", + "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" }, "dependencies": { - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dev": true, - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-glob": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", - "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", "dev": true, "requires": { - "is-extglob": "^2.1.1" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" } }, - "is-number": { + "normalize-path": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } } } }, "chownr": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", - "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "dev": true }, "chrome-trace-event": { @@ -4724,12 +4756,6 @@ "safe-buffer": "^5.0.1" } }, - "circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", - "dev": true - }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -4742,12 +4768,6 @@ "static-extend": "^0.1.1" }, "dependencies": { - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true - }, "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", @@ -4756,15 +4776,32 @@ "requires": { "is-descriptor": "^0.1.0" } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + } + } + }, + "clean-css": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", + "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==", + "dev": true, + "requires": { + "source-map": "~0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true } } }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, "cli-cursor": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", @@ -4775,58 +4812,74 @@ } }, "cli-spinners": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.1.0.tgz", - "integrity": "sha512-8B00fJOEh1HPrx4fo5eW16XmE1PcL1tGpGrxy63CXGP9nHdPBN63X75hA1zhvQuhVztJWLqV58Roj2qlNM7cAA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.5.0.tgz", + "integrity": "sha512-PC+AmIuK04E6aeSs/pUccSujsTzBhu4HzC2dL+CfJB/Jcc2qTRbEwZQDfIUpt2Xl8BodYBEq8w4fc0kU2I9DjQ==", "dev": true }, "cli-table": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz", - "integrity": "sha1-9TsFJmqLGguTSz0IIebi3FkUriM=", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.4.tgz", + "integrity": "sha512-1vinpnX/ZERcmE443i3SZTmU5DF0rPO9DrL4I2iVAllhxzCM9SzPlHnz19fsZB78htkKZvYBvj6SZ6vXnaxmTA==", "dev": true, "requires": { - "colors": "1.0.3" - }, - "dependencies": { - "colors": { - "version": "1.0.3", - "resolved": "http://registry.npmjs.org/colors/-/colors-1.0.3.tgz", - "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", - "dev": true - } + "chalk": "^2.4.1", + "string-width": "^4.2.0" } }, "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", "dev": true }, "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", "dev": true, "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" }, "dependencies": { "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^4.1.0" } } } @@ -4838,54 +4891,44 @@ "dev": true }, "clone-deep": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-2.0.2.tgz", - "integrity": "sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", + "integrity": "sha1-TnPdCen7lxzDhnDF3O2cGJZIHMY=", "dev": true, "requires": { - "for-own": "^1.0.0", - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.0", - "shallow-clone": "^1.0.0" - }, - "dependencies": { - "for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } + "for-own": "^0.1.3", + "is-plain-object": "^2.0.1", + "kind-of": "^3.0.2", + "lazy-cache": "^1.0.3", + "shallow-clone": "^0.1.2" } }, "clsx": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.0.4.tgz", - "integrity": "sha512-1mQ557MIZTrL/140j+JVdRM6e31/OA4vTYxXgqIIZlndyfjHpyawKZia1Im05Vp9BWmImkcNrNtFYQMyFcgJDg==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", + "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==" }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", "dev": true }, + "coa": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", + "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", + "dev": true, + "requires": { + "@types/q": "^1.5.1", + "chalk": "^2.4.1", + "q": "^1.1.2" + } + }, "codemirror": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.47.0.tgz", - "integrity": "sha512-kV49Fr+NGFHFc/Imsx6g180hSlkGhuHxTSDDmDHOuyln0MQYFLixDY4+bFkBVeCEiepYfDimAF/e++9jPJk4QA==" + "version": "5.58.3", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.58.3.tgz", + "integrity": "sha512-KBhB+juiyOOgn0AqtRmWyAT3yoElkuvWTI6hsHa9E6GQrl6bk/fdAYcvuqW1/upO9T9rtEtapWdw4XYcNiVDEA==" }, "collection-visit": { "version": "1.0.0", @@ -4897,11 +4940,20 @@ "object-visit": "^1.0.0" } }, + "color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/color/-/color-3.1.3.tgz", + "integrity": "sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ==", + "dev": true, + "requires": { + "color-convert": "^1.9.1", + "color-string": "^1.5.4" + } + }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "requires": { "color-name": "1.1.3" } @@ -4909,26 +4961,43 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, - "color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" + "color-string": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.4.tgz", + "integrity": "sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw==", + "dev": true, + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "colorette": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", + "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==", + "dev": true }, "combined-stream": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", - "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, "requires": { "delayed-stream": "~1.0.0" } }, "commander": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", - "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==" + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "common-tags": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz", + "integrity": "sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw==", + "dev": true }, "commondir": { "version": "1.0.1", @@ -4936,46 +5005,61 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, - "compare-versions": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.4.0.tgz", - "integrity": "sha512-tK69D7oNXXqUW3ZNo/z7NXTEz22TCF0pTE+YF9cxvaAM9XnkLo1fV621xCLrRR6aevJlKxExkss0vWqUCUpqdg==", - "dev": true - }, "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", "dev": true }, + "compose-function": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/compose-function/-/compose-function-3.0.3.tgz", + "integrity": "sha1-ntZ18TzFRQHTCVCkhv9qe6OrGF8=", + "dev": true, + "requires": { + "arity-n": "^1.0.4" + } + }, "compressible": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.15.tgz", - "integrity": "sha512-4aE67DL33dSW9gw4CI2H/yTxqHLNcxp0yS6jB+4h+wr3e43+1z7vm0HU9qXOH8j+qjKuL8+UtkOxYQSMq60Ylw==", + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", "dev": true, "requires": { - "mime-db": ">= 1.36.0 < 2" + "mime-db": ">= 1.43.0 < 2" } }, "compression": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.3.tgz", - "integrity": "sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", "dev": true, "requires": { "accepts": "~1.3.5", "bytes": "3.0.0", - "compressible": "~2.0.14", + "compressible": "~2.0.16", "debug": "2.6.9", - "on-headers": "~1.0.1", + "on-headers": "~1.0.2", "safe-buffer": "5.1.2", "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } } }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true }, "concat-stream": { "version": "1.6.2", @@ -4989,12 +5073,6 @@ "typedarray": "^0.0.6" }, "dependencies": { - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -5002,9 +5080,9 @@ "dev": true }, "readable-stream": { - "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -5027,25 +5105,22 @@ } } }, + "confusing-browser-globals": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.10.tgz", + "integrity": "sha512-gNld/3lySHwuhaVluJUKLePYirM3QNCKzVxqAdhJII9/WXKVX5PURzMVJspS1jTslSqjeuG4KMVTSouit5YPHA==", + "dev": true + }, "connect-history-api-fallback": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz", - "integrity": "sha1-sGhzk0vF40T+9hGhlqb6rgruAVo=", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", + "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", "dev": true }, "console-browserify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", - "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", - "dev": true, - "requires": { - "date-now": "^0.1.4" - } - }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", "dev": true }, "constants-browserify": { @@ -5061,10 +5136,13 @@ "dev": true }, "content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", - "dev": true + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } }, "content-type": { "version": "1.0.4", @@ -5073,17 +5151,18 @@ "dev": true }, "convert-source-map": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", - "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, "requires": { "safe-buffer": "~5.1.1" } }, "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", "dev": true }, "cookie-signature": { @@ -5113,62 +5192,73 @@ "dev": true }, "core-js": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", - "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==" + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==" + }, + "core-js-compat": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.8.1.tgz", + "integrity": "sha512-a16TLmy9NVD1rkjUGbwuyWkiDoN0FDpAwrfLONvHFQx0D9k7J9y0srwMT8QP/Z6HE3MIFaVynEeYwZwPX1o5RQ==", + "dev": true, + "requires": { + "browserslist": "^4.15.0", + "semver": "7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true + } + } + }, + "core-js-pure": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.8.1.tgz", + "integrity": "sha512-Se+LaxqXlVXGvmexKGPvnUIYC1jwXu1H6Pkyb3uBM5d8/NELMYCHs/4/roD7721NxrTLyv7e5nXd5/QLBO+10g==", + "dev": true }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true }, "cosmiconfig": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.6.tgz", - "integrity": "sha512-6DWfizHriCrFWURP1/qyhsiFvYdlJzbCzmtFWh744+KyWsJo5+kPzUZZaMRSSItoYc0pxFX7gEO7ZC1/gN/7AQ==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, "requires": { + "import-fresh": "^2.0.0", "is-directory": "^0.3.1", - "js-yaml": "^3.9.0", + "js-yaml": "^3.13.1", "parse-json": "^4.0.0" } }, "create-ecdh": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", - "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", "dev": true, "requires": { "bn.js": "^4.1.0", - "elliptic": "^6.0.0" - } - }, - "create-emotion": { - "version": "9.2.12", - "resolved": "https://registry.npmjs.org/create-emotion/-/create-emotion-9.2.12.tgz", - "integrity": "sha512-P57uOF9NL2y98Xrbl2OuiDQUZ30GVmASsv5fbsjF4Hlraip2kyAvMm+2PoYUvFFw03Fhgtxk3RqZSm2/qHL9hA==", - "requires": { - "@emotion/hash": "^0.6.2", - "@emotion/memoize": "^0.6.1", - "@emotion/stylis": "^0.7.0", - "@emotion/unitless": "^0.6.2", - "csstype": "^2.5.2", - "stylis": "^3.5.0", - "stylis-rule-sheet": "^0.0.10" - } - }, - "create-emotion-server": { - "version": "9.2.12", - "resolved": "https://registry.npmjs.org/create-emotion-server/-/create-emotion-server-9.2.12.tgz", - "integrity": "sha512-ET+E6A5MkQTEBNDYAnjh6+0cB33qStFXhtflkZNPEaOmvzYlB/xcPnpUk4J7ul3MVa8PCQx2Ei5g2MGY/y1n+g==", - "requires": { - "html-tokenize": "^2.0.0", - "multipipe": "^1.0.2", - "through": "^2.3.8" + "elliptic": "^6.5.3" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } } }, "create-hash": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "dev": true, "requires": { @@ -5181,7 +5271,7 @@ }, "create-hmac": { "version": "1.1.7", - "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "dev": true, "requires": { @@ -5193,23 +5283,25 @@ "sha.js": "^2.4.8" } }, - "create-react-context": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/create-react-context/-/create-react-context-0.2.3.tgz", - "integrity": "sha512-CQBmD0+QGgTaxDL3OX1IDXYqjkp2It4RIbcb99jS6AEg27Ga+a9G3JtK6SIu0HBwPLZlmwt9F7UwWA4Bn92Rag==", - "requires": { - "fbjs": "^0.8.0", - "gud": "^1.0.0" - } - }, "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, "requires": { - "lru-cache": "^4.0.1", + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, "crypto-browserify": { @@ -5235,11 +5327,29 @@ "version": "2.2.4", "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", + "dev": true, "requires": { "inherits": "^2.0.3", "source-map": "^0.6.1", "source-map-resolve": "^0.5.2", "urix": "^0.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "css-blank-pseudo": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz", + "integrity": "sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w==", + "dev": true, + "requires": { + "postcss": "^7.0.5" } }, "css-color-keywords": { @@ -5247,142 +5357,308 @@ "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=" }, - "css-loader": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-1.0.1.tgz", - "integrity": "sha512-+ZHAZm/yqvJ2kDtPne3uX0C+Vr3Zn5jFn2N4HywtS5ujwvsVkyg0VArEXpl3BgczDA8anieki1FIzhchX4yrDw==", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "css-selector-tokenizer": "^0.7.0", - "icss-utils": "^2.1.0", - "loader-utils": "^1.0.2", - "lodash": "^4.17.11", - "postcss": "^6.0.23", - "postcss-modules-extract-imports": "^1.2.0", - "postcss-modules-local-by-default": "^1.2.0", - "postcss-modules-scope": "^1.1.0", - "postcss-modules-values": "^1.3.0", - "postcss-value-parser": "^3.3.0", - "source-list-map": "^2.0.0" - } + "css-color-names": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", + "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=", + "dev": true }, - "css-select": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", - "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "css-declaration-sorter": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz", + "integrity": "sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==", "dev": true, "requires": { - "boolbase": "~1.0.0", - "css-what": "2.1", - "domutils": "1.5.1", - "nth-check": "~1.0.1" + "postcss": "^7.0.1", + "timsort": "^0.3.0" } }, - "css-selector-tokenizer": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.1.tgz", - "integrity": "sha512-xYL0AMZJ4gFzJQsHUKa5jiWWi2vH77WVNg7JYRyewwj6oPh4yb/y6Y9ZCw9dsj/9UauMhtuxR+ogQd//EdEVNA==", + "css-has-pseudo": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz", + "integrity": "sha512-Z8hnfsZu4o/kt+AuFzeGpLVhFOGO9mluyHBaA2bA8aCGTwah5sT3WV/fTHH8UNZUytOIImuGPrl/prlb4oX4qQ==", "dev": true, "requires": { - "cssesc": "^0.1.0", - "fastparse": "^1.1.1", - "regexpu-core": "^1.0.0" + "postcss": "^7.0.6", + "postcss-selector-parser": "^5.0.0-rc.4" }, "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", "dev": true }, - "regexpu-core": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", - "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", "dev": true, "requires": { - "regenerate": "^1.2.1", - "regjsgen": "^0.2.0", - "regjsparser": "^0.1.4" + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" } - }, - "regjsgen": { - "version": "0.2.0", - "resolved": "http://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", - "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", + } + } + }, + "css-loader": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.4.2.tgz", + "integrity": "sha512-jYq4zdZT0oS0Iykt+fqnzVLRIeiPWhka+7BqPn+oSIpWJAHak5tmB/WZrJ2a21JhCeFyNnnlroSl8c+MtVndzA==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "cssesc": "^3.0.0", + "icss-utils": "^4.1.1", + "loader-utils": "^1.2.3", + "normalize-path": "^3.0.0", + "postcss": "^7.0.23", + "postcss-modules-extract-imports": "^2.0.0", + "postcss-modules-local-by-default": "^3.0.2", + "postcss-modules-scope": "^2.1.1", + "postcss-modules-values": "^3.0.0", + "postcss-value-parser": "^4.0.2", + "schema-utils": "^2.6.0" + }, + "dependencies": { + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, - "regjsparser": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", - "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", - "dev": true, - "requires": { - "jsesc": "~0.5.0" - } + "postcss-value-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", + "dev": true } } }, + "css-prefers-color-scheme": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz", + "integrity": "sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg==", + "dev": true, + "requires": { + "postcss": "^7.0.5" + } + }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "dev": true, + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "css-select-base-adapter": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", + "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", + "dev": true + }, "css-to-react-native": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-2.3.1.tgz", - "integrity": "sha512-yO+oEx1Lf+hDKasqQRVrAvzMCz825Huh1VMlEEDlRWyAhFb/FWb6I0KpEF1PkyKQ7NEdcx9d5M2ZEWgJAsgPvQ==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-2.3.2.tgz", + "integrity": "sha512-VOFaeZA053BqvvvqIA8c9n0+9vFppVBAHCp6JgFTtTMU3Mzi+XnelJ9XC9ul3BqFzZyQ5N+H0SnwsWT2Ebchxw==", "requires": { "camelize": "^1.0.0", "css-color-keywords": "^1.0.0", "postcss-value-parser": "^3.3.0" } }, + "css-tree": { + "version": "1.0.0-alpha.37", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", + "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", + "dev": true, + "requires": { + "mdn-data": "2.0.4", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, "css-what": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", "dev": true }, + "cssdb": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-4.4.0.tgz", + "integrity": "sha512-LsTAR1JPEM9TpGhl/0p3nQecC2LJ0kD8X5YARu1hk/9I1gril5vDtMZyNxcEpxxDj34YNck/ucjuoUd66K03oQ==", + "dev": true + }, "cssesc": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", - "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true + }, + "cssnano": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz", + "integrity": "sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==", + "dev": true, + "requires": { + "cosmiconfig": "^5.0.0", + "cssnano-preset-default": "^4.0.7", + "is-resolvable": "^1.0.0", + "postcss": "^7.0.0" + } + }, + "cssnano-preset-default": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz", + "integrity": "sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA==", + "dev": true, + "requires": { + "css-declaration-sorter": "^4.0.1", + "cssnano-util-raw-cache": "^4.0.1", + "postcss": "^7.0.0", + "postcss-calc": "^7.0.1", + "postcss-colormin": "^4.0.3", + "postcss-convert-values": "^4.0.1", + "postcss-discard-comments": "^4.0.2", + "postcss-discard-duplicates": "^4.0.2", + "postcss-discard-empty": "^4.0.1", + "postcss-discard-overridden": "^4.0.1", + "postcss-merge-longhand": "^4.0.11", + "postcss-merge-rules": "^4.0.3", + "postcss-minify-font-values": "^4.0.2", + "postcss-minify-gradients": "^4.0.2", + "postcss-minify-params": "^4.0.2", + "postcss-minify-selectors": "^4.0.2", + "postcss-normalize-charset": "^4.0.1", + "postcss-normalize-display-values": "^4.0.2", + "postcss-normalize-positions": "^4.0.2", + "postcss-normalize-repeat-style": "^4.0.2", + "postcss-normalize-string": "^4.0.2", + "postcss-normalize-timing-functions": "^4.0.2", + "postcss-normalize-unicode": "^4.0.1", + "postcss-normalize-url": "^4.0.1", + "postcss-normalize-whitespace": "^4.0.2", + "postcss-ordered-values": "^4.1.2", + "postcss-reduce-initial": "^4.0.3", + "postcss-reduce-transforms": "^4.0.2", + "postcss-svgo": "^4.0.2", + "postcss-unique-selectors": "^4.0.1" + } + }, + "cssnano-util-get-arguments": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz", + "integrity": "sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=", "dev": true }, - "cssom": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.4.tgz", - "integrity": "sha512-+7prCSORpXNeR4/fUP3rL+TzqtiFfhMvTd7uEqMdgPvLPt4+uzFUeufx5RHjGTACCargg/DiEt/moMQmvnfkog==" + "cssnano-util-get-match": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz", + "integrity": "sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=", + "dev": true }, - "cssstyle": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.3.1.tgz", - "integrity": "sha512-tNvaxM5blOnxanyxI6panOsnfiyLRj3HV4qjqqS45WPNS1usdYWRUQjqTEEELK73lpeP/1KoIGYUwrBn/VcECA==", + "cssnano-util-raw-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", + "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", + "dev": true, "requires": { - "cssom": "0.3.x" + "postcss": "^7.0.0" } }, - "csstype": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.8.tgz", - "integrity": "sha512-msVS9qTuMT5zwAGCVm4mxfrZ18BNc6Csd0oJAtiFMZ1FAx1CCvy2+5MDmYoix63LM/6NDbNtodCiGYGmFgO0dA==" + "cssnano-util-same-parent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz", + "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==", + "dev": true }, - "currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dev": true, + "requires": { + "css-tree": "^1.1.2" + }, + "dependencies": { + "css-tree": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.2.tgz", + "integrity": "sha512-wCoWush5Aeo48GLhfHPbmvZs59Z+M7k5+B1xDnXbdWNcEF423DoFdqSWE0PM5aNk5nI5cp1q7ms36zGApY/sKQ==", + "dev": true, + "requires": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + } + }, + "mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + }, + "cssstyle": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.4.0.tgz", + "integrity": "sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA==", "dev": true, "requires": { - "array-find-index": "^1.0.1" + "cssom": "0.3.x" } }, + "csstype": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.5.tgz", + "integrity": "sha512-uVDi8LpBUKQj6sdxNaTetL6FpeCqTjOvAQuQUa/qAqq8oOd4ivkbhgnqayl0dnPal8Tb/yB1tF+gOvCBiicaiQ==" + }, "cyclist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", "dev": true }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dev": true, + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, "d3": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/d3/-/d3-5.12.0.tgz", - "integrity": "sha512-flYVMoVuhPFHd9zVCe2BxIszUWqBcd5fvQGMNRmSiBrgdnh6Vlruh60RJQTouAK9xPbOB0plxMvBm4MoyODXNg==", + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-5.16.0.tgz", + "integrity": "sha512-4PL5hHaHwX4m7Zr1UapXW23apo6pexCgdetdJ5kTmADpG/7T9Gkxw0M0tf/pjoB63ezCCm0u5UaFYy2aMt0Mcw==", "requires": { "d3-array": "1", "d3-axis": "1", @@ -5428,9 +5704,9 @@ "integrity": "sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ==" }, "d3-brush": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.1.3.tgz", - "integrity": "sha512-v8bbYyCFKjyCzFk/tdWqXwDykY8YWqhXYjcYxfILIit085VZOpj4XJKOMccTsvWxgzSLMJQg5SiqHjslsipEDg==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.1.6.tgz", + "integrity": "sha512-7RW+w7HfMCPyZLifTz/UnJmI5kdkXtpCbombUSs8xniAyo0vIbrDzDwUJB6eJOgl9u5DQOt2TQlYumxzD1SvYA==", "requires": { "d3-dispatch": "1", "d3-drag": "1", @@ -5454,9 +5730,9 @@ "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==" }, "d3-color": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.3.0.tgz", - "integrity": "sha512-NHODMBlj59xPAwl2BDiO2Mog6V+PrGRtBfWKqKRrs9MCqlSkIEb0Z/SfY7jW29ReHTDC/j+vwXhnZcXI3+3fbg==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz", + "integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==" }, "d3-contour": { "version": "1.3.2", @@ -5467,23 +5743,23 @@ } }, "d3-dispatch": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.5.tgz", - "integrity": "sha512-vwKx+lAqB1UuCeklr6Jh1bvC4SZgbSqbkGBLClItFBIYH4vqDJCA7qfoy14lXmJdnBOdxndAMxjCbImJYW7e6g==" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz", + "integrity": "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==" }, "d3-drag": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.4.tgz", - "integrity": "sha512-ICPurDETFAelF1CTHdIyiUM4PsyZLaM+7oIBhmyP+cuVjze5vDZ8V//LdOFjg0jGnFIZD/Sfmk0r95PSiu78rw==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.5.tgz", + "integrity": "sha512-rD1ohlkKQwMZYkQlYVCrSFxsWPzI97+W+PaEIBNTMxRuxz9RF0Hi5nJWHGVJ3Om9d2fRTe1yOBINJyy/ahV95w==", "requires": { "d3-dispatch": "1", "d3-selection": "1" } }, "d3-dsv": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.1.1.tgz", - "integrity": "sha512-1EH1oRGSkeDUlDRbhsFytAXU6cAmXFzc52YUe6MRlPClmWb85MP1J5x+YJRzya4ynZWnbELdSAvATFW/MbxaXw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.2.0.tgz", + "integrity": "sha512-9yVlqvZcSOMhCYzniHE7EVUws7Fa1zgw+/EAV2BxJoG3ME19V6BQFBwI855XQDsxyOuG7NibqRMTtiF/Qup46g==", "requires": { "commander": "2", "iconv-lite": "0.4", @@ -5491,14 +5767,14 @@ } }, "d3-ease": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.5.tgz", - "integrity": "sha512-Ct1O//ly5y5lFM9YTdu+ygq7LleSgSE4oj7vUt9tPLHUi8VCV7QoizGpdWRWAwCO9LdYzIrQDg97+hGVdsSGPQ==" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.7.tgz", + "integrity": "sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ==" }, "d3-fetch": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.1.2.tgz", - "integrity": "sha512-S2loaQCV/ZeyTyIF2oP8D1K9Z4QizUzW7cWeAOAS4U88qOt3Ucf6GsmgthuYSdyB2HyEm4CeGvkQxWsmInsIVA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.2.0.tgz", + "integrity": "sha512-yC78NBVcd2zFAyR/HnUiBS7Lf6inSCoWcSxFfw8FYL7ydiqe80SazNwoffcqOfs95XaLo7yebsmQqDKSsXUtvA==", "requires": { "d3-dsv": "1" } @@ -5515,45 +5791,45 @@ } }, "d3-format": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.1.tgz", - "integrity": "sha512-TUswGe6hfguUX1CtKxyG2nymO+1lyThbkS1ifLX0Sr+dOQtAD5gkrffpHnx+yHNKUZ0Bmg5T4AjUQwugPDrm0g==" + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz", + "integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==" }, "d3-geo": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.11.6.tgz", - "integrity": "sha512-z0J8InXR9e9wcgNtmVnPTj0TU8nhYT6lD/ak9may2PdKqXIeHUr8UbFLoCtrPYNsjv6YaLvSDQVl578k6nm7GA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.12.1.tgz", + "integrity": "sha512-XG4d1c/UJSEX9NfU02KwBL6BYPj8YKHxgBEw5om2ZnTRSbIcego6dhHwcxuSR3clxh0EpE38os1DVPOmnYtTPg==", "requires": { "d3-array": "1" } }, "d3-hierarchy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.8.tgz", - "integrity": "sha512-L+GHMSZNwTpiq4rt9GEsNcpLa4M96lXMR8M/nMG9p5hBE0jy6C+3hWtyZMenPQdwla249iJy7Nx0uKt3n+u9+w==" + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz", + "integrity": "sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==" }, "d3-interpolate": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.3.2.tgz", - "integrity": "sha512-NlNKGopqaz9qM1PXh9gBF1KSCVh+jSFErrSlD/4hybwoNX/gt1d8CDbDW+3i+5UOHhjC6s6nMvRxcuoMVNgL2w==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz", + "integrity": "sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==", "requires": { "d3-color": "1" } }, "d3-path": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.8.tgz", - "integrity": "sha512-J6EfUNwcMQ+aM5YPOB8ZbgAZu6wc82f/0WFxrxwV6Ll8wBwLaHLKCqQ5Imub02JriCVVdPjgI+6P3a4EWJCxAg==" + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" }, "d3-polygon": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.5.tgz", - "integrity": "sha512-RHhh1ZUJZfhgoqzWWuRhzQJvO7LavchhitSTHGu9oj6uuLFzYZVeBzaWTQ2qSO6bz2w55RMoOCf0MsLCDB6e0w==" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.6.tgz", + "integrity": "sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ==" }, "d3-quadtree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.6.tgz", - "integrity": "sha512-NUgeo9G+ENQCQ1LsRr2qJg3MQ4DJvxcDNCiohdJGHt5gRhBW6orIB5m5FJ9kK3HNL8g9F4ERVoBzcEwQBfXWVA==" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.7.tgz", + "integrity": "sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA==" }, "d3-random": { "version": "1.1.2", @@ -5583,40 +5859,40 @@ } }, "d3-selection": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.0.tgz", - "integrity": "sha512-EYVwBxQGEjLCKF2pJ4+yrErskDnz5v403qvAid96cNdCMr8rmCYfY5RGzWz24mdIbxmDf6/4EAH+K9xperD5jg==" + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz", + "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" }, "d3-shape": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.5.tgz", - "integrity": "sha512-VKazVR3phgD+MUCldapHD7P9kcrvPcexeX/PkMJmkUov4JM8IxsSg1DvbYoYich9AtdTsa5nNk2++ImPiDiSxg==", + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", "requires": { "d3-path": "1" } }, "d3-time": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.0.11.tgz", - "integrity": "sha512-Z3wpvhPLW4vEScGeIMUckDW7+3hWKOQfAWg/U7PlWBnQmeKQ00gCUsTtWSYulrKNA7ta8hJ+xXc6MHrMuITwEw==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz", + "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==" }, "d3-time-format": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.1.3.tgz", - "integrity": "sha512-6k0a2rZryzGm5Ihx+aFMuO1GgelgIz+7HhB4PH4OEndD5q2zGn1mDfRdNrulspOfR6JXkb2sThhDK41CSK85QA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.3.0.tgz", + "integrity": "sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==", "requires": { "d3-time": "1" } }, "d3-timer": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.9.tgz", - "integrity": "sha512-rT34J5HnQUHhcLvhSB9GjCkN0Ddd5Y8nCwDBG2u6wQEeYxT/Lf51fTFFkldeib/sE/J0clIe0pnCfs6g/lRbyg==" + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.10.tgz", + "integrity": "sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==" }, "d3-transition": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.2.0.tgz", - "integrity": "sha512-VJ7cmX/FPIPJYuaL2r1o1EMHLttvoIuZhhuAlRoOxDzogV8iQS6jYulDm3xEU3TqL80IZIhI551/ebmCMrkvhw==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.3.2.tgz", + "integrity": "sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==", "requires": { "d3-color": "1", "d3-dispatch": "1", @@ -5644,24 +5920,25 @@ } }, "dagre": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.4.tgz", - "integrity": "sha512-Dj0csFDrWYKdavwROb9FccHfTC4fJbyF/oJdL9LNZJ8WUvl968P6PAKEriGqfbdArVJEmmfA+UyumgWEwcHU6A==", + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz", + "integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==", "requires": { - "graphlib": "^2.1.7", - "lodash": "^4.17.4" + "graphlib": "^2.1.8", + "lodash": "^4.17.15" } }, "damerau-levenshtein": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz", - "integrity": "sha1-AxkcQyy27qFou3fzpV/9zLiXhRQ=", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz", + "integrity": "sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==", "dev": true }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, "requires": { "assert-plus": "^1.0.0" } @@ -5670,6 +5947,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", + "dev": true, "requires": { "abab": "^2.0.0", "whatwg-mimetype": "^2.2.0", @@ -5677,9 +5955,10 @@ }, "dependencies": { "whatwg-url": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", - "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, "requires": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", @@ -5694,16 +5973,11 @@ "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==", "dev": true }, - "date-now": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", - "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", - "dev": true - }, "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, "requires": { "ms": "2.0.0" } @@ -5717,18 +5991,28 @@ "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true }, "deep-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", - "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", - "dev": true + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", + "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "dev": true, + "requires": { + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.1", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.2.0" + } }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true }, "deepmerge": { "version": "2.2.1", @@ -5736,60 +6020,13 @@ "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==" }, "default-gateway": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-2.7.2.tgz", - "integrity": "sha512-lAc4i9QJR0YHSDFdzeBQKfZ1SRDG3hsJNEkrpcZa8QhBfidLAilT60BDEIVUUGqosFp425KOgB3uYqcnQrWafQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", + "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==", "dev": true, "requires": { - "execa": "^0.10.0", + "execa": "^1.0.0", "ip-regex": "^2.1.0" - }, - "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", - "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - } - } - }, - "default-require-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", - "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", - "dev": true, - "requires": { - "strip-bom": "^3.0.0" - }, - "dependencies": { - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - } } }, "defaults": { @@ -5808,14 +6045,6 @@ "dev": true, "requires": { "object-keys": "^1.0.12" - }, - "dependencies": { - "object-keys": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", - "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", - "dev": true - } } }, "define-property": { @@ -5857,44 +6086,62 @@ "kind-of": "^6.0.2" } }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true } } }, "del": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", + "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", "dev": true, "requires": { - "globby": "^5.0.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "rimraf": "^2.2.8" + "@types/glob": "^7.1.1", + "globby": "^6.1.0", + "is-path-cwd": "^2.0.0", + "is-path-in-cwd": "^2.0.0", + "p-map": "^2.0.0", + "pify": "^4.0.1", + "rimraf": "^2.6.3" + }, + "dependencies": { + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + } } }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, "depd": { @@ -5904,9 +6151,9 @@ "dev": true }, "des.js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", - "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", "dev": true, "requires": { "inherits": "^2.0.1", @@ -5923,6 +6170,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "dev": true, "requires": { "repeating": "^2.0.0" } @@ -5939,21 +6187,77 @@ "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==", "dev": true }, + "detect-port-alt": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz", + "integrity": "sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==", + "dev": true, + "requires": { + "address": "^1.0.1", + "debug": "^2.6.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, "diff-sequences": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.3.0.tgz", - "integrity": "sha512-xLqpez+Zj9GKSnPWS0WZw1igGocZ+uua8+y+5dDNTT934N3QuY1sp2LkHzwiaYQGz60hMq0pjAshdeXm5VUOEw==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz", + "integrity": "sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==", "dev": true }, "diffie-hellman": { "version": "5.0.3", - "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "dev": true, "requires": { "bn.js": "^4.1.0", "miller-rabin": "^4.0.0", "randombytes": "^2.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "dir-glob": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", + "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "path-type": "^3.0.0" + }, + "dependencies": { + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } } }, "discontinuous-range": { @@ -5988,20 +6292,30 @@ } }, "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "requires": { "esutils": "^2.0.2" } }, + "dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dev": true, + "requires": { + "utila": "~0.4" + } + }, "dom-helpers": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", - "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.0.tgz", + "integrity": "sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ==", "requires": { - "@babel/runtime": "^7.1.2" + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" } }, "dom-serializer": { @@ -6014,12 +6328,6 @@ "entities": "^1.1.1" } }, - "dom-walk": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz", - "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=", - "dev": true - }, "domain-browser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", @@ -6036,6 +6344,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "dev": true, "requires": { "webidl-conversions": "^4.0.2" } @@ -6059,43 +6368,51 @@ "domelementtype": "1" } }, - "duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, "requires": { - "readable-stream": "^2.0.2" + "no-case": "^3.0.4", + "tslib": "^2.0.3" }, "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==", + "dev": true } } }, + "dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "dotenv": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", + "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==", + "dev": true + }, + "dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", + "dev": true + }, + "duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -6115,9 +6432,9 @@ "dev": true }, "readable-stream": { - "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -6144,6 +6461,7 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, "requires": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -6156,15 +6474,15 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.82", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.82.tgz", - "integrity": "sha512-NI4nB2IWGcU4JVT1AE8kBb/dFor4zjLHMLsOROPahppeHrR0FG5uslxMmkp/thO1MvPjM2xhlKoY29/I60s0ew==", + "version": "1.3.628", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.628.tgz", + "integrity": "sha512-fmhO4YGo/kapy+xL9Eq/cZwDASaTHZu3psIFYo4yc+RY1LzbZr84xjKlDImDrlrmWhOxsrDi98nX097U/xK/cQ==", "dev": true }, "elliptic": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz", - "integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", + "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", "dev": true, "requires": { "bn.js": "^4.4.0", @@ -6174,69 +6492,52 @@ "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0", "minimalistic-crypto-utils": "^1.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } } }, "emoji-regex": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.5.1.tgz", - "integrity": "sha512-PAHp6TxrCy7MGMFidro8uikr+zlJJKJ/Q6mm2ExZ7HwkyR9lSVFfE3kt36qcwa24BQL7y0G9axycGjK1A/0uNQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, "emojis-list": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", - "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", "dev": true }, - "emotion": { - "version": "9.2.12", - "resolved": "https://registry.npmjs.org/emotion/-/emotion-9.2.12.tgz", - "integrity": "sha512-hcx7jppaI8VoXxIWEhxpDW7I+B4kq9RNzQLmsrF6LY8BGKqe2N+gFAQr0EfuFucFlPs2A9HM4+xNj4NeqEWIOQ==", - "requires": { - "babel-plugin-emotion": "^9.2.11", - "create-emotion": "^9.2.12" - } - }, - "emotion-server": { - "version": "9.2.12", - "resolved": "https://registry.npmjs.org/emotion-server/-/emotion-server-9.2.12.tgz", - "integrity": "sha512-Bhjdl7eNoIeiAVa2QPP5d+1nP/31SiO/K1P/qI9cdXCydg91NwGYmteqhhge8u7PF8fLGTEVQfcPwj21815eBw==", - "requires": { - "create-emotion-server": "^9.2.12" - } - }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", "dev": true }, - "encoding": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", - "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", - "requires": { - "iconv-lite": "~0.4.13" - } - }, "end-of-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "dev": true, "requires": { "once": "^1.4.0" } }, "enhanced-resolve": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz", - "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz", + "integrity": "sha1-TW5omzcl+GCQknzMhs2fFjW4ni4=", "dev": true, "requires": { "graceful-fs": "^4.1.2", - "memory-fs": "^0.4.0", - "tapable": "^1.0.0" + "memory-fs": "^0.2.0", + "tapable": "^0.1.8" } }, "entities": { @@ -6245,67 +6546,52 @@ "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" }, "enzyme": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.10.0.tgz", - "integrity": "sha512-p2yy9Y7t/PFbPoTvrWde7JIYB2ZyGC+NgTNbVEGvZ5/EyoYSr9aG/2rSbVvyNvMHEhw9/dmGUJHWtfQIEiX9pg==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.11.0.tgz", + "integrity": "sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw==", "dev": true, "requires": { - "array.prototype.flat": "^1.2.1", - "cheerio": "^1.0.0-rc.2", - "function.prototype.name": "^1.1.0", + "array.prototype.flat": "^1.2.3", + "cheerio": "^1.0.0-rc.3", + "enzyme-shallow-equal": "^1.0.1", + "function.prototype.name": "^1.1.2", "has": "^1.0.3", - "html-element-map": "^1.0.0", - "is-boolean-object": "^1.0.0", - "is-callable": "^1.1.4", - "is-number-object": "^1.0.3", - "is-regex": "^1.0.4", - "is-string": "^1.0.4", + "html-element-map": "^1.2.0", + "is-boolean-object": "^1.0.1", + "is-callable": "^1.1.5", + "is-number-object": "^1.0.4", + "is-regex": "^1.0.5", + "is-string": "^1.0.5", "is-subset": "^0.1.1", "lodash.escape": "^4.0.1", "lodash.isequal": "^4.5.0", - "object-inspect": "^1.6.0", - "object-is": "^1.0.1", + "object-inspect": "^1.7.0", + "object-is": "^1.0.2", "object.assign": "^4.1.0", - "object.entries": "^1.0.4", - "object.values": "^1.0.4", - "raf": "^3.4.0", + "object.entries": "^1.1.1", + "object.values": "^1.1.1", + "raf": "^3.4.1", "rst-selector-parser": "^2.2.3", - "string.prototype.trim": "^1.1.2" + "string.prototype.trim": "^1.2.1" } }, "enzyme-adapter-react-16": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.14.0.tgz", - "integrity": "sha512-7PcOF7pb4hJUvjY7oAuPGpq3BmlCig3kxXGi2kFx0YzJHppqX1K8IIV9skT1IirxXlu8W7bneKi+oQ10QRnhcA==", + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.5.tgz", + "integrity": "sha512-33yUJGT1nHFQlbVI5qdo5Pfqvu/h4qPwi1o0a6ZZsjpiqq92a3HjynDhwd1IeED+Su60HDWV8mxJqkTnLYdGkw==", "dev": true, "requires": { - "enzyme-adapter-utils": "^1.12.0", + "enzyme-adapter-utils": "^1.13.1", + "enzyme-shallow-equal": "^1.0.4", "has": "^1.0.3", "object.assign": "^4.1.0", - "object.values": "^1.1.0", + "object.values": "^1.1.1", "prop-types": "^15.7.2", - "react-is": "^16.8.6", + "react-is": "^16.13.1", "react-test-renderer": "^16.0.0-0", "semver": "^5.7.0" }, "dependencies": { - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "dev": true, - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - }, - "react-is": { - "version": "16.9.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.9.0.tgz", - "integrity": "sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw==", - "dev": true - }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -6315,51 +6601,53 @@ } }, "enzyme-adapter-utils": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.12.0.tgz", - "integrity": "sha512-wkZvE0VxcFx/8ZsBw0iAbk3gR1d9hK447ebnSYBf95+r32ezBq+XDSAvRErkc4LZosgH8J7et7H7/7CtUuQfBA==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.14.0.tgz", + "integrity": "sha512-F/z/7SeLt+reKFcb7597IThpDp0bmzcH1E9Oabqv+o01cID2/YInlqHbFl7HzWBl4h3OdZYedtwNDOmSKkk0bg==", "dev": true, "requires": { - "airbnb-prop-types": "^2.13.2", - "function.prototype.name": "^1.1.0", - "object.assign": "^4.1.0", - "object.fromentries": "^2.0.0", + "airbnb-prop-types": "^2.16.0", + "function.prototype.name": "^1.1.3", + "has": "^1.0.3", + "object.assign": "^4.1.2", + "object.fromentries": "^2.0.3", "prop-types": "^15.7.2", - "semver": "^5.6.0" + "semver": "^5.7.1" }, "dependencies": { - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "dev": true, - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - }, - "react-is": { - "version": "16.9.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.9.0.tgz", - "integrity": "sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw==", + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true } } }, - "enzyme-to-json": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/enzyme-to-json/-/enzyme-to-json-3.3.5.tgz", - "integrity": "sha512-DmH1wJ68HyPqKSYXdQqB33ZotwfUhwQZW3IGXaNXgR69Iodaoj8TF/D9RjLdz4pEhGq2Tx2zwNUIjBuqoZeTgA==", + "enzyme-shallow-equal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.4.tgz", + "integrity": "sha512-MttIwB8kKxypwHvRynuC3ahyNc+cFbR8mjVIltnmzQ0uKGqmsfO4bfBuLxb0beLNPhjblUEYvEbsg+VSygvF1Q==", "dev": true, "requires": { - "lodash": "^4.17.4" + "has": "^1.0.3", + "object-is": "^1.1.2" + } + }, + "enzyme-to-json": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/enzyme-to-json/-/enzyme-to-json-3.6.1.tgz", + "integrity": "sha512-15tXuONeq5ORoZjV/bUo2gbtZrN2IH+Z6DvL35QmZyKHgbY1ahn6wcnLd9Xv9OjiwbAXiiP8MRZwbZrCv1wYNg==", + "dev": true, + "requires": { + "@types/cheerio": "^0.22.22", + "lodash": "^4.17.15", + "react-is": "^16.12.0" } }, "errno": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", "dev": true, "requires": { "prr": "~1.0.1" @@ -6369,27 +6657,35 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, "requires": { "is-arrayish": "^0.2.1" } }, "es-abstract": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", - "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", "dev": true, "requires": { - "es-to-primitive": "^1.1.1", + "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", - "has": "^1.0.1", - "is-callable": "^1.1.3", - "is-regex": "^1.0.4" + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" } }, "es-to-primitive": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", - "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, "requires": { "is-callable": "^1.1.4", @@ -6397,6 +6693,44 @@ "is-symbol": "^1.0.2" } }, + "es5-ext": { + "version": "0.10.53", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "dev": true, + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dev": true, + "requires": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -6409,223 +6743,355 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escodegen": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.0.tgz", - "integrity": "sha512-IeMV45ReixHS53K/OmfKAIztN/igDHzTJUhZM3k1jMhIZWjk45SMwAtBsEXiJp3vSPmTcu6CXn7mDvFHRN66fw==", + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", "dev": true, "requires": { - "esprima": "^3.1.3", + "esprima": "^4.0.1", "estraverse": "^4.2.0", "esutils": "^2.0.2", "optionator": "^0.8.1", "source-map": "~0.6.1" }, "dependencies": { - "esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", - "dev": true + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true } } }, "eslint": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.8.0.tgz", - "integrity": "sha512-Zok6Bru3y2JprqTNm14mgQ15YQu/SMDkWdnmHfFg770DIUlmMFd/gqqzCHekxzjHZJxXv3tmTpH0C1icaYJsRQ==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "ajv": "^6.5.3", + "ajv": "^6.10.0", "chalk": "^2.1.0", "cross-spawn": "^6.0.5", "debug": "^4.0.1", - "doctrine": "^2.1.0", - "eslint-scope": "^4.0.0", - "eslint-utils": "^1.3.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^4.0.0", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", "esquery": "^1.0.1", "esutils": "^2.0.2", - "file-entry-cache": "^2.0.0", + "file-entry-cache": "^5.0.1", "functional-red-black-tree": "^1.0.1", - "glob": "^7.1.2", - "globals": "^11.7.0", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", "ignore": "^4.0.6", + "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", - "inquirer": "^6.1.0", - "is-resolvable": "^1.1.0", - "js-yaml": "^3.12.0", + "inquirer": "^7.0.0", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.3.0", - "lodash": "^4.17.5", + "lodash": "^4.17.14", "minimatch": "^3.0.4", "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.2", - "pluralize": "^7.0.0", + "optionator": "^0.8.3", "progress": "^2.0.0", "regexpp": "^2.0.1", - "require-uncached": "^1.0.3", - "semver": "^5.5.1", - "strip-ansi": "^4.0.0", - "strip-json-comments": "^2.0.1", - "table": "^5.0.2", - "text-table": "^0.2.0" + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" }, "dependencies": { - "ajv": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.4.tgz", - "integrity": "sha512-4Wyjt8+t6YszqaXnLDfMmG/8AlO5Zbcsy3ATHncCzjW/NoPzAId8AK6749Ybjmdt+kUY1gP60fCu46oDxPv/mg==", + "ansi-escapes": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", "dev": true, "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "type-fest": "^0.11.0" + }, + "dependencies": { + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "dev": true + } } }, "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true }, "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "color-convert": "^2.0.1" } }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "restore-cursor": "^3.1.0" } }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "color-name": "~1.1.4" } }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "debug": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", - "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } }, "globals": { - "version": "11.8.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.8.0.tgz", - "integrity": "sha512-io6LkyPVuzCHBSQV9fmOwxZkUk6nIaGmxheLDgmuFv89j0fm2aqDbIXKAGfzCMHqz3HLF2Zf8WSG6VqMh2qFmA==", + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "import-fresh": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.2.tgz", + "integrity": "sha512-cTPNrlvJT6twpYy+YmKUKrTSjWFs3bjYjAhCwm+z4EOCubZxAuO+hHpRN64TqjEaYSHs7tJAE0w1CKMGmsG/lw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "inquirer": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", + "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.19", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.6.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "strip-ansi": { + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "resolve-from": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + } } }, "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true } } }, "eslint-config-airbnb": { - "version": "17.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-17.1.0.tgz", - "integrity": "sha512-R9jw28hFfEQnpPau01NO5K/JWMGLi6aymiF6RsnMURjTk+MqZKllCqGK/0tOvHkPi/NWSSOU2Ced/GX++YxLnw==", + "version": "17.1.1", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-17.1.1.tgz", + "integrity": "sha512-xCu//8a/aWqagKljt+1/qAM62BYZeNq04HmdevG5yUGWpja0I/xhqd6GdLRch5oetEGFiJAnvtGuTEAese53Qg==", "dev": true, "requires": { - "eslint-config-airbnb-base": "^13.1.0", + "eslint-config-airbnb-base": "^13.2.0", "object.assign": "^4.1.0", - "object.entries": "^1.0.4" + "object.entries": "^1.1.0" } }, "eslint-config-airbnb-base": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-13.1.0.tgz", - "integrity": "sha512-XWwQtf3U3zIoKO1BbHh6aUhJZQweOwSt4c2JrPDg9FP3Ltv3+YfEv7jIDB8275tVnO/qOHbfuYg3kzw6Je7uWw==", + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-13.2.0.tgz", + "integrity": "sha512-1mg/7eoB4AUeB0X1c/ho4vb2gYkNH8Trr/EgCT/aGmKhhG+F6vF5s8+iRBlWAzFIAphxIdp3YfEKgEl0f9Xg+w==", "dev": true, "requires": { - "eslint-restricted-globals": "^0.1.1", + "confusing-browser-globals": "^1.0.5", "object.assign": "^4.1.0", - "object.entries": "^1.0.4" + "object.entries": "^1.1.0" } }, "eslint-config-prettier": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-5.0.0.tgz", - "integrity": "sha512-c17Aqiz5e8LEqoc/QPmYnaxQFAHTx2KlCZBPxXXjEMmNchOLnV/7j0HoPZuC+rL/tDC9bazUYOKJW9bOhftI/w==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-5.1.0.tgz", + "integrity": "sha512-+tpiaLm3wl6fPW5nq0dDyVowQM0FT61lAdWZ+sDWgk6kKzgbOnCDwlcbwI38cyCBhq+Z3ret5Iofp6/gZpO0zw==", "dev": true, "requires": { "get-stdin": "^6.0.0" - }, - "dependencies": { - "get-stdin": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", - "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", - "dev": true - } + } + }, + "eslint-config-react-app": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-5.2.1.tgz", + "integrity": "sha512-pGIZ8t0mFLcV+6ZirRgYK6RVqUIKRIi9MmgzUEmrIknsn3AdO0I32asO86dJgloHq+9ZPl8UIg8mYrvgP5u2wQ==", + "dev": true, + "requires": { + "confusing-browser-globals": "^1.0.9" } }, "eslint-import-resolver-node": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", - "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz", + "integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==", "dev": true, "requires": { "debug": "^2.6.9", - "resolve": "^1.5.0" + "resolve": "^1.13.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } } }, "eslint-import-resolver-webpack": { @@ -6646,98 +7112,96 @@ "semver": "^5.3.0" }, "dependencies": { - "enhanced-resolve": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz", - "integrity": "sha1-TW5omzcl+GCQknzMhs2fFjW4ni4=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.2.0", - "tapable": "^0.1.8" - } - }, - "memory-fs": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.2.0.tgz", - "integrity": "sha1-8rslNovBIeORwlIN6Slpyu4KApA=", - "dev": true - }, - "resolve": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.0.tgz", - "integrity": "sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw==", + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { - "path-parse": "^1.0.6" + "ms": "2.0.0" } }, - "tapable": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.1.10.tgz", - "integrity": "sha1-KcNXB8K3DlDQdIK10gLo7URtr9Q=", + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true } } }, + "eslint-loader": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/eslint-loader/-/eslint-loader-3.0.3.tgz", + "integrity": "sha512-+YRqB95PnNvxNp1HEjQmvf9KNvCin5HXYYseOXVC2U0KEcw4IkQ2IQEBG46j7+gW39bMzeu0GsUhVbBY3Votpw==", + "dev": true, + "requires": { + "fs-extra": "^8.1.0", + "loader-fs-cache": "^1.0.2", + "loader-utils": "^1.2.3", + "object-hash": "^2.0.1", + "schema-utils": "^2.6.1" + } + }, "eslint-module-utils": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.2.0.tgz", - "integrity": "sha1-snA2LNiLGkitMIl2zn+lTphBF0Y=", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz", + "integrity": "sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==", "dev": true, "requires": { - "debug": "^2.6.8", - "pkg-dir": "^1.0.0" + "debug": "^2.6.9", + "pkg-dir": "^2.0.0" }, "dependencies": { - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "requires": { - "pinkie-promise": "^2.0.0" - } - }, - "pkg-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", - "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { - "find-up": "^1.0.0" + "ms": "2.0.0" } } } }, + "eslint-plugin-flowtype": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-4.6.0.tgz", + "integrity": "sha512-W5hLjpFfZyZsXfo5anlu7HM970JBDqbEshAJUkeczP6BFCIfJXuiIBQXyberLRtOStT0OGPF8efeTbxlHk4LpQ==", + "dev": true, + "requires": { + "lodash": "^4.17.15" + } + }, "eslint-plugin-import": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.14.0.tgz", - "integrity": "sha512-FpuRtniD/AY6sXByma2Wr0TXvXJ4nA/2/04VPlfpmUDPOpOY264x+ILiwnrk/k4RINgDAyFZByxqPUbSQ5YE7g==", + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz", + "integrity": "sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw==", "dev": true, "requires": { + "array-includes": "^3.1.1", + "array.prototype.flat": "^1.2.3", "contains-path": "^0.1.0", - "debug": "^2.6.8", + "debug": "^2.6.9", "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.1", - "eslint-module-utils": "^2.2.0", - "has": "^1.0.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.3", + "eslint-import-resolver-node": "^0.3.4", + "eslint-module-utils": "^2.6.0", + "has": "^1.0.3", + "minimatch": "^3.0.4", + "object.values": "^1.1.1", "read-pkg-up": "^2.0.0", - "resolve": "^1.6.0" + "resolve": "^1.17.0", + "tsconfig-paths": "^3.9.0" }, "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, "doctrine": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", @@ -6753,149 +7217,106 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true - }, - "load-json-file": { - "version": "2.0.0", - "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "requires": { - "error-ex": "^1.2.0" - } - }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "dev": true, - "requires": { - "pify": "^2.0.0" - } - }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "dev": true, - "requires": { - "load-json-file": "^2.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" - } - }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "dev": true, - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true } } }, "eslint-plugin-jsx-a11y": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.1.2.tgz", - "integrity": "sha512-7gSSmwb3A+fQwtw0arguwMdOdzmKUgnUcbSNlo+GjKLAQFuC2EZxWqG9XHRI8VscBJD5a8raz3RuxQNFW+XJbw==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.4.1.tgz", + "integrity": "sha512-0rGPJBbwHoGNPU73/QCLP/vveMlM1b1Z9PponxO87jfr6tuH5ligXbDT6nHSSzBC8ovX2Z+BQu7Bk5D/Xgq9zg==", "dev": true, "requires": { - "aria-query": "^3.0.0", - "array-includes": "^3.0.3", + "@babel/runtime": "^7.11.2", + "aria-query": "^4.2.2", + "array-includes": "^3.1.1", "ast-types-flow": "^0.0.7", - "axobject-query": "^2.0.1", - "damerau-levenshtein": "^1.0.4", - "emoji-regex": "^6.5.1", + "axe-core": "^4.0.2", + "axobject-query": "^2.2.0", + "damerau-levenshtein": "^1.0.6", + "emoji-regex": "^9.0.0", "has": "^1.0.3", - "jsx-ast-utils": "^2.0.1" + "jsx-ast-utils": "^3.1.0", + "language-tags": "^1.0.5" + }, + "dependencies": { + "emoji-regex": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.0.tgz", + "integrity": "sha512-DNc3KFPK18bPdElMJnf/Pkv5TXhxFU3YFDEuGLDRtPmV4rkmCjBkCSEp22u6rBHdSN9Vlp/GK7k98prmE1Jgug==", + "dev": true + } } }, "eslint-plugin-react": { - "version": "7.11.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.11.1.tgz", - "integrity": "sha512-cVVyMadRyW7qsIUh3FHp3u6QHNhOgVrLQYdQEB1bPWBsgbNCHdFAeNMquBMCcZJu59eNthX053L70l7gRt4SCw==", + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.21.5.tgz", + "integrity": "sha512-8MaEggC2et0wSF6bUeywF7qQ46ER81irOdWS4QWxnnlAEsnzeBevk1sWh7fhpCghPpXb+8Ks7hvaft6L/xsR6g==", "dev": true, "requires": { - "array-includes": "^3.0.3", + "array-includes": "^3.1.1", + "array.prototype.flatmap": "^1.2.3", "doctrine": "^2.1.0", "has": "^1.0.3", - "jsx-ast-utils": "^2.0.1", - "prop-types": "^15.6.2" + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "object.entries": "^1.1.2", + "object.fromentries": "^2.0.2", + "object.values": "^1.1.1", + "prop-types": "^15.7.2", + "resolve": "^1.18.1", + "string.prototype.matchall": "^4.0.2" + }, + "dependencies": { + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + } } }, "eslint-plugin-react-hooks": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.2.0.tgz", - "integrity": "sha512-jSlnBjV2cmyIeL555H/FbvuSbQ1AtpHjLMHuPrQnt1eVA6lX8yufdygh7AArI2m8ct7ChHGx2uOaCuxq2MUn6g==", - "dev": true - }, - "eslint-restricted-globals": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz", - "integrity": "sha1-NfDVy8ZMLj7WLpO0saevBbp+1Nc=", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.5.1.tgz", + "integrity": "sha512-Y2c4b55R+6ZzwtTppKwSmK/Kar8AdLiC2f9NADCuxbcTgPPg41Gyqa6b9GppgXSvCtkRw43ZE86CT5sejKC6/g==", "dev": true }, "eslint-scope": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", - "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "requires": { - "esrecurse": "^4.1.0", + "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "eslint-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.2.tgz", - "integrity": "sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", "dev": true, "requires": { - "eslint-visitor-keys": "^1.0.0" + "eslint-visitor-keys": "^1.1.0" } }, "eslint-visitor-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true }, "espree": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-4.1.0.tgz", - "integrity": "sha512-I5BycZW6FCVIub93TeVY1s7vjhP9CY6cXCznIRfiig7nRviKZYdRnj/sHEWC6A7WE9RDWOFq9+7OsWSYz8qv2w==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", "dev": true, "requires": { - "acorn": "^6.0.2", - "acorn-jsx": "^5.0.0", - "eslint-visitor-keys": "^1.0.0" - }, - "dependencies": { - "acorn": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.2.tgz", - "integrity": "sha512-GXmKIvbrN3TV7aVqAzVFaMW8F8wzVX7voEBRO3bDA64+EX37YSayggRJP5Xig6HYHBkWKpFg9W5gg6orklubhg==", - "dev": true - } + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.1.0" } }, "esprima": { @@ -6904,32 +7325,50 @@ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, "esquery": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", + "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", "dev": true, "requires": { - "estraverse": "^4.0.0" + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } } }, "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "requires": { - "estraverse": "^4.1.0" + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } } }, "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=" + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true }, "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true }, "etag": { "version": "1.8.1", @@ -6938,15 +7377,15 @@ "dev": true }, "eventemitter3": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", - "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "dev": true }, "events": { - "version": "1.1.1", - "resolved": "http://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", + "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==", "dev": true }, "eventsource": { @@ -6969,9 +7408,9 @@ } }, "exec-sh": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.2.tgz", - "integrity": "sha512-9sLAvzhI5nc8TpuQUh4ahMdCrWT00wPWz7j47/emR5+2qEfoZP5zzUXvx+vdx+H6ohhnsYC31iX04QLYJK8zTg==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.4.tgz", + "integrity": "sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A==", "dev": true }, "execa": { @@ -6987,47 +7426,8 @@ "p-finally": "^1.0.0", "signal-exit": "^3.0.0", "strip-eof": "^1.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - } } }, - "exenv": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", - "integrity": "sha1-KueOhdmJQVhnCwPUe+wfA72Ru50=" - }, "exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", @@ -7049,6 +7449,15 @@ "to-regex": "^3.0.1" }, "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", @@ -7070,76 +7479,53 @@ } }, "expect": { - "version": "24.7.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-24.7.1.tgz", - "integrity": "sha512-mGfvMTPduksV3xoI0xur56pQsg2vJjNf5+a+bXOjqCkiCBbmCayrBbHS/75y9K430cfqyocPr2ZjiNiRx4SRKw==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-24.9.0.tgz", + "integrity": "sha512-wvVAx8XIol3Z5m9zvZXiyZOQ+sRJqNTIm6sGjdWlaZIeupQGO3WbYI+15D/AmEwZywL6wtJkbAbJtzkOfBuR0Q==", "dev": true, "requires": { - "@jest/types": "^24.7.0", + "@jest/types": "^24.9.0", "ansi-styles": "^3.2.0", - "jest-get-type": "^24.3.0", - "jest-matcher-utils": "^24.7.0", - "jest-message-util": "^24.7.1", - "jest-regex-util": "^24.3.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "jest-get-type": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.3.0.tgz", - "integrity": "sha512-HYF6pry72YUlVcvUx3sEpMRwXEWGEPlJ0bSPVnB3b3n++j4phUEoSPcS6GC0pPJ9rpyPSe4cb5muFo6D39cXow==", - "dev": true - }, - "jest-regex-util": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.3.0.tgz", - "integrity": "sha512-tXQR1NEOyGlfylyEjg1ImtScwMq8Oh3iJbGTjN7p0J23EuVX1MA8rwU69K4sLbCmwzgCUbVkm0FkSF9TdzOhtg==", - "dev": true - } + "jest-get-type": "^24.9.0", + "jest-matcher-utils": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-regex-util": "^24.9.0" } }, "express": { - "version": "4.16.4", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", - "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", "dev": true, "requires": { - "accepts": "~1.3.5", + "accepts": "~1.3.7", "array-flatten": "1.1.1", - "body-parser": "1.18.3", - "content-disposition": "0.5.2", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", "content-type": "~1.0.4", - "cookie": "0.3.1", + "cookie": "0.4.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "~1.1.2", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.1.1", + "finalhandler": "~1.1.2", "fresh": "0.5.2", "merge-descriptors": "1.0.1", "methods": "~1.1.2", "on-finished": "~2.3.0", - "parseurl": "~1.3.2", + "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.4", - "qs": "6.5.2", - "range-parser": "~1.2.0", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", "safe-buffer": "5.1.2", - "send": "0.16.2", - "serve-static": "1.13.2", - "setprototypeof": "1.1.0", - "statuses": "~1.4.0", - "type-is": "~1.6.16", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" }, @@ -7150,31 +7536,77 @@ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", "dev": true }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", "dev": true + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "dev": true + } + } + }, + "ext": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", + "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", + "dev": true, + "requires": { + "type": "^2.0.0" + }, + "dependencies": { + "type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.1.0.tgz", + "integrity": "sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA==", + "dev": true } } }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true }, "extend-shallow": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", - "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, "requires": { - "kind-of": "^1.1.0" + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } } }, "external-editor": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", - "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", "dev": true, "requires": { "chardet": "^0.7.0", @@ -7246,9 +7678,9 @@ } }, "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true } } @@ -7256,174 +7688,201 @@ "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - }, - "fancy-log": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", - "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", - "requires": { - "ansi-gray": "^0.1.1", - "color-support": "^1.1.3", - "parse-node-version": "^1.0.0", - "time-stamp": "^1.0.0" - } + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true }, "fast-deep-equal": { - "version": "1.1.0", - "resolved": "http://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" - }, - "fastparse": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", - "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, - "faye-websocket": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", - "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", - "dev": true, - "requires": { - "websocket-driver": ">=0.5.1" - } - }, - "fb-watchman": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz", - "integrity": "sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg=", + "fast-glob": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz", + "integrity": "sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==", "dev": true, "requires": { - "bser": "^2.0.0" - } - }, - "fbjs": { - "version": "0.8.17", - "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.17.tgz", - "integrity": "sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=", - "requires": { - "core-js": "^1.0.0", - "isomorphic-fetch": "^2.1.1", - "loose-envify": "^1.0.0", - "object-assign": "^4.1.0", - "promise": "^7.1.1", - "setimmediate": "^1.0.5", - "ua-parser-js": "^0.7.18" - }, - "dependencies": { - "core-js": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", - "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" - } - } - }, - "fbjs-scripts": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/fbjs-scripts/-/fbjs-scripts-0.8.3.tgz", - "integrity": "sha512-aUJ/uEzMIiBYuj/blLp4sVNkQQ7ZEB/lyplG1IzzOmZ83meiWecrGg5jBo4wWrxXmO4RExdtsSV1QkTjPt2Gag==", - "requires": { - "ansi-colors": "^1.0.1", - "babel-core": "^6.7.2", - "babel-preset-fbjs": "^2.1.2", - "core-js": "^2.4.1", - "cross-spawn": "^5.1.0", - "fancy-log": "^1.3.2", - "object-assign": "^4.0.1", - "plugin-error": "^0.1.2", - "semver": "^5.1.0", - "through2": "^2.0.0" + "@mrmlnc/readdir-enhanced": "^2.2.1", + "@nodelib/fs.stat": "^1.1.2", + "glob-parent": "^3.1.0", + "is-glob": "^4.0.0", + "merge2": "^1.2.3", + "micromatch": "^3.1.10" }, "dependencies": { - "babel-core": { - "version": "6.26.3", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", - "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", - "requires": { - "babel-code-frame": "^6.26.0", - "babel-generator": "^6.26.0", - "babel-helpers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-register": "^6.26.0", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "convert-source-map": "^1.5.1", - "debug": "^2.6.9", - "json5": "^0.5.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.4", - "path-is-absolute": "^1.0.1", - "private": "^0.1.8", - "slash": "^1.0.0", - "source-map": "^0.5.7" + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } } }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" } }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } } } }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "fb-watchman": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", + "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "dev": true, + "requires": { + "bser": "2.1.1" + } + }, "figgy-pudding": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", - "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", + "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==", "dev": true }, "figures": { @@ -7436,110 +7895,191 @@ } }, "file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", "dev": true, "requires": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" + "flat-cache": "^2.0.1" } }, "file-loader": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-2.0.0.tgz", - "integrity": "sha512-YCsBfd1ZGCyonOKLxPiKPdu+8ld9HAaMEvJewzz+b2eTF7uL5Zm/HdBF6FjCrpCMRq25Mi0U1gl4pwn2TlH7hQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-4.3.0.tgz", + "integrity": "sha512-aKrYPYjF1yG3oX0kWRrqrSMfgftm7oJW5M+m4owoldH5C51C0RkIwB++JbRvEW3IU6/ZG5n8UvEcdgwOt2UOWA==", "dev": true, "requires": { - "loader-utils": "^1.0.2", - "schema-utils": "^1.0.0" + "loader-utils": "^1.2.3", + "schema-utils": "^2.5.0" } }, - "fileset": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", - "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", - "dev": true, + "file-selector": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.1.19.tgz", + "integrity": "sha512-kCWw3+Aai8Uox+5tHCNgMFaUdgidxvMnLWO6fM5sZ0hA2wlHP5/DHGF0ECe84BiB95qdJbKNEJhWKVDvMN+JDQ==", "requires": { - "glob": "^7.0.3", - "minimatch": "^3.0.3" + "tslib": "^2.0.1" + }, + "dependencies": { + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + } } }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "optional": true + }, + "filesize": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.0.1.tgz", + "integrity": "sha512-u4AYWPgbI5GBhs6id1KdImZWn5yfyFrrQ8OWZdN7ZMfA8Bf4HcO0BGo9bmUIEV8yrp8I1xVfJ/dn90GtFNNJcg==", + "dev": true + }, "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "to-regex-range": "^5.0.1" } }, "finalhandler": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", - "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", "dev": true, "requires": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "statuses": "~1.4.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } } }, "find-cache-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", - "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", "dev": true, "requires": { "commondir": "^1.0.1", - "make-dir": "^1.0.0", - "pkg-dir": "^2.0.0" + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + } } }, "find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "dev": true }, "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "find-yarn-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", + "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", "dev": true, "requires": { - "locate-path": "^2.0.0" + "micromatch": "^4.0.2" } }, "flat-cache": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", - "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", "dev": true, "requires": { - "circular-json": "^0.3.1", - "del": "^2.0.2", - "graceful-fs": "^4.1.2", - "write": "^0.2.1" + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" } }, + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, + "flatten": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.3.tgz", + "integrity": "sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==", + "dev": true + }, "flush-write-stream": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", @@ -7557,9 +8097,9 @@ "dev": true }, "readable-stream": { - "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -7583,47 +8123,20 @@ } }, "focus-trap": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-3.0.0.tgz", - "integrity": "sha512-jTFblf0tLWbleGjj2JZsAKbgtZTdL1uC48L8FcmSDl4c2vDoU4NycN1kgV5vJhuq1mxNFkw7uWZ1JAGlINWvyw==", - "requires": { - "tabbable": "^3.1.0", - "xtend": "^4.0.1" - }, - "dependencies": { - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" - } - } - }, - "focus-trap-react": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/focus-trap-react/-/focus-trap-react-4.0.1.tgz", - "integrity": "sha512-UUZKVEn5cFbF6yUnW7lbXNW0iqN617ShSqYKgxctUvWw1wuylLtyVmC0RmPQNnJ/U+zoKc/djb0tZMs0uN/0QQ==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-6.2.2.tgz", + "integrity": "sha512-qWovH9+LGoKqREvJaTCzJyO0hphQYGz+ap5Hc4NqXHNhZBdxCi5uBPPcaOUw66fHmzXLVwvETLvFgpwPILqKpg==", "requires": { - "focus-trap": "^3.0.0" + "tabbable": "^5.1.4" } }, "follow-redirects": { - "version": "1.5.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.9.tgz", - "integrity": "sha512-Bh65EZI/RU8nx0wbYF9shkFZlqLP+6WT/5FnA3cE/djNSuKNHJEinGGZgu/cQEkeeb2GdFOgenAmn8qaqYke2w==", + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", "dev": true, "requires": { "debug": "=3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } } }, "for-in": { @@ -7632,54 +8145,208 @@ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "formik": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/formik/-/formik-1.5.1.tgz", - "integrity": "sha512-FBWGBKQkcCE4d5b5l2fKccD9d1QxNxw/0bQTRvp3EjzA8Bnjmsm9H/Oy0375UA8P3FPmfJkF4cXLLdEqK7fP5A==", + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "dev": true, "requires": { - "create-react-context": "^0.2.2", - "deepmerge": "^2.1.1", - "hoist-non-react-statics": "^2.5.5", - "lodash": "^4.17.11", - "lodash-es": "^4.17.11", - "prop-types": "^15.6.1", - "react-fast-compare": "^2.0.1", - "tiny-warning": "^1.0.2", - "tslib": "^1.9.3" + "for-in": "^1.0.1" } }, - "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", "dev": true }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "fork-ts-checker-webpack-plugin": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-3.1.1.tgz", + "integrity": "sha512-DuVkPNrM12jR41KM2e+N+styka0EgLkTnXmNcXdgOM37vtGeY+oCBK/Jx0hzSeEU6memFCtWb4htrHPMDfwwUQ==", "dev": true, "requires": { - "map-cache": "^0.2.2" - } - }, - "fresh": { - "version": "0.5.2", + "babel-code-frame": "^6.22.0", + "chalk": "^2.4.1", + "chokidar": "^3.3.0", + "micromatch": "^3.1.10", + "minimatch": "^3.0.4", + "semver": "^5.6.0", + "tapable": "^1.0.0", + "worker-rpc": "^0.1.0" + }, + "dependencies": { + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "dev": true + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "formik": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/formik/-/formik-2.2.6.tgz", + "integrity": "sha512-Kxk2zQRafy56zhLmrzcbryUpMBvT0tal5IvcifK5+4YNGelKsnrODFJ0sZQRMQboblWNym4lAW3bt+tf2vApSA==", + "requires": { + "deepmerge": "^2.1.1", + "hoist-non-react-statics": "^3.3.0", + "lodash": "^4.17.14", + "lodash-es": "^4.17.14", + "react-fast-compare": "^2.0.1", + "tiny-warning": "^1.0.2", + "tslib": "^1.10.0" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "dev": true + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fresh": { + "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", "dev": true @@ -7701,9 +8368,9 @@ "dev": true }, "readable-stream": { - "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -7727,15 +8394,25 @@ } }, "fs-extra": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz", - "integrity": "sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, "requires": { - "graceful-fs": "^4.1.2", + "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, "fs-write-stream-atomic": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", @@ -7746,554 +8423,52 @@ "iferr": "^0.1.5", "imurmurhash": "^0.1.4", "readable-stream": "1 || 2" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.7.tgz", - "integrity": "sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw==", - "dev": true, - "optional": true, - "requires": { - "nan": "^2.9.2", - "node-pre-gyp": "^0.10.0" }, "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { + "isarray": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "chownr": { + "string_decoder": { "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "debug": { - "version": "2.6.9", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, - "optional": true, "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" + "safe-buffer": "~5.1.0" } - }, - "glob": { - "version": "7.1.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true, - "dev": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "dev": true - }, - "minipass": { - "version": "2.3.5", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "needle": { - "version": "2.2.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "^2.1.2", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.10.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true - }, - "npm-packlist": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.6.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true, - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "dev": true, - "optional": true - }, - "semver": { - "version": "5.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "tar": { - "version": "4.4.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.3.4", - "minizlib": "^1.1.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "yallist": { - "version": "3.0.3", - "bundled": true, - "dev": true } } }, - "fstream": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", - "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", + "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "inherits": "~2.0.0", - "mkdirp": ">=0.5 0", - "rimraf": "2" - } + "optional": true }, "function-bind": { "version": "1.1.1", @@ -8302,14 +8477,15 @@ "dev": true }, "function.prototype.name": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.0.tgz", - "integrity": "sha512-Bs0VRrTz4ghD8pTmbJQD1mZ8A/mN0ur/jGz+A6FBxPDUPkm1tNfF6bhTYPA7i7aF4lZJVr+OXTNNrnnIl58Wfg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.3.tgz", + "integrity": "sha512-H51qkbNSp8mtkJt+nyW1gyStBiKZxfRqySNUR99ylq6BPXHKI4SEvIlTKp4odLfjRKJV04DFWMU3G/YRlQOsag==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "is-callable": "^1.1.3" + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1", + "functions-have-names": "^1.2.1" } }, "functional-red-black-tree": { @@ -8319,9 +8495,9 @@ "dev": true }, "functions-have-names": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.1.1.tgz", - "integrity": "sha512-U0kNHUoxwPNPWOJaMG7Z00d4a/qZVrFtzWJRaK8V9goaVOCXBSQSJpt3MYGNtkScKEBKovxLjnNdC9MlXwo5Pw==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.2.tgz", + "integrity": "sha512-bLgc3asbWdwPbx2mNk2S49kmJCuQeu0nfmaOgbs8WIyzzkw3r4htszdIi9Q9EMezDPTYuJx2wvjZ/EwgAthpnA==", "dev": true }, "fuzzaldrin": { @@ -8330,70 +8506,55 @@ "integrity": "sha1-kCBMPi/appQbso0WZF1BgGOpDps=", "dev": true }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "dev": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - } - } + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true }, - "gaze": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", - "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-intrinsic": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.1.tgz", + "integrity": "sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg==", "dev": true, "requires": { - "globule": "^1.0.0" + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" } }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", + "dev": true + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true }, "get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", "dev": true }, "get-stream": { - "version": "3.0.0", - "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } }, "get-value": { "version": "2.0.6", @@ -8405,14 +8566,15 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, "requires": { "assert-plus": "^1.0.0" } }, "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -8423,63 +8585,101 @@ "path-is-absolute": "^1.0.0" } }, - "global": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", - "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=", + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", "dev": true, "requires": { - "min-document": "^2.19.0", - "process": "~0.5.1" + "is-glob": "^4.0.1" } }, - "global-modules-path": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/global-modules-path/-/global-modules-path-2.3.0.tgz", - "integrity": "sha512-HchvMJNYh9dGSCy8pOQ2O8u/hoXaL+0XhnrwH0RyLiSXMMTl9W3N6KUU73+JFOg5PGjtzl6VZzUQsnrpm7Szag==", + "glob-to-regexp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", + "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=", "dev": true }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==" + "global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dev": true, + "requires": { + "global-prefix": "^3.0.0" + } }, - "globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", "dev": true, "requires": { - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "dependencies": { + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + } } }, - "globule": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", - "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==", + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + }, + "globby": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.2.tgz", + "integrity": "sha512-yTzMmKygLp8RUpG1Ymu2VXPSJQZjNAZPD4ywgYEaG7e4tBJeUQBO8OpXrf1RCNcEs5alsoJYPAMiIHP0cmeC7w==", "dev": true, "requires": { - "glob": "~7.1.1", - "lodash": "~4.17.10", - "minimatch": "~3.0.2" + "array-union": "^1.0.1", + "dir-glob": "2.0.0", + "fast-glob": "^2.0.2", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" + }, + "dependencies": { + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + } } }, "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true }, "graphlib": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.7.tgz", - "integrity": "sha512-TyI9jIy2J4j0qgPmOOrHTCtpPqJGN/aurBwc6ZT+bRii+di1I+Wv3obRhVrmBEXet+qkMaEX67dXrwsd3QQM6w==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", + "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", "requires": { - "lodash": "^4.17.5" + "lodash": "^4.17.15" } }, "growly": { @@ -8488,43 +8688,44 @@ "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", "dev": true }, - "gud": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz", - "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==" - }, - "handle-thing": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.0.tgz", - "integrity": "sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ==", - "dev": true - }, - "handlebars": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.3.tgz", - "integrity": "sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA==", + "gzip-size": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz", + "integrity": "sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==", "dev": true, "requires": { - "neo-async": "^2.6.0", - "optimist": "^0.6.1", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4" + "duplexer": "^0.1.1", + "pify": "^4.0.1" } }, + "handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true + }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true }, "har-validator": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz", - "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "dev": true, "requires": { - "ajv": "^5.3.0", + "ajv": "^6.12.3", "har-schema": "^2.0.0" } }, + "harmony-reflect": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.1.tgz", + "integrity": "sha512-WJTeyp0JzGtHcuMsi7rw2VwtkvLa+JyfEKJCFyfcS0+CDkjQ5lHPu7zEhFZP+PDSRrEgXa5Ah0l1MbgbE41XjA==", + "dev": true + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -8540,13 +8741,6 @@ "integrity": "sha1-Ngd+8dFfMzSEqn+neihgbxxlWzc=", "requires": { "ansi-regex": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" - } } }, "has-flag": { @@ -8555,15 +8749,9 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", - "dev": true - }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", "dev": true }, "has-value": { @@ -8575,14 +8763,6 @@ "get-value": "^2.0.6", "has-values": "^1.0.0", "isobject": "^3.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } } }, "has-values": { @@ -8595,6 +8775,12 @@ "kind-of": "^4.0.0" }, "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", @@ -8627,13 +8813,22 @@ } }, "hash-base": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", - "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", "dev": true, "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } } }, "hash-sum": { @@ -8642,26 +8837,38 @@ "integrity": "sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ=" }, "hash.js": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.5.tgz", - "integrity": "sha512-eWI5HG9Np+eHV1KQhisXWwM+4EPPYe5dFX1UZZH7k/E3JzDEazVH+VGlZi6R94ZqImq+A3D1mCEtrFIfg/E7sA==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", "dev": true, "requires": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" } }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "hex-color-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", + "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==", + "dev": true + }, "history": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/history/-/history-4.9.0.tgz", - "integrity": "sha512-H2DkjCjXf0Op9OAr6nJ56fcRkTSNrUiv41vNJ6IswJjif6wlpZK0BTfFbi7qK9dXLSYZxkq5lBsj3vUjlYBYZA==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", "requires": { "@babel/runtime": "^7.1.2", "loose-envify": "^1.2.0", - "resolve-pathname": "^2.2.0", + "resolve-pathname": "^3.0.0", "tiny-invariant": "^1.0.2", "tiny-warning": "^1.0.0", - "value-equal": "^0.4.0" + "value-equal": "^1.0.1" } }, "hmac-drbg": { @@ -8676,23 +8883,17 @@ } }, "hoist-non-react-statics": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", - "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" - }, - "home-or-tmp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", - "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz", + "integrity": "sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==", "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.1" + "react-is": "^16.7.0" } }, "hosted-git-info": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", "dev": true }, "hpack.js": { @@ -8714,9 +8915,9 @@ "dev": true }, "readable-stream": { - "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -8730,7 +8931,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { @@ -8739,10 +8940,28 @@ } } }, + "hsl-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz", + "integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=", + "dev": true + }, + "hsla-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", + "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=", + "dev": true + }, + "html-comment-regex": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", + "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==", + "dev": true + }, "html-element-map": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/html-element-map/-/html-element-map-1.1.0.tgz", - "integrity": "sha512-iqiG3dTZmy+uUaTmHarTL+3/A2VW9ox/9uasKEZC+R/wAtUrTcRlXPSaPqsnWPfIu8wqn09jQNwMRqzL54jSYA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/html-element-map/-/html-element-map-1.2.0.tgz", + "integrity": "sha512-0uXq8HsuG1v2TmQ8QkIhzbrqeskE4kn52Q18QJ9iAA/SnHoEKXWiUxHQtclRsCFWEUD2So34X+0+pZZu862nnw==", "dev": true, "requires": { "array-filter": "^1.0.0" @@ -8752,25 +8971,75 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "dev": true, "requires": { "whatwg-encoding": "^1.0.1" } }, "html-entities": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", - "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=" + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.3.3.tgz", + "integrity": "sha512-/VulV3SYni1taM7a4RMdceqzJWR39gpZHjBwUnsCFKWV/GJkD14CJ5F7eWcZozmHJK0/f/H5U3b3SiPkuvxMgg==" }, - "html-tokenize": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/html-tokenize/-/html-tokenize-2.0.0.tgz", - "integrity": "sha1-izqaXetHXK5qb5ZxYA0sIKspglE=", + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "html-minifier-terser": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", + "integrity": "sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg==", + "dev": true, "requires": { - "buffer-from": "~0.1.1", - "inherits": "~2.0.1", - "minimist": "~0.0.8", - "readable-stream": "~1.0.27-1", - "through2": "~0.4.1" + "camel-case": "^4.1.1", + "clean-css": "^4.2.3", + "commander": "^4.1.1", + "he": "^1.2.0", + "param-case": "^3.0.3", + "relateurl": "^0.2.7", + "terser": "^4.6.3" + }, + "dependencies": { + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true + } + } + }, + "html-webpack-plugin": { + "version": "4.0.0-beta.11", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.0.0-beta.11.tgz", + "integrity": "sha512-4Xzepf0qWxf8CGg7/WQM5qBB2Lc/NFI7MhU59eUDTkuQp3skZczH4UA1d6oQyDEIoMDgERVhRyTdtUPZ5s5HBg==", + "dev": true, + "requires": { + "html-minifier-terser": "^5.0.1", + "loader-utils": "^1.2.3", + "lodash": "^4.17.15", + "pretty-error": "^2.1.1", + "tapable": "^1.1.3", + "util.promisify": "1.0.0" + }, + "dependencies": { + "tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "dev": true + }, + "util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" + } + } } }, "htmlparser2": { @@ -8785,34 +9054,6 @@ "entities": "^1.1.1", "inherits": "^2.0.1", "readable-stream": "^3.1.1" - }, - "dependencies": { - "readable-stream": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", - "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "safe-buffer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", - "dev": true - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - } - } } }, "http-deceiver": { @@ -8822,362 +9063,55 @@ "dev": true }, "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", "dev": true, "requires": { "depd": "~1.1.2", "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + } } }, - "http-parser-js": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.0.tgz", - "integrity": "sha512-cZdEF7r4gfRIq7ezX9J0T+kQmJNOub71dWbgAXVHDct80TKP4MCETtZQ31xyv38UwgzkWPYF/Xc0ge55dW9Z9w==", - "dev": true - }, "http-proxy": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", - "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", "dev": true, "requires": { - "eventemitter3": "^3.0.0", + "eventemitter3": "^4.0.0", "follow-redirects": "^1.0.0", "requires-port": "^1.0.0" } }, "http-proxy-middleware": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", - "integrity": "sha512-Fs25KVMPAIIcgjMZkVHJoKg9VcXcC1C8yb9JUgeDvVXY0S/zgVIhMb+qVswDIgtJe2DfckMSY2d6TuTEutlk6Q==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-1.0.6.tgz", + "integrity": "sha512-NyL6ZB6cVni7pl+/IT2W0ni5ME00xR0sN27AQZZrpKn1b+qRh+mLbBxIq9Cq1oGfmTc7BUq4HB77mxwCaxAYNg==", "dev": true, "requires": { - "http-proxy": "^1.16.2", - "is-glob": "^4.0.0", - "lodash": "^4.17.5", - "micromatch": "^3.1.9" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-glob": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", - "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - } + "@types/http-proxy": "^1.17.4", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "lodash": "^4.17.20", + "micromatch": "^4.0.2" } }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, "requires": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", @@ -9198,25 +9132,28 @@ "safer-buffer": ">= 2.1.2 < 3" } }, - "icss-replace-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", - "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=", - "dev": true - }, "icss-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-2.1.0.tgz", - "integrity": "sha1-g/Cg7DeL8yRheLbCrZE28TWxyWI=", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz", + "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==", + "dev": true, + "requires": { + "postcss": "^7.0.14" + } + }, + "identity-obj-proxy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz", + "integrity": "sha1-lNK9qWCERT7zb7xarsN+D3nx/BQ=", "dev": true, "requires": { - "postcss": "^6.0.1" + "harmony-reflect": "^1.4.6" } }, "ieee754": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz", - "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "dev": true }, "iferr": { @@ -9231,6 +9168,56 @@ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, + "immer": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-1.10.0.tgz", + "integrity": "sha512-O3sR1/opvCDGLEVcvrGTMtLac8GJ5IwZC4puPrLuRj3l7ICKvkmA0vGuU9OW8mV9WIBRnaxp5GJh9IEAaNOoYg==", + "dev": true + }, + "import-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", + "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", + "dev": true, + "requires": { + "import-from": "^2.1.0" + } + }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "dev": true, + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + } + } + }, + "import-from": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", + "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + } + } + }, "import-local": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", @@ -9260,15 +9247,6 @@ "path-exists": "^3.0.0" } }, - "p-limit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", - "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, "p-locate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", @@ -9278,10 +9256,10 @@ "p-limit": "^2.0.0" } }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true }, "pkg-dir": { @@ -9301,25 +9279,16 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, - "in-publish": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz", - "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=", - "dev": true - }, "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true }, - "indexof": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "indexes-of": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", + "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", "dev": true }, "infer-owner": { @@ -9339,107 +9308,141 @@ } }, "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true }, "inquirer": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.0.tgz", - "integrity": "sha512-QIEQG4YyQ2UYZGDC4srMZ7BjHOmNk1lR2JQj5UknBapklm6WHA+VVH7N+sUdX3A7NeCfGF8o4X1S3Ao7nAcIeg==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", + "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", "dev": true, "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", "cli-cursor": "^2.1.0", "cli-width": "^2.0.0", - "external-editor": "^3.0.0", + "external-editor": "^3.0.3", "figures": "^2.0.0", - "lodash": "^4.17.10", + "lodash": "^4.17.12", "mute-stream": "0.0.7", "run-async": "^2.2.0", - "rxjs": "^6.1.0", + "rxjs": "^6.4.0", "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", + "strip-ansi": "^5.1.0", "through": "^2.3.6" }, "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } } }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "ansi-regex": "^4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + } } } } }, "internal-ip": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-3.0.1.tgz", - "integrity": "sha512-NXXgESC2nNVtU+pqmC9e6R8B1GpKxzsAQhffvh5AL79qKnodd+L7tnEQmTiUAVngqLalPbSqRA7XGIEL5nCd0Q==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", + "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==", "dev": true, "requires": { - "default-gateway": "^2.6.0", - "ipaddr.js": "^1.5.2" + "default-gateway": "^4.2.0", + "ipaddr.js": "^1.9.0" + } + }, + "internal-slot": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.2.tgz", + "integrity": "sha512-2cQNfwhAfJIkU4KZPkDI+Gj5yNNnbqi40W9Gge6dfnk4TocEVm00B3bdiL+JINrbGJil2TeHvM4rETGzk/f/0g==", + "dev": true, + "requires": { + "es-abstract": "^1.17.0-next.1", + "has": "^1.0.3", + "side-channel": "^1.0.2" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } } }, "interpret": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", - "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", "dev": true }, "invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, "requires": { "loose-envify": "^1.0.0" } }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true - }, "ip": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", @@ -9449,12 +9452,19 @@ "ip-regex": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=" + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true }, "ipaddr.js": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", - "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true + }, + "is-absolute-url": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", + "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=", "dev": true }, "is-accessor-descriptor": { @@ -9464,74 +9474,61 @@ "dev": true, "requires": { "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } } }, "is-alphabetical": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.2.tgz", - "integrity": "sha512-V0xN4BYezDHcBSKb1QHUFMlR4as/XEuCZBzMJUU4n7+Cbt33SmUnSol+pnXFvLxSHNq2CemUXNdaXV6Flg7+xg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", "dev": true }, "is-alphanumerical": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.2.tgz", - "integrity": "sha512-pyfU/0kHdISIgslFfZN9nfY1Gk3MquQgUm1mJTjdkEPpkAKNWuBTSqFwewOpR7N351VkErCiyV71zX7mlQQqsg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", "dev": true, "requires": { "is-alphabetical": "^1.0.0", "is-decimal": "^1.0.0" } }, + "is-arguments": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz", + "integrity": "sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==", + "dev": true, + "requires": { + "call-bind": "^1.0.0" + } + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true }, "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "requires": { - "binary-extensions": "^1.0.0" + "binary-extensions": "^2.0.0" } }, "is-boolean-object": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.0.tgz", - "integrity": "sha1-mPiygDBoQhmpXzdc+9iM40Bd/5M=", - "dev": true - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-builtin-module": { - "version": "1.0.0", - "resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.0.tgz", + "integrity": "sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==", "dev": true, "requires": { - "builtin-modules": "^1.0.0" + "call-bind": "^1.0.0" } }, "is-callable": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==", "dev": true }, "is-ci": { @@ -9543,6 +9540,29 @@ "ci-info": "^2.0.0" } }, + "is-color-stop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", + "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=", + "dev": true, + "requires": { + "css-color-names": "^0.0.4", + "hex-color-regex": "^1.1.0", + "hsl-regex": "^1.0.0", + "hsla-regex": "^1.0.0", + "rgb-regex": "^1.0.1", + "rgba-regex": "^1.0.0" + } + }, + "is-core-module": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", + "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, "is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", @@ -9550,29 +9570,18 @@ "dev": true, "requires": { "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } } }, "is-date-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", "dev": true }, "is-decimal": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.2.tgz", - "integrity": "sha512-TRzl7mOCchnhchN+f3ICUCzYvL9ul7R+TYOsZ8xia++knyZAJfv/uA1FvQXsAnYIl1T3B2X5E/J7Wb1QXiIBXg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", "dev": true }, "is-descriptor": { @@ -9597,7 +9606,14 @@ "is-directory": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=" + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", + "dev": true + }, + "is-docker": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz", + "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==", + "dev": true }, "is-extendable": { "version": "0.1.1", @@ -9605,18 +9621,22 @@ "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", "dev": true }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, "is-finite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", - "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "requires": { - "number-is-nan": "^1.0.0" - } + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", + "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", + "dev": true }, "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, "is-generator-fn": { @@ -9625,56 +9645,69 @@ "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", "dev": true, "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "is-extglob": "^2.1.1" } }, + "is-negative-zero": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, "is-number-object": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.3.tgz", - "integrity": "sha1-8mWrian0RQNO9q/xWo8AsA9VF5k=", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", + "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", + "dev": true + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", "dev": true }, "is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", "dev": true }, "is-path-in-cwd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", - "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", + "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", "dev": true, "requires": { - "is-path-inside": "^1.0.0" + "is-path-inside": "^2.1.0" } }, "is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", + "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", "dev": true, "requires": { - "path-is-inside": "^1.0.1" + "path-is-inside": "^1.0.2" } }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -9682,46 +9715,45 @@ "dev": true, "requires": { "isobject": "^3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } } }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", - "dev": true - }, "is-regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", - "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", "dev": true, "requires": { - "has": "^1.0.1" + "has-symbols": "^1.0.1" } }, + "is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", + "dev": true + }, "is-resolvable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", "dev": true }, + "is-root": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz", + "integrity": "sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==", + "dev": true + }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true }, "is-string": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.4.tgz", - "integrity": "sha1-zDqbaYV9Yh6WNyWiTK7shzuCbmQ=", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", "dev": true }, "is-subset": { @@ -9730,26 +9762,35 @@ "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=", "dev": true }, + "is-svg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", + "integrity": "sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ==", + "dev": true, + "requires": { + "html-comment-regex": "^1.1.0" + } + }, "is-symbol": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", - "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", "dev": true, "requires": { - "has-symbols": "^1.0.0" + "has-symbols": "^1.0.1" } }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true }, + "is-what": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.12.0.tgz", + "integrity": "sha512-2ilQz5/f/o9V7WRWJQmpFYNmQFZ9iM+OXRonZKcYgTkCzjb949Vi4h282PD1UfmgHk666rcWonbRJ++KI41VGw==" + }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -9770,139 +9811,54 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true }, "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "requires": { - "isarray": "1.0.0" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - } - } - }, - "isomorphic-fetch": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", - "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", - "requires": { - "node-fetch": "^1.0.1", - "whatwg-fetch": ">=0.10.0" - } + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "istanbul-api": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-2.1.4.tgz", - "integrity": "sha512-aAFQL0HA2BLUl18XmTQ7H7CGKI58DtZFvvfmg6e+rA3iNFergvpi16czLV4CpI7HOImMeZ5mqI62dvSNVtUQVA==", - "dev": true, - "requires": { - "async": "^2.6.1", - "compare-versions": "^3.2.1", - "fileset": "^2.0.3", - "istanbul-lib-coverage": "^2.0.4", - "istanbul-lib-hook": "^2.0.6", - "istanbul-lib-instrument": "^3.2.0", - "istanbul-lib-report": "^2.0.7", - "istanbul-lib-source-maps": "^3.0.5", - "istanbul-reports": "^2.2.2", - "js-yaml": "^3.13.0", - "make-dir": "^2.1.0", - "minimatch": "^3.0.4", - "once": "^1.4.0" - }, - "dependencies": { - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - } - } + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true }, "istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-LXTBICkMARVgo579kWDm8SqfB6nvSDKNqIOBEjmJRnL04JvoMHCYGWaMddQnseJYtkEuEvO/sIcOxPLk9gERug==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", "dev": true }, - "istanbul-lib-hook": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.6.tgz", - "integrity": "sha512-829DKONApZ7UCiPXcOYWSgkFXa4+vNYoNOt3F+4uDJLKL1OotAoVwvThoEj1i8jmOj7odbYcR3rnaHu+QroaXg==", + "istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", "dev": true, "requires": { - "append-transform": "^1.0.0" + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" } }, - "istanbul-lib-instrument": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.2.0.tgz", - "integrity": "sha512-06IM3xShbNW4NgZv5AP4QH0oHqf1/ivFo8eFys0ZjPXHGldHJQWb3riYOKXqmOqfxXBfxu4B+g/iuhOPZH0RJg==", + "istanbul-lib-report": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", + "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", "dev": true, "requires": { - "@babel/generator": "^7.0.0", - "@babel/parser": "^7.0.0", - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.0.0", - "@babel/types": "^7.0.0", - "istanbul-lib-coverage": "^2.0.4", - "semver": "^6.0.0" + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "supports-color": "^6.1.0" }, "dependencies": { - "semver": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.0.0.tgz", - "integrity": "sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ==", - "dev": true - } - } - }, - "istanbul-lib-report": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.7.tgz", - "integrity": "sha512-wLH6beJBFbRBLiTlMOBxmb85cnVM1Vyl36N48e4e/aTKSM3WbOx7zbVIH1SQ537fhhsPbX0/C5JB4qsmyRXXyA==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^2.0.4", - "make-dir": "^2.1.0", - "supports-color": "^6.0.0" - }, - "dependencies": { - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", "dev": true }, "supports-color": { @@ -9917,717 +9873,594 @@ } }, "istanbul-lib-source-maps": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.5.tgz", - "integrity": "sha512-eDhZ7r6r1d1zQPVZehLc3D0K14vRba/eBYkz3rw16DLOrrTzve9RmnkcwrrkWVgO1FL3EK5knujVe5S8QHE9xw==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", + "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", "dev": true, "requires": { "debug": "^4.1.1", - "istanbul-lib-coverage": "^2.0.4", + "istanbul-lib-coverage": "^2.0.5", "make-dir": "^2.1.0", - "rimraf": "^2.6.2", + "rimraf": "^2.6.3", "source-map": "^0.6.1" }, "dependencies": { "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true } } }, "istanbul-reports": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.2.tgz", - "integrity": "sha512-ZFuTdBQ3PSaPnm02aEA4R6mzQ2AF9w03CYiXADzWbbE48v/EFOWF4MaX4FT0NRdqIk48I7o0RPi+S8TMswaCbQ==", + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.7.tgz", + "integrity": "sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg==", "dev": true, "requires": { - "handlebars": "^4.1.0" + "html-escaper": "^2.0.0" } }, "jest": { - "version": "24.7.1", - "resolved": "https://registry.npmjs.org/jest/-/jest-24.7.1.tgz", - "integrity": "sha512-AbvRar5r++izmqo5gdbAjTeA6uNRGoNRuj5vHB0OnDXo2DXWZJVuaObiGgtlvhKb+cWy2oYbQSfxv7Q7GjnAtA==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-24.9.0.tgz", + "integrity": "sha512-YvkBL1Zm7d2B1+h5fHEOdyjCG+sGMz4f8D86/0HiqJ6MB4MnDc8FgP5vdWsGnemOQro7lnYo8UakZ3+5A0jxGw==", "dev": true, "requires": { "import-local": "^2.0.0", - "jest-cli": "^24.7.1" + "jest-cli": "^24.9.0" }, "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, "jest-cli": { - "version": "24.7.1", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-24.7.1.tgz", - "integrity": "sha512-32OBoSCVPzcTslGFl6yVCMzB2SqX3IrWwZCY5mZYkb0D2WsogmU3eV2o8z7+gRQa4o4sZPX/k7GU+II7CxM6WQ==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-24.9.0.tgz", + "integrity": "sha512-+VLRKyitT3BWoMeSUIHRxV/2g8y9gw91Jh5z2UmXZzkZKpbC08CSehVxgHUwTpy+HwGcns/tqafQDJW7imYvGg==", "dev": true, "requires": { - "@jest/core": "^24.7.1", - "@jest/test-result": "^24.7.1", - "@jest/types": "^24.7.0", + "@jest/core": "^24.9.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", "chalk": "^2.0.1", "exit": "^0.1.2", "import-local": "^2.0.0", "is-ci": "^2.0.0", - "jest-config": "^24.7.1", - "jest-util": "^24.7.1", - "jest-validate": "^24.7.0", + "jest-config": "^24.9.0", + "jest-util": "^24.9.0", + "jest-validate": "^24.9.0", "prompts": "^2.0.1", "realpath-native": "^1.1.0", - "yargs": "^12.0.2" - } - }, - "jest-get-type": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.3.0.tgz", - "integrity": "sha512-HYF6pry72YUlVcvUx3sEpMRwXEWGEPlJ0bSPVnB3b3n++j4phUEoSPcS6GC0pPJ9rpyPSe4cb5muFo6D39cXow==", - "dev": true - }, - "jest-validate": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-24.7.0.tgz", - "integrity": "sha512-cgai/gts9B2chz1rqVdmLhzYxQbgQurh1PEQSvSgPZ8KGa1AqXsqC45W5wKEwzxKrWqypuQrQxnF4+G9VejJJA==", - "dev": true, - "requires": { - "@jest/types": "^24.7.0", - "camelcase": "^5.0.0", - "chalk": "^2.0.1", - "jest-get-type": "^24.3.0", - "leven": "^2.1.0", - "pretty-format": "^24.7.0" - } - }, - "pretty-format": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.7.0.tgz", - "integrity": "sha512-apen5cjf/U4dj7tHetpC7UEFCvtAgnNZnBDkfPv3fokzIqyOJckAG9OlAPC1BlFALnqT/lGB2tl9EJjlK6eCsA==", - "dev": true, - "requires": { - "@jest/types": "^24.7.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" - } - }, - "react-is": { - "version": "16.8.6", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", - "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" + "yargs": "^13.3.0" } } } }, "jest-changed-files": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-24.7.0.tgz", - "integrity": "sha512-33BgewurnwSfJrW7T5/ZAXGE44o7swLslwh8aUckzq2e17/2Os1V0QU506ZNik3hjs8MgnEMKNkcud442NCDTw==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-24.9.0.tgz", + "integrity": "sha512-6aTWpe2mHF0DhL28WjdkO8LyGjs3zItPET4bMSeXU6T3ub4FPMw+mcOcbdGXQOAfmLcxofD23/5Bl9Z4AkFwqg==", "dev": true, "requires": { - "@jest/types": "^24.7.0", + "@jest/types": "^24.9.0", "execa": "^1.0.0", "throat": "^4.0.0" } }, "jest-config": { - "version": "24.7.1", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-24.7.1.tgz", - "integrity": "sha512-8FlJNLI+X+MU37j7j8RE4DnJkvAghXmBWdArVzypW6WxfGuxiL/CCkzBg0gHtXhD2rxla3IMOSUAHylSKYJ83g==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-24.9.0.tgz", + "integrity": "sha512-RATtQJtVYQrp7fvWg6f5y3pEFj9I+H8sWw4aKxnDZ96mob5i5SD6ZEGWgMLXQ4LE8UurrjbdlLWdUeo+28QpfQ==", "dev": true, "requires": { "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^24.7.1", - "@jest/types": "^24.7.0", - "babel-jest": "^24.7.1", + "@jest/test-sequencer": "^24.9.0", + "@jest/types": "^24.9.0", + "babel-jest": "^24.9.0", "chalk": "^2.0.1", "glob": "^7.1.1", - "jest-environment-jsdom": "^24.7.1", - "jest-environment-node": "^24.7.1", - "jest-get-type": "^24.3.0", - "jest-jasmine2": "^24.7.1", + "jest-environment-jsdom": "^24.9.0", + "jest-environment-node": "^24.9.0", + "jest-get-type": "^24.9.0", + "jest-jasmine2": "^24.9.0", "jest-regex-util": "^24.3.0", - "jest-resolve": "^24.7.1", - "jest-util": "^24.7.1", - "jest-validate": "^24.7.0", + "jest-resolve": "^24.9.0", + "jest-util": "^24.9.0", + "jest-validate": "^24.9.0", "micromatch": "^3.1.10", - "pretty-format": "^24.7.0", + "pretty-format": "^24.9.0", "realpath-native": "^1.1.0" }, "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, - "jest-get-type": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.3.0.tgz", - "integrity": "sha512-HYF6pry72YUlVcvUx3sEpMRwXEWGEPlJ0bSPVnB3b3n++j4phUEoSPcS6GC0pPJ9rpyPSe4cb5muFo6D39cXow==", - "dev": true - }, - "jest-regex-util": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.3.0.tgz", - "integrity": "sha512-tXQR1NEOyGlfylyEjg1ImtScwMq8Oh3iJbGTjN7p0J23EuVX1MA8rwU69K4sLbCmwzgCUbVkm0FkSF9TdzOhtg==", + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, - "jest-validate": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-24.7.0.tgz", - "integrity": "sha512-cgai/gts9B2chz1rqVdmLhzYxQbgQurh1PEQSvSgPZ8KGa1AqXsqC45W5wKEwzxKrWqypuQrQxnF4+G9VejJJA==", + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "@jest/types": "^24.7.0", - "camelcase": "^5.0.0", - "chalk": "^2.0.1", - "jest-get-type": "^24.3.0", - "leven": "^2.1.0", - "pretty-format": "^24.7.0" + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, - "pretty-format": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.7.0.tgz", - "integrity": "sha512-apen5cjf/U4dj7tHetpC7UEFCvtAgnNZnBDkfPv3fokzIqyOJckAG9OlAPC1BlFALnqT/lGB2tl9EJjlK6eCsA==", + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "@jest/types": "^24.7.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" } }, - "react-is": { - "version": "16.8.6", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", - "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "dev": true, "requires": { - "has-flag": "^3.0.0" + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" } } } }, "jest-diff": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.7.0.tgz", - "integrity": "sha512-ULQZ5B1lWpH70O4xsANC4tf4Ko6RrpwhE3PtG6ERjMg1TiYTC2Wp4IntJVGro6a8HG9luYHhhmF4grF0Pltckg==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.9.0.tgz", + "integrity": "sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==", "dev": true, "requires": { "chalk": "^2.0.1", - "diff-sequences": "^24.3.0", - "jest-get-type": "^24.3.0", - "pretty-format": "^24.7.0" + "diff-sequences": "^24.9.0", + "jest-get-type": "^24.9.0", + "pretty-format": "^24.9.0" + } + }, + "jest-docblock": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-24.9.0.tgz", + "integrity": "sha512-F1DjdpDMJMA1cN6He0FNYNZlo3yYmOtRUnktrT9Q37njYzC5WEaDdmbynIgy0L/IvXvvgsG8OsqhLPXTpfmZAA==", + "dev": true, + "requires": { + "detect-newline": "^2.1.0" + } + }, + "jest-each": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-24.9.0.tgz", + "integrity": "sha512-ONi0R4BvW45cw8s2Lrx8YgbeXL1oCQ/wIDwmsM3CqM/nlblNCPmnC3IPQlMbRFZu3wKdQ2U8BqM6lh3LJ5Bsog==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "chalk": "^2.0.1", + "jest-get-type": "^24.9.0", + "jest-util": "^24.9.0", + "pretty-format": "^24.9.0" + } + }, + "jest-environment-jsdom": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-24.9.0.tgz", + "integrity": "sha512-Zv9FV9NBRzLuALXjvRijO2351DRQeLYXtpD4xNvfoVFw21IOKNhZAEUKcbiEtjTkm2GsJ3boMVgkaR7rN8qetA==", + "dev": true, + "requires": { + "@jest/environment": "^24.9.0", + "@jest/fake-timers": "^24.9.0", + "@jest/types": "^24.9.0", + "jest-mock": "^24.9.0", + "jest-util": "^24.9.0", + "jsdom": "^11.5.1" + } + }, + "jest-environment-jsdom-fourteen": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom-fourteen/-/jest-environment-jsdom-fourteen-1.0.1.tgz", + "integrity": "sha512-DojMX1sY+at5Ep+O9yME34CdidZnO3/zfPh8UW+918C5fIZET5vCjfkegixmsi7AtdYfkr4bPlIzmWnlvQkP7Q==", + "dev": true, + "requires": { + "@jest/environment": "^24.3.0", + "@jest/fake-timers": "^24.3.0", + "@jest/types": "^24.3.0", + "jest-mock": "^24.0.0", + "jest-util": "^24.0.0", + "jsdom": "^14.1.0" }, "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", "dev": true }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "jsdom": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-14.1.0.tgz", + "integrity": "sha512-O901mfJSuTdwU2w3Sn+74T+RnDVP+FuV5fH8tcPWyqrseRAb0s5xOtPgCFiPOtLcyK7CLIJwPyD83ZqQWvA5ng==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "abab": "^2.0.0", + "acorn": "^6.0.4", + "acorn-globals": "^4.3.0", + "array-equal": "^1.0.0", + "cssom": "^0.3.4", + "cssstyle": "^1.1.1", + "data-urls": "^1.1.0", + "domexception": "^1.0.1", + "escodegen": "^1.11.0", + "html-encoding-sniffer": "^1.0.2", + "nwsapi": "^2.1.3", + "parse5": "5.1.0", + "pn": "^1.1.0", + "request": "^2.88.0", + "request-promise-native": "^1.0.5", + "saxes": "^3.1.9", + "symbol-tree": "^3.2.2", + "tough-cookie": "^2.5.0", + "w3c-hr-time": "^1.0.1", + "w3c-xmlserializer": "^1.1.2", + "webidl-conversions": "^4.0.2", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^7.0.0", + "ws": "^6.1.2", + "xml-name-validator": "^3.0.0" } }, - "jest-get-type": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.3.0.tgz", - "integrity": "sha512-HYF6pry72YUlVcvUx3sEpMRwXEWGEPlJ0bSPVnB3b3n++j4phUEoSPcS6GC0pPJ9rpyPSe4cb5muFo6D39cXow==", + "parse5": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", + "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==", "dev": true }, - "pretty-format": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.7.0.tgz", - "integrity": "sha512-apen5cjf/U4dj7tHetpC7UEFCvtAgnNZnBDkfPv3fokzIqyOJckAG9OlAPC1BlFALnqT/lGB2tl9EJjlK6eCsA==", + "whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", "dev": true, "requires": { - "@jest/types": "^24.7.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" } }, - "react-is": { - "version": "16.8.6", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", - "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "ws": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", + "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "async-limiter": "~1.0.0" } } } }, - "jest-docblock": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-24.3.0.tgz", - "integrity": "sha512-nlANmF9Yq1dufhFlKG9rasfQlrY7wINJbo3q01tu56Jv5eBU5jirylhF2O5ZBnLxzOVBGRDz/9NAwNyBtG4Nyg==", + "jest-environment-node": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-24.9.0.tgz", + "integrity": "sha512-6d4V2f4nxzIzwendo27Tr0aFm+IXWa0XEUnaH6nU0FMaozxovt+sfRvh4J47wL1OvF83I3SSTu0XK+i4Bqe7uA==", "dev": true, "requires": { - "detect-newline": "^2.1.0" + "@jest/environment": "^24.9.0", + "@jest/fake-timers": "^24.9.0", + "@jest/types": "^24.9.0", + "jest-mock": "^24.9.0", + "jest-util": "^24.9.0" } }, - "jest-each": { - "version": "24.7.1", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-24.7.1.tgz", - "integrity": "sha512-4fsS8fEfLa3lfnI1Jw6NxjhyRTgfpuOVTeUZZFyVYqeTa4hPhr2YkToUhouuLTrL2eMGOfpbdMyRx0GQ/VooKA==", + "jest-get-type": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz", + "integrity": "sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==", + "dev": true + }, + "jest-haste-map": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.9.0.tgz", + "integrity": "sha512-kfVFmsuWui2Sj1Rp1AJ4D9HqJwE4uwTlS/vO+eRUaMmd54BFpli2XhMQnPC2k4cHFVbB2Q2C+jtI1AGLgEnCjQ==", "dev": true, "requires": { - "@jest/types": "^24.7.0", - "chalk": "^2.0.1", - "jest-get-type": "^24.3.0", - "jest-util": "^24.7.1", - "pretty-format": "^24.7.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "jest-get-type": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.3.0.tgz", - "integrity": "sha512-HYF6pry72YUlVcvUx3sEpMRwXEWGEPlJ0bSPVnB3b3n++j4phUEoSPcS6GC0pPJ9rpyPSe4cb5muFo6D39cXow==", - "dev": true - }, - "pretty-format": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.7.0.tgz", - "integrity": "sha512-apen5cjf/U4dj7tHetpC7UEFCvtAgnNZnBDkfPv3fokzIqyOJckAG9OlAPC1BlFALnqT/lGB2tl9EJjlK6eCsA==", - "dev": true, - "requires": { - "@jest/types": "^24.7.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" - } - }, - "react-is": { - "version": "16.8.6", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", - "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "jest-environment-jsdom": { - "version": "24.7.1", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-24.7.1.tgz", - "integrity": "sha512-Gnhb+RqE2JuQGb3kJsLF8vfqjt3PHKSstq4Xc8ic+ax7QKo4Z0RWGucU3YV+DwKR3T9SYc+3YCUQEJs8r7+Jxg==", - "dev": true, - "requires": { - "@jest/environment": "^24.7.1", - "@jest/fake-timers": "^24.7.1", - "@jest/types": "^24.7.0", - "jest-mock": "^24.7.0", - "jest-util": "^24.7.1", - "jsdom": "^11.5.1" - } - }, - "jest-environment-node": { - "version": "24.7.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-24.7.1.tgz", - "integrity": "sha512-GJJQt1p9/C6aj6yNZMvovZuxTUd+BEJprETdvTKSb4kHcw4mFj8777USQV0FJoJ4V3djpOwA5eWyPwfq//PFBA==", - "dev": true, - "requires": { - "@jest/environment": "^24.7.1", - "@jest/fake-timers": "^24.7.1", - "@jest/types": "^24.7.0", - "jest-mock": "^24.7.0", - "jest-util": "^24.7.1" - } - }, - "jest-get-type": { - "version": "22.4.3", - "resolved": "http://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", - "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==", - "dev": true - }, - "jest-haste-map": { - "version": "24.7.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.7.1.tgz", - "integrity": "sha512-g0tWkzjpHD2qa03mTKhlydbmmYiA2KdcJe762SbfFo/7NIMgBWAA0XqQlApPwkWOF7Cxoi/gUqL0i6DIoLpMBw==", - "dev": true, - "requires": { - "@jest/types": "^24.7.0", + "@jest/types": "^24.9.0", "anymatch": "^2.0.0", "fb-watchman": "^2.0.0", "fsevents": "^1.2.7", "graceful-fs": "^4.1.15", "invariant": "^2.2.4", - "jest-serializer": "^24.4.0", - "jest-util": "^24.7.1", - "jest-worker": "^24.6.0", + "jest-serializer": "^24.9.0", + "jest-util": "^24.9.0", + "jest-worker": "^24.9.0", "micromatch": "^3.1.10", "sane": "^4.0.3", "walker": "^1.0.7" }, "dependencies": { - "graceful-fs": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true - } - } - }, - "jest-jasmine2": { - "version": "24.7.1", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-24.7.1.tgz", - "integrity": "sha512-Y/9AOJDV1XS44wNwCaThq4Pw3gBPiOv/s6NcbOAkVRRUEPu+36L2xoPsqQXsDrxoBerqeyslpn2TpCI8Zr6J2w==", - "dev": true, - "requires": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^24.7.1", - "@jest/test-result": "^24.7.1", - "@jest/types": "^24.7.0", - "chalk": "^2.0.1", - "co": "^4.6.0", - "expect": "^24.7.1", - "is-generator-fn": "^2.0.0", - "jest-each": "^24.7.1", - "jest-matcher-utils": "^24.7.0", - "jest-message-util": "^24.7.1", - "jest-runtime": "^24.7.1", - "jest-snapshot": "^24.7.1", - "jest-util": "^24.7.1", - "pretty-format": "^24.7.0", - "throat": "^4.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, - "pretty-format": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.7.0.tgz", - "integrity": "sha512-apen5cjf/U4dj7tHetpC7UEFCvtAgnNZnBDkfPv3fokzIqyOJckAG9OlAPC1BlFALnqT/lGB2tl9EJjlK6eCsA==", + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", "dev": true, + "optional": true, "requires": { - "@jest/types": "^24.7.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" + "bindings": "^1.5.0", + "nan": "^2.12.1" } }, - "react-is": { - "version": "16.8.6", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", - "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==", + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "has-flag": "^3.0.0" + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } - } - } - }, - "jest-leak-detector": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-24.7.0.tgz", - "integrity": "sha512-zV0qHKZGXtmPVVzT99CVEcHE9XDf+8LwiE0Ob7jjezERiGVljmqKFWpV2IkG+rkFIEUHFEkMiICu7wnoPM/RoQ==", - "dev": true, - "requires": { - "pretty-format": "^24.7.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" } }, - "pretty-format": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.7.0.tgz", - "integrity": "sha512-apen5cjf/U4dj7tHetpC7UEFCvtAgnNZnBDkfPv3fokzIqyOJckAG9OlAPC1BlFALnqT/lGB2tl9EJjlK6eCsA==", + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "dev": true, "requires": { - "@jest/types": "^24.7.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" } - }, - "react-is": { - "version": "16.8.6", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", - "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==", - "dev": true } } }, + "jest-jasmine2": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-24.9.0.tgz", + "integrity": "sha512-Cq7vkAgaYKp+PsX+2/JbTarrk0DmNhsEtqBXNwUHkdlbrTBLtMJINADf2mf5FkowNsq8evbPc07/qFO0AdKTzw==", + "dev": true, + "requires": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^24.9.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "chalk": "^2.0.1", + "co": "^4.6.0", + "expect": "^24.9.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^24.9.0", + "jest-matcher-utils": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-runtime": "^24.9.0", + "jest-snapshot": "^24.9.0", + "jest-util": "^24.9.0", + "pretty-format": "^24.9.0", + "throat": "^4.0.0" + } + }, + "jest-leak-detector": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-24.9.0.tgz", + "integrity": "sha512-tYkFIDsiKTGwb2FG1w8hX9V0aUb2ot8zY/2nFg087dUageonw1zrLMP4W6zsRO59dPkTSKie+D4rhMuP9nRmrA==", + "dev": true, + "requires": { + "jest-get-type": "^24.9.0", + "pretty-format": "^24.9.0" + } + }, "jest-matcher-utils": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-24.7.0.tgz", - "integrity": "sha512-158ieSgk3LNXeUhbVJYRXyTPSCqNgVXOp/GT7O94mYd3pk/8+odKTyR1JLtNOQSPzNi8NFYVONtvSWA/e1RDXg==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz", + "integrity": "sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA==", "dev": true, "requires": { "chalk": "^2.0.1", - "jest-diff": "^24.7.0", - "jest-get-type": "^24.3.0", - "pretty-format": "^24.7.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "jest-get-type": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.3.0.tgz", - "integrity": "sha512-HYF6pry72YUlVcvUx3sEpMRwXEWGEPlJ0bSPVnB3b3n++j4phUEoSPcS6GC0pPJ9rpyPSe4cb5muFo6D39cXow==", - "dev": true - }, - "pretty-format": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.7.0.tgz", - "integrity": "sha512-apen5cjf/U4dj7tHetpC7UEFCvtAgnNZnBDkfPv3fokzIqyOJckAG9OlAPC1BlFALnqT/lGB2tl9EJjlK6eCsA==", - "dev": true, - "requires": { - "@jest/types": "^24.7.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" - } - }, - "react-is": { - "version": "16.8.6", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", - "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "jest-diff": "^24.9.0", + "jest-get-type": "^24.9.0", + "pretty-format": "^24.9.0" } }, "jest-message-util": { - "version": "24.7.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.7.1.tgz", - "integrity": "sha512-dk0gqVtyqezCHbcbk60CdIf+8UHgD+lmRHifeH3JRcnAqh4nEyPytSc9/L1+cQyxC+ceaeP696N4ATe7L+omcg==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.9.0.tgz", + "integrity": "sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "@jest/test-result": "^24.7.1", - "@jest/types": "^24.7.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", "@types/stack-utils": "^1.0.1", "chalk": "^2.0.1", "micromatch": "^3.1.10", @@ -10635,435 +10468,264 @@ "stack-utils": "^1.0.1" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, "requires": { - "has-flag": "^3.0.0" + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" } } } }, "jest-mock": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-24.7.0.tgz", - "integrity": "sha512-6taW4B4WUcEiT2V9BbOmwyGuwuAFT2G8yghF7nyNW1/2gq5+6aTqSPcS9lS6ArvEkX55vbPAS/Jarx5LSm4Fng==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-24.9.0.tgz", + "integrity": "sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w==", "dev": true, "requires": { - "@jest/types": "^24.7.0" + "@jest/types": "^24.9.0" } }, "jest-pnp-resolver": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz", - "integrity": "sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", + "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", "dev": true }, "jest-regex-util": { - "version": "23.3.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-23.3.0.tgz", - "integrity": "sha1-X4ZylUfCeFxAAs6qj4Sf6MpHG8U=", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz", + "integrity": "sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==", "dev": true }, "jest-resolve": { - "version": "24.7.1", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-24.7.1.tgz", - "integrity": "sha512-Bgrc+/UUZpGJ4323sQyj85hV9d+ANyPNu6XfRDUcyFNX1QrZpSoM0kE4Mb2vZMAYTJZsBFzYe8X1UaOkOELSbw==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-24.9.0.tgz", + "integrity": "sha512-TaLeLVL1l08YFZAt3zaPtjiVvyy4oSA6CRe+0AFPPVX3Q/VI0giIWWoAvoS5L96vj9Dqxj4fB5p2qrHCmTU/MQ==", "dev": true, "requires": { - "@jest/types": "^24.7.0", + "@jest/types": "^24.9.0", "browser-resolve": "^1.11.3", "chalk": "^2.0.1", "jest-pnp-resolver": "^1.2.1", "realpath-native": "^1.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } } }, "jest-resolve-dependencies": { - "version": "24.7.1", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-24.7.1.tgz", - "integrity": "sha512-2Eyh5LJB2liNzfk4eo7bD1ZyBbqEJIyyrFtZG555cSWW9xVHxII2NuOkSl1yUYTAYCAmM2f2aIT5A7HzNmubyg==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-24.9.0.tgz", + "integrity": "sha512-Fm7b6AlWnYhT0BXy4hXpactHIqER7erNgIsIozDXWl5dVm+k8XdGVe1oTg1JyaFnOxarMEbax3wyRJqGP2Pq+g==", "dev": true, "requires": { - "@jest/types": "^24.7.0", + "@jest/types": "^24.9.0", "jest-regex-util": "^24.3.0", - "jest-snapshot": "^24.7.1" - }, - "dependencies": { - "jest-regex-util": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.3.0.tgz", - "integrity": "sha512-tXQR1NEOyGlfylyEjg1ImtScwMq8Oh3iJbGTjN7p0J23EuVX1MA8rwU69K4sLbCmwzgCUbVkm0FkSF9TdzOhtg==", - "dev": true - } + "jest-snapshot": "^24.9.0" } }, "jest-runner": { - "version": "24.7.1", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-24.7.1.tgz", - "integrity": "sha512-aNFc9liWU/xt+G9pobdKZ4qTeG/wnJrJna3VqunziDNsWT3EBpmxXZRBMKCsNMyfy+A/XHiV+tsMLufdsNdgCw==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-24.9.0.tgz", + "integrity": "sha512-KksJQyI3/0mhcfspnxxEOBueGrd5E4vV7ADQLT9ESaCzz02WnbdbKWIf5Mkaucoaj7obQckYPVX6JJhgUcoWWg==", "dev": true, "requires": { "@jest/console": "^24.7.1", - "@jest/environment": "^24.7.1", - "@jest/test-result": "^24.7.1", - "@jest/types": "^24.7.0", + "@jest/environment": "^24.9.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", "chalk": "^2.4.2", "exit": "^0.1.2", "graceful-fs": "^4.1.15", - "jest-config": "^24.7.1", + "jest-config": "^24.9.0", "jest-docblock": "^24.3.0", - "jest-haste-map": "^24.7.1", - "jest-jasmine2": "^24.7.1", - "jest-leak-detector": "^24.7.0", - "jest-message-util": "^24.7.1", - "jest-resolve": "^24.7.1", - "jest-runtime": "^24.7.1", - "jest-util": "^24.7.1", + "jest-haste-map": "^24.9.0", + "jest-jasmine2": "^24.9.0", + "jest-leak-detector": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-resolve": "^24.9.0", + "jest-runtime": "^24.9.0", + "jest-util": "^24.9.0", "jest-worker": "^24.6.0", "source-map-support": "^0.5.6", "throat": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "graceful-fs": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true - }, - "source-map-support": { - "version": "0.5.12", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", - "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } } }, "jest-runtime": { - "version": "24.7.1", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-24.7.1.tgz", - "integrity": "sha512-0VAbyBy7tll3R+82IPJpf6QZkokzXPIS71aDeqh+WzPRXRCNz6StQ45otFariPdJ4FmXpDiArdhZrzNAC3sj6A==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-24.9.0.tgz", + "integrity": "sha512-8oNqgnmF3v2J6PVRM2Jfuj8oX3syKmaynlDMMKQ4iyzbQzIG6th5ub/lM2bCMTmoTKM3ykcUYI2Pw9xwNtjMnw==", "dev": true, "requires": { "@jest/console": "^24.7.1", - "@jest/environment": "^24.7.1", + "@jest/environment": "^24.9.0", "@jest/source-map": "^24.3.0", - "@jest/transform": "^24.7.1", - "@jest/types": "^24.7.0", - "@types/yargs": "^12.0.2", + "@jest/transform": "^24.9.0", + "@jest/types": "^24.9.0", + "@types/yargs": "^13.0.0", "chalk": "^2.0.1", "exit": "^0.1.2", "glob": "^7.1.3", "graceful-fs": "^4.1.15", - "jest-config": "^24.7.1", - "jest-haste-map": "^24.7.1", - "jest-message-util": "^24.7.1", - "jest-mock": "^24.7.0", + "jest-config": "^24.9.0", + "jest-haste-map": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-mock": "^24.9.0", "jest-regex-util": "^24.3.0", - "jest-resolve": "^24.7.1", - "jest-snapshot": "^24.7.1", - "jest-util": "^24.7.1", - "jest-validate": "^24.7.0", + "jest-resolve": "^24.9.0", + "jest-snapshot": "^24.9.0", + "jest-util": "^24.9.0", + "jest-validate": "^24.9.0", "realpath-native": "^1.1.0", "slash": "^2.0.0", "strip-bom": "^3.0.0", - "yargs": "^12.0.2" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "graceful-fs": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true - }, - "jest-get-type": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.3.0.tgz", - "integrity": "sha512-HYF6pry72YUlVcvUx3sEpMRwXEWGEPlJ0bSPVnB3b3n++j4phUEoSPcS6GC0pPJ9rpyPSe4cb5muFo6D39cXow==", - "dev": true - }, - "jest-regex-util": { - "version": "24.3.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.3.0.tgz", - "integrity": "sha512-tXQR1NEOyGlfylyEjg1ImtScwMq8Oh3iJbGTjN7p0J23EuVX1MA8rwU69K4sLbCmwzgCUbVkm0FkSF9TdzOhtg==", - "dev": true - }, - "jest-validate": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-24.7.0.tgz", - "integrity": "sha512-cgai/gts9B2chz1rqVdmLhzYxQbgQurh1PEQSvSgPZ8KGa1AqXsqC45W5wKEwzxKrWqypuQrQxnF4+G9VejJJA==", - "dev": true, - "requires": { - "@jest/types": "^24.7.0", - "camelcase": "^5.0.0", - "chalk": "^2.0.1", - "jest-get-type": "^24.3.0", - "leven": "^2.1.0", - "pretty-format": "^24.7.0" - } - }, - "pretty-format": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.7.0.tgz", - "integrity": "sha512-apen5cjf/U4dj7tHetpC7UEFCvtAgnNZnBDkfPv3fokzIqyOJckAG9OlAPC1BlFALnqT/lGB2tl9EJjlK6eCsA==", - "dev": true, - "requires": { - "@jest/types": "^24.7.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" - } - }, - "react-is": { - "version": "16.8.6", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", - "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==", - "dev": true - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "yargs": "^13.3.0" } }, "jest-serializer": { - "version": "24.4.0", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-24.4.0.tgz", - "integrity": "sha512-k//0DtglVstc1fv+GY/VHDIjrtNjdYvYjMlbLUed4kxrE92sIUewOi5Hj3vrpB8CXfkJntRPDRjCrCvUhBdL8Q==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-24.9.0.tgz", + "integrity": "sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ==", "dev": true }, "jest-snapshot": { - "version": "24.7.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-24.7.1.tgz", - "integrity": "sha512-8Xk5O4p+JsZZn4RCNUS3pxA+ORKpEKepE+a5ejIKrId9CwrVN0NY+vkqEkXqlstA5NMBkNahXkR/4qEBy0t5yA==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-24.9.0.tgz", + "integrity": "sha512-uI/rszGSs73xCM0l+up7O7a40o90cnrk429LOiK3aeTvfC0HHmldbd81/B7Ix81KSFe1lwkbl7GnBGG4UfuDew==", "dev": true, "requires": { "@babel/types": "^7.0.0", - "@jest/types": "^24.7.0", + "@jest/types": "^24.9.0", "chalk": "^2.0.1", - "expect": "^24.7.1", - "jest-diff": "^24.7.0", - "jest-matcher-utils": "^24.7.0", - "jest-message-util": "^24.7.1", - "jest-resolve": "^24.7.1", + "expect": "^24.9.0", + "jest-diff": "^24.9.0", + "jest-get-type": "^24.9.0", + "jest-matcher-utils": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-resolve": "^24.9.0", "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", - "pretty-format": "^24.7.0", - "semver": "^5.5.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "pretty-format": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.7.0.tgz", - "integrity": "sha512-apen5cjf/U4dj7tHetpC7UEFCvtAgnNZnBDkfPv3fokzIqyOJckAG9OlAPC1BlFALnqT/lGB2tl9EJjlK6eCsA==", - "dev": true, - "requires": { - "@jest/types": "^24.7.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" - } - }, - "react-is": { - "version": "16.8.6", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", - "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "pretty-format": "^24.9.0", + "semver": "^6.2.0" } }, "jest-util": { - "version": "24.7.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.7.1.tgz", - "integrity": "sha512-/KilOue2n2rZ5AnEBYoxOXkeTu6vi7cjgQ8MXEkih0oeAXT6JkS3fr7/j8+engCjciOU1Nq5loMSKe0A1oeX0A==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.9.0.tgz", + "integrity": "sha512-x+cZU8VRmOJxbA1K5oDBdxQmdq0OIdADarLxk0Mq+3XS4jgvhG/oKGWcIDCtPG0HgjxOYvF+ilPJQsAyXfbNOg==", "dev": true, "requires": { - "@jest/console": "^24.7.1", - "@jest/fake-timers": "^24.7.1", - "@jest/source-map": "^24.3.0", - "@jest/test-result": "^24.7.1", - "@jest/types": "^24.7.0", + "@jest/console": "^24.9.0", + "@jest/fake-timers": "^24.9.0", + "@jest/source-map": "^24.9.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", "callsites": "^3.0.0", "chalk": "^2.0.1", "graceful-fs": "^4.1.15", @@ -11073,937 +10735,1699 @@ "source-map": "^0.6.0" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "graceful-fs": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } } } }, "jest-validate": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-23.6.0.tgz", - "integrity": "sha512-OFKapYxe72yz7agrDAWi8v2WL8GIfVqcbKRCLbRG9PAxtzF9b1SEDdTpytNDN12z2fJynoBwpMpvj2R39plI2A==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-24.9.0.tgz", + "integrity": "sha512-HPIt6C5ACwiqSiwi+OfSSHbK8sG7akG8eATl+IPKaeIjtPOeBUd/g3J7DghugzxrGjI93qS/+RPKe1H6PqvhRQ==", "dev": true, "requires": { + "@jest/types": "^24.9.0", + "camelcase": "^5.3.1", "chalk": "^2.0.1", - "jest-get-type": "^22.1.0", - "leven": "^2.1.0", - "pretty-format": "^23.6.0" + "jest-get-type": "^24.9.0", + "leven": "^3.1.0", + "pretty-format": "^24.9.0" + } + }, + "jest-watch-typeahead": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-0.4.2.tgz", + "integrity": "sha512-f7VpLebTdaXs81rg/oj4Vg/ObZy2QtGzAmGLNsqUS5G5KtSN68tFcIsbvNODfNyQxU78g7D8x77o3bgfBTR+2Q==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^2.4.1", + "jest-regex-util": "^24.9.0", + "jest-watcher": "^24.3.0", + "slash": "^3.0.0", + "string-length": "^3.1.0", + "strip-ansi": "^5.0.0" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "ansi-escapes": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "type-fest": "^0.11.0" } }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "string-length": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-3.1.0.tgz", + "integrity": "sha512-Ttp5YvkGm5v9Ijagtaz1BnN+k9ObpvS0eIBblPMp2YWL8FBmi9qblQ9fexc2k/CXFgrTIteU3jAw3payCnwSTA==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "astral-regex": "^1.0.0", + "strip-ansi": "^5.2.0" } }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "ansi-regex": "^4.1.0" } + }, + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "dev": true } } }, "jest-watcher": { - "version": "24.7.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-24.7.1.tgz", - "integrity": "sha512-Wd6TepHLRHVKLNPacEsBwlp9raeBIO+01xrN24Dek4ggTS8HHnOzYSFnvp+6MtkkJ3KfMzy220KTi95e2rRkrw==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-24.9.0.tgz", + "integrity": "sha512-+/fLOfKPXXYJDYlks62/4R4GoT+GU1tYZed99JSCOsmzkkF7727RqKrjNAxtfO4YpGv11wybgRvCjR73lK2GZw==", + "dev": true, + "requires": { + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "@types/yargs": "^13.0.0", + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.1", + "jest-util": "^24.9.0", + "string-length": "^2.0.0" + } + }, + "jest-websocket-mock": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/jest-websocket-mock/-/jest-websocket-mock-2.2.0.tgz", + "integrity": "sha512-lc3wwXOEyNa4ZpcgJtUG3mmKMAq5FAsKYiZph0p/+PAJrAPuX4JCIfJMdJ/urRsLBG51fwm/wlVPNbR6s2nzNw==", + "dev": true + }, + "jest-worker": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", + "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", + "dev": true, + "requires": { + "merge-stream": "^2.0.0", + "supports-color": "^6.1.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "jsdom": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", + "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", + "dev": true, + "requires": { + "abab": "^2.0.0", + "acorn": "^5.5.3", + "acorn-globals": "^4.1.0", + "array-equal": "^1.0.0", + "cssom": ">= 0.3.2 < 0.4.0", + "cssstyle": "^1.0.0", + "data-urls": "^1.0.0", + "domexception": "^1.0.1", + "escodegen": "^1.9.1", + "html-encoding-sniffer": "^1.0.2", + "left-pad": "^1.3.0", + "nwsapi": "^2.0.7", + "parse5": "4.0.0", + "pn": "^1.1.0", + "request": "^2.87.0", + "request-promise-native": "^1.0.5", + "sax": "^1.2.4", + "symbol-tree": "^3.2.2", + "tough-cookie": "^2.3.4", + "w3c-hr-time": "^1.0.1", + "webidl-conversions": "^4.0.2", + "whatwg-encoding": "^1.0.3", + "whatwg-mimetype": "^2.1.0", + "whatwg-url": "^6.4.1", + "ws": "^5.2.0", + "xml-name-validator": "^3.0.0" + }, + "dependencies": { + "acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", + "dev": true + }, + "parse5": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "dev": true + } + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, + "requires": { + "jsonify": "~0.0.0" + } + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "json3": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz", + "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==", + "dev": true + }, + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "jsx-ast-utils": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz", + "integrity": "sha512-EIsmt3O3ljsU6sot/J4E1zDRxfBNrhjyf/OKjlydwgEimQuznlM4Wv7U+ueONJMyEn1WRE0K8dhi3dVAXYT24Q==", + "dev": true, + "requires": { + "array-includes": "^3.1.2", + "object.assign": "^4.1.2" + } + }, + "killable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", + "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==", + "dev": true + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + } + } + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, + "language-subtag-registry": { + "version": "0.3.21", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz", + "integrity": "sha512-L0IqwlIXjilBVVYKFT37X9Ih11Um5NEl9cbJIuU/SwP/zEEAbBPOnEeeuxVMf45ydWQRDQN3Nqc96OgbH1K+Pg==", + "dev": true + }, + "language-tags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", + "integrity": "sha1-0yHbxNowuovzAk4ED6XBRmH5GTo=", + "dev": true, + "requires": { + "language-subtag-registry": "~0.3.2" + } + }, + "last-call-webpack-plugin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz", + "integrity": "sha512-7KI2l2GIZa9p2spzPIVZBYyNKkN+e/SQPpnjlTiPhdbDW3F86tdKKELxKpzJ5sgU19wQWsACULZmpTPYHeWO5w==", + "dev": true, + "requires": { + "lodash": "^4.17.5", + "webpack-sources": "^1.1.0" + } + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "dev": true + }, + "left-pad": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", + "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", + "dev": true + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "levenary": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz", + "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==", + "dev": true, + "requires": { + "leven": "^3.1.0" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, + "load-json-file": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-5.3.0.tgz", + "integrity": "sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.15", + "parse-json": "^4.0.0", + "pify": "^4.0.1", + "strip-bom": "^3.0.0", + "type-fest": "^0.3.0" + } + }, + "loader-fs-cache": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/loader-fs-cache/-/loader-fs-cache-1.0.3.tgz", + "integrity": "sha512-ldcgZpjNJj71n+2Mf6yetz+c9bM4xpKtNds4LbqXzU/PTdeAX0g3ytnU1AJMEcTk2Lex4Smpe3Q/eCTsvUBxbA==", + "dev": true, + "requires": { + "find-cache-dir": "^0.1.1", + "mkdirp": "^0.5.1" + }, + "dependencies": { + "find-cache-dir": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-0.1.1.tgz", + "integrity": "sha1-yN765XyKUqinhPnjHFfHQumToLk=", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "mkdirp": "^0.5.1", + "pkg-dir": "^1.0.0" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "pkg-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", + "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", + "dev": true, + "requires": { + "find-up": "^1.0.0" + } + } + } + }, + "loader-runner": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", + "dev": true + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + } + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + }, + "lodash-es": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.15.tgz", + "integrity": "sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==" + }, + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", + "dev": true + }, + "lodash.escape": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", + "integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=", + "dev": true + }, + "lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", + "dev": true + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", + "dev": true + }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "dev": true + }, + "lodash.template": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", + "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", + "dev": true, + "requires": { + "lodash._reinterpolate": "^3.0.0", + "lodash.templatesettings": "^4.0.0" + } + }, + "lodash.templatesettings": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", + "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", + "dev": true, + "requires": { + "lodash._reinterpolate": "^3.0.0" + } + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", + "dev": true + }, + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "requires": { + "chalk": "^2.0.1" + } + }, + "loglevel": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.1.tgz", + "integrity": "sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "requires": { + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==", + "dev": true + } + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "luxon": { + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.25.0.tgz", + "integrity": "sha512-hEgLurSH8kQRjY6i4YLey+mcKVAWXbDNlZRmM6AgWDJ1cY3atl8Ztf5wEY7VBReFbmGnwQPz7KYJblL8B2k0jQ==", + "optional": true + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "make-plural": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-4.3.0.tgz", + "integrity": "sha512-xTYd4JVHpSCW+aqDof6w/MebaMVNTVYBZhbB/vi513xXdiPT92JMVCo0Jq8W2UZnzYRFeVbQiQ+I25l13JuKvA==", + "requires": { + "minimist": "^1.2.0" + } + }, + "makeerror": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", + "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", + "dev": true, + "requires": { + "tmpl": "1.0.x" + } + }, + "mamacro": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz", + "integrity": "sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==", + "dev": true + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "mdn-data": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", + "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==", + "dev": true + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "memoize-one": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.1.1.tgz", + "integrity": "sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA==" + }, + "memory-fs": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.2.0.tgz", + "integrity": "sha1-8rslNovBIeORwlIN6Slpyu4KApA=", + "dev": true + }, + "merge-anything": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/merge-anything/-/merge-anything-2.4.4.tgz", + "integrity": "sha512-l5XlriUDJKQT12bH+rVhAHjwIuXWdAIecGwsYjv2LJo+dA1AeRTmeQS+3QBpO6lEthBMDi2IUMpLC1yyRvGlwQ==", + "requires": { + "is-what": "^3.3.1" + } + }, + "merge-deep": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.2.tgz", + "integrity": "sha512-T7qC8kg4Zoti1cFd8Cr0M+qaZfOwjlPDEdZIIPPB2JZctjaPM4fX+i7HOId69tAti2fvO6X5ldfYUONDODsrkA==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "clone-deep": "^0.2.4", + "kind-of": "^3.0.2" + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "messageformat-parser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/messageformat-parser/-/messageformat-parser-2.0.0.tgz", + "integrity": "sha512-C2ZjB5GlLeikkeoMCTcwEeb68LrFl9osxQzXHIPh0Wcj+43wNsoKpRRKq9rm204sAIdknrdcoeQMUnzvDuMf6g==" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "microevent.ts": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/microevent.ts/-/microevent.ts-0.1.1.tgz", + "integrity": "sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g==", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", "dev": true, "requires": { - "@jest/test-result": "^24.7.1", - "@jest/types": "^24.7.0", - "@types/yargs": "^12.0.9", - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.1", - "jest-util": "^24.7.1", - "string-length": "^2.0.0" + "bn.js": "^4.0.0", + "brorand": "^1.0.1" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true } } }, - "jest-worker": { - "version": "24.6.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.6.0.tgz", - "integrity": "sha512-jDwgW5W9qGNvpI1tNnvajh0a5IE/PuGLFmHk6aR/BZFz8tSgGw17GsDPXAJ6p91IvYDjOw8GpFbvvZGAK+DPQQ==", + "mime": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.7.tgz", + "integrity": "sha512-dhNd1uA2u397uQk3Nv5LM4lm93WYDUXFn3Fu291FJerns4jyTudqhIWe4W04YLy7Uk1tm1Ore04NpjRvQp/NPA==", + "dev": true + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "dev": true + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", "dev": true, "requires": { - "merge-stream": "^1.0.1", - "supports-color": "^6.1.0" + "mime-db": "1.44.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "mini-create-react-context": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz", + "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==", + "requires": { + "@babel/runtime": "^7.12.1", + "tiny-warning": "^1.0.3" + } + }, + "mini-css-extract-plugin": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz", + "integrity": "sha512-lp3GeY7ygcgAmVIcRPBVhIkf8Us7FZjA+ILpal44qLdSu11wmjKQ3d9k15lfD7pO4esu9eUIAW7qiYIBppv40A==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "normalize-url": "1.9.1", + "schema-utils": "^1.0.0", + "webpack-sources": "^1.1.0" }, "dependencies": { - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" } } } }, - "js-base64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz", - "integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==", + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", "dev": true }, - "js-levenshtein": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.4.tgz", - "integrity": "sha512-PxfGzSs0ztShKrUYPIn5r0MtyAhYcCwmndozzpz8YObbPnD1jFxzlBGbRnX2mIu6Z13xN6+PTu05TQFnZFlzow==", + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", "dev": true }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "minipass": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", + "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", + "dev": true, "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "yallist": "^4.0.0" } }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + "minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } }, - "jsdom": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", - "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", + "minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", "dev": true, "requires": { - "abab": "^2.0.0", - "acorn": "^5.5.3", - "acorn-globals": "^4.1.0", - "array-equal": "^1.0.0", - "cssom": ">= 0.3.2 < 0.4.0", - "cssstyle": "^1.0.0", - "data-urls": "^1.0.0", - "domexception": "^1.0.1", - "escodegen": "^1.9.1", - "html-encoding-sniffer": "^1.0.2", - "left-pad": "^1.3.0", - "nwsapi": "^2.0.7", - "parse5": "4.0.0", - "pn": "^1.1.0", - "request": "^2.87.0", - "request-promise-native": "^1.0.5", - "sax": "^1.2.4", - "symbol-tree": "^3.2.2", - "tough-cookie": "^2.3.4", - "w3c-hr-time": "^1.0.1", - "webidl-conversions": "^4.0.2", - "whatwg-encoding": "^1.0.3", - "whatwg-mimetype": "^2.1.0", - "whatwg-url": "^6.4.1", - "ws": "^5.2.0", - "xml-name-validator": "^3.0.0" + "minipass": "^3.0.0" + } + }, + "minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" }, "dependencies": { - "cssstyle": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.1.1.tgz", - "integrity": "sha512-364AI1l/M5TYcFH83JnOH/pSqgaNnKmYgKrm0didZMGKWjQB60dymwWy1rKUgL3J1ffdq9xVi2yGLHdSjjSNog==", + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "cssom": "0.3.x" + "is-plain-object": "^2.0.4" } } } }, - "jsesc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=" + "mixin-object": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", + "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", + "dev": true, + "requires": { + "for-in": "^0.1.3", + "is-extendable": "^0.1.1" + }, + "dependencies": { + "for-in": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", + "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=", + "dev": true + } + } }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + "mock-socket": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.0.3.tgz", + "integrity": "sha512-SxIiD2yE/By79p3cNAAXyLQWTvEFNEzcAO7PH+DzRqKSFaplAPFjiQLmw8ofmpCsZf+Rhfn2/xCJagpdGmYdTw==", + "dev": true, + "requires": { + "url-parse": "^1.4.4" + } }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + "moo": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.1.tgz", + "integrity": "sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w==", + "dev": true }, - "json-stable-stringify-without-jsonify": { + "move-concurrently": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + "multicast-dns": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", + "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", + "dev": true, + "requires": { + "dns-packet": "^1.3.1", + "thunky": "^1.0.2" + } }, - "json3": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", - "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", + "multicast-dns-service-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", + "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", "dev": true }, - "json5": { - "version": "0.5.1", - "resolved": "http://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=" + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "requires": { - "graceful-fs": "^4.1.6" - } + "nan": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", + "dev": true, + "optional": true }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + } } }, - "jsx-ast-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz", - "integrity": "sha1-6AGxs5mF4g//yHtA43SAgOLcrH8=", + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "nearley": { + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz", + "integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==", "dev": true, "requires": { - "array-includes": "^3.0.3" + "commander": "^2.19.0", + "moo": "^0.5.0", + "railroad-diagrams": "^1.0.0", + "randexp": "0.4.6" } }, - "killable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", - "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==", + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", "dev": true }, - "kind-of": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", - "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=" - }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, - "lcid": { + "next-tick": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", "dev": true, "requires": { - "invert-kv": "^1.0.0" + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==", + "dev": true + } } }, - "left-pad": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", - "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", + "node-forge": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", "dev": true }, - "leven": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", - "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", "dev": true }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "node-libs-browser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", + "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", + "dev": true, "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.1", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.11.0", + "vm-browserify": "^1.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + } } }, - "linear-layout-vector": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/linear-layout-vector/-/linear-layout-vector-0.0.1.tgz", - "integrity": "sha1-OYEU1zA7bsx/1rJzr3uEAdi6nHA=" + "node-modules-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", + "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", + "dev": true }, - "load-json-file": { - "version": "1.1.0", - "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "node-notifier": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.3.tgz", + "integrity": "sha512-M4UBGcs4jeOK9CjTsYwkvH6/MzuUmGCyTW+kCY7uO+1ZVr0+FHGdPdIf5CCLqAaxnRrWidyoQlNkMIIVwbKB8Q==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" + "growly": "^1.3.0", + "is-wsl": "^1.1.0", + "semver": "^5.5.0", + "shellwords": "^0.1.1", + "which": "^1.3.0" }, "dependencies": { - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "requires": { - "error-ex": "^1.2.0" - } + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true } } }, - "loader-runner": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", - "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", + "node-releases": { + "version": "1.1.67", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.67.tgz", + "integrity": "sha512-V5QF9noGFl3EymEwUYzO+3NTDpGfQB4ve6Qfnzf3UNydMhjQRVPR1DZTuvWiLzaFJYw2fmDwAfnRNEVb64hSIg==", "dev": true }, - "loader-utils": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", - "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, "requires": { - "big.js": "^3.1.3", - "emojis-list": "^2.0.0", - "json5": "^0.5.0" + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "dev": true, "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" + "remove-trailing-separator": "^1.0.1" } }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", + "dev": true }, - "lodash-es": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.11.tgz", - "integrity": "sha512-DHb1ub+rMjjrxqlB3H56/6MXtm1lSksDp2rA2cNWjG8mlDUYFhUj3Di2Zn5IwSU87xLv8tNIQ7sSwE/YOX/D/Q==" + "normalize-url": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", + "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", + "dev": true, + "requires": { + "object-assign": "^4.0.1", + "prepend-http": "^1.0.0", + "query-string": "^4.1.0", + "sort-keys": "^1.0.0" + } }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", - "dev": true + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } }, - "lodash.escape": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", - "integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=", - "dev": true + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dev": true, + "requires": { + "boolbase": "~1.0.0" + } }, - "lodash.flattendeep": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "num2fraction": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", "dev": true }, - "lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", + "nwsapi": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", + "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", "dev": true }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true }, - "lodash.tail": { + "object-assign": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz", - "integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=", - "dev": true + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, - "log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", "dev": true, "requires": { - "chalk": "^2.0.1" + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "has-flag": "^3.0.0" + "is-descriptor": "^0.1.0" } } } }, - "loglevel": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz", - "integrity": "sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=", + "object-hash": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.0.3.tgz", + "integrity": "sha512-JPKn0GMu+Fa3zt3Bmr66JhokJU5BaNBIh4ZeTlaCBzrBsOeXzwcKKAK1tbLiPKgvwmPXsDvvLHoWh5Bm7ofIYg==", "dev": true }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } + "object-inspect": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", + "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==", + "dev": true }, - "loud-rejection": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "object-is": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.4.tgz", + "integrity": "sha512-1ZvAZ4wlF7IyPVOcE1Omikt7UpaFlOQq0HlSti+ZvDH3UiD2brwGMwDbyV43jao2bKJ+4+WdPJHSd7kgzKYVqg==", "dev": true, "requires": { - "currently-unhandled": "^0.4.1", - "signal-exit": "^3.0.0" + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" } }, - "lower-case": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", - "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=" + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true }, - "lru-cache": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", - "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" + "isobject": "^3.0.0" } }, - "make-dir": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", "dev": true, "requires": { - "pify": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" } }, - "make-plural": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-4.3.0.tgz", - "integrity": "sha512-xTYd4JVHpSCW+aqDof6w/MebaMVNTVYBZhbB/vi513xXdiPT92JMVCo0Jq8W2UZnzYRFeVbQiQ+I25l13JuKvA==", + "object.entries": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.3.tgz", + "integrity": "sha512-ym7h7OZebNS96hn5IJeyUmaWhaSM4SVtAPPfNLQEI2MYWCO2egsITb9nab2+i/Pwibx+R0mtn+ltKJXRSeTMGg==", + "dev": true, "requires": { - "minimist": "^1.2.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "optional": true - } + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1", + "has": "^1.0.3" } }, - "makeerror": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", - "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", + "object.fromentries": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.3.tgz", + "integrity": "sha512-IDUSMXs6LOSJBWE++L0lzIbSqHl9KDCfff2x/JSEIDtEUavUnyMYC2ZGay/04Zq4UT8lvd4xNhU4/YHKibAOlw==", "dev": true, "requires": { - "tmpl": "1.0.x" + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1", + "has": "^1.0.3" } }, - "mamacro": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz", - "integrity": "sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==", - "dev": true - }, - "map-age-cleaner": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.2.tgz", - "integrity": "sha512-UN1dNocxQq44IhJyMI4TU8phc2m9BddacHRPRjKGLYaF0jqd3xLz0jS0skpAU9WgYyoR4gHtUpzytNBS385FWQ==", + "object.getownpropertydescriptors": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.1.tgz", + "integrity": "sha512-6DtXgZ/lIZ9hqx4GtZETobXLR/ZLaa0aqV0kzbn80Rf8Z2e/XFnhA0I7p07N2wH8bBBltr2xQPi6sbKWAY2Eng==", "dev": true, "requires": { - "p-defer": "^1.0.0" + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1" } }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true - }, - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", "dev": true, "requires": { - "object-visit": "^1.0.0" + "isobject": "^3.0.1" } }, - "md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "object.values": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.2.tgz", + "integrity": "sha512-MYC0jvJopr8EK6dPBiO8Nb9mvjdypOachO5REGk6MXzujbBrAisKo3HmdEI6kZDL6fC31Mwee/5YbtMebixeag==", "dev": true, "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1", + "has": "^1.0.3" } }, - "media-typer": { - "version": "0.3.0", - "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", "dev": true }, - "mem": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", - "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", "dev": true, "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" - }, - "dependencies": { - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "p-is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", - "dev": true - } + "ee-first": "1.1.1" } }, - "memoize-one": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.0.4.tgz", - "integrity": "sha512-P0z5IeAH6qHHGkJIXWw0xC2HNEgkx/9uWWBQw64FJj3/ol14VYdfVGWWr0fXfjhhv3TKVIqUq65os6O4GUNksA==" + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true }, - "memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } + "wrappy": "1" } }, - "meow": { - "version": "3.7.0", - "resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", "dev": true, "requires": { - "camelcase-keys": "^2.0.0", - "decamelize": "^1.1.2", - "loud-rejection": "^1.0.0", - "map-obj": "^1.0.1", - "minimist": "^1.1.3", - "normalize-package-data": "^2.3.4", - "object-assign": "^4.0.1", - "read-pkg-up": "^1.0.1", - "redent": "^1.0.0", - "trim-newlines": "^1.0.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } + "mimic-fn": "^1.0.0" } }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", - "dev": true - }, - "merge-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", - "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", + "open": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/open/-/open-7.3.0.tgz", + "integrity": "sha512-mgLwQIx2F/ye9SmbrUkurZCnkoXyXyu9EbHtJZrICjVAJfyMArdHp3KkixGdZx1ZHFPNIwl0DDM1dFFqXbTLZw==", "dev": true, "requires": { - "readable-stream": "^2.0.1" + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" }, "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "is-docker": "^2.0.0" } } } }, - "messageformat-parser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/messageformat-parser/-/messageformat-parser-2.0.0.tgz", - "integrity": "sha512-C2ZjB5GlLeikkeoMCTcwEeb68LrFl9osxQzXHIPh0Wcj+43wNsoKpRRKq9rm204sAIdknrdcoeQMUnzvDuMf6g==" + "opn": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", + "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", + "dev": true, + "requires": { + "is-wsl": "^1.1.0" + } }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "dev": true + "optimize-css-assets-webpack-plugin": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.3.tgz", + "integrity": "sha512-q9fbvCRS6EYtUKKSwI87qm2IxlyJK5b4dygW1rKUBT6mMDhdG5e5bZT63v6tnJR9F9FB/H5a0HTmtw+laUBxKA==", + "dev": true, + "requires": { + "cssnano": "^4.1.10", + "last-call-webpack-plugin": "^3.0.0" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "ora": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-3.4.0.tgz", + "integrity": "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-spinners": "^2.0.0", + "log-symbols": "^2.2.0", + "strip-ansi": "^5.2.0", + "wcwidth": "^1.0.1" }, "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "is-plain-object": "^2.0.4" + "ansi-regex": "^4.1.0" } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true } } }, - "miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "original": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", + "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", "dev": true, "requires": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" + "url-parse": "^1.4.3" } }, - "mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", "dev": true }, - "mime-db": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", - "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true }, - "mime-types": { - "version": "2.1.21", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", - "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", + "p-each-series": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-1.0.0.tgz", + "integrity": "sha1-kw89Et0fUOdDRFeiLNbwSsatf3E=", + "dev": true, "requires": { - "mime-db": "~1.37.0" + "p-reduce": "^1.0.0" } }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", "dev": true }, - "min-document": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", - "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "requires": { - "dom-walk": "^0.1.0" + "p-try": "^2.0.0" } }, - "mini-create-react-context": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz", - "integrity": "sha512-2v+OeetEyliMt5VHMXsBhABoJ0/M4RCe7fatd/fBy6SMiKazUSEt3gxxypfnk2SHMkdBYvorHRoQxuGoiwbzAw==", + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, "requires": { - "@babel/runtime": "^7.4.0", - "gud": "^1.0.0", - "tiny-warning": "^1.0.2" + "p-limit": "^2.2.0" } }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "p-reduce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz", + "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=", "dev": true }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "p-retry": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz", + "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==", + "dev": true, "requires": { - "brace-expansion": "^1.1.7" + "retry": "^0.12.0" } }, - "minimist": { - "version": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true }, - "mississippi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", - "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "parallel-transform": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", + "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", "dev": true, "requires": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^3.0.0", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" + "cyclist": "^1.0.1", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" }, "dependencies": { "isarray": { @@ -12013,9 +12437,9 @@ "dev": true }, "readable-stream": { - "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -12035,1395 +12459,1328 @@ "requires": { "safe-buffer": "~5.1.0" } - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true } } }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", "dev": true, "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" + "dot-case": "^3.0.4", + "tslib": "^2.0.3" }, "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==", + "dev": true } } }, - "mixin-object": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", - "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "requires": { - "for-in": "^0.1.3", - "is-extendable": "^0.1.1" + "callsites": "^3.0.0" }, "dependencies": { - "for-in": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", - "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=", + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true } } }, - "mkdirp": { - "version": "0.5.1", - "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "parse-asn1": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "dev": true, "requires": { - "minimist": "0.0.8" + "asn1.js": "^5.2.0", + "browserify-aes": "^1.0.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" } }, - "moo": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/moo/-/moo-0.4.3.tgz", - "integrity": "sha512-gFD2xGCl8YFgGHsqJ9NKRVdwlioeW3mI1iqfLNYQOv0+6JRwG58Zk9DIGQgyIaffSYaO1xsKnMaYzzNr1KyIAw==", - "dev": true + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } }, - "move-concurrently": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", - "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "parse5": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", + "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", "dev": true, "requires": { - "aproba": "^1.1.1", - "copy-concurrently": "^1.0.0", - "fs-write-stream-atomic": "^1.0.8", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.3" + "@types/node": "*" } }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true }, - "multicast-dns": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", - "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", + "pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", "dev": true, "requires": { - "dns-packet": "^1.3.1", - "thunky": "^1.0.2" + "no-case": "^3.0.4", + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==", + "dev": true + } } }, - "multicast-dns-service-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", - "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, - "multipipe": { + "path-is-inside": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-1.0.2.tgz", - "integrity": "sha1-zBPv2DPJzamfIk+GhGG44aP9k50=", - "requires": { - "duplexer2": "^0.1.2", - "object-assign": "^4.1.0" - } + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true }, - "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", "dev": true }, - "nan": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz", - "integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==", - "dev": true, - "optional": true + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "requires": { + "isarray": "0.0.1" + } + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "pify": "^2.0.0" }, "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } } }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "nearley": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.18.0.tgz", - "integrity": "sha512-/zQOMCeJcioI0xJtd5RpBiWw2WP7wLe6vq8/3Yu0rEwgus/G/+pViX80oA87JdVgjRt2895mZSv2VfZmy4W1uw==", + "pbkdf2": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", + "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", "dev": true, "requires": { - "commander": "^2.19.0", - "moo": "^0.4.3", - "railroad-diagrams": "^1.0.0", - "randexp": "0.4.6", - "semver": "^5.4.1" + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" } }, - "negotiator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", "dev": true }, - "neo-async": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.0.tgz", - "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==", + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", "dev": true }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true }, - "no-case": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", - "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, "requires": { - "lower-case": "^1.1.1" + "pinkie": "^2.0.0" } }, - "node-fetch": { - "version": "1.6.3", - "resolved": "http://registry.npmjs.org/node-fetch/-/node-fetch-1.6.3.tgz", - "integrity": "sha1-3CNO3WSJmC1Y6PDbT2lQKavNjAQ=", + "pirates": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", + "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", + "dev": true, "requires": { - "encoding": "^0.1.11", - "is-stream": "^1.0.1" + "node-modules-regexp": "^1.0.0" } }, - "node-forge": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz", - "integrity": "sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ==", - "dev": true - }, - "node-gyp": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", - "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==", + "pkg-conf": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-3.1.0.tgz", + "integrity": "sha512-m0OTbR/5VPNPqO1ph6Fqbj7Hv6QU7gR/tQW40ZqrL1rjgCU85W6C1bJn0BItuJqnR98PWzw7Z8hHeChD1WrgdQ==", "dev": true, "requires": { - "fstream": "^1.0.0", - "glob": "^7.0.3", - "graceful-fs": "^4.1.2", - "mkdirp": "^0.5.0", - "nopt": "2 || 3", - "npmlog": "0 || 1 || 2 || 3 || 4", - "osenv": "0", - "request": "^2.87.0", - "rimraf": "2", - "semver": "~5.3.0", - "tar": "^2.0.0", - "which": "1" + "find-up": "^3.0.0", + "load-json-file": "^5.2.0" }, "dependencies": { - "nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "abbrev": "1" + "locate-path": "^3.0.0" } }, - "semver": { - "version": "5.3.0", - "resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz", - "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", - "dev": true - } - } - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", - "dev": true - }, - "node-libs-browser": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.1.0.tgz", - "integrity": "sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==", - "dev": true, - "requires": { - "assert": "^1.1.1", - "browserify-zlib": "^0.2.0", - "buffer": "^4.3.0", - "console-browserify": "^1.1.0", - "constants-browserify": "^1.0.0", - "crypto-browserify": "^3.11.0", - "domain-browser": "^1.1.1", - "events": "^1.0.0", - "https-browserify": "^1.0.0", - "os-browserify": "^0.3.0", - "path-browserify": "0.0.0", - "process": "^0.11.10", - "punycode": "^1.2.4", - "querystring-es3": "^0.2.0", - "readable-stream": "^2.3.3", - "stream-browserify": "^2.0.1", - "stream-http": "^2.7.2", - "string_decoder": "^1.0.0", - "timers-browserify": "^2.0.4", - "tty-browserify": "0.0.0", - "url": "^0.11.0", - "util": "^0.10.3", - "vm-browserify": "0.0.4" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", - "dev": true - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "p-limit": "^2.0.0" } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true } } }, - "node-modules-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", - "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", - "dev": true - }, - "node-notifier": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.0.tgz", - "integrity": "sha512-SUDEb+o71XR5lXSTyivXd9J7fCloE3SyP4lSgt3lU2oSANiox+SxlNRGPjDKrwU1YN3ix2KN/VGGCg0t01rttQ==", - "dev": true, - "requires": { - "growly": "^1.3.0", - "is-wsl": "^1.1.0", - "semver": "^5.5.0", - "shellwords": "^0.1.1", - "which": "^1.3.0" - } - }, - "node-releases": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.0.2.tgz", - "integrity": "sha512-zP8Asfg13lG9KDAW85rylSxXBYvaSdtjMIYKHUk8c1fM8drmFwRqbSYKYD+UlNVPUvrceSvgLUKHMOWR5jPWQg==", - "dev": true, - "requires": { - "semver": "^5.3.0" - } - }, - "node-sass": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.12.0.tgz", - "integrity": "sha512-A1Iv4oN+Iel6EPv77/HddXErL2a+gZ4uBeZUy+a8O35CFYTXhgA8MgLCWBtwpGZdCvTvQ9d+bQxX/QC36GDPpQ==", + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", "dev": true, "requires": { - "async-foreach": "^0.1.3", - "chalk": "^1.1.1", - "cross-spawn": "^3.0.0", - "gaze": "^1.0.0", - "get-stdin": "^4.0.1", - "glob": "^7.0.3", - "in-publish": "^2.0.0", - "lodash": "^4.17.11", - "meow": "^3.7.0", - "mkdirp": "^0.5.1", - "nan": "^2.13.2", - "node-gyp": "^3.8.0", - "npmlog": "^4.0.0", - "request": "^2.88.0", - "sass-graph": "^2.2.4", - "stdout-stream": "^1.4.0", - "true-case-path": "^1.0.2" + "find-up": "^2.1.0" }, "dependencies": { - "cross-spawn": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", - "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { - "lru-cache": "^4.0.1", - "which": "^1.2.9" + "p-limit": "^1.1.0" } }, - "nan": { - "version": "2.13.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz", - "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==", + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true } } }, - "nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", - "requires": { - "abbrev": "1" - } - }, - "normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "is-builtin-module": "^1.0.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "dev": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "dev": true, - "requires": { - "boolbase": "~1.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, - "nwsapi": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.0.9.tgz", - "integrity": "sha512-nlWFSCTYQcHk/6A9FFnfhKc14c3aFhfdNBXgo8Qgi9QTBu/qg3Ww+Uiz9wMzXd1T8GFxPc2QIHB6Qtf2XFryFQ==", - "dev": true - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", "dev": true, "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" + "find-up": "^3.0.0" }, "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "locate-path": "^3.0.0" } }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true } } }, - "object-inspect": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", - "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", + "pn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", + "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", "dev": true }, - "object-is": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.1.tgz", - "integrity": "sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY=", + "pnp-webpack-plugin": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz", + "integrity": "sha512-7Wjy+9E3WwLOEL30D+m8TSTF7qJJUJLONBnwQp0518siuMxUQUbgZwssaFX+QKlZkjHZcw/IpZCt/H0srrntSg==", + "dev": true, + "requires": { + "ts-pnp": "^1.1.6" + } + }, + "pofile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pofile/-/pofile-1.1.0.tgz", + "integrity": "sha512-6XYcNkXWGiJ2CVXogTP7uJ6ZXQCldYLZc16wgRp8tqRaBTTyIfF+TUT3EQJPXTLAT7OTPpTAoaFdoXKfaTRU1w==", "dev": true }, - "object-keys": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", - "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=" + "popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "portfinder": { + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", + "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", "dev": true, "requires": { - "isobject": "^3.0.0" + "async": "^2.6.2", + "debug": "^3.1.1", + "mkdirp": "^0.5.5" }, "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true } } }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "postcss": { + "version": "7.0.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", + "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" }, "dependencies": { - "object-keys": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", - "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, - "object.entries": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.0.4.tgz", - "integrity": "sha1-G/mk3SKI9bM/Opk9JXZh8F0WGl8=", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.6.1", - "function-bind": "^1.1.0", - "has": "^1.0.1" - } - }, - "object.fromentries": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.0.tgz", - "integrity": "sha512-9iLiI6H083uiqUuvzyY6qrlmc/Gz8hLQFOcb/Ri/0xXFkSNS3ctV+CbE6yM2+AnkYfOB3dGjdzC0wrMLIhQICA==", + "postcss-attribute-case-insensitive": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz", + "integrity": "sha512-clkFxk/9pcdb4Vkn0hAHq3YnxBQ2p0CGD1dy24jN+reBck+EWxMbxSUqN4Yj7t0w8csl87K6p0gxBe1utkJsYA==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.11.0", - "function-bind": "^1.1.1", - "has": "^1.0.1" + "postcss": "^7.0.2", + "postcss-selector-parser": "^6.0.2" } }, - "object.getownpropertydescriptors": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", - "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "postcss-browser-comments": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-browser-comments/-/postcss-browser-comments-3.0.0.tgz", + "integrity": "sha512-qfVjLfq7HFd2e0HW4s1dvU8X080OZdG46fFbIBFjW7US7YPDcWfRvdElvwMJr2LI6hMmD+7LnH2HcmXTs+uOig==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.5.1" + "postcss": "^7" } }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "postcss-calc": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.5.tgz", + "integrity": "sha512-1tKHutbGtLtEZF6PT4JSihCHfIVldU72mZ8SdZHIYriIZ9fh9k9aWSppaT8rHsyI3dX+KSR+W+Ix9BMY3AODrg==", "dev": true, "requires": { - "isobject": "^3.0.1" + "postcss": "^7.0.27", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.0.2" }, "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "postcss-value-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", "dev": true } } }, - "object.values": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz", - "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==", + "postcss-color-functional-notation": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz", + "integrity": "sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.12.0", - "function-bind": "^1.1.1", - "has": "^1.0.3" + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" } }, - "obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", - "dev": true - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "postcss-color-gray": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-gray/-/postcss-color-gray-5.0.0.tgz", + "integrity": "sha512-q6BuRnAGKM/ZRpfDascZlIZPjvwsRye7UDNalqVz3s7GDxMtqPY6+Q871liNxsonUw8oC61OG+PSaysYpl1bnw==", "dev": true, "requires": { - "ee-first": "1.1.1" + "@csstools/convert-colors": "^1.4.0", + "postcss": "^7.0.5", + "postcss-values-parser": "^2.0.0" } }, - "on-headers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", - "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=", - "dev": true + "postcss-color-hex-alpha": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.3.tgz", + "integrity": "sha512-PF4GDel8q3kkreVXKLAGNpHKilXsZ6xuu+mOQMHWHLPNyjiUBOr75sp5ZKJfmv1MCus5/DWUGcK9hm6qHEnXYw==", + "dev": true, + "requires": { + "postcss": "^7.0.14", + "postcss-values-parser": "^2.0.1" + } }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "postcss-color-mod-function": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/postcss-color-mod-function/-/postcss-color-mod-function-3.0.3.tgz", + "integrity": "sha512-YP4VG+xufxaVtzV6ZmhEtc+/aTXH3d0JLpnYfxqTvwZPbJhWqp8bSY3nfNzNRFLgB4XSaBA82OE4VjOOKpCdVQ==", "dev": true, "requires": { - "wrappy": "1" + "@csstools/convert-colors": "^1.4.0", + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" } }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "postcss-color-rebeccapurple": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz", + "integrity": "sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g==", "dev": true, "requires": { - "mimic-fn": "^1.0.0" + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" } }, - "opencollective": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/opencollective/-/opencollective-1.0.3.tgz", - "integrity": "sha1-ruY3K8KBRFg2kMPKja7PwSDdDvE=", + "postcss-colormin": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz", + "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==", "dev": true, "requires": { - "babel-polyfill": "6.23.0", - "chalk": "1.1.3", - "inquirer": "3.0.6", - "minimist": "1.2.0", - "node-fetch": "1.6.3", - "opn": "4.0.2" - }, - "dependencies": { - "ansi-escapes": { - "version": "1.4.0", - "resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", - "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", - "dev": true - }, - "chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", - "dev": true - }, - "external-editor": { - "version": "2.2.0", - "resolved": "http://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", - "dev": true, - "requires": { - "chardet": "^0.4.0", - "iconv-lite": "^0.4.17", - "tmp": "^0.0.33" - } - }, - "inquirer": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.0.6.tgz", - "integrity": "sha1-4EqqnQW3o8ubD0B9BDdfBEcZA0c=", - "dev": true, - "requires": { - "ansi-escapes": "^1.1.0", - "chalk": "^1.0.0", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^2.0.1", - "figures": "^2.0.0", - "lodash": "^4.3.0", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rx": "^4.1.0", - "string-width": "^2.0.0", - "strip-ansi": "^3.0.0", - "through": "^2.3.6" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "opn": { - "version": "4.0.2", - "resolved": "http://registry.npmjs.org/opn/-/opn-4.0.2.tgz", - "integrity": "sha1-erwi5kTf9jsKltWrfyeQwPAavJU=", - "dev": true, - "requires": { - "object-assign": "^4.0.1", - "pinkie-promise": "^2.0.0" - } - } + "browserslist": "^4.0.0", + "color": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" } }, - "opn": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.4.0.tgz", - "integrity": "sha512-YF9MNdVy/0qvJvDtunAOzFw9iasOQHpVthTCvGzxt61Il64AYSGdK+rYwld7NAfk9qJ7dt+hymBNSc9LNYS+Sw==", + "postcss-convert-values": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", + "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", "dev": true, "requires": { - "is-wsl": "^1.1.0" + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" } }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "postcss-custom-media": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-7.0.8.tgz", + "integrity": "sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg==", "dev": true, "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - }, - "dependencies": { - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true - } + "postcss": "^7.0.14" } }, - "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "postcss-custom-properties": { + "version": "8.0.11", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-8.0.11.tgz", + "integrity": "sha512-nm+o0eLdYqdnJ5abAJeXp4CEU1c1k+eB2yMCvhgzsds/e0umabFrN6HoTy/8Q4K5ilxERdl/JD1LO5ANoYBeMA==", + "dev": true, "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "wordwrap": "~1.0.0" + "postcss": "^7.0.17", + "postcss-values-parser": "^2.0.1" } }, - "ora": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-3.4.0.tgz", - "integrity": "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==", + "postcss-custom-selectors": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz", + "integrity": "sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w==", "dev": true, "requires": { - "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", - "cli-spinners": "^2.0.0", - "log-symbols": "^2.2.0", - "strip-ansi": "^5.2.0", - "wcwidth": "^1.0.1" + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0-rc.3" }, "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", "dev": true }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" } } } }, - "original": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", - "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", - "dev": true, - "requires": { - "url-parse": "^1.4.3" - } - }, - "os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", - "dev": true - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" - }, - "os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "postcss-dir-pseudo-class": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz", + "integrity": "sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw==", "dev": true, "requires": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0-rc.3" }, "dependencies": { - "invert-kv": { + "cssesc": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", "dev": true }, - "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", "dev": true, "requires": { - "invert-kv": "^2.0.0" + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" } } } }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + "postcss-discard-comments": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", + "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "postcss-discard-duplicates": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", + "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", "dev": true, "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" + "postcss": "^7.0.0" } }, - "p-defer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", - "dev": true + "postcss-discard-empty": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", + "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } }, - "p-each-series": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-1.0.0.tgz", - "integrity": "sha1-kw89Et0fUOdDRFeiLNbwSsatf3E=", + "postcss-discard-overridden": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", + "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", "dev": true, "requires": { - "p-reduce": "^1.0.0" + "postcss": "^7.0.0" } }, - "p-finally": { + "postcss-double-position-gradients": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-1.0.0.tgz", + "integrity": "sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA==", + "dev": true, + "requires": { + "postcss": "^7.0.5", + "postcss-values-parser": "^2.0.0" + } }, - "p-is-promise": { - "version": "1.1.0", - "resolved": "http://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", - "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=", - "dev": true + "postcss-env-function": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-2.0.2.tgz", + "integrity": "sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw==", + "dev": true, + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "postcss-flexbugs-fixes": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-4.1.0.tgz", + "integrity": "sha512-jr1LHxQvStNNAHlgco6PzY308zvLklh7SJVYuWUwyUQncofaAlD2l+P/gxKHOdqWKe7xJSkVLFF/2Tp+JqMSZA==", "dev": true, "requires": { - "p-try": "^1.0.0" + "postcss": "^7.0.0" } }, - "p-locate": { + "postcss-focus-visible": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz", + "integrity": "sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g==", + "dev": true, + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-focus-within": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz", + "integrity": "sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w==", + "dev": true, + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-font-variant": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-4.0.1.tgz", + "integrity": "sha512-I3ADQSTNtLTTd8uxZhtSOrTCQ9G4qUVKPjHiDk0bV75QSxXjVWiJVJ2VLdspGUi9fbW9BcjKJoRvxAH1pckqmA==", + "dev": true, + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-gap-properties": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz", + "integrity": "sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg==", "dev": true, "requires": { - "p-limit": "^1.1.0" + "postcss": "^7.0.2" } }, - "p-map": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", - "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", - "dev": true + "postcss-image-set-function": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz", + "integrity": "sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw==", + "dev": true, + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } }, - "p-reduce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz", - "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=", - "dev": true + "postcss-initial": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-3.0.2.tgz", + "integrity": "sha512-ugA2wKonC0xeNHgirR4D3VWHs2JcU08WAi1KFLVcnb7IN89phID6Qtg2RIctWbnvp1TM2BOmDtX8GGLCKdR8YA==", + "dev": true, + "requires": { + "lodash.template": "^4.5.0", + "postcss": "^7.0.2" + } }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true + "postcss-lab-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz", + "integrity": "sha512-whLy1IeZKY+3fYdqQFuDBf8Auw+qFuVnChWjmxm/UhHWqNHZx+B99EwxTvGYmUBqe3Fjxs4L1BoZTJmPu6usVg==", + "dev": true, + "requires": { + "@csstools/convert-colors": "^1.4.0", + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } }, - "pako": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", - "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==", - "dev": true + "postcss-load-config": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.2.tgz", + "integrity": "sha512-/rDeGV6vMUo3mwJZmeHfEDvwnTKKqQ0S7OHUi/kJvvtx3aWtyWG2/0ZWnzCt2keEclwN6Tf0DST2v9kITdOKYw==", + "dev": true, + "requires": { + "cosmiconfig": "^5.0.0", + "import-cwd": "^2.0.0" + } }, - "parallel-transform": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", - "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", + "postcss-loader": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz", + "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", "dev": true, "requires": { - "cyclist": "^1.0.1", - "inherits": "^2.0.3", - "readable-stream": "^2.1.5" + "loader-utils": "^1.1.0", + "postcss": "^7.0.0", + "postcss-load-config": "^2.0.0", + "schema-utils": "^1.0.0" }, "dependencies": { - "isarray": { + "schema-utils": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" } } } }, - "parse-asn1": { - "version": "5.1.1", - "resolved": "http://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", - "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", + "postcss-logical": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-3.0.0.tgz", + "integrity": "sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA==", "dev": true, "requires": { - "asn1.js": "^4.0.0", - "browserify-aes": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3" - } - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" + "postcss": "^7.0.2" } }, - "parse-node-version": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", - "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==" - }, - "parse5": { + "postcss-media-minmax": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", - "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", - "dev": true - }, - "parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", - "dev": true - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true - }, - "path-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", - "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", - "dev": true - }, - "path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" - }, - "path-to-regexp": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", - "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz", + "integrity": "sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw==", + "dev": true, "requires": { - "isarray": "0.0.1" + "postcss": "^7.0.2" } }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "postcss-merge-longhand": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", + "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" + "css-color-names": "0.0.4", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "stylehacks": "^4.0.0" } }, - "pbkdf2": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", - "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "postcss-merge-rules": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz", + "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==", "dev": true, "requires": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "cssnano-util-same-parent": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0", + "vendors": "^1.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "dev": true, + "requires": { + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } } }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, - "pify": { - "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true + "postcss-minify-font-values": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", + "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", + "dev": true, + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "postcss-minify-gradients": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz", + "integrity": "sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==", "dev": true, "requires": { - "pinkie": "^2.0.0" + "cssnano-util-get-arguments": "^4.0.0", + "is-color-stop": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" } }, - "pirates": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", - "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", + "postcss-minify-params": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz", + "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==", "dev": true, "requires": { - "node-modules-regexp": "^1.0.0" + "alphanum-sort": "^1.0.0", + "browserslist": "^4.0.0", + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "uniqs": "^2.0.0" } }, - "pkg-conf": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", - "integrity": "sha1-ISZRTKbyq/69FoWW3xi6V4Z/AFg=", + "postcss-minify-selectors": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz", + "integrity": "sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==", "dev": true, "requires": { - "find-up": "^2.0.0", - "load-json-file": "^4.0.0" + "alphanum-sort": "^1.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" }, "dependencies": { - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true } } }, - "pkg-dir": { + "postcss-modules-extract-imports": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", + "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", "dev": true, "requires": { - "find-up": "^2.1.0" + "postcss": "^7.0.5" } }, - "plugin-error": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", - "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", + "postcss-modules-local-by-default": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz", + "integrity": "sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw==", + "dev": true, "requires": { - "ansi-cyan": "^0.1.1", - "ansi-red": "^0.1.1", - "arr-diff": "^1.0.1", - "arr-union": "^2.0.1", - "extend-shallow": "^1.1.2" + "icss-utils": "^4.1.1", + "postcss": "^7.0.32", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", + "dev": true + } } }, - "pluralize": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", - "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", - "dev": true + "postcss-modules-scope": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz", + "integrity": "sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ==", + "dev": true, + "requires": { + "postcss": "^7.0.6", + "postcss-selector-parser": "^6.0.0" + } }, - "pn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", - "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==" + "postcss-modules-values": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz", + "integrity": "sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==", + "dev": true, + "requires": { + "icss-utils": "^4.0.0", + "postcss": "^7.0.6" + } }, - "pofile": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pofile/-/pofile-1.0.11.tgz", - "integrity": "sha512-Vy9eH1dRD9wHjYt/QqXcTz+RnX/zg53xK+KljFSX30PvdDMb2z+c6uDUeblUGqqJgz3QFsdlA0IJvHziPmWtQg==", - "dev": true + "postcss-nesting": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-7.0.1.tgz", + "integrity": "sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg==", + "dev": true, + "requires": { + "postcss": "^7.0.2" + } }, - "popper.js": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.0.tgz", - "integrity": "sha512-+G+EkOPoE5S/zChTpmBSSDYmhXJ5PsW8eMhH8cP/CQHMFPBG/kC9Y5IIw6qNYgdJ+/COf0ddY2li28iHaZRSjw==" + "postcss-normalize": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize/-/postcss-normalize-8.0.1.tgz", + "integrity": "sha512-rt9JMS/m9FHIRroDDBGSMsyW1c0fkvOJPy62ggxSHUldJO7B195TqFMqIf+lY5ezpDcYOV4j86aUp3/XbxzCCQ==", + "dev": true, + "requires": { + "@csstools/normalize.css": "^10.1.0", + "browserslist": "^4.6.2", + "postcss": "^7.0.17", + "postcss-browser-comments": "^3.0.0", + "sanitize.css": "^10.0.0" + } }, - "portfinder": { - "version": "1.0.20", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.20.tgz", - "integrity": "sha512-Yxe4mTyDzTd59PZJY4ojZR8F+E5e97iq2ZOHPz3HDgSvYC5siNad2tLooQ5y5QHyQhc3xVqvyk/eNA3wuoa7Sw==", + "postcss-normalize-charset": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", + "integrity": "sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==", "dev": true, "requires": { - "async": "^1.5.2", - "debug": "^2.2.0", - "mkdirp": "0.5.x" + "postcss": "^7.0.0" + } + }, + "postcss-normalize-display-values": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz", + "integrity": "sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==", + "dev": true, + "requires": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-normalize-positions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz", + "integrity": "sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA==", + "dev": true, + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-normalize-repeat-style": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz", + "integrity": "sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q==", + "dev": true, + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-normalize-string": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz", + "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==", + "dev": true, + "requires": { + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-normalize-timing-functions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz", + "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==", + "dev": true, + "requires": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-normalize-unicode": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", + "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-normalize-url": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", + "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==", + "dev": true, + "requires": { + "is-absolute-url": "^2.0.0", + "normalize-url": "^3.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" }, "dependencies": { - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "normalize-url": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", + "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==", "dev": true } } }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true + "postcss-normalize-whitespace": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz", + "integrity": "sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==", + "dev": true, + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "postcss-ordered-values": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz", + "integrity": "sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==", "dev": true, "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-overflow-shorthand": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-2.0.0.tgz", + "integrity": "sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g==", + "dev": true, + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-page-break": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-2.0.0.tgz", + "integrity": "sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ==", + "dev": true, + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-place": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-4.0.1.tgz", + "integrity": "sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg==", + "dev": true, + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-preset-env": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-6.7.0.tgz", + "integrity": "sha512-eU4/K5xzSFwUFJ8hTdTQzo2RBLbDVt83QZrAvI07TULOkmyQlnYlpwep+2yIK+K+0KlZO4BvFcleOCCcUtwchg==", + "dev": true, + "requires": { + "autoprefixer": "^9.6.1", + "browserslist": "^4.6.4", + "caniuse-lite": "^1.0.30000981", + "css-blank-pseudo": "^0.1.4", + "css-has-pseudo": "^0.10.0", + "css-prefers-color-scheme": "^3.1.1", + "cssdb": "^4.4.0", + "postcss": "^7.0.17", + "postcss-attribute-case-insensitive": "^4.0.1", + "postcss-color-functional-notation": "^2.0.1", + "postcss-color-gray": "^5.0.0", + "postcss-color-hex-alpha": "^5.0.3", + "postcss-color-mod-function": "^3.0.3", + "postcss-color-rebeccapurple": "^4.0.1", + "postcss-custom-media": "^7.0.8", + "postcss-custom-properties": "^8.0.11", + "postcss-custom-selectors": "^5.1.2", + "postcss-dir-pseudo-class": "^5.0.0", + "postcss-double-position-gradients": "^1.0.0", + "postcss-env-function": "^2.0.2", + "postcss-focus-visible": "^4.0.0", + "postcss-focus-within": "^3.0.0", + "postcss-font-variant": "^4.0.0", + "postcss-gap-properties": "^2.0.0", + "postcss-image-set-function": "^3.0.1", + "postcss-initial": "^3.0.0", + "postcss-lab-function": "^2.0.1", + "postcss-logical": "^3.0.0", + "postcss-media-minmax": "^4.0.0", + "postcss-nesting": "^7.0.0", + "postcss-overflow-shorthand": "^2.0.0", + "postcss-page-break": "^2.0.0", + "postcss-place": "^4.0.1", + "postcss-pseudo-class-any-link": "^6.0.0", + "postcss-replace-overflow-wrap": "^3.0.0", + "postcss-selector-matches": "^4.0.0", + "postcss-selector-not": "^4.0.0" + } + }, + "postcss-pseudo-class-any-link": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz", + "integrity": "sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew==", + "dev": true, + "requires": { + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0-rc.3" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", + "dev": true }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" } } } }, - "postcss-modules-extract-imports": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.1.tgz", - "integrity": "sha512-6jt9XZwUhwmRUhb/CkyJY020PYaPJsCyt3UjbaWo6XEbH/94Hmv6MP7fG2C5NDU/BcHzyGYxNtHvM+LTf9HrYw==", + "postcss-reduce-initial": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", + "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==", "dev": true, "requires": { - "postcss": "^6.0.1" + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0" } }, - "postcss-modules-local-by-default": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz", - "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=", + "postcss-reduce-transforms": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz", + "integrity": "sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg==", "dev": true, "requires": { - "css-selector-tokenizer": "^0.7.0", - "postcss": "^6.0.1" + "cssnano-util-get-match": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" } }, - "postcss-modules-scope": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz", - "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=", + "postcss-replace-overflow-wrap": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz", + "integrity": "sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw==", "dev": true, "requires": { - "css-selector-tokenizer": "^0.7.0", - "postcss": "^6.0.1" + "postcss": "^7.0.2" } }, - "postcss-modules-values": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz", - "integrity": "sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA=", + "postcss-safe-parser": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-4.0.1.tgz", + "integrity": "sha512-xZsFA3uX8MO3yAda03QrG3/Eg1LN3EPfjjf07vke/46HERLZyHrTsQ9E1r1w1W//fWEhtYNndo2hQplN2cVpCQ==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-selector-matches": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz", + "integrity": "sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "postcss": "^7.0.2" + } + }, + "postcss-selector-not": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-4.0.0.tgz", + "integrity": "sha512-W+bkBZRhqJaYN8XAnbbZPLWMvZD1wKTu0UxtFKdhtGjWYmxhkUneoeOhRJKdAE5V7ZTlnbHfCR+6bNwK9e1dTQ==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "postcss": "^7.0.2" + } + }, + "postcss-selector-parser": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz", + "integrity": "sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1", + "util-deprecate": "^1.0.2" + } + }, + "postcss-svgo": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.2.tgz", + "integrity": "sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw==", + "dev": true, + "requires": { + "is-svg": "^3.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "svgo": "^1.0.0" + } + }, + "postcss-unique-selectors": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz", + "integrity": "sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==", "dev": true, "requires": { - "icss-replace-symbols": "^1.1.0", - "postcss": "^6.0.1" + "alphanum-sort": "^1.0.0", + "postcss": "^7.0.0", + "uniqs": "^2.0.0" } }, "postcss-value-parser": { @@ -13431,72 +13788,96 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" }, + "postcss-values-parser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz", + "integrity": "sha512-2tLuBsA6P4rYTNKCXYG/71C7j1pU6pK503suYOmn4xYrQIzW+opD+7FAFNuGSdZC/3Qfy334QbeMu7MEb8gOxg==", + "dev": true, + "requires": { + "flatten": "^1.0.2", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "dev": true }, "prettier": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.18.2.tgz", - "integrity": "sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", + "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "dev": true + }, + "pretty-bytes": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.4.1.tgz", + "integrity": "sha512-s1Iam6Gwz3JI5Hweaz4GoCD1WUNUIyzePFy5+Js2hjwGVt2Z79wNN+ZKOZ2vB6C+Xs6njyB84Z1IthQg8d9LxA==", "dev": true }, + "pretty-error": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.2.tgz", + "integrity": "sha512-EY5oDzmsX5wvuynAByrmY0P0hcp+QpnAKbJng2A2MPjVKXCxrDSUkzghVJ4ZGPIv+JC4gX8fPUWscC0RtjsWGw==", + "dev": true, + "requires": { + "lodash": "^4.17.20", + "renderkid": "^2.0.4" + } + }, "pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", + "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", "dev": true, "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" + "@jest/types": "^24.9.0", + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0", + "react-is": "^16.8.4" }, "dependencies": { "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } } } }, - "private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==" - }, "process": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", - "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=", + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", "dev": true }, "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true }, "progress": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.1.tgz", - "integrity": "sha512-OE+a6vzqazc+K6LxJrX5UPyKFvGnL5CYmq2jFGNIBWHpc4QyE49/YOumcrpQFJpfejmvRtbJzgO1zPmMCqlbBg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, "promise": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.1.0.tgz", + "integrity": "sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q==", + "dev": true, "requires": { - "asap": "~2.0.3" + "asap": "~2.0.6" } }, "promise-inflight": { @@ -13506,22 +13887,23 @@ "dev": true }, "prompts": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.0.4.tgz", - "integrity": "sha512-HTzM3UWp/99A0gk51gAegwo1QRYA7xjcZufMNe33rCclFszUYAuHe1fIN/3ZmiHeGPkUsNaRyQm1hHOfM0PKxA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz", + "integrity": "sha512-awZAKrk3vN6CroQukBL+R9051a4R3zCZBlJm/HBfrSZ8iTpYix3VX1vU4mveiLpiwmOJT4wokTF9m6HUk4KqWQ==", "dev": true, "requires": { - "kleur": "^3.0.2", - "sisteransi": "^1.0.0" + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" } }, "prop-types": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.2.tgz", - "integrity": "sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==", + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", "requires": { - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" } }, "prop-types-exact": { @@ -13535,14 +13917,23 @@ "reflect.ownkeys": "^0.2.0" } }, + "prop-types-extra": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", + "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", + "requires": { + "react-is": "^16.3.2", + "warning": "^4.0.0" + } + }, "proxy-addr": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", - "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", "dev": true, "requires": { "forwarded": "~0.1.2", - "ipaddr.js": "1.8.0" + "ipaddr.js": "1.9.1" } }, "prr": { @@ -13552,23 +13943,19 @@ "dev": true }, "pseudolocale": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pseudolocale/-/pseudolocale-1.1.0.tgz", - "integrity": "sha512-OZ8I/hwYEJ3beN3IEcNnt8EpcqblH0/x23hulKBXjs+WhTTEle+ijCHCkh2bd+cIIeCuCwSCbBe93IthGG6hLw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pseudolocale/-/pseudolocale-1.2.0.tgz", + "integrity": "sha512-k0OQFvIlvpRdzR0dPVrrbWX7eE9EaZ6gpZtTlFSDi1Gf9tMy9wiANCNu7JZ0drcKgUri/39a2mBbH0goiQmrmQ==", "dev": true, "requires": { "commander": "*" } }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" - }, "psl": { - "version": "1.1.29", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", - "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true }, "public-encrypt": { "version": "4.0.3", @@ -13582,6 +13969,14 @@ "parse-asn1": "^5.0.0", "randombytes": "^2.0.1", "safe-buffer": "^5.1.2" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } } }, "pump": { @@ -13614,176 +14009,449 @@ "end-of-stream": "^1.1.0", "once": "^1.3.1" } - } - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true - }, - "querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", - "dev": true - }, - "querystringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.0.tgz", - "integrity": "sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg==", - "dev": true - }, - "raf": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", - "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", - "dev": true, - "requires": { - "performance-now": "^2.1.0" - } - }, - "railroad-diagrams": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", - "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=", - "dev": true - }, - "ramda": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.25.0.tgz", - "integrity": "sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ==", - "dev": true - }, - "randexp": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", - "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", - "dev": true, - "requires": { - "discontinuous-range": "1.0.0", - "ret": "~0.1.10" - } - }, - "randombytes": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz", - "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dev": true, - "requires": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" - } - }, - "range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", - "dev": true - }, - "raw-body": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", - "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", - "dev": true, - "requires": { - "bytes": "3.0.0", - "http-errors": "1.6.3", - "iconv-lite": "0.4.23", - "unpipe": "1.0.0" - }, - "dependencies": { - "iconv-lite": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + } + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "query-string": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", + "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", + "dev": true, + "requires": { + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + } + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "dev": true, + "requires": { + "performance-now": "^2.1.0" + } + }, + "railroad-diagrams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", + "integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=", + "dev": true + }, + "ramda": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz", + "integrity": "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==", + "dev": true + }, + "randexp": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", + "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", + "dev": true, + "requires": { + "discontinuous-range": "1.0.0", + "ret": "~0.1.10" + } + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "dev": true, + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "dependencies": { + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true + } + } + }, + "react": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", + "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" + } + }, + "react-app-polyfill": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-1.0.6.tgz", + "integrity": "sha512-OfBnObtnGgLGfweORmdZbyEz+3dgVePQBb3zipiaDsMHV1NpWm0rDFYIVXFV/AK+x4VIIfWHhrdMIeoTLyRr2g==", + "dev": true, + "requires": { + "core-js": "^3.5.0", + "object-assign": "^4.1.1", + "promise": "^8.0.3", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.3", + "whatwg-fetch": "^3.0.0" + }, + "dependencies": { + "core-js": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.8.1.tgz", + "integrity": "sha512-9Id2xHY1W7m8hCl8NkhQn5CufmF/WuR30BTRewvCXc1aZd3kMECwNZ69ndLbekKfakw9Rf2Xyc+QR6E7Gg+obg==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", + "dev": true + } + } + }, + "react-codemirror2": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/react-codemirror2/-/react-codemirror2-6.0.1.tgz", + "integrity": "sha512-rutEKVgvFhWcy/GeVA1hFbqrO89qLqgqdhUr7YhYgIzdyICdlRQv+ztuNvOFQMXrO0fLt0VkaYOdMdYdQgsSUA==" + }, + "react-dev-utils": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-10.2.1.tgz", + "integrity": "sha512-XxTbgJnYZmxuPtY3y/UV0D8/65NKkmaia4rXzViknVnZeVlklSh8u6TnaEYPfAi/Gh1TP4mEOXHI6jQOPbeakQ==", + "dev": true, + "requires": { + "@babel/code-frame": "7.8.3", + "address": "1.1.2", + "browserslist": "4.10.0", + "chalk": "2.4.2", + "cross-spawn": "7.0.1", + "detect-port-alt": "1.1.6", + "escape-string-regexp": "2.0.0", + "filesize": "6.0.1", + "find-up": "4.1.0", + "fork-ts-checker-webpack-plugin": "3.1.1", + "global-modules": "2.0.0", + "globby": "8.0.2", + "gzip-size": "5.1.1", + "immer": "1.10.0", + "inquirer": "7.0.4", + "is-root": "2.1.0", + "loader-utils": "1.2.3", + "open": "^7.0.2", + "pkg-up": "3.1.0", + "react-error-overlay": "^6.0.7", + "recursive-readdir": "2.2.2", + "shell-quote": "1.7.2", + "strip-ansi": "6.0.0", + "text-table": "0.2.0" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "ansi-escapes": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "dev": true, + "requires": { + "type-fest": "^0.11.0" + } + }, + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "browserslist": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.10.0.tgz", + "integrity": "sha512-TpfK0TDgv71dzuTsEAlQiHeWQ/tiPqgNZVdv046fvNtBZrjbv2O3TsWCDU0AWGJJKCF/KsjNdLzR9hXOsh/CfA==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001035", + "electron-to-chromium": "^1.3.378", + "node-releases": "^1.1.52", + "pkg-up": "^3.1.0" + } + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cross-spawn": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", + "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "dev": true + }, + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + }, + "dependencies": { + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + } + } + }, + "inquirer": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.4.tgz", + "integrity": "sha512-Bu5Td5+j11sCkqfqmUTiwv+tWisMtP0L7Q8WrqA2C/BbBhy1YTdFrvjjlrKq8oagA/tLQBski2Gcx/Sqyi2qSQ==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^2.4.2", + "cli-cursor": "^3.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.15", + "mute-stream": "0.0.8", + "run-async": "^2.2.0", + "rxjs": "^6.5.3", + "string-width": "^4.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + }, + "dependencies": { + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", + "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^2.0.0", + "json5": "^1.0.1" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "isexe": "^2.0.0" } } } }, - "react": { - "version": "16.10.2", - "resolved": "https://registry.npmjs.org/react/-/react-16.10.2.tgz", - "integrity": "sha512-MFVIq0DpIhrHFyqLU0S3+4dIcBhhOvBE8bJ/5kHPVOVaGdo0KuiQzpcjCPsf585WvhypqtrMILyoE2th6dT+Lw==", + "react-dom": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", + "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", - "prop-types": "^15.6.2" + "prop-types": "^15.6.2", + "scheduler": "^0.19.1" } }, - "react-codemirror2": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/react-codemirror2/-/react-codemirror2-6.0.0.tgz", - "integrity": "sha512-D7y9qZ05FbUh9blqECaJMdDwKluQiO3A9xB+fssd5jKM7YAXucRuEOlX32mJQumUvHUkHRHqXIPBjm6g0FW0Ag==" - }, - "react-dom": { - "version": "16.10.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.10.2.tgz", - "integrity": "sha512-kWGDcH3ItJK4+6Pl9DZB16BXYAZyrYQItU4OMy0jAkv5aNqc+mAKb4TpFtAteI6TJZu+9ZlNhaeNQSVQDHJzkw==", + "react-dropzone": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-9.0.0.tgz", + "integrity": "sha512-wZ2o9B2qkdE3RumWhfyZT9swgJYJPeU5qHEcMU8weYpmLex1eeWX0CC32/Y0VutB+BBi2D+iePV/YZIiB4kZGw==", "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", + "attr-accept": "^1.1.3", + "file-selector": "^0.1.8", "prop-types": "^15.6.2", - "scheduler": "^0.16.2" - }, - "dependencies": { - "scheduler": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-BqYVWqwz6s1wZMhjFvLfVR5WXP7ZY32M/wYPo04CcuPM7XZEbV2TBNW7Z0UkguPTl0dWMA59VbNXxK6q+pHItg==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - } + "prop-types-extra": "^1.1.0" } }, + "react-error-overlay": { + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.8.tgz", + "integrity": "sha512-HvPuUQnLp5H7TouGq3kzBeioJmXms1wHy9EGjz2OURWBp4qZO6AfGEcnxts1D/CbwPLRAgTMPCEgYhA3sEM4vw==", + "dev": true + }, "react-fast-compare": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" }, - "react-hot-loader": { - "version": "4.3.11", - "resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.3.11.tgz", - "integrity": "sha512-T0G5jURyTsFLoiW6MTr5Q35UHC/B2pmYJ7+VBjk8yMDCEABRmCGy4g6QwxoB4pWg4/xYvVTa/Pbqnsgx/+NLuA==", - "dev": true, - "requires": { - "fast-levenshtein": "^2.0.6", - "global": "^4.3.0", - "hoist-non-react-statics": "^2.5.0", - "prop-types": "^15.6.1", - "react-lifecycles-compat": "^3.0.4", - "shallowequal": "^1.0.2" - } - }, "react-is": { - "version": "16.6.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.6.0.tgz", - "integrity": "sha512-q8U7k0Fi7oxF1HvQgyBjPwDXeMplEsArnKt2iYhuIF86+GBbgLHdAmokL3XUFjTd7Q363OSNG55FOGUdONVn1g==" + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "react-lifecycles-compat": { "version": "3.0.4", @@ -13791,478 +14459,444 @@ "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, "react-router": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.1.2.tgz", - "integrity": "sha512-yjEuMFy1ONK246B+rsa0cUam5OeAQ8pyclRDgpxuSCrAlJ1qN9uZ5IgyKC7gQg0w8OM50NXHEegPh/ks9YuR2A==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", + "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==", "requires": { "@babel/runtime": "^7.1.2", "history": "^4.9.0", "hoist-non-react-statics": "^3.1.0", "loose-envify": "^1.3.1", - "mini-create-react-context": "^0.3.0", + "mini-create-react-context": "^0.4.0", "path-to-regexp": "^1.7.0", "prop-types": "^15.6.2", "react-is": "^16.6.0", "tiny-invariant": "^1.0.2", "tiny-warning": "^1.0.0" - }, - "dependencies": { - "hoist-non-react-statics": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz", - "integrity": "sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==", - "requires": { - "react-is": "^16.7.0" - }, - "dependencies": { - "react-is": { - "version": "16.10.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.10.2.tgz", - "integrity": "sha512-INBT1QEgtcCCgvccr5/86CfD71fw9EPmDxgiJX4I2Ddr6ZsV6iFXsuby+qWJPtmNuMY0zByTsG4468P7nHuNWA==" - } - } - } } }, "react-router-dom": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.1.2.tgz", - "integrity": "sha512-7BPHAaIwWpZS074UKaw1FjVdZBSVWEk8IuDXdB+OkLb8vd/WRQIpA4ag9WQk61aEfQs47wHyjWUoUGGZxpQXew==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", + "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==", "requires": { "@babel/runtime": "^7.1.2", "history": "^4.9.0", "loose-envify": "^1.3.1", "prop-types": "^15.6.2", - "react-router": "5.1.2", + "react-router": "5.2.0", "tiny-invariant": "^1.0.2", "tiny-warning": "^1.0.0" } }, - "react-test-renderer": { - "version": "16.9.0", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.9.0.tgz", - "integrity": "sha512-R62stB73qZyhrJo7wmCW9jgl/07ai+YzvouvCXIJLBkRlRqLx4j9RqcLEAfNfU3OxTGucqR2Whmn3/Aad6L3hQ==", - "dev": true, - "requires": { - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "react-is": "^16.9.0", - "scheduler": "^0.15.0" - }, - "dependencies": { - "react-is": { - "version": "16.9.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.9.0.tgz", - "integrity": "sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw==", - "dev": true - } - } - }, - "react-virtualized": { - "version": "9.21.1", - "resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.21.1.tgz", - "integrity": "sha512-E53vFjRRMCyUTEKuDLuGH1ld/9TFzjf/fFW816PE4HFXWZorESbSTYtiZz1oAjra0MminaUU1EnvUxoGuEFFPA==", - "requires": { - "babel-runtime": "^6.26.0", - "clsx": "^1.0.1", - "dom-helpers": "^2.4.0 || ^3.0.0", - "linear-layout-vector": "0.0.1", - "loose-envify": "^1.3.0", - "prop-types": "^15.6.0", - "react-lifecycles-compat": "^3.0.4" - } - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "dev": true, - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "dev": true, - "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" + "react-scripts": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-3.4.4.tgz", + "integrity": "sha512-7J7GZyF/QvZkKAZLneiOIhHozvOMHey7hO9cdO9u68jjhGZlI8hDdOm6UyuHofn6Ajc9Uji5I6Psm/nKNuWdyw==", + "dev": true, + "requires": { + "@babel/core": "7.9.0", + "@svgr/webpack": "4.3.3", + "@typescript-eslint/eslint-plugin": "^2.10.0", + "@typescript-eslint/parser": "^2.10.0", + "babel-eslint": "10.1.0", + "babel-jest": "^24.9.0", + "babel-loader": "8.1.0", + "babel-plugin-named-asset-import": "^0.3.6", + "babel-preset-react-app": "^9.1.2", + "camelcase": "^5.3.1", + "case-sensitive-paths-webpack-plugin": "2.3.0", + "css-loader": "3.4.2", + "dotenv": "8.2.0", + "dotenv-expand": "5.1.0", + "eslint": "^6.6.0", + "eslint-config-react-app": "^5.2.1", + "eslint-loader": "3.0.3", + "eslint-plugin-flowtype": "4.6.0", + "eslint-plugin-import": "2.20.1", + "eslint-plugin-jsx-a11y": "6.2.3", + "eslint-plugin-react": "7.19.0", + "eslint-plugin-react-hooks": "^1.6.1", + "file-loader": "4.3.0", + "fs-extra": "^8.1.0", + "fsevents": "2.1.2", + "html-webpack-plugin": "4.0.0-beta.11", + "identity-obj-proxy": "3.0.0", + "jest": "24.9.0", + "jest-environment-jsdom-fourteen": "1.0.1", + "jest-resolve": "24.9.0", + "jest-watch-typeahead": "0.4.2", + "mini-css-extract-plugin": "0.9.0", + "optimize-css-assets-webpack-plugin": "5.0.3", + "pnp-webpack-plugin": "1.6.4", + "postcss-flexbugs-fixes": "4.1.0", + "postcss-loader": "3.0.0", + "postcss-normalize": "8.0.1", + "postcss-preset-env": "6.7.0", + "postcss-safe-parser": "4.0.1", + "react-app-polyfill": "^1.0.6", + "react-dev-utils": "^10.2.1", + "resolve": "1.15.0", + "resolve-url-loader": "3.1.2", + "sass-loader": "8.0.2", + "semver": "6.3.0", + "style-loader": "0.23.1", + "terser-webpack-plugin": "2.3.8", + "ts-pnp": "1.1.6", + "url-loader": "2.3.0", + "webpack": "4.42.0", + "webpack-dev-server": "3.11.0", + "webpack-manifest-plugin": "2.2.0", + "workbox-webpack-plugin": "4.3.1" }, "dependencies": { - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "requires": { - "pinkie-promise": "^2.0.0" + "@babel/core": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.0.tgz", + "integrity": "sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.9.0", + "@babel/helper-module-transforms": "^7.9.0", + "@babel/helpers": "^7.9.0", + "@babel/parser": "^7.9.0", + "@babel/template": "^7.8.6", + "@babel/traverse": "^7.9.0", + "@babel/types": "^7.9.0", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.13", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } } - } - } - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "aria-query": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", + "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=", "dev": true, "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "ast-types-flow": "0.0.7", + "commander": "^2.11.0" } }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } + "ms": "2.1.2" } }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", "dev": true, "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } + "esutils": "^2.0.2", + "isarray": "^1.0.0" } }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "eslint-plugin-import": { + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.20.1.tgz", + "integrity": "sha512-qQHgFOTjguR+LnYRoToeZWT62XM55MBVXObHM6SKFd1VzDcX/vqT1kAz8ssqigh5eMj8qXcRoXXGZpPP6RfdCw==", "dev": true, "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "array-includes": "^3.0.3", + "array.prototype.flat": "^1.2.1", + "contains-path": "^0.1.0", + "debug": "^2.6.9", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.2", + "eslint-module-utils": "^2.4.1", + "has": "^1.0.3", + "minimatch": "^3.0.4", + "object.values": "^1.1.0", + "read-pkg-up": "^2.0.0", + "resolve": "^1.12.0" }, "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "ms": "2.0.0" } }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true } } }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "eslint-plugin-jsx-a11y": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.3.tgz", + "integrity": "sha512-CawzfGt9w83tyuVekn0GDPU9ytYtxyxyFZ3aSWROmnRRFQFT2BiPJd7jvRdzNDi6oLWaS2asMeYSNMjWTV4eNg==", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" + "@babel/runtime": "^7.4.5", + "aria-query": "^3.0.0", + "array-includes": "^3.0.3", + "ast-types-flow": "^0.0.7", + "axobject-query": "^2.0.2", + "damerau-levenshtein": "^1.0.4", + "emoji-regex": "^7.0.2", + "has": "^1.0.3", + "jsx-ast-utils": "^2.2.1" + } + }, + "eslint-plugin-react": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.19.0.tgz", + "integrity": "sha512-SPT8j72CGuAP+JFbT0sJHOB80TX/pu44gQ4vXH/cq+hQTiY2PuZ6IHkqXJV6x1b28GDdo1lbInjKUrrdUf0LOQ==", + "dev": true, + "requires": { + "array-includes": "^3.1.1", + "doctrine": "^2.1.0", + "has": "^1.0.3", + "jsx-ast-utils": "^2.2.3", + "object.entries": "^1.1.1", + "object.fromentries": "^2.0.2", + "object.values": "^1.1.1", + "prop-types": "^15.7.2", + "resolve": "^1.15.1", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.2", + "xregexp": "^4.3.0" }, "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "esutils": "^2.0.2" + } + }, + "resolve": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", + "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", + "dev": true, + "requires": { + "is-core-module": "^2.1.0", + "path-parse": "^1.0.6" } } } }, - "is-accessor-descriptor": { + "eslint-plugin-react-hooks": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.7.0.tgz", + "integrity": "sha512-iXTCFcOmlWvw4+TOE8CLWj6yX1GwzT0Y6cUfHHZqWnSk144VmVIRcVGtUAzrLES7C798lmvnt02C7rxaOX1HNA==", + "dev": true + }, + "isarray": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "jsx-ast-utils": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz", + "integrity": "sha512-z1xSldJ6imESSzOjd3NNkieVJKRlKYSOtMG8SFyCj2FIrvSaSuli/WjpBkEzCBoR9bYYYFgqJw61Xhu7Lcgk+w==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "array-includes": "^3.1.1", + "object.assign": "^4.1.0" } }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "resolve": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.0.tgz", + "integrity": "sha512-+hTmAldEGE80U2wJJDC1lebb5jWqvTYAfm3YZ1ckk1gBr0MnCqUKlwK1e+anaFljIl+F5tR5IoZcm4ZDA1zMQw==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "path-parse": "^1.0.6" } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + } + } + }, + "react-test-renderer": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.14.0.tgz", + "integrity": "sha512-L8yPjqPE5CZO6rKsKXRO/rVPiaCOy0tQQJbC+UjPNlobl5mad59lvPjwFsQHTvL03caVDIVr9x9/OSgDe6I5Eg==", + "dev": true, + "requires": { + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "react-is": "^16.8.6", + "scheduler": "^0.19.1" + } + }, + "react-virtualized": { + "version": "9.22.2", + "resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.22.2.tgz", + "integrity": "sha512-5j4h4FhxTdOpBKtePSs1yk6LDNT4oGtUwjT7Nkh61Z8vv3fTG/XeOf8J4li1AYaexOwTXnw0HFVxsV0GBUqwRw==", + "requires": { + "@babel/runtime": "^7.7.2", + "clsx": "^1.0.4", + "dom-helpers": "^5.1.3", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-lifecycles-compat": "^3.0.4" + } + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + }, + "dependencies": { + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" } }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "error-ex": "^1.2.0" } }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true + } + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" } }, - "readable-stream": { - "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "p-try": "^1.0.0" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "p-limit": "^1.1.0" } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true } } }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, "realpath-native": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", @@ -14272,14 +14906,13 @@ "util.promisify": "^1.0.0" } }, - "redent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "recursive-readdir": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz", + "integrity": "sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg==", "dev": true, "requires": { - "indent-string": "^2.1.0", - "strip-indent": "^1.0.1" + "minimatch": "3.0.4" } }, "reflect.ownkeys": { @@ -14289,15 +14922,15 @@ "dev": true }, "regenerate": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", - "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", "dev": true }, "regenerate-unicode-properties": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-7.0.0.tgz", - "integrity": "sha512-s5NGghCE4itSlUS+0WUj88G6cfMVMmH8boTPNvABf8od+2dhT9WDlWu8n01raQAJZMOK8Ch6jSexaRO7swd6aw==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", + "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", "dev": true, "requires": { "regenerate": "^1.4.0" @@ -14309,12 +14942,12 @@ "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" }, "regenerator-transform": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.13.3.tgz", - "integrity": "sha512-5ipTrZFSq5vU2YoGoww4uaRVAK4wyYC4TSICibbfEPOruUu8FFP7ErV0BjmbIOEpn3O/k9na9UEdYR/3m7N6uA==", + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", + "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", "dev": true, "requires": { - "private": "^0.1.6" + "@babel/runtime": "^7.8.4" } }, "regex-not": { @@ -14325,25 +14958,41 @@ "requires": { "extend-shallow": "^3.0.2", "safe-regex": "^1.1.0" + } + }, + "regex-parser": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", + "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==", + "dev": true + }, + "regexp.prototype.flags": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", + "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" }, "dependencies": { - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", "dev": true, "requires": { - "is-plain-object": "^2.0.4" + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" } } } @@ -14355,29 +15004,29 @@ "dev": true }, "regexpu-core": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.2.0.tgz", - "integrity": "sha512-Z835VSnJJ46CNBttalHD/dB+Sj2ezmY6Xp38npwU87peK6mqOzOpV8eYktdkLTEkzzD+JsTcxd84ozd8I14+rw==", + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.1.tgz", + "integrity": "sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==", "dev": true, "requires": { "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^7.0.0", - "regjsgen": "^0.4.0", - "regjsparser": "^0.3.0", + "regenerate-unicode-properties": "^8.2.0", + "regjsgen": "^0.5.1", + "regjsparser": "^0.6.4", "unicode-match-property-ecmascript": "^1.0.4", - "unicode-match-property-value-ecmascript": "^1.0.2" + "unicode-match-property-value-ecmascript": "^1.2.0" } }, "regjsgen": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.4.0.tgz", - "integrity": "sha512-X51Lte1gCYUdlwhF28+2YMO0U6WeN0GLpgpA7LK7mbdDnkQYiwvEpmpe0F/cv5L14EbxgrdayAG3JETBv0dbXA==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", + "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==", "dev": true }, "regjsparser": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.3.0.tgz", - "integrity": "sha512-zza72oZBBHzt64G7DxdqrOo/30bhHkwMUoT0WqfGu98XLd7N+1tsy5MJ96Bk4MD0y74n629RhmrGW6XlnLLwCA==", + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", + "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", "dev": true, "requires": { "jsesc": "~0.5.0" @@ -14391,13 +15040,11 @@ } } }, - "relative": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/relative/-/relative-3.0.2.tgz", - "integrity": "sha1-Dc2OxUpdNaPBXhBFA9ZTdbWlNn8=", - "requires": { - "isobject": "^2.0.0" - } + "relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "dev": true }, "remove-trailing-separator": { "version": "1.1.0", @@ -14405,6 +15052,36 @@ "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", "dev": true }, + "renderkid": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.4.tgz", + "integrity": "sha512-K2eXrSOJdq+HuKzlcjOlGoOarUu5SDguDEhE7+Ah4zuOWL40j8A/oHvLlLob9PSTNvVnBd+/q0Er1QfpEuem5g==", + "dev": true, + "requires": { + "css-select": "^1.1.0", + "dom-converter": "^0.2", + "htmlparser2": "^3.3.0", + "lodash": "^4.17.20", + "strip-ansi": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, "repeat-element": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", @@ -14421,14 +15098,16 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, "requires": { "is-finite": "^1.0.0" } }, "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "dev": true, "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -14437,7 +15116,7 @@ "extend": "~3.0.2", "forever-agent": "~0.6.1", "form-data": "~2.3.2", - "har-validator": "~5.1.0", + "har-validator": "~5.1.3", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", @@ -14447,29 +15126,29 @@ "performance-now": "^2.1.0", "qs": "~6.5.2", "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", + "tough-cookie": "~2.5.0", "tunnel-agent": "^0.6.0", "uuid": "^3.3.2" } }, "request-promise-core": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", - "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", + "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", "dev": true, "requires": { - "lodash": "^4.13.1" + "lodash": "^4.17.19" } }, "request-promise-native": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.5.tgz", - "integrity": "sha1-UoF3D2jgyXGeUWP9P6tIIhX0/aU=", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", + "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", "dev": true, "requires": { - "request-promise-core": "1.1.1", - "stealthy-require": "^1.1.0", - "tough-cookie": ">=2.3.3" + "request-promise-core": "1.1.4", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" } }, "require-directory": { @@ -14479,29 +15158,11 @@ "dev": true }, "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true }, - "require-uncached": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", - "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", - "dev": true, - "requires": { - "caller-path": "^0.1.0", - "resolve-from": "^1.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", - "dev": true - } - } - }, "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -14509,11 +15170,13 @@ "dev": true }, "resolve": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", - "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", + "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", + "dev": true, "requires": { - "path-parse": "^1.0.5" + "is-core-module": "^2.1.0", + "path-parse": "^1.0.6" } }, "resolve-cwd": { @@ -14534,19 +15197,93 @@ } }, "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true }, "resolve-pathname": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz", - "integrity": "sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "resolve-url-loader": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-3.1.2.tgz", + "integrity": "sha512-QEb4A76c8Mi7I3xNKXlRKQSlLBwjUV/ULFMP+G7n3/7tJZ8MG5wsZ3ucxP1Jz8Vevn6fnJsxDx9cIls+utGzPQ==", + "dev": true, + "requires": { + "adjust-sourcemap-loader": "3.0.0", + "camelcase": "5.3.1", + "compose-function": "3.0.3", + "convert-source-map": "1.7.0", + "es6-iterator": "2.0.3", + "loader-utils": "1.2.3", + "postcss": "7.0.21", + "rework": "1.0.1", + "rework-visit": "1.0.0", + "source-map": "0.6.1" + }, + "dependencies": { + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "dev": true + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", + "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^2.0.0", + "json5": "^1.0.1" + } + }, + "postcss": { + "version": "7.0.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.21.tgz", + "integrity": "sha512-uIFtJElxJo29QC753JzhidoAhvp/e/Exezkdhfmt8AymWT6/5B7W1WmponYWkHk2eg6sONyTch0A3nkMPun3SQ==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } }, "restore-cursor": { "version": "2.0.0", @@ -14564,13 +15301,55 @@ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "dev": true + }, + "rework": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rework/-/rework-1.0.1.tgz", + "integrity": "sha1-MIBqhBNCtUUQqkEQhQzUhTQUSqc=", + "dev": true, + "requires": { + "convert-source-map": "^0.3.3", + "css": "^2.0.0" + }, + "dependencies": { + "convert-source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-0.3.5.tgz", + "integrity": "sha1-8dgClQr33SYxof6+BZZVDIarMZA=", + "dev": true + } + } + }, + "rework-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rework-visit/-/rework-visit-1.0.0.tgz", + "integrity": "sha1-mUWygD8hni96ygCtuLyfZA+ELJo=", + "dev": true + }, + "rgb-regex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", + "integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=", + "dev": true + }, + "rgba-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", + "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=", + "dev": true + }, "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "dev": true, "requires": { - "glob": "^7.0.5" + "glob": "^7.1.3" } }, "ripemd160": { @@ -14583,6 +15362,15 @@ "inherits": "^2.0.1" } }, + "rrule": { + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/rrule/-/rrule-2.6.6.tgz", + "integrity": "sha512-h6tb/hRo9SNv8xKjcvsEfdmhXvElMXsU3Yz0KmqMehUqxP6a4Qjmth2EuL1FsjdawADjajLS0eBbWfsZzn3SIw==", + "requires": { + "luxon": "^1.21.3", + "tslib": "^1.10.0" + } + }, "rst-selector-parser": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", @@ -14594,19 +15382,16 @@ } }, "rsvp": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.4.tgz", - "integrity": "sha512-6FomvYPfs+Jy9TfXmBpBuMWNH94SgCsZmJKcanySzgNNP6LjWxBvyLTa9KaMfDDM5oxRfrKDB0r/qeRsLwnBfA==", + "version": "4.8.5", + "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", + "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", "dev": true }, "run-async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", - "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", - "dev": true, - "requires": { - "is-promise": "^2.1.0" - } + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true }, "run-queue": { "version": "1.0.3", @@ -14622,16 +15407,10 @@ "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" }, - "rx": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", - "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=", - "dev": true - }, "rxjs": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", - "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz", + "integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==", "dev": true, "requires": { "tslib": "^1.9.0" @@ -14640,11 +15419,12 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true }, "safe-regex": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, "requires": { @@ -14673,129 +15453,167 @@ "walker": "~1.0.5" }, "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } - } - }, - "sass-graph": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz", - "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=", - "dev": true, - "requires": { - "glob": "^7.0.0", - "lodash": "^4.0.0", - "scss-tokenizer": "^0.2.3", - "yargs": "^7.0.0" - }, - "dependencies": { - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", - "dev": true - }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, - "os-locale": { - "version": "1.4.0", - "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "lcid": "^1.0.0" + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" } }, - "which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", - "dev": true - }, - "yargs": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", - "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", - "dev": true, - "requires": { - "camelcase": "^3.0.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^1.4.0", - "read-pkg-up": "^1.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^1.0.2", - "which-module": "^1.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^5.0.0" - } - }, - "yargs-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", - "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "dev": true, "requires": { - "camelcase": "^3.0.0" + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" } } } }, + "sanitize.css": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-10.0.0.tgz", + "integrity": "sha512-vTxrZz4dX5W86M6oVWVdOVe72ZiPs41Oi7Z6Km4W5Turyz28mrXSJhhEBZoRtzJWIv3833WKVwLSDWWkEfupMg==", + "dev": true + }, "sass-loader": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.1.0.tgz", - "integrity": "sha512-+G+BKGglmZM2GUSfT9TLuEp6tzehHPjAMoRRItOojWIqIGPloVCMhNIQuG639eJ+y033PaGTSjLaTHts8Kw79w==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-8.0.2.tgz", + "integrity": "sha512-7o4dbSK8/Ol2KflEmSco4jTjQoV988bM82P9CZdmo9hR3RLnvNc0ufMNdMrB0caq38JQ/FgF4/7RcbcfKzxoFQ==", "dev": true, "requires": { - "clone-deep": "^2.0.1", - "loader-utils": "^1.0.1", - "lodash.tail": "^4.1.1", - "neo-async": "^2.5.0", - "pify": "^3.0.0", - "semver": "^5.5.0" + "clone-deep": "^4.0.1", + "loader-utils": "^1.2.3", + "neo-async": "^2.6.1", + "schema-utils": "^2.6.1", + "semver": "^6.3.0" }, "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true + }, + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + } } } }, @@ -14809,76 +15627,29 @@ "version": "3.1.11", "resolved": "https://registry.npmjs.org/saxes/-/saxes-3.1.11.tgz", "integrity": "sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g==", + "dev": true, "requires": { "xmlchars": "^2.1.1" } }, "scheduler": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.15.0.tgz", - "integrity": "sha512-xAefmSfN6jqAa7Kuq7LIJY0bwAPG3xlCj0HMEBQk1lxYiDKZscY2xJ5U/61ZTrYbmNQbXa+gc7czPkVo11tnCg==", - "dev": true, + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", + "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" } }, "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dev": true, - "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - }, - "dependencies": { - "ajv": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.4.tgz", - "integrity": "sha512-4Wyjt8+t6YszqaXnLDfMmG/8AlO5Zbcsy3ATHncCzjW/NoPzAId8AK6749Ybjmdt+kUY1gP60fCu46oDxPv/mg==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - } - } - }, - "scss-tokenizer": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz", - "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", "dev": true, "requires": { - "js-base64": "^2.1.8", - "source-map": "^0.4.2" - }, - "dependencies": { - "source-map": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", - "dev": true, - "requires": { - "amdefine": ">=0.0.4" - } - } + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" } }, "select-hose": { @@ -14888,23 +15659,24 @@ "dev": true }, "selfsigned": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.4.tgz", - "integrity": "sha512-9AukTiDmHXGXWtWjembZ5NDmVvP2695EtpgbCsxCa68w3c88B+alqbmZ4O3hZ4VWGXeGWzEVdvqgAJD8DQPCDw==", + "version": "1.10.8", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz", + "integrity": "sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==", "dev": true, "requires": { - "node-forge": "0.7.5" + "node-forge": "^0.10.0" } }, "semver": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true }, "send": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", - "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", "dev": true, "requires": { "debug": "2.6.9", @@ -14914,19 +15686,53 @@ "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "~1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", "on-finished": "~2.3.0", - "range-parser": "~1.2.0", - "statuses": "~1.4.0" + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } } }, "serialize-javascript": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", - "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==", - "dev": true + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } }, "serve-index": { "version": "1.9.1", @@ -14941,18 +15747,53 @@ "http-errors": "~1.6.2", "mime-types": "~2.1.17", "parseurl": "~1.3.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + } } }, "serve-static": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", - "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", "dev": true, "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "parseurl": "~1.3.2", - "send": "0.16.2" + "parseurl": "~1.3.3", + "send": "0.17.1" } }, "set-blocking": { @@ -14987,17 +15828,18 @@ "setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true }, "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", "dev": true }, "sha.js": { "version": "2.4.11", - "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, "requires": { @@ -15006,34 +15848,45 @@ } }, "shallow-clone": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-1.0.0.tgz", - "integrity": "sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==", + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", + "integrity": "sha1-WQnodLp3EG1zrEFM/sH/yofZcGA=", "dev": true, "requires": { "is-extendable": "^0.1.1", - "kind-of": "^5.0.0", + "kind-of": "^2.0.1", + "lazy-cache": "^0.2.3", "mixin-object": "^2.0.1" }, "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", + "integrity": "sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU=", + "dev": true, + "requires": { + "is-buffer": "^1.0.2" + } + }, + "lazy-cache": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", + "integrity": "sha1-f+3fLctu23fRHvHRF6tf/fCrG2U=", "dev": true } } }, - "shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", - "dev": true - }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, "requires": { "shebang-regex": "^1.0.0" } @@ -15041,7 +15894,14 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "shell-quote": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", + "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==", + "dev": true }, "shellwords": { "version": "0.1.1", @@ -15049,30 +15909,68 @@ "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", "dev": true }, + "side-channel": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.3.tgz", + "integrity": "sha512-A6+ByhlLkksFoUepsGxfj5x1gTSrs+OydsRptUxeNCabQpCFUvcwIczgOigI8vhY/OJCnPnyE9rGiwgvr9cS1g==", + "dev": true, + "requires": { + "es-abstract": "^1.18.0-next.0", + "object-inspect": "^1.8.0" + } + }, "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "dev": true, + "requires": { + "is-arrayish": "^0.3.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "dev": true + } + } + }, "sisteransi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.0.tgz", - "integrity": "sha512-N+z4pHB4AmUv0SjveWRd6q1Nj5w62m5jodv+GD8lvmbY/83T/rpbJGZOnK5T149OldDj4Db07BSv9xY4K6NTPQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "dev": true }, "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true }, "slice-ansi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", - "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", "dev": true, "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", "is-fullwidth-code-point": "^2.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + } } }, "snapdragon": { @@ -15091,6 +15989,15 @@ "use": "^3.1.0" }, "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", @@ -15108,12 +16015,6 @@ "requires": { "is-extendable": "^0.1.0" } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true } } }, @@ -15166,16 +16067,10 @@ "kind-of": "^6.0.2" } }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true } } @@ -15187,33 +16082,23 @@ "dev": true, "requires": { "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } } }, "sockjs": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", - "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.20.tgz", + "integrity": "sha512-SpmVOVpdq0DJc0qArhF3E5xsxvaiqGNb73XfgBpK1y3UD5gs8DSo8aCTsuT5pX8rssdc2NDIzANwP9eCAiSdTA==", "dev": true, "requires": { "faye-websocket": "^0.10.0", - "uuid": "^3.0.1" + "uuid": "^3.4.0", + "websocket-driver": "0.6.5" } }, "sockjs-client": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.3.0.tgz", - "integrity": "sha512-R9jxEzhnnrdxLCNln0xg5uGHqMnkhPSTzUZH2eXcR03S/On9Yvoq2wyUZILRUhZCNVu2PmwWVoyuiPz8th8zbg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.4.0.tgz", + "integrity": "sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g==", "dev": true, "requires": { "debug": "^3.2.5", @@ -15225,31 +16110,40 @@ }, "dependencies": { "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "requires": { "ms": "^2.1.1" } }, "faye-websocket": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.1.tgz", - "integrity": "sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg=", + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", + "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", "dev": true, "requires": { "websocket-driver": ">=0.5.1" } }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true } } }, + "sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", + "dev": true, + "requires": { + "is-plain-obj": "^1.0.0" + } + }, "source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -15257,16 +16151,17 @@ "dev": true }, "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" }, "source-map-resolve": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", - "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "dev": true, "requires": { - "atob": "^2.1.1", + "atob": "^2.1.2", "decode-uri-component": "^0.2.0", "resolve-url": "^0.2.1", "source-map-url": "^0.4.0", @@ -15274,29 +16169,33 @@ } }, "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, "requires": { - "source-map": "^0.5.6" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" }, "dependencies": { "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true } } }, "source-map-url": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true }, "spdx-correct": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.2.tgz", - "integrity": "sha512-q9hedtzyXHr5S0A1vEPoK/7l8NpfkFYTq6iCY+Pno2ZbdZR6WexZFtqeVGkGxW3TEJMN914Z55EnAGMmenlIQQ==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", @@ -15304,15 +16203,15 @@ } }, "spdx-exceptions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", "dev": true }, "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, "requires": { "spdx-exceptions": "^2.1.0", @@ -15320,15 +16219,15 @@ } }, "spdx-license-ids": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.1.tgz", - "integrity": "sha512-TfOfPcYGBB5sDuPn3deByxPhmfegAhpDYKSOXZQN81Oyrrif8ZCodOLzK3AesELnCx03kikhyDwh0pfvvQvF8w==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz", + "integrity": "sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ==", "dev": true }, "spdy": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.0.tgz", - "integrity": "sha512-ot0oEGT/PGUpzf/6uk4AWLqkq+irlqHXkrdbk51oWONh3bxQmBuljxPNl66zlRRcIJStWq0QkLUCPOPjgjvU0Q==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", "dev": true, "requires": { "debug": "^4.1.0", @@ -15339,18 +16238,18 @@ }, "dependencies": { "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true } } @@ -15370,39 +16269,19 @@ }, "dependencies": { "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true - }, - "readable-stream": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.1.1.tgz", - "integrity": "sha512-DkN66hPyqDhnIQ6Jcsvx9bFjhw214O4poMBcIMgPVpQvNy9a0e0Uhg5SqySyDKAmUlwt8LonTBz1ezOnM8pUdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "string_decoder": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", - "integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } } } }, @@ -15413,27 +16292,6 @@ "dev": true, "requires": { "extend-shallow": "^3.0.0" - }, - "dependencies": { - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } } }, "sprintf-js": { @@ -15442,9 +16300,10 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "sshpk": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz", - "integrity": "sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA==", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -15458,20 +16317,38 @@ } }, "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-7.1.0.tgz", + "integrity": "sha512-77/WrDZUWocK0mvA5NTRQyveUf+wsrIc6vyrxpS8tVvYBcX215QbafrJR3KtkpskIzoFLqqNuuYQvxaMjXJ/0g==", "dev": true, "requires": { - "figgy-pudding": "^3.5.1" + "figgy-pudding": "^3.5.1", + "minipass": "^3.1.1" } }, - "stack-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", - "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", + "stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", "dev": true }, + "stack-utils": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.4.tgz", + "integrity": "sha512-IPDJfugEGbfizBwBZRZ3xpccMdRyP5lqsBWXGQWimVjua/ccLCeMOAVjlc1R7LxFjo5sEDhyNIXd8mo/AiDS9w==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + } + } + }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -15494,61 +16371,21 @@ } }, "statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", "dev": true }, - "stdout-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", - "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==", - "dev": true, - "requires": { - "readable-stream": "^2.0.1" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, "stealthy-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "dev": true }, "stream-browserify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", - "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", "dev": true, "requires": { "inherits": "~2.0.1", @@ -15562,9 +16399,9 @@ "dev": true }, "readable-stream": { - "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -15617,9 +16454,9 @@ "dev": true }, "readable-stream": { - "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -15639,12 +16476,6 @@ "requires": { "safe-buffer": "~5.1.0" } - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", - "dev": true } } }, @@ -15654,6 +16485,12 @@ "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", "dev": true }, + "strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", + "dev": true + }, "string-length": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", @@ -15664,12 +16501,6 @@ "strip-ansi": "^4.0.0" }, "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", @@ -15682,106 +16513,141 @@ } }, "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "dev": true, "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "string.prototype.matchall": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.3.tgz", + "integrity": "sha512-OBxYDA2ifZQ2e13cP82dWFMaCV9CGF8GzmN4fljBVw5O5wep0lu4gacm1OL6MjROoUnB8VbkWRThqkV2YFLNxw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1", + "has-symbols": "^1.0.1", + "internal-slot": "^1.0.2", + "regexp.prototype.flags": "^1.3.0", + "side-channel": "^1.0.3" + } + }, + "string.prototype.trim": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.3.tgz", + "integrity": "sha512-16IL9pIBA5asNOSukPfxX2W68BaBvxyiRK16H3RA/lWW9BDosh+w7f+LhomPHpXJ82QEe7w7/rY/S1CV97raLg==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1" + } + }, + "string.prototype.trimend": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz", + "integrity": "sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz", + "integrity": "sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" }, "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } } } }, - "string.prototype.trim": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.0.tgz", - "integrity": "sha512-9EIjYD/WdlvLpn987+ctkLf0FfvBefOCuiEr2henD8X+7jfwPnyvTdmW8OJhj5p+M0/96mBdynLWkxUr+rHlpg==", + "stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", "dev": true, "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.13.0", - "function-bind": "^1.1.1" + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" }, "dependencies": { - "es-abstract": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", - "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.0", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "is-callable": "^1.1.4", - "is-regex": "^1.0.4", - "object-keys": "^1.0.12" - } - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", "dev": true } } }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - }, "strip-ansi": { - "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + } } }, "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-comments": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-1.0.2.tgz", + "integrity": "sha512-kL97alc47hoyIQSV165tTt9rG5dn4w1dNnBhOQ3bOU1Nc1hel09jnXANaHJ7vzHLd4Ju8kseDGzlev96pghLFw==", "dev": true, "requires": { - "is-utf8": "^0.2.0" + "babel-extract-comments": "^1.0.0", + "babel-plugin-transform-object-rest-spread": "^6.26.0" } }, "strip-eof": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, - "strip-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", - "dev": true, - "requires": { - "get-stdin": "^4.0.1" - } - }, "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, "style-loader": { @@ -15792,37 +16658,61 @@ "requires": { "loader-utils": "^1.1.0", "schema-utils": "^1.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } } }, "styled-components": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-4.2.0.tgz", - "integrity": "sha512-L/LzkL3ZbBhqIVHdR7DbYujy4tqvTNRfc+4JWDCYyhTatI+8CRRQUmdaR0+ARl03DWsfKLhjewll5uNLrqrl4A==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-4.4.1.tgz", + "integrity": "sha512-RNqj14kYzw++6Sr38n7197xG33ipEOktGElty4I70IKzQF1jzaD1U4xQ+Ny/i03UUhHlC5NWEO+d8olRCDji6g==", "requires": { "@babel/helper-module-imports": "^7.0.0", - "@emotion/is-prop-valid": "^0.7.3", + "@babel/traverse": "^7.0.0", + "@emotion/is-prop-valid": "^0.8.1", "@emotion/unitless": "^0.7.0", "babel-plugin-styled-components": ">= 1", "css-to-react-native": "^2.2.2", "memoize-one": "^5.0.0", + "merge-anything": "^2.2.4", "prop-types": "^15.5.4", "react-is": "^16.6.0", "stylis": "^3.5.0", "stylis-rule-sheet": "^0.0.10", "supports-color": "^5.5.0" + } + }, + "stylehacks": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", + "integrity": "sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" }, "dependencies": { - "@emotion/unitless": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.3.tgz", - "integrity": "sha512-4zAPlpDEh2VwXswwr/t8xGNDGg8RQiPxtxZ3qQEXyQsBV39ptTdESCjuBvGze1nLMVrxmTIKmnO/nAV8Tqjjzg==" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "dev": true, "requires": { - "has-flag": "^3.0.0" + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" } } } @@ -15838,325 +16728,248 @@ "integrity": "sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw==" }, "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - }, - "symbol-tree": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", - "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=" + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } }, - "tabbable": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-3.1.2.tgz", - "integrity": "sha512-wjB6puVXTYO0BSFtCmWQubA/KIn7Xvajw0x0l6eJUudMG/EAiJvIUnyNX6xO4NpGrJ16lbD0eUseB9WxW0vlpQ==" + "svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "dev": true }, - "table": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/table/-/table-5.1.0.tgz", - "integrity": "sha512-e542in22ZLhD/fOIuXs/8yDZ9W61ltF8daM88rkRNtgTIct+vI2fTnAyu/Db2TCfEcI8i7mjZz6meLq0nW7TYg==", + "svgo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", + "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", "dev": true, "requires": { - "ajv": "^6.5.3", - "lodash": "^4.17.10", - "slice-ansi": "1.0.0", - "string-width": "^2.1.1" + "chalk": "^2.4.1", + "coa": "^2.0.2", + "css-select": "^2.0.0", + "css-select-base-adapter": "^0.1.1", + "css-tree": "1.0.0-alpha.37", + "csso": "^4.0.2", + "js-yaml": "^3.13.1", + "mkdirp": "~0.5.1", + "object.values": "^1.1.0", + "sax": "~1.2.4", + "stable": "^0.1.8", + "unquote": "~1.1.1", + "util.promisify": "~1.0.0" }, "dependencies": { - "ajv": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.4.tgz", - "integrity": "sha512-4Wyjt8+t6YszqaXnLDfMmG/8AlO5Zbcsy3ATHncCzjW/NoPzAId8AK6749Ybjmdt+kUY1gP60fCu46oDxPv/mg==", + "css-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", + "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", "dev": true, "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "boolbase": "^1.0.0", + "css-what": "^3.2.1", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" } }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "css-what": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", + "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==", "dev": true }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } } } }, - "tapable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.0.tgz", - "integrity": "sha512-IlqtmLVaZA2qab8epUXbVWRn3aB1imbDMJtjB3nu4X0NqPkcY/JH9ZtCBWKHWPxs8Svi9tyo8w2dBoi07qZbBA==", + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, - "tar": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz", - "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==", - "dev": true, - "requires": { - "block-stream": "*", - "fstream": "^1.0.12", - "inherits": "2" - } + "tabbable": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-5.1.5.tgz", + "integrity": "sha512-oVAPrWgLLqrbvQE8XqcU7CVBq6SQbaIbHkhOca3u7/jzuQvyZycrUKPCGr04qpEIUslmUlULbSeN+m3QrKEykA==" }, - "terser": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.4.2.tgz", - "integrity": "sha512-Uufrsvhj9O1ikwgITGsZ5EZS6qPokUOkCegS7fYOdGTv+OA90vndUbU6PEjr5ePqHfNUbGyMO7xyIZv2MhsALQ==", + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", "dev": true, "requires": { - "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" }, "dependencies": { - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, - "source-map-support": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", - "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - } - } - }, - "terser-webpack-plugin": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz", - "integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==", - "dev": true, - "requires": { - "cacache": "^12.0.2", - "find-cache-dir": "^2.1.0", - "is-wsl": "^1.1.0", - "schema-utils": "^1.0.0", - "serialize-javascript": "^2.1.2", - "source-map": "^0.6.1", - "terser": "^4.1.2", - "webpack-sources": "^1.4.0", - "worker-farm": "^1.7.0" - }, - "dependencies": { - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } - }, - "p-limit": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", - "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "requires": { - "find-up": "^3.0.0" + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" } } } }, - "test-exclude": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.2.tgz", - "integrity": "sha512-N2pvaLpT8guUpb5Fe1GJlmvmzH3x+DAKmmyEQmFP792QcLYoGE1syxztSvPD1V8yPe6VrcCt6YGQVjSRjCASsA==", + "tapable": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.1.10.tgz", + "integrity": "sha1-KcNXB8K3DlDQdIK10gLo7URtr9Q=", + "dev": true + }, + "terser": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", + "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", "dev": true, "requires": { - "glob": "^7.1.3", - "minimatch": "^3.0.4", - "read-pkg-up": "^4.0.0", - "require-main-filename": "^2.0.0" + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" }, "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "terser-webpack-plugin": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.8.tgz", + "integrity": "sha512-/fKw3R+hWyHfYx7Bv6oPqmk4HGQcrWLtV3X6ggvPuwPNHSnzvVV51z6OaaCOus4YLjutYGOz3pEpbhe6Up2s1w==", + "dev": true, + "requires": { + "cacache": "^13.0.1", + "find-cache-dir": "^3.3.1", + "jest-worker": "^25.4.0", + "p-limit": "^2.3.0", + "schema-utils": "^2.6.6", + "serialize-javascript": "^4.0.0", + "source-map": "^0.6.1", + "terser": "^4.6.12", + "webpack-sources": "^1.4.3" + }, + "dependencies": { + "find-cache-dir": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", + "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", "dev": true, "requires": { - "locate-path": "^3.0.0" + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" } }, - "load-json-file": { + "has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true }, - "p-limit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", - "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "jest-worker": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.5.0.tgz", + "integrity": "sha512-/dsSmUkIy5EBGfv/IjjqmFxrNAUpBERfGs1oHROyD7yxjG/w+t0GOJDX8O1k32ySmd7+a5IhnJU2qQFcJ4n1vw==", "dev": true, "requires": { - "p-try": "^2.0.0" + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" } }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "semver": "^6.0.0" } }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, "requires": { - "pify": "^3.0.0" + "find-up": "^4.0.0" } }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "dev": true, - "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - } - }, - "read-pkg-up": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", - "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { - "find-up": "^3.0.0", - "read-pkg": "^3.0.0" + "has-flag": "^4.0.0" } - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true } } }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -16171,47 +16984,82 @@ }, "through": { "version": "2.3.8", - "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true }, "through2": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.4.2.tgz", - "integrity": "sha1-2/WGYDEVHsg1K7bE22SiKSqEC5s=", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, "requires": { - "readable-stream": "~1.0.17", - "xtend": "~2.1.1" + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } } }, "thunky": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.0.3.tgz", - "integrity": "sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow==", - "dev": true - }, - "time-stamp": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", - "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=" + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true }, "timers-browserify": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", - "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==", + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", + "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", "dev": true, "requires": { "setimmediate": "^1.0.4" } }, + "timsort": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", + "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", + "dev": true + }, "tiny-invariant": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.4.tgz", - "integrity": "sha512-lMhRd/djQJ3MoaHEBrw8e2/uM4rs9YMNk0iOr8rHQ0QdbM7D4l0gFl3szKdeixrlyfm9Zqi4dxHCM2qVG8ND5g==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", + "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" }, "tiny-warning": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.2.tgz", - "integrity": "sha512-rru86D9CpQRLvsFG5XFdy0KdLAvjdQDyZCsRcuu60WtzFylDM3eAWSxEVz5kzL2Gp544XiUvPbVKtOA/txLi9Q==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, "tippy.js": { "version": "5.1.2", @@ -16254,17 +17102,6 @@ "dev": true, "requires": { "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } } }, "to-regex": { @@ -16277,115 +17114,90 @@ "extend-shallow": "^3.0.2", "regex-not": "^1.0.2", "safe-regex": "^1.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } } }, "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "is-number": "^7.0.0" } }, - "touch": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/touch/-/touch-2.0.2.tgz", - "integrity": "sha512-qjNtvsFXTRq7IuMLweVgFxmEuQ6gLbRs2jQxL80TtZ31dEKWYIxRXquij6w6VimyDek5hD3PytljHmEtAs2u0A==", - "requires": { - "nopt": "~1.0.10" - } + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "dev": true }, "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - } + "psl": "^1.1.28", + "punycode": "^2.1.1" } }, "tr46": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "dev": true, "requires": { "punycode": "^2.1.0" } }, - "trim-newlines": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", - "dev": true - }, "trim-right": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=" + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true }, - "true-case-path": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", - "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==", + "ts-pnp": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.1.6.tgz", + "integrity": "sha512-CrG5GqAAzMT7144Cl+UIFP7mz/iIhiy+xQ6GGcnjTezhALT02uPMRw7tgDSESgB5MsfKt55+GPWw4ir1kVtMIQ==", + "dev": true + }, + "tsconfig-paths": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", + "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", "dev": true, "requires": { - "glob": "^7.1.2" + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + } } }, "tslib": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", - "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" + }, + "tsutils": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", + "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } }, "tty-browserify": { "version": "0.0.0", @@ -16397,6 +17209,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, "requires": { "safe-buffer": "^5.0.1" } @@ -16404,24 +17217,38 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "dev": true }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, "requires": { "prelude-ls": "~1.1.2" } }, + "type-fest": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", + "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", + "dev": true + }, "type-is": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", - "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "dev": true, "requires": { "media-typer": "0.3.0", - "mime-types": "~2.1.18" + "mime-types": "~2.1.24" } }, "typedarray": { @@ -16430,37 +17257,6 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, - "typescript": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz", - "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==", - "dev": true - }, - "ua-parser-js": { - "version": "0.7.19", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.19.tgz", - "integrity": "sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ==" - }, - "uglify-js": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz", - "integrity": "sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==", - "dev": true, - "optional": true, - "requires": { - "commander": "~2.20.0", - "source-map": "~0.6.1" - }, - "dependencies": { - "commander": { - "version": "2.20.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.1.tgz", - "integrity": "sha512-cCuLsMhJeWQ/ZpsFTbE765kvVfoeSddc4nU3up4fV+fDBcfUXnbITJ+JzhkdjzOqhURjZgujxaioam4RM9yGUg==", - "dev": true, - "optional": true - } - } - }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", @@ -16478,15 +17274,15 @@ } }, "unicode-match-property-value-ecmascript": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.0.2.tgz", - "integrity": "sha512-Rx7yODZC1L/T8XKo/2kNzVAQaRE88AaMvI1EF/Xnj3GW2wzN6fop9DDWuFAKUVFH7vozkz26DzP0qyWLKLIVPQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", + "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==", "dev": true }, "unicode-property-aliases-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.4.tgz", - "integrity": "sha512-2WSLa6OdYd2ng8oqiGIWnJqyFArvhn+5vgx5GTxMbUYjCYKUcuKS62YLFF0R/BDGlB1yzXjQOLtPAfHsgirEpg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", + "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==", "dev": true }, "union-value": { @@ -16499,16 +17295,20 @@ "get-value": "^2.0.6", "is-extendable": "^0.1.1", "set-value": "^2.0.1" - }, - "dependencies": { - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true - } } }, + "uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", + "dev": true + }, + "uniqs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", + "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=", + "dev": true + }, "unique-filename": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", @@ -16530,7 +17330,8 @@ "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true }, "unpipe": { "version": "1.0.0", @@ -16538,6 +17339,12 @@ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", "dev": true }, + "unquote": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", + "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=", + "dev": true + }, "unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", @@ -16581,30 +17388,19 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true } } }, "upath": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", - "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", "dev": true }, - "upper-case": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", - "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=" - }, "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", + "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", "dev": true, "requires": { "punycode": "^2.1.0" @@ -16613,7 +17409,8 @@ "urix": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true }, "url": { "version": "0.11.0", @@ -16633,13 +17430,24 @@ } } }, + "url-loader": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-2.3.0.tgz", + "integrity": "sha512-goSdg8VY+7nPZKUEChZSEtW5gjbS66USIGCeSJ1OVOJ7Yfuh/36YxCwMi5HVEJh6mqUYOoy3NJ0vlOMrWsSHog==", + "dev": true, + "requires": { + "loader-utils": "^1.2.3", + "mime": "^2.4.4", + "schema-utils": "^2.5.0" + } + }, "url-parse": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.4.tgz", - "integrity": "sha512-/92DTTorg4JjktLNLe6GPS2/RvAd/RGr6LuktmWSMLEOa6rjnlrFXNgSbSmkNvCoL2T028A0a1JaJLzRMlFoHg==", + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", + "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", "dev": true, "requires": { - "querystringify": "^2.0.0", + "querystringify": "^2.1.1", "requires-port": "^1.0.0" } }, @@ -16650,29 +17458,67 @@ "dev": true }, "util": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", - "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", "dev": true, "requires": { "inherits": "2.0.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + } } }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true }, "util.promisify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", - "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", + "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "object.getownpropertydescriptors": "^2.0.3" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.2", + "has-symbols": "^1.0.1", + "object.getownpropertydescriptors": "^2.1.0" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } } }, + "utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", + "dev": true + }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -16680,14 +17526,15 @@ "dev": true }, "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true }, "v8-compile-cache": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.2.tgz", - "integrity": "sha512-1wFuMUIM16MDJRCrpbpuEPTUGmM5QMUg0cr3KFwra2XgOgFcPGDQHDh3CszSCD2Zewc/dh/pamNEW8CbfDebUw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz", + "integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==", "dev": true }, "validate-npm-package-license": { @@ -16701,9 +17548,9 @@ } }, "value-equal": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.4.0.tgz", - "integrity": "sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" }, "vary": { "version": "1.1.2", @@ -16711,10 +17558,17 @@ "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", "dev": true }, + "vendors": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz", + "integrity": "sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==", + "dev": true + }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, "requires": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", @@ -16722,26 +17576,25 @@ } }, "vm-browserify": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", - "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=", - "dev": true, - "requires": { - "indexof": "0.0.1" - } + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", + "dev": true }, "w3c-hr-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", - "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "dev": true, "requires": { - "browser-process-hrtime": "^0.1.2" + "browser-process-hrtime": "^1.0.0" } }, "w3c-xmlserializer": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz", "integrity": "sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg==", + "dev": true, "requires": { "domexception": "^1.0.1", "webidl-conversions": "^4.0.2", @@ -16757,15 +17610,275 @@ "makeerror": "1.0.x" } }, + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "requires": { + "loose-envify": "^1.0.0" + } + }, "watchpack": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", - "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", + "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==", "dev": true, "requires": { - "chokidar": "^2.0.2", + "chokidar": "^3.4.1", "graceful-fs": "^4.1.2", - "neo-async": "^2.5.0" + "neo-async": "^2.5.0", + "watchpack-chokidar2": "^2.0.1" + } + }, + "watchpack-chokidar2": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz", + "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==", + "dev": true, + "optional": true, + "requires": { + "chokidar": "^2.1.8" + }, + "dependencies": { + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true, + "optional": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "optional": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + } + }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "dev": true, + "optional": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "optional": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + } + }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "dev": true, + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "optional": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "optional": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "optional": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "optional": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true, + "optional": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "optional": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "optional": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "optional": true, + "requires": { + "is-plain-object": "^2.0.4" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "optional": true + } + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "optional": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "optional": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "optional": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } } }, "wbuf": { @@ -16789,12 +17902,13 @@ "webidl-conversions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true }, "webpack": { - "version": "4.41.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.41.2.tgz", - "integrity": "sha512-Zhw69edTGfbz9/8JJoyRQ/pq8FYUoY0diOXqW0T6yhgdhCv6wr0hra5DwwWexNRns2Z2+gsnrNcbe9hbGBgk/A==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.42.0.tgz", + "integrity": "sha512-EzJRHvwQyBiYrYqhyjW9AqM90dE4+s1/XtCfn7uWg6cS72zH+2VPFAlsnW0+W0cDi0XRjNKUMoJtpSi50+Ph6w==", "dev": true, "requires": { "@webassemblyjs/ast": "1.8.5", @@ -16817,741 +17931,685 @@ "node-libs-browser": "^2.2.1", "schema-utils": "^1.0.0", "tapable": "^1.1.3", - "terser-webpack-plugin": "^1.4.1", + "terser-webpack-plugin": "^1.4.3", "watchpack": "^1.6.0", "webpack-sources": "^1.4.1" }, "dependencies": { "acorn": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.0.tgz", - "integrity": "sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw==", - "dev": true - }, - "ajv": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", - "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", - "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", - "dev": true - }, - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true - }, - "eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "events": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", - "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==", - "dev": true - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", "dev": true }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { - "minimist": "^1.2.0" + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, - "loader-utils": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", - "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "cacache": { + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", + "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", "dev": true, "requires": { - "big.js": "^5.2.2", - "emojis-list": "^2.0.0", - "json5": "^1.0.1" + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" } }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "neo-async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", - "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", - "dev": true - }, - "node-libs-browser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", - "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", - "dev": true, - "requires": { - "assert": "^1.1.1", - "browserify-zlib": "^0.2.0", - "buffer": "^4.3.0", - "console-browserify": "^1.1.0", - "constants-browserify": "^1.0.0", - "crypto-browserify": "^3.11.0", - "domain-browser": "^1.1.1", - "events": "^3.0.0", - "https-browserify": "^1.0.0", - "os-browserify": "^0.3.0", - "path-browserify": "0.0.1", - "process": "^0.11.10", - "punycode": "^1.2.4", - "querystring-es3": "^0.2.0", - "readable-stream": "^2.3.3", - "stream-browserify": "^2.0.1", - "stream-http": "^2.7.2", - "string_decoder": "^1.0.0", - "timers-browserify": "^2.0.4", - "tty-browserify": "0.0.0", - "url": "^0.11.0", - "util": "^0.11.0", - "vm-browserify": "^1.0.1" - } - }, - "path-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", - "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", - "dev": true - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", - "dev": true - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "enhanced-resolve": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz", + "integrity": "sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "graceful-fs": "^4.1.2", + "memory-fs": "^0.5.0", + "tapable": "^1.0.0" }, "dependencies": { - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "errno": "^0.1.3", + "readable-stream": "^2.0.1" } } } }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", "dev": true, "requires": { - "safe-buffer": "~5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", - "dev": true - } + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" } }, - "tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", - "dev": true - }, - "util": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", - "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "inherits": "2.0.3" + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, - "vm-browserify": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", - "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true - } - } - }, - "webpack-cli": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.1.2.tgz", - "integrity": "sha512-Cnqo7CeqeSvC6PTdts+dywNi5CRlIPbLx1AoUPK2T6vC1YAugMG3IOoO9DmEscd+Dghw7uRlnzV1KwOe5IrtgQ==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "cross-spawn": "^6.0.5", - "enhanced-resolve": "^4.1.0", - "global-modules-path": "^2.3.0", - "import-local": "^2.0.0", - "interpret": "^1.1.0", - "loader-utils": "^1.1.0", - "supports-color": "^5.5.0", - "v8-compile-cache": "^2.0.2", - "yargs": "^12.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "decamelize": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz", - "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==", - "dev": true, - "requires": { - "xregexp": "4.0.0" - } }, - "execa": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", - "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "find-up": { + "is-number": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "import-local": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", - "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "pkg-dir": "^3.0.0", - "resolve-cwd": "^2.0.0" + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, - "invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, - "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", - "dev": true, - "requires": { - "invert-kv": "^2.0.0" - } + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "yallist": "^3.0.2" } }, - "mem": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.0.0.tgz", - "integrity": "sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA==", + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", "dev": true, "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^1.0.0", - "p-is-promise": "^1.1.0" + "errno": "^0.1.3", + "readable-stream": "^2.0.1" } }, - "os-locale": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.0.1.tgz", - "integrity": "sha512-7g5e7dmXPtzcP4bgsZ8ixDVqA7oWYuEz4lOSujeWyliPai4gfVDiFIcwBg3aGCPnmSGfzOKTK3ccPn0CKv3DBw==", + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "execa": "^0.10.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" } }, - "p-limit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", - "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { - "p-try": "^2.0.0" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" } }, - "p-try": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", - "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", "dev": true, "requires": { - "find-up": "^3.0.0" + "figgy-pudding": "^3.5.1" } }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "safe-buffer": "~5.1.0" } }, - "yargs": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.2.tgz", - "integrity": "sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ==", + "tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "dev": true + }, + "terser-webpack-plugin": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", + "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", "dev": true, "requires": { - "cliui": "^4.0.0", - "decamelize": "^2.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^10.1.0" + "cacache": "^12.0.2", + "find-cache-dir": "^2.1.0", + "is-wsl": "^1.1.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^4.0.0", + "source-map": "^0.6.1", + "terser": "^4.1.2", + "webpack-sources": "^1.4.0", + "worker-farm": "^1.7.0" } }, - "yargs-parser": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", - "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "dev": true, "requires": { - "camelcase": "^4.1.0" + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true } } }, "webpack-dev-middleware": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.4.0.tgz", - "integrity": "sha512-Q9Iyc0X9dP9bAsYskAVJ/hmIZZQwf/3Sy4xCAZgL5cUkjZmUZLt4l5HpbST/Pdgjn3u6pE7u5OdGd1apgzRujA==", + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz", + "integrity": "sha512-djelc/zGiz9nZj/U7PTBi2ViorGJXEWo/3ltkPbDyxCXhhEXkW0ce99falaok4TPj+AsxLiXJR0EBOb0zh9fKQ==", "dev": true, "requires": { - "memory-fs": "~0.4.1", - "mime": "^2.3.1", - "range-parser": "^1.0.3", + "memory-fs": "^0.4.1", + "mime": "^2.4.4", + "mkdirp": "^0.5.1", + "range-parser": "^1.2.1", "webpack-log": "^2.0.0" }, "dependencies": { - "mime": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.0.tgz", - "integrity": "sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==", + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true + }, + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } } } }, "webpack-dev-server": { - "version": "3.1.14", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.1.14.tgz", - "integrity": "sha512-mGXDgz5SlTxcF3hUpfC8hrQ11yhAttuUQWf1Wmb+6zo3x6rb7b9mIfuQvAPLdfDRCGRGvakBWHdHOa0I9p/EVQ==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz", + "integrity": "sha512-PUxZ+oSTxogFQgkTtFndEtJIPNmml7ExwufBZ9L2/Xyyd5PnOL5UreWe5ZT7IU25DSdykL9p1MLQzmLh2ljSeg==", "dev": true, "requires": { "ansi-html": "0.0.7", "bonjour": "^3.5.0", - "chokidar": "^2.0.0", - "compression": "^1.5.2", - "connect-history-api-fallback": "^1.3.0", - "debug": "^3.1.0", - "del": "^3.0.0", - "express": "^4.16.2", - "html-entities": "^1.2.0", - "http-proxy-middleware": "~0.18.0", + "chokidar": "^2.1.8", + "compression": "^1.7.4", + "connect-history-api-fallback": "^1.6.0", + "debug": "^4.1.1", + "del": "^4.1.1", + "express": "^4.17.1", + "html-entities": "^1.3.1", + "http-proxy-middleware": "0.19.1", "import-local": "^2.0.0", - "internal-ip": "^3.0.1", + "internal-ip": "^4.3.0", "ip": "^1.1.5", - "killable": "^1.0.0", - "loglevel": "^1.4.1", - "opn": "^5.1.0", - "portfinder": "^1.0.9", + "is-absolute-url": "^3.0.3", + "killable": "^1.0.1", + "loglevel": "^1.6.8", + "opn": "^5.5.0", + "p-retry": "^3.0.1", + "portfinder": "^1.0.26", "schema-utils": "^1.0.0", - "selfsigned": "^1.9.1", - "semver": "^5.6.0", - "serve-index": "^1.7.2", - "sockjs": "0.3.19", - "sockjs-client": "1.3.0", - "spdy": "^4.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^5.1.0", + "selfsigned": "^1.10.7", + "semver": "^6.3.0", + "serve-index": "^1.9.1", + "sockjs": "0.3.20", + "sockjs-client": "1.4.0", + "spdy": "^4.0.2", + "strip-ansi": "^3.0.1", + "supports-color": "^6.1.0", "url": "^0.11.0", - "webpack-dev-middleware": "3.4.0", + "webpack-dev-middleware": "^3.7.2", "webpack-log": "^2.0.0", - "yargs": "12.0.2" + "ws": "^6.2.1", + "yargs": "^13.3.2" }, "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { - "ms": "^2.1.1" + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" } }, - "decamelize": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz", - "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==", + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", "dev": true, "requires": { - "xregexp": "4.0.0" + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" } }, - "del": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", - "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dev": true, "requires": { - "globby": "^6.1.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "p-map": "^1.1.1", - "pify": "^3.0.0", - "rimraf": "^2.2.8" + "ms": "2.1.2" } }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" + "is-extendable": "^0.1.0" } }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "locate-path": "^3.0.0" + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" } }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", "dev": true, + "optional": true, "requires": { - "pump": "^3.0.0" + "bindings": "^1.5.0", + "nan": "^2.12.1" } }, - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", "dev": true, "requires": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" }, "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } } } }, - "import-local": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", - "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "http-proxy-middleware": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", + "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", "dev": true, "requires": { - "pkg-dir": "^3.0.0", - "resolve-cwd": "^2.0.0" + "http-proxy": "^1.17.0", + "is-glob": "^4.0.0", + "lodash": "^4.17.11", + "micromatch": "^3.1.10" } }, - "invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "is-absolute-url": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", + "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", "dev": true }, - "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", "dev": true, "requires": { - "invert-kv": "^2.0.0" + "binary-extensions": "^1.0.0" } }, - "locate-path": { + "is-number": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "kind-of": "^3.0.2" } }, - "mem": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.0.0.tgz", - "integrity": "sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA==", + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^1.0.0", - "p-is-promise": "^1.1.0" + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + } } }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "dev": true, "requires": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "p-limit": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.1.0.tgz", - "integrity": "sha512-NhURkNcrVB+8hNfLuysU8enY5xn2KXphsHBaC2YmRNTZRc7RWusw6apSpdEj3jo4CMb6W9nrF6tTnsJsJeyu6g==", + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", "dev": true, "requires": { - "p-try": "^2.0.0" + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" } }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" } }, - "p-try": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", - "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==", - "dev": true - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "find-up": "^3.0.0" + "safe-buffer": "~5.1.0" } }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "ansi-regex": "^2.0.0" } }, "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", "dev": true, "requires": { "has-flag": "^3.0.0" } }, - "yargs": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.2.tgz", - "integrity": "sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ==", + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "dev": true, "requires": { - "cliui": "^4.0.0", - "decamelize": "^2.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^10.1.0" + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" } }, - "yargs-parser": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", - "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "ws": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", + "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", "dev": true, "requires": { - "camelcase": "^4.1.0" + "async-limiter": "~1.0.0" } } } @@ -17564,12 +18622,35 @@ "requires": { "ansi-colors": "^3.0.0", "uuid": "^3.3.2" + } + }, + "webpack-manifest-plugin": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-2.2.0.tgz", + "integrity": "sha512-9S6YyKKKh/Oz/eryM1RyLVDVmy3NSPV0JXMRhZ18fJsq+AwGxUY34X54VNwkzYcEmEkDwNxuEOboCZEebJXBAQ==", + "dev": true, + "requires": { + "fs-extra": "^7.0.0", + "lodash": ">=3.5 <5", + "object.entries": "^1.1.0", + "tapable": "^1.0.0" }, "dependencies": { - "ansi-colors": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", - "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", "dev": true } } @@ -17582,41 +18663,51 @@ "requires": { "source-list-map": "^2.0.0", "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "websocket-driver": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.0.tgz", - "integrity": "sha1-DK+dLXVdk67gSdS90NP+LMoqJOs=", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz", + "integrity": "sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY=", "dev": true, "requires": { - "http-parser-js": ">=0.4.0", "websocket-extensions": ">=0.1.1" } }, "websocket-extensions": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", - "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", "dev": true }, "whatwg-encoding": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, "requires": { "iconv-lite": "0.4.24" } }, "whatwg-fetch": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", - "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==" + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.5.0.tgz", + "integrity": "sha512-jXkLtsR42xhXg7akoDKvKWE40eJeI+2KZqcp2h3NsOrRnDvtWX36KcKl30dy+hxECivdk2BVUHVNrPtoMBUx6A==", + "dev": true }, "whatwg-mimetype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.2.0.tgz", - "integrity": "sha512-5YSO1nMd5D1hY3WzAQV3PzZL83W3YeyR1yW9PcH26Weh1t+Vzh9B6XkDh7aXm83HBZ4nSMvkjvN2H2ySWIvBgw==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true }, "whatwg-url": { "version": "6.5.0", @@ -17633,6 +18724,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, "requires": { "isexe": "^2.0.0" } @@ -17643,19 +18735,189 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "workbox-background-sync": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-4.3.1.tgz", + "integrity": "sha512-1uFkvU8JXi7L7fCHVBEEnc3asPpiAL33kO495UMcD5+arew9IbKW2rV5lpzhoWcm/qhGB89YfO4PmB/0hQwPRg==", "dev": true, "requires": { - "string-width": "^1.0.2 || 2" + "workbox-core": "^4.3.1" } }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" + "workbox-broadcast-update": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-4.3.1.tgz", + "integrity": "sha512-MTSfgzIljpKLTBPROo4IpKjESD86pPFlZwlvVG32Kb70hW+aob4Jxpblud8EhNb1/L5m43DUM4q7C+W6eQMMbA==", + "dev": true, + "requires": { + "workbox-core": "^4.3.1" + } + }, + "workbox-build": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-4.3.1.tgz", + "integrity": "sha512-UHdwrN3FrDvicM3AqJS/J07X0KXj67R8Cg0waq1MKEOqzo89ap6zh6LmaLnRAjpB+bDIz+7OlPye9iii9KBnxw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.3.4", + "@hapi/joi": "^15.0.0", + "common-tags": "^1.8.0", + "fs-extra": "^4.0.2", + "glob": "^7.1.3", + "lodash.template": "^4.4.0", + "pretty-bytes": "^5.1.0", + "stringify-object": "^3.3.0", + "strip-comments": "^1.0.2", + "workbox-background-sync": "^4.3.1", + "workbox-broadcast-update": "^4.3.1", + "workbox-cacheable-response": "^4.3.1", + "workbox-core": "^4.3.1", + "workbox-expiration": "^4.3.1", + "workbox-google-analytics": "^4.3.1", + "workbox-navigation-preload": "^4.3.1", + "workbox-precaching": "^4.3.1", + "workbox-range-requests": "^4.3.1", + "workbox-routing": "^4.3.1", + "workbox-strategies": "^4.3.1", + "workbox-streams": "^4.3.1", + "workbox-sw": "^4.3.1", + "workbox-window": "^4.3.1" + }, + "dependencies": { + "fs-extra": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", + "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + } + } + }, + "workbox-cacheable-response": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-4.3.1.tgz", + "integrity": "sha512-Rp5qlzm6z8IOvnQNkCdO9qrDgDpoPNguovs0H8C+wswLuPgSzSp9p2afb5maUt9R1uTIwOXrVQMmPfPypv+npw==", + "dev": true, + "requires": { + "workbox-core": "^4.3.1" + } + }, + "workbox-core": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-4.3.1.tgz", + "integrity": "sha512-I3C9jlLmMKPxAC1t0ExCq+QoAMd0vAAHULEgRZ7kieCdUd919n53WC0AfvokHNwqRhGn+tIIj7vcb5duCjs2Kg==", + "dev": true + }, + "workbox-expiration": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-4.3.1.tgz", + "integrity": "sha512-vsJLhgQsQouv9m0rpbXubT5jw0jMQdjpkum0uT+d9tTwhXcEZks7qLfQ9dGSaufTD2eimxbUOJfWLbNQpIDMPw==", + "dev": true, + "requires": { + "workbox-core": "^4.3.1" + } + }, + "workbox-google-analytics": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-4.3.1.tgz", + "integrity": "sha512-xzCjAoKuOb55CBSwQrbyWBKqp35yg1vw9ohIlU2wTy06ZrYfJ8rKochb1MSGlnoBfXGWss3UPzxR5QL5guIFdg==", + "dev": true, + "requires": { + "workbox-background-sync": "^4.3.1", + "workbox-core": "^4.3.1", + "workbox-routing": "^4.3.1", + "workbox-strategies": "^4.3.1" + } + }, + "workbox-navigation-preload": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-4.3.1.tgz", + "integrity": "sha512-K076n3oFHYp16/C+F8CwrRqD25GitA6Rkd6+qAmLmMv1QHPI2jfDwYqrytOfKfYq42bYtW8Pr21ejZX7GvALOw==", + "dev": true, + "requires": { + "workbox-core": "^4.3.1" + } + }, + "workbox-precaching": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-4.3.1.tgz", + "integrity": "sha512-piSg/2csPoIi/vPpp48t1q5JLYjMkmg5gsXBQkh/QYapCdVwwmKlU9mHdmy52KsDGIjVaqEUMFvEzn2LRaigqQ==", + "dev": true, + "requires": { + "workbox-core": "^4.3.1" + } + }, + "workbox-range-requests": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-4.3.1.tgz", + "integrity": "sha512-S+HhL9+iTFypJZ/yQSl/x2Bf5pWnbXdd3j57xnb0V60FW1LVn9LRZkPtneODklzYuFZv7qK6riZ5BNyc0R0jZA==", + "dev": true, + "requires": { + "workbox-core": "^4.3.1" + } + }, + "workbox-routing": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-4.3.1.tgz", + "integrity": "sha512-FkbtrODA4Imsi0p7TW9u9MXuQ5P4pVs1sWHK4dJMMChVROsbEltuE79fBoIk/BCztvOJ7yUpErMKa4z3uQLX+g==", + "dev": true, + "requires": { + "workbox-core": "^4.3.1" + } + }, + "workbox-strategies": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-4.3.1.tgz", + "integrity": "sha512-F/+E57BmVG8dX6dCCopBlkDvvhg/zj6VDs0PigYwSN23L8hseSRwljrceU2WzTvk/+BSYICsWmRq5qHS2UYzhw==", + "dev": true, + "requires": { + "workbox-core": "^4.3.1" + } + }, + "workbox-streams": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-4.3.1.tgz", + "integrity": "sha512-4Kisis1f/y0ihf4l3u/+ndMkJkIT4/6UOacU3A4BwZSAC9pQ9vSvJpIi/WFGQRH/uPXvuVjF5c2RfIPQFSS2uA==", + "dev": true, + "requires": { + "workbox-core": "^4.3.1" + } + }, + "workbox-sw": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-4.3.1.tgz", + "integrity": "sha512-0jXdusCL2uC5gM3yYFT6QMBzKfBr2XTk0g5TPAV4y8IZDyVNDyj1a8uSXy3/XrvkVTmQvLN4O5k3JawGReXr9w==", + "dev": true + }, + "workbox-webpack-plugin": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/workbox-webpack-plugin/-/workbox-webpack-plugin-4.3.1.tgz", + "integrity": "sha512-gJ9jd8Mb8wHLbRz9ZvGN57IAmknOipD3W4XNE/Lk/4lqs5Htw4WOQgakQy/o/4CoXQlMCYldaqUg+EJ35l9MEQ==", + "dev": true, + "requires": { + "@babel/runtime": "^7.0.0", + "json-stable-stringify": "^1.0.1", + "workbox-build": "^4.3.1" + } + }, + "workbox-window": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-4.3.1.tgz", + "integrity": "sha512-C5gWKh6I58w3GeSc0wp2Ne+rqVw8qwcmZnQGpjiek8A2wpbxSJb1FdCoQVO+jDJs35bFgo/WETgl1fqgsxN0Hg==", + "dev": true, + "requires": { + "workbox-core": "^4.3.1" + } }, "worker-farm": { "version": "1.7.0", @@ -17666,34 +18928,62 @@ "errno": "~0.1.7" } }, + "worker-rpc": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/worker-rpc/-/worker-rpc-0.1.1.tgz", + "integrity": "sha512-P1WjMrUB3qgJNI9jfmpZ/htmBEjFh//6l/5y8SD9hg1Ef5zTTVVoRjTrTEzPrNBQvmhMxkoTsjOXN10GWU7aCg==", + "dev": true, + "requires": { + "microevent.ts": "~0.1.1" + } + }, "wrap-ansi": { - "version": "2.1.0", - "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" }, "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" } }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "ansi-regex": "^4.1.0" } } } @@ -17705,9 +18995,9 @@ "dev": true }, "write": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", "dev": true, "requires": { "mkdirp": "^0.5.1" @@ -17736,58 +19026,78 @@ "xml-name-validator": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true }, "xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" - }, - "xregexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz", - "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, - "xtend": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", - "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", + "xregexp": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.4.1.tgz", + "integrity": "sha512-2u9HwfadaJaY9zHtRRnH6BY6CQVNQKkYm3oLtC9gJXXzfsbACg5X5e4EZZGVAH+YIfa+QA9lsFQTTe3HURF3ag==", + "dev": true, "requires": { - "object-keys": "~0.4.0" + "@babel/runtime-corejs3": "^7.12.1" } }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", "dev": true }, "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yaml": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", + "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==", + "dev": true }, "yargs": { - "version": "12.0.5", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", - "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", "dev": true, "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.2.0", + "cliui": "^5.0.0", "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.0.0", + "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", + "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", - "string-width": "^2.0.0", + "string-width": "^3.0.0", "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^11.1.1" + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" }, "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, "find-up": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", @@ -17797,6 +19107,12 @@ "locate-path": "^3.0.0" } }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, "locate-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", @@ -17807,15 +19123,6 @@ "path-exists": "^3.0.0" } }, - "p-limit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", - "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, "p-locate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", @@ -17825,30 +19132,42 @@ "p-limit": "^2.0.0" } }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } } } }, "yargs-parser": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", - "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", "dev": true, "requires": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - } } } } diff --git a/awx/ui_next/package.json b/awx/ui_next/package.json index b5a104a19d65..b052a3183f40 100644 --- a/awx/ui_next/package.json +++ b/awx/ui_next/package.json @@ -1,82 +1,95 @@ { - "name": "awx-react", - "version": "1.0.0", - "description": "", - "main": "index.jsx", - "scripts": { - "start": "webpack-dev-server --config ./webpack.config.js --mode development", - "test": "TZ='UTC' jest --coverage", - "test-watch": "TZ='UTC' jest --watch", - "lint": "eslint --ext .js --ext .jsx .", - "add-locale": "lingui add-locale", - "extract-strings": "lingui extract", - "compile-strings": "lingui compile", - "prettier": "prettier --write \"src/**/*.{js,jsx,scss}\"", - "prettier-check": "prettier --check \"src/**/*.{js,jsx,scss}\"" + "name": "ui_next", + "version": "0.1.0", + "private": true, + "engines": { + "node": "14.x" + }, + "dependencies": { + "@lingui/react": "^2.9.1", + "@patternfly/patternfly": "4.70.2", + "@patternfly/react-core": "4.84.3", + "@patternfly/react-icons": "4.7.22", + "@patternfly/react-table": "^4.19.15", + "ansi-to-html": "^0.6.11", + "axios": "^0.21.1", + "codemirror": "^5.47.0", + "d3": "^5.12.0", + "dagre": "^0.8.4", + "formik": "^2.1.2", + "has-ansi": "^3.0.0", + "html-entities": "^1.2.1", + "js-yaml": "^3.13.1", + "prop-types": "^15.6.2", + "react": "^16.13.1", + "react-codemirror2": "^6.0.0", + "react-dom": "^16.13.1", + "react-router-dom": "^5.1.2", + "react-virtualized": "^9.21.1", + "rrule": "^2.6.4", + "styled-components": "^4.2.0" }, - "keywords": [], - "author": "", - "license": "Apache", "devDependencies": { - "@babel/core": "^7.2.0", - "@babel/plugin-proposal-class-properties": "^7.1.0", - "@babel/polyfill": "^7.0.0", - "@babel/preset-env": "^7.1.0", - "@babel/preset-react": "^7.0.0", - "@lingui/cli": "^2.7.4", - "@lingui/macro": "^2.7.2", + "@babel/polyfill": "^7.8.7", + "@cypress/instrument-cra": "^1.4.0", + "@lingui/cli": "^2.9.2", + "@lingui/macro": "^2.9.1", "@nteract/mockument": "^1.0.4", "babel-core": "^7.0.0-bridge.0", - "babel-eslint": "^10.0.1", - "babel-jest": "^24.7.1", - "babel-loader": "^8.0.4", - "babel-plugin-macros": "^2.4.2", - "babel-plugin-styled-components": "^1.10.0", - "css-loader": "^1.0.0", "enzyme": "^3.10.0", "enzyme-adapter-react-16": "^1.14.0", "enzyme-to-json": "^3.3.5", - "eslint": "^5.6.0", + "eslint": "^6.8.0", "eslint-config-airbnb": "^17.1.0", "eslint-config-prettier": "^5.0.0", "eslint-import-resolver-webpack": "0.11.1", "eslint-plugin-import": "^2.14.0", - "eslint-plugin-jsx-a11y": "^6.1.1", + "eslint-plugin-jsx-a11y": "^6.4.1", "eslint-plugin-react": "^7.11.1", "eslint-plugin-react-hooks": "^2.2.0", - "file-loader": "^2.0.0", - "history": "^4.9.0", - "jest": "^24.7.1", - "node-sass": "^4.12.0", + "http-proxy-middleware": "^1.0.3", + "jest-websocket-mock": "^2.0.2", + "mock-socket": "^9.0.3", "prettier": "^1.18.2", - "react-hot-loader": "^4.3.3", - "sass-loader": "^7.1.0", - "style-loader": "^0.23.0", - "webpack": "^4.41.2", - "webpack-cli": "^3.0.8", - "webpack-dev-server": "^3.1.14" + "react-scripts": "^3.4.4" }, - "dependencies": { - "@lingui/react": "^2.7.2", - "@patternfly/patternfly": "^2.46.1", - "@patternfly/react-core": "^3.129.3", - "@patternfly/react-icons": "^3.14.28", - "@patternfly/react-tokens": "^2.7.14", - "ansi-to-html": "^0.6.11", - "axios": "^0.18.1", - "codemirror": "^5.47.0", - "d3": "^5.12.0", - "dagre": "^0.8.4", - "formik": "^1.5.1", - "has-ansi": "^3.0.0", - "html-entities": "^1.2.1", - "js-yaml": "^3.13.1", - "prop-types": "^15.6.2", - "react": "^16.10.2", - "react-codemirror2": "^6.0.0", - "react-dom": "^16.10.2", - "react-router-dom": "^5.1.2", - "react-virtualized": "^9.21.1", - "styled-components": "^4.2.0" + "scripts": { + "start": "PORT=3001 HTTPS=true DANGEROUSLY_DISABLE_HOST_CHECK=true react-scripts start", + "start-instrumented": "DEBUG=instrument-cra PORT=3001 HTTPS=true DANGEROUSLY_DISABLE_HOST_CHECK=true react-scripts -r @cypress/instrument-cra start", + "build": "INLINE_RUNTIME_CHUNK=false react-scripts build", + "test": "TZ='UTC' react-scripts test --coverage --watchAll=false", + "test-watch": "TZ='UTC' react-scripts test", + "eject": "react-scripts eject", + "lint": "eslint --ext .js --ext .jsx .", + "add-locale": "lingui add-locale", + "extract-strings": "lingui extract", + "compile-strings": "lingui compile", + "prettier": "prettier --write \"src/**/*.{js,jsx,scss}\"", + "prettier-check": "prettier --check \"src/**/*.{js,jsx,scss}\"" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "jest": { + "snapshotSerializers": [ + "enzyme-to-json/serializer" + ], + "collectCoverageFrom": [ + "src/**/*.{js,jsx}", + "testUtils/**/*.{js,jsx}" + ], + "coveragePathIgnorePatterns": [ + "/src/locales", + "index.js" + ] } } diff --git a/awx/ui_next/public/index.html b/awx/ui_next/public/index.html new file mode 100644 index 000000000000..dc5174aa7c31 --- /dev/null +++ b/awx/ui_next/public/index.html @@ -0,0 +1,30 @@ + + + + <% if (process.env.NODE_ENV === 'production') { %> + + + <% } %> + + + + + AWX + + + + <% if (process.env.NODE_ENV === 'production') { %> +
+ <% } else { %> +
+ <% } %> + + diff --git a/awx/ui_next/public/installing.html b/awx/ui_next/public/installing.html new file mode 100644 index 000000000000..6495b7091c5d --- /dev/null +++ b/awx/ui_next/public/installing.html @@ -0,0 +1,25 @@ + + + + + + + + + + +
+ +

AWX is installing.

+

This page will refresh when complete.

+
+
+ + diff --git a/awx/ui/client/assets/awx-logo.pdf b/awx/ui_next/public/static/media/awx-logo.pdf similarity index 100% rename from awx/ui/client/assets/awx-logo.pdf rename to awx/ui_next/public/static/media/awx-logo.pdf diff --git a/awx/ui/client/assets/default.strings.json b/awx/ui_next/public/static/media/default.strings.json similarity index 100% rename from awx/ui/client/assets/default.strings.json rename to awx/ui_next/public/static/media/default.strings.json diff --git a/awx/ui/client/assets/logo-header.svg b/awx/ui_next/public/static/media/logo-header.svg similarity index 100% rename from awx/ui/client/assets/logo-header.svg rename to awx/ui_next/public/static/media/logo-header.svg diff --git a/awx/ui/client/assets/logo-header.svg.old b/awx/ui_next/public/static/media/logo-header.svg.old similarity index 100% rename from awx/ui/client/assets/logo-header.svg.old rename to awx/ui_next/public/static/media/logo-header.svg.old diff --git a/awx/ui/client/assets/logo-login.svg b/awx/ui_next/public/static/media/logo-login.svg similarity index 100% rename from awx/ui/client/assets/logo-login.svg rename to awx/ui_next/public/static/media/logo-login.svg diff --git a/awx/ui/client/assets/logo-login.svg.old b/awx/ui_next/public/static/media/logo-login.svg.old similarity index 100% rename from awx/ui/client/assets/logo-login.svg.old rename to awx/ui_next/public/static/media/logo-login.svg.old diff --git a/awx/ui_next/public/static/media/pfbg_2000.jpg b/awx/ui_next/public/static/media/pfbg_2000.jpg new file mode 100644 index 000000000000..0b2ceb367f39 Binary files /dev/null and b/awx/ui_next/public/static/media/pfbg_2000.jpg differ diff --git a/awx/ui_next/public/static/media/pfbg_576.jpg b/awx/ui_next/public/static/media/pfbg_576.jpg new file mode 100644 index 000000000000..a0a7fc3cdb07 Binary files /dev/null and b/awx/ui_next/public/static/media/pfbg_576.jpg differ diff --git a/awx/ui_next/public/static/media/pfbg_576@2x.jpg b/awx/ui_next/public/static/media/pfbg_576@2x.jpg new file mode 100644 index 000000000000..af83172425dc Binary files /dev/null and b/awx/ui_next/public/static/media/pfbg_576@2x.jpg differ diff --git a/awx/ui_next/public/static/media/pfbg_768.jpg b/awx/ui_next/public/static/media/pfbg_768.jpg new file mode 100644 index 000000000000..9a24ec7a21db Binary files /dev/null and b/awx/ui_next/public/static/media/pfbg_768.jpg differ diff --git a/awx/ui_next/public/static/media/pfbg_768@2x.jpg b/awx/ui_next/public/static/media/pfbg_768@2x.jpg new file mode 100644 index 000000000000..a4ca09cb8266 Binary files /dev/null and b/awx/ui_next/public/static/media/pfbg_768@2x.jpg differ diff --git a/awx/ui_next/src/App.jsx b/awx/ui_next/src/App.jsx index 704515c1138b..5833714d893d 100644 --- a/awx/ui_next/src/App.jsx +++ b/awx/ui_next/src/App.jsx @@ -1,220 +1,87 @@ -import React, { Component, Fragment } from 'react'; -import { withRouter } from 'react-router-dom'; -import { global_breakpoint_md } from '@patternfly/react-tokens'; +import React from 'react'; import { - Nav, - NavList, - Page, - PageHeader as PFPageHeader, - PageSidebar, -} from '@patternfly/react-core'; -import styled from 'styled-components'; -import { t } from '@lingui/macro'; -import { withI18n } from '@lingui/react'; - -import { ConfigAPI, MeAPI, RootAPI } from '@api'; -import About from '@components/About'; -import AlertModal from '@components/AlertModal'; -import NavExpandableGroup from '@components/NavExpandableGroup'; -import BrandLogo from '@components/BrandLogo'; -import PageHeaderToolbar from '@components/PageHeaderToolbar'; -import ErrorDetail from '@components/ErrorDetail'; -import { ConfigProvider } from '@contexts/Config'; - -const PageHeader = styled(PFPageHeader)` - & .pf-c-page__header-brand-link { - color: inherit; - - &:hover { - color: inherit; - } - - & svg { - height: 76px; - } - } -`; - -class App extends Component { - constructor(props) { - super(props); - - // initialize with a closed navbar if window size is small - const isNavOpen = - typeof window !== 'undefined' && - window.innerWidth >= parseInt(global_breakpoint_md.value, 10); - - this.state = { - ansible_version: null, - custom_virtualenvs: null, - me: null, - version: null, - isAboutModalOpen: false, - isNavOpen, - configError: null, - }; - - this.handleLogout = this.handleLogout.bind(this); - this.handleAboutClose = this.handleAboutClose.bind(this); - this.handleAboutOpen = this.handleAboutOpen.bind(this); - this.handleNavToggle = this.handleNavToggle.bind(this); - this.handleConfigErrorClose = this.handleConfigErrorClose.bind(this); - } - - async componentDidMount() { - await this.loadConfig(); - } - - // eslint-disable-next-line class-methods-use-this - async handleLogout() { - const { history } = this.props; - await RootAPI.logout(); - history.replace('/login'); - } - - handleAboutOpen() { - this.setState({ isAboutModalOpen: true }); - } - - handleAboutClose() { - this.setState({ isAboutModalOpen: false }); - } - - handleNavToggle() { - this.setState(({ isNavOpen }) => ({ isNavOpen: !isNavOpen })); - } - - handleConfigErrorClose() { - this.setState({ - configError: null, - }); - } - - async loadConfig() { - try { - const [configRes, meRes] = await Promise.all([ - ConfigAPI.read(), - MeAPI.read(), - ]); - const { - data: { - ansible_version, - custom_virtualenvs, - project_base_dir, - project_local_paths, - version, - }, - } = configRes; - const { - data: { - results: [me], - }, - } = meRes; - - this.setState({ - ansible_version, - custom_virtualenvs, - project_base_dir, - project_local_paths, - version, - me, - }); - } catch (err) { - this.setState({ configError: err }); - } - } - - render() { - const { - ansible_version, - custom_virtualenvs, - project_base_dir, - project_local_paths, - isAboutModalOpen, - isNavOpen, - me, - version, - configError, - } = this.state; - const { - i18n, - render = () => {}, - routeGroups = [], - navLabel = '', - } = this.props; - - const header = ( - } - logoProps={{ href: '/' }} - toolbar={ - - } - /> - ); - - const sidebar = ( - - - {routeGroups.map(({ groupId, groupTitle, routes }) => ( - - ))} - - - } - /> - ); - - return ( - - - - {render({ routeGroups })} - - - - - {i18n._(t`Failed to retrieve configuration.`)} - - - - ); + useRouteMatch, + useLocation, + HashRouter, + Route, + Switch, + Redirect, +} from 'react-router-dom'; +import { I18n, I18nProvider } from '@lingui/react'; + +import AppContainer from './components/AppContainer'; +import Background from './components/Background'; +import NotFound from './screens/NotFound'; +import Login from './screens/Login'; + +import ja from './locales/ja/messages'; +import en from './locales/en/messages'; +import { isAuthenticated } from './util/auth'; +import { getLanguageWithoutRegionCode } from './util/language'; + +import getRouteConfig from './routeConfig'; + +const ProtectedRoute = ({ children, ...rest }) => + isAuthenticated(document.cookie) ? ( + {children} + ) : ( + + ); + +function App() { + const catalogs = { en, ja }; + let language = getLanguageWithoutRegionCode(navigator); + if (!Object.keys(catalogs).includes(language)) { + // If there isn't a string catalog available for the browser's + // preferred language, default to one that has strings. + language = 'en'; } + const match = useRouteMatch(); + const { hash, search, pathname } = useLocation(); + + return ( + + + {({ i18n }) => ( + + + + + + + + + + + + + + + {getRouteConfig(i18n) + .flatMap(({ routes }) => routes) + .map(({ path, screen: Screen }) => ( + + + + )) + .concat( + + + + )} + + + + + + )} + + + ); } -export { App as _App }; -export default withI18n()(withRouter(App)); +export default () => ( + + + +); diff --git a/awx/ui_next/src/App.test.jsx b/awx/ui_next/src/App.test.jsx index 88a1f394a9af..a6fe414b3961 100644 --- a/awx/ui_next/src/App.test.jsx +++ b/awx/ui_next/src/App.test.jsx @@ -1,121 +1,17 @@ import React from 'react'; - -import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers'; -import { ConfigAPI, MeAPI, RootAPI } from '@api'; -import { asyncFlush } from '../jest.setup'; +import { act } from 'react-dom/test-utils'; +import { mountWithContexts } from '../testUtils/enzymeHelpers'; import App from './App'; jest.mock('./api'); describe('', () => { - const ansible_version = '111'; - const custom_virtualenvs = []; - const version = '222'; - - beforeEach(() => { - ConfigAPI.read = () => - Promise.resolve({ - data: { - ansible_version, - custom_virtualenvs, - version, - }, - }); - MeAPI.read = () => Promise.resolve({ data: { results: [{}] } }); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - test('expected content is rendered', () => { - const appWrapper = mountWithContexts( - - routeGroups.map(({ groupId }) =>
) - } - /> - ); - - // page components - expect(appWrapper.length).toBe(1); - expect(appWrapper.find('PageHeader').length).toBe(1); - expect(appWrapper.find('PageSidebar').length).toBe(1); - - // sidebar groups and route links - expect(appWrapper.find('NavExpandableGroup').length).toBe(2); - expect(appWrapper.find('a[href="/#/foo"]').length).toBe(1); - expect(appWrapper.find('a[href="/#/bar"]').length).toBe(1); - expect(appWrapper.find('a[href="/#/fiz"]').length).toBe(1); - - // inline render - expect(appWrapper.find('#group_one').length).toBe(1); - expect(appWrapper.find('#group_two').length).toBe(1); - }); - - test('opening the about modal renders prefetched config data', async done => { - const wrapper = mountWithContexts(); - wrapper.update(); - - // open about modal - const aboutDropdown = 'Dropdown QuestionCircleIcon'; - const aboutButton = 'DropdownItem li button'; - const aboutModalContent = 'AboutModalBoxContent'; - const aboutModalClose = 'button[aria-label="Close Dialog"]'; - - await waitForElement(wrapper, aboutDropdown); - wrapper.find(aboutDropdown).simulate('click'); - - const button = await waitForElement( - wrapper, - aboutButton, - el => !el.props().disabled - ); - button.simulate('click'); - - // check about modal content - const content = await waitForElement(wrapper, aboutModalContent); - expect(content.find('dd').text()).toContain(ansible_version); - expect(content.find('pre').text()).toContain(`< AWX ${version} >`); - - // close about modal - wrapper.find(aboutModalClose).simulate('click'); - expect(wrapper.find(aboutModalContent)).toHaveLength(0); - - done(); - }); - - test('handleNavToggle sets state.isNavOpen to opposite', () => { - const appWrapper = mountWithContexts().find('App'); - - const { handleNavToggle } = appWrapper.instance(); - [true, false, true, false, true].forEach(expected => { - expect(appWrapper.state().isNavOpen).toBe(expected); - handleNavToggle(); + test('renders ok', async () => { + let wrapper; + await act(async () => { + wrapper = mountWithContexts(); }); - }); - - test('onLogout makes expected call to api client', async done => { - const appWrapper = mountWithContexts().find('App'); - appWrapper.instance().handleLogout(); - await asyncFlush(); - expect(RootAPI.logout).toHaveBeenCalledTimes(1); - done(); + expect(wrapper.length).toBe(1); }); }); diff --git a/awx/ui_next/src/RootProvider.jsx b/awx/ui_next/src/RootProvider.jsx deleted file mode 100644 index 92b3e83776fa..000000000000 --- a/awx/ui_next/src/RootProvider.jsx +++ /dev/null @@ -1,27 +0,0 @@ -import React, { Component } from 'react'; -import { I18nProvider } from '@lingui/react'; - -import { HashRouter } from 'react-router-dom'; - -import { getLanguageWithoutRegionCode } from '@util/language'; -import ja from '../build/locales/ja/messages'; -import en from '../build/locales/en/messages'; - -class RootProvider extends Component { - render() { - const { children } = this.props; - - const catalogs = { en, ja }; - const language = getLanguageWithoutRegionCode(navigator); - - return ( - - - {children} - - - ); - } -} - -export default RootProvider; diff --git a/awx/ui_next/src/api/Base.js b/awx/ui_next/src/api/Base.js index 33e36269945b..cd0a76c1ec09 100644 --- a/awx/ui_next/src/api/Base.js +++ b/awx/ui_next/src/api/Base.js @@ -1,6 +1,13 @@ import axios from 'axios'; -import { encodeQueryString } from '@util/qs'; +import { SESSION_TIMEOUT_KEY } from '../constants'; +import { encodeQueryString } from '../util/qs'; +import debounce from '../util/debounce'; + +const updateStorage = debounce((key, val) => { + window.localStorage.setItem(key, val); + window.dispatchEvent(new Event('storage')); +}, 500); const defaultHttp = axios.create({ xsrfCookieName: 'csrftoken', @@ -10,6 +17,15 @@ const defaultHttp = axios.create({ }, }); +defaultHttp.interceptors.response.use(response => { + const timeout = response?.headers['session-timeout']; + if (timeout) { + const timeoutDate = new Date().getTime() + timeout * 1000; + updateStorage(SESSION_TIMEOUT_KEY, String(timeoutDate)); + } + return response; +}); + class Base { constructor(http = defaultHttp, baseURL) { this.http = http; @@ -45,6 +61,10 @@ class Base { update(id, data) { return this.http.patch(`${this.baseUrl}${id}/`, data); } + + copy(id, data) { + return this.http.post(`${this.baseUrl}${id}/copy/`, data); + } } export default Base; diff --git a/awx/ui_next/src/api/index.js b/awx/ui_next/src/api/index.js index f9acea42112c..cddf01e25924 100644 --- a/awx/ui_next/src/api/index.js +++ b/awx/ui_next/src/api/index.js @@ -1,11 +1,18 @@ +import ActivityStream from './models/ActivityStream'; import AdHocCommands from './models/AdHocCommands'; +import Applications from './models/Applications'; +import Auth from './models/Auth'; import Config from './models/Config'; +import CredentialInputSources from './models/CredentialInputSources'; import CredentialTypes from './models/CredentialTypes'; import Credentials from './models/Credentials'; +import Dashboard from './models/Dashboard'; import Groups from './models/Groups'; import Hosts from './models/Hosts'; import InstanceGroups from './models/InstanceGroups'; +import Instances from './models/Instances'; import Inventories from './models/Inventories'; +import InventoryScripts from './models/InventoryScripts'; import InventorySources from './models/InventorySources'; import InventoryUpdates from './models/InventoryUpdates'; import JobTemplates from './models/JobTemplates'; @@ -13,26 +20,41 @@ import Jobs from './models/Jobs'; import Labels from './models/Labels'; import Me from './models/Me'; import NotificationTemplates from './models/NotificationTemplates'; +import Notifications from './models/Notifications'; import Organizations from './models/Organizations'; -import Projects from './models/Projects'; import ProjectUpdates from './models/ProjectUpdates'; +import Projects from './models/Projects'; +import Roles from './models/Roles'; import Root from './models/Root'; +import Schedules from './models/Schedules'; +import Settings from './models/Settings'; import SystemJobs from './models/SystemJobs'; import Teams from './models/Teams'; +import Tokens from './models/Tokens'; import UnifiedJobTemplates from './models/UnifiedJobTemplates'; import UnifiedJobs from './models/UnifiedJobs'; import Users from './models/Users'; -import WorkflowJobs from './models/WorkflowJobs'; +import WorkflowApprovals from './models/WorkflowApprovals'; +import WorkflowApprovalTemplates from './models/WorkflowApprovalTemplates'; +import WorkflowJobTemplateNodes from './models/WorkflowJobTemplateNodes'; import WorkflowJobTemplates from './models/WorkflowJobTemplates'; +import WorkflowJobs from './models/WorkflowJobs'; +const ActivityStreamAPI = new ActivityStream(); const AdHocCommandsAPI = new AdHocCommands(); +const ApplicationsAPI = new Applications(); +const AuthAPI = new Auth(); const ConfigAPI = new Config(); -const CredentialsAPI = new Credentials(); +const CredentialInputSourcesAPI = new CredentialInputSources(); const CredentialTypesAPI = new CredentialTypes(); +const CredentialsAPI = new Credentials(); +const DashboardAPI = new Dashboard(); const GroupsAPI = new Groups(); const HostsAPI = new Hosts(); const InstanceGroupsAPI = new InstanceGroups(); +const InstancesAPI = new Instances(); const InventoriesAPI = new Inventories(); +const InventoryScriptsAPI = new InventoryScripts(); const InventorySourcesAPI = new InventorySources(); const InventoryUpdatesAPI = new InventoryUpdates(); const JobTemplatesAPI = new JobTemplates(); @@ -40,27 +62,42 @@ const JobsAPI = new Jobs(); const LabelsAPI = new Labels(); const MeAPI = new Me(); const NotificationTemplatesAPI = new NotificationTemplates(); +const NotificationsAPI = new Notifications(); const OrganizationsAPI = new Organizations(); -const ProjectsAPI = new Projects(); const ProjectUpdatesAPI = new ProjectUpdates(); +const ProjectsAPI = new Projects(); +const RolesAPI = new Roles(); const RootAPI = new Root(); +const SchedulesAPI = new Schedules(); +const SettingsAPI = new Settings(); const SystemJobsAPI = new SystemJobs(); const TeamsAPI = new Teams(); +const TokensAPI = new Tokens(); const UnifiedJobTemplatesAPI = new UnifiedJobTemplates(); const UnifiedJobsAPI = new UnifiedJobs(); const UsersAPI = new Users(); -const WorkflowJobsAPI = new WorkflowJobs(); +const WorkflowApprovalsAPI = new WorkflowApprovals(); +const WorkflowApprovalTemplatesAPI = new WorkflowApprovalTemplates(); +const WorkflowJobTemplateNodesAPI = new WorkflowJobTemplateNodes(); const WorkflowJobTemplatesAPI = new WorkflowJobTemplates(); +const WorkflowJobsAPI = new WorkflowJobs(); export { + ActivityStreamAPI, AdHocCommandsAPI, + ApplicationsAPI, + AuthAPI, ConfigAPI, - CredentialsAPI, + CredentialInputSourcesAPI, CredentialTypesAPI, + CredentialsAPI, + DashboardAPI, GroupsAPI, HostsAPI, InstanceGroupsAPI, + InstancesAPI, InventoriesAPI, + InventoryScriptsAPI, InventorySourcesAPI, InventoryUpdatesAPI, JobTemplatesAPI, @@ -68,15 +105,23 @@ export { LabelsAPI, MeAPI, NotificationTemplatesAPI, + NotificationsAPI, OrganizationsAPI, - ProjectsAPI, ProjectUpdatesAPI, + ProjectsAPI, + RolesAPI, RootAPI, + SchedulesAPI, + SettingsAPI, SystemJobsAPI, TeamsAPI, + TokensAPI, UnifiedJobTemplatesAPI, UnifiedJobsAPI, UsersAPI, - WorkflowJobsAPI, + WorkflowApprovalsAPI, + WorkflowApprovalTemplatesAPI, + WorkflowJobTemplateNodesAPI, WorkflowJobTemplatesAPI, + WorkflowJobsAPI, }; diff --git a/awx/ui_next/src/api/mixins/InstanceGroups.mixin.js b/awx/ui_next/src/api/mixins/InstanceGroups.mixin.js index e3b2be1cb51f..e6745ac52265 100644 --- a/awx/ui_next/src/api/mixins/InstanceGroups.mixin.js +++ b/awx/ui_next/src/api/mixins/InstanceGroups.mixin.js @@ -1,10 +1,9 @@ const InstanceGroupsMixin = parent => class extends parent { readInstanceGroups(resourceId, params) { - return this.http.get( - `${this.baseUrl}${resourceId}/instance_groups/`, - params - ); + return this.http.get(`${this.baseUrl}${resourceId}/instance_groups/`, { + params, + }); } associateInstanceGroup(resourceId, instanceGroupId) { diff --git a/awx/ui_next/src/api/mixins/Notifications.mixin.js b/awx/ui_next/src/api/mixins/Notifications.mixin.js index 0198f0054f67..87a7002ec5c3 100644 --- a/awx/ui_next/src/api/mixins/Notifications.mixin.js +++ b/awx/ui_next/src/api/mixins/Notifications.mixin.js @@ -87,6 +87,13 @@ const NotificationsMixin = parent => notificationId, notificationType ) { + if (notificationType === 'approvals') { + return this.associateNotificationTemplatesApprovals( + resourceId, + notificationId + ); + } + if (notificationType === 'started') { return this.associateNotificationTemplatesStarted( resourceId, @@ -126,6 +133,13 @@ const NotificationsMixin = parent => notificationId, notificationType ) { + if (notificationType === 'approvals') { + return this.disassociateNotificationTemplatesApprovals( + resourceId, + notificationId + ); + } + if (notificationType === 'started') { return this.disassociateNotificationTemplatesStarted( resourceId, diff --git a/awx/ui_next/src/api/mixins/Schedules.mixin.js b/awx/ui_next/src/api/mixins/Schedules.mixin.js new file mode 100644 index 000000000000..d7dad6d40abf --- /dev/null +++ b/awx/ui_next/src/api/mixins/Schedules.mixin.js @@ -0,0 +1,16 @@ +const SchedulesMixin = parent => + class extends parent { + createSchedule(id, data) { + return this.http.post(`${this.baseUrl}${id}/schedules/`, data); + } + + readSchedules(id, params) { + return this.http.get(`${this.baseUrl}${id}/schedules/`, { params }); + } + + readScheduleOptions(id) { + return this.http.options(`${this.baseUrl}${id}/schedules/`); + } + }; + +export default SchedulesMixin; diff --git a/awx/ui_next/src/api/models/ActivityStream.js b/awx/ui_next/src/api/models/ActivityStream.js new file mode 100644 index 000000000000..99b65bc63458 --- /dev/null +++ b/awx/ui_next/src/api/models/ActivityStream.js @@ -0,0 +1,10 @@ +import Base from '../Base'; + +class ActivityStream extends Base { + constructor(http) { + super(http); + this.baseUrl = '/api/v2/activity_stream/'; + } +} + +export default ActivityStream; diff --git a/awx/ui_next/src/api/models/Applications.js b/awx/ui_next/src/api/models/Applications.js new file mode 100644 index 000000000000..a8fe15f6949d --- /dev/null +++ b/awx/ui_next/src/api/models/Applications.js @@ -0,0 +1,20 @@ +import Base from '../Base'; + +class Applications extends Base { + constructor(http) { + super(http); + this.baseUrl = '/api/v2/applications/'; + } + + readTokens(appId, params) { + return this.http.get(`${this.baseUrl}${appId}/tokens/`, { + params, + }); + } + + readTokenOptions(appId) { + return this.http.options(`${this.baseUrl}${appId}/tokens/`); + } +} + +export default Applications; diff --git a/awx/ui_next/src/api/models/Auth.js b/awx/ui_next/src/api/models/Auth.js new file mode 100644 index 000000000000..5743b4f3d527 --- /dev/null +++ b/awx/ui_next/src/api/models/Auth.js @@ -0,0 +1,10 @@ +import Base from '../Base'; + +class Auth extends Base { + constructor(http) { + super(http); + this.baseUrl = '/api/v2/auth/'; + } +} + +export default Auth; diff --git a/awx/ui_next/src/api/models/Config.js b/awx/ui_next/src/api/models/Config.js index f8e89df245c5..878ddfad70b1 100644 --- a/awx/ui_next/src/api/models/Config.js +++ b/awx/ui_next/src/api/models/Config.js @@ -4,6 +4,7 @@ class Config extends Base { constructor(http) { super(http); this.baseUrl = '/api/v2/config/'; + this.read = this.read.bind(this); } } diff --git a/awx/ui_next/src/api/models/CredentialInputSources.js b/awx/ui_next/src/api/models/CredentialInputSources.js new file mode 100644 index 000000000000..ec09cba2673b --- /dev/null +++ b/awx/ui_next/src/api/models/CredentialInputSources.js @@ -0,0 +1,10 @@ +import Base from '../Base'; + +class CredentialInputSources extends Base { + constructor(http) { + super(http); + this.baseUrl = '/api/v2/credential_input_sources/'; + } +} + +export default CredentialInputSources; diff --git a/awx/ui_next/src/api/models/CredentialTypes.js b/awx/ui_next/src/api/models/CredentialTypes.js index 65906cdcbdee..39247b5ebcce 100644 --- a/awx/ui_next/src/api/models/CredentialTypes.js +++ b/awx/ui_next/src/api/models/CredentialTypes.js @@ -5,6 +5,32 @@ class CredentialTypes extends Base { super(http); this.baseUrl = '/api/v2/credential_types/'; } + + async loadAllTypes( + acceptableKinds = ['machine', 'cloud', 'net', 'ssh', 'vault', 'kubernetes'] + ) { + const pageSize = 200; + // The number of credential types a user can have is unlimited. In practice, it is unlikely for + // users to have more than a page at the maximum request size. + const { + data: { next, results }, + } = await this.read({ page_size: pageSize }); + let nextResults = []; + if (next) { + const { data } = await this.read({ + page_size: pageSize, + page: 2, + }); + nextResults = data.results; + } + return results + .concat(nextResults) + .filter(type => acceptableKinds.includes(type.kind)); + } + + test(id, data) { + return this.http.post(`${this.baseUrl}${id}/test/`, data); + } } export default CredentialTypes; diff --git a/awx/ui_next/src/api/models/CredentialTypes.test.js b/awx/ui_next/src/api/models/CredentialTypes.test.js new file mode 100644 index 000000000000..d68ff06a0cb1 --- /dev/null +++ b/awx/ui_next/src/api/models/CredentialTypes.test.js @@ -0,0 +1,68 @@ +import CredentialTypes from './CredentialTypes'; + +const typesData = [ + { id: 1, kind: 'machine' }, + { id: 2, kind: 'cloud' }, +]; + +describe('CredentialTypesAPI', () => { + test('should load all types', async () => { + const getPromise = () => + Promise.resolve({ + data: { + results: typesData, + }, + }); + const mockHttp = { get: jest.fn(getPromise) }; + const CredentialTypesAPI = new CredentialTypes(mockHttp); + + const types = await CredentialTypesAPI.loadAllTypes(); + + expect(mockHttp.get).toHaveBeenCalledTimes(1); + expect(mockHttp.get.mock.calls[0]).toEqual([ + `/api/v2/credential_types/`, + { params: { page_size: 200 } }, + ]); + expect(types).toEqual(typesData); + }); + + test('should load all types (2 pages)', async () => { + const getPromise = () => + Promise.resolve({ + data: { + results: typesData, + next: 2, + }, + }); + const mockHttp = { get: jest.fn(getPromise) }; + const CredentialTypesAPI = new CredentialTypes(mockHttp); + + const types = await CredentialTypesAPI.loadAllTypes(); + + expect(mockHttp.get).toHaveBeenCalledTimes(2); + expect(mockHttp.get.mock.calls[0]).toEqual([ + `/api/v2/credential_types/`, + { params: { page_size: 200 } }, + ]); + expect(mockHttp.get.mock.calls[1]).toEqual([ + `/api/v2/credential_types/`, + { params: { page_size: 200, page: 2 } }, + ]); + expect(types).toHaveLength(4); + }); + + test('should filter by acceptable kinds', async () => { + const getPromise = () => + Promise.resolve({ + data: { + results: typesData, + }, + }); + const mockHttp = { get: jest.fn(getPromise) }; + const CredentialTypesAPI = new CredentialTypes(mockHttp); + + const types = await CredentialTypesAPI.loadAllTypes(['machine']); + + expect(types).toEqual([typesData[0]]); + }); +}); diff --git a/awx/ui_next/src/api/models/Credentials.js b/awx/ui_next/src/api/models/Credentials.js index 2e3634b4e5f0..1560357bd13f 100644 --- a/awx/ui_next/src/api/models/Credentials.js +++ b/awx/ui_next/src/api/models/Credentials.js @@ -4,6 +4,58 @@ class Credentials extends Base { constructor(http) { super(http); this.baseUrl = '/api/v2/credentials/'; + + this.readAccessList = this.readAccessList.bind(this); + this.readAccessOptions = this.readAccessOptions.bind(this); + this.readInputSources = this.readInputSources.bind(this); + } + + readAccessList(id, params) { + return this.http.get(`${this.baseUrl}${id}/access_list/`, { + params, + }); + } + + readAccessOptions(id) { + return this.http.options(`${this.baseUrl}${id}/access_list/`); + } + + readInputSources(id) { + const maxRequests = 5; + let requestCounter = 0; + const fetchInputSources = async (pageNo = 1, inputSources = []) => { + try { + requestCounter++; + const { data } = await this.http.get( + `${this.baseUrl}${id}/input_sources/`, + { + params: { + page: pageNo, + page_size: 200, + }, + } + ); + if (data?.next && requestCounter <= maxRequests) { + return fetchInputSources( + pageNo + 1, + inputSources.concat(data.results) + ); + } + return Promise.resolve({ + data: { + results: inputSources.concat(data.results), + }, + }); + } catch (error) { + return Promise.reject(error); + } + }; + + return fetchInputSources(); + } + + test(id, data) { + return this.http.post(`${this.baseUrl}${id}/test/`, data); } } diff --git a/awx/ui_next/src/api/models/Dashboard.js b/awx/ui_next/src/api/models/Dashboard.js new file mode 100644 index 000000000000..aa1d86340a2a --- /dev/null +++ b/awx/ui_next/src/api/models/Dashboard.js @@ -0,0 +1,16 @@ +import Base from '../Base'; + +class Dashboard extends Base { + constructor(http) { + super(http); + this.baseUrl = '/api/v2/dashboard/'; + } + + readJobGraph(params) { + return this.http.get(`${this.baseUrl}graphs/jobs/`, { + params, + }); + } +} + +export default Dashboard; diff --git a/awx/ui_next/src/api/models/Groups.js b/awx/ui_next/src/api/models/Groups.js index 019ba0ea949c..3f9aa928e033 100644 --- a/awx/ui_next/src/api/models/Groups.js +++ b/awx/ui_next/src/api/models/Groups.js @@ -4,6 +4,55 @@ class Groups extends Base { constructor(http) { super(http); this.baseUrl = '/api/v2/groups/'; + + this.associateHost = this.associateHost.bind(this); + this.createHost = this.createHost.bind(this); + this.readAllHosts = this.readAllHosts.bind(this); + this.disassociateHost = this.disassociateHost.bind(this); + } + + associateHost(id, hostId) { + return this.http.post(`${this.baseUrl}${id}/hosts/`, { + id: hostId, + }); + } + + createHost(id, data) { + return this.http.post(`${this.baseUrl}${id}/hosts/`, data); + } + + readAllHosts(id, params) { + return this.http.get(`${this.baseUrl}${id}/all_hosts/`, { + params, + }); + } + + disassociateHost(id, host) { + return this.http.post(`${this.baseUrl}${id}/hosts/`, { + id: host.id, + disassociate: true, + }); + } + + readChildren(id, params) { + return this.http.get(`${this.baseUrl}${id}/children/`, { params }); + } + + associateChildGroup(id, childId) { + return this.http.post(`${this.baseUrl}${id}/children/`, { id: childId }); + } + + disassociateChildGroup(id, childId) { + return this.http.post(`${this.baseUrl}${id}/children/`, { + disassociate: id, + id: childId, + }); + } + + readPotentialGroups(id, params) { + return this.http.get(`${this.baseUrl}${id}/potential_children/`, { + params, + }); } } diff --git a/awx/ui_next/src/api/models/Hosts.js b/awx/ui_next/src/api/models/Hosts.js index d36b5f15a3af..ae90bf2826f9 100644 --- a/awx/ui_next/src/api/models/Hosts.js +++ b/awx/ui_next/src/api/models/Hosts.js @@ -4,6 +4,35 @@ class Hosts extends Base { constructor(http) { super(http); this.baseUrl = '/api/v2/hosts/'; + + this.readFacts = this.readFacts.bind(this); + this.readAllGroups = this.readAllGroups.bind(this); + this.readGroupsOptions = this.readGroupsOptions.bind(this); + this.associateGroup = this.associateGroup.bind(this); + this.disassociateGroup = this.disassociateGroup.bind(this); + } + + readFacts(id) { + return this.http.get(`${this.baseUrl}${id}/ansible_facts/`); + } + + readAllGroups(id, params) { + return this.http.get(`${this.baseUrl}${id}/all_groups/`, { params }); + } + + readGroupsOptions(id) { + return this.http.options(`${this.baseUrl}${id}/groups/`); + } + + associateGroup(id, groupId) { + return this.http.post(`${this.baseUrl}${id}/groups/`, { id: groupId }); + } + + disassociateGroup(id, group) { + return this.http.post(`${this.baseUrl}${id}/groups/`, { + id: group.id, + disassociate: true, + }); } } diff --git a/awx/ui_next/src/api/models/InstanceGroups.js b/awx/ui_next/src/api/models/InstanceGroups.js index 94464e0f42a0..82704c95d156 100644 --- a/awx/ui_next/src/api/models/InstanceGroups.js +++ b/awx/ui_next/src/api/models/InstanceGroups.js @@ -4,6 +4,37 @@ class InstanceGroups extends Base { constructor(http) { super(http); this.baseUrl = '/api/v2/instance_groups/'; + + this.associateInstance = this.associateInstance.bind(this); + this.disassociateInstance = this.disassociateInstance.bind(this); + this.readInstanceOptions = this.readInstanceOptions.bind(this); + this.readInstances = this.readInstances.bind(this); + this.readJobs = this.readJobs.bind(this); + } + + associateInstance(instanceGroupId, instanceId) { + return this.http.post(`${this.baseUrl}${instanceGroupId}/instances/`, { + id: instanceId, + }); + } + + disassociateInstance(instanceGroupId, instanceId) { + return this.http.post(`${this.baseUrl}${instanceGroupId}/instances/`, { + id: instanceId, + disassociate: true, + }); + } + + readInstances(id, params) { + return this.http.get(`${this.baseUrl}${id}/instances/`, { params }); + } + + readInstanceOptions(id) { + return this.http.options(`${this.baseUrl}${id}/instances/`); + } + + readJobs(id) { + return this.http.get(`${this.baseUrl}${id}/jobs/`); } } diff --git a/awx/ui_next/src/api/models/Instances.js b/awx/ui_next/src/api/models/Instances.js new file mode 100644 index 000000000000..41fa06d5f750 --- /dev/null +++ b/awx/ui_next/src/api/models/Instances.js @@ -0,0 +1,10 @@ +import Base from '../Base'; + +class Instances extends Base { + constructor(http) { + super(http); + this.baseUrl = '/api/v2/instances/'; + } +} + +export default Instances; diff --git a/awx/ui_next/src/api/models/Inventories.js b/awx/ui_next/src/api/models/Inventories.js index 08640173d413..c9d774e002b8 100644 --- a/awx/ui_next/src/api/models/Inventories.js +++ b/awx/ui_next/src/api/models/Inventories.js @@ -7,7 +7,9 @@ class Inventories extends InstanceGroupsMixin(Base) { this.baseUrl = '/api/v2/inventories/'; this.readAccessList = this.readAccessList.bind(this); + this.readAccessOptions = this.readAccessOptions.bind(this); this.readHosts = this.readHosts.bind(this); + this.readHostDetail = this.readHostDetail.bind(this); this.readGroups = this.readGroups.bind(this); this.readGroupsOptions = this.readGroupsOptions.bind(this); this.promoteGroup = this.promoteGroup.bind(this); @@ -19,28 +21,95 @@ class Inventories extends InstanceGroupsMixin(Base) { }); } + readAccessOptions(id) { + return this.http.options(`${this.baseUrl}${id}/access_list/`); + } + createHost(id, data) { return this.http.post(`${this.baseUrl}${id}/hosts/`, data); } readHosts(id, params) { - return this.http.get(`${this.baseUrl}${id}/hosts/`, { params }); + return this.http.get(`${this.baseUrl}${id}/hosts/`, { + params, + }); + } + + async readHostDetail(inventoryId, hostId) { + const { + data: { results }, + } = await this.http.get( + `${this.baseUrl}${inventoryId}/hosts/?id=${hostId}` + ); + + if (Array.isArray(results) && results.length) { + return results[0]; + } + + throw new Error( + `How did you get here? Host not found for Inventory ID: ${inventoryId}` + ); } readGroups(id, params) { - return this.http.get(`${this.baseUrl}${id}/groups/`, { params }); + return this.http.get(`${this.baseUrl}${id}/groups/`, { + params, + }); } readGroupsOptions(id) { return this.http.options(`${this.baseUrl}${id}/groups/`); } + readHostsOptions(id) { + return this.http.options(`${this.baseUrl}${id}/hosts/`); + } + promoteGroup(inventoryId, groupId) { return this.http.post(`${this.baseUrl}${inventoryId}/groups/`, { id: groupId, disassociate: true, }); } + + readSources(inventoryId, params) { + return this.http.get(`${this.baseUrl}${inventoryId}/inventory_sources/`, { + params, + }); + } + + async readSourceDetail(inventoryId, sourceId) { + const { + data: { results }, + } = await this.http.get( + `${this.baseUrl}${inventoryId}/inventory_sources/?id=${sourceId}` + ); + + if (Array.isArray(results) && results.length) { + return results[0]; + } + + throw new Error( + `How did you get here? Source not found for Inventory ID: ${inventoryId}` + ); + } + + syncAllSources(inventoryId) { + return this.http.post( + `${this.baseUrl}${inventoryId}/update_inventory_sources/` + ); + } + + readAdHocOptions(inventoryId) { + return this.http.options(`${this.baseUrl}${inventoryId}/ad_hoc_commands/`); + } + + launchAdHocCommands(inventoryId, values) { + return this.http.post( + `${this.baseUrl}${inventoryId}/ad_hoc_commands/`, + values + ); + } } export default Inventories; diff --git a/awx/ui_next/src/api/models/InventoryScripts.js b/awx/ui_next/src/api/models/InventoryScripts.js new file mode 100644 index 000000000000..17214cd5fd81 --- /dev/null +++ b/awx/ui_next/src/api/models/InventoryScripts.js @@ -0,0 +1,10 @@ +import Base from '../Base'; + +class InventoryScripts extends Base { + constructor(http) { + super(http); + this.baseUrl = '/api/v2/inventory_scripts/'; + } +} + +export default InventoryScripts; diff --git a/awx/ui_next/src/api/models/InventorySources.js b/awx/ui_next/src/api/models/InventorySources.js index be43f988eb0b..8d20076ba815 100644 --- a/awx/ui_next/src/api/models/InventorySources.js +++ b/awx/ui_next/src/api/models/InventorySources.js @@ -1,11 +1,32 @@ import Base from '../Base'; +import NotificationsMixin from '../mixins/Notifications.mixin'; import LaunchUpdateMixin from '../mixins/LaunchUpdate.mixin'; +import SchedulesMixin from '../mixins/Schedules.mixin'; -class InventorySources extends LaunchUpdateMixin(Base) { +class InventorySources extends LaunchUpdateMixin( + NotificationsMixin(SchedulesMixin(Base)) +) { constructor(http) { super(http); this.baseUrl = '/api/v2/inventory_sources/'; + + this.createSyncStart = this.createSyncStart.bind(this); + this.destroyGroups = this.destroyGroups.bind(this); + this.destroyHosts = this.destroyHosts.bind(this); + } + + createSyncStart(sourceId, extraVars) { + return this.http.post(`${this.baseUrl}${sourceId}/update/`, { + extra_vars: extraVars, + }); } -} + destroyGroups(id) { + return this.http.delete(`${this.baseUrl}${id}/groups/`); + } + + destroyHosts(id) { + return this.http.delete(`${this.baseUrl}${id}/hosts/`); + } +} export default InventorySources; diff --git a/awx/ui_next/src/api/models/InventoryUpdates.js b/awx/ui_next/src/api/models/InventoryUpdates.js index a4dc05b3922d..1700c7b26b46 100644 --- a/awx/ui_next/src/api/models/InventoryUpdates.js +++ b/awx/ui_next/src/api/models/InventoryUpdates.js @@ -5,7 +5,11 @@ class InventoryUpdates extends LaunchUpdateMixin(Base) { constructor(http) { super(http); this.baseUrl = '/api/v2/inventory_updates/'; + this.createSyncCancel = this.createSyncCancel.bind(this); } -} + createSyncCancel(sourceId) { + return this.http.post(`${this.baseUrl}${sourceId}/cancel/`); + } +} export default InventoryUpdates; diff --git a/awx/ui_next/src/api/models/JobTemplates.js b/awx/ui_next/src/api/models/JobTemplates.js index cec450ce0d47..44281f1511b3 100644 --- a/awx/ui_next/src/api/models/JobTemplates.js +++ b/awx/ui_next/src/api/models/JobTemplates.js @@ -1,8 +1,11 @@ import Base from '../Base'; import NotificationsMixin from '../mixins/Notifications.mixin'; import InstanceGroupsMixin from '../mixins/InstanceGroups.mixin'; +import SchedulesMixin from '../mixins/Schedules.mixin'; -class JobTemplates extends InstanceGroupsMixin(NotificationsMixin(Base)) { +class JobTemplates extends SchedulesMixin( + InstanceGroupsMixin(NotificationsMixin(Base)) +) { constructor(http) { super(http); this.baseUrl = '/api/v2/job_templates/'; @@ -13,19 +16,27 @@ class JobTemplates extends InstanceGroupsMixin(NotificationsMixin(Base)) { this.disassociateLabel = this.disassociateLabel.bind(this); this.readCredentials = this.readCredentials.bind(this); this.readAccessList = this.readAccessList.bind(this); - this.generateLabel = this.generateLabel.bind(this); + this.readAccessOptions = this.readAccessOptions.bind(this); + this.readWebhookKey = this.readWebhookKey.bind(this); } launch(id, data) { return this.http.post(`${this.baseUrl}${id}/launch/`, data); } + readTemplateOptions(id) { + return this.http.options(`${this.baseUrl}${id}/`); + } + readLaunch(id) { return this.http.get(`${this.baseUrl}${id}/launch/`); } - associateLabel(id, label) { - return this.http.post(`${this.baseUrl}${id}/labels/`, label); + associateLabel(id, label, orgId) { + return this.http.post(`${this.baseUrl}${id}/labels/`, { + name: label.name, + organization: orgId, + }); } disassociateLabel(id, label) { @@ -35,15 +46,10 @@ class JobTemplates extends InstanceGroupsMixin(NotificationsMixin(Base)) { }); } - generateLabel(id, label, orgId) { - return this.http.post(`${this.baseUrl}${id}/labels/`, { - name: label.name, - organization: orgId, - }); - } - readCredentials(id, params) { - return this.http.get(`${this.baseUrl}${id}/credentials/`, { params }); + return this.http.get(`${this.baseUrl}${id}/credentials/`, { + params, + }); } associateCredentials(id, credentialId) { @@ -60,7 +66,39 @@ class JobTemplates extends InstanceGroupsMixin(NotificationsMixin(Base)) { } readAccessList(id, params) { - return this.http.get(`${this.baseUrl}${id}/access_list/`, { params }); + return this.http.get(`${this.baseUrl}${id}/access_list/`, { + params, + }); + } + + readAccessOptions(id) { + return this.http.options(`${this.baseUrl}${id}/access_list/`); + } + + readScheduleList(id, params) { + return this.http.get(`${this.baseUrl}${id}/schedules/`, { + params, + }); + } + + readSurvey(id) { + return this.http.get(`${this.baseUrl}${id}/survey_spec/`); + } + + updateSurvey(id, survey) { + return this.http.post(`${this.baseUrl}${id}/survey_spec/`, survey); + } + + destroySurvey(id) { + return this.http.delete(`${this.baseUrl}${id}/survey_spec/`); + } + + readWebhookKey(id) { + return this.http.get(`${this.baseUrl}${id}/webhook_key/`); + } + + updateWebhookKey(id) { + return this.http.post(`${this.baseUrl}${id}/webhook_key/`); } } diff --git a/awx/ui_next/src/api/models/Jobs.js b/awx/ui_next/src/api/models/Jobs.js index 06b929c0ba4b..fc9bbb233449 100644 --- a/awx/ui_next/src/api/models/Jobs.js +++ b/awx/ui_next/src/api/models/Jobs.js @@ -1,13 +1,29 @@ import Base from '../Base'; import RelaunchMixin from '../mixins/Relaunch.mixin'; -const BASE_URLS = { - playbook: '/jobs/', - project: '/project_updates/', - system: '/system_jobs/', - inventory: '/inventory_updates/', - command: '/ad_hoc_commands/', - workflow: '/workflow_jobs/', +const getBaseURL = type => { + switch (type) { + case 'playbook': + case 'job': + return '/jobs/'; + case 'project': + case 'project_update': + return '/project_updates/'; + case 'system': + case 'system_job': + return '/system_jobs/'; + case 'inventory': + case 'inventory_update': + return '/inventory_updates/'; + case 'command': + case 'ad_hoc_command': + return '/ad_hoc_commands/'; + case 'workflow': + case 'workflow_job': + return '/workflow_jobs/'; + default: + throw new Error('Unable to find matching job type'); + } }; class Jobs extends RelaunchMixin(Base) { @@ -16,16 +32,24 @@ class Jobs extends RelaunchMixin(Base) { this.baseUrl = '/api/v2/jobs/'; } + cancel(id, type) { + return this.http.post(`/api/v2${getBaseURL(type)}${id}/cancel/`); + } + + readCredentials(id, type) { + return this.http.get(`/api/v2${getBaseURL(type)}${id}/credentials/`); + } + readDetail(id, type) { - return this.http.get(`/api/v2${BASE_URLS[type]}${id}/`); + return this.http.get(`/api/v2${getBaseURL(type)}${id}/`); } readEvents(id, type = 'playbook', params = {}) { let endpoint; if (type === 'playbook') { - endpoint = `/api/v2${BASE_URLS[type]}${id}/job_events/`; + endpoint = `/api/v2${getBaseURL(type)}${id}/job_events/`; } else { - endpoint = `/api/v2${BASE_URLS[type]}${id}/events/`; + endpoint = `/api/v2${getBaseURL(type)}${id}/events/`; } return this.http.get(endpoint, { params }); } diff --git a/awx/ui_next/src/api/models/NotificationTemplates.js b/awx/ui_next/src/api/models/NotificationTemplates.js index 7736921ad256..69cd5f40228a 100644 --- a/awx/ui_next/src/api/models/NotificationTemplates.js +++ b/awx/ui_next/src/api/models/NotificationTemplates.js @@ -5,6 +5,10 @@ class NotificationTemplates extends Base { super(http); this.baseUrl = '/api/v2/notification_templates/'; } + + test(id) { + return this.http.post(`${this.baseUrl}${id}/test/`); + } } export default NotificationTemplates; diff --git a/awx/ui_next/src/api/models/Notifications.js b/awx/ui_next/src/api/models/Notifications.js new file mode 100644 index 000000000000..68405c0986bc --- /dev/null +++ b/awx/ui_next/src/api/models/Notifications.js @@ -0,0 +1,10 @@ +import Base from '../Base'; + +class Notifications extends Base { + constructor(http) { + super(http); + this.baseUrl = '/api/v2/notifications/'; + } +} + +export default Notifications; diff --git a/awx/ui_next/src/api/models/Organizations.js b/awx/ui_next/src/api/models/Organizations.js index 3cbe64c28452..ce236067b4b2 100644 --- a/awx/ui_next/src/api/models/Organizations.js +++ b/awx/ui_next/src/api/models/Organizations.js @@ -12,9 +12,61 @@ class Organizations extends InstanceGroupsMixin(NotificationsMixin(Base)) { return this.http.get(`${this.baseUrl}${id}/access_list/`, { params }); } + readAccessOptions(id) { + return this.http.options(`${this.baseUrl}${id}/access_list/`); + } + readTeams(id, params) { return this.http.get(`${this.baseUrl}${id}/teams/`, { params }); } + + readTeamsOptions(id) { + return this.http.options(`${this.baseUrl}${id}/teams/`); + } + + readGalaxyCredentials(id, params) { + return this.http.get(`${this.baseUrl}${id}/galaxy_credentials/`, { + params, + }); + } + + createUser(id, data) { + return this.http.post(`${this.baseUrl}${id}/users/`, data); + } + + readNotificationTemplatesApprovals(id, params) { + return this.http.get( + `${this.baseUrl}${id}/notification_templates_approvals/`, + { params } + ); + } + + associateNotificationTemplatesApprovals(resourceId, notificationId) { + return this.http.post( + `${this.baseUrl}${resourceId}/notification_templates_approvals/`, + { id: notificationId } + ); + } + + disassociateNotificationTemplatesApprovals(resourceId, notificationId) { + return this.http.post( + `${this.baseUrl}${resourceId}/notification_templates_approvals/`, + { id: notificationId, disassociate: true } + ); + } + + associateGalaxyCredential(resourceId, credentialId) { + return this.http.post(`${this.baseUrl}${resourceId}/galaxy_credentials/`, { + id: credentialId, + }); + } + + disassociateGalaxyCredential(resourceId, credentialId) { + return this.http.post(`${this.baseUrl}${resourceId}/galaxy_credentials/`, { + id: credentialId, + disassociate: true, + }); + } } export default Organizations; diff --git a/awx/ui_next/src/api/models/Organizations.test.jsx b/awx/ui_next/src/api/models/Organizations.test.jsx index cd22a09bb891..d9f45c954817 100644 --- a/awx/ui_next/src/api/models/Organizations.test.jsx +++ b/awx/ui_next/src/api/models/Organizations.test.jsx @@ -1,5 +1,5 @@ -import Organizations from './Organizations'; import { describeNotificationMixin } from '../../../testUtils/apiReusable'; +import Organizations from './Organizations'; describe('OrganizationsAPI', () => { const orgId = 1; diff --git a/awx/ui_next/src/api/models/Projects.js b/awx/ui_next/src/api/models/Projects.js index 742150e5aaa7..38879a2bc2ef 100644 --- a/awx/ui_next/src/api/models/Projects.js +++ b/awx/ui_next/src/api/models/Projects.js @@ -1,13 +1,18 @@ import Base from '../Base'; import NotificationsMixin from '../mixins/Notifications.mixin'; import LaunchUpdateMixin from '../mixins/LaunchUpdate.mixin'; +import SchedulesMixin from '../mixins/Schedules.mixin'; -class Projects extends LaunchUpdateMixin(NotificationsMixin(Base)) { +class Projects extends SchedulesMixin( + LaunchUpdateMixin(NotificationsMixin(Base)) +) { constructor(http) { super(http); this.baseUrl = '/api/v2/projects/'; this.readAccessList = this.readAccessList.bind(this); + this.readAccessOptions = this.readAccessOptions.bind(this); + this.readInventories = this.readInventories.bind(this); this.readPlaybooks = this.readPlaybooks.bind(this); this.readSync = this.readSync.bind(this); this.sync = this.sync.bind(this); @@ -17,6 +22,14 @@ class Projects extends LaunchUpdateMixin(NotificationsMixin(Base)) { return this.http.get(`${this.baseUrl}${id}/access_list/`, { params }); } + readAccessOptions(id) { + return this.http.options(`${this.baseUrl}${id}/access_list/`); + } + + readInventories(id) { + return this.http.get(`${this.baseUrl}${id}/inventories/`); + } + readPlaybooks(id) { return this.http.get(`${this.baseUrl}${id}/playbooks/`); } diff --git a/awx/ui_next/src/api/models/Roles.js b/awx/ui_next/src/api/models/Roles.js new file mode 100644 index 000000000000..3f89c8eb5fcc --- /dev/null +++ b/awx/ui_next/src/api/models/Roles.js @@ -0,0 +1,23 @@ +import Base from '../Base'; + +class Roles extends Base { + constructor(http) { + super(http); + this.baseUrl = '/api/v2/roles/'; + } + + disassociateUserRole(roleId, userId) { + return this.http.post(`${this.baseUrl}${roleId}/users/`, { + disassociate: true, + id: userId, + }); + } + + disassociateTeamRole(roleId, teamId) { + return this.http.post(`${this.baseUrl}${roleId}/teams/`, { + disassociate: true, + id: teamId, + }); + } +} +export default Roles; diff --git a/awx/ui_next/src/api/models/Root.js b/awx/ui_next/src/api/models/Root.js index ffba170c4172..e930d3cc59de 100644 --- a/awx/ui_next/src/api/models/Root.js +++ b/awx/ui_next/src/api/models/Root.js @@ -25,6 +25,14 @@ class Root extends Base { logout() { return this.http.get(`${this.baseUrl}logout/`); } + + readAssetVariables() { + // TODO: There's better ways of doing this. Build tools, scripts, + // automation etc. should relocate this variable file to an importable + // location in src prior to building. That said, a raw http call + // works for now. + return this.http.get('/static/media/default.strings.json'); + } } export default Root; diff --git a/awx/ui_next/src/api/models/Schedules.js b/awx/ui_next/src/api/models/Schedules.js new file mode 100644 index 000000000000..7f20e992ae4d --- /dev/null +++ b/awx/ui_next/src/api/models/Schedules.js @@ -0,0 +1,22 @@ +import Base from '../Base'; + +class Schedules extends Base { + constructor(http) { + super(http); + this.baseUrl = '/api/v2/schedules/'; + } + + createPreview(data) { + return this.http.post(`${this.baseUrl}preview/`, data); + } + + readCredentials(resourceId, params) { + return this.http.get(`${this.baseUrl}${resourceId}/credentials/`, params); + } + + readZoneInfo() { + return this.http.get(`${this.baseUrl}zoneinfo/`); + } +} + +export default Schedules; diff --git a/awx/ui_next/src/api/models/Settings.js b/awx/ui_next/src/api/models/Settings.js new file mode 100644 index 000000000000..3c85f68da64c --- /dev/null +++ b/awx/ui_next/src/api/models/Settings.js @@ -0,0 +1,30 @@ +import Base from '../Base'; + +class Settings extends Base { + constructor(http) { + super(http); + this.baseUrl = '/api/v2/settings/'; + } + + readAllOptions() { + return this.http.options(`${this.baseUrl}all/`); + } + + updateAll(data) { + return this.http.patch(`${this.baseUrl}all/`, data); + } + + readCategory(category) { + return this.http.get(`${this.baseUrl}${category}/`); + } + + readCategoryOptions(category) { + return this.http.options(`${this.baseUrl}${category}/`); + } + + createTest(category, data) { + return this.http.post(`${this.baseUrl}${category}/test/`, data); + } +} + +export default Settings; diff --git a/awx/ui_next/src/api/models/Teams.js b/awx/ui_next/src/api/models/Teams.js index 585eb1086d90..180c59032ce1 100644 --- a/awx/ui_next/src/api/models/Teams.js +++ b/awx/ui_next/src/api/models/Teams.js @@ -7,7 +7,9 @@ class Teams extends Base { } associateRole(teamId, roleId) { - return this.http.post(`${this.baseUrl}${teamId}/roles/`, { id: roleId }); + return this.http.post(`${this.baseUrl}${teamId}/roles/`, { + id: roleId, + }); } disassociateRole(teamId, roleId) { @@ -16,6 +18,30 @@ class Teams extends Base { disassociate: true, }); } + + readRoles(teamId, params) { + return this.http.get(`${this.baseUrl}${teamId}/roles/`, { + params, + }); + } + + readRoleOptions(teamId) { + return this.http.options(`${this.baseUrl}${teamId}/roles/`); + } + + readAccessList(teamId, params) { + return this.http.get(`${this.baseUrl}${teamId}/access_list/`, { + params, + }); + } + + readAccessOptions(id) { + return this.http.options(`${this.baseUrl}${id}/access_list/`); + } + + readUsersAccessOptions(teamId) { + return this.http.options(`${this.baseUrl}${teamId}/users/`); + } } export default Teams; diff --git a/awx/ui_next/src/api/models/Teams.test.jsx b/awx/ui_next/src/api/models/Teams.test.jsx index 9bcbe654130b..b618b1dddd63 100644 --- a/awx/ui_next/src/api/models/Teams.test.jsx +++ b/awx/ui_next/src/api/models/Teams.test.jsx @@ -16,10 +16,9 @@ describe('TeamsAPI', () => { await TeamsAPI.associateRole(teamId, roleId); expect(mockHttp.post).toHaveBeenCalledTimes(1); - expect(mockHttp.post.mock.calls[0]).toContainEqual( - `/api/v2/teams/${teamId}/roles/`, - { id: roleId } - ); + expect( + mockHttp.post.mock.calls[0] + ).toContainEqual(`/api/v2/teams/${teamId}/roles/`, { id: roleId }); done(); }); diff --git a/awx/ui_next/src/api/models/Tokens.js b/awx/ui_next/src/api/models/Tokens.js new file mode 100644 index 000000000000..5dd490808d81 --- /dev/null +++ b/awx/ui_next/src/api/models/Tokens.js @@ -0,0 +1,10 @@ +import Base from '../Base'; + +class Tokens extends Base { + constructor(http) { + super(http); + this.baseUrl = '/api/v2/tokens/'; + } +} + +export default Tokens; diff --git a/awx/ui_next/src/api/models/Users.js b/awx/ui_next/src/api/models/Users.js index a76c4e49b7d8..c9d47826e2e8 100644 --- a/awx/ui_next/src/api/models/Users.js +++ b/awx/ui_next/src/api/models/Users.js @@ -7,7 +7,13 @@ class Users extends Base { } associateRole(userId, roleId) { - return this.http.post(`${this.baseUrl}${userId}/roles/`, { id: roleId }); + return this.http.post(`${this.baseUrl}${userId}/roles/`, { + id: roleId, + }); + } + + createToken(userId, data) { + return this.http.post(`${this.baseUrl}${userId}/authorized_tokens/`, data); } disassociateRole(userId, roleId) { @@ -16,6 +22,48 @@ class Users extends Base { disassociate: true, }); } + + readOrganizations(userId, params) { + return this.http.get(`${this.baseUrl}${userId}/organizations/`, { + params, + }); + } + + readRoles(userId, params) { + return this.http.get(`${this.baseUrl}${userId}/roles/`, { + params, + }); + } + + readRoleOptions(userId) { + return this.http.options(`${this.baseUrl}${userId}/roles/`); + } + + readTeams(userId, params) { + return this.http.get(`${this.baseUrl}${userId}/teams/`, { + params, + }); + } + + readTeamsOptions(userId) { + return this.http.options(`${this.baseUrl}${userId}/teams/`); + } + + readTokens(userId, params) { + return this.http.get(`${this.baseUrl}${userId}/tokens/`, { + params, + }); + } + + readAdminOfOrganizations(userId, params) { + return this.http.get(`${this.baseUrl}${userId}/admin_of_organizations/`, { + params, + }); + } + + readTokenOptions(userId) { + return this.http.options(`${this.baseUrl}${userId}/tokens/`); + } } export default Users; diff --git a/awx/ui_next/src/api/models/Users.test.jsx b/awx/ui_next/src/api/models/Users.test.jsx index 10ec37411da5..6eb662f4d7a5 100644 --- a/awx/ui_next/src/api/models/Users.test.jsx +++ b/awx/ui_next/src/api/models/Users.test.jsx @@ -16,10 +16,9 @@ describe('UsersAPI', () => { await UsersAPI.associateRole(userId, roleId); expect(mockHttp.post).toHaveBeenCalledTimes(1); - expect(mockHttp.post.mock.calls[0]).toContainEqual( - `/api/v2/users/${userId}/roles/`, - { id: roleId } - ); + expect( + mockHttp.post.mock.calls[0] + ).toContainEqual(`/api/v2/users/${userId}/roles/`, { id: roleId }); done(); }); diff --git a/awx/ui_next/src/api/models/WorkflowApprovalTemplates.js b/awx/ui_next/src/api/models/WorkflowApprovalTemplates.js new file mode 100644 index 000000000000..83b14784ab74 --- /dev/null +++ b/awx/ui_next/src/api/models/WorkflowApprovalTemplates.js @@ -0,0 +1,10 @@ +import Base from '../Base'; + +class WorkflowApprovalTemplates extends Base { + constructor(http) { + super(http); + this.baseUrl = '/api/v2/workflow_approval_templates/'; + } +} + +export default WorkflowApprovalTemplates; diff --git a/awx/ui_next/src/api/models/WorkflowApprovals.js b/awx/ui_next/src/api/models/WorkflowApprovals.js new file mode 100644 index 000000000000..4674d338c56a --- /dev/null +++ b/awx/ui_next/src/api/models/WorkflowApprovals.js @@ -0,0 +1,18 @@ +import Base from '../Base'; + +class WorkflowApprovals extends Base { + constructor(http) { + super(http); + this.baseUrl = '/api/v2/workflow_approvals/'; + } + + approve(id) { + return this.http.post(`${this.baseUrl}${id}/approve/`); + } + + deny(id) { + return this.http.post(`${this.baseUrl}${id}/deny/`); + } +} + +export default WorkflowApprovals; diff --git a/awx/ui_next/src/api/models/WorkflowJobTemplateNodes.js b/awx/ui_next/src/api/models/WorkflowJobTemplateNodes.js new file mode 100644 index 000000000000..b628312d0389 --- /dev/null +++ b/awx/ui_next/src/api/models/WorkflowJobTemplateNodes.js @@ -0,0 +1,73 @@ +import Base from '../Base'; + +class WorkflowJobTemplateNodes extends Base { + constructor(http) { + super(http); + this.baseUrl = '/api/v2/workflow_job_template_nodes/'; + } + + createApprovalTemplate(id, data) { + return this.http.post( + `${this.baseUrl}${id}/create_approval_template/`, + data + ); + } + + associateSuccessNode(id, idToAssociate) { + return this.http.post(`${this.baseUrl}${id}/success_nodes/`, { + id: idToAssociate, + }); + } + + associateFailureNode(id, idToAssociate) { + return this.http.post(`${this.baseUrl}${id}/failure_nodes/`, { + id: idToAssociate, + }); + } + + associateAlwaysNode(id, idToAssociate) { + return this.http.post(`${this.baseUrl}${id}/always_nodes/`, { + id: idToAssociate, + }); + } + + disassociateSuccessNode(id, idToDissociate) { + return this.http.post(`${this.baseUrl}${id}/success_nodes/`, { + id: idToDissociate, + disassociate: true, + }); + } + + disassociateFailuresNode(id, idToDissociate) { + return this.http.post(`${this.baseUrl}${id}/failure_nodes/`, { + id: idToDissociate, + disassociate: true, + }); + } + + disassociateAlwaysNode(id, idToDissociate) { + return this.http.post(`${this.baseUrl}${id}/always_nodes/`, { + id: idToDissociate, + disassociate: true, + }); + } + + readCredentials(id) { + return this.http.get(`${this.baseUrl}${id}/credentials/`); + } + + associateCredentials(id, credentialId) { + return this.http.post(`${this.baseUrl}${id}/credentials/`, { + id: credentialId, + }); + } + + disassociateCredentials(id, credentialId) { + return this.http.post(`${this.baseUrl}${id}/credentials/`, { + id: credentialId, + disassociate: true, + }); + } +} + +export default WorkflowJobTemplateNodes; diff --git a/awx/ui_next/src/api/models/WorkflowJobTemplates.js b/awx/ui_next/src/api/models/WorkflowJobTemplates.js index 07da2531f43a..beed5be9ad97 100644 --- a/awx/ui_next/src/api/models/WorkflowJobTemplates.js +++ b/awx/ui_next/src/api/models/WorkflowJobTemplates.js @@ -1,13 +1,105 @@ import Base from '../Base'; +import SchedulesMixin from '../mixins/Schedules.mixin'; +import NotificationsMixin from '../mixins/Notifications.mixin'; -class WorkflowJobTemplates extends Base { +class WorkflowJobTemplates extends SchedulesMixin(NotificationsMixin(Base)) { constructor(http) { super(http); this.baseUrl = '/api/v2/workflow_job_templates/'; } + readWebhookKey(id) { + return this.http.get(`${this.baseUrl}${id}/webhook_key/`); + } + + readWorkflowJobTemplateOptions(id) { + return this.http.options(`${this.baseUrl}${id}/`); + } + + updateWebhookKey(id) { + return this.http.post(`${this.baseUrl}${id}/webhook_key/`); + } + + associateLabel(id, label, orgId) { + return this.http.post(`${this.baseUrl}${id}/labels/`, { + name: label.name, + organization: orgId, + }); + } + + createNode(id, data) { + return this.http.post(`${this.baseUrl}${id}/workflow_nodes/`, data); + } + + disassociateLabel(id, label) { + return this.http.post(`${this.baseUrl}${id}/labels/`, { + id: label.id, + disassociate: true, + }); + } + + launch(id, data) { + return this.http.post(`${this.baseUrl}${id}/launch/`, data); + } + + readLaunch(id) { + return this.http.get(`${this.baseUrl}${id}/launch/`); + } + readNodes(id, params) { - return this.http.get(`${this.baseUrl}${id}/workflow_nodes/`, { params }); + return this.http.get(`${this.baseUrl}${id}/workflow_nodes/`, { + params, + }); + } + + readAccessList(id, params) { + return this.http.get(`${this.baseUrl}${id}/access_list/`, { + params, + }); + } + + readAccessOptions(id) { + return this.http.options(`${this.baseUrl}${id}/access_list/`); + } + + readSurvey(id) { + return this.http.get(`${this.baseUrl}${id}/survey_spec/`); + } + + updateSurvey(id, survey) { + return this.http.post(`${this.baseUrl}${id}/survey_spec/`, survey); + } + + destroySurvey(id) { + return this.http.delete(`${this.baseUrl}${id}/survey_spec/`); + } + + readNotificationTemplatesApprovals(id, params) { + return this.http.get( + `${this.baseUrl}${id}/notification_templates_approvals/`, + { + params, + } + ); + } + + associateNotificationTemplatesApprovals(resourceId, notificationId) { + return this.http.post( + `${this.baseUrl}${resourceId}/notification_templates_approvals/`, + { + id: notificationId, + } + ); + } + + disassociateNotificationTemplatesApprovals(resourceId, notificationId) { + return this.http.post( + `${this.baseUrl}${resourceId}/notification_templates_approvals/`, + { + id: notificationId, + disassociate: true, + } + ); } } diff --git a/awx/ui_next/src/api/models/WorkflowJobs.js b/awx/ui_next/src/api/models/WorkflowJobs.js index 8a7102cc99f6..87e336e8f56b 100644 --- a/awx/ui_next/src/api/models/WorkflowJobs.js +++ b/awx/ui_next/src/api/models/WorkflowJobs.js @@ -6,6 +6,10 @@ class WorkflowJobs extends RelaunchMixin(Base) { super(http); this.baseUrl = '/api/v2/workflow_jobs/'; } + + readNodes(id, params) { + return this.http.get(`${this.baseUrl}${id}/workflow_nodes/`, { params }); + } } export default WorkflowJobs; diff --git a/awx/ui_next/src/app.scss b/awx/ui_next/src/app.scss deleted file mode 100644 index edf29254b300..000000000000 --- a/awx/ui_next/src/app.scss +++ /dev/null @@ -1,260 +0,0 @@ -// https://github.com/patternfly/patternfly-react/issues/1294 -#app { - height: 100%; -} - -// -// sidebar overrides -// - -.pf-c-page__sidebar { - --pf-c-page__sidebar--md--Width: 255px; - - .pf-c-nav { - overflow-y: auto; - - .pf-c-nav__section { - --pf-c-nav__section--MarginTop: 8px; - } - - .pf-c-nav__section + .pf-c-nav__section { - --pf-c-nav__section--MarginTop: 8px; - } - - .pf-c-nav__simple-list .pf-c-nav__link { - --pf-c-nav__simple-list-link--PaddingBottom: 6px; - --pf-c-nav__simple-list-link--PaddingTop: 6px; - } - - .pf-c-nav__section-title { - --pf-c-nav__section-title--PaddingLeft: 24px; - } - - .pf-c-nav__link { - display: flex; - align-items: center; - padding-right: 64px; - } - } -} - -// -// data list overrides -// - -.pf-c-data-list { - --pf-global--target-size--MinHeight: 32px; - --pf-global--target-size--MinWidth: 32px; - --pf-global--FontSize--md: 14px; - - .pf-c-badge:not(:last-child), - .pf-c-switch:not(:last-child) { - margin-right: 18px; - } -} - -.pf-c-data-list__item-row { - --pf-c-data-list__item-row--PaddingRight: 20px; - --pf-c-data-list__item-row--PaddingLeft: 20px; -} - -.pf-c-data-list__item-content { - --pf-c-data-list__item-content--PaddingBottom: 16px; - - min-height: 59px; - align-items: center; -} - -.pf-c-data-list__item-control { - --pf-c-data-list__item-control--PaddingTop: 16px; - --pf-c-data-list__item-control--MarginRight: 8px; - --pf-c-data-list__item-control--PaddingBottom: 16px; -} - -.pf-c-data-list__item { - --pf-c-data-list__item--PaddingLeft: 20px; - --pf-c-data-list__item--PaddingRight: 20px; -} - -.pf-c-data-list__cell { - --pf-c-data-list__cell--PaddingTop: 16px; - --pf-c-data-list__cell-cell--PaddingTop: 16px; - - &.pf-c-data-list__cell--divider { - --pf-c-data-list__cell-cell--MarginRight: 0; - --pf-c-data-list__cell--PaddingTop: 12px; - flex-grow: 0; - } -} - -// -// pf modal overrides -// - -.awx-c-modal.pf-c-modal-box { - margin: 0; - padding: 24px; - width: 600px; - - .pf-c-modal-box__body { - overflow: auto; - } - - .pf-c-modal-box__footer > .pf-c-button:not(:last-child) { - margin-right: 20px; - } -} - -.pf-c-modal-box__footer { - --pf-c-modal-box__footer--PaddingTop: 20px; - --pf-c-modal-box__footer--PaddingRight: 20px; - --pf-c-modal-box__footer--PaddingBottom: 20px; - --pf-c-modal-box__footer--PaddingLeft: 20px; - --pf-c-modal-box__footer--MarginTop: 24px; - justify-content: flex-end; -} - -.pf-c-modal-box__header { - --pf-c-modal-box__header--PaddingTop: 10px; - --pf-c-modal-box__header--PaddingRight: 0; - --pf-c-modal-box__header--PaddingBottom: 0; - --pf-c-modal-box__header--PaddingLeft: 20px; -} - -.pf-c-modal-box__body { - --pf-c-modal-box__body--PaddingLeft: 20px; - --pf-c-modal-box__body--PaddingRight: 20px; - --pf-c-modal-box__body--PaddingBottom: 5px; -} - -// -// pf tooltip overrides -// - -.pf-c-tooltip__content { - --pf-c-tooltip__content--PaddingTop: 0.71rem; - --pf-c-tooltip__content--PaddingRight: 0.71rem; - --pf-c-tooltip__content--PaddingBottom: 0.71rem; - --pf-c-tooltip__content--PaddingLeft: 0.71rem; -} -// higher specificity needed to override PF styles added dynamically to page -.pf-c-tooltip .pf-c-tooltip__content { - text-align: left; -} - -// -// pf empty state overrides -// - -.pf-c-empty-state { - align-self: center; -} - -// -// assorted custom component styles -// note that these should be given a consistent prefix -// and bem style, as well as moved into component-based scss files -// - -.awx-c-card { - position: relative; -} - -// -// PF Alert notification component overrides -// - -.pf-c-alert__title { - --pf-c-alert__title--PaddingTop: 20px; - --pf-c-alert__title--PaddingRight: 20px; - --pf-c-alert__title--PaddingBottom: 20px; - --pf-c-alert__title--PaddingLeft: 20px; -} - -.pf-c-alert__description { - --pf-c-alert__description--PaddingRight: 20px; - --pf-c-alert__description--PaddingBottom: 20px; - --pf-c-alert__description--PaddingLeft: 20px; -} - -.pf-c-alert { - position: absolute; - width: 100%; - z-index: 20; -} - -.at-u-textRight { - text-align: right; -} - -// -// AlertModal styles -// - -.at-c-alertModal.pf-c-modal-box { - border: 0; - border-left: 56px solid black; - - .at-c-alertModal__icon { - position: absolute; - font-size: 23px; - top: 28px; - left: -39px; - } -} - -.at-c-alertModal--warning.pf-c-modal-box { - border-color: var(--pf-global--warning-color--100); - - .pf-c-title { - color: var(--pf-global--warning-color--200); - } - - .at-c-alertModal__icon { - color: var(--pf-global--warning-color--200); - } -} - -.at-c-alertModal--danger.pf-c-modal-box { - border-color: var(--pf-global--danger-color--100); - - .pf-c-title { - color: var(--pf-global--danger-color--200); - } - - .at-c-alertModal__icon { - color: white; - } -} - -.at-c-alertModal--info.pf-c-modal-box { - border-color: var(--pf-global--info-color--100); - - .pf-c-title { - color: var(--pf-global--info-color--200); - } - - .at-c-alertModal__icon { - color: var(--pf-global--info-color--200); - } -} - -.at-c-alertModal--success.pf-c-modal-box { - border-color: var(--pf-global--success-color--100); - - .pf-c-title { - color: var(--pf-global--success-color--200); - } - - .at-c-alertModal__icon { - color: var(--pf-global--success-color--200); - } -} - -// -// LoginModal overrides -// - -.pf-m-error p.pf-c-form__helper-text { - color: var(--pf-global--danger-color--100); -} diff --git a/awx/ui_next/src/components/About/About.jsx b/awx/ui_next/src/components/About/About.jsx index 785ecf4a19db..f68f75b61305 100644 --- a/awx/ui_next/src/components/About/About.jsx +++ b/awx/ui_next/src/components/About/About.jsx @@ -10,10 +10,10 @@ import { } from '@patternfly/react-core'; import { BrandName } from '../../variables'; -import brandLogoImg from '../../../images/brand-logo.svg'; +import brandLogoImg from './brand-logo.svg'; -class About extends React.Component { - static createSpeechBubble(version) { +function About({ ansible_version, version, isOpen, onClose, i18n }) { + const createSpeechBubble = () => { let text = `${BrandName} ${version}`; let top = ''; let bottom = ''; @@ -28,31 +28,22 @@ class About extends React.Component { bottom = ` --${bottom}-- `; return top + text + bottom; - } + }; - constructor(props) { - super(props); + const speechBubble = createSpeechBubble(); - this.createSpeechBubble = this.constructor.createSpeechBubble.bind(this); - } - - render() { - const { ansible_version, version, isOpen, onClose, i18n } = this.props; - - const speechBubble = this.createSpeechBubble(version); - - return ( - -
-          {speechBubble}
-          {`
+  return (
+    
+      
+        {speechBubble}
+        {`
           \\
           \\   ^__^
               (oo)\\_______
@@ -60,18 +51,17 @@ class About extends React.Component {
                   ||----w |
                   ||     ||
                     `}
-        
- - - - {i18n._(t`Ansible Version`)} - - {ansible_version} - - -
- ); - } +
+ + + + {i18n._(t`Ansible Version`)} + + {ansible_version} + + +
+ ); } About.propTypes = { diff --git a/awx/ui_next/images/brand-logo.svg b/awx/ui_next/src/components/About/brand-logo.svg similarity index 100% rename from awx/ui_next/images/brand-logo.svg rename to awx/ui_next/src/components/About/brand-logo.svg diff --git a/awx/ui_next/src/components/ActionButtonCell/ActionButtonCell.jsx b/awx/ui_next/src/components/ActionButtonCell/ActionButtonCell.jsx deleted file mode 100644 index ce8f58ef27fe..000000000000 --- a/awx/ui_next/src/components/ActionButtonCell/ActionButtonCell.jsx +++ /dev/null @@ -1,10 +0,0 @@ -import DataListCell from '@components/DataListCell'; -import styled from 'styled-components'; - -const ActionButtonCell = styled(DataListCell)` - & > :not(:first-child) { - margin-left: 20px; - } -`; -ActionButtonCell.displayName = 'ActionButtonCell'; -export default ActionButtonCell; diff --git a/awx/ui_next/src/components/ActionButtonCell/ActionButtonCell.test.jsx b/awx/ui_next/src/components/ActionButtonCell/ActionButtonCell.test.jsx deleted file mode 100644 index 894e481a65ac..000000000000 --- a/awx/ui_next/src/components/ActionButtonCell/ActionButtonCell.test.jsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react'; -import { mount } from 'enzyme'; -import ActionButtonCell from './ActionButtonCell'; - -describe('ActionButtonCell', () => { - test('renders the expected content', () => { - const wrapper = mount(); - expect(wrapper).toHaveLength(1); - }); -}); diff --git a/awx/ui_next/src/components/ActionButtonCell/index.js b/awx/ui_next/src/components/ActionButtonCell/index.js deleted file mode 100644 index 6eff34a9c4bc..000000000000 --- a/awx/ui_next/src/components/ActionButtonCell/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ActionButtonCell'; diff --git a/awx/ui_next/src/components/AdHocCommands/AdHocCommands.jsx b/awx/ui_next/src/components/AdHocCommands/AdHocCommands.jsx new file mode 100644 index 000000000000..89387b9e8b51 --- /dev/null +++ b/awx/ui_next/src/components/AdHocCommands/AdHocCommands.jsx @@ -0,0 +1,160 @@ +import React, { useCallback, useEffect, useState, useContext } from 'react'; +import { useHistory, useParams } from 'react-router-dom'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import PropTypes from 'prop-types'; +import { Button, DropdownItem } from '@patternfly/react-core'; + +import useRequest, { useDismissableError } from '../../util/useRequest'; +import { InventoriesAPI, CredentialTypesAPI } from '../../api'; + +import AlertModal from '../AlertModal'; +import ErrorDetail from '../ErrorDetail'; +import AdHocCommandsWizard from './AdHocCommandsWizard'; +import { KebabifiedContext } from '../../contexts/Kebabified'; +import ContentLoading from '../ContentLoading'; +import ContentError from '../ContentError'; + +function AdHocCommands({ adHocItems, i18n, hasListItems }) { + const history = useHistory(); + const { id } = useParams(); + + const [isWizardOpen, setIsWizardOpen] = useState(false); + const { isKebabified, onKebabModalChange } = useContext(KebabifiedContext); + + const verbosityOptions = [ + { value: '0', key: '0', label: i18n._(t`0 (Normal)`) }, + { value: '1', key: '1', label: i18n._(t`1 (Verbose)`) }, + { value: '2', key: '2', label: i18n._(t`2 (More Verbose)`) }, + { value: '3', key: '3', label: i18n._(t`3 (Debug)`) }, + { value: '4', key: '4', label: i18n._(t`4 (Connection Debug)`) }, + ]; + useEffect(() => { + if (isKebabified) { + onKebabModalChange(isWizardOpen); + } + }, [isKebabified, isWizardOpen, onKebabModalChange]); + + const { + result: { moduleOptions, credentialTypeId, isAdHocDisabled }, + request: fetchData, + error: fetchError, + } = useRequest( + useCallback(async () => { + const [options, cred] = await Promise.all([ + InventoriesAPI.readAdHocOptions(id), + CredentialTypesAPI.read({ namespace: 'ssh' }), + ]); + return { + moduleOptions: options.data.actions.GET.module_name.choices, + credentialTypeId: cred.data.results[0].id, + isAdHocDisabled: !options.data.actions.POST, + }; + }, [id]), + { moduleOptions: [], isAdHocDisabled: true } + ); + useEffect(() => { + fetchData(); + }, [fetchData]); + const { + isLoading: isLaunchLoading, + error: launchError, + request: launchAdHocCommands, + } = useRequest( + useCallback( + async values => { + const { data } = await InventoriesAPI.launchAdHocCommands(id, values); + history.push(`/jobs/command/${data.id}/output`); + }, + + [id, history] + ) + ); + + const { error, dismissError } = useDismissableError( + launchError || fetchError + ); + + const handleSubmit = async values => { + const { credential, ...remainingValues } = values; + const newCredential = credential[0].id; + + const manipulatedValues = { + credential: newCredential, + ...remainingValues, + }; + await launchAdHocCommands(manipulatedValues); + }; + + if (isLaunchLoading) { + return ; + } + + if (error && isWizardOpen) { + return ( + { + dismissError(); + setIsWizardOpen(false); + }} + > + {launchError ? ( + <> + {i18n._(t`Failed to launch job.`)} + + + ) : ( + + )} + + ); + } + return ( + // render buttons for drop down and for toolbar + // if modal is open render the modal + <> + {isKebabified ? ( + setIsWizardOpen(true)} + > + {i18n._(t`Run Command`)} + + ) : ( + + )} + + {isWizardOpen && ( + setIsWizardOpen(false)} + onLaunch={handleSubmit} + onDismissError={() => dismissError()} + /> + )} + + ); +} + +AdHocCommands.propTypes = { + adHocItems: PropTypes.arrayOf(PropTypes.object).isRequired, + hasListItems: PropTypes.bool.isRequired, +}; + +export default withI18n()(AdHocCommands); diff --git a/awx/ui_next/src/components/AdHocCommands/AdHocCommands.test.jsx b/awx/ui_next/src/components/AdHocCommands/AdHocCommands.test.jsx new file mode 100644 index 000000000000..2cbe7250f0a0 --- /dev/null +++ b/awx/ui_next/src/components/AdHocCommands/AdHocCommands.test.jsx @@ -0,0 +1,345 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { + mountWithContexts, + waitForElement, +} from '../../../testUtils/enzymeHelpers'; +import { CredentialTypesAPI, InventoriesAPI, CredentialsAPI } from '../../api'; +import AdHocCommands from './AdHocCommands'; + +jest.mock('../../api/models/CredentialTypes'); +jest.mock('../../api/models/Inventories'); +jest.mock('../../api/models/Credentials'); +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useParams: () => ({ + id: 1, + }), +})); +const credentials = [ + { 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' }, +]; + +const adHocItems = [ + { + name: 'Inventory 1 Org 0', + }, + { name: 'Inventory 2 Org 0' }, +]; + +describe('', () => { + beforeEach(() => { + InventoriesAPI.readAdHocOptions.mockResolvedValue({ + data: { + actions: { + GET: { + module_name: { + choices: [ + ['command', 'command'], + ['shell', 'shell'], + ], + }, + }, + POST: {}, + }, + }, + }); + CredentialTypesAPI.read.mockResolvedValue({ + data: { count: 1, results: [{ id: 1, name: 'cred' }] }, + }); + }); + let wrapper; + afterEach(() => { + wrapper.unmount(); + jest.clearAllMocks(); + }); + + test('mounts successfully', async () => { + await act(async () => { + wrapper = mountWithContexts( + + ); + }); + expect(wrapper.find('AdHocCommands').length).toBe(1); + }); + + test('should open the wizard', async () => { + InventoriesAPI.readAdHocOptions.mockResolvedValue({ + data: { + actions: { + GET: { + module_name: { + choices: [ + ['command', 'command'], + ['foo', 'foo'], + ], + }, + verbosity: { choices: [[1], [2]] }, + }, + }, + }, + }); + CredentialTypesAPI.read.mockResolvedValue({ + data: { results: [{ id: 1 }] }, + }); + await act(async () => { + wrapper = mountWithContexts( + + ); + }); + await act(async () => + wrapper.find('button[aria-label="Run Command"]').prop('onClick')() + ); + + wrapper.update(); + + expect(wrapper.find('AdHocCommandsWizard').length).toBe(1); + }); + + test('should submit properly', async () => { + InventoriesAPI.launchAdHocCommands.mockResolvedValue({ data: { id: 1 } }); + CredentialsAPI.read.mockResolvedValue({ + data: { + results: credentials, + count: 5, + }, + }); + await act(async () => { + wrapper = mountWithContexts( + + ); + }); + + await act(async () => + wrapper.find('button[aria-label="Run Command"]').prop('onClick')() + ); + wrapper.update(); + + expect(wrapper.find('Button[type="submit"]').prop('isDisabled')).toBe(true); + + expect( + wrapper + .find('WizardNavItem[content="Machine credential"]') + .prop('isDisabled') + ).toBe(true); + + await act(async () => { + wrapper.find('AnsibleSelect[name="module_name"]').prop('onChange')( + {}, + 'command' + ); + wrapper.find('input#module_args').simulate('change', { + target: { value: 'foo', name: 'module_args' }, + }); + wrapper.find('AnsibleSelect[name="verbosity"]').prop('onChange')({}, 1); + }); + + wrapper.update(); + + expect(wrapper.find('Button[type="submit"]').prop('isDisabled')).toBe( + false + ); + await act(async () => + wrapper.find('Button[type="submit"]').prop('onClick')() + ); + await waitForElement(wrapper, 'ContentEmpty', el => el.length === 0); + // second step of wizard + + await act(async () => { + wrapper + .find('input[aria-labelledby="check-action-item-4"]') + .simulate('change', { target: { checked: true } }); + }); + + wrapper.update(); + + expect( + wrapper.find('CheckboxListItem[label="Cred 4"]').prop('isSelected') + ).toBe(true); + + await act(async () => + wrapper.find('Button[type="submit"]').prop('onClick')() + ); + + expect(InventoriesAPI.launchAdHocCommands).toBeCalledWith(1, { + module_args: 'foo', + diff_mode: false, + credential: 4, + job_type: 'run', + become_enabled: '', + extra_vars: '---', + forks: 0, + limit: 'Inventory 1 Org 0, Inventory 2 Org 0', + module_name: 'command', + verbosity: 1, + }); + }); + + test('should throw error on submission properly', async () => { + InventoriesAPI.launchAdHocCommands.mockRejectedValue( + new Error({ + response: { + config: { + method: 'post', + url: '/api/v2/inventories/1/ad_hoc_commands', + }, + data: 'An error occurred', + status: 403, + }, + }) + ); + InventoriesAPI.readAdHocOptions.mockResolvedValue({ + data: { + actions: { + GET: { + module_name: { + choices: [ + ['command', 'command'], + ['foo', 'foo'], + ], + }, + verbosity: { choices: [[1], [2]] }, + }, + }, + }, + }); + CredentialTypesAPI.read.mockResolvedValue({ + data: { results: [{ id: 1 }] }, + }); + CredentialsAPI.read.mockResolvedValue({ + data: { + results: credentials, + count: 5, + }, + }); + await act(async () => { + wrapper = mountWithContexts( + + ); + }); + + await act(async () => + wrapper.find('button[aria-label="Run Command"]').prop('onClick')() + ); + wrapper.update(); + + expect(wrapper.find('Button[type="submit"]').prop('isDisabled')).toBe(true); + expect( + wrapper + .find('WizardNavItem[content="Machine credential"]') + .prop('isDisabled') + ).toBe(true); + + await act(async () => { + wrapper.find('AnsibleSelect[name="module_name"]').prop('onChange')( + {}, + 'command' + ); + wrapper.find('input#module_args').simulate('change', { + target: { value: 'foo', name: 'module_args' }, + }); + wrapper.find('AnsibleSelect[name="verbosity"]').prop('onChange')({}, 1); + }); + + wrapper.update(); + + expect(wrapper.find('Button[type="submit"]').prop('isDisabled')).toBe( + false + ); + + await act(async () => + wrapper.find('Button[type="submit"]').prop('onClick')() + ); + + await waitForElement(wrapper, 'ContentEmpty', el => el.length === 0); + + // second step of wizard + + await act(async () => { + wrapper + .find('input[aria-labelledby="check-action-item-4"]') + .simulate('change', { target: { checked: true } }); + }); + + wrapper.update(); + + expect( + wrapper.find('CheckboxListItem[label="Cred 4"]').prop('isSelected') + ).toBe(true); + + await act(async () => + wrapper.find('Button[type="submit"]').prop('onClick')() + ); + + await waitForElement(wrapper, 'ErrorDetail', el => el.length > 0); + }); + + test('should disable run command button due to permissions', async () => { + InventoriesAPI.readHosts.mockResolvedValue({ + data: { results: [], count: 0 }, + }); + InventoriesAPI.readAdHocOptions.mockResolvedValue({ + data: { + actions: { + GET: { module_name: { choices: [['module']] } }, + }, + }, + }); + await act(async () => { + wrapper = mountWithContexts( + + ); + }); + await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); + const runCommandsButton = wrapper.find('button[aria-label="Run Command"]'); + expect(runCommandsButton.prop('disabled')).toBe(true); + }); + + test('should disable run command button due to lack of list items', async () => { + InventoriesAPI.readHosts.mockResolvedValue({ + data: { results: [], count: 0 }, + }); + InventoriesAPI.readAdHocOptions.mockResolvedValue({ + data: { + actions: { + GET: { module_name: { choices: [['module']] } }, + }, + }, + }); + await act(async () => { + wrapper = mountWithContexts( + + ); + }); + await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); + const runCommandsButton = wrapper.find('button[aria-label="Run Command"]'); + expect(runCommandsButton.prop('disabled')).toBe(true); + }); + + test('should open alert modal when error on fetching data', async () => { + InventoriesAPI.readAdHocOptions.mockRejectedValue( + new Error({ + response: { + config: { + method: 'options', + url: '/api/v2/inventories/1/', + }, + data: 'An error occurred', + status: 403, + }, + }) + ); + await act(async () => { + wrapper = mountWithContexts( + + ); + }); + await act(async () => wrapper.find('button').prop('onClick')()); + wrapper.update(); + expect(wrapper.find('ErrorDetail').length).toBe(1); + }); +}); diff --git a/awx/ui_next/src/components/AdHocCommands/AdHocCommandsWizard.jsx b/awx/ui_next/src/components/AdHocCommands/AdHocCommandsWizard.jsx new file mode 100644 index 000000000000..865564ba9263 --- /dev/null +++ b/awx/ui_next/src/components/AdHocCommands/AdHocCommandsWizard.jsx @@ -0,0 +1,142 @@ +import React, { useState } from 'react'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import { ExclamationCircleIcon as PFExclamationCircleIcon } from '@patternfly/react-icons'; +import { Tooltip } from '@patternfly/react-core'; +import { withFormik, useFormikContext } from 'formik'; +import PropTypes from 'prop-types'; + +import styled from 'styled-components'; +import Wizard from '../Wizard'; +import AdHocCredentialStep from './AdHocCredentialStep'; +import AdHocDetailsStep from './AdHocDetailsStep'; + +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; + color: var(--pf-global--danger-color--100); +`; + +function AdHocCommandsWizard({ + onLaunch, + i18n, + moduleOptions, + verbosityOptions, + onCloseWizard, + credentialTypeId, +}) { + const [currentStepId, setCurrentStepId] = useState(1); + const [enableLaunch, setEnableLaunch] = useState(false); + + const { values, errors, touched } = useFormikContext(); + + const enabledNextOnDetailsStep = () => { + if (!values.module_name) { + return false; + } + + if (values.module_name === 'shell' || values.module_name === 'command') { + if (values.module_args) { + return true; + // eslint-disable-next-line no-else-return + } else { + return false; + } + } + return undefined; // makes the linter happy; + }; + const hasDetailsStepError = errors.module_args && touched.module_args; + + const steps = [ + { + id: 1, + key: 1, + name: hasDetailsStepError ? ( + + {i18n._(t`Details`)} + + + + + ) : ( + i18n._(t`Details`) + ), + component: ( + + ), + enableNext: enabledNextOnDetailsStep(), + nextButtonText: i18n._(t`Next`), + }, + { + id: 2, + key: 2, + name: i18n._(t`Machine credential`), + component: ( + setEnableLaunch(true)} + /> + ), + enableNext: enableLaunch && Object.values(errors).length === 0, + nextButtonText: i18n._(t`Launch`), + canJumpTo: currentStepId >= 2, + }, + ]; + + const currentStep = steps.find(step => step.id === currentStepId); + + return ( + setCurrentStepId(step.id)} + onClose={() => onCloseWizard()} + onSave={() => { + onLaunch(values); + }} + steps={steps} + title={i18n._(t`Run command`)} + nextButtonText={currentStep.nextButtonText || undefined} + backButtonText={i18n._(t`Back`)} + cancelButtonText={i18n._(t`Cancel`)} + /> + ); +} + +const FormikApp = withFormik({ + mapPropsToValues({ adHocItems, verbosityOptions }) { + const adHocItemStrings = adHocItems.map(item => item.name).join(', '); + return { + limit: adHocItemStrings || 'all', + credential: [], + module_args: '', + verbosity: verbosityOptions[0].value, + forks: 0, + diff_mode: false, + become_enabled: '', + module_name: '', + extra_vars: '---', + job_type: 'run', + }; + }, +})(AdHocCommandsWizard); + +FormikApp.propTypes = { + onLaunch: PropTypes.func.isRequired, + moduleOptions: PropTypes.arrayOf(PropTypes.array).isRequired, + verbosityOptions: PropTypes.arrayOf(PropTypes.object).isRequired, + onCloseWizard: PropTypes.func.isRequired, + credentialTypeId: PropTypes.number.isRequired, +}; +export default withI18n()(FormikApp); diff --git a/awx/ui_next/src/components/AdHocCommands/AdHocCommandsWizard.test.jsx b/awx/ui_next/src/components/AdHocCommands/AdHocCommandsWizard.test.jsx new file mode 100644 index 000000000000..fa2575fd8bec --- /dev/null +++ b/awx/ui_next/src/components/AdHocCommands/AdHocCommandsWizard.test.jsx @@ -0,0 +1,207 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { + mountWithContexts, + waitForElement, +} from '../../../testUtils/enzymeHelpers'; +import { CredentialsAPI } from '../../api'; +import AdHocCommandsWizard from './AdHocCommandsWizard'; + +jest.mock('../../api/models/CredentialTypes'); +jest.mock('../../api/models/Inventories'); +jest.mock('../../api/models/Credentials'); +const verbosityOptions = [ + { value: '0', key: '0', label: '0 (Normal)' }, + { value: '1', key: '1', label: '1 (Verbose)' }, + { value: '2', key: '2', label: '2 (More Verbose)' }, + { value: '3', key: '3', label: '3 (Debug)' }, + { value: '4', key: '4', label: '4 (Connection Debug)' }, +]; +const moduleOptions = [ + ['command', 'command'], + ['shell', 'shell'], +]; +const adHocItems = [ + { name: 'Inventory 1' }, + { name: 'Inventory 2' }, + { name: 'inventory 3' }, +]; +describe('', () => { + let wrapper; + const onLaunch = jest.fn(); + beforeEach(async () => { + await act(async () => { + wrapper = mountWithContexts( + {}} + credentialTypeId={1} + /> + ); + }); + }); + afterEach(() => { + jest.clearAllMocks(); + wrapper.unmount(); + }); + + test('should mount properly', async () => { + expect(wrapper.find('AdHocCommandsWizard').length).toBe(1); + }); + + test('next and nav item should be disabled', async () => { + await waitForElement(wrapper, 'WizardNavItem', el => el.length > 0); + expect( + wrapper.find('WizardNavItem[content="Details"]').prop('isCurrent') + ).toBe(true); + expect( + wrapper.find('WizardNavItem[content="Details"]').prop('isDisabled') + ).toBe(false); + expect( + wrapper + .find('WizardNavItem[content="Machine credential"]') + .prop('isDisabled') + ).toBe(true); + expect( + wrapper + .find('WizardNavItem[content="Machine credential"]') + .prop('isCurrent') + ).toBe(false); + expect(wrapper.find('Button[type="submit"]').prop('isDisabled')).toBe(true); + }); + + test('next button should become active, and should navigate to the next step', async () => { + await waitForElement(wrapper, 'WizardNavItem', el => el.length > 0); + + await act(async () => { + wrapper.find('AnsibleSelect[name="module_name"]').prop('onChange')( + {}, + 'command' + ); + wrapper.find('input#module_args').simulate('change', { + target: { value: 'foo', name: 'module_args' }, + }); + wrapper.find('AnsibleSelect[name="verbosity"]').prop('onChange')({}, 1); + }); + wrapper.update(); + expect(wrapper.find('Button[type="submit"]').prop('isDisabled')).toBe( + false + ); + await act(async () => + wrapper.find('Button[type="submit"]').prop('onClick')() + ); + + wrapper.update(); + }); + test('launch button should become active', async () => { + CredentialsAPI.read.mockResolvedValue({ + data: { + results: [ + { id: 1, name: 'Cred 1', url: '' }, + { id: 2, name: 'Cred2', url: '' }, + ], + count: 2, + }, + }); + await waitForElement(wrapper, 'WizardNavItem', el => el.length > 0); + + await act(async () => { + wrapper.find('AnsibleSelect[name="module_name"]').prop('onChange')( + {}, + 'command' + ); + wrapper.find('input#module_args').simulate('change', { + target: { value: 'foo', name: 'module_args' }, + }); + wrapper.find('AnsibleSelect[name="verbosity"]').prop('onChange')({}, 1); + }); + wrapper.update(); + expect(wrapper.find('Button[type="submit"]').prop('isDisabled')).toBe( + false + ); + await act(async () => + wrapper.find('Button[type="submit"]').prop('onClick')() + ); + + wrapper.update(); + await waitForElement(wrapper, 'OptionsList', el => el.length > 0); + expect(wrapper.find('CheckboxListItem').length).toBe(2); + expect(wrapper.find('Button[type="submit"]').prop('isDisabled')).toBe(true); + + await act(async () => { + wrapper + .find('input[aria-labelledby="check-action-item-1"]') + .simulate('change', { target: { checked: true } }); + }); + + wrapper.update(); + + expect( + wrapper.find('CheckboxListItem[label="Cred 1"]').prop('isSelected') + ).toBe(true); + expect(wrapper.find('Button[type="submit"]').prop('isDisabled')).toBe( + false + ); + + await act(async () => + wrapper.find('Button[type="submit"]').prop('onClick')() + ); + + expect(onLaunch).toHaveBeenCalled(); + }); + test('should show error in navigation bar', async () => { + await waitForElement(wrapper, 'WizardNavItem', el => el.length > 0); + + await act(async () => { + wrapper.find('AnsibleSelect[name="module_name"]').prop('onChange')( + {}, + 'command' + ); + wrapper.find('input#module_args').simulate('change', { + target: { value: '', name: 'module_args' }, + }); + }); + waitForElement(wrapper, 'ExclamationCircleIcon', el => el.length > 0); + }); + + test('expect credential step to throw error', async () => { + CredentialsAPI.read.mockRejectedValue( + new Error({ + response: { + config: { + method: 'get', + url: '/api/v2/credentals', + }, + data: 'An error occurred', + status: 403, + }, + }) + ); + await waitForElement(wrapper, 'WizardNavItem', el => el.length > 0); + + await act(async () => { + wrapper.find('AnsibleSelect[name="module_name"]').prop('onChange')( + {}, + 'command' + ); + wrapper.find('input#module_args').simulate('change', { + target: { value: 'foo', name: 'module_args' }, + }); + wrapper.find('AnsibleSelect[name="verbosity"]').prop('onChange')({}, 1); + }); + wrapper.update(); + expect(wrapper.find('Button[type="submit"]').prop('isDisabled')).toBe( + false + ); + + await act(async () => + wrapper.find('Button[type="submit"]').prop('onClick')() + ); + + wrapper.update(); + expect(wrapper.find('ContentError').length).toBe(1); + }); +}); diff --git a/awx/ui_next/src/components/AdHocCommands/AdHocCredentialStep.jsx b/awx/ui_next/src/components/AdHocCommands/AdHocCredentialStep.jsx new file mode 100644 index 000000000000..e95f0b05cbcc --- /dev/null +++ b/awx/ui_next/src/components/AdHocCommands/AdHocCredentialStep.jsx @@ -0,0 +1,128 @@ +import React, { useEffect, useCallback } from 'react'; +import { useHistory } from 'react-router-dom'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import PropTypes from 'prop-types'; +import { useField } from 'formik'; +import { Form, FormGroup } from '@patternfly/react-core'; +import { CredentialsAPI } from '../../api'; +import Popover from '../Popover'; + +import { getQSConfig, parseQueryString, mergeParams } from '../../util/qs'; +import useRequest from '../../util/useRequest'; +import ContentError from '../ContentError'; +import ContentLoading from '../ContentLoading'; +import { required } from '../../util/validators'; +import OptionsList from '../OptionsList'; + +const QS_CONFIG = getQSConfig('credentials', { + page: 1, + page_size: 5, + order_by: 'name', +}); + +function AdHocCredentialStep({ i18n, credentialTypeId, onEnableLaunch }) { + const history = useHistory(); + const { + error, + isLoading, + request: fetchCredentials, + result: { credentials, credentialCount }, + } = useRequest( + useCallback(async () => { + const params = parseQueryString(QS_CONFIG, history.location.search); + + const { + data: { results, count }, + } = await CredentialsAPI.read( + mergeParams(params, { credential_type: credentialTypeId }) + ); + + return { + credentials: results, + credentialCount: count, + }; + }, [credentialTypeId, history.location.search]), + { credentials: [], credentialCount: 0 } + ); + + useEffect(() => { + fetchCredentials(); + }, [fetchCredentials]); + + const [credentialField, credentialMeta, credentialHelpers] = useField({ + name: 'credential', + validate: required(null, i18n), + }); + if (error) { + return ; + } + if (isLoading) { + return ; + } + return ( +
+ + } + > + { + credentialHelpers.setValue([value]); + onEnableLaunch(); + }} + deselectItem={() => { + credentialHelpers.setValue([]); + }} + /> + +
+ ); +} + +AdHocCredentialStep.propTypes = { + credentialTypeId: PropTypes.number.isRequired, + onEnableLaunch: PropTypes.func.isRequired, +}; +export default withI18n()(AdHocCredentialStep); diff --git a/awx/ui_next/src/components/AdHocCommands/AdHocCredentialStep.test.jsx b/awx/ui_next/src/components/AdHocCommands/AdHocCredentialStep.test.jsx new file mode 100644 index 000000000000..873b792278f1 --- /dev/null +++ b/awx/ui_next/src/components/AdHocCommands/AdHocCredentialStep.test.jsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { Formik } from 'formik'; +import { + mountWithContexts, + waitForElement, +} from '../../../testUtils/enzymeHelpers'; +import { CredentialsAPI } from '../../api'; +import AdHocCredentialStep from './AdHocCredentialStep'; + +jest.mock('../../api/models/Credentials'); + +describe('', () => { + const onEnableLaunch = jest.fn(); + let wrapper; + beforeEach(async () => { + CredentialsAPI.read.mockResolvedValue({ + data: { + results: [ + { id: 1, name: 'Cred 1', url: 'wwww.google.com' }, + { id: 2, name: 'Cred2', url: 'wwww.google.com' }, + ], + count: 2, + }, + }); + await act(async () => { + wrapper = mountWithContexts( + + + + ); + }); + }); + afterEach(() => { + jest.clearAllMocks(); + wrapper.unmount(); + }); + + test('should mount properly', async () => { + await waitForElement(wrapper, 'OptionsList', el => el.length > 0); + }); + + test('should call api', async () => { + await waitForElement(wrapper, 'OptionsList', el => el.length > 0); + expect(CredentialsAPI.read).toHaveBeenCalled(); + expect(wrapper.find('CheckboxListItem').length).toBe(2); + }); +}); diff --git a/awx/ui_next/src/components/AdHocCommands/AdHocDetailsStep.jsx b/awx/ui_next/src/components/AdHocCommands/AdHocDetailsStep.jsx new file mode 100644 index 000000000000..46a3617d7e68 --- /dev/null +++ b/awx/ui_next/src/components/AdHocCommands/AdHocDetailsStep.jsx @@ -0,0 +1,323 @@ +/* eslint-disable react/no-unescaped-entities */ +import React from 'react'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import PropTypes from 'prop-types'; +import { useField } from 'formik'; +import { Form, FormGroup, Switch, Checkbox } from '@patternfly/react-core'; +import styled from 'styled-components'; + +import { BrandName } from '../../variables'; +import AnsibleSelect from '../AnsibleSelect'; +import FormField from '../FormField'; +import { VariablesField } from '../CodeMirrorInput'; +import { + FormColumnLayout, + FormFullWidthLayout, + FormCheckboxLayout, +} from '../FormLayout'; +import Popover from '../Popover'; +import { required } from '../../util/validators'; + +const TooltipWrapper = styled.div` + text-align: left; +`; + +// Setting BrandName to a variable here is necessary to get the jest tests +// passing. Attempting to use BrandName in the template literal results +// in failing tests. +const brandName = BrandName; + +function AdHocDetailsStep({ i18n, verbosityOptions, moduleOptions }) { + const [moduleNameField, moduleNameMeta, moduleNameHelpers] = useField({ + name: 'module_name', + validate: required(null, i18n), + }); + + const [variablesField] = useField('extra_vars'); + const [diffModeField, , diffModeHelpers] = useField('diff_mode'); + const [becomeEnabledField, , becomeEnabledHelpers] = useField( + 'become_enabled' + ); + const [verbosityField, verbosityMeta, verbosityHelpers] = useField({ + name: 'verbosity', + validate: required(null, i18n), + }); + + const argumentsRequired = + moduleNameField.value === 'command' || moduleNameField.value === 'shell'; + const [, argumentsMeta, argumentsHelpers] = useField({ + name: 'module_args', + validate: argumentsRequired && required(null, i18n), + }); + + const isValid = !argumentsMeta.error || !argumentsMeta.touched; + + return ( +
+ + + + } + > + ({ + value: value[0], + label: value[0], + key: value[0], + })), + ]} + onChange={(event, value) => { + if (value !== 'command' && value !== 'shell') { + argumentsHelpers.setTouched(false); + } + moduleNameHelpers.setValue(value); + }} + /> + + argumentsHelpers.setTouched(true)} + isRequired={ + moduleNameField.value === 'command' || + moduleNameField.value === 'shell' + } + tooltip={ + moduleNameField.value ? ( + <> + {i18n._( + t`These arguments are used with the specified module. You can find information about ${moduleNameField.value} by clicking ` + )} + + {' '} + {i18n._(t`here.`)} + + + ) : ( + i18n._(t`These arguments are used with the specified module.`) + ) + } + /> + + } + > + { + verbosityHelpers.setValue(parseInt(value, 10)); + }} + /> + + + {i18n._( + t`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` + )}{' '} + + {i18n._(t`here`)} + + + } + /> + + {i18n._( + t`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` + )}{' '} + + {i18n._(t`here.`)} + + + } + /> + + + } + > + { + diffModeHelpers.setValue(!diffModeField.value); + }} + aria-label={i18n._(t`toggle changes`)} + /> + + + + + {i18n._(t`Enable privilege escalation`)} +   + + {i18n._(t`Enables creation of a provisioning + callback URL. Using the URL a host can contact ${brandName} + and request a configuration update using this job + template`)} +   + --become + {i18n._(t`option to the`)}   + ansible + {i18n._(t`command`)} +

+ } + /> + + } + id="become_enabled" + isChecked={becomeEnabledField.value} + onChange={checked => { + becomeEnabledHelpers.setValue(checked); + }} + /> +
+
+
+ + +

+ {i18n._( + t`Pass extra command line changes. There are two ansible command line parameters: ` + )} +
+ -e, --extra-vars +
+ {i18n._(t`Provide key/value pairs using either + YAML or JSON.`)} +

+ JSON: +
+ +
+                    {'{'}
+                    {'\n  '}"somevar": "somevalue",
+                    {'\n  '}"password": "magic"
+                    {'\n'}
+                    {'}'}
+                  
+
+ YAML: +
+ +
+                    ---
+                    {'\n'}somevar: somevalue
+                    {'\n'}password: magic
+                  
+
+ + } + label={i18n._(t`Extra variables`)} + aria-label={i18n._(t`Extra variables`)} + /> +
+
+
+ ); +} + +AdHocDetailsStep.propTypes = { + moduleOptions: PropTypes.arrayOf(PropTypes.array).isRequired, + verbosityOptions: PropTypes.arrayOf(PropTypes.object).isRequired, +}; + +export default withI18n()(AdHocDetailsStep); diff --git a/awx/ui_next/src/components/AdHocCommands/AdHocDetailsStep.test.jsx b/awx/ui_next/src/components/AdHocCommands/AdHocDetailsStep.test.jsx new file mode 100644 index 000000000000..4b621b8e5f4a --- /dev/null +++ b/awx/ui_next/src/components/AdHocCommands/AdHocDetailsStep.test.jsx @@ -0,0 +1,134 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { Formik } from 'formik'; +import { mountWithContexts } from '../../../testUtils/enzymeHelpers'; +import DetailsStep from './AdHocDetailsStep'; + +jest.mock('../../api/models/Credentials'); + +const verbosityOptions = [ + { key: -1, value: '', label: '', isDisabled: false }, + { key: 0, value: 0, label: '0', isDisabled: false }, + { key: 1, value: 1, label: '1', isDisabled: false }, +]; +const moduleOptions = [ + ['command', 'command'], + ['shell', 'shell'], +]; +const onLimitChange = jest.fn(); +const initialValues = { + limit: ['Inventory 1', 'inventory 2'], + credential: [], + module_args: '', + arguments: '', + verbosity: '', + forks: 0, + changes: false, + escalation: false, + extra_vars: '---', + module_name: 'shell', +}; + +describe('', () => { + let wrapper; + + afterEach(() => { + jest.clearAllMocks(); + wrapper.unmount(); + }); + + test('should mount properly', async () => { + await act(async () => { + wrapper = mountWithContexts( + + + + ); + }); + }); + + test('should show all the fields', async () => { + await act(async () => { + wrapper = mountWithContexts( + + + + ); + }); + expect(wrapper.find('FormGroup[label="Module"]').length).toBe(1); + expect(wrapper.find('FormField[label="Arguments"]').length).toBe(1); + expect(wrapper.find('FormGroup[label="Verbosity"]').length).toBe(1); + expect(wrapper.find('FormField[label="Limit"]').length).toBe(1); + expect(wrapper.find('FormField[name="forks"]').length).toBe(1); + expect(wrapper.find('FormGroup[label="Show changes"]').length).toBe(1); + expect(wrapper.find('FormGroup[name="become_enabled"]').length).toBe(1); + expect(wrapper.find('VariablesField').length).toBe(1); + }); + + test('shold update form values', async () => { + await act(async () => { + wrapper = mountWithContexts( + + + + ); + }); + + await act(async () => { + wrapper.find('AnsibleSelect[name="module_name"]').prop('onChange')( + {}, + 'command' + ); + wrapper.find('input#module_args').simulate('change', { + target: { value: 'foo', name: 'module_args' }, + }); + wrapper.find('input#limit').simulate('change', { + target: { + value: 'Inventory 1, inventory 2, new inventory', + name: 'limit', + }, + }); + wrapper.find('AnsibleSelect[name="verbosity"]').prop('onChange')({}, 1); + + wrapper.find('TextInputBase[name="forks"]').simulate('change', { + target: { value: 10, name: 'forks' }, + }); + wrapper.find('Switch').invoke('onChange')(); + wrapper + .find('Checkbox[aria-label="Enable privilege escalation"]') + .invoke('onChange')(true, { + currentTarget: { value: true, type: 'change', checked: true }, + }); + }); + wrapper.update(); + expect( + wrapper.find('AnsibleSelect[name="module_name"]').prop('value') + ).toBe('command'); + expect(wrapper.find('input#module_args').prop('value')).toBe('foo'); + expect(wrapper.find('AnsibleSelect[name="verbosity"]').prop('value')).toBe( + 1 + ); + expect(wrapper.find('TextInputBase[name="forks"]').prop('value')).toBe(10); + expect(wrapper.find('TextInputBase[name="limit"]').prop('value')).toBe( + 'Inventory 1, inventory 2, new inventory' + ); + expect(wrapper.find('Switch').prop('isChecked')).toBe(true); + expect( + wrapper + .find('Checkbox[aria-label="Enable privilege escalation"]') + .prop('isChecked') + ).toBe(true); + }); +}); diff --git a/awx/ui_next/src/components/AdHocCommands/index.js b/awx/ui_next/src/components/AdHocCommands/index.js new file mode 100644 index 000000000000..fa981f01e49b --- /dev/null +++ b/awx/ui_next/src/components/AdHocCommands/index.js @@ -0,0 +1 @@ +export { default } from './AdHocCommands'; diff --git a/awx/ui_next/src/components/AddDropDownButton/AddDropDownButton.jsx b/awx/ui_next/src/components/AddDropDownButton/AddDropDownButton.jsx index 288a30733fb6..c78bac17fb4a 100644 --- a/awx/ui_next/src/components/AddDropDownButton/AddDropDownButton.jsx +++ b/awx/ui_next/src/components/AddDropDownButton/AddDropDownButton.jsx @@ -1,25 +1,32 @@ -import React, { useState, useRef, useEffect } from 'react'; -import { Link } from 'react-router-dom'; +import React, { useState, useRef, useEffect, Fragment } from 'react'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; import PropTypes from 'prop-types'; import { Dropdown, DropdownPosition } from '@patternfly/react-core'; -import { ToolbarAddButton } from '@components/PaginatedDataList'; +import { ToolbarAddButton } from '../PaginatedDataList'; +import { useKebabifiedMenu } from '../../contexts/Kebabified'; -function AddDropDownButton({ dropdownItems }) { +function AddDropDownButton({ dropdownItems, i18n }) { + const { isKebabified } = useKebabifiedMenu(); const [isOpen, setIsOpen] = useState(false); const element = useRef(null); - const toggle = e => { - if (!element || !element.current.contains(e.target)) { - setIsOpen(false); - } - }; - useEffect(() => { + const toggle = e => { + if (!isKebabified && (!element || !element.current.contains(e.target))) { + setIsOpen(false); + } + }; + document.addEventListener('click', toggle, false); return () => { document.removeEventListener('click', toggle); }; - }, []); + }, [isKebabified]); + + if (isKebabified) { + return {dropdownItems}; + } return (
@@ -27,29 +34,22 @@ function AddDropDownButton({ dropdownItems }) { isPlain isOpen={isOpen} position={DropdownPosition.right} - toggle={ setIsOpen(!isOpen)} />} - dropdownItems={dropdownItems.map(item => ( - - {item.label} - - ))} + toggle={ + setIsOpen(!isOpen)} + /> + } + dropdownItems={dropdownItems} />
); } AddDropDownButton.propTypes = { - dropdownItems: PropTypes.arrayOf( - PropTypes.shape({ - label: PropTypes.string.isRequired, - url: PropTypes.string.isRequired, - }) - ).isRequired, + dropdownItems: PropTypes.arrayOf(PropTypes.element.isRequired).isRequired, }; export { AddDropDownButton as _AddDropDownButton }; -export default AddDropDownButton; +export default withI18n()(AddDropDownButton); diff --git a/awx/ui_next/src/components/AddDropDownButton/AddDropDownButton.test.jsx b/awx/ui_next/src/components/AddDropDownButton/AddDropDownButton.test.jsx index a30e3f9cd8bd..d1e16aaa223a 100644 --- a/awx/ui_next/src/components/AddDropDownButton/AddDropDownButton.test.jsx +++ b/awx/ui_next/src/components/AddDropDownButton/AddDropDownButton.test.jsx @@ -1,14 +1,12 @@ import React from 'react'; -import { mountWithContexts } from '@testUtils/enzymeHelpers'; +import { DropdownItem } from '@patternfly/react-core'; +import { mountWithContexts } from '../../../testUtils/enzymeHelpers'; import AddDropDownButton from './AddDropDownButton'; describe('', () => { const dropdownItems = [ - { - key: 'inventory', - label: 'Inventory', - url: `inventory/inventory/add/`, - }, + Add, + Route, ]; test('should be closed initially', () => { const wrapper = mountWithContexts( @@ -23,7 +21,7 @@ describe('', () => { ); wrapper.find('button').simulate('click'); expect(wrapper.find('Dropdown').prop('isOpen')).toEqual(true); - expect(wrapper.find('Link')).toHaveLength(dropdownItems.length); + expect(wrapper.find('DropdownItem')).toHaveLength(dropdownItems.length); }); test('should close when button re-clicked', () => { diff --git a/awx/ui_next/src/components/AddRole/AddResourceRole.jsx b/awx/ui_next/src/components/AddRole/AddResourceRole.jsx index 2a1189dfb6a1..e339142b52e2 100644 --- a/awx/ui_next/src/components/AddRole/AddResourceRole.jsx +++ b/awx/ui_next/src/components/AddRole/AddResourceRole.jsx @@ -1,107 +1,73 @@ -import React, { Fragment } from 'react'; +import React, { Fragment, useState } from 'react'; import PropTypes from 'prop-types'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; -import { Wizard } from '@patternfly/react-core'; +import SelectableCard from '../SelectableCard'; +import Wizard from '../Wizard'; import SelectResourceStep from './SelectResourceStep'; import SelectRoleStep from './SelectRoleStep'; -import SelectableCard from './SelectableCard'; import { TeamsAPI, UsersAPI } from '../../api'; const readUsers = async queryParams => UsersAPI.read(Object.assign(queryParams, { is_superuser: false })); -const readTeams = async queryParams => TeamsAPI.read(queryParams); - -class AddResourceRole extends React.Component { - constructor(props) { - super(props); +const readUsersOptions = async () => UsersAPI.readOptions(); - this.state = { - selectedResource: null, - selectedResourceRows: [], - selectedRoleRows: [], - currentStepId: 1, - maxEnabledStep: 1, - }; +const readTeams = async queryParams => TeamsAPI.read(queryParams); - this.handleResourceCheckboxClick = this.handleResourceCheckboxClick.bind( - this - ); - this.handleResourceSelect = this.handleResourceSelect.bind(this); - this.handleRoleCheckboxClick = this.handleRoleCheckboxClick.bind(this); - this.handleWizardNext = this.handleWizardNext.bind(this); - this.handleWizardSave = this.handleWizardSave.bind(this); - this.handleWizardGoToStep = this.handleWizardGoToStep.bind(this); - } +const readTeamsOptions = async () => TeamsAPI.readOptions(); - handleResourceCheckboxClick(user) { - const { selectedResourceRows, currentStepId } = this.state; +function AddResourceRole({ onSave, onClose, roles, i18n, resource }) { + const [selectedResource, setSelectedResource] = useState(null); + const [selectedResourceRows, setSelectedResourceRows] = useState([]); + const [selectedRoleRows, setSelectedRoleRows] = useState([]); + const [currentStepId, setCurrentStepId] = useState(1); + const [maxEnabledStep, setMaxEnabledStep] = useState(1); + const handleResourceCheckboxClick = user => { const selectedIndex = selectedResourceRows.findIndex( selectedRow => selectedRow.id === user.id ); - if (selectedIndex > -1) { selectedResourceRows.splice(selectedIndex, 1); - const stateToUpdate = { selectedResourceRows }; if (selectedResourceRows.length === 0) { - stateToUpdate.maxEnabledStep = currentStepId; + setMaxEnabledStep(currentStepId); } - this.setState(stateToUpdate); + setSelectedRoleRows(selectedResourceRows); } else { - this.setState(prevState => ({ - selectedResourceRows: [...prevState.selectedResourceRows, user], - })); + setSelectedResourceRows([...selectedResourceRows, user]); } - } - - handleRoleCheckboxClick(role) { - const { selectedRoleRows } = this.state; + }; + const handleRoleCheckboxClick = role => { const selectedIndex = selectedRoleRows.findIndex( selectedRow => selectedRow.id === role.id ); if (selectedIndex > -1) { selectedRoleRows.splice(selectedIndex, 1); - this.setState({ selectedRoleRows }); + setSelectedRoleRows(selectedRoleRows); } else { - this.setState(prevState => ({ - selectedRoleRows: [...prevState.selectedRoleRows, role], - })); + setSelectedRoleRows([...selectedRoleRows, role]); } - } + }; - handleResourceSelect(resourceType) { - this.setState({ - selectedResource: resourceType, - selectedResourceRows: [], - selectedRoleRows: [], - }); - } + const handleResourceSelect = resourceType => { + setSelectedResource(resourceType); + setSelectedResourceRows([]); + setSelectedRoleRows([]); + }; - handleWizardNext(step) { - this.setState({ - currentStepId: step.id, - maxEnabledStep: step.id, - }); - } + const handleWizardNext = step => { + setCurrentStepId(step.id); + setMaxEnabledStep(step.id); + }; - handleWizardGoToStep(step) { - this.setState({ - currentStepId: step.id, - }); - } - - async handleWizardSave() { - const { onSave } = this.props; - const { - selectedResourceRows, - selectedRoleRows, - selectedResource, - } = this.state; + const handleWizardGoToStep = step => { + setCurrentStepId(step.id); + }; + const handleWizardSave = async () => { try { const roleRequests = []; @@ -130,190 +96,198 @@ class AddResourceRole extends React.Component { } catch (err) { // TODO: handle this error } - } - - render() { - const { - selectedResource, - selectedResourceRows, - selectedRoleRows, - currentStepId, - maxEnabledStep, - } = this.state; - const { onClose, roles, i18n } = this.props; - - const userSearchColumns = [ - { - name: i18n._(t`Username`), - key: 'username', - isDefault: true, - }, - { - name: i18n._(t`First Name`), - key: 'first_name', - }, - { - name: i18n._(t`Last Name`), - key: 'last_name', - }, - ]; + }; - const userSortColumns = [ - { - name: i18n._(t`Username`), - key: 'username', - }, - { - name: i18n._(t`First Name`), - key: 'first_name', - }, - { - name: i18n._(t`Last Name`), - key: 'last_name', - }, - ]; + // Object roles can be user only, so we remove them when + // showing role choices for team access + const selectableRoles = { ...roles }; + if (selectedResource === 'teams') { + Object.keys(roles).forEach(key => { + if (selectableRoles[key].user_only) { + delete selectableRoles[key]; + } + }); + } - const teamSearchColumns = [ - { - name: i18n._(t`Name`), - key: 'name', - isDefault: true, - }, - { - name: i18n._(t`Created By (Username)`), - key: 'created_by__username', - }, - { - name: i18n._(t`Modified By (Username)`), - key: 'modified_by__username', - }, - ]; + const userSearchColumns = [ + { + name: i18n._(t`Username`), + key: 'username__icontains', + isDefault: true, + }, + { + name: i18n._(t`First Name`), + key: 'first_name__icontains', + }, + { + name: i18n._(t`Last Name`), + key: 'last_name__icontains', + }, + ]; + const userSortColumns = [ + { + name: i18n._(t`Username`), + key: 'username', + }, + { + name: i18n._(t`First Name`), + key: 'first_name', + }, + { + name: i18n._(t`Last Name`), + key: 'last_name', + }, + ]; + const teamSearchColumns = [ + { + name: i18n._(t`Name`), + key: 'name', + isDefault: true, + }, + { + name: i18n._(t`Created By (Username)`), + key: 'created_by__username', + }, + { + name: i18n._(t`Modified By (Username)`), + key: 'modified_by__username', + }, + ]; - const teamSortColumns = [ - { - name: i18n._(t`Name`), - key: 'name', - }, - ]; + const teamSortColumns = [ + { + name: i18n._(t`Name`), + key: 'name', + }, + ]; - let wizardTitle = ''; + let wizardTitle = ''; - switch (selectedResource) { - case 'users': - wizardTitle = i18n._(t`Add User Roles`); - break; - case 'teams': - wizardTitle = i18n._(t`Add Team Roles`); - break; - default: - wizardTitle = i18n._(t`Add Roles`); - } + switch (selectedResource) { + case 'users': + wizardTitle = i18n._(t`Add User Roles`); + break; + case 'teams': + wizardTitle = i18n._(t`Add Team Roles`); + break; + default: + wizardTitle = i18n._(t`Add Roles`); + } - const steps = [ - { - id: 1, - name: i18n._(t`Select a Resource Type`), - component: ( -
-
- {i18n._( - t`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.` - )} -
- this.handleResourceSelect('users')} - /> + const steps = [ + { + id: 1, + name: i18n._(t`Select a Resource Type`), + component: ( +
+
+ {i18n._( + t`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.` + )} +
+ handleResourceSelect('users')} + /> + {resource?.type === 'credential' && !resource?.organization ? null : ( this.handleResourceSelect('teams')} + onClick={() => handleResourceSelect('teams')} /> -
- ), - enableNext: selectedResource !== null, - }, - { - id: 2, - name: i18n._(t`Select Items from List`), - component: ( - - {selectedResource === 'users' && ( - - )} - {selectedResource === 'teams' && ( - - )} - - ), - enableNext: selectedResourceRows.length > 0, - canJumpTo: maxEnabledStep >= 2, - }, - { - id: 3, - name: i18n._(t`Select Roles to Apply`), - component: ( - - ), - nextButtonText: i18n._(t`Save`), - enableNext: selectedRoleRows.length > 0, - canJumpTo: maxEnabledStep >= 3, - }, - ]; + )} +
+ ), + enableNext: selectedResource !== null, + }, + { + id: 2, + name: i18n._(t`Select Items from List`), + component: ( + + {selectedResource === 'users' && ( + + )} + {selectedResource === 'teams' && ( + + )} + + ), + enableNext: selectedResourceRows.length > 0, + canJumpTo: maxEnabledStep >= 2, + }, + { + id: 3, + name: i18n._(t`Select Roles to Apply`), + component: ( + + ), + nextButtonText: i18n._(t`Save`), + enableNext: selectedRoleRows.length > 0, + canJumpTo: maxEnabledStep >= 3, + }, + ]; - const currentStep = steps.find(step => step.id === currentStepId); + const currentStep = steps.find(step => step.id === currentStepId); - // TODO: somehow internationalize steps and currentStep.nextButtonText - return ( - - ); - } + // TODO: somehow internationalize steps and currentStep.nextButtonText + return ( + handleWizardGoToStep(step)} + steps={steps} + title={wizardTitle} + nextButtonText={currentStep.nextButtonText || undefined} + backButtonText={i18n._(t`Back`)} + cancelButtonText={i18n._(t`Cancel`)} + /> + ); } AddResourceRole.propTypes = { onClose: PropTypes.func.isRequired, onSave: PropTypes.func.isRequired, roles: PropTypes.shape(), + resource: PropTypes.shape(), }; AddResourceRole.defaultProps = { roles: {}, + resource: {}, }; export { AddResourceRole as _AddResourceRole }; diff --git a/awx/ui_next/src/components/AddRole/AddResourceRole.test.jsx b/awx/ui_next/src/components/AddRole/AddResourceRole.test.jsx index 8a17a10a97fe..264f7cdb289f 100644 --- a/awx/ui_next/src/components/AddRole/AddResourceRole.test.jsx +++ b/awx/ui_next/src/components/AddRole/AddResourceRole.test.jsx @@ -1,19 +1,46 @@ /* eslint-disable react/jsx-pascal-case */ import React from 'react'; import { shallow } from 'enzyme'; -import { mountWithContexts } from '../../../testUtils/enzymeHelpers'; +import { act } from 'react-dom/test-utils'; + +import { + mountWithContexts, + waitForElement, +} from '../../../testUtils/enzymeHelpers'; import AddResourceRole, { _AddResourceRole } from './AddResourceRole'; import { TeamsAPI, UsersAPI } from '../../api'; -jest.mock('../../api'); +jest.mock('../../api/models/Teams'); +jest.mock('../../api/models/Users'); + +// TODO: Once error handling is functional in +// this component write tests for it describe('<_AddResourceRole />', () => { UsersAPI.read.mockResolvedValue({ data: { count: 2, - results: [{ id: 1, username: 'foo' }, { id: 2, username: 'bar' }], + results: [ + { id: 1, username: 'foo', url: '' }, + { id: 2, username: 'bar', url: '' }, + ], }, }); + UsersAPI.readOptions.mockResolvedValue({ + data: { related: {}, actions: { GET: {} } }, + }); + TeamsAPI.read.mockResolvedValue({ + data: { + count: 2, + results: [ + { id: 1, name: 'Team foo', url: '' }, + { id: 2, name: 'Team bar', url: '' }, + ], + }, + }); + TeamsAPI.readOptions.mockResolvedValue({ + data: { related: {}, actions: { GET: {} } }, + }); const roles = { admin_role: { description: 'Can manage all aspects of the organization', @@ -36,186 +63,180 @@ describe('<_AddResourceRole />', () => { /> ); }); - test('handleRoleCheckboxClick properly updates state', () => { - const wrapper = shallow( - <_AddResourceRole - onClose={() => {}} - onSave={() => {}} - roles={roles} - i18n={{ _: val => val.toString() }} - /> - ); - wrapper.setState({ - selectedRoleRows: [ - { - description: 'Can manage all aspects of the organization', - name: 'Admin', - id: 1, - }, - ], - }); - wrapper.instance().handleRoleCheckboxClick({ - description: 'Can manage all aspects of the organization', - name: 'Admin', - id: 1, + test('should save properly', async () => { + let wrapper; + act(() => { + wrapper = mountWithContexts( + {}} onSave={() => {}} roles={roles} />, + { context: { network: { handleHttpError: () => {} } } } + ); }); - expect(wrapper.state('selectedRoleRows')).toEqual([]); - wrapper.instance().handleRoleCheckboxClick({ - description: 'Can manage all aspects of the organization', - name: 'Admin', - id: 1, - }); - expect(wrapper.state('selectedRoleRows')).toEqual([ - { - description: 'Can manage all aspects of the organization', - name: 'Admin', - id: 1, - }, - ]); - }); - test('handleResourceCheckboxClick properly updates state', () => { - const wrapper = shallow( - <_AddResourceRole - onClose={() => {}} - onSave={() => {}} - roles={roles} - i18n={{ _: val => val.toString() }} - /> + wrapper.update(); + + // Step 1 + const selectableCardWrapper = wrapper.find('SelectableCard'); + expect(selectableCardWrapper.length).toBe(2); + act(() => wrapper.find('SelectableCard[label="Users"]').prop('onClick')()); + wrapper.update(); + await act(async () => + wrapper.find('Button[type="submit"]').prop('onClick')() ); - wrapper.setState({ - selectedResourceRows: [ - { - id: 1, - username: 'foobar', - }, - ], - }); - wrapper.instance().handleResourceCheckboxClick({ - id: 1, - username: 'foobar', - }); - expect(wrapper.state('selectedResourceRows')).toEqual([]); - wrapper.instance().handleResourceCheckboxClick({ - id: 1, - username: 'foobar', - }); - expect(wrapper.state('selectedResourceRows')).toEqual([ - { - id: 1, - username: 'foobar', - }, - ]); + wrapper.update(); + + // Step 2 + await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0); + act(() => + wrapper.find('DataListCheck[name="foo"]').invoke('onChange')(true) + ); + wrapper.update(); + expect(wrapper.find('DataListCheck[name="foo"]').prop('checked')).toBe( + true + ); + act(() => wrapper.find('Button[type="submit"]').prop('onClick')()); + wrapper.update(); + + // Step 3 + act(() => + wrapper.find('Checkbox[aria-label="Admin"]').invoke('onChange')(true) + ); + wrapper.update(); + expect(wrapper.find('Checkbox[aria-label="Admin"]').prop('isChecked')).toBe( + true + ); + + // Save + await act(async () => + wrapper.find('Button[type="submit"]').prop('onClick')() + ); + expect(UsersAPI.associateRole).toBeCalledWith(1, 1); }); - test('clicking user/team cards updates state', () => { - const spy = jest.spyOn(_AddResourceRole.prototype, 'handleResourceSelect'); - const wrapper = mountWithContexts( - {}} onSave={() => {}} roles={roles} />, - { context: { network: { handleHttpError: () => {} } } } - ).find('AddResourceRole'); + + test('should successfuly click user/team cards', async () => { + let wrapper; + act(() => { + wrapper = mountWithContexts( + {}} onSave={() => {}} roles={roles} />, + { context: { network: { handleHttpError: () => {} } } } + ); + }); + wrapper.update(); + const selectableCardWrapper = wrapper.find('SelectableCard'); expect(selectableCardWrapper.length).toBe(2); - selectableCardWrapper.first().simulate('click'); - expect(spy).toHaveBeenCalledWith('users'); - expect(wrapper.state('selectedResource')).toBe('users'); - selectableCardWrapper.at(1).simulate('click'); - expect(spy).toHaveBeenCalledWith('teams'); - expect(wrapper.state('selectedResource')).toBe('teams'); - }); - test('handleResourceSelect clears out selected lists and sets selectedResource', () => { - const wrapper = shallow( - <_AddResourceRole - onClose={() => {}} - onSave={() => {}} - roles={roles} - i18n={{ _: val => val.toString() }} - /> + act(() => wrapper.find('SelectableCard[label="Users"]').prop('onClick')()); + wrapper.update(); + + await waitForElement( + wrapper, + 'SelectableCard[label="Users"]', + el => el.prop('isSelected') === true ); - wrapper.setState({ - selectedResource: 'teams', - selectedResourceRows: [ - { - id: 1, - username: 'foobar', - }, - ], - selectedRoleRows: [ - { - description: 'Can manage all aspects of the organization', - id: 1, - name: 'Admin', - }, - ], - }); - wrapper.instance().handleResourceSelect('users'); - expect(wrapper.state()).toEqual({ - selectedResource: 'users', - selectedResourceRows: [], - selectedRoleRows: [], - currentStepId: 1, - maxEnabledStep: 1, + act(() => wrapper.find('SelectableCard[label="Teams"]').prop('onClick')()); + wrapper.update(); + + await waitForElement( + wrapper, + 'SelectableCard[label="Teams"]', + el => el.prop('isSelected') === true + ); + }); + + test('should reset values with resource type changes', async () => { + let wrapper; + act(() => { + wrapper = mountWithContexts( + {}} onSave={() => {}} roles={roles} />, + { context: { network: { handleHttpError: () => {} } } } + ); }); - wrapper.instance().handleResourceSelect('teams'); - expect(wrapper.state()).toEqual({ - selectedResource: 'teams', - selectedResourceRows: [], - selectedRoleRows: [], - currentStepId: 1, - maxEnabledStep: 1, + wrapper.update(); + + // Step 1 + const selectableCardWrapper = wrapper.find('SelectableCard'); + expect(selectableCardWrapper.length).toBe(2); + act(() => wrapper.find('SelectableCard[label="Users"]').prop('onClick')()); + wrapper.update(); + await act(async () => + wrapper.find('Button[type="submit"]').prop('onClick')() + ); + wrapper.update(); + + // Step 2 + await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0); + act(() => + wrapper.find('DataListCheck[name="foo"]').invoke('onChange')(true) + ); + wrapper.update(); + expect(wrapper.find('DataListCheck[name="foo"]').prop('checked')).toBe( + true + ); + act(() => wrapper.find('Button[type="submit"]').prop('onClick')()); + wrapper.update(); + + // Step 3 + act(() => + wrapper.find('Checkbox[aria-label="Admin"]').invoke('onChange')(true) + ); + wrapper.update(); + expect(wrapper.find('Checkbox[aria-label="Admin"]').prop('isChecked')).toBe( + true + ); + + // Go back to step 1 + act(() => { + wrapper + .find('WizardNavItem[content="Select a Resource Type"]') + .find('button') + .prop('onClick')({ id: 1 }); }); + wrapper.update(); + expect( + wrapper + .find('WizardNavItem[content="Select a Resource Type"]') + .prop('isCurrent') + ).toBe(true); + + // Go back to step 1 and this time select teams. Doing so should clear following steps + act(() => wrapper.find('SelectableCard[label="Teams"]').prop('onClick')()); + wrapper.update(); + await act(async () => + wrapper.find('Button[type="submit"]').prop('onClick')() + ); + wrapper.update(); + + // Make sure no teams have been selected + await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0); + wrapper + .find('DataListCheck') + .map(item => expect(item.prop('checked')).toBe(false)); + act(() => wrapper.find('Button[type="submit"]').prop('onClick')()); + wrapper.update(); + + // Make sure that no roles have been selected + wrapper + .find('Checkbox') + .map(card => expect(card.prop('isChecked')).toBe(false)); + + // Make sure the save button is disabled + expect(wrapper.find('Button[type="submit"]').prop('isDisabled')).toBe(true); }); - test('handleWizardSave makes correct api calls, calls onSave when done', async () => { - const handleSave = jest.fn(); + + test('should not display team as a choice in case credential does not have organization', () => { const wrapper = mountWithContexts( - {}} onSave={handleSave} roles={roles} />, + {}} + onSave={() => {}} + roles={roles} + resource={{ type: 'credential', organization: null }} + />, { context: { network: { handleHttpError: () => {} } } } - ).find('AddResourceRole'); - wrapper.setState({ - selectedResource: 'users', - selectedResourceRows: [ - { - id: 1, - username: 'foobar', - }, - ], - selectedRoleRows: [ - { - description: 'Can manage all aspects of the organization', - id: 1, - name: 'Admin', - }, - { - description: 'May run any executable resources in the organization', - id: 2, - name: 'Execute', - }, - ], - }); - await wrapper.instance().handleWizardSave(); - expect(UsersAPI.associateRole).toHaveBeenCalledTimes(2); - expect(handleSave).toHaveBeenCalled(); - wrapper.setState({ - selectedResource: 'teams', - selectedResourceRows: [ - { - id: 1, - name: 'foobar', - }, - ], - selectedRoleRows: [ - { - description: 'Can manage all aspects of the organization', - id: 1, - name: 'Admin', - }, - { - description: 'May run any executable resources in the organization', - id: 2, - name: 'Execute', - }, - ], - }); - await wrapper.instance().handleWizardSave(); - expect(TeamsAPI.associateRole).toHaveBeenCalledTimes(2); - expect(handleSave).toHaveBeenCalled(); + ); + + expect(wrapper.find('SelectableCard').length).toBe(1); + wrapper.find('SelectableCard[label="Users"]').simulate('click'); + wrapper.update(); + expect( + wrapper.find('SelectableCard[label="Users"]').prop('isSelected') + ).toBe(true); }); }); diff --git a/awx/ui_next/src/components/AddRole/CheckboxCard.jsx b/awx/ui_next/src/components/AddRole/CheckboxCard.jsx index a1b56939d049..361bf1a60dfb 100644 --- a/awx/ui_next/src/components/AddRole/CheckboxCard.jsx +++ b/awx/ui_next/src/components/AddRole/CheckboxCard.jsx @@ -1,19 +1,27 @@ import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; -import { Checkbox } from '@patternfly/react-core'; +import { Checkbox as PFCheckbox } from '@patternfly/react-core'; +import styled from 'styled-components'; + +const CheckboxWrapper = styled.div` + display: flex; + border: 1px solid var(--pf-global--BorderColor--200); + border-radius: var(--pf-global--BorderRadius--sm); + padding: 10px; +`; + +const Checkbox = styled(PFCheckbox)` + width: 100%; + & label { + width: 100%; + } +`; class CheckboxCard extends Component { render() { const { name, description, isSelected, onSelect, itemId } = this.props; return ( -
+ -
+ ); } } diff --git a/awx/ui_next/src/components/AddRole/SelectResourceStep.jsx b/awx/ui_next/src/components/AddRole/SelectResourceStep.jsx index 64e60c9f2112..f9a73d24ceee 100644 --- a/awx/ui_next/src/components/AddRole/SelectResourceStep.jsx +++ b/awx/ui_next/src/components/AddRole/SelectResourceStep.jsx @@ -1,132 +1,122 @@ -import React, { Fragment } from 'react'; +import React, { Fragment, useCallback, useEffect } from 'react'; import PropTypes from 'prop-types'; -import { withRouter } from 'react-router-dom'; +import { withRouter, useLocation } from 'react-router-dom'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; -import { SearchColumns, SortColumns } from '@types'; +import useRequest from '../../util/useRequest'; + +import { SearchColumns, SortColumns } from '../../types'; import PaginatedDataList from '../PaginatedDataList'; import DataListToolbar from '../DataListToolbar'; import CheckboxListItem from '../CheckboxListItem'; import SelectedList from '../SelectedList'; import { getQSConfig, parseQueryString } from '../../util/qs'; -class SelectResourceStep extends React.Component { - constructor(props) { - super(props); - - this.state = { - isInitialized: false, - count: null, - error: false, - resources: [], - }; - - this.qsConfig = getQSConfig('resource', { - page: 1, - page_size: 5, - order_by: `${ - props.sortColumns.filter(col => col.key === 'name').length - ? 'name' - : 'username' - }`, - }); - } - - componentDidMount() { - this.readResourceList(); - } - - componentDidUpdate(prevProps) { - const { location } = this.props; - if (location !== prevProps.location) { - this.readResourceList(); - } - } - - async readResourceList() { - const { onSearch, location } = this.props; - const queryParams = parseQueryString(this.qsConfig, location.search); +const QS_Config = sortColumns => { + return getQSConfig('resource', { + page: 1, + page_size: 5, + order_by: `${ + sortColumns.filter(col => col.key === 'name').length ? 'name' : 'username' + }`, + }); +}; +function SelectResourceStep({ + searchColumns, + sortColumns, + displayKey, + onRowClick, + selectedLabel, + selectedResourceRows, + fetchItems, + fetchOptions, + i18n, +}) { + const location = useLocation(); - this.setState({ - isLoading: true, - error: false, - }); - try { - const { data } = await onSearch(queryParams); - const { count, results } = data; + const { + isLoading, + error, + request: readResourceList, + result: { resources, itemCount, relatedSearchableKeys, searchableKeys }, + } = useRequest( + useCallback(async () => { + const queryParams = parseQueryString( + QS_Config(sortColumns), + location.search + ); - this.setState({ + const [ + { + data: { count, results }, + }, + actionsResponse, + ] = await Promise.all([fetchItems(queryParams), fetchOptions()]); + return { resources: results, - count, - isInitialized: true, - isLoading: false, - error: false, - }); - } catch (err) { - this.setState({ - isLoading: false, - error: true, - }); + itemCount: count, + relatedSearchableKeys: ( + actionsResponse?.data?.related_search_fields || [] + ).map(val => val.slice(0, -8)), + searchableKeys: Object.keys( + actionsResponse.data.actions?.GET || {} + ).filter(key => actionsResponse.data.actions?.GET[key].filterable), + }; + }, [location, fetchItems, fetchOptions, sortColumns]), + { + resources: [], + itemCount: 0, + relatedSearchableKeys: [], + searchableKeys: [], } - } - - render() { - const { isInitialized, isLoading, count, error, resources } = this.state; + ); - const { - searchColumns, - sortColumns, - displayKey, - onRowClick, - selectedLabel, - selectedResourceRows, - i18n, - } = this.props; + useEffect(() => { + readResourceList(); + }, [readResourceList]); - return ( - - {isLoading &&
{i18n._(t`Loading...`)}
} - {isInitialized && ( - -
- {i18n._( - t`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.` - )} -
- {selectedResourceRows.length > 0 && ( - - )} - ( - i.id === item.id)} - itemId={item.id} - key={item.id} - name={item[displayKey]} - label={item[displayKey]} - onSelect={() => onRowClick(item)} - /> - )} - renderToolbar={props => } - showPageSizeOptions={false} - /> -
+ return ( + +
+ {i18n._( + t`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.` + )} +
+ {selectedResourceRows.length > 0 && ( + + )} + ( + i.id === item.id)} + itemId={item.id} + key={item.id} + name={item[displayKey]} + label={item[displayKey]} + onSelect={() => onRowClick(item)} + onDeselect={() => onRowClick(item)} + /> )} - {error ?
error
: ''} -
- ); - } + renderToolbar={props => } + showPageSizeOptions={false} + /> +
+ ); } SelectResourceStep.propTypes = { @@ -134,7 +124,7 @@ SelectResourceStep.propTypes = { sortColumns: SortColumns, displayKey: PropTypes.string, onRowClick: PropTypes.func, - onSearch: PropTypes.func.isRequired, + fetchItems: PropTypes.func.isRequired, selectedLabel: PropTypes.string, selectedResourceRows: PropTypes.arrayOf(PropTypes.object), }; diff --git a/awx/ui_next/src/components/AddRole/SelectResourceStep.test.jsx b/awx/ui_next/src/components/AddRole/SelectResourceStep.test.jsx index 9d25292c9680..4e307b7595bf 100644 --- a/awx/ui_next/src/components/AddRole/SelectResourceStep.test.jsx +++ b/awx/ui_next/src/components/AddRole/SelectResourceStep.test.jsx @@ -1,7 +1,11 @@ import React from 'react'; -import { createMemoryHistory } from 'history'; +import { act } from 'react-dom/test-utils'; + import { shallow } from 'enzyme'; -import { mountWithContexts } from '../../../testUtils/enzymeHelpers'; +import { + mountWithContexts, + waitForElement, +} from '../../../testUtils/enzymeHelpers'; import { sleep } from '../../../testUtils/testUtils'; import SelectResourceStep from './SelectResourceStep'; @@ -9,7 +13,7 @@ describe('', () => { const searchColumns = [ { name: 'Username', - key: 'username', + key: 'username__icontains', isDefault: true, }, ]; @@ -30,12 +34,13 @@ describe('', () => { sortColumns={sortColumns} displayKey="username" onRowClick={() => {}} - onSearch={() => {}} + fetchItems={() => {}} + fetchOptions={() => {}} /> ); }); - test('fetches resources on mount', async () => { + test('fetches resources on mount and adds items to list', async () => { const handleSearch = jest.fn().mockResolvedValue({ data: { count: 2, @@ -45,61 +50,34 @@ describe('', () => { ], }, }); - mountWithContexts( - {}} - onSearch={handleSearch} - /> - ); - expect(handleSearch).toHaveBeenCalledWith({ - order_by: 'username', - page: 1, - page_size: 5, - }); - }); - - test('readResourceList properly adds rows to state', async () => { - const selectedResourceRows = [{ id: 1, username: 'foo', url: 'item/1' }]; - const handleSearch = jest.fn().mockResolvedValue({ + const options = jest.fn().mockResolvedValue({ data: { - count: 2, - results: [ - { id: 1, username: 'foo', url: 'item/1' }, - { id: 2, username: 'bar', url: 'item/2' }, - ], + actions: { + GET: {}, + POST: {}, + }, + related_search_fields: [], }, }); - const history = createMemoryHistory({ - initialEntries: [ - '/organizations/1/access?resource.page=1&resource.order_by=-username', - ], + let wrapper; + await act(async () => { + wrapper = mountWithContexts( + {}} + fetchItems={handleSearch} + fetchOptions={options} + /> + ); }); - const wrapper = await mountWithContexts( - {}} - onSearch={handleSearch} - selectedResourceRows={selectedResourceRows} - />, - { - context: { router: { history, route: { location: history.location } } }, - } - ).find('SelectResourceStep'); - await wrapper.instance().readResourceList(); expect(handleSearch).toHaveBeenCalledWith({ - order_by: '-username', + order_by: 'username', page: 1, page_size: 5, }); - expect(wrapper.state('resources')).toEqual([ - { id: 1, username: 'foo', url: 'item/1' }, - { id: 2, username: 'bar', url: 'item/2' }, - ]); + waitForElement(wrapper, 'CheckBoxListItem', el => el.length === 2); }); test('clicking on row fires callback with correct params', async () => { @@ -111,20 +89,34 @@ describe('', () => { { id: 2, username: 'bar', url: 'item/2' }, ], }; - const wrapper = mountWithContexts( - ({ data })} - selectedResourceRows={[]} - /> - ); + const options = jest.fn().mockResolvedValue({ + data: { + actions: { + GET: {}, + POST: {}, + }, + related_search_fields: [], + }, + }); + let wrapper; + await act(async () => { + wrapper = mountWithContexts( + ({ data })} + fetchOptions={options} + selectedResourceRows={[]} + /> + ); + }); await sleep(0); wrapper.update(); const checkboxListItemWrapper = wrapper.find('CheckboxListItem'); expect(checkboxListItemWrapper.length).toBe(2); + checkboxListItemWrapper .first() .find('input[type="checkbox"]') diff --git a/awx/ui_next/src/components/AddRole/SelectRoleStep.jsx b/awx/ui_next/src/components/AddRole/SelectRoleStep.jsx index 826c2d52aa15..32f0e6a96cb2 100644 --- a/awx/ui_next/src/components/AddRole/SelectRoleStep.jsx +++ b/awx/ui_next/src/components/AddRole/SelectRoleStep.jsx @@ -7,59 +7,55 @@ import { t } from '@lingui/macro'; import CheckboxCard from './CheckboxCard'; import SelectedList from '../SelectedList'; -class RolesStep extends React.Component { - render() { - const { - onRolesClick, - roles, - selectedListKey, - selectedListLabel, - selectedResourceRows, - selectedRoleRows, - i18n, - } = this.props; - - return ( - -
- {i18n._( - t`Choose roles to apply to the selected resources. Note that all selected roles will be applied to all selected resources.` - )} -
-
- {selectedResourceRows.length > 0 && ( - - )} -
-
- {Object.keys(roles).map(role => ( - item.id === roles[role].id - )} - key={roles[role].id} - name={roles[role].name} - onSelect={() => onRolesClick(roles[role])} - /> - ))} -
-
- ); - } +function RolesStep({ + onRolesClick, + roles, + selectedListKey, + selectedListLabel, + selectedResourceRows, + selectedRoleRows, + i18n, +}) { + return ( + +
+ {i18n._( + t`Choose roles to apply to the selected resources. Note that all selected roles will be applied to all selected resources.` + )} +
+
+ {selectedResourceRows.length > 0 && ( + + )} +
+
+ {Object.keys(roles).map(role => ( + item.id === roles[role].id + )} + key={roles[role].id} + name={roles[role].name} + onSelect={() => onRolesClick(roles[role])} + /> + ))} +
+
+ ); } RolesStep.propTypes = { diff --git a/awx/ui_next/src/components/AddRole/SelectableCard.jsx b/awx/ui_next/src/components/AddRole/SelectableCard.jsx deleted file mode 100644 index 475af3d2ce86..000000000000 --- a/awx/ui_next/src/components/AddRole/SelectableCard.jsx +++ /dev/null @@ -1,65 +0,0 @@ -import React, { Component } 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; - font-weight: bold; - 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 Label = styled.div` - display: flex; - flex: 1; - align-items: center; - padding: 20px; -`; - -class SelectableCard extends Component { - render() { - const { label, onClick, isSelected, dataCy } = this.props; - - return ( - - - - - ); - } -} - -SelectableCard.propTypes = { - label: PropTypes.string, - onClick: PropTypes.func.isRequired, - isSelected: PropTypes.bool, -}; - -SelectableCard.defaultProps = { - label: '', - isSelected: false, -}; - -export default SelectableCard; diff --git a/awx/ui_next/src/components/AddRole/index.js b/awx/ui_next/src/components/AddRole/index.js index 806e1721463f..52e9ec78d470 100644 --- a/awx/ui_next/src/components/AddRole/index.js +++ b/awx/ui_next/src/components/AddRole/index.js @@ -1,5 +1,4 @@ export { default as AddResourceRole } from './AddResourceRole'; export { default as CheckboxCard } from './CheckboxCard'; -export { default as SelectableCard } from './SelectableCard'; export { default as SelectResourceStep } from './SelectResourceStep'; export { default as SelectRoleStep } from './SelectRoleStep'; diff --git a/awx/ui_next/src/components/AlertModal/AlertModal.jsx b/awx/ui_next/src/components/AlertModal/AlertModal.jsx index 783f08f0a500..0c443300bed1 100644 --- a/awx/ui_next/src/components/AlertModal/AlertModal.jsx +++ b/awx/ui_next/src/components/AlertModal/AlertModal.jsx @@ -1,41 +1,89 @@ +import 'styled-components/macro'; import React from 'react'; - -import { Modal } from '@patternfly/react-core'; - +import { Modal, Title } from '@patternfly/react-core'; import { - ExclamationTriangleIcon, + CheckCircleIcon, ExclamationCircleIcon, + ExclamationTriangleIcon, InfoCircleIcon, - CheckCircleIcon, + TimesCircleIcon, } from '@patternfly/react-icons'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import styled from 'styled-components'; -const getIcon = variant => { - let icon; - if (variant === 'warning') { - icon = ; - } else if (variant === 'danger') { - icon = ; +const Header = styled.div` + display: flex; + svg { + margin-right: 16px; } - if (variant === 'info') { - icon = ; - } - if (variant === 'success') { - icon = ; - } - return icon; -}; +`; + +function AlertModal({ + i18n, + isOpen = null, + title, + label, + variant, + children, + i18nHash, + ...props +}) { + const variantIcons = { + danger: ( + + ), + error: ( + + ), + info: ( + + ), + success: ( + + ), + warning: ( + + ), + }; + + const customHeader = ( +
+ {variant ? variantIcons[variant] : null} + + {title} + +
+ ); -export default ({ variant, children, ...props }) => { - const { isOpen = null } = props; - props.isOpen = Boolean(isOpen); return ( {children} - {getIcon(variant)} ); -}; +} + +export default withI18n()(AlertModal); diff --git a/awx/ui_next/src/components/AlertModal/AlertModal.test.jsx b/awx/ui_next/src/components/AlertModal/AlertModal.test.jsx index 4c9389543f58..0173e5a378f9 100644 --- a/awx/ui_next/src/components/AlertModal/AlertModal.test.jsx +++ b/awx/ui_next/src/components/AlertModal/AlertModal.test.jsx @@ -1,11 +1,13 @@ import React from 'react'; -import { mount } from 'enzyme'; +import { mountWithContexts } from '../../../testUtils/enzymeHelpers'; import AlertModal from './AlertModal'; describe('AlertModal', () => { test('renders the expected content', () => { - const wrapper = mount(); + const wrapper = mountWithContexts( + Are you sure? + ); expect(wrapper).toHaveLength(1); }); }); diff --git a/awx/ui_next/src/components/AnsibleSelect/AnsibleSelect.jsx b/awx/ui_next/src/components/AnsibleSelect/AnsibleSelect.jsx index cf860ab8f05d..62b8983eb45e 100644 --- a/awx/ui_next/src/components/AnsibleSelect/AnsibleSelect.jsx +++ b/awx/ui_next/src/components/AnsibleSelect/AnsibleSelect.jsx @@ -12,42 +12,44 @@ import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; import { FormSelect, FormSelectOption } from '@patternfly/react-core'; -class AnsibleSelect extends React.Component { - constructor(props) { - super(props); - this.onSelectChange = this.onSelectChange.bind(this); - } - - onSelectChange(val, event) { - const { onChange, name } = this.props; +function AnsibleSelect({ + id, + data, + i18n, + isValid, + onBlur, + value, + className, + isDisabled, + onChange, + name, +}) { + const onSelectChange = (val, event) => { event.target.name = name; onChange(event, val); - } - - render() { - const { id, data, i18n, isValid, onBlur, value, className } = this.props; + }; - return ( - - {data.map(option => ( - - ))} - - ); - } + return ( + + {data.map(option => ( + + ))} + + ); } const Option = shape({ @@ -62,6 +64,7 @@ AnsibleSelect.defaultProps = { isValid: true, onBlur: () => {}, className: '', + isDisabled: false, }; AnsibleSelect.propTypes = { @@ -72,6 +75,7 @@ AnsibleSelect.propTypes = { onChange: func.isRequired, value: oneOfType([string, number]).isRequired, className: string, + isDisabled: bool, }; export { AnsibleSelect as _AnsibleSelect }; diff --git a/awx/ui_next/src/components/AnsibleSelect/AnsibleSelect.test.jsx b/awx/ui_next/src/components/AnsibleSelect/AnsibleSelect.test.jsx index d9bd7c669d15..bb671c8823a2 100644 --- a/awx/ui_next/src/components/AnsibleSelect/AnsibleSelect.test.jsx +++ b/awx/ui_next/src/components/AnsibleSelect/AnsibleSelect.test.jsx @@ -1,21 +1,22 @@ import React from 'react'; import { mountWithContexts } from '../../../testUtils/enzymeHelpers'; -import AnsibleSelect, { _AnsibleSelect } from './AnsibleSelect'; +import AnsibleSelect from './AnsibleSelect'; const mockData = [ { key: 'baz', label: 'Baz', - value: '/venv/baz/', + value: '/var/lib/awx/venv/baz/', }, { key: 'default', label: 'Default', - value: '/venv/ansible/', + value: '/var/lib/awx/venv/ansible/', }, ]; describe('', () => { + const onChange = jest.fn(); test('initially renders succesfully', async () => { mountWithContexts( ', () => { }); test('calls "onSelectChange" on dropdown select change', () => { - const spy = jest.spyOn(_AnsibleSelect.prototype, 'onSelectChange'); const wrapper = mountWithContexts( {}} + onChange={onChange} data={mockData} /> ); - expect(spy).not.toHaveBeenCalled(); + expect(onChange).not.toHaveBeenCalled(); wrapper.find('select').simulate('change'); - expect(spy).toHaveBeenCalled(); + expect(onChange).toHaveBeenCalled(); }); test('Returns correct select options', () => { diff --git a/awx/ui_next/src/components/AppContainer/AppContainer.jsx b/awx/ui_next/src/components/AppContainer/AppContainer.jsx new file mode 100644 index 000000000000..0abd198c07e5 --- /dev/null +++ b/awx/ui_next/src/components/AppContainer/AppContainer.jsx @@ -0,0 +1,251 @@ +import React, { useEffect, useState, useCallback, useRef } from 'react'; +import { useHistory, useLocation, withRouter } from 'react-router-dom'; +import { + Button, + Nav, + NavList, + Page, + PageHeader as PFPageHeader, + PageSidebar, +} from '@patternfly/react-core'; +import { t } from '@lingui/macro'; +import { withI18n } from '@lingui/react'; +import styled from 'styled-components'; + +import { ConfigAPI, MeAPI, RootAPI } from '../../api'; +import { ConfigProvider } from '../../contexts/Config'; +import { SESSION_TIMEOUT_KEY } from '../../constants'; +import { isAuthenticated } from '../../util/auth'; +import About from '../About'; +import AlertModal from '../AlertModal'; +import ErrorDetail from '../ErrorDetail'; +import BrandLogo from './BrandLogo'; +import NavExpandableGroup from './NavExpandableGroup'; +import PageHeaderToolbar from './PageHeaderToolbar'; + +// 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; + +const PageHeader = styled(PFPageHeader)` + & .pf-c-page__header-brand-link { + color: inherit; + + &:hover { + color: inherit; + } + } +`; + +/** + * 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, val); + setStorageVal(val); + }; + return [storageVal, setValue]; +} + +function AppContainer({ i18n, navRouteConfig = [], children }) { + const history = useHistory(); + const { pathname } = useLocation(); + const [config, setConfig] = useState({}); + const [configError, setConfigError] = useState(null); + const [isAboutModalOpen, setIsAboutModalOpen] = useState(false); + const [isReady, setIsReady] = useState(false); + + const sessionTimeoutId = useRef(); + const sessionIntervalId = useRef(); + const [sessionTimeout, setSessionTimeout] = useStorage(SESSION_TIMEOUT_KEY); + const [timeoutWarning, setTimeoutWarning] = useState(false); + const [timeRemaining, setTimeRemaining] = useState(null); + + const handleAboutModalOpen = () => setIsAboutModalOpen(true); + const handleAboutModalClose = () => setIsAboutModalOpen(false); + const handleConfigErrorClose = () => setConfigError(null); + const handleSessionTimeout = () => setTimeoutWarning(true); + + const handleLogout = useCallback(async () => { + await RootAPI.logout(); + setSessionTimeout(null); + }, [setSessionTimeout]); + + const handleSessionContinue = () => { + MeAPI.read(); + setTimeoutWarning(false); + }; + + useEffect(() => { + if (!isAuthenticated(document.cookie)) history.replace('/login'); + const calcRemaining = () => + parseInt(sessionTimeout, 10) - new Date().getTime(); + const updateRemaining = () => setTimeRemaining(calcRemaining()); + setTimeoutWarning(false); + clearTimeout(sessionTimeoutId.current); + clearInterval(sessionIntervalId.current); + sessionTimeoutId.current = setTimeout( + handleSessionTimeout, + Math.min(calcRemaining() - SESSION_WARNING_DURATION * 1000, MAX_TIMEOUT) + ); + sessionIntervalId.current = setInterval(updateRemaining, 1000); + return () => { + clearTimeout(sessionTimeoutId.current); + clearInterval(sessionIntervalId.current); + }; + }, [history, sessionTimeout]); + + useEffect(() => { + if (timeRemaining !== null && timeRemaining <= 1) { + handleLogout(); + } + }, [handleLogout, timeRemaining]); + + useEffect(() => { + const loadConfig = async () => { + if (config?.version) return; + try { + const [ + { data }, + { + data: { + results: [me], + }, + }, + ] = await Promise.all([ConfigAPI.read(), MeAPI.read()]); + setConfig({ ...data, me }); + setIsReady(true); + } catch (err) { + if (err.response.status === 401) { + handleLogout(); + return; + } + setConfigError(err); + } + }; + loadConfig(); + }, [config, pathname, handleLogout]); + + const header = ( + } + logoProps={{ href: '/' }} + headerTools={ + + } + /> + ); + + const sidebar = ( + + + {navRouteConfig.map(({ groupId, groupTitle, routes }) => ( + + ))} + + + } + /> + ); + + return ( + <> + + {isReady && {children}} + + + + {i18n._(t`Failed to retrieve configuration.`)} + + + 0 && timeRemaining !== null} + onClose={handleLogout} + showClose={false} + variant="warning" + actions={[ + , + , + ]} + > + {i18n._( + t`You will be logged out in ${Number( + Math.max(Math.floor(timeRemaining / 1000), 0) + )} seconds due to inactivity.` + )} + + + ); +} + +export { AppContainer as _AppContainer }; +export default withI18n()(withRouter(AppContainer)); diff --git a/awx/ui_next/src/components/AppContainer/AppContainer.test.jsx b/awx/ui_next/src/components/AppContainer/AppContainer.test.jsx new file mode 100644 index 000000000000..bdb67db490e6 --- /dev/null +++ b/awx/ui_next/src/components/AppContainer/AppContainer.test.jsx @@ -0,0 +1,124 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { + mountWithContexts, + waitForElement, +} from '../../../testUtils/enzymeHelpers'; +import { ConfigAPI, MeAPI, RootAPI } from '../../api'; +import AppContainer from './AppContainer'; + +jest.mock('../../api'); + +describe('', () => { + const ansible_version = '111'; + const custom_virtualenvs = []; + const version = '222'; + + beforeEach(() => { + ConfigAPI.read.mockResolvedValue({ + data: { + ansible_version, + custom_virtualenvs, + version, + }, + }); + MeAPI.read.mockResolvedValue({ data: { results: [{}] } }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('expected content is rendered', async () => { + const routeConfig = [ + { + groupTitle: 'Group One', + groupId: 'group_one', + routes: [ + { title: 'Foo', path: '/foo' }, + { title: 'Bar', path: '/bar' }, + ], + }, + { + groupTitle: 'Group Two', + groupId: 'group_two', + routes: [{ title: 'Fiz', path: '/fiz' }], + }, + ]; + + let wrapper; + await act(async () => { + wrapper = mountWithContexts( + + {routeConfig.map(({ groupId }) => ( +
+ ))} + + ); + }); + wrapper.update(); + + // page components + expect(wrapper.length).toBe(1); + expect(wrapper.find('PageHeader').length).toBe(1); + expect(wrapper.find('PageSidebar').length).toBe(1); + + // sidebar groups and route links + expect(wrapper.find('NavExpandableGroup').length).toBe(2); + expect(wrapper.find('a[href="/foo"]').length).toBe(1); + expect(wrapper.find('a[href="/bar"]').length).toBe(1); + expect(wrapper.find('a[href="/fiz"]').length).toBe(1); + + expect(wrapper.find('#group_one').length).toBe(1); + expect(wrapper.find('#group_two').length).toBe(1); + }); + + test('opening the about modal renders prefetched config data', async () => { + const aboutDropdown = 'Dropdown QuestionCircleIcon'; + const aboutButton = 'DropdownItem li button'; + const aboutModalContent = 'AboutModalBoxContent'; + const aboutModalClose = 'button[aria-label="Close Dialog"]'; + + let wrapper; + await act(async () => { + wrapper = mountWithContexts(); + }); + + // open about dropdown menu + await waitForElement(wrapper, aboutDropdown); + wrapper.find(aboutDropdown).simulate('click'); + + // open about modal + ( + await waitForElement(wrapper, aboutButton, el => !el.props().disabled) + ).simulate('click'); + + // check about modal content + const content = await waitForElement(wrapper, aboutModalContent); + expect(content.find('dd').text()).toContain(ansible_version); + expect(content.find('pre').text()).toContain(`< AWX ${version} >`); + + // close about modal + wrapper.find(aboutModalClose).simulate('click'); + expect(wrapper.find(aboutModalContent)).toHaveLength(0); + }); + + test('logout makes expected call to api client', async () => { + const userMenuButton = 'UserIcon'; + const logoutButton = '#logout-button button'; + + let wrapper; + await act(async () => { + wrapper = mountWithContexts(); + }); + + // open the user menu + expect(wrapper.find(logoutButton)).toHaveLength(0); + wrapper.find(userMenuButton).simulate('click'); + expect(wrapper.find(logoutButton)).toHaveLength(1); + + // logout + wrapper.find(logoutButton).simulate('click'); + expect(RootAPI.logout).toHaveBeenCalledTimes(1); + }); +}); diff --git a/awx/ui_next/src/components/AppContainer/BrandLogo.jsx b/awx/ui_next/src/components/AppContainer/BrandLogo.jsx new file mode 100644 index 000000000000..be15af67735a --- /dev/null +++ b/awx/ui_next/src/components/AppContainer/BrandLogo.jsx @@ -0,0 +1,18 @@ +import React from 'react'; + +import styled from 'styled-components'; + +const BrandImg = styled.img` + flex: initial; + height: 76px; + width: initial; + padding-left: 0px; + margin: 0px 0px 0px 0px; + max-width: 100px; + max-height: initial; + pointer-events: none; +`; + +const BrandLogo = () => ; + +export default BrandLogo; diff --git a/awx/ui_next/src/components/BrandLogo/BrandLogo.test.jsx b/awx/ui_next/src/components/AppContainer/BrandLogo.test.jsx similarity index 85% rename from awx/ui_next/src/components/BrandLogo/BrandLogo.test.jsx rename to awx/ui_next/src/components/AppContainer/BrandLogo.test.jsx index 02ba64458613..641332be6663 100644 --- a/awx/ui_next/src/components/BrandLogo/BrandLogo.test.jsx +++ b/awx/ui_next/src/components/AppContainer/BrandLogo.test.jsx @@ -4,11 +4,11 @@ import BrandLogo from './BrandLogo'; let logoWrapper; let brandLogoElem; -let svgElem; +let imgElem; const findChildren = () => { brandLogoElem = logoWrapper.find('BrandLogo'); - svgElem = logoWrapper.find('svg'); + imgElem = logoWrapper.find('img'); }; describe('', () => { @@ -17,6 +17,6 @@ describe('', () => { findChildren(); expect(logoWrapper.length).toBe(1); expect(brandLogoElem.length).toBe(1); - expect(svgElem.length).toBe(1); + expect(imgElem.length).toBe(1); }); }); diff --git a/awx/ui_next/src/components/AppContainer/NavExpandableGroup.jsx b/awx/ui_next/src/components/AppContainer/NavExpandableGroup.jsx new file mode 100644 index 000000000000..45c3b91faeac --- /dev/null +++ b/awx/ui_next/src/components/AppContainer/NavExpandableGroup.jsx @@ -0,0 +1,66 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { matchPath, Link, withRouter } from 'react-router-dom'; +import { NavExpandable, NavItem } from '@patternfly/react-core'; + +class NavExpandableGroup extends Component { + constructor(props) { + super(props); + const { routes } = this.props; + + // Extract a list of paths from the route params and store them for later. This creates + // an array of url paths associated with any NavItem component rendered by this component. + this.navItemPaths = routes.map(({ path }) => path); + this.isActiveGroup = this.isActiveGroup.bind(this); + this.isActivePath = this.isActivePath.bind(this); + } + + isActiveGroup() { + return this.navItemPaths.some(this.isActivePath); + } + + isActivePath(path) { + const { history } = this.props; + return Boolean(matchPath(history.location.pathname, { path })); + } + + render() { + const { groupId, groupTitle, routes } = this.props; + + if (routes.length === 1) { + const [{ path }] = routes; + return ( + + {groupTitle} + + ); + } + + return ( + + {routes.map(({ path, title }) => ( + + {title} + + ))} + + ); + } +} + +NavExpandableGroup.propTypes = { + groupId: PropTypes.string.isRequired, + groupTitle: PropTypes.string.isRequired, + routes: PropTypes.arrayOf(PropTypes.object).isRequired, +}; + +export default withRouter(NavExpandableGroup); diff --git a/awx/ui_next/src/components/NavExpandableGroup/NavExpandableGroup.test.jsx b/awx/ui_next/src/components/AppContainer/NavExpandableGroup.test.jsx similarity index 100% rename from awx/ui_next/src/components/NavExpandableGroup/NavExpandableGroup.test.jsx rename to awx/ui_next/src/components/AppContainer/NavExpandableGroup.test.jsx diff --git a/awx/ui_next/src/components/AppContainer/PageHeaderToolbar.jsx b/awx/ui_next/src/components/AppContainer/PageHeaderToolbar.jsx new file mode 100644 index 000000000000..4d31af328e6c --- /dev/null +++ b/awx/ui_next/src/components/AppContainer/PageHeaderToolbar.jsx @@ -0,0 +1,126 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import { + Dropdown, + DropdownItem, + DropdownToggle, + DropdownPosition, + PageHeaderTools, + PageHeaderToolsGroup, + PageHeaderToolsItem, + Tooltip, +} from '@patternfly/react-core'; +import { QuestionCircleIcon, UserIcon } from '@patternfly/react-icons'; + +const DOCLINK = + 'https://docs.ansible.com/ansible-tower/latest/html/userguide/index.html'; + +function PageHeaderToolbar({ + isAboutDisabled, + onAboutClick, + onLogoutClick, + loggedInUser, + i18n, +}) { + const [isHelpOpen, setIsHelpOpen] = useState(false); + const [isUserOpen, setIsUserOpen] = useState(false); + + const handleHelpSelect = () => { + setIsHelpOpen(!isHelpOpen); + }; + + const handleUserSelect = () => { + setIsUserOpen(!isUserOpen); + }; + + return ( + + + {i18n._(t`Info`)}
}> + + + + + } + dropdownItems={[ + + {i18n._(t`Help`)} + , + + {i18n._(t`About`)} + , + ]} + /> + + + {i18n._(t`User`)}
}> + + + + {loggedInUser && ( + + {loggedInUser.username} + + )} + + } + dropdownItems={[ + + {i18n._(t`User Details`)} + , + + {i18n._(t`Logout`)} + , + ]} + /> + + + + + ); +} + +PageHeaderToolbar.propTypes = { + isAboutDisabled: PropTypes.bool, + onAboutClick: PropTypes.func.isRequired, + onLogoutClick: PropTypes.func.isRequired, +}; + +PageHeaderToolbar.defaultProps = { + isAboutDisabled: false, +}; + +export default withI18n()(PageHeaderToolbar); diff --git a/awx/ui_next/src/components/PageHeaderToolbar/PageHeaderToolbar.test.jsx b/awx/ui_next/src/components/AppContainer/PageHeaderToolbar.test.jsx similarity index 100% rename from awx/ui_next/src/components/PageHeaderToolbar/PageHeaderToolbar.test.jsx rename to awx/ui_next/src/components/AppContainer/PageHeaderToolbar.test.jsx diff --git a/awx/ui_next/src/components/AppContainer/index.jsx b/awx/ui_next/src/components/AppContainer/index.jsx new file mode 100644 index 000000000000..4fa9f5e563fe --- /dev/null +++ b/awx/ui_next/src/components/AppContainer/index.jsx @@ -0,0 +1,3 @@ +import AppContainer from './AppContainer'; + +export default AppContainer; diff --git a/awx/ui_next/src/components/AssociateModal/AssociateModal.jsx b/awx/ui_next/src/components/AssociateModal/AssociateModal.jsx new file mode 100644 index 000000000000..bddb53b949e3 --- /dev/null +++ b/awx/ui_next/src/components/AssociateModal/AssociateModal.jsx @@ -0,0 +1,164 @@ +import React, { Fragment, useEffect, useCallback } from 'react'; +import { useHistory } from 'react-router-dom'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import { Button, Modal } from '@patternfly/react-core'; +import OptionsList from '../OptionsList'; +import useRequest from '../../util/useRequest'; +import { getQSConfig, parseQueryString } from '../../util/qs'; +import useSelected from '../../util/useSelected'; + +const QS_CONFIG = (order_by = 'name') => { + return getQSConfig('associate', { + page: 1, + page_size: 5, + order_by, + }); +}; + +function AssociateModal({ + i18n, + header = i18n._(t`Items`), + title = i18n._(t`Select Items`), + onClose, + onAssociate, + fetchRequest, + optionsRequest, + isModalOpen = false, + displayKey = 'name', +}) { + const history = useHistory(); + const { selected, handleSelect } = useSelected([]); + + const { + request: fetchItems, + result: { items, itemCount, relatedSearchableKeys, searchableKeys }, + error: contentError, + isLoading, + } = useRequest( + useCallback(async () => { + const params = parseQueryString( + QS_CONFIG(displayKey), + history.location.search + ); + const [ + { + data: { count, results }, + }, + actionsResponse, + ] = await Promise.all([fetchRequest(params), optionsRequest()]); + + return { + items: results, + itemCount: count, + relatedSearchableKeys: ( + actionsResponse?.data?.related_search_fields || [] + ).map(val => val.slice(0, -8)), + searchableKeys: Object.keys( + actionsResponse.data.actions?.GET || {} + ).filter(key => actionsResponse.data.actions?.GET[key].filterable), + }; + }, [fetchRequest, optionsRequest, history.location.search, displayKey]), + { + items: [], + itemCount: 0, + relatedSearchableKeys: [], + searchableKeys: [], + } + ); + + useEffect(() => { + fetchItems(); + }, [fetchItems]); + + const clearQSParams = () => { + const parts = history.location.search.replace(/^\?/, '').split('&'); + const { namespace } = QS_CONFIG(displayKey); + const otherParts = parts.filter( + param => !param.startsWith(`${namespace}.`) + ); + history.replace(`${history.location.pathname}?${otherParts.join('&')}`); + }; + + const handleSave = async () => { + await onAssociate(selected); + clearQSParams(); + onClose(); + }; + + const handleClose = () => { + clearQSParams(); + onClose(); + }; + + return ( + + + {i18n._(t`Save`)} + , + , + ]} + > + + + + ); +} + +export default withI18n()(AssociateModal); diff --git a/awx/ui_next/src/components/AssociateModal/AssociateModal.test.jsx b/awx/ui_next/src/components/AssociateModal/AssociateModal.test.jsx new file mode 100644 index 000000000000..4b58e900e38d --- /dev/null +++ b/awx/ui_next/src/components/AssociateModal/AssociateModal.test.jsx @@ -0,0 +1,86 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; + +import { + mountWithContexts, + waitForElement, +} from '../../../testUtils/enzymeHelpers'; +import AssociateModal from './AssociateModal'; +import mockHosts from './data.hosts.json'; + +jest.mock('../../api'); + +describe('', () => { + let wrapper; + const onClose = jest.fn(); + const onAssociate = jest.fn().mockResolvedValue(); + const fetchRequest = jest.fn().mockReturnValue({ data: { ...mockHosts } }); + const optionsRequest = jest.fn().mockResolvedValue({ + data: { + actions: { + GET: {}, + POST: {}, + }, + related_search_fields: [], + }, + }); + + beforeEach(async () => { + await act(async () => { + wrapper = mountWithContexts( + + ); + }); + await waitForElement(wrapper, 'ContentLoading', el => el.length === 0); + }); + + afterEach(() => { + wrapper.unmount(); + jest.clearAllMocks(); + }); + + test('should render successfully', () => { + expect(wrapper.find('AssociateModal').length).toBe(1); + }); + + test('should fetch and render list items', () => { + expect(fetchRequest).toHaveBeenCalledTimes(1); + expect(wrapper.find('CheckboxListItem').length).toBe(3); + }); + + test('should update selected list chips when items are selected', () => { + expect(wrapper.find('SelectedList Chip')).toHaveLength(0); + act(() => { + wrapper + .find('CheckboxListItem') + .first() + .invoke('onSelect')(); + }); + wrapper.update(); + expect(wrapper.find('SelectedList Chip')).toHaveLength(1); + wrapper.find('SelectedList Chip button').simulate('click'); + expect(wrapper.find('SelectedList Chip')).toHaveLength(0); + }); + + test('save button should call onAssociate', () => { + act(() => { + wrapper + .find('CheckboxListItem') + .first() + .invoke('onSelect')(); + }); + wrapper.find('button[aria-label="Save"]').simulate('click'); + expect(onAssociate).toHaveBeenCalledTimes(1); + }); + + test('cancel button should call onClose', () => { + wrapper.find('button[aria-label="Cancel"]').simulate('click'); + expect(onClose).toHaveBeenCalledTimes(1); + }); +}); diff --git a/awx/ui_next/src/components/AssociateModal/data.hosts.json b/awx/ui_next/src/components/AssociateModal/data.hosts.json new file mode 100644 index 000000000000..07c6ef7d9f8d --- /dev/null +++ b/awx/ui_next/src/components/AssociateModal/data.hosts.json @@ -0,0 +1,393 @@ + +{ + "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": "", + "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": "", + "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": "", + "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_next/src/components/AssociateModal/index.js b/awx/ui_next/src/components/AssociateModal/index.js new file mode 100644 index 000000000000..1a9df3aa33c8 --- /dev/null +++ b/awx/ui_next/src/components/AssociateModal/index.js @@ -0,0 +1 @@ +export { default } from './AssociateModal'; diff --git a/awx/ui_next/src/components/Background/Background.jsx b/awx/ui_next/src/components/Background/Background.jsx index d44711fa17e2..755393712c26 100644 --- a/awx/ui_next/src/components/Background/Background.jsx +++ b/awx/ui_next/src/components/Background/Background.jsx @@ -1,20 +1,10 @@ import React, { Fragment } from 'react'; -import { BackgroundImage, BackgroundImageSrc } from '@patternfly/react-core'; -import bgFilter from '@patternfly/patternfly/assets/images/background-filter.svg'; - -const backgroundImageConfig = { - [BackgroundImageSrc.xs]: '/assets/images/pfbg_576.jpg', - [BackgroundImageSrc.xs2x]: '/assets/images/pfbg_576@2x.jpg', - [BackgroundImageSrc.sm]: '/assets/images/pfbg_768.jpg', - [BackgroundImageSrc.sm2x]: '/assets/images/pfbg_768@2x.jpg', - [BackgroundImageSrc.lg]: '/assets/images/pfbg_2000.jpg', - [BackgroundImageSrc.filter]: `${bgFilter}#image_overlay`, -}; +import { BackgroundImage } from '@patternfly/react-core'; export default ({ children }) => ( - + {children} ); diff --git a/awx/ui_next/src/components/Background/Background.test.jsx b/awx/ui_next/src/components/Background/Background.test.jsx index 382bdba66c3e..4d306f79a5a1 100644 --- a/awx/ui_next/src/components/Background/Background.test.jsx +++ b/awx/ui_next/src/components/Background/Background.test.jsx @@ -11,7 +11,7 @@ describe('Background', () => { ); expect(wrapper).toHaveLength(1); - expect(wrapper.find('BackgroundImage')).toHaveLength(1); + expect(wrapper.find('.pf-c-background-image')).toHaveLength(1); expect(wrapper.find('#test')).toHaveLength(1); }); }); diff --git a/awx/ui_next/src/components/BrandLogo/BrandLogo.jsx b/awx/ui_next/src/components/BrandLogo/BrandLogo.jsx deleted file mode 100644 index 7943d1c287cd..000000000000 --- a/awx/ui_next/src/components/BrandLogo/BrandLogo.jsx +++ /dev/null @@ -1,374 +0,0 @@ -import React, { Component } from 'react'; -import { withI18n } from '@lingui/react'; -import { t } from '@lingui/macro'; - -import styled from 'styled-components'; - -const ST0 = styled.g` - display: none; -`; - -const ST1 = styled.path` - display: inline; - fill: #ed1c24; -`; - -const ST2 = styled.path` - fill: #42210b; -`; - -const ST3 = styled.path` - fill: #ffffff; -`; - -const ST4 = styled.path` - fill: #c69c6d; - stroke: #8c6239; - stroke-width: 5; - stroke-miterlimit: 10; -`; - -const ST5 = styled.path` - fill: #ffffff; - stroke: #42210b; - stroke-width: 3; - stroke-miterlimit: 10; -`; - -const ST6 = styled.ellipse` - fill: #ed1c24; - stroke: #8c6239; - stroke-width: 5; - stroke-miterlimit: 10; -`; - -const ST7 = styled.path` - fill: #a67c52; -`; - -const ST8 = styled.path` - fill: #ed1c24; -`; - -const ST9 = styled.ellipse` - fill: #42210b; -`; - -class BrandLogo extends Component { - render() { - const { i18n } = this.props; - return ( - - {i18n._(t`AWX Logo`)} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); - } -} - -export default withI18n()(BrandLogo); diff --git a/awx/ui_next/src/components/BrandLogo/index.js b/awx/ui_next/src/components/BrandLogo/index.js deleted file mode 100644 index aa50d3906dbf..000000000000 --- a/awx/ui_next/src/components/BrandLogo/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './BrandLogo'; diff --git a/awx/ui_next/src/components/Breadcrumbs/Breadcrumbs.jsx b/awx/ui_next/src/components/Breadcrumbs/Breadcrumbs.jsx deleted file mode 100644 index 65b5978c9212..000000000000 --- a/awx/ui_next/src/components/Breadcrumbs/Breadcrumbs.jsx +++ /dev/null @@ -1,81 +0,0 @@ -import React, { Fragment } from 'react'; -import PropTypes from 'prop-types'; -import { - PageSection as PFPageSection, - PageSectionVariants, - Breadcrumb, - BreadcrumbItem, - BreadcrumbHeading as PFBreadcrumbHeading, -} from '@patternfly/react-core'; -import { Link, Route, withRouter } from 'react-router-dom'; -import styled from 'styled-components'; - -const PageSection = styled(PFPageSection)` - padding-top: 10px; - padding-bottom: 10px; -`; - -const BreadcrumbHeading = styled(PFBreadcrumbHeading)` - --pf-c-breadcrumb__heading--FontSize: 20px; - line-height: 24px; - flex: 100%; -`; - -const Breadcrumbs = ({ breadcrumbConfig }) => { - const { light } = PageSectionVariants; - - return ( - - - ( - - )} - /> - - - ); -}; - -const Crumb = ({ breadcrumbConfig, match }) => { - const crumb = breadcrumbConfig[match.url]; - - let crumbElement = ( - - {crumb} - - ); - - if (match.isExact) { - crumbElement = ( - {crumb} - ); - } - - if (!crumb) { - crumbElement = null; - } - - return ( - - {crumbElement} - ( - - )} - /> - - ); -}; - -Breadcrumbs.propTypes = { - breadcrumbConfig: PropTypes.objectOf(PropTypes.string).isRequired, -}; - -Crumb.propTypes = { - breadcrumbConfig: PropTypes.objectOf(PropTypes.string).isRequired, -}; - -export default withRouter(Breadcrumbs); diff --git a/awx/ui_next/src/components/Breadcrumbs/Breadcrumbs.test.jsx b/awx/ui_next/src/components/Breadcrumbs/Breadcrumbs.test.jsx deleted file mode 100644 index 83e5e8c3fe37..000000000000 --- a/awx/ui_next/src/components/Breadcrumbs/Breadcrumbs.test.jsx +++ /dev/null @@ -1,66 +0,0 @@ -import React from 'react'; -import { mount } from 'enzyme'; -import { MemoryRouter } from 'react-router-dom'; -import Breadcrumbs from './Breadcrumbs'; - -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('Breadcrumb'); - breadcrumbItem = breadcrumbWrapper.find('BreadcrumbItem'); - breadcrumbHeading = breadcrumbWrapper.find('BreadcrumbHeading'); - }; - - test('initially renders succesfully', () => { - breadcrumbWrapper = mount( - - - - ); - - 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'); - breadcrumbWrapper.unmount(); - }); - - 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 = mount( - - - - ); - - expect(breadcrumbWrapper.find('BreadcrumbItem')).toHaveLength( - crumbLength - ); - breadcrumbWrapper.unmount(); - }); - }); -}); diff --git a/awx/ui_next/src/components/Breadcrumbs/index.js b/awx/ui_next/src/components/Breadcrumbs/index.js deleted file mode 100644 index 3ff68ca58940..000000000000 --- a/awx/ui_next/src/components/Breadcrumbs/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Breadcrumbs'; diff --git a/awx/ui_next/src/components/Card/CardActionsRow.jsx b/awx/ui_next/src/components/Card/CardActionsRow.jsx index 8312207fa951..3652692a8150 100644 --- a/awx/ui_next/src/components/Card/CardActionsRow.jsx +++ b/awx/ui_next/src/components/Card/CardActionsRow.jsx @@ -3,13 +3,8 @@ import { CardActions } from '@patternfly/react-core'; import styled from 'styled-components'; const CardActionsWrapper = styled.div` - display: flex; - justify-content: flex-end; margin-top: 20px; - - & > .pf-c-card__actions > :not(:first-child) { - margin-left: 0.5rem; - } + --pf-c-card__actions--PaddingLeft: 0; `; function CardActionsRow({ children }) { diff --git a/awx/ui_next/src/components/Card/TabbedCardHeader.js b/awx/ui_next/src/components/Card/TabbedCardHeader.js deleted file mode 100644 index 2fa040a9dbde..000000000000 --- a/awx/ui_next/src/components/Card/TabbedCardHeader.js +++ /dev/null @@ -1,13 +0,0 @@ -import styled from 'styled-components'; -import { CardHeader } from '@patternfly/react-core'; - -const TabbedCardHeader = styled(CardHeader)` - --pf-c-card--first-child--PaddingTop: 0; - --pf-c-card--child--PaddingLeft: 0; - --pf-c-card--child--PaddingRight: 0; - --pf-c-card__header--not-last-child--PaddingBottom: 24px; - --pf-c-card__header--not-last-child--PaddingBottom: 0; - position: relative; -`; - -export default TabbedCardHeader; diff --git a/awx/ui_next/src/components/Card/index.js b/awx/ui_next/src/components/Card/index.js index 860e50a05147..93de96efcabf 100644 --- a/awx/ui_next/src/components/Card/index.js +++ b/awx/ui_next/src/components/Card/index.js @@ -1,3 +1,2 @@ -export { default as TabbedCardHeader } from './TabbedCardHeader'; export { default as CardBody } from './CardBody'; export { default as CardActionsRow } from './CardActionsRow'; diff --git a/awx/ui_next/src/components/CardCloseButton/CardCloseButton.jsx b/awx/ui_next/src/components/CardCloseButton/CardCloseButton.jsx deleted file mode 100644 index 3802d1e9e0e8..000000000000 --- a/awx/ui_next/src/components/CardCloseButton/CardCloseButton.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react'; -import { string } from 'prop-types'; -import { Link as RRLink } from 'react-router-dom'; -import { Button } from '@patternfly/react-core'; -import { TimesIcon } from '@patternfly/react-icons'; -import { withI18n } from '@lingui/react'; -import { t } from '@lingui/macro'; -import styled from 'styled-components'; - -const Link = styled(RRLink)` - position: absolute; - top: 5px; - right: 4px; - color: var(--pf-c-button--m-plain--Color); -`; - -function CardCloseButton({ linkTo, i18n, i18nHash, ...props }) { - if (linkTo) { - return ( - - - - ); - } - return ( - - ); -} -CardCloseButton.propTypes = { - linkTo: string, -}; -CardCloseButton.defaultProps = { - linkTo: null, -}; - -export default withI18n()(CardCloseButton); diff --git a/awx/ui_next/src/components/CardCloseButton/CardCloseButton.test.jsx b/awx/ui_next/src/components/CardCloseButton/CardCloseButton.test.jsx deleted file mode 100644 index 3564971c72c4..000000000000 --- a/awx/ui_next/src/components/CardCloseButton/CardCloseButton.test.jsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import { mountWithContexts } from '../../../testUtils/enzymeHelpers'; -import CardCloseButton from './CardCloseButton'; - -describe('', () => { - test('should render close button', () => { - const wrapper = mountWithContexts(); - const button = wrapper.find('Button'); - expect(button).toHaveLength(1); - expect(button.prop('variant')).toBe('plain'); - expect(button.prop('aria-label')).toBe('Close'); - expect(wrapper.find('Link')).toHaveLength(0); - }); - - test('should render close link when `linkTo` prop provided', () => { - const wrapper = mountWithContexts(); - expect(wrapper.find('Button')).toHaveLength(0); - const link = wrapper.find('Link'); - expect(link).toHaveLength(1); - expect(link.prop('to')).toEqual('/foo'); - expect(link.prop('aria-label')).toEqual('Close'); - }); -}); diff --git a/awx/ui_next/src/components/CardCloseButton/index.js b/awx/ui_next/src/components/CardCloseButton/index.js deleted file mode 100644 index 5f2d2157be75..000000000000 --- a/awx/ui_next/src/components/CardCloseButton/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './CardCloseButton'; diff --git a/awx/ui_next/src/components/CheckboxListItem/CheckboxListItem.jsx b/awx/ui_next/src/components/CheckboxListItem/CheckboxListItem.jsx index 94e8f820175f..befaa106745a 100644 --- a/awx/ui_next/src/components/CheckboxListItem/CheckboxListItem.jsx +++ b/awx/ui_next/src/components/CheckboxListItem/CheckboxListItem.jsx @@ -4,22 +4,23 @@ import { DataListItem, DataListItemRow, DataListItemCells, - DataListCell, + DataListCheck, + Radio, } from '@patternfly/react-core'; -import DataListCheck from '@components/DataListCheck'; -import DataListRadio from '@components/DataListRadio'; -import VerticalSeparator from '../VerticalSeparator'; +import DataListCell from '../DataListCell'; const CheckboxListItem = ({ + isDisabled = false, + isRadio = false, + isSelected = false, itemId, - name, label, - isSelected, - onSelect, + name, onDeselect, - isRadio, + onSelect, }) => { - const CheckboxRadio = isRadio ? DataListRadio : DataListCheck; + const CheckboxRadio = isRadio ? Radio : DataListCheck; + return ( - - ,