mirror of
https://github.com/ansible/awx.git
synced 2026-05-19 14:57:39 -02:30
Mesh UI support
- add endpoint - delete endpoint (wip) - associate - disassociate
This commit is contained in:
committed by
Seth Foster
parent
93500f9fea
commit
82ad7dcf40
@@ -32,6 +32,10 @@ class Instances extends Base {
|
|||||||
return this.http.get(`${this.baseUrl}${instanceId}/receptor_addresses/`);
|
return this.http.get(`${this.baseUrl}${instanceId}/receptor_addresses/`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateReceptorAddresses(instanceId, data) {
|
||||||
|
return this.http.post(`${this.baseUrl}${instanceId}/receptor_addresses/`, data);
|
||||||
|
}
|
||||||
|
|
||||||
deprovisionInstance(instanceId) {
|
deprovisionInstance(instanceId) {
|
||||||
return this.http.patch(`${this.baseUrl}${instanceId}/`, {
|
return this.http.patch(`${this.baseUrl}${instanceId}/`, {
|
||||||
node_state: 'deprovisioning',
|
node_state: 'deprovisioning',
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ class ReceptorAddresses extends Base {
|
|||||||
super(http);
|
super(http);
|
||||||
this.baseUrl = 'api/v2/receptor_addresses/';
|
this.baseUrl = 'api/v2/receptor_addresses/';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateReceptorAddresses(instanceId, data) {
|
||||||
|
return this.http.post(`${this.baseUrl}`, data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ReceptorAddresses;
|
export default ReceptorAddresses;
|
||||||
|
|||||||
97
awx/ui/src/components/AddEndpointModal/AddEndpointModal.js
Normal file
97
awx/ui/src/components/AddEndpointModal/AddEndpointModal.js
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import { Form, FormGroup, Modal } from '@patternfly/react-core';
|
||||||
|
import { InstancesAPI } from 'api';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
import { FormColumnLayout } from 'components/FormLayout';
|
||||||
|
import FormField, {
|
||||||
|
CheckboxField,
|
||||||
|
} from 'components/FormField';
|
||||||
|
import FormActionGroup from '../FormActionGroup/FormActionGroup';
|
||||||
|
|
||||||
|
function AddEndpointModal({
|
||||||
|
title = t`Add endpoint`,
|
||||||
|
onClose,
|
||||||
|
isAddEndpointModalOpen = false,
|
||||||
|
instance,
|
||||||
|
ouiaId,
|
||||||
|
}) {
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEndpointAdd = async (values) => {
|
||||||
|
try {
|
||||||
|
values.id = instance.id;
|
||||||
|
InstancesAPI.updateReceptorAddresses(instance.id, values);
|
||||||
|
onClose();
|
||||||
|
} catch (error) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
ouiaId={ouiaId}
|
||||||
|
variant="large"
|
||||||
|
title={title}
|
||||||
|
aria-label={t`Add Endpoint modal`}
|
||||||
|
isOpen={isAddEndpointModalOpen}
|
||||||
|
onClose={handleClose}
|
||||||
|
actions={[]}
|
||||||
|
>
|
||||||
|
<Formik
|
||||||
|
initialValues={{
|
||||||
|
listener_port: 1001
|
||||||
|
}}
|
||||||
|
onSubmit={handleEndpointAdd}
|
||||||
|
>
|
||||||
|
{(formik) => (
|
||||||
|
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
|
||||||
|
<FormColumnLayout>
|
||||||
|
<FormField
|
||||||
|
id="address"
|
||||||
|
label={t`Address`}
|
||||||
|
name="address"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
id="websocket_path"
|
||||||
|
label={t`Websocket path`}
|
||||||
|
name="websocket path"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
id="listener_port"
|
||||||
|
label={t`Listener Port`}
|
||||||
|
name="listener_port"
|
||||||
|
type="number"
|
||||||
|
tooltip={t`Select the port that Receptor will listen on for incoming connections, e.g. 27199.`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormGroup fieldId="endpoint" label={t`Options`}>
|
||||||
|
<CheckboxField
|
||||||
|
id="peers_from_control_nodes"
|
||||||
|
name="peers_from_control_nodes"
|
||||||
|
label={t`Peers from control nodes`}
|
||||||
|
tooltip={t`If enabled, control nodes will peer to this instance automatically. If disabled, instance will be connected only to associated peers.`}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
<FormActionGroup
|
||||||
|
onCancel={handleClose}
|
||||||
|
onSubmit={formik.handleSubmit}
|
||||||
|
/>
|
||||||
|
</FormColumnLayout>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AddEndpointModal;
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
|
|
||||||
|
import {
|
||||||
|
mountWithContexts,
|
||||||
|
waitForElement,
|
||||||
|
} from '../../../testUtils/enzymeHelpers';
|
||||||
|
import AssociateModal from './AddEndpointModal';
|
||||||
|
import mockHosts from './data.hosts.json';
|
||||||
|
|
||||||
|
jest.mock('../../api');
|
||||||
|
|
||||||
|
describe('<AssociateModal />', () => {
|
||||||
|
let wrapper;
|
||||||
|
let onClose;
|
||||||
|
let onAssociate;
|
||||||
|
let fetchRequest;
|
||||||
|
let optionsRequest;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
onClose = jest.fn();
|
||||||
|
onAssociate = jest.fn().mockResolvedValue();
|
||||||
|
fetchRequest = jest.fn().mockReturnValue({ data: { ...mockHosts } });
|
||||||
|
optionsRequest = jest.fn().mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
actions: {
|
||||||
|
GET: {},
|
||||||
|
POST: {},
|
||||||
|
},
|
||||||
|
related_search_fields: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<AssociateModal
|
||||||
|
onClose={onClose}
|
||||||
|
onAssociate={onAssociate}
|
||||||
|
fetchRequest={fetchRequest}
|
||||||
|
optionsRequest={optionsRequest}
|
||||||
|
isModalOpen
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
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(optionsRequest).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);
|
||||||
|
});
|
||||||
|
});
|
||||||
393
awx/ui/src/components/AddEndpointModal/data.hosts.json
Normal file
393
awx/ui/src/components/AddEndpointModal/data.hosts.json
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
1
awx/ui/src/components/AddEndpointModal/index.js
Normal file
1
awx/ui/src/components/AddEndpointModal/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './AddEndpointModal';
|
||||||
@@ -12,6 +12,7 @@ import { SettingsAPI } from 'api';
|
|||||||
import ContentLoading from 'components/ContentLoading';
|
import ContentLoading from 'components/ContentLoading';
|
||||||
import InstanceDetail from './InstanceDetail';
|
import InstanceDetail from './InstanceDetail';
|
||||||
import InstancePeerList from './InstancePeers';
|
import InstancePeerList from './InstancePeers';
|
||||||
|
import InstanceEndPointList from './InstanceEndPointList';
|
||||||
|
|
||||||
function Instance({ setBreadcrumb }) {
|
function Instance({ setBreadcrumb }) {
|
||||||
const { me } = useConfig();
|
const { me } = useConfig();
|
||||||
@@ -54,7 +55,8 @@ function Instance({ setBreadcrumb }) {
|
|||||||
}, [request]);
|
}, [request]);
|
||||||
|
|
||||||
if (isK8s) {
|
if (isK8s) {
|
||||||
tabsArray.push({ name: t`Peers`, link: `${match.url}/peers`, id: 1 });
|
tabsArray.push({ name: t`Endpoints`, link: `${match.url}/endpoints`, id: 1 });
|
||||||
|
tabsArray.push({ name: t`Peers`, link: `${match.url}/peers`, id: 2 });
|
||||||
}
|
}
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <ContentLoading />;
|
return <ContentLoading />;
|
||||||
@@ -72,6 +74,11 @@ function Instance({ setBreadcrumb }) {
|
|||||||
<Route path="/instances/:id/details" key="details">
|
<Route path="/instances/:id/details" key="details">
|
||||||
<InstanceDetail isK8s={isK8s} setBreadcrumb={setBreadcrumb} />
|
<InstanceDetail isK8s={isK8s} setBreadcrumb={setBreadcrumb} />
|
||||||
</Route>
|
</Route>
|
||||||
|
{isK8s && (
|
||||||
|
<Route path="/instances/:id/endpoints" key="endpoints">
|
||||||
|
<InstanceEndPointList setBreadcrumb={setBreadcrumb} />
|
||||||
|
</Route>
|
||||||
|
)}
|
||||||
{isK8s && (
|
{isK8s && (
|
||||||
<Route path="/instances/:id/peers" key="peers">
|
<Route path="/instances/:id/peers" key="peers">
|
||||||
<InstancePeerList setBreadcrumb={setBreadcrumb} />
|
<InstancePeerList setBreadcrumb={setBreadcrumb} />
|
||||||
|
|||||||
@@ -0,0 +1,188 @@
|
|||||||
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import { CardBody } from 'components/Card';
|
||||||
|
import PaginatedTable, {
|
||||||
|
getSearchableKeys,
|
||||||
|
HeaderCell,
|
||||||
|
HeaderRow,
|
||||||
|
ToolbarAddButton,
|
||||||
|
} from 'components/PaginatedTable';
|
||||||
|
import AddEndpointModal from 'components/AddEndpointModal';
|
||||||
|
import useToast from 'hooks/useToast';
|
||||||
|
import { getQSConfig } from 'util/qs';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
import useRequest from 'hooks/useRequest';
|
||||||
|
import DataListToolbar from 'components/DataListToolbar';
|
||||||
|
import { InstancesAPI, ReceptorAPI } from 'api';
|
||||||
|
import useExpanded from 'hooks/useExpanded';
|
||||||
|
import useSelected from 'hooks/useSelected';
|
||||||
|
import InstanceEndPointListItem from './InstanceEndPointListItem';
|
||||||
|
|
||||||
|
const QS_CONFIG = getQSConfig('peer', {
|
||||||
|
page: 1,
|
||||||
|
page_size: 20,
|
||||||
|
order_by: 'pk',
|
||||||
|
});
|
||||||
|
|
||||||
|
function InstanceEndPointList({ setBreadcrumb }) {
|
||||||
|
const { id } = useParams();
|
||||||
|
const [isAddEndpointModalOpen, setisAddEndpointModalOpen] = useState(false);
|
||||||
|
const { Toast, toastProps } = useToast();
|
||||||
|
const {
|
||||||
|
isLoading,
|
||||||
|
error: contentError,
|
||||||
|
request: fetchEndpoints,
|
||||||
|
result: { instance, endpoints, count, relatedSearchableKeys, searchableKeys },
|
||||||
|
} = useRequest(
|
||||||
|
useCallback(async () => {
|
||||||
|
const [
|
||||||
|
{ data: detail },
|
||||||
|
{
|
||||||
|
data: { results },
|
||||||
|
},
|
||||||
|
actions,
|
||||||
|
] = await Promise.all([
|
||||||
|
InstancesAPI.readDetail(id),
|
||||||
|
ReceptorAPI.read(),
|
||||||
|
InstancesAPI.readOptions(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const endpoint_list = []
|
||||||
|
|
||||||
|
for(let q = 0; q < results.length; q++) {
|
||||||
|
const receptor = results[q];
|
||||||
|
if(id.toString() === receptor.instance.toString()) {
|
||||||
|
endpoint_list.push(receptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
instance: detail,
|
||||||
|
endpoints: endpoint_list,
|
||||||
|
count: endpoint_list.length,
|
||||||
|
relatedSearchableKeys: (actions?.data?.related_search_fields || []).map(
|
||||||
|
(val) => val.slice(0, -8)
|
||||||
|
),
|
||||||
|
searchableKeys: getSearchableKeys(actions.data.actions?.GET),
|
||||||
|
};
|
||||||
|
}, [id]),
|
||||||
|
{
|
||||||
|
instance: {},
|
||||||
|
endpoints: [],
|
||||||
|
count: 0,
|
||||||
|
relatedSearchableKeys: [],
|
||||||
|
searchableKeys: [],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchEndpoints();
|
||||||
|
}, [fetchEndpoints]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (instance) {
|
||||||
|
setBreadcrumb(instance);
|
||||||
|
}
|
||||||
|
}, [instance, setBreadcrumb]);
|
||||||
|
|
||||||
|
const { expanded, isAllExpanded, handleExpand, expandAll } =
|
||||||
|
useExpanded(endpoints);
|
||||||
|
const { selected, isAllSelected, handleSelect, clearSelected, selectAll } =
|
||||||
|
useSelected(endpoints);
|
||||||
|
|
||||||
|
const handleEndpointDelete = async () => {
|
||||||
|
// console.log(selected)
|
||||||
|
// InstancesAPI.updateReceptorAddresses(instance.id, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isHopNode = instance.node_type === 'hop';
|
||||||
|
const isExecutionNode = instance.node_type === 'execution';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CardBody>
|
||||||
|
<PaginatedTable
|
||||||
|
contentError={contentError}
|
||||||
|
hasContentLoading={
|
||||||
|
isLoading
|
||||||
|
}
|
||||||
|
items={endpoints}
|
||||||
|
itemCount={count}
|
||||||
|
pluralizedItemName={t`Endpoints`}
|
||||||
|
qsConfig={QS_CONFIG}
|
||||||
|
onRowClick={handleSelect}
|
||||||
|
clearSelected={clearSelected}
|
||||||
|
toolbarSearchableKeys={searchableKeys}
|
||||||
|
toolbarRelatedSearchableKeys={relatedSearchableKeys}
|
||||||
|
toolbarSearchColumns={[
|
||||||
|
{
|
||||||
|
name: t`Name`,
|
||||||
|
key: 'hostname__icontains',
|
||||||
|
isDefault: true,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
toolbarSortColumns={[
|
||||||
|
{
|
||||||
|
name: t`Name`,
|
||||||
|
key: 'hostname',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
headerRow={
|
||||||
|
<HeaderRow qsConfig={QS_CONFIG} isExpandable>
|
||||||
|
<HeaderCell sortKey="address">{t`Address`}</HeaderCell>
|
||||||
|
<HeaderCell sortKey="port">{t`Port`}</HeaderCell>
|
||||||
|
</HeaderRow>
|
||||||
|
}
|
||||||
|
renderToolbar={(props) => (
|
||||||
|
<DataListToolbar
|
||||||
|
{...props}
|
||||||
|
isAllSelected={isAllSelected}
|
||||||
|
onSelectAll={selectAll}
|
||||||
|
isAllExpanded={isAllExpanded}
|
||||||
|
onExpandAll={expandAll}
|
||||||
|
qsConfig={QS_CONFIG}
|
||||||
|
additionalControls={[
|
||||||
|
(isExecutionNode || isHopNode) && (
|
||||||
|
<ToolbarAddButton
|
||||||
|
ouiaId="add-endpoint-button"
|
||||||
|
key="add-endpoint"
|
||||||
|
defaultLabel={t`Add`}
|
||||||
|
onClick={() => setisAddEndpointModalOpen(true)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
(isExecutionNode || isHopNode) && (
|
||||||
|
<ToolbarAddButton
|
||||||
|
ouiaId="delete-endpoint-button"
|
||||||
|
key="delete-endpoint"
|
||||||
|
defaultLabel={t`Delete`}
|
||||||
|
onClick={() => handleEndpointDelete()}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
renderRow={(endpoint, index) => (
|
||||||
|
<InstanceEndPointListItem
|
||||||
|
isSelected={selected.some((row) => row.id === endpoint.id)}
|
||||||
|
onSelect={() => handleSelect(endpoint)}
|
||||||
|
isExpanded={expanded.some((row) => row.id === endpoint.id)}
|
||||||
|
onExpand={() => handleExpand(endpoint)}
|
||||||
|
key={endpoint.id}
|
||||||
|
peerInstance={endpoint}
|
||||||
|
rowIndex={index}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{isAddEndpointModalOpen && (
|
||||||
|
<AddEndpointModal
|
||||||
|
isAddEndpointModalOpen={isAddEndpointModalOpen}
|
||||||
|
onClose={() => setisAddEndpointModalOpen(false)}
|
||||||
|
title={t`New endpoint`}
|
||||||
|
instance={instance}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Toast {...toastProps} />
|
||||||
|
</CardBody>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InstanceEndPointList;
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import 'styled-components/macro';
|
||||||
|
import { Tr, Td } from '@patternfly/react-table';
|
||||||
|
|
||||||
|
function InstanceEndPointListItem({
|
||||||
|
peerInstance,
|
||||||
|
isSelected,
|
||||||
|
onSelect,
|
||||||
|
isExpanded,
|
||||||
|
onExpand,
|
||||||
|
rowIndex,
|
||||||
|
}) {
|
||||||
|
const labelId = `check-action-${peerInstance.id}`;
|
||||||
|
return (
|
||||||
|
<Tr
|
||||||
|
id={`peerInstance-row-${peerInstance.id}`}
|
||||||
|
ouiaId={`peerInstance-row-${peerInstance.id}`}
|
||||||
|
>
|
||||||
|
<Td
|
||||||
|
expand={{
|
||||||
|
rowIndex,
|
||||||
|
isExpanded,
|
||||||
|
onToggle: onExpand,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Td
|
||||||
|
select={{
|
||||||
|
rowIndex,
|
||||||
|
isSelected,
|
||||||
|
onSelect,
|
||||||
|
}}
|
||||||
|
dataLabel={t`Selected`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Td id={labelId} dataLabel={t`Address`}>
|
||||||
|
<Link to={`/instances/${peerInstance.instance}/details`}>
|
||||||
|
<b>{peerInstance.address}</b>
|
||||||
|
</Link>
|
||||||
|
</Td>
|
||||||
|
|
||||||
|
<Td id={labelId} dataLabel={t`Port`}>
|
||||||
|
<Link to={`/instances/${peerInstance.instance}/details`}>
|
||||||
|
<b>{peerInstance.port}</b>
|
||||||
|
</Link>
|
||||||
|
</Td>
|
||||||
|
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InstanceEndPointListItem;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './InstanceEndPointList';
|
||||||
@@ -47,7 +47,7 @@ function InstancePeerList({ setBreadcrumb }) {
|
|||||||
const [
|
const [
|
||||||
{ data: detail },
|
{ data: detail },
|
||||||
{
|
{
|
||||||
data: { results, count: itemNumber },
|
data: { results },
|
||||||
},
|
},
|
||||||
actions,
|
actions,
|
||||||
instances,
|
instances,
|
||||||
@@ -72,7 +72,7 @@ function InstancePeerList({ setBreadcrumb }) {
|
|||||||
return {
|
return {
|
||||||
instance: detail,
|
instance: detail,
|
||||||
peers: address_list,
|
peers: address_list,
|
||||||
count: itemNumber,
|
count: address_list.length,
|
||||||
relatedSearchableKeys: (actions?.data?.related_search_fields || []).map(
|
relatedSearchableKeys: (actions?.data?.related_search_fields || []).map(
|
||||||
(val) => val.slice(0, -8)
|
(val) => val.slice(0, -8)
|
||||||
),
|
),
|
||||||
@@ -283,7 +283,7 @@ function InstancePeerList({ setBreadcrumb }) {
|
|||||||
key="disassociate"
|
key="disassociate"
|
||||||
onDisassociate={handlePeersDiassociate}
|
onDisassociate={handlePeersDiassociate}
|
||||||
itemsToDisassociate={selected}
|
itemsToDisassociate={selected}
|
||||||
modalTitle={t`Remove instance from peers?`}
|
modalTitle={t`Remove peers?`}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
]}
|
]}
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ function InstancePeerListItem({
|
|||||||
}}
|
}}
|
||||||
dataLabel={t`Selected`}
|
dataLabel={t`Selected`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Td id={labelId} dataLabel={t`Address`}>
|
<Td id={labelId} dataLabel={t`Address`}>
|
||||||
<Link to={`/instances/${peerInstance.instance}/details`}>
|
<Link to={`/instances/${peerInstance.instance}/details`}>
|
||||||
<b>{peerInstance.address}</b>
|
<b>{peerInstance.address}</b>
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ function Instances() {
|
|||||||
[`/instances/${instance.id}`]: `${instance.hostname}`,
|
[`/instances/${instance.id}`]: `${instance.hostname}`,
|
||||||
[`/instances/${instance.id}/details`]: t`Details`,
|
[`/instances/${instance.id}/details`]: t`Details`,
|
||||||
[`/instances/${instance.id}/peers`]: t`Peers`,
|
[`/instances/${instance.id}/peers`]: t`Peers`,
|
||||||
|
[`/instances/${instance.id}/endpoints`]: t`Endpoints`,
|
||||||
[`/instances/${instance.id}/edit`]: t`Edit Instance`,
|
[`/instances/${instance.id}/edit`]: t`Edit Instance`,
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
Reference in New Issue
Block a user