diff --git a/client/app/scripts/components/node-details/node-details-labels.js b/client/app/scripts/components/node-details/node-details-labels.js
index 6823f1330..2fd01a482 100644
--- a/client/app/scripts/components/node-details/node-details-labels.js
+++ b/client/app/scripts/components/node-details/node-details-labels.js
@@ -6,7 +6,6 @@ import MatchedText from '../matched-text';
import NodeDetailsControlButton from './node-details-control-button';
import ShowMore from '../show-more';
-
const Controls = controls => (
{sortBy(controls, 'rank').map(control =>
- {field.label}
+ title={field.entries.label} key={field.id}>
+ {field.entries.label}
-
))}
diff --git a/client/app/scripts/utils/__tests__/search-utils-test.js b/client/app/scripts/utils/__tests__/search-utils-test.js
index 301b431a2..1596e3d62 100644
--- a/client/app/scripts/utils/__tests__/search-utils-test.js
+++ b/client/app/scripts/utils/__tests__/search-utils-test.js
@@ -30,9 +30,17 @@ describe('SearchUtils', () => {
tables: [{
id: 'metric1',
rows: [{
- id: 'row1',
- label: 'Row 1',
- value: 'Row Value 1'
+ entries: {
+ id: 'row1',
+ label: 'Row 1',
+ value: 'Row Value 1'
+ }
+ }, {
+ entries: {
+ id: 'row2',
+ label: 'Row 2',
+ value: 'Row Value 2'
+ }
}]
}],
},
diff --git a/client/app/scripts/utils/search-utils.js b/client/app/scripts/utils/search-utils.js
index b8f74d047..c297ae96a 100644
--- a/client/app/scripts/utils/search-utils.js
+++ b/client/app/scripts/utils/search-utils.js
@@ -154,9 +154,10 @@ export function searchTopology(nodes, { prefix, query, metric, comp, value }) {
tables.forEach((table) => {
if (table.get('rows')) {
table.get('rows').forEach((field) => {
+ const entries = field.get('entries');
const keyPath = [nodeId, 'tables', field.get('id')];
- nodeMatches = findNodeMatch(nodeMatches, keyPath, field.get('value'),
- query, prefix, field.get('label'));
+ nodeMatches = findNodeMatch(nodeMatches, keyPath, entries.get('value'),
+ query, prefix, entries.get('label'));
});
}
});
diff --git a/probe/docker/container.go b/probe/docker/container.go
index 50856ee0f..f23ed71fd 100644
--- a/probe/docker/container.go
+++ b/probe/docker/container.go
@@ -379,8 +379,8 @@ func (c *container) getBaseNode() report.Node {
}).WithParents(report.EmptySets.
Add(report.ContainerImage, report.MakeStringSet(report.MakeContainerImageNodeID(c.Image()))),
)
- result = result.AddPrefixTable(LabelPrefix, c.container.Config.Labels)
- result = result.AddPrefixTable(EnvPrefix, c.env())
+ result = result.AddPrefixLabels(LabelPrefix, c.container.Config.Labels)
+ result = result.AddPrefixLabels(EnvPrefix, c.env())
return result
}
diff --git a/probe/docker/reporter.go b/probe/docker/reporter.go
index b1e2eb75c..accd18301 100644
--- a/probe/docker/reporter.go
+++ b/probe/docker/reporter.go
@@ -258,7 +258,7 @@ func (r *Reporter) containerImageTopology() report.Topology {
}
nodeID := report.MakeContainerImageNodeID(imageID)
node := report.MakeNodeWith(nodeID, latests)
- node = node.AddPrefixTable(ImageLabelPrefix, image.Labels)
+ node = node.AddPrefixLabels(ImageLabelPrefix, image.Labels)
result.AddNode(node)
})
diff --git a/probe/kubernetes/meta.go b/probe/kubernetes/meta.go
index c7db23d0c..20fb213bb 100644
--- a/probe/kubernetes/meta.go
+++ b/probe/kubernetes/meta.go
@@ -56,5 +56,5 @@ func (m meta) MetaNode(id string) report.Node {
Name: m.Name(),
Namespace: m.Namespace(),
Created: m.Created(),
- }).AddPrefixTable(LabelPrefix, m.Labels())
+ }).AddPrefixLabels(LabelPrefix, m.Labels())
}
diff --git a/probe/overlay/weave.go b/probe/overlay/weave.go
index 819d88eb5..13e34412c 100644
--- a/probe/overlay/weave.go
+++ b/probe/overlay/weave.go
@@ -19,34 +19,35 @@ import (
// Keys for use in Node
const (
- WeavePeerName = "weave_peer_name"
- WeavePeerNickName = "weave_peer_nick_name"
- WeaveDNSHostname = "weave_dns_hostname"
- WeaveMACAddress = "weave_mac_address"
- WeaveVersion = "weave_version"
- WeaveEncryption = "weave_encryption"
- WeaveProtocol = "weave_protocol"
- WeavePeerDiscovery = "weave_peer_discovery"
- WeaveTargetCount = "weave_target_count"
- WeaveConnectionCount = "weave_connection_count"
- WeavePeerCount = "weave_peer_count"
- WeaveTrustedSubnets = "weave_trusted_subnet_count"
- WeaveIPAMTableID = "weave_ipam_table"
- WeaveIPAMStatus = "weave_ipam_status"
- WeaveIPAMRange = "weave_ipam_range"
- WeaveIPAMDefaultSubnet = "weave_ipam_default_subnet"
- WeaveDNSTableID = "weave_dns_table"
- WeaveDNSDomain = "weave_dns_domain"
- WeaveDNSUpstream = "weave_dns_upstream"
- WeaveDNSTTL = "weave_dns_ttl"
- WeaveDNSEntryCount = "weave_dns_entry_count"
- WeaveProxyTableID = "weave_proxy_table"
- WeaveProxyStatus = "weave_proxy_status"
- WeaveProxyAddress = "weave_proxy_address"
- WeavePluginTableID = "weave_plugin_table"
- WeavePluginStatus = "weave_plugin_status"
- WeavePluginDriver = "weave_plugin_driver"
- WeaveConnectionsTablePrefix = "weave_connections_table_"
+ WeavePeerName = "weave_peer_name"
+ WeavePeerNickName = "weave_peer_nick_name"
+ WeaveDNSHostname = "weave_dns_hostname"
+ WeaveMACAddress = "weave_mac_address"
+ WeaveVersion = "weave_version"
+ WeaveEncryption = "weave_encryption"
+ WeaveProtocol = "weave_protocol"
+ WeavePeerDiscovery = "weave_peer_discovery"
+ WeaveTargetCount = "weave_target_count"
+ WeaveConnectionCount = "weave_connection_count"
+ WeavePeerCount = "weave_peer_count"
+ WeaveTrustedSubnets = "weave_trusted_subnet_count"
+ WeaveIPAMTableID = "weave_ipam_table"
+ WeaveIPAMStatus = "weave_ipam_status"
+ WeaveIPAMRange = "weave_ipam_range"
+ WeaveIPAMDefaultSubnet = "weave_ipam_default_subnet"
+ WeaveDNSTableID = "weave_dns_table"
+ WeaveDNSDomain = "weave_dns_domain"
+ WeaveDNSUpstream = "weave_dns_upstream"
+ WeaveDNSTTL = "weave_dns_ttl"
+ WeaveDNSEntryCount = "weave_dns_entry_count"
+ WeaveProxyTableID = "weave_proxy_table"
+ WeaveProxyStatus = "weave_proxy_status"
+ WeaveProxyAddress = "weave_proxy_address"
+ WeavePluginTableID = "weave_plugin_table"
+ WeavePluginStatus = "weave_plugin_status"
+ WeavePluginDriver = "weave_plugin_driver"
+ WeaveConnectionsTablePrefix = "weave_connections_table_"
+ WeaveConnectionsMulticolumnTablePrefix = "weave_connections_multicolumn_table_"
)
var (
@@ -73,14 +74,20 @@ var (
}
weaveTableTemplates = report.TableTemplates{
- WeaveIPAMTableID: {ID: WeaveIPAMTableID, Label: "IPAM",
+ WeaveIPAMTableID: {
+ ID: WeaveIPAMTableID,
+ Label: "IPAM",
+ Type: report.PropertyListType,
FixedRows: map[string]string{
WeaveIPAMStatus: "Status",
WeaveIPAMRange: "Range",
WeaveIPAMDefaultSubnet: "Default Subnet",
},
},
- WeaveDNSTableID: {ID: WeaveDNSTableID, Label: "DNS",
+ WeaveDNSTableID: {
+ ID: WeaveDNSTableID,
+ Label: "DNS",
+ Type: report.PropertyListType,
FixedRows: map[string]string{
WeaveDNSDomain: "Domain",
WeaveDNSUpstream: "Upstream",
@@ -88,13 +95,19 @@ var (
WeaveDNSEntryCount: "Entries",
},
},
- WeaveProxyTableID: {ID: WeaveProxyTableID, Label: "Proxy",
+ WeaveProxyTableID: {
+ ID: WeaveProxyTableID,
+ Label: "Proxy",
+ Type: report.PropertyListType,
FixedRows: map[string]string{
WeaveProxyStatus: "Status",
WeaveProxyAddress: "Address",
},
},
- WeavePluginTableID: {ID: WeavePluginTableID, Label: "Plugin",
+ WeavePluginTableID: {
+ ID: WeavePluginTableID,
+ Label: "Plugin",
+ Type: report.PropertyListType,
FixedRows: map[string]string{
WeavePluginStatus: "Status",
WeavePluginDriver: "Driver Name",
@@ -103,8 +116,15 @@ var (
WeaveConnectionsTablePrefix: {
ID: WeaveConnectionsTablePrefix,
Label: "Connections",
+ Type: report.PropertyListType,
Prefix: WeaveConnectionsTablePrefix,
},
+ WeaveConnectionsMulticolumnTablePrefix: {
+ ID: WeaveConnectionsMulticolumnTablePrefix,
+ Label: "Connections",
+ Type: report.MulticolumnTableType,
+ Prefix: WeaveConnectionsMulticolumnTablePrefix,
+ },
}
)
@@ -434,28 +454,31 @@ func (w *Weave) addCurrentPeerInfo(latests map[string]string, node report.Node)
latests[WeavePluginStatus] = "running"
latests[WeavePluginDriver] = "weave"
}
- node = node.AddPrefixTable(WeaveConnectionsTablePrefix, getConnectionsTable(w.statusCache.Router))
+ node = node.AddPrefixTable(WeaveConnectionsMulticolumnTablePrefix, getConnectionsTable(w.statusCache.Router))
node = node.WithParents(report.EmptySets.Add(report.Host, report.MakeStringSet(w.hostID)))
return latests, node
}
-func getConnectionsTable(router weave.Router) map[string]string {
+func getConnectionsTable(router weave.Router) []report.Row {
const (
outboundArrow = "->"
inboundArrow = "<-"
)
- table := make(map[string]string, len(router.Connections))
+ table := make([]report.Row, len(router.Connections))
for _, conn := range router.Connections {
arrow := inboundArrow
if conn.Outbound {
arrow = outboundArrow
}
- // TODO: we should probably use a multicolumn table for this
- // but there is no mechanism to support it yet.
- key := fmt.Sprintf("%s %s", arrow, conn.Address)
- value := fmt.Sprintf("%s, %s", conn.State, conn.Info)
- table[key] = value
+ table = append(table, report.Row{
+ ID: conn.Address,
+ Entries: map[string]string{
+ "ip": fmt.Sprintf("%s %s", arrow, conn.Address),
+ "state": conn.State,
+ "info": conn.Info,
+ },
+ })
}
return table
}
diff --git a/render/detailed/tables_test.go b/render/detailed/tables_test.go
index 02f723391..634755c54 100644
--- a/render/detailed/tables_test.go
+++ b/render/detailed/tables_test.go
@@ -35,23 +35,25 @@ func TestNodeTables(t *testing.T) {
{
ID: docker.EnvPrefix,
Label: "Environment Variables",
- Rows: []report.MetadataRow{},
+ Rows: []report.Row{},
},
{
ID: docker.LabelPrefix,
Label: "Docker Labels",
- Rows: []report.MetadataRow{
+ Rows: []report.Row{
{
- ID: "label_label1",
- Label: "label1",
- Value: "label1value",
+ Entries: map[string]string{
+ "id": "label_label1",
+ "label": "label1",
+ "value": "label1value",
+ },
},
},
},
{
ID: docker.ImageTableID,
Label: "Image",
- Rows: []report.MetadataRow{},
+ Rows: []report.Row{},
},
},
},
diff --git a/report/table.go b/report/table.go
index 74cbc1a71..791fe62ea 100644
--- a/report/table.go
+++ b/report/table.go
@@ -15,10 +15,33 @@ import (
const (
MaxTableRows = 20
TruncationCountPrefix = "table_truncation_count_"
+ MulticolumnTableType = "multicolumn-table"
+ PropertyListType = "property-list"
)
-// AddPrefixTable appends arbitrary key-value pairs to the Node, returning a new node.
-func (node Node) AddPrefixTable(prefix string, labels map[string]string) Node {
+// AddPrefixTable appends arbitrary rows to the Node, returning a new node.
+func (node Node) AddPrefixTable(prefix string, rows []Row) Node {
+ count := 0
+ for _, row := range rows {
+ if count >= MaxTableRows {
+ break
+ }
+ // TODO: Figure a more natural way of storing rows
+ for column, value := range row.Entries {
+ key := fmt.Sprintf("%s %s", row.ID, column)
+ node = node.WithLatest(prefix+key, mtime.Now(), value)
+ }
+ count++
+ }
+ if len(rows) > MaxTableRows {
+ truncationCount := fmt.Sprintf("%d", len(rows)-MaxTableRows)
+ node = node.WithLatest(TruncationCountPrefix+prefix, mtime.Now(), truncationCount)
+ }
+ return node
+}
+
+// AddPrefixLabels appends arbitrary key-value pairs to the Node, returning a new node.
+func (node Node) AddPrefixLabels(prefix string, labels map[string]string) Node {
count := 0
for key, value := range labels {
if count >= MaxTableRows {
@@ -34,33 +57,86 @@ func (node Node) AddPrefixTable(prefix string, labels map[string]string) Node {
return node
}
-// ExtractTable returns the key-value pairs to build a table from this node
-func (node Node) ExtractTable(template TableTemplate) (rows map[string]string, truncationCount int) {
- rows = map[string]string{}
+// ExtractTable returns the rows to build a table from this node
+func (node Node) ExtractTable(template TableTemplate) (rows []Row, truncationCount int) {
+ rows = []Row{}
+ switch template.Type {
+ case MulticolumnTableType:
+ keyRows := map[string]Row{}
+ node.Latest.ForEach(func(key string, _ time.Time, value string) {
+ if len(template.Prefix) > 0 && strings.HasPrefix(key, template.Prefix) {
+ rowID, column := "", ""
+ fmt.Sscanf(key[len(template.Prefix):], "%s %s", &rowID, &column)
+ if _, ok := keyRows[rowID]; !ok {
+ keyRows[rowID] = Row{
+ ID: rowID,
+ Entries: map[string]string{},
+ }
+ }
+ keyRows[rowID].Entries[column] = value
+ }
+ })
+ for _, row := range keyRows {
+ rows = append(rows, row)
+ }
+ // By default assume it's a property list (for backward compatibility)
+ default:
+ keyValues := map[string]string{}
+ node.Latest.ForEach(func(key string, _ time.Time, value string) {
+ if label, ok := template.FixedRows[key]; ok {
+ keyValues[label] = value
+ }
+ if len(template.Prefix) > 0 && strings.HasPrefix(key, template.Prefix) {
+ label := key[len(template.Prefix):]
+ keyValues[label] = value
+ }
+ })
+ labels := make([]string, 0, len(rows))
+ for label := range keyValues {
+ labels = append(labels, label)
+ }
+ sort.Strings(labels)
+ for _, label := range labels {
+ rows = append(rows, Row{
+ ID: "label_" + label,
+ Entries: map[string]string{
+ "label": label,
+ "value": keyValues[label],
+ },
+ })
+ }
+ }
+
truncationCount = 0
- node.Latest.ForEach(func(key string, _ time.Time, value string) {
- if label, ok := template.FixedRows[key]; ok {
- rows[label] = value
- }
- if len(template.Prefix) > 0 && strings.HasPrefix(key, template.Prefix) {
- label := key[len(template.Prefix):]
- rows[label] = value
- }
- })
if str, ok := node.Latest.Lookup(TruncationCountPrefix + template.Prefix); ok {
if n, err := fmt.Sscanf(str, "%d", &truncationCount); n != 1 || err != nil {
log.Warn("Unexpected truncation count format %q", str)
}
}
+
return rows, truncationCount
}
+type Column struct {
+ ID string `json:"id"`
+ Label string `json:"label"`
+ DataType string `json:"dataType"`
+ Alignment string `json:"alignment"`
+}
+
+type Row struct {
+ ID string `json:"id"`
+ Entries map[string]string `json:"entries"`
+}
+
// Table is the type for a table in the UI.
type Table struct {
- ID string `json:"id"`
- Label string `json:"label"`
- Rows []MetadataRow `json:"rows"`
- TruncationCount int `json:"truncationCount,omitempty"`
+ ID string `json:"id"`
+ Label string `json:"label"`
+ Type string `json:"type"`
+ Columns []Column `json:"columns"`
+ Rows []Row `json:"rows"`
+ TruncationCount int `json:"truncationCount,omitempty"`
}
type tablesByID []Table
@@ -72,9 +148,14 @@ func (t tablesByID) Less(i, j int) bool { return t[i].ID < t[j].ID }
// Copy returns a copy of the Table.
func (t Table) Copy() Table {
result := Table{
- ID: t.ID,
- Label: t.Label,
- Rows: make([]MetadataRow, 0, len(t.Rows)),
+ ID: t.ID,
+ Label: t.Label,
+ Type: t.Type,
+ Columns: make([]Column, 0, len(t.Columns)),
+ Rows: make([]Row, 0, len(t.Rows)),
+ }
+ for _, column := range t.Columns {
+ result.Columns = append(result.Columns, column)
}
for _, row := range t.Rows {
result.Rows = append(result.Rows, row)
@@ -82,18 +163,13 @@ func (t Table) Copy() Table {
return result
}
-// FixedRow describes a row which is part of a TableTemplate and whose value is extracted
-// from a predetermined key
-type FixedRow struct {
- Label string `json:"label"`
- Key string `json:"key"`
-}
-
// TableTemplate describes how to render a table for the UI.
type TableTemplate struct {
- ID string `json:"id"`
- Label string `json:"label"`
- Prefix string `json:"prefix"`
+ ID string `json:"id"`
+ Label string `json:"label"`
+ Prefix string `json:"prefix"`
+ Type string `json:"type"`
+ Columns []Column `json:"columns"`
// FixedRows indicates what predetermined rows to render each entry is
// indexed by the key to extract the row value is mapped to the row
// label
@@ -126,10 +202,13 @@ func (t TableTemplate) Merge(other TableTemplate) TableTemplate {
fixedRows = other.FixedRows
}
+ // TODO: Refactor the merging logic, as mixing
+ // the types now might result in invalid tables.
return TableTemplate{
ID: max(t.ID, other.ID),
Label: max(t.Label, other.Label),
Prefix: max(t.Prefix, other.Prefix),
+ Type: max(t.Type, other.Type),
FixedRows: fixedRows,
}
}
@@ -142,25 +221,13 @@ func (t TableTemplates) Tables(node Node) []Table {
var result []Table
for _, template := range t {
rows, truncationCount := node.ExtractTable(template)
- table := Table{
+ result = append(result, Table{
ID: template.ID,
Label: template.Label,
- Rows: []MetadataRow{},
+ Type: template.Type,
+ Rows: rows,
TruncationCount: truncationCount,
- }
- keys := make([]string, 0, len(rows))
- for k := range rows {
- keys = append(keys, k)
- }
- sort.Strings(keys)
- for _, key := range keys {
- table.Rows = append(table.Rows, MetadataRow{
- ID: "label_" + key,
- Label: key,
- Value: rows[key],
- })
- }
- result = append(result, table)
+ })
}
sort.Sort(tablesByID(result))
return result
diff --git a/report/table_test.go b/report/table_test.go
index 72b6d94cd..017762a7a 100644
--- a/report/table_test.go
+++ b/report/table_test.go
@@ -16,7 +16,7 @@ func TestPrefixTables(t *testing.T) {
}
nmd := report.MakeNode("foo1")
- nmd = nmd.AddPrefixTable("foo_", want)
+ nmd = nmd.AddPrefixLabels("foo_", want)
have, truncationCount := nmd.ExtractTable(report.TableTemplate{Prefix: "foo_"})
if truncationCount != 0 {
@@ -62,7 +62,7 @@ func TestTruncation(t *testing.T) {
nmd := report.MakeNode("foo1")
- nmd = nmd.AddPrefixTable("foo_", want)
+ nmd = nmd.AddPrefixLabels("foo_", want)
_, truncationCount := nmd.ExtractTable(report.TableTemplate{Prefix: "foo_"})
if truncationCount != wantTruncationCount {