diff --git a/client/app/scripts/charts/node-shape-circle.js b/client/app/scripts/charts/node-shape-circle.js
new file mode 100644
index 000000000..e4c32ade8
--- /dev/null
+++ b/client/app/scripts/charts/node-shape-circle.js
@@ -0,0 +1,22 @@
+import React from 'react';
+
+export default function NodeShapeCircle({onlyHighlight, highlighted, size, color}) {
+ const hightlightNode = ;
+
+ if (onlyHighlight) {
+ return (
+
+ {highlighted && hightlightNode}
+
+ );
+ }
+
+ return (
+
+ {highlighted && hightlightNode}
+
+
+
+
+ );
+}
diff --git a/client/app/scripts/charts/node-shape-cloud.js b/client/app/scripts/charts/node-shape-cloud.js
new file mode 100644
index 000000000..770fc588b
--- /dev/null
+++ b/client/app/scripts/charts/node-shape-cloud.js
@@ -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 (
+
+ {highlighted &&
+ }
+
+
+
+
+ );
+}
diff --git a/client/app/scripts/charts/node-shape-hex.js b/client/app/scripts/charts/node-shape-hex.js
new file mode 100644
index 000000000..71b351b10
--- /dev/null
+++ b/client/app/scripts/charts/node-shape-hex.js
@@ -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 = ;
+
+ if (onlyHighlight) {
+ return (
+
+ {highlighted && hightlightNode}
+
+ );
+ }
+
+ return (
+
+ {highlighted && hightlightNode}
+
+
+
+
+ );
+}
diff --git a/client/app/scripts/charts/node-shape-rounded-square.js b/client/app/scripts/charts/node-shape-rounded-square.js
new file mode 100644
index 000000000..4542595b7
--- /dev/null
+++ b/client/app/scripts/charts/node-shape-rounded-square.js
@@ -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 (
+
+ );
+}
diff --git a/client/app/scripts/charts/node-shape-square.js b/client/app/scripts/charts/node-shape-square.js
new file mode 100644
index 000000000..ea1c1507e
--- /dev/null
+++ b/client/app/scripts/charts/node-shape-square.js
@@ -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 = ;
+
+ if (onlyHighlight) {
+ return (
+
+ {highlighted && hightlightNode}
+
+ );
+ }
+
+ return (
+
+ {highlighted && hightlightNode}
+
+
+
+
+ );
+}
diff --git a/client/app/scripts/charts/node-shape-stack.js b/client/app/scripts/charts/node-shape-stack.js
new file mode 100644
index 000000000..70d8ff5b9
--- /dev/null
+++ b/client/app/scripts/charts/node-shape-stack.js
@@ -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 (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/client/app/scripts/charts/node.js b/client/app/scripts/charts/node.js
index f78f9f4ab..13c24b402 100644
--- a/client/app/scripts/charts/node.js
+++ b/client/app/scripts/charts/node.js
@@ -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 (
- {props.highlighted && }
-
-
-
+
{label}
diff --git a/client/app/scripts/charts/nodes-chart.js b/client/app/scripts/charts/nodes-chart.js
index adb804830..91030f316 100644
--- a/client/app/scripts/charts/nodes-chart.js
+++ b/client/app/scripts/charts/nodes-chart.js
@@ -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
});
diff --git a/client/app/scripts/components/debug-toolbar.js b/client/app/scripts/components/debug-toolbar.js
index 1d11e62fe..88853b0dc 100644
--- a/client/app/scripts/components/debug-toolbar.js
+++ b/client/app/scripts/components/debug-toolbar.js
@@ -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 {
+
);
}
diff --git a/client/app/scripts/stores/app-store.js b/client/app/scripts/stores/app-store.js
index 58c261b66..fa6b4036d 100644
--- a/client/app/scripts/stores/app-store.js
+++ b/client/app/scripts/stores/app-store.js
@@ -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
};
}
diff --git a/client/app/styles/main.less b/client/app/styles/main.less
index bafac4127..62dffa8b9 100644
--- a/client/app/styles/main.less
+++ b/client/app/styles/main.less
@@ -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 {
diff --git a/render/expected/expected.go b/render/expected/expected.go
index b4946d7a9..b06079e7b 100644
--- a/render/expected/expected.go
+++ b/render/expected/expected.go
@@ -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),
diff --git a/render/mapping.go b/render/mapping.go
index a94f95069..be6526634 100644
--- a/render/mapping.go
+++ b/render/mapping.go
@@ -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}
}
diff --git a/render/renderable_node.go b/render/renderable_node.go
index e79657137..c35357cd8 100644
--- a/render/renderable_node.go
+++ b/render/renderable_node.go
@@ -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,
}
}
diff --git a/render/short_lived_connections_test.go b/render/short_lived_connections_test.go
index 1ac2f08fc..e3efd476b 100644
--- a/render/short_lived_connections_test.go
+++ b/render/short_lived_connections_test.go
@@ -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,
},
diff --git a/tools/lint b/tools/lint
index 89d534ba8..a2c405797 100755
--- a/tools/lint
+++ b/tools/lint
@@ -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