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 ( ); - }, + } - renderEmptyTopologyError: function(show) { + renderEmptyTopologyError(show) { return ( ); - }, + } - 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 (
@@ -59,7 +58,4 @@ const Logo = React.createClass({
); } - -}); - -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: [