feat(my-joy-beta): create instance firewall

fixes #986
This commit is contained in:
Sérgio Ramos 2018-01-09 15:59:04 +00:00 committed by Sérgio Ramos
parent 069e56f921
commit 54d4b61250
8 changed files with 349 additions and 40 deletions

View File

@ -782,6 +782,7 @@ exports[`renders <Hostname values /> without throwing 1`] = `
Instance name Instance name
hostname hostname
s
</small> </small>
<div <div
className="c4" className="c4"

View File

@ -75,7 +75,7 @@ export const Hostname = ({ values, network, service, ...hostname }) => (
: network : network
? 'Network' ? 'Network'
: service ? 'CNS service' : 'Instance name'}{' '} : service ? 'CNS service' : 'Instance name'}{' '}
hostname hostname{values.length === 1 ? '' : 's'}
</SmallBordered> </SmallBordered>
<FlexItem> <FlexItem>
<Margin right={1}> <Margin right={1}>

View File

@ -0,0 +1,161 @@
import React, { Fragment } from 'react';
import { Field } from 'redux-form';
import { Margin, Padding } from 'styled-components-spacing';
import Flex, { FlexItem } from 'styled-flex-component';
import styled from 'styled-components';
import remcalc from 'remcalc';
import constantCase from 'constant-case';
import {
NameIcon,
H3,
P,
FormGroup,
FormLabel,
Input,
FormMeta,
Button,
Toggle,
Card,
CardOutlet,
Divider,
Row,
Col,
TagList
} from 'joyent-ui-toolkit';
import Tag from '@components/instances/tags';
import Title from './title';
const Box = styled.div`
display: inline-block;
background-color: ${props => props.theme.white};
border: ${remcalc(1)} solid ${props => props.theme.grey};
min-width: 100%;
`;
const Wildcards = {
vmall: 'All VMs in DC',
any: 'Any'
};
const parsePartial = (p, index) => {
if (p[0] === 'wildcard') {
return <span key={index}>{Wildcards[p[1]]}</span>;
}
if (p[0] === 'tag') {
const value = Array.isArray(p[1]) ? p[1][1] : '';
const name = Array.isArray(p[1]) ? p[1][0] : p[1];
return <Tag key={index} name={name} value={value} />;
}
}
const Rule = ({ enabled, rule_obj }) => {
const { action, protocol } = rule_obj;
const froms = rule_obj.from.map(parsePartial);
const tos = rule_obj.to.map(parsePartial);
return (
<Box disabled={!enabled}>
<Padding left={3} right={3} top={2} bottom={2}>
<Row>
<Col xs={3}>
<Flex justifyStart alignCenter contentStretch>
<FlexItem>
<b>From:{' '}</b>
</FlexItem>
<FlexItem grow={1}><TagList>{froms}</TagList></FlexItem>
</Flex>
</Col>
<Col xs={3}>
<Flex justifyStart alignCenter contentStretch>
<FlexItem>
<b>To:{' '}</b>
</FlexItem>
<FlexItem grow={1}><TagList>{tos}</TagList></FlexItem>
</Flex>
</Col>
<Col xs={2}>
<Flex justifyStart alignCenter contentStretch>
<FlexItem>
<b>Protocol:{' '}</b>
</FlexItem>
<FlexItem grow={1}>{protocol.name}</FlexItem>
</Flex>
</Col>
<Col xs={2}>
<Flex justifyStart alignCenter contentStretch>
<FlexItem>
<b>Ports:{' '}</b>
</FlexItem>
<FlexItem grow={1}>{protocol.targets.join(';')}</FlexItem>
</Flex>
</Col>
<Col xs={2}>
<Flex justifyStart alignCenter contentStretch>
<FlexItem>
<b>Action:{' '}</b>
</FlexItem>
<FlexItem grow={1}>{constantCase(action)}</FlexItem>
</Flex>
</Col>
</Row>
</Padding>
</Box>
);
};
export default ({
defaultRules = [],
tagRules = [],
enabled = false,
handleSubmit
}) => (
<form onSubmit={handleSubmit}>
<Margin bottom={4}>
<FormGroup name="enabled" field={Field}>
<Toggle>Enable firewall rules</Toggle>
</FormGroup>
{enabled ? (
<FormGroup name="show-inactive" field={Field}>
<Toggle>Show inactive rules</Toggle>
</FormGroup>
) : null}
</Margin>
{enabled && defaultRules.length ? (
<Fragment>
<H3>Default firewall rules</H3>
<span /> {/* trick H3 margin sibling rule */}
<Margin top={3}>
{defaultRules.map(rule => (
<Margin bottom={2}>
<Rule key={rule.id} {...rule} />
</Margin>
))}
</Margin>
</Fragment>
) : null}
{enabled && tagRules.length && defaultRules.length ? (
<Divider height={remcalc(18)} transparent />
) : null}
{enabled && tagRules.length ? (
<Fragment>
<H3>Firewall rules from instance tags</H3>
<span /> {/* trick H3 margin sibling rule */}
<Margin top={3}>
{tagRules.map(rule => (
<Margin bottom={2}>
<Rule key={rule.id} {...rule} />
</Margin>
))}
</Margin>
</Fragment>
) : null}
{enabled && (tagRules.length || defaultRules.length) ? (
<Divider height={remcalc(12)} transparent />
) : null}
</form>
);

View File

@ -163,7 +163,6 @@ Array [
/> />
</div>, </div>,
<form />, <form />,
<div />,
] ]
`; `;
@ -329,7 +328,6 @@ Array [
height="0.0625rem" height="0.0625rem"
/> />
</div>, </div>,
<div />,
] ]
`; `;
@ -754,16 +752,14 @@ Array [
pointer-events: none; pointer-events: none;
} }
<div> <button
<button
className="c0 c1 c2" className="c0 c1 c2"
href="" href=""
onClick={undefined} onClick={undefined}
type="button" type="button"
> >
Next Next
</button> </button>,
</div>,
] ]
`; `;
@ -1226,7 +1222,6 @@ Array [
</div> </div>
</div> </div>
</form>, </form>,
<div />,
] ]
`; `;
@ -2746,16 +2741,14 @@ Array [
pointer-events: none; pointer-events: none;
} }
<div> <button
<button
className="c0 c1 c2" className="c0 c1 c2"
href="" href=""
onClick={undefined} onClick={undefined}
type="button" type="button"
> >
Next Next
</button> </button>,
</div>,
] ]
`; `;
@ -3370,15 +3363,13 @@ Array [
border-color: rgb(216,216,216); border-color: rgb(216,216,216);
} }
<div> <button
<button
className="c0 c1 c2" className="c0 c1 c2"
href="" href=""
onClick={undefined} onClick={undefined}
type="button" type="button"
> >
Edit Edit
</button> </button>,
</div>,
] ]
`; `;

View File

@ -0,0 +1,142 @@
import React, { Fragment } from 'react';
import { compose, graphql } from 'react-apollo';
import { Margin } from 'styled-components-spacing';
import ReduxForm from 'declarative-redux-form';
import { set } from 'react-redux-values';
import { connect } from 'react-redux';
import get from 'lodash.get';
import find from 'lodash.find';
import forceArray from 'force-array';
import Title from '@components/create-instance/title';
import FirewallForm from '@components/create-instance/firewall';
import ListFwRules from '@graphql/list-fw-rules.gql';
import { StatusLoader, FirewallIcon, P, H3, Button } from 'joyent-ui-toolkit';
const FORM_NAME = 'CREATE-INSTANCE-FIREWALL';
const Firewall = ({
defaultRules = [],
tagRules = [],
expanded = false,
proceeded = false,
loading = false,
enabled = false,
handleNext,
handleEdit
}) => (
<Fragment>
<Title icon={<FirewallIcon />}>Firewall</Title>
{expanded ? (
<Margin bottom={3}>
<P>
Cloud Firewall rules control traffic across instances. Enabling the
firewall adds a default set of rules and rules defined by your chosen
tags.{' '}
<a
target="__blank"
href="https://docs.joyent.com/public-cloud/network/firewall"
>
Read more
</a>
</P>
</Margin>
) : null}
{loading && expanded ? <StatusLoader /> : null}
{!loading ? (
<ReduxForm
form={FORM_NAME}
destroyOnUnmount={false}
forceUnregisterOnUnmount={true}
>
{props =>
expanded ? (
<FirewallForm
{...props}
defaultRules={defaultRules}
tagRules={tagRules}
enabled={enabled}
/>
) : null
}
</ReduxForm>
) : null}
{proceeded && !expanded ? (
<Margin bottom={4}>
<H3>{enabled ? 'Firewall Enabled' : 'Firewall Not Enabled'}</H3>
</Margin>
) : null}
<Fragment>
{expanded ? (
<Button type="button" onClick={handleNext}>
Next
</Button>
) : proceeded ? (
<Button type="button" onClick={handleEdit} secondary>
Edit
</Button>
) : null}
</Fragment>
</Fragment>
);
export default compose(
connect(
({ form, values }, ownProps) => ({
...ownProps,
enabled: get(form, `${FORM_NAME}.values.enabled`, false),
showInactive: get(form, `${FORM_NAME}.values.show-inactive`, false),
tags: get(values, 'create-instance-tags', [])
}),
(dispatch, { history }) => ({
handleNext: () => {
dispatch(
set({ name: 'create-instance-firewall-proceeded', value: true })
);
return history.push('/instances/~create/cns');
},
handleEdit: () => {
return history.push('/instances/~create/firewall');
}
})
),
graphql(ListFwRules, {
options: ({ tags, expanded, enabled }) => ({
fetchPolicy: expanded && enabled ? 'cache-first' : 'cache-only',
variables: {
tags: tags.map(({ name, value }) => ({ name, value }))
}
}),
props: ({ ownProps, data }) => {
const { enabled, showInactive, tags = [] } = ownProps;
const {
firewall_rules_create_machine = [],
loading,
error,
refetch
} = data;
const rules = forceArray(firewall_rules_create_machine)
.filter(({ enabled }) => enabled || showInactive)
.map(({ rule_obj, ...rule }) => ({
...rule,
rule_obj: {
...rule_obj,
from: forceArray(rule_obj.from).map(f => forceArray(f)),
to: forceArray(rule_obj.to).map(t => forceArray(t))
}
}));
return {
defaultRules: rules.filter(({ tag }) => !tag),
tagRules: rules.filter(({ tag }) => tag),
loading,
error,
refetch
};
}
})
)(Firewall);

View File

@ -6,12 +6,13 @@ import { ViewContainer, H2, Button, Divider } from 'joyent-ui-toolkit';
import Name from '@containers/create-instance/name'; import Name from '@containers/create-instance/name';
import Image from '@containers/create-instance/image'; import Image from '@containers/create-instance/image';
import Metadata from '@containers/create-instance/metadata';
import Tags from '@containers/create-instance/tags';
import Package from '@containers/create-instance/package'; import Package from '@containers/create-instance/package';
import Tags from '@containers/create-instance/tags';
import Metadata from '@containers/create-instance/metadata';
import Networks from '@containers/create-instance/networks'; import Networks from '@containers/create-instance/networks';
import Affinity from '@containers/create-instance/affinity'; import Firewall from '@containers/create-instance/firewall';
import CNS from '@containers/create-instance/cns'; import CNS from '@containers/create-instance/cns';
import Affinity from '@containers/create-instance/affinity';
export default ({ step, ...props }) => ( export default ({ step, ...props }) => (
<ViewContainer> <ViewContainer>
@ -36,6 +37,9 @@ export default ({ step, ...props }) => (
<Margin bottom={4}> <Margin bottom={4}>
<Networks {...props} expanded={step === 'networks'} /> <Networks {...props} expanded={step === 'networks'} />
</Margin> </Margin>
<Margin bottom={5}>
<Firewall {...props} expanded={step === 'firewall'} />
</Margin>
<Margin bottom={4}> <Margin bottom={4}>
<CNS {...props} expanded={step === 'cns'} /> <CNS {...props} expanded={step === 'cns'} />
</Margin> </Margin>

View File

@ -78,7 +78,7 @@ export const Networks = ({
)} )}
</ReduxForm> </ReduxForm>
) : null} ) : null}
<div> <Fragment>
{expanded ? ( {expanded ? (
<Button type="button" onClick={handleNext}> <Button type="button" onClick={handleNext}>
Next Next
@ -88,7 +88,7 @@ export const Networks = ({
Edit Edit
</Button> </Button>
) : null} ) : null}
</div> </Fragment>
</Fragment> </Fragment>
); );
}; };
@ -130,7 +130,7 @@ export default compose(
set({ name: 'create-instance-networks-proceeded', value: true }) set({ name: 'create-instance-networks-proceeded', value: true })
); );
return history.push('/instances/~create/cns'); return history.push('/instances/~create/firewall');
}, },
handleEdit: () => { handleEdit: () => {
return history.push('/instances/~create/networks'); return history.push('/instances/~create/networks');

View File

@ -0,0 +1,10 @@
query rules($tags: [KeyValueInput]!) {
firewall_rules_create_machine(tags: $tags) {
id
enabled
rule_obj
rule_str
global
tag
}
}