mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-04 10:41:14 +00:00
Upgraded dev deps and linted JS according to latest airbnb rules
This commit is contained in:
@@ -7,7 +7,8 @@
|
||||
},
|
||||
"rules": {
|
||||
"comma-dangle": 0,
|
||||
"func-names": 0,
|
||||
"object-curly-spacing": 0,
|
||||
"react/jsx-closing-bracket-location": 0,
|
||||
"react/sort-comp": 0,
|
||||
"react/prop-types": 0
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ const log = debug('scope:app-actions');
|
||||
export function changeTopologyOption(option, value, topologyId) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.CHANGE_TOPOLOGY_OPTION,
|
||||
topologyId: topologyId,
|
||||
option: option,
|
||||
value: value
|
||||
topologyId,
|
||||
option,
|
||||
value
|
||||
});
|
||||
updateRoute();
|
||||
// update all request workers with new options
|
||||
@@ -53,7 +53,7 @@ export function clickCloseDetails(nodeId) {
|
||||
export function clickCloseTerminal(pipeId, closePipe) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.CLICK_CLOSE_TERMINAL,
|
||||
pipeId: pipeId
|
||||
pipeId
|
||||
});
|
||||
if (closePipe) {
|
||||
deletePipe(pipeId);
|
||||
@@ -130,7 +130,7 @@ export function clickShowTopologyForNode(topologyId, nodeId) {
|
||||
export function clickTopology(topologyId) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.CLICK_TOPOLOGY,
|
||||
topologyId: topologyId
|
||||
topologyId
|
||||
});
|
||||
updateRoute();
|
||||
resetUpdateBuffer();
|
||||
@@ -149,7 +149,7 @@ export function openWebsocket() {
|
||||
export function clearControlError(nodeId) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.CLEAR_CONTROL_ERROR,
|
||||
nodeId: nodeId
|
||||
nodeId
|
||||
});
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ export function closeWebsocket() {
|
||||
export function doControl(nodeId, control) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.DO_CONTROL,
|
||||
nodeId: nodeId
|
||||
nodeId
|
||||
});
|
||||
doControlRequest(nodeId, control);
|
||||
}
|
||||
@@ -170,14 +170,14 @@ export function doControl(nodeId, control) {
|
||||
export function enterEdge(edgeId) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.ENTER_EDGE,
|
||||
edgeId: edgeId
|
||||
edgeId
|
||||
});
|
||||
}
|
||||
|
||||
export function enterNode(nodeId) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.ENTER_NODE,
|
||||
nodeId: nodeId
|
||||
nodeId
|
||||
});
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ export function hitEsc() {
|
||||
updateRoute();
|
||||
// Dont deselect node on ESC if there is a controlPipe (keep terminal open)
|
||||
} else if (AppStore.getTopCardNodeId() && !controlPipe) {
|
||||
AppDispatcher.dispatch({type: ActionTypes.DESELECT_NODE});
|
||||
AppDispatcher.dispatch({ type: ActionTypes.DESELECT_NODE });
|
||||
updateRoute();
|
||||
}
|
||||
}
|
||||
@@ -199,21 +199,21 @@ export function hitEsc() {
|
||||
export function leaveEdge(edgeId) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.LEAVE_EDGE,
|
||||
edgeId: edgeId
|
||||
edgeId
|
||||
});
|
||||
}
|
||||
|
||||
export function leaveNode(nodeId) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.LEAVE_NODE,
|
||||
nodeId: nodeId
|
||||
nodeId
|
||||
});
|
||||
}
|
||||
|
||||
export function receiveControlError(nodeId, err) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.DO_CONTROL_ERROR,
|
||||
nodeId: nodeId,
|
||||
nodeId,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
@@ -221,14 +221,14 @@ export function receiveControlError(nodeId, err) {
|
||||
export function receiveControlSuccess(nodeId) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.DO_CONTROL_SUCCESS,
|
||||
nodeId: nodeId
|
||||
nodeId
|
||||
});
|
||||
}
|
||||
|
||||
export function receiveNodeDetails(details) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.RECEIVE_NODE_DETAILS,
|
||||
details: details
|
||||
details
|
||||
});
|
||||
}
|
||||
|
||||
@@ -238,7 +238,7 @@ export function receiveNodesDelta(delta) {
|
||||
} else {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.RECEIVE_NODES_DELTA,
|
||||
delta: delta
|
||||
delta
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -246,7 +246,7 @@ export function receiveNodesDelta(delta) {
|
||||
export function receiveTopologies(topologies) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.RECEIVE_TOPOLOGIES,
|
||||
topologies: topologies
|
||||
topologies
|
||||
});
|
||||
getNodesDelta(
|
||||
AppStore.getCurrentTopologyUrl(),
|
||||
@@ -270,8 +270,8 @@ export function receiveControlPipeFromParams(pipeId, rawTty) {
|
||||
// TODO add nodeId
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.RECEIVE_CONTROL_PIPE,
|
||||
pipeId: pipeId,
|
||||
rawTty: rawTty
|
||||
pipeId,
|
||||
rawTty
|
||||
});
|
||||
}
|
||||
|
||||
@@ -289,9 +289,9 @@ export function receiveControlPipe(pipeId, nodeId, rawTty) {
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.RECEIVE_CONTROL_PIPE,
|
||||
nodeId: nodeId,
|
||||
pipeId: pipeId,
|
||||
rawTty: rawTty
|
||||
nodeId,
|
||||
pipeId,
|
||||
rawTty
|
||||
});
|
||||
|
||||
updateRoute();
|
||||
@@ -300,14 +300,14 @@ export function receiveControlPipe(pipeId, nodeId, rawTty) {
|
||||
export function receiveControlPipeStatus(pipeId, status) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.RECEIVE_CONTROL_PIPE_STATUS,
|
||||
pipeId: pipeId,
|
||||
status: status
|
||||
pipeId,
|
||||
status
|
||||
});
|
||||
}
|
||||
|
||||
export function receiveError(errorUrl) {
|
||||
AppDispatcher.dispatch({
|
||||
errorUrl: errorUrl,
|
||||
errorUrl,
|
||||
type: ActionTypes.RECEIVE_ERROR
|
||||
});
|
||||
}
|
||||
@@ -321,7 +321,7 @@ export function receiveNotFound(nodeId) {
|
||||
|
||||
export function route(state) {
|
||||
AppDispatcher.dispatch({
|
||||
state: state,
|
||||
state,
|
||||
type: ActionTypes.ROUTE_TOPOLOGY
|
||||
});
|
||||
getTopologies(
|
||||
|
||||
@@ -7,23 +7,23 @@ 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; });
|
||||
.x(d => d.x)
|
||||
.y(d => d.y);
|
||||
|
||||
const animConfig = {stiffness: 80, damping: 20};
|
||||
|
||||
const flattenPoints = function(points) {
|
||||
const flattenPoints = points => {
|
||||
const flattened = {};
|
||||
points.forEach(function(point, i) {
|
||||
flattened['x' + i] = spring(point.x, animConfig);
|
||||
flattened['y' + i] = spring(point.y, animConfig);
|
||||
points.forEach((point, i) => {
|
||||
flattened[`x${i}`] = spring(point.x, animConfig);
|
||||
flattened[`y${i}`] = spring(point.y, animConfig);
|
||||
});
|
||||
return flattened;
|
||||
};
|
||||
|
||||
const extractPoints = function(points) {
|
||||
const extractPoints = points => {
|
||||
const extracted = [];
|
||||
_.each(points, function(value, key) {
|
||||
_.each(points, (value, key) => {
|
||||
const axis = key[0];
|
||||
const index = key.slice(1);
|
||||
if (!extracted[index]) {
|
||||
@@ -70,10 +70,11 @@ export default class Edge extends React.Component {
|
||||
|
||||
return (
|
||||
<Motion style={points}>
|
||||
{function(interpolated) {
|
||||
{(interpolated) => {
|
||||
const path = line(extractPoints(interpolated));
|
||||
return (
|
||||
<g className={classes} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} id={props.id}>
|
||||
<g className={classes} onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave} id={props.id}>
|
||||
<path d={path} className="shadow" />
|
||||
<path d={path} className="link" />
|
||||
</g>
|
||||
|
||||
@@ -3,7 +3,11 @@ import d3 from 'd3';
|
||||
|
||||
import { isContrastMode } from '../utils/contrast-utils';
|
||||
|
||||
const CLOUD_PATH = 'M 1920,384 Q 1920,225 1807.5,112.5 1695,0 1536,0 H 448 Q 263,0 131.5,131.5 0,263 0,448 0,580 71,689.5 142,799 258,853 q -2,28 -2,43 0,212 150,362 150,150 362,150 158,0 286.5,-88 128.5,-88 187.5,-230 70,62 166,62 106,0 181,-75 75,-75 75,-181 0,-75 -41,-138 129,-30 213,-134.5 84,-104.5 84,-239.5 z';
|
||||
const CLOUD_PATH = 'M 1920,384 Q 1920,225 1807.5,112.5 1695,0 1536,0 H 448 '
|
||||
+ 'Q 263,0 131.5,131.5 0,263 0,448 0,580 71,689.5 142,799 258,853 '
|
||||
+ 'q -2,28 -2,43 0,212 150,362 150,150 362,150 158,0 286.5,-88 128.5,-88 '
|
||||
+ '187.5,-230 70,62 166,62 106,0 181,-75 75,-75 75,-181 0,-75 -41,-138 '
|
||||
+ '129,-30 213,-134.5 84,-104.5 84,-239.5 z';
|
||||
|
||||
function toPoint(stringPair) {
|
||||
return stringPair.split(',').map(p => parseFloat(p, 10));
|
||||
@@ -24,14 +28,12 @@ export default function NodeShapeCloud({highlighted, size, color}) {
|
||||
const baseScale = (size * 2) / pathSize;
|
||||
const strokeWidth = isContrastMode() ? 6 / baseScale : 4 / baseScale;
|
||||
|
||||
const pathProps = (v) => {
|
||||
return {
|
||||
d: CLOUD_PATH,
|
||||
fill: 'none',
|
||||
transform: `scale(-${v * baseScale}) translate(-${cx},-${cy})`,
|
||||
strokeWidth
|
||||
};
|
||||
};
|
||||
const pathProps = v => ({
|
||||
d: CLOUD_PATH,
|
||||
fill: 'none',
|
||||
transform: `scale(-${v * baseScale}) translate(-${cx},-${cy})`,
|
||||
strokeWidth
|
||||
});
|
||||
|
||||
return (
|
||||
<g className="shape shape-cloud">
|
||||
|
||||
@@ -16,12 +16,10 @@ function polygon(r, sides) {
|
||||
|
||||
export default function NodeShapeHeptagon({onlyHighlight, highlighted, size, color}) {
|
||||
const scaledSize = size * 1.0;
|
||||
const pathProps = (v) => {
|
||||
return {
|
||||
d: line(polygon(scaledSize * v, 7)),
|
||||
transform: `rotate(90)`
|
||||
};
|
||||
};
|
||||
const pathProps = v => ({
|
||||
d: line(polygon(scaledSize * v, 7)),
|
||||
transform: 'rotate(90)'
|
||||
});
|
||||
|
||||
const hightlightNode = <path className="highlighted" {...pathProps(0.7)} />;
|
||||
|
||||
@@ -42,4 +40,3 @@ export default function NodeShapeHeptagon({onlyHighlight, highlighted, size, col
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,12 +25,10 @@ function getPoints(h) {
|
||||
|
||||
|
||||
export default function NodeShapeHex({onlyHighlight, highlighted, size, color}) {
|
||||
const pathProps = (v) => {
|
||||
return {
|
||||
d: getPoints(size * v * 2),
|
||||
transform: `rotate(90) translate(-${size * getWidth(v)}, -${size * v})`
|
||||
};
|
||||
};
|
||||
const pathProps = v => ({
|
||||
d: getPoints(size * v * 2),
|
||||
transform: `rotate(90) translate(-${size * getWidth(v)}, -${size * v})`
|
||||
});
|
||||
|
||||
const hightlightNode = <path className="highlighted" {...pathProps(0.7)} />;
|
||||
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function NodeShapeSquare({onlyHighlight, highlighted, size, color, rx = 0, ry = 0}) {
|
||||
const rectProps = (v) => {
|
||||
return {
|
||||
width: v * size * 2,
|
||||
height: v * size * 2,
|
||||
rx: v * size * rx,
|
||||
ry: v * size * ry,
|
||||
transform: `translate(-${size * v}, -${size * v})`
|
||||
};
|
||||
};
|
||||
const rectProps = v => ({
|
||||
width: v * size * 2,
|
||||
height: v * size * 2,
|
||||
rx: v * size * rx,
|
||||
ry: v * size * ry,
|
||||
transform: `translate(-${size * v}, -${size * v})`
|
||||
});
|
||||
|
||||
const hightlightNode = <rect className="highlighted" {...rectProps(0.7)} />;
|
||||
|
||||
|
||||
@@ -14,18 +14,15 @@ import NodeShapeCloud from './node-shape-cloud';
|
||||
|
||||
function stackedShape(Shape) {
|
||||
const factory = React.createFactory(NodeShapeStack);
|
||||
|
||||
return function(props) {
|
||||
return factory(Object.assign({}, props, {shape: Shape}));
|
||||
};
|
||||
return props => factory(Object.assign({}, props, {shape: Shape}));
|
||||
}
|
||||
|
||||
const nodeShapes = {
|
||||
'circle': NodeShapeCircle,
|
||||
'hexagon': NodeShapeHex,
|
||||
'heptagon': NodeShapeHeptagon,
|
||||
'square': NodeShapeRoundedSquare,
|
||||
'cloud': NodeShapeCloud
|
||||
circle: NodeShapeCircle,
|
||||
hexagon: NodeShapeHex,
|
||||
heptagon: NodeShapeHeptagon,
|
||||
square: NodeShapeRoundedSquare,
|
||||
cloud: NodeShapeCloud
|
||||
};
|
||||
|
||||
function getNodeShape({shape, stack}) {
|
||||
@@ -98,7 +95,7 @@ export default class Node extends React.Component {
|
||||
labelOffsetY: spring(labelOffsetY, animConfig),
|
||||
subLabelOffsetY: spring(subLabelOffsetY, animConfig)
|
||||
}}>
|
||||
{function(interpolated) {
|
||||
{(interpolated) => {
|
||||
const transform = `translate(${interpolated.x},${interpolated.y})`;
|
||||
return (
|
||||
<g className={classes} transform={transform} id={props.id}
|
||||
@@ -107,11 +104,13 @@ export default class Node extends React.Component {
|
||||
size={nodeScale(interpolated.f)}
|
||||
color={color}
|
||||
{...props} />
|
||||
<text className="node-label" textAnchor="middle" style={{fontSize: interpolated.labelFontSize}}
|
||||
<text className="node-label" textAnchor="middle"
|
||||
style={{fontSize: interpolated.labelFontSize}}
|
||||
x="0" y={interpolated.labelOffsetY + nodeScale(0.5 * interpolated.f)}>
|
||||
{label}
|
||||
</text>
|
||||
<text className="node-sublabel" textAnchor="middle" style={{fontSize: interpolated.subLabelFontSize}}
|
||||
<text className="node-sublabel" textAnchor="middle"
|
||||
style={{fontSize: interpolated.subLabelFontSize}}
|
||||
x="0" y={interpolated.subLabelOffsetY + nodeScale(0.5 * interpolated.f)}>
|
||||
{subLabel}
|
||||
</text>
|
||||
@@ -127,7 +126,7 @@ export default class Node extends React.Component {
|
||||
const allowedChars = maxWidth / averageCharLength;
|
||||
let truncatedText = text;
|
||||
if (text && text.length > allowedChars) {
|
||||
truncatedText = text.slice(0, allowedChars) + '...';
|
||||
truncatedText = `${text.slice(0, allowedChars)}...`;
|
||||
}
|
||||
return truncatedText;
|
||||
}
|
||||
|
||||
@@ -100,7 +100,8 @@ export default class NodesChart extends React.Component {
|
||||
}
|
||||
|
||||
renderGraphNodes(nodes, nodeScale) {
|
||||
const hasSelectedNode = this.props.selectedNodeId && this.props.nodes.has(this.props.selectedNodeId);
|
||||
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;
|
||||
const zoomScale = this.state.scale;
|
||||
@@ -117,9 +118,7 @@ export default class NodesChart extends React.Component {
|
||||
&& (this.props.selectedNodeId === node.get('id') || adjacency.includes(node.get('id')));
|
||||
return node.set('focused', focused);
|
||||
};
|
||||
const setBlurred = node => {
|
||||
return node.set('blurred', hasSelectedNode && !node.get('focused'));
|
||||
};
|
||||
const setBlurred = node => node.set('blurred', hasSelectedNode && !node.get('focused'));
|
||||
|
||||
// make sure blurred nodes are in the background
|
||||
const sortNodes = node => {
|
||||
@@ -138,55 +137,49 @@ export default class NodesChart extends React.Component {
|
||||
.map(setFocused)
|
||||
.map(setBlurred)
|
||||
.sortBy(sortNodes)
|
||||
.map(node => {
|
||||
return (<Node
|
||||
blurred={node.get('blurred')}
|
||||
focused={node.get('focused')}
|
||||
highlighted={node.get('highlighted')}
|
||||
topologyId={this.props.topologyId}
|
||||
shape={node.get('shape')}
|
||||
stack={node.get('stack')}
|
||||
onClick={onNodeClick}
|
||||
key={node.get('id')}
|
||||
id={node.get('id')}
|
||||
label={node.get('label')}
|
||||
pseudo={node.get('pseudo')}
|
||||
nodeCount={node.get('nodeCount')}
|
||||
subLabel={node.get('subLabel')}
|
||||
rank={node.get('rank')}
|
||||
selectedNodeScale={selectedNodeScale}
|
||||
nodeScale={nodeScale}
|
||||
zoomScale={zoomScale}
|
||||
dx={node.get('x')}
|
||||
dy={node.get('y')}
|
||||
/>
|
||||
);
|
||||
});
|
||||
.map(node => <Node
|
||||
blurred={node.get('blurred')}
|
||||
focused={node.get('focused')}
|
||||
highlighted={node.get('highlighted')}
|
||||
topologyId={this.props.topologyId}
|
||||
shape={node.get('shape')}
|
||||
stack={node.get('stack')}
|
||||
onClick={onNodeClick}
|
||||
key={node.get('id')}
|
||||
id={node.get('id')}
|
||||
label={node.get('label')}
|
||||
pseudo={node.get('pseudo')}
|
||||
nodeCount={node.get('nodeCount')}
|
||||
subLabel={node.get('subLabel')}
|
||||
rank={node.get('rank')}
|
||||
selectedNodeScale={selectedNodeScale}
|
||||
nodeScale={nodeScale}
|
||||
zoomScale={zoomScale}
|
||||
dx={node.get('x')}
|
||||
dy={node.get('y')}
|
||||
/>);
|
||||
}
|
||||
|
||||
renderGraphEdges(edges) {
|
||||
const selectedNodeId = this.props.selectedNodeId;
|
||||
const hasSelectedNode = selectedNodeId && this.props.nodes.has(selectedNodeId);
|
||||
|
||||
const setHighlighted = edge => {
|
||||
return edge.set('highlighted', _.includes(this.props.highlightedEdgeIds, edge.get('id')));
|
||||
};
|
||||
const setBlurred = edge => {
|
||||
return (edge.set('blurred', hasSelectedNode
|
||||
&& edge.get('source') !== selectedNodeId
|
||||
&& edge.get('target') !== selectedNodeId));
|
||||
};
|
||||
const setHighlighted = edge => edge.set('highlighted', _.includes(this.props.highlightedEdgeIds,
|
||||
edge.get('id')));
|
||||
|
||||
const setBlurred = edge => edge.set('blurred', hasSelectedNode
|
||||
&& edge.get('source') !== selectedNodeId
|
||||
&& edge.get('target') !== selectedNodeId);
|
||||
|
||||
return edges
|
||||
.toIndexedSeq()
|
||||
.map(setHighlighted)
|
||||
.map(setBlurred)
|
||||
.map(edge => {
|
||||
return (
|
||||
<Edge key={edge.get('id')} id={edge.get('id')} points={edge.get('points')}
|
||||
blurred={edge.get('blurred')} highlighted={edge.get('highlighted')} />
|
||||
);
|
||||
});
|
||||
.map(edge => <Edge key={edge.get('id')} id={edge.get('id')}
|
||||
points={edge.get('points')}
|
||||
blurred={edge.get('blurred')} highlighted={edge.get('highlighted')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderMaxNodesError(show) {
|
||||
@@ -203,9 +196,12 @@ export default class NodesChart extends React.Component {
|
||||
<NodesError faIconClass="fa-circle-thin" hidden={!show}>
|
||||
<div className="heading">Nothing to show. This can have any of these reasons:</div>
|
||||
<ul>
|
||||
<li>We haven't received any reports from probes recently. Are the probes properly configured?</li>
|
||||
<li>There are nodes, but they're currently hidden. Check the view options in the bottom-left if they allow for showing hidden nodes.</li>
|
||||
<li>Containers view only: you're not running Docker, or you don't have any containers.</li>
|
||||
<li>We haven't received any reports from probes recently.
|
||||
Are the probes properly configured?</li>
|
||||
<li>There are nodes, but they're currently hidden. Check the view options
|
||||
in the bottom-left if they allow for showing hidden nodes.</li>
|
||||
<li>Containers view only: you're not running Docker,
|
||||
or you don't have any containers.</li>
|
||||
</ul>
|
||||
</NodesError>
|
||||
);
|
||||
@@ -217,7 +213,7 @@ export default class NodesChart extends React.Component {
|
||||
const scale = this.state.scale;
|
||||
|
||||
const translate = this.state.panTranslate;
|
||||
const transform = 'translate(' + translate + ') scale(' + scale + ')';
|
||||
const transform = `translate(${translate}) scale(${scale})`;
|
||||
const svgClassNames = this.state.maxNodesExceeded || nodeElements.size === 0 ? 'hide' : '';
|
||||
const errorEmpty = this.renderEmptyTopologyError(AppStore.isTopologyEmpty());
|
||||
const errorMaxNodesExceeded = this.renderMaxNodesError(this.state.maxNodesExceeded);
|
||||
@@ -226,9 +222,10 @@ export default class NodesChart extends React.Component {
|
||||
<div className="nodes-chart">
|
||||
{errorEmpty}
|
||||
{errorMaxNodesExceeded}
|
||||
<svg width="100%" height="100%" id="nodes-chart-canvas" className={svgClassNames} onClick={this.handleMouseClick}>
|
||||
<svg width="100%" height="100%" id="nodes-chart-canvas"
|
||||
className={svgClassNames} onClick={this.handleMouseClick}>
|
||||
<g transform="translate(24,24) scale(0.25)">
|
||||
<Logo/>
|
||||
<Logo />
|
||||
</g>
|
||||
<g className="canvas" transform={transform}>
|
||||
<g className="edges">
|
||||
@@ -244,30 +241,28 @@ export default class NodesChart extends React.Component {
|
||||
}
|
||||
|
||||
initNodes(topology) {
|
||||
return topology.map((node, id) => {
|
||||
// copy relevant fields to state nodes
|
||||
return makeMap({
|
||||
id: id,
|
||||
label: node.get('label_major'),
|
||||
pseudo: node.get('pseudo'),
|
||||
subLabel: node.get('label_minor'),
|
||||
nodeCount: node.get('node_count'),
|
||||
rank: node.get('rank'),
|
||||
shape: node.get('shape'),
|
||||
stack: node.get('stack'),
|
||||
x: 0,
|
||||
y: 0
|
||||
});
|
||||
});
|
||||
// copy relevant fields to state nodes
|
||||
return topology.map((node, id) => makeMap({
|
||||
id,
|
||||
label: node.get('label_major'),
|
||||
pseudo: node.get('pseudo'),
|
||||
subLabel: node.get('label_minor'),
|
||||
nodeCount: node.get('node_count'),
|
||||
rank: node.get('rank'),
|
||||
shape: node.get('shape'),
|
||||
stack: node.get('stack'),
|
||||
x: 0,
|
||||
y: 0
|
||||
}));
|
||||
}
|
||||
|
||||
initEdges(topology, stateNodes) {
|
||||
let edges = makeMap();
|
||||
|
||||
topology.forEach(function(node, nodeId) {
|
||||
topology.forEach((node, nodeId) => {
|
||||
const adjacency = node.get('adjacency');
|
||||
if (adjacency) {
|
||||
adjacency.forEach(function(adjacent) {
|
||||
adjacency.forEach(adjacent => {
|
||||
const edge = [nodeId, adjacent];
|
||||
const edgeId = edge.join(EDGE_ID_SEPARATOR);
|
||||
|
||||
@@ -282,8 +277,8 @@ export default class NodesChart extends React.Component {
|
||||
edges = edges.set(edgeId, makeMap({
|
||||
id: edgeId,
|
||||
value: 1,
|
||||
source: source,
|
||||
target: target
|
||||
source,
|
||||
target
|
||||
}));
|
||||
}
|
||||
});
|
||||
@@ -305,7 +300,7 @@ export default class NodesChart extends React.Component {
|
||||
const adjacency = AppStore.getAdjacentNodes(props.selectedNodeId);
|
||||
const adjacentLayoutNodeIds = [];
|
||||
|
||||
adjacency.forEach(function(adjacentId) {
|
||||
adjacency.forEach(adjacentId => {
|
||||
// filter loopback
|
||||
if (adjacentId !== props.selectedNodeId) {
|
||||
adjacentLayoutNodeIds.push(adjacentId);
|
||||
@@ -315,7 +310,8 @@ export default class NodesChart extends React.Component {
|
||||
// move origin node to center of viewport
|
||||
const zoomScale = state.scale;
|
||||
const translate = state.panTranslate;
|
||||
const centerX = (-translate[0] + (props.width + MARGINS.left - DETAILS_PANEL_WIDTH) / 2) / zoomScale;
|
||||
const centerX = (-translate[0] + (props.width + MARGINS.left
|
||||
- DETAILS_PANEL_WIDTH) / 2) / zoomScale;
|
||||
const centerY = (-translate[1] + (props.height + MARGINS.top) / 2) / zoomScale;
|
||||
stateNodes = stateNodes.mergeIn([props.selectedNodeId], {
|
||||
x: centerX,
|
||||
@@ -379,12 +375,10 @@ export default class NodesChart extends React.Component {
|
||||
this.zoom.scale(state.scale);
|
||||
this.zoom.translate(state.panTranslate);
|
||||
|
||||
const nodes = state.nodes.map(node => {
|
||||
return node.merge({
|
||||
x: node.get('px'),
|
||||
y: node.get('py')
|
||||
});
|
||||
});
|
||||
const nodes = state.nodes.map(node => node.merge({
|
||||
x: node.get('px'),
|
||||
y: node.get('py')
|
||||
}));
|
||||
|
||||
const edges = state.edges.map(edge => {
|
||||
if (edge.has('ppoints')) {
|
||||
@@ -422,7 +416,7 @@ export default class NodesChart extends React.Component {
|
||||
const timedLayouter = timely(doLayout);
|
||||
const graph = timedLayouter(stateNodes, stateEdges, options);
|
||||
|
||||
log('graph layout took ' + timedLayouter.time + 'ms');
|
||||
log(`graph layout took ${timedLayouter.time}ms`);
|
||||
|
||||
// layout was aborted
|
||||
if (!graph) {
|
||||
@@ -432,15 +426,11 @@ export default class NodesChart extends React.Component {
|
||||
stateEdges = graph.edges;
|
||||
|
||||
// save coordinates for restore
|
||||
stateNodes = stateNodes.map(node => {
|
||||
return node.merge({
|
||||
px: node.get('x'),
|
||||
py: node.get('y')
|
||||
});
|
||||
});
|
||||
stateEdges = stateEdges.map(edge => {
|
||||
return edge.set('ppoints', edge.get('points'));
|
||||
});
|
||||
stateNodes = stateNodes.map(node => node.merge({
|
||||
px: node.get('x'),
|
||||
py: node.get('y')
|
||||
}));
|
||||
stateEdges = stateEdges.map(edge => edge.set('ppoints', edge.get('points')));
|
||||
|
||||
// adjust layout based on viewport
|
||||
const xFactor = (props.width - MARGINS.left - MARGINS.right) / graph.width;
|
||||
@@ -458,7 +448,7 @@ export default class NodesChart extends React.Component {
|
||||
nodes: stateNodes,
|
||||
edges: stateEdges,
|
||||
scale: zoomScale,
|
||||
nodeScale: nodeScale,
|
||||
nodeScale,
|
||||
maxNodesExceeded: false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
import React from 'react';
|
||||
|
||||
export default class NodesError extends React.Component {
|
||||
render() {
|
||||
let classNames = 'nodes-chart-error';
|
||||
if (this.props.hidden) {
|
||||
classNames += ' hide';
|
||||
}
|
||||
const iconClassName = 'fa ' + this.props.faIconClass;
|
||||
|
||||
return (
|
||||
<div className={classNames}>
|
||||
<div className="nodes-chart-error-icon">
|
||||
<span className={iconClassName} />
|
||||
</div>
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
export default function NodesError({children, faIconClass, hidden}) {
|
||||
let classNames = 'nodes-chart-error';
|
||||
if (hidden) {
|
||||
classNames += ' hide';
|
||||
}
|
||||
const iconClassName = `fa ${faIconClass}`;
|
||||
|
||||
return (
|
||||
<div className={classNames}>
|
||||
<div className="nodes-chart-error-icon">
|
||||
<span className={iconClassName} />
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ function runLayoutEngine(graph, imNodes, imEdges, opts) {
|
||||
let edges = imEdges;
|
||||
|
||||
if (nodes.size > MAX_NODES) {
|
||||
log('Too many nodes for graph layout engine. Limit: ' + MAX_NODES);
|
||||
log(`Too many nodes for graph layout engine. Limit: ${MAX_NODES}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -54,8 +54,8 @@ function runLayoutEngine(graph, imNodes, imEdges, opts) {
|
||||
|
||||
// configure node margins
|
||||
graph.setGraph({
|
||||
nodesep: nodesep,
|
||||
ranksep: ranksep
|
||||
nodesep,
|
||||
ranksep
|
||||
});
|
||||
|
||||
// add nodes to the graph if not already there
|
||||
@@ -140,6 +140,7 @@ function runLayoutEngine(graph, imNodes, imEdges, opts) {
|
||||
* @return {Object} modified layout
|
||||
*/
|
||||
function layoutSingleNodes(layout, opts) {
|
||||
const result = Object.assign({}, layout);
|
||||
const options = opts || {};
|
||||
const margins = options.margins || DEFAULT_MARGINS;
|
||||
const scale = options.scale || DEFAULT_SCALE;
|
||||
@@ -203,12 +204,12 @@ function layoutSingleNodes(layout, opts) {
|
||||
});
|
||||
|
||||
// adjust layout dimensions if graph is now bigger
|
||||
layout.width = Math.max(layout.width, singleX + nodeWidth / 2 + nodesep);
|
||||
layout.height = Math.max(layout.height, singleY + nodeHeight / 2 + ranksep);
|
||||
layout.nodes = nodes;
|
||||
result.width = Math.max(layout.width, singleX + nodeWidth / 2 + nodesep);
|
||||
result.height = Math.max(layout.height, singleY + nodeHeight / 2 + ranksep);
|
||||
result.nodes = nodes;
|
||||
}
|
||||
|
||||
return layout;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -218,6 +219,7 @@ function layoutSingleNodes(layout, opts) {
|
||||
* @return {Object} modified layout
|
||||
*/
|
||||
function shiftLayoutToCenter(layout, opts) {
|
||||
const result = Object.assign({}, layout);
|
||||
const options = opts || {};
|
||||
const margins = options.margins || DEFAULT_MARGINS;
|
||||
const width = options.width || DEFAULT_WIDTH;
|
||||
@@ -233,14 +235,12 @@ function shiftLayoutToCenter(layout, opts) {
|
||||
offsetY = (height - layout.height) / 2 + margins.top;
|
||||
}
|
||||
|
||||
layout.nodes = layout.nodes.map(node => {
|
||||
return node.merge({
|
||||
x: node.get('x') + offsetX,
|
||||
y: node.get('y') + offsetY
|
||||
});
|
||||
});
|
||||
result.nodes = layout.nodes.map(node => node.merge({
|
||||
x: node.get('x') + offsetX,
|
||||
y: node.get('y') + offsetY
|
||||
}));
|
||||
|
||||
layout.edges = layout.edges.map(edge => {
|
||||
result.edges = layout.edges.map(edge => {
|
||||
const points = edge.get('points').map(point => ({
|
||||
x: point.x + offsetX,
|
||||
y: point.y + offsetY
|
||||
@@ -248,7 +248,7 @@ function shiftLayoutToCenter(layout, opts) {
|
||||
return edge.set('points', points);
|
||||
});
|
||||
|
||||
return layout;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -320,16 +320,16 @@ function cloneLayout(layout, nodes, edges) {
|
||||
* @return {Object} modified layout
|
||||
*/
|
||||
function copyLayoutProperties(layout, nodeCache, edgeCache) {
|
||||
layout.nodes = layout.nodes.map(node => {
|
||||
return node.merge(nodeCache.get(node.get('id')));
|
||||
});
|
||||
layout.edges = layout.edges.map(edge => {
|
||||
if (edgeCache.has(edge.get('id')) && hasSameEndpoints(edgeCache.get(edge.get('id')), layout.nodes)) {
|
||||
const result = Object.assign({}, layout);
|
||||
result.nodes = layout.nodes.map(node => node.merge(nodeCache.get(node.get('id'))));
|
||||
result.edges = layout.edges.map(edge => {
|
||||
if (edgeCache.has(edge.get('id'))
|
||||
&& hasSameEndpoints(edgeCache.get(edge.get('id')), result.nodes)) {
|
||||
return edge.merge(edgeCache.get(edge.get('id')));
|
||||
}
|
||||
return setSimpleEdgePoints(edge, nodeCache);
|
||||
});
|
||||
return layout;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -361,7 +361,8 @@ export function doLayout(immNodes, immEdges, opts) {
|
||||
let layout;
|
||||
|
||||
++layoutRuns;
|
||||
if (!options.forceRelayout && cachedLayout && nodeCache && edgeCache && !hasUnseenNodes(immNodes, nodeCache)) {
|
||||
if (!options.forceRelayout && cachedLayout && nodeCache && edgeCache
|
||||
&& !hasUnseenNodes(immNodes, nodeCache)) {
|
||||
log('skip layout, trivial adjustment', ++layoutRunsTrivial, layoutRuns);
|
||||
layout = cloneLayout(cachedLayout, immNodes, immEdges);
|
||||
// copy old properties, works also if nodes get re-added
|
||||
|
||||
@@ -28,7 +28,8 @@ describe('NodeDetails', () => {
|
||||
|
||||
it('shows n/a when node was not found', () => {
|
||||
const c = TestUtils.renderIntoDocument(<NodeDetails notFound />);
|
||||
const notFound = TestUtils.findRenderedDOMComponentWithClass(c, 'node-details-header-notavailable');
|
||||
const notFound = TestUtils.findRenderedDOMComponentWithClass(c,
|
||||
'node-details-header-notavailable');
|
||||
expect(notFound).toBeDefined();
|
||||
});
|
||||
|
||||
|
||||
@@ -99,7 +99,8 @@ export default class App extends React.Component {
|
||||
<Logo />
|
||||
</svg>
|
||||
</div>
|
||||
<Topologies topologies={this.state.topologies} currentTopology={this.state.currentTopology} />
|
||||
<Topologies topologies={this.state.topologies}
|
||||
currentTopology={this.state.currentTopology} />
|
||||
</div>
|
||||
|
||||
<Nodes nodes={this.state.nodes} highlightedNodeIds={this.state.highlightedNodeIds}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint react/jsx-no-bind: "off" */
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
@@ -11,36 +12,28 @@ const SHAPES = ['circle', 'hexagon', 'square', 'heptagon'];
|
||||
const NODE_COUNTS = [1, 2, 3];
|
||||
const STACK_VARIANTS = [true, false];
|
||||
|
||||
const sample = function(collection) {
|
||||
return _.range(_.random(4)).map(() => _.sample(collection));
|
||||
};
|
||||
const sample = (collection) => _.range(_.random(4)).map(() => _.sample(collection));
|
||||
|
||||
const deltaAdd = function(name, adjacency = [], shape = 'circle', stack = false, nodeCount = 1) {
|
||||
return {
|
||||
'adjacency': adjacency,
|
||||
'controls': {},
|
||||
'shape': shape,
|
||||
'stack': stack,
|
||||
'node_count': nodeCount,
|
||||
'id': name,
|
||||
'label_major': name,
|
||||
'label_minor': 'weave-1',
|
||||
'latest': {},
|
||||
'metadata': {},
|
||||
'origins': [],
|
||||
'rank': 'alpine'
|
||||
};
|
||||
};
|
||||
const deltaAdd = (name, adjacency = [], shape = 'circle', stack = false, nodeCount = 1) => ({
|
||||
adjacency,
|
||||
controls: {},
|
||||
shape,
|
||||
stack,
|
||||
node_count: nodeCount,
|
||||
id: name,
|
||||
label_major: name,
|
||||
label_minor: 'weave-1',
|
||||
latest: {},
|
||||
metadata: {},
|
||||
origins: [],
|
||||
rank: 'alpine'
|
||||
});
|
||||
|
||||
function addAllVariants() {
|
||||
const newNodes = _.flattenDeep(SHAPES.map(s => {
|
||||
return STACK_VARIANTS.map(stack => {
|
||||
if (!stack) return [deltaAdd([s, 1, stack].join('-'), [], s, stack, 1)];
|
||||
return NODE_COUNTS.map(n => {
|
||||
return deltaAdd([s, n, stack].join('-'), [], s, stack, n);
|
||||
});
|
||||
});
|
||||
}));
|
||||
const newNodes = _.flattenDeep(SHAPES.map(s => STACK_VARIANTS.map(stack => {
|
||||
if (!stack) return [deltaAdd([s, 1, stack].join('-'), [], s, stack, 1)];
|
||||
return NODE_COUNTS.map(n => deltaAdd([s, n, stack].join('-'), [], s, stack, n));
|
||||
})));
|
||||
|
||||
receiveNodesDelta({
|
||||
add: newNodes
|
||||
@@ -50,7 +43,7 @@ function addAllVariants() {
|
||||
function addNodes(n) {
|
||||
const ns = AppStore.getNodes();
|
||||
const nodeNames = ns.keySeq().toJS();
|
||||
const newNodeNames = _.range(ns.size, ns.size + n).map((i) => 'zing' + i);
|
||||
const newNodeNames = _.range(ns.size, ns.size + n).map((i) => `zing${i}`);
|
||||
const allNodes = _(nodeNames).concat(newNodeNames).value();
|
||||
|
||||
receiveNodesDelta({
|
||||
|
||||
@@ -2,21 +2,14 @@ import React from 'react';
|
||||
|
||||
import DetailsCard from './details-card';
|
||||
|
||||
export default class Details extends React.Component {
|
||||
|
||||
export default function Details({controlStatus, details, nodes}) {
|
||||
// render all details as cards, later cards go on top
|
||||
render() {
|
||||
const details = this.props.details.toIndexedSeq();
|
||||
return (
|
||||
<div className="details">
|
||||
{details.map((obj, index) => {
|
||||
return (
|
||||
<DetailsCard key={obj.id} index={index} cardCount={details.size}
|
||||
nodes={this.props.nodes}
|
||||
nodeControlStatus={this.props.controlStatus[obj.id]} {...obj} />
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="details">
|
||||
{details.toIndexedSeq().map((obj, index) => <DetailsCard key={obj.id}
|
||||
index={index} cardCount={details.size} nodes={nodes}
|
||||
nodeControlStatus={controlStatus[obj.id]} {...obj} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ import React from 'react';
|
||||
|
||||
import { getNodeColor, getNodeColorDark } from '../utils/color-utils';
|
||||
import Terminal from './terminal';
|
||||
import { DETAILS_PANEL_WIDTH, DETAILS_PANEL_MARGINS, DETAILS_PANEL_OFFSET } from '../constants/styles';
|
||||
import { DETAILS_PANEL_WIDTH, DETAILS_PANEL_MARGINS,
|
||||
DETAILS_PANEL_OFFSET } from '../constants/styles';
|
||||
|
||||
export default function EmeddedTerminal({pipe, nodeId, details}) {
|
||||
const node = details.get(nodeId);
|
||||
|
||||
@@ -7,21 +7,25 @@ import { clickDownloadGraph, clickForceRelayout, clickPauseUpdate,
|
||||
clickResumeUpdate } from '../actions/app-actions';
|
||||
import { basePathSlash } from '../utils/web-api-utils';
|
||||
|
||||
export default (props) => {
|
||||
export default function Footer(props) {
|
||||
const { hostname, updatePaused, updatePausedAt, version } = props;
|
||||
const contrastMode = isContrastMode();
|
||||
|
||||
// link url to switch contrast with current UI state
|
||||
const otherContrastModeUrl = contrastMode ? basePathSlash(window.location.pathname) : contrastModeUrl;
|
||||
const otherContrastModeTitle = contrastMode ? 'Switch to normal contrast' : 'Switch to high contrast';
|
||||
const forceRelayoutTitle = 'Force re-layout (might reduce edge crossings, but may shift nodes around)';
|
||||
const otherContrastModeUrl = contrastMode
|
||||
? basePathSlash(window.location.pathname) : contrastModeUrl;
|
||||
const otherContrastModeTitle = contrastMode
|
||||
? 'Switch to normal contrast' : 'Switch to high contrast';
|
||||
const forceRelayoutTitle = 'Force re-layout (might reduce edge crossings, '
|
||||
+ 'but may shift nodes around)';
|
||||
|
||||
// pause button
|
||||
const isPaused = updatePaused;
|
||||
const updateCount = getUpdateBufferSize();
|
||||
const hasUpdates = updateCount > 0;
|
||||
const pausedAgo = moment(updatePausedAt).fromNow();
|
||||
const pauseTitle = isPaused ? `Paused ${pausedAgo}` : 'Pause updates (freezes the nodes in their current layout)';
|
||||
const pauseTitle = isPaused
|
||||
? `Paused ${pausedAgo}` : 'Pause updates (freezes the nodes in their current layout)';
|
||||
const pauseAction = isPaused ? clickResumeUpdate : clickPauseUpdate;
|
||||
const pauseClassName = isPaused ? 'footer-icon footer-icon-active' : 'footer-icon';
|
||||
let pauseLabel = '';
|
||||
@@ -64,4 +68,4 @@ export default (props) => {
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,59 +1,58 @@
|
||||
/* eslint max-len: "off" */
|
||||
import React from 'react';
|
||||
|
||||
export default class Logo extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<g className="logo">
|
||||
<path fill="#32324B" d="M114.937,118.165l75.419-67.366c-5.989-4.707-12.71-8.52-19.981-11.211l-55.438,49.52V118.165z"/>
|
||||
<path fill="#32324B" d="M93.265,108.465l-20.431,18.25c1.86,7.57,4.88,14.683,8.87,21.135l11.561-10.326V108.465z"/>
|
||||
<path fill="#00D2FF" d="M155.276,53.074V35.768C151.815,35.27,148.282,35,144.685,35c-3.766,0-7.465,0.286-11.079,0.828v36.604
|
||||
L155.276,53.074z"/>
|
||||
<path fill="#00D2FF" d="M155.276,154.874V82.133l-21.671,19.357v80.682c3.614,0.543,7.313,0.828,11.079,0.828
|
||||
c4.41,0,8.723-0.407,12.921-1.147l58.033-51.838c1.971-6.664,3.046-13.712,3.046-21.015c0-3.439-0.254-6.817-0.708-10.132
|
||||
L155.276,154.874z"/>
|
||||
<path fill="#FF4B19" d="M155.276,133.518l58.14-51.933c-2.77-6.938-6.551-13.358-11.175-19.076l-46.965,41.951V133.518z"/>
|
||||
<path fill="#FF4B19" d="M133.605,123.817l-18.668,16.676V41.242c-8.086,3.555-15.409,8.513-21.672,14.567V162.19
|
||||
c4.885,4.724,10.409,8.787,16.444,12.03l23.896-21.345V123.817z"/>
|
||||
<polygon fill="#32324B" points="325.563,124.099 339.389,72.22 357.955,72.22 337.414,144.377 315.556,144.377 303.311,95.79
|
||||
291.065,144.377 269.207,144.377 248.666,72.22 267.232,72.22 281.058,124.099 294.752,72.22 311.869,72.22 "/>
|
||||
<path fill="#32324B" d="M426.429,120.676c-2.106,14.352-13.167,24.623-32.128,24.623c-20.146,0-35.025-12.114-35.025-36.605
|
||||
c0-24.622,15.406-37.395,35.025-37.395c21.726,0,33.182,15.933,33.182,37.263v3.819h-49.772c0,8.031,3.291,18.17,16.327,18.17
|
||||
c7.242,0,12.904-3.555,14.353-10.27L426.429,120.676z M408.654,99.608c-0.659-10.008-7.11-13.694-14.484-13.694
|
||||
c-8.427,0-14.879,5.135-15.801,13.694H408.654z"/>
|
||||
<path fill="#32324B" d="M480.628,97.634v-2.502c0-5.662-2.37-9.351-13.036-9.351c-13.298,0-13.694,7.375-13.694,9.877h-17.117
|
||||
c0-10.666,4.477-24.359,31.338-24.359c25.676,0,30.285,12.771,30.285,23.174v39.766c0,2.897,0.131,5.267,0.395,7.11l0.527,3.028
|
||||
h-18.172v-7.241c-5.134,5.134-12.245,8.163-22.384,8.163c-14.221,0-25.018-8.296-25.018-22.648c0-16.59,15.67-20.146,21.99-21.199
|
||||
L480.628,97.634z M480.628,111.195l-6.979,1.054c-3.819,0.658-8.427,1.315-11.192,1.843c-3.029,0.527-5.662,1.186-7.637,2.765
|
||||
c-1.844,1.449-2.765,3.425-2.765,5.926c0,2.107,0.79,8.69,10.666,8.69c5.793,0,10.928-2.105,13.693-4.872
|
||||
c3.556-3.555,4.214-8.032,4.214-11.587V111.195z"/>
|
||||
<polygon fill="#32324B" points="549.495,144.377 525.399,144.377 501.698,72.221 521.186,72.221 537.775,127.392 554.499,72.221
|
||||
573.459,72.221 "/>
|
||||
<path fill="#32324B" d="M641.273,120.676c-2.106,14.352-13.167,24.623-32.128,24.623c-20.146,0-35.025-12.114-35.025-36.605
|
||||
c0-24.622,15.406-37.395,35.025-37.395c21.726,0,33.182,15.933,33.182,37.263v3.819h-49.772c0,8.031,3.291,18.17,16.327,18.17
|
||||
c7.242,0,12.904-3.555,14.354-10.27L641.273,120.676z M623.498,99.608c-0.659-10.008-7.109-13.694-14.483-13.694
|
||||
c-8.428,0-14.88,5.135-15.802,13.694H623.498z"/>
|
||||
<path fill="#32324B" d="M682.976,80.873c-7.524,0-16.896,2.376-16.896,10.692c0,17.952,46.201,1.452,46.201,30.229
|
||||
c0,9.637-5.676,22.309-30.229,22.309c-19.009,0-27.721-9.636-28.249-22.44h11.881c0.264,7.788,5.147,13.332,17.688,13.332
|
||||
c14.52,0,17.952-6.204,17.952-12.54c0-13.332-24.421-7.788-37.753-15.181c-4.885-2.771-8.316-7.128-8.316-15.048
|
||||
c0-11.616,10.824-20.461,27.853-20.461c20.989,0,27.193,12.145,27.589,20.196h-11.484
|
||||
C698.685,83.381,691.556,80.873,682.976,80.873z"/>
|
||||
<path fill="#32324B" d="M756.233,134.994c10.429,0,17.953-5.939,19.009-16.632h10.957c-1.98,17.028-13.597,25.74-29.966,25.74
|
||||
c-18.744,0-32.076-12.012-32.076-35.905c0-23.76,13.464-36.433,32.209-36.433c16.104,0,27.721,8.712,29.568,25.213h-10.956
|
||||
c-1.452-11.353-9.24-16.104-18.877-16.104c-12.012,0-20.856,8.448-20.856,27.324C735.245,127.471,744.485,134.994,756.233,134.994z
|
||||
"/>
|
||||
<path fill="#32324B" d="M830.418,144.103c-19.141,0-32.341-12.145-32.341-36.169c0-23.893,13.2-36.169,32.341-36.169
|
||||
c19.009,0,32.209,12.145,32.209,36.169C862.627,132.091,849.427,144.103,830.418,144.103z M830.418,134.994
|
||||
c12.145,0,21.12-7.392,21.12-27.061c0-19.536-8.976-27.061-21.12-27.061c-12.276,0-21.253,7.393-21.253,27.061
|
||||
C809.165,127.603,818.142,134.994,830.418,134.994z"/>
|
||||
<path fill="#32324B" d="M888.629,72.688v10.692c3.96-6.732,12.54-11.616,22.969-11.616c19.009,0,30.757,12.673,30.757,36.169
|
||||
c0,23.629-12.145,36.169-31.152,36.169c-10.429,0-18.745-4.224-22.573-11.22v35.641h-10.824V72.688H888.629z M910.409,134.994
|
||||
c12.145,0,20.857-7.392,20.857-27.061c0-19.536-8.713-27.061-20.857-27.061c-12.275,0-21.912,7.393-21.912,27.061
|
||||
C888.497,127.603,898.134,134.994,910.409,134.994z"/>
|
||||
<path fill="#32324B" d="M1016.801,119.022c-1.452,12.408-10.032,25.08-30.229,25.08c-18.745,0-32.341-12.804-32.341-36.037
|
||||
c0-21.912,13.464-36.301,32.209-36.301c19.8,0,30.757,14.784,30.757,38.018h-51.878c0.265,13.332,5.809,25.212,21.385,25.212
|
||||
c11.484,0,18.217-7.128,19.141-16.104L1016.801,119.022z M1005.448,101.201c-1.056-14.916-9.636-20.328-19.272-20.328
|
||||
c-10.824,0-19.141,7.26-20.46,20.328H1005.448z"/>
|
||||
</g>
|
||||
);
|
||||
}
|
||||
export default function Logo() {
|
||||
return (
|
||||
<g className="logo">
|
||||
<path fill="#32324B" d="M114.937,118.165l75.419-67.366c-5.989-4.707-12.71-8.52-19.981-11.211l-55.438,49.52V118.165z" />
|
||||
<path fill="#32324B" d="M93.265,108.465l-20.431,18.25c1.86,7.57,4.88,14.683,8.87,21.135l11.561-10.326V108.465z" />
|
||||
<path fill="#00D2FF" d="M155.276,53.074V35.768C151.815,35.27,148.282,35,144.685,35c-3.766,0-7.465,0.286-11.079,0.828v36.604
|
||||
L155.276,53.074z" />
|
||||
<path fill="#00D2FF" d="M155.276,154.874V82.133l-21.671,19.357v80.682c3.614,0.543,7.313,0.828,11.079,0.828
|
||||
c4.41,0,8.723-0.407,12.921-1.147l58.033-51.838c1.971-6.664,3.046-13.712,3.046-21.015c0-3.439-0.254-6.817-0.708-10.132
|
||||
L155.276,154.874z" />
|
||||
<path fill="#FF4B19" d="M155.276,133.518l58.14-51.933c-2.77-6.938-6.551-13.358-11.175-19.076l-46.965,41.951V133.518z" />
|
||||
<path fill="#FF4B19" d="M133.605,123.817l-18.668,16.676V41.242c-8.086,3.555-15.409,8.513-21.672,14.567V162.19
|
||||
c4.885,4.724,10.409,8.787,16.444,12.03l23.896-21.345V123.817z" />
|
||||
<polygon fill="#32324B" points="325.563,124.099 339.389,72.22 357.955,72.22 337.414,144.377 315.556,144.377 303.311,95.79
|
||||
291.065,144.377 269.207,144.377 248.666,72.22 267.232,72.22 281.058,124.099 294.752,72.22 311.869,72.22 " />
|
||||
<path fill="#32324B" d="M426.429,120.676c-2.106,14.352-13.167,24.623-32.128,24.623c-20.146,0-35.025-12.114-35.025-36.605
|
||||
c0-24.622,15.406-37.395,35.025-37.395c21.726,0,33.182,15.933,33.182,37.263v3.819h-49.772c0,8.031,3.291,18.17,16.327,18.17
|
||||
c7.242,0,12.904-3.555,14.353-10.27L426.429,120.676z M408.654,99.608c-0.659-10.008-7.11-13.694-14.484-13.694
|
||||
c-8.427,0-14.879,5.135-15.801,13.694H408.654z" />
|
||||
<path fill="#32324B" d="M480.628,97.634v-2.502c0-5.662-2.37-9.351-13.036-9.351c-13.298,0-13.694,7.375-13.694,9.877h-17.117
|
||||
c0-10.666,4.477-24.359,31.338-24.359c25.676,0,30.285,12.771,30.285,23.174v39.766c0,2.897,0.131,5.267,0.395,7.11l0.527,3.028
|
||||
h-18.172v-7.241c-5.134,5.134-12.245,8.163-22.384,8.163c-14.221,0-25.018-8.296-25.018-22.648c0-16.59,15.67-20.146,21.99-21.199
|
||||
L480.628,97.634z M480.628,111.195l-6.979,1.054c-3.819,0.658-8.427,1.315-11.192,1.843c-3.029,0.527-5.662,1.186-7.637,2.765
|
||||
c-1.844,1.449-2.765,3.425-2.765,5.926c0,2.107,0.79,8.69,10.666,8.69c5.793,0,10.928-2.105,13.693-4.872
|
||||
c3.556-3.555,4.214-8.032,4.214-11.587V111.195z" />
|
||||
<polygon fill="#32324B" points="549.495,144.377 525.399,144.377 501.698,72.221 521.186,72.221 537.775,127.392 554.499,72.221
|
||||
573.459,72.221 " />
|
||||
<path fill="#32324B" d="M641.273,120.676c-2.106,14.352-13.167,24.623-32.128,24.623c-20.146,0-35.025-12.114-35.025-36.605
|
||||
c0-24.622,15.406-37.395,35.025-37.395c21.726,0,33.182,15.933,33.182,37.263v3.819h-49.772c0,8.031,3.291,18.17,16.327,18.17
|
||||
c7.242,0,12.904-3.555,14.354-10.27L641.273,120.676z M623.498,99.608c-0.659-10.008-7.109-13.694-14.483-13.694
|
||||
c-8.428,0-14.88,5.135-15.802,13.694H623.498z" />
|
||||
<path fill="#32324B" d="M682.976,80.873c-7.524,0-16.896,2.376-16.896,10.692c0,17.952,46.201,1.452,46.201,30.229
|
||||
c0,9.637-5.676,22.309-30.229,22.309c-19.009,0-27.721-9.636-28.249-22.44h11.881c0.264,7.788,5.147,13.332,17.688,13.332
|
||||
c14.52,0,17.952-6.204,17.952-12.54c0-13.332-24.421-7.788-37.753-15.181c-4.885-2.771-8.316-7.128-8.316-15.048
|
||||
c0-11.616,10.824-20.461,27.853-20.461c20.989,0,27.193,12.145,27.589,20.196h-11.484
|
||||
C698.685,83.381,691.556,80.873,682.976,80.873z" />
|
||||
<path fill="#32324B" d="M756.233,134.994c10.429,0,17.953-5.939,19.009-16.632h10.957c-1.98,17.028-13.597,25.74-29.966,25.74
|
||||
c-18.744,0-32.076-12.012-32.076-35.905c0-23.76,13.464-36.433,32.209-36.433c16.104,0,27.721,8.712,29.568,25.213h-10.956
|
||||
c-1.452-11.353-9.24-16.104-18.877-16.104c-12.012,0-20.856,8.448-20.856,27.324C735.245,127.471,744.485,134.994,756.233,134.994z
|
||||
" />
|
||||
<path fill="#32324B" d="M830.418,144.103c-19.141,0-32.341-12.145-32.341-36.169c0-23.893,13.2-36.169,32.341-36.169
|
||||
c19.009,0,32.209,12.145,32.209,36.169C862.627,132.091,849.427,144.103,830.418,144.103z M830.418,134.994
|
||||
c12.145,0,21.12-7.392,21.12-27.061c0-19.536-8.976-27.061-21.12-27.061c-12.276,0-21.253,7.393-21.253,27.061
|
||||
C809.165,127.603,818.142,134.994,830.418,134.994z" />
|
||||
<path fill="#32324B" d="M888.629,72.688v10.692c3.96-6.732,12.54-11.616,22.969-11.616c19.009,0,30.757,12.673,30.757,36.169
|
||||
c0,23.629-12.145,36.169-31.152,36.169c-10.429,0-18.745-4.224-22.573-11.22v35.641h-10.824V72.688H888.629z M910.409,134.994
|
||||
c12.145,0,20.857-7.392,20.857-27.061c0-19.536-8.713-27.061-20.857-27.061c-12.275,0-21.912,7.393-21.912,27.061
|
||||
C888.497,127.603,898.134,134.994,910.409,134.994z" />
|
||||
<path fill="#32324B" d="M1016.801,119.022c-1.452,12.408-10.032,25.08-30.229,25.08c-18.745,0-32.341-12.804-32.341-36.037
|
||||
c0-21.912,13.464-36.301,32.209-36.301c19.8,0,30.757,14.784,30.757,38.018h-51.878c0.265,13.332,5.809,25.212,21.385,25.212
|
||||
c11.484,0,18.217-7.128,19.141-16.104L1016.801,119.022z M1005.448,101.201c-1.056-14.916-9.636-20.328-19.272-20.328
|
||||
c-10.824,0-19.141,7.26-20.46,20.328H1005.448z" />
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -44,7 +44,8 @@ export default class NodeDetails extends React.Component {
|
||||
return (
|
||||
<div className="node-details-tools-wrapper">
|
||||
<div className="node-details-tools">
|
||||
{showSwitchTopology && <span title={topologyTitle} className="fa fa-exchange" onClick={this.handleShowTopologyForNode} />}
|
||||
{showSwitchTopology && <span title={topologyTitle}
|
||||
className="fa fa-exchange" onClick={this.handleShowTopologyForNode} />}
|
||||
<span title="Close details" className="fa fa-close" onClick={this.handleClickClose} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -60,7 +61,7 @@ export default class NodeDetails extends React.Component {
|
||||
const tools = this.renderTools();
|
||||
const styles = {
|
||||
header: {
|
||||
'backgroundColor': nodeColor
|
||||
backgroundColor: nodeColor
|
||||
}
|
||||
};
|
||||
|
||||
@@ -103,7 +104,8 @@ export default class NodeDetails extends React.Component {
|
||||
</div>
|
||||
<div className="node-details-content">
|
||||
<p className="node-details-content-info">
|
||||
<strong>{this.props.label}</strong> is not visible to Scope when it is not communicating.
|
||||
<strong>{this.props.label}</strong> is not visible to Scope when it
|
||||
is not communicating.
|
||||
Details will become available here when it communicates again.
|
||||
</p>
|
||||
</div>
|
||||
@@ -113,7 +115,8 @@ export default class NodeDetails extends React.Component {
|
||||
|
||||
renderTable(table) {
|
||||
const key = _.snakeCase(table.title);
|
||||
return <NodeDetailsTable title={table.title} key={key} rows={table.rows} isNumeric={table.numeric} />;
|
||||
return (<NodeDetailsTable title={table.title} key={key} rows={table.rows}
|
||||
isNumeric={table.numeric} />);
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -136,10 +139,10 @@ export default class NodeDetails extends React.Component {
|
||||
const tools = this.renderTools();
|
||||
const styles = {
|
||||
controls: {
|
||||
'backgroundColor': brightenColor(nodeColor)
|
||||
backgroundColor: brightenColor(nodeColor)
|
||||
},
|
||||
header: {
|
||||
'backgroundColor': nodeColor
|
||||
backgroundColor: nodeColor
|
||||
}
|
||||
};
|
||||
|
||||
@@ -174,23 +177,20 @@ export default class NodeDetails extends React.Component {
|
||||
<NodeDetailsInfo rows={details.metadata} />
|
||||
</div>}
|
||||
|
||||
{details.connections && details.connections.map(connections => {
|
||||
return (
|
||||
<div className="node-details-content-section" key={connections.id}>
|
||||
<NodeDetailsTable {...connections} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{details.connections && details.connections.map(connections => <div
|
||||
className="node-details-content-section" key={connections.id}>
|
||||
<NodeDetailsTable {...connections} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{details.children && details.children.map(children => {
|
||||
return (
|
||||
<div className="node-details-content-section" key={children.topologyId}>
|
||||
<NodeDetailsTable {...children} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{details.children && details.children.map(children => <div
|
||||
className="node-details-content-section" key={children.topologyId}>
|
||||
<NodeDetailsTable {...children} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{details.docker_labels && details.docker_labels.length > 0 && <div className="node-details-content-section">
|
||||
{details.docker_labels && details.docker_labels.length > 0
|
||||
&& <div className="node-details-content-section">
|
||||
<div className="node-details-content-section-header">Docker Labels</div>
|
||||
<NodeDetailsLabels rows={details.docker_labels} />
|
||||
</div>}
|
||||
|
||||
@@ -2,31 +2,25 @@ import React from 'react';
|
||||
|
||||
import NodeDetailsControlButton from './node-details-control-button';
|
||||
|
||||
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';
|
||||
} else {
|
||||
spinnerClassName += ' node-details-controls-spinner hide';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="node-details-controls">
|
||||
{this.props.error && <div className="node-details-controls-error" title={this.props.error}>
|
||||
<span className="node-details-controls-error-icon fa fa-warning" />
|
||||
<span className="node-details-controls-error-messages">{this.props.error}</span>
|
||||
</div>}
|
||||
<span className="node-details-controls-buttons">
|
||||
{this.props.controls && this.props.controls.map(control => {
|
||||
return (
|
||||
<NodeDetailsControlButton nodeId={this.props.nodeId} control={control}
|
||||
pending={this.props.pending} key={control.id} />
|
||||
);
|
||||
})}
|
||||
</span>
|
||||
{this.props.controls && <span title="Applying..." className={spinnerClassName}></span>}
|
||||
</div>
|
||||
);
|
||||
export default function NodeDetailsControls({controls, error, nodeId, pending}) {
|
||||
let spinnerClassName = 'fa fa-circle-o-notch fa-spin';
|
||||
if (pending) {
|
||||
spinnerClassName += ' node-details-controls-spinner';
|
||||
} else {
|
||||
spinnerClassName += ' node-details-controls-spinner hide';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="node-details-controls">
|
||||
{error && <div className="node-details-controls-error" title={error}>
|
||||
<span className="node-details-controls-error-icon fa fa-warning" />
|
||||
<span className="node-details-controls-error-messages">{error}</span>
|
||||
</div>}
|
||||
<span className="node-details-controls-buttons">
|
||||
{controls && controls.map(control => <NodeDetailsControlButton
|
||||
nodeId={nodeId} control={control} pending={pending} key={control.id} />)}
|
||||
</span>
|
||||
{controls && <span title="Applying..." className={spinnerClassName}></span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,19 +4,17 @@ import Sparkline from '../sparkline';
|
||||
import metricFeeder from '../../hoc/metric-feeder';
|
||||
import { formatMetric } from '../../utils/string-utils';
|
||||
|
||||
class NodeDetailsHealthItem extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="node-details-health-item">
|
||||
<div className="node-details-health-item-value">{formatMetric(this.props.value, this.props)}</div>
|
||||
<div className="node-details-health-item-sparkline">
|
||||
<Sparkline data={this.props.samples} max={this.props.max}
|
||||
first={this.props.first} last={this.props.last} />
|
||||
</div>
|
||||
<div className="node-details-health-item-label">{this.props.label}</div>
|
||||
function NodeDetailsHealthItem(props) {
|
||||
return (
|
||||
<div className="node-details-health-item">
|
||||
<div className="node-details-health-item-value">{formatMetric(props.value, props)}</div>
|
||||
<div className="node-details-health-item-sparkline">
|
||||
<Sparkline data={props.samples} max={props.max}
|
||||
first={props.first} last={props.last} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<div className="node-details-health-item-label">{props.label}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default metricFeeder(NodeDetailsHealthItem);
|
||||
|
||||
@@ -3,15 +3,15 @@ import React from 'react';
|
||||
import metricFeeder from '../../hoc/metric-feeder';
|
||||
import { formatMetric } from '../../utils/string-utils';
|
||||
|
||||
class NodeDetailsHealthOverflowItem extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="node-details-health-overflow-item">
|
||||
<div className="node-details-health-overflow-item-value">{formatMetric(this.props.value, this.props)}</div>
|
||||
<div className="node-details-health-overflow-item-label truncate">{this.props.label}</div>
|
||||
function NodeDetailsHealthOverflowItem(props) {
|
||||
return (
|
||||
<div className="node-details-health-overflow-item">
|
||||
<div className="node-details-health-overflow-item-value">
|
||||
{formatMetric(props.value, props)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<div className="node-details-health-overflow-item-label truncate">{props.label}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default metricFeeder(NodeDetailsHealthOverflowItem);
|
||||
|
||||
@@ -32,13 +32,11 @@ export default class NodeDetailsHealth extends React.Component {
|
||||
return (
|
||||
<div className="node-details-health" style={{flexWrap, justifyContent}}>
|
||||
<div className="node-details-health-wrapper">
|
||||
{primeMetrics.map(item => {
|
||||
return <NodeDetailsHealthItem key={item.id} {...item} />;
|
||||
})}
|
||||
{primeMetrics.map(item => <NodeDetailsHealthItem key={item.id} {...item} />)}
|
||||
{showOverflow && <NodeDetailsHealthOverflow items={overflowMetrics}
|
||||
handleClick={() => this.handleClickMore()} />}
|
||||
handleClick={this.handleClickMore} />}
|
||||
</div>
|
||||
<ShowMore handleClick={() => this.handleClickMore()} collection={this.props.metrics}
|
||||
<ShowMore handleClick={this.handleClickMore} collection={this.props.metrics}
|
||||
expanded={this.state.expanded} notShown={notShown} hideNumber />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -27,19 +27,16 @@ export default class NodeDetailsInfo extends React.Component {
|
||||
}
|
||||
return (
|
||||
<div className="node-details-info">
|
||||
{rows.map(field => {
|
||||
return (
|
||||
<div className="node-details-info-field" key={field.id}>
|
||||
<div className="node-details-info-field-label truncate" title={field.label}>
|
||||
{field.label}
|
||||
</div>
|
||||
<div className="node-details-info-field-value truncate" title={field.value}>
|
||||
{field.value}
|
||||
</div>
|
||||
{rows.map(field => (<div className="node-details-info-field" key={field.id}>
|
||||
<div className="node-details-info-field-label truncate" title={field.label}>
|
||||
{field.label}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<ShowMore handleClick={() => this.handleClickMore()} collection={this.props.rows}
|
||||
<div className="node-details-info-field-value truncate" title={field.value}>
|
||||
{field.value}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<ShowMore handleClick={this.handleClickMore} collection={this.props.rows}
|
||||
expanded={this.state.expanded} notShown={notShown} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,22 +1,17 @@
|
||||
import React from 'react';
|
||||
|
||||
export default class NodeDetailsLabels extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="node-details-labels">
|
||||
{this.props.rows.map(field => {
|
||||
return (
|
||||
<div className="node-details-labels-field" key={field.id}>
|
||||
<div className="node-details-labels-field-label truncate" title={field.label}>
|
||||
{field.label}
|
||||
</div>
|
||||
<div className="node-details-labels-field-value truncate" title={field.value}>
|
||||
{field.value}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default function NodeDetailsLabels({rows}) {
|
||||
return (
|
||||
<div className="node-details-labels">
|
||||
{rows.map(field => (<div className="node-details-labels-field" key={field.id}>
|
||||
<div className="node-details-labels-field-label truncate" title={field.label}>
|
||||
{field.label}
|
||||
</div>
|
||||
<div className="node-details-labels-field-value truncate" title={field.value}>
|
||||
{field.value}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,13 +16,14 @@ export default class NodeDetailsRelatives extends React.Component {
|
||||
handleLimitClick(ev) {
|
||||
ev.preventDefault();
|
||||
const limit = this.state.limit ? 0 : this.DEFAULT_LIMIT;
|
||||
this.setState({limit: limit});
|
||||
this.setState({limit});
|
||||
}
|
||||
|
||||
render() {
|
||||
let relatives = this.props.relatives;
|
||||
const limited = this.state.limit > 0 && relatives.length > this.state.limit;
|
||||
const showLimitAction = limited || (this.state.limit === 0 && relatives.length > this.DEFAULT_LIMIT);
|
||||
const showLimitAction = limited || (this.state.limit === 0
|
||||
&& relatives.length > this.DEFAULT_LIMIT);
|
||||
const limitActionText = limited ? 'Show more' : 'Show less';
|
||||
if (limited) {
|
||||
relatives = relatives.slice(0, this.state.limit);
|
||||
@@ -30,10 +31,9 @@ export default class NodeDetailsRelatives extends React.Component {
|
||||
|
||||
return (
|
||||
<div className="node-details-relatives">
|
||||
{relatives.map(relative => {
|
||||
return <NodeDetailsRelativesLink {...relative} key={relative.id} />;
|
||||
})}
|
||||
{showLimitAction && <span className="node-details-relatives-more" onClick={this.handleLimitClick}>{limitActionText}</span>}
|
||||
{relatives.map(relative => <NodeDetailsRelativesLink {...relative} key={relative.id} />)}
|
||||
{showLimitAction && <span className="node-details-relatives-more"
|
||||
onClick={this.handleLimitClick}>{limitActionText}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,14 +2,12 @@ import React from 'react';
|
||||
|
||||
import { formatMetric } from '../../utils/string-utils';
|
||||
|
||||
class NodeDetailsTableNodeMetric extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<td className="node-details-table-node-metric">
|
||||
{formatMetric(this.props.value, this.props)}
|
||||
</td>
|
||||
);
|
||||
}
|
||||
function NodeDetailsTableNodeMetric(props) {
|
||||
return (
|
||||
<td className="node-details-table-node-metric">
|
||||
{formatMetric(props.value, props)}
|
||||
</td>
|
||||
);
|
||||
}
|
||||
|
||||
export default NodeDetailsTableNodeMetric;
|
||||
|
||||
@@ -25,7 +25,8 @@ export default class NodeDetailsTable extends React.Component {
|
||||
|
||||
handleHeaderClick(ev, headerId) {
|
||||
ev.preventDefault();
|
||||
const sortedDesc = headerId === this.state.sortBy ? !this.state.sortedDesc : this.state.sortedDesc;
|
||||
const sortedDesc = headerId === this.state.sortBy
|
||||
? !this.state.sortedDesc : this.state.sortedDesc;
|
||||
const sortBy = headerId;
|
||||
this.setState({sortBy, sortedDesc});
|
||||
}
|
||||
@@ -47,17 +48,15 @@ export default class NodeDetailsTable extends React.Component {
|
||||
|
||||
getMetaDataSorters() {
|
||||
// returns an array of sorters that will take a node
|
||||
return _.get(this.props.nodes, [0, 'metadata'], []).map((field, index) => {
|
||||
return node => {
|
||||
const nodeMetadataField = node.metadata[index];
|
||||
if (nodeMetadataField) {
|
||||
if (isNumberField(nodeMetadataField)) {
|
||||
return parseFloat(nodeMetadataField.value);
|
||||
}
|
||||
return nodeMetadataField.value;
|
||||
return _.get(this.props.nodes, [0, 'metadata'], []).map((field, index) => node => {
|
||||
const nodeMetadataField = node.metadata[index];
|
||||
if (nodeMetadataField) {
|
||||
if (isNumberField(nodeMetadataField)) {
|
||||
return parseFloat(nodeMetadataField.value);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
return nodeMetadataField.value;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -81,8 +80,9 @@ export default class NodeDetailsTable extends React.Component {
|
||||
['metrics', 'metadata'].forEach(collection => {
|
||||
if (node[collection]) {
|
||||
node[collection].forEach(field => {
|
||||
field.valueType = collection;
|
||||
values[field.id] = field;
|
||||
const result = Object.assign({}, field);
|
||||
result.valueType = collection;
|
||||
values[field.id] = result;
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -103,7 +103,8 @@ export default class NodeDetailsTable extends React.Component {
|
||||
this.handleHeaderClick(ev, header.id);
|
||||
};
|
||||
// sort by first metric by default
|
||||
const isSorted = this.state.sortBy !== null ? header.id === this.state.sortBy : header.id === defaultSortBy;
|
||||
const isSorted = this.state.sortBy !== null
|
||||
? header.id === this.state.sortBy : header.id === defaultSortBy;
|
||||
const isSortedDesc = isSorted && this.state.sortedDesc;
|
||||
const isSortedAsc = isSorted && !isSortedDesc;
|
||||
if (isSorted) {
|
||||
@@ -111,8 +112,10 @@ export default class NodeDetailsTable extends React.Component {
|
||||
}
|
||||
return (
|
||||
<td className={headerClasses.join(' ')} onClick={onHeaderClick} key={header.id}>
|
||||
{isSortedAsc && <span className="node-details-table-header-sorter fa fa-caret-up" />}
|
||||
{isSortedDesc && <span className="node-details-table-header-sorter fa fa-caret-down" />}
|
||||
{isSortedAsc
|
||||
&& <span className="node-details-table-header-sorter fa fa-caret-up" />}
|
||||
{isSortedDesc
|
||||
&& <span className="node-details-table-header-sorter fa fa-caret-down" />}
|
||||
{header.label}
|
||||
</td>
|
||||
);
|
||||
@@ -145,7 +148,8 @@ export default class NodeDetailsTable extends React.Component {
|
||||
|
||||
render() {
|
||||
const headers = this.renderHeaders();
|
||||
let nodes = _.sortBy(this.props.nodes, this.getValueForSortBy, 'label', this.getMetaDataSorters());
|
||||
let nodes = _.sortBy(this.props.nodes, this.getValueForSortBy, 'label',
|
||||
this.getMetaDataSorters());
|
||||
const limited = nodes && this.state.limit > 0 && nodes.length > this.state.limit;
|
||||
const expanded = this.state.limit === 0;
|
||||
const notShown = nodes.length - this.DEFAULT_LIMIT;
|
||||
@@ -176,7 +180,8 @@ export default class NodeDetailsTable extends React.Component {
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
<ShowMore handleClick={() => this.handleLimitClick()} collection={this.props.nodes} expanded={expanded} notShown={notShown} />
|
||||
<ShowMore handleClick={this.handleLimitClick} collection={this.props.nodes}
|
||||
expanded={expanded} notShown={notShown} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,11 +19,11 @@ export default class ShowMore extends React.Component {
|
||||
const limitActionIcon = !expanded && notShown > 0 ? 'fa fa-caret-down' : 'fa fa-caret-up';
|
||||
|
||||
if (!showLimitAction) {
|
||||
return <span/>;
|
||||
return <span />;
|
||||
}
|
||||
return (
|
||||
<div className="show-more" onClick={this.handleClick}>
|
||||
{limitActionText} <span className={'show-more-icon ' + limitActionIcon}/>
|
||||
{limitActionText} <span className={`show-more-icon ${limitActionIcon}`} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
export default class Sidebar extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="sidebar">
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default function Sidebar({children}) {
|
||||
return (
|
||||
<div className="sidebar">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -30,12 +30,10 @@ export default class Sparkline extends React.Component {
|
||||
this.line.interpolate(this.props.interpolate);
|
||||
|
||||
// Convert dates into D3 dates
|
||||
data = data.map(d => {
|
||||
return {
|
||||
date: parseDate(d.date),
|
||||
value: d.value
|
||||
};
|
||||
});
|
||||
data = data.map(d => ({
|
||||
date: parseDate(d.date),
|
||||
value: d.value
|
||||
}));
|
||||
|
||||
// determine date range
|
||||
let firstDate = this.props.first ? parseDate(this.props.first) : data[0].date;
|
||||
@@ -51,16 +49,17 @@ export default class Sparkline extends React.Component {
|
||||
|
||||
// determine value range
|
||||
const minValue = this.props.min !== undefined ? this.props.min : d3.min(data, d => d.value);
|
||||
const maxValue = this.props.max !== undefined ? Math.max(this.props.max, d3.max(data, d => d.value)) : d3.max(data, d => d.value);
|
||||
const maxValue = this.props.max !== undefined
|
||||
? Math.max(this.props.max, d3.max(data, d => d.value)) : d3.max(data, d => d.value);
|
||||
this.y.domain([minValue, maxValue]);
|
||||
|
||||
const lastValue = data[data.length - 1].value;
|
||||
const lastX = this.x(lastDate);
|
||||
const lastY = this.y(lastValue);
|
||||
const title = 'Last ' + d3.round((lastDate - firstDate) / 1000) + ' seconds, ' +
|
||||
data.length + ' samples, min: ' + d3.round(d3.min(data, d => d.value), 2) +
|
||||
', max: ' + d3.round(d3.max(data, d => d.value), 2) +
|
||||
', mean: ' + d3.round(d3.mean(data, d => d.value), 2);
|
||||
const title = `Last ${d3.round((lastDate - firstDate) / 1000)} seconds, ` +
|
||||
`${data.length} samples, min: ${d3.round(d3.min(data, d => d.value), 2)}` +
|
||||
`, max: ${d3.round(d3.max(data, d => d.value), 2)}` +
|
||||
`, mean: ${d3.round(d3.mean(data, d => d.value), 2)}`;
|
||||
|
||||
return {title, lastX, lastY, data};
|
||||
}
|
||||
|
||||
@@ -1,38 +1,36 @@
|
||||
import React from 'react';
|
||||
|
||||
export default class Status extends React.Component {
|
||||
render() {
|
||||
let title = '';
|
||||
let text = 'Trying to reconnect...';
|
||||
let showWarningIcon = false;
|
||||
let classNames = 'status sidebar-item';
|
||||
export default function Status({errorUrl, topologiesLoaded, topology, websocketClosed}) {
|
||||
let title = '';
|
||||
let text = 'Trying to reconnect...';
|
||||
let showWarningIcon = false;
|
||||
let classNames = 'status sidebar-item';
|
||||
|
||||
if (this.props.errorUrl) {
|
||||
title = `Cannot reach Scope. Make sure the following URL is reachable: ${this.props.errorUrl}`;
|
||||
classNames += ' status-loading';
|
||||
showWarningIcon = true;
|
||||
} else if (!this.props.topologiesLoaded) {
|
||||
text = 'Connecting to Scope...';
|
||||
classNames += ' status-loading';
|
||||
showWarningIcon = true;
|
||||
} else if (this.props.websocketClosed) {
|
||||
classNames += ' status-loading';
|
||||
showWarningIcon = true;
|
||||
} else if (this.props.topology) {
|
||||
const stats = this.props.topology.get('stats');
|
||||
text = `${stats.get('node_count')} nodes`;
|
||||
if (stats.get('filtered_nodes')) {
|
||||
text = `${text} (${stats.get('filtered_nodes')} filtered)`;
|
||||
}
|
||||
classNames += ' status-stats';
|
||||
showWarningIcon = false;
|
||||
if (errorUrl) {
|
||||
title = `Cannot reach Scope. Make sure the following URL is reachable: ${errorUrl}`;
|
||||
classNames += ' status-loading';
|
||||
showWarningIcon = true;
|
||||
} else if (!topologiesLoaded) {
|
||||
text = 'Connecting to Scope...';
|
||||
classNames += ' status-loading';
|
||||
showWarningIcon = true;
|
||||
} else if (websocketClosed) {
|
||||
classNames += ' status-loading';
|
||||
showWarningIcon = true;
|
||||
} else if (topology) {
|
||||
const stats = topology.get('stats');
|
||||
text = `${stats.get('node_count')} nodes`;
|
||||
if (stats.get('filtered_nodes')) {
|
||||
text = `${text} (${stats.get('filtered_nodes')} filtered)`;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames}>
|
||||
{showWarningIcon && <span className="status-icon fa fa-exclamation-circle" />}
|
||||
<span className="status-label" title={title}>{text}</span>
|
||||
</div>
|
||||
);
|
||||
classNames += ' status-stats';
|
||||
showWarningIcon = false;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames}>
|
||||
{showWarningIcon && <span className="status-icon fa fa-exclamation-circle" />}
|
||||
<span className="status-label" title={title}>{text}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint no-return-assign: "off", react/jsx-no-bind: "off" */
|
||||
import debug from 'debug';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
@@ -10,7 +11,7 @@ import { getPipeStatus, basePath } from '../utils/web-api-utils';
|
||||
import Term from '../vendor/term.js';
|
||||
|
||||
const wsProto = location.protocol === 'https:' ? 'wss' : 'ws';
|
||||
const wsUrl = wsProto + '://' + location.host + basePath(location.pathname);
|
||||
const wsUrl = `${wsProto}://${location.host}${basePath(location.pathname)}`;
|
||||
const log = debug('scope:terminal');
|
||||
|
||||
const DEFAULT_COLS = 80;
|
||||
@@ -66,7 +67,7 @@ function openNewWindow(url, bcr, minWidth = 200) {
|
||||
};
|
||||
|
||||
const windowOptionsString = Object.keys(windowOptions)
|
||||
.map((k) => k + '=' + windowOptions[k])
|
||||
.map((k) => `${k}=${windowOptions[k]}`)
|
||||
.join(',');
|
||||
|
||||
window.open(url, '', windowOptionsString);
|
||||
@@ -92,7 +93,7 @@ export default class Terminal extends React.Component {
|
||||
}
|
||||
|
||||
createWebsocket(term) {
|
||||
const socket = new WebSocket(wsUrl + '/api/pipe/' + this.getPipeId());
|
||||
const socket = new WebSocket(`${wsUrl}/api/pipe/${this.getPipeId()}`);
|
||||
socket.binaryType = 'arraybuffer';
|
||||
|
||||
getPipeStatus(this.getPipeId());
|
||||
@@ -157,8 +158,8 @@ export default class Terminal extends React.Component {
|
||||
|
||||
this.resizeTimeout = setTimeout(() => {
|
||||
this.setState({
|
||||
pixelPerCol: pixelPerCol,
|
||||
pixelPerRow: pixelPerRow
|
||||
pixelPerCol,
|
||||
pixelPerRow
|
||||
});
|
||||
this.handleResize();
|
||||
}, 10);
|
||||
|
||||
@@ -19,7 +19,8 @@ export default class Topologies extends React.Component {
|
||||
const isActive = subTopology === this.props.currentTopology;
|
||||
const topologyId = subTopology.get('id');
|
||||
const title = this.renderTitle(subTopology);
|
||||
const className = isActive ? 'topologies-sub-item topologies-sub-item-active' : 'topologies-sub-item';
|
||||
const className = isActive
|
||||
? 'topologies-sub-item topologies-sub-item-active' : 'topologies-sub-item';
|
||||
|
||||
return (
|
||||
<div className={className} title={title} key={topologyId} rel={topologyId}
|
||||
@@ -32,13 +33,14 @@ export default class Topologies extends React.Component {
|
||||
}
|
||||
|
||||
renderTitle(topology) {
|
||||
return ['Nodes: ' + topology.getIn(['stats', 'node_count']),
|
||||
'Connections: ' + topology.getIn(['stats', 'node_count'])].join('\n');
|
||||
return `Nodes: ${topology.getIn(['stats', 'node_count'])}\n`
|
||||
+ `Connections: ${topology.getIn(['stats', 'node_count'])}`;
|
||||
}
|
||||
|
||||
renderTopology(topology) {
|
||||
const isActive = topology === this.props.currentTopology;
|
||||
const className = isActive ? 'topologies-item-main topologies-item-main-active' : 'topologies-item-main';
|
||||
const className = isActive
|
||||
? 'topologies-item-main topologies-item-main-active' : 'topologies-item-main';
|
||||
const topologyId = topology.get('id');
|
||||
const title = this.renderTitle(topology);
|
||||
|
||||
@@ -50,7 +52,8 @@ export default class Topologies extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
<div className="topologies-sub">
|
||||
{topology.has('sub_topologies') && topology.get('sub_topologies').map(this.renderSubTopology)}
|
||||
{topology.has('sub_topologies')
|
||||
&& topology.get('sub_topologies').map(this.renderSubTopology)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -27,7 +27,7 @@ export default class TopologyOptions extends React.Component {
|
||||
activeValue = activeOptions.get(option);
|
||||
} else {
|
||||
// get default value
|
||||
items.forEach(function(item) {
|
||||
items.forEach(item => {
|
||||
if (item.get('default')) {
|
||||
activeValue = item.get('value');
|
||||
}
|
||||
@@ -35,7 +35,7 @@ export default class TopologyOptions extends React.Component {
|
||||
}
|
||||
|
||||
// render active option as text, add other options as actions
|
||||
items.forEach(function(item) {
|
||||
items.forEach(item => {
|
||||
if (item.get('value') === activeValue) {
|
||||
activeText = item.get('display');
|
||||
} else {
|
||||
@@ -62,9 +62,7 @@ export default class TopologyOptions extends React.Component {
|
||||
|
||||
return (
|
||||
<div className="topology-options">
|
||||
{options.toIndexedSeq().map(function(items) {
|
||||
return this.renderOption(items);
|
||||
}, this)}
|
||||
{options.toIndexedSeq().map(items => this.renderOption(items))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,11 +5,9 @@ const log = debug('scope:dispatcher');
|
||||
|
||||
const instance = new Dispatcher();
|
||||
|
||||
instance.dispatch = _.wrap(Dispatcher.prototype.dispatch, function(func) {
|
||||
const args = Array.prototype.slice.call(arguments, 1);
|
||||
const type = args[0] && args[0].type;
|
||||
log(type, args[0]);
|
||||
func.apply(this, args);
|
||||
instance.dispatch = _.wrap(Dispatcher.prototype.dispatch, (func, payload) => {
|
||||
log(payload.type, payload);
|
||||
func.call(instance, payload);
|
||||
});
|
||||
|
||||
export default instance;
|
||||
|
||||
@@ -148,7 +148,8 @@ export default ComposedComponent => class extends React.Component {
|
||||
.filter(dateFilter);
|
||||
|
||||
const lastValue = samples.length > 0 ? samples[samples.length - 1].value : null;
|
||||
const slidingWindow = {first: movingFirstDate, last: movingLastDate, max, samples, value: lastValue};
|
||||
const slidingWindow = {first: movingFirstDate,
|
||||
last: movingLastDate, max, samples, value: lastValue};
|
||||
|
||||
return <ComposedComponent {...this.props} {...slidingWindow} />;
|
||||
}
|
||||
|
||||
@@ -4,9 +4,8 @@ jest.dontMock('../app-store');
|
||||
|
||||
// Appstore test suite using Jasmine matchers
|
||||
|
||||
describe('AppStore', function() {
|
||||
describe('AppStore', () => {
|
||||
const ActionTypes = require('../../constants/action-types').default;
|
||||
let AppDispatcher;
|
||||
let AppStore;
|
||||
let registeredCallback;
|
||||
|
||||
@@ -150,22 +149,22 @@ describe('AppStore', function() {
|
||||
state: {}
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
beforeEach(() => {
|
||||
AppStore = require('../app-store').default;
|
||||
AppDispatcher = AppStore.getDispatcher();
|
||||
const AppDispatcher = AppStore.getDispatcher();
|
||||
const callback = AppDispatcher.dispatch.bind(AppDispatcher);
|
||||
registeredCallback = callback;
|
||||
});
|
||||
|
||||
// topology tests
|
||||
|
||||
it('init with no topologies', function() {
|
||||
it('init with no topologies', () => {
|
||||
const topos = AppStore.getTopologies();
|
||||
expect(topos.size).toBe(0);
|
||||
expect(AppStore.getCurrentTopology()).toBeUndefined();
|
||||
});
|
||||
|
||||
it('get current topology', function() {
|
||||
it('get current topology', () => {
|
||||
registeredCallback(ClickTopologyAction);
|
||||
registeredCallback(ReceiveTopologiesAction);
|
||||
|
||||
@@ -175,7 +174,7 @@ describe('AppStore', function() {
|
||||
expect(AppStore.getCurrentTopologyOptions().get('option1')).toBeDefined();
|
||||
});
|
||||
|
||||
it('get sub-topology', function() {
|
||||
it('get sub-topology', () => {
|
||||
registeredCallback(ReceiveTopologiesAction);
|
||||
registeredCallback(ClickSubTopologyAction);
|
||||
|
||||
@@ -187,7 +186,7 @@ describe('AppStore', function() {
|
||||
|
||||
// topology options
|
||||
|
||||
it('changes topology option', function() {
|
||||
it('changes topology option', () => {
|
||||
// default options
|
||||
registeredCallback(ReceiveTopologiesAction);
|
||||
registeredCallback(ClickTopologyAction);
|
||||
@@ -211,11 +210,11 @@ describe('AppStore', function() {
|
||||
expect(AppStore.getAppState().topologyOptions.topo1.option1).toBe('off');
|
||||
});
|
||||
|
||||
it('sets topology options from route', function() {
|
||||
it('sets topology options from route', () => {
|
||||
RouteAction.state = {
|
||||
'topologyId': 'topo1',
|
||||
'selectedNodeId': null,
|
||||
'topologyOptions': {'topo1': {'option1': 'on'}}};
|
||||
topologyId: 'topo1',
|
||||
selectedNodeId: null,
|
||||
topologyOptions: {topo1: {option1: 'on'}}};
|
||||
registeredCallback(RouteAction);
|
||||
expect(AppStore.getActiveTopologyOptions().get('option1')).toBe('on');
|
||||
expect(AppStore.getAppState().topologyOptions.topo1.option1).toBe('on');
|
||||
@@ -227,11 +226,11 @@ describe('AppStore', function() {
|
||||
expect(AppStore.getAppState().topologyOptions.topo1.option1).toBe('on');
|
||||
});
|
||||
|
||||
it('uses default topology options from route', function() {
|
||||
it('uses default topology options from route', () => {
|
||||
RouteAction.state = {
|
||||
'topologyId': 'topo1',
|
||||
'selectedNodeId': null,
|
||||
'topologyOptions': null};
|
||||
topologyId: 'topo1',
|
||||
selectedNodeId: null,
|
||||
topologyOptions: null};
|
||||
registeredCallback(RouteAction);
|
||||
registeredCallback(ReceiveTopologiesAction);
|
||||
registeredCallback(ClickTopologyAction);
|
||||
@@ -241,7 +240,7 @@ describe('AppStore', function() {
|
||||
|
||||
// nodes delta
|
||||
|
||||
it('replaces adjacency on update', function() {
|
||||
it('replaces adjacency on update', () => {
|
||||
registeredCallback(ReceiveNodesDeltaAction);
|
||||
expect(AppStore.getNodes().toJS().n1.adjacency).toEqual(['n1', 'n2']);
|
||||
registeredCallback(ReceiveNodesDeltaUpdateAction);
|
||||
@@ -250,18 +249,18 @@ describe('AppStore', function() {
|
||||
|
||||
// browsing
|
||||
|
||||
it('shows nodes that were received', function() {
|
||||
it('shows nodes that were received', () => {
|
||||
registeredCallback(ReceiveNodesDeltaAction);
|
||||
expect(AppStore.getNodes().toJS()).toEqual(NODE_SET);
|
||||
});
|
||||
|
||||
it('knows a route was set', function() {
|
||||
it('knows a route was set', () => {
|
||||
expect(AppStore.isRouteSet()).toBeFalsy();
|
||||
registeredCallback(RouteAction);
|
||||
expect(AppStore.isRouteSet()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('gets selected node after click', function() {
|
||||
it('gets selected node after click', () => {
|
||||
registeredCallback(ReceiveNodesDeltaAction);
|
||||
|
||||
registeredCallback(ClickNodeAction);
|
||||
@@ -273,7 +272,7 @@ describe('AppStore', function() {
|
||||
expect(AppStore.getNodes().toJS()).toEqual(NODE_SET);
|
||||
});
|
||||
|
||||
it('keeps showing nodes on navigating back after node click', function() {
|
||||
it('keeps showing nodes on navigating back after node click', () => {
|
||||
registeredCallback(ReceiveTopologiesAction);
|
||||
registeredCallback(ClickTopologyAction);
|
||||
registeredCallback(ReceiveNodesDeltaAction);
|
||||
@@ -284,13 +283,13 @@ describe('AppStore', function() {
|
||||
expect(AppStore.getAppState().selectedNodeId).toEqual('n1');
|
||||
|
||||
// go back in browsing
|
||||
RouteAction.state = {'topologyId': 'topo1', 'selectedNodeId': null};
|
||||
RouteAction.state = {topologyId: 'topo1', selectedNodeId: null};
|
||||
registeredCallback(RouteAction);
|
||||
expect(AppStore.getSelectedNodeId()).toBe(null);
|
||||
expect(AppStore.getNodes().toJS()).toEqual(NODE_SET);
|
||||
});
|
||||
|
||||
it('closes details when changing topologies', function() {
|
||||
it('closes details when changing topologies', () => {
|
||||
registeredCallback(ReceiveTopologiesAction);
|
||||
registeredCallback(ClickTopologyAction);
|
||||
registeredCallback(ReceiveNodesDeltaAction);
|
||||
@@ -309,7 +308,7 @@ describe('AppStore', function() {
|
||||
|
||||
// connection errors
|
||||
|
||||
it('resets topology on websocket reconnect', function() {
|
||||
it('resets topology on websocket reconnect', () => {
|
||||
registeredCallback(ReceiveNodesDeltaAction);
|
||||
expect(AppStore.getNodes().toJS()).toEqual(NODE_SET);
|
||||
|
||||
@@ -326,7 +325,7 @@ describe('AppStore', function() {
|
||||
|
||||
// adjacency test
|
||||
|
||||
it('returns the correct adjacency set for a node', function() {
|
||||
it('returns the correct adjacency set for a node', () => {
|
||||
registeredCallback(ReceiveNodesDeltaAction);
|
||||
expect(AppStore.getAdjacentNodes().size).toEqual(0);
|
||||
|
||||
@@ -341,7 +340,7 @@ describe('AppStore', function() {
|
||||
|
||||
// empty topology
|
||||
|
||||
it('detects that the topology is empty', function() {
|
||||
it('detects that the topology is empty', () => {
|
||||
registeredCallback(ReceiveTopologiesAction);
|
||||
registeredCallback(ClickTopologyAction);
|
||||
expect(AppStore.isTopologyEmpty()).toBeFalsy();
|
||||
@@ -355,7 +354,7 @@ describe('AppStore', function() {
|
||||
|
||||
// selection of relatives
|
||||
|
||||
it('keeps relatives as a stack', function() {
|
||||
it('keeps relatives as a stack', () => {
|
||||
registeredCallback(ClickNodeAction);
|
||||
expect(AppStore.getSelectedNodeId()).toBe('n1');
|
||||
expect(AppStore.getNodeDetails().size).toEqual(1);
|
||||
@@ -377,7 +376,7 @@ describe('AppStore', function() {
|
||||
expect(AppStore.getNodeDetails().has('rel1')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('keeps clears stack when sibling is clicked', function() {
|
||||
it('keeps clears stack when sibling is clicked', () => {
|
||||
registeredCallback(ClickNodeAction);
|
||||
expect(AppStore.getSelectedNodeId()).toBe('n1');
|
||||
expect(AppStore.getNodeDetails().size).toEqual(1);
|
||||
@@ -400,7 +399,7 @@ describe('AppStore', function() {
|
||||
expect(AppStore.getNodeDetails().has('rel1')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('selectes relatives topology while keeping node selected', function() {
|
||||
it('selectes relatives topology while keeping node selected', () => {
|
||||
registeredCallback(ClickTopologyAction);
|
||||
registeredCallback(ReceiveTopologiesAction);
|
||||
expect(AppStore.getCurrentTopology().get('name')).toBe('Topo1');
|
||||
|
||||
@@ -62,12 +62,12 @@ const topologySorter = topology => topology.get('rank');
|
||||
// map for easy lookup
|
||||
function processTopologies(nextTopologies) {
|
||||
// add IDs to topology objects in-place
|
||||
updateTopologyIds(nextTopologies);
|
||||
const topologiesWithId = updateTopologyIds(nextTopologies);
|
||||
|
||||
// cache URLs by ID
|
||||
topologyUrlsById = setTopologyUrlsById(topologyUrlsById, nextTopologies);
|
||||
topologyUrlsById = setTopologyUrlsById(topologyUrlsById, topologiesWithId);
|
||||
|
||||
const immNextTopologies = fromJS(nextTopologies).sortBy(topologySorter);
|
||||
const immNextTopologies = fromJS(topologiesWithId).sortBy(topologySorter);
|
||||
topologies = topologies.mergeDeep(immNextTopologies);
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ function setDefaultTopologyOptions(topologyList) {
|
||||
topologyList.forEach(topology => {
|
||||
let defaultOptions = makeOrderedMap();
|
||||
if (topology.has('options')) {
|
||||
topology.get('options').forEach(function(items, option) {
|
||||
topology.get('options').forEach((items, option) => {
|
||||
items.forEach(item => {
|
||||
if (item.get('default') === true) {
|
||||
defaultOptions = defaultOptions.set(option, item.get('value'));
|
||||
@@ -106,9 +106,7 @@ function closeNodeDetails(nodeId) {
|
||||
if (nodeDetails.size > 0) {
|
||||
const popNodeId = nodeId || nodeDetails.keySeq().last();
|
||||
// remove pipe if it belongs to the node being closed
|
||||
controlPipes = controlPipes.filter(pipe => {
|
||||
return pipe.get('nodeId') !== popNodeId;
|
||||
});
|
||||
controlPipes = controlPipes.filter(pipe => pipe.get('nodeId') !== popNodeId);
|
||||
nodeDetails = nodeDetails.delete(popNodeId);
|
||||
}
|
||||
if (nodeDetails.size === 0 || selectedNodeId === nodeId) {
|
||||
@@ -135,7 +133,7 @@ export class AppStore extends Store {
|
||||
return {
|
||||
controlPipe: this.getControlPipe(),
|
||||
nodeDetails: this.getNodeDetailsState(),
|
||||
selectedNodeId: selectedNodeId,
|
||||
selectedNodeId,
|
||||
topologyId: currentTopologyId,
|
||||
topologyOptions: topologyOptions.toJS() // all options
|
||||
};
|
||||
@@ -152,7 +150,7 @@ export class AppStore extends Store {
|
||||
if (nodes.has(nodeId)) {
|
||||
adjacentNodes = makeSet(nodes.get(nodeId).get('adjacency'));
|
||||
// fill up set with reverse edges
|
||||
nodes.forEach(function(node, id) {
|
||||
nodes.forEach((node, id) => {
|
||||
if (node.get('adjacency') && node.get('adjacency').includes(nodeId)) {
|
||||
adjacentNodes = adjacentNodes.add(id);
|
||||
}
|
||||
@@ -200,12 +198,10 @@ export class AppStore extends Store {
|
||||
const adjacency = nodes.get(mouseOverNodeId).get('adjacency');
|
||||
if (adjacency) {
|
||||
return _.flatten(
|
||||
adjacency.forEach(function(nodeId) {
|
||||
return [
|
||||
[nodeId, mouseOverNodeId].join(EDGE_ID_SEPARATOR),
|
||||
[mouseOverNodeId, nodeId].join(EDGE_ID_SEPARATOR)
|
||||
];
|
||||
})
|
||||
adjacency.map((nodeId) => [
|
||||
[nodeId, mouseOverNodeId].join(EDGE_ID_SEPARATOR),
|
||||
[mouseOverNodeId, nodeId].join(EDGE_ID_SEPARATOR)
|
||||
]).toJS()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -235,9 +231,9 @@ export class AppStore extends Store {
|
||||
}
|
||||
|
||||
getNodeDetailsState() {
|
||||
return nodeDetails.toIndexedSeq().map(details => {
|
||||
return {id: details.id, label: details.label, topologyId: details.topologyId};
|
||||
}).toJS();
|
||||
return nodeDetails.toIndexedSeq().map(details => ({
|
||||
id: details.id, label: details.label, topologyId: details.topologyId
|
||||
})).toJS();
|
||||
}
|
||||
|
||||
getTopCardNodeId() {
|
||||
@@ -281,7 +277,8 @@ export class AppStore extends Store {
|
||||
}
|
||||
|
||||
isTopologyEmpty() {
|
||||
return currentTopology && currentTopology.get('stats') && currentTopology.get('stats').get('node_count') === 0 && nodes.size === 0;
|
||||
return currentTopology && currentTopology.get('stats')
|
||||
&& currentTopology.get('stats').get('node_count') === 0 && nodes.size === 0;
|
||||
}
|
||||
|
||||
isUpdatePaused() {
|
||||
@@ -298,327 +295,329 @@ export class AppStore extends Store {
|
||||
}
|
||||
|
||||
switch (payload.type) {
|
||||
case ActionTypes.CHANGE_TOPOLOGY_OPTION:
|
||||
resumeUpdate();
|
||||
if (topologyOptions.getIn([payload.topologyId, payload.option])
|
||||
!== payload.value) {
|
||||
nodes = nodes.clear();
|
||||
}
|
||||
topologyOptions = topologyOptions.setIn(
|
||||
[payload.topologyId, payload.option],
|
||||
payload.value
|
||||
);
|
||||
this.__emitChange();
|
||||
break;
|
||||
|
||||
case ActionTypes.CLEAR_CONTROL_ERROR:
|
||||
controlStatus = controlStatus.removeIn([payload.nodeId, 'error']);
|
||||
this.__emitChange();
|
||||
break;
|
||||
|
||||
case ActionTypes.CLICK_BACKGROUND:
|
||||
closeAllNodeDetails();
|
||||
this.__emitChange();
|
||||
break;
|
||||
|
||||
case ActionTypes.CLICK_CLOSE_DETAILS:
|
||||
closeNodeDetails(payload.nodeId);
|
||||
this.__emitChange();
|
||||
break;
|
||||
|
||||
case ActionTypes.CLICK_CLOSE_TERMINAL:
|
||||
controlPipes = controlPipes.clear();
|
||||
this.__emitChange();
|
||||
break;
|
||||
|
||||
case ActionTypes.CLICK_FORCE_RELAYOUT:
|
||||
forceRelayout = true;
|
||||
// fire only once, reset after emitChange
|
||||
setTimeout(() => {
|
||||
forceRelayout = false;
|
||||
}, 0);
|
||||
this.__emitChange();
|
||||
break;
|
||||
|
||||
case ActionTypes.CLICK_NODE:
|
||||
const prevSelectedNodeId = selectedNodeId;
|
||||
const prevDetailsStackSize = nodeDetails.size;
|
||||
// click on sibling closes all
|
||||
closeAllNodeDetails();
|
||||
// select new node if it's not the same (in that case just delesect)
|
||||
if (prevDetailsStackSize > 1 || prevSelectedNodeId !== payload.nodeId) {
|
||||
// dont set origin if a node was already selected, suppresses animation
|
||||
const origin = prevSelectedNodeId === null ? payload.origin : null;
|
||||
nodeDetails = nodeDetails.set(
|
||||
payload.nodeId,
|
||||
{
|
||||
id: payload.nodeId,
|
||||
label: payload.label,
|
||||
origin,
|
||||
topologyId: currentTopologyId
|
||||
}
|
||||
case ActionTypes.CHANGE_TOPOLOGY_OPTION: {
|
||||
resumeUpdate();
|
||||
if (topologyOptions.getIn([payload.topologyId, payload.option])
|
||||
!== payload.value) {
|
||||
nodes = nodes.clear();
|
||||
}
|
||||
topologyOptions = topologyOptions.setIn(
|
||||
[payload.topologyId, payload.option],
|
||||
payload.value
|
||||
);
|
||||
selectedNodeId = payload.nodeId;
|
||||
}
|
||||
this.__emitChange();
|
||||
break;
|
||||
|
||||
case ActionTypes.CLICK_PAUSE_UPDATE:
|
||||
updatePausedAt = new Date;
|
||||
this.__emitChange();
|
||||
break;
|
||||
|
||||
case ActionTypes.CLICK_RELATIVE:
|
||||
if (nodeDetails.has(payload.nodeId)) {
|
||||
// bring to front
|
||||
const details = nodeDetails.get(payload.nodeId);
|
||||
nodeDetails = nodeDetails.delete(payload.nodeId);
|
||||
nodeDetails = nodeDetails.set(payload.nodeId, details);
|
||||
} else {
|
||||
nodeDetails = nodeDetails.set(
|
||||
payload.nodeId,
|
||||
{
|
||||
id: payload.nodeId,
|
||||
label: payload.label,
|
||||
origin: payload.origin,
|
||||
topologyId: payload.topologyId
|
||||
}
|
||||
);
|
||||
}
|
||||
this.__emitChange();
|
||||
break;
|
||||
|
||||
case ActionTypes.CLICK_RESUME_UPDATE:
|
||||
resumeUpdate();
|
||||
this.__emitChange();
|
||||
break;
|
||||
|
||||
case ActionTypes.CLICK_SHOW_TOPOLOGY_FOR_NODE:
|
||||
resumeUpdate();
|
||||
nodeDetails = nodeDetails.filter((v, k) => k === payload.nodeId);
|
||||
controlPipes = controlPipes.clear();
|
||||
selectedNodeId = payload.nodeId;
|
||||
if (payload.topologyId !== currentTopologyId) {
|
||||
setTopology(payload.topologyId);
|
||||
nodes = nodes.clear();
|
||||
}
|
||||
this.__emitChange();
|
||||
break;
|
||||
|
||||
case ActionTypes.CLICK_TOPOLOGY:
|
||||
resumeUpdate();
|
||||
closeAllNodeDetails();
|
||||
if (payload.topologyId !== currentTopologyId) {
|
||||
setTopology(payload.topologyId);
|
||||
nodes = nodes.clear();
|
||||
}
|
||||
this.__emitChange();
|
||||
break;
|
||||
|
||||
case ActionTypes.CLOSE_WEBSOCKET:
|
||||
if (!websocketClosed) {
|
||||
websocketClosed = true;
|
||||
this.__emitChange();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case ActionTypes.DESELECT_NODE:
|
||||
closeNodeDetails();
|
||||
this.__emitChange();
|
||||
break;
|
||||
|
||||
case ActionTypes.DO_CONTROL:
|
||||
controlStatus = controlStatus.set(payload.nodeId, makeMap({
|
||||
pending: true,
|
||||
error: null
|
||||
}));
|
||||
this.__emitChange();
|
||||
break;
|
||||
|
||||
case ActionTypes.ENTER_EDGE:
|
||||
mouseOverEdgeId = payload.edgeId;
|
||||
this.__emitChange();
|
||||
break;
|
||||
|
||||
case ActionTypes.ENTER_NODE:
|
||||
mouseOverNodeId = payload.nodeId;
|
||||
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:
|
||||
controlStatus = controlStatus.set(payload.nodeId, makeMap({
|
||||
pending: false,
|
||||
error: payload.error
|
||||
}));
|
||||
this.__emitChange();
|
||||
break;
|
||||
|
||||
case ActionTypes.DO_CONTROL_SUCCESS:
|
||||
controlStatus = controlStatus.set(payload.nodeId, makeMap({
|
||||
pending: false,
|
||||
error: null
|
||||
}));
|
||||
this.__emitChange();
|
||||
break;
|
||||
|
||||
case ActionTypes.RECEIVE_CONTROL_PIPE:
|
||||
controlPipes = controlPipes.set(payload.pipeId, makeOrderedMap({
|
||||
id: payload.pipeId,
|
||||
nodeId: payload.nodeId,
|
||||
raw: payload.rawTty
|
||||
}));
|
||||
this.__emitChange();
|
||||
break;
|
||||
|
||||
case ActionTypes.RECEIVE_CONTROL_PIPE_STATUS:
|
||||
if (controlPipes.has(payload.pipeId)) {
|
||||
controlPipes = controlPipes.setIn([payload.pipeId, 'status'], payload.status);
|
||||
case ActionTypes.CLEAR_CONTROL_ERROR: {
|
||||
controlStatus = controlStatus.removeIn([payload.nodeId, 'error']);
|
||||
this.__emitChange();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case ActionTypes.RECEIVE_ERROR:
|
||||
if (errorUrl !== null) {
|
||||
errorUrl = payload.errorUrl;
|
||||
case ActionTypes.CLICK_BACKGROUND: {
|
||||
closeAllNodeDetails();
|
||||
this.__emitChange();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case ActionTypes.RECEIVE_NODE_DETAILS:
|
||||
errorUrl = null;
|
||||
|
||||
// disregard if node is not selected anymore
|
||||
if (nodeDetails.has(payload.details.id)) {
|
||||
nodeDetails = nodeDetails.update(payload.details.id, obj => {
|
||||
obj.notFound = false;
|
||||
obj.details = payload.details;
|
||||
return obj;
|
||||
});
|
||||
}
|
||||
this.__emitChange();
|
||||
break;
|
||||
|
||||
case ActionTypes.RECEIVE_NODES_DELTA:
|
||||
const emptyMessage = !payload.delta.add && !payload.delta.remove
|
||||
&& !payload.delta.update;
|
||||
// this action is called frequently, good to check if something changed
|
||||
const emitChange = !emptyMessage || errorUrl !== null;
|
||||
|
||||
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) && _.includes(mouseOverEdgeId, nodeId)) {
|
||||
mouseOverEdgeId = null;
|
||||
}
|
||||
nodes = nodes.delete(nodeId);
|
||||
});
|
||||
|
||||
// update existing nodes
|
||||
_.each(payload.delta.update, function(node) {
|
||||
if (nodes.has(node.id)) {
|
||||
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, fromJS(makeNode(node)));
|
||||
});
|
||||
|
||||
if (emitChange) {
|
||||
case ActionTypes.CLICK_CLOSE_DETAILS: {
|
||||
closeNodeDetails(payload.nodeId);
|
||||
this.__emitChange();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case ActionTypes.RECEIVE_NOT_FOUND:
|
||||
if (nodeDetails.has(payload.nodeId)) {
|
||||
nodeDetails = nodeDetails.update(payload.nodeId, obj => {
|
||||
obj.notFound = true;
|
||||
return obj;
|
||||
});
|
||||
this.__emitChange();
|
||||
}
|
||||
break;
|
||||
|
||||
case ActionTypes.RECEIVE_TOPOLOGIES:
|
||||
errorUrl = null;
|
||||
topologyUrlsById = topologyUrlsById.clear();
|
||||
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;
|
||||
hostname = payload.hostname;
|
||||
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;
|
||||
if (payload.state.controlPipe) {
|
||||
controlPipes = makeOrderedMap({
|
||||
[payload.state.controlPipe.id]:
|
||||
makeOrderedMap(payload.state.controlPipe)
|
||||
});
|
||||
} else {
|
||||
case ActionTypes.CLICK_CLOSE_TERMINAL: {
|
||||
controlPipes = controlPipes.clear();
|
||||
this.__emitChange();
|
||||
break;
|
||||
}
|
||||
if (payload.state.nodeDetails) {
|
||||
nodeDetails = makeOrderedMap(payload.state.nodeDetails.map(obj => [obj.id, obj]));
|
||||
} else {
|
||||
nodeDetails = nodeDetails.clear();
|
||||
case ActionTypes.CLICK_FORCE_RELAYOUT: {
|
||||
forceRelayout = true;
|
||||
// fire only once, reset after emitChange
|
||||
setTimeout(() => {
|
||||
forceRelayout = false;
|
||||
}, 0);
|
||||
this.__emitChange();
|
||||
break;
|
||||
}
|
||||
topologyOptions = fromJS(payload.state.topologyOptions)
|
||||
|| topologyOptions;
|
||||
this.__emitChange();
|
||||
break;
|
||||
case ActionTypes.CLICK_NODE: {
|
||||
const prevSelectedNodeId = selectedNodeId;
|
||||
const prevDetailsStackSize = nodeDetails.size;
|
||||
// click on sibling closes all
|
||||
closeAllNodeDetails();
|
||||
// select new node if it's not the same (in that case just delesect)
|
||||
if (prevDetailsStackSize > 1 || prevSelectedNodeId !== payload.nodeId) {
|
||||
// dont set origin if a node was already selected, suppresses animation
|
||||
const origin = prevSelectedNodeId === null ? payload.origin : null;
|
||||
nodeDetails = nodeDetails.set(
|
||||
payload.nodeId,
|
||||
{
|
||||
id: payload.nodeId,
|
||||
label: payload.label,
|
||||
origin,
|
||||
topologyId: currentTopologyId
|
||||
}
|
||||
);
|
||||
selectedNodeId = payload.nodeId;
|
||||
}
|
||||
this.__emitChange();
|
||||
break;
|
||||
}
|
||||
case ActionTypes.CLICK_PAUSE_UPDATE: {
|
||||
updatePausedAt = new Date;
|
||||
this.__emitChange();
|
||||
break;
|
||||
}
|
||||
case ActionTypes.CLICK_RELATIVE: {
|
||||
if (nodeDetails.has(payload.nodeId)) {
|
||||
// bring to front
|
||||
const details = nodeDetails.get(payload.nodeId);
|
||||
nodeDetails = nodeDetails.delete(payload.nodeId);
|
||||
nodeDetails = nodeDetails.set(payload.nodeId, details);
|
||||
} else {
|
||||
nodeDetails = nodeDetails.set(
|
||||
payload.nodeId,
|
||||
{
|
||||
id: payload.nodeId,
|
||||
label: payload.label,
|
||||
origin: payload.origin,
|
||||
topologyId: payload.topologyId
|
||||
}
|
||||
);
|
||||
}
|
||||
this.__emitChange();
|
||||
break;
|
||||
}
|
||||
case ActionTypes.CLICK_RESUME_UPDATE: {
|
||||
resumeUpdate();
|
||||
this.__emitChange();
|
||||
break;
|
||||
}
|
||||
case ActionTypes.CLICK_SHOW_TOPOLOGY_FOR_NODE: {
|
||||
resumeUpdate();
|
||||
nodeDetails = nodeDetails.filter((v, k) => k === payload.nodeId);
|
||||
controlPipes = controlPipes.clear();
|
||||
selectedNodeId = payload.nodeId;
|
||||
if (payload.topologyId !== currentTopologyId) {
|
||||
setTopology(payload.topologyId);
|
||||
nodes = nodes.clear();
|
||||
}
|
||||
this.__emitChange();
|
||||
break;
|
||||
}
|
||||
case ActionTypes.CLICK_TOPOLOGY: {
|
||||
resumeUpdate();
|
||||
closeAllNodeDetails();
|
||||
if (payload.topologyId !== currentTopologyId) {
|
||||
setTopology(payload.topologyId);
|
||||
nodes = nodes.clear();
|
||||
}
|
||||
this.__emitChange();
|
||||
break;
|
||||
}
|
||||
case ActionTypes.CLOSE_WEBSOCKET: {
|
||||
if (!websocketClosed) {
|
||||
websocketClosed = true;
|
||||
this.__emitChange();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ActionTypes.DESELECT_NODE: {
|
||||
closeNodeDetails();
|
||||
this.__emitChange();
|
||||
break;
|
||||
}
|
||||
case ActionTypes.DO_CONTROL: {
|
||||
controlStatus = controlStatus.set(payload.nodeId, makeMap({
|
||||
pending: true,
|
||||
error: null
|
||||
}));
|
||||
this.__emitChange();
|
||||
break;
|
||||
}
|
||||
case ActionTypes.ENTER_EDGE: {
|
||||
mouseOverEdgeId = payload.edgeId;
|
||||
this.__emitChange();
|
||||
break;
|
||||
}
|
||||
case ActionTypes.ENTER_NODE: {
|
||||
mouseOverNodeId = payload.nodeId;
|
||||
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;
|
||||
|
||||
default:
|
||||
break;
|
||||
this.__emitChange();
|
||||
break;
|
||||
}
|
||||
case ActionTypes.DO_CONTROL_ERROR: {
|
||||
controlStatus = controlStatus.set(payload.nodeId, makeMap({
|
||||
pending: false,
|
||||
error: payload.error
|
||||
}));
|
||||
this.__emitChange();
|
||||
break;
|
||||
}
|
||||
case ActionTypes.DO_CONTROL_SUCCESS: {
|
||||
controlStatus = controlStatus.set(payload.nodeId, makeMap({
|
||||
pending: false,
|
||||
error: null
|
||||
}));
|
||||
this.__emitChange();
|
||||
break;
|
||||
}
|
||||
case ActionTypes.RECEIVE_CONTROL_PIPE: {
|
||||
controlPipes = controlPipes.set(payload.pipeId, makeOrderedMap({
|
||||
id: payload.pipeId,
|
||||
nodeId: payload.nodeId,
|
||||
raw: payload.rawTty
|
||||
}));
|
||||
this.__emitChange();
|
||||
break;
|
||||
}
|
||||
case ActionTypes.RECEIVE_CONTROL_PIPE_STATUS: {
|
||||
if (controlPipes.has(payload.pipeId)) {
|
||||
controlPipes = controlPipes.setIn([payload.pipeId, 'status'], payload.status);
|
||||
this.__emitChange();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ActionTypes.RECEIVE_ERROR: {
|
||||
if (errorUrl !== null) {
|
||||
errorUrl = payload.errorUrl;
|
||||
this.__emitChange();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ActionTypes.RECEIVE_NODE_DETAILS: {
|
||||
errorUrl = null;
|
||||
|
||||
// disregard if node is not selected anymore
|
||||
if (nodeDetails.has(payload.details.id)) {
|
||||
nodeDetails = nodeDetails.update(payload.details.id, obj => {
|
||||
const result = Object.assign({}, obj);
|
||||
result.notFound = false;
|
||||
result.details = payload.details;
|
||||
return result;
|
||||
});
|
||||
}
|
||||
this.__emitChange();
|
||||
break;
|
||||
}
|
||||
case ActionTypes.RECEIVE_NODES_DELTA: {
|
||||
const emptyMessage = !payload.delta.add && !payload.delta.remove
|
||||
&& !payload.delta.update;
|
||||
// this action is called frequently, good to check if something changed
|
||||
const emitChange = !emptyMessage || errorUrl !== null;
|
||||
|
||||
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, (nodeId) => {
|
||||
// in case node disappears before mouseleave event
|
||||
if (mouseOverNodeId === nodeId) {
|
||||
mouseOverNodeId = null;
|
||||
}
|
||||
if (nodes.has(nodeId) && _.includes(mouseOverEdgeId, nodeId)) {
|
||||
mouseOverEdgeId = null;
|
||||
}
|
||||
nodes = nodes.delete(nodeId);
|
||||
});
|
||||
|
||||
// update existing nodes
|
||||
_.each(payload.delta.update, (node) => {
|
||||
if (nodes.has(node.id)) {
|
||||
nodes = nodes.set(node.id, nodes.get(node.id).merge(makeNode(node)));
|
||||
}
|
||||
});
|
||||
|
||||
// add new nodes
|
||||
_.each(payload.delta.add, (node) => {
|
||||
nodes = nodes.set(node.id, fromJS(makeNode(node)));
|
||||
});
|
||||
|
||||
if (emitChange) {
|
||||
this.__emitChange();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ActionTypes.RECEIVE_NOT_FOUND: {
|
||||
if (nodeDetails.has(payload.nodeId)) {
|
||||
nodeDetails = nodeDetails.update(payload.nodeId, obj => {
|
||||
const result = Object.assign({}, obj);
|
||||
result.notFound = true;
|
||||
return result;
|
||||
});
|
||||
this.__emitChange();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ActionTypes.RECEIVE_TOPOLOGIES: {
|
||||
errorUrl = null;
|
||||
topologyUrlsById = topologyUrlsById.clear();
|
||||
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;
|
||||
hostname = payload.hostname;
|
||||
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;
|
||||
if (payload.state.controlPipe) {
|
||||
controlPipes = makeOrderedMap({
|
||||
[payload.state.controlPipe.id]:
|
||||
makeOrderedMap(payload.state.controlPipe)
|
||||
});
|
||||
} else {
|
||||
controlPipes = controlPipes.clear();
|
||||
}
|
||||
if (payload.state.nodeDetails) {
|
||||
nodeDetails = makeOrderedMap(payload.state.nodeDetails.map(obj => [obj.id, obj]));
|
||||
} else {
|
||||
nodeDetails = nodeDetails.clear();
|
||||
}
|
||||
topologyOptions = fromJS(payload.state.topologyOptions)
|
||||
|| topologyOptions;
|
||||
this.__emitChange();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,4 +5,4 @@ import ReactDOM from 'react-dom';
|
||||
|
||||
import { TerminalApp } from './components/terminal-app.js';
|
||||
|
||||
ReactDOM.render(<TerminalApp/>, document.getElementById('app'));
|
||||
ReactDOM.render(<TerminalApp />, document.getElementById('app'));
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
jest.dontMock('../string-utils');
|
||||
|
||||
describe('StringUtils', function() {
|
||||
describe('StringUtils', () => {
|
||||
const StringUtils = require('../string-utils');
|
||||
|
||||
describe('formatMetric', function() {
|
||||
describe('formatMetric', () => {
|
||||
const formatMetric = StringUtils.formatMetric;
|
||||
|
||||
it('it should render 0', function() {
|
||||
it('it should render 0', () => {
|
||||
expect(formatMetric(0)).toBe('0.00');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
jest.dontMock('../web-api-utils');
|
||||
|
||||
describe('WebApiUtils', function() {
|
||||
describe('WebApiUtils', () => {
|
||||
const WebApiUtils = require('../web-api-utils');
|
||||
|
||||
describe('basePath', function() {
|
||||
describe('basePath', () => {
|
||||
const basePath = WebApiUtils.basePath;
|
||||
|
||||
it('should handle /scope/terminal.html', function() {
|
||||
it('should handle /scope/terminal.html', () => {
|
||||
expect(basePath('/scope/terminal.html')).toBe('/scope');
|
||||
});
|
||||
|
||||
it('should handle /scope/', function() {
|
||||
it('should handle /scope/', () => {
|
||||
expect(basePath('/scope/')).toBe('/scope');
|
||||
});
|
||||
|
||||
it('should handle /scope', function() {
|
||||
it('should handle /scope', () => {
|
||||
expect(basePath('/scope')).toBe('/scope');
|
||||
});
|
||||
|
||||
it('should handle /', function() {
|
||||
it('should handle /', () => {
|
||||
expect(basePath('/')).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,9 +15,8 @@ const letterRange = endLetterRange - startLetterRange;
|
||||
function text2degree(text) {
|
||||
const input = text.substr(0, 2).toUpperCase();
|
||||
let num = 0;
|
||||
let charCode;
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
charCode = Math.max(Math.min(input[i].charCodeAt(), endLetterRange), startLetterRange);
|
||||
const charCode = Math.max(Math.min(input[i].charCodeAt(), endLetterRange), startLetterRange);
|
||||
num += Math.pow(letterRange, input.length - i - 1) * (charCode - startLetterRange);
|
||||
}
|
||||
hueScale.domain([0, Math.pow(letterRange, input.length)]);
|
||||
|
||||
@@ -8,21 +8,20 @@ const prefix = {
|
||||
svg: 'http://www.w3.org/2000/svg'
|
||||
};
|
||||
const cssSkipValues = {
|
||||
'auto': true,
|
||||
auto: true,
|
||||
'0px 0px': true,
|
||||
'visible': true,
|
||||
'pointer': true
|
||||
visible: true,
|
||||
pointer: true
|
||||
};
|
||||
|
||||
function setInlineStyles(svg, target, emptySvgDeclarationComputed) {
|
||||
function explicitlySetStyle(element, targetEl) {
|
||||
const cSSStyleDeclarationComputed = getComputedStyle(element);
|
||||
let value;
|
||||
let computedStyleStr = '';
|
||||
_.each(cSSStyleDeclarationComputed, key => {
|
||||
value = cSSStyleDeclarationComputed.getPropertyValue(key);
|
||||
const value = cSSStyleDeclarationComputed.getPropertyValue(key);
|
||||
if (value !== emptySvgDeclarationComputed.getPropertyValue(key) && !cssSkipValues[value]) {
|
||||
computedStyleStr += key + ':' + value + ';';
|
||||
computedStyleStr += `${key}:${value};`;
|
||||
}
|
||||
});
|
||||
targetEl.setAttribute('style', computedStyleStr);
|
||||
@@ -70,23 +69,22 @@ function download(source, name) {
|
||||
if (name) {
|
||||
filename = name;
|
||||
} else if (window.document.title) {
|
||||
filename = window.document.title.replace(/[^a-z0-9]/gi, '-').toLowerCase()
|
||||
+ '-' + (+new Date);
|
||||
filename = `${window.document.title.replace(/[^a-z0-9]/gi, '-').toLowerCase()}-${(+new Date)}`;
|
||||
}
|
||||
|
||||
const url = window.URL.createObjectURL(new Blob(source,
|
||||
{'type': 'text\/xml'}
|
||||
{type: 'text\/xml'}
|
||||
));
|
||||
|
||||
const a = document.createElement('a');
|
||||
document.body.appendChild(a);
|
||||
a.setAttribute('class', 'svg-crowbar');
|
||||
a.setAttribute('download', filename + '.svg');
|
||||
a.setAttribute('download', `${filename}.svg`);
|
||||
a.setAttribute('href', url);
|
||||
a.style.display = 'none';
|
||||
a.click();
|
||||
|
||||
setTimeout(function() {
|
||||
setTimeout(() => {
|
||||
window.URL.revokeObjectURL(url);
|
||||
}, 10);
|
||||
}
|
||||
@@ -120,7 +118,7 @@ function getSVG(doc, emptySvgDeclarationComputed) {
|
||||
function cleanup() {
|
||||
const crowbarElements = document.querySelectorAll('.svg-crowbar');
|
||||
|
||||
[].forEach.call(crowbarElements, function(el) {
|
||||
[].forEach.call(crowbarElements, (el) => {
|
||||
el.parentNode.removeChild(el);
|
||||
});
|
||||
|
||||
|
||||
@@ -23,17 +23,17 @@ export function updateRoute() {
|
||||
|
||||
if (shouldReplaceState(prevState, state)) {
|
||||
// Replace the top of the history rather than pushing on a new item.
|
||||
page.replace('/state/' + stateUrl, state, dispatch);
|
||||
page.replace(`/state/${stateUrl}`, state, dispatch);
|
||||
} else {
|
||||
page.show('/state/' + stateUrl, state, dispatch);
|
||||
page.show(`/state/${stateUrl}`, state, dispatch);
|
||||
}
|
||||
}
|
||||
|
||||
page('/', function() {
|
||||
page('/', () => {
|
||||
updateRoute();
|
||||
});
|
||||
|
||||
page('/state/:state', function(ctx) {
|
||||
page('/state/:state', (ctx) => {
|
||||
const state = JSON.parse(ctx.params.state);
|
||||
route(state);
|
||||
});
|
||||
|
||||
@@ -10,34 +10,30 @@ export function findTopologyById(subTree, topologyId) {
|
||||
if (!foundTopology && topology.has('sub_topologies')) {
|
||||
foundTopology = findTopologyById(topology.get('sub_topologies'), topologyId);
|
||||
}
|
||||
if (foundTopology) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return foundTopology;
|
||||
}
|
||||
|
||||
|
||||
export function updateNodeDegrees(nodes, edges) {
|
||||
return nodes.map(node => {
|
||||
const nodeId = node.get('id');
|
||||
const degree = edges.count(edge => {
|
||||
return edge.get('source') === nodeId || edge.get('target') === nodeId;
|
||||
});
|
||||
const degree = edges.count(edge => edge.get('source') === nodeId
|
||||
|| edge.get('target') === nodeId);
|
||||
return node.set('degree', degree);
|
||||
});
|
||||
}
|
||||
|
||||
/* set topology.id in place on each topology */
|
||||
export function updateTopologyIds(topologies) {
|
||||
topologies.forEach(topology => {
|
||||
topology.id = topology.url.split('/').pop();
|
||||
return topologies.map(topology => {
|
||||
const result = Object.assign({}, topology);
|
||||
result.id = topology.url.split('/').pop();
|
||||
if (topology.sub_topologies) {
|
||||
updateTopologyIds(topology.sub_topologies);
|
||||
result.sub_topologies = updateTopologyIds(topology.sub_topologies);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
return topologies;
|
||||
}
|
||||
|
||||
// adds ID field to topology (based on last part of URL path) and save urls in
|
||||
|
||||
@@ -46,13 +46,14 @@ function consolidateBuffer() {
|
||||
let toAdd = _.union(first.add, second.add);
|
||||
let toUpdate = _.union(first.update, second.update);
|
||||
let toRemove = _.union(first.remove, second.remove);
|
||||
log('Consolidating delta buffer', 'add', _.size(toAdd), 'update', _.size(toUpdate), 'remove', _.size(toRemove));
|
||||
log('Consolidating delta buffer', 'add', _.size(toAdd), 'update',
|
||||
_.size(toUpdate), 'remove', _.size(toRemove));
|
||||
|
||||
// check if an added node in first was updated in second -> add second update
|
||||
toAdd = _.map(toAdd, node => {
|
||||
const updateNode = _.find(second.update, {'id': node.id});
|
||||
const updateNode = _.find(second.update, {id: node.id});
|
||||
if (updateNode) {
|
||||
toUpdate = _.reject(toUpdate, {'id': node.id});
|
||||
toUpdate = _.reject(toUpdate, {id: node.id});
|
||||
return updateNode;
|
||||
}
|
||||
return node;
|
||||
@@ -63,18 +64,18 @@ function consolidateBuffer() {
|
||||
|
||||
// check if an added node in first was removed in second -> dont add, dont remove
|
||||
_.each(first.add, node => {
|
||||
const removedNode = _.find(second.remove, {'id': node.id});
|
||||
const removedNode = _.find(second.remove, {id: node.id});
|
||||
if (removedNode) {
|
||||
toAdd = _.reject(toAdd, {'id': node.id});
|
||||
toRemove = _.reject(toRemove, {'id': node.id});
|
||||
toAdd = _.reject(toAdd, {id: node.id});
|
||||
toRemove = _.reject(toRemove, {id: node.id});
|
||||
}
|
||||
});
|
||||
|
||||
// check if an updated node in first was removed in second -> remove
|
||||
_.each(first.update, node => {
|
||||
const removedNode = _.find(second.remove, {'id': node.id});
|
||||
const removedNode = _.find(second.remove, {id: node.id});
|
||||
if (removedNode) {
|
||||
toUpdate = _.reject(toUpdate, {'id': node.id});
|
||||
toUpdate = _.reject(toUpdate, {id: node.id});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -82,7 +83,8 @@ function consolidateBuffer() {
|
||||
// remove -> add is fine for the store
|
||||
|
||||
// update buffer
|
||||
log('Consolidated delta buffer', 'add', _.size(toAdd), 'update', _.size(toUpdate), 'remove', _.size(toRemove));
|
||||
log('Consolidated delta buffer', 'add', _.size(toAdd), 'update',
|
||||
_.size(toUpdate), 'remove', _.size(toRemove));
|
||||
deltaBuffer.set(0, {
|
||||
add: toAdd.length > 0 ? toAdd : null,
|
||||
update: toUpdate.length > 0 ? toUpdate : null,
|
||||
|
||||
@@ -23,9 +23,7 @@ let controlErrorTimer = 0;
|
||||
|
||||
function buildOptionsQuery(options) {
|
||||
if (options) {
|
||||
return options.reduce(function(query, value, param) {
|
||||
return `${query}&${param}=${value}`;
|
||||
}, '');
|
||||
return options.reduce((query, value, param) => `${query}&${param}=${value}`, '');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
@@ -52,11 +50,11 @@ export function basePathSlash(urlPath) {
|
||||
// "/scope" -> "/scope/"
|
||||
// "/" -> "/"
|
||||
//
|
||||
return basePath(urlPath) + '/';
|
||||
return `${basePath(urlPath)}/`;
|
||||
}
|
||||
|
||||
const wsProto = location.protocol === 'https:' ? 'wss' : 'ws';
|
||||
const wsUrl = wsProto + '://' + location.host + basePath(location.pathname);
|
||||
const wsUrl = `${wsProto}://${location.host}${basePath(location.pathname)}`;
|
||||
|
||||
function createWebsocket(topologyUrl, optionsQuery) {
|
||||
if (socket) {
|
||||
@@ -67,30 +65,29 @@ function createWebsocket(topologyUrl, optionsQuery) {
|
||||
// right away
|
||||
}
|
||||
|
||||
socket = new WebSocket(wsUrl + topologyUrl
|
||||
+ '/ws?t=' + updateFrequency + '&' + optionsQuery);
|
||||
socket = new WebSocket(`${wsUrl}${topologyUrl}/ws?t=${updateFrequency}&${optionsQuery}`);
|
||||
|
||||
socket.onopen = function() {
|
||||
socket.onopen = () => {
|
||||
openWebsocket();
|
||||
};
|
||||
|
||||
socket.onclose = function() {
|
||||
socket.onclose = () => {
|
||||
clearTimeout(reconnectTimer);
|
||||
log('Closing websocket to ' + topologyUrl, socket.readyState);
|
||||
log(`Closing websocket to ${topologyUrl}`, socket.readyState);
|
||||
socket = null;
|
||||
closeWebsocket();
|
||||
|
||||
reconnectTimer = setTimeout(function() {
|
||||
reconnectTimer = setTimeout(() => {
|
||||
createWebsocket(topologyUrl, optionsQuery);
|
||||
}, reconnectTimerInterval);
|
||||
};
|
||||
|
||||
socket.onerror = function() {
|
||||
log('Error in websocket to ' + topologyUrl);
|
||||
socket.onerror = () => {
|
||||
log(`Error in websocket to ${topologyUrl}`);
|
||||
receiveError(currentUrl);
|
||||
};
|
||||
|
||||
socket.onmessage = function(event) {
|
||||
socket.onmessage = (event) => {
|
||||
const msg = JSON.parse(event.data);
|
||||
receiveNodesDelta(msg);
|
||||
};
|
||||
@@ -103,17 +100,17 @@ export function getTopologies(options) {
|
||||
const optionsQuery = buildOptionsQuery(options);
|
||||
const url = `api/topology?${optionsQuery}`;
|
||||
reqwest({
|
||||
url: url,
|
||||
success: function(res) {
|
||||
url,
|
||||
success: (res) => {
|
||||
receiveTopologies(res);
|
||||
topologyTimer = setTimeout(function() {
|
||||
topologyTimer = setTimeout(() => {
|
||||
getTopologies(options);
|
||||
}, TOPOLOGY_INTERVAL);
|
||||
},
|
||||
error: function(err) {
|
||||
log('Error in topology request: ' + err.responseText);
|
||||
error: (err) => {
|
||||
log(`Error in topology request: ${err.responseText}`);
|
||||
receiveError(url);
|
||||
topologyTimer = setTimeout(function() {
|
||||
topologyTimer = setTimeout(() => {
|
||||
getTopologies(options);
|
||||
}, TOPOLOGY_INTERVAL / 2);
|
||||
}
|
||||
@@ -139,15 +136,15 @@ export function getNodeDetails(topologyUrlsById, nodeMap) {
|
||||
const url = [topologyUrl, '/', encodeURIComponent(obj.id)]
|
||||
.join('').substr(1);
|
||||
reqwest({
|
||||
url: url,
|
||||
success: function(res) {
|
||||
url,
|
||||
success: (res) => {
|
||||
// make sure node is still selected
|
||||
if (nodeMap.has(res.node.id)) {
|
||||
receiveNodeDetails(res.node);
|
||||
}
|
||||
},
|
||||
error: function(err) {
|
||||
log('Error in node details request: ' + err.responseText);
|
||||
error: (err) => {
|
||||
log(`Error in node details request: ${err.responseText}`);
|
||||
// dont treat missing node as error
|
||||
if (err.status === 404) {
|
||||
receiveNotFound(obj.id);
|
||||
@@ -165,13 +162,13 @@ export function getApiDetails() {
|
||||
clearTimeout(apiDetailsTimer);
|
||||
const url = 'api';
|
||||
reqwest({
|
||||
url: url,
|
||||
success: function(res) {
|
||||
url,
|
||||
success: (res) => {
|
||||
receiveApiDetails(res);
|
||||
apiDetailsTimer = setTimeout(getApiDetails, API_INTERVAL);
|
||||
},
|
||||
error: function(err) {
|
||||
log('Error in api details request: ' + err.responseText);
|
||||
error: (err) => {
|
||||
log(`Error in api details request: ${err.responseText}`);
|
||||
receiveError(url);
|
||||
apiDetailsTimer = setTimeout(getApiDetails, API_INTERVAL / 2);
|
||||
}
|
||||
@@ -184,16 +181,16 @@ export function doControlRequest(nodeId, control) {
|
||||
+ `${encodeURIComponent(control.nodeId)}/${control.id}`;
|
||||
reqwest({
|
||||
method: 'POST',
|
||||
url: url,
|
||||
success: function(res) {
|
||||
url,
|
||||
success: (res) => {
|
||||
receiveControlSuccess(nodeId);
|
||||
if (res && res.pipe) {
|
||||
receiveControlPipe(res.pipe, nodeId, res.raw_tty, true);
|
||||
}
|
||||
},
|
||||
error: function(err) {
|
||||
error: (err) => {
|
||||
receiveControlError(nodeId, err.response);
|
||||
controlErrorTimer = setTimeout(function() {
|
||||
controlErrorTimer = setTimeout(() => {
|
||||
clearControlError(nodeId);
|
||||
}, 10000);
|
||||
}
|
||||
@@ -204,12 +201,12 @@ export function deletePipe(pipeId) {
|
||||
const url = `api/pipe/${encodeURIComponent(pipeId)}`;
|
||||
reqwest({
|
||||
method: 'DELETE',
|
||||
url: url,
|
||||
success: function() {
|
||||
url,
|
||||
success: () => {
|
||||
log('Closed the pipe!');
|
||||
},
|
||||
error: function(err) {
|
||||
log('Error closing pipe:' + err);
|
||||
error: (err) => {
|
||||
log(`Error closing pipe:${err}`);
|
||||
receiveError(url);
|
||||
}
|
||||
});
|
||||
@@ -219,11 +216,11 @@ export function getPipeStatus(pipeId) {
|
||||
const url = `api/pipe/${encodeURIComponent(pipeId)}/check`;
|
||||
reqwest({
|
||||
method: 'GET',
|
||||
url: url,
|
||||
error: function(err) {
|
||||
url,
|
||||
error: (err) => {
|
||||
log('ERROR: unexpected response:', err);
|
||||
},
|
||||
success: function(res) {
|
||||
success: (res) => {
|
||||
const status = {
|
||||
200: 'PIPE_ALIVE',
|
||||
204: 'PIPE_DELETED'
|
||||
|
||||
@@ -29,36 +29,36 @@
|
||||
"timely": "0.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "6.0.1",
|
||||
"babel-core": "6.1.4",
|
||||
"babel-eslint": "4.1.5",
|
||||
"babel-jest": "6.0.1",
|
||||
"babel-loader": "6.1.0",
|
||||
"babel-preset-es2015": "6.1.4",
|
||||
"babel-preset-react": "6.1.4",
|
||||
"css-loader": "0.22.0",
|
||||
"eslint": "1.9.0",
|
||||
"eslint-config-airbnb": "1.0.0",
|
||||
"eslint-loader": "1.1.1",
|
||||
"autoprefixer": "6.3.3",
|
||||
"babel-core": "6.7.2",
|
||||
"babel-eslint": "5.0.0",
|
||||
"babel-jest": "9.0.3",
|
||||
"babel-loader": "6.2.4",
|
||||
"babel-preset-es2015": "6.6.0",
|
||||
"babel-preset-react": "6.5.0",
|
||||
"css-loader": "0.23.1",
|
||||
"eslint": "2.4.0",
|
||||
"eslint-config-airbnb": "6.1.0",
|
||||
"eslint-loader": "1.3.0",
|
||||
"eslint-plugin-jasmine": "1.6.0",
|
||||
"eslint-plugin-react": "3.8.0",
|
||||
"file-loader": "0.8.4",
|
||||
"eslint-plugin-react": "4.2.2",
|
||||
"file-loader": "0.8.5",
|
||||
"http-proxy-rules": "^1.0.1",
|
||||
"jest-cli": "~0.7.1",
|
||||
"json-loader": "0.5.3",
|
||||
"less": "~2.5.1",
|
||||
"less-loader": "2.2.1",
|
||||
"postcss-loader": "0.7.0",
|
||||
"jest-cli": "~0.9.2",
|
||||
"json-loader": "0.5.4",
|
||||
"less": "~2.6.1",
|
||||
"less-loader": "2.2.2",
|
||||
"postcss-loader": "0.8.2",
|
||||
"style-loader": "0.13.0",
|
||||
"url": "0.11.0",
|
||||
"url-loader": "0.5.6",
|
||||
"url-loader": "0.5.7",
|
||||
"webpack": "~1.12.4"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"express": "~4.13.3",
|
||||
"http-proxy": "^1.12.0",
|
||||
"react-hot-loader": "~1.3.0",
|
||||
"webpack-dev-server": "~1.12.1"
|
||||
"webpack-dev-server": "~1.14.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack --config webpack.production.config.js",
|
||||
|
||||
Reference in New Issue
Block a user