mirror of
https://github.com/weaveworks/scope.git
synced 2026-07-20 22:10:30 +00:00
Copy paste of MoC controls as initial networks-view legend
Needs a bit of de-dup / customization oops, bad typo More fleshing out the structure for network-view onHover netview-legend: highlight relevant nodes. And the bool rolls on. Handle nodes w/ no networks better Corrects deselect-node when used w/ new network-view behaviour Net view details "node" can be open when with no nodes selected. Hitting "esc" from: - card 0: network-a - card 1: node-a was not deselecting node-a Deselect selectedNetwork correctly onEsc Ooops, trailing ws breaks linting. Adds NodeNetworksOverlay stub Expands on NodeNetworksOverlay stub and adds arcs and colors Expand and collapse networks legend Open arc for network circle, shift for stack Show our base hue range in the debug bar too.. Was trying to smooth out our hue selector but turned out to be tricky.. Uniquify random data generator!
This commit is contained in:
@@ -32,6 +32,53 @@ export function toggleHelp() {
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Networks
|
||||
//
|
||||
|
||||
|
||||
export function showNetworks(visible) {
|
||||
return {
|
||||
type: ActionTypes.SHOW_NETWORKS,
|
||||
visible
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function selectNetwork(networkId) {
|
||||
return {
|
||||
type: ActionTypes.SELECT_NETWORK,
|
||||
networkId
|
||||
};
|
||||
}
|
||||
|
||||
export function pinNetwork(networkId) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: ActionTypes.PIN_NETWORK,
|
||||
networkId,
|
||||
});
|
||||
|
||||
updateRoute(getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function unpinNetwork(networkId) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: ActionTypes.UNPIN_NETWORK,
|
||||
networkId,
|
||||
});
|
||||
|
||||
updateRoute(getState);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Metrics
|
||||
//
|
||||
|
||||
export function selectMetric(metricId) {
|
||||
return {
|
||||
type: ActionTypes.SELECT_METRIC,
|
||||
|
||||
47
client/app/scripts/charts/node-networks-overlay.js
Normal file
47
client/app/scripts/charts/node-networks-overlay.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
import d3 from 'd3';
|
||||
import { List as makeList } from 'immutable';
|
||||
import { getNodeColor } from '../utils/color-utils';
|
||||
import { isContrastMode } from '../utils/contrast-utils';
|
||||
|
||||
|
||||
const padding = 0.05;
|
||||
const offset = Math.PI;
|
||||
const arc = d3.svg.arc()
|
||||
.startAngle(d => d.startAngle + offset)
|
||||
.endAngle(d => d.endAngle + offset);
|
||||
const arcScale = d3.scale.linear()
|
||||
.range([Math.PI * 0.25 + padding, Math.PI * 1.75 - padding]);
|
||||
|
||||
|
||||
function NodeNetworksOverlay({size, stack, networks = makeList()}) {
|
||||
arcScale.domain([0, networks.size]);
|
||||
const radius = size * 0.9;
|
||||
|
||||
const paths = networks.map((n, i) => {
|
||||
const d = arc({
|
||||
padAngle: 0.05,
|
||||
innerRadius: radius,
|
||||
outerRadius: radius + 4,
|
||||
startAngle: arcScale(i),
|
||||
endAngle: arcScale(i + 1)
|
||||
});
|
||||
|
||||
return (<path d={d} style={{fill: getNodeColor(n)}} key={n} />);
|
||||
});
|
||||
|
||||
let transform = '';
|
||||
if (stack) {
|
||||
const contrastMode = isContrastMode();
|
||||
const [dx, dy] = contrastMode ? [0, 8] : [0, 5];
|
||||
transform = `translate(${dx}, ${dy * -1.5})`;
|
||||
}
|
||||
|
||||
return (
|
||||
<g transform={transform}>
|
||||
{paths.toJS()}
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
export default NodeNetworksOverlay;
|
||||
@@ -30,14 +30,12 @@ function getPoints(h) {
|
||||
}
|
||||
|
||||
|
||||
export default function NodeShapeHex({id, highlighted, size, color, metric, networks}) {
|
||||
export default function NodeShapeHex({id, highlighted, size, color, metric}) {
|
||||
const pathProps = v => ({
|
||||
d: getPoints(size * v * 2),
|
||||
transform: `rotate(90) translate(-${size * getWidth(v)}, -${size * v})`
|
||||
});
|
||||
|
||||
console.log('netz', networks);
|
||||
|
||||
const shadowSize = 0.45;
|
||||
const upperHexBitHeight = -0.25 * size * shadowSize;
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import NodeShapeRoundedSquare from './node-shape-rounded-square';
|
||||
import NodeShapeHex from './node-shape-hex';
|
||||
import NodeShapeHeptagon from './node-shape-heptagon';
|
||||
import NodeShapeCloud from './node-shape-cloud';
|
||||
import NodeNetworksOverlay from './node-networks-overlay';
|
||||
|
||||
function stackedShape(Shape) {
|
||||
const factory = React.createFactory(NodeShapeStack);
|
||||
@@ -75,8 +76,9 @@ class Node extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { blurred, focused, highlighted, label, matches = makeMap(),
|
||||
pseudo, rank, subLabel, scaleFactor, transform, zoomScale, exportingGraph } = this.props;
|
||||
const { blurred, focused, highlighted, label, matches = makeMap(), networks,
|
||||
pseudo, rank, subLabel, scaleFactor, transform, zoomScale, exportingGraph,
|
||||
showingNetworks, stack } = this.props;
|
||||
const { hovered, matched } = this.state;
|
||||
const nodeScale = focused ? this.props.selectedNodeScale : this.props.nodeScale;
|
||||
|
||||
@@ -100,11 +102,14 @@ class Node extends React.Component {
|
||||
|
||||
const NodeShapeType = getNodeShape(this.props);
|
||||
const useSvgLabels = exportingGraph;
|
||||
|
||||
const size = nodeScale(scaleFactor);
|
||||
return (
|
||||
<g className={nodeClassName} transform={transform}
|
||||
onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
|
||||
{showingNetworks && <NodeNetworksOverlay size={size} networks={networks}
|
||||
stack={stack} />}
|
||||
|
||||
{useSvgLabels ?
|
||||
|
||||
svgLabels(label, subLabel, labelClassName, subLabelClassName, labelOffsetY) :
|
||||
@@ -124,7 +129,7 @@ class Node extends React.Component {
|
||||
|
||||
<g onClick={this.handleMouseClick}>
|
||||
<NodeShapeType
|
||||
size={nodeScale(scaleFactor)}
|
||||
size={size}
|
||||
color={color}
|
||||
{...this.props} />
|
||||
</g>
|
||||
@@ -152,7 +157,8 @@ class Node extends React.Component {
|
||||
export default connect(
|
||||
state => ({
|
||||
searchQuery: state.get('searchQuery'),
|
||||
exportingGraph: state.get('exportingGraph')
|
||||
exportingGraph: state.get('exportingGraph'),
|
||||
showingNetworks: state.get('showingNetworks'),
|
||||
}),
|
||||
{ clickNode, enterNode, leaveNode }
|
||||
)(Node);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { fromJS, Map as makeMap } from 'immutable';
|
||||
import { fromJS, Map as makeMap, List as makeList } from 'immutable';
|
||||
|
||||
import { getAdjacentNodes } from '../utils/topology-utils';
|
||||
import NodeContainer from './node-container';
|
||||
@@ -9,7 +9,7 @@ class NodesChartNodes extends React.Component {
|
||||
render() {
|
||||
const { adjacentNodes, highlightedNodeIds, layoutNodes, layoutPrecision,
|
||||
mouseOverNodeId, nodeScale, scale, searchNodeMatches = makeMap(),
|
||||
searchQuery, selectedMetric, selectedNodeScale, selectedNodeId,
|
||||
searchQuery, selectedMetric, selectedNetwork, selectedNodeScale, selectedNodeId,
|
||||
topCardNode } = this.props;
|
||||
|
||||
const zoomScale = scale;
|
||||
@@ -23,7 +23,8 @@ class NodesChartNodes extends React.Component {
|
||||
const setBlurred = node => node.set('blurred',
|
||||
selectedNodeId && !node.get('focused')
|
||||
|| searchQuery && !searchNodeMatches.has(node.get('id'))
|
||||
&& !node.get('highlighted'));
|
||||
&& !node.get('highlighted')
|
||||
|| selectedNetwork && !(node.get('networks') || makeList()).contains(selectedNetwork));
|
||||
|
||||
// make sure blurred nodes are in the background
|
||||
const sortNodes = node => {
|
||||
@@ -91,6 +92,7 @@ function mapStateToProps(state) {
|
||||
highlightedNodeIds: state.get('highlightedNodeIds'),
|
||||
mouseOverNodeId: state.get('mouseOverNodeId'),
|
||||
selectedMetric: state.get('selectedMetric'),
|
||||
selectedNetwork: state.get('selectedNetwork'),
|
||||
selectedNodeId: state.get('selectedNodeId'),
|
||||
searchNodeMatches: state.getIn(['searchNodeMatches', currentTopologyId]),
|
||||
searchQuery: state.get('searchQuery'),
|
||||
|
||||
@@ -179,7 +179,7 @@ class NodesChart extends React.Component {
|
||||
rank: node.get('rank'),
|
||||
shape: node.get('shape'),
|
||||
stack: node.get('stack'),
|
||||
networks: node.get('networknetworks'),
|
||||
networks: node.get('networks'),
|
||||
}));
|
||||
});
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import { focusSearch, pinNextMetric, hitBackspace, hitEnter, hitEsc, unpinMetric
|
||||
import Details from './details';
|
||||
import Nodes from './nodes';
|
||||
import MetricSelector from './metric-selector';
|
||||
import NetworkSelector from './networks-selector';
|
||||
import EmbeddedTerminal from './embedded-terminal';
|
||||
import { getRouter } from '../utils/router-utils';
|
||||
import DebugToolbar, { showingDebugToolbar,
|
||||
@@ -97,7 +98,8 @@ class App extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { showingDetails, showingHelp, showingMetricsSelector, showingTerminal } = this.props;
|
||||
const { showingDetails, showingHelp, showingMetricsSelector, showingNetworkSelector,
|
||||
showingTerminal } = this.props;
|
||||
|
||||
return (
|
||||
<div className="app">
|
||||
@@ -124,6 +126,7 @@ class App extends React.Component {
|
||||
<Sidebar>
|
||||
<Status />
|
||||
{showingMetricsSelector && <MetricSelector />}
|
||||
{showingNetworkSelector && <NetworkSelector />}
|
||||
<TopologyOptions />
|
||||
</Sidebar>
|
||||
|
||||
@@ -142,6 +145,7 @@ function mapStateToProps(state) {
|
||||
showingDetails: state.get('nodeDetails').size > 0,
|
||||
showingHelp: state.get('showingHelp'),
|
||||
showingMetricsSelector: state.get('availableCanvasMetrics').count() > 0,
|
||||
showingNetworkSelector: state.get('availableNetworks').count() > 0,
|
||||
showingTerminal: state.get('controlPipes').size > 0,
|
||||
urlState: getUrlState(state)
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint react/jsx-no-bind: "off" */
|
||||
import React from 'react';
|
||||
import d3 from 'd3';
|
||||
import _ from 'lodash';
|
||||
import Perf from 'react-addons-perf';
|
||||
import { connect } from 'react-redux';
|
||||
@@ -9,13 +10,14 @@ import debug from 'debug';
|
||||
const log = debug('scope:debug-panel');
|
||||
|
||||
import { receiveNodesDelta } from '../actions/app-actions';
|
||||
import { getNodeColor, getNodeColorDark } from '../utils/color-utils';
|
||||
import { getNodeColor, getNodeColorDark, text2degree } from '../utils/color-utils';
|
||||
|
||||
|
||||
const SHAPES = ['square', 'hexagon', 'heptagon', 'circle'];
|
||||
const NODE_COUNTS = [1, 2, 3];
|
||||
const STACK_VARIANTS = [false, true];
|
||||
const METRIC_FILLS = [0, 0.1, 50, 99.9, 100];
|
||||
const NETWORKS = ['be', 'fe', 'lb', 'db'];
|
||||
|
||||
const LOREM = `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
|
||||
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
|
||||
@@ -23,7 +25,7 @@ ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor i
|
||||
voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
|
||||
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.`;
|
||||
|
||||
const sample = (collection) => _.range(_.random(4)).map(() => _.sample(collection));
|
||||
const sample = (collection, n = 4) => _.sampleSize(collection, _.random(n));
|
||||
|
||||
|
||||
const shapeTypes = {
|
||||
@@ -41,7 +43,10 @@ const LABEL_PREFIXES = _.range('A'.charCodeAt(), 'Z'.charCodeAt() + 1)
|
||||
// const randomLetter = () => _.sample(LABEL_PREFIXES);
|
||||
|
||||
|
||||
const deltaAdd = (name, adjacency = [], shape = 'circle', stack = false, nodeCount = 1) => ({
|
||||
const deltaAdd = (
|
||||
name, adjacency = [], shape = 'circle', stack = false, nodeCount = 1,
|
||||
networks = ['fe', 'be']
|
||||
) => ({
|
||||
adjacency,
|
||||
controls: {},
|
||||
shape,
|
||||
@@ -53,7 +58,7 @@ const deltaAdd = (name, adjacency = [], shape = 'circle', stack = false, nodeCou
|
||||
latest: {},
|
||||
origins: [],
|
||||
rank: name,
|
||||
networks: ['fe', 'be'],
|
||||
networks
|
||||
});
|
||||
|
||||
|
||||
@@ -170,7 +175,8 @@ class DebugToolbar extends React.Component {
|
||||
sample(allNodes),
|
||||
_.sample(SHAPES),
|
||||
_.sample(STACK_VARIANTS),
|
||||
_.sample(NODE_COUNTS)
|
||||
_.sample(NODE_COUNTS),
|
||||
sample(NETWORKS, 3)
|
||||
))
|
||||
}));
|
||||
|
||||
@@ -208,8 +214,21 @@ class DebugToolbar extends React.Component {
|
||||
<button onClick={this.toggleColors}>toggle</button>
|
||||
</div>
|
||||
|
||||
{this.state.showColors && [getNodeColor, getNodeColorDark].map(fn => (
|
||||
<table>
|
||||
{this.state.showColors &&
|
||||
<table>
|
||||
<tbody>
|
||||
{LABEL_PREFIXES.map(r => (
|
||||
<tr key={r}>
|
||||
<td
|
||||
title={`${r}`}
|
||||
style={{backgroundColor: d3.hsl(text2degree(r), 0.5, 0.5).toString()}} />
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>}
|
||||
|
||||
{this.state.showColors && [getNodeColor, getNodeColorDark].map((fn, i) => (
|
||||
<table key={i}>
|
||||
<tbody>
|
||||
{LABEL_PREFIXES.map(r => (
|
||||
<tr key={r}>
|
||||
|
||||
69
client/app/scripts/components/network-selector-item.js
Normal file
69
client/app/scripts/components/network-selector-item.js
Normal file
@@ -0,0 +1,69 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { selectNetwork, pinNetwork, unpinNetwork } from '../actions/app-actions';
|
||||
import { getNodeColor } from '../utils/color-utils';
|
||||
|
||||
class NetworkSelectorItem extends React.Component {
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.onMouseOver = this.onMouseOver.bind(this);
|
||||
this.onMouseClick = this.onMouseClick.bind(this);
|
||||
}
|
||||
|
||||
onMouseOver() {
|
||||
const k = this.props.network.get('id');
|
||||
this.props.selectNetwork(k);
|
||||
}
|
||||
|
||||
onMouseClick() {
|
||||
const k = this.props.network.get('id');
|
||||
const pinnedNetwork = this.props.pinnedNetwork;
|
||||
|
||||
if (k === pinnedNetwork) {
|
||||
this.props.unpinNetwork(k);
|
||||
} else {
|
||||
this.props.pinNetwork(k);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {network, selectedNetwork, pinnedNetwork} = this.props;
|
||||
const id = network.get('id');
|
||||
const isPinned = (id === pinnedNetwork);
|
||||
const isSelected = (id === selectedNetwork);
|
||||
const className = classNames('metric-selector-action', {
|
||||
'metric-selector-action-selected': isSelected
|
||||
});
|
||||
const style = {
|
||||
backgroundColor: getNodeColor(id)
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
key={id}
|
||||
className={className}
|
||||
onMouseOver={this.onMouseOver}
|
||||
onClick={this.onMouseClick}
|
||||
style={style}>
|
||||
{network.get('label')}
|
||||
{isPinned && <span className="fa fa-thumb-tack"></span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
selectedNetwork: state.get('selectedNetwork'),
|
||||
pinnedNetwork: state.get('pinnedNetwork')
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
{ selectNetwork, pinNetwork, unpinNetwork }
|
||||
)(NetworkSelectorItem);
|
||||
59
client/app/scripts/components/networks-selector.js
Normal file
59
client/app/scripts/components/networks-selector.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { selectNetwork, showNetworks } from '../actions/app-actions';
|
||||
import NetworkSelectorItem from './network-selector-item';
|
||||
|
||||
class NetworkSelector extends React.Component {
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.onClick = this.onClick.bind(this);
|
||||
this.onMouseOut = this.onMouseOut.bind(this);
|
||||
}
|
||||
|
||||
onClick() {
|
||||
return this.props.showNetworks(!this.props.showingNetworks);
|
||||
}
|
||||
|
||||
onMouseOut() {
|
||||
this.props.selectNetwork(this.props.pinnedNetwork);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { availableNetworks, showingNetworks } = this.props;
|
||||
|
||||
const items = availableNetworks.map(network => (
|
||||
<NetworkSelectorItem key={network.get('id')} network={network} />
|
||||
));
|
||||
|
||||
const className = classNames('metric-selector-action', {
|
||||
'metric-selector-action-selected': showingNetworks
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="metric-selector">
|
||||
<div className="metric-selector-wrapper" onMouseLeave={this.onMouseOut}>
|
||||
<div className={className} onClick={this.onClick}>
|
||||
Networks
|
||||
</div>
|
||||
{showingNetworks && items}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
availableNetworks: state.get('availableNetworks'),
|
||||
showingNetworks: state.get('showingNetworks'),
|
||||
pinnedNetwork: state.get('pinnedNetwork')
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
{ selectNetwork, showNetworks }
|
||||
)(NetworkSelector);
|
||||
@@ -47,7 +47,12 @@ const ACTION_TYPES = [
|
||||
'ROUTE_TOPOLOGY',
|
||||
'SELECT_METRIC',
|
||||
'SHOW_HELP',
|
||||
'SET_EXPORTING_GRAPH'
|
||||
'SET_EXPORTING_GRAPH',
|
||||
|
||||
'SELECT_NETWORK',
|
||||
'PIN_NETWORK',
|
||||
'UNPIN_NETWORK',
|
||||
'SHOW_NETWORKS',
|
||||
];
|
||||
|
||||
export default _.zipObject(ACTION_TYPES, ACTION_TYPES);
|
||||
|
||||
@@ -20,6 +20,7 @@ const topologySorter = topology => topology.get('rank');
|
||||
|
||||
export const initialState = makeMap({
|
||||
availableCanvasMetrics: makeList(),
|
||||
availableNetworks: makeList(),
|
||||
controlPipes: makeOrderedMap(), // pipeId -> controlPipe
|
||||
controlStatus: makeMap(),
|
||||
currentTopology: null,
|
||||
@@ -39,6 +40,7 @@ export const initialState = makeMap({
|
||||
// class of metric, e.g. 'cpu', rather than 'host_cpu' or 'process_cpu'.
|
||||
// allows us to keep the same metric "type" selected when the topology changes.
|
||||
pinnedMetricType: null,
|
||||
pinnedNetwork: null,
|
||||
plugins: makeList(),
|
||||
pinnedSearches: makeList(), // list of node filters
|
||||
routeSet: false,
|
||||
@@ -46,8 +48,10 @@ export const initialState = makeMap({
|
||||
searchNodeMatches: makeMap(),
|
||||
searchQuery: null,
|
||||
selectedMetric: null,
|
||||
selectedNetwork: null,
|
||||
selectedNodeId: null,
|
||||
showingHelp: false,
|
||||
showingNetworks: false,
|
||||
topologies: makeList(),
|
||||
topologiesLoaded: false,
|
||||
topologyOptions: makeOrderedMap(), // topologyId -> options
|
||||
@@ -102,25 +106,46 @@ function setDefaultTopologyOptions(state, topologyList) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function shouldCloseExisting(state) {
|
||||
const nodeDetails = state.get('nodeDetails');
|
||||
if (nodeDetails.size > 0 && nodeDetails.valueSeq().last().topologyId === 'networks') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function closeNodeDetails(state, nodeId) {
|
||||
const nodeDetails = state.get('nodeDetails');
|
||||
if (nodeDetails.size > 0) {
|
||||
const popNodeId = nodeId || nodeDetails.keySeq().last();
|
||||
// remove pipe if it belongs to the node being closed
|
||||
state = state.update('controlPipes',
|
||||
controlPipes => controlPipes.filter(pipe => pipe.get('nodeId') !== popNodeId));
|
||||
state = state.deleteIn(['nodeDetails', popNodeId]);
|
||||
if (nodeDetails.size === 0) {
|
||||
return state;
|
||||
}
|
||||
if (state.get('nodeDetails').size === 0 || state.get('selectedNodeId') === nodeId) {
|
||||
nodeId = nodeId || nodeDetails.keySeq().last();
|
||||
|
||||
// remove pipe if it belongs to the node being closed
|
||||
state = state.update('controlPipes',
|
||||
controlPipes => controlPipes.filter(pipe => pipe.get('nodeId') !== nodeId));
|
||||
state = state.deleteIn(['nodeDetails', nodeId]);
|
||||
|
||||
// FIXME: duplicated state in a sense, look into reselect or something, we could derive the
|
||||
// selectedNetwork from the contents of nodeDetails
|
||||
//
|
||||
// clear this additional state
|
||||
if (state.get('selectedNetwork') === nodeId) {
|
||||
state = state.set('selectedNetwork', null);
|
||||
state = state.set('pinnedNetwork', null);
|
||||
}
|
||||
|
||||
// TODO: could also be derived state.
|
||||
if (state.get('selectedNodeId') === nodeId) {
|
||||
state = state.set('selectedNodeId', null);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
function closeAllNodeDetails(state) {
|
||||
while (state.get('nodeDetails').size) {
|
||||
state = closeNodeDetails(state);
|
||||
}
|
||||
state.get('nodeDetails').keySeq().forEach(nodeId => {
|
||||
state = closeNodeDetails(state, nodeId);
|
||||
});
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -183,8 +208,10 @@ export function rootReducer(state = initialState, action) {
|
||||
const prevSelectedNodeId = state.get('selectedNodeId');
|
||||
const prevDetailsStackSize = state.get('nodeDetails').size;
|
||||
|
||||
if (shouldCloseExisting(state)) {
|
||||
// click on sibling closes all
|
||||
state = closeAllNodeDetails(state);
|
||||
state = closeAllNodeDetails(state);
|
||||
}
|
||||
|
||||
// select new node if it's not the same (in that case just delesect)
|
||||
if (prevDetailsStackSize > 1 || prevSelectedNodeId !== action.nodeId) {
|
||||
@@ -265,6 +292,47 @@ export function rootReducer(state = initialState, action) {
|
||||
return state;
|
||||
}
|
||||
|
||||
//
|
||||
// networks
|
||||
//
|
||||
|
||||
case ActionTypes.SHOW_NETWORKS: {
|
||||
return state.set('showingNetworks', action.visible);
|
||||
}
|
||||
|
||||
case ActionTypes.SELECT_NETWORK: {
|
||||
return state.set('selectedNetwork', action.networkId);
|
||||
}
|
||||
|
||||
case ActionTypes.PIN_NETWORK: {
|
||||
state = closeAllNodeDetails(state);
|
||||
|
||||
state = state.setIn(['nodeDetails', action.networkId],
|
||||
{
|
||||
id: action.networkId,
|
||||
label: action.networkId,
|
||||
origin: null,
|
||||
topologyId: 'networks'
|
||||
}
|
||||
);
|
||||
|
||||
return state.merge({
|
||||
pinnedNetwork: action.networkId,
|
||||
selectedNetwork: action.networkId
|
||||
});
|
||||
}
|
||||
|
||||
case ActionTypes.UNPIN_NETWORK: {
|
||||
state = closeNodeDetails(state, action.networkId);
|
||||
return state.merge({
|
||||
pinnedNetwork: null,
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// metrics
|
||||
//
|
||||
|
||||
case ActionTypes.SELECT_METRIC: {
|
||||
return state.set('selectedMetric', action.metricId);
|
||||
}
|
||||
@@ -481,6 +549,15 @@ export function rootReducer(state = initialState, action) {
|
||||
// apply pinned searches, filters nodes that dont match
|
||||
state = applyPinnedSearches(state);
|
||||
|
||||
state = state.set('availableNetworks', state.get('nodes')
|
||||
.valueSeq()
|
||||
.flatMap(node => (node.get('networks') || makeList()).map(n => (
|
||||
makeMap({id: n, label: n})
|
||||
)))
|
||||
.toSet()
|
||||
.toList()
|
||||
.sort());
|
||||
|
||||
state = state.set('availableCanvasMetrics', state.get('nodes')
|
||||
.valueSeq()
|
||||
.flatMap(n => (n.get('metrics') || makeList()).map(m => (
|
||||
@@ -579,6 +656,16 @@ export function rootReducer(state = initialState, action) {
|
||||
if (!isDeepEqual(state.get('nodeDetails').keySeq(), actionNodeDetails.keySeq())) {
|
||||
state = state.set('nodeDetails', actionNodeDetails);
|
||||
}
|
||||
//
|
||||
// load up network view state
|
||||
// TODO: cleanup/extract.
|
||||
//
|
||||
const networkNodes = action.state.nodeDetails.filter(n => n.topologyId === 'networks');
|
||||
if (networkNodes.length > 0) {
|
||||
state = state.set('pinnedNetwork', networkNodes[0].id);
|
||||
state = state.set('selectedNetwork', networkNodes[0].id);
|
||||
state = state.set('showingNetworks', true);
|
||||
}
|
||||
} else {
|
||||
state = state.update('nodeDetails', nodeDetails => nodeDetails.clear());
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ const letterRange = endLetterRange - startLetterRange;
|
||||
/**
|
||||
* Converts a text to a 360 degree value
|
||||
*/
|
||||
function text2degree(text) {
|
||||
export function text2degree(text) {
|
||||
const input = text.substr(0, 2).toUpperCase();
|
||||
let num = 0;
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
|
||||
@@ -341,6 +341,11 @@ h2 {
|
||||
transition: opacity .5s @base-ease;
|
||||
text-align: center;
|
||||
|
||||
.node-networks-overlay {
|
||||
fill: none;
|
||||
stroke: @background-darker-secondary-color;
|
||||
}
|
||||
|
||||
.node-label,
|
||||
.node-sublabel {
|
||||
line-height: 125%;
|
||||
|
||||
Reference in New Issue
Block a user