diff --git a/README.md b/README.md
index 05523e304..06a59636c 100644
--- a/README.md
+++ b/README.md
@@ -211,6 +211,14 @@ The SCOPE_SERVICE_TOKEN is found when you [log in to the Scope service](https://
- "--service-token"
- "${SCOPE_SERVICE_TOKEN}"
+## Using Weave Scope with Amazon's EC2 Container Service
+
+We currently provide three options for launching Weave Scope in ECS:
+
+* A [CloudFormation template](https://www.weave.works/deploy-weave-aws-cloudformation-template/) to launch and easily evaluate Scope directly from your browser.
+* An [Amazon Machine Image (AMI)](https://github.com/weaveworks/integrations/tree/master/aws/ecs#weaves-ecs-amis) for each ECS region.
+* [A simple way to tailor the AMIs to your needs](https://github.com/weaveworks/integrations/tree/master/aws/ecs#creating-your-own-customized-weave-ecs-ami).
+
## Using Weave Scope with Kubernetes
Scope comes with built-in Kubernetes support. We recommend to run Scope natively
@@ -325,40 +333,47 @@ kill -USR1 $(pgrep -f scope-probe)
docker logs weavescope
```
-- Both the Scope App and the Scope Probe offer
- [HTTP endpoints with profiling information](https://golang.org/pkg/net/http/pprof/).
- These cover things such as CPU usage and memory consumption:
- * The Scope App enables its HTTP profiling endpoints by default, which
- are accessible on the same port the Scope UI is served (4040).
- * The Scope Probe doesn't enable its profiling endpoints by default.
- To enable them, you must launch Scope with `--probe.http.listen addr:port`.
- For instance, launching Scope with `scope launch --probe.http.listen :4041`, will
- allow you access the Scope Probe's profiling endpoints on port 4041.
+Both the Scope App and the Scope Probe offer [HTTP endpoints with profiling information](https://golang.org/pkg/net/http/pprof/).
+These cover things such as CPU usage and memory consumption:
+- The Scope App enables its HTTP profiling endpoints by default, which
+ are accessible on the same port the Scope UI is served (4040).
+- The Scope Probe doesn't enable its profiling endpoints by default.
+ To enable them, you must launch Scope with `--probe.http.listen addr:port`.
+ For instance, launching Scope with `scope launch --probe.http.listen :4041`, will
+ allow you access the Scope Probe's profiling endpoints on port 4041.
- Then, you can collect profiles in the usual way. For instance:
+Then, you can collect profiles in the usual way. For instance:
- * To collect the memory profile of the Scope App:
+- To collect the memory profile of the Scope App:
- ```
+```
go tool pprof http://localhost:4040/debug/pprof/heap
```
- * To collect the CPU profile of the Scope Probe:
- ```
+- To collect the CPU profile of the Scope Probe:
+
+```
go tool pprof http://localhost:4041/debug/pprof/profile
```
- If you don't have `go` installed, you can use a Docker container instead:
+If you don't have `go` installed, you can use a Docker container instead:
- * To collect the memory profile of the Scope App:
+- To collect the memory profile of the Scope App:
- ```
+```
docker run --net=host -v $PWD:/root/pprof golang go tool pprof http://localhost:4040/debug/pprof/heap
```
- * To collect the CPU profile of the Scope Probe:
- ```
+- To collect the CPU profile of the Scope Probe:
+
+```
docker run --net=host -v $PWD:/root/pprof golang go tool pprof http://localhost:4041/debug/pprof/profile
```
- You will find the output profiles in your working directory.
+You will find the output profiles in your working directory. To analyse the dump, do something like:
+
+```
+go tool pprof prog/scope pprof.localhost\:4040.samples.cpu.001.pb.gz
+Entering interactive mode (type "help" for commands)
+(pprof) pdf >cpu.pdf
+```
diff --git a/app/api_topologies.go b/app/api_topologies.go
index 9c400e34e..0d4a61fdd 100644
--- a/app/api_topologies.go
+++ b/app/api_topologies.go
@@ -73,7 +73,7 @@ func init() {
Options: []APITopologyOption{
// Show the user why there are filtered nodes in this view.
// Don't give them the option to show those nodes.
- {"hide", "Unconnected nodes hidden", render.Noop},
+ {"hide", "Unconnected nodes hidden", nil},
},
},
}
@@ -285,14 +285,21 @@ func renderedForRequest(r *http.Request, topology APITopologyDesc) (render.Rende
for _, group := range topology.Options {
value := r.FormValue(group.ID)
for _, opt := range group.Options {
+ if opt.filter == nil {
+ continue
+ }
if (value == "" && group.Default == opt.Value) || (opt.Value != "" && opt.Value == value) {
filters = append(filters, opt.filter)
}
}
}
- return topology.renderer, func(renderer render.Renderer) render.Renderer {
- return render.MakeFilter(render.ComposeFilterFuncs(filters...), renderer)
+ var decorator render.Decorator
+ if len(filters) > 0 {
+ decorator = func(renderer render.Renderer) render.Renderer {
+ return render.MakeFilter(render.ComposeFilterFuncs(filters...), renderer)
+ }
}
+ return topology.renderer, decorator
}
type reportRenderHandler func(
diff --git a/client/.eslintrc b/client/.eslintrc
index aa6943f9e..7320a3b9f 100644
--- a/client/.eslintrc
+++ b/client/.eslintrc
@@ -7,6 +7,7 @@
},
"rules": {
"comma-dangle": 0,
+ "no-param-reassign": 0,
"object-curly-spacing": 0,
"react/jsx-closing-bracket-location": 0,
"react/prefer-stateless-function": 0,
diff --git a/client/app/scripts/actions/app-actions.js b/client/app/scripts/actions/app-actions.js
index 1b654a934..1202cd371 100644
--- a/client/app/scripts/actions/app-actions.js
+++ b/client/app/scripts/actions/app-actions.js
@@ -1,6 +1,5 @@
import debug from 'debug';
-import AppDispatcher from '../dispatcher/app-dispatcher';
import ActionTypes from '../constants/action-types';
import { saveGraph } from '../utils/file-utils';
import { modulo } from '../utils/math-utils';
@@ -9,108 +8,123 @@ import { bufferDeltaUpdate, resumeUpdate,
resetUpdateBuffer } from '../utils/update-buffer-utils';
import { doControlRequest, getNodesDelta, getNodeDetails,
getTopologies, deletePipe } from '../utils/web-api-utils';
-import AppStore from '../stores/app-store';
+import { getActiveTopologyOptions,
+ getCurrentTopologyUrl } from '../utils/topology-utils';
const log = debug('scope:app-actions');
export function showHelp() {
- AppDispatcher.dispatch({type: ActionTypes.SHOW_HELP});
+ return {type: ActionTypes.SHOW_HELP};
}
export function hideHelp() {
- AppDispatcher.dispatch({type: ActionTypes.HIDE_HELP});
+ return {type: ActionTypes.HIDE_HELP};
}
export function toggleHelp() {
- if (AppStore.getShowingHelp()) {
- hideHelp();
- } else {
- showHelp();
- }
+ return (dispatch, getState) => {
+ if (getState().get('showingHelp')) {
+ dispatch(hideHelp());
+ } else {
+ dispatch(showHelp());
+ }
+ };
}
export function selectMetric(metricId) {
- AppDispatcher.dispatch({
+ return {
type: ActionTypes.SELECT_METRIC,
metricId
- });
-}
-
-export function pinNextMetric(delta) {
- const metrics = AppStore.getAvailableCanvasMetrics().map(m => m.get('id'));
- const currentIndex = metrics.indexOf(AppStore.getSelectedMetric());
- const nextIndex = modulo(currentIndex + delta, metrics.count());
- const nextMetric = metrics.get(nextIndex);
-
- AppDispatcher.dispatch({
- type: ActionTypes.PIN_METRIC,
- metricId: nextMetric,
- });
- updateRoute();
+ };
}
export function pinMetric(metricId) {
- AppDispatcher.dispatch({
- type: ActionTypes.PIN_METRIC,
- metricId,
- });
- updateRoute();
+ return (dispatch, getState) => {
+ dispatch({
+ type: ActionTypes.PIN_METRIC,
+ metricId,
+ });
+ updateRoute(getState);
+ };
}
export function unpinMetric() {
- AppDispatcher.dispatch({
- type: ActionTypes.UNPIN_METRIC,
- });
- updateRoute();
+ return (dispatch, getState) => {
+ dispatch({
+ type: ActionTypes.UNPIN_METRIC,
+ });
+ updateRoute(getState);
+ };
+}
+
+export function pinNextMetric(delta) {
+ return (dispatch, getState) => {
+ const state = getState();
+ const metrics = state.get('availableCanvasMetrics').map(m => m.get('id'));
+ const currentIndex = metrics.indexOf(state.get('selectedMetric'));
+ const nextIndex = modulo(currentIndex + delta, metrics.count());
+ const nextMetric = metrics.get(nextIndex);
+
+ dispatch(pinMetric(nextMetric));
+ };
}
export function changeTopologyOption(option, value, topologyId) {
- AppDispatcher.dispatch({
- type: ActionTypes.CHANGE_TOPOLOGY_OPTION,
- topologyId,
- option,
- value
- });
- updateRoute();
- // update all request workers with new options
- resetUpdateBuffer();
- getTopologies(
- AppStore.getActiveTopologyOptions()
- );
- getNodesDelta(
- AppStore.getCurrentTopologyUrl(),
- AppStore.getActiveTopologyOptions()
- );
- getNodeDetails(
- AppStore.getTopologyUrlsById(),
- AppStore.getNodeDetails()
- );
+ return (dispatch, getState) => {
+ dispatch({
+ type: ActionTypes.CHANGE_TOPOLOGY_OPTION,
+ topologyId,
+ option,
+ value
+ });
+ updateRoute(getState);
+ // update all request workers with new options
+ resetUpdateBuffer();
+ const state = getState();
+ getTopologies(getActiveTopologyOptions(state), dispatch);
+ getNodesDelta(
+ getCurrentTopologyUrl(state),
+ getActiveTopologyOptions(state),
+ dispatch
+ );
+ getNodeDetails(
+ state.get('topologyUrlsById'),
+ state.get('nodeDetails'),
+ dispatch
+ );
+ };
}
export function clickBackground() {
- AppDispatcher.dispatch({
- type: ActionTypes.CLICK_BACKGROUND
- });
- updateRoute();
+ return (dispatch, getState) => {
+ dispatch({
+ type: ActionTypes.CLICK_BACKGROUND
+ });
+ updateRoute(getState);
+ };
}
export function clickCloseDetails(nodeId) {
- AppDispatcher.dispatch({
- type: ActionTypes.CLICK_CLOSE_DETAILS,
- nodeId
- });
- updateRoute();
+ return (dispatch, getState) => {
+ dispatch({
+ type: ActionTypes.CLICK_CLOSE_DETAILS,
+ nodeId
+ });
+ updateRoute(getState);
+ };
}
export function clickCloseTerminal(pipeId, closePipe) {
- AppDispatcher.dispatch({
- type: ActionTypes.CLICK_CLOSE_TERMINAL,
- pipeId
- });
- if (closePipe) {
- deletePipe(pipeId);
- }
- updateRoute();
+ return (dispatch, getState) => {
+ dispatch({
+ type: ActionTypes.CLICK_CLOSE_TERMINAL,
+ pipeId
+ });
+ if (closePipe) {
+ deletePipe(pipeId, dispatch);
+ }
+ updateRoute(getState);
+ };
}
export function clickDownloadGraph() {
@@ -118,285 +132,340 @@ export function clickDownloadGraph() {
}
export function clickForceRelayout() {
- AppDispatcher.dispatch({
- type: ActionTypes.CLICK_FORCE_RELAYOUT
- });
+ return (dispatch) => {
+ dispatch({
+ type: ActionTypes.CLICK_FORCE_RELAYOUT,
+ forceRelayout: true
+ });
+ // fire only once, reset after dispatch
+ setTimeout(() => {
+ dispatch({
+ type: ActionTypes.CLICK_FORCE_RELAYOUT,
+ forceRelayout: false
+ });
+ }, 100);
+ };
}
export function clickNode(nodeId, label, origin) {
- AppDispatcher.dispatch({
- type: ActionTypes.CLICK_NODE,
- origin,
- label,
- nodeId
- });
- updateRoute();
- getNodeDetails(
- AppStore.getTopologyUrlsById(),
- AppStore.getNodeDetails()
- );
+ return (dispatch, getState) => {
+ dispatch({
+ type: ActionTypes.CLICK_NODE,
+ origin,
+ label,
+ nodeId
+ });
+ updateRoute(getState);
+ const state = getState();
+ getNodeDetails(
+ state.get('topologyUrlsById'),
+ state.get('nodeDetails'),
+ dispatch
+ );
+ };
}
export function clickPauseUpdate() {
- AppDispatcher.dispatch({
+ return {
type: ActionTypes.CLICK_PAUSE_UPDATE
- });
+ };
}
export function clickRelative(nodeId, topologyId, label, origin) {
- AppDispatcher.dispatch({
- type: ActionTypes.CLICK_RELATIVE,
- label,
- origin,
- nodeId,
- topologyId
- });
- updateRoute();
- getNodeDetails(
- AppStore.getTopologyUrlsById(),
- AppStore.getNodeDetails()
- );
+ return (dispatch, getState) => {
+ dispatch({
+ type: ActionTypes.CLICK_RELATIVE,
+ label,
+ origin,
+ nodeId,
+ topologyId
+ });
+ updateRoute(getState);
+ const state = getState();
+ getNodeDetails(
+ state.get('topologyUrlsById'),
+ state.get('nodeDetails'),
+ dispatch
+ );
+ };
}
export function clickResumeUpdate() {
- AppDispatcher.dispatch({
- type: ActionTypes.CLICK_RESUME_UPDATE
- });
- resumeUpdate();
+ return (dispatch, getState) => {
+ dispatch({
+ type: ActionTypes.CLICK_RESUME_UPDATE
+ });
+ resumeUpdate(getState);
+ };
}
export function clickShowTopologyForNode(topologyId, nodeId) {
- AppDispatcher.dispatch({
- type: ActionTypes.CLICK_SHOW_TOPOLOGY_FOR_NODE,
- topologyId,
- nodeId
- });
- updateRoute();
- resetUpdateBuffer();
- getNodesDelta(
- AppStore.getCurrentTopologyUrl(),
- AppStore.getActiveTopologyOptions()
- );
+ return (dispatch, getState) => {
+ dispatch({
+ type: ActionTypes.CLICK_SHOW_TOPOLOGY_FOR_NODE,
+ topologyId,
+ nodeId
+ });
+ updateRoute(getState);
+ // update all request workers with new options
+ resetUpdateBuffer();
+ const state = getState();
+ getNodesDelta(
+ getCurrentTopologyUrl(state),
+ getActiveTopologyOptions(state),
+ dispatch
+ );
+ };
}
export function clickTopology(topologyId) {
- AppDispatcher.dispatch({
- type: ActionTypes.CLICK_TOPOLOGY,
- topologyId
- });
- updateRoute();
- resetUpdateBuffer();
- getNodesDelta(
- AppStore.getCurrentTopologyUrl(),
- AppStore.getActiveTopologyOptions()
- );
+ return (dispatch, getState) => {
+ dispatch({
+ type: ActionTypes.CLICK_TOPOLOGY,
+ topologyId
+ });
+ updateRoute(getState);
+ // update all request workers with new options
+ resetUpdateBuffer();
+ const state = getState();
+ getNodesDelta(
+ getCurrentTopologyUrl(state),
+ getActiveTopologyOptions(state),
+ dispatch
+ );
+ };
}
export function openWebsocket() {
- AppDispatcher.dispatch({
+ return {
type: ActionTypes.OPEN_WEBSOCKET
- });
+ };
}
export function clearControlError(nodeId) {
- AppDispatcher.dispatch({
+ return {
type: ActionTypes.CLEAR_CONTROL_ERROR,
nodeId
- });
+ };
}
export function closeWebsocket() {
- AppDispatcher.dispatch({
+ return {
type: ActionTypes.CLOSE_WEBSOCKET
- });
+ };
}
export function doControl(nodeId, control) {
- AppDispatcher.dispatch({
- type: ActionTypes.DO_CONTROL,
- nodeId
- });
- doControlRequest(nodeId, control);
+ return (dispatch) => {
+ dispatch({
+ type: ActionTypes.DO_CONTROL,
+ nodeId
+ });
+ doControlRequest(nodeId, control, dispatch);
+ };
}
export function enterEdge(edgeId) {
- AppDispatcher.dispatch({
+ return {
type: ActionTypes.ENTER_EDGE,
edgeId
- });
+ };
}
export function enterNode(nodeId) {
- AppDispatcher.dispatch({
+ return {
type: ActionTypes.ENTER_NODE,
nodeId
- });
+ };
}
export function hitEsc() {
- const controlPipe = AppStore.getControlPipe();
- if (AppStore.getShowingHelp()) {
- hideHelp();
- } else if (controlPipe && controlPipe.get('status') === 'PIPE_DELETED') {
- AppDispatcher.dispatch({
- type: ActionTypes.CLICK_CLOSE_TERMINAL,
- pipeId: controlPipe.get('id')
- });
- updateRoute();
- // Don't deselect node on ESC if there is a controlPipe (keep terminal open)
- } else if (AppStore.getTopCardNodeId() && !controlPipe) {
- AppDispatcher.dispatch({ type: ActionTypes.DESELECT_NODE });
- updateRoute();
- }
+ return (dispatch, getState) => {
+ const state = getState();
+ const controlPipe = state.get('controlPipes').last();
+ if (state.get('showingHelp')) {
+ dispatch(hideHelp());
+ } else if (controlPipe && controlPipe.get('status') === 'PIPE_DELETED') {
+ dispatch({
+ type: ActionTypes.CLICK_CLOSE_TERMINAL,
+ pipeId: controlPipe.get('id')
+ });
+ updateRoute(getState);
+ // Don't deselect node on ESC if there is a controlPipe (keep terminal open)
+ } else if (state.get('nodeDetails').last() && !controlPipe) {
+ dispatch({ type: ActionTypes.DESELECT_NODE });
+ updateRoute(getState);
+ }
+ };
}
export function leaveEdge(edgeId) {
- AppDispatcher.dispatch({
+ return {
type: ActionTypes.LEAVE_EDGE,
edgeId
- });
+ };
}
export function leaveNode(nodeId) {
- AppDispatcher.dispatch({
+ return {
type: ActionTypes.LEAVE_NODE,
nodeId
- });
+ };
}
export function receiveControlError(nodeId, err) {
- AppDispatcher.dispatch({
+ return {
type: ActionTypes.DO_CONTROL_ERROR,
nodeId,
error: err
- });
+ };
}
export function receiveControlSuccess(nodeId) {
- AppDispatcher.dispatch({
+ return {
type: ActionTypes.DO_CONTROL_SUCCESS,
nodeId
- });
+ };
}
export function receiveNodeDetails(details) {
- AppDispatcher.dispatch({
+ return {
type: ActionTypes.RECEIVE_NODE_DETAILS,
details
- });
+ };
}
export function receiveNodesDelta(delta) {
- if (AppStore.isUpdatePaused()) {
- bufferDeltaUpdate(delta);
- } else {
- AppDispatcher.dispatch({
- type: ActionTypes.RECEIVE_NODES_DELTA,
- delta
- });
- }
+ return (dispatch, getState) => {
+ if (delta.add || delta.update || delta.remove) {
+ const state = getState();
+ if (state.get('updatePausedAt') !== null) {
+ bufferDeltaUpdate(delta);
+ } else {
+ dispatch({
+ type: ActionTypes.RECEIVE_NODES_DELTA,
+ delta
+ });
+ }
+ }
+ };
}
export function receiveTopologies(topologies) {
- AppDispatcher.dispatch({
- type: ActionTypes.RECEIVE_TOPOLOGIES,
- topologies
- });
- getNodesDelta(
- AppStore.getCurrentTopologyUrl(),
- AppStore.getActiveTopologyOptions()
- );
- getNodeDetails(
- AppStore.getTopologyUrlsById(),
- AppStore.getNodeDetails()
- );
+ return (dispatch, getState) => {
+ dispatch({
+ type: ActionTypes.RECEIVE_TOPOLOGIES,
+ topologies
+ });
+ const state = getState();
+ getNodesDelta(
+ getCurrentTopologyUrl(state),
+ getActiveTopologyOptions(state),
+ dispatch
+ );
+ getNodeDetails(
+ state.get('topologyUrlsById'),
+ state.get('nodeDetails'),
+ dispatch
+ );
+ };
}
export function receiveApiDetails(apiDetails) {
- AppDispatcher.dispatch({
+ return {
type: ActionTypes.RECEIVE_API_DETAILS,
hostname: apiDetails.hostname,
version: apiDetails.version,
plugins: apiDetails.plugins
- });
+ };
}
export function receiveControlNodeRemoved(nodeId) {
- AppDispatcher.dispatch({
- type: ActionTypes.RECEIVE_CONTROL_NODE_REMOVED,
- nodeId
- });
- updateRoute();
+ return (dispatch, getState) => {
+ dispatch({
+ type: ActionTypes.RECEIVE_CONTROL_NODE_REMOVED,
+ nodeId
+ });
+ updateRoute(getState);
+ };
}
export function receiveControlPipeFromParams(pipeId, rawTty) {
// TODO add nodeId
- AppDispatcher.dispatch({
+ return {
type: ActionTypes.RECEIVE_CONTROL_PIPE,
pipeId,
rawTty
- });
+ };
}
export function receiveControlPipe(pipeId, nodeId, rawTty) {
- if (nodeId !== AppStore.getTopCardNodeId()) {
- log('Node was deselected before we could set up control!');
- deletePipe(pipeId);
- return;
- }
+ return (dispatch, getState) => {
+ const state = getState();
+ if (state.get('nodeDetails').last()
+ && nodeId !== state.get('nodeDetails').last().id) {
+ log('Node was deselected before we could set up control!');
+ deletePipe(pipeId, dispatch);
+ return;
+ }
- const controlPipe = AppStore.getControlPipe();
- if (controlPipe && controlPipe.get('id') !== pipeId) {
- deletePipe(controlPipe.get('id'));
- }
+ const controlPipe = state.get('controlPipes').last();
+ if (controlPipe && controlPipe.get('id') !== pipeId) {
+ deletePipe(controlPipe.get('id'), dispatch);
+ }
- AppDispatcher.dispatch({
- type: ActionTypes.RECEIVE_CONTROL_PIPE,
- nodeId,
- pipeId,
- rawTty
- });
+ dispatch({
+ type: ActionTypes.RECEIVE_CONTROL_PIPE,
+ nodeId,
+ pipeId,
+ rawTty
+ });
- updateRoute();
+ updateRoute(getState);
+ };
}
export function receiveControlPipeStatus(pipeId, status) {
- AppDispatcher.dispatch({
+ return {
type: ActionTypes.RECEIVE_CONTROL_PIPE_STATUS,
pipeId,
status
- });
+ };
}
export function receiveError(errorUrl) {
- AppDispatcher.dispatch({
+ return {
errorUrl,
type: ActionTypes.RECEIVE_ERROR
- });
+ };
}
export function receiveNotFound(nodeId) {
- AppDispatcher.dispatch({
+ return {
nodeId,
type: ActionTypes.RECEIVE_NOT_FOUND
- });
+ };
}
-export function route(state) {
- AppDispatcher.dispatch({
- state,
- type: ActionTypes.ROUTE_TOPOLOGY
- });
- getTopologies(
- AppStore.getActiveTopologyOptions()
- );
- getNodesDelta(
- AppStore.getCurrentTopologyUrl(),
- AppStore.getActiveTopologyOptions()
- );
- getNodeDetails(
- AppStore.getTopologyUrlsById(),
- AppStore.getNodeDetails()
- );
+export function route(urlState) {
+ return (dispatch, getState) => {
+ dispatch({
+ state: urlState,
+ type: ActionTypes.ROUTE_TOPOLOGY
+ });
+ // update all request workers with new options
+ const state = getState();
+ getTopologies(getActiveTopologyOptions(state), dispatch);
+ getNodesDelta(
+ getCurrentTopologyUrl(state),
+ getActiveTopologyOptions(state),
+ dispatch
+ );
+ getNodeDetails(
+ state.get('topologyUrlsById'),
+ state.get('nodeDetails'),
+ dispatch
+ );
+ };
}
diff --git a/client/app/scripts/charts/edge-container.js b/client/app/scripts/charts/edge-container.js
index d19723070..b0545329d 100644
--- a/client/app/scripts/charts/edge-container.js
+++ b/client/app/scripts/charts/edge-container.js
@@ -1,9 +1,8 @@
import _ from 'lodash';
import d3 from 'd3';
import React from 'react';
+import { connect } from 'react-redux';
import { Motion, spring } from 'react-motion';
-import PureRenderMixin from 'react-addons-pure-render-mixin';
-import reactMixin from 'react-mixin';
import { Map as makeMap } from 'immutable';
import Edge from './edge';
@@ -29,7 +28,7 @@ const buildPath = (points, layoutPrecision) => {
return extracted;
};
-export default class EdgeContainer extends React.Component {
+class EdgeContainer extends React.Component {
constructor(props, context) {
super(props, context);
@@ -96,4 +95,4 @@ export default class EdgeContainer extends React.Component {
}
-reactMixin.onClass(EdgeContainer, PureRenderMixin);
+export default connect()(EdgeContainer);
diff --git a/client/app/scripts/charts/edge.js b/client/app/scripts/charts/edge.js
index 8689f6b84..458a8a7c3 100644
--- a/client/app/scripts/charts/edge.js
+++ b/client/app/scripts/charts/edge.js
@@ -1,11 +1,10 @@
import React from 'react';
-import PureRenderMixin from 'react-addons-pure-render-mixin';
-import reactMixin from 'react-mixin';
+import { connect } from 'react-redux';
import classNames from 'classnames';
import { enterEdge, leaveEdge } from '../actions/app-actions';
-export default class Edge extends React.Component {
+class Edge extends React.Component {
constructor(props, context) {
super(props, context);
@@ -27,12 +26,15 @@ export default class Edge extends React.Component {
}
handleMouseEnter(ev) {
- enterEdge(ev.currentTarget.id);
+ this.props.enterEdge(ev.currentTarget.id);
}
handleMouseLeave(ev) {
- leaveEdge(ev.currentTarget.id);
+ this.props.leaveEdge(ev.currentTarget.id);
}
}
-reactMixin.onClass(Edge, PureRenderMixin);
+export default connect(
+ null,
+ { enterEdge, leaveEdge }
+)(Edge);
diff --git a/client/app/scripts/charts/node-container.js b/client/app/scripts/charts/node-container.js
index 75506b290..74820303d 100644
--- a/client/app/scripts/charts/node-container.js
+++ b/client/app/scripts/charts/node-container.js
@@ -1,13 +1,12 @@
import _ from 'lodash';
import React from 'react';
-import PureRenderMixin from 'react-addons-pure-render-mixin';
-import reactMixin from 'react-mixin';
+import { connect } from 'react-redux';
import d3 from 'd3';
import { Motion, spring } from 'react-motion';
import Node from './node';
-export default class NodeContainer extends React.Component {
+class NodeContainer extends React.Component {
render() {
const { dx, dy, focused, layoutPrecision, zoomScale } = this.props;
const animConfig = [80, 20]; // stiffness, damping
@@ -30,4 +29,4 @@ export default class NodeContainer extends React.Component {
}
}
-reactMixin.onClass(NodeContainer, PureRenderMixin);
+export default connect()(NodeContainer);
diff --git a/client/app/scripts/charts/node.js b/client/app/scripts/charts/node.js
index c7469c884..5d96faa41 100644
--- a/client/app/scripts/charts/node.js
+++ b/client/app/scripts/charts/node.js
@@ -1,7 +1,6 @@
import React from 'react';
import ReactDOM from 'react-dom';
-import PureRenderMixin from 'react-addons-pure-render-mixin';
-import reactMixin from 'react-mixin';
+import { connect } from 'react-redux';
import classNames from 'classnames';
import { clickNode, enterNode, leaveNode } from '../actions/app-actions';
@@ -45,7 +44,7 @@ function ellipsis(text, fontSize, maxWidth) {
return truncatedText;
}
-export default class Node extends React.Component {
+class Node extends React.Component {
constructor(props, context) {
super(props, context);
@@ -116,18 +115,22 @@ export default class Node extends React.Component {
handleMouseClick(ev) {
ev.stopPropagation();
- clickNode(this.props.id, this.props.label, ReactDOM.findDOMNode(this).getBoundingClientRect());
+ this.props.clickNode(this.props.id, this.props.label,
+ ReactDOM.findDOMNode(this).getBoundingClientRect());
}
handleMouseEnter() {
- enterNode(this.props.id);
+ this.props.enterNode(this.props.id);
this.setState({ hovered: true });
}
handleMouseLeave() {
- leaveNode(this.props.id);
+ this.props.leaveNode(this.props.id);
this.setState({ hovered: false });
}
}
-reactMixin.onClass(Node, PureRenderMixin);
+export default connect(
+ null,
+ { clickNode, enterNode, leaveNode }
+)(Node);
diff --git a/client/app/scripts/charts/nodes-chart-edges.js b/client/app/scripts/charts/nodes-chart-edges.js
index 4d1ef1ef5..4fa976288 100644
--- a/client/app/scripts/charts/nodes-chart-edges.js
+++ b/client/app/scripts/charts/nodes-chart-edges.js
@@ -1,10 +1,10 @@
import React from 'react';
-import PureRenderMixin from 'react-addons-pure-render-mixin';
-import reactMixin from 'react-mixin';
+import { connect } from 'react-redux';
+import { hasSelectedNode as hasSelectedNodeFn } from '../utils/topology-utils';
import EdgeContainer from './edge-container';
-export default class NodesChartEdges extends React.Component {
+class NodesChartEdges extends React.Component {
render() {
const {hasSelectedNode, highlightedEdgeIds, layoutEdges, layoutPrecision,
selectedNodeId} = this.props;
@@ -36,4 +36,14 @@ export default class NodesChartEdges extends React.Component {
}
}
-reactMixin.onClass(NodesChartEdges, PureRenderMixin);
+function mapStateToProps(state) {
+ return {
+ hasSelectedNode: hasSelectedNodeFn(state),
+ selectedNodeId: state.get('selectedNodeId'),
+ highlightedEdgeIds: state.get('highlightedEdgeIds')
+ };
+}
+
+export default connect(
+ mapStateToProps
+)(NodesChartEdges);
diff --git a/client/app/scripts/charts/nodes-chart-elements.js b/client/app/scripts/charts/nodes-chart-elements.js
index 075827818..7d921f0a8 100644
--- a/client/app/scripts/charts/nodes-chart-elements.js
+++ b/client/app/scripts/charts/nodes-chart-elements.js
@@ -1,31 +1,22 @@
import React from 'react';
-import PureRenderMixin from 'react-addons-pure-render-mixin';
-import reactMixin from 'react-mixin';
+import { connect } from 'react-redux';
import NodesChartEdges from './nodes-chart-edges';
import NodesChartNodes from './nodes-chart-nodes';
-export default class NodesChartElements extends React.Component {
+class NodesChartElements extends React.Component {
render() {
const props = this.props;
return (