Merge pull request #884 from weaveworks/node-shapes

Node shapes
This commit is contained in:
Simon
2016-02-22 14:13:14 +01:00
16 changed files with 402 additions and 40 deletions
@@ -0,0 +1,22 @@
import React from 'react';
export default function NodeShapeCircle({onlyHighlight, highlighted, size, color}) {
const hightlightNode = <circle r={size * 0.7} className="highlighted" />;
if (onlyHighlight) {
return (
<g className="shape">
{highlighted && hightlightNode}
</g>
);
}
return (
<g className="shape">
{highlighted && hightlightNode}
<circle r={size * 0.5} className="border" stroke={color} />
<circle r={size * 0.45} className="shadow" />
<circle r={Math.max(2, size * 0.125)} className="node" />
</g>
);
}
@@ -0,0 +1,41 @@
import React from 'react';
import d3 from 'd3';
const CLOUD_PATH = 'M 1920,384 Q 1920,225 1807.5,112.5 1695,0 1536,0 H 448 Q 263,0 131.5,131.5 0,263 0,448 0,580 71,689.5 142,799 258,853 q -2,28 -2,43 0,212 150,362 150,150 362,150 158,0 286.5,-88 128.5,-88 187.5,-230 70,62 166,62 106,0 181,-75 75,-75 75,-181 0,-75 -41,-138 129,-30 213,-134.5 84,-104.5 84,-239.5 z';
function toPoint(stringPair) {
return stringPair.split(',').map(p => parseFloat(p, 10));
}
function getExtents(svgPath) {
const points = svgPath.split(' ').filter(s => s.length > 1).map(toPoint);
return [d3.extent(points, p => p[0]), d3.extent(points, p => p[1])];
}
export default function NodeShapeCloud({highlighted, size, color}) {
const [[minx, maxx], [miny, maxy]] = getExtents(CLOUD_PATH);
const width = (maxx - minx);
const height = (maxy - miny);
const cx = width / 2;
const cy = height / 2;
const pathSize = (width + height) / 2;
const baseScale = (size * 2) / pathSize;
const pathProps = (v) => {
return {
d: CLOUD_PATH,
transform: `scale(-${v * baseScale}) translate(-${cx},-${cy})`,
style: {strokeWidth: 1 / baseScale}
};
};
return (
<g className="shape">
{highlighted &&
<path className="highlighted" {...pathProps(0.7)} />}
<path className="border" stroke={color} {...pathProps(0.5)} />
<path className="shadow" {...pathProps(0.45)} />
<circle className="node" r={Math.max(2, (size * 0.125))} />
</g>
);
}
@@ -0,0 +1,53 @@
import React from 'react';
import d3 from 'd3';
const line = d3.svg.line()
.interpolate('cardinal-closed')
.tension(0.25);
function getWidth(h) {
return (Math.sqrt(3) / 2) * h;
}
function getPoints(h) {
const w = getWidth(h);
const points = [
[w * 0.5, 0],
[w, 0.25 * h],
[w, 0.75 * h],
[w * 0.5, h],
[0, 0.75 * h],
[0, 0.25 * h]
];
return line(points);
}
export default function NodeShapeHex({onlyHighlight, highlighted, size, color}) {
const pathProps = (v) => {
return {
d: getPoints(size * v * 2),
transform: `rotate(90) translate(-${size * getWidth(v)}, -${size * v})`
};
};
const hightlightNode = <path className="highlighted" {...pathProps(0.7)} />;
if (onlyHighlight) {
return (
<g className="shape">
{highlighted && hightlightNode}
</g>
);
}
return (
<g className="shape">
{highlighted && hightlightNode}
<path className="border" stroke={color} {...pathProps(0.5)} />
<path className="shadow" {...pathProps(0.45)} />
<circle className="node" r={Math.max(2, (size * 0.125))} />
</g>
);
}
@@ -0,0 +1,10 @@
import React from 'react';
import NodeShapeSquare from './node-shape-square';
// TODO how to express a cmp in terms of another cmp? (Rather than a sub-cmp as here).
export default function NodeShapeRoundedSquare(props) {
return (
<NodeShapeSquare {...props} rx="0.4" ry="0.4" />
);
}
@@ -0,0 +1,32 @@
import React from 'react';
export default function NodeShapeSquare({onlyHighlight, highlighted, size, color, rx = 0, ry = 0}) {
const rectProps = (v) => {
return {
width: v * size * 2,
height: v * size * 2,
rx: v * size * rx,
ry: v * size * ry,
transform: `translate(-${size * v}, -${size * v})`
};
};
const hightlightNode = <rect className="highlighted" {...rectProps(0.7)} />;
if (onlyHighlight) {
return (
<g className="shape">
{highlighted && hightlightNode}
</g>
);
}
return (
<g className="shape">
{highlighted && hightlightNode}
<rect className="border" stroke={color} {...rectProps(0.5)} />
<rect className="shadow" {...rectProps(0.45)} />
<circle className="node" r={Math.max(2, (size * 0.125))} />
</g>
);
}
@@ -0,0 +1,34 @@
import React from 'react';
import _ from 'lodash';
function dissoc(obj, key) {
const newObj = _.clone(obj);
delete newObj[key];
return newObj;
}
export default function NodeShapeStack(props) {
const propsNoHighlight = dissoc(props, 'highlighted');
const propsOnlyHighlight = Object.assign({}, props, {onlyHighlight: true});
const Shape = props.shape;
const [dx, dy] = [0, 4];
const dsx = (props.size * 2 + (dx * 2)) / (props.size * 2);
const dsy = (props.size * 2 + (dy * 2)) / (props.size * 2);
const hls = [dsx, dsy];
return (
<g transform={`translate(${dx * -1}, ${dy * -1})`} className="stack">
<g transform={`scale(${hls})translate(${dx}, ${dy}) `} className="stack">
<Shape {...propsOnlyHighlight} />
</g>
<g transform={`translate(${dx * 2}, ${dy * 2})`}>
<Shape {...propsNoHighlight} />
</g>
<g transform={`translate(${dx * 1}, ${dy * 1})`}>
<Shape {...propsNoHighlight} />
</g>
<Shape {...propsNoHighlight} />
</g>
);
}
+36 -4
View File
@@ -5,6 +5,35 @@ import { Motion, spring } from 'react-motion';
import { clickNode, enterNode, leaveNode } from '../actions/app-actions';
import { getNodeColor } from '../utils/color-utils';
import NodeShapeCircle from './node-shape-circle';
import NodeShapeStack from './node-shape-stack';
import NodeShapeRoundedSquare from './node-shape-rounded-square';
import NodeShapeHex from './node-shape-hex';
import NodeShapeCloud from './node-shape-cloud';
function stackedShape(Shape) {
const factory = React.createFactory(NodeShapeStack);
return function(props) {
return factory(Object.assign({}, props, {shape: Shape}));
};
}
const nodeShapes = {
'circle': NodeShapeCircle,
'hexagon': NodeShapeHex,
'square': NodeShapeRoundedSquare,
'cloud': NodeShapeCloud
};
function getNodeShape({shape, stack}) {
const nodeShape = nodeShapes[shape];
if (!nodeShape) {
throw new Error(`Unkown shape: ${shape}!`);
}
return stack ? stackedShape(nodeShape) : nodeShape;
}
export default class Node extends React.Component {
constructor(props, context) {
super(props, context);
@@ -52,8 +81,11 @@ export default class Node extends React.Component {
if (this.props.pseudo) {
classNames.push('pseudo');
}
const classes = classNames.join(' ');
const NodeShapeType = getNodeShape(this.props);
return (
<Motion style={{
x: spring(this.props.dx, animConfig),
@@ -69,10 +101,10 @@ export default class Node extends React.Component {
return (
<g className={classes} transform={transform} id={props.id}
onClick={onMouseClick} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
{props.highlighted && <circle r={nodeScale(0.7 * interpolated.f)} className="highlighted"></circle>}
<circle r={nodeScale(0.5 * interpolated.f)} className="border" stroke={color}></circle>
<circle r={nodeScale(0.45 * interpolated.f)} className="shadow"></circle>
<circle r={Math.max(2, nodeScale(0.125 * interpolated.f))} className="node"></circle>
<NodeShapeType
size={nodeScale(interpolated.f)}
color={color}
{...props} />
<text className="node-label" textAnchor="middle" style={{fontSize: interpolated.labelFontSize}}
x="0" y={interpolated.labelOffsetY + nodeScale(0.5 * interpolated.f)}>
{label}
+7
View File
@@ -142,11 +142,15 @@ export default class NodesChart extends React.Component {
blurred={node.get('blurred')}
focused={node.get('focused')}
highlighted={node.get('highlighted')}
topologyId={this.props.topologyId}
shape={node.get('shape')}
stack={node.get('stack')}
onClick={onNodeClick}
key={node.get('id')}
id={node.get('id')}
label={node.get('label')}
pseudo={node.get('pseudo')}
nodeCount={node.get('nodeCount')}
subLabel={node.get('subLabel')}
rank={node.get('rank')}
selectedNodeScale={selectedNodeScale}
@@ -243,7 +247,10 @@ export default class NodesChart extends React.Component {
label: node.get('label_major'),
pseudo: node.get('pseudo'),
subLabel: node.get('label_minor'),
nodeCount: node.get('node_count'),
rank: node.get('rank'),
shape: node.get('shape'),
stack: node.get('stack'),
x: 0,
y: 0
});
+28 -2
View File
@@ -7,15 +7,21 @@ const log = debug('scope:debug-panel');
import { receiveNodesDelta } from '../actions/app-actions';
import AppStore from '../stores/app-store';
const SHAPES = ['circle', 'hexagon', 'square'];
const NODE_COUNTS = [1, 2, 3];
const STACK_VARIANTS = [true, false];
const sample = function(collection) {
return _.range(_.random(4)).map(() => _.sample(collection));
};
const deltaAdd = function(name, adjacency = []) {
const deltaAdd = function(name, adjacency = [], shape = 'circle', stack = false, nodeCount = 1) {
return {
'adjacency': adjacency,
'controls': {},
'shape': shape,
'stack': stack,
'node_count': nodeCount,
'id': name,
'label_major': name,
'label_minor': 'weave-1',
@@ -26,6 +32,21 @@ const deltaAdd = function(name, adjacency = []) {
};
};
function addAllVariants() {
const newNodes = _.flattenDeep(SHAPES.map(s => {
return STACK_VARIANTS.map(stack => {
if (!stack) return [deltaAdd([s, 1, stack].join('-'), [], s, stack, 1)];
return NODE_COUNTS.map(n => {
return deltaAdd([s, n, stack].join('-'), [], s, stack, n);
});
});
}));
receiveNodesDelta({
add: newNodes
});
}
function addNodes(n) {
const ns = AppStore.getNodes();
const nodeNames = ns.keySeq().toJS();
@@ -33,7 +54,11 @@ function addNodes(n) {
const allNodes = _(nodeNames).concat(newNodeNames).value();
receiveNodesDelta({
add: newNodeNames.map((name) => deltaAdd(name, sample(allNodes)))
add: newNodeNames.map((name) => deltaAdd(name,
sample(allNodes)),
_.sample(SHAPES),
_.sample(STACK_VARIANTS),
_.sample(NODE_COUNTS))
});
}
@@ -65,6 +90,7 @@ export class DebugToolbar extends React.Component {
<button onClick={() => addNodes(10)}>+10</button>
<input type="number" onChange={this.onChange} value={this.state.nodesToAdd} />
<button onClick={() => addNodes(this.state.nodesToAdd)}>+</button>
<button onClick={() => addAllVariants()}>Variants</button>
</div>
);
}
+3
View File
@@ -39,8 +39,11 @@ function makeNode(node) {
id: node.id,
label_major: node.label_major,
label_minor: node.label_minor,
node_count: node.node_count,
rank: node.rank,
pseudo: node.pseudo,
stack: node.stack,
shape: node.shape,
adjacency: node.adjacency
};
}
+23 -18
View File
@@ -303,6 +303,10 @@ h2 {
}
}
g.stack g.shape .border {
stroke-width: @node-border-stroke-width - 1;
}
g.node {
cursor: pointer;
transition: opacity .5s ease-in-out;
@@ -357,28 +361,29 @@ h2 {
}
circle.border {
stroke-width: @node-border-stroke-width;
fill: none;
}
.shape {
.border {
stroke-width: @node-border-stroke-width;
fill: @background-color;
}
circle.shadow {
stroke: none;
fill: @background-lighter-color;
}
.shadow {
stroke: none;
fill: @background-lighter-color;
}
circle.node {
fill: @text-color;
}
.node {
fill: @text-color;
}
circle.highlighted {
fill: @weave-blue;
fill-opacity: @node-highlight-fill-opacity;
stroke: @weave-blue;
stroke-width: @node-highlight-stroke-width;
stroke-opacity: @node-highlight-stroke-opacity;
.highlighted {
fill: @weave-blue;
fill-opacity: @node-highlight-fill-opacity;
stroke: @weave-blue;
stroke-width: @node-highlight-stroke-width;
stroke-opacity: @node-highlight-stroke-opacity;
}
}
}
.details {
+44
View File
@@ -10,6 +10,12 @@ import (
// Exported for testing.
var (
circle = "circle"
square = "square"
pentagon = "pentagon"
hexagon = "hexagon"
cloud = "cloud"
uncontainedServerID = render.MakePseudoNodeID(render.UncontainedID, fixture.ServerHostName)
unknownPseudoNode1ID = render.MakePseudoNodeID("10.10.10.10", fixture.ServerIP, "80")
unknownPseudoNode2ID = render.MakePseudoNodeID("10.10.10.11", fixture.ServerIP, "80")
@@ -18,6 +24,7 @@ var (
ID: unknownPseudoNode1ID,
LabelMajor: "10.10.10.10",
Pseudo: true,
Shape: circle,
Node: report.MakeNode().WithAdjacent(adjacent),
EdgeMetadata: report.EdgeMetadata{
EgressPacketCount: newu64(70),
@@ -30,6 +37,7 @@ var (
ID: unknownPseudoNode2ID,
LabelMajor: "10.10.10.11",
Pseudo: true,
Shape: circle,
Node: report.MakeNode().WithAdjacent(adjacent),
EdgeMetadata: report.EdgeMetadata{
EgressPacketCount: newu64(50),
@@ -42,6 +50,7 @@ var (
ID: render.TheInternetID,
LabelMajor: render.TheInternetMajor,
Pseudo: true,
Shape: cloud,
Node: report.MakeNode().WithAdjacent(adjacent),
EdgeMetadata: report.EdgeMetadata{
EgressPacketCount: newu64(60),
@@ -61,6 +70,7 @@ var (
LabelMinor: fmt.Sprintf("%s (%s)", fixture.ClientHostID, fixture.Client1PID),
Rank: fixture.Client1Name,
Pseudo: false,
Shape: square,
Node: report.MakeNode().WithAdjacent(ServerProcessID),
EdgeMetadata: report.EdgeMetadata{
EgressPacketCount: newu64(10),
@@ -73,6 +83,7 @@ var (
LabelMinor: fmt.Sprintf("%s (%s)", fixture.ClientHostID, fixture.Client2PID),
Rank: fixture.Client2Name,
Pseudo: false,
Shape: square,
Node: report.MakeNode().WithAdjacent(ServerProcessID),
EdgeMetadata: report.EdgeMetadata{
EgressPacketCount: newu64(20),
@@ -85,6 +96,7 @@ var (
LabelMinor: fmt.Sprintf("%s (%s)", fixture.ServerHostID, fixture.ServerPID),
Rank: fixture.ServerName,
Pseudo: false,
Shape: square,
Node: report.MakeNode(),
EdgeMetadata: report.EdgeMetadata{
IngressPacketCount: newu64(210),
@@ -97,6 +109,7 @@ var (
LabelMinor: fmt.Sprintf("%s (%s)", fixture.ServerHostID, fixture.NonContainerPID),
Rank: fixture.NonContainerName,
Pseudo: false,
Shape: square,
Node: report.MakeNode().WithAdjacent(render.TheInternetID),
EdgeMetadata: report.EdgeMetadata{},
},
@@ -116,6 +129,8 @@ var (
LabelMinor: "2 processes",
Rank: fixture.Client1Name,
Pseudo: false,
Shape: square,
Stack: true,
Children: report.MakeNodeSet(
fixture.Report.Process.Nodes[fixture.ClientProcess1NodeID],
fixture.Report.Process.Nodes[fixture.ClientProcess2NodeID],
@@ -132,6 +147,8 @@ var (
LabelMinor: "1 process",
Rank: fixture.ServerName,
Pseudo: false,
Shape: square,
Stack: true,
Children: report.MakeNodeSet(
fixture.Report.Process.Nodes[fixture.ServerProcessNodeID],
),
@@ -147,6 +164,8 @@ var (
LabelMinor: "1 process",
Rank: fixture.NonContainerName,
Pseudo: false,
Shape: square,
Stack: true,
Children: report.MakeNodeSet(
fixture.Report.Process.Nodes[fixture.NonContainerProcessNodeID],
),
@@ -168,6 +187,7 @@ var (
LabelMinor: fixture.ClientHostName,
Rank: fixture.ClientContainerImageName,
Pseudo: false,
Shape: hexagon,
Children: report.MakeNodeSet(
fixture.Report.Process.Nodes[fixture.ClientProcess1NodeID],
fixture.Report.Process.Nodes[fixture.ClientProcess2NodeID],
@@ -185,6 +205,7 @@ var (
LabelMinor: fixture.ServerHostName,
Rank: fixture.ServerContainerImageName,
Pseudo: false,
Shape: hexagon,
Children: report.MakeNodeSet(
fixture.Report.Process.Nodes[fixture.ServerProcessNodeID],
),
@@ -201,6 +222,8 @@ var (
LabelMinor: fixture.ServerHostName,
Rank: "",
Pseudo: true,
Shape: square,
Stack: true,
Children: report.MakeNodeSet(
fixture.Report.Process.Nodes[fixture.NonContainerProcessNodeID],
),
@@ -220,6 +243,8 @@ var (
LabelMinor: "1 container",
Rank: fixture.ClientContainerImageName,
Pseudo: false,
Shape: hexagon,
Stack: true,
Children: report.MakeNodeSet(
fixture.Report.Process.Nodes[fixture.ClientProcess1NodeID],
fixture.Report.Process.Nodes[fixture.ClientProcess2NodeID],
@@ -237,6 +262,8 @@ var (
LabelMinor: "1 container",
Rank: fixture.ServerContainerImageName,
Pseudo: false,
Shape: hexagon,
Stack: true,
Children: report.MakeNodeSet(
fixture.Report.Process.Nodes[fixture.ServerProcessNodeID],
fixture.Report.Container.Nodes[fixture.ServerContainerNodeID],
@@ -253,6 +280,8 @@ var (
LabelMinor: fixture.ServerHostName,
Rank: "",
Pseudo: true,
Shape: square,
Stack: true,
Children: report.MakeNodeSet(
fixture.Report.Process.Nodes[fixture.NonContainerProcessNodeID],
),
@@ -274,6 +303,7 @@ var (
LabelMinor: "hostname.com", // after first .
Rank: "hostname.com",
Pseudo: false,
Shape: circle,
Children: report.MakeNodeSet(
fixture.Report.Container.Nodes[fixture.ServerContainerNodeID],
fixture.Report.Container.Nodes[fixture.ServerProcessNodeID],
@@ -291,6 +321,7 @@ var (
LabelMinor: "hostname.com", // after first .
Rank: "hostname.com",
Pseudo: false,
Shape: circle,
Children: report.MakeNodeSet(
fixture.Report.Container.Nodes[fixture.ClientContainerNodeID],
fixture.Report.Process.Nodes[fixture.ClientProcess1NodeID],
@@ -307,6 +338,7 @@ var (
ID: pseudoHostID1,
LabelMajor: fixture.UnknownClient1IP,
Pseudo: true,
Shape: circle,
Node: report.MakeNode().WithAdjacent(ServerHostRenderedID),
EdgeMetadata: report.EdgeMetadata{},
Children: report.MakeNodeSet(
@@ -318,6 +350,7 @@ var (
ID: pseudoHostID2,
LabelMajor: fixture.UnknownClient3IP,
Pseudo: true,
Shape: circle,
Node: report.MakeNode().WithAdjacent(ServerHostRenderedID),
EdgeMetadata: report.EdgeMetadata{},
},
@@ -325,6 +358,7 @@ var (
ID: render.TheInternetID,
LabelMajor: render.TheInternetMajor,
Pseudo: true,
Shape: cloud,
Node: report.MakeNode().WithAdjacent(ServerHostRenderedID),
EdgeMetadata: report.EdgeMetadata{},
},
@@ -340,6 +374,7 @@ var (
LabelMinor: "1 container",
Rank: "ping/pong-a",
Pseudo: false,
Shape: pentagon,
Children: report.MakeNodeSet(
fixture.Report.Process.Nodes[fixture.ClientProcess1NodeID],
fixture.Report.Process.Nodes[fixture.ClientProcess2NodeID],
@@ -359,6 +394,7 @@ var (
LabelMinor: "1 container",
Rank: "ping/pong-b",
Pseudo: false,
Shape: pentagon,
Children: report.MakeNodeSet(
fixture.Report.Process.Nodes[fixture.ServerProcessNodeID],
fixture.Report.Container.Nodes[fixture.ServerContainerNodeID],
@@ -377,6 +413,8 @@ var (
LabelMinor: fixture.ServerHostName,
Rank: "",
Pseudo: true,
Shape: square,
Stack: true,
Children: report.MakeNodeSet(
fixture.Report.Process.Nodes[fixture.NonContainerProcessNodeID],
),
@@ -387,6 +425,7 @@ var (
ID: render.TheInternetID,
LabelMajor: render.TheInternetMajor,
Pseudo: true,
Shape: cloud,
Node: report.MakeNode().WithAdjacent(ServerPodRenderedID),
EdgeMetadata: report.EdgeMetadata{
EgressPacketCount: newu64(60),
@@ -404,6 +443,8 @@ var (
LabelMinor: "2 pods",
Rank: fixture.ServiceID,
Pseudo: false,
Shape: pentagon,
Stack: true,
Children: report.MakeNodeSet(
fixture.Report.Process.Nodes[fixture.ClientProcess1NodeID],
fixture.Report.Process.Nodes[fixture.ClientProcess2NodeID],
@@ -429,6 +470,8 @@ var (
LabelMinor: fixture.ServerHostName,
Rank: "",
Pseudo: true,
Shape: square,
Stack: true,
Children: report.MakeNodeSet(
fixture.Report.Process.Nodes[fixture.NonContainerProcessNodeID],
),
@@ -439,6 +482,7 @@ var (
ID: render.TheInternetID,
LabelMajor: render.TheInternetMajor,
Pseudo: true,
Shape: cloud,
Node: report.MakeNode().WithAdjacent(ServiceRenderedID),
EdgeMetadata: report.EdgeMetadata{
EgressPacketCount: newu64(60),
+48 -15
View File
@@ -39,6 +39,12 @@ const (
// If the output is empty, the node shall be omitted from the rendered topology.
type MapFunc func(RenderableNode, report.Networks) RenderableNodes
func theInternetNode(m RenderableNode) RenderableNode {
r := newDerivedPseudoNode(TheInternetID, TheInternetMajor, m)
r.Shape = Cloud
return r
}
// MapEndpointIdentity maps an endpoint topology node to a single endpoint
// renderable node. As it is only ever run on endpoint topology nodes, we
// expect that certain keys are present.
@@ -64,7 +70,7 @@ func MapEndpointIdentity(m RenderableNode, local report.Networks) RenderableNode
// If the dstNodeAddr is not in a network local to this report, we emit an
// internet node
if ip := net.ParseIP(addr); ip != nil && !local.Contains(ip) {
return RenderableNodes{TheInternetID: newDerivedPseudoNode(TheInternetID, TheInternetMajor, m)}
return RenderableNodes{TheInternetID: theInternetNode(m)}
}
// We are a 'client' pseudo node if the port is in the ephemeral port range.
@@ -117,7 +123,9 @@ func MapProcessIdentity(m RenderableNode, _ report.Networks) RenderableNodes {
rank, _ = m.Latest.Lookup(process.Name)
)
return RenderableNodes{id: NewRenderableNodeWith(id, major, minor, rank, m)}
node := NewRenderableNodeWith(id, major, minor, rank, m)
node.Shape = Square
return RenderableNodes{id: node}
}
// MapContainerIdentity maps a container topology node to a container
@@ -138,6 +146,7 @@ func MapContainerIdentity(m RenderableNode, _ report.Networks) RenderableNodes {
node := NewRenderableNodeWith(id, major, minor, rank, m)
node.ControlNode = m.ID
node.Shape = Hexagon
return RenderableNodes{id: node}
}
@@ -180,7 +189,10 @@ func MapContainerImageIdentity(m RenderableNode, _ report.Networks) RenderableNo
rank = imageID
)
return RenderableNodes{id: NewRenderableNodeWith(id, major, "", rank, m)}
node := NewRenderableNodeWith(id, major, "", rank, m)
node.Shape = Hexagon
node.Stack = true
return RenderableNodes{id: node}
}
// MapPodIdentity maps a pod topology node to pod renderable node. As it is
@@ -198,7 +210,9 @@ func MapPodIdentity(m RenderableNode, _ report.Networks) RenderableNodes {
rank, _ = m.Latest.Lookup(kubernetes.PodID)
)
return RenderableNodes{id: NewRenderableNodeWith(id, major, "", rank, m)}
node := NewRenderableNodeWith(id, major, "", rank, m)
node.Shape = Pentagon
return RenderableNodes{id: node}
}
// MapServiceIdentity maps a service topology node to service renderable node. As it is
@@ -216,7 +230,10 @@ func MapServiceIdentity(m RenderableNode, _ report.Networks) RenderableNodes {
rank, _ = m.Latest.Lookup(kubernetes.ServiceID)
)
return RenderableNodes{id: NewRenderableNodeWith(id, major, "", rank, m)}
node := NewRenderableNodeWith(id, major, "", rank, m)
node.Shape = Pentagon
node.Stack = true
return RenderableNodes{id: node}
}
// MapAddressIdentity maps an address topology node to an address renderable
@@ -242,7 +259,7 @@ func MapAddressIdentity(m RenderableNode, local report.Networks) RenderableNodes
// If the addr is not in a network local to this report, we emit an
// internet node
if !local.Contains(net.ParseIP(addr)) {
return RenderableNodes{TheInternetID: newDerivedPseudoNode(TheInternetID, TheInternetMajor, m)}
return RenderableNodes{TheInternetID: theInternetNode(m)}
}
// Otherwise generate a pseudo node for every
@@ -281,7 +298,9 @@ func MapHostIdentity(m RenderableNode, _ report.Networks) RenderableNodes {
major = hostname
}
return RenderableNodes{id: NewRenderableNodeWith(id, major, minor, rank, m)}
node := NewRenderableNodeWith(id, major, minor, rank, m)
node.Shape = Circle
return RenderableNodes{id: node}
}
// MapEndpoint2IP maps endpoint nodes to their IP address, for joining
@@ -299,7 +318,7 @@ func MapEndpoint2IP(m RenderableNode, local report.Networks) RenderableNodes {
return RenderableNodes{}
}
if ip := net.ParseIP(addr); ip != nil && !local.Contains(ip) {
return RenderableNodes{TheInternetID: newDerivedPseudoNode(TheInternetID, TheInternetMajor, m)}
return RenderableNodes{TheInternetID: theInternetNode(m)}
}
// We don't always know what port a container is listening on, and
@@ -376,8 +395,9 @@ func MapIP2Container(n RenderableNode, _ report.Networks) RenderableNodes {
}
id := MakeContainerID(containerID)
return RenderableNodes{id: NewDerivedNode(id, n.WithParents(report.EmptySets))}
node := NewDerivedNode(id, n.WithParents(report.EmptySets))
node.Shape = Hexagon
return RenderableNodes{id: node}
}
// MapEndpoint2Process maps endpoint RenderableNodes to process
@@ -402,7 +422,9 @@ func MapEndpoint2Process(n RenderableNode, _ report.Networks) RenderableNodes {
}
id := MakeProcessID(report.ExtractHostID(n.Node), pid)
return RenderableNodes{id: NewDerivedNode(id, n.WithParents(report.EmptySets))}
node := NewDerivedNode(id, n.WithParents(report.EmptySets))
node.Shape = Square
return RenderableNodes{id: node}
}
// MapProcess2Container maps process RenderableNodes to container
@@ -440,12 +462,15 @@ func MapProcess2Container(n RenderableNode, _ report.Networks) RenderableNodes {
if containerID, ok := n.Node.Latest.Lookup(docker.ContainerID); ok {
id = MakeContainerID(containerID)
node = NewDerivedNode(id, n)
node.Shape = Hexagon
} else {
nCopy := n.Copy()
nCopy.Node = nCopy.Node.WithID("").WithTopology("") // Wipe the ID so it cannot be rendered.
id = MakePseudoNodeID(UncontainedID, hostID)
node = newDerivedPseudoNode(id, UncontainedMajor, nCopy)
node.LabelMinor = hostID
node.Shape = Square
node.Stack = true
}
node.Children = node.Children.Add(n.Node)
@@ -475,6 +500,8 @@ func MapProcess2Name(n RenderableNode, _ report.Networks) RenderableNodes {
node.Node.Topology = "process_name"
node.Node.ID = name
node.Children = node.Children.Add(n.Node)
node.Shape = Square
node.Stack = true
return RenderableNodes{name: node}
}
@@ -529,6 +556,8 @@ func MapContainer2ContainerImage(n RenderableNode, _ report.Networks) Renderable
result.Node.Topology = "container_image"
result.Node.ID = report.MakeContainerImageNodeID(imageID)
result.Shape = Hexagon
result.Stack = true
return RenderableNodes{id: result}
}
@@ -561,6 +590,8 @@ func MapPod2Service(n RenderableNode, _ report.Networks) RenderableNodes {
n := NewDerivedNode(id, n.WithParents(report.EmptySets))
n.Node.Counters = n.Node.Counters.Add(podsKey, 1)
n.Children = n.Children.Add(n.Node)
n.Shape = Pentagon
n.Stack = true
result[id] = n
}
return result
@@ -600,6 +631,8 @@ func MapContainerImage2Name(n RenderableNode, _ report.Networks) RenderableNodes
node.LabelMajor = name
node.Rank = name
node.Node = n.Node.Copy() // Propagate NMD for container counting.
node.Shape = Hexagon
node.Stack = true
return RenderableNodes{id: node}
}
@@ -624,6 +657,7 @@ func MapX2Host(n RenderableNode, _ report.Networks) RenderableNodes {
id := MakeHostID(report.ExtractHostID(n.Node))
result := NewDerivedNode(id, n.WithParents(report.EmptySets))
result.Children = result.Children.Add(n.Node)
result.Shape = Circle
return RenderableNodes{id: result}
}
@@ -669,7 +703,7 @@ func MapContainer2Pod(n RenderableNode, _ report.Networks) RenderableNodes {
}
result.Children = result.Children.Add(n.Node)
result.Shape = Pentagon
return RenderableNodes{id: result}
}
@@ -693,12 +727,11 @@ func MapContainer2Hostname(n RenderableNode, _ report.Networks) RenderableNodes
// Add container id key to the counters, which will later be counted to produce the minor label
result.Counters = result.Counters.Add(ContainersKey, 1)
result.Node.Topology = "container_hostname"
result.Node.ID = id
result.Children = result.Children.Add(n.Node)
result.Shape = Hexagon
result.Stack = true
return RenderableNodes{id: result}
}
+18
View File
@@ -15,11 +15,22 @@ type RenderableNode struct {
Pseudo bool `json:"pseudo,omitempty"` // sort-of a placeholder node, for rendering purposes
Children report.NodeSet `json:"children,omitempty"` // Nodes which have been grouped into this one
ControlNode string `json:"-"` // ID of node from which to show the controls in the UI
Shape string `json:"shape"` // Shape node should be rendered as
Stack bool `json:"stack"` // Should UI render this node as a stack?
report.EdgeMetadata `json:"metadata"` // Numeric sums
report.Node
}
// Shapes that are allowed
const (
Circle = "circle"
Square = "square"
Pentagon = "pentagon"
Hexagon = "hexagon"
Cloud = "cloud"
)
// NewRenderableNode makes a new RenderableNode
func NewRenderableNode(id string) RenderableNode {
return RenderableNode{
@@ -30,6 +41,7 @@ func NewRenderableNode(id string) RenderableNode {
Pseudo: false,
EdgeMetadata: report.EdgeMetadata{},
Node: report.MakeNode(),
Shape: Circle,
}
}
@@ -44,6 +56,7 @@ func NewRenderableNodeWith(id, major, minor, rank string, node RenderableNode) R
Children: node.Children.Copy(),
EdgeMetadata: node.EdgeMetadata.Copy(),
Node: node.Node.Copy(),
Shape: Circle,
}
}
@@ -59,6 +72,7 @@ func NewDerivedNode(id string, node RenderableNode) RenderableNode {
EdgeMetadata: node.EdgeMetadata.Copy(),
Node: node.Node.Copy(),
ControlNode: "", // Do not propagate ControlNode when making a derived node!
Shape: Circle,
}
}
@@ -72,6 +86,7 @@ func newDerivedPseudoNode(id, major string, node RenderableNode) RenderableNode
Children: node.Children.Copy(),
EdgeMetadata: node.EdgeMetadata.Copy(),
Node: node.Node.Copy(),
Shape: Circle,
}
}
@@ -113,6 +128,7 @@ func (rn RenderableNode) Merge(other RenderableNode) RenderableNode {
panic(result.ID)
}
result.Stack = result.Stack || rn.Stack
result.Children = rn.Children.Merge(other.Children)
result.EdgeMetadata = rn.EdgeMetadata.Merge(other.EdgeMetadata)
result.Node = rn.Node.Merge(other.Node)
@@ -132,6 +148,8 @@ func (rn RenderableNode) Copy() RenderableNode {
EdgeMetadata: rn.EdgeMetadata.Copy(),
Node: rn.Node.Copy(),
ControlNode: rn.ControlNode,
Shape: rn.Shape,
Stack: rn.Stack,
}
}
+2
View File
@@ -74,6 +74,7 @@ var (
ID: render.TheInternetID,
LabelMajor: render.TheInternetMajor,
Pseudo: true,
Shape: "cloud",
Node: report.MakeNode().WithAdjacent(render.MakeContainerID(containerID)),
},
render.MakeContainerID(containerID): {
@@ -82,6 +83,7 @@ var (
LabelMinor: serverHostID,
Rank: "",
Pseudo: false,
Shape: "hexagon",
Node: report.MakeNode(),
ControlNode: containerNodeID,
},
+1 -1
View File
@@ -148,7 +148,7 @@ function lint_files {
function list_files {
if [ $# -gt 0 ]; then
find "$@" -type f | grep -vE '(^\./\.git|^\./\.pkg|/vendor/)'
find "$@" -type f | grep -vE '(^\./\.git|^\./\.pkg|/vendor/|/client/node_modules/)'
else
git diff --cached --name-only
fi