diff --git a/awx/ui_next/.eslintrc b/awx/ui_next/.eslintrc
index b7c86c305a..350f50379b 100644
--- a/awx/ui_next/.eslintrc
+++ b/awx/ui_next/.eslintrc
@@ -78,7 +78,8 @@
"src",
"theme",
"gridColumns",
- "rows"
+ "rows",
+ "href"
],
"ignore": ["Ansible", "Tower", "JSON", "YAML", "lg"],
"ignoreComponent": [
diff --git a/awx/ui_next/src/components/AppContainer/PageHeaderToolbar.jsx b/awx/ui_next/src/components/AppContainer/PageHeaderToolbar.jsx
index 1238665601..bd8abe0849 100644
--- a/awx/ui_next/src/components/AppContainer/PageHeaderToolbar.jsx
+++ b/awx/ui_next/src/components/AppContainer/PageHeaderToolbar.jsx
@@ -22,6 +22,8 @@ import {
} from '@patternfly/react-icons';
import { WorkflowApprovalsAPI } from '../../api';
import useRequest from '../../util/useRequest';
+import getDocsBaseUrl from '../../util/getDocsBaseUrl';
+import { useConfig } from '../../contexts/Config';
import useWsPendingApprovalCount from './useWsPendingApprovalCount';
const PendingWorkflowApprovals = styled.div`
@@ -35,9 +37,6 @@ const PendingWorkflowApprovalBadge = styled(Badge)`
margin-left: 10px;
`;
-const DOCLINK =
- 'https://docs.ansible.com/ansible-tower/latest/html/userguide/index.html';
-
function PageHeaderToolbar({
isAboutDisabled,
onAboutClick,
@@ -47,6 +46,7 @@ function PageHeaderToolbar({
}) {
const [isHelpOpen, setIsHelpOpen] = useState(false);
const [isUserOpen, setIsUserOpen] = useState(false);
+ const config = useConfig();
const {
request: fetchPendingApprovalCount,
@@ -101,37 +101,39 @@ function PageHeaderToolbar({
- {i18n._(t`Info`)}}>
-
-
-
-
- }
- dropdownItems={[
-
- {i18n._(t`Help`)}
- ,
-
- {i18n._(t`About`)}
- ,
- ]}
- />
-
-
+
+
+
+
+ }
+ dropdownItems={[
+
+ {i18n._(t`Help`)}
+ ,
+
+ {i18n._(t`About`)}
+ ,
+ ]}
+ />
+ {i18n._(t`User`)}}>
{
// keeps page from fully reloading
@@ -262,6 +266,19 @@ function AdvancedSearch({
+
+
+
);
}
diff --git a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/AzureSubForm.jsx b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/AzureSubForm.jsx
index 7b3f4b1d00..8729445851 100644
--- a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/AzureSubForm.jsx
+++ b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/AzureSubForm.jsx
@@ -12,6 +12,8 @@ import {
HostFilterField,
} from './SharedFields';
import { required } from '../../../../util/validators';
+import getDocsBaseUrl from '../../../../util/getDocsBaseUrl';
+import { useConfig } from '../../../../contexts/Config';
const AzureSubForm = ({ autoPopulateCredential, i18n }) => {
const { setFieldValue } = useFormikContext();
@@ -19,6 +21,7 @@ const AzureSubForm = ({ autoPopulateCredential, i18n }) => {
name: 'credential',
validate: required(i18n._(t`Select a value for this field`), i18n),
});
+ const config = useConfig();
const handleCredentialUpdate = useCallback(
value => {
@@ -27,8 +30,9 @@ const AzureSubForm = ({ autoPopulateCredential, i18n }) => {
[setFieldValue]
);
- const pluginLink =
- 'http://docs.ansible.com/ansible-tower/latest/html/userguide/inventories.html#inventory-plugins';
+ const pluginLink = `${getDocsBaseUrl(
+ config
+ )}/html/userguide/inventories.html#inventory-plugins`;
const configLink =
'https://docs.ansible.com/ansible/latest/collections/azure/azcollection/azure_rm_inventory.html';
diff --git a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/EC2SubForm.jsx b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/EC2SubForm.jsx
index d4f41879ba..c5a988fa3d 100644
--- a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/EC2SubForm.jsx
+++ b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/EC2SubForm.jsx
@@ -11,10 +11,13 @@ import {
EnabledValueField,
HostFilterField,
} from './SharedFields';
+import getDocsBaseUrl from '../../../../util/getDocsBaseUrl';
+import { useConfig } from '../../../../contexts/Config';
const EC2SubForm = ({ i18n }) => {
const { setFieldValue } = useFormikContext();
const [credentialField] = useField('credential');
+ const config = useConfig();
const handleCredentialUpdate = useCallback(
value => {
@@ -23,8 +26,9 @@ const EC2SubForm = ({ i18n }) => {
[setFieldValue]
);
- const pluginLink =
- 'http://docs.ansible.com/ansible-tower/latest/html/userguide/inventories.html#inventory-plugins';
+ const pluginLink = `${getDocsBaseUrl(
+ config
+ )}/html/userguide/inventories.html#inventory-plugins`;
const configLink =
'https://docs.ansible.com/ansible/latest/collections/amazon/aws/aws_ec2_inventory.html';
diff --git a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/GCESubForm.jsx b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/GCESubForm.jsx
index eee39c1d93..793211107a 100644
--- a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/GCESubForm.jsx
+++ b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/GCESubForm.jsx
@@ -12,6 +12,8 @@ import {
SourceVarsField,
} from './SharedFields';
import { required } from '../../../../util/validators';
+import getDocsBaseUrl from '../../../../util/getDocsBaseUrl';
+import { useConfig } from '../../../../contexts/Config';
const GCESubForm = ({ autoPopulateCredential, i18n }) => {
const { setFieldValue } = useFormikContext();
@@ -19,6 +21,7 @@ const GCESubForm = ({ autoPopulateCredential, i18n }) => {
name: 'credential',
validate: required(i18n._(t`Select a value for this field`), i18n),
});
+ const config = useConfig();
const handleCredentialUpdate = useCallback(
value => {
@@ -27,8 +30,9 @@ const GCESubForm = ({ autoPopulateCredential, i18n }) => {
[setFieldValue]
);
- const pluginLink =
- 'http://docs.ansible.com/ansible-tower/latest/html/userguide/inventories.html#inventory-plugins';
+ const pluginLink = `${getDocsBaseUrl(
+ config
+ )}/html/userguide/inventories.html#inventory-plugins`;
const configLink =
'https://docs.ansible.com/ansible/latest/collections/google/cloud/gcp_compute_inventory.html';
diff --git a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/OpenStackSubForm.jsx b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/OpenStackSubForm.jsx
index 7404bfea6e..3d85189b10 100644
--- a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/OpenStackSubForm.jsx
+++ b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/OpenStackSubForm.jsx
@@ -12,6 +12,8 @@ import {
HostFilterField,
} from './SharedFields';
import { required } from '../../../../util/validators';
+import getDocsBaseUrl from '../../../../util/getDocsBaseUrl';
+import { useConfig } from '../../../../contexts/Config';
const OpenStackSubForm = ({ autoPopulateCredential, i18n }) => {
const { setFieldValue } = useFormikContext();
@@ -19,6 +21,7 @@ const OpenStackSubForm = ({ autoPopulateCredential, i18n }) => {
name: 'credential',
validate: required(i18n._(t`Select a value for this field`), i18n),
});
+ const config = useConfig();
const handleCredentialUpdate = useCallback(
value => {
@@ -27,8 +30,9 @@ const OpenStackSubForm = ({ autoPopulateCredential, i18n }) => {
[setFieldValue]
);
- const pluginLink =
- 'http://docs.ansible.com/ansible-tower/latest/html/userguide/inventories.html#inventory-plugins';
+ const pluginLink = `${getDocsBaseUrl(
+ config
+ )}/html/userguide/inventories.html#inventory-plugins`;
const configLink =
'https://docs.ansible.com/ansible/latest/collections/openstack/cloud/openstack_inventory.html';
diff --git a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/SatelliteSubForm.jsx b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/SatelliteSubForm.jsx
index 90dd5e8e0d..0d27c3b298 100644
--- a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/SatelliteSubForm.jsx
+++ b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/SatelliteSubForm.jsx
@@ -12,6 +12,8 @@ import {
HostFilterField,
} from './SharedFields';
import { required } from '../../../../util/validators';
+import getDocsBaseUrl from '../../../../util/getDocsBaseUrl';
+import { useConfig } from '../../../../contexts/Config';
const SatelliteSubForm = ({ autoPopulateCredential, i18n }) => {
const { setFieldValue } = useFormikContext();
@@ -19,6 +21,7 @@ const SatelliteSubForm = ({ autoPopulateCredential, i18n }) => {
name: 'credential',
validate: required(i18n._(t`Select a value for this field`), i18n),
});
+ const config = useConfig();
const handleCredentialUpdate = useCallback(
value => {
@@ -27,8 +30,9 @@ const SatelliteSubForm = ({ autoPopulateCredential, i18n }) => {
[setFieldValue]
);
- const pluginLink =
- 'http://docs.ansible.com/ansible-tower/latest/html/userguide/inventories.html#inventory-plugins';
+ const pluginLink = `${getDocsBaseUrl(
+ config
+ )}/html/userguide/inventories.html#inventory-plugins`;
const configLink =
'https://docs.ansible.com/ansible/latest/collections/theforeman/foreman/foreman_inventory.html';
diff --git a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/TowerSubForm.jsx b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/TowerSubForm.jsx
index 7898e05400..dd3f57df41 100644
--- a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/TowerSubForm.jsx
+++ b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/TowerSubForm.jsx
@@ -12,6 +12,8 @@ import {
SourceVarsField,
} from './SharedFields';
import { required } from '../../../../util/validators';
+import getDocsBaseUrl from '../../../../util/getDocsBaseUrl';
+import { useConfig } from '../../../../contexts/Config';
const TowerSubForm = ({ autoPopulateCredential, i18n }) => {
const { setFieldValue } = useFormikContext();
@@ -19,6 +21,7 @@ const TowerSubForm = ({ autoPopulateCredential, i18n }) => {
name: 'credential',
validate: required(i18n._(t`Select a value for this field`), i18n),
});
+ const config = useConfig();
const handleCredentialUpdate = useCallback(
value => {
@@ -27,8 +30,9 @@ const TowerSubForm = ({ autoPopulateCredential, i18n }) => {
[setFieldValue]
);
- const pluginLink =
- 'http://docs.ansible.com/ansible-tower/latest/html/userguide/inventories.html#inventory-plugins';
+ const pluginLink = `${getDocsBaseUrl(
+ config
+ )}/html/userguide/inventories.html#inventory-plugins`;
const configLink =
'https://docs.ansible.com/ansible/latest/collections/awx/awx/tower_inventory.html';
diff --git a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/VMwareSubForm.jsx b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/VMwareSubForm.jsx
index 89025d5fd3..f9b0b16eb4 100644
--- a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/VMwareSubForm.jsx
+++ b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/VMwareSubForm.jsx
@@ -12,6 +12,8 @@ import {
HostFilterField,
} from './SharedFields';
import { required } from '../../../../util/validators';
+import getDocsBaseUrl from '../../../../util/getDocsBaseUrl';
+import { useConfig } from '../../../../contexts/Config';
const VMwareSubForm = ({ autoPopulateCredential, i18n }) => {
const { setFieldValue } = useFormikContext();
@@ -19,6 +21,7 @@ const VMwareSubForm = ({ autoPopulateCredential, i18n }) => {
name: 'credential',
validate: required(i18n._(t`Select a value for this field`), i18n),
});
+ const config = useConfig();
const handleCredentialUpdate = useCallback(
value => {
@@ -27,8 +30,9 @@ const VMwareSubForm = ({ autoPopulateCredential, i18n }) => {
[setFieldValue]
);
- const pluginLink =
- 'http://docs.ansible.com/ansible-tower/latest/html/userguide/inventories.html#inventory-plugins';
+ const pluginLink = `${getDocsBaseUrl(
+ config
+ )}/html/userguide/inventories.html#inventory-plugins`;
const configLink =
'https://docs.ansible.com/ansible/latest/collections/community/vmware/vmware_vm_inventory_inventory.html';
diff --git a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/VirtualizationSubForm.jsx b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/VirtualizationSubForm.jsx
index d338d1554a..e081d9f629 100644
--- a/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/VirtualizationSubForm.jsx
+++ b/awx/ui_next/src/screens/Inventory/shared/InventorySourceSubForms/VirtualizationSubForm.jsx
@@ -12,6 +12,8 @@ import {
SourceVarsField,
} from './SharedFields';
import { required } from '../../../../util/validators';
+import getDocsBaseUrl from '../../../../util/getDocsBaseUrl';
+import { useConfig } from '../../../../contexts/Config';
const VirtualizationSubForm = ({ autoPopulateCredential, i18n }) => {
const { setFieldValue } = useFormikContext();
@@ -19,6 +21,7 @@ const VirtualizationSubForm = ({ autoPopulateCredential, i18n }) => {
name: 'credential',
validate: required(i18n._(t`Select a value for this field`), i18n),
});
+ const config = useConfig();
const handleCredentialUpdate = useCallback(
value => {
@@ -27,8 +30,9 @@ const VirtualizationSubForm = ({ autoPopulateCredential, i18n }) => {
[setFieldValue]
);
- const pluginLink =
- 'http://docs.ansible.com/ansible-tower/latest/html/userguide/inventories.html#inventory-plugins';
+ const pluginLink = `${getDocsBaseUrl(
+ config
+ )}/html/userguide/inventories.html#inventory-plugins`;
const configLink =
'https://docs.ansible.com/ansible/latest/collections/ovirt/ovirt/ovirt_inventory.html';
diff --git a/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx b/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx
index 293986b779..06df7fe3cd 100644
--- a/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx
+++ b/awx/ui_next/src/screens/Job/JobOutput/JobOutput.jsx
@@ -21,7 +21,7 @@ import {
ToolbarToggleGroup,
Tooltip,
} from '@patternfly/react-core';
-import { SearchIcon } from '@patternfly/react-icons';
+import { SearchIcon, QuestionCircleIcon } from '@patternfly/react-icons';
import AlertModal from '../../../components/AlertModal';
import { CardBody as _CardBody } from '../../../components/Card';
@@ -47,6 +47,8 @@ import {
removeParams,
getQSConfig,
} from '../../../util/qs';
+import getDocsBaseUrl from '../../../util/getDocsBaseUrl';
+import { useConfig } from '../../../contexts/Config';
const QS_CONFIG = getQSConfig('job_output', {
order_by: 'start_line',
@@ -280,6 +282,7 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
const jobSocketCounter = useRef(0);
const interval = useRef(null);
const history = useHistory();
+ const config = useConfig();
const [contentError, setContentError] = useState(null);
const [cssMap, setCssMap] = useState({});
const [currentlyLoading, setCurrentlyLoading] = useState([]);
@@ -730,6 +733,21 @@ function JobOutput({ job, eventRelatedSearchableKeys, eventSearchableKeys }) {
) : (
renderSearchComponent(i18n)
)}
+
+
+
diff --git a/awx/ui_next/src/screens/NotificationTemplate/shared/CustomMessagesSubForm.jsx b/awx/ui_next/src/screens/NotificationTemplate/shared/CustomMessagesSubForm.jsx
index fca1b0317d..42fe7a65e2 100644
--- a/awx/ui_next/src/screens/NotificationTemplate/shared/CustomMessagesSubForm.jsx
+++ b/awx/ui_next/src/screens/NotificationTemplate/shared/CustomMessagesSubForm.jsx
@@ -1,6 +1,6 @@
import 'styled-components/macro';
import React, { useEffect, useRef } from 'react';
-import { Trans, withI18n } from '@lingui/react';
+import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { useField, useFormikContext } from 'formik';
import { Switch, Text } from '@patternfly/react-core';
@@ -9,6 +9,8 @@ import {
SubFormLayout,
} from '../../../components/FormLayout';
import CodeEditorField from '../../../components/CodeEditor/CodeEditorField';
+import { useConfig } from '../../../contexts/Config';
+import getDocsBaseUrl from '../../../util/getDocsBaseUrl';
function CustomMessagesSubForm({ defaultMessages, type, i18n }) {
const [useCustomField, , useCustomHelpers] = useField('useCustomMessages');
@@ -16,6 +18,7 @@ function CustomMessagesSubForm({ defaultMessages, type, i18n }) {
const showBodies = ['email', 'pagerduty', 'webhook'].includes(type);
const { setFieldValue } = useFormikContext();
+ const config = useConfig();
const mountedRef = useRef(null);
useEffect(
function resetToDefaultMessages() {
@@ -69,11 +72,9 @@ function CustomMessagesSubForm({ defaultMessages, type, i18n }) {
css="margin-bottom: var(--pf-c-content--MarginBottom)"
>
-
- 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:{' '}
-
+ {i18n._(t`Use custom messages to change the content of
+ notifications sent when a job starts, succeeds, or fails. Use
+ curly braces to access information about the job:`)}{' '}
{'{{'} job_friendly_name {'}}'}
@@ -81,23 +82,22 @@ function CustomMessagesSubForm({ defaultMessages, type, i18n }) {
{'{{'} url {'}}'}
- , or attributes of the job such as{' '}
+ ,{' '}
{'{{'} job.status {'}}'}
.{' '}
-
- You may apply a number of possible variables in the message.
- Refer to the{' '}
- {' '}
+ {i18n._(t`You may apply a number of possible variables in the
+ message. For more information, refer to the`)}{' '}
- Ansible Tower documentation
- {' '}
- for more details.
+ {i18n._(t`Ansible Tower Documentation.`)}
+
diff --git a/awx/ui_next/src/screens/Project/shared/ProjectSubForms/GitSubForm.jsx b/awx/ui_next/src/screens/Project/shared/ProjectSubForms/GitSubForm.jsx
index 32d2d85565..dc90e12df3 100644
--- a/awx/ui_next/src/screens/Project/shared/ProjectSubForms/GitSubForm.jsx
+++ b/awx/ui_next/src/screens/Project/shared/ProjectSubForms/GitSubForm.jsx
@@ -9,90 +9,97 @@ import {
ScmCredentialFormField,
ScmTypeOptions,
} from './SharedFields';
+import { useConfig } from '../../../../contexts/Config';
+import getDocsBaseUrl from '../../../../util/getDocsBaseUrl';
const GitSubForm = ({
i18n,
credential,
onCredentialSelection,
scmUpdateOnLaunch,
-}) => (
- <>
-
- {i18n._(t`Example URLs for GIT Source Control include:`)}
-
-
- https://github.com/ansible/ansible.git
-
-
- git@github.com:ansible/ansible.git
-
-
- git://servername.example.com/ansible.git
-
-
- {i18n._(t`Note: When using SSH protocol for GitHub or
+}) => {
+ const config = useConfig();
+ return (
+ <>
+
+ {i18n._(t`Example URLs for GIT Source Control include:`)}
+
+
+ https://github.com/ansible/ansible.git
+
+
+ git@github.com:ansible/ansible.git
+
+
+ git://servername.example.com/ansible.git
+
+
+ {i18n._(t`Note: When using SSH protocol for GitHub or
Bitbucket, enter an SSH key only, do not enter a username
(other than git). Additionally, GitHub and Bitbucket do
not support password authentication when using SSH. GIT
read only protocol (git://) does not use username or
password information.`)}
-
- }
- />
-
-
- {i18n._(t`A refspec to fetch (passed to the Ansible git
+
+ }
+ />
+
+
+ {i18n._(t`A refspec to fetch (passed to the Ansible git
module). This parameter allows access to references via
the branch field not otherwise available.`)}
-
-
- {i18n._(t`Note: This field assumes the remote name is "origin".`)}
-
-
- {i18n._(t`Examples include:`)}
-
- {i18n._(t`The first fetches all references. The second
+
+
+ {i18n._(t`Note: This field assumes the remote name is "origin".`)}
+
+
+ {i18n._(t`Examples include:`)}
+