mirror of
https://github.com/ansible/awx.git
synced 2026-01-23 15:38:06 -03:30
Mesh UI support
- add endpoint - delete endpoint (wip) - associate - disassociate
This commit is contained in:
parent
93500f9fea
commit
82ad7dcf40
@ -32,6 +32,10 @@ class Instances extends Base {
|
||||
return this.http.get(`${this.baseUrl}${instanceId}/receptor_addresses/`);
|
||||
}
|
||||
|
||||
updateReceptorAddresses(instanceId, data) {
|
||||
return this.http.post(`${this.baseUrl}${instanceId}/receptor_addresses/`, data);
|
||||
}
|
||||
|
||||
deprovisionInstance(instanceId) {
|
||||
return this.http.patch(`${this.baseUrl}${instanceId}/`, {
|
||||
node_state: 'deprovisioning',
|
||||
|
||||
@ -5,6 +5,10 @@ class ReceptorAddresses extends Base {
|
||||
super(http);
|
||||
this.baseUrl = 'api/v2/receptor_addresses/';
|
||||
}
|
||||
|
||||
updateReceptorAddresses(instanceId, data) {
|
||||
return this.http.post(`${this.baseUrl}`, data);
|
||||
}
|
||||
}
|
||||
|
||||
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 InstanceDetail from './InstanceDetail';
|
||||
import InstancePeerList from './InstancePeers';
|
||||
import InstanceEndPointList from './InstanceEndPointList';
|
||||
|
||||
function Instance({ setBreadcrumb }) {
|
||||
const { me } = useConfig();
|
||||
@ -54,7 +55,8 @@ function Instance({ setBreadcrumb }) {
|
||||
}, [request]);
|
||||
|
||||
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) {
|
||||
return <ContentLoading />;
|
||||
@ -72,6 +74,11 @@ function Instance({ setBreadcrumb }) {
|
||||
<Route path="/instances/:id/details" key="details">
|
||||
<InstanceDetail isK8s={isK8s} setBreadcrumb={setBreadcrumb} />
|
||||
</Route>
|
||||
{isK8s && (
|
||||
<Route path="/instances/:id/endpoints" key="endpoints">
|
||||
<InstanceEndPointList setBreadcrumb={setBreadcrumb} />
|
||||
</Route>
|
||||
)}
|
||||
{isK8s && (
|
||||
<Route path="/instances/:id/peers" key="peers">
|
||||
<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 [
|
||||
{ data: detail },
|
||||
{
|
||||
data: { results, count: itemNumber },
|
||||
data: { results },
|
||||
},
|
||||
actions,
|
||||
instances,
|
||||
@ -72,7 +72,7 @@ function InstancePeerList({ setBreadcrumb }) {
|
||||
return {
|
||||
instance: detail,
|
||||
peers: address_list,
|
||||
count: itemNumber,
|
||||
count: address_list.length,
|
||||
relatedSearchableKeys: (actions?.data?.related_search_fields || []).map(
|
||||
(val) => val.slice(0, -8)
|
||||
),
|
||||
@ -283,7 +283,7 @@ function InstancePeerList({ setBreadcrumb }) {
|
||||
key="disassociate"
|
||||
onDisassociate={handlePeersDiassociate}
|
||||
itemsToDisassociate={selected}
|
||||
modalTitle={t`Remove instance from peers?`}
|
||||
modalTitle={t`Remove peers?`}
|
||||
/>
|
||||
),
|
||||
]}
|
||||
|
||||
@ -43,7 +43,6 @@ function InstancePeerListItem({
|
||||
}}
|
||||
dataLabel={t`Selected`}
|
||||
/>
|
||||
|
||||
<Td id={labelId} dataLabel={t`Address`}>
|
||||
<Link to={`/instances/${peerInstance.instance}/details`}>
|
||||
<b>{peerInstance.address}</b>
|
||||
|
||||
@ -25,6 +25,7 @@ function Instances() {
|
||||
[`/instances/${instance.id}`]: `${instance.hostname}`,
|
||||
[`/instances/${instance.id}/details`]: t`Details`,
|
||||
[`/instances/${instance.id}/peers`]: t`Peers`,
|
||||
[`/instances/${instance.id}/endpoints`]: t`Endpoints`,
|
||||
[`/instances/${instance.id}/edit`]: t`Edit Instance`,
|
||||
});
|
||||
}, []);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user