mirror of
https://github.com/ansible/awx.git
synced 2026-01-11 10:00:01 -03:30
Add new node details; update legend.
This commit is contained in:
parent
7e627e1d1e
commit
89a6162dcd
@ -14,8 +14,12 @@ import {
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
import {
|
||||
ExclamationIcon as PFExclamationIcon,
|
||||
CheckIcon as PFCheckIcon,
|
||||
ExclamationIcon,
|
||||
CheckIcon,
|
||||
OutlinedClockIcon,
|
||||
PlusIcon,
|
||||
MinusIcon,
|
||||
ResourcesEmptyIcon,
|
||||
} from '@patternfly/react-icons';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
@ -27,23 +31,20 @@ const Wrapper = styled.div`
|
||||
background-color: rgba(255, 255, 255, 0.85);
|
||||
`;
|
||||
const Button = styled(PFButton)`
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 10px;
|
||||
padding: 0;
|
||||
font-size: 11px;
|
||||
&&& {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 10px;
|
||||
padding: 0;
|
||||
font-size: 11px;
|
||||
background-color: white;
|
||||
border: 1px solid #ccc;
|
||||
color: black;
|
||||
}
|
||||
`;
|
||||
const DescriptionListDescription = styled(PFDescriptionListDescription)`
|
||||
font-size: 11px;
|
||||
`;
|
||||
const ExclamationIcon = styled(PFExclamationIcon)`
|
||||
fill: white;
|
||||
margin-left: 2px;
|
||||
`;
|
||||
const CheckIcon = styled(PFCheckIcon)`
|
||||
fill: white;
|
||||
margin-left: 2px;
|
||||
`;
|
||||
const DescriptionList = styled(PFDescriptionList)`
|
||||
gap: 7px;
|
||||
`;
|
||||
@ -70,9 +71,7 @@ function Legend() {
|
||||
<DescriptionList isHorizontal isFluid>
|
||||
<DescriptionListGroup>
|
||||
<DescriptionListTerm>
|
||||
<Button variant="primary" isSmall>
|
||||
{t`C`}
|
||||
</Button>
|
||||
<Button isSmall>{t`C`}</Button>
|
||||
</DescriptionListTerm>
|
||||
<DescriptionListDescription>{t`Control node`}</DescriptionListDescription>
|
||||
</DescriptionListGroup>
|
||||
@ -110,27 +109,133 @@ function Legend() {
|
||||
<DescriptionListGroup>
|
||||
<DescriptionListTerm>
|
||||
<Button
|
||||
icon={<CheckIcon />}
|
||||
icon={
|
||||
<CheckIcon
|
||||
style={{ fill: 'white', marginLeft: '2px', marginTop: '3px' }}
|
||||
/>
|
||||
}
|
||||
isSmall
|
||||
style={{ border: '1px solid gray', backgroundColor: '#3E8635' }}
|
||||
style={{ backgroundColor: '#3E8635' }}
|
||||
/>
|
||||
</DescriptionListTerm>
|
||||
<DescriptionListDescription>{t`Healthy`}</DescriptionListDescription>
|
||||
<DescriptionListDescription>{t`Ready`}</DescriptionListDescription>
|
||||
</DescriptionListGroup>
|
||||
<DescriptionListGroup>
|
||||
<DescriptionListTerm>
|
||||
<Button variant="danger" icon={<ExclamationIcon />} isSmall />
|
||||
<Button
|
||||
icon={
|
||||
<OutlinedClockIcon
|
||||
style={{ fill: 'white', marginLeft: '3px', marginTop: '3px' }}
|
||||
/>
|
||||
}
|
||||
isSmall
|
||||
style={{ backgroundColor: '#0066CC' }}
|
||||
/>
|
||||
</DescriptionListTerm>
|
||||
<DescriptionListDescription>{t`Installed`}</DescriptionListDescription>
|
||||
</DescriptionListGroup>
|
||||
<DescriptionListGroup>
|
||||
<DescriptionListTerm>
|
||||
<Button
|
||||
icon={
|
||||
<PlusIcon
|
||||
style={{ fill: 'white', marginLeft: '3px', marginTop: '3px' }}
|
||||
/>
|
||||
}
|
||||
isSmall
|
||||
style={{ backgroundColor: '#6A6E73' }}
|
||||
/>
|
||||
</DescriptionListTerm>
|
||||
<DescriptionListDescription>{t`Provisioning`}</DescriptionListDescription>
|
||||
</DescriptionListGroup>
|
||||
<DescriptionListGroup>
|
||||
<DescriptionListTerm>
|
||||
<Button
|
||||
icon={
|
||||
<MinusIcon
|
||||
style={{ fill: 'white', marginLeft: '3px', marginTop: '3px' }}
|
||||
/>
|
||||
}
|
||||
isSmall
|
||||
style={{ backgroundColor: '#6A6E73' }}
|
||||
/>
|
||||
</DescriptionListTerm>
|
||||
<DescriptionListDescription>{t`Deprovisioning`}</DescriptionListDescription>
|
||||
</DescriptionListGroup>
|
||||
<DescriptionListGroup>
|
||||
<DescriptionListTerm>
|
||||
<Button
|
||||
icon={
|
||||
<ResourcesEmptyIcon
|
||||
style={{ fill: 'white', marginLeft: '3px', marginTop: '3px' }}
|
||||
/>
|
||||
}
|
||||
isSmall
|
||||
style={{ backgroundColor: '#F0AB00' }}
|
||||
/>
|
||||
</DescriptionListTerm>
|
||||
<DescriptionListDescription>{t`Unavailable`}</DescriptionListDescription>
|
||||
</DescriptionListGroup>
|
||||
<DescriptionListGroup>
|
||||
<DescriptionListTerm>
|
||||
<Button
|
||||
icon={
|
||||
<ExclamationIcon
|
||||
style={{ fill: 'white', marginLeft: '3px', marginTop: '3px' }}
|
||||
/>
|
||||
}
|
||||
isSmall
|
||||
style={{ backgroundColor: '#C9190B' }}
|
||||
/>
|
||||
</DescriptionListTerm>
|
||||
<DescriptionListDescription>{t`Error`}</DescriptionListDescription>
|
||||
</DescriptionListGroup>
|
||||
<DescriptionListGroup>
|
||||
<DescriptionListTerm>
|
||||
<Button
|
||||
isSmall
|
||||
style={{ border: '1px solid gray', backgroundColor: '#e6e6e6' }}
|
||||
/>
|
||||
<svg width="20" height="15" xmlns="http://www.w3.org/2000/svg">
|
||||
<line
|
||||
x1="0"
|
||||
y1="9"
|
||||
x2="20"
|
||||
y2="9"
|
||||
stroke="#666"
|
||||
strokeWidth="4"
|
||||
/>
|
||||
</svg>
|
||||
</DescriptionListTerm>
|
||||
<DescriptionListDescription>{t`Disabled`}</DescriptionListDescription>
|
||||
<DescriptionListDescription>{t`Established`}</DescriptionListDescription>
|
||||
</DescriptionListGroup>
|
||||
<DescriptionListGroup>
|
||||
<DescriptionListTerm>
|
||||
<svg width="20" height="15" xmlns="http://www.w3.org/2000/svg">
|
||||
<line
|
||||
x1="0"
|
||||
y1="9"
|
||||
x2="20"
|
||||
y2="9"
|
||||
stroke="#666"
|
||||
strokeWidth="4"
|
||||
strokeDasharray="6"
|
||||
/>
|
||||
</svg>
|
||||
</DescriptionListTerm>
|
||||
<DescriptionListDescription>{t`Adding`}</DescriptionListDescription>
|
||||
</DescriptionListGroup>
|
||||
<DescriptionListGroup>
|
||||
<DescriptionListTerm>
|
||||
<svg width="20" height="15" xmlns="http://www.w3.org/2000/svg">
|
||||
<line
|
||||
x1="0"
|
||||
y1="9"
|
||||
x2="20"
|
||||
y2="9"
|
||||
stroke="#C9190B"
|
||||
strokeWidth="4"
|
||||
strokeDasharray="6"
|
||||
/>
|
||||
</svg>
|
||||
</DescriptionListTerm>
|
||||
<DescriptionListDescription>{t`Removing`}</DescriptionListDescription>
|
||||
</DescriptionListGroup>
|
||||
</DescriptionList>
|
||||
</Wrapper>
|
||||
|
||||
@ -1,8 +1,13 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { t } from '@lingui/macro';
|
||||
import styled from 'styled-components';
|
||||
import debounce from 'util/debounce';
|
||||
import * as d3 from 'd3';
|
||||
import { InstancesAPI } from 'api';
|
||||
import useRequest, { useDismissableError } from 'hooks/useRequest';
|
||||
import AlertModal from 'components/AlertModal';
|
||||
import ErrorDetail from 'components/ErrorDetail';
|
||||
import Legend from './Legend';
|
||||
import Tooltip from './Tooltip';
|
||||
import ContentLoading from './ContentLoading';
|
||||
@ -38,9 +43,38 @@ const Loader = styled(ContentLoading)`
|
||||
function MeshGraph({ data, showLegend, zoom, setShowZoomControls }) {
|
||||
const [isNodeSelected, setIsNodeSelected] = useState(false);
|
||||
const [selectedNode, setSelectedNode] = useState(null);
|
||||
const [nodeDetail, setNodeDetail] = useState(null);
|
||||
const [simulationProgress, setSimulationProgress] = useState(null);
|
||||
const history = useHistory();
|
||||
const {
|
||||
result: { instance, instanceGroups },
|
||||
error: fetchError,
|
||||
isLoading,
|
||||
request: fetchDetails,
|
||||
} = useRequest(
|
||||
useCallback(async () => {
|
||||
const { data: instanceData } = await InstancesAPI.readDetail(
|
||||
selectedNode.id
|
||||
);
|
||||
const { data: instanceGroupsData } = await InstancesAPI.readInstanceGroup(
|
||||
selectedNode.id
|
||||
);
|
||||
|
||||
return {
|
||||
instance: instanceData,
|
||||
instanceGroups: instanceGroupsData,
|
||||
};
|
||||
}, [selectedNode]),
|
||||
{
|
||||
result: {},
|
||||
}
|
||||
);
|
||||
|
||||
const { error: fetchInstanceError, dismissError } =
|
||||
useDismissableError(fetchError);
|
||||
|
||||
useEffect(() => {
|
||||
fetchDetails();
|
||||
}, [selectedNode, fetchDetails]);
|
||||
|
||||
const draw = () => {
|
||||
let width;
|
||||
@ -134,7 +168,9 @@ function MeshGraph({ data, showLegend, zoom, setShowZoomControls }) {
|
||||
.attr('class', (_, i) => `link-${i}`)
|
||||
.attr('data-cy', (d) => `${d.source.hostname}-${d.target.hostname}`)
|
||||
.style('fill', 'none')
|
||||
.style('stroke', '#ccc')
|
||||
.style('stroke', (d) =>
|
||||
d.link_state === 'removing' ? '#C9190B' : '#CCC'
|
||||
)
|
||||
.style('stroke-width', '2px')
|
||||
.style('stroke-dasharray', (d) => renderLinkState(d.link_state))
|
||||
.attr('pointer-events', 'none')
|
||||
@ -158,7 +194,6 @@ function MeshGraph({ data, showLegend, zoom, setShowZoomControls }) {
|
||||
deselectSiblings(d);
|
||||
})
|
||||
.on('click', (_, d) => {
|
||||
setNodeDetail(d);
|
||||
highlightSelected(d);
|
||||
});
|
||||
|
||||
@ -272,7 +307,9 @@ function MeshGraph({ data, showLegend, zoom, setShowZoomControls }) {
|
||||
.selectAll(`.link-${s.index}`)
|
||||
.transition()
|
||||
.duration(50)
|
||||
.style('stroke', '#ccc')
|
||||
.style('stroke', (d) =>
|
||||
d.link_state === 'removing' ? '#C9190B' : '#CCC'
|
||||
)
|
||||
.style('stroke-width', '2px')
|
||||
.attr('marker-end', 'url(#end)');
|
||||
});
|
||||
@ -319,14 +356,33 @@ function MeshGraph({ data, showLegend, zoom, setShowZoomControls }) {
|
||||
return (
|
||||
<div id="chart" style={{ position: 'relative', height: '100%' }}>
|
||||
{showLegend && <Legend />}
|
||||
<Tooltip
|
||||
isNodeSelected={isNodeSelected}
|
||||
renderNodeIcon={renderNodeIcon(selectedNode)}
|
||||
nodeDetail={nodeDetail}
|
||||
redirectToDetailsPage={() =>
|
||||
redirectToDetailsPage(selectedNode, history)
|
||||
}
|
||||
/>
|
||||
{instance && (
|
||||
<>
|
||||
{fetchInstanceError && (
|
||||
<AlertModal
|
||||
variant="error"
|
||||
title={t`Error!`}
|
||||
isOpen
|
||||
onClose={dismissError}
|
||||
>
|
||||
{t`Failed to update instance.`}
|
||||
<ErrorDetail error={fetchInstanceError} />
|
||||
</AlertModal>
|
||||
)}
|
||||
<Tooltip
|
||||
isNodeSelected={isNodeSelected}
|
||||
renderNodeIcon={renderNodeIcon(selectedNode)}
|
||||
selectedNode={selectedNode}
|
||||
fetchInstance={fetchDetails}
|
||||
instanceGroups={instanceGroups}
|
||||
instanceDetail={instance}
|
||||
isLoading={isLoading}
|
||||
redirectToDetailsPage={() =>
|
||||
redirectToDetailsPage(selectedNode, history)
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Loader className="simulation-loader" progress={simulationProgress} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import React from 'react';
|
||||
import { t } from '@lingui/macro';
|
||||
import React, { useState, useCallback, useEffect } from 'react';
|
||||
import { t, Plural } from '@lingui/macro';
|
||||
import styled from 'styled-components';
|
||||
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,
|
||||
@ -8,26 +12,41 @@ import {
|
||||
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: 20%;
|
||||
width: 25%;
|
||||
background-color: rgba(255, 255, 255, 0.85);
|
||||
`;
|
||||
const Button = styled(PFButton)`
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 10px;
|
||||
padding: 0;
|
||||
font-size: 11px;
|
||||
&&& {
|
||||
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;
|
||||
@ -39,12 +58,95 @@ const DescriptionListGroup = styled(PFDescriptionListGroup)`
|
||||
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,
|
||||
nodeDetail,
|
||||
instanceDetail,
|
||||
instanceGroups,
|
||||
isLoading,
|
||||
redirectToDetailsPage,
|
||||
}) {
|
||||
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 ? (
|
||||
@ -62,6 +164,17 @@ function Tooltip({
|
||||
</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}
|
||||
@ -71,36 +184,130 @@ function Tooltip({
|
||||
</Text>
|
||||
<Divider component="div" />
|
||||
</TextContent>
|
||||
<DescriptionList isHorizontal isFluid>
|
||||
<DescriptionListGroup>
|
||||
<DescriptionListTerm>
|
||||
<Button variant="primary" isSmall>
|
||||
{renderNodeIcon}
|
||||
</Button>
|
||||
</DescriptionListTerm>
|
||||
<DescriptionListDescription>
|
||||
<PFButton
|
||||
variant="link"
|
||||
isInline
|
||||
onClick={redirectToDetailsPage}
|
||||
>
|
||||
{nodeDetail.hostname}
|
||||
</PFButton>
|
||||
</DescriptionListDescription>
|
||||
</DescriptionListGroup>
|
||||
<DescriptionListGroup>
|
||||
<DescriptionListTerm>{t`Type`}</DescriptionListTerm>
|
||||
<DescriptionListDescription>
|
||||
{nodeDetail.node_type} {t`node`}
|
||||
</DescriptionListDescription>
|
||||
</DescriptionListGroup>
|
||||
<DescriptionListGroup>
|
||||
<DescriptionListTerm>{t`Status`}</DescriptionListTerm>
|
||||
<DescriptionListDescription>
|
||||
<StatusLabel status={nodeDetail.node_state} />
|
||||
</DescriptionListDescription>
|
||||
</DescriptionListGroup>
|
||||
</DescriptionList>
|
||||
{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={!instanceDetail.enabled}
|
||||
// isDisabled={!me?.is_superuser || !instance.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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user