2017-12-21 02:12:42 +02:00
|
|
|
import React, { Component } from 'react';
|
|
|
|
import { Row, Col } from 'react-styled-flexboxgrid';
|
|
|
|
import styled, { withTheme } from 'styled-components';
|
|
|
|
import { Margin } from 'styled-components-spacing';
|
|
|
|
import remcalc from 'remcalc';
|
|
|
|
import is from 'styled-is';
|
|
|
|
import titleCase from 'title-case';
|
|
|
|
import distanceInWordsToNow from 'date-fns/distance_in_words_to_now';
|
|
|
|
import copy from 'clipboard-copy';
|
|
|
|
|
|
|
|
import {
|
|
|
|
Card,
|
|
|
|
CardOutlet,
|
|
|
|
Divider,
|
|
|
|
ResetIcon,
|
|
|
|
Button,
|
|
|
|
FormLabel,
|
|
|
|
Input,
|
|
|
|
H2,
|
|
|
|
Label,
|
|
|
|
QueryBreakpoints,
|
|
|
|
DotIcon,
|
|
|
|
DeleteIcon,
|
|
|
|
StartIcon,
|
|
|
|
StopIcon,
|
|
|
|
TooltipContainer,
|
|
|
|
TooltipTarget,
|
|
|
|
Tooltip,
|
|
|
|
ClipboardIcon
|
|
|
|
} from 'joyent-ui-toolkit';
|
|
|
|
|
|
|
|
const { SmallOnly, Medium } = QueryBreakpoints;
|
|
|
|
|
|
|
|
const stateColor = {
|
|
|
|
PROVISIONING: 'primary',
|
|
|
|
RUNNING: 'green',
|
|
|
|
STOPPING: 'grey',
|
|
|
|
STOPPED: 'grey',
|
|
|
|
DELETED: 'secondaryActive',
|
|
|
|
FAILED: 'red'
|
|
|
|
};
|
|
|
|
|
|
|
|
const GreyLabel = styled(Label)`
|
|
|
|
opacity: 0.5;
|
|
|
|
padding-right: ${remcalc(3)};
|
|
|
|
`;
|
|
|
|
|
|
|
|
const Flex = styled.div`
|
|
|
|
align-items: center;
|
|
|
|
display: flex;
|
|
|
|
justify-content: flex-start;
|
|
|
|
|
|
|
|
@media (max-width: ${remcalc(767)}) {
|
|
|
|
display: block;
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
|
|
|
const InputIconWrapper = styled.div`
|
|
|
|
display: flex;
|
|
|
|
margin-bottom: ${remcalc(10)};
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
${is('noMargin')`
|
|
|
|
margin-bottom: ${remcalc(0)};
|
|
|
|
`};
|
|
|
|
|
|
|
|
input {
|
|
|
|
padding-right: ${remcalc(30)};
|
|
|
|
}
|
|
|
|
|
|
|
|
div {
|
|
|
|
position: relative;
|
|
|
|
left: ${remcalc(-26)};
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
|
|
|
const VerticalDivider = styled.div`
|
|
|
|
width: ${remcalc(1)};
|
|
|
|
background: ${props => props.theme.grey};
|
|
|
|
height: ${remcalc(24)};
|
|
|
|
display: flex;
|
|
|
|
align-self: flex-end;
|
|
|
|
margin: 0 ${remcalc(18)};
|
|
|
|
|
|
|
|
@media (max-width: ${remcalc(767)}) {
|
|
|
|
display: none;
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
|
|
|
const ClipboardIconActionable = styled(ClipboardIcon)`
|
|
|
|
cursor: pointer;
|
|
|
|
`;
|
|
|
|
|
|
|
|
export class CopyToClipboardTooltip extends Component {
|
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
|
|
|
|
this.state = {
|
|
|
|
copied: false
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
handleClick = () => {
|
|
|
|
const { children: text } = this.props;
|
|
|
|
|
|
|
|
copy(text);
|
|
|
|
|
|
|
|
this.setState(
|
|
|
|
{
|
|
|
|
copied: true
|
|
|
|
},
|
|
|
|
() => {
|
|
|
|
setTimeout(() => {
|
|
|
|
this.setState({
|
|
|
|
copied: false
|
|
|
|
});
|
|
|
|
}, 4000);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
render = () => (
|
|
|
|
<TooltipContainer hoverable>
|
|
|
|
<TooltipTarget>
|
|
|
|
<ClipboardIconActionable onClick={this.handleClick} />
|
|
|
|
</TooltipTarget>
|
|
|
|
<Tooltip placement="top" success={Boolean(this.state.copied)}>
|
|
|
|
{this.state.copied ? 'Copied To Clipboard' : 'Copy To Clipboard'}
|
|
|
|
</Tooltip>
|
|
|
|
</TooltipContainer>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
export const CopiableField = ({ label, text, ...rest }) => (
|
|
|
|
<Row>
|
|
|
|
<Col xs={12} md={7}>
|
|
|
|
<FormLabel>{label}</FormLabel>
|
|
|
|
<InputIconWrapper {...rest}>
|
|
|
|
<Input fluid value={text} />
|
|
|
|
<CopyToClipboardTooltip>{text}</CopyToClipboardTooltip>
|
|
|
|
</InputIconWrapper>
|
|
|
|
</Col>
|
|
|
|
</Row>
|
|
|
|
);
|
|
|
|
|
|
|
|
export const Meta = ({
|
|
|
|
created,
|
|
|
|
updated,
|
|
|
|
state,
|
|
|
|
brand,
|
|
|
|
image,
|
|
|
|
...instance
|
|
|
|
}) => [
|
|
|
|
<Row middle="xs">
|
|
|
|
<Col xs={12}>
|
|
|
|
<H2>{(instance.package || {}).name}</H2>
|
|
|
|
</Col>
|
|
|
|
</Row>,
|
|
|
|
<Margin top={2} bottom={3}>
|
|
|
|
<Flex>
|
|
|
|
<Label>{image ? titleCase(image.name) : 'Custom Image'}</Label>
|
|
|
|
<VerticalDivider />
|
|
|
|
<Label>
|
|
|
|
{brand === 'LX'
|
|
|
|
? 'Infrastructure Container'
|
|
|
|
: 'Hardware Virtual Machine'}
|
|
|
|
</Label>
|
|
|
|
<VerticalDivider />
|
|
|
|
<Flex>
|
|
|
|
<DotIcon
|
|
|
|
borderRadius="50%"
|
|
|
|
marginRight={remcalc(6)}
|
|
|
|
marginTop={remcalc(1)}
|
|
|
|
width={remcalc(11)}
|
|
|
|
height={remcalc(11)}
|
|
|
|
color={stateColor[state]}
|
|
|
|
/>
|
|
|
|
{titleCase(state)}
|
|
|
|
</Flex>
|
|
|
|
</Flex>
|
|
|
|
<Margin top={1}>
|
|
|
|
<Flex>
|
|
|
|
<Flex>
|
|
|
|
<GreyLabel>Created: </GreyLabel>
|
|
|
|
<Label> {distanceInWordsToNow(created)} ago</Label>
|
|
|
|
</Flex>
|
|
|
|
<VerticalDivider />
|
|
|
|
<Flex>
|
|
|
|
<GreyLabel>Updated: </GreyLabel>
|
|
|
|
<Label> {distanceInWordsToNow(updated)} ago</Label>
|
|
|
|
</Flex>
|
|
|
|
</Flex>
|
|
|
|
</Margin>
|
|
|
|
</Margin>
|
|
|
|
];
|
|
|
|
|
|
|
|
export default withTheme(
|
|
|
|
({
|
|
|
|
instance = {},
|
|
|
|
starting = false,
|
|
|
|
stopping = false,
|
|
|
|
rebooting = false,
|
2018-01-04 13:05:22 +02:00
|
|
|
removing = false,
|
2017-12-21 02:12:42 +02:00
|
|
|
onAction,
|
|
|
|
theme = {}
|
|
|
|
}) => (
|
|
|
|
<Row>
|
|
|
|
<Col xs={12} sm={12} md={9}>
|
|
|
|
<Card>
|
|
|
|
<CardOutlet big>
|
|
|
|
<Meta {...instance} />
|
|
|
|
<Row between="xs">
|
|
|
|
<Col xs={9}>
|
|
|
|
<SmallOnly>
|
|
|
|
<Button
|
|
|
|
type="button"
|
|
|
|
loading={starting}
|
2018-01-04 13:05:22 +02:00
|
|
|
disabled={instance.state !== 'STOPPED'}
|
2017-12-21 02:12:42 +02:00
|
|
|
onClick={() => onAction('start')}
|
|
|
|
secondary
|
2018-01-04 13:05:22 +02:00
|
|
|
small
|
2017-12-21 02:12:42 +02:00
|
|
|
icon
|
|
|
|
>
|
2018-01-04 13:05:22 +02:00
|
|
|
<StartIcon disabled={instance.state !== 'STOPPED'} />
|
2017-12-21 02:12:42 +02:00
|
|
|
</Button>
|
|
|
|
</SmallOnly>
|
|
|
|
<Medium>
|
|
|
|
<Button
|
|
|
|
type="button"
|
|
|
|
loading={starting}
|
2018-01-04 13:05:22 +02:00
|
|
|
disabled={instance.state !== 'STOPPED'}
|
2017-12-21 02:12:42 +02:00
|
|
|
onClick={() => onAction('start')}
|
|
|
|
secondary
|
|
|
|
bold
|
|
|
|
icon
|
|
|
|
>
|
2018-01-04 13:05:22 +02:00
|
|
|
<StartIcon disabled={instance.state !== 'STOPPED'} />
|
2017-12-21 02:12:42 +02:00
|
|
|
<span>Start</span>
|
|
|
|
</Button>
|
|
|
|
</Medium>
|
|
|
|
<SmallOnly>
|
|
|
|
<Button
|
|
|
|
type="button"
|
|
|
|
loading={stopping}
|
2018-01-04 13:05:22 +02:00
|
|
|
disabled={instance.state !== 'RUNNING'}
|
2017-12-21 02:12:42 +02:00
|
|
|
onClick={() => onAction('stop')}
|
|
|
|
secondary
|
2018-01-04 13:05:22 +02:00
|
|
|
small
|
2017-12-21 02:12:42 +02:00
|
|
|
icon
|
|
|
|
>
|
2018-01-04 13:05:22 +02:00
|
|
|
<StopIcon disabled={instance.state !== 'RUNNING'} />
|
2017-12-21 02:12:42 +02:00
|
|
|
</Button>
|
|
|
|
</SmallOnly>
|
|
|
|
<Medium>
|
|
|
|
<Button
|
|
|
|
type="button"
|
|
|
|
loading={stopping}
|
2018-01-04 13:05:22 +02:00
|
|
|
disabled={instance.state !== 'RUNNING'}
|
2017-12-21 02:12:42 +02:00
|
|
|
onClick={() => onAction('stop')}
|
|
|
|
secondary
|
|
|
|
bold
|
|
|
|
icon
|
|
|
|
>
|
2018-01-04 13:05:22 +02:00
|
|
|
<StopIcon disabled={instance.state !== 'RUNNING'} />
|
2017-12-21 02:12:42 +02:00
|
|
|
<span>Stop</span>
|
|
|
|
</Button>
|
|
|
|
</Medium>
|
|
|
|
<SmallOnly>
|
|
|
|
<Button
|
|
|
|
type="button"
|
|
|
|
loading={rebooting}
|
2018-01-04 13:05:22 +02:00
|
|
|
disabled={instance.state !== 'RUNNING'}
|
2017-12-21 02:12:42 +02:00
|
|
|
onClick={() => onAction('reboot')}
|
|
|
|
secondary
|
2018-01-04 13:05:22 +02:00
|
|
|
small
|
2017-12-21 02:12:42 +02:00
|
|
|
icon
|
|
|
|
>
|
2018-01-04 13:05:22 +02:00
|
|
|
<ResetIcon disabled={instance.state !== 'RUNNING'} />
|
2017-12-21 02:12:42 +02:00
|
|
|
</Button>
|
|
|
|
</SmallOnly>
|
|
|
|
<Medium>
|
|
|
|
<Button
|
|
|
|
type="button"
|
|
|
|
loading={rebooting}
|
2018-01-04 13:05:22 +02:00
|
|
|
disabled={instance.state !== 'RUNNING'}
|
2017-12-21 02:12:42 +02:00
|
|
|
onClick={() => onAction('reboot')}
|
|
|
|
secondary
|
|
|
|
bold
|
|
|
|
icon
|
|
|
|
>
|
2018-01-04 13:05:22 +02:00
|
|
|
<ResetIcon disabled={instance.state !== 'RUNNING'} />
|
2017-12-21 02:12:42 +02:00
|
|
|
<span>Reboot</span>
|
|
|
|
</Button>
|
|
|
|
</Medium>
|
|
|
|
</Col>
|
|
|
|
<Col xs={3}>
|
|
|
|
<SmallOnly>
|
|
|
|
<Button
|
|
|
|
type="button"
|
2018-01-04 13:05:22 +02:00
|
|
|
loading={removing}
|
|
|
|
disabled={
|
|
|
|
['RUNNING', 'STOPPED'].indexOf(instance.state) < 0
|
|
|
|
}
|
2018-01-03 17:59:29 +02:00
|
|
|
onClick={() => onAction('remove')}
|
2017-12-21 02:12:42 +02:00
|
|
|
secondary
|
2018-01-04 13:05:22 +02:00
|
|
|
small
|
2017-12-21 02:12:42 +02:00
|
|
|
right
|
|
|
|
icon
|
|
|
|
error
|
|
|
|
>
|
|
|
|
<DeleteIcon
|
|
|
|
fill={theme.red}
|
2018-01-04 13:05:22 +02:00
|
|
|
disabled={
|
|
|
|
['RUNNING', 'STOPPED'].indexOf(instance.state) < 0
|
|
|
|
}
|
2017-12-21 02:12:42 +02:00
|
|
|
/>
|
|
|
|
</Button>
|
|
|
|
</SmallOnly>
|
|
|
|
<Medium>
|
|
|
|
<Button
|
|
|
|
type="button"
|
2018-01-04 13:05:22 +02:00
|
|
|
loading={removing}
|
|
|
|
disabled={
|
|
|
|
['RUNNING', 'STOPPED'].indexOf(instance.state) < 0
|
|
|
|
}
|
2018-01-03 17:59:29 +02:00
|
|
|
onClick={() => onAction('remove')}
|
2017-12-21 02:12:42 +02:00
|
|
|
secondary
|
|
|
|
bold
|
|
|
|
right
|
|
|
|
icon
|
|
|
|
error
|
|
|
|
>
|
|
|
|
<DeleteIcon
|
2018-01-04 13:05:22 +02:00
|
|
|
fill={
|
|
|
|
['RUNNING', 'STOPPED'].indexOf(instance.state) >= 0
|
|
|
|
? theme.red
|
|
|
|
: undefined
|
|
|
|
}
|
|
|
|
disabled={
|
|
|
|
['RUNNING', 'STOPPED'].indexOf(instance.state) < 0
|
|
|
|
}
|
2017-12-21 02:12:42 +02:00
|
|
|
/>
|
|
|
|
<span>Remove</span>
|
|
|
|
</Button>
|
|
|
|
</Medium>
|
|
|
|
</Col>
|
|
|
|
</Row>
|
|
|
|
<Margin bottom={5} top={4}>
|
|
|
|
<Divider height={remcalc(1)} />
|
|
|
|
</Margin>
|
|
|
|
<CopiableField
|
|
|
|
text={(instance.id || '').split('-')[0]}
|
|
|
|
label="Short ID"
|
|
|
|
/>
|
|
|
|
<CopiableField text={instance.id} label="ID" />
|
|
|
|
<CopiableField text={instance.compute_node} label="CN UUID" />
|
|
|
|
{instance.image && (
|
|
|
|
<CopiableField text={instance.image.id} label="Image UUID" />
|
|
|
|
)}
|
|
|
|
<CopiableField
|
|
|
|
text={`ssh root@${instance.primary_ip}`}
|
|
|
|
label="Login"
|
|
|
|
/>
|
|
|
|
{(instance.ips || []).map((ip, i) => (
|
|
|
|
<CopiableField
|
|
|
|
key={i}
|
|
|
|
noMargin={i === instance.ips.length - 1}
|
|
|
|
text={ip}
|
|
|
|
label={`IP address ${i + 1}`}
|
|
|
|
/>
|
|
|
|
))}
|
|
|
|
</CardOutlet>
|
|
|
|
</Card>
|
|
|
|
</Col>
|
|
|
|
</Row>
|
|
|
|
)
|
|
|
|
);
|