foreman_remote_execution 4.3.1 → 4.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/job_invocations_controller.rb +27 -22
  3. data/app/controllers/foreman_remote_execution/concerns/api/v2/registration_commands_controller_extensions.rb +19 -0
  4. data/app/controllers/job_invocations_controller.rb +1 -1
  5. data/app/controllers/job_templates_controller.rb +4 -4
  6. data/app/controllers/ui_job_wizard_controller.rb +19 -0
  7. data/app/helpers/job_invocations_helper.rb +2 -2
  8. data/app/helpers/remote_execution_helper.rb +40 -9
  9. data/app/lib/actions/remote_execution/run_host_job.rb +36 -6
  10. data/app/lib/foreman_remote_execution/provider_input.rb +29 -0
  11. data/app/models/concerns/foreman_remote_execution/host_extensions.rb +7 -5
  12. data/app/models/concerns/foreman_remote_execution/smart_proxy_extensions.rb +6 -0
  13. data/app/models/host_proxy_invocation.rb +4 -0
  14. data/app/models/host_status/execution_status.rb +5 -5
  15. data/app/models/invocation_provider_input_value.rb +12 -0
  16. data/app/models/job_invocation.rb +35 -12
  17. data/app/models/job_invocation_composer.rb +74 -19
  18. data/app/models/remote_execution_provider.rb +18 -3
  19. data/app/models/setting/remote_execution.rb +11 -1
  20. data/app/models/ssh_execution_provider.rb +4 -4
  21. data/app/models/targeting.rb +5 -1
  22. data/app/models/template_invocation.rb +2 -0
  23. data/app/overrides/execution_interface.rb +8 -8
  24. data/app/overrides/subnet_proxies.rb +6 -6
  25. data/app/services/renderer_methods.rb +12 -0
  26. data/app/views/job_invocations/_form.html.erb +8 -0
  27. data/app/views/job_invocations/index.html.erb +1 -1
  28. data/app/views/templates/ssh/module_action.erb +1 -0
  29. data/app/views/templates/ssh/puppet_run_once.erb +1 -0
  30. data/config/routes.rb +1 -0
  31. data/db/migrate/20180110104432_rename_template_invocation_permission.rb +1 -1
  32. data/db/migrate/20190111153330_remove_remote_execution_without_proxy_setting.rb +4 -4
  33. data/db/migrate/20210312074713_add_provider_inputs.rb +10 -0
  34. data/db/migrate/2021051713291621250977_add_host_proxy_invocations.rb +12 -0
  35. data/extra/cockpit/foreman-cockpit-session +6 -6
  36. data/foreman_remote_execution.gemspec +1 -1
  37. data/lib/foreman_remote_execution/engine.rb +14 -12
  38. data/lib/foreman_remote_execution/version.rb +1 -1
  39. data/locale/action_names.rb +1 -0
  40. data/locale/de/foreman_remote_execution.po +77 -27
  41. data/locale/en/foreman_remote_execution.po +77 -27
  42. data/locale/en_GB/foreman_remote_execution.po +77 -27
  43. data/locale/es/foreman_remote_execution.po +77 -27
  44. data/locale/foreman_remote_execution.pot +241 -163
  45. data/locale/fr/foreman_remote_execution.po +77 -27
  46. data/locale/ja/foreman_remote_execution.po +77 -27
  47. data/locale/ko/foreman_remote_execution.po +77 -27
  48. data/locale/pt_BR/foreman_remote_execution.po +77 -27
  49. data/locale/ru/foreman_remote_execution.po +77 -27
  50. data/locale/zh_CN/foreman_remote_execution.po +77 -27
  51. data/locale/zh_TW/foreman_remote_execution.po +77 -27
  52. data/package.json +4 -2
  53. data/test/functional/api/v2/job_invocations_controller_test.rb +14 -1
  54. data/test/helpers/remote_execution_helper_test.rb +16 -0
  55. data/test/unit/job_invocation_composer_test.rb +100 -3
  56. data/test/unit/job_invocation_report_template_test.rb +57 -0
  57. data/test/unit/job_invocation_test.rb +1 -1
  58. data/webpack/JobWizard/JobWizard.js +95 -11
  59. data/webpack/JobWizard/JobWizard.scss +53 -0
  60. data/webpack/JobWizard/JobWizardConstants.js +16 -0
  61. data/webpack/JobWizard/JobWizardSelectors.js +47 -0
  62. data/webpack/JobWizard/__tests__/__snapshots__/integration.test.js.snap +43 -0
  63. data/webpack/JobWizard/__tests__/fixtures.js +128 -0
  64. data/webpack/JobWizard/__tests__/integration.test.js +84 -0
  65. data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +110 -0
  66. data/webpack/JobWizard/steps/AdvancedFields/DescriptionField.js +67 -0
  67. data/webpack/JobWizard/steps/AdvancedFields/Fields.js +195 -0
  68. data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +144 -0
  69. data/webpack/JobWizard/steps/AdvancedFields/__tests__/DescriptionField.test.js +23 -0
  70. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +109 -0
  71. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +123 -0
  72. data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +94 -0
  73. data/webpack/JobWizard/steps/Schedule/QueryType.js +48 -0
  74. data/webpack/JobWizard/steps/Schedule/RepeatOn.js +61 -0
  75. data/webpack/JobWizard/steps/Schedule/ScheduleType.js +25 -0
  76. data/webpack/JobWizard/steps/Schedule/StartEndDates.js +51 -0
  77. data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +22 -0
  78. data/webpack/JobWizard/steps/Schedule/index.js +41 -0
  79. data/webpack/JobWizard/steps/form/FormHelpers.js +20 -0
  80. data/webpack/JobWizard/steps/form/Formatter.js +149 -0
  81. data/webpack/JobWizard/steps/form/GroupedSelectField.js +91 -0
  82. data/webpack/JobWizard/steps/form/NumberInput.js +33 -0
  83. data/webpack/JobWizard/steps/form/SelectField.js +60 -0
  84. data/webpack/JobWizard/steps/form/__tests__/Formatter.test.js.example +76 -0
  85. data/webpack/__mocks__/foremanReact/common/helpers.js +1 -0
  86. data/webpack/__mocks__/foremanReact/components/SearchBar.js +18 -1
  87. data/webpack/__mocks__/foremanReact/redux/API/APISelectors.js +21 -2
  88. data/webpack/__mocks__/foremanReact/redux/API/index.js +5 -0
  89. data/webpack/__mocks__/foremanReact/routes/common/PageLayout/PageLayout.js +10 -0
  90. data/webpack/global_index.js +6 -0
  91. data/webpack/index.js +3 -4
  92. data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +83 -0
  93. data/webpack/react_app/components/RecentJobsCard/constants.js +1 -0
  94. data/webpack/react_app/components/RecentJobsCard/index.js +1 -0
  95. data/webpack/react_app/components/RecentJobsCard/styles.css +15 -0
  96. data/webpack/react_app/components/RegistrationExtension/RexInterface.js +50 -0
  97. data/webpack/react_app/components/RegistrationExtension/__tests__/RexInterface.test.js +9 -0
  98. data/webpack/react_app/components/RegistrationExtension/__tests__/__snapshots__/RexInterface.test.js.snap +35 -0
  99. data/webpack/react_app/components/TargetingHosts/__tests__/TargetingHostsSelectors.test.js +8 -3
  100. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +1 -0
  101. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsSelectors.test.js.snap +7 -2
  102. data/webpack/react_app/extend/fillRecentJobsCard.js +11 -0
  103. data/webpack/react_app/extend/fillregistrationAdvanced.js +11 -0
  104. data/webpack/react_app/extend/reducers.js +5 -0
  105. metadata +52 -8
  106. data/app/models/concerns/foreman_remote_execution/orchestration/ssh.rb +0 -70
  107. data/app/views/api/v2/registration/_form.html.erb +0 -12
  108. data/test/models/orchestration/ssh_test.rb +0 -56
@@ -0,0 +1,33 @@
1
+ import React, { useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { FormGroup, TextInput, ValidatedOptions } from '@patternfly/react-core';
4
+ import { translate as __ } from 'foremanReact/common/I18n';
5
+
6
+ export const NumberInput = ({ formProps, inputProps }) => {
7
+ const [validated, setValidated] = useState();
8
+ return (
9
+ <FormGroup
10
+ {...formProps}
11
+ helperTextInvalid={__('Has to be a number')}
12
+ validated={validated}
13
+ >
14
+ <TextInput
15
+ type="text"
16
+ {...inputProps}
17
+ onChange={newValue => {
18
+ setValidated(
19
+ /^\d+$/.test(newValue) || newValue === ''
20
+ ? ValidatedOptions.noval
21
+ : ValidatedOptions.error
22
+ );
23
+ inputProps.onChange(newValue);
24
+ }}
25
+ />
26
+ </FormGroup>
27
+ );
28
+ };
29
+
30
+ NumberInput.propTypes = {
31
+ formProps: PropTypes.object.isRequired,
32
+ inputProps: PropTypes.object.isRequired,
33
+ };
@@ -0,0 +1,60 @@
1
+ import React, { useState } from 'react';
2
+ import { FormGroup, Select, SelectOption } from '@patternfly/react-core';
3
+ import PropTypes from 'prop-types';
4
+
5
+ export const SelectField = ({
6
+ label,
7
+ fieldId,
8
+ options,
9
+ value,
10
+ setValue,
11
+ labelIcon,
12
+ isRequired,
13
+ ...props
14
+ }) => {
15
+ const onSelect = (event, selection) => {
16
+ setValue(selection);
17
+ setIsOpen(false);
18
+ };
19
+ const [isOpen, setIsOpen] = useState(false);
20
+ return (
21
+ <FormGroup
22
+ label={label}
23
+ fieldId={fieldId}
24
+ labelIcon={labelIcon}
25
+ isRequired={isRequired}
26
+ >
27
+ <Select
28
+ selections={value}
29
+ onSelect={onSelect}
30
+ onToggle={setIsOpen}
31
+ isOpen={isOpen}
32
+ className="without_select2"
33
+ maxHeight="45vh"
34
+ menuAppendTo={() => document.body}
35
+ {...props}
36
+ >
37
+ {options.map((option, index) => (
38
+ <SelectOption key={index} value={option} />
39
+ ))}
40
+ </Select>
41
+ </FormGroup>
42
+ );
43
+ };
44
+ SelectField.propTypes = {
45
+ label: PropTypes.string,
46
+ fieldId: PropTypes.string.isRequired,
47
+ options: PropTypes.array,
48
+ value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
49
+ setValue: PropTypes.func.isRequired,
50
+ labelIcon: PropTypes.node,
51
+ isRequired: PropTypes.bool,
52
+ };
53
+
54
+ SelectField.defaultProps = {
55
+ label: null,
56
+ options: [],
57
+ labelIcon: null,
58
+ value: null,
59
+ isRequired: false,
60
+ };
@@ -0,0 +1,76 @@
1
+ import React from 'react';
2
+ import { Provider } from 'react-redux';
3
+ import configureMockStore from 'redux-mock-store';
4
+ import * as patternfly from '@patternfly/react-core';
5
+ import { mount, shallow } from '@theforeman/test';
6
+ import { formatter } from '../Formatter';
7
+
8
+ jest.spyOn(patternfly, 'Select');
9
+ jest.spyOn(patternfly, 'SelectOption');
10
+ jest.spyOn(patternfly, 'FormGroup');
11
+ patternfly.Select.mockImplementation(props => <div props={props} />);
12
+ patternfly.SelectOption.mockImplementation(props => <div props={props} />);
13
+ patternfly.FormGroup.mockImplementation(props => <div props={props} />);
14
+ const mockStore = configureMockStore([]);
15
+ const store = mockStore({});
16
+
17
+ describe('formatter', () => {
18
+ it('render date input', () => {
19
+ const props = {
20
+ name: 'date adv',
21
+ required: false,
22
+ options: '',
23
+ advanced: true,
24
+ value_type: 'date',
25
+ resource_type: 'ansible_roles',
26
+ default: '',
27
+ hidden_value: false,
28
+ };
29
+ expect(shallow(formatter(props, {}, jest.fn()))).toMatchSnapshot();
30
+ });
31
+ it('render text input', () => {
32
+ const props = {
33
+ name: 'plain adv hidden',
34
+ required: true,
35
+ description: 'some Description',
36
+ options: '',
37
+ advanced: true,
38
+ value_type: 'plain',
39
+ resource_type: 'ansible_roles',
40
+ default: 'Default val',
41
+ hidden_value: true,
42
+ };
43
+ expect(shallow(formatter(props, {}, jest.fn()))).toMatchSnapshot();
44
+ });
45
+ it('render select input', () => {
46
+ const props = {
47
+ name: 'adv plain search',
48
+ required: false,
49
+ input_type: 'user',
50
+ options: 'option 1\r\noption 2\r\noption 3\r\noption 4',
51
+ advanced: true,
52
+ value_type: 'plain',
53
+ resource_type: 'ansible_roles',
54
+ default: '',
55
+ hidden_value: false,
56
+ };
57
+ expect(shallow(formatter(props, {}, jest.fn()))).toMatchSnapshot();
58
+ });
59
+ it('render search input', () => {
60
+ const props = {
61
+ name: 'search adv',
62
+ required: false,
63
+ options: '',
64
+ advanced: true,
65
+ value_type: 'search',
66
+ resource_type: 'foreman_tasks/tasks',
67
+ default: '',
68
+ hidden_value: false,
69
+ };
70
+ expect(
71
+ mount(
72
+ <Provider store={store}>{formatter(props, {}, jest.fn())}</Provider>
73
+ )
74
+ ).toMatchSnapshot();
75
+ });
76
+ });
@@ -0,0 +1 @@
1
+ export const foremanUrl = path => `foreman${path}`;
@@ -1,2 +1,19 @@
1
- const SearchBar = () => jest.fn();
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ const SearchBar = ({ onChange }) => (
5
+ <input
6
+ className="foreman-search"
7
+ onChange={onChange}
8
+ placeholder="Filter..."
9
+ />
10
+ );
2
11
  export default SearchBar;
12
+
13
+ SearchBar.propTypes = {
14
+ onChange: PropTypes.func,
15
+ };
16
+
17
+ SearchBar.defaultProps = {
18
+ onChange: () => null,
19
+ };
@@ -1,2 +1,21 @@
1
- export const selectAPIStatus = () => 'RESOLVED';
2
- export const selectAPIResponse = state => state;
1
+ export const selectAPI = state => state;
2
+ export const selectAPIByKey = (state, key) => selectAPI(state)[key] || {};
3
+
4
+ export const selectAPIStatus = (state, key) =>
5
+ selectAPIByKey(state, key).status;
6
+
7
+ export const selectAPIPayload = (state, key) =>
8
+ selectAPIByKey(state, key).payload || {};
9
+
10
+ export const selectAPIResponse = (state, key) =>
11
+ selectAPIByKey(state, key).response || {};
12
+
13
+ export const selectAPIError = (state, key) =>
14
+ selectAPIStatus(state, key) === 'ERROR'
15
+ ? selectAPIResponse(state, key)
16
+ : null;
17
+
18
+ export const selectAPIErrorMessage = (state, key) => {
19
+ const error = selectAPIError(state, key);
20
+ return error && error.message;
21
+ };
@@ -0,0 +1,5 @@
1
+ export const API = {
2
+ get: jest.fn(),
3
+ };
4
+
5
+ export const get = data => ({ type: 'get-some-type', ...data });
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ const PageLayout = ({ children }) => <div>{children}</div>;
5
+
6
+ PageLayout.propTypes = {
7
+ children: PropTypes.node.isRequired,
8
+ };
9
+
10
+ export default PageLayout;
@@ -1,4 +1,10 @@
1
1
  import { registerRoutes } from 'foremanReact/routes/RoutingService';
2
2
  import routes from './Routes/routes';
3
+ import fillregistrationAdvanced from './react_app/extend/fillregistrationAdvanced';
4
+ import fillRecentJobsCard from './react_app/extend/fillRecentJobsCard';
5
+ import registerReducers from './react_app/extend/reducers';
3
6
 
7
+ registerReducers();
4
8
  registerRoutes('foreman_remote_execution', routes);
9
+ fillRecentJobsCard();
10
+ fillregistrationAdvanced();
data/webpack/index.js CHANGED
@@ -1,8 +1,9 @@
1
- import { registerReducer } from 'foremanReact/common/MountingService';
2
1
  import componentRegistry from 'foremanReact/components/componentRegistry';
3
2
  import JobInvocationContainer from './react_app/components/jobInvocations';
4
3
  import TargetingHosts from './react_app/components/TargetingHosts';
5
- import rootReducer from './react_app/redux/reducers';
4
+ import registerReducers from './react_app/extend/reducers';
5
+
6
+ registerReducers();
6
7
 
7
8
  const components = [
8
9
  { name: 'JobInvocationContainer', type: JobInvocationContainer },
@@ -12,5 +13,3 @@ const components = [
12
13
  components.forEach(component => {
13
14
  componentRegistry.register(component);
14
15
  });
15
-
16
- registerReducer('foremanRemoteExecutionReducers', rootReducer);
@@ -0,0 +1,83 @@
1
+ /* eslint-disable camelcase */
2
+
3
+ import PropTypes from 'prop-types';
4
+ import React from 'react';
5
+ import Skeleton from 'react-loading-skeleton';
6
+ import ElipsisWithTooltip from 'react-ellipsis-with-tooltip';
7
+
8
+ import { Grid, GridItem } from '@patternfly/react-core';
9
+ import {
10
+ PropertiesSidePanel,
11
+ PropertyItem,
12
+ } from '@patternfly/react-catalog-view-extension';
13
+ import { ArrowIcon, ErrorCircleOIcon, OkIcon } from '@patternfly/react-icons';
14
+
15
+ import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
16
+ import CardItem from 'foremanReact/components/HostDetails/Templates/CardItem/CardTemplate';
17
+ import RelativeDateTime from 'foremanReact/components/common/dates/RelativeDateTime';
18
+ import { translate as __ } from 'foremanReact/common/I18n';
19
+ import './styles.css';
20
+
21
+ const RecentJobsCard = ({ hostDetails: { name } }) => {
22
+ const jobsUrl =
23
+ name && `/api/job_invocations?search=host%3D${name}&per_page=3`;
24
+ const {
25
+ response: { results: jobs },
26
+ } = useAPI('get', jobsUrl);
27
+
28
+ const iconMarkup = status => {
29
+ if (status === 1) return <ErrorCircleOIcon color="#C9190B" />;
30
+ return <OkIcon color="#3E8635" />;
31
+ };
32
+
33
+ return (
34
+ <CardItem
35
+ header={
36
+ <span>
37
+ {__('Recent Jobs')}{' '}
38
+ <a href={`/job_invocations?search=host+%3D+${name}`}>
39
+ <ArrowIcon />
40
+ </a>
41
+ </span>
42
+ }
43
+ >
44
+ <PropertiesSidePanel>
45
+ {jobs?.map(({ status, status_label, id, start_at, description }) => (
46
+ <PropertyItem
47
+ key={id}
48
+ label={
49
+ description ? (
50
+ <Grid>
51
+ <GridItem span={8}>
52
+ <ElipsisWithTooltip>{description}</ElipsisWithTooltip>
53
+ </GridItem>
54
+ <GridItem span={1}>{iconMarkup(status)}</GridItem>
55
+ <GridItem span={3}>{status_label}</GridItem>
56
+ </Grid>
57
+ ) : (
58
+ <Skeleton />
59
+ )
60
+ }
61
+ value={
62
+ start_at ? (
63
+ <a href={`/job_invocations/${id}`}>
64
+ <RelativeDateTime date={start_at} />
65
+ </a>
66
+ ) : (
67
+ <Skeleton />
68
+ )
69
+ }
70
+ />
71
+ ))}
72
+ </PropertiesSidePanel>
73
+ </CardItem>
74
+ );
75
+ };
76
+
77
+ export default RecentJobsCard;
78
+
79
+ RecentJobsCard.propTypes = {
80
+ hostDetails: PropTypes.shape({
81
+ name: PropTypes.string,
82
+ }).isRequired,
83
+ };
@@ -0,0 +1 @@
1
+ export const HOST_DETAILS_JOBS = 'HOST_DETAILS_JOBS';
@@ -0,0 +1 @@
1
+ export { default } from './RecentJobsCard';
@@ -0,0 +1,15 @@
1
+ .properties-side-panel-pf-property-value {
2
+ font-size: 14px !important;
3
+ margin-top: 8px;
4
+ word-break: break-word;
5
+ }
6
+
7
+ .properties-side-panel-pf-property-label {
8
+ font-weight: 700 !important;
9
+ font-size: 14px !important;
10
+ margin: 0 !important;
11
+ }
12
+
13
+ .properties-side-panel-pf-property {
14
+ margin-top: 24px;
15
+ }
@@ -0,0 +1,50 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ import { translate as __ } from 'foremanReact/common/I18n';
5
+
6
+ import { FormGroup, TextInput, Popover } from '@patternfly/react-core';
7
+
8
+ import { HelpIcon } from '@patternfly/react-icons';
9
+
10
+ const RexInterface = ({ isLoading, onChange }) => (
11
+ <FormGroup
12
+ label={__('Remote Execution Interface')}
13
+ fieldId="reg_rex_interface"
14
+ labelIcon={
15
+ <Popover
16
+ bodyContent={
17
+ <div>
18
+ {__('Identifier of the Host interface for Remote execution')}
19
+ </div>
20
+ }
21
+ >
22
+ <button
23
+ className="pf-c-form__group-label-help"
24
+ onClick={e => e.preventDefault()}
25
+ >
26
+ <HelpIcon noVerticalAlign />
27
+ </button>
28
+ </Popover>
29
+ }
30
+ >
31
+ <TextInput
32
+ type="text"
33
+ onBlur={e => onChange({ remoteExecutionInterface: e.target.value })}
34
+ id="reg_rex_interface_input"
35
+ isDisabled={isLoading}
36
+ />
37
+ </FormGroup>
38
+ );
39
+
40
+ RexInterface.propTypes = {
41
+ onChange: PropTypes.func,
42
+ isLoading: PropTypes.bool,
43
+ };
44
+
45
+ RexInterface.defaultProps = {
46
+ onChange: undefined,
47
+ isLoading: false,
48
+ };
49
+
50
+ export default RexInterface;
@@ -0,0 +1,9 @@
1
+ import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
2
+ import RexInterface from '../RexInterface';
3
+
4
+ const fixtures = {
5
+ renders: { isLoading: false, onChange: () => {} },
6
+ };
7
+
8
+ describe('RexInterface', () =>
9
+ testComponentSnapshotsWithFixtures(RexInterface, fixtures));
@@ -0,0 +1,35 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`RexInterface renders 1`] = `
4
+ <FormGroup
5
+ fieldId="reg_rex_interface"
6
+ label="Remote Execution Interface"
7
+ labelIcon={
8
+ <Popover
9
+ bodyContent={
10
+ <div>
11
+ Identifier of the Host interface for Remote execution
12
+ </div>
13
+ }
14
+ >
15
+ <button
16
+ className="pf-c-form__group-label-help"
17
+ onClick={[Function]}
18
+ >
19
+ <HelpIcon
20
+ color="currentColor"
21
+ noVerticalAlign={true}
22
+ size="sm"
23
+ />
24
+ </button>
25
+ </Popover>
26
+ }
27
+ >
28
+ <TextInput
29
+ id="reg_rex_interface_input"
30
+ isDisabled={false}
31
+ onBlur={[Function]}
32
+ type="text"
33
+ />
34
+ </FormGroup>
35
+ `;