mirror of
https://github.com/ZwareBear/awx.git
synced 2026-04-18 14:01:49 -05:00
322 lines
10 KiB
JavaScript
322 lines
10 KiB
JavaScript
import React, { useState, useCallback, useEffect } from 'react';
|
|
import { t, Plural } from '@lingui/macro';
|
|
import styled from 'styled-components';
|
|
import { useConfig } from 'contexts/Config';
|
|
import useRequest, { useDismissableError } from 'hooks/useRequest';
|
|
import useDebounce from 'hooks/useDebounce';
|
|
import { InstancesAPI } from 'api';
|
|
import computeForks from 'util/computeForks';
|
|
import {
|
|
Button as PFButton,
|
|
DescriptionList as PFDescriptionList,
|
|
DescriptionListTerm,
|
|
DescriptionListGroup as PFDescriptionListGroup,
|
|
DescriptionListDescription,
|
|
Divider,
|
|
Progress,
|
|
ProgressMeasureLocation,
|
|
ProgressSize,
|
|
Slider,
|
|
TextContent,
|
|
Text as PFText,
|
|
TextVariants,
|
|
} from '@patternfly/react-core';
|
|
import { DownloadIcon } from '@patternfly/react-icons';
|
|
import ContentLoading from 'components/ContentLoading';
|
|
import InstanceToggle from 'components/InstanceToggle';
|
|
import StatusLabel from 'components/StatusLabel';
|
|
import AlertModal from 'components/AlertModal';
|
|
import ErrorDetail from 'components/ErrorDetail';
|
|
import { formatDateString } from 'util/dates';
|
|
|
|
const Wrapper = styled.div`
|
|
position: absolute;
|
|
top: -20px;
|
|
right: 0;
|
|
padding: 10px;
|
|
width: 25%;
|
|
background-color: rgba(255, 255, 255, 0.85);
|
|
`;
|
|
const Button = styled(PFButton)`
|
|
&&& {
|
|
width: 30px;
|
|
height: 30px;
|
|
border-radius: 15px;
|
|
padding: 0;
|
|
font-size: 16px;
|
|
background-color: white;
|
|
border: 1px solid #ccc;
|
|
color: black;
|
|
}
|
|
`;
|
|
const DescriptionList = styled(PFDescriptionList)`
|
|
gap: 0;
|
|
`;
|
|
const DescriptionListGroup = styled(PFDescriptionListGroup)`
|
|
align-items: center;
|
|
margin-top: 10px;
|
|
`;
|
|
const Text = styled(PFText)`
|
|
margin: 10px 0 5px;
|
|
`;
|
|
|
|
const Unavailable = styled.span`
|
|
color: var(--pf-global--danger-color--200);
|
|
`;
|
|
|
|
const SliderHolder = styled.div`
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
`;
|
|
|
|
const SliderForks = styled.div`
|
|
flex-grow: 1;
|
|
margin-right: 8px;
|
|
margin-left: 8px;
|
|
text-align: center;
|
|
`;
|
|
|
|
function renderInstanceGroups(instanceGroups) {
|
|
return instanceGroups.map((g) => <StatusLabel status={g.name} />);
|
|
}
|
|
|
|
function usedCapacity(instance) {
|
|
if (instance.enabled) {
|
|
return (
|
|
<Progress
|
|
value={Math.round(100 - instance.percent_capacity_remaining)}
|
|
measureLocation={ProgressMeasureLocation.top}
|
|
size={ProgressSize.sm}
|
|
title={t`Used capacity`}
|
|
/>
|
|
);
|
|
}
|
|
return <Unavailable>{t`Unavailable`}</Unavailable>;
|
|
}
|
|
|
|
function Tooltip({
|
|
fetchInstance,
|
|
isNodeSelected,
|
|
renderNodeIcon,
|
|
instanceDetail,
|
|
instanceGroups,
|
|
isLoading,
|
|
redirectToDetailsPage,
|
|
}) {
|
|
const { me = {} } = useConfig();
|
|
|
|
const [forks, setForks] = useState(
|
|
computeForks(
|
|
instanceDetail.mem_capacity,
|
|
instanceDetail.cpu_capacity,
|
|
instanceDetail.capacity_adjustment
|
|
)
|
|
);
|
|
|
|
const { error: updateInstanceError, request: updateInstance } = useRequest(
|
|
useCallback(
|
|
async (values) => {
|
|
await InstancesAPI.update(instanceDetail.id, values);
|
|
},
|
|
[instanceDetail]
|
|
)
|
|
);
|
|
|
|
const debounceUpdateInstance = useDebounce(updateInstance, 100);
|
|
|
|
const { error: updateError, dismissError: dismissUpdateError } =
|
|
useDismissableError(updateInstanceError);
|
|
|
|
const handleChangeValue = (value) => {
|
|
const roundedValue = Math.round(value * 100) / 100;
|
|
setForks(
|
|
computeForks(
|
|
instanceDetail.mem_capacity,
|
|
instanceDetail.cpu_capacity,
|
|
roundedValue
|
|
)
|
|
);
|
|
debounceUpdateInstance({ capacity_adjustment: roundedValue });
|
|
};
|
|
|
|
useEffect(() => {
|
|
setForks(
|
|
computeForks(
|
|
instanceDetail.mem_capacity,
|
|
instanceDetail.cpu_capacity,
|
|
instanceDetail.capacity_adjustment
|
|
)
|
|
);
|
|
}, [instanceDetail]);
|
|
|
|
return (
|
|
<Wrapper className="tooltip" data-cy="tooltip">
|
|
{isNodeSelected === false ? (
|
|
<TextContent>
|
|
<Text
|
|
component={TextVariants.small}
|
|
style={{ fontWeight: 'bold', color: 'black' }}
|
|
>
|
|
{t`Details`}
|
|
</Text>
|
|
<Divider component="div" />
|
|
<Text component={TextVariants.small}>
|
|
{t`Click on a node icon to display the details.`}
|
|
</Text>
|
|
</TextContent>
|
|
) : (
|
|
<>
|
|
{updateError && (
|
|
<AlertModal
|
|
variant="error"
|
|
title={t`Error!`}
|
|
isOpen
|
|
onClose={dismissUpdateError}
|
|
>
|
|
{t`Failed to update instance.`}
|
|
<ErrorDetail error={updateError} />
|
|
</AlertModal>
|
|
)}
|
|
<TextContent>
|
|
<Text
|
|
component={TextVariants.small}
|
|
style={{ fontWeight: 'bold', color: 'black' }}
|
|
>
|
|
{t`Details`}
|
|
</Text>
|
|
<Divider component="div" />
|
|
</TextContent>
|
|
{isLoading && <ContentLoading />}
|
|
{!isLoading && (
|
|
<DescriptionList>
|
|
<DescriptionListGroup>
|
|
<DescriptionListDescription>
|
|
<Button>{renderNodeIcon}</Button>{' '}
|
|
<PFButton
|
|
variant="link"
|
|
isInline
|
|
onClick={redirectToDetailsPage}
|
|
>
|
|
{instanceDetail.hostname}
|
|
</PFButton>
|
|
</DescriptionListDescription>
|
|
</DescriptionListGroup>
|
|
<DescriptionListGroup>
|
|
<DescriptionListTerm>{t`Instance status`}</DescriptionListTerm>
|
|
<DescriptionListDescription>
|
|
<StatusLabel status={instanceDetail.node_state} />
|
|
</DescriptionListDescription>
|
|
</DescriptionListGroup>
|
|
<DescriptionListGroup>
|
|
<DescriptionListTerm>{t`Instance type`}</DescriptionListTerm>
|
|
<DescriptionListDescription>
|
|
{instanceDetail.node_type}
|
|
</DescriptionListDescription>
|
|
</DescriptionListGroup>
|
|
{instanceDetail.related?.install_bundle && (
|
|
<DescriptionListGroup>
|
|
<DescriptionListTerm>{t`Download bundle`}</DescriptionListTerm>
|
|
<DescriptionListDescription>
|
|
<a href={`${instanceDetail.related.install_bundle}`}>
|
|
<PFButton
|
|
ouiaId="job-output-download-button"
|
|
variant="plain"
|
|
aria-label={t`Download Bundle`}
|
|
>
|
|
<DownloadIcon />
|
|
</PFButton>
|
|
</a>
|
|
</DescriptionListDescription>
|
|
</DescriptionListGroup>
|
|
)}
|
|
{instanceDetail.ip_address && (
|
|
<DescriptionListGroup>
|
|
<DescriptionListTerm>{t`IP address`}</DescriptionListTerm>
|
|
<DescriptionListDescription>
|
|
{instanceDetail.ip_address}
|
|
</DescriptionListDescription>
|
|
</DescriptionListGroup>
|
|
)}
|
|
{instanceGroups && (
|
|
<DescriptionListGroup>
|
|
<DescriptionListTerm>{t`Instance groups`}</DescriptionListTerm>
|
|
<DescriptionListDescription>
|
|
{renderInstanceGroups(instanceGroups.results)}
|
|
</DescriptionListDescription>
|
|
</DescriptionListGroup>
|
|
)}
|
|
{instanceDetail.node_type !== 'hop' && (
|
|
<>
|
|
<DescriptionListGroup>
|
|
<DescriptionListTerm>{t`Forks`}</DescriptionListTerm>
|
|
<DescriptionListDescription>
|
|
<SliderHolder data-cy="slider-holder">
|
|
<div data-cy="cpu-capacity">{t`CPU ${instanceDetail.cpu_capacity}`}</div>
|
|
<SliderForks data-cy="slider-forks">
|
|
<div data-cy="number-forks">
|
|
<Plural
|
|
value={forks}
|
|
one="# fork"
|
|
other="# forks"
|
|
/>
|
|
</div>
|
|
<Slider
|
|
areCustomStepsContinuous
|
|
max={1}
|
|
min={0}
|
|
step={0.1}
|
|
value={instanceDetail.capacity_adjustment}
|
|
onChange={handleChangeValue}
|
|
isDisabled={
|
|
!me?.is_superuser || !instanceDetail.enabled
|
|
}
|
|
data-cy="slider"
|
|
/>
|
|
</SliderForks>
|
|
<div data-cy="mem-capacity">{t`RAM ${instanceDetail.mem_capacity}`}</div>
|
|
</SliderHolder>
|
|
</DescriptionListDescription>
|
|
</DescriptionListGroup>
|
|
<DescriptionListGroup>
|
|
<DescriptionListTerm>{t`Capacity`}</DescriptionListTerm>
|
|
<DescriptionListDescription>
|
|
{usedCapacity(instanceDetail)}
|
|
</DescriptionListDescription>
|
|
</DescriptionListGroup>
|
|
<DescriptionListGroup>
|
|
<DescriptionListDescription>
|
|
<InstanceToggle
|
|
css="display: inline-flex;"
|
|
fetchInstances={fetchInstance}
|
|
instance={instanceDetail}
|
|
/>
|
|
</DescriptionListDescription>
|
|
</DescriptionListGroup>
|
|
</>
|
|
)}
|
|
|
|
<DescriptionListGroup>
|
|
<DescriptionListTerm>{t`Last modified`}</DescriptionListTerm>
|
|
<DescriptionListDescription>
|
|
{formatDateString(instanceDetail.modified)}
|
|
</DescriptionListDescription>
|
|
</DescriptionListGroup>
|
|
<DescriptionListGroup>
|
|
<DescriptionListTerm>{t`Last seen`}</DescriptionListTerm>
|
|
<DescriptionListDescription>
|
|
{instanceDetail.last_seen
|
|
? formatDateString(instanceDetail.last_seen)
|
|
: `not found`}
|
|
</DescriptionListDescription>
|
|
</DescriptionListGroup>
|
|
</DescriptionList>
|
|
)}
|
|
</>
|
|
)}
|
|
</Wrapper>
|
|
);
|
|
}
|
|
|
|
export default Tooltip;
|