mirror of
https://github.com/weaveworks/scope.git
synced 2026-08-18 20:07:06 +00:00
Resolved dependency cycle between actions and web utils
This commit is contained in:
@@ -4,24 +4,18 @@ import { defaults } from 'lodash';
|
||||
import { Map as makeMap, List } from 'immutable';
|
||||
|
||||
import {
|
||||
closeWebsocket, openWebsocket, receiveError,
|
||||
receiveApiDetails, receiveNodesDelta, receiveNodeDetails,
|
||||
receiveControlPipeStatus, receiveTopologies, receiveNotFound,
|
||||
receiveNodesForTopology, receiveNodes,
|
||||
receiveError,
|
||||
receiveNodeDetails,
|
||||
receiveNotFound, receiveNodesForTopology, receiveNodes,
|
||||
} from '../actions/app-actions';
|
||||
|
||||
import { getCurrentTopologyUrl } from './topology-utils';
|
||||
import { layersTopologyIdsSelector } from '../selectors/resource-view/layout';
|
||||
import { activeTopologyOptionsSelector } from '../selectors/topology';
|
||||
import { isPausedSelector } from '../selectors/time-travel';
|
||||
|
||||
import { API_REFRESH_INTERVAL, TOPOLOGY_REFRESH_INTERVAL } from '../constants/timer';
|
||||
|
||||
const log = debug('scope:web-api-utils');
|
||||
|
||||
const reconnectTimerInterval = 5000;
|
||||
const updateFrequency = '5s';
|
||||
const FIRST_RENDER_TOO_LONG_THRESHOLD = 100; // ms
|
||||
const csrfToken = (() => {
|
||||
// Check for token at window level or parent level (for iframe);
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
@@ -37,15 +31,6 @@ const csrfToken = (() => {
|
||||
return token;
|
||||
})();
|
||||
|
||||
let socket;
|
||||
let reconnectTimer = 0;
|
||||
let topologyTimer = 0;
|
||||
let apiDetailsTimer = 0;
|
||||
let currentUrl = null;
|
||||
let createWebsocketAt = null;
|
||||
let firstMessageOnWebsocketAt = null;
|
||||
let continuePolling = true;
|
||||
|
||||
export function buildUrlQuery(params = makeMap(), state = null) {
|
||||
// Attach the time travel timestamp to every request to the backend.
|
||||
if (state) {
|
||||
@@ -103,7 +88,7 @@ export function getReportUrl(timestamp) {
|
||||
return `${getApiPath()}/api/report?${buildUrlQuery(makeMap({ timestamp }))}`;
|
||||
}
|
||||
|
||||
function topologiesUrl(state) {
|
||||
export function topologiesUrl(state) {
|
||||
const activeTopologyOptions = activeTopologyOptionsSelector(state);
|
||||
const optionsQuery = buildUrlQuery(activeTopologyOptions, state);
|
||||
return `${getApiPath()}/api/topology?${optionsQuery}`;
|
||||
@@ -114,68 +99,12 @@ export function getWebsocketUrl(host = window.location.host, pathname = window.l
|
||||
return `${wsProto}://${host}${getApiPath(pathname)}`;
|
||||
}
|
||||
|
||||
function buildWebsocketUrl(topologyUrl, topologyOptions = makeMap(), state) {
|
||||
export function buildWebsocketUrl(topologyUrl, topologyOptions = makeMap(), state) {
|
||||
topologyOptions = topologyOptions.set('t', updateFrequency);
|
||||
const optionsQuery = buildUrlQuery(topologyOptions, state);
|
||||
return `${getWebsocketUrl()}${topologyUrl}/ws?${optionsQuery}`;
|
||||
}
|
||||
|
||||
function createWebsocket(websocketUrl, getState, dispatch) {
|
||||
if (socket) {
|
||||
socket.onclose = null;
|
||||
socket.onerror = null;
|
||||
socket.close();
|
||||
// onclose() is not called, but that's fine since we're opening a new one
|
||||
// right away
|
||||
}
|
||||
|
||||
// profiling
|
||||
createWebsocketAt = new Date();
|
||||
firstMessageOnWebsocketAt = null;
|
||||
|
||||
socket = new WebSocket(websocketUrl);
|
||||
|
||||
socket.onopen = () => {
|
||||
log(`Opening websocket to ${websocketUrl}`);
|
||||
dispatch(openWebsocket());
|
||||
};
|
||||
|
||||
socket.onclose = () => {
|
||||
clearTimeout(reconnectTimer);
|
||||
log(`Closing websocket to ${websocketUrl}`, socket.readyState);
|
||||
socket = null;
|
||||
dispatch(closeWebsocket());
|
||||
|
||||
if (continuePolling && !isPausedSelector(getState())) {
|
||||
reconnectTimer = setTimeout(() => {
|
||||
createWebsocket(websocketUrl, getState, dispatch);
|
||||
}, reconnectTimerInterval);
|
||||
}
|
||||
};
|
||||
|
||||
socket.onerror = () => {
|
||||
log(`Error in websocket to ${websocketUrl}`);
|
||||
dispatch(receiveError(websocketUrl));
|
||||
};
|
||||
|
||||
socket.onmessage = (event) => {
|
||||
const msg = JSON.parse(event.data);
|
||||
dispatch(receiveNodesDelta(msg));
|
||||
|
||||
// profiling (receiveNodesDelta triggers synchronous render)
|
||||
if (!firstMessageOnWebsocketAt) {
|
||||
firstMessageOnWebsocketAt = new Date();
|
||||
const timeToFirstMessage = firstMessageOnWebsocketAt - createWebsocketAt;
|
||||
if (timeToFirstMessage > FIRST_RENDER_TOO_LONG_THRESHOLD) {
|
||||
log(
|
||||
'Time (ms) to first nodes render after websocket was created',
|
||||
firstMessageOnWebsocketAt - createWebsocketAt
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* XHR wrapper. Applies a CSRF token (if it exists) and content-type to all requests.
|
||||
* Any opts that get passed in will override the defaults.
|
||||
@@ -210,7 +139,7 @@ function getNodesForTopologies(state, dispatch, topologyIds, topologyOptions = m
|
||||
);
|
||||
}
|
||||
|
||||
function getNodesOnce(getState, dispatch) {
|
||||
export function getNodesOnce(getState, dispatch) {
|
||||
const state = getState();
|
||||
const topologyUrl = getCurrentTopologyUrl(state);
|
||||
const topologyOptions = activeTopologyOptionsSelector(state);
|
||||
@@ -246,63 +175,6 @@ export function getResourceViewNodesSnapshot(state, dispatch) {
|
||||
getNodesForTopologies(state, dispatch, topologyIds);
|
||||
}
|
||||
|
||||
function pollTopologies(getState, dispatch, initialPoll = false) {
|
||||
// Used to resume polling when navigating between pages in Weave Cloud.
|
||||
continuePolling = initialPoll === true ? true : continuePolling;
|
||||
clearTimeout(topologyTimer);
|
||||
// NOTE: getState is called every time to make sure the up-to-date state is used.
|
||||
const url = topologiesUrl(getState());
|
||||
doRequest({
|
||||
error: (req) => {
|
||||
log(`Error in topology request: ${req.responseText}`);
|
||||
dispatch(receiveError(url));
|
||||
// Only retry in stand-alone mode
|
||||
if (continuePolling && !isPausedSelector(getState())) {
|
||||
topologyTimer = setTimeout(() => {
|
||||
pollTopologies(getState, dispatch);
|
||||
}, TOPOLOGY_REFRESH_INTERVAL);
|
||||
}
|
||||
},
|
||||
success: (res) => {
|
||||
if (continuePolling && !isPausedSelector(getState())) {
|
||||
dispatch(receiveTopologies(res));
|
||||
topologyTimer = setTimeout(() => {
|
||||
pollTopologies(getState, dispatch);
|
||||
}, TOPOLOGY_REFRESH_INTERVAL);
|
||||
}
|
||||
},
|
||||
url
|
||||
});
|
||||
}
|
||||
|
||||
function getTopologiesOnce(getState, dispatch) {
|
||||
const url = topologiesUrl(getState());
|
||||
doRequest({
|
||||
error: (req) => {
|
||||
log(`Error in topology request: ${req.responseText}`);
|
||||
dispatch(receiveError(url));
|
||||
},
|
||||
success: (res) => {
|
||||
dispatch(receiveTopologies(res));
|
||||
},
|
||||
url
|
||||
});
|
||||
}
|
||||
|
||||
function updateWebsocketChannel(getState, dispatch, forceRequest) {
|
||||
const topologyUrl = getCurrentTopologyUrl(getState());
|
||||
const topologyOptions = activeTopologyOptionsSelector(getState());
|
||||
const websocketUrl = buildWebsocketUrl(topologyUrl, topologyOptions, getState());
|
||||
// Only recreate websocket if url changed or if forced (weave cloud instance reload);
|
||||
const isNewUrl = websocketUrl !== currentUrl;
|
||||
// `topologyUrl` can be undefined initially, so only create a socket if it is truthy
|
||||
// and no socket exists, or if we get a new url.
|
||||
if (topologyUrl && (!socket || isNewUrl || forceRequest)) {
|
||||
createWebsocket(websocketUrl, getState, dispatch);
|
||||
currentUrl = websocketUrl;
|
||||
}
|
||||
}
|
||||
|
||||
export function getNodeDetails(getState, dispatch) {
|
||||
const state = getState();
|
||||
const nodeMap = state.get('nodeDetails');
|
||||
@@ -349,48 +221,6 @@ export function getNodeDetails(getState, dispatch) {
|
||||
}
|
||||
}
|
||||
|
||||
export function getTopologies(getState, dispatch, forceRequest) {
|
||||
if (isPausedSelector(getState())) {
|
||||
getTopologiesOnce(getState, dispatch);
|
||||
} else {
|
||||
pollTopologies(getState, dispatch, forceRequest);
|
||||
}
|
||||
}
|
||||
|
||||
export function getNodes(getState, dispatch, forceRequest = false) {
|
||||
if (isPausedSelector(getState())) {
|
||||
getNodesOnce(getState, dispatch);
|
||||
} else {
|
||||
updateWebsocketChannel(getState, dispatch, forceRequest);
|
||||
}
|
||||
getNodeDetails(getState, dispatch);
|
||||
}
|
||||
|
||||
export function getApiDetails(dispatch) {
|
||||
clearTimeout(apiDetailsTimer);
|
||||
const url = `${getApiPath()}/api`;
|
||||
doRequest({
|
||||
error: (req) => {
|
||||
log(`Error in api details request: ${req.responseText}`);
|
||||
receiveError(url);
|
||||
if (continuePolling) {
|
||||
apiDetailsTimer = setTimeout(() => {
|
||||
getApiDetails(dispatch);
|
||||
}, API_REFRESH_INTERVAL / 2);
|
||||
}
|
||||
},
|
||||
success: (res) => {
|
||||
dispatch(receiveApiDetails(res));
|
||||
if (continuePolling) {
|
||||
apiDetailsTimer = setTimeout(() => {
|
||||
getApiDetails(dispatch);
|
||||
}, API_REFRESH_INTERVAL);
|
||||
}
|
||||
},
|
||||
url
|
||||
});
|
||||
}
|
||||
|
||||
export function doResizeTty(pipeId, control, cols, rows) {
|
||||
const url = `${getApiPath()}/api/control/${encodeURIComponent(control.probeId)}/`
|
||||
+ `${encodeURIComponent(control.nodeId)}/${control.id}`;
|
||||
@@ -420,44 +250,3 @@ export function deletePipe(pipeId, dispatch) {
|
||||
url
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function getPipeStatus(pipeId, dispatch) {
|
||||
const url = `${getApiPath()}/api/pipe/${encodeURIComponent(pipeId)}/check`;
|
||||
doRequest({
|
||||
complete: (res) => {
|
||||
const status = {
|
||||
204: 'PIPE_ALIVE',
|
||||
404: 'PIPE_DELETED'
|
||||
}[res.status];
|
||||
|
||||
if (!status) {
|
||||
log('Unexpected pipe status:', res.status);
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(receiveControlPipeStatus(pipeId, status));
|
||||
},
|
||||
method: 'GET',
|
||||
url
|
||||
});
|
||||
}
|
||||
|
||||
export function stopPolling() {
|
||||
clearTimeout(apiDetailsTimer);
|
||||
clearTimeout(topologyTimer);
|
||||
continuePolling = false;
|
||||
}
|
||||
|
||||
export function teardownWebsockets() {
|
||||
clearTimeout(reconnectTimer);
|
||||
if (socket) {
|
||||
socket.onerror = null;
|
||||
socket.onclose = null;
|
||||
socket.onmessage = null;
|
||||
socket.onopen = null;
|
||||
socket.close();
|
||||
socket = null;
|
||||
currentUrl = null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user