Skip to content

Commit

Permalink
Merge pull request #420 from weaveworks/without-system-containers
Browse files Browse the repository at this point in the history
Add filtering options to views; add options for filtering out system containers & unconnected containers.
  • Loading branch information
peterbourgon committed Sep 7, 2015
2 parents 6a6622e + 27caf0a commit 3a48abf
Show file tree
Hide file tree
Showing 16 changed files with 459 additions and 94 deletions.
45 changes: 37 additions & 8 deletions app/api_topologies.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,18 @@ import (

// APITopologyDesc is returned in a list by the /api/topology handler.
type APITopologyDesc struct {
Name string `json:"name"`
URL string `json:"url"`
SubTopologies []APITopologyDesc `json:"sub_topologies,omitempty"`
Stats *topologyStats `json:"stats,omitempty"`
Name string `json:"name"`
URL string `json:"url"`
SubTopologies []APITopologyDesc `json:"sub_topologies,omitempty"`
Options map[string][]APITopologyOption `json:"options"`
Stats *topologyStats `json:"stats,omitempty"`
}

// APITopologyOption describes a &param=value to a given topology.
type APITopologyOption struct {
Value string `json:"value"`
Display string `json:"display"`
Default bool `json:"default,omitempty"`
}

type topologyStats struct {
Expand All @@ -29,30 +37,51 @@ func makeTopologyList(rep xfer.Reporter) func(w http.ResponseWriter, r *http.Req
topologies = []APITopologyDesc{}
)
for name, def := range topologyRegistry {
// Don't show sub-topologies at the top level.
if def.parent != "" {
continue // subtopology, don't show at top level
continue
}

// Collect all sub-topologies of this one, depth=1 only.
subTopologies := []APITopologyDesc{}
for subName, subDef := range topologyRegistry {
if subDef.parent == name {
subTopologies = append(subTopologies, APITopologyDesc{
Name: subDef.human,
URL: "/api/topology/" + subName,
Stats: stats(subDef.renderer.Render(rpt)),
Name: subDef.human,
URL: "/api/topology/" + subName,
Options: makeTopologyOptions(subDef),
Stats: stats(subDef.renderer.Render(rpt)),
})
}
}

// Append.
topologies = append(topologies, APITopologyDesc{
Name: def.human,
URL: "/api/topology/" + name,
SubTopologies: subTopologies,
Options: makeTopologyOptions(def),
Stats: stats(def.renderer.Render(rpt)),
})
}
respondWith(w, http.StatusOK, topologies)
}
}

func makeTopologyOptions(view topologyView) map[string][]APITopologyOption {
options := map[string][]APITopologyOption{}
for param, optionVals := range view.options {
for _, optionVal := range optionVals {
options[param] = append(options[param], APITopologyOption{
Value: optionVal.value,
Display: optionVal.human,
Default: optionVal.def,
})
}
}
return options
}

func stats(r render.RenderableNodes) *topologyStats {
var (
nodes int
Expand Down
28 changes: 28 additions & 0 deletions app/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,14 @@ func captureTopology(rep xfer.Reporter, f func(xfer.Reporter, topologyView, http
http.NotFound(w, r)
return
}
for param, opts := range topology.options {
value := r.FormValue(param)
for _, opt := range opts {
if (value == "" && opt.def) || (opt.value != "" && opt.value == value) {
topology.renderer = opt.decorator(topology.renderer)
}
}
}
f(rep, topology, w, r)
}
}
Expand Down Expand Up @@ -138,11 +146,19 @@ var topologyRegistry = map[string]topologyView{
human: "Containers",
parent: "",
renderer: render.ContainerWithImageNameRenderer{},
options: optionParams{"system": {
{"show", "Show system containers", false, nop},
{"hide", "Hide system containers", true, render.FilterSystem},
}},
},
"containers-by-image": {
human: "by image",
parent: "containers",
renderer: render.ContainerImageRenderer,
options: optionParams{"system": {
{"show", "Show system containers", false, nop},
{"hide", "Hide system containers", true, render.FilterSystem},
}},
},
"hosts": {
human: "Hosts",
Expand All @@ -155,4 +171,16 @@ type topologyView struct {
human string
parent string
renderer render.Renderer
options optionParams
}

type optionParams map[string][]optionValue // param: values

type optionValue struct {
value string // "hide"
human string // "Hide system containers"
def bool
decorator func(render.Renderer) render.Renderer
}

func nop(r render.Renderer) render.Renderer { return r }
16 changes: 13 additions & 3 deletions client/app/scripts/actions/app-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ let RouterUtils;
let WebapiUtils;

module.exports = {
changeTopologyOption: function(option, value) {
AppDispatcher.dispatch({
type: ActionTypes.CHANGE_TOPOLOGY_OPTION,
option: option,
value: value
});
RouterUtils.updateRoute();
WebapiUtils.getNodesDelta(AppStore.getCurrentTopologyUrl(), AppStore.getActiveTopologyOptions());
},

clickCloseDetails: function() {
AppDispatcher.dispatch({
type: ActionTypes.CLICK_CLOSE_DETAILS
Expand All @@ -27,7 +37,7 @@ module.exports = {
topologyId: topologyId
});
RouterUtils.updateRoute();
WebapiUtils.getNodesDelta(AppStore.getCurrentTopologyUrl());
WebapiUtils.getNodesDelta(AppStore.getCurrentTopologyUrl(), AppStore.getActiveTopologyOptions());
},

openWebsocket: function() {
Expand Down Expand Up @@ -96,7 +106,7 @@ module.exports = {
type: ActionTypes.RECEIVE_TOPOLOGIES,
topologies: topologies
});
WebapiUtils.getNodesDelta(AppStore.getCurrentTopologyUrl());
WebapiUtils.getNodesDelta(AppStore.getCurrentTopologyUrl(), AppStore.getActiveTopologyOptions());
WebapiUtils.getNodeDetails(AppStore.getCurrentTopologyUrl(), AppStore.getSelectedNodeId());
},

Expand All @@ -119,7 +129,7 @@ module.exports = {
state: state,
type: ActionTypes.ROUTE_TOPOLOGY
});
WebapiUtils.getNodesDelta(AppStore.getCurrentTopologyUrl());
WebapiUtils.getNodesDelta(AppStore.getCurrentTopologyUrl(), AppStore.getActiveTopologyOptions());
WebapiUtils.getNodeDetails(AppStore.getCurrentTopologyUrl(), AppStore.getSelectedNodeId());
}
};
Expand Down
6 changes: 5 additions & 1 deletion client/app/scripts/charts/nodes-chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,12 @@ const NodesChart = React.createClass({

debug('graph layout took ' + timedLayouter.time + 'ms');

// adjust layout based on viewport
// layout was aborted
if (!graph) {
return;
}

// adjust layout based on viewport
const xFactor = (props.width - MARGINS.left - MARGINS.right) / graph.width;
const yFactor = props.height / graph.height;
const zoomFactor = Math.min(xFactor, yFactor);
Expand Down
5 changes: 5 additions & 0 deletions client/app/scripts/components/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const Logo = require('./logo');
const AppStore = require('../stores/app-store');
const Status = require('./status.js');
const Topologies = require('./topologies.js');
const TopologyOptions = require('./topology-options.js');
const WebapiUtils = require('../utils/web-api-utils');
const AppActions = require('../actions/app-actions');
const Details = require('./details');
Expand All @@ -14,8 +15,10 @@ const ESC_KEY_CODE = 27;

function getStateFromStores() {
return {
activeTopologyOptions: AppStore.getActiveTopologyOptions(),
currentTopology: AppStore.getCurrentTopology(),
currentTopologyId: AppStore.getCurrentTopologyId(),
currentTopologyOptions: AppStore.getCurrentTopologyOptions(),
errorUrl: AppStore.getErrorUrl(),
highlightedEdgeIds: AppStore.getHighlightedEdgeIds(),
highlightedNodeIds: AppStore.getHighlightedNodeIds(),
Expand Down Expand Up @@ -67,6 +70,8 @@ const App = React.createClass({
<div className="header">
<Logo />
<Topologies topologies={this.state.topologies} currentTopology={this.state.currentTopology} />
<TopologyOptions options={this.state.currentTopologyOptions}
activeOptions={this.state.activeTopologyOptions} />
<Status errorUrl={this.state.errorUrl} websocketClosed={this.state.websocketClosed} />
</div>

Expand Down
77 changes: 77 additions & 0 deletions client/app/scripts/components/topology-options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
const React = require('react');
const _ = require('lodash');
const mui = require('material-ui');
const DropDownMenu = mui.DropDownMenu;

const AppActions = require('../actions/app-actions');

const TopologyOptions = React.createClass({

componentDidMount: function() {
this.fixWidth();
},

onChange: function(ev, index, item) {
ev.preventDefault();
AppActions.changeTopologyOption(item.option, item.payload);
},

renderOption: function(items) {
let selected = 0;
let key;
const activeOptions = this.props.activeOptions;
const menuItems = items.map(function(item, index) {
if (activeOptions[item.option] && activeOptions[item.option] === item.value) {
selected = index;
}
key = item.option;
return {
option: item.option,
payload: item.value,
text: item.display
};
});

return (
<DropDownMenu menuItems={menuItems} onChange={this.onChange} key={key}
selectedIndex={selected} />
);
},

render: function() {
const options = _.sortBy(
_.map(this.props.options, function(items, optionId) {
_.each(items, function(item) {
item.option = optionId;
});
items.option = optionId;
return items;
}),
'option'
);

return (
<div className="topology-options" ref="container">
{options.map(function(items) {
return this.renderOption(items);
}, this)}
</div>
);
},

componentDidUpdate: function() {
this.fixWidth();
},

fixWidth: function() {
const containerNode = this.refs.container.getDOMNode();
_.each(containerNode.childNodes, function(child) {
// set drop down width to length of current label
const label = child.getElementsByClassName('mui-menu-label')[0];
const width = label.getBoundingClientRect().width + 40;
child.style.width = width + 'px';
});
}
});

module.exports = TopologyOptions;
1 change: 1 addition & 0 deletions client/app/scripts/constants/action-types.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const keymirror = require('keymirror');

module.exports = keymirror({
CHANGE_TOPOLOGY_OPTION: null,
CLICK_CLOSE_DETAILS: null,
CLICK_NODE: null,
CLICK_TOPOLOGY: null,
Expand Down
Loading

0 comments on commit 3a48abf

Please sign in to comment.