Skip to content

Commit

Permalink
Fixes #36822 - Design new hosts page
Browse files Browse the repository at this point in the history
Created a HostIndex component

Update the TableIndexPage as a part of this action to accept things like
select all

Co-authored-by: Jeremy Lenz <[email protected]>
  • Loading branch information
parthaa and jeremylenz committed Oct 25, 2023
1 parent b8e2e05 commit d005c3f
Show file tree
Hide file tree
Showing 23 changed files with 889 additions and 27 deletions.
5 changes: 5 additions & 0 deletions app/controllers/hosts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -941,4 +941,9 @@ def redirection_url_on_host_deletion
def current_host_details_path(host)
Setting['host_details_ui'] ? host_details_page_path(host) : host_path(host)
end

def hosts_path
Setting[:new_hosts_page] ? '/new/hosts' : super
end

end
5 changes: 5 additions & 0 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -415,10 +415,15 @@ def ui_settings
labFeatures: Setting[:lab_features],
safeMode: Setting[:safemode_render],
displayFqdnForHosts: Setting[:display_fqdn_for_hosts],
displayNewHostsPage: Setting[:new_hosts_page]
}
end

def current_host_details_path(host)
Setting['host_details_ui'] ? host_details_page_path(host) : host_path(host)
end

def hosts_path
Setting[:new_hosts_page] ? '/new/hosts' : '/hosts'
end
end
5 changes: 5 additions & 0 deletions app/registries/foreman/settings/general.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@
description: N_("Whether or not to show a menu to access experimental lab features (requires reload of page)"),
default: false,
full_name: N_('Show Experimental Labs'))
setting('new_hosts_page',
type: :boolean,
description: N_("Whether or not to show the new overview page for All Hosts (requires reload of page)"),
default: false,
full_name: N_('Show New Host Overview Page'))
setting('display_fqdn_for_hosts',
type: :boolean,
description: N_('Display names of hosts as FQDNs. If disabled, only display names of hosts as hostnames.'),
Expand Down
8 changes: 7 additions & 1 deletion app/registries/menu/loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,13 @@ def self.load
end

menu.sub_menu :hosts_menu, :caption => N_('Hosts'), :icon => 'fa fa-server' do
menu.item :hosts, :caption => N_('All Hosts')
menu.item :hosts, :caption => N_('All Hosts'),
:if => proc { !Setting[:new_hosts_page] }
menu.item :newhosts, :caption => N_('All Hosts'),
:if => proc { Setting[:new_hosts_page] },
:url => '/new/hosts',
:url_hash => { :controller => 'api/v2/hosts', :action => 'index' }

menu.item :newhost, :caption => N_('Create Host'),
:url_hash => {:controller => '/hosts', :action => 'new'}
menu.item :register_hosts, :caption => N_('Register Host'),
Expand Down
3 changes: 2 additions & 1 deletion app/views/api/v2/hosts/main.json.rabl
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ object @host

extends "api/v2/hosts/base"
extends "api/v2/smart_proxies/children_nodes"
extends "api/v2/layouts/permissions"

# we need to cache results with @last_reports, rabl can't pass custom parameters to attriute methods
# we need to cache results with @last_reports, rabl can't pass custom parameters to attribute methods
@object.global_status_label(:last_reports => @last_reports)
@object.configuration_status(:last_reports => @last_reports)
@object.configuration_status_label(:last_reports => @last_reports)
Expand Down
3 changes: 3 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -551,9 +551,12 @@
end

match 'host_statuses' => 'react#index', :via => :get
match 'new/hosts/auto_complete_search', :via => :get, :to => 'hosts#auto_complete_search', :as => "auto_complete_search_hosts_new"
constraints(id: /[^\/]+/) do
match 'new/hosts/:id' => 'react#index', :via => :get, :as => :host_details_page
end
match 'new/hosts/' => 'react#index', :via => :get

get 'page-not-found' => 'react#index'
get 'links/:type(/:section)' => 'links#show', :as => 'external_link', :constraints => { section: %r{.*} }
end
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@ export const useForemanDocUrl = () => useForemanMetadata().docUrl;
export const useForemanOrganization = () => useForemanMetadata().organization;
export const useForemanLocation = () => useForemanMetadata().location;
export const useForemanUser = () => useForemanMetadata().user;

export const useForemanHostsPageUrl = () => {
const { displayNewHostsPage } = useForemanSettings();
return displayNewHostsPage ? '/new/hosts' : '/hosts';
};
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,12 @@ import { useAPI } from '../../common/hooks/API/APIHooks';
import TabRouter from './Tabs/TabRouter';
import RedirectToEmptyHostPage from './EmptyState';
import BreadcrumbBar from '../BreadcrumbBar';
import { foremanUrl } from '../../common/helpers';
import { CardExpansionContextWrapper } from './CardExpansionContext';
import Head from '../Head';
import { useForemanSettings } from '../../Root/Context/ForemanContext';
import {
useForemanSettings,
useForemanHostsPageUrl,
} from '../../Root/Context/ForemanContext';

const HostDetails = ({
match: {
Expand Down Expand Up @@ -113,7 +115,7 @@ const HostDetails = ({
switcherItemUrl: '/new/hosts/:name',
}}
breadcrumbItems={[
{ caption: __('Hosts'), url: foremanUrl('/hosts') },
{ caption: __('Hosts'), url: useForemanHostsPageUrl() },

Check failure on line 118 in webpack/assets/javascripts/react_app/components/HostDetails/index.js

View workflow job for this annotation

GitHub Actions / test (12)

React Hook "useForemanHostsPageUrl" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?

Check failure on line 118 in webpack/assets/javascripts/react_app/components/HostDetails/index.js

View workflow job for this annotation

GitHub Actions / test (14)

React Hook "useForemanHostsPageUrl" is called conditionally. React Hooks must be called in the exact same order in every component render. Did you accidentally call a React Hook after an early return?
{
caption: displayFqdnForHosts
? response.name
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { Dropdown, KebabToggle } from '@patternfly/react-core';

/**
* Generate a button or a dropdown of buttons
* @param {String} title The title of the button for the title and text inside the button
* @param {Object} action action to preform when the button is click can be href with data-method or Onclick
* @return {Function} button component or splitbutton component
*/
export const ActionKebab = ({ items }) => {
const [isOpen, setIsOpen] = useState(false);
if (!items.length) return null;
return (
<>
{items.length > 0 && (
<Dropdown
ouiaId="action-buttons-dropdown"
toggle={
<KebabToggle
aria-label="toggle action dropdown"
onToggle={setIsOpen}
/>
}
isOpen={isOpen}
isPlain
dropdownItems={items}
/>
)}
</>
);
};

ActionKebab.propTypes = {
items: PropTypes.arrayOf(PropTypes.node),
};

ActionKebab.defaultProps = {
items: [],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { selectComponentByWeight } from '../common/Slot/SlotSelectors';

export const selectKebabItems = () =>
selectComponentByWeight('hosts-index-kebab');
124 changes: 124 additions & 0 deletions webpack/assets/javascripts/react_app/components/HostsIndex/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import React, { createContext } from 'react';
import PropTypes from 'prop-types';
import { useSelector, shallowEqual } from 'react-redux';
import { Td } from '@patternfly/react-table';
import { ToolbarItem } from '@patternfly/react-core';
import { translate as __ } from '../../common/I18n';
import TableIndexPage from '../PF4/TableIndexPage/TableIndexPage';
import { ActionKebab } from './ActionKebab';
import { HOSTS_API_PATH, API_REQUEST_KEY } from '../../routes/Hosts/constants';
import { selectKebabItems } from './Selectors';
import { useAPI } from '../../common/hooks/API/APIHooks';
import { useBulkSelect } from '../PF4/TableIndexPage/Table/TableHooks';
import SelectAllCheckbox from '../PF4/TableIndexPage/Table/SelectAllCheckbox';
import { getPageStats } from '../PF4/TableIndexPage/Table/helpers';

export const ForemanHostsIndexActionsBarContext = createContext({});

const HostsIndex = () => {
const columns = {
name: {
title: __('Name'),
wrapper: ({ id, name }) => <a href={`hosts/${id}`}>{name}</a>,
isSorted: true,
},
};
const defaultParams = { search: '' }; // search ||

const response = useAPI('get', `${HOSTS_API_PATH}?include_permissions=true`, {
key: API_REQUEST_KEY,
params: defaultParams,
});

const {
response: {
search: apiSearchQuery,
results,
total,
per_page: perPage,
page,
},
} = response;

const { pageRowCount } = getPageStats({ total, page, perPage });

const { fetchBulkParams, ...selectAllOptions } = useBulkSelect({
results,
metadata: { total, page },
initialSearchQuery: apiSearchQuery || '',
});

const {
selectAll,
selectPage,
selectNone,
selectedCount,
selectOne,
areAllRowsOnPageSelected,
areAllRowsSelected,
isSelected,
} = selectAllOptions;

const selectionToolbar = (
<ToolbarItem key="selectAll">
<SelectAllCheckbox
{...{
selectAll,
selectPage,
selectNone,
selectedCount,
pageRowCount,
}}
totalCount={total}
areAllRowsOnPageSelected={areAllRowsOnPageSelected()}
areAllRowsSelected={areAllRowsSelected()}
/>
</ToolbarItem>
);

const RowSelectTd = ({ rowData }) => (
<Td
select={{
rowIndex: rowData.id,
onSelect: (_event, isSelecting) => {
selectOne(isSelecting, rowData.id);
},
isSelected: isSelected(rowData.id),
disable: false,
}}
/>
);

RowSelectTd.propTypes = {
rowData: PropTypes.object.isRequired,
};

const actionNode = [];
const registeredItems = useSelector(selectKebabItems, shallowEqual);
const customToolbarItems = (
<ForemanHostsIndexActionsBarContext.Provider
value={{ ...selectAllOptions, fetchBulkParams }}
>
<ActionKebab items={actionNode.concat(registeredItems)} />
</ForemanHostsIndexActionsBarContext.Provider>
);

return (
<TableIndexPage
apiUrl={HOSTS_API_PATH}
apiOptions={{ key: API_REQUEST_KEY }}
header={__('Hosts')}
controller="hosts"
isDeleteable
columns={columns}
creatable={false}
replacementResponse={response}
customToolbarItems={customToolbarItems}
selectionToolbar={selectionToolbar}
showCheckboxes
rowSelectTd={RowSelectTd}
/>
);
};

export default HostsIndex;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.table-select-all-checkbox {
font-weight: normal;
}
Loading

0 comments on commit d005c3f

Please sign in to comment.