mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-03 02:00:43 +00:00
0
client/.eslintignore
Normal file
0
client/.eslintignore
Normal file
@@ -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()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
}}
|
||||
</Motion>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
}}
|
||||
</Motion>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<NodesError faIconClass="fa-ban" hidden={!show}>
|
||||
<div className="centered">Too many nodes to show in the browser.<br />{errorHint}</div>
|
||||
</NodesError>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
renderEmptyTopologyError: function(show) {
|
||||
renderEmptyTopologyError(show) {
|
||||
return (
|
||||
<NodesError faIconClass="fa-circle-thin" hidden={!show}>
|
||||
<div className="heading">Nothing to show. This can have any of these reasons:</div>
|
||||
@@ -195,9 +203,9 @@ const NodesChart = React.createClass({
|
||||
</ul>
|
||||
</NodesError>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
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({
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
module.exports = NodesError;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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';
|
||||
});
|
||||
|
||||
@@ -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({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
module.exports = App;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<div id="details">
|
||||
<div style={{height: '100%', paddingBottom: 8, borderRadius: 2,
|
||||
@@ -26,7 +30,4 @@ const Details = React.createClass({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
module.exports = Details;
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<div className="logo">
|
||||
<svg width="100%" height="100%" viewBox="0 0 1089 217">
|
||||
@@ -59,7 +58,4 @@ const Logo = React.createClass({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
module.exports = Logo;
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
renderNotAvailable: function() {
|
||||
renderNotAvailable() {
|
||||
return (
|
||||
<div className="node-details">
|
||||
<div className="node-details-header node-details-header-notavailable">
|
||||
@@ -71,9 +66,9 @@ const NodeDetails = React.createClass({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
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({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<span className={className} title={this.props.control.human} onClick={this.handleClick} />
|
||||
);
|
||||
},
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
module.exports = NodeDetailsControls;
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<div className="node-details-table-row-value">
|
||||
@@ -11,6 +10,4 @@ const NodeDetailsTableRowNumber = React.createClass({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = NodeDetailsTableRowNumber;
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<div className="node-details-table-row-value">
|
||||
@@ -13,6 +12,4 @@ const NodeDetailsTableRowSparkline = React.createClass({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = NodeDetailsTableRowSparkline;
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<div className="node-details-table-row-value">
|
||||
@@ -15,6 +14,4 @@ const NodeDetailsTableRowValue = React.createClass({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = NodeDetailsTableRowValue;
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<div className="node-details-table">
|
||||
<h4 className="node-details-table-title truncate" title={this.props.title}>
|
||||
@@ -32,7 +31,4 @@ const NodeDetailsTable = React.createClass({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
module.exports = NodeDetailsTable;
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<NodesChart
|
||||
highlightedEdgeIds={this.props.highlightedEdgeIds}
|
||||
@@ -36,19 +38,16 @@ const Nodes = React.createClass({
|
||||
topMargin={this.props.topMargin}
|
||||
/>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<div className="sidebar">
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
module.exports = Sidebar;
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<div/>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
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.
|
||||
};
|
||||
|
||||
@@ -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({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
module.exports = Status;
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
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({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
render: function() {
|
||||
render() {
|
||||
const topologies = _.sortBy(this.props.topologies, function(topology) {
|
||||
return topology.name;
|
||||
});
|
||||
@@ -64,7 +69,4 @@ const Topologies = React.createClass({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
module.exports = Topologies;
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<span className="sidebar-item-action" onClick={this.onClick}>
|
||||
{this.props.value}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
module.exports = TopologyOptionAction;
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<TopologyOptionAction option={option} value={action} topologyId={topologyId} key={action} />
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* 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({
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
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({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
module.exports = TopologyOptions;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,4 +1,2 @@
|
||||
|
||||
module.exports = {
|
||||
EDGE_ID_SEPARATOR: '-'
|
||||
};
|
||||
export const EDGE_ID_SEPARATOR = '-';
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
<App/>,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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/"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -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: [
|
||||
|
||||
Reference in New Issue
Block a user