diff --git a/client/app/scripts/charts/node-networks-overlay.js b/client/app/scripts/charts/node-networks-overlay.js index b728de051..b50841239 100644 --- a/client/app/scripts/charts/node-networks-overlay.js +++ b/client/app/scripts/charts/node-networks-overlay.js @@ -5,41 +5,46 @@ import { getNodeColor } from '../utils/color-utils'; import { isContrastMode } from '../utils/contrast-utils'; +const h = 5; 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]); - +const rx = 1; +const ry = rx; +const labelOffset = 38; function NodeNetworksOverlay({size, stack, networks = makeList()}) { - arcScale.domain([0, networks.size]); - const radius = size * 0.9; + const r = size * 0.5; + const offset = r + labelOffset; + const w = Math.max(size, (size / 4) * networks.size); + const x = d3.scale.ordinal() + .domain(networks.map((n, i) => i).toJS()) + .rangeBands([w * -0.5, w * 0.5], padding, 0); - 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 (); - }); + const bars = networks.map((n, i) => ( + + )); let transform = ''; if (stack) { const contrastMode = isContrastMode(); - const [dx, dy] = contrastMode ? [0, 8] : [0, 5]; + const [dx, dy] = contrastMode ? [0, 8] : [0, 0]; transform = `translate(${dx}, ${dy * -1.5})`; } return ( - {paths.toJS()} + {bars.toJS()} ); } diff --git a/client/app/scripts/charts/node.js b/client/app/scripts/charts/node.js index acd942702..fae17bf7d 100644 --- a/client/app/scripts/charts/node.js +++ b/client/app/scripts/charts/node.js @@ -107,9 +107,6 @@ class Node extends React.Component { - {showingNetworks && } - {useSvgLabels ? svgLabels(label, subLabel, labelClassName, subLabelClassName, labelOffsetY) : @@ -133,6 +130,9 @@ class Node extends React.Component { color={color} {...this.props} /> + + {showingNetworks && } ); } diff --git a/client/app/scripts/charts/nodes-chart-edges.js b/client/app/scripts/charts/nodes-chart-edges.js index 29d26ffe0..a67eb5287 100644 --- a/client/app/scripts/charts/nodes-chart-edges.js +++ b/client/app/scripts/charts/nodes-chart-edges.js @@ -1,6 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; -import { Map as makeMap } from 'immutable'; +import { Map as makeMap, List as makeList } from 'immutable'; import { hasSelectedNode as hasSelectedNodeFn } from '../utils/topology-utils'; import EdgeContainer from './edge-container'; @@ -9,7 +9,7 @@ class NodesChartEdges extends React.Component { render() { const { hasSelectedNode, highlightedEdgeIds, layoutEdges, layoutPrecision, searchNodeMatches = makeMap(), searchQuery, - selectedNodeId } = this.props; + selectedNodeId, selectedNetwork, selectedNetworkNodes } = this.props; return ( @@ -18,10 +18,16 @@ class NodesChartEdges extends React.Component { const targetSelected = selectedNodeId === edge.get('target'); const highlighted = highlightedEdgeIds.has(edge.get('id')); const focused = hasSelectedNode && (sourceSelected || targetSelected); - const blurred = !(highlightedEdgeIds.size > 0 && highlighted) - && ((hasSelectedNode && !sourceSelected && !targetSelected) - || !focused && searchQuery && !(searchNodeMatches.has(edge.get('source')) - && searchNodeMatches.has(edge.get('target')))); + const otherNodesSelected = hasSelectedNode && !sourceSelected && !targetSelected; + const noMatches = searchQuery && + !(searchNodeMatches.has(edge.get('source')) && + searchNodeMatches.has(edge.get('target'))); + const noSelectedNetworks = selectedNetwork && + !(selectedNetworkNodes.contains(edge.get('source')) && + selectedNetworkNodes.contains(edge.get('target'))); + const blurred = !highlighted && (otherNodesSelected || + !focused && noMatches || + !focused && noSelectedNetworks); return ( n.get('id') === selectedNetwork)); // make sure blurred nodes are in the background const sortNodes = node => { diff --git a/client/app/scripts/components/debug-toolbar.js b/client/app/scripts/components/debug-toolbar.js index e52e22876..97145ff7c 100644 --- a/client/app/scripts/components/debug-toolbar.js +++ b/client/app/scripts/components/debug-toolbar.js @@ -17,7 +17,9 @@ 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 NETWORKS = [ + 'be', 'fe', 'zb', 'db', 're', 'gh', 'jk', 'lol', 'nw' +].map(n => ({id: n, label: n, colorKey: n})); 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 @@ -45,7 +47,7 @@ const LABEL_PREFIXES = _.range('A'.charCodeAt(), 'Z'.charCodeAt() + 1) const deltaAdd = ( name, adjacency = [], shape = 'circle', stack = false, nodeCount = 1, - networks = ['fe', 'be'] + networks = NETWORKS ) => ({ adjacency, controls: {}, @@ -176,7 +178,7 @@ class DebugToolbar extends React.Component { _.sample(SHAPES), _.sample(STACK_VARIANTS), _.sample(NODE_COUNTS), - sample(NETWORKS, 3) + sample(NETWORKS, 10) )) })); diff --git a/client/app/scripts/reducers/root.js b/client/app/scripts/reducers/root.js index f304d1a5b..d94c35cf5 100644 --- a/client/app/scripts/reducers/root.js +++ b/client/app/scripts/reducers/root.js @@ -33,6 +33,7 @@ export const initialState = makeMap({ hostname: '...', mouseOverEdgeId: null, mouseOverNodeId: null, + networkNodes: makeMap(), nodeDetails: makeOrderedMap(), // nodeId -> details nodes: makeOrderedMap(), // nodeId -> node // nodes cache, infrequently updated, used for search @@ -81,6 +82,24 @@ function processTopologies(state, nextTopologies) { return state.mergeDeepIn(['topologies'], immNextTopologies); } +function getNetworkNodes(nodes) { + const networks = {}; + nodes.forEach(node => (node.get('networks') || makeList()).forEach(n => { + const networkId = n.get('id'); + networks[networkId] = (networks[networkId] || []).concat([node.get('id')]); + })); + return fromJS(networks); +} + +function getAvailableNetworks(nodes) { + return nodes + .valueSeq() + .flatMap(node => node.get('networks') || makeList()) + .toSet() + .toList() + .sortBy(m => m.get('label')); +} + function setTopology(state, topologyId) { state = state.set('currentTopology', findTopologyById( state.get('topologies'), topologyId)); @@ -526,19 +545,15 @@ export function rootReducer(state = initialState, action) { const networks = node.get('metadata') .find(field => field.get('id') === 'docker_container_networks'); if (networks) { - return node.set('networks', fromJS(networks.get('value').split(', '))); + return node.set('networks', fromJS( + networks.get('value').split(', ').map(n => ({id: n, label: n, colorKey: n})))); } } return node; })); - state = state.set('availableNetworks', state.get('nodes') - .valueSeq() - .flatMap(node => node.get('networks') || makeList()) - .toSet() - .toList() - .sort() - .map(n => makeMap({id: n, label: n}))); + state = state.set('networkNodes', getNetworkNodes(state.get('nodes'))); + state = state.set('availableNetworks', getAvailableNetworks(state.get('nodes'))); // optimize color coding for networks const networkPrefix = longestCommonPrefix(state.get('availableNetworks') @@ -547,7 +562,8 @@ export function rootReducer(state = initialState, action) { if (networkPrefix) { state = state.update('nodes', nodes => nodes.map(node => node.update('networks', - networks => networks.map(n => n.substr(networkPrefix.length))))); + networks => networks.map(n => n.set('colorKey', + n.get('colorKey').substr(networkPrefix.length)))))); state = state.update('availableNetworks', networks => networks.map(network => network @@ -642,6 +658,7 @@ export function rootReducer(state = initialState, action) { } if (action.state.pinnedNetwork) { state = state.set('pinnedNetwork', action.state.pinnedNetwork); + state = state.set('selectedNetwork', action.state.pinnedNetwork); } if (action.state.controlPipe) { state = state.set('controlPipes', makeOrderedMap({ diff --git a/client/app/styles/main.less b/client/app/styles/main.less index bff5b4f64..900d23a91 100644 --- a/client/app/styles/main.less +++ b/client/app/styles/main.less @@ -341,9 +341,9 @@ h2 { transition: opacity .5s @base-ease; text-align: center; - .node-networks-overlay { - fill: none; - stroke: @background-darker-secondary-color; + .node-network { + // stroke: @background-lighter-color; + // stroke-width: 4px; } .node-label,