mirror of
https://github.com/weaveworks/scope.git
synced 2026-07-28 01:31:17 +00:00
Initial version of the resource view (#2296)
* Added resource view selector button * Showing resource boxes in the resource view * Crude CPU resource view prototype * Improved the viewMode state logic * Extracted zooming into a separate wrapper component * Split the layout selectors between graph-view and resource-view * Proper zooming logic for the resource view * Moved all node networks utils to selectors * Improved the zoom caching logic * Further refactoring of selectors * Added sticky labels to the resource boxes * Added panning translation limits in the resource view * Renamed GridModeSelector -> ViewModeSelector * Polished the topology resource view selection logic * Search bar hidden in the resource view * Added per-layer topology names to the resource view * Made metric selectors work for the resource view * Adjusted the viewport selectors * Renamed viewport selector to canvas (+ maximal zoom fix) * Showing more useful metric info in the resource box labels * Fetching only necessary nodes for the resource view * Refactored the resource view layer component * Addressed first batch UI comments (from the Scope meeting) * Switch to deep zooming transform in the resource view to avoid SVG precision errors * Renamed and moved resource view components * Polished all the resource view components * Changing the available metrics selection * Improved and polished the state transition logic for the resource view * Separated zoom limits from the zoom active state * Renaming and bunch of comments * Addressed all the UI comments (@davkal + @fons) * Made graph view selectors independent from resource view selectors
This commit is contained in:
@@ -93,8 +93,12 @@ function download(source, name) {
|
||||
}, 10);
|
||||
}
|
||||
|
||||
function getSVGElement() {
|
||||
return document.getElementById('canvas');
|
||||
}
|
||||
|
||||
function getSVG(doc, emptySvgDeclarationComputed) {
|
||||
const svg = document.getElementById('nodes-chart-canvas');
|
||||
const svg = getSVGElement();
|
||||
const target = svg.cloneNode(true);
|
||||
|
||||
target.setAttribute('version', '1.1');
|
||||
@@ -127,7 +131,7 @@ function cleanup() {
|
||||
});
|
||||
|
||||
// hide embedded logo
|
||||
const svg = document.getElementById('nodes-chart-canvas');
|
||||
const svg = getSVGElement();
|
||||
svg.setAttribute('class', '');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import { fromJS } from 'immutable';
|
||||
|
||||
import { nodeNetworksSelector } from '../selectors/node-networks';
|
||||
|
||||
export function getNetworkNodes(state) {
|
||||
const networksMap = {};
|
||||
nodeNetworksSelector(state).forEach((networks, nodeId) => {
|
||||
networks.forEach((network) => {
|
||||
const networkId = network.get('id');
|
||||
networksMap[networkId] = networksMap[networkId] || [];
|
||||
networksMap[networkId].push(nodeId);
|
||||
});
|
||||
});
|
||||
return fromJS(networksMap);
|
||||
}
|
||||
@@ -48,8 +48,8 @@ export function getUrlState(state) {
|
||||
|
||||
const urlState = {
|
||||
controlPipe: cp ? cp.toJS() : null,
|
||||
topologyViewMode: state.get('gridMode') ? 'grid' : 'topo',
|
||||
nodeDetails: nodeDetails.toJS(),
|
||||
topologyViewMode: state.get('topologyViewMode'),
|
||||
pinnedMetricType: state.get('pinnedMetricType'),
|
||||
pinnedSearches: state.get('pinnedSearches').toJS(),
|
||||
searchQuery: state.get('searchQuery'),
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { endsWith } from 'lodash';
|
||||
import { Set as makeSet, List as makeList } from 'immutable';
|
||||
|
||||
import { isResourceViewModeSelector } from '../selectors/topology';
|
||||
import { pinnedMetricSelector } from '../selectors/node-metric';
|
||||
|
||||
//
|
||||
// top priority first
|
||||
@@ -132,8 +134,12 @@ export function getCurrentTopologyOptions(state) {
|
||||
}
|
||||
|
||||
export function isTopologyEmpty(state) {
|
||||
return state.getIn(['currentTopology', 'stats', 'node_count'], 0) === 0
|
||||
&& state.get('nodes').size === 0;
|
||||
// Consider a topology in the resource view empty if it has no pinned metric.
|
||||
const resourceViewEmpty = isResourceViewModeSelector(state) && !pinnedMetricSelector(state);
|
||||
// Otherwise (in graph and table view), we only look at the node count.
|
||||
const nodeCount = state.getIn(['currentTopology', 'stats', 'node_count'], 0);
|
||||
const nodesEmpty = nodeCount === 0 && state.get('nodes').size === 0;
|
||||
return resourceViewEmpty || nodesEmpty;
|
||||
}
|
||||
|
||||
|
||||
|
||||
16
client/app/scripts/utils/transform-utils.js
Normal file
16
client/app/scripts/utils/transform-utils.js
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
const applyTranslateX = ({ scaleX = 1, translateX = 0 }, x) => (x * scaleX) + translateX;
|
||||
const applyTranslateY = ({ scaleY = 1, translateY = 0 }, y) => (y * scaleY) + translateY;
|
||||
const applyScaleX = ({ scaleX = 1 }, width) => width * scaleX;
|
||||
const applyScaleY = ({ scaleY = 1 }, height) => height * scaleY;
|
||||
|
||||
export const applyTransform = (transform, { width, height, x, y }) => ({
|
||||
x: applyTranslateX(transform, x),
|
||||
y: applyTranslateY(transform, y),
|
||||
width: applyScaleX(transform, width),
|
||||
height: applyScaleY(transform, height),
|
||||
});
|
||||
|
||||
export const transformToString = ({ translateX = 0, translateY = 0, scaleX = 1, scaleY = 1 }) => (
|
||||
`translate(${translateX},${translateY}) scale(${scaleX},${scaleY})`
|
||||
);
|
||||
@@ -2,6 +2,7 @@ import debug from 'debug';
|
||||
import reqwest from 'reqwest';
|
||||
import trimStart from 'lodash/trimStart';
|
||||
import defaults from 'lodash/defaults';
|
||||
import { Map as makeMap } from 'immutable';
|
||||
|
||||
import { blurSearch, clearControlError, closeWebsocket, openWebsocket, receiveError,
|
||||
receiveApiDetails, receiveNodesDelta, receiveNodeDetails, receiveControlError,
|
||||
@@ -9,6 +10,7 @@ import { blurSearch, clearControlError, closeWebsocket, openWebsocket, receiveEr
|
||||
receiveControlSuccess, receiveTopologies, receiveNotFound,
|
||||
receiveNodesForTopology } from '../actions/app-actions';
|
||||
|
||||
import { layersTopologyIdsSelector } from '../selectors/resource-view/layout';
|
||||
import { API_INTERVAL, TOPOLOGY_INTERVAL } from '../constants/timer';
|
||||
|
||||
const log = debug('scope:web-api-utils');
|
||||
@@ -157,13 +159,12 @@ function doRequest(opts) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets nodes for all topologies (for search)
|
||||
* Does a one-time fetch of all the nodes for a custom list of topologies.
|
||||
*/
|
||||
export function getAllNodes(getState, dispatch) {
|
||||
const state = getState();
|
||||
const topologyOptions = state.get('topologyOptions');
|
||||
function getNodesForTopologies(getState, dispatch, topologyIds, topologyOptions = makeMap()) {
|
||||
// fetch sequentially
|
||||
state.get('topologyUrlsById')
|
||||
getState().get('topologyUrlsById')
|
||||
.filter((_, topologyId) => topologyIds.contains(topologyId))
|
||||
.reduce((sequence, topologyUrl, topologyId) => sequence.then(() => {
|
||||
const optionsQuery = buildOptionsQuery(topologyOptions.get(topologyId));
|
||||
// Trim the leading slash from the url before requesting.
|
||||
@@ -175,6 +176,28 @@ export function getAllNodes(getState, dispatch) {
|
||||
Promise.resolve());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets nodes for all topologies (for search).
|
||||
*/
|
||||
export function getAllNodes(getState, dispatch) {
|
||||
const state = getState();
|
||||
const topologyOptions = state.get('topologyOptions');
|
||||
const topologyIds = state.get('topologyUrlsById').keySeq();
|
||||
getNodesForTopologies(getState, dispatch, topologyIds, topologyOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* One-time update of all the nodes of topologies that appear in the current resource view.
|
||||
*/
|
||||
export function getResourceViewNodesSnapshot(getState, dispatch) {
|
||||
const topologyIds = layersTopologyIdsSelector(getState());
|
||||
// TODO: Remove the timeout and replace it with normal polling once we figure how to make
|
||||
// resource view dynamic (from the UI point of view, the challenge is to make it stable).
|
||||
setTimeout(() => {
|
||||
getNodesForTopologies(getState, dispatch, topologyIds);
|
||||
}, 1200);
|
||||
}
|
||||
|
||||
export function getTopologies(options, dispatch, initialPoll) {
|
||||
// Used to resume polling when navigating between pages in Weave Cloud.
|
||||
continuePolling = initialPoll === true ? true : continuePolling;
|
||||
@@ -204,6 +227,8 @@ export function getTopologies(options, dispatch, initialPoll) {
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: topologyUrl and options are always used for the current topology so they as arguments
|
||||
// can be replaced by the `state` and then retrieved here internally from selectors.
|
||||
export function getNodesDelta(topologyUrl, options, dispatch) {
|
||||
const optionsQuery = buildOptionsQuery(options);
|
||||
// Only recreate websocket if url changed or if forced (weave cloud instance reload);
|
||||
|
||||
Reference in New Issue
Block a user