mirror of
https://github.com/weaveworks/scope.git
synced 2026-09-06 10:17:19 +00:00
Merge pull request #3643 from weaveworks/security-updates
Update to newer eslint
This commit is contained in:
@@ -31,6 +31,7 @@
|
||||
"no-param-reassign": 0,
|
||||
"no-restricted-properties": 0,
|
||||
"object-curly-spacing": 0,
|
||||
"react/destructuring-assignment": 0,
|
||||
"react/jsx-closing-bracket-location": 0,
|
||||
"react/jsx-filename-extension": [
|
||||
2,
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
v8.9.0
|
||||
v8.10.0
|
||||
|
||||
@@ -1,42 +1,19 @@
|
||||
import debug from 'debug';
|
||||
import { fromJS } from 'immutable';
|
||||
|
||||
import ActionTypes from '../constants/action-types';
|
||||
import { saveGraph } from '../utils/file-utils';
|
||||
import { clearStoredViewState, updateRoute } from '../utils/router-utils';
|
||||
import {
|
||||
doControlRequest,
|
||||
getAllNodes,
|
||||
getResourceViewNodesSnapshot,
|
||||
getNodeDetails,
|
||||
getTopologies,
|
||||
deletePipe,
|
||||
stopPolling,
|
||||
teardownWebsockets,
|
||||
getNodes,
|
||||
} from '../utils/web-api-utils';
|
||||
import { isPausedSelector } from '../selectors/time-travel';
|
||||
import {
|
||||
availableMetricTypesSelector,
|
||||
nextPinnedMetricTypeSelector,
|
||||
previousPinnedMetricTypeSelector,
|
||||
pinnedMetricSelector,
|
||||
} from '../selectors/node-metric';
|
||||
import {
|
||||
isResourceViewModeSelector,
|
||||
resourceViewAvailableSelector,
|
||||
} from '../selectors/topology';
|
||||
import { isResourceViewModeSelector } from '../selectors/topology';
|
||||
|
||||
import {
|
||||
GRAPH_VIEW_MODE,
|
||||
TABLE_VIEW_MODE,
|
||||
RESOURCE_VIEW_MODE,
|
||||
} from '../constants/naming';
|
||||
|
||||
|
||||
const log = debug('scope:app-actions');
|
||||
|
||||
|
||||
export function showHelp() {
|
||||
return { type: ActionTypes.SHOW_HELP };
|
||||
}
|
||||
@@ -181,41 +158,10 @@ export function updateSearch(searchQuery = '', pinnedSearches = []) {
|
||||
};
|
||||
}
|
||||
|
||||
export function focusSearch() {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({ type: ActionTypes.FOCUS_SEARCH });
|
||||
// update nodes cache to allow search across all topologies,
|
||||
// wait a second until animation is over
|
||||
// NOTE: This will cause matching recalculation (and rerendering)
|
||||
// of all the nodes in the topology, instead applying it only on
|
||||
// the nodes delta. The solution would be to implement deeper
|
||||
// search selectors with per-node caching instead of per-topology.
|
||||
setTimeout(() => {
|
||||
getAllNodes(getState(), dispatch);
|
||||
}, 1200);
|
||||
};
|
||||
}
|
||||
|
||||
export function blurSearch() {
|
||||
return { type: ActionTypes.BLUR_SEARCH };
|
||||
}
|
||||
|
||||
export function changeTopologyOption(option, value, topologyId, addOrRemove) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
addOrRemove,
|
||||
option,
|
||||
topologyId,
|
||||
type: ActionTypes.CHANGE_TOPOLOGY_OPTION,
|
||||
value
|
||||
});
|
||||
updateRoute(getState);
|
||||
// update all request workers with new options
|
||||
getTopologies(getState, dispatch);
|
||||
getNodes(getState, dispatch);
|
||||
};
|
||||
}
|
||||
|
||||
export function clickBackground() {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
@@ -225,18 +171,6 @@ export function clickBackground() {
|
||||
};
|
||||
}
|
||||
|
||||
export function clickCloseDetails(nodeId) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
nodeId,
|
||||
type: ActionTypes.CLICK_CLOSE_DETAILS
|
||||
});
|
||||
// Pull the most recent details for the next details panel that comes into focus.
|
||||
getNodeDetails(getState, dispatch);
|
||||
updateRoute(getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function closeTerminal(pipeId) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
@@ -297,102 +231,6 @@ export function setTableView() {
|
||||
};
|
||||
}
|
||||
|
||||
export function setResourceView() {
|
||||
return (dispatch, getState) => {
|
||||
if (resourceViewAvailableSelector(getState())) {
|
||||
dispatch({
|
||||
type: ActionTypes.SET_VIEW_MODE,
|
||||
viewMode: RESOURCE_VIEW_MODE,
|
||||
});
|
||||
// Pin the first metric if none of the visible ones is pinned.
|
||||
const state = getState();
|
||||
if (!pinnedMetricSelector(state)) {
|
||||
const firstAvailableMetricType = availableMetricTypesSelector(state).first();
|
||||
dispatch(pinMetric(firstAvailableMetricType));
|
||||
}
|
||||
getResourceViewNodesSnapshot(getState(), dispatch);
|
||||
updateRoute(getState);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function clickNode(nodeId, label, origin, topologyId = null) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
label,
|
||||
nodeId,
|
||||
origin,
|
||||
topologyId,
|
||||
type: ActionTypes.CLICK_NODE,
|
||||
});
|
||||
updateRoute(getState);
|
||||
getNodeDetails(getState, dispatch);
|
||||
};
|
||||
}
|
||||
|
||||
export function pauseTimeAtNow() {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: ActionTypes.PAUSE_TIME_AT_NOW
|
||||
});
|
||||
updateRoute(getState);
|
||||
if (!getState().get('nodesLoaded')) {
|
||||
getNodes(getState, dispatch);
|
||||
if (isResourceViewModeSelector(getState())) {
|
||||
getResourceViewNodesSnapshot(getState(), dispatch);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function clickRelative(nodeId, topologyId, label, origin) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
label,
|
||||
nodeId,
|
||||
origin,
|
||||
topologyId,
|
||||
type: ActionTypes.CLICK_RELATIVE
|
||||
});
|
||||
updateRoute(getState);
|
||||
getNodeDetails(getState, dispatch);
|
||||
};
|
||||
}
|
||||
|
||||
function updateTopology(dispatch, getState) {
|
||||
const state = getState();
|
||||
// If we're in the resource view, get the snapshot of all the relevant node topologies.
|
||||
if (isResourceViewModeSelector(state)) {
|
||||
getResourceViewNodesSnapshot(state, dispatch);
|
||||
}
|
||||
updateRoute(getState);
|
||||
// NOTE: This is currently not needed for our static resource
|
||||
// view, but we'll need it here later and it's simpler to just
|
||||
// keep it than to redo the nodes delta updating logic.
|
||||
getNodes(getState, dispatch);
|
||||
}
|
||||
|
||||
export function clickShowTopologyForNode(topologyId, nodeId) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
nodeId,
|
||||
topologyId,
|
||||
type: ActionTypes.CLICK_SHOW_TOPOLOGY_FOR_NODE
|
||||
});
|
||||
updateTopology(dispatch, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function clickTopology(topologyId) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
topologyId,
|
||||
type: ActionTypes.CLICK_TOPOLOGY
|
||||
});
|
||||
updateTopology(dispatch, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function cacheZoomState(zoomState) {
|
||||
return {
|
||||
type: ActionTypes.CACHE_ZOOM_STATE,
|
||||
@@ -420,17 +258,6 @@ export function closeWebsocket() {
|
||||
};
|
||||
}
|
||||
|
||||
export function doControl(nodeId, control) {
|
||||
return (dispatch) => {
|
||||
dispatch({
|
||||
control,
|
||||
nodeId,
|
||||
type: ActionTypes.DO_CONTROL
|
||||
});
|
||||
doControlRequest(nodeId, control, dispatch);
|
||||
};
|
||||
}
|
||||
|
||||
export function enterEdge(edgeId) {
|
||||
return {
|
||||
edgeId,
|
||||
@@ -528,23 +355,6 @@ export function receiveNodesDelta(delta) {
|
||||
};
|
||||
}
|
||||
|
||||
export function resumeTime() {
|
||||
return (dispatch, getState) => {
|
||||
if (isPausedSelector(getState())) {
|
||||
dispatch({
|
||||
type: ActionTypes.RESUME_TIME
|
||||
});
|
||||
updateRoute(getState);
|
||||
// After unpausing, all of the following calls will re-activate polling.
|
||||
getTopologies(getState, dispatch);
|
||||
getNodes(getState, dispatch, true);
|
||||
if (isResourceViewModeSelector(getState())) {
|
||||
getResourceViewNodesSnapshot(getState(), dispatch);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function receiveNodes(nodes) {
|
||||
return {
|
||||
nodes,
|
||||
@@ -552,26 +362,6 @@ export function receiveNodes(nodes) {
|
||||
};
|
||||
}
|
||||
|
||||
export function jumpToTime(timestamp) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
timestamp,
|
||||
type: ActionTypes.JUMP_TO_TIME,
|
||||
});
|
||||
updateRoute(getState);
|
||||
getTopologies(getState, dispatch);
|
||||
if (!getState().get('nodesLoaded')) {
|
||||
getNodes(getState, dispatch);
|
||||
if (isResourceViewModeSelector(getState())) {
|
||||
getResourceViewNodesSnapshot(getState(), dispatch);
|
||||
}
|
||||
} else {
|
||||
// Get most recent details before freezing the state.
|
||||
getNodeDetails(getState, dispatch);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function receiveNodesForTopology(nodes, topologyId) {
|
||||
return {
|
||||
nodes,
|
||||
@@ -580,53 +370,6 @@ export function receiveNodesForTopology(nodes, topologyId) {
|
||||
};
|
||||
}
|
||||
|
||||
export function receiveTopologies(topologies) {
|
||||
return (dispatch, getState) => {
|
||||
const firstLoad = !getState().get('topologiesLoaded');
|
||||
dispatch({
|
||||
topologies,
|
||||
type: ActionTypes.RECEIVE_TOPOLOGIES
|
||||
});
|
||||
getNodes(getState, dispatch);
|
||||
// Populate search matches on first load
|
||||
const state = getState();
|
||||
// Fetch all the relevant nodes once on first load
|
||||
if (firstLoad && isResourceViewModeSelector(state)) {
|
||||
getResourceViewNodesSnapshot(state, dispatch);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function receiveApiDetails(apiDetails) {
|
||||
return (dispatch, getState) => {
|
||||
const isFirstTime = !getState().get('version');
|
||||
const pausedAt = getState().get('pausedAt');
|
||||
|
||||
dispatch({
|
||||
capabilities: fromJS(apiDetails.capabilities || {}),
|
||||
hostname: apiDetails.hostname,
|
||||
newVersion: apiDetails.newVersion,
|
||||
plugins: apiDetails.plugins,
|
||||
type: ActionTypes.RECEIVE_API_DETAILS,
|
||||
version: apiDetails.version,
|
||||
});
|
||||
|
||||
// On initial load either start time travelling at the pausedAt timestamp
|
||||
// (if it was given as URL param) if time travelling is enabled, otherwise
|
||||
// simply pause at the present time which is arguably the next best thing
|
||||
// we could do.
|
||||
// NOTE: We can't make this decision before API details are received because
|
||||
// we have no prior info on whether time travel would be available.
|
||||
if (isFirstTime && pausedAt) {
|
||||
if (apiDetails.capabilities && apiDetails.capabilities.historic_reports) {
|
||||
dispatch(jumpToTime(pausedAt));
|
||||
} else {
|
||||
dispatch(pauseTimeAtNow());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function receiveControlNodeRemoved(nodeId) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
@@ -647,34 +390,6 @@ export function receiveControlPipeFromParams(pipeId, rawTty, resizeTtyControl) {
|
||||
};
|
||||
}
|
||||
|
||||
export function receiveControlPipe(pipeId, nodeId, rawTty, resizeTtyControl, control) {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
if (state.get('nodeDetails').last()
|
||||
&& nodeId !== state.get('nodeDetails').last().id) {
|
||||
log('Node was deselected before we could set up control!');
|
||||
deletePipe(pipeId, dispatch);
|
||||
return;
|
||||
}
|
||||
|
||||
const controlPipe = state.get('controlPipes').last();
|
||||
if (controlPipe && controlPipe.get('id') !== pipeId) {
|
||||
deletePipe(controlPipe.get('id'), dispatch);
|
||||
}
|
||||
|
||||
dispatch({
|
||||
control,
|
||||
nodeId,
|
||||
pipeId,
|
||||
rawTty,
|
||||
resizeTtyControl,
|
||||
type: ActionTypes.RECEIVE_CONTROL_PIPE
|
||||
});
|
||||
|
||||
updateRoute(getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function receiveControlPipeStatus(pipeId, status) {
|
||||
return {
|
||||
pipeId,
|
||||
@@ -708,44 +423,9 @@ export function setContrastMode(enabled) {
|
||||
};
|
||||
}
|
||||
|
||||
export function getTopologiesWithInitialPoll() {
|
||||
return (dispatch, getState) => {
|
||||
getTopologies(getState, dispatch, true);
|
||||
};
|
||||
}
|
||||
|
||||
export function route(urlState) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
state: urlState,
|
||||
type: ActionTypes.ROUTE_TOPOLOGY
|
||||
});
|
||||
// Handle Time Travel state update through separate actions as it's more complex.
|
||||
// This is mostly to handle switching contexts Explore <-> Monitor in WC while
|
||||
// the timestamp keeps changing - e.g. if we were Time Travelling in Scope and
|
||||
// then went live in Monitor, switching back to Explore should properly close
|
||||
// the Time Travel etc, not just update the pausedAt state directly.
|
||||
if (!urlState.pausedAt) {
|
||||
dispatch(resumeTime());
|
||||
} else {
|
||||
dispatch(jumpToTime(urlState.pausedAt));
|
||||
}
|
||||
// update all request workers with new options
|
||||
getTopologies(getState, dispatch);
|
||||
getNodes(getState, dispatch);
|
||||
// If we are landing on the resource view page, we need to fetch not only all the
|
||||
// nodes for the current topology, but also the nodes of all the topologies that make
|
||||
// the layers in the resource view.
|
||||
const state = getState();
|
||||
if (isResourceViewModeSelector(state)) {
|
||||
getResourceViewNodesSnapshot(state, dispatch);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function resetLocalViewState() {
|
||||
return (dispatch) => {
|
||||
dispatch({type: ActionTypes.RESET_LOCAL_VIEW_STATE});
|
||||
dispatch({ type: ActionTypes.RESET_LOCAL_VIEW_STATE });
|
||||
clearStoredViewState();
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
window.location.href = window.location.href.split('#')[0];
|
||||
@@ -768,16 +448,6 @@ export function changeInstance() {
|
||||
};
|
||||
}
|
||||
|
||||
export function shutdown() {
|
||||
return (dispatch) => {
|
||||
stopPolling();
|
||||
teardownWebsockets();
|
||||
dispatch({
|
||||
type: ActionTypes.SHUTDOWN
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function setMonitorState(monitor) {
|
||||
return {
|
||||
monitor,
|
||||
|
||||
@@ -0,0 +1,608 @@
|
||||
/*
|
||||
|
||||
This file consists of functions that both dispatch actions to Redux and also make API requests.
|
||||
|
||||
TODO: Refactor all the methods below so that the split between actions and
|
||||
requests is more clear, and make user components make explicit calls to requests
|
||||
and dispatch actions when handling request promises.
|
||||
|
||||
*/
|
||||
import debug from 'debug';
|
||||
import { fromJS } from 'immutable';
|
||||
|
||||
import ActionTypes from '../constants/action-types';
|
||||
import { RESOURCE_VIEW_MODE } from '../constants/naming';
|
||||
import {
|
||||
API_REFRESH_INTERVAL,
|
||||
TOPOLOGY_REFRESH_INTERVAL,
|
||||
} from '../constants/timer';
|
||||
import { updateRoute } from '../utils/router-utils';
|
||||
import { getCurrentTopologyUrl } from '../utils/topology-utils';
|
||||
import {
|
||||
doRequest,
|
||||
getApiPath,
|
||||
getAllNodes,
|
||||
getNodesOnce,
|
||||
deletePipe,
|
||||
getNodeDetails,
|
||||
getResourceViewNodesSnapshot,
|
||||
topologiesUrl,
|
||||
buildWebsocketUrl,
|
||||
} from '../utils/web-api-utils';
|
||||
import {
|
||||
availableMetricTypesSelector,
|
||||
pinnedMetricSelector,
|
||||
} from '../selectors/node-metric';
|
||||
import {
|
||||
isResourceViewModeSelector,
|
||||
resourceViewAvailableSelector,
|
||||
activeTopologyOptionsSelector,
|
||||
} from '../selectors/topology';
|
||||
import { isPausedSelector } from '../selectors/time-travel';
|
||||
|
||||
import {
|
||||
receiveControlNodeRemoved,
|
||||
receiveControlPipeStatus,
|
||||
receiveControlSuccess,
|
||||
receiveControlError,
|
||||
receiveError,
|
||||
pinMetric,
|
||||
openWebsocket,
|
||||
closeWebsocket,
|
||||
receiveNodesDelta,
|
||||
clearControlError,
|
||||
blurSearch,
|
||||
} from './app-actions';
|
||||
|
||||
|
||||
const log = debug('scope:app-actions');
|
||||
const reconnectTimerInterval = 5000;
|
||||
const FIRST_RENDER_TOO_LONG_THRESHOLD = 100; // ms
|
||||
|
||||
let socket;
|
||||
let topologyTimer = 0;
|
||||
let controlErrorTimer = 0;
|
||||
let reconnectTimer = 0;
|
||||
let apiDetailsTimer = 0;
|
||||
let continuePolling = true;
|
||||
let firstMessageOnWebsocketAt = null;
|
||||
let createWebsocketAt = null;
|
||||
let currentUrl = null;
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function teardownWebsockets() {
|
||||
clearTimeout(reconnectTimer);
|
||||
if (socket) {
|
||||
socket.onerror = null;
|
||||
socket.onclose = null;
|
||||
socket.onmessage = null;
|
||||
socket.onopen = null;
|
||||
socket.close();
|
||||
socket = null;
|
||||
currentUrl = null;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
function getNodes(getState, dispatch, forceRequest = false) {
|
||||
if (isPausedSelector(getState())) {
|
||||
getNodesOnce(getState, dispatch);
|
||||
} else {
|
||||
updateWebsocketChannel(getState, dispatch, forceRequest);
|
||||
}
|
||||
getNodeDetails(getState, dispatch);
|
||||
}
|
||||
|
||||
export function pauseTimeAtNow() {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: ActionTypes.PAUSE_TIME_AT_NOW
|
||||
});
|
||||
updateRoute(getState);
|
||||
if (!getState().get('nodesLoaded')) {
|
||||
getNodes(getState, dispatch);
|
||||
if (isResourceViewModeSelector(getState())) {
|
||||
getResourceViewNodesSnapshot(getState(), dispatch);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function receiveTopologies(topologies) {
|
||||
return (dispatch, getState) => {
|
||||
const firstLoad = !getState().get('topologiesLoaded');
|
||||
dispatch({
|
||||
topologies,
|
||||
type: ActionTypes.RECEIVE_TOPOLOGIES
|
||||
});
|
||||
getNodes(getState, dispatch);
|
||||
// Populate search matches on first load
|
||||
const state = getState();
|
||||
// Fetch all the relevant nodes once on first load
|
||||
if (firstLoad && isResourceViewModeSelector(state)) {
|
||||
getResourceViewNodesSnapshot(state, dispatch);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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 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 getTopologies(getState, dispatch, forceRequest) {
|
||||
if (isPausedSelector(getState())) {
|
||||
getTopologiesOnce(getState, dispatch);
|
||||
} else {
|
||||
pollTopologies(getState, dispatch, forceRequest);
|
||||
}
|
||||
}
|
||||
|
||||
export function jumpToTime(timestamp) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
timestamp,
|
||||
type: ActionTypes.JUMP_TO_TIME,
|
||||
});
|
||||
updateRoute(getState);
|
||||
getTopologies(getState, dispatch);
|
||||
if (!getState().get('nodesLoaded')) {
|
||||
getNodes(getState, dispatch);
|
||||
if (isResourceViewModeSelector(getState())) {
|
||||
getResourceViewNodesSnapshot(getState(), dispatch);
|
||||
}
|
||||
} else {
|
||||
// Get most recent details before freezing the state.
|
||||
getNodeDetails(getState, dispatch);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function receiveApiDetails(apiDetails) {
|
||||
return (dispatch, getState) => {
|
||||
const isFirstTime = !getState().get('version');
|
||||
const pausedAt = getState().get('pausedAt');
|
||||
|
||||
dispatch({
|
||||
capabilities: fromJS(apiDetails.capabilities || {}),
|
||||
hostname: apiDetails.hostname,
|
||||
newVersion: apiDetails.newVersion,
|
||||
plugins: apiDetails.plugins,
|
||||
type: ActionTypes.RECEIVE_API_DETAILS,
|
||||
version: apiDetails.version,
|
||||
});
|
||||
|
||||
// On initial load either start time travelling at the pausedAt timestamp
|
||||
// (if it was given as URL param) if time travelling is enabled, otherwise
|
||||
// simply pause at the present time which is arguably the next best thing
|
||||
// we could do.
|
||||
// NOTE: We can't make this decision before API details are received because
|
||||
// we have no prior info on whether time travel would be available.
|
||||
if (isFirstTime && pausedAt) {
|
||||
if (apiDetails.capabilities && apiDetails.capabilities.historic_reports) {
|
||||
dispatch(jumpToTime(pausedAt));
|
||||
} else {
|
||||
dispatch(pauseTimeAtNow());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
function stopPolling() {
|
||||
clearTimeout(apiDetailsTimer);
|
||||
clearTimeout(topologyTimer);
|
||||
continuePolling = false;
|
||||
}
|
||||
|
||||
export function focusSearch() {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({ type: ActionTypes.FOCUS_SEARCH });
|
||||
// update nodes cache to allow search across all topologies,
|
||||
// wait a second until animation is over
|
||||
// NOTE: This will cause matching recalculation (and rerendering)
|
||||
// of all the nodes in the topology, instead applying it only on
|
||||
// the nodes delta. The solution would be to implement deeper
|
||||
// search selectors with per-node caching instead of per-topology.
|
||||
setTimeout(() => {
|
||||
getAllNodes(getState(), dispatch);
|
||||
}, 1200);
|
||||
};
|
||||
}
|
||||
|
||||
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 receiveControlPipe(pipeId, nodeId, rawTty, resizeTtyControl, control) {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
if (state.get('nodeDetails').last()
|
||||
&& nodeId !== state.get('nodeDetails').last().id) {
|
||||
log('Node was deselected before we could set up control!');
|
||||
deletePipe(pipeId, dispatch);
|
||||
return;
|
||||
}
|
||||
|
||||
const controlPipe = state.get('controlPipes').last();
|
||||
if (controlPipe && controlPipe.get('id') !== pipeId) {
|
||||
deletePipe(controlPipe.get('id'), dispatch);
|
||||
}
|
||||
|
||||
dispatch({
|
||||
control,
|
||||
nodeId,
|
||||
pipeId,
|
||||
rawTty,
|
||||
resizeTtyControl,
|
||||
type: ActionTypes.RECEIVE_CONTROL_PIPE
|
||||
});
|
||||
|
||||
updateRoute(getState);
|
||||
};
|
||||
}
|
||||
|
||||
function doControlRequest(nodeId, control, dispatch) {
|
||||
clearTimeout(controlErrorTimer);
|
||||
const url = `${getApiPath()}/api/control/${encodeURIComponent(control.probeId)}/`
|
||||
+ `${encodeURIComponent(control.nodeId)}/${control.id}`;
|
||||
doRequest({
|
||||
error: (err) => {
|
||||
dispatch(receiveControlError(nodeId, err.response));
|
||||
controlErrorTimer = setTimeout(() => {
|
||||
dispatch(clearControlError(nodeId));
|
||||
}, 10000);
|
||||
},
|
||||
method: 'POST',
|
||||
success: (res) => {
|
||||
dispatch(receiveControlSuccess(nodeId));
|
||||
if (res) {
|
||||
if (res.pipe) {
|
||||
dispatch(blurSearch());
|
||||
const resizeTtyControl = res.resize_tty_control
|
||||
&& { id: res.resize_tty_control, nodeId: control.nodeId, probeId: control.probeId };
|
||||
dispatch(receiveControlPipe(
|
||||
res.pipe,
|
||||
nodeId,
|
||||
res.raw_tty,
|
||||
resizeTtyControl,
|
||||
control
|
||||
));
|
||||
}
|
||||
if (res.removedNode) {
|
||||
dispatch(receiveControlNodeRemoved(nodeId));
|
||||
}
|
||||
}
|
||||
},
|
||||
url
|
||||
});
|
||||
}
|
||||
|
||||
export function doControl(nodeId, control) {
|
||||
return (dispatch) => {
|
||||
dispatch({
|
||||
control,
|
||||
nodeId,
|
||||
type: ActionTypes.DO_CONTROL
|
||||
});
|
||||
doControlRequest(nodeId, control, dispatch);
|
||||
};
|
||||
}
|
||||
|
||||
export function shutdown() {
|
||||
return (dispatch) => {
|
||||
stopPolling();
|
||||
teardownWebsockets();
|
||||
dispatch({
|
||||
type: ActionTypes.SHUTDOWN
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function setResourceView() {
|
||||
return (dispatch, getState) => {
|
||||
if (resourceViewAvailableSelector(getState())) {
|
||||
dispatch({
|
||||
type: ActionTypes.SET_VIEW_MODE,
|
||||
viewMode: RESOURCE_VIEW_MODE,
|
||||
});
|
||||
// Pin the first metric if none of the visible ones is pinned.
|
||||
const state = getState();
|
||||
if (!pinnedMetricSelector(state)) {
|
||||
const firstAvailableMetricType = availableMetricTypesSelector(state).first();
|
||||
dispatch(pinMetric(firstAvailableMetricType));
|
||||
}
|
||||
getResourceViewNodesSnapshot(getState(), dispatch);
|
||||
updateRoute(getState);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function changeTopologyOption(option, value, topologyId, addOrRemove) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
addOrRemove,
|
||||
option,
|
||||
topologyId,
|
||||
type: ActionTypes.CHANGE_TOPOLOGY_OPTION,
|
||||
value
|
||||
});
|
||||
updateRoute(getState);
|
||||
// update all request workers with new options
|
||||
getTopologies(getState, dispatch);
|
||||
getNodes(getState, dispatch);
|
||||
};
|
||||
}
|
||||
|
||||
export function getTopologiesWithInitialPoll() {
|
||||
return (dispatch, getState) => {
|
||||
getTopologies(getState, dispatch, true);
|
||||
};
|
||||
}
|
||||
|
||||
export function resumeTime() {
|
||||
return (dispatch, getState) => {
|
||||
if (isPausedSelector(getState())) {
|
||||
dispatch({
|
||||
type: ActionTypes.RESUME_TIME
|
||||
});
|
||||
updateRoute(getState);
|
||||
// After unpausing, all of the following calls will re-activate polling.
|
||||
getTopologies(getState, dispatch);
|
||||
getNodes(getState, dispatch, true);
|
||||
if (isResourceViewModeSelector(getState())) {
|
||||
getResourceViewNodesSnapshot(getState(), dispatch);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function route(urlState) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
state: urlState,
|
||||
type: ActionTypes.ROUTE_TOPOLOGY
|
||||
});
|
||||
// Handle Time Travel state update through separate actions as it's more complex.
|
||||
// This is mostly to handle switching contexts Explore <-> Monitor in WC while
|
||||
// the timestamp keeps changing - e.g. if we were Time Travelling in Scope and
|
||||
// then went live in Monitor, switching back to Explore should properly close
|
||||
// the Time Travel etc, not just update the pausedAt state directly.
|
||||
if (!urlState.pausedAt) {
|
||||
dispatch(resumeTime());
|
||||
} else {
|
||||
dispatch(jumpToTime(urlState.pausedAt));
|
||||
}
|
||||
// update all request workers with new options
|
||||
getTopologies(getState, dispatch);
|
||||
getNodes(getState, dispatch);
|
||||
// If we are landing on the resource view page, we need to fetch not only all the
|
||||
// nodes for the current topology, but also the nodes of all the topologies that make
|
||||
// the layers in the resource view.
|
||||
const state = getState();
|
||||
if (isResourceViewModeSelector(state)) {
|
||||
getResourceViewNodesSnapshot(state, dispatch);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function clickCloseDetails(nodeId) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
nodeId,
|
||||
type: ActionTypes.CLICK_CLOSE_DETAILS
|
||||
});
|
||||
// Pull the most recent details for the next details panel that comes into focus.
|
||||
getNodeDetails(getState, dispatch);
|
||||
updateRoute(getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function clickNode(nodeId, label, origin, topologyId = null) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
label,
|
||||
nodeId,
|
||||
origin,
|
||||
topologyId,
|
||||
type: ActionTypes.CLICK_NODE,
|
||||
});
|
||||
updateRoute(getState);
|
||||
getNodeDetails(getState, dispatch);
|
||||
};
|
||||
}
|
||||
|
||||
export function clickRelative(nodeId, topologyId, label, origin) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
label,
|
||||
nodeId,
|
||||
origin,
|
||||
topologyId,
|
||||
type: ActionTypes.CLICK_RELATIVE
|
||||
});
|
||||
updateRoute(getState);
|
||||
getNodeDetails(getState, dispatch);
|
||||
};
|
||||
}
|
||||
|
||||
function updateTopology(dispatch, getState) {
|
||||
const state = getState();
|
||||
// If we're in the resource view, get the snapshot of all the relevant node topologies.
|
||||
if (isResourceViewModeSelector(state)) {
|
||||
getResourceViewNodesSnapshot(state, dispatch);
|
||||
}
|
||||
updateRoute(getState);
|
||||
// NOTE: This is currently not needed for our static resource
|
||||
// view, but we'll need it here later and it's simpler to just
|
||||
// keep it than to redo the nodes delta updating logic.
|
||||
getNodes(getState, dispatch);
|
||||
}
|
||||
|
||||
export function clickShowTopologyForNode(topologyId, nodeId) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
nodeId,
|
||||
topologyId,
|
||||
type: ActionTypes.CLICK_SHOW_TOPOLOGY_FOR_NODE
|
||||
});
|
||||
updateTopology(dispatch, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function clickTopology(topologyId) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
topologyId,
|
||||
type: ActionTypes.CLICK_TOPOLOGY
|
||||
});
|
||||
updateTopology(dispatch, getState);
|
||||
};
|
||||
}
|
||||
@@ -88,12 +88,11 @@ export default class EdgeContainer extends React.PureComponent {
|
||||
...waypointsMap.toJS(),
|
||||
}}>
|
||||
{
|
||||
({ interpolatedThickness, ...interpolatedWaypoints }) =>
|
||||
transformedEdge(
|
||||
forwardedProps,
|
||||
waypointsMapToArray(fromJS(interpolatedWaypoints)),
|
||||
interpolatedThickness
|
||||
)
|
||||
({ interpolatedThickness, ...interpolatedWaypoints }) => transformedEdge(
|
||||
forwardedProps,
|
||||
waypointsMapToArray(fromJS(interpolatedWaypoints)),
|
||||
interpolatedThickness
|
||||
)
|
||||
}
|
||||
</Motion>
|
||||
);
|
||||
|
||||
@@ -7,7 +7,8 @@ import {
|
||||
getMetricValue,
|
||||
getMetricColor,
|
||||
} from '../utils/metric-utils';
|
||||
import { clickNode, enterNode, leaveNode } from '../actions/app-actions';
|
||||
import { clickNode } from '../actions/request-actions';
|
||||
import { enterNode, leaveNode } from '../actions/app-actions';
|
||||
import { trackAnalyticsEvent } from '../utils/tracking-utils';
|
||||
import { getNodeColor } from '../utils/color-utils';
|
||||
import MatchedResults from '../components/matched-results';
|
||||
|
||||
@@ -63,9 +63,9 @@ class NodesChartElements extends React.Component {
|
||||
nodeDisplayLayer(node) {
|
||||
if (node.get('id') === this.props.mouseOverNodeId) {
|
||||
return HOVERED_NODES_LAYER;
|
||||
} else if (node.get('blurred') && !node.get('focused')) {
|
||||
} if (node.get('blurred') && !node.get('focused')) {
|
||||
return BLURRED_NODES_LAYER;
|
||||
} else if (node.get('highlighted')) {
|
||||
} if (node.get('highlighted')) {
|
||||
return HIGHLIGHTED_NODES_LAYER;
|
||||
}
|
||||
return NORMAL_NODES_LAYER;
|
||||
@@ -74,9 +74,9 @@ class NodesChartElements extends React.Component {
|
||||
edgeDisplayLayer(edge) {
|
||||
if (edge.get('id') === this.props.mouseOverEdgeId) {
|
||||
return HOVERED_EDGES_LAYER;
|
||||
} else if (edge.get('blurred') && !edge.get('focused')) {
|
||||
} if (edge.get('blurred') && !edge.get('focused')) {
|
||||
return BLURRED_EDGES_LAYER;
|
||||
} else if (edge.get('highlighted')) {
|
||||
} if (edge.get('highlighted')) {
|
||||
return HIGHLIGHTED_EDGES_LAYER;
|
||||
}
|
||||
return NORMAL_EDGES_LAYER;
|
||||
@@ -140,8 +140,8 @@ class NodesChartElements extends React.Component {
|
||||
const sourceInNetwork = selectedNetworkNodesIds.contains(edge.get('source'));
|
||||
const targetInNetwork = selectedNetworkNodesIds.contains(edge.get('target'));
|
||||
const notInNetwork = this.props.selectedNetwork && (!sourceInNetwork || !targetInNetwork);
|
||||
return edge.set('blurred', !edge.get('highlighted') && !edge.get('focused') &&
|
||||
(otherNodesSelected || notMatched || notInNetwork));
|
||||
return edge.set('blurred', !edge.get('highlighted') && !edge.get('focused')
|
||||
&& (otherNodesSelected || notMatched || notInNetwork));
|
||||
}
|
||||
|
||||
edgeScaleDecorator(edge) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* eslint react/jsx-no-bind: "off", no-multi-comp: "off" */
|
||||
/* eslint react/jsx-no-bind: "off" */
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { connect } from 'react-redux';
|
||||
@@ -6,7 +6,8 @@ import { List as makeList, Map as makeMap } from 'immutable';
|
||||
import capitalize from 'lodash/capitalize';
|
||||
|
||||
import NodeDetailsTable from '../components/node-details/node-details-table';
|
||||
import { clickNode, sortOrderChanged } from '../actions/app-actions';
|
||||
import { clickNode } from '../actions/request-actions';
|
||||
import { sortOrderChanged } from '../actions/app-actions';
|
||||
import { shownNodesSelector } from '../selectors/node-filters';
|
||||
import { trackAnalyticsEvent } from '../utils/tracking-utils';
|
||||
import { findTopologyById } from '../utils/topology-utils';
|
||||
@@ -55,7 +56,11 @@ function getColumns(nodes, topologies) {
|
||||
.toList()
|
||||
.flatMap((n) => {
|
||||
const metadata = (n.get('metadata') || makeList())
|
||||
.map(m => makeMap({ dataType: m.get('dataType'), id: m.get('id'), label: m.get('label') }));
|
||||
.map(m => makeMap({
|
||||
dataType: m.get('dataType'),
|
||||
id: m.get('id'),
|
||||
label: m.get('label')
|
||||
}));
|
||||
return metadata;
|
||||
})
|
||||
.toSet()
|
||||
@@ -68,7 +73,10 @@ function getColumns(nodes, topologies) {
|
||||
.toList()
|
||||
.flatMap((n) => {
|
||||
const metadata = (n.get('parents') || makeList())
|
||||
.map(m => makeMap({ id: m.get('topologyId'), label: topologyLabel(topologies, m.get('topologyId')) }));
|
||||
.map(m => makeMap({
|
||||
id: m.get('topologyId'),
|
||||
label: topologyLabel(topologies, m.get('topologyId'))
|
||||
}));
|
||||
return metadata;
|
||||
})
|
||||
.toSet()
|
||||
@@ -91,7 +99,9 @@ function renderIdCell({
|
||||
<Icon color={getNodeColor(rank, label)} />
|
||||
</div>
|
||||
<div className="truncate">
|
||||
{label} {showSubLabel && <span className="nodes-grid-label-minor">{labelMinor}</span>}
|
||||
{label}
|
||||
{' '}
|
||||
{showSubLabel && <span className="nodes-grid-label-minor">{labelMinor}</span>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -126,8 +136,9 @@ class NodesGrid extends React.Component {
|
||||
const {
|
||||
nodes, gridSortedBy, gridSortedDesc, searchNodeMatches, searchQuery, windowHeight, topologies
|
||||
} = this.props;
|
||||
const height =
|
||||
this.tableRef ? windowHeight - this.tableRef.getBoundingClientRect().top - 30 : 0;
|
||||
const height = this.tableRef
|
||||
? windowHeight - this.tableRef.getBoundingClientRect().top - 30
|
||||
: 0;
|
||||
const cmpStyle = {
|
||||
height,
|
||||
paddingLeft: 40,
|
||||
@@ -152,20 +163,22 @@ class NodesGrid extends React.Component {
|
||||
|
||||
return (
|
||||
<div className="nodes-grid" ref={this.saveTableRef}>
|
||||
{nodes.size > 0 && <NodeDetailsTable
|
||||
style={cmpStyle}
|
||||
className={className}
|
||||
renderIdCell={renderIdCell}
|
||||
tbodyStyle={tbodyStyle}
|
||||
topologyId={this.props.currentTopologyId}
|
||||
onSortChange={this.onSortChange}
|
||||
onClickRow={this.onClickRow}
|
||||
sortedBy={gridSortedBy}
|
||||
sortedDesc={gridSortedDesc}
|
||||
selectedNodeId={this.props.selectedNodeId}
|
||||
limit={1000}
|
||||
{...detailsData}
|
||||
/>}
|
||||
{nodes.size > 0 && (
|
||||
<NodeDetailsTable
|
||||
style={cmpStyle}
|
||||
className={className}
|
||||
renderIdCell={renderIdCell}
|
||||
tbodyStyle={tbodyStyle}
|
||||
topologyId={this.props.currentTopologyId}
|
||||
onSortChange={this.onSortChange}
|
||||
onClickRow={this.onClickRow}
|
||||
sortedBy={gridSortedBy}
|
||||
sortedDesc={gridSortedDesc}
|
||||
selectedNodeId={this.props.selectedNodeId}
|
||||
limit={1000}
|
||||
{...detailsData}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -408,7 +408,7 @@ function copyLayoutProperties(layout, nodeCache, edgeCache) {
|
||||
if (edgeCache.has(edge.get('id'))
|
||||
&& hasSameEndpoints(edgeCache.get(edge.get('id')), result.nodes)) {
|
||||
return edge.merge(edgeCache.get(edge.get('id')));
|
||||
} else if (nodeCache.get(edge.get('source')) && nodeCache.get(edge.get('target'))) {
|
||||
} if (nodeCache.get(edge.get('source')) && nodeCache.get(edge.get('target'))) {
|
||||
return setSimpleEdgePoints(edge, nodeCache);
|
||||
}
|
||||
return edge;
|
||||
|
||||
@@ -18,9 +18,7 @@ import Status from './status';
|
||||
import Topologies from './topologies';
|
||||
import TopologyOptions from './topology-options';
|
||||
import Overlay from './overlay';
|
||||
import { getApiDetails } from '../utils/web-api-utils';
|
||||
import {
|
||||
focusSearch,
|
||||
pinNextMetric,
|
||||
pinPreviousMetric,
|
||||
hitEsc,
|
||||
@@ -29,12 +27,16 @@ import {
|
||||
setGraphView,
|
||||
setMonitorState,
|
||||
setTableView,
|
||||
setResourceView,
|
||||
setStoreViewState,
|
||||
shutdown,
|
||||
setViewportDimensions,
|
||||
getTopologiesWithInitialPoll,
|
||||
} from '../actions/app-actions';
|
||||
import {
|
||||
focusSearch,
|
||||
getApiDetails,
|
||||
setResourceView,
|
||||
getTopologiesWithInitialPoll,
|
||||
shutdown,
|
||||
} from '../actions/request-actions';
|
||||
import Details from './details';
|
||||
import Nodes from './nodes';
|
||||
import TimeControl from './time-control';
|
||||
@@ -42,7 +44,8 @@ import TimeTravelWrapper from './time-travel-wrapper';
|
||||
import ViewModeSelector from './view-mode-selector';
|
||||
import NetworkSelector from './networks-selector';
|
||||
import DebugToolbar, { showingDebugToolbar, toggleDebugToolbar } from './debug-toolbar';
|
||||
import { getRouter, getUrlState } from '../utils/router-utils';
|
||||
import { getUrlState } from '../utils/router-utils';
|
||||
import { getRouter } from '../router';
|
||||
import { trackAnalyticsEvent } from '../utils/tracking-utils';
|
||||
import { availableNetworksSelector } from '../selectors/node-networks';
|
||||
import { timeTravelSupportedSelector } from '../selectors/time-travel';
|
||||
@@ -210,19 +213,23 @@ class App extends React.Component {
|
||||
|
||||
{showingTroubleshootingMenu && <TroubleshootingMenu />}
|
||||
|
||||
{showingDetails && <Details
|
||||
{showingDetails && (
|
||||
<Details
|
||||
renderNodeDetailsExtras={this.props.renderNodeDetailsExtras}
|
||||
/>}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="header">
|
||||
{timeTravelSupported && this.props.renderTimeTravel()}
|
||||
|
||||
<div className="selectors">
|
||||
<div className="logo">
|
||||
{!isIframe &&
|
||||
{!isIframe
|
||||
&& (
|
||||
<svg width="100%" height="100%" viewBox="0 0 1089 217">
|
||||
<Logo />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<Search />
|
||||
|
||||
@@ -26,6 +26,8 @@ class CloudFeature extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
/* eslint-disable react/forbid-prop-types */
|
||||
// TODO: Remove this component as part of https://github.com/weaveworks/scope/issues/3278.
|
||||
CloudFeature.contextTypes = {
|
||||
router: PropTypes.object,
|
||||
serviceStore: PropTypes.object,
|
||||
@@ -36,5 +38,6 @@ CloudFeature.childContextTypes = {
|
||||
router: PropTypes.object,
|
||||
store: PropTypes.object
|
||||
};
|
||||
/* eslint-enable react/forbid-prop-types */
|
||||
|
||||
export default connect()(CloudFeature);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/* eslint react/jsx-no-bind: "off" */
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { sampleSize, sample, random, range, flattenDeep, times } from 'lodash';
|
||||
import {
|
||||
sampleSize, sample, random, range, flattenDeep, times
|
||||
} from 'lodash';
|
||||
import { fromJS, Set as makeSet } from 'immutable';
|
||||
import { hsl } from 'd3-color';
|
||||
import debug from 'debug';
|
||||
@@ -17,7 +19,7 @@ const STACK_VARIANTS = [false, true];
|
||||
const METRIC_FILLS = [0, 0.1, 50, 99.9, 100];
|
||||
const NETWORKS = [
|
||||
'be', 'fe', 'zb', 'db', 're', 'gh', 'jk', 'lol', 'nw'
|
||||
].map(n => ({colorKey: n, id: n, label: n}));
|
||||
].map(n => ({ colorKey: n, id: n, label: n }));
|
||||
|
||||
const INTERNET = 'the-internet';
|
||||
const LOREM = `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
|
||||
@@ -58,7 +60,7 @@ const deltaAdd = (name, adjacency = [], shape = 'circle', stack = false, network
|
||||
|
||||
function addMetrics(availableMetrics, node, v) {
|
||||
const metrics = availableMetrics.size > 0 ? availableMetrics : fromJS([
|
||||
{id: 'host_cpu_usage_percent', label: 'CPU'}
|
||||
{ id: 'host_cpu_usage_percent', label: 'CPU' }
|
||||
]);
|
||||
|
||||
return Object.assign({}, node, {
|
||||
@@ -151,13 +153,13 @@ class DebugToolbar extends React.Component {
|
||||
}
|
||||
|
||||
onChange(ev) {
|
||||
this.setState({nodesToAdd: parseInt(ev.target.value, 10)});
|
||||
this.setState({ nodesToAdd: parseInt(ev.target.value, 10) });
|
||||
}
|
||||
|
||||
toggleColors() {
|
||||
this.setState({
|
||||
showColors: !this.state.showColors
|
||||
});
|
||||
this.setState(prevState => ({
|
||||
showColors: !prevState.showColors
|
||||
}));
|
||||
}
|
||||
|
||||
asyncDispatch(v) {
|
||||
@@ -283,46 +285,54 @@ class DebugToolbar extends React.Component {
|
||||
<div className="debug-panel">
|
||||
<div>
|
||||
<strong>Add nodes </strong>
|
||||
<button onClick={() => this.addNodes(1)}>+1</button>
|
||||
<button onClick={() => this.addNodes(10)}>+10</button>
|
||||
<button type="button" onClick={() => this.addNodes(1)}>+1</button>
|
||||
<button type="button" onClick={() => this.addNodes(10)}>+10</button>
|
||||
<input type="number" onChange={this.onChange} value={this.state.nodesToAdd} />
|
||||
<button onClick={() => this.addNodes(this.state.nodesToAdd)}>+</button>
|
||||
<button onClick={() => this.asyncDispatch(addAllVariants)}>Variants</button>
|
||||
<button onClick={() => this.asyncDispatch(addAllMetricVariants(availableMetrics))}>
|
||||
<button type="button" onClick={() => this.addNodes(this.state.nodesToAdd)}>+</button>
|
||||
<button type="button" onClick={() => this.asyncDispatch(addAllVariants)}>
|
||||
Variants
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => this.asyncDispatch(addAllMetricVariants(availableMetrics))}>
|
||||
Metric Variants
|
||||
</button>
|
||||
<button onClick={() => this.addNodes(1, LOREM)}>Long name</button>
|
||||
<button onClick={() => this.addInternetNode()}>Internet</button>
|
||||
<button onClick={() => this.removeNode()}>Remove node</button>
|
||||
<button onClick={() => this.updateAdjacencies()}>Update adj.</button>
|
||||
<button type="button" onClick={() => this.addNodes(1, LOREM)}>Long name</button>
|
||||
<button type="button" onClick={() => this.addInternetNode()}>Internet</button>
|
||||
<button type="button" onClick={() => this.removeNode()}>Remove node</button>
|
||||
<button type="button" onClick={() => this.updateAdjacencies()}>Update adj.</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong>Logging </strong>
|
||||
<button onClick={() => enableLog('*')}>scope:*</button>
|
||||
<button onClick={() => enableLog('dispatcher')}>scope:dispatcher</button>
|
||||
<button onClick={() => enableLog('app-key-press')}>scope:app-key-press</button>
|
||||
<button onClick={() => enableLog('terminal')}>scope:terminal</button>
|
||||
<button onClick={() => disableLog()}>Disable log</button>
|
||||
<button type="button" onClick={() => enableLog('*')}>scope:*</button>
|
||||
<button type="button" onClick={() => enableLog('dispatcher')}>scope:dispatcher</button>
|
||||
<button type="button" onClick={() => enableLog('app-key-press')}>
|
||||
scope:app-key-press
|
||||
</button>
|
||||
<button type="button" onClick={() => enableLog('terminal')}>scope:terminal</button>
|
||||
<button type="button" onClick={() => disableLog()}>Disable log</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong>Colors </strong>
|
||||
<button onClick={this.toggleColors}>toggle</button>
|
||||
<button type="button" onClick={this.toggleColors}>toggle</button>
|
||||
</div>
|
||||
|
||||
{this.state.showColors &&
|
||||
<table>
|
||||
<tbody>
|
||||
{LABEL_PREFIXES.map(r => (
|
||||
<tr key={r}>
|
||||
<td
|
||||
title={`${r}`}
|
||||
style={{backgroundColor: hsl(text2degree(r), 0.5, 0.5).toString()}} />
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>}
|
||||
{this.state.showColors
|
||||
&& (
|
||||
<table>
|
||||
<tbody>
|
||||
{LABEL_PREFIXES.map(r => (
|
||||
<tr key={r}>
|
||||
<td
|
||||
title={`${r}`}
|
||||
style={{ backgroundColor: hsl(text2degree(r), 0.5, 0.5).toString() }} />
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
|
||||
{this.state.showColors && [getNodeColor, getNodeColorDark].map(fn => (
|
||||
<table key={fn}>
|
||||
@@ -330,7 +340,7 @@ class DebugToolbar extends React.Component {
|
||||
{LABEL_PREFIXES.map(r => (
|
||||
<tr key={r}>
|
||||
{LABEL_PREFIXES.map(c => (
|
||||
<td key={c} title={`(${r}, ${c})`} style={{backgroundColor: fn(r, c)}} />
|
||||
<td key={c} title={`(${r}, ${c})`} style={{ backgroundColor: fn(r, c) }} />
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
@@ -340,14 +350,20 @@ class DebugToolbar extends React.Component {
|
||||
|
||||
<div>
|
||||
<strong>State </strong>
|
||||
<button onClick={() => this.setLoading(true)}>Set doing initial load</button>
|
||||
<button onClick={() => this.setLoading(false)}>Stop</button>
|
||||
<button type="button" onClick={() => this.setLoading(true)}>
|
||||
Set doing initial load
|
||||
</button>
|
||||
<button type="button" onClick={() => this.setLoading(false)}>Stop</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong>Short-lived nodes </strong>
|
||||
<button onClick={() => this.setShortLived()}>Toggle short-lived nodes</button>
|
||||
<button onClick={() => this.setIntermittent()}>Toggle intermittent nodes</button>
|
||||
<button type="button" onClick={() => this.setShortLived()}>
|
||||
Toggle short-lived nodes
|
||||
</button>
|
||||
<button type="button" onClick={() => this.setIntermittent()}>
|
||||
Toggle intermittent nodes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -31,8 +31,8 @@ class EmeddedTerminal extends React.Component {
|
||||
}
|
||||
|
||||
getTransform() {
|
||||
const dx = this.state.mounted ? 0 :
|
||||
window.innerWidth - DETAILS_PANEL_WIDTH - DETAILS_PANEL_MARGINS.right;
|
||||
const dx = this.state.mounted ? 0
|
||||
: window.innerWidth - DETAILS_PANEL_WIDTH - DETAILS_PANEL_MARGINS.right;
|
||||
return `translateX(${dx}px)`;
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ class EmeddedTerminal extends React.Component {
|
||||
<div
|
||||
onTransitionEnd={this.handleTransitionEnd}
|
||||
className="terminal-animation-wrapper"
|
||||
style={{transform: this.getTransform()}} >
|
||||
style={{transform: this.getTransform()}}>
|
||||
<Terminal
|
||||
key={pipe.get('id')}
|
||||
pipe={pipe}
|
||||
|
||||
@@ -42,15 +42,19 @@ class Footer extends React.Component {
|
||||
return (
|
||||
<div className="footer">
|
||||
<div className="footer-status">
|
||||
{versionUpdate &&
|
||||
<a
|
||||
className="footer-versionupdate"
|
||||
title={versionUpdateTitle}
|
||||
href={versionUpdate.get('downloadUrl')}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
Update available: {versionUpdate.get('version')}
|
||||
</a>
|
||||
{versionUpdate
|
||||
&& (
|
||||
<a
|
||||
className="footer-versionupdate"
|
||||
title={versionUpdateTitle}
|
||||
href={versionUpdate.get('downloadUrl')}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
Update available:
|
||||
{' '}
|
||||
{versionUpdate.get('version')}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
<span className="footer-label">Version</span>
|
||||
{version || '...'}
|
||||
@@ -64,15 +68,21 @@ class Footer extends React.Component {
|
||||
|
||||
<div className="footer-tools">
|
||||
<button
|
||||
type="button"
|
||||
className="footer-icon"
|
||||
onClick={this.handleRelayoutClick}
|
||||
title={forceRelayoutTitle}>
|
||||
<i className="fa fa-sync" />
|
||||
</button>
|
||||
<button onClick={this.handleContrastClick} className="footer-icon" title={otherContrastModeTitle}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={this.handleContrastClick}
|
||||
className="footer-icon"
|
||||
title={otherContrastModeTitle}>
|
||||
<i className="fa fa-adjust" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={this.props.toggleTroubleshootingMenu}
|
||||
className="footer-icon"
|
||||
title="Open troubleshooting menu"
|
||||
@@ -80,7 +90,11 @@ class Footer extends React.Component {
|
||||
>
|
||||
<i className="fa fa-bug" />
|
||||
</button>
|
||||
<button className="footer-icon" onClick={this.props.toggleHelp} title="Show help">
|
||||
<button
|
||||
type="button"
|
||||
className="footer-icon"
|
||||
onClick={this.props.toggleHelp}
|
||||
title="Show help">
|
||||
<i className="fa fa-question" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -7,26 +7,26 @@ import { hideHelp } from '../actions/app-actions';
|
||||
|
||||
|
||||
const GENERAL_SHORTCUTS = [
|
||||
{key: 'esc', label: 'Close active panel'},
|
||||
{key: '/', label: 'Activate search field'},
|
||||
{key: '?', label: 'Toggle shortcut menu'},
|
||||
{key: 'g', label: 'Switch to Graph view'},
|
||||
{key: 't', label: 'Switch to Table view'},
|
||||
{key: 'r', label: 'Switch to Resources view'},
|
||||
{ key: 'esc', label: 'Close active panel' },
|
||||
{ key: '/', label: 'Activate search field' },
|
||||
{ key: '?', label: 'Toggle shortcut menu' },
|
||||
{ key: 'g', label: 'Switch to Graph view' },
|
||||
{ key: 't', label: 'Switch to Table view' },
|
||||
{ key: 'r', label: 'Switch to Resources view' },
|
||||
];
|
||||
|
||||
|
||||
const CANVAS_METRIC_SHORTCUTS = [
|
||||
{key: '<', label: 'Select and pin previous metric'},
|
||||
{key: '>', label: 'Select and pin next metric'},
|
||||
{key: 'q', label: 'Unpin current metric'},
|
||||
{ key: '<', label: 'Select and pin previous metric' },
|
||||
{ key: '>', label: 'Select and pin next metric' },
|
||||
{ key: 'q', label: 'Unpin current metric' },
|
||||
];
|
||||
|
||||
|
||||
function renderShortcuts(cuts) {
|
||||
return (
|
||||
<div>
|
||||
{cuts.map(({key, label}) => (
|
||||
{cuts.map(({ key, label }) => (
|
||||
<div key={key} className="help-panel-shortcuts-shortcut">
|
||||
<div className="key"><kbd>{key}</kbd></div>
|
||||
<div className="label">{label}</div>
|
||||
@@ -51,9 +51,16 @@ function renderShortcutPanel() {
|
||||
|
||||
|
||||
const BASIC_SEARCHES = [
|
||||
{label: 'All fields for foo', term: 'foo'},
|
||||
{ label: 'All fields for foo', term: 'foo' },
|
||||
{
|
||||
label: <span>Any field matching <b>pid</b> for the value 12345</span>,
|
||||
label: (
|
||||
<span>
|
||||
Any field matching
|
||||
<b>pid</b>
|
||||
{' '}
|
||||
for the value 12345
|
||||
</span>
|
||||
),
|
||||
term: 'pid: 12345'
|
||||
},
|
||||
];
|
||||
@@ -65,16 +72,37 @@ const REGEX_SEARCHES = [
|
||||
term: 'foo|bar'
|
||||
},
|
||||
{
|
||||
label: <span><b>command</b> field for foobar or foobaz</span>,
|
||||
label: (
|
||||
<span>
|
||||
<b>command</b>
|
||||
{' '}
|
||||
field for foobar or foobaz
|
||||
</span>
|
||||
),
|
||||
term: 'command: foo(bar|baz)'
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
const METRIC_SEARCHES = [
|
||||
{label: <span><b>CPU</b> greater than 4%</span>, term: 'cpu > 4%'},
|
||||
{
|
||||
label: <span><b>Memory</b> less than 10 megabytes</span>,
|
||||
label: (
|
||||
<span>
|
||||
<b>CPU</b>
|
||||
{' '}
|
||||
greater than 4%
|
||||
</span>
|
||||
),
|
||||
term: 'cpu > 4%'
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<span>
|
||||
<b>Memory</b>
|
||||
{' '}
|
||||
less than 10 megabytes
|
||||
</span>
|
||||
),
|
||||
term: 'memory < 10mb'
|
||||
},
|
||||
];
|
||||
@@ -83,7 +111,7 @@ const METRIC_SEARCHES = [
|
||||
function renderSearches(searches) {
|
||||
return (
|
||||
<div>
|
||||
{searches.map(({term, label}) => (
|
||||
{searches.map(({ term, label }) => (
|
||||
<div key={term} className="help-panel-search-row">
|
||||
<div className="help-panel-search-row-term">
|
||||
<i className="fa fa-search search-label-icon" />
|
||||
@@ -117,7 +145,7 @@ function renderSearchPanel() {
|
||||
|
||||
function renderFieldsPanel(currentTopologyName, searchableFields) {
|
||||
const none = (
|
||||
<span style={{fontStyle: 'italic'}}>None</span>
|
||||
<span style={{ fontStyle: 'italic' }}>None</span>
|
||||
);
|
||||
const currentTopology = (
|
||||
<span className="help-panel-fields-current-topology">
|
||||
@@ -129,8 +157,14 @@ function renderFieldsPanel(currentTopologyName, searchableFields) {
|
||||
<div className="help-panel-fields">
|
||||
<h2>Fields and Metrics</h2>
|
||||
<p>
|
||||
Searchable fields and metrics in the <br />
|
||||
currently selected {currentTopology} topology:
|
||||
Searchable fields and metrics in the
|
||||
{' '}
|
||||
<br />
|
||||
currently selected
|
||||
{' '}
|
||||
{currentTopology}
|
||||
{' '}
|
||||
topology:
|
||||
</p>
|
||||
<div className="help-panel-fields-fields">
|
||||
<div className="help-panel-fields-fields-column">
|
||||
@@ -162,7 +196,7 @@ function HelpPanel({
|
||||
}) {
|
||||
return (
|
||||
<div className="help-panel-wrapper">
|
||||
<div className="help-panel" style={{marginTop: canvasMargins.top}}>
|
||||
<div className="help-panel" style={{ marginTop: canvasMargins.top }}>
|
||||
<div className="help-panel-header">
|
||||
<h2>Help</h2>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,8 @@ const Match = (searchTerms, match) => (
|
||||
<div className="matched-results-match" key={match.label}>
|
||||
<div className="matched-results-match-wrapper">
|
||||
<span className="matched-results-match-label">
|
||||
{match.label}:
|
||||
{match.label}
|
||||
:
|
||||
</span>
|
||||
<MatchedText
|
||||
text={match.text}
|
||||
@@ -42,10 +43,12 @@ export default class MatchedResults extends React.PureComponent {
|
||||
.take(SHOW_ROW_COUNT)
|
||||
.map(fieldId => Match(searchTerms, matches.get(fieldId)))
|
||||
}
|
||||
{moreFieldMatches &&
|
||||
{moreFieldMatches
|
||||
&& (
|
||||
<div className="matched-results-more" title={moreFieldMatchesTitle}>
|
||||
{`${moreFieldMatches.size} more matches`}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -22,7 +22,8 @@ class MetricSelector extends React.Component {
|
||||
|
||||
return (
|
||||
<div className="metric-selector">
|
||||
{hasMetrics &&
|
||||
{hasMetrics
|
||||
&& (
|
||||
<div className="metric-selector-wrapper" onMouseLeave={this.onMouseOut}>
|
||||
{availableMetrics.map(metric => (
|
||||
<MetricSelectorItem
|
||||
@@ -31,6 +32,7 @@ class MetricSelector extends React.Component {
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -6,7 +6,7 @@ import { connect } from 'react-redux';
|
||||
import { Map as makeMap } from 'immutable';
|
||||
import { noop } from 'lodash';
|
||||
|
||||
import { clickCloseDetails, clickShowTopologyForNode } from '../actions/app-actions';
|
||||
import { clickCloseDetails, clickShowTopologyForNode } from '../actions/request-actions';
|
||||
import { brightenColor, getNeutralColor, getNodeColorDark } from '../utils/color-utils';
|
||||
import { isGenericTable, isPropertyList } from '../utils/node-details-utils';
|
||||
import { resetDocumentTitle, setDocumentTitle } from '../utils/title-utils';
|
||||
@@ -56,13 +56,18 @@ class NodeDetails extends React.Component {
|
||||
return (
|
||||
<div className="node-details-tools-wrapper">
|
||||
<div className="node-details-tools">
|
||||
{showSwitchTopology &&
|
||||
{showSwitchTopology
|
||||
&& (
|
||||
<i
|
||||
title={topologyTitle}
|
||||
className="fa fa-long-arrow-alt-left"
|
||||
onClick={this.handleShowTopologyForNode}>
|
||||
<span>Show in <span>{this.props.topologyId.replace(/-/g, ' ')}</span></span>
|
||||
<span>
|
||||
Show in
|
||||
<span>{this.props.topologyId.replace(/-/g, ' ')}</span>
|
||||
</span>
|
||||
</i>
|
||||
)
|
||||
}
|
||||
<i
|
||||
title="Close details"
|
||||
@@ -81,9 +86,9 @@ class NodeDetails extends React.Component {
|
||||
// mounted, the spinner is displayed blurred the whole time in Chrome (possibly
|
||||
// caused by a bug having to do with animating the details panel).
|
||||
const spinnerClassName = classNames('fa fa-circle-notch', { 'fa-spin': this.props.mounted });
|
||||
const nodeColor = (node ?
|
||||
getNodeColorDark(node.get('rank'), label, node.get('pseudo')) :
|
||||
getNeutralColor());
|
||||
const nodeColor = (node
|
||||
? getNodeColorDark(node.get('rank'), label, node.get('pseudo'))
|
||||
: getNeutralColor());
|
||||
const tools = this.renderTools();
|
||||
const styles = {
|
||||
header: {
|
||||
@@ -130,7 +135,9 @@ class NodeDetails extends React.Component {
|
||||
</div>
|
||||
<div className="node-details-content">
|
||||
<p className="node-details-content-info">
|
||||
<strong>{this.props.label}</strong> not found!
|
||||
<strong>{this.props.label}</strong>
|
||||
{' '}
|
||||
not found!
|
||||
</p>
|
||||
</div>
|
||||
<Overlay faded={this.props.transitioning} />
|
||||
@@ -176,14 +183,17 @@ class NodeDetails extends React.Component {
|
||||
<MatchedText text={details.label} match={nodeMatches.get('label')} />
|
||||
</h2>
|
||||
<div className="node-details-header-relatives">
|
||||
{details.parents && <NodeDetailsRelatives
|
||||
{details.parents && (
|
||||
<NodeDetailsRelatives
|
||||
matches={nodeMatches.get('parents')}
|
||||
relatives={details.parents} />}
|
||||
relatives={details.parents} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showControls &&
|
||||
{showControls
|
||||
&& (
|
||||
<div className="tour-step-anchor node-details-controls-wrapper" style={styles.controls}>
|
||||
<NodeDetailsControls
|
||||
nodeId={this.props.nodeId}
|
||||
@@ -191,10 +201,12 @@ class NodeDetails extends React.Component {
|
||||
pending={pending}
|
||||
error={error} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<div className="node-details-content">
|
||||
{details.metrics &&
|
||||
{details.metrics
|
||||
&& (
|
||||
<div className="node-details-content-section">
|
||||
<div className="node-details-content-section-header">Status</div>
|
||||
<NodeDetailsHealth
|
||||
@@ -202,12 +214,15 @@ class NodeDetails extends React.Component {
|
||||
topologyId={topologyId}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{details.metadata &&
|
||||
{details.metadata
|
||||
&& (
|
||||
<div className="node-details-content-section">
|
||||
<div className="node-details-content-section-header">Info</div>
|
||||
<NodeDetailsInfo rows={details.metadata} matches={nodeMatches.get('metadata')} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
{details.connections && details.connections.filter(cs => cs.connections.length > 0)
|
||||
@@ -219,7 +234,7 @@ class NodeDetails extends React.Component {
|
||||
nodeIdKey="nodeId"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
|
||||
{details.children && details.children.map(children => (
|
||||
<div className="node-details-content-section" key={children.topologyId}>
|
||||
@@ -233,11 +248,13 @@ class NodeDetails extends React.Component {
|
||||
<div className="node-details-content-section" key={table.id}>
|
||||
<div className="node-details-content-section-header">
|
||||
{table.label && table.label.length > 0 && table.label}
|
||||
{table.truncationCount > 0 &&
|
||||
{table.truncationCount > 0
|
||||
&& (
|
||||
<span
|
||||
className="node-details-content-section-header-warning">
|
||||
<Warning text={getTruncationText(table.truncationCount)} />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{this.renderTable(table)}
|
||||
@@ -266,7 +283,7 @@ class NodeDetails extends React.Component {
|
||||
matches={nodeMatches.get('tables')}
|
||||
/>
|
||||
);
|
||||
} else if (isPropertyList(table)) {
|
||||
} if (isPropertyList(table)) {
|
||||
return (
|
||||
<NodeDetailsPropertyList
|
||||
rows={table.rows}
|
||||
|
||||
@@ -70,8 +70,9 @@ describe('NodeDetailsTable', () => {
|
||||
// (that is, first by row and then by column), the indexes we are interested in are of the
|
||||
// form columnIndex + n * columns.length, where n >= 0. Therefore we take only the values
|
||||
// at the index which divided by columns.length gives a reminder columnIndex.
|
||||
const filteredValues = values.filter((element, index) =>
|
||||
index % columns.length === columnIndex);
|
||||
const filteredValues = values.filter(
|
||||
(element, index) => index % columns.length === columnIndex
|
||||
);
|
||||
// Array comparison
|
||||
expect(filteredValues).toEqual(expectedValues);
|
||||
}
|
||||
@@ -91,7 +92,7 @@ describe('NodeDetailsTable', () => {
|
||||
sortedBy="kubernetes_ip"
|
||||
nodeIdKey="id"
|
||||
nodes={nodes}
|
||||
/>
|
||||
/>
|
||||
</Provider>
|
||||
));
|
||||
|
||||
@@ -127,7 +128,7 @@ describe('NodeDetailsTable', () => {
|
||||
sortedBy="kubernetes_namespace"
|
||||
nodeIdKey="id"
|
||||
nodes={nodes}
|
||||
/>
|
||||
/>
|
||||
</Provider>
|
||||
));
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { isEmpty } from 'lodash';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { trackAnalyticsEvent } from '../../utils/tracking-utils';
|
||||
import { doControl } from '../../actions/app-actions';
|
||||
import { doControl } from '../../actions/request-actions';
|
||||
|
||||
class NodeDetailsControlButton extends React.Component {
|
||||
constructor(props, context) {
|
||||
|
||||
@@ -15,11 +15,13 @@ export default function NodeDetailsControls({
|
||||
|
||||
return (
|
||||
<div className="node-details-controls">
|
||||
{error &&
|
||||
{error
|
||||
&& (
|
||||
<div className="node-details-controls-error" title={error}>
|
||||
<i className="node-details-controls-error-icon fa fa-exclamation-triangle" />
|
||||
<span className="node-details-controls-error-messages">{error}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<span className="node-details-controls-buttons">
|
||||
{sortBy(controls, 'rank').map(control => (
|
||||
|
||||
@@ -46,9 +46,9 @@ export default class NodeDetailsGenericTable extends React.Component {
|
||||
}
|
||||
|
||||
handleLimitClick() {
|
||||
this.setState({
|
||||
limit: this.state.limit ? 0 : NODE_DETAILS_DATA_ROWS_DEFAULT_LIMIT
|
||||
});
|
||||
this.setState(prevState => ({
|
||||
limit: prevState.limit ? 0 : NODE_DETAILS_DATA_ROWS_DEFAULT_LIMIT
|
||||
}));
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -62,8 +62,11 @@ export default class NodeDetailsGenericTable extends React.Component {
|
||||
// If there are rows that would be hidden behind 'show more', keep them
|
||||
// expanded if any of them match the search query; otherwise hide them.
|
||||
if (this.state.limit > 0 && rows.length > this.state.limit) {
|
||||
const hasHiddenMatch = rows.slice(this.state.limit).some(row =>
|
||||
columns.some(column => matches.has(genericTableEntryKey(row, column))));
|
||||
const hasHiddenMatch = rows
|
||||
.slice(this.state.limit)
|
||||
.some(
|
||||
row => columns.some(column => matches.has(genericTableEntryKey(row, column)))
|
||||
);
|
||||
if (!hasHiddenMatch) {
|
||||
notShown = rows.length - NODE_DETAILS_DATA_ROWS_DEFAULT_LIMIT;
|
||||
rows = rows.slice(0, this.state.limit);
|
||||
@@ -94,15 +97,17 @@ export default class NodeDetailsGenericTable extends React.Component {
|
||||
title={value}
|
||||
key={column.id}
|
||||
style={styles[index]}>
|
||||
{column.dataType === 'link' ?
|
||||
<a
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
className="node-details-table-node-link"
|
||||
href={value}>
|
||||
{value}
|
||||
</a> :
|
||||
<MatchedText text={value} match={match} />
|
||||
{column.dataType === 'link'
|
||||
? (
|
||||
<a
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
className="node-details-table-node-link"
|
||||
href={value}>
|
||||
{value}
|
||||
</a>
|
||||
)
|
||||
: <MatchedText text={value} match={match} />
|
||||
}
|
||||
</td>
|
||||
);
|
||||
|
||||
@@ -13,8 +13,9 @@ export default class NodeDetailsHealth extends React.Component {
|
||||
}
|
||||
|
||||
handleClickMore() {
|
||||
const expanded = !this.state.expanded;
|
||||
this.setState({expanded});
|
||||
this.setState(prevState => ({
|
||||
expanded: !prevState.expanded
|
||||
}));
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -38,18 +39,22 @@ export default class NodeDetailsHealth extends React.Component {
|
||||
return (
|
||||
<div className="node-details-health" style={{ justifyContent: 'space-around' }}>
|
||||
<div className="node-details-health-wrapper">
|
||||
{shownWithData.map(item => (<NodeDetailsHealthLinkItem
|
||||
{...item}
|
||||
key={item.id}
|
||||
topologyId={topologyId}
|
||||
/>))}
|
||||
{shownWithData.map(item => (
|
||||
<NodeDetailsHealthLinkItem
|
||||
{...item}
|
||||
key={item.id}
|
||||
topologyId={topologyId}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="node-details-health-wrapper">
|
||||
{shownEmpty.map(item => (<NodeDetailsHealthLinkItem
|
||||
{...item}
|
||||
key={item.id}
|
||||
topologyId={topologyId}
|
||||
/>))}
|
||||
{shownEmpty.map(item => (
|
||||
<NodeDetailsHealthLinkItem
|
||||
{...item}
|
||||
key={item.id}
|
||||
topologyId={topologyId}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<ShowMore
|
||||
handleClick={this.handleClickMore}
|
||||
|
||||
@@ -17,8 +17,9 @@ class NodeDetailsInfo extends React.Component {
|
||||
}
|
||||
|
||||
handleClickMore() {
|
||||
const expanded = !this.state.expanded;
|
||||
this.setState({expanded});
|
||||
this.setState(prevState => ({
|
||||
expanded: !prevState.expanded
|
||||
}));
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -47,18 +48,22 @@ class NodeDetailsInfo extends React.Component {
|
||||
{field.label}
|
||||
</div>
|
||||
<div className="node-details-info-field-value truncate" title={title}>
|
||||
{field.dataType === 'link' ?
|
||||
<a
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
className="truncate node-details-table-node-link"
|
||||
href={value}>
|
||||
{value}
|
||||
</a> :
|
||||
<MatchedText
|
||||
text={value}
|
||||
truncate={field.truncate}
|
||||
match={matches.get(field.id)} />
|
||||
{field.dataType === 'link'
|
||||
? (
|
||||
<a
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
className="truncate node-details-table-node-link"
|
||||
href={value}>
|
||||
{value}
|
||||
</a>
|
||||
)
|
||||
: (
|
||||
<MatchedText
|
||||
text={value}
|
||||
truncate={field.truncate}
|
||||
match={matches.get(field.id)} />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,10 +9,12 @@ import ShowMore from '../show-more';
|
||||
|
||||
const Controls = controls => (
|
||||
<div className="node-details-property-list-controls">
|
||||
{sortBy(controls, 'rank').map(control => (<NodeDetailsControlButton
|
||||
nodeId={control.nodeId}
|
||||
control={control}
|
||||
key={control.id} />))}
|
||||
{sortBy(controls, 'rank').map(control => (
|
||||
<NodeDetailsControlButton
|
||||
nodeId={control.nodeId}
|
||||
control={control}
|
||||
key={control.id} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -26,8 +28,9 @@ export default class NodeDetailsPropertyList extends React.Component {
|
||||
}
|
||||
|
||||
handleLimitClick() {
|
||||
const limit = this.state.limit ? 0 : NODE_DETAILS_DATA_ROWS_DEFAULT_LIMIT;
|
||||
this.setState({limit});
|
||||
this.setState(prevState => ({
|
||||
limit: prevState.limit ? 0 : NODE_DETAILS_DATA_ROWS_DEFAULT_LIMIT
|
||||
}));
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { clickRelative } from '../../actions/app-actions';
|
||||
import { clickRelative } from '../../actions/request-actions';
|
||||
import { trackAnalyticsEvent } from '../../utils/tracking-utils';
|
||||
import MatchedText from '../matched-text';
|
||||
|
||||
|
||||
@@ -15,8 +15,9 @@ export default class NodeDetailsRelatives extends React.Component {
|
||||
|
||||
handleLimitClick(ev) {
|
||||
ev.preventDefault();
|
||||
const limit = this.state.limit ? 0 : NODE_DETAILS_DATA_ROWS_DEFAULT_LIMIT;
|
||||
this.setState({limit});
|
||||
this.setState(prevState => ({
|
||||
limit: prevState.limit ? 0 : NODE_DETAILS_DATA_ROWS_DEFAULT_LIMIT
|
||||
}));
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -37,13 +38,16 @@ export default class NodeDetailsRelatives extends React.Component {
|
||||
<NodeDetailsRelativesLink
|
||||
key={relative.id}
|
||||
match={matches.get(relative.id)}
|
||||
{...relative} />))}
|
||||
{showLimitAction &&
|
||||
<span
|
||||
className="node-details-relatives-more"
|
||||
onClick={this.handleLimitClick}>
|
||||
{limitActionText}
|
||||
</span>
|
||||
{...relative} />
|
||||
))}
|
||||
{showLimitAction
|
||||
&& (
|
||||
<span
|
||||
className="node-details-relatives-more"
|
||||
onClick={this.handleLimitClick}>
|
||||
{limitActionText}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -33,9 +33,9 @@ export default class NodeDetailsTableHeaders extends React.Component {
|
||||
}
|
||||
|
||||
const style = colStyles[index];
|
||||
const label =
|
||||
(style.width === NODE_DETAILS_TABLE_CW.XS && NODE_DETAILS_TABLE_XS_LABEL[header.id]) ?
|
||||
NODE_DETAILS_TABLE_XS_LABEL[header.id] : header.label;
|
||||
const label = (
|
||||
style.width === NODE_DETAILS_TABLE_CW.XS && NODE_DETAILS_TABLE_XS_LABEL[header.id]
|
||||
) ? NODE_DETAILS_TABLE_XS_LABEL[header.id] : header.label;
|
||||
|
||||
return (
|
||||
<td className={headerClasses.join(' ')} style={style} title={header.label} key={header.id}>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { clickRelative } from '../../actions/app-actions';
|
||||
import { clickRelative } from '../../actions/request-actions';
|
||||
import { trackAnalyticsEvent } from '../../utils/tracking-utils';
|
||||
import { dismissRowClickProps } from './node-details-table-row';
|
||||
import { dismissRowClickProps } from '../../utils/dom-utils';
|
||||
|
||||
|
||||
class NodeDetailsTableNodeLink extends React.Component {
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from 'react';
|
||||
import CloudLink from '../cloud-link';
|
||||
import { formatMetric } from '../../utils/string-utils';
|
||||
import { trackAnalyticsEvent } from '../../utils/tracking-utils';
|
||||
import { dismissRowClickProps } from './node-details-table-row';
|
||||
import { dismissRowClickProps } from '../../utils/dom-utils';
|
||||
|
||||
class NodeDetailsTableNodeMetricLink extends React.Component {
|
||||
constructor(props) {
|
||||
|
||||
@@ -54,14 +54,17 @@ function renderValues(node, columns = [], columnStyles = [], timestamp = null, t
|
||||
title={title}
|
||||
style={style}
|
||||
key={field.id}>
|
||||
{field.dataType === 'link' ?
|
||||
<a
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
className="node-details-table-node-link"
|
||||
href={value}>{value}
|
||||
</a> :
|
||||
value}
|
||||
{field.dataType === 'link'
|
||||
? (
|
||||
<a
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
className="node-details-table-node-link"
|
||||
href={value}>
|
||||
{value}
|
||||
</a>
|
||||
)
|
||||
: value}
|
||||
</td>
|
||||
);
|
||||
}
|
||||
@@ -72,13 +75,14 @@ function renderValues(node, columns = [], columnStyles = [], timestamp = null, t
|
||||
title={field.value}
|
||||
style={style}
|
||||
key={field.id}>
|
||||
{intersperse(field.relatives.map(relative =>
|
||||
(<NodeDetailsTableNodeLink
|
||||
{intersperse(field.relatives.map(relative => (
|
||||
<NodeDetailsTableNodeLink
|
||||
key={relative.id}
|
||||
linkable
|
||||
nodeId={relative.id}
|
||||
{...relative}
|
||||
/>)), ' ')}
|
||||
/>
|
||||
)), ' ')}
|
||||
</td>
|
||||
);
|
||||
}
|
||||
@@ -98,19 +102,6 @@ function renderValues(node, columns = [], columnStyles = [], timestamp = null, t
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Table row children may react to onClick events but the row
|
||||
* itself does detect a click by looking at onMouseUp. To stop
|
||||
* the bubbling of clicks on child elements we need to dismiss
|
||||
* the onMouseUp event.
|
||||
*/
|
||||
export const dismissRowClickProps = {
|
||||
onMouseUp: (ev) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
export default class NodeDetailsTableRow extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
@@ -150,8 +141,8 @@ export default class NodeDetailsTableRow extends React.Component {
|
||||
const { pageX, pageY } = ev;
|
||||
const { originX, originY } = this.mouseDrag;
|
||||
const movedTheMouseTooMuch = (
|
||||
Math.abs(originX - pageX) > thresholdPx ||
|
||||
Math.abs(originY - pageY) > thresholdPx
|
||||
Math.abs(originX - pageX) > thresholdPx
|
||||
|| Math.abs(originY - pageY) > thresholdPx
|
||||
);
|
||||
if (movedTheMouseTooMuch && originX && originY) {
|
||||
return;
|
||||
@@ -182,7 +173,7 @@ export default class NodeDetailsTableRow extends React.Component {
|
||||
onMouseLeave={this.onMouseLeave}
|
||||
className={className}>
|
||||
<td className="node-details-table-node-label truncate" style={firstColumnStyle}>
|
||||
{this.props.renderIdCell(Object.assign(node, {nodeId, topologyId}))}
|
||||
{this.props.renderIdCell(Object.assign(node, { nodeId, topologyId }))}
|
||||
</td>
|
||||
{values}
|
||||
</tr>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { connect } from 'react-redux';
|
||||
import { find, get, union, sortBy, groupBy, concat, debounce } from 'lodash';
|
||||
import {
|
||||
find, get, union, sortBy, groupBy, concat, debounce
|
||||
} from 'lodash';
|
||||
|
||||
import { NODE_DETAILS_DATA_ROWS_DEFAULT_LIMIT } from '../../constants/limits';
|
||||
import { TABLE_ROW_FOCUS_DEBOUNCE_INTERVAL } from '../../constants/timer';
|
||||
@@ -18,7 +20,7 @@ import {
|
||||
|
||||
function getDefaultSortedBy(columns, nodes) {
|
||||
// default sorter specified by columns
|
||||
const defaultSortColumn = find(columns, {defaultSort: true});
|
||||
const defaultSortColumn = find(columns, { defaultSort: true });
|
||||
if (defaultSortColumn) {
|
||||
return defaultSortColumn.id;
|
||||
}
|
||||
@@ -49,7 +51,7 @@ function getNodeValue(node, header) {
|
||||
if (isIP(header)) {
|
||||
// Format the IPs so that they are sorted numerically.
|
||||
return ipToPaddedString(field.value);
|
||||
} else if (isNumeric(header)) {
|
||||
} if (isNumeric(header)) {
|
||||
return parseFloat(field.value);
|
||||
}
|
||||
return field.value;
|
||||
@@ -125,7 +127,7 @@ function getSortedNodes(nodes, sortedByHeader, sortedDesc) {
|
||||
// have a minimal height. That prevents auto-scroll under a focus if the
|
||||
// number of table rows shrinks.
|
||||
function minHeightConstraint(height = 0) {
|
||||
return <tr className="min-height-constraint" style={{height}} />;
|
||||
return <tr className="min-height-constraint" style={{ height }} />;
|
||||
}
|
||||
|
||||
|
||||
@@ -158,8 +160,9 @@ class NodeDetailsTable extends React.Component {
|
||||
}
|
||||
|
||||
handleLimitClick() {
|
||||
const limit = this.state.limit ? 0 : this.props.limit;
|
||||
this.setState({ limit });
|
||||
this.setState(prevState => ({
|
||||
limit: prevState.limit ? 0 : this.props.limit
|
||||
}));
|
||||
}
|
||||
|
||||
focusRow(rowIndex, node) {
|
||||
@@ -202,7 +205,7 @@ class NodeDetailsTable extends React.Component {
|
||||
|
||||
getColumnHeaders() {
|
||||
const columns = this.props.columns || [];
|
||||
return [{id: 'label', label: this.props.label}].concat(columns);
|
||||
return [{ id: 'label', label: this.props.label }].concat(columns);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -218,8 +221,8 @@ class NodeDetailsTable extends React.Component {
|
||||
|
||||
const sortedBy = this.state.sortedBy || getDefaultSortedBy(columns, this.props.nodes);
|
||||
const sortedByHeader = this.getColumnHeaders().find(h => h.id === sortedBy);
|
||||
const sortedDesc = (this.state.sortedDesc === null) ?
|
||||
defaultSortDesc(sortedByHeader) : this.state.sortedDesc;
|
||||
const sortedDesc = (this.state.sortedDesc === null)
|
||||
? defaultSortDesc(sortedByHeader) : this.state.sortedDesc;
|
||||
|
||||
let nodes = getSortedNodes(this.props.nodes, sortedByHeader, sortedDesc);
|
||||
|
||||
@@ -261,12 +264,14 @@ class NodeDetailsTable extends React.Component {
|
||||
<div className="node-details-table-wrapper">
|
||||
<table className="node-details-table">
|
||||
<thead ref={this.saveTableHeadRef}>
|
||||
{this.props.nodes && this.props.nodes.length > 0 && <NodeDetailsTableHeaders
|
||||
headers={headers}
|
||||
sortedBy={sortedBy}
|
||||
sortedDesc={sortedDesc}
|
||||
onClick={this.updateSorted}
|
||||
/>}
|
||||
{this.props.nodes && this.props.nodes.length > 0 && (
|
||||
<NodeDetailsTableHeaders
|
||||
headers={headers}
|
||||
sortedBy={sortedBy}
|
||||
sortedDesc={sortedDesc}
|
||||
onClick={this.updateSorted}
|
||||
/>
|
||||
)}
|
||||
</thead>
|
||||
<tbody
|
||||
style={this.props.tbodyStyle}
|
||||
@@ -308,7 +313,7 @@ NodeDetailsTable.defaultProps = {
|
||||
limit: NODE_DETAILS_DATA_ROWS_DEFAULT_LIMIT,
|
||||
// key to identify a node in a row (used for topology links)
|
||||
nodeIdKey: 'id',
|
||||
onSortChange: () => {},
|
||||
onSortChange: () => { },
|
||||
sortedBy: null,
|
||||
sortedDesc: null,
|
||||
};
|
||||
|
||||
@@ -35,11 +35,13 @@ class NodesResourcesLayer extends React.Component {
|
||||
/>
|
||||
))}
|
||||
</g>
|
||||
{!layoutNodes.isEmpty() && <NodeResourcesLayerTopology
|
||||
{!layoutNodes.isEmpty() && (
|
||||
<NodeResourcesLayerTopology
|
||||
verticalPosition={layerVerticalPosition}
|
||||
transform={transform}
|
||||
topologyId={topologyId}
|
||||
/>}
|
||||
/>
|
||||
)}
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,11 +13,21 @@ export default class NodeResourcesMetricBoxInfo extends React.Component {
|
||||
<span>
|
||||
<strong>
|
||||
{showExtendedInfo ? humanizedRelativeConsumption : humanizedAbsoluteConsumption}
|
||||
</strong> used
|
||||
{showExtendedInfo &&
|
||||
</strong>
|
||||
{' '}
|
||||
used
|
||||
{showExtendedInfo
|
||||
&& (
|
||||
<i>
|
||||
{' - '}({humanizedAbsoluteConsumption} / <strong>{humanizedTotalCapacity}</strong>)
|
||||
{' - '}
|
||||
(
|
||||
{humanizedAbsoluteConsumption}
|
||||
{' '}
|
||||
/
|
||||
<strong>{humanizedTotalCapacity}</strong>
|
||||
)
|
||||
</i>
|
||||
)
|
||||
}
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@ import { connect } from 'react-redux';
|
||||
import theme from 'weaveworks-ui-components/lib/theme';
|
||||
|
||||
import NodeResourcesMetricBoxInfo from './node-resources-metric-box-info';
|
||||
import { clickNode } from '../../actions/app-actions';
|
||||
import { clickNode } from '../../actions/request-actions';
|
||||
import { trackAnalyticsEvent } from '../../utils/tracking-utils';
|
||||
import { applyTransform } from '../../utils/transform-utils';
|
||||
import { RESOURCE_VIEW_MODE } from '../../constants/naming';
|
||||
@@ -112,9 +112,9 @@ class NodeResourcesMetricBox extends React.Component {
|
||||
// TODO: Show `+ 31 nodes` kind of tag in their stead.
|
||||
if (!showNode) return null;
|
||||
|
||||
const resourceUsageTooltipInfo = showCapacity ?
|
||||
metricSummary.get('humanizedRelativeConsumption') :
|
||||
metricSummary.get('humanizedAbsoluteConsumption');
|
||||
const resourceUsageTooltipInfo = showCapacity
|
||||
? metricSummary.get('humanizedRelativeConsumption')
|
||||
: metricSummary.get('humanizedAbsoluteConsumption');
|
||||
|
||||
return (
|
||||
<g
|
||||
@@ -123,13 +123,25 @@ class NodeResourcesMetricBox extends React.Component {
|
||||
onClick={this.handleClick}
|
||||
ref={this.saveNodeRef}
|
||||
>
|
||||
<title>{label} - {type} usage at {resourceUsageTooltipInfo}</title>
|
||||
{showCapacity && <rect
|
||||
<title>
|
||||
{label}
|
||||
{' '}
|
||||
-
|
||||
{' '}
|
||||
{type}
|
||||
{' '}
|
||||
usage at
|
||||
{' '}
|
||||
{resourceUsageTooltipInfo}
|
||||
</title>
|
||||
{showCapacity && (
|
||||
<rect
|
||||
className="frame"
|
||||
rx={theme.borderRadius.soft}
|
||||
ry={theme.borderRadius.soft}
|
||||
{...this.defaultRectProps()}
|
||||
/>}
|
||||
/>
|
||||
)}
|
||||
<rect
|
||||
className="bar"
|
||||
fill={color}
|
||||
@@ -137,13 +149,15 @@ class NodeResourcesMetricBox extends React.Component {
|
||||
ry={theme.borderRadius.soft}
|
||||
{...this.defaultRectProps(relativeConsumption)}
|
||||
/>
|
||||
{showInfo && <NodeResourcesMetricBoxInfo
|
||||
{showInfo && (
|
||||
<NodeResourcesMetricBoxInfo
|
||||
label={label}
|
||||
metricSummary={metricSummary}
|
||||
width={width - (2 * RESOURCES_LABEL_PADDING)}
|
||||
x={x + RESOURCES_LABEL_PADDING}
|
||||
y={y + RESOURCES_LABEL_PADDING}
|
||||
/>}
|
||||
/>
|
||||
)}
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { connect } from 'react-redux';
|
||||
|
||||
import NodesChart from '../charts/nodes-chart';
|
||||
import NodesGrid from '../charts/nodes-grid';
|
||||
import NodesResources from '../components/nodes-resources';
|
||||
import NodesResources from './nodes-resources';
|
||||
import NodesError from '../charts/nodes-error';
|
||||
import DelayedShow from '../utils/delayed-show';
|
||||
import { Loading, getNodeType } from './loading';
|
||||
@@ -44,9 +44,9 @@ class Nodes extends React.Component {
|
||||
return (
|
||||
<NodesError faIconClass="far fa-circle" hidden={!nodesDisplayEmpty}>
|
||||
<div className="heading">Nothing to show. This can have any of these reasons:</div>
|
||||
{topologyNodeCountZero ?
|
||||
renderCauses(NODES_STATS_COUNT_ZERO_CAUSES) :
|
||||
renderCauses(NODES_NOT_DISPLAYED_CAUSES)}
|
||||
{topologyNodeCountZero
|
||||
? renderCauses(NODES_STATS_COUNT_ZERO_CAUSES)
|
||||
: renderCauses(NODES_NOT_DISPLAYED_CAUSES)}
|
||||
</NodesError>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,16 @@ const Plugin = ({
|
||||
}) => {
|
||||
const error = status !== 'ok';
|
||||
const className = classNames({ error });
|
||||
const tip = (<span>Description: {description}<br />Status: {status}</span>);
|
||||
const tip = (
|
||||
<span>
|
||||
Description:
|
||||
{description}
|
||||
<br />
|
||||
Status:
|
||||
{' '}
|
||||
{status}
|
||||
</span>
|
||||
);
|
||||
|
||||
// Inner span to hold styling so we don't effect the "before:content"
|
||||
return (
|
||||
|
||||
@@ -4,7 +4,12 @@ import { isEmpty } from 'lodash';
|
||||
import { Search } from 'weaveworks-ui-components';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { blurSearch, focusSearch, updateSearch, toggleHelp } from '../actions/app-actions';
|
||||
import {
|
||||
blurSearch, updateSearch, toggleHelp
|
||||
} from '../actions/app-actions';
|
||||
import {
|
||||
focusSearch
|
||||
} from '../actions/request-actions';
|
||||
import { searchMatchCountByTopologySelector } from '../selectors/search';
|
||||
import { isResourceViewModeSelector } from '../selectors/topology';
|
||||
import { slugify } from '../utils/string-utils';
|
||||
@@ -107,7 +112,9 @@ class SearchComponent extends React.Component {
|
||||
onBlur={this.props.blurSearch}
|
||||
/>
|
||||
<SearchHint active={this.props.searchFocused && isEmpty(pinnedSearches)}>
|
||||
{searchHint} <SearchHintIcon
|
||||
{searchHint}
|
||||
{' '}
|
||||
<SearchHintIcon
|
||||
className="fa fa-question-circle"
|
||||
onMouseDown={this.props.toggleHelp}
|
||||
/>
|
||||
|
||||
@@ -24,7 +24,9 @@ export default class ShowMore extends React.PureComponent {
|
||||
}
|
||||
return (
|
||||
<div className="show-more" onClick={this.handleClick}>
|
||||
{limitActionText} <span className={`show-more-icon ${limitActionIcon}`} />
|
||||
{limitActionText}
|
||||
{' '}
|
||||
<span className={`show-more-icon ${limitActionIcon}`} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -72,8 +72,8 @@ export default class Sparkline extends React.Component {
|
||||
const min = formatMetricSvg(d3Min(data, d => d.value), this.props);
|
||||
const max = formatMetricSvg(d3Max(data, d => d.value), this.props);
|
||||
const mean = formatMetricSvg(d3Mean(data, d => d.value), this.props);
|
||||
const title = `Last ${Math.round((lastDate - firstDate) / 1000)} seconds, ` +
|
||||
`${data.length} samples, min: ${min}, max: ${max}, mean: ${mean}`;
|
||||
const title = `Last ${Math.round((lastDate - firstDate) / 1000)} seconds, `
|
||||
+ `${data.length} samples, min: ${min}, max: ${max}, mean: ${mean}`;
|
||||
|
||||
return {
|
||||
data, lastX, lastY, title
|
||||
@@ -122,7 +122,8 @@ export default class Sparkline extends React.Component {
|
||||
strokeDasharray={strokeDasharray}
|
||||
d={this.line(graph.data)}
|
||||
/>
|
||||
{hasData && <circle
|
||||
{hasData && (
|
||||
<circle
|
||||
className="sparkcircle"
|
||||
cx={graph.lastX}
|
||||
cy={graph.lastY}
|
||||
@@ -130,7 +131,8 @@ export default class Sparkline extends React.Component {
|
||||
fillOpacity={fillOpacity}
|
||||
stroke="none"
|
||||
r={radius}
|
||||
/>}
|
||||
/>
|
||||
)}
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -51,12 +51,14 @@ class TerminalApp extends React.Component {
|
||||
|
||||
return (
|
||||
<div className="terminal-app" style={style}>
|
||||
{this.props.controlPipe && <Terminal
|
||||
{this.props.controlPipe && (
|
||||
<Terminal
|
||||
pipe={this.props.controlPipe}
|
||||
titleBarColor={this.state.titleBarColor}
|
||||
statusBarColor={this.state.statusBarColor}
|
||||
title={this.state.title}
|
||||
embedded={false} />}
|
||||
embedded={false} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,9 +8,12 @@ import { Terminal as Term } from 'xterm';
|
||||
import * as fit from 'xterm/lib/addons/fit/fit';
|
||||
|
||||
import { closeTerminal } from '../actions/app-actions';
|
||||
import { getPipeStatus } from '../actions/request-actions';
|
||||
import { getNeutralColor } from '../utils/color-utils';
|
||||
import { setDocumentTitle } from '../utils/title-utils';
|
||||
import { getPipeStatus, deletePipe, doResizeTty, getWebsocketUrl, basePath } from '../utils/web-api-utils';
|
||||
import {
|
||||
deletePipe, doResizeTty, getWebsocketUrl, basePath
|
||||
} from '../utils/web-api-utils';
|
||||
|
||||
const log = debug('scope:terminal');
|
||||
|
||||
@@ -327,6 +330,7 @@ class Terminal extends React.Component {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
getControlStatusIcon() {
|
||||
const icon = this.props.controlStatus && this.props.controlStatus.get('control').icon;
|
||||
return (
|
||||
@@ -339,8 +343,7 @@ class Terminal extends React.Component {
|
||||
}
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const controlStatus = state.get('controlPipes').find(pipe =>
|
||||
pipe.get('nodeId') === ownProps.pipe.get('nodeId'));
|
||||
const controlStatus = state.get('controlPipes').find(pipe => pipe.get('nodeId') === ownProps.pipe.get('nodeId'));
|
||||
return { controlStatus };
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { connect } from 'react-redux';
|
||||
import { TimestampTag } from 'weaveworks-ui-components';
|
||||
|
||||
import { trackAnalyticsEvent } from '../utils/tracking-utils';
|
||||
import { pauseTimeAtNow, resumeTime } from '../actions/app-actions';
|
||||
import { pauseTimeAtNow, resumeTime } from '../actions/request-actions';
|
||||
import { isPausedSelector, timeTravelSupportedSelector } from '../selectors/time-travel';
|
||||
|
||||
|
||||
@@ -64,10 +64,14 @@ class TimeControl extends React.Component {
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{isPaused &&
|
||||
{isPaused
|
||||
&& (
|
||||
<span className="time-control-info">
|
||||
Showing state from <TimestampTag inheritStyles relative timestamp={pausedAt} />
|
||||
Showing state from
|
||||
{' '}
|
||||
<TimestampTag inheritStyles relative timestamp={pausedAt} />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { TimeTravel } from 'weaveworks-ui-components';
|
||||
|
||||
import { jumpToTime, resumeTime, pauseTimeAtNow } from '../actions/app-actions';
|
||||
import { jumpToTime, resumeTime, pauseTimeAtNow } from '../actions/request-actions';
|
||||
|
||||
class TimeTravelWrapper extends React.Component {
|
||||
handleLiveModeChange = (showingLive) => {
|
||||
|
||||
@@ -5,7 +5,7 @@ import classnames from 'classnames';
|
||||
import { trackAnalyticsEvent } from '../utils/tracking-utils';
|
||||
import { searchMatchCountByTopologySelector } from '../selectors/search';
|
||||
import { isResourceViewModeSelector } from '../selectors/topology';
|
||||
import { clickTopology } from '../actions/app-actions';
|
||||
import { clickTopology } from '../actions/request-actions';
|
||||
|
||||
|
||||
function basicTopologyInfo(topology, searchMatchCount) {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { trackAnalyticsEvent } from '../utils/tracking-utils';
|
||||
import { getCurrentTopologyOptions } from '../utils/topology-utils';
|
||||
import { activeTopologyOptionsSelector } from '../selectors/topology';
|
||||
import TopologyOptionAction from './topology-option-action';
|
||||
import { changeTopologyOption } from '../actions/app-actions';
|
||||
import { changeTopologyOption } from '../actions/request-actions';
|
||||
|
||||
class TopologyOptions extends React.Component {
|
||||
constructor(props, context) {
|
||||
@@ -101,7 +101,8 @@ class TopologyOptions extends React.Component {
|
||||
return (
|
||||
<div className="topology-option" key={optionId}>
|
||||
<div className="topology-option-wrapper">
|
||||
{option.get('selectType') === 'union' &&
|
||||
{option.get('selectType') === 'union'
|
||||
&& (
|
||||
<TopologyOptionAction
|
||||
onClick={this.handleNoneClick}
|
||||
optionId={optionId}
|
||||
@@ -109,6 +110,7 @@ class TopologyOptions extends React.Component {
|
||||
topologyId={currentTopologyId}
|
||||
activeValue={activeValue}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{option.get('options').map(item => (
|
||||
<TopologyOptionAction
|
||||
|
||||
@@ -36,11 +36,19 @@ class DebugMenu extends React.Component {
|
||||
>
|
||||
<i className="fa fa-code" />
|
||||
<span className="description">Save raw data as JSON</span>
|
||||
{pausedAt && <span className="soft"> ({pausedAt})</span>}
|
||||
{pausedAt && (
|
||||
<span className="soft">
|
||||
{' '}
|
||||
(
|
||||
{pausedAt}
|
||||
)
|
||||
</span>
|
||||
)}
|
||||
</a>
|
||||
</div>
|
||||
<div className="troubleshooting-menu-item">
|
||||
<button
|
||||
type="button"
|
||||
className="footer-icon"
|
||||
onClick={this.props.clickDownloadGraph}
|
||||
title="Save canvas as SVG (does not include search highlighting)"
|
||||
@@ -52,6 +60,7 @@ class DebugMenu extends React.Component {
|
||||
</div>
|
||||
<div className="troubleshooting-menu-item">
|
||||
<button
|
||||
type="button"
|
||||
className="footer-icon"
|
||||
title="Reset view state"
|
||||
onClick={this.handleClickReset}
|
||||
|
||||
@@ -3,7 +3,8 @@ import { connect } from 'react-redux';
|
||||
|
||||
import ViewModeButton from './view-mode-button';
|
||||
import MetricSelector from './metric-selector';
|
||||
import { setGraphView, setTableView, setResourceView } from '../actions/app-actions';
|
||||
import { setResourceView } from '../actions/request-actions';
|
||||
import { setGraphView, setTableView } from '../actions/app-actions';
|
||||
import { availableMetricsSelector } from '../selectors/node-metric';
|
||||
import {
|
||||
isResourceViewModeSelector,
|
||||
|
||||
@@ -12,8 +12,9 @@ class Warning extends React.Component {
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
const expanded = !this.state.expanded;
|
||||
this.setState({ expanded });
|
||||
this.setState(prevState => ({
|
||||
expanded: !prevState.expanded
|
||||
}));
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -56,11 +56,11 @@ export default class ZoomControl extends React.Component {
|
||||
|
||||
return (
|
||||
<div className="zoom-control">
|
||||
<button className="zoom-in" onClick={this.handleZoomIn}>
|
||||
<button className="zoom-in" type="button" onClick={this.handleZoomIn}>
|
||||
<i className="fa fa-plus" />
|
||||
</button>
|
||||
<Slider value={value} max={1} step={SLIDER_STEP} vertical onChange={this.handleChange} />
|
||||
<button className="zoom-out" onClick={this.handleZoomOut}>
|
||||
<button className="zoom-out" type="button" onClick={this.handleZoomOut}>
|
||||
<i className="fa fa-minus" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -8,8 +8,8 @@ import { drag } from 'd3-drag';
|
||||
import { event as d3Event, select } from 'd3-selection';
|
||||
import { zoomFactor } from 'weaveworks-ui-components/lib/utils/zooming';
|
||||
|
||||
import Logo from '../components/logo';
|
||||
import ZoomControl from '../components/zoom-control';
|
||||
import Logo from './logo';
|
||||
import ZoomControl from './zoom-control';
|
||||
import { cacheZoomState } from '../actions/app-actions';
|
||||
import { applyTransform, inverseTransform } from '../utils/transform-utils';
|
||||
import { activeTopologyZoomCacheKeyPathSelector } from '../selectors/zooming';
|
||||
@@ -116,12 +116,14 @@ class ZoomableCanvas extends React.Component {
|
||||
{this.props.children(this.state)}
|
||||
</g>
|
||||
</svg>
|
||||
{this.canChangeZoom() && <ZoomControl
|
||||
{this.canChangeZoom() && (
|
||||
<ZoomControl
|
||||
zoomAction={this.handleZoomControlAction}
|
||||
minScale={this.state.minScale}
|
||||
maxScale={this.state.maxScale}
|
||||
scale={this.state.scaleX}
|
||||
/>}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@ export function nodeResourceViewColorDecorator(node) {
|
||||
// Decorates the resource node with dimensions taken from its metric summary.
|
||||
export function nodeResourceBoxDecorator(node) {
|
||||
const metricSummary = node.get('metricSummary', makeMap());
|
||||
const width = metricSummary.get('showCapacity') ?
|
||||
metricSummary.get('totalCapacity') :
|
||||
metricSummary.get('absoluteConsumption');
|
||||
const width = metricSummary.get('showCapacity')
|
||||
? metricSummary.get('totalCapacity')
|
||||
: metricSummary.get('absoluteConsumption');
|
||||
const height = RESOURCES_LAYER_HEIGHT;
|
||||
|
||||
return node.merge(makeMap({ height, width }));
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/* eslint-disable import/no-webpack-loader-syntax, import/no-unresolved */
|
||||
import debug from 'debug';
|
||||
import moment from 'moment';
|
||||
import { size, each, includes, isEqual } from 'lodash';
|
||||
import {
|
||||
size, each, includes, isEqual
|
||||
} from 'lodash';
|
||||
import {
|
||||
fromJS,
|
||||
is as isDeepEqual,
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import page from 'page';
|
||||
import stableStringify from 'json-stable-stringify';
|
||||
import { each } from 'lodash';
|
||||
|
||||
import { route } from './actions/request-actions';
|
||||
import { storageGet, storageSet } from './utils/storage-utils';
|
||||
import {
|
||||
decodeURL, encodeURL, isStoreViewStateEnabled, STORAGE_STATE_KEY
|
||||
} from './utils/router-utils';
|
||||
|
||||
// Temporarily detect old topology options to avoid breaking things between releases
|
||||
// Related to https://github.com/weaveworks/scope/pull/2404
|
||||
function detectOldOptions(topologyOptions) {
|
||||
let bad = false;
|
||||
each(topologyOptions, (topology) => {
|
||||
each(topology, (option) => {
|
||||
if (typeof option === 'string') {
|
||||
bad = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
return bad;
|
||||
}
|
||||
|
||||
export function getRouter(initialState) {
|
||||
return (dispatch, getState) => {
|
||||
// strip any trailing '/'s.
|
||||
page.base(window.location.pathname.replace(/\/$/, ''));
|
||||
|
||||
page('/', () => {
|
||||
// recover from storage state on empty URL
|
||||
const storageState = storageGet(STORAGE_STATE_KEY);
|
||||
if (storageState && isStoreViewStateEnabled(getState())) {
|
||||
const parsedState = JSON.parse(decodeURL(storageState));
|
||||
const dirtyOptions = detectOldOptions(parsedState.topologyOptions);
|
||||
if (dirtyOptions) {
|
||||
dispatch(route(initialState));
|
||||
} else {
|
||||
const mergedState = Object.assign(initialState, parsedState);
|
||||
// push storage state to URL
|
||||
window.location.hash = `!/state/${stableStringify(mergedState)}`;
|
||||
dispatch(route(mergedState));
|
||||
}
|
||||
} else {
|
||||
dispatch(route(initialState));
|
||||
}
|
||||
});
|
||||
|
||||
page('/state/:state', (ctx) => {
|
||||
const state = JSON.parse(decodeURL(ctx.params.state));
|
||||
const dirtyOptions = detectOldOptions(state.topologyOptions);
|
||||
const nextState = dirtyOptions ? initialState : state;
|
||||
|
||||
// back up state in storage and redirect
|
||||
if (isStoreViewStateEnabled(getState())) {
|
||||
storageSet(STORAGE_STATE_KEY, encodeURL(stableStringify(state)));
|
||||
}
|
||||
|
||||
dispatch(route(nextState));
|
||||
});
|
||||
|
||||
return page;
|
||||
};
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { createMapSelector, createListSelector } from 'reselect-map';
|
||||
import { fromJS, Map as makeMap, List as makeList } from 'immutable';
|
||||
|
||||
import { modulo } from '../utils/math-utils';
|
||||
import { isGraphViewModeSelector, isResourceViewModeSelector } from '../selectors/topology';
|
||||
import { isGraphViewModeSelector, isResourceViewModeSelector } from './topology';
|
||||
import { RESOURCE_VIEW_METRICS } from '../constants/resources';
|
||||
|
||||
|
||||
@@ -93,8 +93,7 @@ const selectedMetricIdSelector = createSelector(
|
||||
availableMetricsSelector,
|
||||
selectedMetricTypeSelector,
|
||||
],
|
||||
(availableMetrics, metricType) =>
|
||||
(availableMetrics.find(m => m.get('label') === metricType) || makeMap()).get('id')
|
||||
(availableMetrics, metricType) => (availableMetrics.find(m => m.get('label') === metricType) || makeMap()).get('id')
|
||||
);
|
||||
|
||||
const topCardNodeSelector = createSelector(
|
||||
|
||||
@@ -22,9 +22,9 @@ const log = debug('scope:nodes-layout');
|
||||
// Used for ordering the resource nodes.
|
||||
const resourceNodeConsumptionComparator = (node) => {
|
||||
const metricSummary = node.get('metricSummary');
|
||||
return metricSummary.get('showCapacity') ?
|
||||
-metricSummary.get('relativeConsumption') :
|
||||
-metricSummary.get('absoluteConsumption');
|
||||
return metricSummary.get('showCapacity')
|
||||
? -metricSummary.get('relativeConsumption')
|
||||
: -metricSummary.get('absoluteConsumption');
|
||||
};
|
||||
|
||||
// A list of topologies shown in the resource view of the active topology (bottom to top).
|
||||
@@ -85,8 +85,10 @@ const decoratedNodesByTopologySelector = createSelector(
|
||||
const isBaseLayer = (index === 0);
|
||||
|
||||
const nodeParentDecorator = nodeParentDecoratorByTopologyId(parentLayerTopologyId);
|
||||
const nodeMetricSummaryDecorator =
|
||||
nodeMetricSummaryDecoratorByType(pinnedMetricType, showCapacity);
|
||||
const nodeMetricSummaryDecorator = nodeMetricSummaryDecoratorByType(
|
||||
pinnedMetricType,
|
||||
showCapacity
|
||||
);
|
||||
|
||||
// Color the node, deduce its anchor point, dimensions and info about its pinned metric.
|
||||
const decoratedTopologyNodes = (topologyNodes || makeMap())
|
||||
@@ -158,8 +160,8 @@ export const layoutNodesByTopologyIdSelector = createSelector(
|
||||
// We fix it by shrinking all the children to by a factor to perfectly fit into the parent.
|
||||
if (totalChildrenWidth > parentWidth) {
|
||||
const shrinkFactor = parentWidth / totalChildrenWidth;
|
||||
log(`Inconsistent data: Children of ${parentNodeId} reported to use more ` +
|
||||
`resource than the node itself - shrinking by factor ${shrinkFactor}`);
|
||||
log(`Inconsistent data: Children of ${parentNodeId} reported to use more `
|
||||
+ `resource than the node itself - shrinking by factor ${shrinkFactor}`);
|
||||
// Shrink all the children.
|
||||
nodesBucket.forEach((_, nodeId) => {
|
||||
let node = positionedNodes.get(nodeId);
|
||||
|
||||
@@ -2,7 +2,9 @@ import { createSelector } from 'reselect';
|
||||
import { createMapSelector } from 'reselect-map';
|
||||
import { Map as makeMap } from 'immutable';
|
||||
|
||||
import { parseQuery, searchNode, searchTopology, getSearchableFields } from '../utils/search-utils';
|
||||
import {
|
||||
parseQuery, searchNode, searchTopology, getSearchableFields
|
||||
} from '../utils/search-utils';
|
||||
|
||||
|
||||
const parsedSearchQuerySelector = createSelector(
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Map as makeMap, OrderedMap as makeOrderedMap } from 'immutable';
|
||||
|
||||
import { buildUrlQuery, basePath, getApiPath, getWebsocketUrl } from '../web-api-utils';
|
||||
import {
|
||||
buildUrlQuery, basePath, getApiPath, getWebsocketUrl
|
||||
} from '../web-api-utils';
|
||||
|
||||
|
||||
describe('WebApiUtils', () => {
|
||||
|
||||
@@ -7,8 +7,8 @@ export function uniformSelect(array, size) {
|
||||
return array;
|
||||
}
|
||||
|
||||
return range(size).map(index =>
|
||||
array[parseInt(index * (array.length / (size - (1 - 1e-9))), 10)]);
|
||||
return range(size)
|
||||
.map(index => array[parseInt(index * (array.length / (size - (1 - 1e-9))), 10)]);
|
||||
}
|
||||
|
||||
export function insertElement(array, index, element) {
|
||||
|
||||
@@ -14,3 +14,16 @@ export function encodeIdAttribute(id) {
|
||||
export function decodeIdAttribute(id) {
|
||||
return id.replace(/__u(\d+)__/gm, (m, d) => String.fromCharCode(d));
|
||||
}
|
||||
|
||||
/**
|
||||
* Table row children may react to onClick events but the row
|
||||
* itself does detect a click by looking at onMouseUp. To stop
|
||||
* the bubbling of clicks on child elements we need to dismiss
|
||||
* the onMouseUp event.
|
||||
*/
|
||||
export const dismissRowClickProps = {
|
||||
onMouseUp: (ev) => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { isPlainObject, mapValues, isEmpty, omitBy } from 'lodash';
|
||||
import {
|
||||
isPlainObject, mapValues, isEmpty, omitBy
|
||||
} from 'lodash';
|
||||
|
||||
|
||||
export function hashDifferenceDeep(A, B) {
|
||||
|
||||
@@ -57,12 +57,12 @@ export function getMetricColor(metric) {
|
||||
: metric && metric.get('id');
|
||||
if (/mem/.test(metricId)) {
|
||||
return 'steelBlue';
|
||||
} else if (/cpu/.test(metricId)) {
|
||||
} if (/cpu/.test(metricId)) {
|
||||
return colors('cpu').toString();
|
||||
} else if (/files/.test(metricId)) {
|
||||
} if (/files/.test(metricId)) {
|
||||
// purple
|
||||
return '#9467bd';
|
||||
} else if (/load/.test(metricId)) {
|
||||
} if (/load/.test(metricId)) {
|
||||
return colors('load').toString();
|
||||
}
|
||||
return 'steelBlue';
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import page from 'page';
|
||||
import stableStringify from 'json-stable-stringify';
|
||||
import { fromJS, is as isDeepEqual } from 'immutable';
|
||||
import { each, omit, omitBy, isEmpty } from 'lodash';
|
||||
import { omit, omitBy, isEmpty } from 'lodash';
|
||||
|
||||
import { route } from '../actions/app-actions';
|
||||
import { hashDifferenceDeep } from './hash-utils';
|
||||
import { storageGet, storageSet } from './storage-utils';
|
||||
import { storageSet } from './storage-utils';
|
||||
|
||||
import { getDefaultTopologyOptions, initialState as initialRootState } from '../reducers/root';
|
||||
|
||||
@@ -17,9 +16,9 @@ const SLASH = '/';
|
||||
const SLASH_REPLACEMENT = '<SLASH>';
|
||||
const PERCENT = '%';
|
||||
const PERCENT_REPLACEMENT = '<PERCENT>';
|
||||
const STORAGE_STATE_KEY = 'scopeViewState';
|
||||
export const STORAGE_STATE_KEY = 'scopeViewState';
|
||||
|
||||
function encodeURL(url) {
|
||||
export function encodeURL(url) {
|
||||
return url
|
||||
.replace(new RegExp(PERCENT, 'g'), PERCENT_REPLACEMENT)
|
||||
.replace(new RegExp(SLASH, 'g'), SLASH_REPLACEMENT);
|
||||
@@ -41,7 +40,7 @@ export function clearStoredViewState() {
|
||||
storageSet(STORAGE_STATE_KEY, '');
|
||||
}
|
||||
|
||||
function isStoreViewStateEnabled(state) {
|
||||
export function isStoreViewStateEnabled(state) {
|
||||
return state.get('storeViewState');
|
||||
}
|
||||
|
||||
@@ -135,59 +134,3 @@ export function updateRoute(getState) {
|
||||
page.show(`/state/${stateUrl}`, state, dispatch);
|
||||
}
|
||||
}
|
||||
|
||||
// Temporarily detect old topology options to avoid breaking things between releases
|
||||
// Related to https://github.com/weaveworks/scope/pull/2404
|
||||
function detectOldOptions(topologyOptions) {
|
||||
let bad = false;
|
||||
each(topologyOptions, (topology) => {
|
||||
each(topology, (option) => {
|
||||
if (typeof option === 'string') {
|
||||
bad = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
return bad;
|
||||
}
|
||||
|
||||
|
||||
export function getRouter(initialState) {
|
||||
return (dispatch, getState) => {
|
||||
// strip any trailing '/'s.
|
||||
page.base(window.location.pathname.replace(/\/$/, ''));
|
||||
|
||||
page('/', () => {
|
||||
// recover from storage state on empty URL
|
||||
const storageState = storageGet(STORAGE_STATE_KEY);
|
||||
if (storageState && isStoreViewStateEnabled(getState())) {
|
||||
const parsedState = JSON.parse(decodeURL(storageState));
|
||||
const dirtyOptions = detectOldOptions(parsedState.topologyOptions);
|
||||
if (dirtyOptions) {
|
||||
dispatch(route(initialState));
|
||||
} else {
|
||||
const mergedState = Object.assign(initialState, parsedState);
|
||||
// push storage state to URL
|
||||
window.location.hash = `!/state/${stableStringify(mergedState)}`;
|
||||
dispatch(route(mergedState));
|
||||
}
|
||||
} else {
|
||||
dispatch(route(initialState));
|
||||
}
|
||||
});
|
||||
|
||||
page('/state/:state', (ctx) => {
|
||||
const state = JSON.parse(decodeURL(ctx.params.state));
|
||||
const dirtyOptions = detectOldOptions(state.topologyOptions);
|
||||
const nextState = dirtyOptions ? initialState : state;
|
||||
|
||||
// back up state in storage and redirect
|
||||
if (isStoreViewStateEnabled(getState())) {
|
||||
storageSet(STORAGE_STATE_KEY, encodeURL(stableStringify(state)));
|
||||
}
|
||||
|
||||
dispatch(route(nextState));
|
||||
});
|
||||
|
||||
return page;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -128,9 +128,9 @@ export function setTopologyUrlsById(topologyUrlsById, topologies) {
|
||||
|
||||
export function filterHiddenTopologies(topologies, currentTopology) {
|
||||
currentTopology = currentTopology || makeMap();
|
||||
return topologies.filter(t => (!t.hide_if_empty || t.stats.node_count > 0 ||
|
||||
t.stats.filtered_nodes > 0 || t.id === currentTopology.get('id') ||
|
||||
t.id === currentTopology.get('parentId')));
|
||||
return topologies.filter(t => (!t.hide_if_empty || t.stats.node_count > 0
|
||||
|| t.stats.filtered_nodes > 0 || t.id === currentTopology.get('id')
|
||||
|| t.id === currentTopology.get('parentId')));
|
||||
}
|
||||
|
||||
export function getCurrentTopologyOptions(state) {
|
||||
|
||||
@@ -4,25 +4,18 @@ import { defaults } from 'lodash';
|
||||
import { Map as makeMap, List } from 'immutable';
|
||||
|
||||
import {
|
||||
blurSearch, clearControlError, closeWebsocket, openWebsocket, receiveError,
|
||||
receiveApiDetails, receiveNodesDelta, receiveNodeDetails, receiveControlError,
|
||||
receiveControlNodeRemoved, receiveControlPipe, receiveControlPipeStatus,
|
||||
receiveControlSuccess, receiveTopologies, receiveNotFound,
|
||||
receiveNodesForTopology, receiveNodes,
|
||||
receiveError,
|
||||
receiveNodeDetails,
|
||||
receiveNotFound, receiveNodesForTopology, receiveNodes,
|
||||
} from '../actions/app-actions';
|
||||
|
||||
import { getCurrentTopologyUrl } from '../utils/topology-utils';
|
||||
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 */
|
||||
@@ -38,16 +31,6 @@ const csrfToken = (() => {
|
||||
return token;
|
||||
})();
|
||||
|
||||
let socket;
|
||||
let reconnectTimer = 0;
|
||||
let topologyTimer = 0;
|
||||
let apiDetailsTimer = 0;
|
||||
let controlErrorTimer = 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) {
|
||||
@@ -105,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}`;
|
||||
@@ -116,73 +99,17 @@ 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.
|
||||
*/
|
||||
function doRequest(opts) {
|
||||
export function doRequest(opts) {
|
||||
const config = defaults(opts, {
|
||||
contentType: 'application/json',
|
||||
type: 'json'
|
||||
@@ -212,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);
|
||||
@@ -248,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');
|
||||
@@ -351,91 +221,12 @@ 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 doControlRequest(nodeId, control, dispatch) {
|
||||
clearTimeout(controlErrorTimer);
|
||||
const url = `${getApiPath()}/api/control/${encodeURIComponent(control.probeId)}/`
|
||||
+ `${encodeURIComponent(control.nodeId)}/${control.id}`;
|
||||
doRequest({
|
||||
error: (err) => {
|
||||
dispatch(receiveControlError(nodeId, err.response));
|
||||
controlErrorTimer = setTimeout(() => {
|
||||
dispatch(clearControlError(nodeId));
|
||||
}, 10000);
|
||||
},
|
||||
method: 'POST',
|
||||
success: (res) => {
|
||||
dispatch(receiveControlSuccess(nodeId));
|
||||
if (res) {
|
||||
if (res.pipe) {
|
||||
dispatch(blurSearch());
|
||||
const resizeTtyControl = res.resize_tty_control &&
|
||||
{id: res.resize_tty_control, nodeId: control.nodeId, probeId: control.probeId};
|
||||
dispatch(receiveControlPipe(
|
||||
res.pipe,
|
||||
nodeId,
|
||||
res.raw_tty,
|
||||
resizeTtyControl,
|
||||
control
|
||||
));
|
||||
}
|
||||
if (res.removedNode) {
|
||||
dispatch(receiveControlNodeRemoved(nodeId));
|
||||
}
|
||||
}
|
||||
},
|
||||
url
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function doResizeTty(pipeId, control, cols, rows) {
|
||||
const url = `${getApiPath()}/api/control/${encodeURIComponent(control.probeId)}/`
|
||||
+ `${encodeURIComponent(control.nodeId)}/${control.id}`;
|
||||
|
||||
return doRequest({
|
||||
data: JSON.stringify({height: rows.toString(), pipeID: pipeId, width: cols.toString()}),
|
||||
data: JSON.stringify({ height: rows.toString(), pipeID: pipeId, width: cols.toString() }),
|
||||
method: 'POST',
|
||||
url,
|
||||
})
|
||||
@@ -459,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;
|
||||
}
|
||||
}
|
||||
|
||||
+9
-9
@@ -8,7 +8,7 @@
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"@babel/polyfill": "^7.0.0",
|
||||
"babel-plugin-lodash": "3.3.2",
|
||||
"babel-plugin-lodash": "3.3.4",
|
||||
"classnames": "2.2.5",
|
||||
"d3-array": "1.2.1",
|
||||
"d3-color": "1.0.3",
|
||||
@@ -56,19 +56,19 @@
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@fortawesome/fontawesome-free": "^5.5.0",
|
||||
"autoprefixer": "7.1.5",
|
||||
"babel-eslint": "8.2.1",
|
||||
"babel-eslint": "10.0.2",
|
||||
"babel-jest": "24.8.0",
|
||||
"babel-loader": "^8.0.0",
|
||||
"babel-plugin-transform-class-properties": "6.24.1",
|
||||
"babel-plugin-transform-object-rest-spread": "6.26.0",
|
||||
"clean-webpack-plugin": "0.1.17",
|
||||
"css-loader": "2.1.1",
|
||||
"eslint": "5.16.0",
|
||||
"eslint-config-airbnb": "16.1.0",
|
||||
"eslint-loader": "2.1.2",
|
||||
"eslint-plugin-import": "2.17.3",
|
||||
"eslint-plugin-jsx-a11y": "6.2.1",
|
||||
"eslint-plugin-react": "7.13.0",
|
||||
"eslint": "6.1.0",
|
||||
"eslint-config-airbnb": "17.1.1",
|
||||
"eslint-loader": "2.2.1",
|
||||
"eslint-plugin-import": "2.18.2",
|
||||
"eslint-plugin-jsx-a11y": "6.2.3",
|
||||
"eslint-plugin-react": "7.14.3",
|
||||
"express": "4.16.4",
|
||||
"file-loader": "1.1.11",
|
||||
"html-webpack-plugin": "3.2.0",
|
||||
@@ -130,6 +130,6 @@
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": "^8.9.0"
|
||||
"node": "^8.10.0"
|
||||
}
|
||||
}
|
||||
|
||||
+174
-170
@@ -18,14 +18,6 @@
|
||||
optionalDependencies:
|
||||
chokidar "^2.0.3"
|
||||
|
||||
"@babel/code-frame@7.0.0-beta.36":
|
||||
version "7.0.0-beta.36"
|
||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0-beta.36.tgz#2349d7ec04b3a06945ae173280ef8579b63728e4"
|
||||
dependencies:
|
||||
chalk "^2.0.0"
|
||||
esutils "^2.0.2"
|
||||
js-tokens "^3.0.0"
|
||||
|
||||
"@babel/code-frame@7.0.0-beta.46":
|
||||
version "7.0.0-beta.46"
|
||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0-beta.46.tgz#e0d002100805daab1461c0fcb32a07e304f3a4f4"
|
||||
@@ -180,14 +172,6 @@
|
||||
"@babel/traverse" "^7.1.0"
|
||||
"@babel/types" "^7.0.0"
|
||||
|
||||
"@babel/helper-function-name@7.0.0-beta.36":
|
||||
version "7.0.0-beta.36"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.36.tgz#366e3bc35147721b69009f803907c4d53212e88d"
|
||||
dependencies:
|
||||
"@babel/helper-get-function-arity" "7.0.0-beta.36"
|
||||
"@babel/template" "7.0.0-beta.36"
|
||||
"@babel/types" "7.0.0-beta.36"
|
||||
|
||||
"@babel/helper-function-name@7.0.0-beta.46":
|
||||
version "7.0.0-beta.46"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.46.tgz#d0c4eed2e220e180f91b02e008dcc4594afe1d39"
|
||||
@@ -204,12 +188,6 @@
|
||||
"@babel/template" "^7.1.0"
|
||||
"@babel/types" "^7.0.0"
|
||||
|
||||
"@babel/helper-get-function-arity@7.0.0-beta.36":
|
||||
version "7.0.0-beta.36"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.36.tgz#f5383bac9a96b274828b10d98900e84ee43e32b8"
|
||||
dependencies:
|
||||
"@babel/types" "7.0.0-beta.36"
|
||||
|
||||
"@babel/helper-get-function-arity@7.0.0-beta.46":
|
||||
version "7.0.0-beta.46"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.46.tgz#7161bfe449b4183dbe25d1fe5579338b7429e5f2"
|
||||
@@ -234,9 +212,10 @@
|
||||
dependencies:
|
||||
"@babel/types" "^7.0.0"
|
||||
|
||||
"@babel/helper-module-imports@^7.0.0":
|
||||
"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.0.0-beta.49":
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz#96081b7111e486da4d2cd971ad1a4fe216cc2e3d"
|
||||
integrity sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==
|
||||
dependencies:
|
||||
"@babel/types" "^7.0.0"
|
||||
|
||||
@@ -735,14 +714,11 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.12.0"
|
||||
|
||||
"@babel/template@7.0.0-beta.36":
|
||||
version "7.0.0-beta.36"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.0.0-beta.36.tgz#02e903de5d68bd7899bce3c5b5447e59529abb00"
|
||||
"@babel/runtime@^7.4.5":
|
||||
version "7.4.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.5.tgz#582bb531f5f9dc67d2fcb682979894f75e253f12"
|
||||
dependencies:
|
||||
"@babel/code-frame" "7.0.0-beta.36"
|
||||
"@babel/types" "7.0.0-beta.36"
|
||||
babylon "7.0.0-beta.36"
|
||||
lodash "^4.2.0"
|
||||
regenerator-runtime "^0.13.2"
|
||||
|
||||
"@babel/template@7.0.0-beta.46":
|
||||
version "7.0.0-beta.46"
|
||||
@@ -769,19 +745,6 @@
|
||||
"@babel/parser" "^7.2.2"
|
||||
"@babel/types" "^7.2.2"
|
||||
|
||||
"@babel/traverse@7.0.0-beta.36":
|
||||
version "7.0.0-beta.36"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.0.0-beta.36.tgz#1dc6f8750e89b6b979de5fe44aa993b1a2192261"
|
||||
dependencies:
|
||||
"@babel/code-frame" "7.0.0-beta.36"
|
||||
"@babel/helper-function-name" "7.0.0-beta.36"
|
||||
"@babel/types" "7.0.0-beta.36"
|
||||
babylon "7.0.0-beta.36"
|
||||
debug "^3.0.1"
|
||||
globals "^11.1.0"
|
||||
invariant "^2.2.0"
|
||||
lodash "^4.2.0"
|
||||
|
||||
"@babel/traverse@7.0.0-beta.46", "@babel/traverse@^7.0.0-beta.42":
|
||||
version "7.0.0-beta.46"
|
||||
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.0.0-beta.46.tgz#29a0c0395b3642f0297e6f8e475bde89f9343755"
|
||||
@@ -825,14 +788,6 @@
|
||||
globals "^11.1.0"
|
||||
lodash "^4.17.11"
|
||||
|
||||
"@babel/types@7.0.0-beta.36":
|
||||
version "7.0.0-beta.36"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.0.0-beta.36.tgz#64f2004353de42adb72f9ebb4665fc35b5499d23"
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
lodash "^4.2.0"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@babel/types@7.0.0-beta.46":
|
||||
version "7.0.0-beta.46"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.0.0-beta.46.tgz#eb84399a699af9fcb244440cce78e1acbeb40e0c"
|
||||
@@ -849,6 +804,15 @@
|
||||
lodash "^4.17.11"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@babel/types@^7.0.0-beta.49":
|
||||
version "7.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.5.0.tgz#e47d43840c2e7f9105bc4d3a2c371b4d0c7832ab"
|
||||
integrity sha512-UFpDVqRABKsW01bvw7/wSUe56uy6RXM5+VJibVVAybDGxEW25jdwiFJEf7ASvSaC7sN7rbE/l3cLp2izav+CtQ==
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
lodash "^4.17.11"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@babel/types@^7.4.0":
|
||||
version "7.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.4.0.tgz#670724f77d24cce6cc7d8cf64599d511d164894c"
|
||||
@@ -1204,7 +1168,7 @@ ajv@^5.0.0, ajv@^5.2.3:
|
||||
fast-json-stable-stringify "^2.0.0"
|
||||
json-schema-traverse "^0.3.0"
|
||||
|
||||
ajv@^6.1.0, ajv@^6.5.5, ajv@^6.9.1:
|
||||
ajv@^6.1.0, ajv@^6.10.0, ajv@^6.5.5, ajv@^6.9.1:
|
||||
version "6.10.0"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1"
|
||||
dependencies:
|
||||
@@ -1292,7 +1256,6 @@ argparse@^1.0.7:
|
||||
aria-query@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-3.0.0.tgz#65b3fcc1ca1155a8c9ae64d6eee297f15d5133cc"
|
||||
integrity sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=
|
||||
dependencies:
|
||||
ast-types-flow "0.0.7"
|
||||
commander "^2.11.0"
|
||||
@@ -1463,7 +1426,6 @@ aws4@^1.8.0:
|
||||
axobject-query@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.0.2.tgz#ea187abe5b9002b377f925d8bf7d1c561adf38f9"
|
||||
integrity sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==
|
||||
dependencies:
|
||||
ast-types-flow "0.0.7"
|
||||
|
||||
@@ -1475,15 +1437,15 @@ babel-code-frame@^6.26.0:
|
||||
esutils "^2.0.2"
|
||||
js-tokens "^3.0.2"
|
||||
|
||||
babel-eslint@8.2.1:
|
||||
version "8.2.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-8.2.1.tgz#136888f3c109edc65376c23ebf494f36a3e03951"
|
||||
babel-eslint@10.0.2:
|
||||
version "10.0.2"
|
||||
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.2.tgz#182d5ac204579ff0881684b040560fdcc1558456"
|
||||
dependencies:
|
||||
"@babel/code-frame" "7.0.0-beta.36"
|
||||
"@babel/traverse" "7.0.0-beta.36"
|
||||
"@babel/types" "7.0.0-beta.36"
|
||||
babylon "7.0.0-beta.36"
|
||||
eslint-scope "~3.7.1"
|
||||
"@babel/code-frame" "^7.0.0"
|
||||
"@babel/parser" "^7.0.0"
|
||||
"@babel/traverse" "^7.0.0"
|
||||
"@babel/types" "^7.0.0"
|
||||
eslint-scope "3.7.1"
|
||||
eslint-visitor-keys "^1.0.0"
|
||||
|
||||
babel-helper-function-name@^6.24.1:
|
||||
@@ -1503,13 +1465,6 @@ babel-helper-get-function-arity@^6.24.1:
|
||||
babel-runtime "^6.22.0"
|
||||
babel-types "^6.24.1"
|
||||
|
||||
babel-helper-module-imports@^7.0.0-beta.3:
|
||||
version "7.0.0-beta.3"
|
||||
resolved "https://registry.yarnpkg.com/babel-helper-module-imports/-/babel-helper-module-imports-7.0.0-beta.3.tgz#e15764e3af9c8e11810c09f78f498a2bdc71585a"
|
||||
dependencies:
|
||||
babel-types "7.0.0-beta.3"
|
||||
lodash "^4.2.0"
|
||||
|
||||
babel-jest@24.8.0, babel-jest@^24.8.0:
|
||||
version "24.8.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.8.0.tgz#5c15ff2b28e20b0f45df43fe6b7f2aae93dba589"
|
||||
@@ -1551,14 +1506,15 @@ babel-plugin-jest-hoist@^24.6.0:
|
||||
dependencies:
|
||||
"@types/babel__traverse" "^7.0.6"
|
||||
|
||||
babel-plugin-lodash@3.3.2:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-lodash/-/babel-plugin-lodash-3.3.2.tgz#da3a5b49ba27447f54463f6c4fa81396ccdd463f"
|
||||
babel-plugin-lodash@3.3.4:
|
||||
version "3.3.4"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-lodash/-/babel-plugin-lodash-3.3.4.tgz#4f6844358a1340baed182adbeffa8df9967bc196"
|
||||
integrity sha512-yDZLjK7TCkWl1gpBeBGmuaDIFhZKmkoL+Cu2MUUjv5VxUZx/z7tBGBCBcQs5RI1Bkz5LLmNdjx7paOyQtMovyg==
|
||||
dependencies:
|
||||
babel-helper-module-imports "^7.0.0-beta.3"
|
||||
babel-types "^6.26.0"
|
||||
"@babel/helper-module-imports" "^7.0.0-beta.49"
|
||||
"@babel/types" "^7.0.0-beta.49"
|
||||
glob "^7.1.1"
|
||||
lodash "^4.17.4"
|
||||
lodash "^4.17.10"
|
||||
require-package-name "^2.0.1"
|
||||
|
||||
babel-plugin-syntax-class-properties@^6.8.0:
|
||||
@@ -1623,14 +1579,6 @@ babel-traverse@^6.24.1, babel-traverse@^6.26.0:
|
||||
invariant "^2.2.2"
|
||||
lodash "^4.17.4"
|
||||
|
||||
babel-types@7.0.0-beta.3:
|
||||
version "7.0.0-beta.3"
|
||||
resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-7.0.0-beta.3.tgz#cd927ca70e0ae8ab05f4aab83778cfb3e6eb20b4"
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
lodash "^4.2.0"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
babel-types@^6.24.1, babel-types@^6.26.0:
|
||||
version "6.26.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497"
|
||||
@@ -1640,10 +1588,6 @@ babel-types@^6.24.1, babel-types@^6.26.0:
|
||||
lodash "^4.17.4"
|
||||
to-fast-properties "^1.0.3"
|
||||
|
||||
babylon@7.0.0-beta.36:
|
||||
version "7.0.0-beta.36"
|
||||
resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.36.tgz#3a3683ba6a9a1e02b0aa507c8e63435e39305b9e"
|
||||
|
||||
babylon@7.0.0-beta.46, babylon@^7.0.0-beta.42:
|
||||
version "7.0.0-beta.46"
|
||||
resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.46.tgz#b6ddaba81bbb130313932757ff9c195d527088b6"
|
||||
@@ -2296,6 +2240,11 @@ concat-stream@^1.5.0:
|
||||
readable-stream "^2.2.2"
|
||||
typedarray "^0.0.6"
|
||||
|
||||
confusing-browser-globals@^1.0.5:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.7.tgz#5ae852bd541a910e7ffb2dbb864a2d21a36ad29b"
|
||||
integrity sha512-cgHI1azax5ATrZ8rJ+ODDML9Fvu67PimB6aNxBrc/QwSaDaM9eTfIEUHx3bBLJJ82ioSb+/5zfsMCCEJax3ByQ==
|
||||
|
||||
console-browserify@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10"
|
||||
@@ -2630,7 +2579,6 @@ dagre@0.8.4:
|
||||
damerau-levenshtein@^1.0.4:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.5.tgz#780cf7144eb2e8dbd1c3bb83ae31100ccc31a414"
|
||||
integrity sha512-CBCRqFnpu715iPmw1KrdOrzRqbdFwQTwAWyyyYS42+iAgHCuXZ+/TdMgQkUENPomxEz9z1BEzuQU2Xw0kUuAgA==
|
||||
|
||||
dashdash@^1.12.0:
|
||||
version "1.14.1"
|
||||
@@ -2656,7 +2604,7 @@ debug@2.6.9, debug@^2.1.1, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@3.1.0, debug@^3.0.0, debug@^3.0.1, debug@^3.1.0:
|
||||
debug@3.1.0, debug@^3.0.0, debug@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||
dependencies:
|
||||
@@ -2702,6 +2650,12 @@ define-properties@^1.1.2:
|
||||
foreach "^2.0.5"
|
||||
object-keys "^1.0.8"
|
||||
|
||||
define-properties@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
|
||||
dependencies:
|
||||
object-keys "^1.0.12"
|
||||
|
||||
define-property@^0.2.5:
|
||||
version "0.2.5"
|
||||
resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116"
|
||||
@@ -2803,7 +2757,6 @@ doctrine@1.5.0, doctrine@^1.2.2:
|
||||
doctrine@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d"
|
||||
integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
|
||||
@@ -2995,7 +2948,7 @@ error-ex@^1.2.0, error-ex@^1.3.1:
|
||||
dependencies:
|
||||
is-arrayish "^0.2.1"
|
||||
|
||||
es-abstract@^1.11.0, es-abstract@^1.5.1:
|
||||
es-abstract@^1.11.0, es-abstract@^1.12.0, es-abstract@^1.5.1:
|
||||
version "1.13.0"
|
||||
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9"
|
||||
dependencies:
|
||||
@@ -3113,29 +3066,35 @@ escope@^3.6.0:
|
||||
esrecurse "^4.1.0"
|
||||
estraverse "^4.1.1"
|
||||
|
||||
eslint-config-airbnb-base@^12.1.0:
|
||||
version "12.1.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-12.1.0.tgz#386441e54a12ccd957b0a92564a4bafebd747944"
|
||||
eslint-config-airbnb-base@^13.2.0:
|
||||
version "13.2.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-13.2.0.tgz#f6ea81459ff4dec2dda200c35f1d8f7419d57943"
|
||||
integrity sha512-1mg/7eoB4AUeB0X1c/ho4vb2gYkNH8Trr/EgCT/aGmKhhG+F6vF5s8+iRBlWAzFIAphxIdp3YfEKgEl0f9Xg+w==
|
||||
dependencies:
|
||||
eslint-restricted-globals "^0.1.1"
|
||||
confusing-browser-globals "^1.0.5"
|
||||
object.assign "^4.1.0"
|
||||
object.entries "^1.1.0"
|
||||
|
||||
eslint-config-airbnb@16.1.0:
|
||||
version "16.1.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-airbnb/-/eslint-config-airbnb-16.1.0.tgz#2546bfb02cc9fe92284bf1723ccf2e87bc45ca46"
|
||||
eslint-config-airbnb@17.1.1:
|
||||
version "17.1.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-airbnb/-/eslint-config-airbnb-17.1.1.tgz#2272e0b86bb1e2b138cdf88d07a3b6f4cda3d626"
|
||||
integrity sha512-xCu//8a/aWqagKljt+1/qAM62BYZeNq04HmdevG5yUGWpja0I/xhqd6GdLRch5oetEGFiJAnvtGuTEAese53Qg==
|
||||
dependencies:
|
||||
eslint-config-airbnb-base "^12.1.0"
|
||||
eslint-config-airbnb-base "^13.2.0"
|
||||
object.assign "^4.1.0"
|
||||
object.entries "^1.1.0"
|
||||
|
||||
eslint-import-resolver-node@^0.3.2:
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz#58f15fb839b8d0576ca980413476aab2472db66a"
|
||||
integrity sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==
|
||||
dependencies:
|
||||
debug "^2.6.9"
|
||||
resolve "^1.5.0"
|
||||
|
||||
eslint-loader@2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/eslint-loader/-/eslint-loader-2.1.2.tgz#453542a1230d6ffac90e4e7cb9cadba9d851be68"
|
||||
eslint-loader@2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-loader/-/eslint-loader-2.2.1.tgz#28b9c12da54057af0845e2a6112701a2f6bf8337"
|
||||
integrity sha512-RLgV9hoCVsMLvOxCuNjdqOrUqIj9oJg8hF44vzJaYqsAHuY9G2YAeN3joQ9nxP0p5Th9iFSIpKo+SD8KISxXRg==
|
||||
dependencies:
|
||||
loader-fs-cache "^1.0.0"
|
||||
loader-utils "^1.0.2"
|
||||
@@ -3146,15 +3105,14 @@ eslint-loader@2.1.2:
|
||||
eslint-module-utils@^2.4.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.4.0.tgz#8b93499e9b00eab80ccb6614e69f03678e84e09a"
|
||||
integrity sha512-14tltLm38Eu3zS+mt0KvILC3q8jyIAH518MlG+HO0p+yK885Lb1UHTY/UgR91eOyGdmxAPb+OLoW4znqIT6Ndw==
|
||||
dependencies:
|
||||
debug "^2.6.8"
|
||||
pkg-dir "^2.0.0"
|
||||
|
||||
eslint-plugin-import@2.17.3:
|
||||
version "2.17.3"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.17.3.tgz#00548b4434c18faebaba04b24ae6198f280de189"
|
||||
integrity sha512-qeVf/UwXFJbeyLbxuY8RgqDyEKCkqV7YC+E5S5uOjAp4tOc8zj01JP3ucoBM8JcEqd1qRasJSg6LLlisirfy0Q==
|
||||
eslint-plugin-import@2.18.2:
|
||||
version "2.18.2"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.18.2.tgz#02f1180b90b077b33d447a17a2326ceb400aceb6"
|
||||
integrity sha512-5ohpsHAiUBRNaBWAF08izwUGlbrJoJJ+W9/TBwsGoR1MnlgfwMIKrFeSjWbt6moabiXW9xNvtFz+97KHRfI4HQ==
|
||||
dependencies:
|
||||
array-includes "^3.0.3"
|
||||
contains-path "^0.1.0"
|
||||
@@ -3163,16 +3121,16 @@ eslint-plugin-import@2.17.3:
|
||||
eslint-import-resolver-node "^0.3.2"
|
||||
eslint-module-utils "^2.4.0"
|
||||
has "^1.0.3"
|
||||
lodash "^4.17.11"
|
||||
minimatch "^3.0.4"
|
||||
object.values "^1.1.0"
|
||||
read-pkg-up "^2.0.0"
|
||||
resolve "^1.11.0"
|
||||
|
||||
eslint-plugin-jsx-a11y@6.2.1:
|
||||
version "6.2.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.1.tgz#4ebba9f339b600ff415ae4166e3e2e008831cf0c"
|
||||
integrity sha512-cjN2ObWrRz0TTw7vEcGQrx+YltMvZoOEx4hWU8eEERDnBIU00OTq7Vr+jA7DFKxiwLNv4tTh5Pq2GUNEa8b6+w==
|
||||
eslint-plugin-jsx-a11y@6.2.3:
|
||||
version "6.2.3"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.3.tgz#b872a09d5de51af70a97db1eea7dc933043708aa"
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.4.5"
|
||||
aria-query "^3.0.0"
|
||||
array-includes "^3.0.3"
|
||||
ast-types-flow "^0.0.7"
|
||||
@@ -3180,42 +3138,34 @@ eslint-plugin-jsx-a11y@6.2.1:
|
||||
damerau-levenshtein "^1.0.4"
|
||||
emoji-regex "^7.0.2"
|
||||
has "^1.0.3"
|
||||
jsx-ast-utils "^2.0.1"
|
||||
jsx-ast-utils "^2.2.1"
|
||||
|
||||
eslint-plugin-react@7.13.0:
|
||||
version "7.13.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.13.0.tgz#bc13fd7101de67996ea51b33873cd9dc2b7e5758"
|
||||
integrity sha512-uA5LrHylu8lW/eAH3bEQe9YdzpPaFd9yAJTwTi/i/BKTD7j6aQMKVAdGM/ML72zD6womuSK7EiGtMKuK06lWjQ==
|
||||
eslint-plugin-react@7.14.3:
|
||||
version "7.14.3"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.14.3.tgz#911030dd7e98ba49e1b2208599571846a66bdf13"
|
||||
integrity sha512-EzdyyBWC4Uz2hPYBiEJrKCUi2Fn+BJ9B/pJQcjw5X+x/H2Nm59S4MJIvL4O5NEE0+WbnQwEBxWY03oUk+Bc3FA==
|
||||
dependencies:
|
||||
array-includes "^3.0.3"
|
||||
doctrine "^2.1.0"
|
||||
has "^1.0.3"
|
||||
jsx-ast-utils "^2.1.0"
|
||||
object.entries "^1.1.0"
|
||||
object.fromentries "^2.0.0"
|
||||
object.values "^1.1.0"
|
||||
prop-types "^15.7.2"
|
||||
resolve "^1.10.1"
|
||||
|
||||
eslint-restricted-globals@^0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz#35f0d5cbc64c2e3ed62e93b4b1a7af05ba7ed4d7"
|
||||
|
||||
eslint-scope@^3.7.1:
|
||||
eslint-scope@3.7.1, eslint-scope@^3.7.1:
|
||||
version "3.7.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8"
|
||||
dependencies:
|
||||
esrecurse "^4.1.0"
|
||||
estraverse "^4.1.1"
|
||||
|
||||
eslint-scope@^4.0.3:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848"
|
||||
dependencies:
|
||||
esrecurse "^4.1.0"
|
||||
estraverse "^4.1.1"
|
||||
|
||||
eslint-scope@~3.7.1:
|
||||
version "3.7.3"
|
||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.3.tgz#bb507200d3d17f60247636160b4826284b108535"
|
||||
eslint-scope@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9"
|
||||
integrity sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==
|
||||
dependencies:
|
||||
esrecurse "^4.1.0"
|
||||
estraverse "^4.1.1"
|
||||
@@ -3228,46 +3178,48 @@ eslint-visitor-keys@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
|
||||
|
||||
eslint@5.16.0:
|
||||
version "5.16.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.16.0.tgz#a1e3ac1aae4a3fbd8296fcf8f7ab7314cbb6abea"
|
||||
eslint@6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.1.0.tgz#06438a4a278b1d84fb107d24eaaa35471986e646"
|
||||
integrity sha512-QhrbdRD7ofuV09IuE2ySWBz0FyXCq0rriLTZXZqaWSI79CVtHVRdkFuFTViiqzZhkCgfOh9USpriuGN2gIpZDQ==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.0.0"
|
||||
ajv "^6.9.1"
|
||||
ajv "^6.10.0"
|
||||
chalk "^2.1.0"
|
||||
cross-spawn "^6.0.5"
|
||||
debug "^4.0.1"
|
||||
doctrine "^3.0.0"
|
||||
eslint-scope "^4.0.3"
|
||||
eslint-scope "^5.0.0"
|
||||
eslint-utils "^1.3.1"
|
||||
eslint-visitor-keys "^1.0.0"
|
||||
espree "^5.0.1"
|
||||
espree "^6.0.0"
|
||||
esquery "^1.0.1"
|
||||
esutils "^2.0.2"
|
||||
file-entry-cache "^5.0.1"
|
||||
functional-red-black-tree "^1.0.1"
|
||||
glob "^7.1.2"
|
||||
glob-parent "^5.0.0"
|
||||
globals "^11.7.0"
|
||||
ignore "^4.0.6"
|
||||
import-fresh "^3.0.0"
|
||||
imurmurhash "^0.1.4"
|
||||
inquirer "^6.2.2"
|
||||
js-yaml "^3.13.0"
|
||||
inquirer "^6.4.1"
|
||||
is-glob "^4.0.0"
|
||||
js-yaml "^3.13.1"
|
||||
json-stable-stringify-without-jsonify "^1.0.1"
|
||||
levn "^0.3.0"
|
||||
lodash "^4.17.11"
|
||||
lodash "^4.17.14"
|
||||
minimatch "^3.0.4"
|
||||
mkdirp "^0.5.1"
|
||||
natural-compare "^1.4.0"
|
||||
optionator "^0.8.2"
|
||||
path-is-inside "^1.0.2"
|
||||
progress "^2.0.0"
|
||||
regexpp "^2.0.1"
|
||||
semver "^5.5.1"
|
||||
strip-ansi "^4.0.0"
|
||||
strip-json-comments "^2.0.1"
|
||||
semver "^6.1.2"
|
||||
strip-ansi "^5.2.0"
|
||||
strip-json-comments "^3.0.1"
|
||||
table "^5.2.3"
|
||||
text-table "^0.2.0"
|
||||
v8-compile-cache "^2.0.3"
|
||||
|
||||
eslint@^2.7.0:
|
||||
version "2.13.1"
|
||||
@@ -3314,9 +3266,9 @@ espree@^3.1.6:
|
||||
acorn "^5.5.0"
|
||||
acorn-jsx "^3.0.0"
|
||||
|
||||
espree@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/espree/-/espree-5.0.1.tgz#5d6526fa4fc7f0788a5cf75b15f30323e2f81f7a"
|
||||
espree@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/espree/-/espree-6.0.0.tgz#716fc1f5a245ef5b9a7fdb1d7b0d3f02322e75f6"
|
||||
dependencies:
|
||||
acorn "^6.0.7"
|
||||
acorn-jsx "^5.0.0"
|
||||
@@ -3960,6 +3912,13 @@ glob-parent@^3.1.0:
|
||||
is-glob "^3.1.0"
|
||||
path-dirname "^1.0.0"
|
||||
|
||||
glob-parent@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.0.0.tgz#1dc99f0f39b006d3e92c2c284068382f0c20e954"
|
||||
integrity sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==
|
||||
dependencies:
|
||||
is-glob "^4.0.1"
|
||||
|
||||
glob-to-regexp@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
|
||||
@@ -4525,9 +4484,10 @@ inquirer@^0.12.0:
|
||||
strip-ansi "^3.0.0"
|
||||
through "^2.3.6"
|
||||
|
||||
inquirer@^6.2.2:
|
||||
version "6.3.1"
|
||||
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.3.1.tgz#7a413b5e7950811013a3db491c61d1f3b776e8e7"
|
||||
inquirer@^6.4.1:
|
||||
version "6.5.0"
|
||||
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.0.tgz#2303317efc9a4ea7ec2e2df6f86569b734accf42"
|
||||
integrity sha512-scfHejeG/lVZSpvCXpsB4j/wQNPM5JC8kiElOI0OUTwmc1RTpXr4H32/HOlQHcZiYl2z2VElwuCVDRG8vFmbnA==
|
||||
dependencies:
|
||||
ansi-escapes "^3.2.0"
|
||||
chalk "^2.4.2"
|
||||
@@ -4535,7 +4495,7 @@ inquirer@^6.2.2:
|
||||
cli-width "^2.0.0"
|
||||
external-editor "^3.0.3"
|
||||
figures "^2.0.0"
|
||||
lodash "^4.17.11"
|
||||
lodash "^4.17.12"
|
||||
mute-stream "0.0.7"
|
||||
run-async "^2.2.0"
|
||||
rxjs "^6.4.0"
|
||||
@@ -4750,6 +4710,13 @@ is-glob@^4.0.0:
|
||||
dependencies:
|
||||
is-extglob "^2.1.1"
|
||||
|
||||
is-glob@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
|
||||
integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
|
||||
dependencies:
|
||||
is-extglob "^2.1.1"
|
||||
|
||||
is-hexadecimal@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.1.tgz#6e084bbc92061fbb0971ec58b6ce6d404e24da69"
|
||||
@@ -5311,7 +5278,7 @@ js-tokens@^3.0.0, js-tokens@^3.0.2:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||
|
||||
js-yaml@^3.13.0, js-yaml@^3.13.1:
|
||||
js-yaml@^3.13.1:
|
||||
version "3.13.1"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
|
||||
dependencies:
|
||||
@@ -5441,19 +5408,19 @@ jsprim@^1.2.2:
|
||||
json-schema "0.2.3"
|
||||
verror "1.10.0"
|
||||
|
||||
jsx-ast-utils@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz#e801b1b39985e20fffc87b40e3748080e2dcac7f"
|
||||
dependencies:
|
||||
array-includes "^3.0.3"
|
||||
|
||||
jsx-ast-utils@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.1.0.tgz#0ee4e2c971fb9601c67b5641b71be80faecf0b36"
|
||||
integrity sha512-yDGDG2DS4JcqhA6blsuYbtsT09xL8AoLuUR2Gb5exrw7UEM19sBcOTq+YBBhrNbl0PUC4R4LnFu+dHg2HKeVvA==
|
||||
dependencies:
|
||||
array-includes "^3.0.3"
|
||||
|
||||
jsx-ast-utils@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.2.1.tgz#4d4973ebf8b9d2837ee91a8208cc66f3a2776cfb"
|
||||
dependencies:
|
||||
array-includes "^3.0.3"
|
||||
object.assign "^4.1.0"
|
||||
|
||||
kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
|
||||
@@ -5647,6 +5614,11 @@ lodash@^4.0.0, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lod
|
||||
version "4.17.4"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
|
||||
|
||||
lodash@^4.17.12, lodash@^4.17.14:
|
||||
version "4.17.15"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
|
||||
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
|
||||
|
||||
log-symbols@^2.0.0, log-symbols@^2.1.0, log-symbols@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
|
||||
@@ -6372,10 +6344,18 @@ object.assign@^4.1.0:
|
||||
has-symbols "^1.0.0"
|
||||
object-keys "^1.0.11"
|
||||
|
||||
object.entries@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.0.tgz#2024fc6d6ba246aee38bdb0ffd5cfbcf371b7519"
|
||||
dependencies:
|
||||
define-properties "^1.1.3"
|
||||
es-abstract "^1.12.0"
|
||||
function-bind "^1.1.1"
|
||||
has "^1.0.3"
|
||||
|
||||
object.fromentries@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.0.tgz#49a543d92151f8277b3ac9600f1e930b189d30ab"
|
||||
integrity sha512-9iLiI6H083uiqUuvzyY6qrlmc/Gz8hLQFOcb/Ri/0xXFkSNS3ctV+CbE6yM2+AnkYfOB3dGjdzC0wrMLIhQICA==
|
||||
dependencies:
|
||||
define-properties "^1.1.2"
|
||||
es-abstract "^1.11.0"
|
||||
@@ -6402,6 +6382,15 @@ object.pick@^1.3.0:
|
||||
dependencies:
|
||||
isobject "^3.0.1"
|
||||
|
||||
object.values@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.0.tgz#bf6810ef5da3e5325790eaaa2be213ea84624da9"
|
||||
dependencies:
|
||||
define-properties "^1.1.3"
|
||||
es-abstract "^1.12.0"
|
||||
function-bind "^1.1.1"
|
||||
has "^1.0.3"
|
||||
|
||||
on-finished@~2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
|
||||
@@ -6642,7 +6631,7 @@ path-is-absolute@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
|
||||
|
||||
path-is-inside@^1.0.1, path-is-inside@^1.0.2:
|
||||
path-is-inside@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
|
||||
|
||||
@@ -7521,6 +7510,10 @@ regenerator-runtime@^0.12.0:
|
||||
version "0.12.1"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de"
|
||||
|
||||
regenerator-runtime@^0.13.2:
|
||||
version "0.13.2"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz#32e59c9a6fb9b1a4aff09b4930ca2d4477343447"
|
||||
|
||||
regenerator-transform@^0.13.4:
|
||||
version "0.13.4"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.13.4.tgz#18f6763cf1382c69c36df76c6ce122cc694284fb"
|
||||
@@ -8021,9 +8014,10 @@ semver@^5.5.0:
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
|
||||
|
||||
semver@^5.5.1:
|
||||
version "5.7.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b"
|
||||
semver@^6.1.2:
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
||||
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
||||
|
||||
semver@~5.3.0:
|
||||
version "5.3.0"
|
||||
@@ -8467,14 +8461,19 @@ strip-indent@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68"
|
||||
|
||||
strip-json-comments@^2.0.1, strip-json-comments@~2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
||||
strip-json-comments@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7"
|
||||
integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==
|
||||
|
||||
strip-json-comments@~1.0.1:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91"
|
||||
|
||||
strip-json-comments@~2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
||||
|
||||
style-loader@0.21.0:
|
||||
version "0.21.0"
|
||||
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.21.0.tgz#68c52e5eb2afc9ca92b6274be277ee59aea3a852"
|
||||
@@ -9181,6 +9180,11 @@ v8-compile-cache@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.2.tgz#a428b28bb26790734c4fc8bc9fa106fccebf6a6c"
|
||||
|
||||
v8-compile-cache@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe"
|
||||
integrity sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==
|
||||
|
||||
validate-npm-package-license@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc"
|
||||
|
||||
Reference in New Issue
Block a user