diff --git a/client/.eslintignore b/client/.eslintignore
new file mode 100644
index 000000000..e69de29bb
diff --git a/client/app/scripts/actions/app-actions.js b/client/app/scripts/actions/app-actions.js
index c62ab43cd..6dc3c3250 100644
--- a/client/app/scripts/actions/app-actions.js
+++ b/client/app/scripts/actions/app-actions.js
@@ -1,207 +1,197 @@
-let ActionTypes;
-let AppDispatcher;
-let AppStore;
-let RouterUtils;
-let WebapiUtils;
+import AppDispatcher from '../dispatcher/app-dispatcher';
+import ActionTypes from '../constants/action-types';
-module.exports = {
- changeTopologyOption: function(option, value, topologyId) {
- AppDispatcher.dispatch({
- type: ActionTypes.CHANGE_TOPOLOGY_OPTION,
- topologyId: topologyId,
- option: option,
- value: value
- });
- RouterUtils.updateRoute();
- // update all request workers with new options
- WebapiUtils.getTopologies(
- AppStore.getActiveTopologyOptions()
- );
- WebapiUtils.getNodesDelta(
- AppStore.getCurrentTopologyUrl(),
- AppStore.getActiveTopologyOptions()
- );
- WebapiUtils.getNodeDetails(
- AppStore.getCurrentTopologyUrl(),
- AppStore.getSelectedNodeId()
- );
- },
+import { updateRoute } from '../utils/router-utils';
+import { doControl as doControlRequest, getNodesDelta, getNodeDetails, getTopologies } from '../utils/web-api-utils';
+import AppStore from '../stores/app-store';
- clickCloseDetails: function() {
- AppDispatcher.dispatch({
- type: ActionTypes.CLICK_CLOSE_DETAILS
- });
- RouterUtils.updateRoute();
- },
+export function changeTopologyOption(option, value, topologyId) {
+ AppDispatcher.dispatch({
+ type: ActionTypes.CHANGE_TOPOLOGY_OPTION,
+ topologyId: topologyId,
+ option: option,
+ value: value
+ });
+ updateRoute();
+ // update all request workers with new options
+ getTopologies(
+ AppStore.getActiveTopologyOptions()
+ );
+ getNodesDelta(
+ AppStore.getCurrentTopologyUrl(),
+ AppStore.getActiveTopologyOptions()
+ );
+ getNodeDetails(
+ AppStore.getCurrentTopologyUrl(),
+ AppStore.getSelectedNodeId()
+ );
+}
- clickNode: function(nodeId) {
- AppDispatcher.dispatch({
- type: ActionTypes.CLICK_NODE,
- nodeId: nodeId
- });
- RouterUtils.updateRoute();
- WebapiUtils.getNodeDetails(
- AppStore.getCurrentTopologyUrl(),
- AppStore.getSelectedNodeId()
- );
- },
+export function clickCloseDetails() {
+ AppDispatcher.dispatch({
+ type: ActionTypes.CLICK_CLOSE_DETAILS
+ });
+ updateRoute();
+}
- clickTopology: function(topologyId) {
- AppDispatcher.dispatch({
- type: ActionTypes.CLICK_TOPOLOGY,
- topologyId: topologyId
- });
- RouterUtils.updateRoute();
- WebapiUtils.getNodesDelta(
- AppStore.getCurrentTopologyUrl(),
- AppStore.getActiveTopologyOptions()
- );
- },
+export function clickNode(nodeId) {
+ AppDispatcher.dispatch({
+ type: ActionTypes.CLICK_NODE,
+ nodeId: nodeId
+ });
+ updateRoute();
+ getNodeDetails(
+ AppStore.getCurrentTopologyUrl(),
+ AppStore.getSelectedNodeId()
+ );
+}
- openWebsocket: function() {
- AppDispatcher.dispatch({
- type: ActionTypes.OPEN_WEBSOCKET
- });
- },
+export function clickTopology(topologyId) {
+ AppDispatcher.dispatch({
+ type: ActionTypes.CLICK_TOPOLOGY,
+ topologyId: topologyId
+ });
+ updateRoute();
+ getNodesDelta(
+ AppStore.getCurrentTopologyUrl(),
+ AppStore.getActiveTopologyOptions()
+ );
+}
- clearControlError: function() {
- AppDispatcher.dispatch({
- type: ActionTypes.CLEAR_CONTROL_ERROR
- });
- },
+export function openWebsocket() {
+ AppDispatcher.dispatch({
+ type: ActionTypes.OPEN_WEBSOCKET
+ });
+}
- closeWebsocket: function() {
- AppDispatcher.dispatch({
- type: ActionTypes.CLOSE_WEBSOCKET
- });
- },
+export function clearControlError() {
+ AppDispatcher.dispatch({
+ type: ActionTypes.CLEAR_CONTROL_ERROR
+ });
+}
- doControl: function(probeId, nodeId, control) {
- AppDispatcher.dispatch({
- type: ActionTypes.DO_CONTROL
- });
- WebapiUtils.doControl(
- probeId,
- nodeId,
- control
- );
- },
+export function closeWebsocket() {
+ AppDispatcher.dispatch({
+ type: ActionTypes.CLOSE_WEBSOCKET
+ });
+}
- enterEdge: function(edgeId) {
- AppDispatcher.dispatch({
- type: ActionTypes.ENTER_EDGE,
- edgeId: edgeId
- });
- },
+export function doControl(probeId, nodeId, control) {
+ AppDispatcher.dispatch({
+ type: ActionTypes.DO_CONTROL
+ });
+ doControlRequest(
+ probeId,
+ nodeId,
+ control
+ );
+}
- enterNode: function(nodeId) {
- AppDispatcher.dispatch({
- type: ActionTypes.ENTER_NODE,
- nodeId: nodeId
- });
- },
+export function enterEdge(edgeId) {
+ AppDispatcher.dispatch({
+ type: ActionTypes.ENTER_EDGE,
+ edgeId: edgeId
+ });
+}
- hitEsc: function() {
- AppDispatcher.dispatch({
- type: ActionTypes.HIT_ESC_KEY
- });
- RouterUtils.updateRoute();
- },
+export function enterNode(nodeId) {
+ AppDispatcher.dispatch({
+ type: ActionTypes.ENTER_NODE,
+ nodeId: nodeId
+ });
+}
- leaveEdge: function(edgeId) {
- AppDispatcher.dispatch({
- type: ActionTypes.LEAVE_EDGE,
- edgeId: edgeId
- });
- },
+export function hitEsc() {
+ AppDispatcher.dispatch({
+ type: ActionTypes.HIT_ESC_KEY
+ });
+ updateRoute();
+}
- leaveNode: function(nodeId) {
- AppDispatcher.dispatch({
- type: ActionTypes.LEAVE_NODE,
- nodeId: nodeId
- });
- },
+export function leaveEdge(edgeId) {
+ AppDispatcher.dispatch({
+ type: ActionTypes.LEAVE_EDGE,
+ edgeId: edgeId
+ });
+}
- receiveControlError: function(err) {
- AppDispatcher.dispatch({
- type: ActionTypes.DO_CONTROL_ERROR,
- error: err
- });
- },
+export function leaveNode(nodeId) {
+ AppDispatcher.dispatch({
+ type: ActionTypes.LEAVE_NODE,
+ nodeId: nodeId
+ });
+}
- receiveControlSuccess: function() {
- AppDispatcher.dispatch({
- type: ActionTypes.DO_CONTROL_SUCCESS
- });
- },
+export function receiveControlError(err) {
+ AppDispatcher.dispatch({
+ type: ActionTypes.DO_CONTROL_ERROR,
+ error: err
+ });
+}
- receiveNodeDetails: function(details) {
- AppDispatcher.dispatch({
- type: ActionTypes.RECEIVE_NODE_DETAILS,
- details: details
- });
- },
+export function receiveControlSuccess() {
+ AppDispatcher.dispatch({
+ type: ActionTypes.DO_CONTROL_SUCCESS
+ });
+}
- receiveNodesDelta: function(delta) {
- AppDispatcher.dispatch({
- type: ActionTypes.RECEIVE_NODES_DELTA,
- delta: delta
- });
- },
+export function receiveNodeDetails(details) {
+ AppDispatcher.dispatch({
+ type: ActionTypes.RECEIVE_NODE_DETAILS,
+ details: details
+ });
+}
- receiveTopologies: function(topologies) {
- AppDispatcher.dispatch({
- type: ActionTypes.RECEIVE_TOPOLOGIES,
- topologies: topologies
- });
- WebapiUtils.getNodesDelta(
- AppStore.getCurrentTopologyUrl(),
- AppStore.getActiveTopologyOptions()
- );
- WebapiUtils.getNodeDetails(
- AppStore.getCurrentTopologyUrl(),
- AppStore.getSelectedNodeId()
- );
- },
+export function receiveNodesDelta(delta) {
+ AppDispatcher.dispatch({
+ type: ActionTypes.RECEIVE_NODES_DELTA,
+ delta: delta
+ });
+}
- receiveApiDetails: function(apiDetails) {
- AppDispatcher.dispatch({
- type: ActionTypes.RECEIVE_API_DETAILS,
- version: apiDetails.version
- });
- },
+export function receiveTopologies(topologies) {
+ AppDispatcher.dispatch({
+ type: ActionTypes.RECEIVE_TOPOLOGIES,
+ topologies: topologies
+ });
+ getNodesDelta(
+ AppStore.getCurrentTopologyUrl(),
+ AppStore.getActiveTopologyOptions()
+ );
+ getNodeDetails(
+ AppStore.getCurrentTopologyUrl(),
+ AppStore.getSelectedNodeId()
+ );
+}
- receiveError: function(errorUrl) {
- AppDispatcher.dispatch({
- errorUrl: errorUrl,
- type: ActionTypes.RECEIVE_ERROR
- });
- },
+export function receiveApiDetails(apiDetails) {
+ AppDispatcher.dispatch({
+ type: ActionTypes.RECEIVE_API_DETAILS,
+ version: apiDetails.version
+ });
+}
- route: function(state) {
- AppDispatcher.dispatch({
- state: state,
- type: ActionTypes.ROUTE_TOPOLOGY
- });
- WebapiUtils.getTopologies(
- AppStore.getActiveTopologyOptions()
- );
- WebapiUtils.getNodesDelta(
- AppStore.getCurrentTopologyUrl(),
- AppStore.getActiveTopologyOptions()
- );
- WebapiUtils.getNodeDetails(
- AppStore.getCurrentTopologyUrl(),
- AppStore.getSelectedNodeId()
- );
- }
-};
+export function receiveError(errorUrl) {
+ AppDispatcher.dispatch({
+ errorUrl: errorUrl,
+ type: ActionTypes.RECEIVE_ERROR
+ });
+}
-// require below export to break circular dep
-
-AppDispatcher = require('../dispatcher/app-dispatcher');
-ActionTypes = require('../constants/action-types');
-
-RouterUtils = require('../utils/router-utils');
-WebapiUtils = require('../utils/web-api-utils');
-AppStore = require('../stores/app-store');
+export function route(state) {
+ AppDispatcher.dispatch({
+ state: state,
+ type: ActionTypes.ROUTE_TOPOLOGY
+ });
+ getTopologies(
+ AppStore.getActiveTopologyOptions()
+ );
+ getNodesDelta(
+ AppStore.getCurrentTopologyUrl(),
+ AppStore.getActiveTopologyOptions()
+ );
+ getNodeDetails(
+ AppStore.getCurrentTopologyUrl(),
+ AppStore.getSelectedNodeId()
+ );
+}
diff --git a/client/app/scripts/charts/edge.js b/client/app/scripts/charts/edge.js
index 83ada988e..44d7e6a06 100644
--- a/client/app/scripts/charts/edge.js
+++ b/client/app/scripts/charts/edge.js
@@ -1,17 +1,16 @@
-const _ = require('lodash');
-const d3 = require('d3');
-const React = require('react');
-const Motion = require('react-motion').Motion;
-const spring = require('react-motion').spring;
+import _ from 'lodash';
+import d3 from 'd3';
+import React from 'react';
+import { Motion, spring } from 'react-motion';
-const AppActions = require('../actions/app-actions');
+import { enterEdge, leaveEdge } from '../actions/app-actions';
const line = d3.svg.line()
.interpolate('basis')
.x(function(d) { return d.x; })
.y(function(d) { return d.y; });
-const animConfig = [80, 20]; // stiffness, bounce
+const animConfig = [80, 20];// stiffness, bounce
const flattenPoints = function(points) {
const flattened = {};
@@ -35,23 +34,26 @@ const extractPoints = function(points) {
return extracted;
};
-const Edge = React.createClass({
+export default class Edge extends React.Component {
+ constructor(props, context) {
+ super(props, context);
+ this.handleMouseEnter = this.handleMouseEnter.bind(this);
+ this.handleMouseLeave = this.handleMouseLeave.bind(this);
- getInitialState: function() {
- return {
+ this.state = {
points: []
};
- },
+ }
- componentWillMount: function() {
+ componentWillMount() {
this.ensureSameLength(this.props.points);
- },
+ }
- componentWillReceiveProps: function(nextProps) {
+ componentWillReceiveProps(nextProps) {
this.ensureSameLength(nextProps.points);
- },
+ }
- render: function() {
+ render() {
const classNames = ['edge'];
const points = flattenPoints(this.props.points);
const props = this.props;
@@ -79,9 +81,9 @@ const Edge = React.createClass({
}}
);
- },
+ }
- ensureSameLength: function(points) {
+ ensureSameLength(points) {
// Spring needs constant list length, hoping that dagre will insert never more than 10
const length = 10;
let missing = length - points.length;
@@ -92,16 +94,13 @@ const Edge = React.createClass({
}
return points;
- },
-
- handleMouseEnter: function(ev) {
- AppActions.enterEdge(ev.currentTarget.id);
- },
-
- handleMouseLeave: function(ev) {
- AppActions.leaveEdge(ev.currentTarget.id);
}
-});
+ handleMouseEnter(ev) {
+ enterEdge(ev.currentTarget.id);
+ }
-module.exports = Edge;
+ handleMouseLeave(ev) {
+ leaveEdge(ev.currentTarget.id);
+ }
+}
diff --git a/client/app/scripts/charts/node.js b/client/app/scripts/charts/node.js
index 81eeb5033..faf0cfb8d 100644
--- a/client/app/scripts/charts/node.js
+++ b/client/app/scripts/charts/node.js
@@ -1,16 +1,18 @@
-const React = require('react');
-const Motion = require('react-motion').Motion;
-const spring = require('react-motion').spring;
+import React from 'react';
+import { Motion, spring } from 'react-motion';
-const AppActions = require('../actions/app-actions');
-const NodeColorMixin = require('../mixins/node-color-mixin');
+import { clickNode, enterNode, leaveNode } from '../actions/app-actions';
+import { getNodeColor } from '../utils/color-utils';
-const Node = React.createClass({
- mixins: [
- NodeColorMixin
- ],
+export default class Node extends React.Component {
+ constructor(props, context) {
+ super(props, context);
+ this.handleMouseClick = this.handleMouseClick.bind(this);
+ this.handleMouseEnter = this.handleMouseEnter.bind(this);
+ this.handleMouseLeave = this.handleMouseLeave.bind(this);
+ }
- render: function() {
+ render() {
const props = this.props;
const nodeScale = props.focused ? props.selectedNodeScale : props.nodeScale;
const zoomScale = this.props.zoomScale;
@@ -23,7 +25,7 @@ const Node = React.createClass({
let labelOffsetY = 18;
let subLabelOffsetY = 35;
const isPseudo = !!this.props.pseudo;
- const color = isPseudo ? '' : this.getNodeColor(this.props.rank, this.props.label);
+ const color = isPseudo ? '' : getNodeColor(this.props.rank, this.props.label);
const onMouseEnter = this.handleMouseEnter;
const onMouseLeave = this.handleMouseLeave;
const onMouseClick = this.handleMouseClick;
@@ -83,9 +85,9 @@ const Node = React.createClass({
}}
);
- },
+ }
- ellipsis: function(text, fontSize, maxWidth) {
+ ellipsis(text, fontSize, maxWidth) {
const averageCharLength = fontSize / 1.5;
const allowedChars = maxWidth / averageCharLength;
let truncatedText = text;
@@ -93,21 +95,18 @@ const Node = React.createClass({
truncatedText = text.slice(0, allowedChars) + '...';
}
return truncatedText;
- },
-
- handleMouseClick: function(ev) {
- ev.stopPropagation();
- AppActions.clickNode(ev.currentTarget.id);
- },
-
- handleMouseEnter: function(ev) {
- AppActions.enterNode(ev.currentTarget.id);
- },
-
- handleMouseLeave: function(ev) {
- AppActions.leaveNode(ev.currentTarget.id);
}
-});
+ handleMouseClick(ev) {
+ ev.stopPropagation();
+ clickNode(ev.currentTarget.id);
+ }
-module.exports = Node;
+ handleMouseEnter(ev) {
+ enterNode(ev.currentTarget.id);
+ }
+
+ handleMouseLeave(ev) {
+ leaveNode(ev.currentTarget.id);
+ }
+}
diff --git a/client/app/scripts/charts/nodes-chart.js b/client/app/scripts/charts/nodes-chart.js
index 4c5507854..5208c3862 100644
--- a/client/app/scripts/charts/nodes-chart.js
+++ b/client/app/scripts/charts/nodes-chart.js
@@ -1,17 +1,19 @@
-const _ = require('lodash');
-const d3 = require('d3');
-const debug = require('debug')('scope:nodes-chart');
-const React = require('react');
-const makeMap = require('immutable').Map;
-const timely = require('timely');
+import _ from 'lodash';
+import d3 from 'd3';
+import debug from 'debug';
+import React from 'react';
+import { Map as makeMap } from 'immutable';
+import timely from 'timely';
-const AppActions = require('../actions/app-actions');
-const AppStore = require('../stores/app-store');
-const Edge = require('./edge');
-const Naming = require('../constants/naming');
-const NodesLayout = require('./nodes-layout');
-const Node = require('./node');
-const NodesError = require('./nodes-error');
+import { clickCloseDetails } from '../actions/app-actions';
+import AppStore from '../stores/app-store';
+import Edge from './edge';
+import { EDGE_ID_SEPARATOR } from '../constants/naming';
+import { doLayout } from './nodes-layout';
+import Node from './node';
+import NodesError from './nodes-error';
+
+const log = debug('scope:nodes-chart');
const MARGINS = {
top: 130,
@@ -24,10 +26,13 @@ const MARGINS = {
const radiusDensity = d3.scale.threshold()
.domain([3, 6]).range([2.5, 3.5, 3]);
-const NodesChart = React.createClass({
+export default class NodesChart extends React.Component {
+ constructor(props, context) {
+ super(props, context);
+ this.handleMouseClick = this.handleMouseClick.bind(this);
+ this.zoomed = this.zoomed.bind(this);
- getInitialState: function() {
- return {
+ this.state = {
nodes: makeMap(),
edges: makeMap(),
panTranslate: [0, 0],
@@ -37,23 +42,26 @@ const NodesChart = React.createClass({
hasZoomed: false,
maxNodesExceeded: false
};
- },
+ }
- componentWillMount: function() {
+ componentWillMount() {
const state = this.updateGraphState(this.props, this.state);
this.setState(state);
- },
+ }
+
+ componentDidMount() {
+ // distinguish pan/zoom from click
+ this.isZooming = false;
- componentDidMount: function() {
this.zoom = d3.behavior.zoom()
.scaleExtent([0.1, 2])
.on('zoom', this.zoomed);
d3.select('.nodes-chart svg')
.call(this.zoom);
- },
+ }
- componentWillReceiveProps: function(nextProps) {
+ componentWillReceiveProps(nextProps) {
// gather state, setState should be called only once here
const state = _.assign({}, this.state);
@@ -76,9 +84,9 @@ const NodesChart = React.createClass({
}
this.setState(state);
- },
+ }
- componentWillUnmount: function() {
+ componentWillUnmount() {
// undoing .call(zoom)
d3.select('.nodes-chart svg')
@@ -87,9 +95,9 @@ const NodesChart = React.createClass({
.on('onmousewheel', null)
.on('dblclick.zoom', null)
.on('touchstart.zoom', null);
- },
+ }
- renderGraphNodes: function(nodes, nodeScale) {
+ renderGraphNodes(nodes, nodeScale) {
const hasSelectedNode = this.props.selectedNodeId && this.props.nodes.has(this.props.selectedNodeId);
const adjacency = hasSelectedNode ? AppStore.getAdjacentNodes(this.props.selectedNodeId) : null;
const onNodeClick = this.props.onNodeClick;
@@ -148,9 +156,9 @@ const NodesChart = React.createClass({
/>
);
});
- },
+ }
- renderGraphEdges: function(edges) {
+ renderGraphEdges(edges) {
const selectedNodeId = this.props.selectedNodeId;
const hasSelectedNode = selectedNodeId && this.props.nodes.has(selectedNodeId);
@@ -173,18 +181,18 @@ const NodesChart = React.createClass({
blurred={edge.get('blurred')} highlighted={edge.get('highlighted')} />
);
});
- },
+ }
- renderMaxNodesError: function(show) {
+ renderMaxNodesError(show) {
const errorHint = 'We\u0027re working on it, but for now, try a different view?';
return (
Too many nodes to show in the browser.
{errorHint}
);
- },
+ }
- renderEmptyTopologyError: function(show) {
+ renderEmptyTopologyError(show) {
return (
Nothing to show. This can have any of these reasons:
@@ -195,9 +203,9 @@ const NodesChart = React.createClass({
);
- },
+ }
- render: function() {
+ render() {
const nodeElements = this.renderGraphNodes(this.state.nodes, this.state.nodeScale);
const edgeElements = this.renderGraphEdges(this.state.edges, this.state.nodeScale);
const scale = this.state.scale;
@@ -224,9 +232,9 @@ const NodesChart = React.createClass({
);
- },
+ }
- initNodes: function(topology) {
+ initNodes(topology) {
return topology.map((node, id) => {
// copy relevant fields to state nodes
return makeMap({
@@ -239,9 +247,9 @@ const NodesChart = React.createClass({
y: 0
});
});
- },
+ }
- initEdges: function(topology, stateNodes) {
+ initEdges(topology, stateNodes) {
let edges = makeMap();
topology.forEach(function(node, nodeId) {
@@ -249,14 +257,14 @@ const NodesChart = React.createClass({
if (adjacency) {
adjacency.forEach(function(adjacent) {
const edge = [nodeId, adjacent];
- const edgeId = edge.join(Naming.EDGE_ID_SEPARATOR);
+ const edgeId = edge.join(EDGE_ID_SEPARATOR);
if (!edges.has(edgeId)) {
const source = edge[0];
const target = edge[1];
if (!stateNodes.has(source) || !stateNodes.has(target)) {
- debug('Missing edge node', edge[0], edge[1]);
+ log('Missing edge node', edge[0], edge[1]);
}
edges = edges.set(edgeId, makeMap({
@@ -271,9 +279,9 @@ const NodesChart = React.createClass({
});
return edges;
- },
+ }
- centerSelectedNode: function(props, state) {
+ centerSelectedNode(props, state) {
let stateNodes = state.nodes;
let stateEdges = state.edges;
const selectedLayoutNode = stateNodes.get(props.selectedNodeId);
@@ -345,19 +353,17 @@ const NodesChart = React.createClass({
edges: stateEdges,
nodes: stateNodes
};
- },
+ }
- isZooming: false, // distinguish pan/zoom from click
-
- handleMouseClick: function() {
+ handleMouseClick() {
if (!this.isZooming) {
- AppActions.clickCloseDetails();
+ clickCloseDetails();
} else {
this.isZooming = false;
}
- },
+ }
- restoreLayout: function(state) {
+ restoreLayout(state) {
// undo any pan/zooming that might have happened
this.zoom.scale(state.scale);
this.zoom.translate(state.panTranslate);
@@ -377,9 +383,9 @@ const NodesChart = React.createClass({
});
return { edges, nodes};
- },
+ }
- updateGraphState: function(props, state) {
+ updateGraphState(props, state) {
const n = props.nodes.size;
if (n === 0) {
@@ -401,10 +407,10 @@ const NodesChart = React.createClass({
topologyId: this.props.topologyId
};
- const timedLayouter = timely(NodesLayout.doLayout);
+ const timedLayouter = timely(doLayout);
const graph = timedLayouter(stateNodes, stateEdges, options);
- debug('graph layout took ' + timedLayouter.time + 'ms');
+ log('graph layout took ' + timedLayouter.time + 'ms');
// layout was aborted
if (!graph) {
@@ -443,17 +449,17 @@ const NodesChart = React.createClass({
nodeScale: nodeScale,
maxNodesExceeded: false
};
- },
+ }
- getNodeScale: function(props) {
+ getNodeScale(props) {
const expanse = Math.min(props.height, props.width);
const nodeSize = expanse / 3; // single node should fill a third of the screen
const maxNodeSize = expanse / 10;
const normalizedNodeSize = Math.min(nodeSize / Math.sqrt(props.nodes.size), maxNodeSize);
return this.state.nodeScale.copy().range([0, normalizedNodeSize]);
- },
+ }
- zoomed: function() {
+ zoomed() {
// debug('zoomed', d3.event.scale, d3.event.translate);
this.isZooming = true;
// dont pan while node is selected
@@ -465,7 +471,4 @@ const NodesChart = React.createClass({
});
}
}
-
-});
-
-module.exports = NodesChart;
+}
diff --git a/client/app/scripts/charts/nodes-error.js b/client/app/scripts/charts/nodes-error.js
index c2edda629..68bc4af89 100644
--- a/client/app/scripts/charts/nodes-error.js
+++ b/client/app/scripts/charts/nodes-error.js
@@ -1,8 +1,7 @@
-const React = require('react');
+import React from 'react';
-const NodesError = React.createClass({
-
- render: function() {
+export default class NodesError extends React.Component {
+ render() {
let classNames = 'nodes-chart-error';
if (this.props.hidden) {
classNames += ' hide';
@@ -18,7 +17,4 @@ const NodesError = React.createClass({
);
}
-
-});
-
-module.exports = NodesError;
+}
diff --git a/client/app/scripts/charts/nodes-layout.js b/client/app/scripts/charts/nodes-layout.js
index 5fc5bf68f..c4db147ad 100644
--- a/client/app/scripts/charts/nodes-layout.js
+++ b/client/app/scripts/charts/nodes-layout.js
@@ -1,10 +1,11 @@
-const dagre = require('dagre');
-const debug = require('debug')('scope:nodes-layout');
-const makeMap = require('immutable').Map;
-const ImmSet = require('immutable').Set;
+import dagre from 'dagre';
+import debug from 'debug';
+import { Map as makeMap, Set as ImmSet } from 'immutable';
-const Naming = require('../constants/naming');
-const TopologyUtils = require('./topology-utils');
+import { EDGE_ID_SEPARATOR } from '../constants/naming';
+import { updateNodeDegrees } from './topology-utils';
+
+const log = debug('scope:nodes-layout');
const MAX_NODES = 100;
const topologyCaches = {};
@@ -32,7 +33,7 @@ function runLayoutEngine(graph, imNodes, imEdges, opts) {
let edges = imEdges;
if (nodes.size > MAX_NODES) {
- debug('Too many nodes for graph layout engine. Limit: ' + MAX_NODES);
+ log('Too many nodes for graph layout engine. Limit: ' + MAX_NODES);
return null;
}
@@ -82,7 +83,7 @@ function runLayoutEngine(graph, imNodes, imEdges, opts) {
// remove edges that are no longer there
graph.edges().forEach(edgeObj => {
const edge = [edgeObj.v, edgeObj.w];
- const edgeId = edge.join(Naming.EDGE_ID_SEPARATOR);
+ const edgeId = edge.join(EDGE_ID_SEPARATOR);
if (!edges.has(edgeId)) {
graph.removeEdge(edgeObj.v, edgeObj.w);
}
@@ -148,14 +149,14 @@ function layoutSingleNodes(layout, opts) {
const nonSingleNodes = nodes.filter(node => node.get('degree') !== 0);
if (nonSingleNodes.size > 0) {
if (aspectRatio < 1) {
- debug('laying out single nodes to the right', aspectRatio);
+ log('laying out single nodes to the right', aspectRatio);
offsetX = nonSingleNodes.maxBy(node => node.get('x')).get('x');
offsetY = nonSingleNodes.minBy(node => node.get('y')).get('y');
if (offsetX) {
offsetX += nodeWidth + nodesep;
}
} else {
- debug('laying out single nodes below', aspectRatio);
+ log('laying out single nodes below', aspectRatio);
offsetX = nonSingleNodes.minBy(node => node.get('x')).get('x');
offsetY = nonSingleNodes.maxBy(node => node.get('y')).get('y');
if (offsetY) {
@@ -264,7 +265,7 @@ export function hasUnseenNodes(nodes, cache) {
const hasUnseen = nodes.size > cache.size
|| !ImmSet.fromKeys(nodes).isSubset(ImmSet.fromKeys(cache));
if (hasUnseen) {
- debug('unseen nodes:', ...ImmSet.fromKeys(nodes).subtract(ImmSet.fromKeys(cache)).toJS());
+ log('unseen nodes:', ...ImmSet.fromKeys(nodes).subtract(ImmSet.fromKeys(cache)).toJS());
}
return hasUnseen;
}
@@ -350,13 +351,13 @@ export function doLayout(immNodes, immEdges, opts) {
++layoutRuns;
if (cachedLayout && nodeCache && edgeCache && !hasUnseenNodes(immNodes, nodeCache)) {
- debug('skip layout, trivial adjustment', ++layoutRunsTrivial, layoutRuns);
+ log('skip layout, trivial adjustment', ++layoutRunsTrivial, layoutRuns);
layout = cloneLayout(cachedLayout, immNodes, immEdges);
// copy old properties, works also if nodes get re-added
layout = copyLayoutProperties(layout, nodeCache, edgeCache);
} else {
const graph = cache.graph;
- const nodesWithDegrees = TopologyUtils.updateNodeDegrees(immNodes, immEdges);
+ const nodesWithDegrees = updateNodeDegrees(immNodes, immEdges);
layout = runLayoutEngine(graph, nodesWithDegrees, immEdges, opts);
layout = layoutSingleNodes(layout, opts);
layout = shiftLayoutToCenter(layout, opts);
diff --git a/client/app/scripts/components/__tests__/node-details-test.js b/client/app/scripts/components/__tests__/node-details-test.js
index 852a5fd85..c1b22a3e2 100644
--- a/client/app/scripts/components/__tests__/node-details-test.js
+++ b/client/app/scripts/components/__tests__/node-details-test.js
@@ -1,19 +1,22 @@
+import React from 'react';
+import Immutable from 'immutable';
+import TestUtils from 'react/lib/ReactTestUtils';
+
+jest.dontMock('../../dispatcher/app-dispatcher');
jest.dontMock('../node-details.js');
-jest.dontMock('../../mixins/node-color-mixin');
+jest.dontMock('../../utils/color-utils');
jest.dontMock('../../utils/title-utils');
+// need ES5 require to keep automocking off
+const NodeDetails = require('../node-details.js').default;
+
describe('NodeDetails', () => {
- let NodeDetails;
let nodes;
let nodeId;
let details;
- const React = require('react');
- const Immutable = require('immutable');
- const TestUtils = require('react/lib/ReactTestUtils');
const makeMap = Immutable.OrderedMap;
beforeEach(() => {
- NodeDetails = require('../node-details.js');
nodes = makeMap();
nodeId = 'n1';
});
diff --git a/client/app/scripts/components/app.js b/client/app/scripts/components/app.js
index f31f70747..6f6e7af3c 100644
--- a/client/app/scripts/components/app.js
+++ b/client/app/scripts/components/app.js
@@ -1,16 +1,16 @@
-const React = require('react');
+import React from 'react';
-const Logo = require('./logo');
-const AppStore = require('../stores/app-store');
-const Sidebar = require('./sidebar.js');
-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');
-const Nodes = require('./nodes');
-const RouterUtils = require('../utils/router-utils');
+import Logo from './logo';
+import AppStore from '../stores/app-store';
+import Sidebar from './sidebar.js';
+import Status from './status.js';
+import Topologies from './topologies.js';
+import TopologyOptions from './topology-options.js';
+import { getApiDetails, getTopologies } from '../utils/web-api-utils';
+import { hitEsc } from '../actions/app-actions';
+import Details from './details';
+import Nodes from './nodes';
+import { getRouter } from '../utils/router-utils';
const ESC_KEY_CODE = 27;
@@ -36,35 +36,37 @@ function getStateFromStores() {
}
-const App = React.createClass({
+export default class App extends React.Component {
+ constructor(props, context) {
+ super(props, context);
+ this.onChange = this.onChange.bind(this);
+ this.onKeyPress = this.onKeyPress.bind(this);
+ this.state = getStateFromStores();
+ }
- getInitialState: function() {
- return getStateFromStores();
- },
-
- componentDidMount: function() {
- AppStore.on(AppStore.CHANGE_EVENT, this.onChange);
+ componentDidMount() {
+ AppStore.addListener(this.onChange);
window.addEventListener('keyup', this.onKeyPress);
- RouterUtils.getRouter().start({hashbang: true});
+ getRouter().start({hashbang: true});
if (!AppStore.isRouteSet()) {
// dont request topologies when already done via router
- WebapiUtils.getTopologies(AppStore.getActiveTopologyOptions());
+ getTopologies(AppStore.getActiveTopologyOptions());
}
- WebapiUtils.getApiDetails();
- },
+ getApiDetails();
+ }
- onChange: function() {
+ onChange() {
this.setState(getStateFromStores());
- },
+ }
- onKeyPress: function(ev) {
+ onKeyPress(ev) {
if (ev.keyCode === ESC_KEY_CODE) {
- AppActions.hitEsc();
+ hitEsc();
}
- },
+ }
- render: function() {
+ render() {
const showingDetails = this.state.selectedNodeId;
const versionString = this.state.version ? 'Version ' + this.state.version : '';
// width of details panel blocking a view
@@ -104,8 +106,5 @@ const App = React.createClass({
);
- },
-
-});
-
-module.exports = App;
+ }
+}
diff --git a/client/app/scripts/components/details.js b/client/app/scripts/components/details.js
index da74daaf3..77637543e 100644
--- a/client/app/scripts/components/details.js
+++ b/client/app/scripts/components/details.js
@@ -1,16 +1,20 @@
-const React = require('react');
+import React from 'react';
-const AppActions = require('../actions/app-actions');
-const NodeDetails = require('./node-details');
+import { clickCloseDetails } from '../actions/app-actions';
+import NodeDetails from './node-details';
-const Details = React.createClass({
+export default class Details extends React.Component {
+ constructor(props, context) {
+ super(props, context);
+ this.handleClickClose = this.handleClickClose.bind(this);
+ }
- handleClickClose: function(ev) {
+ handleClickClose(ev) {
ev.preventDefault();
- AppActions.clickCloseDetails();
- },
+ clickCloseDetails();
+ }
- render: function() {
+ render() {
return (
);
}
-
-});
-
-module.exports = Details;
+}
diff --git a/client/app/scripts/components/logo.js b/client/app/scripts/components/logo.js
index 003e40c79..fe5c53e4d 100644
--- a/client/app/scripts/components/logo.js
+++ b/client/app/scripts/components/logo.js
@@ -1,8 +1,7 @@
-const React = require('react');
+import React from 'react';
-const Logo = React.createClass({
-
- render: function() {
+export default class Logo extends React.Component {
+ render() {
return (
);
}
-
-});
-
-module.exports = Logo;
+}
diff --git a/client/app/scripts/components/node-details.js b/client/app/scripts/components/node-details.js
index aef686035..10721a2d2 100644
--- a/client/app/scripts/components/node-details.js
+++ b/client/app/scripts/components/node-details.js
@@ -1,28 +1,23 @@
-const _ = require('lodash');
-const React = require('react');
+import _ from 'lodash';
+import React from 'react';
-const NodeDetailsControls = require('./node-details/node-details-controls');
-const NodeDetailsTable = require('./node-details/node-details-table');
-const NodeColorMixin = require('../mixins/node-color-mixin');
-const TitleUtils = require('../utils/title-utils');
+import NodeDetailsControls from './node-details/node-details-controls';
+import NodeDetailsTable from './node-details/node-details-table';
+import { brightenColor, getNodeColorDark } from '../utils/color-utils';
+import { resetDocumentTitle, setDocumentTitle } from '../utils/title-utils';
-const NodeDetails = React.createClass({
-
- mixins: [
- NodeColorMixin
- ],
-
- componentDidMount: function() {
+export default class NodeDetails extends React.Component {
+ componentDidMount() {
this.updateTitle();
- },
+ }
- componentWillUnmount: function() {
- TitleUtils.resetTitle();
- },
+ componentWillUnmount() {
+ resetDocumentTitle();
+ }
- renderLoading: function() {
+ renderLoading() {
const node = this.props.nodes.get(this.props.nodeId);
- const nodeColor = this.getNodeColorDark(node.get('rank'), node.get('label_major'));
+ const nodeColor = getNodeColorDark(node.get('rank'), node.get('label_major'));
const styles = {
header: {
'backgroundColor': nodeColor
@@ -48,9 +43,9 @@ const NodeDetails = React.createClass({
);
- },
+ }
- renderNotAvailable: function() {
+ renderNotAvailable() {
return (
@@ -71,9 +66,9 @@ const NodeDetails = React.createClass({
);
- },
+ }
- render: function() {
+ render() {
const details = this.props.details;
const nodeExists = this.props.nodes && this.props.nodes.has(this.props.nodeId);
@@ -86,14 +81,14 @@ const NodeDetails = React.createClass({
}
return this.renderLoading();
- },
+ }
- renderDetails: function() {
+ renderDetails() {
const details = this.props.details;
- const nodeColor = this.getNodeColorDark(details.rank, details.label_major);
+ const nodeColor = getNodeColorDark(details.rank, details.label_major);
const styles = {
controls: {
- 'backgroundColor': this.brightenColor(nodeColor)
+ 'backgroundColor': brightenColor(nodeColor)
},
header: {
'backgroundColor': nodeColor
@@ -126,16 +121,13 @@ const NodeDetails = React.createClass({
);
- },
-
- componentDidUpdate: function() {
- this.updateTitle();
- },
-
- updateTitle: function() {
- TitleUtils.setTitle(this.props.details && this.props.details.label_major);
}
-});
+ componentDidUpdate() {
+ this.updateTitle();
+ }
-module.exports = NodeDetails;
+ updateTitle() {
+ setDocumentTitle(this.props.details && this.props.details.label_major);
+ }
+}
diff --git a/client/app/scripts/components/node-details/node-details-control-button.js b/client/app/scripts/components/node-details/node-details-control-button.js
index c66c9b0ad..48be014ed 100644
--- a/client/app/scripts/components/node-details/node-details-control-button.js
+++ b/client/app/scripts/components/node-details/node-details-control-button.js
@@ -1,10 +1,14 @@
-const React = require('react');
+import React from 'react';
-const AppActions = require('../../actions/app-actions');
+import { doControl } from '../../actions/app-actions';
-const NodeDetailsControlButton = React.createClass({
+export default class NodeDetailsControlButton extends React.Component {
+ constructor(props, context) {
+ super(props, context);
+ this.handleClick = this.handleClick.bind(this);
+ }
- render: function() {
+ render() {
let className = `node-control-button fa ${this.props.control.icon}`;
if (this.props.pending) {
className += ' node-control-button-pending';
@@ -12,13 +16,10 @@ const NodeDetailsControlButton = React.createClass({
return (
);
- },
-
- handleClick: function(ev) {
- ev.preventDefault();
- AppActions.doControl(this.props.control.probeId, this.props.control.nodeId, this.props.control.id);
}
-});
-
-module.exports = NodeDetailsControlButton;
+ handleClick(ev) {
+ ev.preventDefault();
+ doControl(this.props.control.probeId, this.props.control.nodeId, this.props.control.id);
+ }
+}
diff --git a/client/app/scripts/components/node-details/node-details-controls.js b/client/app/scripts/components/node-details/node-details-controls.js
index 8034bcce9..153081db5 100644
--- a/client/app/scripts/components/node-details/node-details-controls.js
+++ b/client/app/scripts/components/node-details/node-details-controls.js
@@ -1,10 +1,9 @@
-const React = require('react');
+import React from 'react';
-const NodeDetailsControlButton = require('./node-details-control-button');
+import NodeDetailsControlButton from './node-details-control-button';
-const NodeDetailsControls = React.createClass({
-
- render: function() {
+export default class NodeDetailsControls extends React.Component {
+ render() {
let spinnerClassName = 'fa fa-circle-o-notch fa-spin';
if (this.props.pending) {
spinnerClassName += ' node-details-controls-spinner';
@@ -30,7 +29,4 @@ const NodeDetailsControls = React.createClass({
);
}
-
-});
-
-module.exports = NodeDetailsControls;
+}
diff --git a/client/app/scripts/components/node-details/node-details-table-row-number.js b/client/app/scripts/components/node-details/node-details-table-row-number.js
index bcfd4a52a..d41eaa10a 100644
--- a/client/app/scripts/components/node-details/node-details-table-row-number.js
+++ b/client/app/scripts/components/node-details/node-details-table-row-number.js
@@ -1,8 +1,7 @@
-const React = require('react');
+import React from 'react';
-const NodeDetailsTableRowNumber = React.createClass({
-
- render: function() {
+export default class NodeDetailsTableRowNumber extends React.Component {
+ render() {
const row = this.props.row;
return (
@@ -11,6 +10,4 @@ const NodeDetailsTableRowNumber = React.createClass({
);
}
-});
-
-module.exports = NodeDetailsTableRowNumber;
+}
diff --git a/client/app/scripts/components/node-details/node-details-table-row-sparkline.js b/client/app/scripts/components/node-details/node-details-table-row-sparkline.js
index d59914aa3..f40b77d0f 100644
--- a/client/app/scripts/components/node-details/node-details-table-row-sparkline.js
+++ b/client/app/scripts/components/node-details/node-details-table-row-sparkline.js
@@ -1,10 +1,9 @@
-const React = require('react');
+import React from 'react';
-const Sparkline = require('../sparkline');
+import Sparkline from '../sparkline';
-const NodeDetailsTableRowSparkline = React.createClass({
-
- render: function() {
+export default class NodeDetailsTableRowSparkline extends React.Component {
+ render() {
const row = this.props.row;
return (
@@ -13,6 +12,4 @@ const NodeDetailsTableRowSparkline = React.createClass({
);
}
-});
-
-module.exports = NodeDetailsTableRowSparkline;
+}
diff --git a/client/app/scripts/components/node-details/node-details-table-row-value.js b/client/app/scripts/components/node-details/node-details-table-row-value.js
index 4cdcf9544..1dc765eb5 100644
--- a/client/app/scripts/components/node-details/node-details-table-row-value.js
+++ b/client/app/scripts/components/node-details/node-details-table-row-value.js
@@ -1,8 +1,7 @@
-const React = require('react');
+import React from 'react';
-const NodeDetailsTableRowValue = React.createClass({
-
- render: function() {
+export default class NodeDetailsTableRowValue extends React.Component {
+ render() {
const row = this.props.row;
return (
@@ -15,6 +14,4 @@ const NodeDetailsTableRowValue = React.createClass({
);
}
-});
-
-module.exports = NodeDetailsTableRowValue;
+}
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 1e92a639f..b95281b26 100644
--- a/client/app/scripts/components/node-details/node-details-table.js
+++ b/client/app/scripts/components/node-details/node-details-table.js
@@ -1,12 +1,11 @@
-const React = require('react');
+import React from 'react';
-const NodeDetailsTableRowValue = require('./node-details-table-row-value');
-const NodeDetailsTableRowNumber = require('./node-details-table-row-number');
-const NodeDetailsTableRowSparkline = require('./node-details-table-row-sparkline');
+import NodeDetailsTableRowValue from './node-details-table-row-value';
+import NodeDetailsTableRowNumber from './node-details-table-row-number';
+import NodeDetailsTableRowSparkline from './node-details-table-row-sparkline';
-const NodeDetailsTable = React.createClass({
-
- render: function() {
+export default class NodeDetailsTable extends React.Component {
+ render() {
return (
@@ -32,7 +31,4 @@ const NodeDetailsTable = React.createClass({
);
}
-
-});
-
-module.exports = NodeDetailsTable;
+}
diff --git a/client/app/scripts/components/nodes.js b/client/app/scripts/components/nodes.js
index 60cb2f932..291d27732 100644
--- a/client/app/scripts/components/nodes.js
+++ b/client/app/scripts/components/nodes.js
@@ -1,28 +1,30 @@
-const React = require('react');
+import React from 'react';
-const NodesChart = require('../charts/nodes-chart');
+import NodesChart from '../charts/nodes-chart';
const navbarHeight = 160;
const marginTop = 0;
-const Nodes = React.createClass({
+export default class Nodes extends React.Component {
+ constructor(props, context) {
+ super(props, context);
+ this.handleResize = this.handleResize.bind(this);
- getInitialState: function() {
- return {
+ this.state = {
width: window.innerWidth,
height: window.innerHeight - navbarHeight - marginTop
};
- },
+ }
- componentDidMount: function() {
+ componentDidMount() {
window.addEventListener('resize', this.handleResize);
- },
+ }
- componentWillUnmount: function() {
+ componentWillUnmount() {
window.removeEventListener('resize', this.handleResize);
- },
+ }
- render: function() {
+ render() {
return (
);
- },
+ }
- handleResize: function() {
+ handleResize() {
this.setDimensions();
- },
+ }
- setDimensions: function() {
+ setDimensions() {
const width = window.innerWidth;
const height = window.innerHeight - navbarHeight - marginTop;
this.setState({height, width});
}
-
-});
-
-module.exports = Nodes;
+}
diff --git a/client/app/scripts/components/sidebar.js b/client/app/scripts/components/sidebar.js
index 659b7226e..101f54808 100644
--- a/client/app/scripts/components/sidebar.js
+++ b/client/app/scripts/components/sidebar.js
@@ -1,15 +1,11 @@
-const React = require('react');
+import React from 'react';
-const Sidebar = React.createClass({
-
- render: function() {
+export default class Sidebar extends React.Component {
+ render() {
return (
{this.props.children}
);
}
-
-});
-
-module.exports = Sidebar;
+}
diff --git a/client/app/scripts/components/sparkline.js b/client/app/scripts/components/sparkline.js
index 216e11bb2..f72fbe748 100644
--- a/client/app/scripts/components/sparkline.js
+++ b/client/app/scripts/components/sparkline.js
@@ -1,26 +1,14 @@
// Forked from: https://github.com/KyleAMathews/react-sparkline at commit a9d7c5203d8f240938b9f2288287aaf0478df013
-const React = require('react');
-const ReactDOM = require('react-dom');
-const d3 = require('d3');
+import React from 'react';
+import ReactDOM from 'react-dom';
+import d3 from 'd3';
-const Sparkline = React.createClass({
- getDefaultProps: function() {
- return {
- width: 100,
- height: 16,
- strokeColor: '#7d7da8',
- strokeWidth: '0.5px',
- interpolate: 'basis',
- circleDiameter: 1.75,
- data: [1, 23, 5, 5, 23, 0, 0, 0, 4, 32, 3, 12, 3, 1, 24, 1, 5, 5, 24, 23] // Some semi-random data.
- };
- },
-
- componentDidMount: function() {
+export default class Sparkline extends React.Component {
+ componentDidMount() {
return this.renderSparkline();
- },
+ }
- renderSparkline: function() {
+ renderSparkline() {
// If the sparkline has already been rendered, remove it.
const el = ReactDOM.findDOMNode(this);
while (el.firstChild) {
@@ -114,17 +102,25 @@ const Sparkline = React.createClass({
attr('fill-opacity', 0.6).
attr('stroke', 'none').
attr('r', this.props.circleDiameter);
- },
+ }
- render: function() {
+ render() {
return (
);
- },
+ }
- componentDidUpdate: function() {
+ componentDidUpdate() {
return this.renderSparkline();
}
-});
+}
-module.exports = Sparkline;
+Sparkline.defaultProps = {
+ width: 100,
+ height: 16,
+ strokeColor: '#7d7da8',
+ strokeWidth: '0.5px',
+ interpolate: 'basis',
+ circleDiameter: 1.75,
+ data: [1, 23, 5, 5, 23, 0, 0, 0, 4, 32, 3, 12, 3, 1, 24, 1, 5, 5, 24, 23] // Some semi-random data.
+};
diff --git a/client/app/scripts/components/status.js b/client/app/scripts/components/status.js
index 8cf330574..11f2b3078 100644
--- a/client/app/scripts/components/status.js
+++ b/client/app/scripts/components/status.js
@@ -1,8 +1,7 @@
-const React = require('react');
+import React from 'react';
-const Status = React.createClass({
-
- render: function() {
+export default class Status extends React.Component {
+ render() {
let title = '';
let text = 'Trying to reconnect...';
let showWarningIcon = false;
@@ -36,7 +35,4 @@ const Status = React.createClass({
);
}
-
-});
-
-module.exports = Status;
+}
diff --git a/client/app/scripts/components/topologies.js b/client/app/scripts/components/topologies.js
index edfcca091..3566d2b34 100644
--- a/client/app/scripts/components/topologies.js
+++ b/client/app/scripts/components/topologies.js
@@ -1,16 +1,21 @@
-const React = require('react');
-const _ = require('lodash');
+import React from 'react';
+import _ from 'lodash';
-const AppActions = require('../actions/app-actions');
+import { clickTopology } from '../actions/app-actions';
-const Topologies = React.createClass({
+export default class Topologies extends React.Component {
+ constructor(props, context) {
+ super(props, context);
+ this.onTopologyClick = this.onTopologyClick.bind(this);
+ this.renderSubTopology = this.renderSubTopology.bind(this);
+ }
- onTopologyClick: function(ev) {
+ onTopologyClick(ev) {
ev.preventDefault();
- AppActions.clickTopology(ev.currentTarget.getAttribute('rel'));
- },
+ clickTopology(ev.currentTarget.getAttribute('rel'));
+ }
- renderSubTopology: function(subTopology) {
+ renderSubTopology(subTopology) {
const isActive = subTopology.name === this.props.currentTopology.name;
const topologyId = subTopology.id;
const title = this.renderTitle(subTopology);
@@ -24,14 +29,14 @@ const Topologies = React.createClass({
);
- },
+ }
- renderTitle: function(topology) {
+ renderTitle(topology) {
return ['Nodes: ' + topology.stats.node_count,
'Connections: ' + topology.stats.node_count].join('\n');
- },
+ }
- renderTopology: function(topology) {
+ renderTopology(topology) {
const isActive = topology.name === this.props.currentTopology.name;
const className = isActive ? 'topologies-item-main topologies-item-main-active' : 'topologies-item-main';
const topologyId = topology.id;
@@ -49,9 +54,9 @@ const Topologies = React.createClass({
);
- },
+ }
- render: function() {
+ render() {
const topologies = _.sortBy(this.props.topologies, function(topology) {
return topology.name;
});
@@ -64,7 +69,4 @@ const Topologies = React.createClass({
);
}
-
-});
-
-module.exports = Topologies;
+}
diff --git a/client/app/scripts/components/topology-option-action.js b/client/app/scripts/components/topology-option-action.js
index 78ecaf6c3..665bbe251 100644
--- a/client/app/scripts/components/topology-option-action.js
+++ b/client/app/scripts/components/topology-option-action.js
@@ -1,22 +1,23 @@
-const React = require('react');
+import React from 'react';
-const AppActions = require('../actions/app-actions');
+import { changeTopologyOption } from '../actions/app-actions';
-const TopologyOptionAction = React.createClass({
+export default class TopologyOptionAction extends React.Component {
+ constructor(props, context) {
+ super(props, context);
+ this.onClick = this.onClick.bind(this);
+ }
- onClick: function(ev) {
+ onClick(ev) {
ev.preventDefault();
- AppActions.changeTopologyOption(this.props.option, this.props.value, this.props.topologyId);
- },
+ changeTopologyOption(this.props.option, this.props.value, this.props.topologyId);
+ }
- render: function() {
+ render() {
return (
{this.props.value}
);
}
-
-});
-
-module.exports = TopologyOptionAction;
+}
diff --git a/client/app/scripts/components/topology-options.js b/client/app/scripts/components/topology-options.js
index 301324a37..9c598a3b3 100644
--- a/client/app/scripts/components/topology-options.js
+++ b/client/app/scripts/components/topology-options.js
@@ -1,22 +1,21 @@
-const React = require('react');
-const _ = require('lodash');
+import React from 'react';
+import _ from 'lodash';
-const TopologyOptionAction = require('./topology-option-action');
+import TopologyOptionAction from './topology-option-action';
-const TopologyOptions = React.createClass({
-
- renderAction: function(action, option, topologyId) {
+export default class TopologyOptions extends React.Component {
+ renderAction(action, option, topologyId) {
return (
);
- },
+ }
/**
* transforms a list of options into one sidebar-item.
* The sidebar text comes from the active option. the actions come from the
* remaining items.
*/
- renderOption: function(items) {
+ renderOption(items) {
let activeText;
let activeValue;
const actions = [];
@@ -53,9 +52,9 @@ const TopologyOptions = React.createClass({
);
- },
+ }
- render: function() {
+ render() {
const options = _.sortBy(
_.map(this.props.options, function(items, optionId) {
_.each(items, function(item) {
@@ -75,7 +74,4 @@ const TopologyOptions = React.createClass({
);
}
-
-});
-
-module.exports = TopologyOptions;
+}
diff --git a/client/app/scripts/constants/action-types.js b/client/app/scripts/constants/action-types.js
index 06ff46af6..2a05b5b78 100644
--- a/client/app/scripts/constants/action-types.js
+++ b/client/app/scripts/constants/action-types.js
@@ -1,26 +1,28 @@
-const keymirror = require('keymirror');
+import _ from 'lodash';
-module.exports = keymirror({
- CHANGE_TOPOLOGY_OPTION: null,
- CLEAR_CONTROL_ERROR: null,
- CLICK_CLOSE_DETAILS: null,
- CLICK_NODE: null,
- CLICK_TOPOLOGY: null,
- CLOSE_WEBSOCKET: null,
- DO_CONTROL: null,
- DO_CONTROL_ERROR: null,
- DO_CONTROL_SUCCESS: null,
- ENTER_EDGE: null,
- ENTER_NODE: null,
- HIT_ESC_KEY: null,
- LEAVE_EDGE: null,
- LEAVE_NODE: null,
- OPEN_WEBSOCKET: null,
- RECEIVE_NODE_DETAILS: null,
- RECEIVE_NODES: null,
- RECEIVE_NODES_DELTA: null,
- RECEIVE_TOPOLOGIES: null,
- RECEIVE_API_DETAILS: null,
- RECEIVE_ERROR: null,
- ROUTE_TOPOLOGY: null
-});
+const ACTION_TYPES = [
+ 'CHANGE_TOPOLOGY_OPTION',
+ 'CLEAR_CONTROL_ERROR',
+ 'CLICK_CLOSE_DETAILS',
+ 'CLICK_NODE',
+ 'CLICK_TOPOLOGY',
+ 'CLOSE_WEBSOCKET',
+ 'DO_CONTROL',
+ 'DO_CONTROL_ERROR',
+ 'DO_CONTROL_SUCCESS',
+ 'ENTER_EDGE',
+ 'ENTER_NODE',
+ 'HIT_ESC_KEY',
+ 'LEAVE_EDGE',
+ 'LEAVE_NODE',
+ 'OPEN_WEBSOCKET',
+ 'RECEIVE_NODE_DETAILS',
+ 'RECEIVE_NODES',
+ 'RECEIVE_NODES_DELTA',
+ 'RECEIVE_TOPOLOGIES',
+ 'RECEIVE_API_DETAILS',
+ 'RECEIVE_ERROR',
+ 'ROUTE_TOPOLOGY'
+];
+
+export default _.zipObject(ACTION_TYPES, ACTION_TYPES);
diff --git a/client/app/scripts/constants/naming.js b/client/app/scripts/constants/naming.js
index 8ed08ebb2..e79bd03fa 100644
--- a/client/app/scripts/constants/naming.js
+++ b/client/app/scripts/constants/naming.js
@@ -1,4 +1,2 @@
-module.exports = {
- EDGE_ID_SEPARATOR: '-'
-};
+export const EDGE_ID_SEPARATOR = '-';
diff --git a/client/app/scripts/dispatcher/app-dispatcher.js b/client/app/scripts/dispatcher/app-dispatcher.js
index 1f24e13fc..5ad898cda 100644
--- a/client/app/scripts/dispatcher/app-dispatcher.js
+++ b/client/app/scripts/dispatcher/app-dispatcher.js
@@ -1,12 +1,13 @@
-const flux = require('flux');
-const _ = require('lodash');
+import { Dispatcher } from 'flux';
+import _ from 'lodash';
-const AppDispatcher = new flux.Dispatcher();
+const instance = new Dispatcher();
-AppDispatcher.dispatch = _.wrap(flux.Dispatcher.prototype.dispatch, function(func) {
+instance.dispatch = _.wrap(Dispatcher.prototype.dispatch, function(func) {
const args = Array.prototype.slice.call(arguments, 1);
// console.log(args[0]);
func.apply(this, args);
});
-module.exports = AppDispatcher;
+export default instance;
+export const dispatch = instance.dispatch.bind(instance);
diff --git a/client/app/scripts/main.js b/client/app/scripts/main.js
index d2ab52ec8..4035f612f 100644
--- a/client/app/scripts/main.js
+++ b/client/app/scripts/main.js
@@ -1,10 +1,10 @@
require('font-awesome-webpack');
require('../styles/main.less');
-const React = require('react');
-const ReactDOM = require('react-dom');
+import React from 'react';
+import ReactDOM from 'react-dom';
-const App = require('./components/app.js');
+import App from './components/app.js';
ReactDOM.render(
,
diff --git a/client/app/scripts/stores/__tests__/app-store-test.js b/client/app/scripts/stores/__tests__/app-store-test.js
index 5deb0fef0..7ca500d8d 100644
--- a/client/app/scripts/stores/__tests__/app-store-test.js
+++ b/client/app/scripts/stores/__tests__/app-store-test.js
@@ -4,7 +4,7 @@ jest.dontMock('../app-store');
// Appstore test suite using Jasmine matchers
describe('AppStore', function() {
- const ActionTypes = require('../../constants/action-types');
+ const ActionTypes = require('../../constants/action-types').default;
let AppDispatcher;
let AppStore;
let registeredCallback;
@@ -134,9 +134,10 @@ describe('AppStore', function() {
};
beforeEach(function() {
- AppDispatcher = require('../../dispatcher/app-dispatcher');
- AppStore = require('../app-store');
- registeredCallback = AppDispatcher.register.mock.calls[0][0];
+ AppStore = require('../app-store').default;
+ AppDispatcher = AppStore.getDispatcher();
+ const callback = AppDispatcher.dispatch.bind(AppDispatcher);
+ registeredCallback = callback;
});
// topology tests
diff --git a/client/app/scripts/stores/app-store.js b/client/app/scripts/stores/app-store.js
index 913474b2e..1a2a956f7 100644
--- a/client/app/scripts/stores/app-store.js
+++ b/client/app/scripts/stores/app-store.js
@@ -1,14 +1,15 @@
-const EventEmitter = require('events').EventEmitter;
-const _ = require('lodash');
-const debug = require('debug')('scope:app-store');
-const Immutable = require('immutable');
+import _ from 'lodash';
+import debug from 'debug';
+import Immutable from 'immutable';
+import { Store } from 'flux/utils';
-const AppDispatcher = require('../dispatcher/app-dispatcher');
-const ActionTypes = require('../constants/action-types');
-const Naming = require('../constants/naming');
+import AppDispatcher from '../dispatcher/app-dispatcher';
+import ActionTypes from '../constants/action-types';
+import { EDGE_ID_SEPARATOR } from '../constants/naming';
const makeOrderedMap = Immutable.OrderedMap;
const makeSet = Immutable.Set;
+const log = debug('scope:app-store');
// Helpers
@@ -104,25 +105,23 @@ function deSelectNode() {
// Store API
-const AppStore = Object.assign({}, EventEmitter.prototype, {
-
- CHANGE_EVENT: 'change',
+export class AppStore extends Store {
// keep at the top
- getAppState: function() {
+ getAppState() {
return {
topologyId: currentTopologyId,
selectedNodeId: this.getSelectedNodeId(),
topologyOptions: topologyOptions.toJS() // all options
};
- },
+ }
- getActiveTopologyOptions: function() {
+ getActiveTopologyOptions() {
// options for current topology
return topologyOptions.get(currentTopologyId);
- },
+ }
- getAdjacentNodes: function(nodeId) {
+ getAdjacentNodes(nodeId) {
adjacentNodes = adjacentNodes.clear();
if (nodes.has(nodeId)) {
@@ -136,36 +135,36 @@ const AppStore = Object.assign({}, EventEmitter.prototype, {
}
return adjacentNodes;
- },
+ }
- getControlError: function() {
+ getControlError() {
return controlError;
- },
+ }
- getCurrentTopology: function() {
+ getCurrentTopology() {
if (!currentTopology) {
currentTopology = setTopology(currentTopologyId);
}
return currentTopology;
- },
+ }
- getCurrentTopologyId: function() {
+ getCurrentTopologyId() {
return currentTopologyId;
- },
+ }
- getCurrentTopologyOptions: function() {
+ getCurrentTopologyOptions() {
return currentTopology && currentTopology.options;
- },
+ }
- getCurrentTopologyUrl: function() {
+ getCurrentTopologyUrl() {
return currentTopology && currentTopology.url;
- },
+ }
- getErrorUrl: function() {
+ getErrorUrl() {
return errorUrl;
- },
+ }
- getHighlightedEdgeIds: function() {
+ getHighlightedEdgeIds() {
if (mouseOverNodeId && nodes.has(mouseOverNodeId)) {
// all neighbour combinations because we dont know which direction exists
const adjacency = nodes.get(mouseOverNodeId).get('adjacency');
@@ -173,8 +172,8 @@ const AppStore = Object.assign({}, EventEmitter.prototype, {
return _.flatten(
adjacency.forEach(function(nodeId) {
return [
- [nodeId, mouseOverNodeId].join(Naming.EDGE_ID_SEPARATOR),
- [mouseOverNodeId, nodeId].join(Naming.EDGE_ID_SEPARATOR)
+ [nodeId, mouseOverNodeId].join(EDGE_ID_SEPARATOR),
+ [mouseOverNodeId, nodeId].join(EDGE_ID_SEPARATOR)
];
})
);
@@ -184,9 +183,9 @@ const AppStore = Object.assign({}, EventEmitter.prototype, {
return mouseOverEdgeId;
}
return null;
- },
+ }
- getHighlightedNodeIds: function() {
+ getHighlightedNodeIds() {
if (mouseOverNodeId) {
const adjacency = this.getAdjacentNodes(mouseOverNodeId);
if (adjacency.size) {
@@ -194,243 +193,238 @@ const AppStore = Object.assign({}, EventEmitter.prototype, {
}
}
if (mouseOverEdgeId) {
- return mouseOverEdgeId.split(Naming.EDGE_ID_SEPARATOR);
+ return mouseOverEdgeId.split(EDGE_ID_SEPARATOR);
}
return null;
- },
+ }
- getNodeDetails: function() {
+ getNodeDetails() {
return nodeDetails;
- },
+ }
- getNodes: function() {
+ getNodes() {
return nodes;
- },
+ }
- getSelectedNodeId: function() {
+ getSelectedNodeId() {
return selectedNodeId;
- },
+ }
- getTopologies: function() {
+ getTopologies() {
return topologies;
- },
+ }
- getVersion: function() {
+ getVersion() {
return version;
- },
+ }
- isControlPending: function() {
+ isControlPending() {
return controlPending;
- },
+ }
- isRouteSet: function() {
+ isRouteSet() {
return routeSet;
- },
+ }
- isTopologiesLoaded: function() {
+ isTopologiesLoaded() {
return topologiesLoaded;
- },
+ }
- isTopologyEmpty: function() {
+ isTopologyEmpty() {
return currentTopology && currentTopology.stats && currentTopology.stats.node_count === 0 && nodes.size === 0;
- },
+ }
- isWebsocketClosed: function() {
+ isWebsocketClosed() {
return websocketClosed;
}
-});
+ __onDispatch(payload) {
+ switch (payload.type) {
-// Store Dispatch Hooks
-
-AppStore.registeredCallback = function(payload) {
- switch (payload.type) {
-
- case ActionTypes.CHANGE_TOPOLOGY_OPTION:
- if (topologyOptions.getIn([payload.topologyId, payload.option])
- !== payload.value) {
- nodes = nodes.clear();
- }
- topologyOptions = topologyOptions.setIn(
- [payload.topologyId, payload.option],
- payload.value
- );
- AppStore.emit(AppStore.CHANGE_EVENT);
- break;
-
- case ActionTypes.CLEAR_CONTROL_ERROR:
- controlError = null;
- AppStore.emit(AppStore.CHANGE_EVENT);
- break;
-
- case ActionTypes.CLICK_CLOSE_DETAILS:
- deSelectNode();
- AppStore.emit(AppStore.CHANGE_EVENT);
- break;
-
- case ActionTypes.CLICK_NODE:
- deSelectNode();
- if (payload.nodeId !== selectedNodeId) {
- // select new node if it's not the same (in that case just delesect)
- selectedNodeId = payload.nodeId;
- }
- AppStore.emit(AppStore.CHANGE_EVENT);
- break;
-
- case ActionTypes.CLICK_TOPOLOGY:
- deSelectNode();
- if (payload.topologyId !== currentTopologyId) {
- setTopology(payload.topologyId);
- nodes = nodes.clear();
- }
- AppStore.emit(AppStore.CHANGE_EVENT);
- break;
-
- case ActionTypes.CLOSE_WEBSOCKET:
- websocketClosed = true;
- AppStore.emit(AppStore.CHANGE_EVENT);
- break;
-
- case ActionTypes.DO_CONTROL:
- controlPending = true;
- controlError = null;
- AppStore.emit(AppStore.CHANGE_EVENT);
- break;
-
- case ActionTypes.ENTER_EDGE:
- mouseOverEdgeId = payload.edgeId;
- AppStore.emit(AppStore.CHANGE_EVENT);
- break;
-
- case ActionTypes.ENTER_NODE:
- mouseOverNodeId = payload.nodeId;
- AppStore.emit(AppStore.CHANGE_EVENT);
- break;
-
- case ActionTypes.HIT_ESC_KEY:
- deSelectNode();
- AppStore.emit(AppStore.CHANGE_EVENT);
- break;
-
- case ActionTypes.LEAVE_EDGE:
- mouseOverEdgeId = null;
- AppStore.emit(AppStore.CHANGE_EVENT);
- break;
-
- case ActionTypes.LEAVE_NODE:
- mouseOverNodeId = null;
- AppStore.emit(AppStore.CHANGE_EVENT);
- break;
-
- case ActionTypes.OPEN_WEBSOCKET:
- // flush nodes cache after re-connect
- nodes = nodes.clear();
- websocketClosed = false;
-
- AppStore.emit(AppStore.CHANGE_EVENT);
- break;
-
- case ActionTypes.DO_CONTROL_ERROR:
- controlPending = false;
- controlError = payload.error;
- AppStore.emit(AppStore.CHANGE_EVENT);
- break;
-
- case ActionTypes.DO_CONTROL_SUCCESS:
- controlPending = false;
- controlError = null;
- AppStore.emit(AppStore.CHANGE_EVENT);
- break;
-
- case ActionTypes.RECEIVE_ERROR:
- errorUrl = payload.errorUrl;
- AppStore.emit(AppStore.CHANGE_EVENT);
- break;
-
- case ActionTypes.RECEIVE_NODE_DETAILS:
- errorUrl = null;
- // disregard if node is not selected anymore
- if (payload.details.id === selectedNodeId) {
- nodeDetails = payload.details;
- }
- AppStore.emit(AppStore.CHANGE_EVENT);
- break;
-
- case ActionTypes.RECEIVE_NODES_DELTA:
- const emptyMessage = !payload.delta.add && !payload.delta.remove
- && !payload.delta.update;
-
- if (!emptyMessage) {
- debug('RECEIVE_NODES_DELTA',
- 'remove', _.size(payload.delta.remove),
- 'update', _.size(payload.delta.update),
- 'add', _.size(payload.delta.add));
- }
-
- errorUrl = null;
-
- // nodes that no longer exist
- _.each(payload.delta.remove, function(nodeId) {
- // in case node disappears before mouseleave event
- if (mouseOverNodeId === nodeId) {
- mouseOverNodeId = null;
+ case ActionTypes.CHANGE_TOPOLOGY_OPTION:
+ if (topologyOptions.getIn([payload.topologyId, payload.option])
+ !== payload.value) {
+ nodes = nodes.clear();
}
- if (nodes.has(nodeId) && _.contains(mouseOverEdgeId, nodeId)) {
- mouseOverEdgeId = null;
+ topologyOptions = topologyOptions.setIn(
+ [payload.topologyId, payload.option],
+ payload.value
+ );
+ this.__emitChange();
+ break;
+
+ case ActionTypes.CLEAR_CONTROL_ERROR:
+ controlError = null;
+ this.__emitChange();
+ break;
+
+ case ActionTypes.CLICK_CLOSE_DETAILS:
+ deSelectNode();
+ this.__emitChange();
+ break;
+
+ case ActionTypes.CLICK_NODE:
+ deSelectNode();
+ if (payload.nodeId !== selectedNodeId) {
+ // select new node if it's not the same (in that case just delesect)
+ selectedNodeId = payload.nodeId;
}
- nodes = nodes.delete(nodeId);
- });
+ this.__emitChange();
+ break;
- // update existing nodes
- _.each(payload.delta.update, function(node) {
- nodes = nodes.set(node.id, nodes.get(node.id).merge(makeNode(node)));
- });
+ case ActionTypes.CLICK_TOPOLOGY:
+ deSelectNode();
+ if (payload.topologyId !== currentTopologyId) {
+ setTopology(payload.topologyId);
+ nodes = nodes.clear();
+ }
+ this.__emitChange();
+ break;
- // add new nodes
- _.each(payload.delta.add, function(node) {
- nodes = nodes.set(node.id, Immutable.fromJS(makeNode(node)));
- });
+ case ActionTypes.CLOSE_WEBSOCKET:
+ websocketClosed = true;
+ this.__emitChange();
+ break;
- AppStore.emit(AppStore.CHANGE_EVENT);
- break;
+ case ActionTypes.DO_CONTROL:
+ controlPending = true;
+ controlError = null;
+ this.__emitChange();
+ break;
- case ActionTypes.RECEIVE_TOPOLOGIES:
- errorUrl = null;
- topologies = processTopologies(payload.topologies);
- setTopology(currentTopologyId);
- // only set on first load, if options are not already set via route
- if (!topologiesLoaded && topologyOptions.size === 0) {
+ case ActionTypes.ENTER_EDGE:
+ mouseOverEdgeId = payload.edgeId;
+ this.__emitChange();
+ break;
+
+ case ActionTypes.ENTER_NODE:
+ mouseOverNodeId = payload.nodeId;
+ this.__emitChange();
+ break;
+
+ case ActionTypes.HIT_ESC_KEY:
+ deSelectNode();
+ this.__emitChange();
+ break;
+
+ case ActionTypes.LEAVE_EDGE:
+ mouseOverEdgeId = null;
+ this.__emitChange();
+ break;
+
+ case ActionTypes.LEAVE_NODE:
+ mouseOverNodeId = null;
+ this.__emitChange();
+ break;
+
+ case ActionTypes.OPEN_WEBSOCKET:
+ // flush nodes cache after re-connect
+ nodes = nodes.clear();
+ websocketClosed = false;
+
+ this.__emitChange();
+ break;
+
+ case ActionTypes.DO_CONTROL_ERROR:
+ controlPending = false;
+ controlError = payload.error;
+ this.__emitChange();
+ break;
+
+ case ActionTypes.DO_CONTROL_SUCCESS:
+ controlPending = false;
+ controlError = null;
+ this.__emitChange();
+ break;
+
+ case ActionTypes.RECEIVE_ERROR:
+ errorUrl = payload.errorUrl;
+ this.__emitChange();
+ break;
+
+ case ActionTypes.RECEIVE_NODE_DETAILS:
+ errorUrl = null;
+ // disregard if node is not selected anymore
+ if (payload.details.id === selectedNodeId) {
+ nodeDetails = payload.details;
+ }
+ this.__emitChange();
+ break;
+
+ case ActionTypes.RECEIVE_NODES_DELTA:
+ const emptyMessage = !payload.delta.add && !payload.delta.remove
+ && !payload.delta.update;
+
+ if (!emptyMessage) {
+ log('RECEIVE_NODES_DELTA',
+ 'remove', _.size(payload.delta.remove),
+ 'update', _.size(payload.delta.update),
+ 'add', _.size(payload.delta.add));
+ }
+
+ errorUrl = null;
+
+ // nodes that no longer exist
+ _.each(payload.delta.remove, function(nodeId) {
+ // in case node disappears before mouseleave event
+ if (mouseOverNodeId === nodeId) {
+ mouseOverNodeId = null;
+ }
+ if (nodes.has(nodeId) && _.contains(mouseOverEdgeId, nodeId)) {
+ mouseOverEdgeId = null;
+ }
+ nodes = nodes.delete(nodeId);
+ });
+
+ // update existing nodes
+ _.each(payload.delta.update, function(node) {
+ nodes = nodes.set(node.id, nodes.get(node.id).merge(makeNode(node)));
+ });
+
+ // add new nodes
+ _.each(payload.delta.add, function(node) {
+ nodes = nodes.set(node.id, Immutable.fromJS(makeNode(node)));
+ });
+
+ this.__emitChange();
+ break;
+
+ case ActionTypes.RECEIVE_TOPOLOGIES:
+ errorUrl = null;
+ topologies = processTopologies(payload.topologies);
+ setTopology(currentTopologyId);
+ // only set on first load, if options are not already set via route
+ if (!topologiesLoaded && topologyOptions.size === 0) {
+ setDefaultTopologyOptions(topologies);
+ }
+ topologiesLoaded = true;
+ this.__emitChange();
+ break;
+
+ case ActionTypes.RECEIVE_API_DETAILS:
+ errorUrl = null;
+ version = payload.version;
+ this.__emitChange();
+ break;
+
+ case ActionTypes.ROUTE_TOPOLOGY:
+ routeSet = true;
+ if (currentTopologyId !== payload.state.topologyId) {
+ nodes = nodes.clear();
+ }
+ setTopology(payload.state.topologyId);
setDefaultTopologyOptions(topologies);
+ selectedNodeId = payload.state.selectedNodeId;
+ topologyOptions = Immutable.fromJS(payload.state.topologyOptions)
+ || topologyOptions;
+ this.__emitChange();
+ break;
+
+ default:
+ break;
+
}
- topologiesLoaded = true;
- AppStore.emit(AppStore.CHANGE_EVENT);
- break;
-
- case ActionTypes.RECEIVE_API_DETAILS:
- errorUrl = null;
- version = payload.version;
- AppStore.emit(AppStore.CHANGE_EVENT);
- break;
-
- case ActionTypes.ROUTE_TOPOLOGY:
- routeSet = true;
- if (currentTopologyId !== payload.state.topologyId) {
- nodes = nodes.clear();
- }
- setTopology(payload.state.topologyId);
- setDefaultTopologyOptions(topologies);
- selectedNodeId = payload.state.selectedNodeId;
- topologyOptions = Immutable.fromJS(payload.state.topologyOptions)
- || topologyOptions;
- AppStore.emit(AppStore.CHANGE_EVENT);
- break;
-
- default:
- break;
-
}
-};
+}
-AppStore.dispatchToken = AppDispatcher.register(AppStore.registeredCallback);
-
-module.exports = AppStore;
+export default new AppStore(AppDispatcher);
diff --git a/client/app/scripts/mixins/node-color-mixin.js b/client/app/scripts/utils/color-utils.js
similarity index 64%
rename from client/app/scripts/mixins/node-color-mixin.js
rename to client/app/scripts/utils/color-utils.js
index 54f3e0b69..539ec578f 100644
--- a/client/app/scripts/mixins/node-color-mixin.js
+++ b/client/app/scripts/utils/color-utils.js
@@ -1,4 +1,4 @@
-const d3 = require('d3');
+import d3 from 'd3';
const PSEUDO_COLOR = '#b1b1cb';
const hueRange = [20, 330]; // exclude red
@@ -40,35 +40,33 @@ function colors(text, secondText) {
return color;
}
-const NodeColorMixin = {
- getNodeColor: function(text, secondText) {
- return colors(text, secondText);
- },
- getNodeColorDark: function(text, secondText) {
- if (!text) {
- return PSEUDO_COLOR;
- }
- const color = d3.rgb(colors(text, secondText));
- let hsl = color.hsl();
+export function getNodeColor(text, secondText) {
+ return colors(text, secondText);
+}
- // ensure darkness
- if (hsl.l > 0.7) {
- hsl = hsl.darker(1.5);
- } else {
- hsl = hsl.darker(1);
- }
-
- return hsl.toString();
- },
- brightenColor: function(color) {
- let hsl = d3.rgb(color).hsl();
- if (hsl.l > 0.5) {
- hsl = hsl.brighter(0.5);
- } else {
- hsl = hsl.brighter(0.8);
- }
- return hsl.toString();
+export function getNodeColorDark(text, secondText) {
+ if (!text) {
+ return PSEUDO_COLOR;
}
-};
+ const color = d3.rgb(colors(text, secondText));
+ let hsl = color.hsl();
-module.exports = NodeColorMixin;
+ // ensure darkness
+ if (hsl.l > 0.7) {
+ hsl = hsl.darker(1.5);
+ } else {
+ hsl = hsl.darker(1);
+ }
+
+ return hsl.toString();
+}
+
+export function brightenColor(color) {
+ let hsl = d3.rgb(color).hsl();
+ if (hsl.l > 0.5) {
+ hsl = hsl.brighter(0.5);
+ } else {
+ hsl = hsl.brighter(0.8);
+ }
+ return hsl.toString();
+}
diff --git a/client/app/scripts/utils/router-utils.js b/client/app/scripts/utils/router-utils.js
index bd484a260..ad020fed9 100644
--- a/client/app/scripts/utils/router-utils.js
+++ b/client/app/scripts/utils/router-utils.js
@@ -1,9 +1,9 @@
-const page = require('page');
+import page from 'page';
-const AppActions = require('../actions/app-actions');
-const AppStore = require('../stores/app-store');
+import { route } from '../actions/app-actions';
+import AppStore from '../stores/app-store';
-function updateRoute() {
+export function updateRoute() {
const state = AppStore.getAppState();
const stateUrl = JSON.stringify(state);
const dispatch = false;
@@ -17,13 +17,9 @@ page('/', function() {
page('/state/:state', function(ctx) {
const state = JSON.parse(ctx.params.state);
- AppActions.route(state);
+ route(state);
});
-module.exports = {
- getRouter: function() {
- return page;
- },
-
- updateRoute: updateRoute
-};
+export function getRouter() {
+ return page;
+}
diff --git a/client/app/scripts/utils/title-utils.js b/client/app/scripts/utils/title-utils.js
index b2db1680f..fed4b86a1 100644
--- a/client/app/scripts/utils/title-utils.js
+++ b/client/app/scripts/utils/title-utils.js
@@ -2,7 +2,7 @@
const PREFIX = 'Weave Scope';
const SEPARATOR = ' - ';
-function setDocumentTitle(title) {
+export function setDocumentTitle(title) {
if (title) {
document.title = [PREFIX, title].join(SEPARATOR);
} else {
@@ -10,11 +10,6 @@ function setDocumentTitle(title) {
}
}
-function resetDocumentTitle() {
+export function resetDocumentTitle() {
setDocumentTitle(null);
}
-
-module.exports = {
- resetTitle: resetDocumentTitle,
- setTitle: setDocumentTitle
-};
diff --git a/client/app/scripts/utils/web-api-utils.js b/client/app/scripts/utils/web-api-utils.js
index 4f33e8883..6677f8fe2 100644
--- a/client/app/scripts/utils/web-api-utils.js
+++ b/client/app/scripts/utils/web-api-utils.js
@@ -1,11 +1,13 @@
+import debug from 'debug';
+import reqwest from 'reqwest';
-const debug = require('debug')('scope:web-api-utils');
-const reqwest = require('reqwest');
-
-const AppActions = require('../actions/app-actions');
+import { clearControlError, closeWebsocket, openWebsocket, receiveError,
+ receiveApiDetails, receiveNodesDelta, receiveNodeDetails, receiveControlError,
+ receiveControlSuccess, receiveTopologies } from '../actions/app-actions';
const wsProto = location.protocol === 'https:' ? 'wss' : 'ws';
const wsUrl = __WS_URL__ || wsProto + '://' + location.host + location.pathname.replace(/\/$/, '');
+const log = debug('scope:web-api-utils');
const apiTimerInterval = 10000;
const reconnectTimerInterval = 5000;
@@ -40,14 +42,14 @@ function createWebsocket(topologyUrl, optionsQuery) {
+ '/ws?t=' + updateFrequency + '&' + optionsQuery);
socket.onopen = function() {
- AppActions.openWebsocket();
+ openWebsocket();
};
socket.onclose = function() {
clearTimeout(reconnectTimer);
socket = null;
- AppActions.closeWebsocket();
- debug('Closed websocket to ' + topologyUrl);
+ closeWebsocket();
+ log('Closed websocket to ' + topologyUrl);
reconnectTimer = setTimeout(function() {
createWebsocket(topologyUrl, optionsQuery);
@@ -55,33 +57,33 @@ function createWebsocket(topologyUrl, optionsQuery) {
};
socket.onerror = function() {
- debug('Error in websocket to ' + topologyUrl);
- AppActions.receiveError(currentUrl);
+ log('Error in websocket to ' + topologyUrl);
+ receiveError(currentUrl);
};
socket.onmessage = function(event) {
const msg = JSON.parse(event.data);
- AppActions.receiveNodesDelta(msg);
+ receiveNodesDelta(msg);
};
}
/* keep URLs relative */
-function getTopologies(options) {
+export function getTopologies(options) {
clearTimeout(topologyTimer);
const optionsQuery = buildOptionsQuery(options);
const url = `api/topology?${optionsQuery}`;
reqwest({
url: url,
success: function(res) {
- AppActions.receiveTopologies(res);
+ receiveTopologies(res);
topologyTimer = setTimeout(function() {
getTopologies(options);
}, topologyTimerInterval / 2);
},
error: function(err) {
- debug('Error in topology request: ' + err);
- AppActions.receiveError(url);
+ log('Error in topology request: ' + err);
+ receiveError(url);
topologyTimer = setTimeout(function() {
getTopologies(options);
}, topologyTimerInterval / 2);
@@ -89,7 +91,7 @@ function getTopologies(options) {
});
}
-function getTopology(topologyUrl, options) {
+export function getNodesDelta(topologyUrl, options) {
const optionsQuery = buildOptionsQuery(options);
// only recreate websocket if url changed
@@ -100,44 +102,44 @@ function getTopology(topologyUrl, options) {
}
}
-function getNodeDetails(topologyUrl, nodeId) {
+export function getNodeDetails(topologyUrl, nodeId) {
if (topologyUrl && nodeId) {
const url = [topologyUrl, '/', encodeURIComponent(nodeId)]
.join('').substr(1);
reqwest({
url: url,
success: function(res) {
- AppActions.receiveNodeDetails(res.node);
+ receiveNodeDetails(res.node);
},
error: function(err) {
- debug('Error in node details request: ' + err.responseText);
+ log('Error in node details request: ' + err.responseText);
// dont treat missing node as error
if (err.status !== 404) {
- AppActions.receiveError(topologyUrl);
+ receiveError(topologyUrl);
}
}
});
}
}
-function getApiDetails() {
+export function getApiDetails() {
clearTimeout(apiDetailsTimer);
const url = 'api';
reqwest({
url: url,
success: function(res) {
- AppActions.receiveApiDetails(res);
+ receiveApiDetails(res);
apiDetailsTimer = setTimeout(getApiDetails, apiTimerInterval);
},
error: function(err) {
- debug('Error in api details request: ' + err);
- AppActions.receiveError(url);
+ log('Error in api details request: ' + err);
+ receiveError(url);
apiDetailsTimer = setTimeout(getApiDetails, apiTimerInterval / 2);
}
});
}
-function doControl(probeId, nodeId, control) {
+export function doControl(probeId, nodeId, control) {
clearTimeout(controlErrorTimer);
const url = `api/control/${encodeURIComponent(probeId)}/`
+ `${encodeURIComponent(nodeId)}/${control}`;
@@ -145,25 +147,13 @@ function doControl(probeId, nodeId, control) {
method: 'POST',
url: url,
success: function() {
- AppActions.receiveControlSuccess();
+ receiveControlSuccess();
},
error: function(err) {
- AppActions.receiveControlError(err.response);
+ receiveControlError(err.response);
controlErrorTimer = setTimeout(function() {
- AppActions.clearControlError();
+ clearControlError();
}, 10000);
}
});
}
-
-module.exports = {
- doControl: doControl,
-
- getNodeDetails: getNodeDetails,
-
- getTopologies: getTopologies,
-
- getApiDetails: getApiDetails,
-
- getNodesDelta: getTopology
-};
diff --git a/client/package.json b/client/package.json
index 4a1385de4..8b1237b45 100644
--- a/client/package.json
+++ b/client/package.json
@@ -13,7 +13,6 @@
"font-awesome": "4.4.0",
"font-awesome-webpack": "0.0.4",
"immutable": "~3.7.4",
- "keymirror": "0.1.1",
"lodash": "~3.10.1",
"materialize-css": "0.97.2",
"page": "1.6.4",
@@ -83,6 +82,7 @@
"json"
],
"unmockedModulePathPatterns": [
+ "/dispatcher/",
"/node_modules/"
]
},
diff --git a/client/webpack.local.config.js b/client/webpack.local.config.js
index ae14b65a9..fa706577b 100644
--- a/client/webpack.local.config.js
+++ b/client/webpack.local.config.js
@@ -22,7 +22,7 @@ var GLOBALS = {
module.exports = {
// Efficiently evaluate modules with source maps
- devtool: 'eval',
+ devtool: 'cheap-module-source-map',
// Set entry point include necessary files for hot load
entry: [