diff --git a/client/app/scripts/components/node-details.js b/client/app/scripts/components/node-details.js
index 075761532..30b446d21 100644
--- a/client/app/scripts/components/node-details.js
+++ b/client/app/scripts/components/node-details.js
@@ -180,7 +180,8 @@ export default class NodeDetails extends React.Component {
{details.connections && details.connections.map(connections =>
-
+
)}
diff --git a/client/app/scripts/components/node-details/node-details-table-node-link.js b/client/app/scripts/components/node-details/node-details-table-node-link.js
index 001f549fc..b463a7e00 100644
--- a/client/app/scripts/components/node-details/node-details-table-node-link.js
+++ b/client/app/scripts/components/node-details/node-details-table-node-link.js
@@ -14,7 +14,7 @@ export default class NodeDetailsTableNodeLink extends React.Component {
handleClick(ev) {
ev.preventDefault();
- clickRelative(this.props.id, this.props.topologyId, this.props.label,
+ clickRelative(this.props.nodeId, this.props.topologyId, this.props.label,
ReactDOM.findDOMNode(this).getBoundingClientRect());
}
diff --git a/client/app/scripts/components/node-details/node-details-table.js b/client/app/scripts/components/node-details/node-details-table.js
index 406651fd0..a31346783 100644
--- a/client/app/scripts/components/node-details/node-details-table.js
+++ b/client/app/scripts/components/node-details/node-details-table.js
@@ -148,6 +148,7 @@ export default class NodeDetailsTable extends React.Component {
render() {
const headers = this.renderHeaders();
+ const { nodeIdKey } = this.props;
let nodes = _.sortBy(this.props.nodes, this.getValueForSortBy, 'label',
this.getMetaDataSorters());
const limited = nodes && this.state.limit > 0 && nodes.length > this.state.limit;
@@ -169,10 +170,12 @@ export default class NodeDetailsTable extends React.Component {
{nodes && nodes.map(node => {
const values = this.renderValues(node);
+ const nodeId = node[nodeIdKey];
return (
|
-
+
|
{values}
@@ -186,3 +189,7 @@ export default class NodeDetailsTable extends React.Component {
);
}
}
+
+NodeDetailsTable.defaultProps = {
+ nodeIdKey: 'id' // key to identify a node in a row (used for topology links)
+};
diff --git a/render/detailed/connections.go b/render/detailed/connections.go
index e5a9d7978..b7bc7d6c1 100644
--- a/render/detailed/connections.go
+++ b/render/detailed/connections.go
@@ -1,6 +1,7 @@
package detailed
import (
+ "fmt"
"sort"
"strconv"
@@ -29,17 +30,46 @@ var (
}
)
-type connectionsRow struct {
+// ConnectionsSummary is the table of connection to/form a node
+type ConnectionsSummary struct {
+ ID string `json:"id"`
+ TopologyID string `json:"topologyId"`
+ Label string `json:"label"`
+ Columns []Column `json:"columns"`
+ Connections []Connection `json:"connections"`
+}
+
+// Connection is a row in the connections table.
+type Connection struct {
+ ID string `json:"id"` // ID of this element in the UI. Must be unique for a given ConnectionsSummary.
+ NodeID string `json:"nodeId"` // ID of a node in the topology. Optional, must be set if linkable is true.
+ Label string `json:"label"`
+ Linkable bool `json:"linkable"`
+ Metadata []MetadataRow `json:"metadata,omitempty"`
+}
+
+type connectionsByID []Connection
+
+func (s connectionsByID) Len() int { return len(s) }
+func (s connectionsByID) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
+func (s connectionsByID) Less(i, j int) bool { return s[i].ID < s[j].ID }
+
+// Intermediate type used as a key to dedupe rows
+type connection struct {
remoteNode, localNode *report.Node
remoteAddr, localAddr string
port string // always the server-side port
}
-func incomingConnectionsTable(topologyID string, n report.Node, ns report.Nodes) NodeSummaryGroup {
+func (row connection) ID() string {
+ return fmt.Sprintf("%s:%s-%s:%s-%s", row.remoteNode.ID, row.remoteAddr, row.localNode.ID, row.localAddr, row.port)
+}
+
+func incomingConnectionsSummary(topologyID string, n report.Node, ns report.Nodes) ConnectionsSummary {
localEndpointIDs := endpointChildIDsOf(n)
// For each node which has an edge TO me
- counts := map[connectionsRow]int{}
+ counts := map[connection]int{}
for _, node := range ns {
if !node.Adjacency.Contains(n.ID) {
continue
@@ -58,7 +88,7 @@ func incomingConnectionsTable(topologyID string, n report.Node, ns report.Nodes)
if !ok {
continue
}
- key := connectionsRow{
+ key := connection{
localNode: &n,
remoteNode: &remoteNode,
port: port,
@@ -75,20 +105,20 @@ func incomingConnectionsTable(topologyID string, n report.Node, ns report.Nodes)
if isInternetNode(n) {
columnHeaders = InternetColumns
}
- return NodeSummaryGroup{
- ID: "incoming-connections",
- TopologyID: topologyID,
- Label: "Inbound",
- Columns: columnHeaders,
- Nodes: connectionRows(counts, isInternetNode(n)),
+ return ConnectionsSummary{
+ ID: "incoming-connections",
+ TopologyID: topologyID,
+ Label: "Inbound",
+ Columns: columnHeaders,
+ Connections: connectionRows(counts, isInternetNode(n)),
}
}
-func outgoingConnectionsTable(topologyID string, n report.Node, ns report.Nodes) NodeSummaryGroup {
+func outgoingConnectionsSummary(topologyID string, n report.Node, ns report.Nodes) ConnectionsSummary {
localEndpoints := endpointChildrenOf(n)
// For each node which has an edge FROM me
- counts := map[connectionsRow]int{}
+ counts := map[connection]int{}
for _, id := range n.Adjacency {
node, ok := ns[id]
if !ok {
@@ -108,7 +138,7 @@ func outgoingConnectionsTable(topologyID string, n report.Node, ns report.Nodes)
if !ok {
continue
}
- key := connectionsRow{
+ key := connection{
localNode: &n,
remoteNode: &remoteNode,
port: port,
@@ -125,12 +155,12 @@ func outgoingConnectionsTable(topologyID string, n report.Node, ns report.Nodes)
if isInternetNode(n) {
columnHeaders = InternetColumns
}
- return NodeSummaryGroup{
- ID: "outgoing-connections",
- TopologyID: topologyID,
- Label: "Outbound",
- Columns: columnHeaders,
- Nodes: connectionRows(counts, isInternetNode(n)),
+ return ConnectionsSummary{
+ ID: "outgoing-connections",
+ TopologyID: topologyID,
+ Label: "Outbound",
+ Columns: columnHeaders,
+ Connections: connectionRows(counts, isInternetNode(n)),
}
}
@@ -158,30 +188,32 @@ func isInternetNode(n report.Node) bool {
return n.ID == render.IncomingInternetID || n.ID == render.OutgoingInternetID
}
-func connectionRows(in map[connectionsRow]int, includeLocal bool) []NodeSummary {
- nodes := []NodeSummary{}
+func connectionRows(in map[connection]int, includeLocal bool) []Connection {
+ output := []Connection{}
for row, count := range in {
// Use MakeNodeSummary to render the id and label of this node
// TODO(paulbellamy): Would be cleaner if we hade just a
// MakeNodeID(*row.remoteode). As we don't need the whole summary.
summary, ok := MakeNodeSummary(*row.remoteNode)
- summary.Metadata, summary.Metrics, summary.DockerLabels = nil, nil, nil
+ connection := Connection{
+ ID: row.ID(),
+ NodeID: summary.ID,
+ Label: summary.Label,
+ Linkable: true,
+ }
if !ok && row.remoteAddr != "" {
- summary = NodeSummary{
- ID: row.remoteAddr + ":" + row.port,
- Label: row.remoteAddr,
- Linkable: false,
- }
+ connection.Label = row.remoteAddr
+ connection.Linkable = false
}
if includeLocal {
- summary.Metadata = append(summary.Metadata,
+ connection.Metadata = append(connection.Metadata,
MetadataRow{
ID: "foo",
Value: row.localAddr,
Datatype: number,
})
}
- summary.Metadata = append(summary.Metadata,
+ connection.Metadata = append(connection.Metadata,
MetadataRow{
ID: portKey,
Value: row.port,
@@ -193,8 +225,8 @@ func connectionRows(in map[connectionsRow]int, includeLocal bool) []NodeSummary
Datatype: number,
},
)
- nodes = append(nodes, summary)
+ output = append(output, connection)
}
- sort.Sort(nodeSummariesByID(nodes))
- return nodes
+ sort.Sort(connectionsByID(output))
+ return output
}
diff --git a/render/detailed/node.go b/render/detailed/node.go
index 8897f3ea9..19f8f3249 100644
--- a/render/detailed/node.go
+++ b/render/detailed/node.go
@@ -15,10 +15,10 @@ import (
// we want deep information about an individual node.
type Node struct {
NodeSummary
- Controls []ControlInstance `json:"controls"`
- Children []NodeSummaryGroup `json:"children,omitempty"`
- Parents []Parent `json:"parents,omitempty"`
- Connections []NodeSummaryGroup `json:"connections,omitempty"`
+ Controls []ControlInstance `json:"controls"`
+ Children []NodeSummaryGroup `json:"children,omitempty"`
+ Parents []Parent `json:"parents,omitempty"`
+ Connections []ConnectionsSummary `json:"connections,omitempty"`
}
// ControlInstance contains a control description, and all the info
@@ -83,9 +83,9 @@ func MakeNode(topologyID string, r report.Report, ns report.Nodes, n report.Node
Controls: controls(r, n),
Children: children(n),
Parents: Parents(r, n),
- Connections: []NodeSummaryGroup{
- incomingConnectionsTable(topologyID, n, ns),
- outgoingConnectionsTable(topologyID, n, ns),
+ Connections: []ConnectionsSummary{
+ incomingConnectionsSummary(topologyID, n, ns),
+ outgoingConnectionsSummary(topologyID, n, ns),
},
}
}
diff --git a/render/detailed/node_test.go b/render/detailed/node_test.go
index 87fe2685b..4abe29ae0 100644
--- a/render/detailed/node_test.go
+++ b/render/detailed/node_test.go
@@ -1,6 +1,7 @@
package detailed_test
import (
+ "fmt"
"testing"
"github.com/weaveworks/scope/probe/docker"
@@ -113,28 +114,25 @@ func TestMakeDetailedHostNode(t *testing.T) {
Nodes: []detailed.NodeSummary{containerImageNodeSummary},
},
},
- Connections: []detailed.NodeSummaryGroup{
+ Connections: []detailed.ConnectionsSummary{
{
- ID: "incoming-connections",
- TopologyID: "hosts",
- Label: "Inbound",
- Columns: detailed.NormalColumns,
- Nodes: []detailed.NodeSummary{},
+ ID: "incoming-connections",
+ TopologyID: "hosts",
+ Label: "Inbound",
+ Columns: detailed.NormalColumns,
+ Connections: []detailed.Connection{},
},
{
ID: "outgoing-connections",
TopologyID: "hosts",
Label: "Outbound",
Columns: detailed.NormalColumns,
- Nodes: []detailed.NodeSummary{
+ Connections: []detailed.Connection{
{
- ID: fixture.ServerHostNodeID,
- Label: "server",
- LabelMinor: "hostname.com",
- Rank: "hostname.com",
- Shape: "circle",
- Linkable: true,
- Adjacency: report.MakeIDList(render.OutgoingInternetID),
+ ID: fmt.Sprintf("%s:%s-%s:%s-%d", fixture.ServerHostNodeID, "", fixture.ClientHostNodeID, "", 80),
+ NodeID: fixture.ServerHostNodeID,
+ Label: "server",
+ Linkable: true,
Metadata: []detailed.MetadataRow{
{
ID: "port",
@@ -223,20 +221,18 @@ func TestMakeDetailedContainerNode(t *testing.T) {
TopologyID: "hosts",
},
},
- Connections: []detailed.NodeSummaryGroup{
+ Connections: []detailed.ConnectionsSummary{
{
ID: "incoming-connections",
TopologyID: "containers",
Label: "Inbound",
Columns: detailed.NormalColumns,
- Nodes: []detailed.NodeSummary{
+ Connections: []detailed.Connection{
{
- ID: fixture.ClientContainerNodeID,
- Label: "client",
- LabelMinor: "client.hostname.com",
- Shape: "hexagon",
- Linkable: true,
- Adjacency: report.MakeIDList(fixture.ServerContainerNodeID),
+ ID: fmt.Sprintf("%s:%s-%s:%s-%d", fixture.ClientContainerNodeID, "", fixture.ServerContainerNodeID, "", 80),
+ NodeID: fixture.ClientContainerNodeID,
+ Label: "client",
+ Linkable: true,
Metadata: []detailed.MetadataRow{
{
ID: "port",
@@ -251,14 +247,10 @@ func TestMakeDetailedContainerNode(t *testing.T) {
},
},
{
- ID: render.IncomingInternetID,
- Label: render.InboundMajor,
- LabelMinor: render.InboundMinor,
- Rank: render.IncomingInternetID,
- Shape: "cloud",
- Linkable: true,
- Pseudo: true,
- Adjacency: report.MakeIDList(fixture.ServerContainerNodeID),
+ ID: fmt.Sprintf("%s:%s-%s:%s-%d", render.IncomingInternetID, "", fixture.ServerContainerNodeID, "", 80),
+ NodeID: render.IncomingInternetID,
+ Label: render.InboundMajor,
+ Linkable: true,
Metadata: []detailed.MetadataRow{
{
ID: "port",
@@ -275,11 +267,11 @@ func TestMakeDetailedContainerNode(t *testing.T) {
},
},
{
- ID: "outgoing-connections",
- TopologyID: "containers",
- Label: "Outbound",
- Columns: detailed.NormalColumns,
- Nodes: []detailed.NodeSummary{},
+ ID: "outgoing-connections",
+ TopologyID: "containers",
+ Label: "Outbound",
+ Columns: detailed.NormalColumns,
+ Connections: []detailed.Connection{},
},
},
}