|
/* eslint-disable camelcase */
|
|
import PropTypes from 'prop-types';
|
|
import React, { useEffect } from 'react';
|
|
import { useSelector, shallowEqual } from 'react-redux';
|
|
import { Link } from 'react-router-dom';
|
|
import {
|
|
Flex,
|
|
FlexItem,
|
|
Grid,
|
|
Tab,
|
|
Tabs,
|
|
GridItem,
|
|
Label,
|
|
Title,
|
|
Text,
|
|
TextVariants,
|
|
PageSection,
|
|
Split,
|
|
SplitItem,
|
|
} from '@patternfly/react-core';
|
|
|
|
import RelativeDateTime from '../../components/common/dates/RelativeDateTime';
|
|
import {
|
|
selectFillsIDs,
|
|
selectSlotMetadata,
|
|
} from '../common/Slot/SlotSelectors';
|
|
|
|
import { selectIsCollapsed } from '../Layout/LayoutSelectors';
|
|
import ActionsBar from './ActionsBar';
|
|
import { registerCoreTabs } from './Tabs';
|
|
import { HOST_DETAILS_API_OPTIONS, TABS_SLOT_ID } from './consts';
|
|
|
|
import { translate as __, sprintf } from '../../common/I18n';
|
|
import HostGlobalStatus from './Status/GlobalStatus';
|
|
import SkeletonLoader from '../common/SkeletonLoader';
|
|
import { STATUS } from '../../constants';
|
|
import './HostDetails.scss';
|
|
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';
|
|
|
|
const HostDetails = ({
|
|
match: {
|
|
params: { id },
|
|
},
|
|
location: { hash },
|
|
history,
|
|
}) => {
|
|
const { response, status } = useAPI(
|
|
'get',
|
|
`/api/hosts/${id}?show_hidden_parameters=true`,
|
|
HOST_DETAILS_API_OPTIONS
|
|
);
|
|
const isNavCollapsed = useSelector(selectIsCollapsed);
|
|
const tabs = useSelector(
|
|
state => selectFillsIDs(state, TABS_SLOT_ID),
|
|
shallowEqual
|
|
);
|
|
|
|
const slotMetadata = useSelector(state =>
|
|
selectSlotMetadata(state, TABS_SLOT_ID)
|
|
);
|
|
|
|
// This is a workaround due to the tabs overflow mechanism in PF4
|
|
useEffect(() => {
|
|
if (tabs?.length) dispatchEvent(new Event('resize'));
|
|
}, [tabs]);
|
|
useEffect(() => {
|
|
registerCoreTabs();
|
|
}, []);
|
|
|
|
const activeTab = decodeURI(
|
|
hash
|
|
.slice(2)
|
|
.split('/')[0]
|
|
.split('?')[0] // Remove query params
|
|
);
|
|
|
|
const filteredTabs =
|
|
tabs?.filter(
|
|
tab => !slotMetadata?.[tab]?.hideTab?.({ hostDetails: response })
|
|
) ?? [];
|
|
|
|
if (status === STATUS.ERROR) return <RedirectToEmptyHostPage hostname={id} />;
|
|
return (
|
|
<>
|
|
<Head>
|
|
<title>{id}</title>
|
|
</Head>
|
|
<PageSection
|
|
className="host-details-header-section"
|
|
isFilled
|
|
variant="light"
|
|
>
|
|
<div className="header-top">
|
|
<SkeletonLoader
|
|
skeletonProps={{ width: 300 }}
|
|
status={status || STATUS.PENDING}
|
|
>
|
|
{response.name && (
|
|
<BreadcrumbBar
|
|
isSwitchable
|
|
isPf4
|
|
onSwitcherItemClick={(e, href) => {
|
|
e.preventDefault();
|
|
history.push(href);
|
|
}}
|
|
resource={{
|
|
nameField: 'name',
|
|
resourceUrl: '/api/v2/hosts?thin=true',
|
|
switcherItemUrl: '/new/hosts/:name',
|
|
}}
|
|
breadcrumbItems={[
|
|
{ caption: __('Hosts'), url: foremanUrl('/hosts') },
|
|
{ caption: response.name },
|
|
]}
|
|
/>
|
|
)}
|
|
</SkeletonLoader>
|
|
<Grid className="hostname-skeleton-rapper">
|
|
<GridItem span={8}>
|
|
<SkeletonLoader status={status || STATUS.PENDING}>
|
|
{response && (
|
|
<>
|
|
<div className="hostname-wrapper">
|
|
<SkeletonLoader status={status || STATUS.PENDING}>
|
|
{response && (
|
|
<Title
|
|
ouiaId="hostname-truncate-title"
|
|
className="hostname-truncate"
|
|
headingLevel="h5"
|
|
size="2xl"
|
|
>
|
|
{response.name}
|
|
</Title>
|
|
)}
|
|
</SkeletonLoader>
|
|
</div>
|
|
<Split style={{ display: 'inline-flex' }} hasGutter>
|
|
<SplitItem>
|
|
<HostGlobalStatus
|
|
hostName={id}
|
|
canForgetStatuses={
|
|
!!response?.permissions?.forget_status_hosts
|
|
}
|
|
/>
|
|
</SplitItem>
|
|
<SplitItem>
|
|
<Label
|
|
isCompact
|
|
color="blue"
|
|
render={({ className, content, componentRef }) => (
|
|
<Link
|
|
to={`/hosts?search=os_title="${response?.operatingsystem_name}"`}
|
|
className={className}
|
|
innerRef={componentRef}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
{content}
|
|
</Link>
|
|
)}
|
|
>
|
|
{response?.operatingsystem_name}
|
|
</Label>
|
|
</SplitItem>
|
|
<SplitItem>
|
|
<Label
|
|
isCompact
|
|
color="blue"
|
|
render={({ className, content, componentRef }) => (
|
|
<Link
|
|
to={`/hosts?search=architecture=${response?.architecture_name}`}
|
|
className={className}
|
|
innerRef={componentRef}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
{content}
|
|
</Link>
|
|
)}
|
|
>
|
|
{response?.architecture_name}
|
|
</Label>
|
|
</SplitItem>
|
|
</Split>
|
|
</>
|
|
)}
|
|
</SkeletonLoader>
|
|
</GridItem>
|
|
<GridItem offset={8} span={4}>
|
|
<Flex>
|
|
<FlexItem align={{ default: 'alignRight' }}>
|
|
<ActionsBar
|
|
computeId={response.compute_resource_id}
|
|
hostFriendlyId={id}
|
|
hostId={response.id}
|
|
hostName={response.name}
|
|
permissions={response.permissions}
|
|
isBuild={response.build}
|
|
/>
|
|
</FlexItem>
|
|
</Flex>
|
|
</GridItem>
|
|
</Grid>
|
|
<SkeletonLoader
|
|
skeletonProps={{ width: 400 }}
|
|
status={status || STATUS.PENDING}
|
|
>
|
|
{response && (
|
|
<Text ouiaId="date-text" component={TextVariants.span}>
|
|
<RelativeDateTime date={response.created_at} defaultValue="N/A">
|
|
{date => sprintf(__('Created %s'), date)}
|
|
</RelativeDateTime>{' '}
|
|
{response.creator
|
|
? `${sprintf(__('by %s'), response.creator)}`
|
|
: ''}{' '}
|
|
<RelativeDateTime date={response.updated_at} defaultValue="N/A">
|
|
{date => sprintf(__('(updated %s)'), date)}
|
|
</RelativeDateTime>
|
|
</Text>
|
|
)}
|
|
</SkeletonLoader>
|
|
</div>
|
|
{tabs && (
|
|
<CardExpansionContextWrapper>
|
|
<TabRouter
|
|
response={response}
|
|
hostName={id}
|
|
status={status}
|
|
tabs={tabs}
|
|
router={history}
|
|
>
|
|
<Tabs
|
|
ouiaId="host-details-tabs"
|
|
activeKey={activeTab}
|
|
className={`host-details-tabs tab-width-${
|
|
isNavCollapsed ? '138' : '263'
|
|
}`}
|
|
>
|
|
{filteredTabs.map(tab => {
|
|
const tabID = `${tab.toLowerCase()}-tab`;
|
|
return (
|
|
<Tab
|
|
key={tab}
|
|
id={tabID}
|
|
ouiaId={tabID}
|
|
eventKey={tab}
|
|
title={slotMetadata?.[tab]?.title || tab}
|
|
/>
|
|
);
|
|
})}
|
|
</Tabs>
|
|
</TabRouter>
|
|
</CardExpansionContextWrapper>
|
|
)}
|
|
</PageSection>
|
|
</>
|
|
);
|
|
};
|
|
|
|
HostDetails.propTypes = {
|
|
match: PropTypes.shape({
|
|
params: PropTypes.shape({
|
|
id: PropTypes.string,
|
|
}),
|
|
}).isRequired,
|
|
location: PropTypes.shape({
|
|
hash: PropTypes.string,
|
|
}).isRequired,
|
|
history: PropTypes.object.isRequired,
|
|
};
|
|
|
|
export default HostDetails;
|