Highlight assigned networks and roles on hover
When a network is hovered, all its connections to roles are highlighted. Implements: blueprint networks-crud-ui Change-Id: I221050cfe06658c0f5193adf64431690bdd0274b
This commit is contained in:
parent
2006759d61
commit
3691bab67d
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
features:
|
||||
- |
|
||||
Added a network topology view accessible under the Network Configuration
|
||||
step in Deployment plan overview page. The Network topology diagram
|
||||
represents Deployment roles, networks and draws connections based on
|
||||
roles-networks assignment. The Diagram also shows high level information
|
||||
about each network. When you hover over a network, all its connections are
|
||||
highlighted for easy identification of assigned Roles.
|
|
@ -14,11 +14,13 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import cx from 'classnames';
|
||||
import { startCase } from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Fragment } from 'react';
|
||||
|
||||
import { getNetworkColorStyle } from './utils';
|
||||
import { NetworksHighlightContext } from './NetworksHighlighter';
|
||||
|
||||
const NetworkListItem = ({
|
||||
className,
|
||||
|
@ -32,15 +34,35 @@ const NetworkListItem = ({
|
|||
disabled ? 'disabled' : name
|
||||
);
|
||||
return (
|
||||
<Fragment>
|
||||
<div ref={lineRef} className="network-line" style={{ borderColor }} />
|
||||
<div className="network" style={{ backgroundColor }}>
|
||||
<h5>
|
||||
<strong>{startCase(name)}</strong>
|
||||
</h5>
|
||||
{children}
|
||||
</div>
|
||||
</Fragment>
|
||||
<NetworksHighlightContext.Consumer>
|
||||
{({ highlightedNetworks, setHighlightedNetworks }) => (
|
||||
<Fragment>
|
||||
<div
|
||||
className={cx('network-line', {
|
||||
highlighted: highlightedNetworks.includes(name)
|
||||
})}
|
||||
>
|
||||
<div className="network-line-ends" style={{ borderColor }} />
|
||||
<div
|
||||
ref={lineRef}
|
||||
className="network-line-edge"
|
||||
style={{ borderColor }}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="network"
|
||||
style={{ backgroundColor }}
|
||||
onMouseEnter={() => setHighlightedNetworks([name])}
|
||||
onMouseLeave={() => setHighlightedNetworks([])}
|
||||
>
|
||||
<h5>
|
||||
<strong>{startCase(name)}</strong>
|
||||
</h5>
|
||||
{children}
|
||||
</div>
|
||||
</Fragment>
|
||||
)}
|
||||
</NetworksHighlightContext.Consumer>
|
||||
);
|
||||
};
|
||||
NetworkListItem.propTypes = {
|
||||
|
|
|
@ -30,6 +30,7 @@ import {
|
|||
} from '../../selectors/networks';
|
||||
import { getParameters } from '../../selectors/parameters';
|
||||
import { getRoles } from '../../selectors/roles';
|
||||
import NetworksHighlighter from './NetworksHighlighter';
|
||||
import RolesList from './RolesList';
|
||||
import NetworksList from './NetworksList';
|
||||
|
||||
|
@ -50,27 +51,39 @@ const messages = defineMessages({
|
|||
defaultMessage: 'Enable network isolation'
|
||||
}
|
||||
});
|
||||
class NetworkTopology extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.networkLineElements = {};
|
||||
this.state = { networkLinePositions: {} };
|
||||
|
||||
this.calculateNetworkPositions = debounce(
|
||||
this.calculateNetworkPositions,
|
||||
class NetworkTopology extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.networkLineElements = {};
|
||||
this.roleNetworkLineElements = {};
|
||||
this.state = { networkLineHeights: {} };
|
||||
|
||||
this.calculateNetworkLineHeights = debounce(
|
||||
this.calculateNetworkLineHeights,
|
||||
100
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.calculateNetworkPositions();
|
||||
this.calculateNetworkLineHeights();
|
||||
}
|
||||
|
||||
calculateNetworkPositions = () =>
|
||||
Object.keys(this.networkLineElements).map(key => {
|
||||
const rect = this.networkLineElements[key].getBoundingClientRect();
|
||||
this.setState(state => (state.networkLinePositions[key] = rect.y));
|
||||
});
|
||||
calculateNetworkLineHeights = () => {
|
||||
const resultObject = Object.assign({}, this.roleNetworkLineElements);
|
||||
Object.keys(this.roleNetworkLineElements).forEach(role =>
|
||||
Object.keys(this.roleNetworkLineElements[role]).forEach(network => {
|
||||
const { y, height } = this.roleNetworkLineElements[role][
|
||||
network
|
||||
].getBoundingClientRect();
|
||||
const start = y + height;
|
||||
const end = this.networkLineElements[network].getBoundingClientRect().y;
|
||||
resultObject[role][network] = end - start;
|
||||
})
|
||||
);
|
||||
|
||||
this.setState({ networkLineHeights: resultObject });
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
|
@ -81,7 +94,7 @@ class NetworkTopology extends Component {
|
|||
parameters
|
||||
} = this.props;
|
||||
return (
|
||||
<div className="flex-container">
|
||||
<NetworksHighlighter>
|
||||
{networkResourceExistsByNetwork.includes(false) && (
|
||||
<Alert type="info">
|
||||
<strong>
|
||||
|
@ -104,7 +117,8 @@ class NetworkTopology extends Component {
|
|||
<div className="network-topology">
|
||||
<RolesList
|
||||
roles={roles}
|
||||
networkLinePositions={this.state.networkLinePositions}
|
||||
networkLineHeights={this.state.networkLineHeights}
|
||||
roleNetworkLineElements={this.roleNetworkLineElements}
|
||||
/>
|
||||
<NetworksList
|
||||
networks={networks}
|
||||
|
@ -113,7 +127,7 @@ class NetworkTopology extends Component {
|
|||
networkLineElements={this.networkLineElements}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</NetworksHighlighter>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* Copyright 2018 Red Hat Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License. You may obtain
|
||||
* a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component, createContext } from 'react';
|
||||
|
||||
export const NetworksHighlightContext = createContext({
|
||||
highlightedNetworks: [],
|
||||
setHighlightedNetworks: () => {}
|
||||
});
|
||||
|
||||
export default class NetworksHighlighter extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.setHighlightedNetworks = highlightedNetworks =>
|
||||
this.setState({ highlightedNetworks });
|
||||
|
||||
this.state = {
|
||||
highlightedNetworks: [],
|
||||
setHighlightedNetworks: this.setHighlightedNetworks
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<NetworksHighlightContext.Provider value={this.state}>
|
||||
{this.props.children}
|
||||
</NetworksHighlightContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
NetworksHighlighter.propTypes = {
|
||||
children: PropTypes.node
|
||||
};
|
|
@ -28,6 +28,7 @@ const NetworksList = ({
|
|||
}) => {
|
||||
const ctlPlaneDefaultRoute = parameters.get('ControlPlaneDefaultRoute');
|
||||
const ctlPlaneSubnetCidr = parameters.get('ControlPlaneSubnetCidr');
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<NetworkListItem
|
||||
|
|
|
@ -17,15 +17,20 @@
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { set } from 'lodash';
|
||||
|
||||
import RoleNetworkLine from './RoleNetworkLine';
|
||||
|
||||
export class RoleCard extends Component {
|
||||
render() {
|
||||
const { role: { name, networks }, networkLinePositions } = this.props;
|
||||
const {
|
||||
networkLineHeights,
|
||||
role: { name, networks },
|
||||
roleNetworkLineElements
|
||||
} = this.props;
|
||||
const allNetworks = networks.unshift('Provisioning');
|
||||
return (
|
||||
<div className="card-pf card-pf-view card-pf-view-select role-card">
|
||||
<div className="card-pf card-pf-view role-card">
|
||||
<div className="card-pf-body">
|
||||
<h2 className="card-pf-title">{name}</h2>
|
||||
</div>
|
||||
|
@ -33,9 +38,12 @@ export class RoleCard extends Component {
|
|||
<ul className="role-networks-list list-unstyled">
|
||||
{allNetworks.map(network => (
|
||||
<RoleNetworkLine
|
||||
lineRef={el =>
|
||||
set(roleNetworkLineElements, [name, network], el)
|
||||
}
|
||||
key={network}
|
||||
networkName={network}
|
||||
networkLinePosition={networkLinePositions[network]}
|
||||
networkLineHeight={networkLineHeights[network]}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
|
@ -45,8 +53,12 @@ export class RoleCard extends Component {
|
|||
}
|
||||
}
|
||||
RoleCard.propTypes = {
|
||||
networkLinePositions: PropTypes.object,
|
||||
role: ImmutablePropTypes.record.isRequired
|
||||
networkLineHeights: PropTypes.object.isRequired,
|
||||
role: ImmutablePropTypes.record.isRequired,
|
||||
roleNetworkLineElements: PropTypes.object.isRequired
|
||||
};
|
||||
RoleCard.defaultProps = {
|
||||
networkLineHeights: {}
|
||||
};
|
||||
|
||||
export default RoleCard;
|
||||
|
|
|
@ -21,63 +21,67 @@ import React from 'react';
|
|||
|
||||
import { getNetworkColorStyle } from './utils';
|
||||
import { getNetworkResourceExistsByNetwork } from '../../selectors/networks';
|
||||
import { NetworksHighlightContext } from './NetworksHighlighter';
|
||||
|
||||
class RoleNetworkLine extends React.Component {
|
||||
getStartingPoint = nicElement => {
|
||||
const rect = nicElement.getBoundingClientRect();
|
||||
return rect.y + rect.height;
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
disabled,
|
||||
networkName,
|
||||
networkLinePosition
|
||||
} = this.props;
|
||||
const { backgroundColor, borderColor } = getNetworkColorStyle(
|
||||
disabled ? 'disabled' : networkName
|
||||
);
|
||||
return (
|
||||
<li className={cx('role-nic', className)} ref={el => (this.element = el)}>
|
||||
<div
|
||||
className="role-network-line"
|
||||
style={{
|
||||
transform: networkLinePosition && 'rotateX(0deg)',
|
||||
height:
|
||||
networkLinePosition &&
|
||||
networkLinePosition - this.getStartingPoint(this.element),
|
||||
bottom:
|
||||
networkLinePosition &&
|
||||
-(networkLinePosition - this.getStartingPoint(this.element)),
|
||||
opacity: networkLinePosition && 1,
|
||||
backgroundColor,
|
||||
borderColor
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className="role-network-point"
|
||||
style={{
|
||||
bottom:
|
||||
networkLinePosition &&
|
||||
-(networkLinePosition - this.getStartingPoint(this.element) + 5),
|
||||
opacity: networkLinePosition && 1,
|
||||
backgroundColor,
|
||||
borderColor
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
<NetworksHighlightContext.Consumer>
|
||||
{({ highlightedNetworks, setHighlightedNetworks }) => {
|
||||
const {
|
||||
className,
|
||||
disabled,
|
||||
lineRef,
|
||||
networkName,
|
||||
networkLineHeight
|
||||
} = this.props;
|
||||
|
||||
const highlighted = highlightedNetworks.includes(networkName);
|
||||
|
||||
const { backgroundColor, borderColor } = getNetworkColorStyle(
|
||||
disabled ? 'disabled' : networkName
|
||||
);
|
||||
|
||||
return (
|
||||
<li className={cx('role-nic', className)} ref={lineRef}>
|
||||
<div
|
||||
style={{
|
||||
height: networkLineHeight,
|
||||
bottom: -networkLineHeight,
|
||||
backgroundColor,
|
||||
borderColor
|
||||
}}
|
||||
className={cx('role-network-line', {
|
||||
highlighted,
|
||||
in: networkLineHeight > 0
|
||||
})}
|
||||
/>
|
||||
<div
|
||||
className={cx('role-network-point', { highlighted })}
|
||||
style={{
|
||||
bottom: -networkLineHeight - 5,
|
||||
opacity: 1,
|
||||
backgroundColor,
|
||||
borderColor
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
}}
|
||||
</NetworksHighlightContext.Consumer>
|
||||
);
|
||||
}
|
||||
}
|
||||
RoleNetworkLine.propTypes = {
|
||||
className: PropTypes.string,
|
||||
disabled: PropTypes.bool.isRequired,
|
||||
networkLinePosition: PropTypes.number,
|
||||
lineRef: PropTypes.func.isRequired,
|
||||
networkLineHeight: PropTypes.number.isRequired,
|
||||
networkName: PropTypes.string.isRequired
|
||||
};
|
||||
RoleNetworkLine.defaultProps = {
|
||||
disabled: false
|
||||
disabled: false,
|
||||
networkLineHeight: 0
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, { networkName }) => ({
|
||||
|
|
|
@ -22,7 +22,7 @@ import { splitListIntoChunks } from '../../utils/immutablejs';
|
|||
import RoleCard from './RoleCard';
|
||||
import { Role } from '../../immutableRecords/roles';
|
||||
|
||||
const RolesList = ({ roles, networkLinePositions }) => {
|
||||
const RolesList = ({ roles, networkLineHeights, roleNetworkLineElements }) => {
|
||||
const rolesChunks = splitListIntoChunks(roles.toList(), 2);
|
||||
return (
|
||||
<Fragment>
|
||||
|
@ -32,15 +32,17 @@ const RolesList = ({ roles, networkLinePositions }) => {
|
|||
.map(role => (
|
||||
<RoleCard
|
||||
key={role.name}
|
||||
networkLinePositions={networkLinePositions}
|
||||
networkLineHeights={networkLineHeights[role.name]}
|
||||
role={role}
|
||||
roleNetworkLineElements={roleNetworkLineElements}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="roles-list">
|
||||
<RoleCard
|
||||
networkLineHeights={networkLineHeights['Undercloud']}
|
||||
role={new Role({ name: 'Undercloud', identifier: 'undercloud' })}
|
||||
networkLinePositions={networkLinePositions}
|
||||
roleNetworkLineElements={roleNetworkLineElements}
|
||||
/>
|
||||
</div>
|
||||
<div className="roles-list">
|
||||
|
@ -49,8 +51,9 @@ const RolesList = ({ roles, networkLinePositions }) => {
|
|||
.map(role => (
|
||||
<RoleCard
|
||||
key={role.name}
|
||||
networkLinePositions={networkLinePositions}
|
||||
networkLineHeights={networkLineHeights[role.name]}
|
||||
role={role}
|
||||
roleNetworkLineElements={roleNetworkLineElements}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
@ -58,8 +61,12 @@ const RolesList = ({ roles, networkLinePositions }) => {
|
|||
);
|
||||
};
|
||||
RolesList.propTypes = {
|
||||
networkLinePositions: PropTypes.object.isRequired,
|
||||
networkLineHeights: PropTypes.object.isRequired,
|
||||
roleNetworkLineElements: PropTypes.object.isRequired,
|
||||
roles: ImmutablePropTypes.map.isRequired
|
||||
};
|
||||
RolesList.defaultProps = {
|
||||
networkLineHeights: {}
|
||||
};
|
||||
|
||||
export default RolesList;
|
||||
|
|
|
@ -63,9 +63,17 @@
|
|||
border-right: 1px solid;
|
||||
background-color: inherit;
|
||||
transform: rotateX(90deg);
|
||||
transform-origin: top right;
|
||||
transform-origin: top center;
|
||||
transition: transform .5s ease-in, opacity .3s ease-in;
|
||||
opacity: 0;
|
||||
&.in {
|
||||
transition: transform .1s ease-in;
|
||||
transform: rotateX(0deg);
|
||||
opacity: 1
|
||||
}
|
||||
&.highlighted {
|
||||
transform: scaleX(3);
|
||||
}
|
||||
}
|
||||
.role-network-point {
|
||||
position: absolute;
|
||||
|
@ -78,7 +86,10 @@
|
|||
border-radius: 5px;
|
||||
border: 1px solid;
|
||||
opacity: 0;
|
||||
transition: opacity .5s .3s ease-in;
|
||||
transition: transform .1s ease-in, opacity .5s .3s ease-in;
|
||||
&.highlighted {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -86,20 +97,36 @@
|
|||
.network-line {
|
||||
grid-column: ~"1 / 4";
|
||||
position: relative;
|
||||
border-top: 1px solid;
|
||||
margin: 20px 9px 0 9px;
|
||||
&:before, &:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid;
|
||||
border-color: inherit;
|
||||
.network-line-edge {
|
||||
transition: transform .1s ease-in;
|
||||
border-top: 1px solid;
|
||||
}
|
||||
.network-line-ends {
|
||||
&:before, &:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid;
|
||||
border-color: inherit;
|
||||
transition: transform .1s ease-in;
|
||||
}
|
||||
&:before { left: -9px; }
|
||||
&:after { right: -9px; }
|
||||
}
|
||||
&.highlighted {
|
||||
.network-line-edge {
|
||||
transform: scaleY(3);
|
||||
}
|
||||
.network-line-ends {
|
||||
&:before, &:after {
|
||||
transform: scale(1.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
&:before { left: -9px; }
|
||||
&:after { right: -9px; }
|
||||
}
|
||||
|
||||
.network {
|
||||
|
|
Loading…
Reference in New Issue