mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-02 17:50:39 +00:00
clear nodes cache when websocket closes connection
* also show reconnection status fixes #162
This commit is contained in:
@@ -39,6 +39,12 @@ module.exports = {
|
||||
WebapiUtils.getNodesDelta(AppStore.getCurrentTopologyUrl());
|
||||
},
|
||||
|
||||
closeWebsocket: function() {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.CLOSE_WEBSOCKET
|
||||
});
|
||||
},
|
||||
|
||||
enterEdge: function(edgeId) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.ENTER_EDGE,
|
||||
@@ -104,6 +110,13 @@ module.exports = {
|
||||
});
|
||||
},
|
||||
|
||||
receiveError: function(errorUrl) {
|
||||
AppDispatcher.dispatch({
|
||||
errorUrl: errorUrl,
|
||||
type: ActionTypes.RECEIVE_ERROR
|
||||
});
|
||||
},
|
||||
|
||||
route: function(state) {
|
||||
AppDispatcher.dispatch({
|
||||
state: state,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const _ = require('lodash');
|
||||
const d3 = require('d3');
|
||||
const debug = require('debug')('nodes-chart');
|
||||
const debug = require('debug')('scope:nodes-chart');
|
||||
const React = require('react');
|
||||
const timely = require('timely');
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const dagre = require('dagre');
|
||||
const debug = require('debug')('nodes-layout');
|
||||
const debug = require('debug')('scope:nodes-layout');
|
||||
const _ = require('lodash');
|
||||
|
||||
const MAX_NODES = 100;
|
||||
|
||||
@@ -15,7 +15,7 @@ const ESC_KEY_CODE = 27;
|
||||
function getStateFromStores() {
|
||||
return {
|
||||
currentTopology: AppStore.getCurrentTopology(),
|
||||
connectionState: AppStore.getConnectionState(),
|
||||
errorUrl: AppStore.getErrorUrl(),
|
||||
currentGrouping: AppStore.getCurrentGrouping(),
|
||||
highlightedEdgeIds: AppStore.getHighlightedEdgeIds(),
|
||||
highlightedNodeIds: AppStore.getHighlightedNodeIds(),
|
||||
@@ -66,7 +66,7 @@ const App = React.createClass({
|
||||
<div className="header">
|
||||
<Logo />
|
||||
<Topologies topologies={this.state.topologies} currentTopology={this.state.currentTopology} />
|
||||
<Status connectionState={this.state.connectionState} />
|
||||
<Status errorUrl={this.state.errorUrl} />
|
||||
</div>
|
||||
|
||||
<Nodes nodes={this.state.nodes} highlightedNodeIds={this.state.highlightedNodeIds}
|
||||
|
||||
@@ -2,21 +2,22 @@ const React = require('react');
|
||||
|
||||
const Status = React.createClass({
|
||||
|
||||
renderConnectionState: function() {
|
||||
return (
|
||||
<div className="status-connection">
|
||||
<span className="status-icon fa fa-exclamation-circle" />
|
||||
<span className="status-label">Scope is disconnected</span>
|
||||
</div>
|
||||
);
|
||||
renderConnectionState: function(errorUrl) {
|
||||
if (errorUrl) {
|
||||
const title = 'Cannot reach Scope. Make sure the following URL is reachable: ' + errorUrl;
|
||||
return (
|
||||
<div className="status-connection" title={title}>
|
||||
<span className="status-icon fa fa-exclamation-circle" />
|
||||
<span className="status-label">Trying to reconnect...</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const isDisconnected = this.props.connectionState === 'disconnected';
|
||||
|
||||
return (
|
||||
<div className="status">
|
||||
{isDisconnected && this.renderConnectionState()}
|
||||
{this.renderConnectionState(this.props.errorUrl)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ module.exports = keymirror({
|
||||
CLICK_GROUPING: null,
|
||||
CLICK_NODE: null,
|
||||
CLICK_TOPOLOGY: null,
|
||||
CLOSE_WEBSOCKET: null,
|
||||
ENTER_EDGE: null,
|
||||
ENTER_NODE: null,
|
||||
HIT_ESC_KEY: null,
|
||||
@@ -15,5 +16,6 @@ module.exports = keymirror({
|
||||
RECEIVE_NODES_DELTA: null,
|
||||
RECEIVE_TOPOLOGIES: null,
|
||||
RECEIVE_API_DETAILS: null,
|
||||
RECEIVE_ERROR: null,
|
||||
ROUTE_TOPOLOGY: null
|
||||
});
|
||||
|
||||
@@ -31,6 +31,10 @@ describe('AppStore', function() {
|
||||
grouping: 'grouped'
|
||||
};
|
||||
|
||||
const CloseWebsocketAction = {
|
||||
type: ActionTypes.CLOSE_WEBSOCKET
|
||||
};
|
||||
|
||||
const HitEscAction = {
|
||||
type: ActionTypes.HIT_ESC_KEY
|
||||
};
|
||||
@@ -64,6 +68,8 @@ describe('AppStore', function() {
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
// clear AppStore singleton
|
||||
delete require.cache[require.resolve('../app-store')];
|
||||
AppStore = require('../app-store');
|
||||
registeredCallback = AppStore.registeredCallback;
|
||||
});
|
||||
@@ -114,22 +120,34 @@ describe('AppStore', function() {
|
||||
});
|
||||
|
||||
it('keeps showing nodes on navigating back after node click', function() {
|
||||
registeredCallback(ReceiveTopologiesAction);
|
||||
registeredCallback(ClickTopologyAction);
|
||||
registeredCallback(ReceiveNodesDeltaAction);
|
||||
// TODO clear AppStore cache
|
||||
|
||||
expect(AppStore.getAppState())
|
||||
.toEqual({"topologyId":"topo1-grouped","grouping":"none","selectedNodeId": null});
|
||||
.toEqual({"topologyId":"topo1","grouping":"none","selectedNodeId": null});
|
||||
|
||||
registeredCallback(ClickNodeAction);
|
||||
expect(AppStore.getAppState())
|
||||
.toEqual({"topologyId":"topo1-grouped","grouping":"none","selectedNodeId": 'n1'});
|
||||
.toEqual({"topologyId":"topo1","grouping":"none","selectedNodeId": 'n1'});
|
||||
|
||||
// go back in browsing
|
||||
RouteAction.state = {"topologyId":"topo1-grouped","grouping":"none","selectedNodeId": null};
|
||||
RouteAction.state = {"topologyId":"topo1","grouping":"none","selectedNodeId": null};
|
||||
registeredCallback(RouteAction);
|
||||
expect(AppStore.getSelectedNodeId()).toBe(null);
|
||||
expect(AppStore.getNodes()).toEqual(NODE_SET);
|
||||
|
||||
});
|
||||
|
||||
// connection errors
|
||||
|
||||
it('resets topology on websocket reconnect', function() {
|
||||
registeredCallback(ReceiveNodesDeltaAction);
|
||||
expect(AppStore.getNodes()).toEqual(NODE_SET);
|
||||
|
||||
registeredCallback(CloseWebsocketAction);
|
||||
expect(AppStore.getNodes()).toEqual({});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const _ = require('lodash');
|
||||
const assign = require('object-assign');
|
||||
const debug = require('debug')('app-store');
|
||||
const debug = require('debug')('scope:app-store');
|
||||
|
||||
const AppDispatcher = require('../dispatcher/app-dispatcher');
|
||||
const ActionTypes = require('../constants/action-types');
|
||||
@@ -29,9 +29,9 @@ function findCurrentTopology(subTree, topologyId) {
|
||||
|
||||
// Initial values
|
||||
|
||||
let connectionState = 'disconnected';
|
||||
let currentGrouping = 'none';
|
||||
let currentTopologyId = 'containers';
|
||||
let errorUrl = null;
|
||||
let version = '';
|
||||
let mouseOverEdgeId = null;
|
||||
let mouseOverNodeId = null;
|
||||
@@ -54,10 +54,6 @@ const AppStore = assign({}, EventEmitter.prototype, {
|
||||
};
|
||||
},
|
||||
|
||||
getConnectionState: function() {
|
||||
return connectionState;
|
||||
},
|
||||
|
||||
getCurrentTopology: function() {
|
||||
return findCurrentTopology(topologies, currentTopologyId);
|
||||
},
|
||||
@@ -74,6 +70,10 @@ const AppStore = assign({}, EventEmitter.prototype, {
|
||||
return currentGrouping;
|
||||
},
|
||||
|
||||
getErrorUrl: function() {
|
||||
return errorUrl;
|
||||
},
|
||||
|
||||
getHighlightedEdgeIds: function() {
|
||||
if (mouseOverNodeId) {
|
||||
// all neighbour combinations because we dont know which direction exists
|
||||
@@ -127,6 +127,7 @@ const AppStore = assign({}, EventEmitter.prototype, {
|
||||
getVersion: function() {
|
||||
return version;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Store Dispatch Hooks
|
||||
@@ -160,6 +161,11 @@ AppStore.registeredCallback = function(payload) {
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
break;
|
||||
|
||||
case ActionTypes.CLOSE_WEBSOCKET:
|
||||
nodes = {};
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
break;
|
||||
|
||||
case ActionTypes.ENTER_EDGE:
|
||||
mouseOverEdgeId = payload.edgeId;
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
@@ -186,7 +192,13 @@ AppStore.registeredCallback = function(payload) {
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
break;
|
||||
|
||||
case ActionTypes.RECEIVE_ERROR:
|
||||
errorUrl = payload.errorUrl;
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
break;
|
||||
|
||||
case ActionTypes.RECEIVE_NODE_DETAILS:
|
||||
errorUrl = null;
|
||||
nodeDetails = payload.details;
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
break;
|
||||
@@ -197,7 +209,7 @@ AppStore.registeredCallback = function(payload) {
|
||||
'update', _.size(payload.delta.update),
|
||||
'add', _.size(payload.delta.add));
|
||||
|
||||
connectionState = 'connected';
|
||||
errorUrl = null;
|
||||
|
||||
// nodes that no longer exist
|
||||
_.each(payload.delta.remove, function(nodeId) {
|
||||
@@ -225,11 +237,13 @@ AppStore.registeredCallback = function(payload) {
|
||||
break;
|
||||
|
||||
case ActionTypes.RECEIVE_TOPOLOGIES:
|
||||
errorUrl = null;
|
||||
topologies = payload.topologies;
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
break;
|
||||
|
||||
case ActionTypes.RECEIVE_API_DETAILS:
|
||||
errorUrl = null;
|
||||
version = payload.version;
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
break;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
const debug = require('debug')('scope:web-api-utils');
|
||||
const reqwest = require('reqwest');
|
||||
|
||||
const AppActions = require('../actions/app-actions');
|
||||
@@ -5,16 +6,21 @@ const AppActions = require('../actions/app-actions');
|
||||
const WS_URL = window.WS_URL || 'ws://' + location.host;
|
||||
|
||||
|
||||
const apiTimerInterval = 10000;
|
||||
const reconnectTimerInterval = 5000;
|
||||
const topologyTimerInterval = apiTimerInterval;
|
||||
const updateFrequency = '5s';
|
||||
|
||||
let socket;
|
||||
let reconnectTimer = 0;
|
||||
let currentUrl = null;
|
||||
let updateFrequency = '5s';
|
||||
let topologyTimer = 0;
|
||||
let apiDetailsTimer = 0;
|
||||
|
||||
function createWebsocket(topologyUrl) {
|
||||
if (socket) {
|
||||
socket.onclose = null;
|
||||
socket.onerror = null;
|
||||
socket.close();
|
||||
}
|
||||
|
||||
@@ -23,10 +29,17 @@ function createWebsocket(topologyUrl) {
|
||||
socket.onclose = function() {
|
||||
clearTimeout(reconnectTimer);
|
||||
socket = null;
|
||||
AppActions.closeWebsocket();
|
||||
debug('Closed websocket to ' + currentUrl);
|
||||
|
||||
reconnectTimer = setTimeout(function() {
|
||||
createWebsocket(topologyUrl);
|
||||
}, 5000);
|
||||
}, reconnectTimerInterval);
|
||||
};
|
||||
|
||||
socket.onerror = function() {
|
||||
debug('Error in websocket to ' + currentUrl);
|
||||
AppActions.receiveError(currentUrl);
|
||||
};
|
||||
|
||||
socket.onmessage = function(event) {
|
||||
@@ -41,26 +54,51 @@ function createWebsocket(topologyUrl) {
|
||||
|
||||
function getTopologies() {
|
||||
clearTimeout(topologyTimer);
|
||||
reqwest('/api/topology', function(res) {
|
||||
AppActions.receiveTopologies(res);
|
||||
topologyTimer = setTimeout(getTopologies, 10000);
|
||||
const url = '/api/topology';
|
||||
reqwest({
|
||||
url: url,
|
||||
success: function(res) {
|
||||
AppActions.receiveTopologies(res);
|
||||
topologyTimer = setTimeout(getTopologies, topologyTimerInterval);
|
||||
},
|
||||
error: function(err) {
|
||||
debug('Error in topology request: ' + err);
|
||||
AppActions.receiveError(url);
|
||||
topologyTimer = setTimeout(getTopologies, topologyTimerInterval / 2);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getNodeDetails(topologyUrl, nodeId) {
|
||||
if (topologyUrl && nodeId) {
|
||||
const url = [topologyUrl, nodeId].join('/');
|
||||
reqwest(url, function(res) {
|
||||
AppActions.receiveNodeDetails(res.node);
|
||||
reqwest({
|
||||
url: url,
|
||||
success: function(res) {
|
||||
AppActions.receiveNodeDetails(res.node);
|
||||
},
|
||||
error: function(err) {
|
||||
debug('Error in node details request: ' + err);
|
||||
AppActions.receiveError(topologyUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getApiDetails() {
|
||||
clearTimeout(apiDetailsTimer);
|
||||
reqwest('/api', function(res) {
|
||||
AppActions.receiveApiDetails(res);
|
||||
apiDetailsTimer = setTimeout(getApiDetails, 10000);
|
||||
const url = '/api';
|
||||
reqwest({
|
||||
url: url,
|
||||
success: function(res) {
|
||||
AppActions.receiveApiDetails(res);
|
||||
apiDetailsTimer = setTimeout(getApiDetails, apiTimerInterval);
|
||||
},
|
||||
error: function(err) {
|
||||
debug('Error in api details request: ' + err);
|
||||
AppActions.receiveError(url);
|
||||
apiDetailsTimer = setTimeout(getApiDetails, apiTimerInterval / 2);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -13,10 +13,6 @@ var DEBUG = !argv.release;
|
||||
var STYLE_LOADER = 'style-loader';
|
||||
var CSS_LOADER = DEBUG ? 'css-loader' : 'css-loader?minimize';
|
||||
var AUTOPREFIXER_LOADER = 'postcss-loader';
|
||||
var GLOBALS = {
|
||||
'process.env.NODE_ENV': DEBUG ? '"development"' : '"production"',
|
||||
'__DEV__': DEBUG
|
||||
};
|
||||
|
||||
//
|
||||
// Common configuration chunk to be used for both
|
||||
|
||||
Reference in New Issue
Block a user