diff --git a/app/views/ansible_roles/index.html.erb b/app/views/ansible_roles/index.html.erb index def0fd832..75cd3646a 100644 --- a/app/views/ansible_roles/index.html.erb +++ b/app/views/ansible_roles/index.html.erb @@ -1,47 +1,19 @@ +<%= webpacked_plugins_js_for :foreman_ansible %> +<%= webpacked_plugins_css_for :foreman_ansible %> + <% title _("Ansible Roles") %> <% title_actions ansible_proxy_import(hash_for_import_ansible_roles_path), documentation_button('#4.1ImportingRoles', :root_url => ansible_doc_url) %> - - - - - - - - - - - - - <% @ansible_roles.each do |role| %> - - - - - - - - - <% end %> - -
<%= sort :name, :as => s_("Role|Name") %><%= _("Hostgroups") %><%= _("Hosts") %><%= _("Variables") %><%= sort :updated_at, :as => _("Imported at") %><%= _("Actions") %>
<%= role.name %><%= link_to role.hostgroups.count, hostgroups_path(:search => "ansible_role = #{role.name}") %><%= link_to role.hosts.count, hosts_path(:search => "ansible_role = #{role.name}")%><%= link_to(role.ansible_variables.count, ansible_variables_path(:search => "ansible_role = #{role}")) %><%= import_time role %> - <% - links = [ - link_to( - _('Variables'), - ansible_variables_path(:search => "ansible_role = #{role}") - ), - display_delete_if_authorized( - hash_for_ansible_role_path(:id => role). - merge(:auth_object => role, :authorizer => authorizer), - :data => { :confirm => _("Delete %s?") % role.name }, - :action => :delete - ) - ] - %> - <%= action_buttons(*links) %> -
+<%= react_component('AnsibleRolesTable', {:ansibleRoles => @ansible_roles.map { |role| { + :name => role.name, + :id => role.id, + :hostgroupsCount => role.hostgroups.count, + :hostsCount => role.hosts.count, + :variablesCount => role.ansible_variables.count, + :importTime => import_time(role), + :updatedAt => role.updated_at +} }})%> <%= will_paginate_with_info @ansible_roles %> diff --git a/webpack/components/AnsibleRoles/AnsibleRolesTable.js b/webpack/components/AnsibleRoles/AnsibleRolesTable.js new file mode 100644 index 000000000..7b57af5b8 --- /dev/null +++ b/webpack/components/AnsibleRoles/AnsibleRolesTable.js @@ -0,0 +1,64 @@ +import React from 'react'; +import { TableComposable, Thead, Tr, Th, Tbody } from '@patternfly/react-table'; +import PropTypes from 'prop-types'; +import { translate as __ } from 'foremanReact/common/I18n'; +import { AnsibleRolesTableRow } from './components/AnsibleRolesTableRow'; + +export const AnsibleRolesTable = props => { + const searchParams = new URLSearchParams(window.location.search); + const sortString = searchParams.get('order'); + + let sortIndex = null; + let sortDirection = null; + if (sortString) { + const sortStrings = sortString.split(' '); + sortIndex = sortStrings[0] === 'name' ? 0 : 6; + // eslint-disable-next-line prefer-destructuring + sortDirection = sortStrings[1]; + } + + const getSortParams = columnIndex => ({ + sortBy: { + index: sortIndex, + direction: sortDirection, + defaultDirection: 'asc', + }, + onSort: (_event, index, direction) => { + if (direction !== null && index !== null) { + searchParams.set( + 'order', + `${index === 0 ? 'name' : 'updated_at'} ${direction}` + ); + window.location.search = searchParams.toString(); + } + }, + columnIndex, + }); + return ( + + + + {__('Name')} + {__('Hostgroups')} + {__('Hosts')} + {__('Variables')} + {__('Imported at')} + {__('Actions')} + + + + {props.ansibleRoles.map(role => ( + + ))} + + + ); +}; + +AnsibleRolesTable.propTypes = { + ansibleRoles: PropTypes.array, +}; + +AnsibleRolesTable.defaultProps = { + ansibleRoles: [], +}; diff --git a/webpack/components/AnsibleRoles/AnsibleRolesTable.test.js b/webpack/components/AnsibleRoles/AnsibleRolesTable.test.js new file mode 100644 index 000000000..a93fbc441 --- /dev/null +++ b/webpack/components/AnsibleRoles/AnsibleRolesTable.test.js @@ -0,0 +1,57 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; + +import { AnsibleRolesTable } from './AnsibleRolesTable'; + +describe('AnsibleRolesTable', () => { + const demoRoles = [ + { + name: 'demo_role_0', + hostgroupsCount: 0, + hostsCount: 0, + id: 1, + updatedAt: '2024-01-28', + importTime: '3 days ago', + variablesCount: 1, + }, + { + name: 'demo_role_1', + hostgroupsCount: 0, + hostsCount: 0, + id: 2, + updatedAt: '2024-01-29', + importTime: '2 days ago', + variablesCount: 2, + }, + { + name: 'demo_role_2', + hostgroupsCount: 0, + hostsCount: 0, + id: 3, + updatedAt: '2024-01-30', + importTime: '1 day ago', + variablesCount: 3, + }, + ]; + + it('should render the table', () => { + const { container } = render( + + ); + expect(container.getElementsByTagName('tr')).toHaveLength( + demoRoles.length + 1 + ); + }); + it('should sort correctly', () => { + Object.defineProperty(window, 'location', { + value: new URL('https://test.url/ansible/ansible_roles'), + writable: true, + }); + render(); + + const importedButton = screen.getByText('Imported at'); + importedButton.click(); // asc + + expect(global.window.location.search).toContain('order=updated_at+asc'); + }); +}); diff --git a/webpack/components/AnsibleRoles/components/AnsibleRolesTableRow.js b/webpack/components/AnsibleRoles/components/AnsibleRolesTableRow.js new file mode 100644 index 000000000..4000d54e8 --- /dev/null +++ b/webpack/components/AnsibleRoles/components/AnsibleRolesTableRow.js @@ -0,0 +1,42 @@ +import React from 'react'; +import { Tr, Td } from '@patternfly/react-table'; +import PropTypes from 'prop-types'; +import { AnsibleRolesTableRowActionButton } from './AnsibleRolesTableRowActionButton'; + +export const AnsibleRolesTableRow = props => ( + + {props.ansibleRole.name} + + + {props.ansibleRole.hostgroupsCount} + + + + + {props.ansibleRole.hostsCount} + + + + + {props.ansibleRole.variablesCount} + + + {props.ansibleRole.importTime} + + + + +); + +AnsibleRolesTableRow.propTypes = { + ansibleRole: PropTypes.object, +}; + +AnsibleRolesTableRow.defaultProps = { + ansibleRole: {}, +}; diff --git a/webpack/components/AnsibleRoles/components/AnsibleRolesTableRowActionButton.js b/webpack/components/AnsibleRoles/components/AnsibleRolesTableRowActionButton.js new file mode 100644 index 000000000..b4ae13513 --- /dev/null +++ b/webpack/components/AnsibleRoles/components/AnsibleRolesTableRowActionButton.js @@ -0,0 +1,65 @@ +import React from 'react'; +import { + Dropdown, + DropdownToggle, + DropdownToggleAction, + DropdownItem, +} from '@patternfly/react-core'; +import PropTypes from 'prop-types'; +import { translate as __, sprintf } from 'foremanReact/common/I18n'; + +export const AnsibleRolesTableRowActionButton = props => { + const [isActionOpen, setIsActionOpen] = React.useState(false); + const onActionToggle = actionOpen => { + setIsActionOpen(actionOpen); + }; + + const dropdownItems = [ + + {__('Delete')} + , + ]; + + return ( + + { + window.location = `/ansible/ansible_variables?search=ansible_role+%3D+${props.ansibleRoleName}`; + }} + > + {__('Variables')} + , + ]} + toggleVariant="default" + splitButtonVariant="action" + onToggle={onActionToggle} + /> + } + isOpen={isActionOpen} + dropdownItems={dropdownItems} + />{' '} + + ); +}; + +AnsibleRolesTableRowActionButton.propTypes = { + ansibleRoleId: PropTypes.number, + ansibleRoleName: PropTypes.string, +}; + +AnsibleRolesTableRowActionButton.defaultProps = { + ansibleRoleId: 0, + ansibleRoleName: '', +}; diff --git a/webpack/index.js b/webpack/index.js index 760aeda43..80fd9ac19 100644 --- a/webpack/index.js +++ b/webpack/index.js @@ -4,6 +4,7 @@ import ReportJsonViewer from './components/ReportJsonViewer'; import AnsibleRolesSwitcher from './components/AnsibleRolesSwitcher'; import WrappedImportRolesAndVariables from './components/AnsibleRolesAndVariables'; import reducer from './reducer'; +import { AnsibleRolesTable } from './components/AnsibleRoles/AnsibleRolesTable'; componentRegistry.register({ name: 'ReportJsonViewer', @@ -19,4 +20,9 @@ componentRegistry.register({ type: WrappedImportRolesAndVariables, }); +componentRegistry.register({ + name: 'AnsibleRolesTable', + type: AnsibleRolesTable, +}); + injectReducer('foremanAnsible', reducer);